This commit is contained in:
2024-03-22 03:47:51 +05:30
parent 8bcf3d211e
commit 89819f6fe2
28440 changed files with 3211033 additions and 2 deletions

View File

@@ -0,0 +1,12 @@
/**
* 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 { type SidebarsUtils } from './sidebars/utils';
import type { CategoryGeneratedIndexMetadata, DocMetadataBase } from '@docusaurus/plugin-content-docs';
export declare function getCategoryGeneratedIndexMetadataList({ docs, sidebarsUtils, }: {
sidebarsUtils: SidebarsUtils;
docs: DocMetadataBase[];
}): CategoryGeneratedIndexMetadata[];

View File

@@ -0,0 +1,37 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCategoryGeneratedIndexMetadataList = void 0;
const utils_1 = require("./sidebars/utils");
const docs_1 = require("./docs");
function getCategoryGeneratedIndexMetadata({ category, sidebarsUtils, docsById, }) {
const { sidebarName, previous, next } = sidebarsUtils.getCategoryGeneratedIndexNavigation(category.link.permalink);
return {
title: category.link.title ?? category.label,
description: category.link.description,
image: category.link.image,
keywords: category.link.keywords,
slug: category.link.slug,
permalink: category.link.permalink,
sidebar: sidebarName,
navigation: {
previous: (0, utils_1.toNavigationLink)(previous, docsById),
next: (0, utils_1.toNavigationLink)(next, docsById),
},
};
}
function getCategoryGeneratedIndexMetadataList({ docs, sidebarsUtils, }) {
const docsById = (0, docs_1.createDocsByIdIndex)(docs);
const categoryGeneratedIndexItems = sidebarsUtils.getCategoryGeneratedIndexList();
return categoryGeneratedIndexItems.map((category) => getCategoryGeneratedIndexMetadata({
category,
sidebarsUtils,
docsById,
}));
}
exports.getCategoryGeneratedIndexMetadataList = getCategoryGeneratedIndexMetadataList;

View File

@@ -0,0 +1,9 @@
/**
* 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 type { PluginOptions } from '@docusaurus/plugin-content-docs';
import type { LoadContext } from '@docusaurus/types';
export declare function cliDocsVersionCommand(version: unknown, { id: pluginId, path: docsPath, sidebarPath }: PluginOptions, { siteDir, i18n }: LoadContext): Promise<void>;

View File

@@ -0,0 +1,92 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.cliDocsVersionCommand = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const files_1 = require("./versions/files");
const validation_1 = require("./versions/validation");
const sidebars_1 = require("./sidebars");
const constants_1 = require("./constants");
async function createVersionedSidebarFile({ siteDir, pluginId, sidebarPath, version, }) {
// Load current sidebar and create a new versioned sidebars file (if needed).
// Note: we don't need the sidebars file to be normalized: it's ok to let
// plugin option changes to impact older, versioned sidebars
// We don't validate here, assuming the user has already built the version
const sidebars = await (0, sidebars_1.loadSidebarsFile)(sidebarPath);
// Do not create a useless versioned sidebars file if sidebars file is empty
// or sidebars are disabled/false)
const shouldCreateVersionedSidebarFile = Object.keys(sidebars).length > 0;
if (shouldCreateVersionedSidebarFile) {
await fs_extra_1.default.outputFile((0, files_1.getVersionSidebarsPath)(siteDir, pluginId, version), `${JSON.stringify(sidebars, null, 2)}\n`, 'utf8');
}
}
// Tests depend on non-default export for mocking.
async function cliDocsVersionCommand(version, { id: pluginId, path: docsPath, sidebarPath }, { siteDir, i18n }) {
// It wouldn't be very user-friendly to show a [default] log prefix,
// so we use [docs] instead of [default]
const pluginIdLogPrefix = pluginId === utils_1.DEFAULT_PLUGIN_ID ? '[docs]' : `[${pluginId}]`;
try {
(0, validation_1.validateVersionName)(version);
}
catch (err) {
logger_1.default.info `${pluginIdLogPrefix}: Invalid version name provided. Try something like: 1.0.0`;
throw err;
}
const versions = (await (0, files_1.readVersionsFile)(siteDir, pluginId)) ?? [];
// Check if version already exists.
if (versions.includes(version)) {
throw new Error(`${pluginIdLogPrefix}: this version already exists! Use a version tag that does not already exist.`);
}
if (i18n.locales.length > 1) {
logger_1.default.info `Versioned docs will be created for the following locales: name=${i18n.locales}`;
}
await Promise.all(i18n.locales.map(async (locale) => {
const localizationDir = path_1.default.resolve(siteDir, i18n.path, i18n.localeConfigs[locale].path);
// Copy docs files.
const docsDir = locale === i18n.defaultLocale
? path_1.default.resolve(siteDir, docsPath)
: (0, files_1.getDocsDirPathLocalized)({
localizationDir,
pluginId,
versionName: constants_1.CURRENT_VERSION_NAME,
});
if (!(await fs_extra_1.default.pathExists(docsDir)) ||
(await fs_extra_1.default.readdir(docsDir)).length === 0) {
if (locale === i18n.defaultLocale) {
throw new Error(logger_1.default.interpolate `${pluginIdLogPrefix}: no docs found in path=${docsDir}.`);
}
else {
logger_1.default.warn `${pluginIdLogPrefix}: no docs found in path=${docsDir}. Skipping.`;
return;
}
}
const newVersionDir = locale === i18n.defaultLocale
? (0, files_1.getVersionDocsDirPath)(siteDir, pluginId, version)
: (0, files_1.getDocsDirPathLocalized)({
localizationDir,
pluginId,
versionName: version,
});
await fs_extra_1.default.copy(docsDir, newVersionDir);
}));
await createVersionedSidebarFile({
siteDir,
pluginId,
version,
sidebarPath,
});
// Update versions.json file.
versions.unshift(version);
await fs_extra_1.default.outputFile((0, files_1.getVersionsFilePath)(siteDir, pluginId), `${JSON.stringify(versions, null, 2)}\n`);
logger_1.default.success `name=${pluginIdLogPrefix}: version name=${version} created!`;
}
exports.cliDocsVersionCommand = cliDocsVersionCommand;

View File

@@ -0,0 +1,15 @@
/**
* 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 type { GlobalPluginData, GlobalVersion, ActivePlugin, ActiveDocContext, DocVersionSuggestions } from '@docusaurus/plugin-content-docs/client';
import type { UseDataOptions } from '@docusaurus/types';
export declare function getActivePlugin(allPluginData: {
[pluginId: string]: GlobalPluginData;
}, pathname: string, options?: UseDataOptions): ActivePlugin | undefined;
export declare const getLatestVersion: (data: GlobalPluginData) => GlobalVersion;
export declare function getActiveVersion(data: GlobalPluginData, pathname: string): GlobalVersion | undefined;
export declare function getActiveDocContext(data: GlobalPluginData, pathname: string): ActiveDocContext;
export declare function getDocVersionSuggestions(data: GlobalPluginData, pathname: string): DocVersionSuggestions;

View File

@@ -0,0 +1,78 @@
/**
* 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 { matchPath } from '@docusaurus/router';
// This code is not part of the api surface, not in ./theme on purpose
// get the data of the plugin that is currently "active"
// ie the docs of that plugin are currently browsed
// it is useful to support multiple docs plugin instances
export function getActivePlugin(allPluginData, pathname, options = {}) {
const activeEntry = Object.entries(allPluginData)
// Route sorting: '/android/foo' should match '/android' instead of '/'
.sort((a, b) => b[1].path.localeCompare(a[1].path))
.find(([, pluginData]) => !!matchPath(pathname, {
path: pluginData.path,
exact: false,
strict: false,
}));
const activePlugin = activeEntry
? { pluginId: activeEntry[0], pluginData: activeEntry[1] }
: undefined;
if (!activePlugin && options.failfast) {
throw new Error(`Can't find active docs plugin for "${pathname}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(allPluginData)
.map((plugin) => plugin.path)
.join(', ')}`);
}
return activePlugin;
}
export const getLatestVersion = (data) => data.versions.find((version) => version.isLast);
export function getActiveVersion(data, pathname) {
const lastVersion = getLatestVersion(data);
// Last version is a route like /docs/*,
// we need to match it last or it would match /docs/version-1.0/* as well
const orderedVersionsMetadata = [
...data.versions.filter((version) => version !== lastVersion),
lastVersion,
];
return orderedVersionsMetadata.find((version) => !!matchPath(pathname, {
path: version.path,
exact: false,
strict: false,
}));
}
export function getActiveDocContext(data, pathname) {
const activeVersion = getActiveVersion(data, pathname);
const activeDoc = activeVersion?.docs.find((doc) => !!matchPath(pathname, {
path: doc.path,
exact: true,
strict: false,
}));
function getAlternateVersionDocs(docId) {
const result = {};
data.versions.forEach((version) => {
version.docs.forEach((doc) => {
if (doc.id === docId) {
result[version.name] = doc;
}
});
});
return result;
}
const alternateVersionDocs = activeDoc
? getAlternateVersionDocs(activeDoc.id)
: {};
return {
activeVersion,
activeDoc,
alternateDocVersions: alternateVersionDocs,
};
}
export function getDocVersionSuggestions(data, pathname) {
const latestVersion = getLatestVersion(data);
const activeDocContext = getActiveDocContext(data, pathname);
const latestDocSuggestion = activeDocContext.alternateDocVersions[latestVersion.name];
return { latestDocSuggestion, latestVersionSuggestion: latestVersion };
}

View File

@@ -0,0 +1,82 @@
/**
* 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 type { UseDataOptions } from '@docusaurus/types';
export type ActivePlugin = {
pluginId: string;
pluginData: GlobalPluginData;
};
export type ActiveDocContext = {
activeVersion?: GlobalVersion;
activeDoc?: GlobalDoc;
alternateDocVersions: {
[versionName: string]: GlobalDoc;
};
};
export type GlobalDoc = {
/**
* For generated index pages, this is the `slug`, **not** `permalink`
* (without base URL). Because slugs have leading slashes but IDs don't,
* there won't be clashes.
*/
id: string;
path: string;
sidebar?: string;
unlisted?: boolean;
};
export type GlobalVersion = {
name: string;
label: string;
isLast: boolean;
path: string;
/** The doc with `slug: /`, or first doc in first sidebar */
mainDocId: string;
docs: GlobalDoc[];
/** Unversioned IDs. In development, this list is empty. */
draftIds: string[];
sidebars?: {
[sidebarId: string]: GlobalSidebar;
};
};
export type GlobalSidebar = {
link?: {
label: string;
path: string;
};
};
export type GlobalPluginData = {
path: string;
versions: GlobalVersion[];
breadcrumbs: boolean;
};
export type DocVersionSuggestions = {
/** Suggest the latest version */
latestVersionSuggestion: GlobalVersion;
/** Suggest the same doc, in latest version (if one exists) */
latestDocSuggestion?: GlobalDoc;
};
export declare const useAllDocsData: () => {
[pluginId: string]: GlobalPluginData;
};
export declare const useDocsData: (pluginId: string | undefined) => GlobalPluginData;
export declare function useActivePlugin(options?: UseDataOptions): ActivePlugin | undefined;
export declare function useActivePluginAndVersion(options?: UseDataOptions): {
activePlugin: ActivePlugin;
activeVersion: GlobalVersion | undefined;
} | undefined;
/** Versions are returned ordered (most recent first). */
export declare function useVersions(pluginId: string | undefined): GlobalVersion[];
export declare function useLatestVersion(pluginId: string | undefined): GlobalVersion;
/**
* Returns `undefined` on doc-unrelated pages, because there's no version
* currently considered as active.
*/
export declare function useActiveVersion(pluginId: string | undefined): GlobalVersion | undefined;
export declare function useActiveDocContext(pluginId: string | undefined): ActiveDocContext;
/**
* Useful to say "hey, you are not on the latest docs version, please switch"
*/
export declare function useDocVersionSuggestions(pluginId: string | undefined): DocVersionSuggestions;

View File

@@ -0,0 +1,67 @@
/**
* 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 { useLocation } from '@docusaurus/router';
import { useAllPluginInstancesData, usePluginData, } from '@docusaurus/useGlobalData';
import { getActivePlugin, getLatestVersion, getActiveVersion, getActiveDocContext, getDocVersionSuggestions, } from './docsClientUtils';
// Important to use a constant object to avoid React useEffect executions etc.
// see https://github.com/facebook/docusaurus/issues/5089
const StableEmptyObject = {};
// In blog-only mode, docs hooks are still used by the theme. We need a fail-
// safe fallback when the docs plugin is not in use
export const useAllDocsData = () => useAllPluginInstancesData('docusaurus-plugin-content-docs') ?? StableEmptyObject;
export const useDocsData = (pluginId) => usePluginData('docusaurus-plugin-content-docs', pluginId, {
failfast: true,
});
// TODO this feature should be provided by docusaurus core
export function useActivePlugin(options = {}) {
const data = useAllDocsData();
const { pathname } = useLocation();
return getActivePlugin(data, pathname, options);
}
export function useActivePluginAndVersion(options = {}) {
const activePlugin = useActivePlugin(options);
const { pathname } = useLocation();
if (!activePlugin) {
return undefined;
}
const activeVersion = getActiveVersion(activePlugin.pluginData, pathname);
return {
activePlugin,
activeVersion,
};
}
/** Versions are returned ordered (most recent first). */
export function useVersions(pluginId) {
const data = useDocsData(pluginId);
return data.versions;
}
export function useLatestVersion(pluginId) {
const data = useDocsData(pluginId);
return getLatestVersion(data);
}
/**
* Returns `undefined` on doc-unrelated pages, because there's no version
* currently considered as active.
*/
export function useActiveVersion(pluginId) {
const data = useDocsData(pluginId);
const { pathname } = useLocation();
return getActiveVersion(data, pathname);
}
export function useActiveDocContext(pluginId) {
const data = useDocsData(pluginId);
const { pathname } = useLocation();
return getActiveDocContext(data, pathname);
}
/**
* Useful to say "hey, you are not on the latest docs version, please switch"
*/
export function useDocVersionSuggestions(pluginId) {
const data = useDocsData(pluginId);
const { pathname } = useLocation();
return getDocVersionSuggestions(data, pathname);
}

View File

@@ -0,0 +1,14 @@
/**
* 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.
*/
/** The name of the version that's actively worked on (e.g. `website/docs`) */
export declare const CURRENT_VERSION_NAME = "current";
/** All doc versions are stored here by version names */
export declare const VERSIONED_DOCS_DIR = "versioned_docs";
/** All doc versioned sidebars are stored here by version names */
export declare const VERSIONED_SIDEBARS_DIR = "versioned_sidebars";
/** The version names. Should 1-1 map to the content of versioned docs dir. */
export declare const VERSIONS_JSON_FILE = "versions.json";

View File

@@ -0,0 +1,17 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VERSIONS_JSON_FILE = exports.VERSIONED_SIDEBARS_DIR = exports.VERSIONED_DOCS_DIR = exports.CURRENT_VERSION_NAME = void 0;
/** The name of the version that's actively worked on (e.g. `website/docs`) */
exports.CURRENT_VERSION_NAME = 'current';
/** All doc versions are stored here by version names */
exports.VERSIONED_DOCS_DIR = 'versioned_docs';
/** All doc versioned sidebars are stored here by version names */
exports.VERSIONED_SIDEBARS_DIR = 'versioned_sidebars';
/** The version names. Should 1-1 map to the content of versioned docs dir. */
exports.VERSIONS_JSON_FILE = 'versions.json';

View File

@@ -0,0 +1,46 @@
/**
* 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 type { MetadataOptions, PluginOptions, CategoryIndexMatcher, DocMetadataBase, VersionMetadata, LoadedVersion } from '@docusaurus/plugin-content-docs';
import type { LoadContext } from '@docusaurus/types';
import type { SidebarsUtils } from './sidebars/utils';
import type { DocFile } from './types';
export declare function readDocFile(versionMetadata: Pick<VersionMetadata, 'contentPath' | 'contentPathLocalized'>, source: string): Promise<DocFile>;
export declare function readVersionDocs(versionMetadata: VersionMetadata, options: Pick<PluginOptions, 'include' | 'exclude' | 'showLastUpdateAuthor' | 'showLastUpdateTime'>): Promise<DocFile[]>;
export type DocEnv = 'production' | 'development';
export declare function processDocMetadata(args: {
docFile: DocFile;
versionMetadata: VersionMetadata;
context: LoadContext;
options: MetadataOptions;
env: DocEnv;
}): Promise<DocMetadataBase>;
export declare function addDocNavigation({ docs, sidebarsUtils, }: {
docs: DocMetadataBase[];
sidebarsUtils: SidebarsUtils;
}): LoadedVersion['docs'];
/**
* The "main doc" is the "version entry point"
* We browse this doc by clicking on a version:
* - the "home" doc (at '/docs/')
* - the first doc of the first sidebar
* - a random doc (if no docs are in any sidebar... edge case)
*/
export declare function getMainDocId({ docs, sidebarsUtils, }: {
docs: DocMetadataBase[];
sidebarsUtils: SidebarsUtils;
}): string;
export declare const isCategoryIndex: CategoryIndexMatcher;
/**
* `guides/sidebar/autogenerated.md` ->
* `'autogenerated', '.md', ['sidebar', 'guides']`
*/
export declare function toCategoryIndexMatcherParam({ source, sourceDirName, }: Pick<DocMetadataBase, 'source' | 'sourceDirName'>): Parameters<CategoryIndexMatcher>[0];
export declare function createDocsByIdIndex<Doc extends {
id: string;
}>(docs: Doc[]): {
[docId: string]: Doc;
};

View File

