mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 19:27:48 +02:00
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:
parent
bfe52cdae3
commit
2f53b1a895
17 changed files with 240 additions and 237 deletions
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 {},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -44,8 +44,8 @@ function createSampleVersion(
|
|||
routePriority: undefined,
|
||||
sidebarFilePath: 'any',
|
||||
isLast: true,
|
||||
docsDirPath: 'any',
|
||||
docsDirPathLocalized: 'any',
|
||||
contentPath: 'any',
|
||||
contentPathLocalized: 'any',
|
||||
docs: [
|
||||
createSampleDoc({
|
||||
id: 'doc1',
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
93
packages/docusaurus-utils/src/markdownLinks.ts
Normal file
93
packages/docusaurus-utils/src/markdownLinks.ts
Normal 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};
|
||||
}
|
Loading…
Add table
Reference in a new issue