fix: correct image source for quoted reply (#8565)

- Since v10 replies are generated on the fly to handle quoted reply (forgejo/forgejo#5677), this means that we have to do some work to construct markdown that is equivalent to the HTML of the comment.
- Images are slightly strange in the context of issues and pull requests, as Forgejo will render them in the context of the repository and as such links such as `/attachments` become `/user/repo/attachments`, the quoted reply did not take into account and would use `/user/repo/attachments` as link which means it gets transformed to `/user/repo//user/repo/attachments`.
- Instead of fixing this on the backend (and maybe break some existing links), teach the quoted reply about this context and remove it from the image source before generating the markdown.

Reported-by: mrwusel (via Matrix)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8565
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
This commit is contained in:
Gusted 2025-07-19 15:03:10 +02:00 committed by Gusted
parent b705cfcdd8
commit 8c8d646099
4 changed files with 38 additions and 27 deletions

View file

@ -531,6 +531,13 @@ const filters = {
}
return el;
},
IMG(el, context) {
const src = el.getAttribute('src');
if (src?.startsWith(context)) {
el.src = src.slice(context.length);
}
return el;
},
};
function hasContent(node) {
@ -538,32 +545,34 @@ function hasContent(node) {
}
// This code matches that of what is done by @github/quote-selection
function preprocessFragment(fragment) {
const nodeIterator = document.createNodeIterator(fragment, NodeFilter.SHOW_ELEMENT, {
acceptNode(node) {
if (node.nodeName in filters && hasContent(node)) {
return NodeFilter.FILTER_ACCEPT;
function preprocessFragment(context) {
return function(fragment) {
const nodeIterator = document.createNodeIterator(fragment, NodeFilter.SHOW_ELEMENT, {
acceptNode(node) {
if (node.nodeName in filters && hasContent(node)) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
},
});
const results = [];
let node = nodeIterator.nextNode();
while (node) {
if (node instanceof HTMLElement) {
results.push(node);
}
return NodeFilter.FILTER_SKIP;
},
});
const results = [];
let node = nodeIterator.nextNode();
while (node) {
if (node instanceof HTMLElement) {
results.push(node);
node = nodeIterator.nextNode();
}
node = nodeIterator.nextNode();
}
// process deepest matches first
results.reverse();
results.reverse();
for (const el of results) {
el.replaceWith(filters[el.nodeName](el));
}
for (const el of results) {
el.replaceWith(filters[el.nodeName](el, context));
}
};
}
function initRepoIssueCommentEdit() {
@ -573,7 +582,7 @@ function initRepoIssueCommentEdit() {
// Quote reply
$(document).on('click', '.quote-reply', async (event) => {
event.preventDefault();
const quote = new MarkdownQuote('', preprocessFragment);
const quote = new MarkdownQuote('', preprocessFragment(event.target.getAttribute('data-context')));
let editorTextArea;
if (event.target.classList.contains('quote-reply-diff')) {