Skip to content

Commit 9d03175

Browse files
committed
fix
1 parent 41b4ef8 commit 9d03175

14 files changed

+237
-58
lines changed

modules/markup/html.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,10 @@ func createLink(href, content, class string) *html.Node {
442442
a := &html.Node{
443443
Type: html.ElementNode,
444444
Data: atom.A.String(),
445-
Attr: []html.Attribute{{Key: "href", Val: href}},
445+
Attr: []html.Attribute{
446+
{Key: "href", Val: href},
447+
{Key: "data-markdown-generated-content"},
448+
},
446449
}
447450

448451
if class != "" {

modules/markup/html_codepreview_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ func TestRenderCodePreview(t *testing.T) {
3030
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
3131
}
3232
test("http://localhost:3000/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", "<p><div>code preview</div></p>")
33-
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
33+
test("http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20", `<p><a href="http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20" data-markdown-generated-content="" rel="nofollow">http://other/owner/repo/src/commit/0123456789/foo/bar.md#L10-L20</a></p>`)
3434
}

modules/markup/html_internal_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ func numericIssueLink(baseURL, class string, index int, marker string) string {
3333

3434
// link an HTML link
3535
func link(href, class, contents string) string {
36-
if class != "" {
37-
class = " class=\"" + class + "\""
38-
}
39-
40-
return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents)
36+
extra := ` data-markdown-generated-content=""`
37+
extra += util.Iif(class != "", ` class="`+class+`"`, "")
38+
return fmt.Sprintf(`<a href="%s"%s>%s</a>`, href, extra, contents)
4139
}
4240

4341
var numericMetas = map[string]string{
@@ -353,7 +351,9 @@ func TestRender_FullIssueURLs(t *testing.T) {
353351
Metas: localMetas,
354352
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
355353
assert.NoError(t, err)
356-
assert.Equal(t, expected, result.String())
354+
actual := result.String()
355+
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
356+
assert.Equal(t, expected, actual)
357357
}
358358
test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6",
359359
"Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6")

modules/markup/html_test.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ func TestRender_CrossReferences(t *testing.T) {
116116
Metas: localMetas,
117117
}, input)
118118
assert.NoError(t, err)
119-
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
119+
actual := strings.TrimSpace(buffer)
120+
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
121+
assert.Equal(t, strings.TrimSpace(expected), actual)
120122
}
121123

122124
test(
@@ -156,7 +158,9 @@ func TestRender_links(t *testing.T) {
156158
},
157159
}, input)
158160
assert.NoError(t, err)
159-
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
161+
actual := strings.TrimSpace(buffer)
162+
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
163+
assert.Equal(t, strings.TrimSpace(expected), actual)
160164
}
161165

162166
oldCustomURLSchemes := setting.Markdown.CustomURLSchemes
@@ -267,7 +271,9 @@ func TestRender_email(t *testing.T) {
267271
},
268272
}, input)
269273
assert.NoError(t, err)
270-
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
274+
actual := strings.TrimSpace(res)
275+
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
276+
assert.Equal(t, strings.TrimSpace(expected), actual)
271277
}
272278
// Text that should be turned into email link
273279

@@ -616,7 +622,9 @@ func TestPostProcess_RenderDocument(t *testing.T) {
616622
Metas: localMetas,
617623
}, strings.NewReader(input), &res)
618624
assert.NoError(t, err)
619-
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
625+
actual := strings.TrimSpace(res.String())
626+
actual = strings.ReplaceAll(actual, ` data-markdown-generated-content=""`, "")
627+
assert.Equal(t, strings.TrimSpace(expected), actual)
620628
}
621629

622630
// Issue index shouldn't be post processing in a document.

modules/markup/sanitizer_default.go

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
107107
"start", "summary", "tabindex", "target",
108108
"title", "type", "usemap", "valign", "value",
109109
"vspace", "width", "itemprop",
110+
"data-markdown-generated-content",
110111
}
111112

112113
generalSafeElements := []string{

routers/api/v1/misc/markup_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expe
3838
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
3939
web.SetForm(ctx, &options)
4040
Markup(ctx)
41-
assert.Equal(t, expectedBody, resp.Body.String())
41+
actual := strings.ReplaceAll(resp.Body.String(), ` data-markdown-generated-content=""`, "")
42+
assert.Equal(t, expectedBody, actual)
4243
assert.Equal(t, expectedCode, resp.Code)
4344
resp.Body.Reset()
4445
}
@@ -58,7 +59,8 @@ func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody
5859
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
5960
web.SetForm(ctx, &options)
6061
Markdown(ctx)
61-
assert.Equal(t, responseBody, resp.Body.String())
62+
actual := strings.ReplaceAll(resp.Body.String(), ` data-markdown-generated-content=""`, "")
63+
assert.Equal(t, responseBody, actual)
6264
assert.Equal(t, responseCode, resp.Code)
6365
resp.Body.Reset()
6466
}

