From 08d9632bdf1f26b782adb52155894519b708f729 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 20 Dec 2018 16:07:10 -0500 Subject: [PATCH 1/3] Fix duplicate heading id --- lib/marked.js | 37 +++++++++++++++++-- test/new/cm_blockquotes.html | 8 ++-- test/new/cm_blockquotes.md | 8 ++-- test/new/toplevel_paragraphs.html | 2 +- test/new/toplevel_paragraphs.md | 2 +- .../markdown_documentation_basics.html | 16 ++++---- test/unit/marked-spec.js | 26 ++++++++++--- 7 files changed, 72 insertions(+), 27 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 462229d233..e34ac17151 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -953,13 +953,13 @@ Renderer.prototype.html = function(html) { return html; }; -Renderer.prototype.heading = function(text, level, raw) { +Renderer.prototype.heading = function(text, level, raw, slugger) { if (this.options.headerIds) { return '' + text + '?@[\]^`{|}~]/g, '') + .replace(/\s/g, '-'); + + var count = this.seen.hasOwnProperty(slug) ? this.seen[slug] + 1 : 0; + this.seen[slug] = count; + + if (count > 0) { + slug = slug + '-' + count; + } + + return slug; +}; + /** * Helpers */ diff --git a/test/new/cm_blockquotes.html b/test/new/cm_blockquotes.html index f63e5e0067..b4d51b1f6b 100644 --- a/test/new/cm_blockquotes.html +++ b/test/new/cm_blockquotes.html @@ -11,7 +11,7 @@

Example 192

The spaces after the > characters can be omitted:

-

Foo

+

Bar

bar baz

@@ -21,7 +21,7 @@

Example 193

The > characters can be indented 1-3 spaces:

-

Foo

+

Baz

bar baz

@@ -30,7 +30,7 @@

Example 194

Four spaces gives us a code block:

-
> # Foo
+
> # Qux
 > bar
 > baz
@@ -39,7 +39,7 @@

Example 195

The Laziness clause allows us to omit the > before paragraph continuation text:

-

Foo

+

Quux

bar baz

diff --git a/test/new/cm_blockquotes.md b/test/new/cm_blockquotes.md index 95a317de44..6a80a6f32d 100644 --- a/test/new/cm_blockquotes.md +++ b/test/new/cm_blockquotes.md @@ -8,7 +8,7 @@ The spaces after the `>` characters can be omitted: -># Foo +># Bar >bar > baz @@ -16,7 +16,7 @@ The spaces after the `>` characters can be omitted: The `>` characters can be indented 1-3 spaces: - > # Foo + > # Baz > bar > baz @@ -24,7 +24,7 @@ The `>` characters can be indented 1-3 spaces: Four spaces gives us a code block: - > # Foo + > # Qux > bar > baz @@ -32,7 +32,7 @@ Four spaces gives us a code block: The Laziness clause allows us to omit the `>` before paragraph continuation text: -> # Foo +> # Quux > bar baz diff --git a/test/new/toplevel_paragraphs.html b/test/new/toplevel_paragraphs.html index d15bfccef0..7fd509470e 100644 --- a/test/new/toplevel_paragraphs.html +++ b/test/new/toplevel_paragraphs.html @@ -12,7 +12,7 @@

how are you

paragraph before head with equals

-

how are you

+

how are you again

paragraph before blockquote

text for blockquote

diff --git a/test/new/toplevel_paragraphs.md b/test/new/toplevel_paragraphs.md index de29be7557..f344fbe349 100644 --- a/test/new/toplevel_paragraphs.md +++ b/test/new/toplevel_paragraphs.md @@ -17,7 +17,7 @@ paragraph before head with hash # how are you paragraph before head with equals -how are you +how are you again =========== paragraph before blockquote diff --git a/test/original/markdown_documentation_basics.html b/test/original/markdown_documentation_basics.html index d5bdbb29a5..dede9d748a 100644 --- a/test/original/markdown_documentation_basics.html +++ b/test/original/markdown_documentation_basics.html @@ -1,4 +1,4 @@ -

Markdown: Basics

+

Markdown: Basics

-

Getting the Gist of Markdown's Formatting Syntax

+

Getting the Gist of Markdown's Formatting Syntax

This page offers a brief overview of what it's like to use Markdown. The syntax page provides complete, detailed documentation for @@ -24,7 +24,7 @@

Getting the Gist of Markdown's Formatting Syntax

Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.

-

Paragraphs, Headers, Blockquotes

+

Paragraphs, Headers, Blockquotes

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 @@ -88,7 +88,7 @@

Paragraphs, Headers, Blockquotes

</blockquote>
-

Phrase Emphasis

+

Phrase Emphasis

Markdown uses asterisks and underscores to indicate spans of emphasis.

@@ -110,7 +110,7 @@

Phrase Emphasis

Or, if you prefer, <strong>use two underscores instead</strong>.</p> -

Lists

+

Lists

Unordered (bulleted) lists use asterisks, pluses, and hyphens (*, +, and -) as list markers. These three markers are @@ -181,7 +181,7 @@

Lists

</ul> -

Links

+

Markdown supports two styles for creating links: inline and reference. With both styles, you use square brackets to delimit the @@ -244,7 +244,7 @@

Links

<a href="http://www.nytimes.com/">The New York Times</a>.</p> -

Images

+

Images

Image syntax is very much like link syntax.

@@ -265,7 +265,7 @@

Images

<img src="/path/to/img.jpg" alt="alt text" title="Title" />
 
-

Code

+

Code

In a regular paragraph, you can create code span by wrapping text in backtick quotes. Any ampersands (&) and angle brackets (< or diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js index 97789afe09..d476ceb57e 100644 --- a/test/unit/marked-spec.js +++ b/test/unit/marked-spec.js @@ -2,15 +2,29 @@ var marked = require('../../lib/marked.js'); describe('Test heading ID functionality', function() { it('should add id attribute by default', function() { - var renderer = new marked.Renderer(marked.defaults); - var header = renderer.heading('test', 1, 'test'); - expect(header).toBe('

test

\n'); + var html = marked('# test'); + expect(html).toBe('

test

\n'); + }); + + it('should add unique id for repeating heading 1280', function() { + var html = marked('# test\n# test\n# test'); + expect(html).toBe('

test

\n

test

\n

test

\n'); + }); + + it('should add id with non-latin chars', function() { + var html = marked('# привет'); + expect(html).toBe('

привет

\n'); + }); + + it('should add id without ampersands 857', function() { + var html = marked('# This & That Section'); + expect(html).toBe('

This & That Section

\n'); }); it('should NOT add id attribute when options set false', function() { - var renderer = new marked.Renderer({ headerIds: false }); - var header = renderer.heading('test', 1, 'test'); - expect(header).toBe('

test

\n'); + var options = { headerIds: false }; + var html = marked('# test', options); + expect(html).toBe('

test

\n'); }); }); From 5c926e227b9fc26934cde530902905667d710781 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 21 Dec 2018 15:23:33 -0800 Subject: [PATCH 2/3] Fix slugger & unit tests --- lib/marked.js | 28 +++++++++++--------- test/unit/marked-spec.js | 55 +++++++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index e34ac17151..6f7ce06720 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -1289,29 +1289,31 @@ Parser.prototype.tok = function() { * Slugger generates header id */ -function Slugger () { - this.seen = {}; -} +function Slugger () { + this.seen = {}; +} /** * Convert string to unique id */ -Slugger.prototype.slug = function (value) { - var slug = value +Slugger.prototype.slug = function (value) { + var slug = value .toLowerCase() .trim() .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '') .replace(/\s/g, '-'); - - var count = this.seen.hasOwnProperty(slug) ? this.seen[slug] + 1 : 0; - this.seen[slug] = count; - if (count > 0) { - slug = slug + '-' + count; + if (this.seen.hasOwnProperty(slug)) { + var originalSlug = slug; + do { + this.seen[originalSlug]++; + slug = originalSlug + '-' + this.seen[originalSlug]; + } while (this.seen.hasOwnProperty(slug)); } - - return slug; + this.seen[slug] = 0; + + return slug; }; /** @@ -1648,6 +1650,8 @@ marked.lexer = Lexer.lex; marked.InlineLexer = InlineLexer; marked.inlineLexer = InlineLexer.output; +marked.Slugger = Slugger; + marked.parse = marked; if (typeof module !== 'undefined' && typeof exports === 'object') { diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js index d476ceb57e..4f71a5ae06 100644 --- a/test/unit/marked-spec.js +++ b/test/unit/marked-spec.js @@ -2,29 +2,54 @@ var marked = require('../../lib/marked.js'); describe('Test heading ID functionality', function() { it('should add id attribute by default', function() { - var html = marked('# test'); - expect(html).toBe('

test

\n'); + var renderer = new marked.Renderer(); + var slugger = new marked.Slugger(); + var header = renderer.heading('test', 1, 'test', slugger); + expect(header).toBe('

test

\n'); }); - it('should add unique id for repeating heading 1280', function() { - var html = marked('# test\n# test\n# test'); - expect(html).toBe('

test

\n

test

\n

test

\n'); + it('should NOT add id attribute when options set false', function() { + var renderer = new marked.Renderer({ headerIds: false }); + var header = renderer.heading('test', 1, 'test'); + expect(header).toBe('

test

\n'); }); +}); - it('should add id with non-latin chars', function() { - var html = marked('# привет'); - expect(html).toBe('

привет

\n'); +describe('Test slugger functionality', function() { + it('should use lowercase slug', function() { + var slugger = new marked.Slugger(); + expect(slugger.slug('Test')).toBe('test'); }); - it('should add id without ampersands 857', function() { - var html = marked('# This & That Section'); - expect(html).toBe('

This & That Section

\n'); + 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 NOT add id attribute when options set false', function() { - var options = { headerIds: false }; - var html = marked('# test', options); - expect(html).toBe('

test

\n'); + it('should be unique to avoid collisions 1401', 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'); }); }); From 632ac5d55d047e460c6c6ac70d81b8dc7920bc76 Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 21 Dec 2018 15:51:05 -0800 Subject: [PATCH 3/3] Fix unit tests for slugger --- test/unit/marked-spec.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js index 4f71a5ae06..c365e8599c 100644 --- a/test/unit/marked-spec.js +++ b/test/unit/marked-spec.js @@ -28,7 +28,14 @@ describe('Test slugger functionality', function() { expect(slugger.slug('test')).toBe('test-2'); }); - it('should be unique to avoid collisions 1401', function() { + 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');