Skip to content

Commit 65ecb6d

Browse files
authored
Allow to generate lower case header references through the conf… (#1310)
Allow to generate lower case header references through the config
2 parents ee8bd9d + 558fa5f commit 65ecb6d

File tree

6 files changed

+96
-24
lines changed

6 files changed

+96
-24
lines changed

config.json.example

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"db": {
44
"dialect": "sqlite",
55
"storage": ":memory:"
6-
}
6+
},
7+
"linkifyHeaderStyle": "gfm"
78
},
89
"development": {
910
"loglevel": "debug",
@@ -13,7 +14,8 @@
1314
"db": {
1415
"dialect": "sqlite",
1516
"storage": "./db.codimd.sqlite"
16-
}
17+
},
18+
"linkifyHeaderStyle": "gfm"
1719
},
1820
"production": {
1921
"domain": "localhost",
@@ -127,6 +129,7 @@
127129
"plantuml":
128130
{
129131
"server": "https://www.plantuml.com/plantuml"
130-
}
132+
},
133+
"linkifyHeaderStyle": "gfm"
131134
}
132135
}

lib/config/default.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,19 @@ module.exports = {
162162
allowEmailRegister: true,
163163
allowGravatar: true,
164164
allowPDFExport: true,
165-
openID: false
165+
openID: false,
166+
// linkifyHeaderStyle - How is a header text converted into a link id.
167+
// Header Example: "3.1. Good Morning my Friend! - Do you have 5$?"
168+
// * 'keep-case' is the legacy CodiMD value.
169+
// Generated id: "31-Good-Morning-my-Friend---Do-you-have-5"
170+
// * 'lower-case' is the same like legacy (see above), but converted to lower-case.
171+
// Generated id: "#31-good-morning-my-friend---do-you-have-5"
172+
// * 'gfm' _GitHub-Flavored Markdown_ style as described here:
173+
// https://gist.github.com/asabaylus/3071099#gistcomment-1593627
174+
// It works like 'lower-case', but making sure the ID is unique.
175+
// This is What GitHub, GitLab and (hopefully) most other tools use.
176+
// Generated id: "31-good-morning-my-friend---do-you-have-5"
177+
// 2nd appearance: "31-good-morning-my-friend---do-you-have-5-1"
178+
// 3rd appearance: "31-good-morning-my-friend---do-you-have-5-2"
179+
linkifyHeaderStyle: 'keep-case'
166180
}

lib/config/environment.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,6 @@ module.exports = {
135135
allowEmailRegister: toBooleanConfig(process.env.CMD_ALLOW_EMAIL_REGISTER),
136136
allowGravatar: toBooleanConfig(process.env.CMD_ALLOW_GRAVATAR),
137137
allowPDFExport: toBooleanConfig(process.env.CMD_ALLOW_PDF_EXPORT),
138-
openID: toBooleanConfig(process.env.CMD_OPENID)
138+
openID: toBooleanConfig(process.env.CMD_OPENID),
139+
linkifyHeaderStyle: process.env.CMD_LINKIFY_HEADER_STYLE
139140
}

