Merge pull request #1401 from styfle/slugger2

Fix duplicate heading id
This commit is contained in:
Josh Bruce 2018-12-31 15:10:57 -06:00 committed by GitHub
commit dfbc9a1efd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 104 additions and 23 deletions

View File

@ -953,13 +953,13 @@ Renderer.prototype.html = function(html) {
return html; return html;
}; };
Renderer.prototype.heading = function(text, level, raw) { Renderer.prototype.heading = function(text, level, raw, slugger) {
if (this.options.headerIds) { if (this.options.headerIds) {
return '<h' return '<h'
+ level + level
+ ' id="' + ' id="'
+ this.options.headerPrefix + this.options.headerPrefix
+ raw.toLowerCase().replace(/[^\w]+/g, '-') + slugger.slug(raw)
+ '">' + '">'
+ text + text
+ '</h' + '</h'
@ -1108,6 +1108,7 @@ function Parser(options) {
this.options.renderer = this.options.renderer || new Renderer(); this.options.renderer = this.options.renderer || new Renderer();
this.renderer = this.options.renderer; this.renderer = this.options.renderer;
this.renderer.options = this.options; this.renderer.options = this.options;
this.slugger = new Slugger();
} }
/** /**
@ -1186,7 +1187,8 @@ Parser.prototype.tok = function() {
return this.renderer.heading( return this.renderer.heading(
this.inline.output(this.token.text), this.inline.output(this.token.text),
this.token.depth, this.token.depth,
unescape(this.inlineText.output(this.token.text))); unescape(this.inlineText.output(this.token.text)),
this.slugger);
} }
case 'code': { case 'code': {
return this.renderer.code(this.token.text, return this.renderer.code(this.token.text,
@ -1283,6 +1285,37 @@ Parser.prototype.tok = function() {
} }
}; };
/**
* Slugger generates header id
*/
function Slugger () {
this.seen = {};
}
/**
* Convert string to unique id
*/
Slugger.prototype.slug = function (value) {
var slug = value
.toLowerCase()
.trim()
.replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '')
.replace(/\s/g, '-');
if (this.seen.hasOwnProperty(slug)) {
var originalSlug = slug;
do {
this.seen[originalSlug]++;
slug = originalSlug + '-' + this.seen[originalSlug];
} while (this.seen.hasOwnProperty(slug));
}
this.seen[slug] = 0;
return slug;
};
/** /**
* Helpers * Helpers
*/ */
@ -1617,6 +1650,8 @@ marked.lexer = Lexer.lex;
marked.InlineLexer = InlineLexer; marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output; marked.inlineLexer = InlineLexer.output;
marked.Slugger = Slugger;
marked.parse = marked; marked.parse = marked;
if (typeof module !== 'undefined' && typeof exports === 'object') { if (typeof module !== 'undefined' && typeof exports === 'object') {

View File

@ -11,7 +11,7 @@ baz</p>
<p>The spaces after the <code>&gt;</code> characters can be omitted:</p> <p>The spaces after the <code>&gt;</code> characters can be omitted:</p>
<blockquote> <blockquote>
<h1 id="foo">Foo</h1> <h1 id="bar">Bar</h1>
<p>bar <p>bar
baz</p> baz</p>
</blockquote> </blockquote>
@ -21,7 +21,7 @@ baz</p>
<p>The <code>&gt;</code> characters can be indented 1-3 spaces:</p> <p>The <code>&gt;</code> characters can be indented 1-3 spaces:</p>
<blockquote> <blockquote>
<h1 id="foo">Foo</h1> <h1 id="baz">Baz</h1>
<p>bar <p>bar
baz</p> baz</p>
</blockquote> </blockquote>
@ -30,7 +30,7 @@ baz</p>
<p>Four spaces gives us a code block:</p> <p>Four spaces gives us a code block:</p>
<pre><code>&gt; # Foo <pre><code>&gt; # Qux
&gt; bar &gt; bar
&gt; baz</code></pre> &gt; baz</code></pre>
@ -39,7 +39,7 @@ baz</p>
<p>The Laziness clause allows us to omit the <code>&gt;</code> before paragraph continuation text:</p> <p>The Laziness clause allows us to omit the <code>&gt;</code> before paragraph continuation text:</p>
<blockquote> <blockquote>
<h1 id="foo">Foo</h1> <h1 id="quux">Quux</h1>
<p>bar <p>bar
baz</p> baz</p>
</blockquote> </blockquote>

View File

@ -8,7 +8,7 @@
The spaces after the `>` characters can be omitted: The spaces after the `>` characters can be omitted:
># Foo ># Bar
>bar >bar
> baz > baz
@ -16,7 +16,7 @@ The spaces after the `>` characters can be omitted:
The `>` characters can be indented 1-3 spaces: The `>` characters can be indented 1-3 spaces:
> # Foo > # Baz
> bar > bar
> baz > baz
@ -24,7 +24,7 @@ The `>` characters can be indented 1-3 spaces:
Four spaces gives us a code block: Four spaces gives us a code block:
> # Foo > # Qux
> bar > bar
> baz > baz
@ -32,7 +32,7 @@ Four spaces gives us a code block:
The Laziness clause allows us to omit the `>` before paragraph continuation text: The Laziness clause allows us to omit the `>` before paragraph continuation text:
> # Foo > # Quux
> bar > bar
baz baz

View File

@ -12,7 +12,7 @@
<h1 id="how-are-you">how are you</h1> <h1 id="how-are-you">how are you</h1>
<p>paragraph before head with equals</p> <p>paragraph before head with equals</p>
<h1 id="how-are-you">how are you</h1> <h1 id="how-are-you-again">how are you again</h1>
<p>paragraph before blockquote</p> <p>paragraph before blockquote</p>
<blockquote><p>text for blockquote</p></blockquote> <blockquote><p>text for blockquote</p></blockquote>

View File

@ -17,7 +17,7 @@ paragraph before head with hash
# how are you # how are you
paragraph before head with equals paragraph before head with equals
how are you how are you again
=========== ===========
paragraph before blockquote paragraph before blockquote

View File

@ -1,4 +1,4 @@
<h1>Markdown: Basics</h1> <h1 id="markdown-basics">Markdown: Basics</h1>
<ul id="ProjectSubmenu"> <ul id="ProjectSubmenu">
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li> <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
@ -8,7 +8,7 @@
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li> <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
</ul> </ul>
<h2>Getting the Gist of Markdown's Formatting Syntax</h2> <h2 id="getting-the-gist-of-markdowns-formatting-syntax">Getting the Gist of Markdown's Formatting Syntax</h2>
<p>This page offers a brief overview of what it's like to use Markdown. <p>This page offers a brief overview of what it's like to use Markdown.
The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for
@ -24,7 +24,7 @@ and translate it to XHTML.</p>
<p><strong>Note:</strong> This document is itself written using Markdown; you <p><strong>Note:</strong> This document is itself written using Markdown; you
can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p> can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p>
<h2>Paragraphs, Headers, Blockquotes</h2> <h2 id="paragraphs-headers-blockquotes">Paragraphs, Headers, Blockquotes</h2>
<p>A paragraph is simply one or more consecutive lines of text, separated <p>A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like a by one or more blank lines. (A blank line is any line that looks like a
@ -88,7 +88,7 @@ dog's back.&lt;/p&gt;
&lt;/blockquote&gt; &lt;/blockquote&gt;
</code></pre> </code></pre>
<h3>Phrase Emphasis</h3> <h3 id="phrase-emphasis">Phrase Emphasis</h3>
<p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p> <p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p>
@ -110,7 +110,7 @@ Some of these words &lt;em&gt;are emphasized also&lt;/em&gt;.&lt;/p&gt;
Or, if you prefer, &lt;strong&gt;use two underscores instead&lt;/strong&gt;.&lt;/p&gt; Or, if you prefer, &lt;strong&gt;use two underscores instead&lt;/strong&gt;.&lt;/p&gt;
</code></pre> </code></pre>
<h2>Lists</h2> <h2 id="lists">Lists</h2>
<p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>, <p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>,
<code>+</code>, and <code>-</code>) as list markers. These three markers are <code>+</code>, and <code>-</code>) as list markers. These three markers are
@ -181,7 +181,7 @@ the paragraphs by 4 spaces or 1 tab:</p>
&lt;/ul&gt; &lt;/ul&gt;
</code></pre> </code></pre>
<h3>Links</h3> <h3 id="links">Links</h3>
<p>Markdown supports two styles for creating links: <em>inline</em> and <p>Markdown supports two styles for creating links: <em>inline</em> and
<em>reference</em>. With both styles, you use square brackets to delimit the <em>reference</em>. With both styles, you use square brackets to delimit the
@ -244,7 +244,7 @@ numbers and spaces, but are <em>not</em> case sensitive:</p>
&lt;a href="http://www.nytimes.com/"&gt;The New York Times&lt;/a&gt;.&lt;/p&gt; &lt;a href="http://www.nytimes.com/"&gt;The New York Times&lt;/a&gt;.&lt;/p&gt;
</code></pre> </code></pre>
<h3>Images</h3> <h3 id="images">Images</h3>
<p>Image syntax is very much like link syntax.</p> <p>Image syntax is very much like link syntax.</p>
@ -265,7 +265,7 @@ numbers and spaces, but are <em>not</em> case sensitive:</p>
<pre><code>&lt;img src="/path/to/img.jpg" alt="alt text" title="Title" /&gt; <pre><code>&lt;img src="/path/to/img.jpg" alt="alt text" title="Title" /&gt;
</code></pre> </code></pre>
<h3>Code</h3> <h3 id="code">Code</h3>
<p>In a regular paragraph, you can create code span by wrapping text in <p>In a regular paragraph, you can create code span by wrapping text in
backtick quotes. Any ampersands (<code>&amp;</code>) and angle brackets (<code>&lt;</code> or backtick quotes. Any ampersands (<code>&amp;</code>) and angle brackets (<code>&lt;</code> or

View File

@ -2,8 +2,9 @@ var marked = require('../../lib/marked.js');
describe('Test heading ID functionality', function() { describe('Test heading ID functionality', function() {
it('should add id attribute by default', function() { it('should add id attribute by default', function() {
var renderer = new marked.Renderer(marked.defaults); var renderer = new marked.Renderer();
var header = renderer.heading('test', 1, 'test'); var slugger = new marked.Slugger();
var header = renderer.heading('test', 1, 'test', slugger);
expect(header).toBe('<h1 id="test">test</h1>\n'); expect(header).toBe('<h1 id="test">test</h1>\n');
}); });
@ -14,6 +15,51 @@ describe('Test heading ID functionality', function() {
}); });
}); });
describe('Test slugger functionality', function() {
it('should use lowercase slug', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('Test')).toBe('test');
});
it('should be unique to avoid collisions 1280', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('test')).toBe('test');
expect(slugger.slug('test')).toBe('test-1');
expect(slugger.slug('test')).toBe('test-2');
});
it('should be unique when slug ends with number', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('test 1')).toBe('test-1');
expect(slugger.slug('test')).toBe('test');
expect(slugger.slug('test')).toBe('test-2');
});
it('should be unique when slug ends with hyphen number', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('foo')).toBe('foo');
expect(slugger.slug('foo')).toBe('foo-1');
expect(slugger.slug('foo 1')).toBe('foo-1-1');
expect(slugger.slug('foo-1')).toBe('foo-1-2');
expect(slugger.slug('foo')).toBe('foo-2');
});
it('should allow non-latin chars', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('привет')).toBe('привет');
});
it('should remove ampersands 857', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('This & That Section')).toBe('this--that-section');
});
it('should remove periods', function() {
var slugger = new marked.Slugger();
expect(slugger.slug('file.txt')).toBe('filetxt');
});
});
describe('Test paragraph token type', function () { describe('Test paragraph token type', function () {
it('should use the "paragraph" type on top level', function () { it('should use the "paragraph" type on top level', function () {
const md = 'A Paragraph.\n\n> A blockquote\n\n- list item\n'; const md = 'A Paragraph.\n\n> A blockquote\n\n- list item\n';