diff --git a/docs/configuration.md b/docs/configuration.md index 830748f37..ba4847598 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -150,6 +150,46 @@ window.$docsify = { }; ``` +## relativePath + +- Type: `Boolean` +- Default: `false` + +If **true** links are relative to the current context. + +For example, the directory structure is as follows: + +```text +. +└── docs + ├── README.md + ├── guide.md + └── zh-cn + ├── README.md + ├── guide.md + └── config + └── example.md +``` + +With relative path **enabled** and current URL `http://domain.com/zh-cn/README`, given links will resolve to: + +```text +guide.md => http://domain.com/zh-cn/guide +config/example.md => http://domain.com/zh-cn/config/example +../README.md => http://domain.com/README +/README.md => http://domain.com/README +``` + +```js +window.$docsify = { + // Relative path enabled + relativePath: true, + + // Relative path disabled (default value) + relativePath: false +}; +``` + ## coverpage - Type: `Boolean|String|String[]|Object` diff --git a/src/core/config.js b/src/core/config.js index 0a8763a85..a1386b274 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -25,7 +25,8 @@ export default function () { formatUpdated: '', externalLinkTarget: '_blank', routerMode: 'hash', - noCompileLinks: [] + noCompileLinks: [], + relativePath: false }, window.$docsify ) diff --git a/src/core/router/history/base.js b/src/core/router/history/base.js index 0f9900d22..7ea763d8d 100644 --- a/src/core/router/history/base.js +++ b/src/core/router/history/base.js @@ -3,7 +3,8 @@ import { isAbsolutePath, stringifyQuery, cleanPath, - replaceSlug + replaceSlug, + resolvePath } from '../util' import {noop, merge} from '../../util/core' @@ -73,9 +74,13 @@ export class History { if (local) { const idIndex = currentRoute.indexOf('?') path = - (idIndex > 0 ? currentRoute.substr(0, idIndex) : currentRoute) + path + (idIndex > 0 ? currentRoute.substring(0, idIndex) : currentRoute) + path } + if (this.config.relativePath && path.indexOf('/') !== 0) { + const currentDir = currentRoute.substring(0, currentRoute.lastIndexOf('/') + 1) + return cleanPath(resolvePath(currentDir + path)) + } return cleanPath('/' + path) } } diff --git a/src/core/router/util.js b/src/core/router/util.js index 217461f47..2ed88c58f 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -53,6 +53,20 @@ export const cleanPath = cached(path => { return path.replace(/^\/+/, '/').replace(/([^:])\/{2,}/g, '$1/') }) +export const resolvePath = cached(path => { + const segments = path.replace(/^\//, '').split('/') + let resolved = [] + for (let i = 0, len = segments.length; i < len; i++) { + const segment = segments[i] + if (segment === '..') { + resolved.pop() + } else if (segment !== '.') { + resolved.push(segment) + } + } + return '/' + resolved.join('/') +}) + export function getPath(...args) { return cleanPath(args.join('/')) } diff --git a/test/unit/base.js b/test/unit/base.js new file mode 100644 index 000000000..036700b99 --- /dev/null +++ b/test/unit/base.js @@ -0,0 +1,62 @@ +/* eslint-env node, chai, mocha */ +require = require('esm')(module/*, options*/) +const {expect} = require('chai') +const {History} = require('../../src/core/router/history/base') + +class MockHistory extends History { + parse(path) { + return {path} + } +} + +describe('router/history/base', function () { + describe('relativePath true', function () { + var history + + beforeEach(function () { + history = new MockHistory({relativePath: true}) + }) + + it('toURL', function () { + // WHEN + const url = history.toURL('guide.md', {}, '/zh-ch/') + + // THEN + expect(url).equal('/zh-ch/guide') + }) + + it('toURL with double dot', function () { + // WHEN + const url = history.toURL('../README.md', {}, '/zh-ch/') + + // THEN + expect(url).equal('/README') + }) + + it('toURL child path', function () { + // WHEN + const url = history.toURL('config/example.md', {}, '/zh-ch/') + + // THEN + expect(url).equal('/zh-ch/config/example') + }) + + it('toURL absolute path', function () { + // WHEN + const url = history.toURL('/README', {}, '/zh-ch/') + + // THEN + expect(url).equal('/README') + }) + }) + + it('toURL without relative path', function () { + const history = new MockHistory({relativePath: false}) + + // WHEN + const url = history.toURL('README', {}, '/zh-ch/') + + // THEN + expect(url).equal('/README') + }) +}) diff --git a/test/unit/util.js b/test/unit/util.js new file mode 100644 index 000000000..1e65dafe0 --- /dev/null +++ b/test/unit/util.js @@ -0,0 +1,30 @@ +/* eslint-env node, chai, mocha */ +require = require('esm')(module/*, options*/) +const {expect} = require('chai') +const {resolvePath} = require('../../src/core/router/util') + +describe('router/util', function () { + it('resolvePath', async function () { + // WHEN + const result = resolvePath('hello.md') + + // THEN + expect(result).equal('/hello.md') + }) + + it('resolvePath with dot', async function () { + // WHEN + const result = resolvePath('./hello.md') + + // THEN + expect(result).equal('/hello.md') + }) + + it('resolvePath with two dots', async function () { + // WHEN + const result = resolvePath('test/../hello.md') + + // THEN + expect(result).equal('/hello.md') + }) +})