@@ -0,0 +1,294 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDocsByIdIndex = exports.toCategoryIndexMatcherParam = exports.isCategoryIndex = exports.getMainDocId = exports.addDocNavigation = exports.processDocMetadata = exports.readVersionDocs = exports.readDocFile = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const lastUpdate_1 = require("./lastUpdate");
const slug_1 = tslib_1.__importDefault(require("./slug"));
const numberPrefix_1 = require("./numberPrefix");
const frontMatter_1 = require("./frontMatter");
const utils_2 = require("./sidebars/utils");
async function readLastUpdateData(filePath, options, lastUpdateFrontMatter) {
const { showLastUpdateAuthor, showLastUpdateTime } = options;
if (showLastUpdateAuthor || showLastUpdateTime) {
const frontMatterTimestamp = lastUpdateFrontMatter?.date
? new Date(lastUpdateFrontMatter.date).getTime() / 1000
: undefined;
if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) {
return {
lastUpdatedAt: frontMatterTimestamp,
lastUpdatedBy: lastUpdateFrontMatter.author,
};
}
// Use fake data in dev for faster development.
const fileLastUpdateData = process.env.NODE_ENV === 'production'
? await (0, lastUpdate_1.getFileLastUpdate)(filePath)
: {
author: 'Author',
timestamp: 1539502055,
};
const { author, timestamp } = fileLastUpdateData ?? {};
return {
lastUpdatedBy: showLastUpdateAuthor
? lastUpdateFrontMatter?.author ?? author
: undefined,
lastUpdatedAt: showLastUpdateTime
? frontMatterTimestamp ?? timestamp
: undefined,
};
}
return {};
}
async function readDocFile(versionMetadata, source) {
const contentPath = await (0, utils_1.getFolderContainingFile)((0, utils_1.getContentPathList)(versionMetadata), source);
const filePath = path_1.default.join(contentPath, source);
const content = await fs_extra_1.default.readFile(filePath, 'utf-8');
return { source, content, contentPath, filePath };
}
exports.readDocFile = readDocFile;
async function readVersionDocs(versionMetadata, options) {
const sources = await (0, utils_1.Globby)(options.include, {
cwd: versionMetadata.contentPath,
ignore: options.exclude,
});
return Promise.all(sources.map((source) => readDocFile(versionMetadata, source)));
}
exports.readVersionDocs = readVersionDocs;
async function doProcessDocMetadata({ docFile, versionMetadata, context, options, env, }) {
const { source, content, contentPath, filePath } = docFile;
const { siteDir, i18n, siteConfig: { markdown: { parseFrontMatter }, }, } = context;
const { frontMatter: unsafeFrontMatter, contentTitle, excerpt, } = await (0, utils_1.parseMarkdownFile)({
filePath,
fileContent: content,
parseFrontMatter,
});
const frontMatter = (0, frontMatter_1.validateDocFrontMatter)(unsafeFrontMatter);
const { custom_edit_url: customEditURL,
// Strip number prefixes by default
// (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc)
// but allow to disable this behavior with front matter
parse_number_prefixes: parseNumberPrefixes = true, last_update: lastUpdateFrontMatter, } = frontMatter;
const lastUpdate = await readLastUpdateData(filePath, options, lastUpdateFrontMatter);
// E.g. api/plugins/myDoc -> myDoc; myDoc -> myDoc
const sourceFileNameWithoutExtension = path_1.default.basename(source, path_1.default.extname(source));
// E.g. api/plugins/myDoc -> api/plugins; myDoc -> .
const sourceDirName = path_1.default.dirname(source);
const { filename: unprefixedFileName, numberPrefix } = parseNumberPrefixes
? options.numberPrefixParser(sourceFileNameWithoutExtension)
: { filename: sourceFileNameWithoutExtension, numberPrefix: undefined };
const baseID = frontMatter.id ?? unprefixedFileName;
if (baseID.includes('/')) {
throw new Error(`Document id "${baseID}" cannot include slash.`);
}
// For autogenerated sidebars, sidebar position can come from filename number
// prefix or front matter
const sidebarPosition = frontMatter.sidebar_position ?? numberPrefix;
// TODO legacy retrocompatibility
// I think it's bad to affect the front matter id with the dirname?
function computeDirNameIdPrefix() {
if (sourceDirName === '.') {
return undefined;
}
// Eventually remove the number prefixes from intermediate directories
return parseNumberPrefixes
? (0, numberPrefix_1.stripPathNumberPrefixes)(sourceDirName, options.numberPrefixParser)
: sourceDirName;
}
const id = [computeDirNameIdPrefix(), baseID].filter(Boolean).join('/');
const docSlug = (0, slug_1.default)({
baseID,
source,
sourceDirName,
frontMatterSlug: frontMatter.slug,
stripDirNumberPrefixes: parseNumberPrefixes,
numberPrefixParser: options.numberPrefixParser,
});
// Note: the title is used by default for page title, sidebar label,
// pagination buttons... frontMatter.title should be used in priority over
// contentTitle (because it can contain markdown/JSX syntax)
const title = frontMatter.title ?? contentTitle ?? baseID;
const description = frontMatter.description ?? excerpt ?? '';
const permalink = (0, utils_1.normalizeUrl)([versionMetadata.path, docSlug]);
function getDocEditUrl() {
const relativeFilePath = path_1.default.relative(contentPath, filePath);
if (typeof options.editUrl === 'function') {
return options.editUrl({
version: versionMetadata.versionName,
versionDocsDirPath: (0, utils_1.posixPath)(path_1.default.relative(siteDir, versionMetadata.contentPath)),
docPath: (0, utils_1.posixPath)(relativeFilePath),
permalink,
locale: context.i18n.currentLocale,
});
}
else if (typeof options.editUrl === 'string') {
const isLocalized = contentPath === versionMetadata.contentPathLocalized;
const baseVersionEditUrl = isLocalized && options.editLocalizedFiles
? versionMetadata.editUrlLocalized
: versionMetadata.editUrl;
return (0, utils_1.getEditUrl)(relativeFilePath, baseVersionEditUrl);
}
return undefined;
}
const draft = (0, utils_1.isDraft)({ env, frontMatter });
const unlisted = (0, utils_1.isUnlisted)({ env, frontMatter });
const formatDate = (locale, date, calendar) => {
try {
return new Intl.DateTimeFormat(locale, {
day: 'numeric',
month: 'short',
year: 'numeric',
timeZone: 'UTC',
calendar,
}).format(date);
}
catch (err) {
logger_1.default.error `Can't format docs lastUpdatedAt date "${String(date)}"`;
throw err;
}
};
// Assign all of object properties during instantiation (if possible) for
// NodeJS optimization.
// Adding properties to object after instantiation will cause hidden
// class transitions.
return {
id,
title,
description,
source: (0, utils_1.aliasedSitePath)(filePath, siteDir),
sourceDirName,
slug: docSlug,
permalink,
draft,
unlisted,
editUrl: customEditURL !== undefined ? customEditURL : getDocEditUrl(),
tags: (0, utils_1.normalizeFrontMatterTags)(versionMetadata.tagsPath, frontMatter.tags),
version: versionMetadata.versionName,
lastUpdatedBy: lastUpdate.lastUpdatedBy,
lastUpdatedAt: lastUpdate.lastUpdatedAt,
formattedLastUpdatedAt: lastUpdate.lastUpdatedAt
? formatDate(i18n.currentLocale, new Date(lastUpdate.lastUpdatedAt * 1000), i18n.localeConfigs[i18n.currentLocale].calendar)
: undefined,
sidebarPosition,
frontMatter,
};
}
async function processDocMetadata(args) {
try {
return await doProcessDocMetadata(args);
}
catch (err) {
throw new Error(`Can't process doc metadata for doc at path path=${args.docFile.filePath} in version name=${args.versionMetadata.versionName}`, { cause: err });
}
}
exports.processDocMetadata = processDocMetadata;
function getUnlistedIds(docs) {
return new Set(docs.filter((doc) => doc.unlisted).map((doc) => doc.id));
}
function addDocNavigation({ docs, sidebarsUtils, }) {
const docsById = createDocsByIdIndex(docs);
const unlistedIds = getUnlistedIds(docs);
// Add sidebar/next/previous to the docs
function addNavData(doc) {
const navigation = sidebarsUtils.getDocNavigation({
docId: doc.id,
displayedSidebar: doc.frontMatter.displayed_sidebar,
unlistedIds,
});
const toNavigationLinkByDocId = (docId, type) => {
if (!docId) {
return undefined;
}
const navDoc = docsById[docId];
if (!navDoc) {
// This could only happen if user provided the ID through front matter
throw new Error(`Error when loading ${doc.id} in ${doc.sourceDirName}: the pagination_${type} front matter points to a non-existent ID ${docId}.`);
}
// Gracefully handle explicitly providing an unlisted doc ID in production
if (navDoc.unlisted) {
return undefined;
}
return (0, utils_2.toDocNavigationLink)(navDoc);
};
const previous = doc.frontMatter.pagination_prev !== undefined
? toNavigationLinkByDocId(doc.frontMatter.pagination_prev, 'prev')
: (0, utils_2.toNavigationLink)(navigation.previous, docsById);
const next = doc.frontMatter.pagination_next !== undefined
? toNavigationLinkByDocId(doc.frontMatter.pagination_next, 'next')
: (0, utils_2.toNavigationLink)(navigation.next, docsById);
return { ...doc, sidebar: navigation.sidebarName, previous, next };
}
const docsWithNavigation = docs.map(addNavData);
// Sort to ensure consistent output for tests
docsWithNavigation.sort((a, b) => a.id.localeCompare(b.id));
return docsWithNavigation;
}
exports.addDocNavigation = addDocNavigation;
/**
* The "main doc" is the "version entry point"
* We browse this doc by clicking on a version:
* - the "home" doc (at '/docs/')
* - the first doc of the first sidebar
* - a random doc (if no docs are in any sidebar... edge case)
*/
function getMainDocId({ docs, sidebarsUtils, }) {
function getMainDoc() {
const versionHomeDoc = docs.find((doc) => doc.slug === '/');
const firstDocIdOfFirstSidebar = sidebarsUtils.getFirstDocIdOfFirstSidebar();
if (versionHomeDoc) {
return versionHomeDoc;
}
else if (firstDocIdOfFirstSidebar) {
return docs.find((doc) => doc.id === firstDocIdOfFirstSidebar);
}
return docs[0];
}
return getMainDoc().id;
}
exports.getMainDocId = getMainDocId;
// By convention, Docusaurus considers some docs are "indexes":
// - index.md
// - readme.md
// - <folder>/<folder>.md
//
// This function is the default implementation of this convention
//
// Those index docs produce a different behavior
// - Slugs do not end with a weird "/index" suffix
// - Auto-generated sidebar categories link to them as intro
const isCategoryIndex = ({ fileName, directories, }) => {
const eligibleDocIndexNames = [
'index',
'readme',
directories[0]?.toLowerCase(),
];
return eligibleDocIndexNames.includes(fileName.toLowerCase());
};
exports.isCategoryIndex = isCategoryIndex;
/**
* `guides/sidebar/autogenerated.md` ->
* `'autogenerated', '.md', ['sidebar', 'guides']`
*/
function toCategoryIndexMatcherParam({ source, sourceDirName, }) {
// source + sourceDirName are always posix-style
return {
fileName: path_1.default.posix.parse(source).name,
extension: path_1.default.posix.parse(source).ext,
directories: sourceDirName.split(path_1.default.posix.sep).reverse(),
};
}
exports.toCategoryIndexMatcherParam = toCategoryIndexMatcherParam;
// Docs are indexed by their id
function createDocsByIdIndex(docs) {
return lodash_1.default.keyBy(docs, (d) => d.id);
}
exports.createDocsByIdIndex = createDocsByIdIndex;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { DocFrontMatter } from '@docusaurus/plugin-content-docs';
export declare function validateDocFrontMatter(frontMatter: {
[key: string]: unknown;
}): DocFrontMatter;

View File

@@ -0,0 +1,54 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateDocFrontMatter = void 0;
const utils_validation_1 = require("@docusaurus/utils-validation");
const FrontMatterLastUpdateErrorMessage = '{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).';
// NOTE: we don't add any default value on purpose here
// We don't want default values to magically appear in doc metadata and props
// While the user did not provide those values explicitly
// We use default values in code instead
const DocFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
id: utils_validation_1.JoiFrontMatter.string(),
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
title: utils_validation_1.JoiFrontMatter.string().allow(''),
hide_title: utils_validation_1.JoiFrontMatter.boolean(),
hide_table_of_contents: utils_validation_1.JoiFrontMatter.boolean(),
keywords: utils_validation_1.JoiFrontMatter.array().items(utils_validation_1.JoiFrontMatter.string().required()),
image: utils_validation_1.URISchema,
// See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398
description: utils_validation_1.JoiFrontMatter.string().allow(''),
slug: utils_validation_1.JoiFrontMatter.string(),
sidebar_label: utils_validation_1.JoiFrontMatter.string(),
sidebar_position: utils_validation_1.JoiFrontMatter.number(),
sidebar_class_name: utils_validation_1.JoiFrontMatter.string(),
sidebar_custom_props: utils_validation_1.JoiFrontMatter.object().unknown(),
displayed_sidebar: utils_validation_1.JoiFrontMatter.string().allow(null),
tags: utils_validation_1.FrontMatterTagsSchema,
pagination_label: utils_validation_1.JoiFrontMatter.string(),
custom_edit_url: utils_validation_1.URISchema.allow('', null),
parse_number_prefixes: utils_validation_1.JoiFrontMatter.boolean(),
pagination_next: utils_validation_1.JoiFrontMatter.string().allow(null),
pagination_prev: utils_validation_1.JoiFrontMatter.string().allow(null),
...utils_validation_1.FrontMatterTOCHeadingLevels,
last_update: utils_validation_1.JoiFrontMatter.object({
author: utils_validation_1.JoiFrontMatter.string(),
date: utils_validation_1.JoiFrontMatter.date().raw(),
})
.or('author', 'date')
.messages({
'object.missing': FrontMatterLastUpdateErrorMessage,
'object.base': FrontMatterLastUpdateErrorMessage,
}),
})
.unknown()
.concat(utils_validation_1.ContentVisibilitySchema);
function validateDocFrontMatter(frontMatter) {
return (0, utils_validation_1.validateFrontMatter)(frontMatter, DocFrontMatterSchema);
}
exports.validateDocFrontMatter = validateDocFrontMatter;

View File

