Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unique ID for each heading #19

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import (
func Markdown(text []byte) []byte {
const htmlFlags = 0
renderer := &renderer{Html: blackfriday.HtmlRenderer(htmlFlags, "", "").(*blackfriday.Html)}
renderer.headerIDs = make(map[string]int)

unsanitized := blackfriday.Markdown(text, renderer, extensions)
sanitized := policy.SanitizeBytes(unsanitized)
return sanitized
return policy.SanitizeBytes(unsanitized)
}

// Heading returns a heading HTML node with title text.
Expand Down Expand Up @@ -91,10 +92,11 @@ var policy = func() *bluemonday.Policy {

type renderer struct {
*blackfriday.Html
headerIDs map[string]int
}

// GitHub Flavored Markdown heading with clickable and hidden anchor.
func (*renderer) Header(out *bytes.Buffer, text func() bool, level int, _ string) {
func (r *renderer) Header(out *bytes.Buffer, text func() bool, level int, _ string) {
marker := out.Len()
doubleSpace(out)

Expand All @@ -114,9 +116,11 @@ func (*renderer) Header(out *bytes.Buffer, text func() bool, level int, _ string
// Failed to parse HTML (probably can never happen), so just use the whole thing.
textContent = html.UnescapeString(textHTML)
}
anchorName := sanitized_anchor_name.Create(textContent)

out.WriteString(fmt.Sprintf(`<h%d><a name="%s" class="anchor" href="#%s" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>`, level, anchorName, anchorName))
id := sanitized_anchor_name.Create(textContent)
id = r.ensureUniqueHeaderID(id)

out.WriteString(fmt.Sprintf(`<h%d><a id="%s" class="anchor" href="#%s" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>`, level, id, id))
out.WriteString(textHTML)
out.WriteString(fmt.Sprintf("</h%d>\n", level))
}
Expand Down Expand Up @@ -330,3 +334,22 @@ func attrEscape(out *bytes.Buffer, src []byte) {
out.Write(src[org:])
}
}

func (r *renderer) ensureUniqueHeaderID(id string) string {
for idx, found := r.headerIDs[id]; found; idx, found = r.headerIDs[id] {
tmp := fmt.Sprintf("%s-%d", id, idx+1)

if _, tmpFound := r.headerIDs[tmp]; !tmpFound {
r.headerIDs[id] = idx + 1
id = tmp
} else {
id = id + "-1"
}
}

if _, found := r.headerIDs[id]; !found {
r.headerIDs[id] = 0
}

return id
}
17 changes: 8 additions & 9 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package github_flavored_markdown_test
package github_flavored_markdown
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

golint: don't use an underscore in package name


import (
"io"
"net/http"
"os"
"testing"

"github.com/shurcooL/github_flavored_markdown"
"github.com/shurcooL/github_flavored_markdown/gfmstyle"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
Expand All @@ -15,7 +14,7 @@ import (
func ExampleMarkdown() {
text := []byte("Hello world github/linguist#1 **cool**, and #1!")

os.Stdout.Write(github_flavored_markdown.Markdown(text))
os.Stdout.Write(Markdown(text))

// Output:
// <p>Hello world github/linguist#1 <strong>cool</strong>, and #1!</p>
Expand All @@ -30,11 +29,11 @@ func ExampleMarkdown_completeHTMLPage() {
markdown := []byte("# GitHub Flavored Markdown\n\nHello.")

io.WriteString(w, `<html><head><meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" /></head><body><article class="markdown-body entry-content" style="padding: 30px;">`)
w.Write(github_flavored_markdown.Markdown(markdown))
w.Write(Markdown(markdown))
io.WriteString(w, `</article></body></html>`)

// Output:
// <html><head><meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" /></head><body><article class="markdown-body entry-content" style="padding: 30px;"><h1><a name="github-flavored-markdown" class="anchor" href="#github-flavored-markdown" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>GitHub Flavored Markdown</h1>
// <html><head><meta charset="utf-8"><link href="/assets/gfm.css" media="all" rel="stylesheet" type="text/css" /><link href="//cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.css" media="all" rel="stylesheet" type="text/css" /></head><body><article class="markdown-body entry-content" style="padding: 30px;"><h1><a id="github-flavored-markdown" class="anchor" href="#github-flavored-markdown" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>GitHub Flavored Markdown</h1>
//
// <p>Hello.</p>
// </article></body></html>
Expand All @@ -48,12 +47,12 @@ func TestComponents(t *testing.T) {
{
// Heading.
text: "## git diff",
want: `<h2><a name="git-diff" class="anchor" href="#git-diff" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>git diff</h2>` + "\n",
want: `<h2><a id="git-diff" class="anchor" href="#git-diff" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>git diff</h2>` + "\n",
},
{
// Heading Link.
text: "### [Some **bold** _italic_ link](http://www.example.com)",
want: `<h3><a name="some-bold-italic-link" class="anchor" href="#some-bold-italic-link" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a><a href="http://www.example.com" rel="nofollow">Some <strong>bold</strong> <em>italic</em> link</a></h3>` + "\n",
want: `<h3><a id="some-bold-italic-link" class="anchor" href="#some-bold-italic-link" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a><a href="http://www.example.com" rel="nofollow">Some <strong>bold</strong> <em>italic</em> link</a></h3>` + "\n",
},
{
// Task List.
Expand Down Expand Up @@ -85,14 +84,14 @@ func TestComponents(t *testing.T) {
}

for _, test := range tests {
if got := string(github_flavored_markdown.Markdown([]byte(test.text))); got != test.want {
if got := string(Markdown([]byte(test.text))); got != test.want {
t.Errorf("\ngot %q\nwant %q", got, test.want)
}
}
}

func ExampleHeading() {
heading := github_flavored_markdown.Heading(atom.H2, "Hello > Goodbye")
heading := Heading(atom.H2, "Hello > Goodbye")
html.Render(os.Stdout, heading)

// Output:
Expand Down
5 changes: 3 additions & 2 deletions sanitize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ index dc83bf7..5260a7d 100644

htmlFlags := 0
renderer := &renderer{Html: blackfriday.HtmlRenderer(htmlFlags, "", "").(*blackfriday.Html)}
renderer.headerIDs = make(map[string]int)

unsanitized := blackfriday.Markdown(text, renderer, extensions)

Expand Down Expand Up @@ -113,11 +114,11 @@ func TestSanitizeAnchorName(t *testing.T) {
}{
{
text: "## Did you just steal this template from Tom's TOML?",
want: `<h2><a name="did-you-just-steal-this-template-from-tom-s-toml" class="anchor" href="#did-you-just-steal-this-template-from-tom-s-toml" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Did you just steal this template from Tom&#39;s TOML?</h2>` + "\n",
want: `<h2><a id="did-you-just-steal-this-template-from-tom-s-toml" class="anchor" href="#did-you-just-steal-this-template-from-tom-s-toml" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>Did you just steal this template from Tom&#39;s TOML?</h2>` + "\n",
},
{
text: `## What about "quotes" & things?`,
want: `<h2><a name="what-about-quotes-things" class="anchor" href="#what-about-quotes-things" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>What about &#34;quotes&#34; &amp; things?</h2>` + "\n",
want: `<h2><a id="what-about-quotes-things" class="anchor" href="#what-about-quotes-things" aria-hidden="true" rel="nofollow"><span class="octicon octicon-link"></span></a>What about &#34;quotes&#34; &amp; things?</h2>` + "\n",
},
}

Expand Down