lib/web/statusRouter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ statusRouter.get('/config', function (req, res) {
9999
version: config.fullversion,
100100
plantumlServer: config.plantuml.server,
101101
DROPBOX_APP_KEY: config.dropbox.appKey,
102-
allowedUploadMimeTypes: config.allowedUploadMimeTypes
102+
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
103+
linkifyHeaderStyle: config.linkifyHeaderStyle
103104
}
104105
res.set({
105106
'Cache-Control': 'private', // only cache by client

public/js/extra.js

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,11 @@ export function renderTags (view) {
168168
}
169169

170170
function slugifyWithUTF8 (text) {
171-
// remove html tags and trim spaces
171+
// remove HTML tags and trim spaces
172172
let newText = stripTags(text.toString().trim())
173-
// replace all spaces in between to dashes
173+
// replace space between words with dashes
174174
newText = newText.replace(/\s+/g, '-')
175-
// slugify string to make it valid for attribute
175+
// slugify string to make it valid as an attribute
176176
newText = newText.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '')
177177
return newText
178178
}
@@ -859,16 +859,44 @@ const anchorForId = id => {
859859
return anchor
860860
}
861861

862+
const createHeaderId = (headerContent, headerIds = null) => {
863+
// to escape characters not allow in css and humanize
864+
const slug = slugifyWithUTF8(headerContent)
865+
let id
866+
if (window.linkifyHeaderStyle === 'keep-case') {
867+
id = slug
868+
} else if (window.linkifyHeaderStyle === 'lower-case') {
869+
// to make compatible with GitHub, GitLab, Pandoc and many more
870+
id = slug.toLowerCase()
871+
} else if (window.linkifyHeaderStyle === 'gfm') {
872+
// see GitHub implementation reference:
873+
// https://gist.github.com/asabaylus/3071099#gistcomment-1593627
874+
// it works like 'lower-case', but ...
875+
const idBase = slug.toLowerCase()
876+
id = idBase
877+
if (headerIds !== null) {
878+
// ... making sure the id is unique
879+
let i = 1
880+
while (headerIds.has(id)) {
881+
id = idBase + '-' + i
882+
i++
883+
}
884+
headerIds.add(id)
885+
}
886+
} else {
887+
throw new Error('Unknown linkifyHeaderStyle value "' + window.linkifyHeaderStyle + '"')
888+
}
889+
return id
890+
}
891+
862892
const linkifyAnchors = (level, containingElement) => {
863893
const headers = containingElement.getElementsByTagName(`h${level}`)
864894

865895
for (let i = 0, l = headers.length; i < l; i++) {
866896
const header = headers[i]
867897
if (header.getElementsByClassName('anchor').length === 0) {
868898
if (typeof header.id === 'undefined' || header.id === '') {
869-
// to escape characters not allow in css and humanize
870-
const id = slugifyWithUTF8(getHeaderContent(header))
871-
header.id = id
899+
header.id = createHeaderId(getHeaderContent(header))
872900
}
873901
if (!(typeof header.id === 'undefined' || header.id === '')) {
874902
header.insertBefore(anchorForId(header.id), header.firstChild)
@@ -894,20 +922,43 @@ function getHeaderContent (header) {
894922
return headerHTML[0].innerHTML
895923
}
896924

925+
function changeHeaderId ($header, id, newId) {
926+
$header.attr('id', newId)
927+
const $headerLink = $header.find(`> a.anchor[href="#${id}"]`)
928+
$headerLink.attr('href', `#${newId}`)
929+
$headerLink.attr('title', newId)
930+
}
931+
897932
export function deduplicatedHeaderId (view) {
933+
// headers contained in the last change
898934
const headers = view.find(':header.raw').removeClass('raw').toArray()
899-
for (let i = 0; i < headers.length; i++) {
900-
const id = $(headers[i]).attr('id')
901-
if (!id) continue
902-
const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray()
903-
for (let j = 0; j < duplicatedHeaders.length; j++) {
904-
if (duplicatedHeaders[j] !== headers[i]) {
905-
const newId = id + j
906-
const $duplicatedHeader = $(duplicatedHeaders[j])
907-
$duplicatedHeader.attr('id', newId)
908-
const $headerLink = $duplicatedHeader.find(`> a.anchor[href="#${id}"]`)
909-
$headerLink.attr('href', `#${newId}`)
910-
$headerLink.attr('title', newId)
935+
if (headers.length === 0) {
936+
return
937+
}
938+
if (window.linkifyHeaderStyle === 'gfm') {
939+
// consistent with GitHub, GitLab, Pandoc & co.
940+
// all headers contained in the document, in order of appearance
941+
const allHeaders = view.find(`:header`).toArray()
942+
// list of finaly assigned header IDs
943+
const headerIds = new Set()
944+
for (let j = 0; j < allHeaders.length; j++) {
945+
const $header = $(allHeaders[j])
946+
const id = $header.attr('id')
947+
const newId = createHeaderId(getHeaderContent($header), headerIds)
948+
changeHeaderId($header, id, newId)
949+
}
950+
} else {
951+
// the legacy way
952+
for (let i = 0; i < headers.length; i++) {
953+
const id = $(headers[i]).attr('id')
954+
if (!id) continue
955+
const duplicatedHeaders = view.find(`:header[id="${id}"]`).toArray()
956+
for (let j = 0; j < duplicatedHeaders.length; j++) {
957+
if (duplicatedHeaders[j] !== headers[i]) {
958+
const newId = id + j
959+
const $header = $(duplicatedHeaders[j])
960+
changeHeaderId($header, id, newId)
961+
}
911962
}
912963
}
913964
}

public/js/lib/common/constant.ejs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ window.plantumlServer = '<%- plantumlServer %>'
66

77
window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %>
88

9+
window.linkifyHeaderStyle = '<%- linkifyHeaderStyle %>'
10+
911
window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>'

0 commit comments

Comments
 (0)