@@ -0,0 +1,9 @@
/**
* 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 type { FullVersion } from './types';
import type { GlobalVersion } from '@docusaurus/plugin-content-docs/client';
export declare function toGlobalDataVersion(version: FullVersion): GlobalVersion;

View File

@@ -0,0 +1,60 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.toGlobalDataVersion = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const docs_1 = require("./docs");
function toGlobalDataDoc(doc) {
return {
id: doc.id,
path: doc.permalink,
// optimize global data size: do not add unlisted: false/undefined
...(doc.unlisted && { unlisted: doc.unlisted }),
// TODO optimize size? remove attribute when no sidebar (breaking change?)
sidebar: doc.sidebar,
};
}
function toGlobalDataGeneratedIndex(doc) {
return {
id: doc.slug,
path: doc.permalink,
sidebar: doc.sidebar,
};
}
function toGlobalSidebars(sidebars, version) {
return lodash_1.default.mapValues(sidebars, (sidebar, sidebarId) => {
const firstLink = version.sidebarsUtils.getFirstLink(sidebarId);
if (!firstLink) {
return {};
}
return {
link: {
path: firstLink.type === 'generated-index'
? firstLink.permalink
: version.docs.find((doc) => doc.id === firstLink.id).permalink,
label: firstLink.label,
},
};
});
}
function toGlobalDataVersion(version) {
return {
name: version.versionName,
label: version.label,
isLast: version.isLast,
path: version.path,
mainDocId: (0, docs_1.getMainDocId)(version),
docs: version.docs
.map(toGlobalDataDoc)
.concat(version.categoryGeneratedIndices.map(toGlobalDataGeneratedIndex)),
draftIds: version.drafts.map((doc) => doc.id),
sidebars: toGlobalSidebars(version.sidebars, version),
};
}
exports.toGlobalDataVersion = toGlobalDataVersion;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { PluginOptions, LoadedContent } from '@docusaurus/plugin-content-docs';
import type { LoadContext, Plugin } from '@docusaurus/types';
export default function pluginContentDocs(context: LoadContext, options: PluginOptions): Promise<Plugin<LoadedContent>>;
export { validateOptions } from './options';

View File

@@ -0,0 +1,233 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateOptions = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const sidebars_1 = require("./sidebars");
const generator_1 = require("./sidebars/generator");
const docs_1 = require("./docs");
const versions_1 = require("./versions");
const cli_1 = require("./cli");
const constants_1 = require("./constants");
const globalData_1 = require("./globalData");
const translations_1 = require("./translations");
const routes_1 = require("./routes");
const utils_2 = require("./sidebars/utils");
async function pluginContentDocs(context, options) {
const { siteDir, generatedFilesDir, baseUrl, siteConfig } = context;
// Mutate options to resolve sidebar path according to siteDir
options.sidebarPath = (0, sidebars_1.resolveSidebarPathOption)(siteDir, options.sidebarPath);
const versionsMetadata = await (0, versions_1.readVersionsMetadata)({ context, options });
const pluginId = options.id;
const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-docs');
const dataDir = path_1.default.join(pluginDataDirRoot, pluginId);
const aliasedSource = (source) => `~docs/${(0, utils_1.posixPath)(path_1.default.relative(pluginDataDirRoot, source))}`;
// TODO env should be injected into all plugins
const env = process.env.NODE_ENV;
return {
name: 'docusaurus-plugin-content-docs',
extendCli(cli) {
const isDefaultPluginId = pluginId === utils_1.DEFAULT_PLUGIN_ID;
// Need to create one distinct command per plugin instance
// otherwise 2 instances would try to execute the command!
const command = isDefaultPluginId
? 'docs:version'
: `docs:version:${pluginId}`;
const commandDescription = isDefaultPluginId
? 'Tag a new docs version'
: `Tag a new docs version (${pluginId})`;
cli
.command(command)
.arguments('<version>')
.description(commandDescription)
.action((version) => (0, cli_1.cliDocsVersionCommand)(version, options, context));
},
getTranslationFiles({ content }) {
return (0, translations_1.getLoadedContentTranslationFiles)(content);
},
getPathsToWatch() {
function getVersionPathsToWatch(version) {
const result = [
...options.include.flatMap((pattern) => (0, utils_1.getContentPathList)(version).map((docsDirPath) => `${docsDirPath}/${pattern}`)),
`${version.contentPath}/**/${generator_1.CategoryMetadataFilenamePattern}`,
];
if (typeof version.sidebarFilePath === 'string') {
result.unshift(version.sidebarFilePath);
}
return result;
}
return versionsMetadata.flatMap(getVersionPathsToWatch);
},
async loadContent() {
async function loadVersionDocsBase(versionMetadata) {
const docFiles = await (0, docs_1.readVersionDocs)(versionMetadata, options);
if (docFiles.length === 0) {
throw new Error(`Docs version "${versionMetadata.versionName}" has no docs! At least one doc should exist at "${path_1.default.relative(siteDir, versionMetadata.contentPath)}".`);
}
function processVersionDoc(docFile) {
return (0, docs_1.processDocMetadata)({
docFile,
versionMetadata,
context,
options,
env,
});
}
return Promise.all(docFiles.map(processVersionDoc));
}
async function doLoadVersion(versionMetadata) {
const docsBase = await loadVersionDocsBase(versionMetadata);
// TODO we only ever need draftIds in further code, not full draft items
// To simplify and prevent mistakes, avoid exposing draft
// replace draft=>draftIds in content loaded
const [drafts, docs] = lodash_1.default.partition(docsBase, (doc) => doc.draft);
const sidebars = await (0, sidebars_1.loadSidebars)(versionMetadata.sidebarFilePath, {
sidebarItemsGenerator: options.sidebarItemsGenerator,
numberPrefixParser: options.numberPrefixParser,
docs,
drafts,
version: versionMetadata,
sidebarOptions: {
sidebarCollapsed: options.sidebarCollapsed,
sidebarCollapsible: options.sidebarCollapsible,
},
categoryLabelSlugger: (0, utils_1.createSlugger)(),
});
const sidebarsUtils = (0, utils_2.createSidebarsUtils)(sidebars);
const docsById = (0, docs_1.createDocsByIdIndex)(docs);
const allDocIds = Object.keys(docsById);
sidebarsUtils.checkLegacyVersionedSidebarNames({
sidebarFilePath: versionMetadata.sidebarFilePath,
versionMetadata,
});
sidebarsUtils.checkSidebarsDocIds({
allDocIds,
sidebarFilePath: versionMetadata.sidebarFilePath,
versionMetadata,
});
return {
...versionMetadata,
docs: (0, docs_1.addDocNavigation)({
docs,
sidebarsUtils,
}),
drafts,
sidebars,
};
}
async function loadVersion(versionMetadata) {
try {
return await doLoadVersion(versionMetadata);
}
catch (err) {
logger_1.default.error `Loading of version failed for version name=${versionMetadata.versionName}`;
throw err;
}
}
return {
loadedVersions: await Promise.all(versionsMetadata.map(loadVersion)),
};
},
translateContent({ content, translationFiles }) {
return (0, translations_1.translateLoadedContent)(content, translationFiles);
},
async contentLoaded({ content, actions }) {
const versions = content.loadedVersions.map(versions_1.toFullVersion);
await (0, routes_1.createAllRoutes)({
baseUrl,
versions,
options,
actions,
aliasedSource,
});
actions.setGlobalData({
path: (0, utils_1.normalizeUrl)([baseUrl, options.routeBasePath]),
versions: versions.map(globalData_1.toGlobalDataVersion),
breadcrumbs: options.breadcrumbs,
});
},
configureWebpack(_config, isServer, utils, content) {
const { rehypePlugins, remarkPlugins, beforeDefaultRehypePlugins, beforeDefaultRemarkPlugins, } = options;
function getSourceToPermalink() {
const allDocs = content.loadedVersions.flatMap((v) => v.docs);
return Object.fromEntries(allDocs.map(({ source, permalink }) => [source, permalink]));
}
const docsMarkdownOptions = {
siteDir,
sourceToPermalink: getSourceToPermalink(),
versionsMetadata,
onBrokenMarkdownLink: (brokenMarkdownLink) => {
logger_1.default.report(siteConfig.onBrokenMarkdownLinks) `Docs markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath} for version number=${brokenMarkdownLink.contentPaths.versionName}`;
},
};
function createMDXLoaderRule() {
const contentDirs = versionsMetadata
.flatMap(utils_1.getContentPathList)
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(utils_1.addTrailingPathSeparator);
return {
test: /\.mdx?$/i,
include: contentDirs,
use: [
{
loader: require.resolve('@docusaurus/mdx-loader'),
options: {
admonitions: options.admonitions,
remarkPlugins,
rehypePlugins,
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
staticDirs: siteConfig.staticDirectories.map((dir) => path_1.default.resolve(siteDir, dir)),
siteDir,
isMDXPartial: (0, utils_1.createAbsoluteFilePathMatcher)(options.exclude, contentDirs),
metadataPath: (mdxPath) => {
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
const aliasedPath = (0, utils_1.aliasedSitePath)(mdxPath, siteDir);
return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedPath)}.json`);
},
// Assets allow to convert some relative images paths to
// require(...) calls
createAssets: ({ frontMatter, }) => ({
image: frontMatter.image,
}),
markdownConfig: siteConfig.markdown,
},
},
{
loader: path_1.default.resolve(__dirname, './markdown/index.js'),
options: docsMarkdownOptions,
},
].filter(Boolean),
};
}
return {
ignoreWarnings: [
// Suppress warnings about non-existing of versions file.
(e) => e.message.includes("Can't resolve") &&
e.message.includes(constants_1.VERSIONS_JSON_FILE),
],
resolve: {
alias: {
'~docs': pluginDataDirRoot,
},
},
module: {
rules: [createMDXLoaderRule()],
},
};
},
};
}
exports.default = pluginContentDocs;
var options_1 = require("./options");
Object.defineProperty(exports, "validateOptions", { enumerable: true, get: function () { return options_1.validateOptions; } });

View File

@@ -0,0 +1,10 @@
/**
* 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.
*/
export declare function getFileLastUpdate(filePath: string): Promise<{
timestamp: number;
author: string;
} | null>;

View File

@@ -0,0 +1,47 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFileLastUpdate = void 0;
const tslib_1 = require("tslib");
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
let showedGitRequirementError = false;
let showedFileNotTrackedError = false;
async function getFileLastUpdate(filePath) {
if (!filePath) {
return null;
}
// Wrap in try/catch in case the shell commands fail
// (e.g. project doesn't use Git, etc).
try {
const result = (0, utils_1.getFileCommitDate)(filePath, {
age: 'newest',
includeAuthor: true,
});
return { timestamp: result.timestamp, author: result.author };
}
catch (err) {
if (err instanceof utils_1.GitNotFoundError) {
if (!showedGitRequirementError) {
logger_1.default.warn('Sorry, the docs plugin last update options require Git.');
showedGitRequirementError = true;
}
}
else if (err instanceof utils_1.FileNotTrackedError) {
if (!showedFileNotTrackedError) {
logger_1.default.warn('Cannot infer the update date for some files, as they are not tracked by git.');
showedFileNotTrackedError = true;
}
}
else {
logger_1.default.warn(err);
}
return null;
}
}
exports.getFileLastUpdate = getFileLastUpdate;

View File

@@ -0,0 +1,9 @@
/**
* 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 type { DocsMarkdownOption } from '../types';
import type { LoaderContext } from 'webpack';
export default function markdownLoader(this: LoaderContext<DocsMarkdownOption>, source: string): void;

View File

@@ -0,0 +1,16 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const linkify_1 = require("./linkify");
function markdownLoader(source) {
const fileString = source;
const callback = this.async();
const options = this.getOptions();
return callback(null, (0, linkify_1.linkify)(fileString, this.resourcePath, options));
}
exports.default = markdownLoader;

View File

@@ -0,0 +1,8 @@
/**
* 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 type { DocsMarkdownOption } from '../types';
export declare function linkify(fileString: string, filePath: string, options: DocsMarkdownOption): string;

View File

@@ -0,0 +1,34 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.linkify = void 0;
const utils_1 = require("@docusaurus/utils");
function getVersion(filePath, options) {
const versionFound = options.versionsMetadata.find((version) => (0, utils_1.getContentPathList)(version).some((docsDirPath) => filePath.startsWith(docsDirPath)));
// At this point, this should never happen, because the MDX loaders' paths are
// literally using the version content paths; but if we allow sourcing content
// from outside the docs directory (through the `include` option, for example;
// is there a compelling use-case?), this would actually be testable
if (!versionFound) {
throw new Error(`Unexpected error: Markdown file at "${filePath}" does not belong to any docs version!`);
}
return versionFound;
}
function linkify(fileString, filePath, options) {
const { siteDir, sourceToPermalink, onBrokenMarkdownLink } = options;
const { newContent, brokenMarkdownLinks } = (0, utils_1.replaceMarkdownLinks)({
siteDir,
fileString,
filePath,
contentPaths: getVersion(filePath, options),
sourceToPermalink,
});
brokenMarkdownLinks.forEach((l) => onBrokenMarkdownLink(l));
return newContent;
}
exports.linkify = linkify;

View File

@@ -0,0 +1,11 @@
/**
* 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 type { NumberPrefixParser } from '@docusaurus/plugin-content-docs';
export declare const DefaultNumberPrefixParser: NumberPrefixParser;
export declare const DisabledNumberPrefixParser: NumberPrefixParser;
export declare function stripNumberPrefix(str: string, parser: NumberPrefixParser): string;
export declare function stripPathNumberPrefixes(path: string, parser: NumberPrefixParser): string;

View File

@@ -0,0 +1,50 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.stripPathNumberPrefixes = exports.stripNumberPrefix = exports.DisabledNumberPrefixParser = exports.DefaultNumberPrefixParser = void 0;
// Best-effort to avoid parsing some patterns as number prefix
// ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640
// ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653
// Both of them would look like 7.0-foo or 2021-11-foo
// note: we could try to parse float numbers in filenames, but that is probably
// not worth it, as a version such as "8.0" can be interpreted as either a
// version or a float. User can configure her own NumberPrefixParser if she
// wants 8.0 to be interpreted as a float
const ignoredPrefixPattern = /^\d+[-_.]\d+/;
const numberPrefixPattern = /^(?<numberPrefix>\d+)\s*[-_.]+\s*(?<suffix>[^-_.\s].*)$/;
// 0-myDoc => {filename: myDoc, numberPrefix: 0}
// 003 - myDoc => {filename: myDoc, numberPrefix: 3}
const DefaultNumberPrefixParser = (filename) => {
if (ignoredPrefixPattern.test(filename)) {
return { filename, numberPrefix: undefined };
}
const match = numberPrefixPattern.exec(filename);
if (!match) {
return { filename, numberPrefix: undefined };
}
return {
filename: match.groups.suffix,
numberPrefix: parseInt(match.groups.numberPrefix, 10),
};
};
exports.DefaultNumberPrefixParser = DefaultNumberPrefixParser;
const DisabledNumberPrefixParser = (filename) => ({ filename, numberPrefix: undefined });
exports.DisabledNumberPrefixParser = DisabledNumberPrefixParser;
// 0-myDoc => myDoc
function stripNumberPrefix(str, parser) {
return parser(str).filename;
}
exports.stripNumberPrefix = stripNumberPrefix;
// 0-myFolder/0-mySubfolder/0-myDoc => myFolder/mySubfolder/myDoc
function stripPathNumberPrefixes(path, parser) {
return path
.split('/')
.map((segment) => stripNumberPrefix(segment, parser))
.join('/');
}
exports.stripPathNumberPrefixes = stripPathNumberPrefixes;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { OptionValidationContext } from '@docusaurus/types';
import type { PluginOptions, Options } from '@docusaurus/plugin-content-docs';
export declare const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'>;
export declare function validateOptions({ validate, options: userOptions, }: OptionValidationContext<Options, PluginOptions>): PluginOptions;

View File

@@ -0,0 +1,127 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateOptions = exports.DEFAULT_OPTIONS = void 0;
const tslib_1 = require("tslib");
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_validation_1 = require("@docusaurus/utils-validation");
const utils_1 = require("@docusaurus/utils");
const generator_1 = require("./sidebars/generator");
const numberPrefix_1 = require("./numberPrefix");
exports.DEFAULT_OPTIONS = {
path: 'docs',
routeBasePath: 'docs',
tagsBasePath: 'tags',
include: ['**/*.{md,mdx}'],
exclude: utils_1.GlobExcludeDefault,
sidebarItemsGenerator: generator_1.DefaultSidebarItemsGenerator,
numberPrefixParser: numberPrefix_1.DefaultNumberPrefixParser,
docsRootComponent: '@theme/DocsRoot',
docVersionRootComponent: '@theme/DocVersionRoot',
docRootComponent: '@theme/DocRoot',
docItemComponent: '@theme/DocItem',
docTagDocListComponent: '@theme/DocTagDocListPage',
docTagsListComponent: '@theme/DocTagsListPage',
docCategoryGeneratedIndexComponent: '@theme/DocCategoryGeneratedIndexPage',
remarkPlugins: [],
rehypePlugins: [],
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [],
showLastUpdateTime: false,
showLastUpdateAuthor: false,
admonitions: true,
includeCurrentVersion: true,
disableVersioning: false,
lastVersion: undefined,
versions: {},
editCurrentVersion: false,
editLocalizedFiles: false,
sidebarCollapsible: true,
sidebarCollapsed: true,
breadcrumbs: true,
};
const VersionOptionsSchema = utils_validation_1.Joi.object({
path: utils_validation_1.Joi.string().allow('').optional(),
label: utils_validation_1.Joi.string().optional(),
banner: utils_validation_1.Joi.string().equal('none', 'unreleased', 'unmaintained').optional(),
badge: utils_validation_1.Joi.boolean().optional(),
className: utils_validation_1.Joi.string().optional(),
noIndex: utils_validation_1.Joi.boolean().optional(),
});
const VersionsOptionsSchema = utils_validation_1.Joi.object()
.pattern(utils_validation_1.Joi.string().required(), VersionOptionsSchema)
.default(exports.DEFAULT_OPTIONS.versions);
const OptionsSchema = utils_validation_1.Joi.object({
path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
editUrl: utils_validation_1.Joi.alternatives().try(utils_validation_1.URISchema, utils_validation_1.Joi.function()),
editCurrentVersion: utils_validation_1.Joi.boolean().default(exports.DEFAULT_OPTIONS.editCurrentVersion),
editLocalizedFiles: utils_validation_1.Joi.boolean().default(exports.DEFAULT_OPTIONS.editLocalizedFiles),
routeBasePath: utils_validation_1.RouteBasePathSchema.default(exports.DEFAULT_OPTIONS.routeBasePath),
tagsBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.tagsBasePath),
// @ts-expect-error: deprecated
homePageId: utils_validation_1.Joi.any().forbidden().messages({
'any.unknown': 'The docs plugin option homePageId is not supported anymore. To make a doc the "home", please add "slug: /" in its front matter. See: https://docusaurus.io/docs/next/docs-introduction#home-page-docs',
}),
include: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.include),
exclude: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.exclude),
sidebarPath: utils_validation_1.Joi.alternatives().try(utils_validation_1.Joi.boolean().invalid(true), utils_validation_1.Joi.string()),
sidebarItemsGenerator: utils_validation_1.Joi.function().default(() => exports.DEFAULT_OPTIONS.sidebarItemsGenerator),
sidebarCollapsible: utils_validation_1.Joi.boolean().default(exports.DEFAULT_OPTIONS.sidebarCollapsible),
sidebarCollapsed: utils_validation_1.Joi.boolean().default(exports.DEFAULT_OPTIONS.sidebarCollapsed),
numberPrefixParser: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.function(),
// Convert boolean values to functions
utils_validation_1.Joi.alternatives().conditional(utils_validation_1.Joi.boolean(), {
then: utils_validation_1.Joi.custom((val) => val ? numberPrefix_1.DefaultNumberPrefixParser : numberPrefix_1.DisabledNumberPrefixParser),
}))
.default(() => exports.DEFAULT_OPTIONS.numberPrefixParser),
docsRootComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docsRootComponent),
docVersionRootComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docVersionRootComponent),
docRootComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docRootComponent),
docItemComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docItemComponent),
docTagsListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docTagsListComponent),
docTagDocListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docTagDocListComponent),
docCategoryGeneratedIndexComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.docCategoryGeneratedIndexComponent),
remarkPlugins: utils_validation_1.RemarkPluginsSchema.default(exports.DEFAULT_OPTIONS.remarkPlugins),
rehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.rehypePlugins),
beforeDefaultRemarkPlugins: utils_validation_1.RemarkPluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRemarkPlugins),
beforeDefaultRehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
admonitions: utils_validation_1.AdmonitionsSchema.default(exports.DEFAULT_OPTIONS.admonitions),
showLastUpdateTime: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showLastUpdateTime),
showLastUpdateAuthor: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showLastUpdateAuthor),
includeCurrentVersion: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.includeCurrentVersion),
onlyIncludeVersions: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string().required()).optional(),
disableVersioning: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.disableVersioning),
lastVersion: utils_validation_1.Joi.string().optional(),
versions: VersionsOptionsSchema,
breadcrumbs: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.breadcrumbs),
});
function validateOptions({ validate, options: userOptions, }) {
let options = userOptions;
if (options.sidebarCollapsible === false) {
// When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't
// want to have the inconsistency warning. We let options.sidebarCollapsible
// become the default value for options.sidebarCollapsed
if (typeof options.sidebarCollapsed === 'undefined') {
options = {
...options,
sidebarCollapsed: false,
};
}
if (options.sidebarCollapsed) {
logger_1.default.warn `The docs plugin config is inconsistent. It does not make sense to use code=${'sidebarCollapsible: false'} and code=${'sidebarCollapsed: true'} at the same time. code=${'sidebarCollapsed: true'} will be ignored.`;
options = {
...options,
sidebarCollapsed: false,
};
}
}
const normalizedOptions = validate(OptionsSchema, options);
return normalizedOptions;
}
exports.validateOptions = validateOptions;

View File

