mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-28 11:12:25 -05:00
[feature] Allow anchor href to work for footnotes, use ID prefix to avoid clashes (#4298)
Updates markdown parser + sanitizer to allow footnote anchors to work properly, with appropriate roles. Footnote anchor IDs and backrefs use the status ID as a prefix to avoid clashes, so that footnotes don't break when multiple footnoted statuses are rendered on the same page (eg., in a thread or on the account's home page). closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4296 Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4298 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
parent
39b11dbfb6
commit
5fbaf5b7be
3 changed files with 38 additions and 5 deletions
|
|
@ -24,6 +24,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/id"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/regexes"
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
|
|
@ -118,6 +119,18 @@ func (f *Formatter) fromMarkdown(
|
|||
}
|
||||
}
|
||||
|
||||
// Inject a footnote ID prefix to avoid
|
||||
// footnote ID clashes. StatusID isn't
|
||||
// always set (eg., when parsing instance
|
||||
// description markdown), so take a random
|
||||
// ULID if it's not.
|
||||
var footnoteIDPrefix string
|
||||
if statusID != "" {
|
||||
footnoteIDPrefix = statusID + "-"
|
||||
} else {
|
||||
footnoteIDPrefix = id.NewULID() + "-"
|
||||
}
|
||||
|
||||
// Instantiate goldmark parser for
|
||||
// markdown, using custom renderer
|
||||
// to add hashtag/mention links.
|
||||
|
|
@ -141,7 +154,9 @@ func (f *Formatter) fromMarkdown(
|
|||
extension.NewLinkify(
|
||||
extension.WithLinkifyURLRegexp(regexes.URLLike),
|
||||
),
|
||||
extension.Footnote,
|
||||
extension.NewFootnote(
|
||||
extension.WithFootnoteIDPrefix(footnoteIDPrefix),
|
||||
),
|
||||
extension.Strikethrough,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@ const (
|
|||
mdCodeBlockWithNewlines = "some code coming up\n\n```\n\n\n\n```\nthat was some code"
|
||||
mdCodeBlockWithNewlinesExpected = "<p>some code coming up</p><pre><code>\n\n\n</code></pre><p>that was some code</p>"
|
||||
mdWithFootnote = "fox mulder,fbi.[^1]\n\n[^1]: federated bureau of investigation"
|
||||
mdWithFootnoteExpected = "<p>fox mulder,fbi.<sup id=\"fnref:1\"><a class=\"footnote-ref\">1</a></sup></p><div><hr><ol><li id=\"fn:1\"><p>federated bureau of investigation <a class=\"footnote-backref\">↩︎</a></p></li></ol></div>"
|
||||
mdWithFootnoteExpected = "<p>fox mulder,fbi.<sup id=\"dummy_status_ID-fnref:1\"><a href=\"#dummy_status_ID-fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p><div><hr><ol><li id=\"dummy_status_ID-fn:1\"><p>federated bureau of investigation <a href=\"#dummy_status_ID-fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p></li></ol></div>"
|
||||
mdWithAttemptedRelative = "hello this is a cheeky relative link: <a href=\"../sneaky.html\">click it!</a>"
|
||||
mdWithAttemptedRelativeExpected = "<p>hello this is a cheeky relative link: click it!</p>"
|
||||
mdWithBlockQuote = "get ready, there's a block quote coming:\n\n>line1\n>line2\n>\n>line3\n\n"
|
||||
mdWithBlockQuoteExpected = "<p>get ready, there's a block quote coming:</p><blockquote><p>line1<br>line2</p><p>line3</p></blockquote>"
|
||||
mdHashtagAndCodeBlock = "#Hashtag\n\n```\n#Hashtag\n```"
|
||||
|
|
@ -156,6 +158,11 @@ func (suite *MarkdownTestSuite) TestParseWithFootnote() {
|
|||
suite.Equal(mdWithFootnoteExpected, formatted.HTML)
|
||||
}
|
||||
|
||||
func (suite *MarkdownTestSuite) TestParseWithAttemptedRelative() {
|
||||
formatted := suite.FromMarkdown(mdWithAttemptedRelative)
|
||||
suite.Equal(mdWithAttemptedRelativeExpected, formatted.HTML)
|
||||
}
|
||||
|
||||
func (suite *MarkdownTestSuite) TestParseWithBlockquote() {
|
||||
formatted := suite.FromMarkdown(mdWithBlockQuote)
|
||||
suite.Equal(mdWithBlockQuoteExpected, formatted.HTML)
|
||||
|
|
|
|||
|
|
@ -124,17 +124,28 @@ var regular *bluemonday.Policy = func() *bluemonday.Policy {
|
|||
*/
|
||||
|
||||
// Permit hyperlinks.
|
||||
p.AllowAttrs("class", "href", "rel").OnElements("a")
|
||||
p.AllowAttrs("class", "rel").OnElements("a")
|
||||
|
||||
// Permit footnote roles on anchor elements.
|
||||
p.AllowAttrs("role").Matching(regexp.MustCompile("^doc-noteref$")).OnElements("a")
|
||||
p.AllowAttrs("role").Matching(regexp.MustCompile("^doc-backlink$")).OnElements("a")
|
||||
|
||||
// URLs must be parseable by net/url.Parse().
|
||||
p.RequireParseableURLs(true)
|
||||
|
||||
// Most common URL schemes only.
|
||||
// Relative URLs are OK as we
|
||||
// need fragments for footnotes.
|
||||
p.AllowRelativeURLs(true)
|
||||
|
||||
// However *only* allow common schemes, and also
|
||||
// relative URLs beginning with "#", ie., fragments.
|
||||
// We don't want URL's like "../../peepee.html".
|
||||
p.AllowURLSchemes("mailto", "http", "https")
|
||||
p.AllowAttrs("href").Matching(regexp.MustCompile("^(?:#|mailto|https://|http://).+$")).OnElements("a")
|
||||
|
||||
// Force rel="noreferrer".
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/noreferrer
|
||||
p.RequireNoReferrerOnLinks(true)
|
||||
p.RequireNoReferrerOnFullyQualifiedLinks(true)
|
||||
|
||||
// Add rel="nofollow" on all fully qualified (not relative) links.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel#nofollow
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue