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'); }); });