@@ -0,0 +1,21 @@
/**
* 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 type { VersionTag, VersionTags } from './types';
import type { SidebarItemDoc } from './sidebars/types';
import type { PropSidebars, PropVersionMetadata, PropTagDocList, PropTagsListPage, PropSidebarItemLink, DocMetadata, LoadedVersion } from '@docusaurus/plugin-content-docs';
export declare function toSidebarDocItemLinkProp({ item, doc, }: {
item: SidebarItemDoc;
doc: Pick<DocMetadata, 'id' | 'title' | 'permalink' | 'unlisted' | 'frontMatter'>;
}): PropSidebarItemLink;
export declare function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars;
export declare function toVersionMetadataProp(pluginId: string, loadedVersion: LoadedVersion): PropVersionMetadata;
export declare function toTagDocListProp({ allTagsPath, tag, docs, }: {
allTagsPath: string;
tag: VersionTag;
docs: DocMetadata[];
}): PropTagDocList;
export declare function toTagsListTagsProp(versionTags: VersionTags): PropTagsListPage['tags'];

View File

@@ -0,0 +1,153 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.toTagsListTagsProp = exports.toTagDocListProp = exports.toVersionMetadataProp = exports.toSidebarsProp = exports.toSidebarDocItemLinkProp = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const docs_1 = require("./docs");
function toSidebarDocItemLinkProp({ item, doc, }) {
const { id, title, permalink, frontMatter: { sidebar_label: sidebarLabel, sidebar_custom_props: customProps, }, unlisted, } = doc;
return {
type: 'link',
label: sidebarLabel ?? item.label ?? title,
href: permalink,
className: item.className,
customProps: item.customProps ?? customProps,
docId: id,
unlisted,
};
}
exports.toSidebarDocItemLinkProp = toSidebarDocItemLinkProp;
function toSidebarsProp(loadedVersion) {
const docsById = (0, docs_1.createDocsByIdIndex)(loadedVersion.docs);
function getDocById(docId) {
const docMetadata = docsById[docId];
if (!docMetadata) {
throw new Error(`Invalid sidebars file. The document with id "${docId}" was used in the sidebar, but no document with this id could be found.
Available document ids are:
- ${Object.keys(docsById).sort().join('\n- ')}`);
}
return docMetadata;
}
const convertDocLink = (item) => {
const doc = getDocById(item.id);
return toSidebarDocItemLinkProp({ item, doc });
};
function getCategoryLinkHref(link) {
switch (link?.type) {
case 'doc':
return getDocById(link.id).permalink;
case 'generated-index':
return link.permalink;
default:
return undefined;
}
}
function getCategoryLinkUnlisted(link) {
if (link?.type === 'doc') {
return getDocById(link.id).unlisted;
}
return false;
}
function getCategoryLinkCustomProps(link) {
switch (link?.type) {
case 'doc':
return getDocById(link.id).frontMatter.sidebar_custom_props;
default:
return undefined;
}
}
function convertCategory(item) {
const { link, ...rest } = item;
const href = getCategoryLinkHref(link);
const linkUnlisted = getCategoryLinkUnlisted(link);
const customProps = item.customProps ?? getCategoryLinkCustomProps(link);
return {
...rest,
items: item.items.map(normalizeItem),
...(href && { href }),
...(linkUnlisted && { linkUnlisted }),
...(customProps && { customProps }),
};
}
function normalizeItem(item) {
switch (item.type) {
case 'category':
return convertCategory(item);
case 'ref':
case 'doc':
return convertDocLink(item);
case 'link':
default:
return item;
}
}
// Transform the sidebar so that all sidebar item will be in the
// form of 'link' or 'category' only.
// This is what will be passed as props to the UI component.
return lodash_1.default.mapValues(loadedVersion.sidebars, (items) => items.map(normalizeItem));
}
exports.toSidebarsProp = toSidebarsProp;
function toVersionDocsProp(loadedVersion) {
return Object.fromEntries(loadedVersion.docs.map((doc) => [
doc.id,
{
id: doc.id,
title: doc.title,
description: doc.description,
sidebar: doc.sidebar,
},
]));
}
function toVersionMetadataProp(pluginId, loadedVersion) {
return {
pluginId,
version: loadedVersion.versionName,
label: loadedVersion.label,
banner: loadedVersion.banner,
badge: loadedVersion.badge,
noIndex: loadedVersion.noIndex,
className: loadedVersion.className,
isLast: loadedVersion.isLast,
docsSidebars: toSidebarsProp(loadedVersion),
docs: toVersionDocsProp(loadedVersion),
};
}
exports.toVersionMetadataProp = toVersionMetadataProp;
function toTagDocListProp({ allTagsPath, tag, docs, }) {
function toDocListProp() {
const list = lodash_1.default.compact(tag.docIds.map((id) => docs.find((doc) => doc.id === id)));
// Sort docs by title
list.sort((doc1, doc2) => doc1.title.localeCompare(doc2.title));
return list.map((doc) => ({
id: doc.id,
title: doc.title,
description: doc.description,
permalink: doc.permalink,
}));
}
return {
label: tag.label,
permalink: tag.permalink,
allTagsPath,
count: tag.docIds.length,
items: toDocListProp(),
unlisted: tag.unlisted,
};
}
exports.toTagDocListProp = toTagDocListProp;
function toTagsListTagsProp(versionTags) {
return Object.values(versionTags)
.filter((tagValue) => !tagValue.unlisted)
.map((tagValue) => ({
label: tagValue.label,
permalink: tagValue.permalink,
count: tagValue.docIds.length,
}));
}
exports.toTagsListTagsProp = toTagsListTagsProp;

View File

@@ -0,0 +1,22 @@
/**
* 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 type { PluginContentLoadedActions, RouteConfig } from '@docusaurus/types';
import type { FullVersion } from './types';
import type { PluginOptions } from '@docusaurus/plugin-content-docs';
type BuildAllRoutesParam = Omit<CreateAllRoutesParam, 'actions'> & {
actions: Omit<PluginContentLoadedActions, 'addRoute' | 'setGlobalData'>;
};
export declare function buildAllRoutes(param: BuildAllRoutesParam): Promise<RouteConfig[]>;
type CreateAllRoutesParam = {
baseUrl: string;
versions: FullVersion[];
options: PluginOptions;
actions: PluginContentLoadedActions;
aliasedSource: (str: string) => string;
};
export declare function createAllRoutes(param: CreateAllRoutesParam): Promise<void>;
export {};

View File

@@ -0,0 +1,168 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAllRoutes = exports.buildAllRoutes = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const props_1 = require("./props");
const tags_1 = require("./tags");
async function buildVersionCategoryGeneratedIndexRoutes({ version, actions, options, aliasedSource, }) {
const slugs = (0, utils_1.createSlugger)();
async function buildCategoryGeneratedIndexRoute(categoryGeneratedIndex) {
const { sidebar, ...prop } = categoryGeneratedIndex;
const propFileName = slugs.slug(`${version.path}-${categoryGeneratedIndex.sidebar}-category-${categoryGeneratedIndex.title}`);
const propData = await actions.createData(`${(0, utils_1.docuHash)(`category/${propFileName}`)}.json`, JSON.stringify(prop, null, 2));
return {
path: categoryGeneratedIndex.permalink,
component: options.docCategoryGeneratedIndexComponent,
exact: true,
modules: {
categoryGeneratedIndex: aliasedSource(propData),
},
// Same as doc, this sidebar route attribute permits to associate this
// subpage to the given sidebar
...(sidebar && { sidebar }),
};
}
return Promise.all(version.categoryGeneratedIndices.map(buildCategoryGeneratedIndexRoute));
}
async function buildVersionDocRoutes({ version, actions, options, }) {
return Promise.all(version.docs.map(async (metadataItem) => {
await actions.createData(
// Note that this created data path must be in sync with
// metadataPath provided to mdx-loader.
`${(0, utils_1.docuHash)(metadataItem.source)}.json`, JSON.stringify(metadataItem, null, 2));
const docRoute = {
path: metadataItem.permalink,
component: options.docItemComponent,
exact: true,
modules: {
content: metadataItem.source,
},
// Because the parent (DocRoot) comp need to access it easily
// This permits to render the sidebar once without unmount/remount when
// navigating (and preserve sidebar state)
...(metadataItem.sidebar && {
sidebar: metadataItem.sidebar,
}),
};
return docRoute;
}));
}
async function buildVersionSidebarRoute(param) {
const [docRoutes, categoryGeneratedIndexRoutes] = await Promise.all([
buildVersionDocRoutes(param),
buildVersionCategoryGeneratedIndexRoutes(param),
]);
const subRoutes = [...docRoutes, ...categoryGeneratedIndexRoutes];
return {
path: param.version.path,
exact: false,
component: param.options.docRootComponent,
routes: subRoutes,
};
}
async function buildVersionTagsRoutes(param) {
const { version, options, actions, aliasedSource } = param;
const versionTags = (0, tags_1.getVersionTags)(version.docs);
async function buildTagsListRoute() {
// Don't create a tags list page if there's no tag
if (Object.keys(versionTags).length === 0) {
return null;
}
const tagsProp = (0, props_1.toTagsListTagsProp)(versionTags);
const tagsPropPath = await actions.createData(`${(0, utils_1.docuHash)(`tags-list-${version.versionName}-prop`)}.json`, JSON.stringify(tagsProp, null, 2));
return {
path: version.tagsPath,
exact: true,
component: options.docTagsListComponent,
modules: {
tags: aliasedSource(tagsPropPath),
},
};
}
async function buildTagDocListRoute(tag) {
const tagProps = (0, props_1.toTagDocListProp)({
allTagsPath: version.tagsPath,
tag,
docs: version.docs,
});
const tagPropPath = await actions.createData(`${(0, utils_1.docuHash)(`tag-${tag.permalink}`)}.json`, JSON.stringify(tagProps, null, 2));
return {
path: tag.permalink,
component: options.docTagDocListComponent,
exact: true,
modules: {
tag: aliasedSource(tagPropPath),
},
};
}
const [tagsListRoute, allTagsDocListRoutes] = await Promise.all([
buildTagsListRoute(),
Promise.all(Object.values(versionTags).map(buildTagDocListRoute)),
]);
return lodash_1.default.compact([tagsListRoute, ...allTagsDocListRoutes]);
}
async function buildVersionRoutes(param) {
const { version, actions, options, aliasedSource } = param;
async function buildVersionSubRoutes() {
const [sidebarRoute, tagsRoutes] = await Promise.all([
buildVersionSidebarRoute(param),
buildVersionTagsRoutes(param),
]);
return [sidebarRoute, ...tagsRoutes];
}
async function doBuildVersionRoutes() {
const versionProp = (0, props_1.toVersionMetadataProp)(options.id, version);
const versionPropPath = await actions.createData(`${(0, utils_1.docuHash)(`version-${version.versionName}-metadata-prop`)}.json`, JSON.stringify(versionProp, null, 2));
const subRoutes = await buildVersionSubRoutes();
return {
path: version.path,
exact: false,
component: options.docVersionRootComponent,
routes: subRoutes,
modules: {
version: aliasedSource(versionPropPath),
},
priority: version.routePriority,
};
}
try {
return await doBuildVersionRoutes();
}
catch (err) {
logger_1.default.error `Can't create version routes for version name=${version.versionName}`;
throw err;
}
}
// TODO we want this buildAllRoutes function to be easily testable
// Ideally, we should avoid side effects here (ie not injecting actions)
async function buildAllRoutes(param) {
const subRoutes = await Promise.all(param.versions.map((version) => buildVersionRoutes({
...param,
version,
})));
// all docs routes are wrapped under a single parent route, this ensures
// the theme layout never unmounts/remounts when navigating between versions
return [
{
path: (0, utils_1.normalizeUrl)([param.baseUrl, param.options.routeBasePath]),
exact: false,
component: param.options.docsRootComponent,
routes: subRoutes,
},
];
}
exports.buildAllRoutes = buildAllRoutes;
async function createAllRoutes(param) {
const routes = await buildAllRoutes(param);
routes.forEach(param.actions.addRoute);
}
exports.createAllRoutes = createAllRoutes;

View File

@@ -0,0 +1,9 @@
/**
* 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.
*/
export { CURRENT_VERSION_NAME, VERSIONED_DOCS_DIR, VERSIONED_SIDEBARS_DIR, VERSIONS_JSON_FILE, } from './constants';
export { filterVersions, getDefaultVersionBanner, getVersionBadge, getVersionBanner, } from './versions';
export { readVersionNames } from './versions/files';

View File

@@ -0,0 +1,25 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.readVersionNames = exports.getVersionBanner = exports.getVersionBadge = exports.getDefaultVersionBanner = exports.filterVersions = exports.VERSIONS_JSON_FILE = exports.VERSIONED_SIDEBARS_DIR = exports.VERSIONED_DOCS_DIR = exports.CURRENT_VERSION_NAME = void 0;
// APIs available to Node.js
// Those are undocumented but used by some third-party plugins
// For this reason it's preferable to avoid doing breaking changes
// See also https://github.com/facebook/docusaurus/pull/6477
var constants_1 = require("./constants");
Object.defineProperty(exports, "CURRENT_VERSION_NAME", { enumerable: true, get: function () { return constants_1.CURRENT_VERSION_NAME; } });
Object.defineProperty(exports, "VERSIONED_DOCS_DIR", { enumerable: true, get: function () { return constants_1.VERSIONED_DOCS_DIR; } });
Object.defineProperty(exports, "VERSIONED_SIDEBARS_DIR", { enumerable: true, get: function () { return constants_1.VERSIONED_SIDEBARS_DIR; } });
Object.defineProperty(exports, "VERSIONS_JSON_FILE", { enumerable: true, get: function () { return constants_1.VERSIONS_JSON_FILE; } });
var versions_1 = require("./versions");
Object.defineProperty(exports, "filterVersions", { enumerable: true, get: function () { return versions_1.filterVersions; } });
Object.defineProperty(exports, "getDefaultVersionBanner", { enumerable: true, get: function () { return versions_1.getDefaultVersionBanner; } });
Object.defineProperty(exports, "getVersionBadge", { enumerable: true, get: function () { return versions_1.getVersionBadge; } });
Object.defineProperty(exports, "getVersionBanner", { enumerable: true, get: function () { return versions_1.getVersionBanner; } });
var files_1 = require("./versions/files");
Object.defineProperty(exports, "readVersionNames", { enumerable: true, get: function () { return files_1.readVersionNames; } });

View File

@@ -0,0 +1,10 @@
/**
* 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 type { SidebarItemsGenerator } from './types';
export declare const CategoryMetadataFilenameBase = "_category_";
export declare const CategoryMetadataFilenamePattern = "_category_.{json,yml,yaml}";
export declare const DefaultSidebarItemsGenerator: SidebarItemsGenerator;

View File

@@ -0,0 +1,210 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultSidebarItemsGenerator = exports.CategoryMetadataFilenamePattern = exports.CategoryMetadataFilenameBase = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const docs_1 = require("../docs");
const BreadcrumbSeparator = '/';
// Just an alias to the make code more explicit
function getLocalDocId(docId) {
return lodash_1.default.last(docId.split('/'));
}
exports.CategoryMetadataFilenameBase = '_category_';
exports.CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}';
// Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449
const DefaultSidebarItemsGenerator = ({ numberPrefixParser, isCategoryIndex, docs: allDocs, item: { dirName: autogenDir }, categoriesMetadata, }) => {
const docsById = (0, docs_1.createDocsByIdIndex)(allDocs);
const findDoc = (docId) => docsById[docId];
const getDoc = (docId) => {
const doc = findDoc(docId);
if (!doc) {
throw new Error(`Can't find any doc with ID ${docId}.
Available doc IDs:
- ${Object.keys(docsById).join('\n- ')}`);
}
return doc;
};
/**
* Step 1. Extract the docs that are in the autogen dir.
*/
function getAutogenDocs() {
function isInAutogeneratedDir(doc) {
return (
// Doc at the root of the autogenerated sidebar dir
doc.sourceDirName === autogenDir ||
// Autogen dir is . and doc is in subfolder
autogenDir === '.' ||
// Autogen dir is not . and doc is in subfolder
// "api/myDoc" startsWith "api/" (note "api2/myDoc" is not included)
doc.sourceDirName.startsWith((0, utils_1.addTrailingSlash)(autogenDir)));
}
const docs = allDocs.filter(isInAutogeneratedDir);
if (docs.length === 0) {
logger_1.default.warn `No docs found in path=${autogenDir}: can't auto-generate a sidebar.`;
}
return docs;
}
/**
* Step 2. Turn the linear file list into a tree structure.
*/
function treeify(docs) {
// Get the category breadcrumb of a doc (relative to the dir of the
// autogenerated sidebar item)
// autogenDir=a/b and docDir=a/b/c/d => returns [c, d]
// autogenDir=a/b and docDir=a/b => returns []
// TODO: try to use path.relative()
function getRelativeBreadcrumb(doc) {
return autogenDir === doc.sourceDirName
? []
: doc.sourceDirName
.replace((0, utils_1.addTrailingSlash)(autogenDir), '')
.split(BreadcrumbSeparator);
}
const treeRoot = {};
docs.forEach((doc) => {
const breadcrumb = getRelativeBreadcrumb(doc);
// We walk down the file's path to generate the fs structure
let currentDir = treeRoot;
breadcrumb.forEach((dir) => {
if (typeof currentDir[dir] === 'undefined') {
currentDir[dir] = {}; // Create new folder.
}
currentDir = currentDir[dir]; // Go into the subdirectory.
});
// We've walked through the path. Register the file in this directory.
currentDir[path_1.default.basename(doc.source)] = doc.id;
});
return treeRoot;
}
/**
* Step 3. Recursively transform the tree-like structure to sidebar items.
* (From a record to an array of items, akin to normalizing shorthand)
*/
function generateSidebar(fsModel) {
function createDocItem(id, fullPath, fileName) {
const { sidebarPosition: position, frontMatter: { sidebar_label: label, sidebar_class_name: className, sidebar_custom_props: customProps, }, } = getDoc(id);
return {
type: 'doc',
id,
position,
source: fileName,
// We don't want these fields to magically appear in the generated
// sidebar
...(label !== undefined && { label }),
...(className !== undefined && { className }),
...(customProps !== undefined && { customProps }),
};
}
function createCategoryItem(dir, fullPath, folderName) {
const categoryMetadata = categoriesMetadata[path_1.default.posix.join(autogenDir, fullPath)];
const allItems = Object.entries(dir).map(([key, content]) => dirToItem(content, key, `${fullPath}/${key}`));
// Try to match a doc inside the category folder,
// using the "local id" (myDoc) or "qualified id" (dirName/myDoc)
function findDocByLocalId(localId) {
return allItems.find((item) => item.type === 'doc' && getLocalDocId(item.id) === localId);
}
function findConventionalCategoryDocLink() {
return allItems.find((item) => {
if (item.type !== 'doc') {
return false;
}
const doc = getDoc(item.id);
return isCategoryIndex((0, docs_1.toCategoryIndexMatcherParam)(doc));
});
}
// In addition to the ID, this function also retrieves metadata of the
// linked doc that could be used as fallback values for category metadata
function getCategoryLinkedDocMetadata() {
const link = categoryMetadata?.link;
if (link !== undefined && link?.type !== 'doc') {
// If a link is explicitly specified, we won't apply conventions
return undefined;
}
const id = link
? findDocByLocalId(link.id)?.id ?? getDoc(link.id).id
: findConventionalCategoryDocLink()?.id;
if (!id) {
return undefined;
}
const doc = getDoc(id);
return {
id,
position: doc.sidebarPosition,
label: doc.frontMatter.sidebar_label ?? doc.title,
customProps: doc.frontMatter.sidebar_custom_props,
className: doc.frontMatter.sidebar_class_name,
};
}
const categoryLinkedDoc = getCategoryLinkedDocMetadata();
const link = categoryLinkedDoc
? {
type: 'doc',
id: categoryLinkedDoc.id, // We "remap" a potentially "local id" to a "qualified id"
}
: categoryMetadata?.link;
// If a doc is linked, remove it from the category subItems
const items = allItems.filter((item) => !(item.type === 'doc' && item.id === categoryLinkedDoc?.id));
const className = categoryMetadata?.className ?? categoryLinkedDoc?.className;
const customProps = categoryMetadata?.customProps ?? categoryLinkedDoc?.customProps;
const { filename, numberPrefix } = numberPrefixParser(folderName);
return {
type: 'category',
label: categoryMetadata?.label ?? categoryLinkedDoc?.label ?? filename,
collapsible: categoryMetadata?.collapsible,
collapsed: categoryMetadata?.collapsed,
position: categoryMetadata?.position ??
categoryLinkedDoc?.position ??
numberPrefix,
source: folderName,
...(customProps !== undefined && { customProps }),
...(className !== undefined && { className }),
items,
...(link && { link }),
};
}
function dirToItem(dir, // The directory item to be transformed.
itemKey, // File/folder name; for categories, it's used to generate the next `relativePath`.
fullPath) {
return typeof dir === 'object'
? createCategoryItem(dir, fullPath, itemKey)
: createDocItem(dir, fullPath, itemKey);
}
return Object.entries(fsModel).map(([key, content]) => dirToItem(content, key, key));
}
/**
* Step 4. Recursively sort the categories/docs + remove the "position"
* attribute from final output. Note: the "position" is only used to sort
* "inside" a sidebar slice. It is not used to sort across multiple
* consecutive sidebar slices (i.e. a whole category composed of multiple
* autogenerated items)
*/
function sortItems(sidebarItems) {
const processedSidebarItems = sidebarItems.map((item) => {
if (item.type === 'category') {
return { ...item, items: sortItems(item.items) };
}
return item;
});
const sortedSidebarItems = lodash_1.default.sortBy(processedSidebarItems, [
'position',
'source',
]);
return sortedSidebarItems.map(({ position, source, ...item }) => item);
}
// TODO: the whole code is designed for pipeline operator
const docs = getAutogenDocs();
const fsModel = treeify(docs);
const sidebarWithPosition = generateSidebar(fsModel);
const sortedSidebar = sortItems(sidebarWithPosition);
return sortedSidebar;
};
exports.DefaultSidebarItemsGenerator = DefaultSidebarItemsGenerator;

