diff --git a/docs/_assets/anothertest.md b/docs/_assets/anothertest.md new file mode 100644 index 0000000000..618cf0d514 --- /dev/null +++ b/docs/_assets/anothertest.md @@ -0,0 +1,3 @@ +# Another test + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/docs/_assets/manual.pdf b/docs/_assets/manual.pdf new file mode 100644 index 0000000000..7cbd9be12c Binary files /dev/null and b/docs/_assets/manual.pdf differ diff --git a/docs/_assets/test.txt b/docs/_assets/test.txt new file mode 100644 index 0000000000..60d1b4c6d8 --- /dev/null +++ b/docs/_assets/test.txt @@ -0,0 +1,3 @@ +This is text.txt + +Hello world! diff --git a/docs/test.md b/docs/test.md new file mode 100644 index 0000000000..cd4a3d9157 --- /dev/null +++ b/docs/test.md @@ -0,0 +1,8 @@ +# Test + +- [test.txt](./_assets/test.txt) +- [anothertest.md](./_assets/anothertest.md) +- [manual.pdf](./_assets/manual.pdf) +- [https://docsify.js.org/quickstart.md](https://docsify.js.org/quickstart.md) +- https://docsify.js.org/quickstart.md + diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index aa7e195d90..cbc25e9cd9 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -import { getParentPath, stringifyQuery } from '../router/util.js'; +import { getParentPath, stringifyQuery, getExtension } from '../router/util.js'; import { noop, isExternal } from '../util/core.js'; import { get } from '../util/ajax.js'; @@ -84,68 +84,87 @@ export function Fetch(Base) { _fetch(cb = noop) { const { query } = this.route; let { path } = this.route; + const { ext } = this.config; - // Prevent loading remote content via URL hash - // Ex: https://foo.com/#//bar.com/file.md if (isExternal(path)) { - history.replaceState(null, '', '#'); - this.router.normalize(); - } else { - const qs = stringifyQuery(query, ['id']); - const { loadNavbar, requestHeaders, loadSidebar } = this.config; - // Abort last request + // Prevent loading remote content via URL hash + // Ex: https://foo.com/#//bar.com/file.md + this._fetchExternal(); + } else { + const pathExt = getExtension(path); const file = this.router.getFile(path); - this.isRemoteUrl = isExternal(file); - // Current page is html - this.isHTML = /\.html$/g.test(file); + if (pathExt && pathExt !== ext && pathExt !== '.html') { + this._fetchNonContent(file); + } else { + this._fetchContent(path, query, file, cb); + } + } + } - // create a handler that should be called if content was fetched successfully - const contentFetched = (text, opt) => { - this._renderMain( - text, - opt, - this._loadSideAndNav(path, qs, loadSidebar, cb) - ); - }; + _fetchExternal() { + history.replaceState(null, '', '#'); + this.router.normalize(); + } - // and a handler that is called if content failed to fetch - const contentFailedToFetch = _error => { - this._fetchFallbackPage(path, qs, cb) || this._fetch404(file, qs, cb); - }; + _fetchContent(path, query, file, cb) { + const qs = stringifyQuery(query, ['id']); + const { loadNavbar, requestHeaders, loadSidebar } = this.config; + // Abort last request + + this.isRemoteUrl = isExternal(file); + // Current page is html + this.isHTML = /\.html$/g.test(file); + + // create a handler that should be called if content was fetched successfully + const contentFetched = (text, opt) => { + this._renderMain( + text, + opt, + this._loadSideAndNav(path, qs, loadSidebar, cb) + ); + }; - // attempt to fetch content from a virtual route, and fallback to fetching the actual file - if (!this.isRemoteUrl) { - this.matchVirtualRoute(path).then(contents => { - if (typeof contents === 'string') { - contentFetched(contents); - } else { - this.#request(file + qs, requestHeaders).then( - contentFetched, - contentFailedToFetch - ); - } - }); - } else { - // if the requested url is not local, just fetch the file - this.#request(file + qs, requestHeaders).then( - contentFetched, - contentFailedToFetch - ); - } + // and a handler that is called if content failed to fetch + const contentFailedToFetch = _error => { + this._fetchFallbackPage(path, qs, cb) || this._fetch404(file, qs, cb); + }; - // Load nav - loadNavbar && - this.#loadNested( - path, - qs, - loadNavbar, - text => this._renderNav(text), - this, - true - ); + // attempt to fetch content from a virtual route, and fallback to fetching the actual file + if (!this.isRemoteUrl) { + this.matchVirtualRoute(path).then(contents => { + if (typeof contents === 'string') { + contentFetched(contents); + } else { + this.#request(file + qs, requestHeaders).then( + contentFetched, + contentFailedToFetch + ); + } + }); + } else { + // if the requested url is not local, just fetch the file + this.#request(file + qs, requestHeaders).then( + contentFetched, + contentFailedToFetch + ); } + + // Load nav + loadNavbar && + this.#loadNested( + path, + qs, + loadNavbar, + text => this._renderNav(text), + this, + true + ); + } + + _fetchNonContent(file) { + window.location.replace(file); } _fetchCover() { diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 3514136a46..b680f553a9 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -231,6 +231,7 @@ export class Compiler { origin.code = highlightCodeCompiler({ renderer }); origin.link = linkCompiler({ renderer, + contentBase, router, linkTarget, linkRel, diff --git a/src/core/render/compiler/link.js b/src/core/render/compiler/link.js index c884b2b909..02d604284f 100644 --- a/src/core/render/compiler/link.js +++ b/src/core/render/compiler/link.js @@ -1,5 +1,5 @@ import { getAndRemoveConfig } from '../utils.js'; -import { isAbsolutePath } from '../../router/util.js'; +import { isAbsolutePath, getPath, getParentPath } from '../../router/util.js'; export const linkCompiler = ({ renderer, @@ -18,29 +18,21 @@ export const linkCompiler = ({ : ''; title = str; - if ( - !isAbsolutePath(href) && - !compilerClass._matchNotCompileLink(href) && - !config.ignore - ) { - if (href === compilerClass.config.homepage) { - href = 'README'; + if (!config.ignore && !compilerClass._matchNotCompileLink(href)) { + if (!isAbsolutePath(href)) { + href = router.toURL(href, null, router.getCurrentPath()); + } else { + attrs.push( + href.indexOf('mailto:') === 0 ? '' : `target="${linkTarget}"` + ); + attrs.push( + href.indexOf('mailto:') === 0 + ? '' + : linkRel !== '' + ? ` rel="${linkRel}"` + : '' + ); } - - href = router.toURL(href, null, router.getCurrentPath()); - } else { - if (!isAbsolutePath(href) && href.slice(0, 2) === './') { - href = - document.URL.replace(/\/(?!.*\/).*/, '/').replace('#/./', '') + href; - } - attrs.push(href.indexOf('mailto:') === 0 ? '' : `target="${linkTarget}"`); - attrs.push( - href.indexOf('mailto:') === 0 - ? '' - : linkRel !== '' - ? ` rel="${linkRel}"` - : '' - ); } if (config.disabled) { diff --git a/src/core/router/history/base.js b/src/core/router/history/base.js index b36e48eb91..17a7e30eac 100644 --- a/src/core/router/history/base.js +++ b/src/core/router/history/base.js @@ -5,6 +5,7 @@ import { cleanPath, replaceSlug, resolvePath, + getExtension, } from '../util.js'; import { noop } from '../../util/core.js'; @@ -32,11 +33,19 @@ export class History { } #getFileName(path, ext) { - return new RegExp(`\\.(${ext.replace(/^\./, '')}|html)$`, 'g').test(path) - ? path - : /\/$/g.test(path) - ? `${path}README${ext}` - : `${path}${ext}`; + const pathExt = getExtension(path); + const endsWithSlash = /\/$/g; + + let filename; + if (pathExt) { + filename = path; + } else if (endsWithSlash.test(path)) { + filename = `${path}README${ext}`; + } else { + filename = `${path}${ext}`; + } + + return filename; } getBasePath() { diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index 9853e7ecc0..26cd9eb05e 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -18,7 +18,7 @@ export class HashHistory extends History { // 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. + // removed during routing. // See here: https://github.com/docsifyjs/docsify/pull/1372 const basePath = path.endsWith('.html') ? path + '#/' + base diff --git a/src/core/router/util.js b/src/core/router/util.js index e3f7608b3d..36706b145f 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -112,3 +112,13 @@ export function getPath(...args) { export const replaceSlug = cached(path => { return path.replace('#', '?id='); }); + +export function getExtension(path) { + const matched = path.match(/\.\w+$/); + + if (!matched) { + return null; + } + + return matched[0]; +} diff --git a/test/integration/render.test.js b/test/integration/render.test.js index 8b65f11ca0..6e91279d32 100644 --- a/test/integration/render.test.js +++ b/test/integration/render.test.js @@ -263,5 +263,47 @@ describe('render', function () { `"

alt text

"` ); }); + + test('relative link with md extension', async function () { + const output = window.marked('[alt text](test.md)'); + + expect(output).toMatchInlineSnapshot( + `"

alt text

"` + ); + }); + + test('relative link with md extension starting with "./"', async function () { + const output = window.marked('[alt text](./test.md)'); + + expect(output).toMatchInlineSnapshot( + `"

alt text

"` + ); + }); + + test('relative link with extension other than md', async function () { + const output = window.marked('[alt text](test.txt)'); + + expect(output).toMatchInlineSnapshot( + `"

alt text

"` + ); + }); + + test('relative link with extension other than md starting with "./"', async function () { + const output = window.marked('[alt text](./test.txt)'); + + expect(output).toMatchInlineSnapshot( + `"

alt text

"` + ); + }); + + test('absolute link with md extension', async function () { + const output = window.marked( + '[alt text](http://www.example.com/test.md)' + ); + + expect(output).toMatchInlineSnapshot( + `"

alt text

"` + ); + }); }); });