templates/repo/diff/comments.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
{{end}}
5050
{{end}}
5151
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}}
52-
{{template "repo/issue/view_content/context_menu" dict "ctxData" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
52+
{{template "repo/issue/view_content/context_menu" dict "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
5353
</div>
5454
</div>
5555
<div class="ui attached segment comment-body">

templates/repo/issue/view_content.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
{{if not $.Repository.IsArchived}}
4949
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index)}}
5050
{{end}}
51-
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" .Issue "delete" false "issue" true "diff" false "IsCommentPoster" $.IsIssuePoster}}
51+
{{template "repo/issue/view_content/context_menu" dict "item" .Issue "delete" false "issue" true "diff" false "IsCommentPoster" $.IsIssuePoster}}
5252
</div>
5353
</div>
5454
<div class="ui attached segment comment-body" role="article">

templates/repo/issue/view_content/comments.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
{{if not $.Repository.IsArchived}}
5656
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
5757
{{end}}
58-
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" true "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
58+
{{template "repo/issue/view_content/context_menu" dict "item" . "delete" true "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
5959
</div>
6060
</div>
6161
<div class="ui attached segment comment-body" role="article">
@@ -430,7 +430,7 @@
430430
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
431431
{{if not $.Repository.IsArchived}}
432432
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
433-
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" false "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
433+
{{template "repo/issue/view_content/context_menu" dict "item" . "delete" false "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
434434
{{end}}
435435
</div>
436436
</div>

templates/repo/issue/view_content/context_menu.tmpl

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,29 @@
55
<div class="menu">
66
{{$referenceUrl := ""}}
77
{{if .issue}}
8-
{{$referenceUrl = printf "%s#%s" .ctxData.Issue.Link .item.HashTag}}
8+
{{$referenceUrl = printf "%s#%s" ctx.RootData.Issue.Link .item.HashTag}}
99
{{else}}
10-
{{$referenceUrl = printf "%s/files#%s" .ctxData.Issue.Link .item.HashTag}}
10+
{{$referenceUrl = printf "%s/files#%s" ctx.RootData.Issue.Link .item.HashTag}}
1111
{{end}}
1212
<div class="item context js-aria-clickable" data-clipboard-text-type="url" data-clipboard-text="{{$referenceUrl}}">{{ctx.Locale.Tr "repo.issues.context.copy_link"}}</div>
13-
{{if .ctxData.IsSigned}}
13+
{{if ctx.RootData.IsSigned}}
1414
{{$needDivider := false}}
15-
{{if not .ctxData.Repository.IsArchived}}
15+
{{if not ctx.RootData.Repository.IsArchived}}
1616
{{$needDivider = true}}
1717
<div class="item context js-aria-clickable quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-raw">{{ctx.Locale.Tr "repo.issues.context.quote_reply"}}</div>
1818
{{if not ctx.Consts.RepoUnitTypeIssues.UnitGlobalDisabled}}
1919
<div class="item context js-aria-clickable reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceUrl}}">{{ctx.Locale.Tr "repo.issues.context.reference_issue"}}</div>
2020
{{end}}
21-
{{if or .ctxData.Permission.IsAdmin .IsCommentPoster .ctxData.HasIssuesOrPullsWritePermission}}
21+
{{if or ctx.RootData.Permission.IsAdmin .IsCommentPoster ctx.RootData.HasIssuesOrPullsWritePermission}}
2222
<div class="divider"></div>
2323
<div class="item context js-aria-clickable edit-content">{{ctx.Locale.Tr "repo.issues.context.edit"}}</div>
2424
{{if .delete}}
25-
<div class="item context js-aria-clickable delete-comment" data-comment-id={{.item.HashTag}} data-url="{{.ctxData.RepoLink}}/comments/{{.item.ID}}/delete" data-locale="{{ctx.Locale.Tr "repo.issues.delete_comment_confirm"}}">{{ctx.Locale.Tr "repo.issues.context.delete"}}</div>
25+
<div class="item context js-aria-clickable delete-comment" data-comment-id={{.item.HashTag}} data-url="{{ctx.RootData.RepoLink}}/comments/{{.item.ID}}/delete" data-locale="{{ctx.Locale.Tr "repo.issues.delete_comment_confirm"}}">{{ctx.Locale.Tr "repo.issues.context.delete"}}</div>
2626
{{end}}
2727
{{end}}
2828
{{end}}
29-
{{$canUserBlock := call .ctxData.CanBlockUser .ctxData.SignedUser .item.Poster}}
30-
{{$canOrgBlock := and .ctxData.Repository.Owner.IsOrganization (call .ctxData.CanBlockUser .ctxData.Repository.Owner .item.Poster)}}
29+
{{$canUserBlock := call ctx.RootData.CanBlockUser ctx.RootData.SignedUser .item.Poster}}
30+
{{$canOrgBlock := and ctx.RootData.Repository.Owner.IsOrganization (call ctx.RootData.CanBlockUser ctx.RootData.Repository.Owner .item.Poster)}}
3131
{{if or $canOrgBlock $canUserBlock}}
3232
{{if $needDivider}}
3333
<div class="divider"></div>
@@ -36,7 +36,7 @@
3636
<div class="item context js-aria-clickable show-modal" data-modal="#block-user-modal" data-modal-modal-blockee="{{.item.Poster.Name}}" data-modal-modal-blockee-name="{{.item.Poster.GetDisplayName}}" data-modal-modal-form.action="{{AppSubUrl}}/user/settings/blocked_users">{{ctx.Locale.Tr "user.block.block.user"}}</div>
3737
{{end}}
3838
{{if $canOrgBlock}}
39-
<div class="item context js-aria-clickable show-modal" data-modal="#block-user-modal" data-modal-modal-blockee="{{.item.Poster.Name}}" data-modal-modal-blockee-name="{{.item.Poster.GetDisplayName}}" data-modal-modal-form.action="{{.ctxData.Repository.Owner.OrganisationLink}}/settings/blocked_users">{{ctx.Locale.Tr "user.block.block.org"}}</div>
39+
<div class="item context js-aria-clickable show-modal" data-modal="#block-user-modal" data-modal-modal-blockee="{{.item.Poster.Name}}" data-modal-modal-blockee-name="{{.item.Poster.GetDisplayName}}" data-modal-modal-form.action="{{ctx.RootData.Repository.Owner.OrganisationLink}}/settings/blocked_users">{{ctx.Locale.Tr "user.block.block.org"}}</div>
4040
{{end}}
4141
{{end}}
4242
{{end}}

templates/repo/issue/view_content/conversation.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
{{template "repo/issue/view_content/show_role" dict "ShowRole" .ShowRole}}
8484
{{if not $.Repository.IsArchived}}
8585
{{template "repo/issue/view_content/add_reaction" dict "ActionURL" (printf "%s/comments/%d/reactions" $.RepoLink .ID)}}
86-
{{template "repo/issue/view_content/context_menu" dict "ctxData" $ "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
86+
{{template "repo/issue/view_content/context_menu" dict "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}}
8787
{{end}}
8888
</div>
8989
</div>

web_src/js/features/repo-issue-edit.ts

+52-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import $ from 'jquery';
21
import {handleReply} from './repo-issue.ts';
32
import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
43
import {POST} from '../modules/fetch.ts';
@@ -7,11 +6,14 @@ import {hideElem, showElem} from '../utils/dom.ts';
76
import {attachRefIssueContextPopup} from './contextpopup.ts';
87
import {initCommentContent, initMarkupContent} from '../markup/content.ts';
98
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
9+
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
1010

11-
async function onEditContent(event) {
12-
event.preventDefault();
11+
async function tryOnEditContent(e) {
12+
const clickTarget = e.target.closest('.edit-content');
13+
if (!clickTarget) return;
1314

14-
const segment = this.closest('.header').nextElementSibling;
15+
e.preventDefault();
16+
const segment = clickTarget.closest('.header').nextElementSibling;
1517
const editContentZone = segment.querySelector('.edit-content-zone');
1618
const renderContent = segment.querySelector('.render-content');
1719
const rawContent = segment.querySelector('.raw-content');
@@ -100,33 +102,53 @@ async function onEditContent(event) {
100102
triggerUploadStateChanged(comboMarkdownEditor.container);
101103
}
102104

103-
export function initRepoIssueCommentEdit() {
104-
// Edit issue or comment content
105-
$(document).on('click', '.edit-content', onEditContent);
105+
function extractSelectedMarkdown(container: HTMLElement) {
106+
const selection = window.getSelection();
107+
if (!selection.rangeCount) return '';
108+
const range = selection.getRangeAt(0);
109+
if (!container.contains(range.commonAncestorContainer)) return '';
106110

107-
// Quote reply
108-
$(document).on('click', '.quote-reply', async function (event) {
109-
event.preventDefault();
110-
const target = this.getAttribute('data-target');
111-
const quote = document.querySelector(`#${target}`).textContent.replace(/\n/g, '\n> ');
112-
const content = `> ${quote}\n\n`;
111+
// todo: if commonAncestorContainer parent has "[data-markdown-original-content]" attribute, use the parent's markdown content
112+
// otherwise, use the selected HTML content and respect all "[data-markdown-original-content]/[data-markdown-generated-content]" attributes
113+
const contents = selection.getRangeAt(0).cloneContents();
114+
const el = document.createElement('div');
115+
el.append(contents);
116+
return convertHtmlToMarkdown(el);
117+
}
113118

114-
let editor;
115-
if (this.classList.contains('quote-reply-diff')) {
116-
const replyBtn = this.closest('.comment-code-cloud').querySelector('button.comment-form-reply');
117-
editor = await handleReply(replyBtn);
118-
} else {
119-
// for normal issue/comment page
120-
editor = getComboMarkdownEditor($('#comment-form .combo-markdown-editor'));
121-
}
122-
if (editor) {
123-
if (editor.value()) {
124-
editor.value(`${editor.value()}\n\n${content}`);
125-
} else {
126-
editor.value(content);
127-
}
128-
editor.focus();
129-
editor.moveCursorToEnd();
130-
}
119+
async function tryOnQuoteReply(e) {
120+
const clickTarget = (e.target as HTMLElement).closest('.quote-reply');
121+
if (!clickTarget) return;
122+
123+
e.preventDefault();
124+
const contentToQuoteId = clickTarget.getAttribute('data-target');
125+
const targetRawToQuote = document.querySelector<HTMLElement>(`#${contentToQuoteId}.raw-content`);
126+
const targetMarkupToQuote = targetRawToQuote.parentElement.querySelector<HTMLElement>('.render-content.markup');
127+
let contentToQuote = extractSelectedMarkdown(targetMarkupToQuote);
128+
if (!contentToQuote) contentToQuote = targetRawToQuote.textContent;
129+
const quotedContent = `${contentToQuote.replace(/^/mg, '> ')}\n`;
130+
131+
let editor;
132+
if (clickTarget.classList.contains('quote-reply-diff')) {
133+
const replyBtn = clickTarget.closest('.comment-code-cloud').querySelector('button.comment-form-reply');
134+
editor = await handleReply(replyBtn);
135+
} else {
136+
// for normal issue/comment page
137+
editor = getComboMarkdownEditor(document.querySelector('#comment-form .combo-markdown-editor'));
138+
}
139+
140+
if (editor.value()) {
141+
editor.value(`${editor.value()}\n\n${quotedContent}`);
142+
} else {
143+
editor.value(quotedContent);
144+
}
145+
editor.focus();
146+
editor.moveCursorToEnd();
147+
}
148+
149+
export function initRepoIssueCommentEdit() {
150+
document.addEventListener('click', (e) => {
151+
tryOnEditContent(e); // Edit issue or comment content
152+
tryOnQuoteReply(e); // Quote reply to the comment editor
131153
});
132154
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {convertHtmlToMarkdown} from './html2markdown.ts';
2+
import {createElementFromHTML} from '../utils/dom.ts';
3+
4+
const h = createElementFromHTML;
5+
6+
test('convertHtmlToMarkdown', () => {
7+
expect(convertHtmlToMarkdown(h(`<h1>h</h1>`))).toBe('# h');
8+
expect(convertHtmlToMarkdown(h(`<strong>txt</strong>`))).toBe('**txt**');
9+
expect(convertHtmlToMarkdown(h(`<em>txt</em>`))).toBe('_txt_');
10+
expect(convertHtmlToMarkdown(h(`<del>txt</del>`))).toBe('~~txt~~');
11+
12+
expect(convertHtmlToMarkdown(h(`<a href="link">txt</a>`))).toBe('[txt](link)');
13+
expect(convertHtmlToMarkdown(h(`<a href="https://link">https://link</a>`))).toBe('https://link');
14+
15+
expect(convertHtmlToMarkdown(h(`<img src="link">`))).toBe('![image](link)');
16+
expect(convertHtmlToMarkdown(h(`<img src="link" alt="name">`))).toBe('![name](link)');
17+
expect(convertHtmlToMarkdown(h(`<img src="link" width="1" height="1">`))).toBe('<img alt="image" width="1" height="1" src="link">');
18+
19+
expect(convertHtmlToMarkdown(h(`<p>txt</p>`))).toBe('txt\n');
20+
expect(convertHtmlToMarkdown(h(`<blockquote>a\nb</blockquote>`))).toBe('> a\n> b\n');
21+
22+
expect(convertHtmlToMarkdown(h(`<ol><li>a<ul><li>b</li></ul></li></ol>`))).toBe('1. a\n * b\n\n');
23+
expect(convertHtmlToMarkdown(h(`<ol><li><input checked>a</li></ol>`))).toBe('1. [x] a\n');
24+
});

0 commit comments

Comments
 (0)