View File

@@ -0,0 +1,13 @@
/**
* 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 type { PluginOptions } from '@docusaurus/plugin-content-docs';
import type { SidebarsConfig, Sidebars, SidebarProcessorParams } from './types';
export declare const DefaultSidebars: SidebarsConfig;
export declare const DisabledSidebars: SidebarsConfig;
export declare function resolveSidebarPathOption(siteDir: string, sidebarPathOption: PluginOptions['sidebarPath']): PluginOptions['sidebarPath'];
export declare function loadSidebarsFile(sidebarFilePath: string | false | undefined): Promise<SidebarsConfig>;
export declare function loadSidebars(sidebarFilePath: string | false | undefined, options: SidebarProcessorParams): Promise<Sidebars>;

View File

@@ -0,0 +1,98 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadSidebars = exports.loadSidebarsFile = exports.resolveSidebarPathOption = exports.DisabledSidebars = exports.DefaultSidebars = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
const combine_promises_1 = tslib_1.__importDefault(require("combine-promises"));
const validation_1 = require("./validation");
const normalization_1 = require("./normalization");
const processor_1 = require("./processor");
const postProcessor_1 = require("./postProcessor");
exports.DefaultSidebars = {
defaultSidebar: [
{
type: 'autogenerated',
dirName: '.',
},
],
};
exports.DisabledSidebars = {};
// If a path is provided, make it absolute
function resolveSidebarPathOption(siteDir, sidebarPathOption) {
return sidebarPathOption
? path_1.default.resolve(siteDir, sidebarPathOption)
: sidebarPathOption;
}
exports.resolveSidebarPathOption = resolveSidebarPathOption;
async function readCategoriesMetadata(contentPath) {
const categoryFiles = await (0, utils_1.Globby)('**/_category_.{json,yml,yaml}', {
cwd: contentPath,
});
const categoryToFile = lodash_1.default.groupBy(categoryFiles, path_1.default.dirname);
return (0, combine_promises_1.default)(lodash_1.default.mapValues(categoryToFile, async (files, folder) => {
const filePath = files[0];
if (files.length > 1) {
logger_1.default.warn `There are more than one category metadata files for path=${folder}: ${files.join(', ')}. The behavior is undetermined.`;
}
const content = await fs_extra_1.default.readFile(path_1.default.join(contentPath, filePath), 'utf-8');
try {
return (0, validation_1.validateCategoryMetadataFile)(js_yaml_1.default.load(content));
}
catch (err) {
logger_1.default.error `The docs sidebar category metadata file path=${filePath} looks invalid!`;
throw err;
}
}));
}
async function loadSidebarsFileUnsafe(sidebarFilePath) {
// false => no sidebars
if (sidebarFilePath === false) {
return exports.DisabledSidebars;
}
// undefined => defaults to autogenerated sidebars
if (typeof sidebarFilePath === 'undefined') {
return exports.DefaultSidebars;
}
// Non-existent sidebars file: no sidebars
// Note: this edge case can happen on versioned docs, not current version
// We avoid creating empty versioned sidebars file with the CLI
if (!(await fs_extra_1.default.pathExists(sidebarFilePath))) {
return exports.DisabledSidebars;
}
// We don't want sidebars to be cached because of hot reloading.
const module = await (0, utils_1.loadFreshModule)(sidebarFilePath);
// TODO unsafe, need to refactor and improve validation
return module;
}
async function loadSidebarsFile(sidebarFilePath) {
const sidebars = await loadSidebarsFileUnsafe(sidebarFilePath);
// TODO unsafe, need to refactor and improve validation
return sidebars;
}
exports.loadSidebarsFile = loadSidebarsFile;
async function loadSidebars(sidebarFilePath, options) {
try {
const sidebarsConfig = await loadSidebarsFileUnsafe(sidebarFilePath);
const normalizedSidebars = (0, normalization_1.normalizeSidebars)(sidebarsConfig);
(0, validation_1.validateSidebars)(normalizedSidebars);
const categoriesMetadata = await readCategoriesMetadata(options.version.contentPath);
const processedSidebars = await (0, processor_1.processSidebars)(normalizedSidebars, categoriesMetadata, options);
return (0, postProcessor_1.postProcessSidebars)(processedSidebars, options);
}
catch (err) {
logger_1.default.error `Sidebars file at path=${sidebarFilePath} failed to be loaded.`;
throw err;
}
}
exports.loadSidebars = loadSidebars;

View File

@@ -0,0 +1,13 @@
/**
* 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 type { NormalizedSidebarItem, NormalizedSidebars, SidebarItemConfig, SidebarsConfig } from './types';
/**
* Normalizes recursively item and all its children. Ensures that at the end
* each item will be an object with the corresponding type.
*/
export declare function normalizeItem(item: SidebarItemConfig): NormalizedSidebarItem[];
export declare function normalizeSidebars(sidebars: SidebarsConfig): NormalizedSidebars;

View File

@@ -0,0 +1,59 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeSidebars = exports.normalizeItem = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("./utils");
function normalizeCategoriesShorthand(sidebar) {
return Object.entries(sidebar).map(([label, items]) => ({
type: 'category',
label,
items,
}));
}
/**
* Normalizes recursively item and all its children. Ensures that at the end
* each item will be an object with the corresponding type.
*/
function normalizeItem(item) {
if (typeof item === 'string') {
return [{ type: 'doc', id: item }];
}
if ((0, utils_1.isCategoriesShorthand)(item)) {
// This will never throw anyways
return normalizeSidebar(item, 'sidebar items slice');
}
if ((item.type === 'doc' || item.type === 'ref') &&
typeof item.label === 'string') {
return [{ ...item, translatable: true }];
}
if (item.type === 'category') {
const normalizedCategory = {
...item,
items: normalizeSidebar(item.items, logger_1.default.interpolate `code=${'items'} of the category name=${item.label}`),
};
return [normalizedCategory];
}
return [item];
}
exports.normalizeItem = normalizeItem;
function normalizeSidebar(sidebar, place) {
if (!Array.isArray(sidebar) && !(0, utils_1.isCategoriesShorthand)(sidebar)) {
throw new Error(logger_1.default.interpolate `Invalid sidebar items collection code=${JSON.stringify(sidebar)} in ${place}: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a code=${'type'} property). See url=${'https://docusaurus.io/docs/sidebar/items'} for all valid syntaxes.`);
}
const normalizedSidebar = Array.isArray(sidebar)
? sidebar
: normalizeCategoriesShorthand(sidebar);
return normalizedSidebar.flatMap((subItem) => normalizeItem(subItem));
}
function normalizeSidebars(sidebars) {
return lodash_1.default.mapValues(sidebars, (sidebar, id) => normalizeSidebar(sidebar, logger_1.default.interpolate `sidebar name=${id}`));
}
exports.normalizeSidebars = normalizeSidebars;

View File

@@ -0,0 +1,11 @@
/**
* 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 type { Sidebars, SidebarProcessorParams, ProcessedSidebars } from './types';
export type SidebarPostProcessorParams = SidebarProcessorParams & {
draftIds: Set<string>;
};
export declare function postProcessSidebars(sidebars: ProcessedSidebars, params: SidebarProcessorParams): Sidebars;

View File

@@ -0,0 +1,80 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.postProcessSidebars = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
function normalizeCategoryLink(category, params) {
if (category.link?.type === 'doc' && params.draftIds.has(category.link.id)) {
return undefined;
}
if (category.link?.type === 'generated-index') {
// Default slug logic can be improved
const getDefaultSlug = () => `/category/${params.categoryLabelSlugger.slug(category.label)}`;
const slug = category.link.slug ?? getDefaultSlug();
const permalink = (0, utils_1.normalizeUrl)([params.version.path, slug]);
return {
...category.link,
slug,
permalink,
};
}
return category.link;
}
function postProcessSidebarItem(item, params) {
if (item.type === 'category') {
// Fail-fast if there's actually no subitems, no because all subitems are
// drafts. This is likely a configuration mistake.
if (item.items.length === 0 && !item.link) {
throw new Error(`Sidebar category ${item.label} has neither any subitem nor a link. This makes this item not able to link to anything.`);
}
const category = {
...item,
collapsed: item.collapsed ?? params.sidebarOptions.sidebarCollapsed,
collapsible: item.collapsible ?? params.sidebarOptions.sidebarCollapsible,
link: normalizeCategoryLink(item, params),
items: item.items
.map((subItem) => postProcessSidebarItem(subItem, params))
.filter((v) => Boolean(v)),
};
// If the current category doesn't have subitems, we render a normal link
// instead.
if (category.items.length === 0) {
// Doesn't make sense to render an empty generated index page, so we
// filter the entire category out as well.
if (!category.link ||
category.link.type === 'generated-index' ||
params.draftIds.has(category.link.id)) {
return null;
}
return {
type: 'doc',
label: category.label,
id: category.link.id,
};
}
// A non-collapsible category can't be collapsed!
if (!category.collapsible) {
category.collapsed = false;
}
return category;
}
if ((item.type === 'doc' || item.type === 'ref') &&
params.draftIds.has(item.id)) {
return null;
}
return item;
}
function postProcessSidebars(sidebars, params) {
const draftIds = new Set(params.drafts.map((d) => d.id));
return lodash_1.default.mapValues(sidebars, (sidebar) => sidebar
.map((item) => postProcessSidebarItem(item, { ...params, draftIds }))
.filter((v) => Boolean(v)));
}
exports.postProcessSidebars = postProcessSidebars;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { NormalizedSidebars, ProcessedSidebars, SidebarProcessorParams, CategoryMetadataFile } from './types';
export declare function processSidebars(unprocessedSidebars: NormalizedSidebars, categoriesMetadata: {
[filePath: string]: CategoryMetadataFile;
}, params: SidebarProcessorParams): Promise<ProcessedSidebars>;

View File

@@ -0,0 +1,78 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.processSidebars = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const combine_promises_1 = tslib_1.__importDefault(require("combine-promises"));
const generator_1 = require("./generator");
const validation_1 = require("./validation");
const docs_1 = require("../docs");
function toSidebarItemsGeneratorDoc(doc) {
return lodash_1.default.pick(doc, [
'id',
'title',
'frontMatter',
'source',
'sourceDirName',
'sidebarPosition',
]);
}
function toSidebarItemsGeneratorVersion(version) {
return lodash_1.default.pick(version, ['versionName', 'contentPath']);
}
// Handle the generation of autogenerated sidebar items and other
// post-processing checks
async function processSidebar(unprocessedSidebar, categoriesMetadata, params) {
const { sidebarItemsGenerator, numberPrefixParser, docs, version } = params;
// Just a minor lazy transformation optimization
const getSidebarItemsGeneratorDocsAndVersion = lodash_1.default.memoize(() => ({
docs: docs.map(toSidebarItemsGeneratorDoc),
version: toSidebarItemsGeneratorVersion(version),
}));
async function processAutoGeneratedItem(item) {
const generatedItems = await sidebarItemsGenerator({
item,
numberPrefixParser,
defaultSidebarItemsGenerator: generator_1.DefaultSidebarItemsGenerator,
isCategoryIndex: docs_1.isCategoryIndex,
...getSidebarItemsGeneratorDocsAndVersion(),
categoriesMetadata,
});
// Process again... weird but sidebar item generated might generate some
// auto-generated items?
// TODO repeatedly process & unwrap autogenerated items until there are no
// more autogenerated items, or when loop count (e.g. 10) is reached
return processItems(generatedItems);
}
async function processItem(item) {
if (item.type === 'category') {
return [
{
...item,
items: (await Promise.all(item.items.map(processItem))).flat(),
},
];
}
if (item.type === 'autogenerated') {
return processAutoGeneratedItem(item);
}
return [item];
}
async function processItems(items) {
return (await Promise.all(items.map(processItem))).flat();
}
const processedSidebar = await processItems(unprocessedSidebar);
return processedSidebar;
}
async function processSidebars(unprocessedSidebars, categoriesMetadata, params) {
const processedSidebars = await (0, combine_promises_1.default)(lodash_1.default.mapValues(unprocessedSidebars, (unprocessedSidebar) => processSidebar(unprocessedSidebar, categoriesMetadata, params)));
(0, validation_1.validateSidebars)(processedSidebars);
return processedSidebars;
}
exports.processSidebars = processSidebars;

View File

@@ -0,0 +1,188 @@
/**
* 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 type { Optional, Required } from 'utility-types';
import type { NumberPrefixParser, SidebarOptions, CategoryIndexMatcher, DocMetadataBase, VersionMetadata } from '@docusaurus/plugin-content-docs';
import type { Slugger } from '@docusaurus/utils';
type Expand<T extends {
[x: string]: unknown;
}> = {
[P in keyof T]: T[P];
};
export type SidebarItemBase = {
className?: string;
customProps?: {
[key: string]: unknown;
};
};
export type SidebarItemDoc = SidebarItemBase & {
type: 'doc' | 'ref';
label?: string;
id: string;
/**
* This is an internal marker. Items with labels defined in the config needs
* to be translated with JSON
*/
translatable?: true;
};
export type SidebarItemHtml = SidebarItemBase & {
type: 'html';
value: string;
defaultStyle?: boolean;
};
export type SidebarItemLink = SidebarItemBase & {
type: 'link';
href: string;
label: string;
autoAddBaseUrl?: boolean;
description?: string;
};
export type SidebarItemAutogenerated = SidebarItemBase & {
type: 'autogenerated';
dirName: string;
};
type SidebarItemCategoryBase = SidebarItemBase & {
type: 'category';
label: string;
collapsed: boolean;
collapsible: boolean;
description?: string;
};
export type SidebarItemCategoryLinkDoc = {
type: 'doc';
id: string;
};
export type SidebarItemCategoryLinkGeneratedIndexConfig = {
type: 'generated-index';
slug?: string;
title?: string;
description?: string;
image?: string;
keywords?: string | readonly string[];
};
export type SidebarItemCategoryLinkGeneratedIndex = {
type: 'generated-index';
slug: string;
permalink: string;
title?: string;
description?: string;
image?: string;
keywords?: string | readonly string[];
};
export type SidebarItemCategoryLinkConfig = SidebarItemCategoryLinkDoc | SidebarItemCategoryLinkGeneratedIndexConfig;
export type SidebarItemCategoryLink = SidebarItemCategoryLinkDoc | SidebarItemCategoryLinkGeneratedIndex;
export type SidebarItemCategoryConfig = Expand<Optional<SidebarItemCategoryBase, 'collapsed' | 'collapsible'> & {
items: SidebarCategoriesShorthand | SidebarItemConfig[];
link?: SidebarItemCategoryLinkConfig;
}>;
export type SidebarCategoriesShorthand = {
[sidebarCategory: string]: SidebarCategoriesShorthand | SidebarItemConfig[];
};
export type SidebarItemConfig = Omit<SidebarItemDoc, 'translatable'> | SidebarItemHtml | SidebarItemLink | SidebarItemAutogenerated | SidebarItemCategoryConfig | string | SidebarCategoriesShorthand;
export type SidebarConfig = SidebarCategoriesShorthand | SidebarItemConfig[];
export type SidebarsConfig = {
[sidebarId: string]: SidebarConfig;
};
export type NormalizedSidebarItemCategory = Expand<Optional<SidebarItemCategoryBase, 'collapsed' | 'collapsible'> & {
items: NormalizedSidebarItem[];
link?: SidebarItemCategoryLinkConfig;
}>;
export type NormalizedSidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | NormalizedSidebarItemCategory | SidebarItemAutogenerated;
export type NormalizedSidebar = NormalizedSidebarItem[];
export type NormalizedSidebars = {
[sidebarId: string]: NormalizedSidebar;
};
export type ProcessedSidebarItemCategory = Expand<Optional<SidebarItemCategoryBase, 'collapsed' | 'collapsible'> & {
items: ProcessedSidebarItem[];
link?: SidebarItemCategoryLinkConfig;
}>;
export type ProcessedSidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | ProcessedSidebarItemCategory;
export type ProcessedSidebar = ProcessedSidebarItem[];
export type ProcessedSidebars = {
[sidebarId: string]: ProcessedSidebar;
};
export type SidebarItemCategory = Expand<SidebarItemCategoryBase & {
items: SidebarItem[];
link?: SidebarItemCategoryLink;
}>;
export type SidebarItemCategoryWithLink = Required<SidebarItemCategory, 'link'>;
export type SidebarItemCategoryWithGeneratedIndex = SidebarItemCategoryWithLink & {
link: SidebarItemCategoryLinkGeneratedIndex;
};
export type SidebarItem = SidebarItemDoc | SidebarItemHtml | SidebarItemLink | SidebarItemCategory;
export type SidebarNavigationItem = SidebarItemDoc | SidebarItemCategoryWithLink;
export type Sidebar = SidebarItem[];
export type SidebarItemType = SidebarItem['type'];
export type Sidebars = {
[sidebarId: string]: Sidebar;
};
export type PropSidebarItemCategory = Expand<SidebarItemCategoryBase & {
items: PropSidebarItem[];
href?: string;
linkUnlisted?: boolean;
}>;
export type PropSidebarItemLink = SidebarItemLink & {
docId?: string;
unlisted?: boolean;
};
export type PropSidebarItemHtml = SidebarItemHtml;
export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory | PropSidebarItemHtml;
export type PropSidebar = PropSidebarItem[];
export type PropSidebars = {
[sidebarId: string]: PropSidebar;
};
export type PropSidebarBreadcrumbsItem = PropSidebarItemLink | PropSidebarItemCategory;
export type CategoryMetadataFile = {
label?: string;
position?: number;
collapsed?: boolean;
collapsible?: boolean;
className?: string;
link?: SidebarItemCategoryLinkConfig | null;
customProps?: {
[key: string]: unknown;
};
};
export type SidebarItemsGeneratorDoc = Pick<DocMetadataBase, 'id' | 'title' | 'frontMatter' | 'source' | 'sourceDirName' | 'sidebarPosition'>;
export type SidebarItemsGeneratorVersion = Pick<VersionMetadata, 'versionName' | 'contentPath'>;
export type SidebarItemsGeneratorArgs = {
/** The sidebar item with type "autogenerated" to be transformed. */
item: SidebarItemAutogenerated;
/** Useful metadata for the version this sidebar belongs to. */
version: SidebarItemsGeneratorVersion;
/** All the docs of that version (unfiltered). */
docs: SidebarItemsGeneratorDoc[];
/** Number prefix parser configured for this plugin. */
numberPrefixParser: NumberPrefixParser;
/** The default category index matcher which you can override. */
isCategoryIndex: CategoryIndexMatcher;
/**
* Key is the path relative to the doc content directory, value is the
* category metadata file's content.
*/
categoriesMetadata: {
[filePath: string]: CategoryMetadataFile;
};
};
export type SidebarItemsGenerator = (generatorArgs: SidebarItemsGeneratorArgs) => NormalizedSidebar | Promise<NormalizedSidebar>;
export type SidebarItemsGeneratorOption = (generatorArgs: {
/**
* Useful to re-use/enhance the default sidebar generation logic from
* Docusaurus.
* @see https://github.com/facebook/docusaurus/issues/4640#issuecomment-822292320
*/
defaultSidebarItemsGenerator: SidebarItemsGenerator;
} & SidebarItemsGeneratorArgs) => NormalizedSidebar | Promise<NormalizedSidebar>;
export type SidebarProcessorParams = {
sidebarItemsGenerator: SidebarItemsGeneratorOption;
numberPrefixParser: NumberPrefixParser;
docs: DocMetadataBase[];
drafts: DocMetadataBase[];
version: VersionMetadata;
categoryLabelSlugger: Slugger;
sidebarOptions: SidebarOptions;
};
export {};

