diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index 08315920c1..9cb0278b17 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -14,33 +14,37 @@ import { addTrailingPathSeparator, aliasedSitePath, docuHash, + getFolderContainingFile, + getPluginI18nPath, + Globby, } from '@docusaurus/utils'; import Yaml from 'js-yaml'; import {contentAuthorsSchema} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; -import type { - PluginOptions, - Content, - ShowcaseMetadata, -} from '@docusaurus/plugin-showcase'; +import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; +import type {ShowcaseContentPaths} from './types'; -// https://stackoverflow.com/a/71166133 -const walk = async (dirPath: string): Promise => - Promise.all( - await fs.readdir(dirPath, {withFileTypes: true}).then((entries) => - entries.map((entry) => { - const childPath = path.join(dirPath, entry.name); - return entry.isDirectory() ? walk(childPath) : childPath; - }), - ), - ); +export function getContentPathList( + contentPaths: ShowcaseContentPaths, +): string[] { + return [contentPaths.contentPathLocalized, contentPaths.contentPath]; +} export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, -): Plugin { - const {siteConfig, siteDir, generatedFilesDir} = context; +): Plugin { + const {siteConfig, siteDir, generatedFilesDir, localizationDir} = context; + + const contentPaths: ShowcaseContentPaths = { + contentPath: path.resolve(siteDir, options.path), + contentPathLocalized: getPluginI18nPath({ + localizationDir, + pluginName: 'docusaurus-plugin-content-pages', + pluginId: options.id, + }), + }; const pluginDataDirRoot = path.join( generatedFilesDir, @@ -48,35 +52,52 @@ export default function pluginContentShowcase( ); const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID); - const showcasePath = path.join(siteDir, options.path); - return { name: 'docusaurus-plugin-showcase', - // getPathsToWatch() { - // return [path.join(siteDir, options.path, 'authors.yaml')]; - // }, + getPathsToWatch() { + const {include} = options; + return getContentPathList(contentPaths).flatMap((contentPath) => + include.map((pattern) => `${contentPath}/${pattern}`), + ); + }, - async loadContent(): Promise { - const files: string[] = await walk(path.join(siteDir, options.path)); - const filteredFiles = files - .flat(Number.POSITIVE_INFINITY) - .filter((file) => file.endsWith('.yaml')); + async loadContent() { + const {include} = options; - const contentPromises = filteredFiles.map(async (file) => { - const rawYaml = await fs.readFile(path.join(file), 'utf-8'); - const yaml = Yaml.load(rawYaml); - const parsedYaml = contentAuthorsSchema.validate(yaml); + if (!(await fs.pathExists(contentPaths.contentPath))) { + return null; + } - if (parsedYaml.error) { - throw new Error(`Validation failed: ${parsedYaml.error.message}`, { - cause: parsedYaml.error, + // const {baseUrl} = siteConfig; + const showcaseFiles = await Globby(include, { + cwd: contentPaths.contentPath, + ignore: options.exclude, + }); + + const filteredFiles = showcaseFiles.filter((file) => + file.endsWith('.yaml'), + ); + + async function processPageSourceFile(relativeSource: string) { + // Lookup in localized folder in priority + const contentPath = await getFolderContainingFile( + getContentPathList(contentPaths), + relativeSource, + ); + + const sourcePath = path.join(contentPath, relativeSource); + const aliasedSourcePath = aliasedSitePath(sourcePath, siteDir); + const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + const unsafeYaml = Yaml.load(rawYaml); + const yaml = contentAuthorsSchema.validate(unsafeYaml); + if (yaml.error) { + throw new Error(`Validation failed: ${yaml.error.message}`, { + cause: yaml.error, }); } - const {title, description, preview, website, source, tags} = - parsedYaml.value; - + const {title, description, preview, website, source, tags} = yaml.value; return { title, description, @@ -85,11 +106,21 @@ export default function pluginContentShowcase( source, tags, }; - }); + } + + async function doProcessPageSourceFile(relativeSource: string) { + try { + return await processPageSourceFile(relativeSource); + } catch (err) { + throw new Error( + `Processing of page source file path=${relativeSource} failed.`, + {cause: err as Error}, + ); + } + } - const content = await Promise.all(contentPromises); return { - website: content, + website: await Promise.all(filteredFiles.map(doProcessPageSourceFile)), }; }, @@ -134,17 +165,19 @@ export default function pluginContentShowcase( }, configureWebpack(_config, isServer, utils, content) { + const contentDirs = getContentPathList(contentPaths); + return { resolve: { alias: { - '~blog': pluginDataDirRoot, + '~showcase': pluginDataDirRoot, }, }, module: { rules: [ { test: /\.mdx?$/i, - include: [...showcasePath] + include: contentDirs // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 .map(addTrailingPathSeparator), use: [ @@ -155,6 +188,10 @@ export default function pluginContentShowcase( path.resolve(siteDir, dir), ), siteDir, + // isMDXPartial: createAbsoluteFilePathMatcher( + // options.exclude, + // contentDirs, + // ), metadataPath: (mdxPath: string) => { // Note that metadataPath must be the same/in-sync as // the path from createData for each MDX. @@ -164,24 +201,15 @@ export default function pluginContentShowcase( `${docuHash(aliasedPath)}.json`, ); }, - // For blog posts a title in markdown is always removed - // Blog posts title are rendered separately - removeContentTitle: true, - // Assets allow to convert some relative images paths to // require() calls - // createAssets: ({ - // frontMatter, - // metadata, - // }: { - // frontMatter: Content['website'][number]; - // metadata: ShowcaseMetadata; - // }): Assets => ({ - // image: frontMatter.preview, - // authorsImageUrls: metadata.frontMatter.preview.map( - // (author) => author.imageURL, - // ), - // }), + createAssets: ({ + frontMatter, + }: { + frontMatter: Content['website'][number]; + }) => ({ + image: frontMatter.preview, + }), markdownConfig: siteConfig.markdown, }, }, diff --git a/packages/docusaurus-plugin-showcase/src/markdownLoader.ts b/packages/docusaurus-plugin-showcase/src/markdownLoader.ts new file mode 100644 index 0000000000..e5c91b7bf7 --- /dev/null +++ b/packages/docusaurus-plugin-showcase/src/markdownLoader.ts @@ -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 {LoaderContext} from 'webpack'; + +export default function markdownLoader( + this: LoaderContext, + fileString: string, +): void { + const callback = this.async(); + + // const options = this.getOptions(); + + // TODO provide additional md processing here? like interlinking pages? + // fileString = linkify(fileString) + + return callback(null, fileString); +} diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-showcase/src/options.ts index 87588d50a7..ea18504447 100644 --- a/packages/docusaurus-plugin-showcase/src/options.ts +++ b/packages/docusaurus-plugin-showcase/src/options.ts @@ -6,6 +6,7 @@ */ import {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation'; +import {GlobExcludeDefault} from '@docusaurus/utils'; import type {OptionValidationContext} from '@docusaurus/types'; import type {PluginOptions, Options} from '@docusaurus/plugin-showcase'; @@ -13,11 +14,16 @@ export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', path: 'src/showcase/website', // Path to data on filesystem, relative to site dir. routeBasePath: '/', // URL Route. + include: ['**/*.{yml,yaml,md,mdx}'], // Extensions to include. + exclude: GlobExcludeDefault, }; const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath), + include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include), + exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude), + id: Joi.string().default(DEFAULT_OPTIONS.id), }); export const contentAuthorsSchema = Joi.object({ diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts index eeb37b1f53..78ed7644d6 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts @@ -16,6 +16,8 @@ declare module '@docusaurus/plugin-showcase' { id?: string; path: string; routeBasePath: string; + include: string[]; + exclude: string[]; }; export type TagType = diff --git a/packages/docusaurus-plugin-showcase/src/types.ts b/packages/docusaurus-plugin-showcase/src/types.ts new file mode 100644 index 0000000000..6ec0b45281 --- /dev/null +++ b/packages/docusaurus-plugin-showcase/src/types.ts @@ -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 type ShowcaseContentPaths = { + contentPath: string; + contentPathLocalized: string; +};