From 939e23b3bffae396c3329e475f5264c397ce4449 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Sat, 12 Sep 2020 13:14:48 +0100 Subject: [PATCH 01/11] 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 --- src/core/router/history/hash.js | 5 ++++- src/core/router/util.js | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index caaea4571..847445400 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -18,7 +18,10 @@ export class HashHistory extends History { const path = window.location.pathname || ''; const base = this.config.basePath; - return /^(\/|https?:)/g.test(base) ? base : cleanPath(path + '/' + base); + const basePath = path.endsWith('.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..c26b8d06c 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -76,8 +76,15 @@ export const resolvePath = cached(path => { return '/' + resolved.join('/'); }); +function normaliseFragment(path) { + return path + .split('/') + .filter(p => !p.includes('#')) + .join('/'); +} + export function getPath(...args) { - return cleanPath(args.join('/')); + return cleanPath(args.map(normaliseFragment).join('/')); } export const replaceSlug = cached(path => { From 36705c754219da569f728611c8acf5d579084842 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Sat, 12 Sep 2020 14:01:42 +0100 Subject: [PATCH 02/11] Add end-to-end test for index file hosting --- cypress/integration/routing/index-file.spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 cypress/integration/routing/index-file.spec.js diff --git a/cypress/integration/routing/index-file.spec.js b/cypress/integration/routing/index-file.spec.js new file mode 100644 index 000000000..bddf9cbce --- /dev/null +++ b/cypress/integration/routing/index-file.spec.js @@ -0,0 +1,9 @@ +context('routing.indexFile', () => { + it('handles index file routing', () => { + cy.visit('http://localhost:3000/index.html'); + + cy.get('.sidebar-nav').contains('Changelog').click(); + + cy.get('#main').should('contain', 'Bug Fixes'); + }) +}); From e42c3df8d94e89c5b3bcc53077c50a980d42ef04 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Mon, 14 Sep 2020 19:05:40 +0100 Subject: [PATCH 03/11] Add code comments for explicit file changes --- src/core/router/history/hash.js | 5 +++++ src/core/router/util.js | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index 847445400..b6e47786e 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -18,6 +18,11 @@ export class HashHistory extends History { const path = window.location.pathname || ''; const base = this.config.basePath; + // 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 = path.endsWith('.html') ? path + '#/' + base : path + '/' + base; diff --git a/src/core/router/util.js b/src/core/router/util.js index c26b8d06c..35d32c355 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -76,6 +76,29 @@ 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('/') From d538c92f30441ae57136e5f4886c3df22fd53b45 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Tue, 15 Sep 2020 16:59:45 +0100 Subject: [PATCH 04/11] Add additional tests for index file hosting --- .../integration/routing/index-file.spec.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cypress/integration/routing/index-file.spec.js b/cypress/integration/routing/index-file.spec.js index bddf9cbce..3aeb0438f 100644 --- a/cypress/integration/routing/index-file.spec.js +++ b/cypress/integration/routing/index-file.spec.js @@ -6,4 +6,33 @@ context('routing.indexFile', () => { cy.get('#main').should('contain', 'Bug Fixes'); }) + + it('handles index file routing with fragments', () => { + cy.visit('http://localhost:3000/index.html#/'); + + cy.get('.sidebar-nav').contains('Changelog').click(); + + cy.get('#main').should('contain', 'Bug Fixes'); + }) + + it('returns 404 for index file with leading fragment', () => { + cy.visit('http://localhost:3000/#/index.html/'); + + cy.get('#main').should('contain', '404'); + }) + + it('returns 500 for index file as folder', () => { + cy.request({ + url: 'http://localhost:3000/index.html/#/', + failOnStatusCode: false + }).then((resp) => expect(500).to.eq(resp.status)) + }) + + + it('does not serve shadowing index markdown file', () => { + cy.request({ + url: 'http://localhost:3000/index.md', + failOnStatusCode: false + }).then((resp) => expect(404).to.eq(resp.status)) + }) }); From 1868d5898e8d2498d8ccbd6f373790a7ae1dbc92 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Wed, 16 Sep 2020 10:26:39 +0100 Subject: [PATCH 05/11] Add additional tests for index file hosting --- cypress/integration/routing/index-file.spec.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cypress/integration/routing/index-file.spec.js b/cypress/integration/routing/index-file.spec.js index 3aeb0438f..1d9da4935 100644 --- a/cypress/integration/routing/index-file.spec.js +++ b/cypress/integration/routing/index-file.spec.js @@ -7,7 +7,7 @@ context('routing.indexFile', () => { cy.get('#main').should('contain', 'Bug Fixes'); }) - it('handles index file routing with fragments', () => { + it('handles index file routing with root fragment', () => { cy.visit('http://localhost:3000/index.html#/'); cy.get('.sidebar-nav').contains('Changelog').click(); @@ -15,6 +15,12 @@ context('routing.indexFile', () => { cy.get('#main').should('contain', 'Bug Fixes'); }) + it('handles index file routing with page fragment', () => { + cy.visit('http://localhost:3000/index.html#/changelog'); + + cy.get('#main').should('contain', 'Bug Fixes'); + }) + it('returns 404 for index file with leading fragment', () => { cy.visit('http://localhost:3000/#/index.html/'); @@ -28,7 +34,6 @@ context('routing.indexFile', () => { }).then((resp) => expect(500).to.eq(resp.status)) }) - it('does not serve shadowing index markdown file', () => { cy.request({ url: 'http://localhost:3000/index.md', From 8aee029837c9e2ec12ca7b2d2472a04ef93a6eeb Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Mon, 19 Oct 2020 17:42:33 +0100 Subject: [PATCH 06/11] [wip] Attempt to switch tests to Jest --- .../integration/routing/index-file.spec.js | 43 ------------------- test/e2e/index-file.test.js | 18 ++++++++ 2 files changed, 18 insertions(+), 43 deletions(-) delete mode 100644 cypress/integration/routing/index-file.spec.js create mode 100644 test/e2e/index-file.test.js diff --git a/cypress/integration/routing/index-file.spec.js b/cypress/integration/routing/index-file.spec.js deleted file mode 100644 index 1d9da4935..000000000 --- a/cypress/integration/routing/index-file.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -context('routing.indexFile', () => { - it('handles index file routing', () => { - cy.visit('http://localhost:3000/index.html'); - - cy.get('.sidebar-nav').contains('Changelog').click(); - - cy.get('#main').should('contain', 'Bug Fixes'); - }) - - it('handles index file routing with root fragment', () => { - cy.visit('http://localhost:3000/index.html#/'); - - cy.get('.sidebar-nav').contains('Changelog').click(); - - cy.get('#main').should('contain', 'Bug Fixes'); - }) - - it('handles index file routing with page fragment', () => { - cy.visit('http://localhost:3000/index.html#/changelog'); - - cy.get('#main').should('contain', 'Bug Fixes'); - }) - - it('returns 404 for index file with leading fragment', () => { - cy.visit('http://localhost:3000/#/index.html/'); - - cy.get('#main').should('contain', '404'); - }) - - it('returns 500 for index file as folder', () => { - cy.request({ - url: 'http://localhost:3000/index.html/#/', - failOnStatusCode: false - }).then((resp) => expect(500).to.eq(resp.status)) - }) - - it('does not serve shadowing index markdown file', () => { - cy.request({ - url: 'http://localhost:3000/index.md', - failOnStatusCode: false - }).then((resp) => expect(404).to.eq(resp.status)) - }) -}); diff --git a/test/e2e/index-file.test.js b/test/e2e/index-file.test.js new file mode 100644 index 000000000..e8d7c2554 --- /dev/null +++ b/test/e2e/index-file.test.js @@ -0,0 +1,18 @@ +const docsifyInit = require('../helpers/docsify-init'); + +describe(`Index file hosting`, function() { + const sharedConfig = { + basePath: `${TEST_HOST}/docs/index.html`, + }; + + test('should serve from index file', async () => { + await docsifyInit({ + config: sharedConfig, + }); + + await expect(page).toHaveText( + '#main', + 'A magical documentation site generator' + ); + }); +}); From f39e07c7b2da951b46460a3ae360631f440eb112 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Fri, 5 Feb 2021 11:17:54 +0000 Subject: [PATCH 07/11] Add e2e test for new Jest test framework --- test/e2e/index-file.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/index-file.test.js b/test/e2e/index-file.test.js index e8d7c2554..bbd8e7f93 100644 --- a/test/e2e/index-file.test.js +++ b/test/e2e/index-file.test.js @@ -2,7 +2,7 @@ const docsifyInit = require('../helpers/docsify-init'); describe(`Index file hosting`, function() { const sharedConfig = { - basePath: `${TEST_HOST}/docs/index.html`, + basePath: `${TEST_HOST}/docs/index.html#/`, }; test('should serve from index file', async () => { From 5e4c79011d1f4ea969879624b234be32d4758d03 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Fri, 5 Feb 2021 11:55:40 +0000 Subject: [PATCH 08/11] Verify sidebar links use file hosting --- test/e2e/index-file.test.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/e2e/index-file.test.js b/test/e2e/index-file.test.js index bbd8e7f93..06ac47135 100644 --- a/test/e2e/index-file.test.js +++ b/test/e2e/index-file.test.js @@ -1,18 +1,28 @@ const docsifyInit = require('../helpers/docsify-init'); describe(`Index file hosting`, function() { - const sharedConfig = { - basePath: `${TEST_HOST}/docs/index.html#/`, + const sharedOptions = { + config: { + basePath: `${TEST_HOST}/docs/index.html#/`, + }, + testURL: `${TEST_HOST}/docs/index.html#/`, }; test('should serve from index file', async () => { - await docsifyInit({ - config: sharedConfig, - }); + 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$/); }); }); From ac5ed770ff25ee7ebb52bf893d1269b30580549b Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Sat, 6 Feb 2021 13:12:14 +0000 Subject: [PATCH 09/11] Fix: endsWith() not supported for IE11 --- src/core/router/history/hash.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index b6e47786e..198c07b4f 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -8,6 +8,10 @@ function replaceHash(path) { location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path); } +function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} + export class HashHistory extends History { constructor(config) { super(config); @@ -23,7 +27,7 @@ export class HashHistory extends History { // prevents the `/index.html` part of the URI from being // remove during routing. // See here: https://github.com/docsifyjs/docsify/pull/1372 - const basePath = path.endsWith('.html') + const basePath = endsWith(path, '.html') ? path + '#/' + base : path + '/' + base; return /^(\/|https?:)/g.test(base) ? base : cleanPath(basePath); From 831527f389d0872bb5e37536f705d4254781d247 Mon Sep 17 00:00:00 2001 From: Ricardo Gladwell Date: Sat, 6 Feb 2021 13:41:35 +0000 Subject: [PATCH 10/11] Refactor: utility method moved to utility file --- src/core/router/history/hash.js | 7 +------ src/core/router/util.js | 4 ++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index 198c07b4f..a2a52aee4 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -1,17 +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); } - -function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; -} - export class HashHistory extends History { constructor(config) { super(config); diff --git a/src/core/router/util.js b/src/core/router/util.js index 35d32c355..4b00d081a 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -113,3 +113,7 @@ export function getPath(...args) { export const replaceSlug = cached(path => { return path.replace('#', '?id='); }); + +export function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +} From d94d259ac0390f5c865ae619a7a18232cbf0709f Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Sun, 7 Feb 2021 01:02:19 -0600 Subject: [PATCH 11/11] Fix IE11 error from use of String.includes() --- src/core/router/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/router/util.js b/src/core/router/util.js index 4b00d081a..aec6153ce 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -102,7 +102,7 @@ export const resolvePath = cached(path => { function normaliseFragment(path) { return path .split('/') - .filter(p => !p.includes('#')) + .filter(p => p.indexOf('#') === -1) .join('/'); }