View File

@@ -0,0 +1,8 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,67 @@
/**
* 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 type { Sidebars, Sidebar, SidebarItem, SidebarItemCategory, SidebarItemLink, SidebarItemDoc, SidebarCategoriesShorthand, SidebarItemConfig, SidebarItemCategoryWithGeneratedIndex, SidebarNavigationItem } from './types';
import type { DocMetadataBase, PropNavigationLink, VersionMetadata } from '@docusaurus/plugin-content-docs';
export declare function isCategoriesShorthand(item: SidebarItemConfig): item is SidebarCategoriesShorthand;
export declare function transformSidebarItems(sidebar: Sidebar, updateFn: (item: SidebarItem) => SidebarItem): Sidebar;
export declare function collectSidebarDocItems(sidebar: Sidebar): SidebarItemDoc[];
export declare function collectSidebarCategories(sidebar: Sidebar): SidebarItemCategory[];
export declare function collectSidebarLinks(sidebar: Sidebar): SidebarItemLink[];
export declare function collectSidebarRefs(sidebar: Sidebar): SidebarItemDoc[];
export declare function collectSidebarDocIds(sidebar: Sidebar): string[];
export declare function collectSidebarNavigation(sidebar: Sidebar): SidebarNavigationItem[];
export declare function collectSidebarsDocIds(sidebars: Sidebars): {
[sidebarId: string]: string[];
};
export declare function collectSidebarsNavigations(sidebars: Sidebars): {
[sidebarId: string]: SidebarNavigationItem[];
};
export type SidebarNavigation = {
sidebarName: string | undefined;
previous: SidebarNavigationItem | undefined;
next: SidebarNavigationItem | undefined;
};
export type SidebarsUtils = {
sidebars: Sidebars;
getFirstDocIdOfFirstSidebar: () => string | undefined;
getSidebarNameByDocId: (docId: string) => string | undefined;
getDocNavigation: (params: {
docId: string;
displayedSidebar: string | null | undefined;
unlistedIds: Set<string>;
}) => SidebarNavigation;
getCategoryGeneratedIndexList: () => SidebarItemCategoryWithGeneratedIndex[];
getCategoryGeneratedIndexNavigation: (categoryGeneratedIndexPermalink: string) => SidebarNavigation;
/**
* This function may return undefined. This is usually a user mistake, because
* it means this sidebar will never be displayed; however, we can still use
* `displayed_sidebar` to make it displayed. Pretty weird but valid use-case
*/
getFirstLink: (sidebarId: string) => {
type: 'doc';
id: string;
label: string;
} | {
type: 'generated-index';
permalink: string;
label: string;
} | undefined;
checkLegacyVersionedSidebarNames: ({ versionMetadata, }: {
sidebarFilePath: string;
versionMetadata: VersionMetadata;
}) => void;
checkSidebarsDocIds: ({ allDocIds, sidebarFilePath, versionMetadata, }: {
allDocIds: string[];
sidebarFilePath: string;
versionMetadata: VersionMetadata;
}) => void;
};
export declare function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils;
export declare function toDocNavigationLink(doc: DocMetadataBase): PropNavigationLink;
export declare function toNavigationLink(navigationItem: SidebarNavigationItem | undefined, docsById: {
[docId: string]: DocMetadataBase;
}): PropNavigationLink | undefined;

View File

@@ -0,0 +1,323 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.toNavigationLink = exports.toDocNavigationLink = exports.createSidebarsUtils = exports.collectSidebarsNavigations = exports.collectSidebarsDocIds = exports.collectSidebarNavigation = exports.collectSidebarDocIds = exports.collectSidebarRefs = exports.collectSidebarLinks = exports.collectSidebarCategories = exports.collectSidebarDocItems = exports.transformSidebarItems = exports.isCategoriesShorthand = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
function isCategoriesShorthand(item) {
return typeof item === 'object' && !item.type;
}
exports.isCategoriesShorthand = isCategoriesShorthand;
function transformSidebarItems(sidebar, updateFn) {
function transformRecursive(item) {
if (item.type === 'category') {
return updateFn({
...item,
items: item.items.map(transformRecursive),
});
}
return updateFn(item);
}
return sidebar.map(transformRecursive);
}
exports.transformSidebarItems = transformSidebarItems;
/**
* Flatten sidebar items into a single flat array (containing categories/docs on
* the same level). Order matters (useful for next/prev nav), top categories
* appear before their child elements
*/
function flattenSidebarItems(items) {
function flattenRecursive(item) {
return item.type === 'category'
? [item, ...item.items.flatMap(flattenRecursive)]
: [item];
}
return items.flatMap(flattenRecursive);
}
function collectSidebarItemsOfType(type, sidebar) {
return flattenSidebarItems(sidebar).filter((item) => item.type === type);
}
function collectSidebarDocItems(sidebar) {
return collectSidebarItemsOfType('doc', sidebar);
}
exports.collectSidebarDocItems = collectSidebarDocItems;
function collectSidebarCategories(sidebar) {
return collectSidebarItemsOfType('category', sidebar);
}
exports.collectSidebarCategories = collectSidebarCategories;
function collectSidebarLinks(sidebar) {
return collectSidebarItemsOfType('link', sidebar);
}
exports.collectSidebarLinks = collectSidebarLinks;
function collectSidebarRefs(sidebar) {
return collectSidebarItemsOfType('ref', sidebar);
}
exports.collectSidebarRefs = collectSidebarRefs;
// /!\ docId order matters for navigation!
function collectSidebarDocIds(sidebar) {
return flattenSidebarItems(sidebar).flatMap((item) => {
if (item.type === 'category') {
return item.link?.type === 'doc' ? [item.link.id] : [];
}
if (item.type === 'doc') {
return [item.id];
}
return [];
});
}
exports.collectSidebarDocIds = collectSidebarDocIds;
function collectSidebarNavigation(sidebar) {
return flattenSidebarItems(sidebar).flatMap((item) => {
if (item.type === 'category' && item.link) {
return [item];
}
if (item.type === 'doc') {
return [item];
}
return [];
});
}
exports.collectSidebarNavigation = collectSidebarNavigation;
function collectSidebarsDocIds(sidebars) {
return lodash_1.default.mapValues(sidebars, collectSidebarDocIds);
}
exports.collectSidebarsDocIds = collectSidebarsDocIds;
function collectSidebarsNavigations(sidebars) {
return lodash_1.default.mapValues(sidebars, collectSidebarNavigation);
}
exports.collectSidebarsNavigations = collectSidebarsNavigations;
function createSidebarsUtils(sidebars) {
const sidebarNameToDocIds = collectSidebarsDocIds(sidebars);
const sidebarNameToNavigationItems = collectSidebarsNavigations(sidebars);
// Reverse mapping
const docIdToSidebarName = Object.fromEntries(Object.entries(sidebarNameToDocIds).flatMap(([sidebarName, docIds]) => docIds.map((docId) => [docId, sidebarName])));
function getFirstDocIdOfFirstSidebar() {
return Object.values(sidebarNameToDocIds)[0]?.[0];
}
function getSidebarNameByDocId(docId) {
return docIdToSidebarName[docId];
}
function emptySidebarNavigation() {
return {
sidebarName: undefined,
previous: undefined,
next: undefined,
};
}
function getDocNavigation({ docId, displayedSidebar, unlistedIds, }) {
const sidebarName = displayedSidebar === undefined
? getSidebarNameByDocId(docId)
: displayedSidebar;
if (!sidebarName) {
return emptySidebarNavigation();
}
let navigationItems = sidebarNameToNavigationItems[sidebarName];
if (!navigationItems) {
throw new Error(`Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`);
}
// Filter unlisted items from navigation
navigationItems = navigationItems.filter((item) => {
if (item.type === 'doc' && unlistedIds.has(item.id)) {
return false;
}
if (item.type === 'category' &&
item.link.type === 'doc' &&
unlistedIds.has(item.link.id)) {
return false;
}
return true;
});
const currentItemIndex = navigationItems.findIndex((item) => {
if (item.type === 'doc') {
return item.id === docId;
}
if (item.type === 'category' && item.link.type === 'doc') {
return item.link.id === docId;
}
return false;
});
if (currentItemIndex === -1) {
return { sidebarName, next: undefined, previous: undefined };
}
return {
sidebarName,
previous: navigationItems[currentItemIndex - 1],
next: navigationItems[currentItemIndex + 1],
};
}
function getCategoryGeneratedIndexList() {
return Object.values(sidebarNameToNavigationItems)
.flat()
.flatMap((item) => {
if (item.type === 'category' && item.link.type === 'generated-index') {
return [item];
}
return [];
});
}
/**
* We identity the category generated index by its permalink (should be
* unique). More reliable than using object identity
*/
function getCategoryGeneratedIndexNavigation(categoryGeneratedIndexPermalink) {
function isCurrentCategoryGeneratedIndexItem(item) {
return (item.type === 'category' &&
item.link.type === 'generated-index' &&
item.link.permalink === categoryGeneratedIndexPermalink);
}
const sidebarName = Object.entries(sidebarNameToNavigationItems).find(([, navigationItems]) => navigationItems.find(isCurrentCategoryGeneratedIndexItem))[0];
const navigationItems = sidebarNameToNavigationItems[sidebarName];
const currentItemIndex = navigationItems.findIndex(isCurrentCategoryGeneratedIndexItem);
return {
sidebarName,
previous: navigationItems[currentItemIndex - 1],
next: navigationItems[currentItemIndex + 1],
};
}
// TODO remove in Docusaurus v4
function getLegacyVersionedPrefix(versionMetadata) {
return `version-${versionMetadata.versionName}/`;
}
// In early v2, sidebar names used to be versioned
// example: "version-2.0.0-alpha.66/my-sidebar-name"
// In v3 it's not the case anymore and we throw an error to explain
// TODO remove in Docusaurus v4
function checkLegacyVersionedSidebarNames({ versionMetadata, sidebarFilePath, }) {
const illegalPrefix = getLegacyVersionedPrefix(versionMetadata);
const legacySidebarNames = Object.keys(sidebars).filter((sidebarName) => sidebarName.startsWith(illegalPrefix));
if (legacySidebarNames.length > 0) {
throw new Error(`Invalid sidebar file at "${(0, utils_1.toMessageRelativeFilePath)(sidebarFilePath)}".
These legacy versioned sidebar names are not supported anymore in Docusaurus v3:
- ${legacySidebarNames.sort().join('\n- ')}
The sidebar names you should now use are:
- ${legacySidebarNames
.sort()
.map((legacyName) => legacyName.split('/').splice(1).join('/'))
.join('\n- ')}
Please remove the "${illegalPrefix}" prefix from your versioned sidebar file.
This breaking change is documented on Docusaurus v3 release notes: https://docusaurus.io/blog/releases/3.0
`);
}
}
// throw a better error message for Docusaurus v3 breaking change
// TODO this can be removed in Docusaurus v4
function handleLegacyVersionedDocIds({ invalidDocIds, sidebarFilePath, versionMetadata, }) {
const illegalPrefix = getLegacyVersionedPrefix(versionMetadata);
// In older v2.0 alpha/betas, versioned docs had a legacy versioned prefix
// Example: "version-1.4/my-doc-id"
//
const legacyVersionedDocIds = invalidDocIds.filter((docId) => docId.startsWith(illegalPrefix));
if (legacyVersionedDocIds.length > 0) {
throw new Error(`Invalid sidebar file at "${(0, utils_1.toMessageRelativeFilePath)(sidebarFilePath)}".
These legacy versioned document ids are not supported anymore in Docusaurus v3:
- ${legacyVersionedDocIds.sort().join('\n- ')}
The document ids you should now use are:
- ${legacyVersionedDocIds
.sort()
.map((legacyId) => legacyId.split('/').splice(1).join('/'))
.join('\n- ')}
Please remove the "${illegalPrefix}" prefix from your versioned sidebar file.
This breaking change is documented on Docusaurus v3 release notes: https://docusaurus.io/blog/releases/3.0
`);
}
}
function checkSidebarsDocIds({ allDocIds, sidebarFilePath, versionMetadata, }) {
const allSidebarDocIds = Object.values(sidebarNameToDocIds).flat();
const invalidDocIds = lodash_1.default.difference(allSidebarDocIds, allDocIds);
if (invalidDocIds.length > 0) {
handleLegacyVersionedDocIds({
invalidDocIds,
sidebarFilePath,
versionMetadata,
});
throw new Error(`Invalid sidebar file at "${(0, utils_1.toMessageRelativeFilePath)(sidebarFilePath)}".
These sidebar document ids do not exist:
- ${invalidDocIds.sort().join('\n- ')}
Available document ids are:
- ${lodash_1.default.uniq(allDocIds).sort().join('\n- ')}
`);
}
}
function getFirstLink(sidebar) {
for (const item of sidebar) {
if (item.type === 'doc') {
return {
type: 'doc',
id: item.id,
label: item.label ?? item.id,
};
}
else if (item.type === 'category') {
if (item.link?.type === 'doc') {
return {
type: 'doc',
id: item.link.id,
label: item.label,
};
}
else if (item.link?.type === 'generated-index') {
return {
type: 'generated-index',
permalink: item.link.permalink,
label: item.label,
};
}
const firstSubItem = getFirstLink(item.items);
if (firstSubItem) {
return firstSubItem;
}
}
}
return undefined;
}
return {
sidebars,
getFirstDocIdOfFirstSidebar,
getSidebarNameByDocId,
getDocNavigation,
getCategoryGeneratedIndexList,
getCategoryGeneratedIndexNavigation,
checkLegacyVersionedSidebarNames,
checkSidebarsDocIds,
getFirstLink: (id) => getFirstLink(sidebars[id]),
};
}
exports.createSidebarsUtils = createSidebarsUtils;
function toDocNavigationLink(doc) {
const { title, permalink, frontMatter: { pagination_label: paginationLabel, sidebar_label: sidebarLabel, }, } = doc;
return { title: paginationLabel ?? sidebarLabel ?? title, permalink };
}
exports.toDocNavigationLink = toDocNavigationLink;
function toNavigationLink(navigationItem, docsById) {
function getDocById(docId) {
const doc = docsById[docId];
if (!doc) {
throw new Error(`Can't create navigation link: no doc found with id=${docId}`);
}
return doc;
}
if (!navigationItem) {
return undefined;
}
if (navigationItem.type === 'category') {
return navigationItem.link.type === 'doc'
? toDocNavigationLink(getDocById(navigationItem.link.id))
: {
title: navigationItem.label,
permalink: navigationItem.link.permalink,
};
}
return toDocNavigationLink(getDocById(navigationItem.id));
}
exports.toNavigationLink = toNavigationLink;

