From 9e4d07926ae062e31ad92c51a152c5d6ef36e52f Mon Sep 17 00:00:00 2001 From: hoijui Date: Mon, 21 Oct 2019 08:26:42 +0200 Subject: [PATCH 01/10] slight doc comment touch-up/simplification [minor] Signed-off-by: hoijui --- public/js/extra.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/js/extra.js b/public/js/extra.js index 03f04664..b45c60ff 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -168,11 +168,11 @@ export function renderTags (view) { } function slugifyWithUTF8 (text) { - // remove html tags and trim spaces + // remove HTML tags and trim spaces let newText = stripTags(text.toString().trim()) - // replace all spaces in between to dashes + // replace space between words with dashes newText = newText.replace(/\s+/g, '-') - // slugify string to make it valid for attribute + // slugify string to make it valid as an attribute newText = newText.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '') return newText } From 34c2bfcfc5e936e88671e920d6f5ac51f9a753ab Mon Sep 17 00:00:00 2001 From: hoijui Date: Mon, 21 Oct 2019 22:10:06 +0200 Subject: [PATCH 02/10] Allow to generate lower case header references through the config (#1305) This makes the references consistent/compatible with GitHub, GitLab, Pandoc and many other tools. This behavior can be enabled in config.json with: ``` "linkifyHeaderStyle": "gfm" ``` Signed-off-by: hoijui --- config.json.example | 3 ++- lib/config/default.js | 3 ++- lib/web/statusRouter.js | 3 ++- public/js/extra.js | 6 +++++- public/js/lib/common/constant.ejs | 2 ++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/config.json.example b/config.json.example index 30bd3ced..c841d864 100644 --- a/config.json.example +++ b/config.json.example @@ -127,6 +127,7 @@ "plantuml": { "server": "https://www.plantuml.com/plantuml" - } + }, + "linkifyHeaderStyle": "gfm" } } diff --git a/lib/config/default.js b/lib/config/default.js index 93de2599..f3dafea9 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -162,5 +162,6 @@ module.exports = { allowEmailRegister: true, allowGravatar: true, allowPDFExport: true, - openID: false + openID: false, + linkifyHeaderStyle: 'keep-case' } diff --git a/lib/web/statusRouter.js b/lib/web/statusRouter.js index f8c1f6cf..f1de7dbf 100644 --- a/lib/web/statusRouter.js +++ b/lib/web/statusRouter.js @@ -99,7 +99,8 @@ statusRouter.get('/config', function (req, res) { version: config.fullversion, plantumlServer: config.plantuml.server, DROPBOX_APP_KEY: config.dropbox.appKey, - allowedUploadMimeTypes: config.allowedUploadMimeTypes + allowedUploadMimeTypes: config.allowedUploadMimeTypes, + linkifyHeaderStyle: config.linkifyHeaderStyle } res.set({ 'Cache-Control': 'private', // only cache by client diff --git a/public/js/extra.js b/public/js/extra.js index b45c60ff..50b75f8a 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -867,7 +867,11 @@ const linkifyAnchors = (level, containingElement) => { if (header.getElementsByClassName('anchor').length === 0) { if (typeof header.id === 'undefined' || header.id === '') { // to escape characters not allow in css and humanize - const id = slugifyWithUTF8(getHeaderContent(header)) + var 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 } if (!(typeof header.id === 'undefined' || header.id === '')) { diff --git a/public/js/lib/common/constant.ejs b/public/js/lib/common/constant.ejs index 7821329d..e812bb8e 100644 --- a/public/js/lib/common/constant.ejs +++ b/public/js/lib/common/constant.ejs @@ -6,4 +6,6 @@ window.plantumlServer = '<%- plantumlServer %>' window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %> +window.linkifyHeaderStyle = <%- JSON.stringify(linkifyHeaderStyle) %> + window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>' From 905414d039f753be0082b16f294c80b744df6463 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 22 Oct 2019 08:40:02 +0200 Subject: [PATCH 03/10] Make `id` block-scoped (thanks @Yukaii) Co-Authored-By: Yukai Huang Signed-off-by: hoijui --- public/js/extra.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/extra.js b/public/js/extra.js index 50b75f8a..7ac331cf 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -867,7 +867,7 @@ const linkifyAnchors = (level, containingElement) => { if (header.getElementsByClassName('anchor').length === 0) { if (typeof header.id === 'undefined' || header.id === '') { // to escape characters not allow in css and humanize - var id = slugifyWithUTF8(getHeaderContent(header)) + let id = slugifyWithUTF8(getHeaderContent(header)) // to make compatible with GitHub, GitLab, Pandoc and many more if (window.linkifyHeaderStyle !== 'keep-case') { id = id.toLowerCase() From c4906c0592178714745b64092c81e68a1f0202f6 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 22 Oct 2019 17:12:47 +0200 Subject: [PATCH 04/10] linkifyHeaderStyle needs no string-ification; is already str. Co-Authored-By: Yukai Huang Signed-off-by: hoijui --- public/js/lib/common/constant.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/lib/common/constant.ejs b/public/js/lib/common/constant.ejs index e812bb8e..057c92c8 100644 --- a/public/js/lib/common/constant.ejs +++ b/public/js/lib/common/constant.ejs @@ -6,6 +6,6 @@ window.plantumlServer = '<%- plantumlServer %>' window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %> -window.linkifyHeaderStyle = <%- JSON.stringify(linkifyHeaderStyle) %> +window.linkifyHeaderStyle = '<%- linkifyHeaderStyle %>' window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>' From 3d5d8e20f73bf8d363975273f8ade70d729aa126 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 29 Oct 2019 09:56:47 +0100 Subject: [PATCH 05/10] document `linkifyHeaderStyle` in default.js Signed-off-by: hoijui --- lib/config/default.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/config/default.js b/lib/config/default.js index f3dafea9..e3aee6ca 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -163,5 +163,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' } From 9742f7d075c2a8bab5169539dc041de18f88a891 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 29 Oct 2019 09:58:59 +0100 Subject: [PATCH 06/10] fix gfm header link generation with respect to `deduplicatedHeaderId` Signed-off-by: hoijui --- public/js/extra.js | 88 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/public/js/extra.js b/public/js/extra.js index 7ac331cf..0b521ccd 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -859,6 +859,37 @@ 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 id_base = slug.toLowerCase() + id = id_base + if (headerIds !== null) { + // ... making sure the id is unique + let i = 1 + while (headerIds.has(id)) { + id = id_base + '-' + 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}`) @@ -866,13 +897,7 @@ const linkifyAnchors = (level, containingElement) => { const 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) @@ -898,20 +923,45 @@ 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 + let 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) + } } } } From c202cca39b1098463b27bd9a57c159334fb4c7f9 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 29 Oct 2019 09:59:42 +0100 Subject: [PATCH 07/10] also use gfm header generation in test and debug configuraitons Signed-off-by: hoijui --- config.json.example | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config.json.example b/config.json.example index c841d864..11422652 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", From 7c5ac3603af7b66fe3d754b8d9025498a26ba2a3 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 29 Oct 2019 10:53:20 +0100 Subject: [PATCH 08/10] allow to define header link generation style via environment var Signed-off-by: hoijui --- lib/config/environment.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/config/environment.js b/lib/config/environment.js index de573e18..905f0e0d 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -135,5 +135,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 } From aaf9466498cecfd2b75a5ece688edff35669c9d5 Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 29 Oct 2019 11:57:51 +0100 Subject: [PATCH 09/10] make `headerIds` `const` [fix] Signed-off-by: hoijui --- public/js/extra.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/extra.js b/public/js/extra.js index 0b521ccd..3d3f7f64 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -943,7 +943,7 @@ export function deduplicatedHeaderId (view) { // all headers contained in the document, in order of appearance const allHeaders = view.find(`:header`).toArray() // list of finaly assigned header IDs - let headerIds = new Set() + const headerIds = new Set() for (let j = 0; j < allHeaders.length; j++) { const $header = $(allHeaders[j]) const id = $header.attr('id') From 558fa5fcf46b0a94c49c503c2e1373c86829ff1e Mon Sep 17 00:00:00 2001 From: hoijui Date: Tue, 29 Oct 2019 13:20:18 +0100 Subject: [PATCH 10/10] make standard conform [fix] Signed-off-by: hoijui --- public/js/extra.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/public/js/extra.js b/public/js/extra.js index 3d3f7f64..1282b801 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -860,7 +860,6 @@ const anchorForId = id => { } const createHeaderId = (headerContent, headerIds = null) => { - // to escape characters not allow in css and humanize const slug = slugifyWithUTF8(headerContent) let id @@ -873,13 +872,13 @@ const createHeaderId = (headerContent, headerIds = null) => { // see GitHub implementation reference: // https://gist.github.com/asabaylus/3071099#gistcomment-1593627 // it works like 'lower-case', but ... - const id_base = slug.toLowerCase() - id = id_base + const idBase = slug.toLowerCase() + id = idBase if (headerIds !== null) { // ... making sure the id is unique let i = 1 while (headerIds.has(id)) { - id = id_base + '-' + i + id = idBase + '-' + i i++ } headerIds.add(id) @@ -924,7 +923,6 @@ function getHeaderContent (header) { } function changeHeaderId ($header, id, newId) { - $header.attr('id', newId) const $headerLink = $header.find(`> a.anchor[href="#${id}"]`) $headerLink.attr('href', `#${newId}`) @@ -932,11 +930,10 @@ function changeHeaderId ($header, id, newId) { } export function deduplicatedHeaderId (view) { - // headers contained in the last change const headers = view.find(':header.raw').removeClass('raw').toArray() - if (headers.length == 0) { - return; + if (headers.length === 0) { + return } if (window.linkifyHeaderStyle === 'gfm') { // consistent with GitHub, GitLab, Pandoc & co.