From 759ffac992b19dbb05b92114ec5620d3bb180d0d Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Sun, 7 Feb 2021 16:25:37 +0000 Subject: [PATCH] fix: Unable to navigate on server without default index support (#1372) * Fix: Cannot serve off `/.../index.html` Docsify must be hosted on a server that supports a default directory index (i.e. maps `/.../` -> `/.../index.html`). Some platforms do not support this, however. For example, HTML apps hosted on the popular game/software platform, Itch.io. This change supports hosting Docsify off an explicit path file, such as `/index.html`. It does this by: 1. Adding handling for paths like `index.html#/blah`, and 2. Normalising paths with fragments back to markdown paths For example, `http://example.org/index.html#/blah` would be mapped to `http://example.org/blah.md`. This fixes: https://github.com/docsifyjs/docsify/issues/427 * Add end-to-end test for index file hosting * Add code comments for explicit file changes * Add additional tests for index file hosting * Add additional tests for index file hosting * [wip] Attempt to switch tests to Jest * Add e2e test for new Jest test framework * Verify sidebar links use file hosting * Fix: endsWith() not supported for IE11 * Refactor: utility method moved to utility file * Fix IE11 error from use of String.includes() Co-authored-by: John Hildenbiddle --- src/core/router/history/hash.js | 13 +++++++++--- src/core/router/util.js | 36 ++++++++++++++++++++++++++++++++- test/e2e/index-file.test.js | 28 +++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 test/e2e/index-file.test.js 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$/); + }); +});