View File

@@ -0,0 +1,11 @@
/**
* 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 type { NormalizedSidebars, CategoryMetadataFile } from './types';
export declare function validateSidebars(sidebars: {
[sidebarId: string]: unknown;
}): asserts sidebars is NormalizedSidebars;
export declare function validateCategoryMetadataFile(unsafeContent: unknown): CategoryMetadataFile;

View File

@@ -0,0 +1,150 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateCategoryMetadataFile = exports.validateSidebars = void 0;
const utils_validation_1 = require("@docusaurus/utils-validation");
// NOTE: we don't add any default values during validation on purpose!
// Config types are exposed to users for typechecking and we use the same type
// in normalization
const sidebarItemBaseSchema = utils_validation_1.Joi.object({
className: utils_validation_1.Joi.string(),
customProps: utils_validation_1.Joi.object().unknown(),
});
const sidebarItemAutogeneratedSchema = sidebarItemBaseSchema.append({
type: 'autogenerated',
dirName: utils_validation_1.Joi.string()
.required()
.pattern(/^[^/](?:.*[^/])?$/)
.message('"dirName" must be a dir path relative to the docs folder root, and should not start or end with slash'),
});
const sidebarItemDocSchema = sidebarItemBaseSchema.append({
type: utils_validation_1.Joi.string().valid('doc', 'ref').required(),
id: utils_validation_1.Joi.string().required(),
label: utils_validation_1.Joi.string(),
translatable: utils_validation_1.Joi.boolean(),
});
const sidebarItemHtmlSchema = sidebarItemBaseSchema.append({
type: 'html',
value: utils_validation_1.Joi.string().required(),
defaultStyle: utils_validation_1.Joi.boolean(),
});
const sidebarItemLinkSchema = sidebarItemBaseSchema.append({
type: 'link',
href: utils_validation_1.URISchema.required(),
autoAddBaseUrl: utils_validation_1.Joi.boolean(),
label: utils_validation_1.Joi.string()
.required()
.messages({ 'any.unknown': '"label" must be a string' }),
description: utils_validation_1.Joi.string().optional().messages({
'any.unknown': '"description" must be a string',
}),
});
const sidebarItemCategoryLinkSchema = utils_validation_1.Joi.object()
.allow(null)
.when('.type', {
switch: [
{
is: 'doc',
then: utils_validation_1.Joi.object({
type: 'doc',
id: utils_validation_1.Joi.string().required(),
}),
},
{
is: 'generated-index',
then: utils_validation_1.Joi.object({
type: 'generated-index',
slug: utils_validation_1.Joi.string().optional(),
// This one is not in the user config, only in the normalized version
// permalink: Joi.string().optional(),
title: utils_validation_1.Joi.string().optional(),
description: utils_validation_1.Joi.string().optional(),
image: utils_validation_1.Joi.string().optional(),
keywords: [utils_validation_1.Joi.string(), utils_validation_1.Joi.array().items(utils_validation_1.Joi.string())],
}),
},
{
is: utils_validation_1.Joi.required(),
then: utils_validation_1.Joi.forbidden().messages({
'any.unknown': 'Unknown sidebar category link type "{.type}".',
}),
},
],
});
const sidebarItemCategorySchema = sidebarItemBaseSchema.append({
type: 'category',
label: utils_validation_1.Joi.string()
.required()
.messages({ 'any.unknown': '"label" must be a string' }),
items: utils_validation_1.Joi.array()
.required()
.messages({ 'any.unknown': '"items" must be an array' }),
// TODO: Joi doesn't allow mutual recursion. See https://github.com/sideway/joi/issues/2611
// .items(Joi.link('#sidebarItemSchema')),
link: sidebarItemCategoryLinkSchema,
collapsed: utils_validation_1.Joi.boolean().messages({
'any.unknown': '"collapsed" must be a boolean',
}),
collapsible: utils_validation_1.Joi.boolean().messages({
'any.unknown': '"collapsible" must be a boolean',
}),
description: utils_validation_1.Joi.string().optional().messages({
'any.unknown': '"description" must be a string',
}),
});
const sidebarItemSchema = utils_validation_1.Joi.object().when('.type', {
switch: [
{ is: 'link', then: sidebarItemLinkSchema },
{
is: utils_validation_1.Joi.string().valid('doc', 'ref').required(),
then: sidebarItemDocSchema,
},
{ is: 'html', then: sidebarItemHtmlSchema },
{ is: 'autogenerated', then: sidebarItemAutogeneratedSchema },
{ is: 'category', then: sidebarItemCategorySchema },
{
is: utils_validation_1.Joi.any().required(),
then: utils_validation_1.Joi.forbidden().messages({
'any.unknown': 'Unknown sidebar item type "{.type}".',
}),
},
],
});
// .id('sidebarItemSchema');
function validateSidebarItem(item) {
// TODO: remove once with proper Joi support
// Because we can't use Joi to validate nested items (see above), we do it
// manually
utils_validation_1.Joi.assert(item, sidebarItemSchema);
if (item.type === 'category') {
item.items.forEach(validateSidebarItem);
}
}
function validateSidebars(sidebars) {
Object.values(sidebars).forEach((sidebar) => {
sidebar.forEach(validateSidebarItem);
});
}
exports.validateSidebars = validateSidebars;
const categoryMetadataFileSchema = utils_validation_1.Joi.object({
label: utils_validation_1.Joi.string(),
position: utils_validation_1.Joi.number(),
collapsed: utils_validation_1.Joi.boolean(),
collapsible: utils_validation_1.Joi.boolean(),
className: utils_validation_1.Joi.string(),
link: sidebarItemCategoryLinkSchema,
customProps: utils_validation_1.Joi.object().unknown(),
});
function validateCategoryMetadataFile(unsafeContent) {
const { error, value } = categoryMetadataFileSchema.validate(unsafeContent);
if (error) {
throw error;
}
return value;
}
exports.validateCategoryMetadataFile = validateCategoryMetadataFile;

View File

@@ -0,0 +1,15 @@
/**
* 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 type { NumberPrefixParser, DocMetadataBase } from '@docusaurus/plugin-content-docs';
export default function getSlug({ baseID, frontMatterSlug, source, sourceDirName, stripDirNumberPrefixes, numberPrefixParser, }: {
baseID: string;
frontMatterSlug?: string;
source: DocMetadataBase['source'];
sourceDirName: DocMetadataBase['sourceDirName'];
stripDirNumberPrefixes?: boolean;
numberPrefixParser?: NumberPrefixParser;
}): string;

View File

@@ -0,0 +1,50 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@docusaurus/utils");
const numberPrefix_1 = require("./numberPrefix");
const docs_1 = require("./docs");
function getSlug({ baseID, frontMatterSlug, source, sourceDirName, stripDirNumberPrefixes = true, numberPrefixParser = numberPrefix_1.DefaultNumberPrefixParser, }) {
function getDirNameSlug() {
const dirNameStripped = stripDirNumberPrefixes
? (0, numberPrefix_1.stripPathNumberPrefixes)(sourceDirName, numberPrefixParser)
: sourceDirName;
const resolveDirname = sourceDirName === '.'
? '/'
: (0, utils_1.addLeadingSlash)((0, utils_1.addTrailingSlash)(dirNameStripped));
return resolveDirname;
}
function computeSlug() {
if (frontMatterSlug?.startsWith('/')) {
return frontMatterSlug;
}
const dirNameSlug = getDirNameSlug();
if (!frontMatterSlug &&
(0, docs_1.isCategoryIndex)((0, docs_1.toCategoryIndexMatcherParam)({ source, sourceDirName }))) {
return dirNameSlug;
}
const baseSlug = frontMatterSlug ?? baseID;
return (0, utils_1.resolvePathname)(baseSlug, getDirNameSlug());
}
function ensureValidSlug(slug) {
if (!(0, utils_1.isValidPathname)(slug)) {
throw new Error(`We couldn't compute a valid slug for document with ID "${baseID}" in "${sourceDirName}" directory.
The slug we computed looks invalid: ${slug}.
Maybe your slug front matter is incorrect or there are special characters in the file path?
By using front matter to set a custom slug, you should be able to fix this error:
---
slug: /my/customDocPath
---
`);
}
return slug;
}
return ensureValidSlug(computeSlug());
}
exports.default = getSlug;

View File

@@ -0,0 +1,9 @@
/**
* 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 type { VersionTags } from './types';
import type { DocMetadata } from '@docusaurus/plugin-content-docs';
export declare function getVersionTags(docs: DocMetadata[]): VersionTags;

View File

@@ -0,0 +1,28 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVersionTags = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
function getVersionTags(docs) {
const groups = (0, utils_1.groupTaggedItems)(docs, (doc) => doc.tags);
return lodash_1.default.mapValues(groups, ({ tag, items: tagDocs }) => {
const tagVisibility = (0, utils_1.getTagVisibility)({
items: tagDocs,
isUnlisted: (item) => item.unlisted,
});
return {
label: tag.label,
docIds: tagVisibility.listedItems.map((item) => item.id),
permalink: tag.permalink,
unlisted: tagVisibility.unlisted,
};
});
}
exports.getVersionTags = getVersionTags;

View File

@@ -0,0 +1,10 @@
/**
* 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 type { LoadedContent } from '@docusaurus/plugin-content-docs';
import type { TranslationFile } from '@docusaurus/types';
export declare function getLoadedContentTranslationFiles(loadedContent: LoadedContent): TranslationFile[];
export declare function translateLoadedContent(loadedContent: LoadedContent, translationFiles: TranslationFile[]): LoadedContent;

View File

@@ -0,0 +1,168 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.translateLoadedContent = exports.getLoadedContentTranslationFiles = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
const constants_1 = require("./constants");
const utils_2 = require("./sidebars/utils");
function getVersionFileName(versionName) {
if (versionName === constants_1.CURRENT_VERSION_NAME) {
return versionName;
}
// I don't like this "version-" prefix,
// but it's for consistency with site/versioned_docs
return `version-${versionName}`;
}
function getSidebarTranslationFileContent(sidebar, sidebarName) {
const categories = (0, utils_2.collectSidebarCategories)(sidebar);
const categoryContent = Object.fromEntries(categories.flatMap((category) => {
const entries = [];
entries.push([
`sidebar.${sidebarName}.category.${category.label}`,
{
message: category.label,
description: `The label for category ${category.label} in sidebar ${sidebarName}`,
},
]);
if (category.link?.type === 'generated-index') {
if (category.link.title) {
entries.push([
`sidebar.${sidebarName}.category.${category.label}.link.generated-index.title`,
{
message: category.link.title,
description: `The generated-index page title for category ${category.label} in sidebar ${sidebarName}`,
},
]);
}
if (category.link.description) {
entries.push([
`sidebar.${sidebarName}.category.${category.label}.link.generated-index.description`,
{
message: category.link.description,
description: `The generated-index page description for category ${category.label} in sidebar ${sidebarName}`,
},
]);
}
}
return entries;
}));
const links = (0, utils_2.collectSidebarLinks)(sidebar);
const linksContent = Object.fromEntries(links.map((link) => [
`sidebar.${sidebarName}.link.${link.label}`,
{
message: link.label,
description: `The label for link ${link.label} in sidebar ${sidebarName}, linking to ${link.href}`,
},
]));
const docs = (0, utils_2.collectSidebarDocItems)(sidebar)
.concat((0, utils_2.collectSidebarRefs)(sidebar))
.filter((item) => item.translatable);
const docLinksContent = Object.fromEntries(docs.map((doc) => [
`sidebar.${sidebarName}.doc.${doc.label}`,
{
message: doc.label,
description: `The label for the doc item ${doc.label} in sidebar ${sidebarName}, linking to the doc ${doc.id}`,
},
]));
return (0, utils_1.mergeTranslations)([categoryContent, linksContent, docLinksContent]);
}
function translateSidebar({ sidebar, sidebarName, sidebarsTranslations, }) {
function transformSidebarCategoryLink(category) {
if (!category.link) {
return undefined;
}
if (category.link.type === 'generated-index') {
const title = sidebarsTranslations[`sidebar.${sidebarName}.category.${category.label}.link.generated-index.title`]?.message ?? category.link.title;
const description = sidebarsTranslations[`sidebar.${sidebarName}.category.${category.label}.link.generated-index.description`]?.message ?? category.link.description;
return {
...category.link,
title,
description,
};
}
return category.link;
}
return (0, utils_2.transformSidebarItems)(sidebar, (item) => {
if (item.type === 'category') {
const link = transformSidebarCategoryLink(item);
return {
...item,
label: sidebarsTranslations[`sidebar.${sidebarName}.category.${item.label}`]
?.message ?? item.label,
...(link && { link }),
};
}
if (item.type === 'link') {
return {
...item,
label: sidebarsTranslations[`sidebar.${sidebarName}.link.${item.label}`]
?.message ?? item.label,
};
}
if ((item.type === 'doc' || item.type === 'ref') && item.translatable) {
return {
...item,
label: sidebarsTranslations[`sidebar.${sidebarName}.doc.${item.label}`]
?.message ?? item.label,
};
}
return item;
});
}
function getSidebarsTranslations(version) {
return (0, utils_1.mergeTranslations)(Object.entries(version.sidebars).map(([sidebarName, sidebar]) => getSidebarTranslationFileContent(sidebar, sidebarName)));
}
function translateSidebars(version, sidebarsTranslations) {
return lodash_1.default.mapValues(version.sidebars, (sidebar, sidebarName) => translateSidebar({
sidebar,
sidebarName,
sidebarsTranslations,
}));
}
function getVersionTranslationFiles(version) {
const versionTranslations = {
'version.label': {
message: version.label,
description: `The label for version ${version.versionName}`,
},
};
const sidebarsTranslations = getSidebarsTranslations(version);
return [
{
path: getVersionFileName(version.versionName),
content: (0, utils_1.mergeTranslations)([versionTranslations, sidebarsTranslations]),
},
];
}
function translateVersion(version, translationFiles) {
const versionTranslations = translationFiles[getVersionFileName(version.versionName)].content;
return {
...version,
label: versionTranslations['version.label']?.message ?? version.label,
sidebars: translateSidebars(version, versionTranslations),
};
}
function getVersionsTranslationFiles(versions) {
return versions.flatMap(getVersionTranslationFiles);
}
function translateVersions(versions, translationFiles) {
return versions.map((version) => translateVersion(version, translationFiles));
}
function getLoadedContentTranslationFiles(loadedContent) {
return getVersionsTranslationFiles(loadedContent.loadedVersions);
}
exports.getLoadedContentTranslationFiles = getLoadedContentTranslationFiles;
function translateLoadedContent(loadedContent, translationFiles) {
const translationFilesMap = lodash_1.default.keyBy(translationFiles, (f) => f.path);
return {
loadedVersions: translateVersions(loadedContent.loadedVersions, translationFilesMap),
};
}
exports.translateLoadedContent = translateLoadedContent;

View File

@@ -0,0 +1,37 @@
/**
* 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 type { BrokenMarkdownLink, Tag } from '@docusaurus/utils';
import type { VersionMetadata, LoadedVersion, CategoryGeneratedIndexMetadata } from '@docusaurus/plugin-content-docs';
import type { SidebarsUtils } from './sidebars/utils';
export type DocFile = {
contentPath: string;
filePath: string;
source: string;
content: string;
};
export type SourceToPermalink = {
[source: string]: string;
};
export type VersionTag = Tag & {
/** All doc ids having this tag. */
docIds: string[];
unlisted: boolean;
};
export type VersionTags = {
[permalink: string]: VersionTag;
};
export type FullVersion = LoadedVersion & {
sidebarsUtils: SidebarsUtils;
categoryGeneratedIndices: CategoryGeneratedIndexMetadata[];
};
export type DocBrokenMarkdownLink = BrokenMarkdownLink<VersionMetadata>;
export type DocsMarkdownOption = {
versionsMetadata: VersionMetadata[];
siteDir: string;
sourceToPermalink: SourceToPermalink;
onBrokenMarkdownLink: (brokenMarkdownLink: DocBrokenMarkdownLink) => void;
};

View File

@@ -0,0 +1,8 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,50 @@
/**
* 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 type { PluginOptions, VersionMetadata } from '@docusaurus/plugin-content-docs';
import type { VersionContext } from './index';
/** `[siteDir]/community_versioned_docs/version-1.0.0` */
export declare function getVersionDocsDirPath(siteDir: string, pluginId: string, versionName: string): string;
/** `[siteDir]/community_versioned_sidebars/version-1.0.0-sidebars.json` */
export declare function getVersionSidebarsPath(siteDir: string, pluginId: string, versionName: string): string;
export declare function getDocsDirPathLocalized({ localizationDir, pluginId, versionName, }: {
localizationDir: string;
pluginId: string;
versionName: string;
}): string;
/** `community` => `[siteDir]/community_versions.json` */
export declare function getVersionsFilePath(siteDir: string, pluginId: string): string;
/**
* Reads the plugin's respective `versions.json` file, and returns its content.
*
* @throws Throws if validation fails, i.e. `versions.json` doesn't contain an
* array of valid version names.
*/
export declare function readVersionsFile(siteDir: string, pluginId: string): Promise<string[] | null>;
/**
* Reads the `versions.json` file, and returns an ordered list of version names.
*
* - If `disableVersioning` is turned on, it will return `["current"]` (requires
* `includeCurrentVersion` to be true);
* - If `includeCurrentVersion` is turned on, "current" will be inserted at the
* beginning, if not already there.
*
* You need to use {@link filterVersions} after this.
*
* @throws Throws an error if `disableVersioning: true` but `versions.json`
* doesn't exist (i.e. site is not versioned)
* @throws Throws an error if versions list is empty (empty `versions.json` or
* `disableVersioning` is true, and not including current version)
*/
export declare function readVersionNames(siteDir: string, options: PluginOptions): Promise<string[]>;
/**
* Gets the path-related version metadata.
*
* @throws Throws if the resolved docs folder or sidebars file doesn't exist.
* Does not throw if a versioned sidebar is missing (since we don't create empty
* files).
*/
export declare function getVersionMetadataPaths({ versionName, context, options, }: VersionContext): Promise<Pick<VersionMetadata, 'contentPath' | 'contentPathLocalized' | 'sidebarFilePath'>>;

