mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-27 06:03:51 +03:00
feat: Add partial quoting
- If you select a portion of the comment, `Quote reply` will not only quote that portion and not copy paste the whole text as it previously did. This is achieved by using the `@github/quote-selection` package. - There's preprocessing to ensure Forgejo-flavored markdown syntax is preserved. - e2e test added. - Resolves #1342
This commit is contained in:
parent
8b7410f35c
commit
2c2ac80030
21 changed files with 303 additions and 68 deletions
|
@ -472,7 +472,7 @@ func createInlineCode(content string) *html.Node {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
func createEmoji(content, class, name string) *html.Node {
|
func createEmoji(content, class, name, alias string) *html.Node {
|
||||||
span := &html.Node{
|
span := &html.Node{
|
||||||
Type: html.ElementNode,
|
Type: html.ElementNode,
|
||||||
Data: atom.Span.String(),
|
Data: atom.Span.String(),
|
||||||
|
@ -484,6 +484,9 @@ func createEmoji(content, class, name string) *html.Node {
|
||||||
if name != "" {
|
if name != "" {
|
||||||
span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: name})
|
span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: name})
|
||||||
}
|
}
|
||||||
|
if alias != "" {
|
||||||
|
span.Attr = append(span.Attr, html.Attribute{Key: "data-alias", Val: alias})
|
||||||
|
}
|
||||||
|
|
||||||
text := &html.Node{
|
text := &html.Node{
|
||||||
Type: html.TextNode,
|
Type: html.TextNode,
|
||||||
|
@ -502,6 +505,7 @@ func createCustomEmoji(alias string) *html.Node {
|
||||||
}
|
}
|
||||||
span.Attr = append(span.Attr, html.Attribute{Key: "class", Val: "emoji"})
|
span.Attr = append(span.Attr, html.Attribute{Key: "class", Val: "emoji"})
|
||||||
span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias})
|
span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias})
|
||||||
|
span.Attr = append(span.Attr, html.Attribute{Key: "data-alias", Val: alias})
|
||||||
|
|
||||||
img := &html.Node{
|
img := &html.Node{
|
||||||
Type: html.ElementNode,
|
Type: html.ElementNode,
|
||||||
|
@ -1147,7 +1151,7 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceContent(node, m[0], m[1], createEmoji(converted.Emoji, "emoji", converted.Description))
|
replaceContent(node, m[0], m[1], createEmoji(converted.Emoji, "emoji", converted.Description, alias))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
|
@ -1169,7 +1173,7 @@ func emojiProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
start = m[1]
|
start = m[1]
|
||||||
val := emoji.FromCode(codepoint)
|
val := emoji.FromCode(codepoint)
|
||||||
if val != nil {
|
if val != nil {
|
||||||
replaceContent(node, m[0], m[1], createEmoji(codepoint, "emoji", val.Description))
|
replaceContent(node, m[0], m[1], createEmoji(codepoint, "emoji", val.Description, val.Aliases[0]))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,42 +329,42 @@ func TestRender_emoji(t *testing.T) {
|
||||||
for i := range emoji.GemojiData {
|
for i := range emoji.GemojiData {
|
||||||
test(
|
test(
|
||||||
emoji.GemojiData[i].Emoji,
|
emoji.GemojiData[i].Emoji,
|
||||||
`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
|
`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`" data-alias="`+emoji.GemojiData[i].Aliases[0]+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
|
||||||
}
|
}
|
||||||
for i := range emoji.GemojiData {
|
for i := range emoji.GemojiData {
|
||||||
test(
|
test(
|
||||||
":"+emoji.GemojiData[i].Aliases[0]+":",
|
":"+emoji.GemojiData[i].Aliases[0]+":",
|
||||||
`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
|
`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`" data-alias="`+emoji.GemojiData[i].Aliases[0]+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text that should be turned into or recognized as emoji
|
// Text that should be turned into or recognized as emoji
|
||||||
test(
|
test(
|
||||||
":gitea:",
|
":gitea:",
|
||||||
`<p><span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
`<p><span class="emoji" aria-label="gitea" data-alias="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
||||||
test(
|
test(
|
||||||
":custom-emoji:",
|
":custom-emoji:",
|
||||||
`<p>:custom-emoji:</p>`)
|
`<p>:custom-emoji:</p>`)
|
||||||
setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:"
|
setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:"
|
||||||
test(
|
test(
|
||||||
":custom-emoji:",
|
":custom-emoji:",
|
||||||
`<p><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`)
|
`<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>`)
|
||||||
test(
|
test(
|
||||||
"这是字符:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:",
|
"这是字符:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:",
|
||||||
`<p>这是字符:1:<span class="emoji" aria-label="thumbs up">👍</span> some<span class="emoji" aria-label="crocodile">🐊</span> `+
|
`<p>这是字符:1:<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span> some<span class="emoji" aria-label="crocodile" data-alias="crocodile">🐊</span> `+
|
||||||
`<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> `+
|
`<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> `+
|
||||||
`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
`<span class="emoji" aria-label="gitea" data-alias="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
|
||||||
test(
|
test(
|
||||||
"Some text with 😄 in the middle",
|
"Some text with 😄 in the middle",
|
||||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p>`)
|
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> in the middle</p>`)
|
||||||
test(
|
test(
|
||||||
"Some text with :smile: in the middle",
|
"Some text with :smile: in the middle",
|
||||||
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p>`)
|
`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> in the middle</p>`)
|
||||||
test(
|
test(
|
||||||
"Some text with 😄😄 2 emoji next to each other",
|
"Some text with 😄😄 2 emoji next to each other",
|
||||||
`<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>`)
|
`<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>`)
|
||||||
test(
|
test(
|
||||||
"😎🤪🔐🤑❓",
|
"😎🤪🔐🤑❓",
|
||||||
`<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>`)
|
`<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>`)
|
||||||
|
|
||||||
// should match nothing
|
// should match nothing
|
||||||
test(
|
test(
|
||||||
|
@ -601,10 +601,10 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
||||||
// Test that other post processing still works.
|
// Test that other post processing still works.
|
||||||
test(
|
test(
|
||||||
":gitea:",
|
":gitea:",
|
||||||
`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`)
|
`<span class="emoji" aria-label="gitea" data-alias="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`)
|
||||||
test(
|
test(
|
||||||
"Some text with 😄 in the middle",
|
"Some text with 😄 in the middle",
|
||||||
`Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle`)
|
`Some text with <span class="emoji" aria-label="grinning face with smiling eyes" data-alias="smile">😄</span> in the middle`)
|
||||||
test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
|
test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
|
||||||
`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`)
|
`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,8 +135,8 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
|
||||||
<p>See commit <a href="/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
|
<p>See commit <a href="/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
|
||||||
<p>Ideas and codes</p>
|
<p>Ideas and codes</p>
|
||||||
<ul>
|
<ul>
|
||||||
<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>
|
<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>
|
||||||
<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>
|
<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>
|
||||||
<li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
|
<li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
|
||||||
<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
|
<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
|
||||||
<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
|
<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
|
||||||
|
@ -422,7 +422,7 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
|
||||||
|
|
||||||
func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
|
func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
|
||||||
testcase := `[Link with emoji :moon: in text](https://gitea.io)`
|
testcase := `[Link with emoji :moon: in text](https://gitea.io)`
|
||||||
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>
|
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>
|
||||||
`
|
`
|
||||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
|
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -855,7 +855,7 @@ mail@domain.com
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -882,7 +882,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -911,7 +911,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -940,7 +940,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -969,7 +969,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -998,7 +998,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -1028,7 +1028,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -1058,7 +1058,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -1088,7 +1088,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -1118,7 +1118,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -1149,7 +1149,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
@ -1180,7 +1180,7 @@ space</p>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span><br/>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span><br/>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
|
||||||
@mention-user test<br/>
|
@mention-user test<br/>
|
||||||
#123<br/>
|
#123<br/>
|
||||||
|
|
|
@ -94,7 +94,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow classes for anchors
|
// Allow classes for anchors
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`ref-issue( ref-external-issue)?`)).OnElements("a")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(ref-issue( ref-external-issue)?|mention)$`)).OnElements("a")
|
||||||
|
|
||||||
// Allow classes for task lists
|
// Allow classes for task lists
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li")
|
||||||
|
@ -110,6 +110,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
|
|
||||||
// Allow icons, emojis, chroma syntax and keyword markup on span
|
// Allow icons, emojis, chroma syntax and keyword markup on span
|
||||||
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")
|
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")
|
||||||
|
policy.AllowAttrs("data-alias").Matching(regexp.MustCompile(`^[a-zA-Z0-9-_+]+$`)).OnElements("span")
|
||||||
|
|
||||||
// Allow 'color' and 'background-color' properties for the style attribute on text elements and table cells.
|
// Allow 'color' and 'background-color' properties for the style attribute on text elements and table cells.
|
||||||
policy.AllowStyles("color", "background-color").OnElements("span", "p", "th", "td")
|
policy.AllowStyles("color", "background-color").OnElements("span", "p", "th", "td")
|
||||||
|
|
|
@ -68,6 +68,13 @@ func Test_Sanitizer(t *testing.T) {
|
||||||
`<a href="javascript:alert('xss')">bad</a>`, `bad`,
|
`<a href="javascript:alert('xss')">bad</a>`, `bad`,
|
||||||
`<a href="vbscript:no">bad</a>`, `bad`,
|
`<a href="vbscript:no">bad</a>`, `bad`,
|
||||||
`<a href="data:1234">bad</a>`, `bad`,
|
`<a href="data:1234">bad</a>`, `bad`,
|
||||||
|
|
||||||
|
// Mention
|
||||||
|
`<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>`,
|
||||||
|
|
||||||
|
// Emoji
|
||||||
|
`<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>`,
|
||||||
|
`<span class="emoji" aria-label="thumbs up" data-alias="(+!)">THUMBS UP</span>`, `<span class="emoji" aria-label="thumbs up">THUMBS UP</span>`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCases); i += 2 {
|
for i := 0; i < len(testCases); i += 2 {
|
||||||
|
|
|
@ -47,12 +47,12 @@ var testMetas = map[string]string{
|
||||||
|
|
||||||
func TestApostrophesInMentions(t *testing.T) {
|
func TestApostrophesInMentions(t *testing.T) {
|
||||||
rendered := RenderMarkdownToHtml(context.Background(), "@mention-user's comment")
|
rendered := RenderMarkdownToHtml(context.Background(), "@mention-user's comment")
|
||||||
assert.EqualValues(t, template.HTML("<p><a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a>'s comment</p>\n"), rendered)
|
assert.EqualValues(t, template.HTML("<p><a href=\"/mention-user\" class=\"mention\" rel=\"nofollow\">@mention-user</a>'s comment</p>\n"), rendered)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNonExistantUserMention(t *testing.T) {
|
func TestNonExistantUserMention(t *testing.T) {
|
||||||
rendered := RenderMarkdownToHtml(context.Background(), "@ThisUserDoesNotExist @mention-user")
|
rendered := RenderMarkdownToHtml(context.Background(), "@ThisUserDoesNotExist @mention-user")
|
||||||
assert.EqualValues(t, template.HTML("<p>@ThisUserDoesNotExist <a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a></p>\n"), rendered)
|
assert.EqualValues(t, template.HTML("<p>@ThisUserDoesNotExist <a href=\"/mention-user\" class=\"mention\" rel=\"nofollow\">@mention-user</a></p>\n"), rendered)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderCommitBody(t *testing.T) {
|
func TestRenderCommitBody(t *testing.T) {
|
||||||
|
@ -111,12 +111,12 @@ func TestRenderCommitBody(t *testing.T) {
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code class="nohighlight">88fc37a3c0</code></a>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code class="nohighlight">88fc37a3c0</code></a>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>
|
||||||
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
|
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
|
||||||
<a href="/mention-user" class="mention">@mention-user</a> test
|
<a href="/mention-user" class="mention">@mention-user</a> test
|
||||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||||
space
|
space
|
||||||
` + "`code <span class=\"emoji\" aria-label=\"thumbs up\">👍</span> <a href=\"/user13/repo11/issues/123\" class=\"ref-issue\">#123</a> code`"
|
` + "`code <span class=\"emoji\" aria-label=\"thumbs up\" data-alias=\"+1\">👍</span> <a href=\"/user13/repo11/issues/123\" class=\"ref-issue\">#123</a> code`"
|
||||||
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
|
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb..
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>
|
||||||
mail@domain.com
|
mail@domain.com
|
||||||
@mention-user test
|
@mention-user test
|
||||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||||
|
@ -174,7 +174,7 @@ https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb..
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||||
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>
|
||||||
mail@domain.com
|
mail@domain.com
|
||||||
@mention-user test
|
@mention-user test
|
||||||
#123
|
#123
|
||||||
|
@ -185,7 +185,7 @@ mail@domain.com
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
|
expected := `<p>space <a href="/mention-user" class="mention" rel="nofollow">@mention-user</a><br/>
|
||||||
/just/a/path.bin
|
/just/a/path.bin
|
||||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
||||||
<a href="/file.bin" rel="nofollow">local link</a>
|
<a href="/file.bin" rel="nofollow">local link</a>
|
||||||
|
@ -200,9 +200,9 @@ func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
|
||||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>
|
||||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
||||||
<a href="/mention-user" rel="nofollow">@mention-user</a> test
|
<a href="/mention-user" class="mention" rel="nofollow">@mention-user</a> test
|
||||||
#123
|
#123
|
||||||
space
|
space
|
||||||
<code>code :+1: #123 code</code></p>
|
<code>code :+1: #123 code</code></p>
|
||||||
|
|
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
||||||
"@citation-js/plugin-bibtex": "0.7.16",
|
"@citation-js/plugin-bibtex": "0.7.16",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
|
"@github/quote-selection": "2.1.0",
|
||||||
"@github/relative-time-element": "4.4.3",
|
"@github/relative-time-element": "4.4.3",
|
||||||
"@github/text-expander-element": "2.8.0",
|
"@github/text-expander-element": "2.8.0",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
|
@ -3177,6 +3178,12 @@
|
||||||
"integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==",
|
"integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@github/quote-selection": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@github/quote-selection/-/quote-selection-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-zyTvG6GpfWuVrRnxa/JpWPlTyj8ItTCMHXNrdXrvNPrSFCsDAiqEaxTW+644lwxXNfzTPQeN11paR9SRRvE2zg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@github/relative-time-element": {
|
"node_modules/@github/relative-time-element": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.3.tgz",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"@citation-js/plugin-bibtex": "0.7.16",
|
"@citation-js/plugin-bibtex": "0.7.16",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
|
"@github/quote-selection": "2.1.0",
|
||||||
"@github/relative-time-element": "4.4.3",
|
"@github/relative-time-element": "4.4.3",
|
||||||
"@github/text-expander-element": "2.8.0",
|
"@github/text-expander-element": "2.8.0",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
|
|
1
release-notes/5677.md
Normal file
1
release-notes/5677.md
Normal file
|
@ -0,0 +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.
|
|
@ -76,7 +76,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||||
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
||||||
<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>
|
<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>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
// Guard wiki sidebar: special syntax
|
// Guard wiki sidebar: special syntax
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui attached segment comment-body">
|
<div class="ui attached segment comment-body">
|
||||||
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
<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}}>
|
||||||
{{if .RenderedContent}}
|
{{if .RenderedContent}}
|
||||||
{{.RenderedContent}}
|
{{.RenderedContent}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui attached segment comment-body" role="article">
|
<div class="ui attached segment comment-body" role="article">
|
||||||
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission $.IsIssuePoster}}data-can-edit="true"{{end}}>
|
<div id="issue-{{.Issue.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission $.IsIssuePoster}}data-can-edit="true"{{end}}>
|
||||||
{{if .Issue.RenderedContent}}
|
{{if .Issue.RenderedContent}}
|
||||||
{{.Issue.RenderedContent}}
|
{{.Issue.RenderedContent}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui attached segment comment-body" role="article">
|
<div class="ui attached segment comment-body" role="article">
|
||||||
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
<div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
||||||
{{if .RenderedContent}}
|
{{if .RenderedContent}}
|
||||||
{{.RenderedContent}}
|
{{.RenderedContent}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -435,7 +435,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui attached segment comment-body">
|
<div class="ui attached segment comment-body">
|
||||||
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
<div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
||||||
{{if .RenderedContent}}
|
{{if .RenderedContent}}
|
||||||
{{.RenderedContent}}
|
{{.RenderedContent}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<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>
|
<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>
|
||||||
{{if and .ctxData.IsSigned (not .ctxData.Repository.IsArchived)}}
|
{{if and .ctxData.IsSigned (not .ctxData.Repository.IsArchived)}}
|
||||||
<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>
|
<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>
|
||||||
{{if not .ctxData.UnitIssuesGlobalDisabled}}
|
{{if not .ctxData.UnitIssuesGlobalDisabled}}
|
||||||
<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>
|
<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>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text comment-content">
|
<div class="text comment-content">
|
||||||
<div class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
<div id="issuecomment-{{.ID}}-content" class="render-content markup" {{if or $.Permission.IsAdmin $.HasIssuesOrPullsWritePermission (and $.IsSigned (eq $.SignedUserID .PosterID))}}data-can-edit="true"{{end}}>
|
||||||
{{if .RenderedContent}}
|
{{if .RenderedContent}}
|
||||||
{{.RenderedContent}}
|
{{.RenderedContent}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -49,7 +49,9 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
err := unittest.InitFixtures(
|
err := unittest.InitFixtures(
|
||||||
unittest.FixturesOptions{
|
unittest.FixturesOptions{
|
||||||
Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),
|
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
||||||
|
Base: setting.AppWorkPath,
|
||||||
|
Dirs: []string{"tests/e2e/fixtures/"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
22
tests/e2e/fixtures/comment.yml
Normal file
22
tests/e2e/fixtures/comment.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
type: 0 # comment
|
||||||
|
poster_id: 2
|
||||||
|
issue_id: 1 # in repo_id 1
|
||||||
|
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:"
|
||||||
|
created_unix: 946684811
|
||||||
|
updated_unix: 946684811
|
||||||
|
content_version: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 1002
|
||||||
|
type: 21 # code comment
|
||||||
|
poster_id: 2
|
||||||
|
issue_id: 19
|
||||||
|
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:"
|
||||||
|
review_id: 1001
|
||||||
|
line: 1
|
||||||
|
tree_path: "test1.txt"
|
||||||
|
created_unix: 946684812
|
||||||
|
invalidated: false
|
||||||
|
content_version: 1
|
8
tests/e2e/fixtures/review.yml
Normal file
8
tests/e2e/fixtures/review.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-
|
||||||
|
id: 1001
|
||||||
|
type: 22
|
||||||
|
reviewer_id: 1
|
||||||
|
issue_id: 2
|
||||||
|
content: "Review Comment"
|
||||||
|
updated_unix: 946684810
|
||||||
|
created_unix: 946684810
|
|
@ -44,6 +44,7 @@ test('Hyperlink paste behaviour', async ({browser}, workerInfo) => {
|
||||||
await page.locator('textarea').press('ControlOrMeta+a');
|
await page.locator('textarea').press('ControlOrMeta+a');
|
||||||
await page.locator('textarea').press('ControlOrMeta+v');
|
await page.locator('textarea').press('ControlOrMeta+v');
|
||||||
await expect(page.locator('textarea')).toHaveValue('https://codeberg.org/forgejo/forgejo#some-anchor');
|
await expect(page.locator('textarea')).toHaveValue('https://codeberg.org/forgejo/forgejo#some-anchor');
|
||||||
|
await page.locator('textarea').fill('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Always focus edit tab first on edit', async ({browser}, workerInfo) => {
|
test('Always focus edit tab first on edit', async ({browser}, workerInfo) => {
|
||||||
|
@ -68,3 +69,109 @@ test('Always focus edit tab first on edit', async ({browser}, workerInfo) => {
|
||||||
await expect(editTab).toHaveClass(/active/);
|
await expect(editTab).toHaveClass(/active/);
|
||||||
await expect(previewTab).not.toHaveClass(/active/);
|
await expect(previewTab).not.toHaveClass(/active/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Quote reply', async ({browser}, workerInfo) => {
|
||||||
|
test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks');
|
||||||
|
const page = await login({browser}, workerInfo);
|
||||||
|
const response = await page.goto('/user2/repo1/issues/1');
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
|
||||||
|
const editorTextarea = page.locator('textarea.markdown-text-editor');
|
||||||
|
|
||||||
|
// Full quote.
|
||||||
|
await page.click('#issuecomment-1001 .comment-container .context-menu');
|
||||||
|
await page.click('#issuecomment-1001 .quote-reply');
|
||||||
|
|
||||||
|
await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/repo1/issues/1#issuecomment-1001:\n\n' +
|
||||||
|
'> ## [](#lorem-ipsum)Lorem Ipsum\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> 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' +
|
||||||
|
'> \n' +
|
||||||
|
'> \\[e^{\\pi i} + 1 = 0\\]\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> #1\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> ```js\n' +
|
||||||
|
"> console.log('evil')\n" +
|
||||||
|
"> alert('evil')\n" +
|
||||||
|
'> ```\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> :+1: :100:\n\n');
|
||||||
|
|
||||||
|
await editorTextarea.fill('');
|
||||||
|
|
||||||
|
// Partial quote.
|
||||||
|
await page.click('#issuecomment-1001 .comment-container .context-menu');
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const range = new Range();
|
||||||
|
range.setStart(document.querySelector('#issuecomment-1001-content #user-content-lorem-ipsum').childNodes[1], 6);
|
||||||
|
range.setEnd(document.querySelector('#issuecomment-1001-content p').childNodes[1].childNodes[0], 7);
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
|
||||||
|
// Add range to window selection
|
||||||
|
selection.addRange(range);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.click('#issuecomment-1001 .quote-reply');
|
||||||
|
|
||||||
|
await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/repo1/issues/1#issuecomment-1001:\n\n' +
|
||||||
|
'> ## Ipsum\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> I would like to say that **I am no**\n\n');
|
||||||
|
|
||||||
|
await editorTextarea.fill('');
|
||||||
|
|
||||||
|
// Another partial quote.
|
||||||
|
await page.click('#issuecomment-1001 .comment-container .context-menu');
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const range = new Range();
|
||||||
|
range.setStart(document.querySelector('#issuecomment-1001-content p').childNodes[1].childNodes[0], 7);
|
||||||
|
range.setEnd(document.querySelector('#issuecomment-1001-content p').childNodes[7].childNodes[0], 3);
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
|
||||||
|
// Add range to window selection
|
||||||
|
selection.addRange(range);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.click('#issuecomment-1001 .quote-reply');
|
||||||
|
|
||||||
|
await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/repo1/issues/1#issuecomment-1001:\n\n' +
|
||||||
|
'> **t appealed** that it took _so long_ for this `feature` to be [cre](https://example.com)\n\n');
|
||||||
|
|
||||||
|
await editorTextarea.fill('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Pull quote reply', async ({browser}, workerInfo) => {
|
||||||
|
test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks');
|
||||||
|
const page = await login({browser}, workerInfo);
|
||||||
|
const response = await page.goto('/user2/commitsonpr/pulls/1/files');
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
|
||||||
|
const editorTextarea = page.locator('textarea.markdown-text-editor');
|
||||||
|
|
||||||
|
// Full quote with no reply handler being open.
|
||||||
|
await page.click('.comment-code-cloud .context-menu');
|
||||||
|
await page.click('.comment-code-cloud .quote-reply');
|
||||||
|
|
||||||
|
await expect(editorTextarea).toHaveValue('@user2 wrote in http://localhost:3003/user2/commitsonpr/pulls/1/files#issuecomment-1002:\n\n' +
|
||||||
|
'> ## [](#lorem-ipsum)Lorem Ipsum\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> 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' +
|
||||||
|
'> \n' +
|
||||||
|
'> \\[e^{\\pi i} + 1 = 0\\]\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> #1\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> ```js\n' +
|
||||||
|
"> console.log('evil')\n" +
|
||||||
|
"> alert('evil')\n" +
|
||||||
|
'> ```\n' +
|
||||||
|
'> \n' +
|
||||||
|
'> :+1: :100:\n\n');
|
||||||
|
|
||||||
|
await editorTextarea.fill('');
|
||||||
|
});
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestIssueTitles(t *testing.T) {
|
||||||
|
|
||||||
titleHTML := []string{
|
titleHTML := []string{
|
||||||
"Title",
|
"Title",
|
||||||
`<span class="emoji" aria-label="thumbs up">👍</span>`,
|
`<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>`,
|
||||||
`<code class="inline-code-block">code</code>`,
|
`<code class="inline-code-block">code</code>`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ import {hideElem, showElem} from '../utils/dom.js';
|
||||||
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
|
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
|
||||||
import {attachRefIssueContextPopup} from './contextpopup.js';
|
import {attachRefIssueContextPopup} from './contextpopup.js';
|
||||||
import {POST, GET} from '../modules/fetch.js';
|
import {POST, GET} from '../modules/fetch.js';
|
||||||
|
import {MarkdownQuote} from '@github/quote-selection';
|
||||||
|
import {toAbsoluteUrl} from '../utils.js';
|
||||||
|
|
||||||
const {csrfToken} = window.config;
|
const {csrfToken} = window.config;
|
||||||
|
|
||||||
|
@ -579,32 +581,105 @@ export function initRepository() {
|
||||||
initUnicodeEscapeButton();
|
initUnicodeEscapeButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
A(el) {
|
||||||
|
if (el.classList.contains('mention') || el.classList.contains('ref-issue')) {
|
||||||
|
return el.textContent;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
PRE(el) {
|
||||||
|
const firstChild = el.children[0];
|
||||||
|
if (firstChild && el.classList.contains('code-block')) {
|
||||||
|
// Get the language of the codeblock.
|
||||||
|
const language = firstChild.className.match(/language-(\S+)/);
|
||||||
|
// Remove trailing newlines.
|
||||||
|
const text = el.textContent.replace(/\n+$/, '');
|
||||||
|
el.textContent = `\`\`\`${language[1]}\n${text}\n\`\`\`\n\n`;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
SPAN(el) {
|
||||||
|
const emojiAlias = el.getAttribute('data-alias');
|
||||||
|
if (emojiAlias && el.classList.contains('emoji')) {
|
||||||
|
return `:${emojiAlias}:`;
|
||||||
|
}
|
||||||
|
if (el.classList.contains('katex')) {
|
||||||
|
const texCode = el.querySelector('annotation[encoding="application/x-tex"]').textContent;
|
||||||
|
if (el.parentElement.classList.contains('katex-display')) {
|
||||||
|
el.textContent = `\\[${texCode}\\]\n\n`;
|
||||||
|
} else {
|
||||||
|
el.textContent = `\\(${texCode}\\)\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function hasContent(node) {
|
||||||
|
return node.nodeName === 'IMG' || node.firstChild !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NodeFilter.FILTER_SKIP;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const results = [];
|
||||||
|
let node = nodeIterator.nextNode();
|
||||||
|
|
||||||
|
while (node) {
|
||||||
|
if (node instanceof HTMLElement) {
|
||||||
|
results.push(node);
|
||||||
|
}
|
||||||
|
node = nodeIterator.nextNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// process deepest matches first
|
||||||
|
results.reverse();
|
||||||
|
|
||||||
|
for (const el of results) {
|
||||||
|
el.replaceWith(filters[el.nodeName](el));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initRepoIssueCommentEdit() {
|
function initRepoIssueCommentEdit() {
|
||||||
// Edit issue or comment content
|
// Edit issue or comment content
|
||||||
$(document).on('click', '.edit-content', onEditContent);
|
$(document).on('click', '.edit-content', onEditContent);
|
||||||
|
|
||||||
// Quote reply
|
// Quote reply
|
||||||
$(document).on('click', '.quote-reply', async function (event) {
|
$(document).on('click', '.quote-reply', async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const target = $(this).data('target');
|
const quote = new MarkdownQuote('', preprocessFragment);
|
||||||
const quote = $(`#${target}`).text().replace(/\n/g, '\n> ');
|
|
||||||
const content = `> ${quote}\n\n`;
|
let editorTextArea;
|
||||||
let editor;
|
if (event.target.classList.contains('quote-reply-diff')) {
|
||||||
if (this.classList.contains('quote-reply-diff')) {
|
// Temporarily store the range so it doesn't get lost (likely caused by async code).
|
||||||
const $replyBtn = $(this).closest('.comment-code-cloud').find('button.comment-form-reply');
|
const currentRange = quote.range;
|
||||||
editor = await handleReply($replyBtn);
|
|
||||||
|
const replyButton = event.target.closest('.comment-code-cloud').querySelector('button.comment-form-reply');
|
||||||
|
editorTextArea = (await handleReply($(replyButton))).textarea;
|
||||||
|
|
||||||
|
quote.range = currentRange;
|
||||||
} else {
|
} else {
|
||||||
// for normal issue/comment page
|
editorTextArea = document.querySelector('#comment-form .combo-markdown-editor textarea');
|
||||||
editor = getComboMarkdownEditor($('#comment-form .combo-markdown-editor'));
|
|
||||||
}
|
}
|
||||||
if (editor) {
|
|
||||||
if (editor.value()) {
|
// Select the whole comment body if there's no selection.
|
||||||
editor.value(`${editor.value()}\n\n${content}`);
|
if (quote.range.collapsed) {
|
||||||
} else {
|
quote.select(document.querySelector(`#${event.target.getAttribute('data-target')}`));
|
||||||
editor.value(content);
|
}
|
||||||
}
|
|
||||||
editor.focus();
|
// If the selection is in the comment body, then insert the quote.
|
||||||
editor.moveCursorToEnd();
|
if (quote.closest(`#${event.target.getAttribute('data-target')}`)) {
|
||||||
|
editorTextArea.value += `@${event.target.getAttribute('data-author')} wrote in ${toAbsoluteUrl(event.target.getAttribute('data-reference-url'))}:`;
|
||||||
|
quote.insert(editorTextArea);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue