Browse Source

feat(uniqueHeaderId): add unique id prefix and suffix to headers

If two headers have similar texts, the generated id could be equal. In order to prevent id clash:
  - A unique suffix is added if a header id already exists
  - Option to add a prefix to header id
  - Update of correspondent tests
  - (credits to nicovalencia)

Closes #81, closes #82
Estevão Soares dos Santos 10 years ago
parent
commit
c367a4b9a1

+ 34 - 5
dist/showdown.js

@@ -1,4 +1,4 @@
-;/*! showdown 16-01-2015 */
+;/*! showdown 18-01-2015 */
 (function(){
  'use strict';
 /**
@@ -9,7 +9,8 @@
 var showdown = {},
     parsers = {},
     globalOptions = {
-        omitExtraWLInCodeBlocks: false
+        omitExtraWLInCodeBlocks: false,
+        prefixHeaderId: false
     };
 
 ///////////////////////////////////////////////////////////////////////////
@@ -32,10 +33,17 @@ showdown.extensions = {};
 //Public methods
 showdown.setOption = function (key, value) {
     globalOptions[key] = value;
-
     return this;
 };
 
+showdown.getOption = function (key) {
+    return globalOptions[key];
+};
+
+showdown.getOptions = function () {
+    return globalOptions;
+};
+
 /**
  * Static Method
  *
@@ -100,7 +108,8 @@ showdown.Converter = function (converterOptions) {
             gHtmlBlocks: [],
             gUrls: {},
             gTitles: {},
-            gListLevel: 0
+            gListLevel: 0,
+            hashLinkCounts: {}
         };
 
         // attacklab: Replace ~ with ~T
@@ -1028,6 +1037,8 @@ showdown.subParser('hashHTMLBlocks', function (text, options, globals) {
 showdown.subParser('headers', function (text, options, globals) {
     'use strict';
 
+    var prefixHeader = options.prefixHeaderId;
+
     // Set text-style headers:
     //	Header 1
     //	========
@@ -1075,7 +1086,25 @@ showdown.subParser('headers', function (text, options, globals) {
         });
 
     function headerId(m) {
-        return m.replace(/[^\w]/g, '').toLowerCase();
+        var title,
+            escapedId = m.replace(/[^\w]/g, '').toLowerCase();
+
+        if (globals.hashLinkCounts[escapedId]) {
+            title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++);
+        } else {
+            title = escapedId;
+            globals.hashLinkCounts[escapedId] = 1;
+        }
+
+        // Prefix id to prevent causing inadvertent pre-existing style matches.
+        if (prefixHeader === true) {
+            prefixHeader = 'section';
+        }
+
+        if (showdown.helper.isString(prefixHeader)) {
+            return prefixHeader + title;
+        }
+        return title;
     }
 
     return text;

File diff suppressed because it is too large
+ 0 - 0
dist/showdown.js.map


File diff suppressed because it is too large
+ 1 - 1
dist/showdown.min.js


File diff suppressed because it is too large
+ 0 - 0
dist/showdown.min.js.map


+ 12 - 3
src/showdown.js

@@ -6,7 +6,8 @@
 var showdown = {},
     parsers = {},
     globalOptions = {
-        omitExtraWLInCodeBlocks: false
+        omitExtraWLInCodeBlocks: false,
+        prefixHeaderId: false
     };
 
 ///////////////////////////////////////////////////////////////////////////
@@ -29,10 +30,17 @@ showdown.extensions = {};
 //Public methods
 showdown.setOption = function (key, value) {
     globalOptions[key] = value;
-
     return this;
 };
 
+showdown.getOption = function (key) {
+    return globalOptions[key];
+};
+
+showdown.getOptions = function () {
+    return globalOptions;
+};
+
 /**
  * Static Method
  *
@@ -97,7 +105,8 @@ showdown.Converter = function (converterOptions) {
             gHtmlBlocks: [],
             gUrls: {},
             gTitles: {},
-            gListLevel: 0
+            gListLevel: 0,
+            hashLinkCounts: {}
         };
 
         // attacklab: Replace ~ with ~T

+ 21 - 1
src/subParsers/headers.js

@@ -5,6 +5,8 @@
 showdown.subParser('headers', function (text, options, globals) {
     'use strict';
 
+    var prefixHeader = options.prefixHeaderId;
+
     // Set text-style headers:
     //	Header 1
     //	========
@@ -52,7 +54,25 @@ showdown.subParser('headers', function (text, options, globals) {
         });
 
     function headerId(m) {
-        return m.replace(/[^\w]/g, '').toLowerCase();
+        var title,
+            escapedId = m.replace(/[^\w]/g, '').toLowerCase();
+
+        if (globals.hashLinkCounts[escapedId]) {
+            title = escapedId + '-' + (globals.hashLinkCounts[escapedId]++);
+        } else {
+            title = escapedId;
+            globals.hashLinkCounts[escapedId] = 1;
+        }
+
+        // Prefix id to prevent causing inadvertent pre-existing style matches.
+        if (prefixHeader === true) {
+            prefixHeader = 'section';
+        }
+
+        if (showdown.helper.isString(prefixHeader)) {
+            return prefixHeader + title;
+        }
+        return title;
     }
 
     return text;

+ 5 - 0
test/cases/repeated-headers.html

@@ -0,0 +1,5 @@
+<h1 id="sametitle">Same Title</h1>
+
+<p>some text</p>
+
+<h1 id="sametitle-1">Same Title</h1>

+ 5 - 0
test/cases/repeated-headers.md

@@ -0,0 +1,5 @@
+# Same Title
+
+some text
+
+# Same Title

Some files were not shown because too many files changed in this diff