summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.json.example6
-rw-r--r--lib/config/default.js13
-rw-r--r--lib/config/environment.js3
-rw-r--r--public/js/extra.js85
-rw-r--r--public/js/lib/common/constant.ejs2
5 files changed, 86 insertions, 23 deletions
diff --git a/config.json.example b/config.json.example
index e3bd54b4..0366c3b2 100644
--- a/config.json.example
+++ b/config.json.example
@@ -3,7 +3,8 @@
"db": {
"dialect": "sqlite",
"storage": ":memory:"
- }
+ },
+ "linkifyHeaderStyle": "gfm"
},
"development": {
"loglevel": "debug",
@@ -13,7 +14,8 @@
"db": {
"dialect": "sqlite",
"storage": "./db.codimd.sqlite"
- }
+ },
+ "linkifyHeaderStyle": "gfm"
},
"production": {
"domain": "localhost",
diff --git a/lib/config/default.js b/lib/config/default.js
index f5cceac6..a52a8a4f 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -158,5 +158,18 @@ module.exports = {
allowGravatar: true,
allowPDFExport: true,
openID: false,
+ // linkifyHeaderStyle - How is a header text converted into a link id.
+ // Header Example: "3.1. Good Morning my Friend! - Do you have 5$?"
+ // * 'keep-case' is the legacy CodiMD value.
+ // Generated id: "31-Good-Morning-my-Friend---Do-you-have-5"
+ // * 'lower-case' is the same like legacy (see above), but converted to lower-case.
+ // Generated id: "#31-good-morning-my-friend---do-you-have-5"
+ // * 'gfm' _GitHub-Flavored Markdown_ style as described here:
+ // https://gist.github.com/asabaylus/3071099#gistcomment-1593627
+ // It works like 'lower-case', but making sure the ID is unique.
+ // This is What GitHub, GitLab and (hopefully) most other tools use.
+ // Generated id: "31-good-morning-my-friend---do-you-have-5"
+ // 2nd appearance: "31-good-morning-my-friend---do-you-have-5-1"
+ // 3rd appearance: "31-good-morning-my-friend---do-you-have-5-2"
linkifyHeaderStyle: 'keep-case'
}
diff --git a/lib/config/environment.js b/lib/config/environment.js
index 716f8b75..944a2fb2 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -134,5 +134,6 @@ module.exports = {
allowEmailRegister: toBooleanConfig(process.env.CMD_ALLOW_EMAIL_REGISTER),
allowGravatar: toBooleanConfig(process.env.CMD_ALLOW_GRAVATAR),
allowPDFExport: toBooleanConfig(process.env.CMD_ALLOW_PDF_EXPORT),
- openID: toBooleanConfig(process.env.CMD_OPENID)
+ openID: toBooleanConfig(process.env.CMD_OPENID),
+ linkifyHeaderStyle: process.env.CMD_LINKIFY_HEADER_STYLE
}
diff --git a/public/js/extra.js b/public/js/extra.js
index d381576f..6cda6171 100644
--- a/public/js/extra.js
+++ b/public/js/extra.js
@@ -825,6 +825,36 @@ const anchorForId = id => {
return anchor
}
+const createHeaderId = (headerContent, headerIds = null) => {
+ // to escape characters not allow in css and humanize
+ const slug = slugifyWithUTF8(headerContent)
+ let id
+ if (window.linkifyHeaderStyle === 'keep-case') {
+ id = slug
+ } else if (window.linkifyHeaderStyle === 'lower-case') {
+ // to make compatible with GitHub, GitLab, Pandoc and many more
+ id = slug.toLowerCase()
+ } else if (window.linkifyHeaderStyle === 'gfm') {
+ // see GitHub implementation reference:
+ // https://gist.github.com/asabaylus/3071099#gistcomment-1593627
+ // it works like 'lower-case', but ...
+ const idBase = slug.toLowerCase()
+ id = idBase
+ if (headerIds !== null) {
+ // ... making sure the id is unique
+ let i = 1
+ while (headerIds.has(id)) {
+ id = idBase + '-' + i
+ i++
+ }
+ headerIds.add(id)
+ }
+ } else {
+ throw new Error('Unknown linkifyHeaderStyle value "' + window.linkifyHeaderStyle + '"')
+ }
+ return id
+}
+
const linkifyAnchors = (level, containingElement) => {
const headers = containingElement.getElementsByTagName(`h${level}`)
@@ -832,13 +862,7 @@ const linkifyAnchors = (level, containingElement) => {
let header = headers[i]
if (header.getElementsByClassName('anchor').length === 0) {
if (typeof header.id === 'undefined' || header.id === '') {
- // to escape characters not allow in css and humanize
- let id = slugifyWithUTF8(getHeaderContent(header))
- // to make compatible with GitHub, GitLab, Pandoc and many more
- if (window.linkifyHeaderStyle !== 'keep-case') {
- id = id.toLowerCase()
- }
- header.id = id
+ header.id = createHeaderId(getHeaderContent(header))
}
if (!(typeof header.id === 'undefined' || header.id === '')) {
header.insertBefore(anchorForId(header.id), header.firstChild)
@@ -864,20 +888,43 @@ function getHeaderContent (header) {
return headerHTML[0].innerHTML
}
+function changeHeaderId ($header, id, newId) {
+ $header.attr('id', newId)
+ const $headerLink = $header.find(`> a.anchor[href="#${id}"]`)
+ $headerLink.attr('href', `#${newId}`)
+ $headerLink.attr('title', newId)
+}
+
export function deduplicatedHeaderId (view) {
+ // headers contained in the last change
const headers = view.find(':header.raw').removeClass('raw').toArray()
- for (let i = 0; i < headers.length; i++) {
- const id = $(headers[i]).attr('id')
- if (!id) continue
- const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray()
- for (let j = 0; j < duplicatedHeaders.length; j++) {
- if (duplicatedHeaders[j] !== headers[i]) {
- const newId = id + j
- const $duplicatedHeader = $(duplicatedHeaders[j])
- $duplicatedHeader.attr('id', newId)
- const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`)
- $headerLink.attr('href', `#${newId}`)
- $headerLink.attr('title', newId)
+ if (headers.length === 0) {
+ return
+ }
+ if (window.linkifyHeaderStyle === 'gfm') {
+ // consistent with GitHub, GitLab, Pandoc & co.
+ // all headers contained in the document, in order of appearance
+ const allHeaders = view.find(`:header`).toArray()
+ // list of finaly assigned header IDs
+ const headerIds = new Set()
+ for (let j = 0; j < allHeaders.length; j++) {
+ const $header = $(allHeaders[j])
+ const id = $header.attr('id')
+ const newId = createHeaderId(getHeaderContent($header), headerIds)
+ changeHeaderId($header, id, newId)
+ }
+ } else {
+ // the legacy way
+ for (let i = 0; i < headers.length; i++) {
+ const id = $(headers[i]).attr('id')
+ if (!id) continue
+ const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray()
+ for (let j = 0; j < duplicatedHeaders.length; j++) {
+ if (duplicatedHeaders[j] !== headers[i]) {
+ const newId = id + j
+ const $header = $(duplicatedHeaders[j])
+ changeHeaderId($header, id, newId)
+ }
}
}
}
diff --git a/public/js/lib/common/constant.ejs b/public/js/lib/common/constant.ejs
index bbcb8c7a..114a9077 100644
--- a/public/js/lib/common/constant.ejs
+++ b/public/js/lib/common/constant.ejs
@@ -5,6 +5,6 @@ window.version = '<%- version %>'
window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %>
-window.linkifyHeaderStyle = <%- JSON.stringify(linkifyHeaderStyle) %>
+window.linkifyHeaderStyle = '<%- linkifyHeaderStyle %>'
window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>'