Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 724ac02

Browse files
authoredFeb 8, 2020
feat: added capability to add css class and id to images + links + refactoring (docsifyjs#820)
* method extraction * support for CSS class and id * Update package.json * Added tests for refactored render-methods * minor refactoring
1 parent 6184e50 commit 724ac02

File tree

14 files changed

+369
-133
lines changed

14 files changed

+369
-133
lines changed
 

‎cypress/integration/sidebar/config.spec.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ context('sidebar.configurations', () => {
239239
'set-target-attribute-for-link',
240240
'disable-link',
241241
'github-task-lists',
242-
'image-resizing',
243242
'customise-id-for-headings',
244243
'markdown-in-html-tag'
245244
]

‎docs/helpers.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ You will get `<a href="/demo/">link</a>`html. Do not worry, you can still set ti
8181
- [ ] bim
8282
- [ ] lim
8383

84-
## Image resizing
84+
## Image
85+
86+
### Resizing
8587

8688
```md
89+
![logo](https://docsify.js.org/_media/icon.svg ':size=WIDTHxHEIGHT')
8790
![logo](https://docsify.js.org/_media/icon.svg ':size=50x100')
8891
![logo](https://docsify.js.org/_media/icon.svg ':size=100')
8992

@@ -96,6 +99,18 @@ You will get `<a href="/demo/">link</a>`html. Do not worry, you can still set ti
9699
![logo](https://docsify.js.org/_media/icon.svg ':size=100')
97100
![logo](https://docsify.js.org/_media/icon.svg ':size=10%')
98101

102+
### Customise class
103+
104+
```md
105+
![logo](https://docsify.js.org/_media/icon.svg ':class=someCssClass')
106+
```
107+
108+
### Customise ID
109+
110+
```md
111+
![logo](https://docsify.js.org/_media/icon.svg ':id=someCssId')
112+
```
113+
99114
## Customise ID for headings
100115

101116
```md

‎package-lock.json

Lines changed: 30 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/docsify-server-renderer/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/core/render/compiler.js

Lines changed: 15 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import marked from 'marked'
2-
import Prism from 'prismjs'
3-
import { helper as helperTpl, tree as treeTpl } from './tpl'
1+
import { tree as treeTpl } from './tpl'
42
import { genTree } from './gen-tree'
53
import { slugify } from './slugify'
64
import { emojify } from './emojify'
75
import { isAbsolutePath, getPath, getParentPath } from '../router/util'
86
import { isFn, merge, cached, isPrimitive } from '../util/core'
9-
10-
// See https://github.com/PrismJS/prism/pull/1367
11-
import 'prismjs/components/prism-markup-templating'
7+
import { imageCompiler } from './compiler/image'
8+
import { highlightCodeCompiler } from './compiler/code'
9+
import { paragraphCompiler } from './compiler/paragraph'
10+
import { taskListCompiler } from './compiler/taskList'
11+
import { taskListItemCompiler } from './compiler/taskListItem'
12+
import { linkCompiler } from './compiler/link'
13+
import marked from 'marked'
1214

1315
const cachedLinks = {}
1416

@@ -189,7 +191,7 @@ export class Compiler {
189191

190192
_initRenderer() {
191193
const renderer = new marked.Renderer()
192-
const { linkTarget, linkRel, router, contentBase } = this
194+
const { linkTarget, router, contentBase } = this
193195
const _self = this
194196
const origin = {}
195197

@@ -224,117 +226,12 @@ export class Compiler {
224226
return `<h${level} id="${slug}"><a href="${url}" data-id="${slug}" class="anchor"><span>${str}</span></a></h${level}>`
225227
}
226228

227-
// Highlight code
228-
origin.code = renderer.code = function (code, lang = '') {
229-
code = code.replace(/@DOCSIFY_QM@/g, '`')
230-
const hl = Prism.highlight(
231-
code,
232-
Prism.languages[lang] || Prism.languages.markup
233-
)
234-
235-
return `<pre v-pre data-lang="${lang}"><code class="lang-${lang}">${hl}</code></pre>`
236-
}
237-
238-
origin.link = renderer.link = function (href, title = '', text) {
239-
let attrs = ''
240-
241-
const { str, config } = getAndRemoveConfig(title)
242-
title = str
243-
244-
if (
245-
!isAbsolutePath(href) &&
246-
!_self._matchNotCompileLink(href) &&
247-
!config.ignore
248-
) {
249-
if (href === _self.config.homepage) {
250-
href = 'README'
251-
}
252-
253-
href = router.toURL(href, null, router.getCurrentPath())
254-
} else {
255-
attrs += href.indexOf('mailto:') === 0 ? '' : ` target="${linkTarget}"`
256-
attrs += href.indexOf('mailto:') === 0 ? '' : (linkRel !== '' ? ` rel="${linkRel}"` : '')
257-
}
258-
259-
if (config.target) {
260-
attrs += ' target=' + config.target
261-
}
262-
263-
if (config.disabled) {
264-
attrs += ' disabled'
265-
href = 'javascript:void(0)'
266-
}
267-
268-
if (title) {
269-
attrs += ` title="${title}"`
270-
}
271-
272-
return `<a href="${href}"${attrs}>${text}</a>`
273-
}
274-
275-
origin.paragraph = renderer.paragraph = function (text) {
276-
let result
277-
if (/^!&gt;/.test(text)) {
278-
result = helperTpl('tip', text)
279-
} else if (/^\?&gt;/.test(text)) {
280-
result = helperTpl('warn', text)
281-
} else {
282-
result = `<p>${text}</p>`
283-
}
284-
285-
return result
286-
}
287-
288-
origin.image = renderer.image = function (href, title, text) {
289-
let url = href
290-
let attrs = ''
291-
292-
const { str, config } = getAndRemoveConfig(title)
293-
title = str
294-
295-
if (config['no-zoom']) {
296-
attrs += ' data-no-zoom'
297-
}
298-
299-
if (title) {
300-
attrs += ` title="${title}"`
301-
}
302-
303-
const size = config.size
304-
if (size) {
305-
const sizes = size.split('x')
306-
if (sizes[1]) {
307-
attrs += 'width=' + sizes[0] + ' height=' + sizes[1]
308-
} else {
309-
attrs += 'width=' + sizes[0]
310-
}
311-
}
312-
313-
if (!isAbsolutePath(href)) {
314-
url = getPath(contentBase, getParentPath(router.getCurrentPath()), href)
315-
}
316-
317-
return `<img src="${url}"data-origin="${href}" alt="${text}"${attrs}>`
318-
}
319-
320-
origin.list = renderer.list = function (body, ordered, start) {
321-
const isTaskList = /<li class="task-list-item">/.test(body.split('class="task-list"')[0])
322-
const isStartReq = start && start > 1
323-
const tag = ordered ? 'ol' : 'ul'
324-
const tagAttrs = [
325-
(isTaskList ? 'class="task-list"' : ''),
326-
(isStartReq ? `start="${start}"` : '')
327-
].join(' ').trim()
328-
329-
return `<${tag} ${tagAttrs}>${body}</${tag}>`
330-
}
331-
332-
origin.listitem = renderer.listitem = function (text) {
333-
const isTaskItem = /^(<input.*type="checkbox"[^>]*>)/.test(text)
334-
const html = isTaskItem ? `<li class="task-list-item"><label>${text}</label></li>` : `<li>${text}</li>`
335-
336-
return html
337-
}
229+
origin.code = highlightCodeCompiler({ renderer })
230+
origin.link = linkCompiler({ renderer, router, linkTarget, compilerClass: _self })
231+
origin.paragraph = paragraphCompiler({ renderer })
232+
origin.image = imageCompiler({ renderer, contentBase, router })
233+
origin.list = taskListCompiler({ renderer })
234+
origin.listitem = taskListItemCompiler({ renderer })
338235

339236
renderer.origin = origin
340237

‎src/core/render/compiler/code.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Prism from 'prismjs'
2+
// See https://github.com/PrismJS/prism/pull/1367
3+
import 'prismjs/components/prism-markup-templating'
4+
5+
export const highlightCodeCompiler = ({ renderer }) => renderer.code = function (code, lang = '') {
6+
const langOrMarkup = Prism.languages[lang] || Prism.languages.markup
7+
const text = Prism.highlight(code.replace(/@DOCSIFY_QM@/g, '`'), langOrMarkup)
8+
9+
return `<pre v-pre data-lang="${lang}"><code class="lang-${lang}">${text}</code></pre>`
10+
}

‎src/core/render/compiler/headline.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
import { getAndRemoveConfig } from '../compiler'
3+
import { slugify } from './slugify'
4+
5+
export const headingCompiler = ({ renderer, router, _self }) => renderer.code = (text, level) => {
6+
let { str, config } = getAndRemoveConfig(text)
7+
const nextToc = { level, title: str }
8+
9+
if (/{docsify-ignore}/g.test(str)) {
10+
str = str.replace('{docsify-ignore}', '')
11+
nextToc.title = str
12+
nextToc.ignoreSubHeading = true
13+
}
14+
15+
if (/{docsify-ignore-all}/g.test(str)) {
16+
str = str.replace('{docsify-ignore-all}', '')
17+
nextToc.title = str
18+
nextToc.ignoreAllSubs = true
19+
}
20+
21+
const slug = slugify(config.id || str)
22+
const url = router.toURL(router.getCurrentPath(), { id: slug })
23+
nextToc.slug = url
24+
_self.toc.push(nextToc)
25+
26+
return `<h${level} id="${slug}"><a href="${url}" data-id="${slug}" class="anchor"><span>${str}</span></a></h${level}>`
27+
}

‎src/core/render/compiler/image.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { getAndRemoveConfig } from '../compiler'
2+
import { isAbsolutePath, getPath, getParentPath } from '../../router/util'
3+
4+
export const imageCompiler = ({ renderer, contentBase, router }) => renderer.image = (href, title, text) => {
5+
let url = href
6+
let attrs = []
7+
8+
const { str, config } = getAndRemoveConfig(title)
9+
title = str
10+
11+
if (config['no-zoom']) {
12+
attrs.push('data-no-zoom')
13+
}
14+
15+
if (title) {
16+
attrs.push(`title="${title}"`)
17+
}
18+
19+
if (config.size) {
20+
const [width, height] = config.size.split('x')
21+
if (height) {
22+
attrs.push(`width="${width}" height="${height}"`)
23+
} else {
24+
attrs.push(`width="${width}" height="${width}"`)
25+
}
26+
}
27+
28+
if (config.class) {
29+
attrs.push(`class="${config.class}"`)
30+
}
31+
32+
if (config.id) {
33+
attrs.push(`id="${config.id}"`)
34+
}
35+
36+
if (!isAbsolutePath(href)) {
37+
url = getPath(contentBase, getParentPath(router.getCurrentPath()), href)
38+
}
39+
40+
return `<img src="${url}" data-origin="${href}" alt="${text}" ${attrs.join(' ')} />`
41+
}
42+

‎src/core/render/compiler/link.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { getAndRemoveConfig } from '../compiler'
2+
import { isAbsolutePath } from '../../router/util'
3+
4+
export const linkCompiler = ({ renderer, router, linkTarget, compilerClass }) => renderer.link = (href, title = '', text) => {
5+
let attrs = []
6+
const { str, config } = getAndRemoveConfig(title)
7+
8+
title = str
9+
10+
if (!isAbsolutePath(href) && !compilerClass._matchNotCompileLink(href) && !config.ignore) {
11+
if (href === compilerClass.config.homepage) {
12+
href = 'README'
13+
}
14+
15+
href = router.toURL(href, null, router.getCurrentPath())
16+
} else {
17+
attrs.push(href.indexOf('mailto:') === 0 ? '' : `target="${linkTarget}"`)
18+
}
19+
20+
if (config.target) {
21+
attrs.push(`target="${config.target}"`)
22+
}
23+
24+
if (config.disabled) {
25+
attrs.push('disabled')
26+
href = 'javascript:void(0)'
27+
}
28+
29+
if (config.class) {
30+
attrs.push(`class="${config.class}"`)
31+
}
32+
33+
if (config.id) {
34+
attrs.push(`id="${config.id}"`)
35+
}
36+
37+
if (title) {
38+
attrs.push(`title="${title}"`)
39+
}
40+
41+
return `<a href="${href}" ${attrs.join(' ')}>${text}</a>`
42+
}

‎src/core/render/compiler/paragraph.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { helper as helperTpl } from '../tpl'
2+
3+
export const paragraphCompiler = ({ renderer }) => renderer.paragraph = text => {
4+
let result
5+
if (/^!&gt;/.test(text)) {
6+
result = helperTpl('tip', text)
7+
} else if (/^\?&gt;/.test(text)) {
8+
result = helperTpl('warn', text)
9+
} else {
10+
result = `<p>${text}</p>`
11+
}
12+
13+
return result
14+
}
15+

‎src/core/render/compiler/taskList.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const taskListCompiler = ({renderer}) => renderer.list = (body, ordered, start) => {
2+
const isTaskList = /<li class="task-list-item">/.test(body.split('class="task-list"')[0])
3+
const isStartReq = start && start > 1
4+
const tag = ordered ? 'ol' : 'ul'
5+
const tagAttrs = [
6+
(isTaskList ? 'class="task-list"' : ''),
7+
(isStartReq ? `start="${start}"` : '')
8+
]
9+
.join(' ')
10+
.trim()
11+
12+
return `<${tag} ${tagAttrs}>${body}</${tag}>`
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const taskListItemCompiler = ({ renderer }) => renderer.listitem = text => {
2+
const isTaskItem = /^(<input.*type="checkbox"[^>]*>)/.test(text)
3+
const html = isTaskItem ? `<li class="task-list-item"><label>${text}</label></li>` : `<li>${text}</li>`
4+
5+
return html
6+
}

‎test/unit/render.js

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('render', function () {
4040
- [linktext](link)
4141
- just text`)
4242
expectSameDom(output, `<ul >
43-
<li><a href="#/link">linktext</a></li>
43+
<li><a href="#/link" >linktext</a></li>
4444
<li>just text</li>
4545
</ul>`)
4646
})
@@ -83,4 +83,155 @@ text
8383
</ul>`)
8484
})
8585
})
86+
87+
describe('image', function () {
88+
it('regular', async function () {
89+
const { docsify } = await init()
90+
const output = docsify.compiler.compile('![alt text](http://imageUrl)')
91+
92+
expectSameDom(output, '<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" /></p>')
93+
})
94+
95+
it('class', async function () {
96+
const { docsify } = await init()
97+
const output = docsify.compiler.compile('![alt text](http://imageUrl \':class=someCssClass\')')
98+
99+
expectSameDom(output, '<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" class="someCssClass" /></p>')
100+
})
101+
102+
it('id', async function () {
103+
const { docsify } = await init()
104+
const output = docsify.compiler.compile('![alt text](http://imageUrl \':id=someCssID\')')
105+
106+
expectSameDom(output, '<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" id="someCssID" /></p>')
107+
})
108+
109+
it('no-zoom', async function () {
110+
const { docsify } = await init()
111+
const output = docsify.compiler.compile('![alt text](http://imageUrl \':no-zoom\')')
112+
113+
expectSameDom(output, '<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" data-no-zoom /></p>')
114+
})
115+
116+
describe('size', function () {
117+
it('width and height', async function () {
118+
const { docsify } = await init()
119+
const output = docsify.compiler.compile('![alt text](http://imageUrl \':size=WIDTHxHEIGHT\')')
120+
121+
expectSameDom(output, '<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" width="WIDTH" height="HEIGHT" /></p>')
122+
})
123+
124+
it('width', async function () {
125+
const { docsify } = await init()
126+
const output = docsify.compiler.compile('![alt text](http://imageUrl \':size=50\')')
127+
128+
expectSameDom(output, '<p><img src="http://imageUrl" data-origin="http://imageUrl" alt="alt text" width="50" height="50" /></p>')
129+
})
130+
})
131+
})
132+
133+
describe('heading', function () {
134+
it('h1', async function () {
135+
const { docsify } = await init()
136+
const output = docsify.compiler.compile('# h1 tag')
137+
expectSameDom(output, `
138+
<h1 id="h1-tag">
139+
<a href="#/?id=h1-tag" data-id="h1-tag" class="anchor">
140+
<span>h1 tag</span>
141+
</a>
142+
</h1>`)
143+
})
144+
145+
it('h2', async function () {
146+
const { docsify } = await init()
147+
const output = docsify.compiler.compile('## h2 tag')
148+
expectSameDom(output, `
149+
<h2 id="h2-tag">
150+
<a href="#/?id=h2-tag" data-id="h2-tag" class="anchor">
151+
<span>h2 tag</span>
152+
</a>
153+
</h2>`)
154+
})
155+
156+
it('h3', async function () {
157+
const { docsify } = await init()
158+
const output = docsify.compiler.compile('### h3 tag')
159+
expectSameDom(output, `
160+
<h3 id="h3-tag">
161+
<a href="#/?id=h3-tag" data-id="h3-tag" class="anchor">
162+
<span>h3 tag</span>
163+
</a>
164+
</h3>`)
165+
})
166+
167+
it('h4', async function () {
168+
const { docsify } = await init()
169+
const output = docsify.compiler.compile('#### h4 tag')
170+
expectSameDom(output, `
171+
<h4 id="h4-tag">
172+
<a href="#/?id=h4-tag" data-id="h4-tag" class="anchor">
173+
<span>h4 tag</span>
174+
</a>
175+
</h4>`)
176+
})
177+
178+
it('h5', async function () {
179+
const { docsify } = await init()
180+
const output = docsify.compiler.compile('##### h5 tag')
181+
expectSameDom(output, `
182+
<h5 id="h5-tag">
183+
<a href="#/?id=h5-tag" data-id="h5-tag" class="anchor">
184+
<span>h5 tag</span>
185+
</a>
186+
</h5>`)
187+
})
188+
189+
it('h6', async function () {
190+
const { docsify } = await init()
191+
const output = docsify.compiler.compile('###### h6 tag')
192+
expectSameDom(output, `
193+
<h6 id="h6-tag">
194+
<a href="#/?id=h6-tag" data-id="h6-tag" class="anchor">
195+
<span>h6 tag</span>
196+
</a>
197+
</h6>`)
198+
})
199+
})
200+
201+
describe('link', function () {
202+
it('regular', async function () {
203+
const { docsify } = await init()
204+
const output = docsify.compiler.compile('[alt text](http://url)')
205+
206+
expectSameDom(output, '<p><a href="http://url" target="_blank">alt text</a></p>')
207+
})
208+
209+
it('disabled', async function () {
210+
const { docsify } = await init()
211+
const output = docsify.compiler.compile('[alt text](http://url \':disabled\')')
212+
213+
expectSameDom(output, '<p><a href="javascript:void(0)" target="_blank" disabled>alt text</a></p>')
214+
})
215+
216+
it('target', async function () {
217+
const { docsify } = await init()
218+
const output = docsify.compiler.compile('[alt text](http://url \':target=_self\')')
219+
220+
expectSameDom(output, '<p><a href="http://url" target="_blank" target="_self">alt text</a></p>')
221+
})
222+
223+
it('class', async function () {
224+
const { docsify } = await init()
225+
const output = docsify.compiler.compile('[alt text](http://url \':class=someCssClass\')')
226+
227+
expectSameDom(output, '<p><a href="http://url" target="_blank" class="someCssClass">alt text</a></p>')
228+
})
229+
230+
it('id', async function () {
231+
const { docsify } = await init()
232+
const output = docsify.compiler.compile('[alt text](http://url \':id=someCssID\')')
233+
234+
expectSameDom(output, '<p><a href="http://url" target="_blank" id="someCssID">alt text</a></p>')
235+
})
236+
})
86237
})

0 commit comments

Comments
 (0)
Please sign in to comment.