diff --git a/docs/_assets/anothertest.md b/docs/_assets/anothertest.md new file mode 100644 index 000000000..618cf0d51 --- /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 000000000..7cbd9be12 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 000000000..60d1b4c6d --- /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 000000000..cd4a3d915 --- /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 aa7e195d9..cbc25e9cd 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 3514136a4..b680f553a 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 c884b2b90..02d604284 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 b36e48eb9..17a7e30ea 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 9853e7ecc..26cd9eb05 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 e3f7608b3..36706b145 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 8b65f11ca..6e91279d3 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

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