Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions packages/docusaurus-plugin-content-docs/src/contentHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,56 @@
* LICENSE file in the root directory of this source tree.
*/

import {createMetadataSourceResolver} from '@docusaurus/utils';
import type {DocMetadata, LoadedContent} from '@docusaurus/plugin-content-docs';

function indexDocsBySource(content: LoadedContent): Map<string, DocMetadata> {
const allDocs = content.loadedVersions.flatMap((v) => v.docs);
return new Map(allDocs.map((doc) => [doc.source, doc]));
}

type ContentHelpers = {
updateContent: (content: LoadedContent) => void;
sourceToDoc: Map<string, DocMetadata>;
sourceToPermalink: Map<string, string>;
getMetadataSource: (source: string) => string;
};

// TODO this is bad, we should have a better way to do this (new lifecycle?)
// The source to doc/permalink is a mutable map passed to the mdx loader
// See https://github.com/facebook/docusaurus/pull/10457
// See https://github.com/facebook/docusaurus/pull/10185
export function createContentHelpers() {
export function createContentHelpers(): ContentHelpers {
const sourceToDoc = new Map<string, DocMetadata>();
const sourceToPermalink = new Map<string, string>();
let metadataSourceResolver = createMetadataSourceResolver({
sources: sourceToDoc.keys(),
createAmbiguousSourceError: createAmbiguousMetadataSourceError,
});

// Mutable map update :/
function updateContent(content: LoadedContent): void {
sourceToDoc.clear();
sourceToPermalink.clear();
indexDocsBySource(content).forEach((value, key) => {
const docsBySource = indexDocsBySource(content);
docsBySource.forEach((value, key) => {
sourceToDoc.set(key, value);
sourceToPermalink.set(key, value.permalink);
});
metadataSourceResolver = createMetadataSourceResolver({
sources: docsBySource.keys(),
createAmbiguousSourceError: createAmbiguousMetadataSourceError,
});
}

function createAmbiguousMetadataSourceError(source: string): Error {
return new Error(`Docusaurus could not safely resolve the docs metadata path for "${source}" because multiple docs paths only differ by U+200B ZERO WIDTH SPACE characters.
Please rename the affected docs files or folders to remove the invisible zero-width spaces.`);
}

function getMetadataSource(source: string): string {
return metadataSourceResolver(source);
}

return {updateContent, sourceToDoc, sourceToPermalink};
return {updateContent, sourceToDoc, sourceToPermalink, getMetadataSource};
}
3 changes: 2 additions & 1 deletion packages/docusaurus-plugin-content-docs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ export default async function pluginContentDocs(
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
const metadataSource = contentHelpers.getMetadataSource(aliasedPath);
return path.join(dataDir, `${docuHash(metadataSource)}.json`);
},
// createAssets converts relative paths to require() calls
createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export {
addTrailingPathSeparator,
} from './pathUtils';
export {md5Hash, simpleHash, docuHash} from './hashUtils';
export {createMetadataSourceResolver} from './metadataUtils';
export {
Globby,
GlobExcludeDefault,
Expand Down
47 changes: 47 additions & 0 deletions packages/docusaurus-utils/src/metadataUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 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.
*/

const ZeroWidthSpace = '\u200B';

function getMetadataSourceKey(source: string): string {
return source.replaceAll(ZeroWidthSpace, '');
}

export function createMetadataSourceResolver({
sources,
createAmbiguousSourceError,
}: {
sources: Iterable<string>;
createAmbiguousSourceError?: (source: string) => Error;
}): (source: string) => string {
const sourceKeyToSource = new Map<string, string>();
const ambiguousSourceKeys = new Set<string>();

for (const source of sources) {
const sourceKey = getMetadataSourceKey(source);
const existingSource = sourceKeyToSource.get(sourceKey);
if (existingSource && existingSource !== source) {
ambiguousSourceKeys.add(sourceKey);
sourceKeyToSource.delete(sourceKey);
} else if (!ambiguousSourceKeys.has(sourceKey)) {
sourceKeyToSource.set(sourceKey, source);
}
}

return (source) => {
const sourceKey = getMetadataSourceKey(source);
if (ambiguousSourceKeys.has(sourceKey)) {
throw (
createAmbiguousSourceError?.(source) ??
new Error(
`Docusaurus could not safely resolve the metadata path for "${source}" because multiple source paths only differ by U+200B ZERO WIDTH SPACE characters.`,
)
);
}
return sourceKeyToSource.get(sourceKey) ?? source;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See https://github.com/facebook/docusaurus/issues/12005
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See https://github.com/facebook/docusaurus/issues/12005
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See https://github.com/facebook/docusaurus/issues/12005
Loading