View File

@@ -0,0 +1,141 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVersionMetadataPaths = exports.readVersionNames = exports.readVersionsFile = exports.getVersionsFilePath = exports.getDocsDirPathLocalized = exports.getVersionSidebarsPath = exports.getVersionDocsDirPath = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const utils_1 = require("@docusaurus/utils");
const constants_1 = require("../constants");
const validation_1 = require("./validation");
/** Add a prefix like `community_version-1.0.0`. No-op for default instance. */
function addPluginIdPrefix(fileOrDir, pluginId) {
return pluginId === utils_1.DEFAULT_PLUGIN_ID
? fileOrDir
: `${pluginId}_${fileOrDir}`;
}
/** `[siteDir]/community_versioned_docs/version-1.0.0` */
function getVersionDocsDirPath(siteDir, pluginId, versionName) {
return path_1.default.join(siteDir, addPluginIdPrefix(constants_1.VERSIONED_DOCS_DIR, pluginId), `version-${versionName}`);
}
exports.getVersionDocsDirPath = getVersionDocsDirPath;
/** `[siteDir]/community_versioned_sidebars/version-1.0.0-sidebars.json` */
function getVersionSidebarsPath(siteDir, pluginId, versionName) {
return path_1.default.join(siteDir, addPluginIdPrefix(constants_1.VERSIONED_SIDEBARS_DIR, pluginId), `version-${versionName}-sidebars.json`);
}
exports.getVersionSidebarsPath = getVersionSidebarsPath;
function getDocsDirPathLocalized({ localizationDir, pluginId, versionName, }) {
return (0, utils_1.getPluginI18nPath)({
localizationDir,
pluginName: 'docusaurus-plugin-content-docs',
pluginId,
subPaths: [
versionName === constants_1.CURRENT_VERSION_NAME
? constants_1.CURRENT_VERSION_NAME
: `version-${versionName}`,
],
});
}
exports.getDocsDirPathLocalized = getDocsDirPathLocalized;
/** `community` => `[siteDir]/community_versions.json` */
function getVersionsFilePath(siteDir, pluginId) {
return path_1.default.join(siteDir, addPluginIdPrefix(constants_1.VERSIONS_JSON_FILE, pluginId));
}
exports.getVersionsFilePath = getVersionsFilePath;
/**
* Reads the plugin's respective `versions.json` file, and returns its content.
*
* @throws Throws if validation fails, i.e. `versions.json` doesn't contain an
* array of valid version names.
*/
async function readVersionsFile(siteDir, pluginId) {
const versionsFilePath = getVersionsFilePath(siteDir, pluginId);
if (await fs_extra_1.default.pathExists(versionsFilePath)) {
const content = await fs_extra_1.default.readJSON(versionsFilePath);
(0, validation_1.validateVersionNames)(content);
return content;
}
return null;
}
exports.readVersionsFile = readVersionsFile;
/**
* Reads the `versions.json` file, and returns an ordered list of version names.
*
* - If `disableVersioning` is turned on, it will return `["current"]` (requires
* `includeCurrentVersion` to be true);
* - If `includeCurrentVersion` is turned on, "current" will be inserted at the
* beginning, if not already there.
*
* You need to use {@link filterVersions} after this.
*
* @throws Throws an error if `disableVersioning: true` but `versions.json`
* doesn't exist (i.e. site is not versioned)
* @throws Throws an error if versions list is empty (empty `versions.json` or
* `disableVersioning` is true, and not including current version)
*/
async function readVersionNames(siteDir, options) {
const versionFileContent = await readVersionsFile(siteDir, options.id);
if (!versionFileContent && options.disableVersioning) {
throw new Error(`Docs: using "disableVersioning: true" option on a non-versioned site does not make sense.`);
}
const versions = options.disableVersioning ? [] : versionFileContent ?? [];
// We add the current version at the beginning, unless:
// - user don't want to; or
// - it's already been explicitly added to versions.json
if (options.includeCurrentVersion &&
!versions.includes(constants_1.CURRENT_VERSION_NAME)) {
versions.unshift(constants_1.CURRENT_VERSION_NAME);
}
if (versions.length === 0) {
throw new Error(`It is not possible to use docs without any version. No version is included because you have requested to not include ${path_1.default.resolve(options.path)} through "includeCurrentVersion: false", while ${options.disableVersioning
? 'versioning is disabled with "disableVersioning: true"'
: `the versions file is empty/non-existent`}.`);
}
return versions;
}
exports.readVersionNames = readVersionNames;
/**
* Gets the path-related version metadata.
*
* @throws Throws if the resolved docs folder or sidebars file doesn't exist.
* Does not throw if a versioned sidebar is missing (since we don't create empty
* files).
*/
async function getVersionMetadataPaths({ versionName, context, options, }) {
const isCurrent = versionName === constants_1.CURRENT_VERSION_NAME;
const contentPathLocalized = getDocsDirPathLocalized({
localizationDir: context.localizationDir,
pluginId: options.id,
versionName,
});
const contentPath = isCurrent
? path_1.default.resolve(context.siteDir, options.path)
: getVersionDocsDirPath(context.siteDir, options.id, versionName);
const sidebarFilePath = isCurrent
? options.sidebarPath
: getVersionSidebarsPath(context.siteDir, options.id, versionName);
if (!(await fs_extra_1.default.pathExists(contentPath))) {
throw new Error(`The docs folder does not exist for version "${versionName}". A docs folder is expected to be found at ${path_1.default.relative(context.siteDir, contentPath)}.`);
}
// If the current version defines a path to a sidebar file that does not
// exist, we throw! Note: for versioned sidebars, the file may not exist (as
// we prefer to not create it rather than to create an empty file)
// See https://github.com/facebook/docusaurus/issues/3366
// See https://github.com/facebook/docusaurus/pull/4775
if (versionName === constants_1.CURRENT_VERSION_NAME &&
typeof sidebarFilePath === 'string' &&
!(await fs_extra_1.default.pathExists(sidebarFilePath))) {
throw new Error(`The path to the sidebar file does not exist at "${path_1.default.relative(context.siteDir, sidebarFilePath)}".
Please set the docs "sidebarPath" field in your config file to:
- a sidebars path that exists
- false: to disable the sidebar
- undefined: for Docusaurus to generate it automatically`);
}
return { contentPath, contentPathLocalized, sidebarFilePath };
}
exports.getVersionMetadataPaths = getVersionMetadataPaths;

View File

@@ -0,0 +1,39 @@
/**
* 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 type { FullVersion } from '../types';
import type { LoadContext } from '@docusaurus/types';
import type { LoadedVersion, PluginOptions, VersionBanner, VersionMetadata } from '@docusaurus/plugin-content-docs';
export type VersionContext = {
/** The version name to get banner of. */
versionName: string;
/** All versions, ordered from newest to oldest. */
versionNames: string[];
lastVersionName: string;
context: LoadContext;
options: PluginOptions;
};
/**
* The default version banner depends on the version's relative position to the
* latest version. More recent ones are "unreleased", and older ones are
* "unmaintained".
*/
export declare function getDefaultVersionBanner({ versionName, versionNames, lastVersionName, }: VersionContext): VersionBanner | null;
export declare function getVersionBanner(context: VersionContext): VersionMetadata['banner'];
export declare function getVersionBadge({ versionName, versionNames, options, }: VersionContext): VersionMetadata['badge'];
export declare function getVersionNoIndex({ versionName, options, }: VersionContext): VersionMetadata['noIndex'];
/**
* Filter versions according to provided options (i.e. `onlyIncludeVersions`).
*
* Note: we preserve the order in which versions are provided; the order of the
* `onlyIncludeVersions` array does not matter
*/
export declare function filterVersions(versionNamesUnfiltered: string[], options: PluginOptions): string[];
export declare function readVersionsMetadata({ context, options, }: {
context: LoadContext;
options: PluginOptions;
}): Promise<VersionMetadata[]>;
export declare function toFullVersion(version: LoadedVersion): FullVersion;

View File

@@ -0,0 +1,173 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.toFullVersion = exports.readVersionsMetadata = exports.filterVersions = exports.getVersionNoIndex = exports.getVersionBadge = exports.getVersionBanner = exports.getDefaultVersionBanner = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const utils_1 = require("@docusaurus/utils");
const constants_1 = require("../constants");
const validation_1 = require("./validation");
const files_1 = require("./files");
const utils_2 = require("../sidebars/utils");
const categoryGeneratedIndex_1 = require("../categoryGeneratedIndex");
function getVersionEditUrls({ contentPath, contentPathLocalized, context, options, }) {
// If the user is using the functional form of editUrl,
// she has total freedom and we can't compute a "version edit url"
if (!options.editUrl || typeof options.editUrl === 'function') {
return { editUrl: undefined, editUrlLocalized: undefined };
}
const editDirPath = options.editCurrentVersion ? options.path : contentPath;
const editDirPathLocalized = options.editCurrentVersion
? (0, files_1.getDocsDirPathLocalized)({
localizationDir: context.localizationDir,
versionName: constants_1.CURRENT_VERSION_NAME,
pluginId: options.id,
})
: contentPathLocalized;
const versionPathSegment = (0, utils_1.posixPath)(path_1.default.relative(context.siteDir, path_1.default.resolve(context.siteDir, editDirPath)));
const versionPathSegmentLocalized = (0, utils_1.posixPath)(path_1.default.relative(context.siteDir, path_1.default.resolve(context.siteDir, editDirPathLocalized)));
const editUrl = (0, utils_1.normalizeUrl)([options.editUrl, versionPathSegment]);
const editUrlLocalized = (0, utils_1.normalizeUrl)([
options.editUrl,
versionPathSegmentLocalized,
]);
return { editUrl, editUrlLocalized };
}
/**
* The default version banner depends on the version's relative position to the
* latest version. More recent ones are "unreleased", and older ones are
* "unmaintained".
*/
function getDefaultVersionBanner({ versionName, versionNames, lastVersionName, }) {
// Current version: good, no banner
if (versionName === lastVersionName) {
return null;
}
// Upcoming versions: unreleased banner
if (versionNames.indexOf(versionName) < versionNames.indexOf(lastVersionName)) {
return 'unreleased';
}
// Older versions: display unmaintained banner
return 'unmaintained';
}
exports.getDefaultVersionBanner = getDefaultVersionBanner;
function getVersionBanner(context) {
const { versionName, options } = context;
const versionBannerOption = options.versions[versionName]?.banner;
if (versionBannerOption) {
return versionBannerOption === 'none' ? null : versionBannerOption;
}
return getDefaultVersionBanner(context);
}
exports.getVersionBanner = getVersionBanner;
function getVersionBadge({ versionName, versionNames, options, }) {
// If site is not versioned or only one version is included
// we don't show the version badge by default
// See https://github.com/facebook/docusaurus/issues/3362
const defaultVersionBadge = versionNames.length !== 1;
return options.versions[versionName]?.badge ?? defaultVersionBadge;
}
exports.getVersionBadge = getVersionBadge;
function getVersionNoIndex({ versionName, options, }) {
return options.versions[versionName]?.noIndex ?? false;
}
exports.getVersionNoIndex = getVersionNoIndex;
function getVersionClassName({ versionName, options, }) {
const defaultVersionClassName = `docs-version-${versionName}`;
return options.versions[versionName]?.className ?? defaultVersionClassName;
}
function getVersionLabel({ versionName, options, }) {
const defaultVersionLabel = versionName === constants_1.CURRENT_VERSION_NAME ? 'Next' : versionName;
return options.versions[versionName]?.label ?? defaultVersionLabel;
}
function getVersionPathPart({ versionName, options, lastVersionName, }) {
function getDefaultVersionPathPart() {
if (versionName === lastVersionName) {
return '';
}
return versionName === constants_1.CURRENT_VERSION_NAME ? 'next' : versionName;
}
return options.versions[versionName]?.path ?? getDefaultVersionPathPart();
}
async function createVersionMetadata(context) {
const { versionName, lastVersionName, options, context: loadContext } = context;
const { sidebarFilePath, contentPath, contentPathLocalized } = await (0, files_1.getVersionMetadataPaths)(context);
const versionPathPart = getVersionPathPart(context);
const routePath = (0, utils_1.normalizeUrl)([
loadContext.baseUrl,
options.routeBasePath,
versionPathPart,
]);
const versionEditUrls = getVersionEditUrls({
contentPath,
contentPathLocalized,
context: loadContext,
options,
});
return {
versionName,
label: getVersionLabel(context),
banner: getVersionBanner(context),
badge: getVersionBadge(context),
noIndex: getVersionNoIndex(context),
className: getVersionClassName(context),
path: routePath,
tagsPath: (0, utils_1.normalizeUrl)([routePath, options.tagsBasePath]),
...versionEditUrls,
isLast: versionName === lastVersionName,
routePriority: versionPathPart === '' ? -1 : undefined,
sidebarFilePath,
contentPath,
contentPathLocalized,
};
}
/**
* Filter versions according to provided options (i.e. `onlyIncludeVersions`).
*
* Note: we preserve the order in which versions are provided; the order of the
* `onlyIncludeVersions` array does not matter
*/
function filterVersions(versionNamesUnfiltered, options) {
if (options.onlyIncludeVersions) {
return versionNamesUnfiltered.filter((name) => options.onlyIncludeVersions.includes(name));
}
return versionNamesUnfiltered;
}
exports.filterVersions = filterVersions;
function getLastVersionName({ versionNames, options, }) {
return (options.lastVersion ??
versionNames.find((name) => name !== constants_1.CURRENT_VERSION_NAME) ??
constants_1.CURRENT_VERSION_NAME);
}
async function readVersionsMetadata({ context, options, }) {
const allVersionNames = await (0, files_1.readVersionNames)(context.siteDir, options);
(0, validation_1.validateVersionsOptions)(allVersionNames, options);
const versionNames = filterVersions(allVersionNames, options);
const lastVersionName = getLastVersionName({ versionNames, options });
const versionsMetadata = await Promise.all(versionNames.map((versionName) => createVersionMetadata({
versionName,
versionNames,
lastVersionName,
context,
options,
})));
return versionsMetadata;
}
exports.readVersionsMetadata = readVersionsMetadata;
function toFullVersion(version) {
const sidebarsUtils = (0, utils_2.createSidebarsUtils)(version.sidebars);
return {
...version,
sidebarsUtils,
categoryGeneratedIndices: (0, categoryGeneratedIndex_1.getCategoryGeneratedIndexMetadataList)({
docs: version.docs,
sidebarsUtils,
}),
};
}
exports.toFullVersion = toFullVersion;

View File

@@ -0,0 +1,17 @@
/**
* 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 type { VersionsOptions } from '@docusaurus/plugin-content-docs';
export declare function validateVersionName(name: unknown): asserts name is string;
export declare function validateVersionNames(names: unknown): asserts names is string[];
/**
* @throws Throws for one of the following invalid options:
* - `lastVersion` is non-existent
* - `versions` includes unknown keys
* - `onlyIncludeVersions` is empty, contains unknown names, or doesn't include
* `latestVersion` (if provided)
*/
export declare function validateVersionsOptions(availableVersionNames: string[], options: VersionsOptions): void;

View File

@@ -0,0 +1,71 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateVersionsOptions = exports.validateVersionNames = exports.validateVersionName = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
function validateVersionName(name) {
if (typeof name !== 'string') {
throw new Error(`Versions should be strings. Found type "${typeof name}" for version ${JSON.stringify(name)}.`);
}
if (!name.trim()) {
throw new Error(`Invalid version name "${name}": version name must contain at least one non-whitespace character.`);
}
const errors = [
[/[/\\]/, 'should not include slash (/) or backslash (\\)'],
[/.{33,}/, 'cannot be longer than 32 characters'],
// eslint-disable-next-line no-control-regex
[/[<>:"|?*\x00-\x1F]/, 'should be a valid file path'],
[/^\.\.?$/, 'should not be "." or ".."'],
];
errors.forEach(([pattern, message]) => {
if (pattern.test(name)) {
throw new Error(`Invalid version name "${name}": version name ${message}.`);
}
});
}
exports.validateVersionName = validateVersionName;
function validateVersionNames(names) {
if (!Array.isArray(names)) {
throw new Error(`The versions file should contain an array of version names! Found content: ${JSON.stringify(names)}`);
}
names.forEach(validateVersionName);
}
exports.validateVersionNames = validateVersionNames;
/**
* @throws Throws for one of the following invalid options:
* - `lastVersion` is non-existent
* - `versions` includes unknown keys
* - `onlyIncludeVersions` is empty, contains unknown names, or doesn't include
* `latestVersion` (if provided)
*/
function validateVersionsOptions(availableVersionNames, options) {
const availableVersionNamesMsg = `Available version names are: ${availableVersionNames.join(', ')}`;
if (options.lastVersion &&
!availableVersionNames.includes(options.lastVersion)) {
throw new Error(`Docs option lastVersion: ${options.lastVersion} is invalid. ${availableVersionNamesMsg}`);
}
const unknownVersionConfigNames = lodash_1.default.difference(Object.keys(options.versions), availableVersionNames);
if (unknownVersionConfigNames.length > 0) {
throw new Error(`Invalid docs option "versions": unknown versions (${unknownVersionConfigNames.join(',')}) found. ${availableVersionNamesMsg}`);
}
if (options.onlyIncludeVersions) {
if (options.onlyIncludeVersions.length === 0) {
throw new Error(`Invalid docs option "onlyIncludeVersions": an empty array is not allowed, at least one version is needed.`);
}
const unknownOnlyIncludeVersionNames = lodash_1.default.difference(options.onlyIncludeVersions, availableVersionNames);
if (unknownOnlyIncludeVersionNames.length > 0) {
throw new Error(`Invalid docs option "onlyIncludeVersions": unknown versions (${unknownOnlyIncludeVersionNames.join(',')}) found. ${availableVersionNamesMsg}`);
}
if (options.lastVersion &&
!options.onlyIncludeVersions.includes(options.lastVersion)) {
throw new Error(`Invalid docs option "lastVersion": if you use both the "onlyIncludeVersions" and "lastVersion" options, then "lastVersion" must be present in the provided "onlyIncludeVersions" array.`);
}
}
}
exports.validateVersionsOptions = validateVersionsOptions;