diff --git a/cypress/integration/sidebar/config.spec.js b/cypress/integration/sidebar/config.spec.js index 507a9031e..d2ffbee68 100644 --- a/cypress/integration/sidebar/config.spec.js +++ b/cypress/integration/sidebar/config.spec.js @@ -239,7 +239,6 @@ context('sidebar.configurations', () => { 'set-target-attribute-for-link', 'disable-link', 'github-task-lists', - 'image-resizing', 'customise-id-for-headings', 'markdown-in-html-tag' ] diff --git a/cypress/snapshots/sidebar/config.spec.js/sidebar.configurations -- go to #helpersid=image-resizing.snap.png b/cypress/snapshots/sidebar/config.spec.js/sidebar.configurations -- go to #helpersid=image-resizing.snap.png deleted file mode 100644 index ababb8153..000000000 Binary files a/cypress/snapshots/sidebar/config.spec.js/sidebar.configurations -- go to #helpersid=image-resizing.snap.png and /dev/null differ diff --git a/docs/helpers.md b/docs/helpers.md index b047d3673..7d551c53e 100644 --- a/docs/helpers.md +++ b/docs/helpers.md @@ -81,9 +81,12 @@ You will get `link`html. Do not worry, you can still set ti - [ ] bim - [ ] lim -## Image resizing +## Image + +### Resizing ```md +![logo](https://docsify.js.org/_media/icon.svg ':size=WIDTHxHEIGHT') ![logo](https://docsify.js.org/_media/icon.svg ':size=50x100') ![logo](https://docsify.js.org/_media/icon.svg ':size=100') @@ -96,6 +99,18 @@ You will get `link`html. Do not worry, you can still set ti ![logo](https://docsify.js.org/_media/icon.svg ':size=100') ![logo](https://docsify.js.org/_media/icon.svg ':size=10%') +### Customise class + +```md +![logo](https://docsify.js.org/_media/icon.svg ':class=someCssClass') +``` + +### Customise ID + +```md +![logo](https://docsify.js.org/_media/icon.svg ':id=someCssId') +``` + ## Customise ID for headings ```md diff --git a/package-lock.json b/package-lock.json index e9ca08c28..5d959ac2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7520,7 +7520,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -7541,12 +7542,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7561,17 +7564,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7688,7 +7694,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -7700,6 +7707,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7714,6 +7722,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7721,12 +7730,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7745,6 +7756,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -7825,7 +7837,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7837,6 +7850,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -7922,7 +7936,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -7958,6 +7973,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7977,6 +7993,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8020,12 +8037,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/packages/docsify-server-renderer/package-lock.json b/packages/docsify-server-renderer/package-lock.json index d26e84572..eae88000a 100644 --- a/packages/docsify-server-renderer/package-lock.json +++ b/packages/docsify-server-renderer/package-lock.json @@ -436,4 +436,4 @@ "integrity": "sha512-aSiJz7rGWNAQq7hjMK9ZYDuEawXupcCWgl3woQQSoDP2Oh8O4srWb/uO1PzzHIsrPEOqrjJ2sUb9FERfzuBabQ==" } } -} +} \ No newline at end of file diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 6973de2b9..26f4cea91 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -1,14 +1,16 @@ -import marked from 'marked' -import Prism from 'prismjs' -import { helper as helperTpl, tree as treeTpl } from './tpl' +import { tree as treeTpl } from './tpl' import { genTree } from './gen-tree' import { slugify } from './slugify' import { emojify } from './emojify' import { isAbsolutePath, getPath, getParentPath } from '../router/util' import { isFn, merge, cached, isPrimitive } from '../util/core' - -// See https://github.com/PrismJS/prism/pull/1367 -import 'prismjs/components/prism-markup-templating' +import { imageCompiler } from './compiler/image' +import { highlightCodeCompiler } from './compiler/code' +import { paragraphCompiler } from './compiler/paragraph' +import { taskListCompiler } from './compiler/taskList' +import { taskListItemCompiler } from './compiler/taskListItem' +import { linkCompiler } from './compiler/link' +import marked from 'marked' const cachedLinks = {} @@ -189,7 +191,7 @@ export class Compiler { _initRenderer() { const renderer = new marked.Renderer() - const { linkTarget, linkRel, router, contentBase } = this + const { linkTarget, router, contentBase } = this const _self = this const origin = {} @@ -224,117 +226,12 @@ export class Compiler { return `${str}` } - // Highlight code - origin.code = renderer.code = function (code, lang = '') { - code = code.replace(/@DOCSIFY_QM@/g, '`') - const hl = Prism.highlight( - code, - Prism.languages[lang] || Prism.languages.markup - ) - - return `
${hl}
` - } - - origin.link = renderer.link = function (href, title = '', text) { - let attrs = '' - - const { str, config } = getAndRemoveConfig(title) - title = str - - if ( - !isAbsolutePath(href) && - !_self._matchNotCompileLink(href) && - !config.ignore - ) { - if (href === _self.config.homepage) { - href = 'README' - } - - href = router.toURL(href, null, router.getCurrentPath()) - } else { - attrs += href.indexOf('mailto:') === 0 ? '' : ` target="${linkTarget}"` - attrs += href.indexOf('mailto:') === 0 ? '' : (linkRel !== '' ? ` rel="${linkRel}"` : '') - } - - if (config.target) { - attrs += ' target=' + config.target - } - - if (config.disabled) { - attrs += ' disabled' - href = 'javascript:void(0)' - } - - if (title) { - attrs += ` title="${title}"` - } - - return `${text}` - } - - origin.paragraph = renderer.paragraph = function (text) { - let result - if (/^!>/.test(text)) { - result = helperTpl('tip', text) - } else if (/^\?>/.test(text)) { - result = helperTpl('warn', text) - } else { - result = `

${text}

` - } - - return result - } - - origin.image = renderer.image = function (href, title, text) { - let url = href - let attrs = '' - - const { str, config } = getAndRemoveConfig(title) - title = str - - if (config['no-zoom']) { - attrs += ' data-no-zoom' - } - - if (title) { - attrs += ` title="${title}"` - } - - const size = config.size - if (size) { - const sizes = size.split('x') - if (sizes[1]) { - attrs += 'width=' + sizes[0] + ' height=' + sizes[1] - } else { - attrs += 'width=' + sizes[0] - } - } - - if (!isAbsolutePath(href)) { - url = getPath(contentBase, getParentPath(router.getCurrentPath()), href) - } - - return `${text}` - } - - origin.list = renderer.list = function (body, ordered, start) { - const isTaskList = /
  • /.test(body.split('class="task-list"')[0]) - const isStartReq = start && start > 1 - const tag = ordered ? 'ol' : 'ul' - const tagAttrs = [ - (isTaskList ? 'class="task-list"' : ''), - (isStartReq ? `start="${start}"` : '') - ].join(' ').trim() - - return `<${tag} ${tagAttrs}>${body}` - } - - origin.listitem = renderer.listitem = function (text) { - const isTaskItem = /^(]*>)/.test(text) - const html = isTaskItem ? `
  • ` : `
  • ${text}
  • ` - - return html - } + origin.code = highlightCodeCompiler({ renderer }) + origin.link = linkCompiler({ renderer, router, linkTarget, compilerClass: _self }) + origin.paragraph = paragraphCompiler({ renderer }) + origin.image = imageCompiler({ renderer, contentBase, router }) + origin.list = taskListCompiler({ renderer }) + origin.listitem = taskListItemCompiler({ renderer }) renderer.origin = origin diff --git a/src/core/render/compiler/code.js b/src/core/render/compiler/code.js new file mode 100644 index 000000000..fb9d88021 --- /dev/null +++ b/src/core/render/compiler/code.js @@ -0,0 +1,10 @@ +import Prism from 'prismjs' +// See https://github.com/PrismJS/prism/pull/1367 +import 'prismjs/components/prism-markup-templating' + +export const highlightCodeCompiler = ({ renderer }) => renderer.code = function (code, lang = '') { + const langOrMarkup = Prism.languages[lang] || Prism.languages.markup + const text = Prism.highlight(code.replace(/@DOCSIFY_QM@/g, '`'), langOrMarkup) + + return `
    ${text}
    ` +} diff --git a/src/core/render/compiler/headline.js b/src/core/render/compiler/headline.js new file mode 100644 index 000000000..2a5cbb795 --- /dev/null +++ b/src/core/render/compiler/headline.js @@ -0,0 +1,27 @@ + +import { getAndRemoveConfig } from '../compiler' +import { slugify } from './slugify' + +export const headingCompiler = ({ renderer, router, _self }) => renderer.code = (text, level) => { + let { str, config } = getAndRemoveConfig(text) + const nextToc = { level, title: str } + + if (/{docsify-ignore}/g.test(str)) { + str = str.replace('{docsify-ignore}', '') + nextToc.title = str + nextToc.ignoreSubHeading = true + } + + if (/{docsify-ignore-all}/g.test(str)) { + str = str.replace('{docsify-ignore-all}', '') + nextToc.title = str + nextToc.ignoreAllSubs = true + } + + const slug = slugify(config.id || str) + const url = router.toURL(router.getCurrentPath(), { id: slug }) + nextToc.slug = url + _self.toc.push(nextToc) + + return `${str}` +} diff --git a/src/core/render/compiler/image.js b/src/core/render/compiler/image.js new file mode 100644 index 000000000..e07602dd8 --- /dev/null +++ b/src/core/render/compiler/image.js @@ -0,0 +1,42 @@ +import { getAndRemoveConfig } from '../compiler' +import { isAbsolutePath, getPath, getParentPath } from '../../router/util' + +export const imageCompiler = ({ renderer, contentBase, router }) => renderer.image = (href, title, text) => { + let url = href + let attrs = [] + + const { str, config } = getAndRemoveConfig(title) + title = str + + if (config['no-zoom']) { + attrs.push('data-no-zoom') + } + + if (title) { + attrs.push(`title="${title}"`) + } + + if (config.size) { + const [width, height] = config.size.split('x') + if (height) { + attrs.push(`width="${width}" height="${height}"`) + } else { + attrs.push(`width="${width}" height="${width}"`) + } + } + + if (config.class) { + attrs.push(`class="${config.class}"`) + } + + if (config.id) { + attrs.push(`id="${config.id}"`) + } + + if (!isAbsolutePath(href)) { + url = getPath(contentBase, getParentPath(router.getCurrentPath()), href) + } + + return `${text}` +} + diff --git a/src/core/render/compiler/link.js b/src/core/render/compiler/link.js new file mode 100644 index 000000000..53e6250cc --- /dev/null +++ b/src/core/render/compiler/link.js @@ -0,0 +1,42 @@ +import { getAndRemoveConfig } from '../compiler' +import { isAbsolutePath } from '../../router/util' + +export const linkCompiler = ({ renderer, router, linkTarget, compilerClass }) => renderer.link = (href, title = '', text) => { + let attrs = [] + const { str, config } = getAndRemoveConfig(title) + + title = str + + if (!isAbsolutePath(href) && !compilerClass._matchNotCompileLink(href) && !config.ignore) { + if (href === compilerClass.config.homepage) { + href = 'README' + } + + href = router.toURL(href, null, router.getCurrentPath()) + } else { + attrs.push(href.indexOf('mailto:') === 0 ? '' : `target="${linkTarget}"`) + } + + if (config.target) { + attrs.push(`target="${config.target}"`) + } + + if (config.disabled) { + attrs.push('disabled') + href = 'javascript:void(0)' + } + + if (config.class) { + attrs.push(`class="${config.class}"`) + } + + if (config.id) { + attrs.push(`id="${config.id}"`) + } + + if (title) { + attrs.push(`title="${title}"`) + } + + return `${text}` +} diff --git a/src/core/render/compiler/paragraph.js b/src/core/render/compiler/paragraph.js new file mode 100644 index 000000000..63ff1c111 --- /dev/null +++ b/src/core/render/compiler/paragraph.js @@ -0,0 +1,15 @@ +import { helper as helperTpl } from '../tpl' + +export const paragraphCompiler = ({ renderer }) => renderer.paragraph = text => { + let result + if (/^!>/.test(text)) { + result = helperTpl('tip', text) + } else if (/^\?>/.test(text)) { + result = helperTpl('warn', text) + } else { + result = `

    ${text}

    ` + } + + return result +} + diff --git a/src/core/render/compiler/taskList.js b/src/core/render/compiler/taskList.js new file mode 100644 index 000000000..fbf76aa9f --- /dev/null +++ b/src/core/render/compiler/taskList.js @@ -0,0 +1,13 @@ +export const taskListCompiler = ({renderer}) => renderer.list = (body, ordered, start) => { + const isTaskList = /
  • /.test(body.split('class="task-list"')[0]) + const isStartReq = start && start > 1 + const tag = ordered ? 'ol' : 'ul' + const tagAttrs = [ + (isTaskList ? 'class="task-list"' : ''), + (isStartReq ? `start="${start}"` : '') + ] + .join(' ') + .trim() + + return `<${tag} ${tagAttrs}>${body}` +} diff --git a/src/core/render/compiler/taskListItem.js b/src/core/render/compiler/taskListItem.js new file mode 100644 index 000000000..df705ce58 --- /dev/null +++ b/src/core/render/compiler/taskListItem.js @@ -0,0 +1,6 @@ +export const taskListItemCompiler = ({ renderer }) => renderer.listitem = text => { + const isTaskItem = /^(]*>)/.test(text) + const html = isTaskItem ? `
  • ` : `
  • ${text}
  • ` + + return html +} diff --git a/test/unit/render.js b/test/unit/render.js index 44b1493f8..d1afea681 100644 --- a/test/unit/render.js +++ b/test/unit/render.js @@ -40,7 +40,7 @@ describe('render', function () { - [linktext](link) - just text`) expectSameDom(output, ``) }) @@ -83,4 +83,155 @@ text `) }) }) + + describe('image', function () { + it('regular', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('![alt text](http://imageUrl)') + + expectSameDom(output, '

    alt text

    ') + }) + + it('class', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('![alt text](http://imageUrl \':class=someCssClass\')') + + expectSameDom(output, '

    alt text

    ') + }) + + it('id', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('![alt text](http://imageUrl \':id=someCssID\')') + + expectSameDom(output, '

    alt text

    ') + }) + + it('no-zoom', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('![alt text](http://imageUrl \':no-zoom\')') + + expectSameDom(output, '

    alt text

    ') + }) + + describe('size', function () { + it('width and height', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('![alt text](http://imageUrl \':size=WIDTHxHEIGHT\')') + + expectSameDom(output, '

    alt text

    ') + }) + + it('width', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('![alt text](http://imageUrl \':size=50\')') + + expectSameDom(output, '

    alt text

    ') + }) + }) + }) + + describe('heading', function () { + it('h1', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('# h1 tag') + expectSameDom(output, ` +

    + + h1 tag + +

    `) + }) + + it('h2', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('## h2 tag') + expectSameDom(output, ` +

    + + h2 tag + +

    `) + }) + + it('h3', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('### h3 tag') + expectSameDom(output, ` +

    + + h3 tag + +

    `) + }) + + it('h4', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('#### h4 tag') + expectSameDom(output, ` +

    + + h4 tag + +

    `) + }) + + it('h5', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('##### h5 tag') + expectSameDom(output, ` +
    + + h5 tag + +
    `) + }) + + it('h6', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('###### h6 tag') + expectSameDom(output, ` +
    + + h6 tag + +
    `) + }) + }) + + describe('link', function () { + it('regular', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('[alt text](http://url)') + + expectSameDom(output, '

    alt text

    ') + }) + + it('disabled', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('[alt text](http://url \':disabled\')') + + expectSameDom(output, '

    alt text

    ') + }) + + it('target', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('[alt text](http://url \':target=_self\')') + + expectSameDom(output, '

    alt text

    ') + }) + + it('class', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('[alt text](http://url \':class=someCssClass\')') + + expectSameDom(output, '

    alt text

    ') + }) + + it('id', async function () { + const { docsify } = await init() + const output = docsify.compiler.compile('[alt text](http://url \':id=someCssID\')') + + expectSameDom(output, '

    alt text

    ') + }) + }) })