Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,24 @@ window.$docsify = {
topMargin: 90, // default: 0
};
```

## rootRelativeImageURL

- Type: `Boolean|String`
- Default: `false`

Configure image URL rendering behavior when inserting markdown images when the image URL is root-relative, i.e. starts with '/'.

By default, Docsify resolves all image paths against the current page's parent path.

E.g. if the current path is `/advanced/guide`, `![](/assets/image.png)` would render as `<img src="advanced/assets/image.png">`.

```js
window.$docsify = {
rootRelativeImageURL = false // default behaviour

rootRelativeImageURL = true // ![](/assets/image.png) renders as <img src="/assets/image.png" />

rootRelativeImageURL = 'my-root-path' // ![](/assets/image.png) renders as <img src="/my-root-path/assets/image.png />
}
```
5 changes: 5 additions & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default function(vm) {
crossOriginLinks: [],
relativePath: false,
topMargin: 0,
rootRelativeImageURL: false,
},
typeof window.$docsify === 'function'
? window.$docsify(vm)
Expand Down Expand Up @@ -79,6 +80,10 @@ export default function(vm) {
config.name = '';
}

if (config.rootRelativeImageURL === true) {
config.rootRelativeImageURL = '';
}

window.$docsify = config;

return config;
Expand Down
16 changes: 14 additions & 2 deletions src/core/render/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class Compiler {
this.linkRel =
this.linkTarget === '_blank' ? config.externalLinkRel || 'noopener' : '';
this.contentBase = router.getBasePath();
this.rootRelativeImageURL = config.rootRelativeImageURL;

const renderer = this._initRenderer();
this.heading = renderer.heading;
Expand Down Expand Up @@ -193,7 +194,13 @@ export class Compiler {

_initRenderer() {
const renderer = new marked.Renderer();
const { linkTarget, linkRel, router, contentBase } = this;
const {
linkTarget,
linkRel,
router,
contentBase,
rootRelativeImageURL,
} = this;
const _self = this;
const origin = {};

Expand Down Expand Up @@ -249,7 +256,12 @@ export class Compiler {
compilerClass: _self,
});
origin.paragraph = paragraphCompiler({ renderer });
origin.image = imageCompiler({ renderer, contentBase, router });
origin.image = imageCompiler({
renderer,
contentBase,
router,
rootRelativeImageURL,
});
origin.list = taskListCompiler({ renderer });
origin.listitem = taskListItemCompiler({ renderer });

Expand Down
21 changes: 17 additions & 4 deletions src/core/render/compiler/image.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { getAndRemoveConfig } from '../utils';
import { isAbsolutePath, getPath, getParentPath } from '../../router/util';

export const imageCompiler = ({ renderer, contentBase, router }) =>
import {
isAbsolutePath,
isPathRootRelative,
getPath,
getParentPath,
} from '../../router/util';

export const imageCompiler = ({
renderer,
contentBase,
router,
rootRelativeImageURL,
}) =>
(renderer.image = (href, title, text) => {
let url = href;
let attrs = [];
Expand Down Expand Up @@ -35,7 +45,10 @@ export const imageCompiler = ({ renderer, contentBase, router }) =>
}

if (!isAbsolutePath(href)) {
url = getPath(contentBase, getParentPath(router.getCurrentPath()), href);
url =
isPathRootRelative(href) && rootRelativeImageURL !== false
? getPath('/' + String(rootRelativeImageURL), href)
: getPath(contentBase, getParentPath(router.getCurrentPath()), href);
}

if (attrs.length > 0) {
Expand Down
4 changes: 4 additions & 0 deletions src/core/router/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export const isAbsolutePath = cached(path => {
return /(:|(\/{2}))/g.test(path);
});

export const isPathRootRelative = cached(path => {
return path[0] === '/';
});

export const removeParams = cached(path => {
return path.split(/[?#]/)[0];
});
Expand Down
4 changes: 3 additions & 1 deletion test/_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ module.exports.init = function(

const rootPath = path.join(__dirname, 'fixtures', fixture);

const dom = initJSDOM(markup);
const runScriptInJSDom =
Object.values(config).length !== 0 ? 'dangerously' : undefined;
const dom = initJSDOM(markup, { runScripts: runScriptInJSDom });
dom.reconfigure({ url: 'file:///' + rootPath });

// Mimic src/core/index.js but for Node.js
Expand Down
65 changes: 65 additions & 0 deletions test/unit/render.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const path = require('path');
const { expect } = require('chai');
const { init, expectSameDom } = require('../_helper');

Expand Down Expand Up @@ -104,6 +105,23 @@ describe('render', function() {
);
});

it('relative image url', async function() {
const { docsify } = await init();
const output = docsify.compiler.compile(
'![alt text](some-path/image.png)'
);
const expectedCompiledPath = path.posix.join(
// must use posix here because if run on windows, paths are joined using backslash (\)
__dirname,
'../fixtures/default',
'some-path/image.png'
);
expectSameDom(
output,
`<p><img src="${expectedCompiledPath}" data-origin="some-path/image.png" alt="alt text"></p>`
);
});

it('class', async function() {
const { docsify } = await init();
const output = docsify.compiler.compile(
Expand Down Expand Up @@ -165,6 +183,53 @@ describe('render', function() {
);
});
});

describe('compiling root-relative image src path', async function() {
it('behaves normally if config.rootRelativeImageURL is set to false', async function() {
const { docsify } = await init('default', {
rootRelativeImageURL: false,
});
const rootRelativePathOutput = docsify.compiler.compile(
'![alt text](/some-path/image.png)'
);
const expectedCompiledPath = path.posix.join(
// must use posix here because if run on windows, file paths use backslash (\)
__dirname,
'../fixtures/default',
'some-path/image.png'
);
expectSameDom(
rootRelativePathOutput,
`<p><img src="${expectedCompiledPath}" data-origin="/some-path/image.png" alt="alt text"></p>`
);
});

it('only uses the image href if config.rootRelativeImageURL is set to true', async function() {
const { docsify } = await init('default', {
rootRelativeImageURL: true,
});
const rootRelativePathOutput = docsify.compiler.compile(
'![alt text](/some-path/image.png)'
);
expectSameDom(
rootRelativePathOutput,
`<p><img src="/some-path/image.png" data-origin="/some-path/image.png" alt="alt text"></p>`
);
});

it('uses config.rootRelativeImageURL as a prefix for the compiled image path, if passed as a string', async function() {
const { docsify } = await init('default', {
rootRelativeImageURL: 'docs',
});
const rootRelativePathOutput = docsify.compiler.compile(
'![alt text](/some-path/image.png)'
);
expectSameDom(
rootRelativePathOutput,
`<p><img src="/docs/some-path/image.png" data-origin="/some-path/image.png" alt="alt text"></p>`
);
});
});
});

describe('heading', function() {
Expand Down