diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index afb49ae0bf..192aab8d65 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -285,7 +285,6 @@ export default function pluginContentDocs( { loader: path.resolve(__dirname, './markdown/index.js'), options: { - siteConfig: context.siteConfig, siteDir: context.siteDir, docsDir: contentPath, sourceToPermalink: sourceToPermalink, diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc1.md b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc1.md new file mode 100644 index 0000000000..f1ec64b8de --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc1.md @@ -0,0 +1,11 @@ +# Don't transform any link here + +![image1](assets/image1.png) + +# Don't replace inside fenced codeblock +```md +![doc4](doc4.md) +``` + +### Non-existing Docs +- [hahaha](hahaha.md) diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc2.md b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc2.md new file mode 100644 index 0000000000..50407eb435 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc2.md @@ -0,0 +1,10 @@ +### Existing Docs + +- [doc1](doc1.md) +- [doc2](./doc2.md) +- [doc3](subdir/doc3.md) + +## Repeating Docs + +- [doc1](doc1.md) +- [doc2](./doc2.md) \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc4.md b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc4.md new file mode 100644 index 0000000000..4b9155e038 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/doc4.md @@ -0,0 +1,18 @@ +### Existing Docs + +- [doc1][doc1] +- [doc2][doc2] + +## Repeating Docs + +- [doc1][doc1] +- [doc2][doc2] + +## Do not replace this +```md +![image1][image1] +``` + +[doc1]: doc1.md +[doc2]: ./doc2.md +[image1]: assets/image1.png diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/subdir/doc3.md b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/subdir/doc3.md new file mode 100644 index 0000000000..6da3139921 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/docs/subdir/doc3.md @@ -0,0 +1,2 @@ +### Relative linking +- [doc1](../doc2.md) diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap new file mode 100644 index 0000000000..c00708c8f4 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transform nothing 1`] = ` +"# Don't transform any link here + +![image1](assets/image1.png) + +# Don't replace inside fenced codeblock +\`\`\`md +![doc4](doc4.md) +\`\`\` + +### Non-existing Docs +- [hahaha](hahaha.md) +" +`; + +exports[`transform relative links 1`] = ` +"### Relative linking +- [doc1](/docs/doc2) +" +`; + +exports[`transform to correct links 1`] = ` +"### Existing Docs + +- [doc1](/docs/doc1) +- [doc2](/docs/doc2) +- [doc3](/docs/subdir/doc3) + +## Repeating Docs + +- [doc1](/docs/doc1) +- [doc2](/docs/doc2)" +`; + +exports[`transforms reference links 1`] = ` +"### Existing Docs + +- [doc1][doc1] +- [doc2][doc2] + +## Repeating Docs + +- [doc1][doc1] +- [doc2][doc2] + +## Do not replace this +\`\`\`md +![image1][image1] +\`\`\` + +[doc1]: /docs/doc1 +[doc2]: /docs/doc2 +[image1]: assets/image1.png +" +`; diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts new file mode 100644 index 0000000000..3fbd8616ba --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import fs from 'fs-extra'; +import path from 'path'; +import linkify from '../linkify'; +import {SourceToPermalink} from '../../types'; + +const siteDir = path.join(__dirname, '__fixtures__'); +const docsDir = path.join(siteDir, 'docs'); +const sourceToPermalink: SourceToPermalink = { + '@site/docs/doc1.md': '/docs/doc1', + '@site/docs/doc2.md': '/docs/doc2', + '@site/docs/subdir/doc3.md': '/docs/subdir/doc3', + '@site/docs/doc4.md': '/docs/doc4', +}; + +const transform = filepath => { + const content = fs.readFileSync(filepath, 'utf-8'); + const transformedContent = linkify( + content, + filepath, + docsDir, + siteDir, + sourceToPermalink, + ); + return [content, transformedContent]; +}; + +test('transform nothing', () => { + const doc1 = path.join(docsDir, 'doc1.md'); + const [content, transformedContent] = transform(doc1); + expect(transformedContent).toMatchSnapshot(); + expect(content).toEqual(transformedContent); +}); + +test('transform to correct links', () => { + const doc2 = path.join(docsDir, 'doc2.md'); + const [content, transformedContent] = transform(doc2); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('](/docs/doc1'); + expect(transformedContent).toContain('](/docs/doc2'); + expect(transformedContent).toContain('](/docs/subdir/doc3'); + expect(transformedContent).not.toContain('](doc1.md)'); + expect(transformedContent).not.toContain('](./doc2.md)'); + expect(transformedContent).not.toContain('](subdir/doc3.md)'); + expect(content).not.toEqual(transformedContent); +}); + +test('transform relative links', () => { + const doc3 = path.join(docsDir, 'subdir', 'doc3.md'); + const [content, transformedContent] = transform(doc3); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('](/docs/doc2'); + expect(transformedContent).not.toContain('](../doc2.md)'); + expect(content).not.toEqual(transformedContent); +}); + +test('transforms reference links', () => { + const doc4 = path.join(docsDir, 'doc4.md'); + const [content, transformedContent] = transform(doc4); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('[doc1]: /docs/doc1'); + expect(transformedContent).toContain('[doc2]: /docs/doc2'); + expect(transformedContent).not.toContain('[doc1]: doc1.md'); + expect(transformedContent).not.toContain('[doc2]: ./doc2.md'); + expect(content).not.toEqual(transformedContent); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/index.ts b/packages/docusaurus-plugin-content-docs/src/markdown/index.ts index e577937ea6..407b2c3709 100644 --- a/packages/docusaurus-plugin-content-docs/src/markdown/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/markdown/index.ts @@ -5,60 +5,24 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; import {getOptions} from 'loader-utils'; -import {resolve} from 'url'; import {loader} from 'webpack'; +import linkify from './linkify'; export = function(fileString: string) { const callback = this.async(); - const options = Object.assign({}, getOptions(this), { - filepath: this.resourcePath, - }); - const {docsDir, siteDir, sourceToPermalink} = options; - - // Determine the source dir. e.g: /docs, /website/versioned_docs/version-1.0.0 - let sourceDir: string | undefined; - const thisSource = this.resourcePath; - if (thisSource.startsWith(docsDir)) { - sourceDir = docsDir; - } - - let content = fileString; - - // Replace internal markdown linking (except in fenced blocks). - if (sourceDir) { - let fencedBlock = false; - const lines = content.split('\n').map(line => { - if (line.trim().startsWith('```')) { - fencedBlock = !fencedBlock; - } - if (fencedBlock) return line; - - let modifiedLine = line; - // Replace inline-style links or reference-style links e.g: - // This is [Document 1](doc1.md) -> we replace this doc1.md with correct link - // [doc1]: doc1.md -> we replace this doc1.md with correct link - const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g; - let mdMatch = mdRegex.exec(modifiedLine); - while (mdMatch !== null) { - // Replace it to correct html link. - const mdLink = mdMatch[1]; - const targetSource = `${sourceDir}/${mdLink}`; - const aliasedSource = (source: string) => - `@site/${path.relative(siteDir, source)}`; - const permalink = - sourceToPermalink[aliasedSource(resolve(thisSource, mdLink))] || - sourceToPermalink[aliasedSource(targetSource)]; - if (permalink) { - modifiedLine = modifiedLine.replace(mdLink, permalink); - } - mdMatch = mdRegex.exec(modifiedLine); - } - return modifiedLine; - }); - content = lines.join('\n'); - } - - return callback && callback(null, content); + const {docsDir, siteDir, sourceToPermalink} = getOptions(this); + return ( + callback && + callback( + null, + linkify( + fileString, + this.resourcePath, + docsDir, + siteDir, + sourceToPermalink, + ), + ) + ); } as loader.Loader; diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts b/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts new file mode 100644 index 0000000000..0d3e360eb3 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import {resolve} from 'url'; +import {SourceToPermalink} from '../types'; + +export default function( + fileString: string, + filePath: string, + docsDir: string, + siteDir: string, + sourceToPermalink: SourceToPermalink, +) { + // Determine the source dir. e.g: /website/docs, /website/versioned_docs/version-1.0.0 + let sourceDir: string | undefined; + const thisSource = filePath; + if (thisSource.startsWith(docsDir)) { + sourceDir = docsDir; + } + + let content = fileString; + + // Replace internal markdown linking (except in fenced blocks). + if (sourceDir) { + let fencedBlock = false; + const lines = content.split('\n').map(line => { + if (line.trim().startsWith('```')) { + fencedBlock = !fencedBlock; + } + if (fencedBlock) return line; + + let modifiedLine = line; + // Replace inline-style links or reference-style links e.g: + // This is [Document 1](doc1.md) -> we replace this doc1.md with correct link + // [doc1]: doc1.md -> we replace this doc1.md with correct link + const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g; + let mdMatch = mdRegex.exec(modifiedLine); + while (mdMatch !== null) { + // Replace it to correct html link. + const mdLink = mdMatch[1]; + const targetSource = `${sourceDir}/${mdLink}`; + const aliasedSource = (source: string) => + `@site/${path.relative(siteDir, source)}`; + const permalink = + sourceToPermalink[aliasedSource(resolve(thisSource, mdLink))] || + sourceToPermalink[aliasedSource(targetSource)]; + if (permalink) { + modifiedLine = modifiedLine.replace(mdLink, permalink); + } + mdMatch = mdRegex.exec(modifiedLine); + } + return modifiedLine; + }); + content = lines.join('\n'); + } + + return content; +}