diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index caaea4571..a2a52aee4 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -1,13 +1,12 @@ import { noop } from '../../util/core'; import { on } from '../../util/dom'; -import { parseQuery, cleanPath, replaceSlug } from '../util'; +import { parseQuery, cleanPath, replaceSlug, endsWith } from '../util'; import { History } from './base'; function replaceHash(path) { const i = location.href.indexOf('#'); location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path); } - export class HashHistory extends History { constructor(config) { super(config); @@ -18,7 +17,15 @@ export class HashHistory extends History { const path = window.location.pathname || ''; const base = this.config.basePath; - return /^(\/|https?:)/g.test(base) ? base : cleanPath(path + '/' + base); + // This handles the case where Docsify is served off an + // explicit file path, i.e.`/base/index.html#/blah`. This + // prevents the `/index.html` part of the URI from being + // remove during routing. + // See here: https://github.com/docsifyjs/docsify/pull/1372 + const basePath = endsWith(path, '.html') + ? path + '#/' + base + : path + '/' + base; + return /^(\/|https?:)/g.test(base) ? base : cleanPath(basePath); } getCurrentPath() { diff --git a/src/core/router/util.js b/src/core/router/util.js index fc3e2f79d..aec6153ce 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -76,10 +76,44 @@ export const resolvePath = cached(path => { return '/' + resolved.join('/'); }); +/** + * Normalises the URI path to handle the case where Docsify is + * hosted off explicit files, i.e. /index.html. This function + * eliminates any path segments that contain `#` fragments. + * + * This is used to map browser URIs to markdown file sources. + * + * For example: + * + * http://example.org/base/index.html#/blah + * + * would be mapped to: + * + * http://example.org/base/blah.md. + * + * See here for more information: + * + * https://github.com/docsifyjs/docsify/pull/1372 + * + * @param {string} path The URI path to normalise + * @return {string} { path, query } + */ + +function normaliseFragment(path) { + return path + .split('/') + .filter(p => p.indexOf('#') === -1) + .join('/'); +} + export function getPath(...args) { - return cleanPath(args.join('/')); + return cleanPath(args.map(normaliseFragment).join('/')); } export const replaceSlug = cached(path => { return path.replace('#', '?id='); }); + +export function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} diff --git a/test/e2e/index-file.test.js b/test/e2e/index-file.test.js new file mode 100644 index 000000000..06ac47135 --- /dev/null +++ b/test/e2e/index-file.test.js @@ -0,0 +1,28 @@ +const docsifyInit = require('../helpers/docsify-init'); + +describe(`Index file hosting`, function() { + const sharedOptions = { + config: { + basePath: `${TEST_HOST}/docs/index.html#/`, + }, + testURL: `${TEST_HOST}/docs/index.html#/`, + }; + + test('should serve from index file', async () => { + await docsifyInit(sharedOptions); + + await expect(page).toHaveText( + '#main', + 'A magical documentation site generator' + ); + expect(page.url()).toMatch(/index\.html#\/$/); + }); + + test('should use index file links in sidebar from index file hosting', async () => { + await docsifyInit(sharedOptions); + + await page.click('a[href="#/quickstart"]'); + await expect(page).toHaveText('#main', 'Quick start'); + expect(page.url()).toMatch(/index\.html#\/quickstart$/); + }); +});