refactor(v2): merge linkify function used in blog and docs and align properties (#4402)

* refactor(v2): merge linkify function used in blog and docs

* refactor(v2): rename docsDirPath and docsDirPathLocalized ad update types

* refactor(v2): rename blogPostsBySource and update types

* improve replaceMarkdownLinks api

Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
Armano 2021-03-12 15:11:08 +01:00 committed by GitHub
parent bfe52cdae3
commit 2f53b1a895
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 240 additions and 237 deletions

View file

@ -7,7 +7,7 @@
import fs from 'fs-extra';
import path from 'path';
import {linkify, LinkifyParams, getPostsBySource} from '../blogUtils';
import {linkify, LinkifyParams, getSourceToPermalink} from '../blogUtils';
import {BlogBrokenMarkdownLink, BlogContentPaths, BlogPost} from '../types';
const siteDir = path.join(__dirname, '__fixtures__', 'website');
@ -43,10 +43,10 @@ const transform = (filePath: string, options?: Partial<LinkifyParams>) => {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const transformedContent = linkify({
filePath,
fileContent,
fileString: fileContent,
siteDir,
contentPaths,
blogPostsBySource: getPostsBySource(blogPosts),
sourceToPermalink: getSourceToPermalink(blogPosts),
onBrokenMarkdownLink: (brokenMarkdownLink) => {
throw new Error(
`Broken markdown link found: ${JSON.stringify(brokenMarkdownLink)}`,
@ -82,12 +82,12 @@ test('report broken markdown links', () => {
expect(onBrokenMarkdownLink).toHaveBeenCalledTimes(2);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(1, {
filePath: path.resolve(folderPath, filePath),
folderPath,
contentPaths,
link: 'postNotExist1.md',
} as BlogBrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, {
filePath: path.resolve(folderPath, filePath),
folderPath,
contentPaths,
link: './postNotExist2.mdx',
} as BlogBrokenMarkdownLink);
});

View file

@ -9,16 +9,14 @@ import fs from 'fs-extra';
import globby from 'globby';
import chalk from 'chalk';
import path from 'path';
import {resolve} from 'url';
import readingTime from 'reading-time';
import {Feed} from 'feed';
import {keyBy} from 'lodash';
import {keyBy, mapValues} from 'lodash';
import {
PluginOptions,
BlogPost,
DateLink,
BlogContentPaths,
BlogBrokenMarkdownLink,
BlogMarkdownLoaderOptions,
} from './types';
import {
@ -31,15 +29,19 @@ import {
getDateTimeFormat,
} from '@docusaurus/utils';
import {LoadContext} from '@docusaurus/types';
import {replaceMarkdownLinks} from '@docusaurus/utils/lib/markdownLinks';
export function truncate(fileString: string, truncateMarker: RegExp): string {
return fileString.split(truncateMarker, 1).shift()!;
}
export function getPostsBySource(
export function getSourceToPermalink(
blogPosts: BlogPost[],
): Record<string, BlogPost> {
return keyBy(blogPosts, (item) => item.metadata.source);
): Record<string, string> {
return mapValues(
keyBy(blogPosts, (item) => item.metadata.source),
(v) => v.metadata.permalink,
);
}
// YYYY-MM-DD-{name}.mdx?
@ -250,73 +252,31 @@ export async function generateBlogPosts(
export type LinkifyParams = {
filePath: string;
fileContent: string;
fileString: string;
} & Pick<
BlogMarkdownLoaderOptions,
'blogPostsBySource' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'
'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'
>;
export function linkify({
filePath,
contentPaths,
fileContent,
fileString,
siteDir,
blogPostsBySource,
sourceToPermalink,
onBrokenMarkdownLink,
}: LinkifyParams): string {
// TODO temporary, should consider the file being in localized folder!
const folderPath = contentPaths.contentPath;
let fencedBlock = false;
const lines = fileContent.split('\n').map((line) => {
if (line.trim().startsWith('```')) {
fencedBlock = !fencedBlock;
}
if (fencedBlock) {
return line;
}
let modifiedLine = line;
const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g;
let mdMatch = mdRegex.exec(modifiedLine);
while (mdMatch !== null) {
const mdLink = mdMatch[1];
const aliasedSource = (source: string) =>
aliasedSitePath(source, siteDir);
const blogPost: BlogPost | undefined =
blogPostsBySource[aliasedSource(resolve(filePath, mdLink))] ||
blogPostsBySource[
aliasedSource(`${contentPaths.contentPathLocalized}/${mdLink}`)
] ||
blogPostsBySource[
aliasedSource(`${contentPaths.contentPath}/${mdLink}`)
];
if (blogPost) {
modifiedLine = modifiedLine.replace(
mdLink,
blogPost.metadata.permalink,
);
} else {
const brokenMarkdownLink: BlogBrokenMarkdownLink = {
folderPath,
const {newContent, brokenMarkdownLinks} = replaceMarkdownLinks({
siteDir,
fileString,
filePath,
link: mdLink,
};
onBrokenMarkdownLink(brokenMarkdownLink);
}
mdMatch = mdRegex.exec(modifiedLine);
}
return modifiedLine;
contentPaths,
sourceToPermalink,
});
return lines.join('\n');
brokenMarkdownLinks.forEach((l) => onBrokenMarkdownLink(l));
return newContent;
}
// Order matters: we look in priority in localized folder

View file

@ -50,7 +50,7 @@ import {
generateBlogFeed,
generateBlogPosts,
getContentPathList,
getPostsBySource,
getSourceToPermalink,
} from './blogUtils';
export default function pluginContentBlog(
@ -416,7 +416,7 @@ export default function pluginContentBlog(
siteDir,
contentPaths,
truncateMarker,
blogPostsBySource: getPostsBySource(blogPosts),
sourceToPermalink: getSourceToPermalink(blogPosts),
onBrokenMarkdownLink: (brokenMarkdownLink) => {
if (onBrokenMarkdownLinks === 'ignore') {
return;

View file

@ -12,13 +12,13 @@ import {BlogMarkdownLoaderOptions} from './types';
const markdownLoader: loader.Loader = function (source) {
const filePath = this.resourcePath;
const fileContent = source as string;
const fileString = source as string;
const callback = this.async();
const markdownLoaderOptions = getOptions(this) as BlogMarkdownLoaderOptions;
// Linkify blog posts
let finalContent = linkify({
fileContent,
fileString,
filePath,
...markdownLoaderOptions,
});

View file

@ -5,10 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/
export type BlogContentPaths = {
contentPath: string;
contentPathLocalized: string;
};
import {
BrokenMarkdownLink,
ContentPaths,
} from '@docusaurus/utils/lib/markdownLinks';
export type BlogContentPaths = ContentPaths;
export interface BlogContent {
blogPosts: BlogPost[];
@ -142,15 +144,11 @@ export interface TagModule {
permalink: string;
}
export type BlogBrokenMarkdownLink = {
folderPath: string;
filePath: string;
link: string;
};
export type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
export type BlogMarkdownLoaderOptions = {
siteDir: string;
contentPaths: BlogContentPaths;
truncateMarker: RegExp;
blogPostsBySource: Record<string, BlogPost>;
sourceToPermalink: Record<string, string>;
onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
};

View file

@ -60,6 +60,8 @@ exports[`translateLoadedContent should return translated loaded content matching
Object {
"loadedVersions": Array [
Object {
"contentPath": "any",
"contentPathLocalized": "any",
"docs": Array [
Object {
"description": "doc1 description",
@ -147,8 +149,6 @@ Object {
"version": "any",
},
],
"docsDirPath": "any",
"docsDirPathLocalized": "any",
"isLast": true,
"mainDocId": "",
"permalinkToSidebar": Object {},
@ -201,6 +201,8 @@ Object {
"versionPath": "/docs/",
},
Object {
"contentPath": "any",
"contentPathLocalized": "any",
"docs": Array [
Object {
"description": "doc1 description",
@ -288,8 +290,6 @@ Object {
"version": "any",
},
],
"docsDirPath": "any",
"docsDirPathLocalized": "any",
"isLast": true,
"mainDocId": "",
"permalinkToSidebar": Object {},
@ -342,6 +342,8 @@ Object {
"versionPath": "/docs/",
},
Object {
"contentPath": "any",
"contentPathLocalized": "any",
"docs": Array [
Object {
"description": "doc1 description",
@ -429,8 +431,6 @@ Object {
"version": "any",
},
],
"docsDirPath": "any",
"docsDirPathLocalized": "any",
"isLast": true,
"mainDocId": "",
"permalinkToSidebar": Object {},

View file

@ -45,7 +45,7 @@ ${markdown}
source,
content,
lastUpdate: {},
docsDirPath: 'docs',
contentPath: 'docs',
filePath: source,
};
};
@ -93,7 +93,7 @@ function createTestUtils({
editUrl: undefined,
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, versionMetadata.docsDirPath)),
posixPath(path.relative(siteDir, versionMetadata.contentPath)),
posixPath(docFileSource),
),
...expectedMetadata,

View file

@ -254,7 +254,7 @@ describe('simple website', () => {
sidebar: 'docs',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, currentVersion.docsDirPath)),
posixPath(path.relative(siteDir, currentVersion.contentPath)),
'hello.md',
),
title: 'Hello, World !',
@ -276,7 +276,7 @@ describe('simple website', () => {
sidebar: 'docs',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, currentVersion.docsDirPath)),
posixPath(path.relative(siteDir, currentVersion.contentPath)),
'foo',
'bar.md',
),
@ -424,7 +424,7 @@ describe('versioned website', () => {
slug: '/foo/barSlug',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, currentVersion.docsDirPath)),
posixPath(path.relative(siteDir, currentVersion.contentPath)),
'foo',
'bar.md',
),
@ -446,7 +446,7 @@ describe('versioned website', () => {
slug: '/',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, currentVersion.docsDirPath)),
posixPath(path.relative(siteDir, currentVersion.contentPath)),
'hello.md',
),
title: 'hello',
@ -467,7 +467,7 @@ describe('versioned website', () => {
slug: '/',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, version101.docsDirPath)),
posixPath(path.relative(siteDir, version101.contentPath)),
'hello.md',
),
title: 'hello',
@ -488,7 +488,7 @@ describe('versioned website', () => {
slug: '/foo/baz',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, version100.docsDirPath)),
posixPath(path.relative(siteDir, version100.contentPath)),
'foo',
'baz.md',
),
@ -649,7 +649,7 @@ describe('versioned website (community)', () => {
slug: '/team',
source: path.posix.join(
'@site',
posixPath(path.relative(siteDir, version100.docsDirPath)),
posixPath(path.relative(siteDir, version100.contentPath)),
'team.md',
),
title: 'team',

View file

@ -44,8 +44,8 @@ function createSampleVersion(
routePriority: undefined,
sidebarFilePath: 'any',
isLast: true,
docsDirPath: 'any',
docsDirPathLocalized: 'any',
contentPath: 'any',
contentPathLocalized: 'any',
docs: [
createSampleDoc({
id: 'doc1',

View file

@ -68,8 +68,8 @@ describe('simple site', () => {
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(simpleSiteDir, 'docs'),
docsDirPathLocalized: path.join(
contentPath: path.join(simpleSiteDir, 'docs'),
contentPathLocalized: path.join(
simpleSiteDir,
'i18n/en/docusaurus-plugin-content-docs/current',
),
@ -218,8 +218,8 @@ describe('versioned site, pluginId=default', () => {
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'docs'),
docsDirPathLocalized: path.join(
contentPath: path.join(versionedSiteDir, 'docs'),
contentPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/current',
),
@ -232,8 +232,8 @@ describe('versioned site, pluginId=default', () => {
};
const v101: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.1'),
docsDirPathLocalized: path.join(
contentPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.1'),
contentPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/version-1.0.1',
),
@ -249,8 +249,8 @@ describe('versioned site, pluginId=default', () => {
};
const v100: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.0'),
docsDirPathLocalized: path.join(
contentPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.0'),
contentPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/version-1.0.0',
),
@ -266,11 +266,11 @@ describe('versioned site, pluginId=default', () => {
};
const vwithSlugs: VersionMetadata = {
docsDirPath: path.join(
contentPath: path.join(
versionedSiteDir,
'versioned_docs/version-withSlugs',
),
docsDirPathLocalized: path.join(
contentPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs/version-withSlugs',
),
@ -615,8 +615,8 @@ describe('versioned site, pluginId=community', () => {
};
const vCurrent: VersionMetadata = {
docsDirPath: path.join(versionedSiteDir, 'community'),
docsDirPathLocalized: path.join(
contentPath: path.join(versionedSiteDir, 'community'),
contentPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs-community/current',
),
@ -629,11 +629,11 @@ describe('versioned site, pluginId=community', () => {
};
const v100: VersionMetadata = {
docsDirPath: path.join(
contentPath: path.join(
versionedSiteDir,
'community_versioned_docs/version-1.0.0',
),
docsDirPathLocalized: path.join(
contentPathLocalized: path.join(
versionedSiteDir,
'i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0',
),

View file

@ -67,23 +67,23 @@ async function readLastUpdateData(
export async function readDocFile(
versionMetadata: Pick<
VersionMetadata,
'docsDirPath' | 'docsDirPathLocalized'
'contentPath' | 'contentPathLocalized'
>,
source: string,
options: LastUpdateOptions,
): Promise<DocFile> {
const docsDirPath = await getFolderContainingFile(
const contentPath = await getFolderContainingFile(
getDocsDirPaths(versionMetadata),
source,
);
const filePath = path.join(docsDirPath, source);
const filePath = path.join(contentPath, source);
const [content, lastUpdate] = await Promise.all([
fs.readFile(filePath, 'utf-8'),
readLastUpdateData(filePath, options),
]);
return {source, content, lastUpdate, docsDirPath, filePath};
return {source, content, lastUpdate, contentPath, filePath};
}
export async function readVersionDocs(
@ -94,7 +94,7 @@ export async function readVersionDocs(
>,
): Promise<DocFile[]> {
const sources = await globby(options.include, {
cwd: versionMetadata.docsDirPath,
cwd: versionMetadata.contentPath,
});
return Promise.all(
sources.map((source) => readDocFile(versionMetadata, source, options)),
@ -112,7 +112,7 @@ export function processDocMetadata({
context: LoadContext;
options: MetadataOptions;
}): DocMetadataBase {
const {source, content, lastUpdate, docsDirPath, filePath} = docFile;
const {source, content, lastUpdate, contentPath, filePath} = docFile;
const {homePageId} = options;
const {siteDir, i18n} = context;
@ -170,20 +170,20 @@ export function processDocMetadata({
const permalink = normalizeUrl([versionMetadata.versionPath, docSlug]);
function getDocEditUrl() {
const relativeFilePath = path.relative(docsDirPath, filePath);
const relativeFilePath = path.relative(contentPath, filePath);
if (typeof options.editUrl === 'function') {
return options.editUrl({
version: versionMetadata.versionName,
versionDocsDirPath: posixPath(
path.relative(siteDir, versionMetadata.docsDirPath),
path.relative(siteDir, versionMetadata.contentPath),
),
docPath: posixPath(relativeFilePath),
permalink,
locale: context.i18n.currentLocale,
});
} else if (typeof options.editUrl === 'string') {
const isLocalized = docsDirPath === versionMetadata.docsDirPathLocalized;
const isLocalized = contentPath === versionMetadata.contentPathLocalized;
const baseVersionEditUrl =
isLocalized && options.editLocalizedFiles
? versionMetadata.versionEditUrlLocalized

View file

@ -145,7 +145,7 @@ export default function pluginContentDocs(
versionMetadata.versionName
} has no docs! At least one doc should exist at path=[${path.relative(
siteDir,
versionMetadata.docsDirPath,
versionMetadata.contentPath,
)}]`,
);
}
@ -337,7 +337,7 @@ export default function pluginContentDocs(
return;
}
reportMessage(
`Docs markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath} for version ${brokenMarkdownLink.version.versionName}`,
`Docs markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath} for version ${brokenMarkdownLink.contentPaths.versionName}`,
siteConfig.onBrokenMarkdownLinks,
);
},

View file

@ -18,19 +18,19 @@ import {VERSIONED_DOCS_DIR, CURRENT_VERSION_NAME} from '../../constants';
function createFakeVersion({
versionName,
docsDirPath,
docsDirPathLocalized,
contentPath,
contentPathLocalized,
}: {
versionName: string;
docsDirPath: string;
docsDirPathLocalized: string;
contentPath: string;
contentPathLocalized: string;
}): VersionMetadata {
return {
versionName,
versionLabel: 'Any',
versionPath: 'any',
docsDirPath,
docsDirPathLocalized,
contentPath,
contentPathLocalized,
sidebarFilePath: 'any',
routePriority: undefined,
isLast: false,
@ -41,8 +41,8 @@ const siteDir = path.join(__dirname, '__fixtures__');
const versionCurrent = createFakeVersion({
versionName: CURRENT_VERSION_NAME,
docsDirPath: path.join(siteDir, 'docs'),
docsDirPathLocalized: path.join(
contentPath: path.join(siteDir, 'docs'),
contentPathLocalized: path.join(
siteDir,
'i18n',
'fr',
@ -53,8 +53,8 @@ const versionCurrent = createFakeVersion({
const version100 = createFakeVersion({
versionName: '1.0.0',
docsDirPath: path.join(siteDir, VERSIONED_DOCS_DIR, 'version-1.0.0'),
docsDirPathLocalized: path.join(
contentPath: path.join(siteDir, VERSIONED_DOCS_DIR, 'version-1.0.0'),
contentPathLocalized: path.join(
siteDir,
'i18n',
'fr',
@ -97,14 +97,14 @@ const transform = (filepath: string, options?: Partial<DocsMarkdownOption>) => {
};
test('transform nothing', () => {
const doc1 = path.join(versionCurrent.docsDirPath, 'doc1.md');
const doc1 = path.join(versionCurrent.contentPath, 'doc1.md');
const [content, transformedContent] = transform(doc1);
expect(transformedContent).toMatchSnapshot();
expect(content).toEqual(transformedContent);
});
test('transform to correct links', () => {
const doc2 = path.join(versionCurrent.docsDirPath, 'doc2.md');
const doc2 = path.join(versionCurrent.contentPath, 'doc2.md');
const [content, transformedContent] = transform(doc2);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/doc1');
@ -119,7 +119,7 @@ test('transform to correct links', () => {
});
test('transform relative links', () => {
const doc3 = path.join(versionCurrent.docsDirPath, 'subdir', 'doc3.md');
const doc3 = path.join(versionCurrent.contentPath, 'subdir', 'doc3.md');
const [content, transformedContent] = transform(doc3);
expect(transformedContent).toMatchSnapshot();
@ -129,7 +129,7 @@ test('transform relative links', () => {
});
test('transforms reference links', () => {
const doc4 = path.join(versionCurrent.docsDirPath, 'doc4.md');
const doc4 = path.join(versionCurrent.contentPath, 'doc4.md');
const [content, transformedContent] = transform(doc4);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('[doc1]: /docs/doc1');
@ -140,7 +140,7 @@ test('transforms reference links', () => {
});
test('report broken markdown links', () => {
const doc5 = path.join(versionCurrent.docsDirPath, 'doc5.md');
const doc5 = path.join(versionCurrent.contentPath, 'doc5.md');
const onBrokenMarkdownLink = jest.fn();
const [content, transformedContent] = transform(doc5, {
onBrokenMarkdownLink,
@ -150,27 +150,27 @@ test('report broken markdown links', () => {
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(1, {
filePath: doc5,
link: 'docNotExist1.md',
version: versionCurrent,
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, {
filePath: doc5,
link: './docNotExist2.mdx',
version: versionCurrent,
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(3, {
filePath: doc5,
link: '../docNotExist3.mdx',
version: versionCurrent,
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(4, {
filePath: doc5,
link: './subdir/docNotExist4.md',
version: versionCurrent,
contentPaths: versionCurrent,
} as BrokenMarkdownLink);
});
test('transforms absolute links in versioned docs', () => {
const doc2 = path.join(version100.docsDirPath, 'doc2.md');
const doc2 = path.join(version100.contentPath, 'doc2.md');
const [content, transformedContent] = transform(doc2);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1');
@ -181,7 +181,7 @@ test('transforms absolute links in versioned docs', () => {
});
test('transforms relative links in versioned docs', () => {
const doc1 = path.join(version100.docsDirPath, 'subdir', 'doc1.md');
const doc1 = path.join(version100.contentPath, 'subdir', 'doc1.md');
const [content, transformedContent] = transform(doc1);
expect(transformedContent).toMatchSnapshot();
expect(transformedContent).toContain('](/docs/1.0.0/doc2');

View file

@ -5,14 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import {resolve} from 'url';
import {
DocsMarkdownOption,
VersionMetadata,
BrokenMarkdownLink,
} from '../types';
import {DocsMarkdownOption} from '../types';
import {getDocsDirPaths} from '../versions';
import {aliasedSitePath} from '@docusaurus/utils';
import {replaceMarkdownLinks} from '@docusaurus/utils/lib/markdownLinks';
function getVersion(filePath: string, options: DocsMarkdownOption) {
const versionFound = options.versionsMetadata.find((version) =>
@ -28,66 +23,22 @@ function getVersion(filePath: string, options: DocsMarkdownOption) {
return versionFound;
}
function replaceMarkdownLinks(
fileString: string,
filePath: string,
version: VersionMetadata,
options: DocsMarkdownOption,
) {
const {siteDir, sourceToPermalink, onBrokenMarkdownLink} = options;
const {docsDirPath, docsDirPathLocalized} = version;
// Replace internal markdown linking (except in fenced blocks).
let fencedBlock = false;
const lines = fileString.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 aliasedSource = (source: string) =>
aliasedSitePath(source, siteDir);
const permalink =
sourceToPermalink[aliasedSource(resolve(filePath, mdLink))] ||
sourceToPermalink[aliasedSource(`${docsDirPathLocalized}/${mdLink}`)] ||
sourceToPermalink[aliasedSource(`${docsDirPath}/${mdLink}`)];
if (permalink) {
modifiedLine = modifiedLine.replace(mdLink, permalink);
} else {
const brokenMarkdownLink: BrokenMarkdownLink = {
version,
filePath,
link: mdLink,
};
onBrokenMarkdownLink(brokenMarkdownLink);
}
mdMatch = mdRegex.exec(modifiedLine);
}
return modifiedLine;
});
return lines.join('\n');
}
export function linkify(
fileString: string,
filePath: string,
options: DocsMarkdownOption,
): string {
const version = getVersion(filePath, options);
return replaceMarkdownLinks(fileString, filePath, version, options);
const {siteDir, sourceToPermalink, onBrokenMarkdownLink} = options;
const {newContent, brokenMarkdownLinks} = replaceMarkdownLinks({
siteDir,
fileString,
filePath,
contentPaths: getVersion(filePath, options),
sourceToPermalink,
});
brokenMarkdownLinks.forEach((l) => onBrokenMarkdownLink(l));
return newContent;
}

View file

@ -8,8 +8,13 @@
// eslint-disable-next-line spaced-comment
/// <reference types="@docusaurus/module-type-aliases" />
import {
BrokenMarkdownLink as IBrokenMarkdownLink,
ContentPaths,
} from '@docusaurus/utils/lib/markdownLinks';
export type DocFile = {
docsDirPath: string; // /!\ may be localized
contentPath: string; // /!\ may be localized
filePath: string; // /!\ may be localized
source: string;
content: string;
@ -18,15 +23,15 @@ export type DocFile = {
export type VersionName = string;
export type VersionMetadata = {
export type VersionMetadata = ContentPaths & {
versionName: VersionName; // 1.0.0
versionLabel: string; // Version 1.0.0
versionPath: string; // /baseUrl/docs/1.0.0
versionEditUrl?: string | undefined;
versionEditUrlLocalized?: string | undefined;
isLast: boolean;
docsDirPath: string; // "versioned_docs/version-1.0.0"
docsDirPathLocalized: string; // "i18n/fr/version-1.0.0/default"
// contentPath: string; // "versioned_docs/version-1.0.0"
// contentPathLocalized: string; // "i18n/fr/version-1.0.0/default"
sidebarFilePath: string; // versioned_sidebars/1.0.0.json
routePriority: number | undefined; // -1 for the latest docs
};
@ -192,11 +197,7 @@ export type GlobalPluginData = {
versions: GlobalVersion[];
};
export type BrokenMarkdownLink = {
filePath: string;
version: VersionMetadata;
link: string;
};
export type BrokenMarkdownLink = IBrokenMarkdownLink<VersionMetadata>;
export type DocsMarkdownOption = {
versionsMetadata: VersionMetadata[];

View file

@ -165,18 +165,18 @@ function getVersionMetadataPaths({
options: Pick<PluginOptions, 'id' | 'path' | 'sidebarPath'>;
}): Pick<
VersionMetadata,
'docsDirPath' | 'docsDirPathLocalized' | 'sidebarFilePath'
'contentPath' | 'contentPathLocalized' | 'sidebarFilePath'
> {
const isCurrentVersion = versionName === CURRENT_VERSION_NAME;
const docsDirPath = isCurrentVersion
const contentPath = isCurrentVersion
? path.resolve(context.siteDir, options.path)
: path.join(
getVersionedDocsDirPath(context.siteDir, options.id),
`version-${versionName}`,
);
const docsDirPathLocalized = getDocsDirPathLocalized({
const contentPathLocalized = getDocsDirPathLocalized({
siteDir: context.siteDir,
locale: context.i18n.currentLocale,
pluginId: options.id,
@ -190,17 +190,17 @@ function getVersionMetadataPaths({
`version-${versionName}-sidebars.json`,
);
return {docsDirPath, docsDirPathLocalized, sidebarFilePath};
return {contentPath, contentPathLocalized, sidebarFilePath};
}
function getVersionEditUrls({
docsDirPath,
docsDirPathLocalized,
contentPath,
contentPathLocalized,
context: {siteDir, i18n},
options: {id, path: currentVersionPath, editUrl, editCurrentVersion},
}: {
docsDirPath: string;
docsDirPathLocalized: string;
contentPath: string;
contentPathLocalized: string;
context: Pick<LoadContext, 'siteDir' | 'i18n'>;
options: Pick<
PluginOptions,
@ -217,7 +217,7 @@ function getVersionEditUrls({
return undefined;
}
const editDirPath = editCurrentVersion ? currentVersionPath : docsDirPath;
const editDirPath = editCurrentVersion ? currentVersionPath : contentPath;
const editDirPathLocalized = editCurrentVersion
? getDocsDirPathLocalized({
siteDir,
@ -225,7 +225,7 @@ function getVersionEditUrls({
versionName: CURRENT_VERSION_NAME,
pluginId: id,
})
: docsDirPathLocalized;
: contentPathLocalized;
const versionPathSegment = posixPath(
path.relative(siteDir, path.resolve(siteDir, editDirPath)),
@ -269,8 +269,8 @@ function createVersionMetadata({
}): VersionMetadata {
const {
sidebarFilePath,
docsDirPath,
docsDirPathLocalized,
contentPath,
contentPathLocalized,
} = getVersionMetadataPaths({
versionName,
context,
@ -298,8 +298,8 @@ function createVersionMetadata({
]);
const versionEditUrls = getVersionEditUrls({
docsDirPath,
docsDirPathLocalized,
contentPath,
contentPathLocalized,
context,
options,
});
@ -316,8 +316,8 @@ function createVersionMetadata({
isLast,
routePriority,
sidebarFilePath,
docsDirPath,
docsDirPathLocalized,
contentPath,
contentPathLocalized,
};
}
@ -328,14 +328,14 @@ function checkVersionMetadataPaths({
versionMetadata: VersionMetadata;
context: Pick<LoadContext, 'siteDir'>;
}) {
const {versionName, docsDirPath, sidebarFilePath} = versionMetadata;
const {versionName, contentPath, sidebarFilePath} = versionMetadata;
const {siteDir} = context;
if (!fs.existsSync(docsDirPath)) {
if (!fs.existsSync(contentPath)) {
throw new Error(
`The docs folder does not exist for version [${versionName}]. A docs folder is expected to be found at ${path.relative(
siteDir,
docsDirPath,
contentPath,
)}`,
);
}
@ -483,8 +483,8 @@ export function readVersionsMetadata({
export function getDocsDirPaths(
versionMetadata: Pick<
VersionMetadata,
'docsDirPath' | 'docsDirPathLocalized'
'contentPath' | 'contentPathLocalized'
>,
): [string, string] {
return [versionMetadata.docsDirPathLocalized, versionMetadata.docsDirPath];
return [versionMetadata.contentPathLocalized, versionMetadata.contentPath];
}

View file

@ -0,0 +1,93 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {resolve} from 'url';
import {aliasedSitePath} from './index';
export type ContentPaths = {
contentPath: string;
contentPathLocalized: string;
};
export type BrokenMarkdownLink<T extends ContentPaths> = {
filePath: string;
contentPaths: T;
link: string;
};
export type ReplaceMarkdownLinksParams<T extends ContentPaths> = {
siteDir: string;
fileString: string;
filePath: string;
contentPaths: T;
sourceToPermalink: Record<string, string>;
};
export type ReplaceMarkdownLinksReturn<T extends ContentPaths> = {
newContent: string;
brokenMarkdownLinks: BrokenMarkdownLink<T>[];
};
export function replaceMarkdownLinks<T extends ContentPaths>({
siteDir,
fileString,
filePath,
contentPaths,
sourceToPermalink,
}: ReplaceMarkdownLinksParams<T>): ReplaceMarkdownLinksReturn<T> {
const {contentPath, contentPathLocalized} = contentPaths;
const brokenMarkdownLinks: BrokenMarkdownLink<T>[] = [];
// Replace internal markdown linking (except in fenced blocks).
let fencedBlock = false;
const lines = fileString.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 aliasedSource = (source: string) =>
aliasedSitePath(source, siteDir);
const permalink: string | undefined =
sourceToPermalink[aliasedSource(resolve(filePath, mdLink))] ||
sourceToPermalink[aliasedSource(`${contentPathLocalized}/${mdLink}`)] ||
sourceToPermalink[aliasedSource(`${contentPath}/${mdLink}`)];
if (permalink) {
modifiedLine = modifiedLine.replace(mdLink, permalink);
} else {
const brokenMarkdownLink: BrokenMarkdownLink<T> = {
contentPaths,
filePath,
link: mdLink,
};
brokenMarkdownLinks.push(brokenMarkdownLink);
}
mdMatch = mdRegex.exec(modifiedLine);
}
return modifiedLine;
});
const newContent = lines.join('\n');
return {newContent, brokenMarkdownLinks};
}