"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 // - /.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;