loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'feat: add partial quoting' (#5677) from gusted/forgejo-partial-qouting into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5677
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Otto <otto@codeberg.org>

Gusted d5a11880 c8ba3308

+303 -68
+7 -3
modules/markup/html.go
··· 472 472 return code 473 473 } 474 474 475 - func createEmoji(content, class, name string) *html.Node { 475 + func createEmoji(content, class, name, alias string) *html.Node { 476 476 span := &html.Node{ 477 477 Type: html.ElementNode, 478 478 Data: atom.Span.String(), ··· 483 483 } 484 484 if name != "" { 485 485 span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: name}) 486 + } 487 + if alias != "" { 488 + span.Attr = append(span.Attr, html.Attribute{Key: "data-alias", Val: alias}) 486 489 } 487 490 488 491 text := &html.Node{ ··· 502 505 } 503 506 span.Attr = append(span.Attr, html.Attribute{Key: "class", Val: "emoji"}) 504 507 span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias}) 508 + span.Attr = append(span.Attr, html.Attribute{Key: "data-alias", Val: alias}) 505 509 506 510 img := &html.Node{ 507 511 Type: html.ElementNode, ··· 1147 1151 continue 1148 1152 } 1149 1153 1150 - replaceContent(node, m[0], m[1], createEmoji(converted.Emoji, "emoji", converted.Description)) 1154 + replaceContent(node, m[0], m[1], createEmoji(converted.Emoji, "emoji", converted.Description, alias)) 1151 1155 node = node.NextSibling.NextSibling 1152 1156 start = 0 1153 1157 } ··· 1169 1173 start = m[1] 1170 1174 val := emoji.FromCode(codepoint) 1171 1175 if val != nil { 1172 - replaceContent(node, m[0], m[1], createEmoji(codepoint, "emoji", val.Description)) 1176 + replaceContent(node, m[0], m[1], createEmoji(codepoint, "emoji", val.Description, val.Aliases[0])) 1173 1177 node = node.NextSibling.NextSibling 1174 1178 start = 0 1175 1179 }
+13 -13
modules/markup/html_test.go
··· 329 329 for i := range emoji.GemojiData { 330 330 test( 331 331 emoji.GemojiData[i].Emoji, 332 - `<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`) 332 + `<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`" data-alias="`+emoji.GemojiData[i].Aliases[0]+`">`+emoji.GemojiData[i].Emoji+`</span></p>`) 333 333 } 334 334 for i := range emoji.GemojiData { 335 335 test( 336 336 ":"+emoji.GemojiData[i].Aliases[0]+":", 337 - `<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`) 337 + `<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`" data-alias="`+emoji.GemojiData[i].Aliases[0]+`">`+emoji.GemojiData[i].Emoji+`</span></p>`) 338 338 } 339 339 340 340 // Text that should be turned into or recognized as emoji 341 341 test( 342 342 ":gitea:", 343 - `<p><span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`) 343 + `<p><span class="emoji" aria-label="gitea" data-alias="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`) 344 344 test( 345 345 ":custom-emoji:", 346 346 `<p>:custom-emoji:</p>`) 347 347 setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:" 348 348 test( 349 349 ":custom-emoji:", 350 - `<p><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`) 350 + `<p><span class="emoji" aria-label="custom-emoji" data-alias="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`) 351 351 test( 352 352 "这是字符:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:", 353 - `<p>这是字符:1:<span class="emoji" aria-label="thumbs up">👍</span> some<span class="emoji" aria-label="crocodile">🐊</span> `+ 354 - `<span class="emoji" aria-label="thumbs up">👍</span><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span> `+ 355 - `<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`) 353 + `<p>这是字符:1:<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span> some<span class="emoji" aria-label="crocodile" data-alias="crocodile">🐊</span> `+ 354 + `<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><span class="emoji" aria-label="custom-emoji" data-alias="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span> `+ 355 + `<span class="emoji" aria-label="gitea" data-alias="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`) 356 356 test( 357 357 "Some text with 😄 in the middle", 358 - `<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p>`) 358 + `<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> in the middle</p>`) 359 359 test( 360 360 "Some text with :smile: in the middle", 361 - `<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p>`) 361 + `<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> in the middle</p>`) 362 362 test( 363 363 "Some text with 😄😄 2 emoji next to each other", 364 - `<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span><span class="emoji" aria-label="grinning face with smiling eyes">😄</span> 2 emoji next to each other</p>`) 364 + `<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span><span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> 2 emoji next to each other</p>`) 365 365 test( 366 366 "😎🤪🔐🤑❓", 367 - `<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`) 367 + `<p><span class="emoji" aria-label="smiling face with sunglasses" data-alias="sunglasses">😎</span><span class="emoji" aria-label="zany face" data-alias="zany_face">🤪</span><span class="emoji" aria-label="locked with key" data-alias="closed_lock_with_key">🔐</span><span class="emoji" aria-label="money-mouth face" data-alias="money_mouth_face">🤑</span><span class="emoji" aria-label="red question mark" data-alias="question">❓</span></p>`) 368 368 369 369 // should match nothing 370 370 test( ··· 601 601 // Test that other post processing still works. 602 602 test( 603 603 ":gitea:", 604 - `<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`) 604 + `<span class="emoji" aria-label="gitea" data-alias="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`) 605 605 test( 606 606 "Some text with 😄 in the middle", 607 - `Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle`) 607 + `Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> in the middle`) 608 608 test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", 609 609 `<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`) 610 610 }
+15 -15
modules/markup/markdown/markdown_test.go
··· 135 135 <p>See commit <a href="/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p> 136 136 <p>Ideas and codes</p> 137 137 <ul> 138 - <li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li> 139 - <li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li> 138 + <li>Bezier widget (by <a href="/r-lyeh" class="mention" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li> 139 + <li>Bezier widget (by <a href="/r-lyeh" class="mention" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li> 140 140 <li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li> 141 141 <li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li> 142 142 <li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li> ··· 422 422 423 423 func TestRenderEmojiInLinks_Issue12331(t *testing.T) { 424 424 testcase := `[Link with emoji :moon: in text](https://gitea.io)` 425 - expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p> 425 + expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon" data-alias="moon">🌔</span> in text</a></p> 426 426 ` 427 427 res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase) 428 428 require.NoError(t, err) ··· 855 855 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 856 856 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 857 857 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 858 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 858 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 859 859 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 860 860 @mention-user test<br/> 861 861 #123<br/> ··· 882 882 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 883 883 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 884 884 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 885 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 885 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 886 886 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 887 887 @mention-user test<br/> 888 888 #123<br/> ··· 911 911 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 912 912 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 913 913 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 914 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 914 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 915 915 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 916 916 @mention-user test<br/> 917 917 #123<br/> ··· 940 940 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 941 941 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 942 942 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 943 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 943 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 944 944 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 945 945 @mention-user test<br/> 946 946 #123<br/> ··· 969 969 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 970 970 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 971 971 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 972 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 972 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 973 973 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 974 974 @mention-user test<br/> 975 975 #123<br/> ··· 998 998 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 999 999 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1000 1000 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1001 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1001 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1002 1002 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1003 1003 @mention-user test<br/> 1004 1004 #123<br/> ··· 1028 1028 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 1029 1029 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1030 1030 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1031 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1031 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1032 1032 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1033 1033 @mention-user test<br/> 1034 1034 #123<br/> ··· 1058 1058 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 1059 1059 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1060 1060 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1061 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1061 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1062 1062 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1063 1063 @mention-user test<br/> 1064 1064 #123<br/> ··· 1088 1088 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 1089 1089 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1090 1090 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1091 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1091 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1092 1092 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1093 1093 @mention-user test<br/> 1094 1094 #123<br/> ··· 1118 1118 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 1119 1119 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1120 1120 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1121 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1121 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1122 1122 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1123 1123 @mention-user test<br/> 1124 1124 #123<br/> ··· 1149 1149 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 1150 1150 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1151 1151 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1152 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1152 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1153 1153 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1154 1154 @mention-user test<br/> 1155 1155 #123<br/> ··· 1180 1180 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/> 1181 1181 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/> 1182 1182 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/> 1183 - <span class="emoji" aria-label="thumbs up">👍</span><br/> 1183 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/> 1184 1184 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/> 1185 1185 @mention-user test<br/> 1186 1186 #123<br/>
+2 -1
modules/markup/sanitizer.go
··· 94 94 } 95 95 96 96 // Allow classes for anchors 97 - policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue( ref-external-issue)?`)).OnElements("a") 97 + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(ref-issue( ref-external-issue)?|mention)$`)).OnElements("a") 98 98 99 99 // Allow classes for task lists 100 100 policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li") ··· 110 110 111 111 // Allow icons, emojis, chroma syntax and keyword markup on span 112 112 policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(emoji)|(language-math display)|(language-math inline))$|^([a-z][a-z0-9]{0,2})$|^` + keywordClass + `$`)).OnElements("span") 113 + policy.AllowAttrs("data-alias").Matching(regexp.MustCompile(`^[a-zA-Z0-9-_+]+$`)).OnElements("span") 113 114 114 115 // Allow 'color' and 'background-color' properties for the style attribute on text elements and table cells. 115 116 policy.AllowStyles("color", "background-color").OnElements("span", "p", "th", "td")
+7
modules/markup/sanitizer_test.go
··· 68 68 `<a href="javascript:alert('xss')">bad</a>`, `bad`, 69 69 `<a href="vbscript:no">bad</a>`, `bad`, 70 70 `<a href="data:1234">bad</a>`, `bad`, 71 + 72 + // Mention 73 + `<a href="/org/forgejo/teams/UI" class="mention" rel="nofollow">@forgejo/UI</a>`, `<a href="/org/forgejo/teams/UI" class="mention" rel="nofollow">@forgejo/UI</a>`, 74 + 75 + // Emoji 76 + `<span class="emoji" aria-label="thumbs up" data-alias="+1">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up" data-alias="+1">THUMBS UP</span>`, 77 + `<span class="emoji" aria-label="thumbs up" data-alias="(+!)">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`, 71 78 } 72 79 73 80 for i := 0; i < len(testCases); i += 2 {
+9 -9
modules/templates/util_render_test.go
··· 47 47 48 48 func TestApostrophesInMentions(t *testing.T) { 49 49 rendered := RenderMarkdownToHtml(context.Background(), "@mention-user's comment") 50 - assert.EqualValues(t, template.HTML("<p><a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a>&#39;s comment</p>\n"), rendered) 50 + assert.EqualValues(t, template.HTML("<p><a href=\"/mention-user\" class=\"mention\" rel=\"nofollow\">@mention-user</a>&#39;s comment</p>\n"), rendered) 51 51 } 52 52 53 53 func TestNonExistantUserMention(t *testing.T) { 54 54 rendered := RenderMarkdownToHtml(context.Background(), "@ThisUserDoesNotExist @mention-user") 55 - assert.EqualValues(t, template.HTML("<p>@ThisUserDoesNotExist <a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a></p>\n"), rendered) 55 + assert.EqualValues(t, template.HTML("<p>@ThisUserDoesNotExist <a href=\"/mention-user\" class=\"mention\" rel=\"nofollow\">@mention-user</a></p>\n"), rendered) 56 56 } 57 57 58 58 func TestRenderCommitBody(t *testing.T) { ··· 111 111 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 112 112 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code class="nohighlight">88fc37a3c0</code></a> 113 113 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 114 - <span class="emoji" aria-label="thumbs up">👍</span> 114 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span> 115 115 <a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a> 116 116 <a href="/mention-user" class="mention">@mention-user</a> test 117 117 <a href="/user13/repo11/issues/123" class="ref-issue">#123</a> 118 118 space 119 - ` + "`code <span class=\"emoji\" aria-label=\"thumbs up\">👍</span> <a href=\"/user13/repo11/issues/123\" class=\"ref-issue\">#123</a> code`" 119 + ` + "`code <span class=\"emoji\" aria-label=\"thumbs up\" data-alias=\"+1\">👍</span> <a href=\"/user13/repo11/issues/123\" class=\"ref-issue\">#123</a> code`" 120 120 assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas)) 121 121 } 122 122 ··· 148 148 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 149 149 https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb 150 150 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 151 - <span class="emoji" aria-label="thumbs up">👍</span> 151 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span> 152 152 mail@domain.com 153 153 @mention-user test 154 154 <a href="/user13/repo11/issues/123" class="ref-issue">#123</a> ··· 174 174 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 175 175 https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb 176 176 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 177 - <span class="emoji" aria-label="thumbs up">👍</span> 177 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span> 178 178 mail@domain.com 179 179 @mention-user test 180 180 #123 ··· 185 185 } 186 186 187 187 func TestRenderMarkdownToHtml(t *testing.T) { 188 - expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/> 188 + expected := `<p>space <a href="/mention-user" class="mention" rel="nofollow">@mention-user</a><br/> 189 189 /just/a/path.bin 190 190 <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a> 191 191 <a href="/file.bin" rel="nofollow">local link</a> ··· 200 200 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 201 201 <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a> 202 202 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 203 - <span class="emoji" aria-label="thumbs up">👍</span> 203 + <span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span> 204 204 <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a> 205 - <a href="/mention-user" rel="nofollow">@mention-user</a> test 205 + <a href="/mention-user" class="mention" rel="nofollow">@mention-user</a> test 206 206 #123 207 207 space 208 208 <code>code :+1: #123 code</code></p>
+7
package-lock.json
··· 9 9 "@citation-js/plugin-bibtex": "0.7.16", 10 10 "@citation-js/plugin-software-formats": "0.6.1", 11 11 "@github/markdown-toolbar-element": "2.2.3", 12 + "@github/quote-selection": "2.1.0", 12 13 "@github/relative-time-element": "4.4.3", 13 14 "@github/text-expander-element": "2.8.0", 14 15 "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", ··· 3095 3096 "version": "2.2.3", 3096 3097 "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.3.tgz", 3097 3098 "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==", 3099 + "license": "MIT" 3100 + }, 3101 + "node_modules/@github/quote-selection": { 3102 + "version": "2.1.0", 3103 + "resolved": "https://registry.npmjs.org/@github/quote-selection/-/quote-selection-2.1.0.tgz", 3104 + "integrity": "sha512-zyTvG6GpfWuVrRnxa/JpWPlTyj8ItTCMHXNrdXrvNPrSFCsDAiqEaxTW+644lwxXNfzTPQeN11paR9SRRvE2zg==", 3098 3105 "license": "MIT" 3099 3106 }, 3100 3107 "node_modules/@github/relative-time-element": {
+1
package.json
··· 8 8 "@citation-js/plugin-bibtex": "0.7.16", 9 9 "@citation-js/plugin-software-formats": "0.6.1", 10 10 "@github/markdown-toolbar-element": "2.2.3", 11 + "@github/quote-selection": "2.1.0", 11 12 "@github/relative-time-element": "4.4.3", 12 13 "@github/text-expander-element": "2.8.0", 13 14 "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
+1
release-notes/5677.md
··· 1 + If you select a portion of a comment and use the 'Quote reply' feature in the context menu, only that portion will be quoted. The markdown syntax is preserved.
+1 -1
routers/api/v1/misc/markup_test.go
··· 76 76 <ul> 77 77 <li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li> 78 78 <li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li> 79 - <li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li> 79 + <li>Bezier widget (by <a href="` + AppURL + `r-lyeh" class="mention" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li> 80 80 </ul> 81 81 `, 82 82 // Guard wiki sidebar: special syntax
+1 -1
templates/repo/diff/comments.tmpl
··· 53 53 </div> 54 54 </div> 55 55 <div class="ui attached segment comment-body"> 56 - <div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 56 + <div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 57 57 {{if .RenderedContent}} 58 58 {{.RenderedContent}} 59 59 {{else}}
+1 -1
templates/repo/issue/view_content.tmpl
··· 52 52 </div> 53 53 </div> 54 54 <div class="ui attached segment comment-body" role="article"> 55 - <div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission $.IsIssuePoster}}data-can-edit="true"{{end}}> 55 + <div id="issue-{{.Issue.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission $.IsIssuePoster}}data-can-edit="true"{{end}}> 56 56 {{if .Issue.RenderedContent}} 57 57 {{.Issue.RenderedContent}} 58 58 {{else}}
+2 -2
templates/repo/issue/view_content/comments.tmpl
··· 59 59 </div> 60 60 </div> 61 61 <div class="ui attached segment comment-body" role="article"> 62 - <div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 62 + <div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 63 63 {{if .RenderedContent}} 64 64 {{.RenderedContent}} 65 65 {{else}} ··· 435 435 </div> 436 436 </div> 437 437 <div class="ui attached segment comment-body"> 438 - <div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 438 + <div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 439 439 {{if .RenderedContent}} 440 440 {{.RenderedContent}} 441 441 {{else}}
+1 -1
templates/repo/issue/view_content/context_menu.tmpl
··· 11 11 {{end}} 12 12 <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 13 {{if and .ctxData.IsSigned (not .ctxData.Repository.IsArchived)}} 14 - <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> 14 + <div class="item context js-aria-clickable quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-content" data-author="{{.item.Poster.Name}}" data-reference-url="{{$referenceUrl}}">{{ctx.Locale.Tr "repo.issues.context.quote_reply"}}</div> 15 15 {{if not .ctxData.UnitIssuesGlobalDisabled}} 16 16 <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> 17 17 {{end}}
+1 -1
templates/repo/issue/view_content/conversation.tmpl
··· 85 85 </div> 86 86 </div> 87 87 <div class="text comment-content"> 88 - <div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 88 + <div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}> 89 89 {{if .RenderedContent}} 90 90 {{.RenderedContent}} 91 91 {{else}}
+3 -1
tests/e2e/e2e_test.go
··· 49 49 50 50 err := unittest.InitFixtures( 51 51 unittest.FixturesOptions{ 52 - Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), 52 + Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), 53 + Base: setting.AppWorkPath, 54 + Dirs: []string{"tests/e2e/fixtures/"}, 53 55 }, 54 56 ) 55 57 if err != nil {
+22
tests/e2e/fixtures/comment.yml
··· 1 + - 2 + id: 1001 3 + type: 0 # comment 4 + poster_id: 2 5 + issue_id: 1 # in repo_id 1 6 + content: "## Lorem Ipsum\nI would like to say that **I am not appealed** that it took _so long_ for this `feature` to be [created](https://example.com) $e^{\\pi i} + 1 = 0$\n$$e^{\\pi i} + 1 = 0$$\n#1\n```js\nconsole.log('evil')\nalert('evil')\n```\n:+1: :100:" 7 + created_unix: 946684811 8 + updated_unix: 946684811 9 + content_version: 1 10 + 11 + - 12 + id: 1002 13 + type: 21 # code comment 14 + poster_id: 2 15 + issue_id: 19 16 + content: "## Lorem Ipsum\nI would like to say that **I am not appealed** that it took _so long_ for this `feature` to be [created](https://example.com) $e^{\\pi i} + 1 = 0$\n$$e^{\\pi i} + 1 = 0$$\n#1\n```js\nconsole.log('evil')\nalert('evil')\n```\n:+1: :100:" 17 + review_id: 1001 18 + line: 1 19 + tree_path: "test1.txt" 20 + created_unix: 946684812 21 + invalidated: false 22 + content_version: 1
+8
tests/e2e/fixtures/review.yml
··· 1 + - 2 + id: 1001 3 + type: 22 4 + reviewer_id: 1 5 + issue_id: 2 6 + content: "Review Comment" 7 + updated_unix: 946684810 8 + created_unix: 946684810
+107
tests/e2e/issue-comment.test.e2e.js
··· 44 44 await page.locator('textarea').press('ControlOrMeta+a'); 45 45 await page.locator('textarea').press('ControlOrMeta+v'); 46 46 await expect(page.locator('textarea')).toHaveValue('https://codeberg.org/forgejo/forgejo#some-anchor'); 47 + await page.locator('textarea').fill(''); 47 48 }); 48 49 49 50 test('Always focus edit tab first on edit', async ({browser}, workerInfo) => { ··· 68 69 await expect(editTab).toHaveClass(/active/); 69 70 await expect(previewTab).not.toHaveClass(/active/); 70 71 }); 72 + 73 + test('Quote reply', async ({browser}, workerInfo) => { 74 + test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); 75 + const page = await login({browser}, workerInfo); 76 + const response = await page.goto('/user2/repo1/issues/1'); 77 + expect(response?.status()).toBe(200); 78 + 79 + const editorTextarea = page.locator('textarea.markdown-text-editor'); 80 + 81 + // Full quote. 82 + await page.click('#issuecomment-1001 .comment-container .context-menu'); 83 + await page.click('#issuecomment-1001 .quote-reply'); 84 + 85 + await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/repo1/issues/1#issuecomment-1001:\n\n' + 86 + '> ## [](#lorem-ipsum)Lorem Ipsum\n' + 87 + '> \n' + 88 + '> I would like to say that **I am not appealed** that it took _so long_ for this `feature` to be [created](https://example.com) \\(e^{\\pi i} + 1 = 0\\)\n' + 89 + '> \n' + 90 + '> \\[e^{\\pi i} + 1 = 0\\]\n' + 91 + '> \n' + 92 + '> #1\n' + 93 + '> \n' + 94 + '> ```js\n' + 95 + "> console.log('evil')\n" + 96 + "> alert('evil')\n" + 97 + '> ```\n' + 98 + '> \n' + 99 + '> :+1: :100:\n\n'); 100 + 101 + await editorTextarea.fill(''); 102 + 103 + // Partial quote. 104 + await page.click('#issuecomment-1001 .comment-container .context-menu'); 105 + 106 + await page.evaluate(() => { 107 + const range = new Range(); 108 + range.setStart(document.querySelector('#issuecomment-1001-content #user-content-lorem-ipsum').childNodes[1], 6); 109 + range.setEnd(document.querySelector('#issuecomment-1001-content p').childNodes[1].childNodes[0], 7); 110 + 111 + const selection = window.getSelection(); 112 + 113 + // Add range to window selection 114 + selection.addRange(range); 115 + }); 116 + 117 + await page.click('#issuecomment-1001 .quote-reply'); 118 + 119 + await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/repo1/issues/1#issuecomment-1001:\n\n' + 120 + '> ## Ipsum\n' + 121 + '> \n' + 122 + '> I would like to say that **I am no**\n\n'); 123 + 124 + await editorTextarea.fill(''); 125 + 126 + // Another partial quote. 127 + await page.click('#issuecomment-1001 .comment-container .context-menu'); 128 + 129 + await page.evaluate(() => { 130 + const range = new Range(); 131 + range.setStart(document.querySelector('#issuecomment-1001-content p').childNodes[1].childNodes[0], 7); 132 + range.setEnd(document.querySelector('#issuecomment-1001-content p').childNodes[7].childNodes[0], 3); 133 + 134 + const selection = window.getSelection(); 135 + 136 + // Add range to window selection 137 + selection.addRange(range); 138 + }); 139 + 140 + await page.click('#issuecomment-1001 .quote-reply'); 141 + 142 + await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/repo1/issues/1#issuecomment-1001:\n\n' + 143 + '> **t appealed** that it took _so long_ for this `feature` to be [cre](https://example.com)\n\n'); 144 + 145 + await editorTextarea.fill(''); 146 + }); 147 + 148 + test('Pull quote reply', async ({browser}, workerInfo) => { 149 + test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); 150 + const page = await login({browser}, workerInfo); 151 + const response = await page.goto('/user2/commitsonpr/pulls/1/files'); 152 + expect(response?.status()).toBe(200); 153 + 154 + const editorTextarea = page.locator('textarea.markdown-text-editor'); 155 + 156 + // Full quote with no reply handler being open. 157 + await page.click('.comment-code-cloud .context-menu'); 158 + await page.click('.comment-code-cloud .quote-reply'); 159 + 160 + await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/commitsonpr/pulls/1/files#issuecomment-1002:\n\n' + 161 + '> ## [](#lorem-ipsum)Lorem Ipsum\n' + 162 + '> \n' + 163 + '> I would like to say that **I am not appealed** that it took _so long_ for this `feature` to be [created](https://example.com) \\(e^{\\pi i} + 1 = 0\\)\n' + 164 + '> \n' + 165 + '> \\[e^{\\pi i} + 1 = 0\\]\n' + 166 + '> \n' + 167 + '> #1\n' + 168 + '> \n' + 169 + '> ```js\n' + 170 + "> console.log('evil')\n" + 171 + "> alert('evil')\n" + 172 + '> ```\n' + 173 + '> \n' + 174 + '> :+1: :100:\n\n'); 175 + 176 + await editorTextarea.fill(''); 177 + });
+1 -1
tests/integration/repo_issue_title_test.go
··· 39 39 40 40 titleHTML := []string{ 41 41 "Title", 42 - `<span class="emoji" aria-label="thumbs up">👍</span>`, 42 + `<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>`, 43 43 `<code class="inline-code-block">code</code>`, 44 44 } 45 45
+93 -18
web_src/js/features/repo-legacy.js
··· 27 27 import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js'; 28 28 import {attachRefIssueContextPopup} from './contextpopup.js'; 29 29 import {POST, GET} from '../modules/fetch.js'; 30 + import {MarkdownQuote} from '@github/quote-selection'; 31 + import {toAbsoluteUrl} from '../utils.js'; 30 32 31 33 const {csrfToken} = window.config; 32 34 ··· 582 584 initUnicodeEscapeButton(); 583 585 } 584 586 587 + const filters = { 588 + A(el) { 589 + if (el.classList.contains('mention') || el.classList.contains('ref-issue')) { 590 + return el.textContent; 591 + } 592 + return el; 593 + }, 594 + PRE(el) { 595 + const firstChild = el.children[0]; 596 + if (firstChild && el.classList.contains('code-block')) { 597 + // Get the language of the codeblock. 598 + const language = firstChild.className.match(/language-(\S+)/); 599 + // Remove trailing newlines. 600 + const text = el.textContent.replace(/\n+$/, ''); 601 + el.textContent = `\`\`\`${language[1]}\n${text}\n\`\`\`\n\n`; 602 + } 603 + return el; 604 + }, 605 + SPAN(el) { 606 + const emojiAlias = el.getAttribute('data-alias'); 607 + if (emojiAlias && el.classList.contains('emoji')) { 608 + return `:${emojiAlias}:`; 609 + } 610 + if (el.classList.contains('katex')) { 611 + const texCode = el.querySelector('annotation[encoding="application/x-tex"]').textContent; 612 + if (el.parentElement.classList.contains('katex-display')) { 613 + el.textContent = `\\[${texCode}\\]\n\n`; 614 + } else { 615 + el.textContent = `\\(${texCode}\\)\n\n`; 616 + } 617 + } 618 + return el; 619 + }, 620 + }; 621 + 622 + function hasContent(node) { 623 + return node.nodeName === 'IMG' || node.firstChild !== null; 624 + } 625 + 626 + // This code matches that of what is done by @github/quote-selection 627 + function preprocessFragment(fragment) { 628 + const nodeIterator = document.createNodeIterator(fragment, NodeFilter.SHOW_ELEMENT, { 629 + acceptNode(node) { 630 + if (node.nodeName in filters && hasContent(node)) { 631 + return NodeFilter.FILTER_ACCEPT; 632 + } 633 + 634 + return NodeFilter.FILTER_SKIP; 635 + }, 636 + }); 637 + const results = []; 638 + let node = nodeIterator.nextNode(); 639 + 640 + while (node) { 641 + if (node instanceof HTMLElement) { 642 + results.push(node); 643 + } 644 + node = nodeIterator.nextNode(); 645 + } 646 + 647 + // process deepest matches first 648 + results.reverse(); 649 + 650 + for (const el of results) { 651 + el.replaceWith(filters[el.nodeName](el)); 652 + } 653 + } 654 + 585 655 function initRepoIssueCommentEdit() { 586 656 // Edit issue or comment content 587 657 $(document).on('click', '.edit-content', onEditContent); 588 658 589 659 // Quote reply 590 - $(document).on('click', '.quote-reply', async function (event) { 660 + $(document).on('click', '.quote-reply', async (event) => { 591 661 event.preventDefault(); 592 - const target = $(this).data('target'); 593 - const quote = $(`#${target}`).text().replace(/\n/g, '\n> '); 594 - const content = `> ${quote}\n\n`; 595 - let editor; 596 - if (this.classList.contains('quote-reply-diff')) { 597 - const $replyBtn = $(this).closest('.comment-code-cloud').find('button.comment-form-reply'); 598 - editor = await handleReply($replyBtn); 662 + const quote = new MarkdownQuote('', preprocessFragment); 663 + 664 + let editorTextArea; 665 + if (event.target.classList.contains('quote-reply-diff')) { 666 + // Temporarily store the range so it doesn't get lost (likely caused by async code). 667 + const currentRange = quote.range; 668 + 669 + const replyButton = event.target.closest('.comment-code-cloud').querySelector('button.comment-form-reply'); 670 + editorTextArea = (await handleReply($(replyButton))).textarea; 671 + 672 + quote.range = currentRange; 599 673 } else { 600 - // for normal issue/comment page 601 - editor = getComboMarkdownEditor($('#comment-form .combo-markdown-editor')); 674 + editorTextArea = document.querySelector('#comment-form .combo-markdown-editor textarea'); 675 + } 676 + 677 + // Select the whole comment body if there's no selection. 678 + if (quote.range.collapsed) { 679 + quote.select(document.querySelector(`#${event.target.getAttribute('data-target')}`)); 602 680 } 603 - if (editor) { 604 - if (editor.value()) { 605 - editor.value(`${editor.value()}\n\n${content}`); 606 - } else { 607 - editor.value(content); 608 - } 609 - editor.focus(); 610 - editor.moveCursorToEnd(); 681 + 682 + // If the selection is in the comment body, then insert the quote. 683 + if (quote.closest(`#${event.target.getAttribute('data-target')}`)) { 684 + editorTextArea.value += `@${event.target.getAttribute('data-author')} wrote in ${toAbsoluteUrl(event.target.getAttribute('data-reference-url'))}:`; 685 + quote.insert(editorTextArea); 611 686 } 612 687 }); 613 688 }