From 2b37b51b36efa7bd066f092887804299b128c58a Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:39:55 +0100 Subject: [PATCH 01/79] wip init --- .../docusaurus-plugin-showcase/.npmignore | 3 + packages/docusaurus-plugin-showcase/README.md | 7 ++ .../docusaurus-plugin-showcase/package.json | 36 +++++++++++ .../docusaurus-plugin-showcase/src/index.ts | 64 +++++++++++++++++++ .../docusaurus-plugin-showcase/src/options.ts | 29 +++++++++ .../src/plugin-showcase.d.ts | 32 ++++++++++ .../docusaurus-plugin-showcase/tsconfig.json | 12 ++++ .../src/theme-classic.d.ts | 8 +++ .../src/theme/Showcase/index.tsx | 13 ++++ website/docusaurus.config.ts | 1 + website/src/components/Showcase.js | 12 ++++ website/src/showcase/authors.json | 4 ++ 12 files changed, 221 insertions(+) create mode 100644 packages/docusaurus-plugin-showcase/.npmignore create mode 100644 packages/docusaurus-plugin-showcase/README.md create mode 100644 packages/docusaurus-plugin-showcase/package.json create mode 100644 packages/docusaurus-plugin-showcase/src/index.ts create mode 100644 packages/docusaurus-plugin-showcase/src/options.ts create mode 100644 packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts create mode 100644 packages/docusaurus-plugin-showcase/tsconfig.json create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx create mode 100644 website/src/components/Showcase.js create mode 100644 website/src/showcase/authors.json diff --git a/packages/docusaurus-plugin-showcase/.npmignore b/packages/docusaurus-plugin-showcase/.npmignore new file mode 100644 index 000000000000..03c9ae1e1b54 --- /dev/null +++ b/packages/docusaurus-plugin-showcase/.npmignore @@ -0,0 +1,3 @@ +.tsbuildinfo* +tsconfig* +__tests__ diff --git a/packages/docusaurus-plugin-showcase/README.md b/packages/docusaurus-plugin-showcase/README.md new file mode 100644 index 000000000000..1d075c625aaf --- /dev/null +++ b/packages/docusaurus-plugin-showcase/README.md @@ -0,0 +1,7 @@ +# `@docusaurus/plugin-showcase` + +Showcase plugin for Docusaurus. + +## Usage + +See [plugin-showcase documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-showcase). diff --git a/packages/docusaurus-plugin-showcase/package.json b/packages/docusaurus-plugin-showcase/package.json new file mode 100644 index 000000000000..bfc1d9374115 --- /dev/null +++ b/packages/docusaurus-plugin-showcase/package.json @@ -0,0 +1,36 @@ +{ + "name": "@docusaurus/plugin-showcase", + "version": "3.0.0", + "description": "Showcase plugin for Docusaurus.", + "main": "lib/index.js", + "types": "src/plugin-showcase.d.ts", + "scripts": { + "build": "tsc", + "watch": "tsc --watch" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/facebook/docusaurus.git", + "directory": "packages/docusaurus-plugin-showcase" + }, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.0.0", + "@docusaurus/types": "3.0.0", + "@docusaurus/utils": "3.0.0", + "@docusaurus/utils-validation": "3.0.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts new file mode 100644 index 000000000000..b0b5f1e2834e --- /dev/null +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -0,0 +1,64 @@ +/** + * 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. + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import fs from 'fs-extra'; +import path from 'path'; +import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; + +import type {LoadContext, Plugin} from '@docusaurus/types'; +import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; + +export default function pluginContentShowcase( + context: LoadContext, + options: PluginOptions, +): Plugin { + const {siteConfig, siteDir, generatedFilesDir} = context; + + const pluginDataDirRoot = path.join( + generatedFilesDir, + 'docusaurus-plugin-showcase', + ); + const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID); + + return { + name: 'docusaurus-plugin-showcase', + + async loadContent() { + const authors = await fs.readJson( + path.join(siteDir, options.path, 'authors.json'), + ); + return authors; + }, + + async contentLoaded({content, actions}) { + if (!content) { + return; + } + + const {addRoute, createData} = actions; + + const dataAuthor = await createData( + 'authors.json', // what is this? + JSON.stringify(content), + ); + + addRoute({ + path: '/showcasetes', + // component: '@site/src/components/Showcase.js', + component: '@theme/Showcase', + modules: { + content: dataAuthor, + }, + exact: true, + }); + }, + }; +} + +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-showcase/src/options.ts new file mode 100644 index 000000000000..137c73c11efe --- /dev/null +++ b/packages/docusaurus-plugin-showcase/src/options.ts @@ -0,0 +1,29 @@ +/** + * 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 {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation'; +import type {OptionValidationContext} from '@docusaurus/types'; +import type {PluginOptions, Options} from '@docusaurus/plugin-showcase'; + +export const DEFAULT_OPTIONS: PluginOptions = { + id: 'showcase', + path: 'src/showcase', // Path to data on filesystem, relative to site dir. + routeBasePath: '/', // URL Route. +}; + +const PluginOptionSchema = Joi.object({ + path: Joi.string().default(DEFAULT_OPTIONS.path), + routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath), +}); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): PluginOptions { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts new file mode 100644 index 000000000000..8c4335253f01 --- /dev/null +++ b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts @@ -0,0 +1,32 @@ +/** + * 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. + */ + +declare module '@docusaurus/plugin-showcase' { + import type {LoadContext, Plugin} from '@docusaurus/types'; + + export type Assets = { + image?: string; + }; + + export type PluginOptions = { + id?: string; + path: string; + routeBasePath: string; + }; + + export type Content = { + title: string; + author: string; + }; + + export type Options = Partial; + + export default function pluginShowcase( + context: LoadContext, + options: PluginOptions, + ): Promise>; +} diff --git a/packages/docusaurus-plugin-showcase/tsconfig.json b/packages/docusaurus-plugin-showcase/tsconfig.json new file mode 100644 index 000000000000..e16d5c2c5d33 --- /dev/null +++ b/packages/docusaurus-plugin-showcase/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": false, + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src"], + "exclude": ["**/__tests__/**"] +} diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 39e7195893b4..4cde56068dac 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -247,6 +247,14 @@ declare module '@theme/BlogPostItems' { export default function BlogPostItem(props: Props): JSX.Element; } +declare module '@theme/Showcase' { + export interface Props { + [key: string]: string; + } + + export default function Showcase(props: Props): JSX.Element; +} + declare module '@theme/BlogPostItem/Container' { import type {ReactNode} from 'react'; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx new file mode 100644 index 000000000000..9373a680c7c0 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -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 React from 'react'; +import type {Props} from '@theme/Showcase'; + +export default function Showcase(props: Props): JSX.Element { + return
{props.author}
; +} diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index bfe47fedd576..ac82efa21851 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -239,6 +239,7 @@ export default async function createConfigAsync() { ], themes: ['live-codeblock', ...dogfoodingThemeInstances], plugins: [ + 'showcase', [ './src/plugins/changelog/index.js', { diff --git a/website/src/components/Showcase.js b/website/src/components/Showcase.js new file mode 100644 index 000000000000..1db561287925 --- /dev/null +++ b/website/src/components/Showcase.js @@ -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. + */ +/* eslint-disable @docusaurus/no-untranslated-text */ +import React from 'react'; + +export default function ShowcaseComponent(props) { + return
Your friends are : {props.content.author}
; +} diff --git a/website/src/showcase/authors.json b/website/src/showcase/authors.json new file mode 100644 index 000000000000..5998be2ef2a0 --- /dev/null +++ b/website/src/showcase/authors.json @@ -0,0 +1,4 @@ +{ + "author": "John Doe", + "title": "My first blog post" +} From f6b0d4622caeaaa5a24adb56208495a987c42643 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Wed, 20 Mar 2024 15:47:26 +0000 Subject: [PATCH 02/79] refactor: apply lint autofix --- project-words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/project-words.txt b/project-words.txt index 891d1c1ff48b..e4faa58bcc26 100644 --- a/project-words.txt +++ b/project-words.txt @@ -312,6 +312,7 @@ setlocal Shiki shiki shortcodes +showcasetes showinfo Sida Simen From cd3908b36f740a8cab5749051181e4083dfad363 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:03:25 +0100 Subject: [PATCH 03/79] wip --- .../docusaurus-plugin-showcase/package.json | 1 + .../docusaurus-plugin-showcase/src/index.ts | 28 +++++++++++++++---- .../docusaurus-plugin-showcase/src/options.ts | 5 ++++ .../src/theme-classic.d.ts | 4 ++- .../src/theme/Showcase/index.tsx | 9 +++++- website/src/showcase/authors.json | 4 --- website/src/showcase/authors.yaml | 2 ++ 7 files changed, 41 insertions(+), 12 deletions(-) delete mode 100644 website/src/showcase/authors.json create mode 100644 website/src/showcase/authors.yaml diff --git a/packages/docusaurus-plugin-showcase/package.json b/packages/docusaurus-plugin-showcase/package.json index bfc1d9374115..be0301b3ad36 100644 --- a/packages/docusaurus-plugin-showcase/package.json +++ b/packages/docusaurus-plugin-showcase/package.json @@ -23,6 +23,7 @@ "@docusaurus/utils": "3.0.0", "@docusaurus/utils-validation": "3.0.0", "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", "tslib": "^2.6.0", "webpack": "^5.88.1" }, diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index b0b5f1e2834e..eff8df2ed3a8 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -10,7 +10,9 @@ import fs from 'fs-extra'; import path from 'path'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; +import Yaml from 'js-yaml'; +import {contentAuthorsSchema} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; @@ -29,11 +31,26 @@ export default function pluginContentShowcase( return { name: 'docusaurus-plugin-showcase', + // getPathsToWatch() { + // return [path.join(siteDir, options.path, 'authors.yaml')]; + // }, + async loadContent() { - const authors = await fs.readJson( - path.join(siteDir, options.path, 'authors.json'), + const yaml = await fs.readFile( + path.join(siteDir, options.path, 'authors.yaml'), + 'utf-8', ); - return authors; + const authors = Yaml.load(yaml); + const parsedAuthors = contentAuthorsSchema.validate(authors); + + if (parsedAuthors.error) { + throw new Error(`Validation failed: ${parsedAuthors.error.message}`, { + cause: parsedAuthors.error, + }); + } + + const validatedAuthors: Content = parsedAuthors.value; + return validatedAuthors; }, async contentLoaded({content, actions}) { @@ -44,13 +61,12 @@ export default function pluginContentShowcase( const {addRoute, createData} = actions; const dataAuthor = await createData( - 'authors.json', // what is this? + 'authors.json', JSON.stringify(content), ); addRoute({ - path: '/showcasetes', - // component: '@site/src/components/Showcase.js', + path: '/showcaseTest', component: '@theme/Showcase', modules: { content: dataAuthor, diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-showcase/src/options.ts index 137c73c11efe..032017c1574e 100644 --- a/packages/docusaurus-plugin-showcase/src/options.ts +++ b/packages/docusaurus-plugin-showcase/src/options.ts @@ -20,6 +20,11 @@ const PluginOptionSchema = Joi.object({ routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath), }); +export const contentAuthorsSchema = Joi.object({ + author: Joi.string().required(), + title: Joi.string().required(), +}); + export function validateOptions({ validate, options, diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 4cde56068dac..24c4965e3246 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -249,7 +249,9 @@ declare module '@theme/BlogPostItems' { declare module '@theme/Showcase' { export interface Props { - [key: string]: string; + content: { + [key: string]: string; + }; } export default function Showcase(props: Props): JSX.Element; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index 9373a680c7c0..e6e7a61421c8 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -7,7 +7,14 @@ import React from 'react'; import type {Props} from '@theme/Showcase'; +import Layout from '@theme/Layout'; export default function Showcase(props: Props): JSX.Element { - return
{props.author}
; + return ( + +
Content for Showcase
+
{props.content.author}
+
{props.content.title}
+
+ ); } diff --git a/website/src/showcase/authors.json b/website/src/showcase/authors.json deleted file mode 100644 index 5998be2ef2a0..000000000000 --- a/website/src/showcase/authors.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "author": "John Doe", - "title": "My first blog post" -} diff --git a/website/src/showcase/authors.yaml b/website/src/showcase/authors.yaml new file mode 100644 index 000000000000..8653c01201bc --- /dev/null +++ b/website/src/showcase/authors.yaml @@ -0,0 +1,2 @@ +author: ozakii +title: testee From a2516dc31a84271c112688379e546a887f94ae26 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Thu, 21 Mar 2024 13:08:45 +0000 Subject: [PATCH 04/79] refactor: apply lint autofix --- project-words.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project-words.txt b/project-words.txt index e4faa58bcc26..fa323348f592 100644 --- a/project-words.txt +++ b/project-words.txt @@ -229,6 +229,7 @@ Outerbounds outerbounds overrideable ozaki +ozakii O’Shannessy pageview Palenight @@ -312,7 +313,6 @@ setlocal Shiki shiki shortcodes -showcasetes showinfo Sida Simen From 7cf981c262512a3b835292cc24ff41bad42778d3 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:42:54 +0100 Subject: [PATCH 05/79] wip routes per yaml --- .../docusaurus-plugin-showcase/src/index.ts | 69 ++- .../src/plugin-showcase.d.ts | 6 +- .../docusaurus-theme-classic/package.json | 1 + .../src/theme-classic.d.ts | 73 +++ .../src/theme/Showcase/FavoriteIcon/index.tsx | 42 ++ .../Showcase/FavoriteIcon/styles.module.css | 54 ++ .../src/theme/Showcase/ShowcaseCard/index.tsx | 240 +++++++++ .../Showcase/ShowcaseCard/styles.module.css | 99 ++++ .../Showcase/ShowcaseFilterToggle/index.tsx | 84 +++ .../ShowcaseFilterToggle/styles.module.css | 57 ++ .../Showcase/ShowcaseTagSelect/index.tsx | 115 +++++ .../ShowcaseTagSelect/styles.module.css | 38 ++ .../theme/Showcase/ShowcaseTooltip/index.tsx | 145 ++++++ .../ShowcaseTooltip/styles.module.css | 45 ++ .../src/theme/Showcase/index.tsx | 487 +++++++++++++++++- .../src/theme/Showcase/styles.module.css | 95 ++++ website/src/showcase/authors.yaml | 2 - website/src/showcase/website/ozaki.yaml | 2 + website/src/showcase/website/seb.yaml | 2 + 19 files changed, 1623 insertions(+), 33 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/styles.module.css delete mode 100644 website/src/showcase/authors.yaml create mode 100644 website/src/showcase/website/ozaki.yaml create mode 100644 website/src/showcase/website/seb.yaml diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index eff8df2ed3a8..c6819a23f6a2 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -35,44 +35,63 @@ export default function pluginContentShowcase( // return [path.join(siteDir, options.path, 'authors.yaml')]; // }, - async loadContent() { - const yaml = await fs.readFile( - path.join(siteDir, options.path, 'authors.yaml'), - 'utf-8', + async loadContent(): Promise { + const files = await fs.readdir( + path.join(siteDir, options.path, 'website'), ); - const authors = Yaml.load(yaml); - const parsedAuthors = contentAuthorsSchema.validate(authors); + const yamlFiles = files.filter((file) => file.endsWith('.yaml')); - if (parsedAuthors.error) { - throw new Error(`Validation failed: ${parsedAuthors.error.message}`, { - cause: parsedAuthors.error, - }); - } + const contentPromises = yamlFiles.map(async (file) => { + const yaml = await fs.readFile( + path.join(siteDir, options.path, 'website', file), + 'utf-8', + ); + const authors = Yaml.load(yaml); + const parsedAuthors = contentAuthorsSchema.validate(authors); + + if (parsedAuthors.error) { + throw new Error(`Validation failed: ${parsedAuthors.error.message}`, { + cause: parsedAuthors.error, + }); + } + + return { + title: parsedAuthors.value.title, + author: parsedAuthors.value.author, // Assuming author is part of Content type + }; + }); - const validatedAuthors: Content = parsedAuthors.value; - return validatedAuthors; + const content = await Promise.all(contentPromises); + return { + website: content, + }; }, async contentLoaded({content, actions}) { if (!content) { return; } + console.log('content:', content); const {addRoute, createData} = actions; - const dataAuthor = await createData( - 'authors.json', - JSON.stringify(content), - ); + await Promise.all( + content.website.map(async (item) => { + const dataAuthor = await createData( + `${item.title}.json`, + JSON.stringify(item), + ); - addRoute({ - path: '/showcaseTest', - component: '@theme/Showcase', - modules: { - content: dataAuthor, - }, - exact: true, - }); + addRoute({ + path: `/${item.title}`, + component: '@theme/Showcase', + modules: { + content: dataAuthor, + }, + exact: true, + }); + }), + ); }, }; } diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts index 8c4335253f01..7080908fba9b 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts @@ -19,8 +19,10 @@ declare module '@docusaurus/plugin-showcase' { }; export type Content = { - title: string; - author: string; + website: { + author: string; + title: string; + }[]; }; export type Options = Partial; diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 9254721778c8..79f0082125f7 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -41,6 +41,7 @@ "postcss": "^8.4.26", "prism-react-renderer": "^2.3.0", "prismjs": "^1.29.0", + "react-popper": "^2.3.0", "react-router-dom": "^5.3.4", "rtlcss": "^4.1.0", "tslib": "^2.6.0", diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 24c4965e3246..8c1af1e55b76 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -254,9 +254,82 @@ declare module '@theme/Showcase' { }; } + export function prepareUserState(): UserState | undefined; + export default function Showcase(props: Props): JSX.Element; } +declare module '@theme/Showcase/ShowcaseCard' { + export type User = { + title: string; + description: string; + preview: string | null; // null = use our serverless screenshot service + website: string; + source: string | null; + tags: TagType[]; + }; + + export interface Props { + readonly user: User; + } + + export default function ShowcaseCard(props: Props): JSX.Element; +} +declare module '@theme/Showcase/ShowcaseTooltip' { + export interface Props { + anchorEl?: HTMLElement | string; + id: string; + text: string; + children: React.ReactElement; + } + + export default function ShowcaseTooltip(props: Props): JSX.Element; +} +declare module '@theme/Showcase/ShowcaseTagSelect' { + import {type ComponentProps, type ReactNode, type ReactElement} from 'react'; + + export interface Props extends ComponentProps<'input'> { + icon: ReactElement>; + label: ReactNode; + tag: TagType; + } + + export function readSearchTags(search: string): TagType[]; + + export default function ShowcaseTagSelect(props: Props): JSX.Element; +} +declare module '@theme/Showcase/ShowcaseFilterToggle' { + export type Operator = 'OR' | 'AND'; + export const OperatorQueryKey = 'operator'; + + export function readOperator(search: string): Operator; + + export default function ShowcaseFilterToggle(): JSX.Element; +} + +declare module '@theme/Showcase/FavoriteIcon' { + import {type ReactNode, type ComponentProps} from 'react'; + + export type SvgIconProps = ComponentProps<'svg'> & { + viewBox?: string; + size?: 'inherit' | 'small' | 'medium' | 'large'; + color?: + | 'inherit' + | 'primary' + | 'secondary' + | 'success' + | 'error' + | 'warning'; + svgClass?: string; // Class attribute on the child + colorAttr?: string; // Applies a color attribute to the SVG element. + children: ReactNode; // Node passed into the SVG element. + }; + + export type Props = Omit; + + export default function FavoriteIcon(props: Props): JSX.Element; +} + declare module '@theme/BlogPostItem/Container' { import type {ReactNode} from 'react'; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx new file mode 100644 index 000000000000..04f0fb4128e3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx @@ -0,0 +1,42 @@ +/** + * 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 React from 'react'; +import clsx from 'clsx'; +import type {Props, SvgIconProps} from '@theme/Showcase/FavoriteIcon'; +import styles from './styles.module.css'; + +function Svg(props: SvgIconProps): JSX.Element { + const { + svgClass, + colorAttr, + children, + color = 'inherit', + size = 'medium', + viewBox = '0 0 24 24', + ...rest + } = props; + + return ( + + {children} + + ); +} + +export default function FavoriteIcon(props: Props): JSX.Element { + return ( + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css new file mode 100644 index 000000000000..db2d48e3ea71 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css @@ -0,0 +1,54 @@ +/** + * 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. + */ + +.svgIcon { + user-select: none; + width: 1em; + height: 1em; + display: inline-block; + fill: currentColor; + flex-shrink: 0; + color: inherit; +} + +/* font-size */ +.small { + font-size: 1.25rem; +} + +.medium { + font-size: 1.5rem; +} + +.large { + font-size: 2.185rem; +} + +/* colors */ +.primary { + color: var(--ifm-color-primary); +} + +.secondary { + color: var(--ifm-color-secondary); +} + +.success { + color: var(--ifm-color-success); +} + +.error { + color: var(--ifm-color-error); +} + +.warning { + color: var(--ifm-color-warning); +} + +.inherit { + color: inherit; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx new file mode 100644 index 000000000000..05ec6298970c --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx @@ -0,0 +1,240 @@ +/** + * 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 React from 'react'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import Translate, {translate} from '@docusaurus/Translate'; +// import Image from '@theme/IdealImage'; +import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; +import Heading from '@theme/Heading'; +import Tooltip from '@theme/Showcase/ShowcaseTooltip'; +import styles from './styles.module.css'; + +const Tags: {[type in TagType]: Tag} = { + favorite: { + label: translate({message: 'Favorite'}), + description: translate({ + message: + 'Our favorite Docusaurus sites that you must absolutely check out!', + id: 'showcase.tag.favorite.description', + }), + color: '#e9669e', + }, + + opensource: { + label: translate({message: 'Open-Source'}), + description: translate({ + message: 'Open-Source Docusaurus sites can be useful for inspiration!', + id: 'showcase.tag.opensource.description', + }), + color: '#39ca30', + }, + + product: { + label: translate({message: 'Product'}), + description: translate({ + message: 'Docusaurus sites associated to a commercial product!', + id: 'showcase.tag.product.description', + }), + color: '#dfd545', + }, + + design: { + label: translate({message: 'Design'}), + description: translate({ + message: + 'Beautiful Docusaurus sites, polished and standing out from the initial template!', + id: 'showcase.tag.design.description', + }), + color: '#a44fb7', + }, + + i18n: { + label: translate({message: 'I18n'}), + description: translate({ + message: + 'Translated Docusaurus sites using the internationalization support with more than 1 locale.', + id: 'showcase.tag.i18n.description', + }), + color: '#127f82', + }, + + versioning: { + label: translate({message: 'Versioning'}), + description: translate({ + message: + 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.', + id: 'showcase.tag.versioning.description', + }), + color: '#fe6829', + }, + + large: { + label: translate({message: 'Large'}), + description: translate({ + message: + 'Very large Docusaurus sites, including many more pages than the average!', + id: 'showcase.tag.large.description', + }), + color: '#8c2f00', + }, + + meta: { + label: translate({message: 'Meta'}), + description: translate({ + message: 'Docusaurus sites of Meta (formerly Facebook) projects', + id: 'showcase.tag.meta.description', + }), + color: '#4267b2', // Facebook blue + }, + + personal: { + label: translate({message: 'Personal'}), + description: translate({ + message: + 'Personal websites, blogs and digital gardens built with Docusaurus', + id: 'showcase.tag.personal.description', + }), + color: '#14cfc3', + }, + + rtl: { + label: translate({message: 'RTL Direction'}), + description: translate({ + message: + 'Docusaurus sites using the right-to-left reading direction support.', + id: 'showcase.tag.rtl.description', + }), + color: '#ffcfc3', + }, +}; + +const TagList = Object.keys(Tags) as TagType[]; + +type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; + +type User = { + title: string; + description: string; + preview: string | null; // null = use our serverless screenshot service + website: string; + source: string | null; + tags: TagType[]; +}; + +type Tag = { + label: string; + description: string; + color: string; +}; + +function sortBy( + array: T[], + getter: (item: T) => string | number | boolean, +): T[] { + const sortedArray = [...array]; + sortedArray.sort((a, b) => + // eslint-disable-next-line no-nested-ternary + getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0, + ); + return sortedArray; +} + +const TagComp = React.forwardRef( + ({label, color, description}, ref) => ( +
  • + {label.toLowerCase()} + +
  • + ), +); + +function ShowcaseCardTag({tags}: {tags: TagType[]}) { + const tagObjects = tags.map((tag) => ({tag, ...Tags[tag]})); + + // Keep same order for all tags + const tagObjectsSorted = sortBy(tagObjects, (tagObject) => + TagList.indexOf(tagObject.tag), + ); + + return ( + <> + {tagObjectsSorted.map((tagObject, index) => { + const id = `showcase_card_tag_${tagObject.tag}`; + + return ( + + + + ); + })} + + ); +} + +// function getCardImage(user: User): string { +// return ( +// user.preview ?? +// `https://slorber-api-screenshot.netlify.app/${encodeURIComponent( +// user.website, +// )}/showcase` +// ); +// } + +function ShowcaseCard({user}: {user: User}) { + // const image = getCardImage(user); + return ( +
  • +
    + {/* {user.title} */} +
    +
    +
    + + + {user.title} + + + {user.tags.includes('favorite') && ( + + )} + {user.source && ( + + source + + )} +
    +

    {user.description}

    +
    +
      + +
    +
  • + ); +} + +export default React.memo(ShowcaseCard); diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/styles.module.css new file mode 100644 index 000000000000..4d45056834f0 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/styles.module.css @@ -0,0 +1,99 @@ +/** + * 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. + */ + +.showcaseCardImage { + overflow: hidden; + height: 150px; + border-bottom: 2px solid var(--ifm-color-emphasis-200); +} + +.showcaseCardHeader { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.showcaseCardTitle { + margin-bottom: 0; + flex: 1 1 auto; +} + +.showcaseCardTitle a { + text-decoration: none; + background: linear-gradient( + var(--ifm-color-primary), + var(--ifm-color-primary) + ) + 0% 100% / 0% 1px no-repeat; + transition: background-size ease-out 200ms; +} + +.showcaseCardTitle a:not(:focus):hover { + background-size: 100% 1px; +} + +.showcaseCardTitle, +.showcaseCardHeader .svgIconFavorite { + margin-right: 0.25rem; +} + +.showcaseCardHeader .svgIconFavorite { + color: var(--site-color-svg-icon-favorite); +} + +.showcaseCardSrcBtn { + margin-left: 6px; + padding-left: 12px; + padding-right: 12px; + border: none; +} + +.showcaseCardSrcBtn:focus-visible { + background-color: var(--ifm-color-secondary-dark); +} + +[data-theme='dark'] .showcaseCardSrcBtn { + background-color: var(--ifm-color-emphasis-200) !important; + color: inherit; +} + +[data-theme='dark'] .showcaseCardSrcBtn:hover { + background-color: var(--ifm-color-emphasis-300) !important; +} + +.showcaseCardBody { + font-size: smaller; + line-height: 1.66; +} + +.cardFooter { + display: flex; + flex-wrap: wrap; +} + +.tag { + font-size: 0.675rem; + border: 1px solid var(--ifm-color-secondary-darkest); + cursor: default; + margin-right: 6px; + margin-bottom: 6px !important; + border-radius: 12px; + display: inline-flex; + align-items: center; +} + +.tag .textLabel { + margin-left: 8px; +} + +.tag .colorLabel { + width: 7px; + height: 7px; + border-radius: 50%; + margin-left: 6px; + margin-right: 6px; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx new file mode 100644 index 000000000000..55c1cc3d527c --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx @@ -0,0 +1,84 @@ +/** + * 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 React, {useState, useEffect, useCallback} from 'react'; +import clsx from 'clsx'; +import {useHistory, useLocation} from '@docusaurus/router'; + +import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; +import {OperatorQueryKey} from '@theme/Showcase/ShowcaseFilterToggle'; +import {prepareUserState} from '@theme/Showcase'; + +import styles from './styles.module.css'; + +function readOperator(search: string): Operator { + return (new URLSearchParams(search).get(OperatorQueryKey) ?? + 'OR') as Operator; +} + +export default function ShowcaseFilterToggle(): JSX.Element { + const id = 'showcase_filter_toggle'; + const location = useLocation(); + const history = useHistory(); + const [operator, setOperator] = useState(false); + useEffect(() => { + setOperator(readOperator(location.search) === 'AND'); + }, [location]); + const toggleOperator = useCallback(() => { + setOperator((o) => !o); + const searchParams = new URLSearchParams(location.search); + searchParams.delete(OperatorQueryKey); + if (!operator) { + searchParams.append(OperatorQueryKey, 'AND'); + } + history.push({ + ...location, + search: searchParams.toString(), + state: prepareUserState(), + }); + }, [operator, location, history]); + + const ClearTag = () => { + history.push({ + ...location, + search: '', + state: prepareUserState(), + }); + }; + + return ( +
    + { + if (e.key === 'Enter') { + toggleOperator(); + } + }} + checked={operator} + /> + + + {/* eslint-disable-next-line @docusaurus/no-untranslated-text */} + +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/styles.module.css new file mode 100644 index 000000000000..4fde44d184fc --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/styles.module.css @@ -0,0 +1,57 @@ +/** + * 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. + */ + +.checkboxLabel { + --height: 25px; + --width: 80px; + --border: 2px; + display: flex; + width: var(--width); + height: var(--height); + position: relative; + border-radius: var(--height); + border: var(--border) solid var(--ifm-color-primary-darkest); + cursor: pointer; + justify-content: space-around; + opacity: 0.75; + transition: opacity var(--ifm-transition-fast) + var(--ifm-transition-timing-default); + box-shadow: var(--ifm-global-shadow-md); +} + +.checkboxLabel:hover { + opacity: 1; + box-shadow: var(--ifm-global-shadow-md), + 0 0 2px 1px var(--ifm-color-primary-dark); +} + +.checkboxLabel::after { + position: absolute; + content: ''; + inset: 0; + width: calc(var(--width) / 2); + height: 100%; + border-radius: var(--height); + background-color: var(--ifm-color-primary-darkest); + transition: transform var(--ifm-transition-fast) + var(--ifm-transition-timing-default); + transform: translateX(calc(var(--width) / 2 - var(--border))); +} + +input:focus-visible ~ .checkboxLabel::after { + outline: 2px solid currentColor; +} + +.checkboxLabel > * { + font-size: 0.8rem; + color: inherit; + transition: opacity 150ms ease-in 50ms; +} + +input:checked ~ .checkboxLabel::after { + transform: translateX(calc(-1 * var(--border))); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx new file mode 100644 index 000000000000..aa73e9c537f3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx @@ -0,0 +1,115 @@ +/** + * 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 React, { + useCallback, + useState, + useEffect, + type ComponentProps, + type ReactNode, + type ReactElement, +} from 'react'; +import {useHistory, useLocation} from '@docusaurus/router'; + +import {prepareUserState} from '@theme/Showcase'; +import styles from './styles.module.css'; + +function toggleListItem(list: T[], item: T): T[] { + const itemIndex = list.indexOf(item); + if (itemIndex === -1) { + return list.concat(item); + } + const newList = [...list]; + newList.splice(itemIndex, 1); + return newList; +} + +type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; +interface Props extends ComponentProps<'input'> { + icon: ReactElement>; + label: ReactNode; + tag: TagType; +} + +const TagQueryStringKey = 'tags'; + +function readSearchTags(search: string): TagType[] { + return new URLSearchParams(search).getAll(TagQueryStringKey) as TagType[]; +} + +function replaceSearchTags(search: string, newTags: TagType[]) { + const searchParams = new URLSearchParams(search); + searchParams.delete(TagQueryStringKey); + newTags.forEach((tag) => searchParams.append(TagQueryStringKey, tag)); + return searchParams.toString(); +} + +function ShowcaseTagSelect( + {id, icon, label, tag, ...rest}: Props, + ref: React.ForwardedRef, +) { + const location = useLocation(); + const history = useHistory(); + const [selected, setSelected] = useState(false); + useEffect(() => { + const tags = readSearchTags(location.search); + setSelected(tags.includes(tag)); + }, [tag, location]); + const toggleTag = useCallback(() => { + const tags = readSearchTags(location.search); + const newTags = toggleListItem(tags, tag); + const newSearch = replaceSearchTags(location.search, newTags); + history.push({ + ...location, + search: newSearch, + state: prepareUserState(), + }); + }, [tag, location, history]); + return ( + <> + { + if (e.key === 'Enter') { + toggleTag(); + } + }} + onFocus={(e) => { + if (e.relatedTarget) { + e.target.nextElementSibling?.dispatchEvent( + new KeyboardEvent('focus'), + ); + } + }} + onBlur={(e) => { + e.target.nextElementSibling?.dispatchEvent(new KeyboardEvent('blur')); + }} + onChange={toggleTag} + checked={selected} + {...rest} + /> + + + ); +} + +export default React.forwardRef(ShowcaseTagSelect); diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/styles.module.css new file mode 100644 index 000000000000..736714703605 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/styles.module.css @@ -0,0 +1,38 @@ +/** + * 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. + */ + +.checkboxLabel:hover { + opacity: 1; + box-shadow: 0 0 2px 1px var(--ifm-color-secondary-darkest); +} + +input[type='checkbox'] + .checkboxLabel { + display: flex; + align-items: center; + cursor: pointer; + line-height: 1.5; + border-radius: 4px; + padding: 0.275rem 0.8rem; + opacity: 0.85; + transition: opacity 200ms ease-out; + border: 2px solid var(--ifm-color-secondary-darkest); +} + +input:focus-visible + .checkboxLabel { + outline: 2px solid currentColor; +} + +input:checked + .checkboxLabel { + opacity: 0.9; + background-color: var(--site-color-checkbox-checked-bg); + border: 2px solid var(--ifm-color-primary-darkest); +} + +input:checked + .checkboxLabel:hover { + opacity: 0.75; + box-shadow: 0 0 2px 1px var(--ifm-color-primary-dark); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx new file mode 100644 index 000000000000..7087fa0e5d2d --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx @@ -0,0 +1,145 @@ +/** + * 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 React, {useEffect, useState, useRef} from 'react'; +import ReactDOM from 'react-dom'; +import {usePopper} from 'react-popper'; +import styles from './styles.module.css'; + +interface Props { + anchorEl?: HTMLElement | string; + id: string; + text: string; + children: React.ReactElement; +} + +export default function Tooltip({ + children, + id, + anchorEl, + text, +}: Props): JSX.Element { + const [open, setOpen] = useState(false); + const [referenceElement, setReferenceElement] = useState( + null, + ); + const [popperElement, setPopperElement] = useState(null); + const [arrowElement, setArrowElement] = useState(null); + const [container, setContainer] = useState(null); + const {styles: popperStyles, attributes} = usePopper( + referenceElement, + popperElement, + { + modifiers: [ + { + name: 'arrow', + options: { + element: arrowElement, + }, + }, + { + name: 'offset', + options: { + offset: [0, 8], + }, + }, + ], + }, + ); + + const timeout = useRef(null); + const tooltipId = `${id}_tooltip`; + + useEffect(() => { + if (anchorEl) { + if (typeof anchorEl === 'string') { + setContainer(document.querySelector(anchorEl)); + } else { + setContainer(anchorEl); + } + } else { + setContainer(document.body); + } + }, [container, anchorEl]); + + useEffect(() => { + const showEvents = ['mouseenter', 'focus']; + const hideEvents = ['mouseleave', 'blur']; + + const handleOpen = () => { + // There is no point in displaying an empty tooltip. + if (text === '') { + return; + } + + // Remove the title ahead of time to avoid displaying + // two tooltips at the same time (native + this one). + referenceElement?.removeAttribute('title'); + + timeout.current = window.setTimeout(() => { + setOpen(true); + }, 400); + }; + + const handleClose = () => { + clearInterval(timeout.current!); + setOpen(false); + }; + + if (referenceElement) { + showEvents.forEach((event) => { + referenceElement.addEventListener(event, handleOpen); + }); + + hideEvents.forEach((event) => { + referenceElement.addEventListener(event, handleClose); + }); + } + + return () => { + if (referenceElement) { + showEvents.forEach((event) => { + referenceElement.removeEventListener(event, handleOpen); + }); + + hideEvents.forEach((event) => { + referenceElement.removeEventListener(event, handleClose); + }); + } + }; + }, [referenceElement, text]); + + return ( + <> + {React.cloneElement(children, { + ref: setReferenceElement, + 'aria-describedby': open ? tooltipId : undefined, + })} + {container + ? ReactDOM.createPortal( + open && ( + + ), + container, + ) + : container} + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css new file mode 100644 index 000000000000..5500c818b625 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css @@ -0,0 +1,45 @@ +/** + * 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. + */ + +.tooltip { + border-radius: 4px; + padding: 4px 8px; + color: var(--site-color-tooltip); + background: var(--site-color-tooltip-background); + font-size: 0.8rem; + z-index: 500; + line-height: 1.4; + font-weight: 500; + max-width: 300px; + opacity: 0.92; +} + +.tooltipArrow { + visibility: hidden; +} + +.tooltipArrow, +.tooltipArrow::before { + position: absolute; + width: 8px; + height: 8px; + background: inherit; +} + +.tooltipArrow::before { + visibility: visible; + content: ''; + transform: rotate(45deg); +} + +.tooltip[data-popper-placement^='top'] > .tooltipArrow { + bottom: -4px; +} + +.tooltip[data-popper-placement^='bottom'] > .tooltipArrow { + top: -4px; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index e6e7a61421c8..d4fc93f365ab 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -5,16 +5,495 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import {useHistory, useLocation} from 'react-router-dom'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; +import Translate, {translate} from '@docusaurus/Translate'; +import {usePluralForm} from '@docusaurus/theme-common'; import type {Props} from '@theme/Showcase'; import Layout from '@theme/Layout'; +import Heading from '@theme/Heading'; +import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; +import ShowcaseCard from '@theme/Showcase/ShowcaseCard'; +import ShowcaseTooltip from '@theme/Showcase/ShowcaseTooltip'; +import ShowcaseTagSelect, { + readSearchTags, +} from '@theme/Showcase/ShowcaseTagSelect'; +import ShowcaseFilterToggle, { + readOperator, +} from '@theme/Showcase/ShowcaseFilterToggle'; +import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; +import styles from './styles.module.css'; + +const TITLE = translate({message: 'Docusaurus Site Showcase'}); +const DESCRIPTION = translate({ + message: 'List of websites people are building with Docusaurus', +}); +const SUBMIT_URL = 'https://github.com/facebook/docusaurus/discussions/7826'; + +type UserState = { + scrollTopPosition: number; + focusedElementId: string | undefined; +}; + +type Tag = { + label: string; + description: string; + color: string; +}; + +const Tags: {[type in TagType]: Tag} = { + favorite: { + label: translate({message: 'Favorite'}), + description: translate({ + message: + 'Our favorite Docusaurus sites that you must absolutely check out!', + id: 'showcase.tag.favorite.description', + }), + color: '#e9669e', + }, + + opensource: { + label: translate({message: 'Open-Source'}), + description: translate({ + message: 'Open-Source Docusaurus sites can be useful for inspiration!', + id: 'showcase.tag.opensource.description', + }), + color: '#39ca30', + }, + + product: { + label: translate({message: 'Product'}), + description: translate({ + message: 'Docusaurus sites associated to a commercial product!', + id: 'showcase.tag.product.description', + }), + color: '#dfd545', + }, + + design: { + label: translate({message: 'Design'}), + description: translate({ + message: + 'Beautiful Docusaurus sites, polished and standing out from the initial template!', + id: 'showcase.tag.design.description', + }), + color: '#a44fb7', + }, + + i18n: { + label: translate({message: 'I18n'}), + description: translate({ + message: + 'Translated Docusaurus sites using the internationalization support with more than 1 locale.', + id: 'showcase.tag.i18n.description', + }), + color: '#127f82', + }, + + versioning: { + label: translate({message: 'Versioning'}), + description: translate({ + message: + 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.', + id: 'showcase.tag.versioning.description', + }), + color: '#fe6829', + }, + + large: { + label: translate({message: 'Large'}), + description: translate({ + message: + 'Very large Docusaurus sites, including many more pages than the average!', + id: 'showcase.tag.large.description', + }), + color: '#8c2f00', + }, + + meta: { + label: translate({message: 'Meta'}), + description: translate({ + message: 'Docusaurus sites of Meta (formerly Facebook) projects', + id: 'showcase.tag.meta.description', + }), + color: '#4267b2', // Facebook blue + }, + + personal: { + label: translate({message: 'Personal'}), + description: translate({ + message: + 'Personal websites, blogs and digital gardens built with Docusaurus', + id: 'showcase.tag.personal.description', + }), + color: '#14cfc3', + }, + + rtl: { + label: translate({message: 'RTL Direction'}), + description: translate({ + message: + 'Docusaurus sites using the right-to-left reading direction support.', + id: 'showcase.tag.rtl.description', + }), + color: '#ffcfc3', + }, +}; + +const TagList = Object.keys(Tags) as TagType[]; + +const Users: User[] = [ + { + title: 'AgileTs', + description: 'Global State and Logic Framework for reactive Applications', + preview: 'https://github.com/ozakione.png', + website: 'https://agile-ts.org/', + source: 'https://github.com/agile-ts/documentation', + tags: ['opensource', 'design'], + }, +]; + +function sortBy( + array: T[], + getter: (item: T) => string | number | boolean, +): T[] { + const sortedArray = [...array]; + sortedArray.sort((a, b) => + // eslint-disable-next-line no-nested-ternary + getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0, + ); + return sortedArray; +} + +function sortUsers() { + let result = Users; + // Sort by site name + result = sortBy(result, (user) => user.title.toLowerCase()); + // Sort by favorite tag, favorites first + result = sortBy(result, (user) => !user.tags.includes('favorite')); + return result; +} + +const sortedUsers = sortUsers(); + +function ShowcaseHeader() { + return ( +
    + {TITLE} +

    {DESCRIPTION}

    + + + 🙏 Please add your site + + +
    + ); +} + +function prepareUserState(): UserState | undefined { + if (ExecutionEnvironment.canUseDOM) { + return { + scrollTopPosition: window.scrollY, + focusedElementId: document.activeElement?.id, + }; + } + + return undefined; +} + +function restoreUserState(userState: UserState | null) { + const {scrollTopPosition, focusedElementId} = userState ?? { + scrollTopPosition: 0, + focusedElementId: undefined, + }; + // @ts-expect-error: if focusedElementId is undefined it returns null + document.getElementById(focusedElementId)?.focus(); + window.scrollTo({top: scrollTopPosition}); +} +type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; + +type User = { + title: string; + description: string; + preview: string | null; // null = use our serverless screenshot service + website: string; + source: string | null; + tags: TagType[]; +}; + +// type Tag = { +// label: string; +// description: string; +// color: string; +// }; + +function filterUsers( + users: User[], + selectedTags: TagType[], + operator: Operator, + searchName: string | null, +) { + if (searchName) { + // eslint-disable-next-line no-param-reassign + users = users.filter((user) => + user.title.toLowerCase().includes(searchName.toLowerCase()), + ); + } + if (selectedTags.length === 0) { + return users; + } + return users.filter((user) => { + if (user.tags.length === 0) { + return false; + } + if (operator === 'AND') { + return selectedTags.every((tag) => user.tags.includes(tag)); + } + return selectedTags.some((tag) => user.tags.includes(tag)); + }); +} + +function useFilteredUsers() { + const location = useLocation(); + const [operator, setOperator] = useState('OR'); + // On SSR / first mount (hydration) no tag is selected + const [selectedTags, setSelectedTags] = useState([]); + const [searchName, setSearchName] = useState(null); + // Sync tags from QS to state (delayed on purpose to avoid SSR/Client + // hydration mismatch) + useEffect(() => { + setSelectedTags(readSearchTags(location.search)); + setOperator(readOperator(location.search)); + setSearchName(readSearchName(location.search)); + restoreUserState(location.state); + }, [location]); + + return useMemo( + () => filterUsers(sortedUsers, selectedTags, operator, searchName), + [selectedTags, operator, searchName], + ); +} + +function useSiteCountPlural() { + const {selectMessage} = usePluralForm(); + return (sitesCount: number) => + selectMessage( + sitesCount, + translate( + { + id: 'showcase.filters.resultCount', + description: + 'Pluralized label for the number of sites found on the showcase. Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)', + message: '1 site|{sitesCount} sites', + }, + {sitesCount}, + ), + ); +} + +function ShowcaseFilters() { + const filteredUsers = useFilteredUsers(); + const siteCountPlural = useSiteCountPlural(); + return ( +
    +
    +
    + + Filters + + {siteCountPlural(filteredUsers.length)} +
    + +
    +
      + {TagList.map((tag, i) => { + const {label, description, color} = Tags[tag]; + const id = `showcase_checkbox_id_${tag}`; + + return ( +
    • + + + ) : ( + + ) + } + /> + +
    • + ); + })} +
    +
    + ); +} + +const SearchNameQueryKey = 'name'; + +function readSearchName(search: string) { + return new URLSearchParams(search).get(SearchNameQueryKey); +} + +function SearchBar() { + const history = useHistory(); + const location = useLocation(); + const [value, setValue] = useState(null); + useEffect(() => { + setValue(readSearchName(location.search)); + }, [location]); + return ( +
    + { + setValue(e.currentTarget.value); + const newSearch = new URLSearchParams(location.search); + newSearch.delete(SearchNameQueryKey); + if (e.currentTarget.value) { + newSearch.set(SearchNameQueryKey, e.currentTarget.value); + } + history.push({ + ...location, + search: newSearch.toString(), + state: prepareUserState(), + }); + setTimeout(() => { + document.getElementById('searchbar')?.focus(); + }, 0); + }} + /> +
    + ); +} + +const favoriteUsers = sortedUsers.filter((user) => + user.tags.includes('favorite'), +); +const otherUsers = sortedUsers.filter( + (user) => !user.tags.includes('favorite'), +); + +function ShowcaseCards() { + const filteredUsers = useFilteredUsers(); + + if (filteredUsers.length === 0) { + return ( +
    +
    + + No result + +
    +
    + ); + } + + return ( +
    + {filteredUsers.length === sortedUsers.length ? ( + <> +
    +
    +
    + + + Our favorites + + + +
    +
      + {favoriteUsers.map((user) => ( + + ))} +
    +
    +
    +
    + + All sites + +
      + {otherUsers.map((user) => ( + + ))} +
    +
    + + ) : ( +
    +
    +
      + {filteredUsers.map((user) => ( + + ))} +
    +
    + )} +
    + ); +} export default function Showcase(props: Props): JSX.Element { return ( -
    Content for Showcase
    -
    {props.content.author}
    -
    {props.content.title}
    +
    {JSON.stringify(props)}
    +
    + + +
    + +
    + +
    ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/styles.module.css new file mode 100644 index 000000000000..d9cfd94b0428 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/styles.module.css @@ -0,0 +1,95 @@ +/** + * 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. + */ + +.filterCheckbox { + justify-content: space-between; +} + +.filterCheckbox, +.checkboxList { + display: flex; + align-items: center; +} + +.filterCheckbox > div:first-child { + display: flex; + flex: 1 1 auto; + align-items: center; +} + +.filterCheckbox > div > * { + margin-bottom: 0; + margin-right: 8px; +} + +.checkboxList { + flex-wrap: wrap; +} + +.checkboxListItem { + user-select: none; + white-space: nowrap; + height: 32px; + font-size: 0.8rem; + margin-top: 0.5rem; + margin-right: 0.5rem; +} + +.checkboxListItem:last-child { + margin-right: 0; +} + +.searchContainer { + margin-left: auto; +} + +.searchContainer input { + height: 30px; + border-radius: 15px; + padding: 10px; + border: 1px solid gray; +} + +.showcaseList { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +.showcaseFavorite { + padding-top: 2rem; + padding-bottom: 2rem; + background-color: var(--site-color-favorite-background); +} + +.showcaseFavoriteHeader { + display: flex; + align-items: center; +} + +.showcaseFavoriteHeader > h2 { + margin-bottom: 0; +} + +.showcaseFavoriteHeader > svg { + width: 30px; + height: 30px; +} + +.svgIconFavoriteXs, +.svgIconFavorite { + color: var(--site-color-svg-icon-favorite); +} + +.svgIconFavoriteXs { + margin-left: 0.625rem; + font-size: 1rem; +} + +.svgIconFavorite { + margin-left: 1rem; +} diff --git a/website/src/showcase/authors.yaml b/website/src/showcase/authors.yaml deleted file mode 100644 index 8653c01201bc..000000000000 --- a/website/src/showcase/authors.yaml +++ /dev/null @@ -1,2 +0,0 @@ -author: ozakii -title: testee diff --git a/website/src/showcase/website/ozaki.yaml b/website/src/showcase/website/ozaki.yaml new file mode 100644 index 000000000000..77fa759f37a0 --- /dev/null +++ b/website/src/showcase/website/ozaki.yaml @@ -0,0 +1,2 @@ +author: ozaki +title: ozaki diff --git a/website/src/showcase/website/seb.yaml b/website/src/showcase/website/seb.yaml new file mode 100644 index 000000000000..b85f96883c43 --- /dev/null +++ b/website/src/showcase/website/seb.yaml @@ -0,0 +1,2 @@ +author: seb +title: seb From ec35998bde8ab028915e2571bdbb7fb71cde5b58 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Thu, 21 Mar 2024 17:47:52 +0000 Subject: [PATCH 06/79] refactor: apply lint autofix --- project-words.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/project-words.txt b/project-words.txt index fa323348f592..891d1c1ff48b 100644 --- a/project-words.txt +++ b/project-words.txt @@ -229,7 +229,6 @@ Outerbounds outerbounds overrideable ozaki -ozakii O’Shannessy pageview Palenight From 8d1b174917a86e83cd3de7f7363c9f5af0d2936d Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:02:40 +0100 Subject: [PATCH 07/79] wip --- .../docusaurus-plugin-showcase/src/index.ts | 46 ++++--- .../docusaurus-plugin-showcase/src/options.ts | 8 +- .../src/plugin-showcase.d.ts | 20 ++- .../docusaurus-theme-classic/package.json | 1 + .../src/theme-classic.d.ts | 12 +- .../Showcase/ShowcaseFilterToggle/index.tsx | 18 ++- .../Showcase/ShowcaseTagSelect/index.tsx | 18 ++- .../src/theme/Showcase/index.tsx | 118 +++++++----------- website/src/components/Showcase.js | 12 -- website/src/showcase/website/dino.yaml | 6 + website/src/showcase/website/ozaki.yaml | 2 - website/src/showcase/website/ozaki/ozaki.yaml | 6 + website/src/showcase/website/seb.yaml | 8 +- 13 files changed, 160 insertions(+), 115 deletions(-) delete mode 100644 website/src/components/Showcase.js create mode 100644 website/src/showcase/website/dino.yaml delete mode 100644 website/src/showcase/website/ozaki.yaml create mode 100644 website/src/showcase/website/ozaki/ozaki.yaml diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index c6819a23f6a2..f66a49d297dd 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -36,28 +36,33 @@ export default function pluginContentShowcase( // }, async loadContent(): Promise { - const files = await fs.readdir( - path.join(siteDir, options.path, 'website'), - ); + const files = await fs.readdir(path.join(siteDir, options.path)); const yamlFiles = files.filter((file) => file.endsWith('.yaml')); const contentPromises = yamlFiles.map(async (file) => { - const yaml = await fs.readFile( - path.join(siteDir, options.path, 'website', file), + const rawYaml = await fs.readFile( + path.join(siteDir, options.path, file), 'utf-8', ); - const authors = Yaml.load(yaml); - const parsedAuthors = contentAuthorsSchema.validate(authors); + const yaml = Yaml.load(rawYaml); + const parsedYaml = contentAuthorsSchema.validate(yaml); - if (parsedAuthors.error) { - throw new Error(`Validation failed: ${parsedAuthors.error.message}`, { - cause: parsedAuthors.error, + if (parsedYaml.error) { + throw new Error(`Validation failed: ${parsedYaml.error.message}`, { + cause: parsedYaml.error, }); } + const {title, description, preview, website, source, tags} = + parsedYaml.value; + return { - title: parsedAuthors.value.title, - author: parsedAuthors.value.author, // Assuming author is part of Content type + title, + description, + preview, + website, + source, + tags, }; }); @@ -71,7 +76,6 @@ export default function pluginContentShowcase( if (!content) { return; } - console.log('content:', content); const {addRoute, createData} = actions; @@ -83,7 +87,7 @@ export default function pluginContentShowcase( ); addRoute({ - path: `/${item.title}`, + path: `/showcaseAll/${item.title}`, component: '@theme/Showcase', modules: { content: dataAuthor, @@ -92,6 +96,20 @@ export default function pluginContentShowcase( }); }), ); + + const showcaseAllData = await createData( + 'showcaseAll.json', + JSON.stringify(content.website), + ); + + addRoute({ + path: '/showcaseAll', + component: '@theme/Showcase', + modules: { + content: showcaseAllData, + }, + exact: true, + }); }, }; } diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-showcase/src/options.ts index 032017c1574e..87588d50a7b2 100644 --- a/packages/docusaurus-plugin-showcase/src/options.ts +++ b/packages/docusaurus-plugin-showcase/src/options.ts @@ -11,7 +11,7 @@ import type {PluginOptions, Options} from '@docusaurus/plugin-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', - path: 'src/showcase', // Path to data on filesystem, relative to site dir. + path: 'src/showcase/website', // Path to data on filesystem, relative to site dir. routeBasePath: '/', // URL Route. }; @@ -21,8 +21,12 @@ const PluginOptionSchema = Joi.object({ }); export const contentAuthorsSchema = Joi.object({ - author: Joi.string().required(), title: Joi.string().required(), + description: Joi.string().required(), + preview: Joi.string().required(), + website: Joi.string().required(), + source: Joi.string().required(), + tags: Joi.array().items(Joi.string()).required(), }); export function validateOptions({ diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts index 7080908fba9b..f71d6c9f06af 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts @@ -18,10 +18,26 @@ declare module '@docusaurus/plugin-showcase' { routeBasePath: string; }; + export type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; + export type Content = { website: { - author: string; title: string; + description: string; + preview: string | null; // null = use our serverless screenshot service + website: string; + source: string | null; + tags: TagType[]; }[]; }; @@ -30,5 +46,5 @@ declare module '@docusaurus/plugin-showcase' { export default function pluginShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; + ): Promise>; } diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 79f0082125f7..05d8caf87382 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -26,6 +26,7 @@ "@docusaurus/plugin-content-blog": "3.0.0", "@docusaurus/plugin-content-docs": "3.0.0", "@docusaurus/plugin-content-pages": "3.0.0", + "@docusaurus/plugin-showcase": "3.0.0", "@docusaurus/theme-common": "3.0.0", "@docusaurus/theme-translations": "3.0.0", "@docusaurus/types": "3.0.0", diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 8c1af1e55b76..446970b74f86 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -248,14 +248,16 @@ declare module '@theme/BlogPostItems' { } declare module '@theme/Showcase' { - export interface Props { - content: { - [key: string]: string; - }; - } + import type {Content} from '@docusaurus/plugin-showcase'; export function prepareUserState(): UserState | undefined; + export type User = Content['website'][number]; + + export type Props = { + content: User[]; + }; + export default function Showcase(props: Props): JSX.Element; } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx index 55c1cc3d527c..d65e0a41d287 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx @@ -9,12 +9,28 @@ import React, {useState, useEffect, useCallback} from 'react'; import clsx from 'clsx'; import {useHistory, useLocation} from '@docusaurus/router'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; import {OperatorQueryKey} from '@theme/Showcase/ShowcaseFilterToggle'; -import {prepareUserState} from '@theme/Showcase'; import styles from './styles.module.css'; +type UserState = { + scrollTopPosition: number; + focusedElementId: string | undefined; +}; + +function prepareUserState(): UserState | undefined { + if (ExecutionEnvironment.canUseDOM) { + return { + scrollTopPosition: window.scrollY, + focusedElementId: document.activeElement?.id, + }; + } + + return undefined; +} + function readOperator(search: string): Operator { return (new URLSearchParams(search).get(OperatorQueryKey) ?? 'OR') as Operator; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx index aa73e9c537f3..3219a051b789 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx @@ -15,9 +15,25 @@ import React, { } from 'react'; import {useHistory, useLocation} from '@docusaurus/router'; -import {prepareUserState} from '@theme/Showcase'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import styles from './styles.module.css'; +type UserState = { + scrollTopPosition: number; + focusedElementId: string | undefined; +}; + +function prepareUserState(): UserState | undefined { + if (ExecutionEnvironment.canUseDOM) { + return { + scrollTopPosition: window.scrollY, + focusedElementId: document.activeElement?.id, + }; + } + + return undefined; +} + function toggleListItem(list: T[], item: T): T[] { const itemIndex = list.indexOf(item); if (itemIndex === -1) { diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index d4fc93f365ab..61bd474edc48 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -12,27 +12,32 @@ import {useHistory, useLocation} from 'react-router-dom'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import Translate, {translate} from '@docusaurus/Translate'; import {usePluralForm} from '@docusaurus/theme-common'; -import type {Props} from '@theme/Showcase'; +import type {User, Props} from '@theme/Showcase'; import Layout from '@theme/Layout'; import Heading from '@theme/Heading'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import ShowcaseCard from '@theme/Showcase/ShowcaseCard'; import ShowcaseTooltip from '@theme/Showcase/ShowcaseTooltip'; -import ShowcaseTagSelect, { - readSearchTags, -} from '@theme/Showcase/ShowcaseTagSelect'; -import ShowcaseFilterToggle, { - readOperator, -} from '@theme/Showcase/ShowcaseFilterToggle'; +import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect'; +import ShowcaseFilterToggle from '@theme/Showcase/ShowcaseFilterToggle'; import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; +import type {TagType} from '@docusaurus/plugin-showcase'; import styles from './styles.module.css'; +type Users = User[]; + const TITLE = translate({message: 'Docusaurus Site Showcase'}); const DESCRIPTION = translate({ message: 'List of websites people are building with Docusaurus', }); const SUBMIT_URL = 'https://github.com/facebook/docusaurus/discussions/7826'; +const OperatorQueryKey = 'operator'; + +function readOperator(search: string): Operator { + return (new URLSearchParams(search).get(OperatorQueryKey) ?? + 'OR') as Operator; +} type UserState = { scrollTopPosition: number; focusedElementId: string | undefined; @@ -145,17 +150,6 @@ const Tags: {[type in TagType]: Tag} = { const TagList = Object.keys(Tags) as TagType[]; -const Users: User[] = [ - { - title: 'AgileTs', - description: 'Global State and Logic Framework for reactive Applications', - preview: 'https://github.com/ozakione.png', - website: 'https://agile-ts.org/', - source: 'https://github.com/agile-ts/documentation', - tags: ['opensource', 'design'], - }, -]; - function sortBy( array: T[], getter: (item: T) => string | number | boolean, @@ -168,17 +162,14 @@ function sortBy( return sortedArray; } -function sortUsers() { - let result = Users; +function sortUsers(users: Users): Users { // Sort by site name - result = sortBy(result, (user) => user.title.toLowerCase()); + let result = sortBy(users, (user) => user.title.toLowerCase()); // Sort by favorite tag, favorites first - result = sortBy(result, (user) => !user.tags.includes('favorite')); + result = sortBy(result, (user) => (user.tags.includes('favorite') ? -1 : 1)); return result; } -const sortedUsers = sortUsers(); - function ShowcaseHeader() { return (
    @@ -213,35 +204,9 @@ function restoreUserState(userState: UserState | null) { document.getElementById(focusedElementId)?.focus(); window.scrollTo({top: scrollTopPosition}); } -type TagType = - | 'favorite' - | 'opensource' - | 'product' - | 'design' - | 'i18n' - | 'versioning' - | 'large' - | 'meta' - | 'personal' - | 'rtl'; - -type User = { - title: string; - description: string; - preview: string | null; // null = use our serverless screenshot service - website: string; - source: string | null; - tags: TagType[]; -}; - -// type Tag = { -// label: string; -// description: string; -// color: string; -// }; function filterUsers( - users: User[], + users: Users, selectedTags: TagType[], operator: Operator, searchName: string | null, @@ -266,7 +231,19 @@ function filterUsers( }); } -function useFilteredUsers() { +const SearchNameQueryKey = 'name'; + +function readSearchName(search: string) { + return new URLSearchParams(search).get(SearchNameQueryKey); +} + +const TagQueryStringKey = 'tags'; + +function readSearchTags(search: string): TagType[] { + return new URLSearchParams(search).getAll(TagQueryStringKey) as TagType[]; +} + +function useFilteredUsers(users: Users) { const location = useLocation(); const [operator, setOperator] = useState('OR'); // On SSR / first mount (hydration) no tag is selected @@ -282,8 +259,8 @@ function useFilteredUsers() { }, [location]); return useMemo( - () => filterUsers(sortedUsers, selectedTags, operator, searchName), - [selectedTags, operator, searchName], + () => filterUsers(sortUsers(users), selectedTags, operator, searchName), + [selectedTags, operator, searchName, users], ); } @@ -304,8 +281,8 @@ function useSiteCountPlural() { ); } -function ShowcaseFilters() { - const filteredUsers = useFilteredUsers(); +function ShowcaseFilters({users}: {users: Users}) { + const filteredUsers = useFilteredUsers(users); const siteCountPlural = useSiteCountPlural(); return (
    @@ -358,12 +335,6 @@ function ShowcaseFilters() { ); } -const SearchNameQueryKey = 'name'; - -function readSearchName(search: string) { - return new URLSearchParams(search).get(SearchNameQueryKey); -} - function SearchBar() { const history = useHistory(); const location = useLocation(); @@ -401,15 +372,8 @@ function SearchBar() { ); } -const favoriteUsers = sortedUsers.filter((user) => - user.tags.includes('favorite'), -); -const otherUsers = sortedUsers.filter( - (user) => !user.tags.includes('favorite'), -); - -function ShowcaseCards() { - const filteredUsers = useFilteredUsers(); +function ShowcaseCards({users}: {users: Users}) { + const filteredUsers = useFilteredUsers(users); if (filteredUsers.length === 0) { return ( @@ -423,9 +387,12 @@ function ShowcaseCards() { ); } + const favoriteUsers = users.filter((user) => user.tags.includes('favorite')); + const otherUsers = users.filter((user) => !user.tags.includes('favorite')); + return (
    - {filteredUsers.length === sortedUsers.length ? ( + {filteredUsers.length === sortUsers(users).length ? ( <>
    @@ -481,18 +448,21 @@ function ShowcaseCards() { } export default function Showcase(props: Props): JSX.Element { + // TODO remove temporary to test showcase specific page + const users = Array.isArray(props.content) ? props.content : [props.content]; + return (
    {JSON.stringify(props)}
    - +
    - +
    ); diff --git a/website/src/components/Showcase.js b/website/src/components/Showcase.js deleted file mode 100644 index 1db561287925..000000000000 --- a/website/src/components/Showcase.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * 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. - */ -/* eslint-disable @docusaurus/no-untranslated-text */ -import React from 'react'; - -export default function ShowcaseComponent(props) { - return
    Your friends are : {props.content.author}
    ; -} diff --git a/website/src/showcase/website/dino.yaml b/website/src/showcase/website/dino.yaml new file mode 100644 index 000000000000..a0cf44b03b76 --- /dev/null +++ b/website/src/showcase/website/dino.yaml @@ -0,0 +1,6 @@ +title: 'Dinosaur' +description: 'Docusaurus dino' +preview: require('./showcase/agilets.png') +website: 'https://agile-ts.org/' +source: 'https://github.com/agile-ts/documentation' +tags: ['opensource', 'design'] diff --git a/website/src/showcase/website/ozaki.yaml b/website/src/showcase/website/ozaki.yaml deleted file mode 100644 index 77fa759f37a0..000000000000 --- a/website/src/showcase/website/ozaki.yaml +++ /dev/null @@ -1,2 +0,0 @@ -author: ozaki -title: ozaki diff --git a/website/src/showcase/website/ozaki/ozaki.yaml b/website/src/showcase/website/ozaki/ozaki.yaml new file mode 100644 index 000000000000..c7f70ca01b71 --- /dev/null +++ b/website/src/showcase/website/ozaki/ozaki.yaml @@ -0,0 +1,6 @@ +title: 'Ozaki' +description: 'Ozaki website' +preview: require('./showcase/agilets.png') +website: 'https://agile-ts.org/' +source: 'https://github.com/agile-ts/documentation' +tags: ['opensource', 'design'] diff --git a/website/src/showcase/website/seb.yaml b/website/src/showcase/website/seb.yaml index b85f96883c43..6ac2b6be3405 100644 --- a/website/src/showcase/website/seb.yaml +++ b/website/src/showcase/website/seb.yaml @@ -1,2 +1,6 @@ -author: seb -title: seb +title: 'Seb' +description: "Docusaurus maintainer's personal website" +preview: require('./showcase/agilets.png') +website: 'https://agile-ts.org/' +source: 'https://github.com/agile-ts/documentation' +tags: ['opensource', 'design'] From 7daa9a1d04270f1d859588b28ea68aeb06eb5677 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Sun, 24 Mar 2024 21:07:25 +0000 Subject: [PATCH 08/79] refactor: apply lint autofix --- project-words.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project-words.txt b/project-words.txt index 891d1c1ff48b..c8e51ba73c3b 100644 --- a/project-words.txt +++ b/project-words.txt @@ -2,6 +2,7 @@ abernathyca Adriaan Agan +agilets alexbdebrie Alexey algoliasearch @@ -228,6 +229,7 @@ orta Outerbounds outerbounds overrideable +Ozaki ozaki O’Shannessy pageview From c542c81cb8c2171abbd9e5cef191babd8208f3b5 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:06:16 +0100 Subject: [PATCH 09/79] ShowcaseDetails component --- .../docusaurus-plugin-showcase/src/index.ts | 69 +++++++++++-------- .../src/theme-classic.d.ts | 18 +++-- .../src/theme/Showcase/index.tsx | 3 +- .../src/theme/ShowcaseDetails/index.tsx | 19 +++++ 4 files changed, 71 insertions(+), 38 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index f66a49d297dd..11cb87a426ab 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -16,6 +16,17 @@ import {contentAuthorsSchema} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; +// 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 default function pluginContentShowcase( context: LoadContext, options: PluginOptions, @@ -36,35 +47,33 @@ export default function pluginContentShowcase( // }, async loadContent(): Promise { - const files = await fs.readdir(path.join(siteDir, options.path)); - const yamlFiles = files.filter((file) => file.endsWith('.yaml')); - - const contentPromises = yamlFiles.map(async (file) => { - const rawYaml = await fs.readFile( - path.join(siteDir, options.path, file), - 'utf-8', - ); - const yaml = Yaml.load(rawYaml); - const parsedYaml = contentAuthorsSchema.validate(yaml); - - if (parsedYaml.error) { - throw new Error(`Validation failed: ${parsedYaml.error.message}`, { - cause: parsedYaml.error, - }); - } - - const {title, description, preview, website, source, tags} = - parsedYaml.value; - - return { - title, - description, - preview, - website, - source, - tags, - }; - }); + const files: string[] = await walk(path.join(siteDir, options.path)); + console.log('allFiles:', files.flat(Number.POSITIVE_INFINITY)); + const contentPromises = files + .flat(Number.POSITIVE_INFINITY) + .map(async (file) => { + const rawYaml = await fs.readFile(path.join(file), 'utf-8'); + const yaml = Yaml.load(rawYaml); + const parsedYaml = contentAuthorsSchema.validate(yaml); + + if (parsedYaml.error) { + throw new Error(`Validation failed: ${parsedYaml.error.message}`, { + cause: parsedYaml.error, + }); + } + + const {title, description, preview, website, source, tags} = + parsedYaml.value; + + return { + title, + description, + preview, + website, + source, + tags, + }; + }); const content = await Promise.all(contentPromises); return { @@ -88,7 +97,7 @@ export default function pluginContentShowcase( addRoute({ path: `/showcaseAll/${item.title}`, - component: '@theme/Showcase', + component: '@theme/ShowcaseDetails', modules: { content: dataAuthor, }, diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 446970b74f86..3ef6fbe45d99 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -247,10 +247,20 @@ declare module '@theme/BlogPostItems' { export default function BlogPostItem(props: Props): JSX.Element; } -declare module '@theme/Showcase' { +declare module '@theme/ShowcaseDetails' { import type {Content} from '@docusaurus/plugin-showcase'; - export function prepareUserState(): UserState | undefined; + export type User = Content['website'][number]; + + export type Props = { + content: User; + }; + + export default function Showcase(props: Props): JSX.Element; +} + +declare module '@theme/Showcase' { + import type {Content} from '@docusaurus/plugin-showcase'; export type User = Content['website'][number]; @@ -296,16 +306,12 @@ declare module '@theme/Showcase/ShowcaseTagSelect' { tag: TagType; } - export function readSearchTags(search: string): TagType[]; - export default function ShowcaseTagSelect(props: Props): JSX.Element; } declare module '@theme/Showcase/ShowcaseFilterToggle' { export type Operator = 'OR' | 'AND'; export const OperatorQueryKey = 'operator'; - export function readOperator(search: string): Operator; - export default function ShowcaseFilterToggle(): JSX.Element; } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index 61bd474edc48..0acd3df1a19f 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -448,8 +448,7 @@ function ShowcaseCards({users}: {users: Users}) { } export default function Showcase(props: Props): JSX.Element { - // TODO remove temporary to test showcase specific page - const users = Array.isArray(props.content) ? props.content : [props.content]; + const users = props.content; return ( diff --git a/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx b/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx new file mode 100644 index 000000000000..99605ad027a6 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx @@ -0,0 +1,19 @@ +/** + * 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 {Props} from '@theme/ShowcaseDetails'; +import Layout from '@theme/Layout'; + +export default function Showcase(props: Props): JSX.Element { + const user = props.content; + + return ( + +
    {user.title}
    +
    + ); +} From dddb8cfc7870e49cc3074307b361c177c028b46e Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Sun, 24 Mar 2024 23:07:36 +0100 Subject: [PATCH 10/79] cspell autolint fix --- website/src/showcase/website/dino.yaml | 2 +- website/src/showcase/website/ozaki/ozaki.yaml | 2 +- website/src/showcase/website/seb.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/showcase/website/dino.yaml b/website/src/showcase/website/dino.yaml index a0cf44b03b76..a79e90954283 100644 --- a/website/src/showcase/website/dino.yaml +++ b/website/src/showcase/website/dino.yaml @@ -1,6 +1,6 @@ title: 'Dinosaur' description: 'Docusaurus dino' -preview: require('./showcase/agilets.png') +preview: require('./showcase/test.png') website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' tags: ['opensource', 'design'] diff --git a/website/src/showcase/website/ozaki/ozaki.yaml b/website/src/showcase/website/ozaki/ozaki.yaml index c7f70ca01b71..d3190820d28d 100644 --- a/website/src/showcase/website/ozaki/ozaki.yaml +++ b/website/src/showcase/website/ozaki/ozaki.yaml @@ -1,6 +1,6 @@ title: 'Ozaki' description: 'Ozaki website' -preview: require('./showcase/agilets.png') +preview: require('./showcase/test.png') website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' tags: ['opensource', 'design'] diff --git a/website/src/showcase/website/seb.yaml b/website/src/showcase/website/seb.yaml index 6ac2b6be3405..66a2ae7737b8 100644 --- a/website/src/showcase/website/seb.yaml +++ b/website/src/showcase/website/seb.yaml @@ -1,6 +1,6 @@ title: 'Seb' description: "Docusaurus maintainer's personal website" -preview: require('./showcase/agilets.png') +preview: require('./showcase/test.png') website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' tags: ['opensource', 'design'] From 1c825e02d2c7c41fef3e8164823ad8b855c5923a Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Sun, 24 Mar 2024 22:12:39 +0000 Subject: [PATCH 11/79] refactor: apply lint autofix --- project-words.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/project-words.txt b/project-words.txt index c8e51ba73c3b..e15f7fd2ee34 100644 --- a/project-words.txt +++ b/project-words.txt @@ -2,7 +2,6 @@ abernathyca Adriaan Agan -agilets alexbdebrie Alexey algoliasearch From 9790934ec40c72ce28927a389523feb7ba1ae21f Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:30:00 +0100 Subject: [PATCH 12/79] wip markdown process: crash --- .../docusaurus-plugin-showcase/src/index.ts | 130 ++++++++++++++---- .../src/plugin-showcase.d.ts | 19 +++ website/src/showcase/website/clem/clem.mdx | 10 ++ 3 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 website/src/showcase/website/clem/clem.mdx diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index 11cb87a426ab..08315920c1fc 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -9,12 +9,21 @@ import fs from 'fs-extra'; import path from 'path'; -import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; +import { + DEFAULT_PLUGIN_ID, + addTrailingPathSeparator, + aliasedSitePath, + docuHash, +} from '@docusaurus/utils'; import Yaml from 'js-yaml'; import {contentAuthorsSchema} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; -import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; +import type { + PluginOptions, + Content, + ShowcaseMetadata, +} from '@docusaurus/plugin-showcase'; // https://stackoverflow.com/a/71166133 const walk = async (dirPath: string): Promise => @@ -39,6 +48,8 @@ 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', @@ -48,32 +59,33 @@ export default function pluginContentShowcase( async loadContent(): Promise { const files: string[] = await walk(path.join(siteDir, options.path)); - console.log('allFiles:', files.flat(Number.POSITIVE_INFINITY)); - const contentPromises = files + const filteredFiles = files .flat(Number.POSITIVE_INFINITY) - .map(async (file) => { - const rawYaml = await fs.readFile(path.join(file), 'utf-8'); - const yaml = Yaml.load(rawYaml); - const parsedYaml = contentAuthorsSchema.validate(yaml); - - if (parsedYaml.error) { - throw new Error(`Validation failed: ${parsedYaml.error.message}`, { - cause: parsedYaml.error, - }); - } - - const {title, description, preview, website, source, tags} = - parsedYaml.value; - - return { - title, - description, - preview, - website, - source, - tags, - }; - }); + .filter((file) => file.endsWith('.yaml')); + + 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 (parsedYaml.error) { + throw new Error(`Validation failed: ${parsedYaml.error.message}`, { + cause: parsedYaml.error, + }); + } + + const {title, description, preview, website, source, tags} = + parsedYaml.value; + + return { + title, + description, + preview, + website, + source, + tags, + }; + }); const content = await Promise.all(contentPromises); return { @@ -91,7 +103,7 @@ export default function pluginContentShowcase( await Promise.all( content.website.map(async (item) => { const dataAuthor = await createData( - `${item.title}.json`, + `${docuHash(item.title)}.json`, JSON.stringify(item), ); @@ -120,6 +132,68 @@ export default function pluginContentShowcase( exact: true, }); }, + + configureWebpack(_config, isServer, utils, content) { + return { + resolve: { + alias: { + '~blog': pluginDataDirRoot, + }, + }, + module: { + rules: [ + { + test: /\.mdx?$/i, + include: [...showcasePath] + // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 + .map(addTrailingPathSeparator), + use: [ + { + loader: require.resolve('@docusaurus/mdx-loader'), + options: { + staticDirs: siteConfig.staticDirectories.map((dir) => + path.resolve(siteDir, dir), + ), + siteDir, + metadataPath: (mdxPath: string) => { + // Note that metadataPath must be the same/in-sync as + // the path from createData for each MDX. + const aliasedPath = aliasedSitePath(mdxPath, siteDir); + return path.join( + dataDir, + `${docuHash(aliasedPath)}.json`, + ); + }, + // 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, + // ), + // }), + markdownConfig: siteConfig.markdown, + }, + }, + { + loader: path.resolve(__dirname, './markdownLoader.js'), + }, + ].filter(Boolean), + }, + ], + }, + }; + }, }; } diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts index f71d6c9f06af..eeb37b1f5386 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts @@ -47,4 +47,23 @@ declare module '@docusaurus/plugin-showcase' { context: LoadContext, options: PluginOptions, ): Promise>; + + export type ShowcaseMetadata = { + /** Path to the Markdown source, with `@site` alias. */ + readonly source: string; + /** + * Used to generate the page h1 heading, tab title, and pagination title. + */ + readonly title: string; + /** Full link including base URL. */ + readonly permalink: string; + /** + * Description used in the meta. Could be an empty string (empty content) + */ + readonly description: string; + /** Front matter, as-is. */ + readonly frontMatter: Content['website'][number] & {[key: string]: unknown}; + /** Tags, normalized. */ + readonly tags: TagType[]; + }; } diff --git a/website/src/showcase/website/clem/clem.mdx b/website/src/showcase/website/clem/clem.mdx new file mode 100644 index 000000000000..985f84bb4c5e --- /dev/null +++ b/website/src/showcase/website/clem/clem.mdx @@ -0,0 +1,10 @@ +--- +title: Clement; +description: Description from frontmatter +preview: https://github.com/ozakione.png +website: https://github.com/ozakione +source: source +tags: ['favorite', 'opensource'] +--- + +# Hello From 49b67463bdc11678790d4906d2447dbee9454b62 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Mon, 25 Mar 2024 11:34:27 +0000 Subject: [PATCH 13/79] refactor: apply lint autofix --- project-words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/project-words.txt b/project-words.txt index e15f7fd2ee34..067bb1119750 100644 --- a/project-words.txt +++ b/project-words.txt @@ -105,6 +105,7 @@ Flightcontrol's Formik FOUC froms +frontmatter funboxteam gabrielcsapo Gifs From 6e40a79b6f2f2d1f8286547ca990f47c726bd160 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:13:35 +0100 Subject: [PATCH 14/79] wip use plugin page code logic --- .../docusaurus-plugin-showcase/src/index.ts | 146 +++++++++++------- .../src/markdownLoader.ts | 22 +++ .../docusaurus-plugin-showcase/src/options.ts | 6 + .../src/plugin-showcase.d.ts | 2 + .../docusaurus-plugin-showcase/src/types.ts | 10 ++ 5 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 packages/docusaurus-plugin-showcase/src/markdownLoader.ts create mode 100644 packages/docusaurus-plugin-showcase/src/types.ts diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index 08315920c1fc..9cb0278b17b3 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'; - -// 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; - }), - ), - ); +import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; +import type {ShowcaseContentPaths} from './types'; + +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 000000000000..e5c91b7bf797 --- /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 87588d50a7b2..ea1850444723 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 eeb37b1f5386..78ed7644d6d6 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 000000000000..6ec0b452815f --- /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; +}; From 984518efafb45cda674a919448aed8f11bc343c8 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:20:09 +0100 Subject: [PATCH 15/79] fix: warning --- packages/docusaurus-theme-classic/src/theme-classic.d.ts | 1 - .../src/theme/Showcase/ShowcaseFilterToggle/index.tsx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 3ef6fbe45d99..1d891565e561 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -310,7 +310,6 @@ declare module '@theme/Showcase/ShowcaseTagSelect' { } declare module '@theme/Showcase/ShowcaseFilterToggle' { export type Operator = 'OR' | 'AND'; - export const OperatorQueryKey = 'operator'; export default function ShowcaseFilterToggle(): JSX.Element; } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx index d65e0a41d287..ff6616c754f7 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx @@ -11,10 +11,11 @@ import {useHistory, useLocation} from '@docusaurus/router'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; -import {OperatorQueryKey} from '@theme/Showcase/ShowcaseFilterToggle'; import styles from './styles.module.css'; +const OperatorQueryKey = 'operator'; + type UserState = { scrollTopPosition: number; focusedElementId: string | undefined; From 993efd235c9ea03136584d064a5bb3aad81ca416 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:03:46 +0100 Subject: [PATCH 16/79] wip process markdown --- .../docusaurus-plugin-showcase/src/index.ts | 92 +++++++++++-------- .../docusaurus-plugin-showcase/src/options.ts | 20 +++- .../src/plugin-showcase.d.ts | 11 +++ .../src/theme/ShowcaseDetails/index.tsx | 4 +- website/src/showcase/website/clem/clem.mdx | 8 +- 5 files changed, 92 insertions(+), 43 deletions(-) diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index 9cb0278b17b3..ba071a5736ac 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -17,10 +17,13 @@ import { getFolderContainingFile, getPluginI18nPath, Globby, + parseMarkdownFile, + aliasedSitePathToRelativePath, + createAbsoluteFilePathMatcher, } from '@docusaurus/utils'; import Yaml from 'js-yaml'; -import {contentAuthorsSchema} from './options'; +import {validateShowcaseFrontMatter} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; import type {ShowcaseContentPaths} from './types'; @@ -31,6 +34,9 @@ export function getContentPathList( return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } +const isMarkdownSource = (source: string) => + source.endsWith('.md') || source.endsWith('.mdx'); + export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, @@ -55,12 +61,13 @@ export default function pluginContentShowcase( return { name: 'docusaurus-plugin-showcase', - getPathsToWatch() { - const {include} = options; - return getContentPathList(contentPaths).flatMap((contentPath) => - include.map((pattern) => `${contentPath}/${pattern}`), - ); - }, + // todo doesn't work + // getPathsToWatch() { + // const {include} = options; + // return getContentPathList(contentPaths).flatMap((contentPath) => + // include.map((pattern) => `${contentPath}/${pattern}`), + // ); + // }, async loadContent() { const {include} = options; @@ -75,42 +82,44 @@ export default function pluginContentShowcase( ignore: options.exclude, }); - const filteredFiles = showcaseFiles.filter((file) => - file.endsWith('.yaml'), - ); - - async function processPageSourceFile(relativeSource: string) { + async function processShowcaseSourceFile(relativeSource: string) { // Lookup in localized folder in priority const contentPath = await getFolderContainingFile( getContentPathList(contentPaths), relativeSource, ); + console.log('contentPath:', contentPath); 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, - }); + if (!isMarkdownSource(sourcePath)) { + const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; + const yaml = validateShowcaseFrontMatter(unsafeYaml); + return { + type: 'yaml', + ...yaml, + }; } - - const {title, description, preview, website, source, tags} = yaml.value; + const rawMarkdown = await fs.readFile(sourcePath, 'utf-8'); + const {frontMatter: unsafeFrontMatter, content} = + await parseMarkdownFile({ + filePath: sourcePath, + fileContent: rawMarkdown, + parseFrontMatter: siteConfig.markdown?.parseFrontMatter, + }); + const frontMatter = validateShowcaseFrontMatter(unsafeFrontMatter); return { - title, - description, - preview, - website, - source, - tags, + type: 'markdown', + ...frontMatter, + content, + sourcePath: aliasedSourcePath, }; } - async function doProcessPageSourceFile(relativeSource: string) { + async function doProcessShowcaseSourceFile(relativeSource: string) { try { - return await processPageSourceFile(relativeSource); + return await processShowcaseSourceFile(relativeSource); } catch (err) { throw new Error( `Processing of page source file path=${relativeSource} failed.`, @@ -120,7 +129,9 @@ export default function pluginContentShowcase( } return { - website: await Promise.all(filteredFiles.map(doProcessPageSourceFile)), + website: await Promise.all( + showcaseFiles.map(doProcessShowcaseSourceFile), + ), }; }, @@ -133,16 +144,23 @@ export default function pluginContentShowcase( await Promise.all( content.website.map(async (item) => { - const dataAuthor = await createData( - `${docuHash(item.title)}.json`, + if (item.type === 'yaml') { + return; + } + await createData( + `${docuHash(item.sourcePath!)}.json`, JSON.stringify(item), ); addRoute({ path: `/showcaseAll/${item.title}`, component: '@theme/ShowcaseDetails', + metadata: { + sourceFilePath: aliasedSitePathToRelativePath(item.sourcePath!), + lastUpdatedAt: undefined, + }, modules: { - content: dataAuthor, + content: item.sourcePath!, }, exact: true, }); @@ -188,10 +206,10 @@ export default function pluginContentShowcase( path.resolve(siteDir, dir), ), siteDir, - // isMDXPartial: createAbsoluteFilePathMatcher( - // options.exclude, - // contentDirs, - // ), + 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. diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-showcase/src/options.ts index ea1850444723..23bbfc4673c2 100644 --- a/packages/docusaurus-plugin-showcase/src/options.ts +++ b/packages/docusaurus-plugin-showcase/src/options.ts @@ -5,10 +5,18 @@ * LICENSE file in the root directory of this source tree. */ -import {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation'; +import { + Joi, + validateFrontMatter, + RouteBasePathSchema, +} from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; import type {OptionValidationContext} from '@docusaurus/types'; -import type {PluginOptions, Options} from '@docusaurus/plugin-showcase'; +import type { + PluginOptions, + Options, + ShowcaseFrontMatter, +} from '@docusaurus/plugin-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', @@ -26,7 +34,7 @@ const PluginOptionSchema = Joi.object({ id: Joi.string().default(DEFAULT_OPTIONS.id), }); -export const contentAuthorsSchema = Joi.object({ +const contentAuthorsSchema = Joi.object({ title: Joi.string().required(), description: Joi.string().required(), preview: Joi.string().required(), @@ -42,3 +50,9 @@ export function validateOptions({ const validatedOptions = validate(PluginOptionSchema, options); return validatedOptions; } + +export function validateShowcaseFrontMatter(frontMatter: { + [key: string]: unknown; +}): ShowcaseFrontMatter { + return validateFrontMatter(frontMatter, contentAuthorsSchema); +} diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts index 78ed7644d6d6..d332e799ac3f 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts @@ -32,13 +32,24 @@ declare module '@docusaurus/plugin-showcase' { | 'personal' | 'rtl'; + export type ShowcaseFrontMatter = { + readonly title: string; + readonly description: string; + readonly preview: string | null; // null = use our serverless screenshot service + readonly website: string; + readonly source: string | null; + readonly tags: TagType[]; + }; + export type Content = { website: { + type: string; title: string; description: string; preview: string | null; // null = use our serverless screenshot service website: string; source: string | null; + sourcePath?: string; tags: TagType[]; }[]; }; diff --git a/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx b/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx index 99605ad027a6..f1e7e7497b4a 100644 --- a/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx @@ -9,11 +9,11 @@ import type {Props} from '@theme/ShowcaseDetails'; import Layout from '@theme/Layout'; export default function Showcase(props: Props): JSX.Element { - const user = props.content; + const user = props; return ( -
    {user.title}
    +
    {JSON.stringify(user)}
    ); } diff --git a/website/src/showcase/website/clem/clem.mdx b/website/src/showcase/website/clem/clem.mdx index 985f84bb4c5e..f1969dda067f 100644 --- a/website/src/showcase/website/clem/clem.mdx +++ b/website/src/showcase/website/clem/clem.mdx @@ -4,7 +4,13 @@ description: Description from frontmatter preview: https://github.com/ozakione.png website: https://github.com/ozakione source: source -tags: ['favorite', 'opensource'] +tags: + - favorite + - opensource --- # Hello + +- some test + +text From 8bee535ffa7a74120551023548418a0e7902deac Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 27 Mar 2024 20:01:22 +0100 Subject: [PATCH 17/79] wip images --- .../docusaurus-plugin-showcase/src/index.ts | 29 ++++++++++++++----- .../src/theme/Showcase/ShowcaseCard/index.tsx | 21 +++++++------- .../src/theme/ShowcaseDetails/index.tsx | 6 ++-- website/src/showcase/website/dino.yaml | 4 +-- website/src/showcase/website/ozaki/ozaki.yaml | 2 +- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index ba071a5736ac..4a8fb29bcd7c 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint-disable @typescript-eslint/no-unused-vars */ + import fs from 'fs-extra'; import path from 'path'; @@ -24,7 +24,7 @@ import { import Yaml from 'js-yaml'; import {validateShowcaseFrontMatter} from './options'; -import type {LoadContext, Plugin} from '@docusaurus/types'; +import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types'; import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; import type {ShowcaseContentPaths} from './types'; @@ -88,7 +88,6 @@ export default function pluginContentShowcase( getContentPathList(contentPaths), relativeSource, ); - console.log('contentPath:', contentPath); const sourcePath = path.join(contentPath, relativeSource); const aliasedSourcePath = aliasedSitePath(sourcePath, siteDir); @@ -142,6 +141,18 @@ export default function pluginContentShowcase( const {addRoute, createData} = actions; + function createPageRouteMetadata( + metadata: Content['website'][number], + ): RouteMetadata { + return { + sourceFilePath: aliasedSitePathToRelativePath(metadata.sourcePath!), + // TODO add support for last updated date in the page plugin + // at least for Markdown files + // lastUpdatedAt: metadata.lastUpdatedAt, + lastUpdatedAt: undefined, + }; + } + await Promise.all( content.website.map(async (item) => { if (item.type === 'yaml') { @@ -152,13 +163,15 @@ export default function pluginContentShowcase( JSON.stringify(item), ); + const routeMetadata = createPageRouteMetadata(item); + + const mdxPath = aliasedSitePathToRelativePath(item.sourcePath!); + console.log('mdxPath', mdxPath); + addRoute({ path: `/showcaseAll/${item.title}`, component: '@theme/ShowcaseDetails', - metadata: { - sourceFilePath: aliasedSitePathToRelativePath(item.sourcePath!), - lastUpdatedAt: undefined, - }, + metadata: routeMetadata, modules: { content: item.sourcePath!, }, @@ -182,7 +195,7 @@ export default function pluginContentShowcase( }); }, - configureWebpack(_config, isServer, utils, content) { + configureWebpack() { const contentDirs = getContentPathList(contentPaths); return { diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx index 05ec6298970c..52a3e49cd899 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx @@ -9,7 +9,6 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; import Translate, {translate} from '@docusaurus/Translate'; -// import Image from '@theme/IdealImage'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import Heading from '@theme/Heading'; import Tooltip from '@theme/Showcase/ShowcaseTooltip'; @@ -191,21 +190,21 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) { ); } -// function getCardImage(user: User): string { -// return ( -// user.preview ?? -// `https://slorber-api-screenshot.netlify.app/${encodeURIComponent( -// user.website, -// )}/showcase` -// ); -// } +function getCardImage(user: User): string { + return ( + user.preview ?? + `https://slorber-api-screenshot.netlify.app/${encodeURIComponent( + user.website, + )}/showcase` + ); +} function ShowcaseCard({user}: {user: User}) { - // const image = getCardImage(user); + const image = getCardImage(user); return (
  • - {/* {user.title} */} + {user.title}
    diff --git a/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx b/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx index f1e7e7497b4a..130164fd030b 100644 --- a/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx @@ -9,11 +9,13 @@ import type {Props} from '@theme/ShowcaseDetails'; import Layout from '@theme/Layout'; export default function Showcase(props: Props): JSX.Element { - const user = props; + const {content: MDXPageContent} = props; + const {title, description} = MDXPageContent; return ( -
    {JSON.stringify(user)}
    +
    Title {JSON.stringify(title)}
    +
    Description {JSON.stringify(description)}
    ); } diff --git a/website/src/showcase/website/dino.yaml b/website/src/showcase/website/dino.yaml index a79e90954283..2fa47aaa4348 100644 --- a/website/src/showcase/website/dino.yaml +++ b/website/src/showcase/website/dino.yaml @@ -1,6 +1,6 @@ title: 'Dinosaur' -description: 'Docusaurus dino' -preview: require('./showcase/test.png') +description: 'Docusaurus dinooooooo' +preview: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Grosser_Panda.JPG/2560px-Grosser_Panda.JPG website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' tags: ['opensource', 'design'] diff --git a/website/src/showcase/website/ozaki/ozaki.yaml b/website/src/showcase/website/ozaki/ozaki.yaml index d3190820d28d..79e006e3f753 100644 --- a/website/src/showcase/website/ozaki/ozaki.yaml +++ b/website/src/showcase/website/ozaki/ozaki.yaml @@ -1,6 +1,6 @@ title: 'Ozaki' description: 'Ozaki website' -preview: require('./showcase/test.png') +preview: /img/aot.jpg website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' tags: ['opensource', 'design'] From 022efa36537d854560e8e35c30432ce446f87ab4 Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Wed, 27 Mar 2024 19:06:39 +0000 Subject: [PATCH 18/79] refactor: apply lint autofix --- packages/docusaurus-plugin-showcase/src/index.ts | 2 -- project-words.txt | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts index 4a8fb29bcd7c..9be678be10b1 100644 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ b/packages/docusaurus-plugin-showcase/src/index.ts @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ - - import fs from 'fs-extra'; import path from 'path'; import { diff --git a/project-words.txt b/project-words.txt index 067bb1119750..3492e600e806 100644 --- a/project-words.txt +++ b/project-words.txt @@ -68,6 +68,7 @@ Datagit's dedup devto dingers +dinooooooo Dmitry docsearch Docsify From 30599c362ed3df3a24ad07d0c490dfa20cf788ad Mon Sep 17 00:00:00 2001 From: ozaki <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:07:36 +0100 Subject: [PATCH 19/79] Update packages/docusaurus-plugin-showcase/package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Lorber --- packages/docusaurus-plugin-showcase/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docusaurus-plugin-showcase/package.json b/packages/docusaurus-plugin-showcase/package.json index be0301b3ad36..b5c3f6988a47 100644 --- a/packages/docusaurus-plugin-showcase/package.json +++ b/packages/docusaurus-plugin-showcase/package.json @@ -1,5 +1,5 @@ { - "name": "@docusaurus/plugin-showcase", + "name": "@docusaurus/plugin-content-showcase", "version": "3.0.0", "description": "Showcase plugin for Docusaurus.", "main": "lib/index.js", From bed9bb306329ffa7e04eb480a489fbad801e1e12 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 29 Mar 2024 14:49:55 +0100 Subject: [PATCH 20/79] wip tests --- .../.npmignore | 0 .../README.md | 7 + .../package.json | 4 +- .../__fixtures__/website/docusaurus.config.js | 21 + .../website/src/showcase/site.yml | 6 + .../__snapshots__/index.test.ts.snap | 19 + .../src/__tests__/frontMatter.test.ts | 479 ++++++++++++++++++ .../src/__tests__/index.test.ts | 32 ++ .../src/__tests__/options.test.ts | 63 +++ .../src/index.ts | 123 +++++ .../src/markdownLoader.ts | 0 .../src/options.ts | 29 +- .../src/plugin-content-showcase.d.ts} | 5 +- .../src/types.ts | 0 .../src/yaml.ts | 24 + .../tsconfig.json | 0 packages/docusaurus-plugin-showcase/README.md | 7 - .../docusaurus-plugin-showcase/src/index.ts | 257 ---------- .../docusaurus-theme-classic/package.json | 2 +- .../src/theme-classic.d.ts | 4 +- .../src/theme/Showcase/index.tsx | 2 +- 21 files changed, 785 insertions(+), 299 deletions(-) rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/.npmignore (100%) create mode 100644 packages/docusaurus-plugin-content-showcase/README.md rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/package.json (87%) create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/index.ts rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/src/markdownLoader.ts (100%) rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/src/options.ts (61%) rename packages/{docusaurus-plugin-showcase/src/plugin-showcase.d.ts => docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts} (94%) rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/src/types.ts (100%) create mode 100644 packages/docusaurus-plugin-content-showcase/src/yaml.ts rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/tsconfig.json (100%) delete mode 100644 packages/docusaurus-plugin-showcase/README.md delete mode 100644 packages/docusaurus-plugin-showcase/src/index.ts diff --git a/packages/docusaurus-plugin-showcase/.npmignore b/packages/docusaurus-plugin-content-showcase/.npmignore similarity index 100% rename from packages/docusaurus-plugin-showcase/.npmignore rename to packages/docusaurus-plugin-content-showcase/.npmignore diff --git a/packages/docusaurus-plugin-content-showcase/README.md b/packages/docusaurus-plugin-content-showcase/README.md new file mode 100644 index 000000000000..33623bba6cbe --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/README.md @@ -0,0 +1,7 @@ +# `@docusaurus/plugin-content-showcase` + +Showcase plugin for Docusaurus. + +## Usage + +See [plugin-content-showcase documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-showcase). diff --git a/packages/docusaurus-plugin-showcase/package.json b/packages/docusaurus-plugin-content-showcase/package.json similarity index 87% rename from packages/docusaurus-plugin-showcase/package.json rename to packages/docusaurus-plugin-content-showcase/package.json index b5c3f6988a47..16cea13dd5b0 100644 --- a/packages/docusaurus-plugin-showcase/package.json +++ b/packages/docusaurus-plugin-content-showcase/package.json @@ -3,7 +3,7 @@ "version": "3.0.0", "description": "Showcase plugin for Docusaurus.", "main": "lib/index.js", - "types": "src/plugin-showcase.d.ts", + "types": "src/plugin-content-showcase.d.ts", "scripts": { "build": "tsc", "watch": "tsc --watch" @@ -14,7 +14,7 @@ "repository": { "type": "git", "url": "https://github.com/facebook/docusaurus.git", - "directory": "packages/docusaurus-plugin-showcase" + "directory": "packages/docusaurus-plugin-content-showcase" }, "license": "MIT", "dependencies": { diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js new file mode 100644 index 000000000000..d048d2caf5a1 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -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. + */ + +module.exports = { + title: 'My Site', + tagline: 'The tagline of my site', + url: 'https://your-docusaurus-site.example.com', + baseUrl: '/', + favicon: 'img/favicon.ico', + markdown: { + parseFrontMatter: async (params) => { + const result = await params.defaultParseFrontMatter(params); + result.frontMatter.custom_frontMatter = 'added by parseFrontMatter'; + return result; + }, + }, +}; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml new file mode 100644 index 000000000000..fcd8f830b83b --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml @@ -0,0 +1,6 @@ +title: "Hello" +description: "World" +preview: github.com/ozakione.png +website: "https://docusaurus.io/" +source: "https://github.com/facebook/docusaurus" +tags: ["opensource", "meta"] diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..f23e1c240f4e --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docusaurus-plugin-content-showcase loads simple showcase 1`] = ` +{ + "website": [ + { + "description": "World", + "preview": "github.com/ozakione.png", + "source": "https://github.com/facebook/docusaurus", + "tags": [ + "opensource", + "meta", + ], + "title": "Hello", + "website": "https://docusaurus.io/", + }, + ], +} +`; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts new file mode 100644 index 000000000000..6a2f7b0e4a4f --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -0,0 +1,479 @@ +/** + * 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 {escapeRegexp} from '@docusaurus/utils'; +import {validateShowcaseFrontMatter} from '../yaml'; +import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; + +function testField(params: { + prefix: string; + validFrontMatters: ShowcaseFrontMatter[]; + convertibleFrontMatter?: [ + ConvertibleFrontMatter: {[key: string]: unknown}, + ConvertedFrontMatter: ShowcaseFrontMatter, + ][]; + invalidFrontMatters?: [ + InvalidFrontMatter: {[key: string]: unknown}, + ErrorMessage: string, + ][]; +}) { + // eslint-disable-next-line jest/require-top-level-describe + test(`[${params.prefix}] accept valid values`, () => { + params.validFrontMatters.forEach((frontMatter) => { + expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + }); + }); + + // eslint-disable-next-line jest/require-top-level-describe + test(`[${params.prefix}] convert valid values`, () => { + params.convertibleFrontMatter?.forEach( + ([convertibleFrontMatter, convertedFrontMatter]) => { + expect(validateShowcaseFrontMatter(convertibleFrontMatter)).toEqual( + convertedFrontMatter, + ); + }, + ); + }); + + // eslint-disable-next-line jest/require-top-level-describe + test(`[${params.prefix}] throw error for values`, () => { + params.invalidFrontMatters?.forEach(([frontMatter, message]) => { + try { + validateShowcaseFrontMatter(frontMatter); + throw new Error( + `Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( + frontMatter, + null, + 2, + )}`, + ); + } catch (err) { + // eslint-disable-next-line jest/no-conditional-expect + expect((err as Error).message).toMatch( + new RegExp(escapeRegexp(message)), + ); + } + }); + }); +} + +describe('doc front matter schema', () => { + it('accepts empty object', () => { + const frontMatter: ShowcaseFrontMatter = {}; + expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + }); + + it('accepts unknown field', () => { + const frontMatter = {abc: '1'}; + expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + }); +}); + +describe('validateShowcaseFrontMatter id', () => { + testField({ + prefix: 'id', + validFrontMatters: [{id: '123'}, {id: 'unique_id'}], + invalidFrontMatters: [[{id: ''}, 'is not allowed to be empty']], + }); +}); + +describe('validateShowcaseFrontMatter title', () => { + testField({ + prefix: 'title', + validFrontMatters: [ + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + {title: ''}, + {title: 'title'}, + ], + }); +}); + +describe('validateShowcaseFrontMatter hide_title', () => { + testField({ + prefix: 'hide_title', + validFrontMatters: [{hide_title: true}, {hide_title: false}], + convertibleFrontMatter: [ + [{hide_title: 'true'}, {hide_title: true}], + [{hide_title: 'false'}, {hide_title: false}], + ], + invalidFrontMatters: [ + [{hide_title: 'yes'}, 'must be a boolean'], + [{hide_title: 'no'}, 'must be a boolean'], + [{hide_title: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter hide_table_of_contents', () => { + testField({ + prefix: 'hide_table_of_contents', + validFrontMatters: [ + {hide_table_of_contents: true}, + {hide_table_of_contents: false}, + ], + convertibleFrontMatter: [ + [{hide_table_of_contents: 'true'}, {hide_table_of_contents: true}], + [{hide_table_of_contents: 'false'}, {hide_table_of_contents: false}], + ], + invalidFrontMatters: [ + [{hide_table_of_contents: 'yes'}, 'must be a boolean'], + [{hide_table_of_contents: 'no'}, 'must be a boolean'], + [{hide_table_of_contents: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter keywords', () => { + testField({ + prefix: 'keywords', + validFrontMatters: [ + {keywords: ['hello']}, + {keywords: ['hello', 'world']}, + {keywords: ['hello', 'world']}, + {keywords: ['hello']}, + ], + invalidFrontMatters: [ + [{keywords: ''}, 'must be an array'], + [{keywords: ['']}, 'is not allowed to be empty'], + [{keywords: []}, 'does not contain 1 required value(s)'], + ], + }); +}); + +describe('validateShowcaseFrontMatter image', () => { + testField({ + prefix: 'image', + validFrontMatters: [ + {image: 'https://docusaurus.io/blog/image.png'}, + {image: '/absolute/image.png'}, + {image: '../relative/image.png'}, + ], + invalidFrontMatters: [ + [{image: ''}, '"image" does not look like a valid url (value=\'\')'], + ], + }); +}); + +describe('validateShowcaseFrontMatter description', () => { + testField({ + prefix: 'description', + validFrontMatters: [ + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + {description: ''}, + {description: 'description'}, + ], + }); +}); + +describe('validateShowcaseFrontMatter slug', () => { + testField({ + prefix: 'slug', + validFrontMatters: [ + {slug: '/'}, + {slug: 'slug'}, + {slug: '/slug/'}, + {slug: './slug'}, + {slug: '../../slug'}, + {slug: '/api/plugins/@docusaurus'}, + {slug: '@site/api/asset'}, + {slug: 'slug1 slug2'}, + ], + invalidFrontMatters: [[{slug: ''}, 'is not allowed to be empty']], + }); +}); + +describe('validateShowcaseFrontMatter sidebar_label', () => { + testField({ + prefix: 'sidebar_label', + validFrontMatters: [{sidebar_label: 'Awesome docs'}], + invalidFrontMatters: [[{sidebar_label: ''}, 'is not allowed to be empty']], + }); +}); + +describe('validateShowcaseFrontMatter sidebar_position', () => { + testField({ + prefix: 'sidebar_position', + validFrontMatters: [ + {sidebar_position: -5}, + {sidebar_position: -3.5}, + {sidebar_position: 0}, + {sidebar_position: 5}, + {sidebar_position: 3.5}, + ], + convertibleFrontMatter: [ + [{sidebar_position: '-1.5'}, {sidebar_position: -1.5}], + [{sidebar_position: '1'}, {sidebar_position: 1}], + [{sidebar_position: '1.5'}, {sidebar_position: 1.5}], + ], + }); +}); + +describe('validateShowcaseFrontMatter sidebar_custom_props', () => { + testField({ + prefix: 'sidebar_custom_props', + validFrontMatters: [ + {sidebar_custom_props: {}}, + {sidebar_custom_props: {prop: 'custom', number: 1, boolean: true}}, + ], + invalidFrontMatters: [ + [{sidebar_custom_props: ''}, 'must be of type object'], + ], + }); +}); + +describe('validateShowcaseFrontMatter custom_edit_url', () => { + testField({ + prefix: 'custom_edit_url', + validFrontMatters: [ + // See https://github.com/demisto/content-docs/pull/616#issuecomment-827087566 + {custom_edit_url: ''}, + {custom_edit_url: null}, + {custom_edit_url: 'https://github.com/facebook/docusaurus/markdown.md'}, + {custom_edit_url: '../../api/docs/markdown.md'}, + {custom_edit_url: '@site/api/docs/markdown.md'}, + ], + }); +}); + +describe('validateShowcaseFrontMatter parse_number_prefixes', () => { + testField({ + prefix: 'parse_number_prefixes', + validFrontMatters: [ + {parse_number_prefixes: true}, + {parse_number_prefixes: false}, + ], + convertibleFrontMatter: [ + [{parse_number_prefixes: 'true'}, {parse_number_prefixes: true}], + [{parse_number_prefixes: 'false'}, {parse_number_prefixes: false}], + ], + invalidFrontMatters: [ + [{parse_number_prefixes: 'yes'}, 'must be a boolean'], + [{parse_number_prefixes: 'no'}, 'must be a boolean'], + [{parse_number_prefixes: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter tags', () => { + testField({ + prefix: 'tags', + validFrontMatters: [{}, {tags: undefined}, {tags: ['tag1', 'tag2']}], + convertibleFrontMatter: [[{tags: ['tag1', 42]}, {tags: ['tag1', '42']}]], + invalidFrontMatters: [ + [ + {tags: 42}, + '"tags" does not look like a valid front matter Yaml array.', + ], + [ + {tags: 'tag1, tag2'}, + '"tags" does not look like a valid front matter Yaml array.', + ], + [{tags: [{}]}, '"tags[0]" does not look like a valid tag'], + [{tags: [true]}, '"tags[0]" does not look like a valid tag'], + [ + {tags: ['tag1', {hey: 'test'}]}, + '"tags[1]" does not look like a valid tag', + ], + ], + }); +}); + +describe('toc_min_heading_level', () => { + testField({ + prefix: 'toc_min_heading_level', + validFrontMatters: [ + {}, + {toc_min_heading_level: undefined}, + {toc_min_heading_level: 2}, + {toc_min_heading_level: 3}, + {toc_min_heading_level: 4}, + {toc_min_heading_level: 5}, + {toc_min_heading_level: 6}, + ], + convertibleFrontMatter: [ + [{toc_min_heading_level: '2'}, {toc_min_heading_level: 2}], + ], + invalidFrontMatters: [ + [ + {toc_min_heading_level: 1}, + '"toc_min_heading_level" must be greater than or equal to 2', + ], + [ + {toc_min_heading_level: 7}, + '"toc_min_heading_level" must be less than or equal to 6', + ], + [ + {toc_min_heading_level: 'hello'}, + '"toc_min_heading_level" must be a number', + ], + [ + {toc_min_heading_level: true}, + '"toc_min_heading_level" must be a number', + ], + ], + }); +}); + +describe('toc_max_heading_level', () => { + testField({ + prefix: 'toc_max_heading_level', + validFrontMatters: [ + {}, + {toc_max_heading_level: undefined}, + {toc_max_heading_level: 2}, + {toc_max_heading_level: 3}, + {toc_max_heading_level: 4}, + {toc_max_heading_level: 5}, + {toc_max_heading_level: 6}, + ], + convertibleFrontMatter: [ + [{toc_max_heading_level: '2'}, {toc_max_heading_level: 2}], + ], + invalidFrontMatters: [ + [ + {toc_max_heading_level: 1}, + '"toc_max_heading_level" must be greater than or equal to 2', + ], + [ + {toc_max_heading_level: 7}, + '"toc_max_heading_level" must be less than or equal to 6', + ], + [ + {toc_max_heading_level: 'hello'}, + '"toc_max_heading_level" must be a number', + ], + [ + {toc_max_heading_level: true}, + '"toc_max_heading_level" must be a number', + ], + ], + }); +}); + +describe('toc min/max consistency', () => { + testField({ + prefix: 'toc min/max', + validFrontMatters: [ + {}, + {toc_min_heading_level: undefined, toc_max_heading_level: undefined}, + {toc_min_heading_level: 2, toc_max_heading_level: 2}, + {toc_min_heading_level: 2, toc_max_heading_level: 6}, + {toc_min_heading_level: 2, toc_max_heading_level: 3}, + {toc_min_heading_level: 3, toc_max_heading_level: 3}, + ], + invalidFrontMatters: [ + [ + {toc_min_heading_level: 4, toc_max_heading_level: 3}, + '"toc_min_heading_level" must be less than or equal to ref:toc_max_heading_level', + ], + [ + {toc_min_heading_level: 6, toc_max_heading_level: 2}, + '"toc_min_heading_level" must be less than or equal to ref:toc_max_heading_level', + ], + ], + }); +}); + +describe('validateShowcaseFrontMatter draft', () => { + testField({ + prefix: 'draft', + validFrontMatters: [{draft: true}, {draft: false}], + convertibleFrontMatter: [ + [{draft: 'true'}, {draft: true}], + [{draft: 'false'}, {draft: false}], + ], + invalidFrontMatters: [ + [{draft: 'yes'}, 'must be a boolean'], + [{draft: 'no'}, 'must be a boolean'], + [{draft: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter unlisted', () => { + testField({ + prefix: 'unlisted', + validFrontMatters: [{unlisted: true}, {unlisted: false}], + convertibleFrontMatter: [ + [{unlisted: 'true'}, {unlisted: true}], + [{unlisted: 'false'}, {unlisted: false}], + ], + invalidFrontMatters: [ + [{unlisted: 'yes'}, 'must be a boolean'], + [{unlisted: 'no'}, 'must be a boolean'], + [{unlisted: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter draft XOR unlisted', () => { + testField({ + prefix: 'draft XOR unlisted', + validFrontMatters: [ + {draft: false}, + {unlisted: false}, + {draft: false, unlisted: false}, + {draft: true, unlisted: false}, + {draft: false, unlisted: true}, + ], + invalidFrontMatters: [ + [ + {draft: true, unlisted: true}, + "Can't be draft and unlisted at the same time.", + ], + ], + }); +}); + +describe('validateShowcaseFrontMatter last_update', () => { + testField({ + prefix: 'last_update', + validFrontMatters: [ + {last_update: undefined}, + {last_update: {author: 'test author', date: undefined}}, + {last_update: {author: undefined, date: '1/1/2000'}}, + {last_update: {author: undefined, date: new Date('1/1/2000')}}, + {last_update: {author: 'test author', date: '1/1/2000'}}, + {last_update: {author: 'test author', date: '1995-12-17T03:24:00'}}, + {last_update: {author: undefined, date: 'December 17, 1995 03:24:00'}}, + ], + invalidFrontMatters: [ + [ + {last_update: null}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: {}}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: ''}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: {invalid: 'key'}}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: {author: 'test author', date: 'I am not a date :('}}, + 'must be a valid date', + ], + [ + {last_update: {author: 'test author', date: '2011-10-45'}}, + 'must be a valid date', + ], + [ + {last_update: {author: 'test author', date: '2011-0-10'}}, + 'must be a valid date', + ], + [ + {last_update: {author: 'test author', date: ''}}, + 'must be a valid date', + ], + ], + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts new file mode 100644 index 000000000000..6669399f900f --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -0,0 +1,32 @@ +/** + * 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 path from 'path'; +import {loadContext} from '@docusaurus/core/src/server/site'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; + +import pluginContentPages from '../index'; +import {validateOptions} from '../options'; + +describe('docusaurus-plugin-content-showcase', () => { + it('loads simple showcase', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const context = await loadContext({siteDir}); + const plugin = pluginContentPages( + context, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/showcase', + }, + }), + ); + const showcaseMetadata = await plugin.loadContent!(); + + expect(showcaseMetadata).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts new file mode 100644 index 000000000000..52d1dd7b7136 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts @@ -0,0 +1,63 @@ +/** + * 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 {normalizePluginOptions} from '@docusaurus/utils-validation'; +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import type {Options} from '@docusaurus/plugin-content-pages'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', +}; + +describe('normalizeShowcasePluginOptions', () => { + it('returns default options for undefined user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('fills in default options for partially defined user options', () => { + expect(testValidate({path: 'src/foo'})).toEqual({ + ...defaultOptions, + path: 'src/foo', + }); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + path: 'src/showcase', + routeBasePath: '/showcase', + include: ['**/*.{yaml,yml}'], + exclude: ['**/$*/'], + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects bad path inputs', () => { + expect(() => { + testValidate({ + // @ts-expect-error: bad attribute + path: 42, + }); + }).toThrowErrorMatchingInlineSnapshot(`""path" must be a string"`); + }); + + it('empty routeBasePath replace default path("/")', () => { + expect( + testValidate({ + routeBasePath: '', + }), + ).toEqual({ + ...defaultOptions, + routeBasePath: '/', + }); + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts new file mode 100644 index 000000000000..6a80d9d90067 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -0,0 +1,123 @@ +/** + * 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 fs from 'fs-extra'; +import path from 'path'; +import { + getFolderContainingFile, + getPluginI18nPath, + Globby, +} from '@docusaurus/utils'; +import Yaml from 'js-yaml'; + +import {validateShowcaseFrontMatter} from './yaml'; +import type {LoadContext, Plugin} from '@docusaurus/types'; +import type {PluginOptions, Content} from '@docusaurus/plugin-content-showcase'; +import type {ShowcaseContentPaths} from './types'; + +export function getContentPathList( + contentPaths: ShowcaseContentPaths, +): string[] { + return [contentPaths.contentPathLocalized, contentPaths.contentPath]; +} + +export default function pluginContentShowcase( + context: LoadContext, + options: PluginOptions, +): Plugin { + const {siteDir, localizationDir} = context; + + const contentPaths: ShowcaseContentPaths = { + contentPath: path.resolve(siteDir, options.path), + contentPathLocalized: getPluginI18nPath({ + localizationDir, + pluginName: 'docusaurus-plugin-content-pages', + pluginId: options.id, + }), + }; + + return { + name: 'docusaurus-plugin-content-showcase', + + // todo doesn't work + // getPathsToWatch() { + // const {include} = options; + // return getContentPathList(contentPaths).flatMap((contentPath) => + // include.map((pattern) => `${contentPath}/${pattern}`), + // ); + // }, + + async loadContent(): Promise { + const {include} = options; + + if (!(await fs.pathExists(contentPaths.contentPath))) { + return null; + } + + // const {baseUrl} = siteConfig; + const showcaseFiles = await Globby(include, { + cwd: contentPaths.contentPath, + ignore: options.exclude, + }); + + async function processShowcaseSourceFile(relativeSource: string) { + // Lookup in localized folder in priority + const contentPath = await getFolderContainingFile( + getContentPathList(contentPaths), + relativeSource, + ); + + const sourcePath = path.join(contentPath, relativeSource); + const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; + return validateShowcaseFrontMatter(unsafeYaml); + } + + async function doProcessShowcaseSourceFile(relativeSource: string) { + try { + return await processShowcaseSourceFile(relativeSource); + } catch (err) { + throw new Error( + `Processing of page source file path=${relativeSource} failed.`, + {cause: err as Error}, + ); + } + } + + return { + website: await Promise.all( + showcaseFiles.map(doProcessShowcaseSourceFile), + ), + }; + }, + + async contentLoaded({content, actions}) { + if (!content) { + return; + } + + const {addRoute, createData} = actions; + + const showcaseAllData = await createData( + 'showcaseAll.json', + JSON.stringify(content.website), + ); + + addRoute({ + path: '/showcaseAll', + component: '@theme/Showcase', + modules: { + content: showcaseAllData, + // img: '@site/src/showcase/website/ozaki/aot.jpg', + }, + exact: true, + }); + }, + }; +} + +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-showcase/src/markdownLoader.ts b/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts similarity index 100% rename from packages/docusaurus-plugin-showcase/src/markdownLoader.ts rename to packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts similarity index 61% rename from packages/docusaurus-plugin-showcase/src/options.ts rename to packages/docusaurus-plugin-content-showcase/src/options.ts index 23bbfc4673c2..3eff51e430db 100644 --- a/packages/docusaurus-plugin-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -5,24 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import { - Joi, - validateFrontMatter, - RouteBasePathSchema, -} from '@docusaurus/utils-validation'; +import {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; import type {OptionValidationContext} from '@docusaurus/types'; -import type { - PluginOptions, - Options, - ShowcaseFrontMatter, -} from '@docusaurus/plugin-showcase'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; 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. + include: ['**/*.{yml,yaml}'], // Extensions to include. exclude: GlobExcludeDefault, }; @@ -34,15 +26,6 @@ const PluginOptionSchema = Joi.object({ id: Joi.string().default(DEFAULT_OPTIONS.id), }); -const contentAuthorsSchema = Joi.object({ - title: Joi.string().required(), - description: Joi.string().required(), - preview: Joi.string().required(), - website: Joi.string().required(), - source: Joi.string().required(), - tags: Joi.array().items(Joi.string()).required(), -}); - export function validateOptions({ validate, options, @@ -50,9 +33,3 @@ export function validateOptions({ const validatedOptions = validate(PluginOptionSchema, options); return validatedOptions; } - -export function validateShowcaseFrontMatter(frontMatter: { - [key: string]: unknown; -}): ShowcaseFrontMatter { - return validateFrontMatter(frontMatter, contentAuthorsSchema); -} diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts similarity index 94% rename from packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts rename to packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index d332e799ac3f..5c0475f5ffcf 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -declare module '@docusaurus/plugin-showcase' { +declare module '@docusaurus/plugin-content-showcase' { import type {LoadContext, Plugin} from '@docusaurus/types'; export type Assets = { @@ -43,7 +43,6 @@ declare module '@docusaurus/plugin-showcase' { export type Content = { website: { - type: string; title: string; description: string; preview: string | null; // null = use our serverless screenshot service @@ -59,7 +58,7 @@ declare module '@docusaurus/plugin-showcase' { export default function pluginShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; + ): Promise>; export type ShowcaseMetadata = { /** Path to the Markdown source, with `@site` alias. */ diff --git a/packages/docusaurus-plugin-showcase/src/types.ts b/packages/docusaurus-plugin-content-showcase/src/types.ts similarity index 100% rename from packages/docusaurus-plugin-showcase/src/types.ts rename to packages/docusaurus-plugin-content-showcase/src/types.ts diff --git a/packages/docusaurus-plugin-content-showcase/src/yaml.ts b/packages/docusaurus-plugin-content-showcase/src/yaml.ts new file mode 100644 index 000000000000..dc3550f4cd17 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/yaml.ts @@ -0,0 +1,24 @@ +/** + * 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 {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; +import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; + +const showcaseFrontMatterSchema = Joi.object({ + title: Joi.string().required(), + description: Joi.string().required(), + preview: Joi.string().required(), + website: Joi.string().required(), + source: Joi.string().required(), + tags: Joi.array().items(Joi.string()).required(), +}); + +export function validateShowcaseFrontMatter(frontMatter: { + [key: string]: unknown; +}): ShowcaseFrontMatter { + return validateFrontMatter(frontMatter, showcaseFrontMatterSchema); +} diff --git a/packages/docusaurus-plugin-showcase/tsconfig.json b/packages/docusaurus-plugin-content-showcase/tsconfig.json similarity index 100% rename from packages/docusaurus-plugin-showcase/tsconfig.json rename to packages/docusaurus-plugin-content-showcase/tsconfig.json diff --git a/packages/docusaurus-plugin-showcase/README.md b/packages/docusaurus-plugin-showcase/README.md deleted file mode 100644 index 1d075c625aaf..000000000000 --- a/packages/docusaurus-plugin-showcase/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# `@docusaurus/plugin-showcase` - -Showcase plugin for Docusaurus. - -## Usage - -See [plugin-showcase documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-showcase). diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts deleted file mode 100644 index 9be678be10b1..000000000000 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * 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 fs from 'fs-extra'; -import path from 'path'; -import { - DEFAULT_PLUGIN_ID, - addTrailingPathSeparator, - aliasedSitePath, - docuHash, - getFolderContainingFile, - getPluginI18nPath, - Globby, - parseMarkdownFile, - aliasedSitePathToRelativePath, - createAbsoluteFilePathMatcher, -} from '@docusaurus/utils'; -import Yaml from 'js-yaml'; - -import {validateShowcaseFrontMatter} from './options'; -import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types'; -import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; -import type {ShowcaseContentPaths} from './types'; - -export function getContentPathList( - contentPaths: ShowcaseContentPaths, -): string[] { - return [contentPaths.contentPathLocalized, contentPaths.contentPath]; -} - -const isMarkdownSource = (source: string) => - source.endsWith('.md') || source.endsWith('.mdx'); - -export default function pluginContentShowcase( - context: LoadContext, - options: PluginOptions, -): 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, - 'docusaurus-plugin-showcase', - ); - const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID); - - return { - name: 'docusaurus-plugin-showcase', - - // todo doesn't work - // getPathsToWatch() { - // const {include} = options; - // return getContentPathList(contentPaths).flatMap((contentPath) => - // include.map((pattern) => `${contentPath}/${pattern}`), - // ); - // }, - - async loadContent() { - const {include} = options; - - if (!(await fs.pathExists(contentPaths.contentPath))) { - return null; - } - - // const {baseUrl} = siteConfig; - const showcaseFiles = await Globby(include, { - cwd: contentPaths.contentPath, - ignore: options.exclude, - }); - - async function processShowcaseSourceFile(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); - if (!isMarkdownSource(sourcePath)) { - const rawYaml = await fs.readFile(sourcePath, 'utf-8'); - const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; - const yaml = validateShowcaseFrontMatter(unsafeYaml); - return { - type: 'yaml', - ...yaml, - }; - } - const rawMarkdown = await fs.readFile(sourcePath, 'utf-8'); - const {frontMatter: unsafeFrontMatter, content} = - await parseMarkdownFile({ - filePath: sourcePath, - fileContent: rawMarkdown, - parseFrontMatter: siteConfig.markdown?.parseFrontMatter, - }); - const frontMatter = validateShowcaseFrontMatter(unsafeFrontMatter); - return { - type: 'markdown', - ...frontMatter, - content, - sourcePath: aliasedSourcePath, - }; - } - - async function doProcessShowcaseSourceFile(relativeSource: string) { - try { - return await processShowcaseSourceFile(relativeSource); - } catch (err) { - throw new Error( - `Processing of page source file path=${relativeSource} failed.`, - {cause: err as Error}, - ); - } - } - - return { - website: await Promise.all( - showcaseFiles.map(doProcessShowcaseSourceFile), - ), - }; - }, - - async contentLoaded({content, actions}) { - if (!content) { - return; - } - - const {addRoute, createData} = actions; - - function createPageRouteMetadata( - metadata: Content['website'][number], - ): RouteMetadata { - return { - sourceFilePath: aliasedSitePathToRelativePath(metadata.sourcePath!), - // TODO add support for last updated date in the page plugin - // at least for Markdown files - // lastUpdatedAt: metadata.lastUpdatedAt, - lastUpdatedAt: undefined, - }; - } - - await Promise.all( - content.website.map(async (item) => { - if (item.type === 'yaml') { - return; - } - await createData( - `${docuHash(item.sourcePath!)}.json`, - JSON.stringify(item), - ); - - const routeMetadata = createPageRouteMetadata(item); - - const mdxPath = aliasedSitePathToRelativePath(item.sourcePath!); - console.log('mdxPath', mdxPath); - - addRoute({ - path: `/showcaseAll/${item.title}`, - component: '@theme/ShowcaseDetails', - metadata: routeMetadata, - modules: { - content: item.sourcePath!, - }, - exact: true, - }); - }), - ); - - const showcaseAllData = await createData( - 'showcaseAll.json', - JSON.stringify(content.website), - ); - - addRoute({ - path: '/showcaseAll', - component: '@theme/Showcase', - modules: { - content: showcaseAllData, - }, - exact: true, - }); - }, - - configureWebpack() { - const contentDirs = getContentPathList(contentPaths); - - return { - resolve: { - alias: { - '~showcase': pluginDataDirRoot, - }, - }, - module: { - rules: [ - { - test: /\.mdx?$/i, - include: contentDirs - // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 - .map(addTrailingPathSeparator), - use: [ - { - loader: require.resolve('@docusaurus/mdx-loader'), - options: { - staticDirs: siteConfig.staticDirectories.map((dir) => - 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. - const aliasedPath = aliasedSitePath(mdxPath, siteDir); - return path.join( - dataDir, - `${docuHash(aliasedPath)}.json`, - ); - }, - // Assets allow to convert some relative images paths to - // require() calls - createAssets: ({ - frontMatter, - }: { - frontMatter: Content['website'][number]; - }) => ({ - image: frontMatter.preview, - }), - markdownConfig: siteConfig.markdown, - }, - }, - { - loader: path.resolve(__dirname, './markdownLoader.js'), - }, - ].filter(Boolean), - }, - ], - }, - }; - }, - }; -} - -export {validateOptions} from './options'; diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 05d8caf87382..1b95a08a5309 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -26,7 +26,7 @@ "@docusaurus/plugin-content-blog": "3.0.0", "@docusaurus/plugin-content-docs": "3.0.0", "@docusaurus/plugin-content-pages": "3.0.0", - "@docusaurus/plugin-showcase": "3.0.0", + "@docusaurus/plugin-content-showcase": "3.0.0", "@docusaurus/theme-common": "3.0.0", "@docusaurus/theme-translations": "3.0.0", "@docusaurus/types": "3.0.0", diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 1d891565e561..d35e90336d61 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -248,7 +248,7 @@ declare module '@theme/BlogPostItems' { } declare module '@theme/ShowcaseDetails' { - import type {Content} from '@docusaurus/plugin-showcase'; + import type {Content} from '@docusaurus/plugin-content-showcase'; export type User = Content['website'][number]; @@ -260,7 +260,7 @@ declare module '@theme/ShowcaseDetails' { } declare module '@theme/Showcase' { - import type {Content} from '@docusaurus/plugin-showcase'; + import type {Content} from '@docusaurus/plugin-content-showcase'; export type User = Content['website'][number]; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index 0acd3df1a19f..a79c0c6457f7 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -21,7 +21,7 @@ import ShowcaseTooltip from '@theme/Showcase/ShowcaseTooltip'; import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect'; import ShowcaseFilterToggle from '@theme/Showcase/ShowcaseFilterToggle'; import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; -import type {TagType} from '@docusaurus/plugin-showcase'; +import type {TagType} from '@docusaurus/plugin-content-showcase'; import styles from './styles.module.css'; type Users = User[]; From c42c15eb3280b6e3bdc809198210bdb69ca7e10e Mon Sep 17 00:00:00 2001 From: OzakIOne Date: Fri, 29 Mar 2024 13:55:15 +0000 Subject: [PATCH 21/79] refactor: apply lint autofix --- project-words.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/project-words.txt b/project-words.txt index 3492e600e806..cca5d6668548 100644 --- a/project-words.txt +++ b/project-words.txt @@ -232,6 +232,7 @@ outerbounds overrideable Ozaki ozaki +ozakione O’Shannessy pageview Palenight From e147034d2e61bd3ee3ca03dc1fb6d105a45696ff Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 29 Mar 2024 20:14:59 +0100 Subject: [PATCH 22/79] wip tests --- .../src/__tests__/frontMatter.test.ts | 430 ++---------------- 1 file changed, 26 insertions(+), 404 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts index 6a2f7b0e4a4f..f4c9a9bdb1d6 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -62,418 +62,40 @@ function testField(params: { } describe('doc front matter schema', () => { - it('accepts empty object', () => { - const frontMatter: ShowcaseFrontMatter = {}; + it('accepts valid frontmatter', () => { + const frontMatter: ShowcaseFrontMatter = { + title: 'title', + description: 'description', + preview: 'preview', + source: 'source', + tags: [], + website: 'website', + }; expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); }); - it('accepts unknown field', () => { - const frontMatter = {abc: '1'}; - expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); - }); -}); - -describe('validateShowcaseFrontMatter id', () => { - testField({ - prefix: 'id', - validFrontMatters: [{id: '123'}, {id: 'unique_id'}], - invalidFrontMatters: [[{id: ''}, 'is not allowed to be empty']], - }); -}); - -describe('validateShowcaseFrontMatter title', () => { - testField({ - prefix: 'title', - validFrontMatters: [ - // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - {title: ''}, - {title: 'title'}, - ], - }); -}); - -describe('validateShowcaseFrontMatter hide_title', () => { - testField({ - prefix: 'hide_title', - validFrontMatters: [{hide_title: true}, {hide_title: false}], - convertibleFrontMatter: [ - [{hide_title: 'true'}, {hide_title: true}], - [{hide_title: 'false'}, {hide_title: false}], - ], - invalidFrontMatters: [ - [{hide_title: 'yes'}, 'must be a boolean'], - [{hide_title: 'no'}, 'must be a boolean'], - [{hide_title: ''}, 'must be a boolean'], - ], - }); -}); - -describe('validateShowcaseFrontMatter hide_table_of_contents', () => { - testField({ - prefix: 'hide_table_of_contents', - validFrontMatters: [ - {hide_table_of_contents: true}, - {hide_table_of_contents: false}, - ], - convertibleFrontMatter: [ - [{hide_table_of_contents: 'true'}, {hide_table_of_contents: true}], - [{hide_table_of_contents: 'false'}, {hide_table_of_contents: false}], - ], - invalidFrontMatters: [ - [{hide_table_of_contents: 'yes'}, 'must be a boolean'], - [{hide_table_of_contents: 'no'}, 'must be a boolean'], - [{hide_table_of_contents: ''}, 'must be a boolean'], - ], - }); -}); - -describe('validateShowcaseFrontMatter keywords', () => { - testField({ - prefix: 'keywords', - validFrontMatters: [ - {keywords: ['hello']}, - {keywords: ['hello', 'world']}, - {keywords: ['hello', 'world']}, - {keywords: ['hello']}, - ], - invalidFrontMatters: [ - [{keywords: ''}, 'must be an array'], - [{keywords: ['']}, 'is not allowed to be empty'], - [{keywords: []}, 'does not contain 1 required value(s)'], - ], - }); -}); - -describe('validateShowcaseFrontMatter image', () => { - testField({ - prefix: 'image', - validFrontMatters: [ - {image: 'https://docusaurus.io/blog/image.png'}, - {image: '/absolute/image.png'}, - {image: '../relative/image.png'}, - ], - invalidFrontMatters: [ - [{image: ''}, '"image" does not look like a valid url (value=\'\')'], - ], - }); -}); - -describe('validateShowcaseFrontMatter description', () => { - testField({ - prefix: 'description', - validFrontMatters: [ - // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 - {description: ''}, - {description: 'description'}, - ], - }); -}); - -describe('validateShowcaseFrontMatter slug', () => { - testField({ - prefix: 'slug', - validFrontMatters: [ - {slug: '/'}, - {slug: 'slug'}, - {slug: '/slug/'}, - {slug: './slug'}, - {slug: '../../slug'}, - {slug: '/api/plugins/@docusaurus'}, - {slug: '@site/api/asset'}, - {slug: 'slug1 slug2'}, - ], - invalidFrontMatters: [[{slug: ''}, 'is not allowed to be empty']], - }); -}); - -describe('validateShowcaseFrontMatter sidebar_label', () => { - testField({ - prefix: 'sidebar_label', - validFrontMatters: [{sidebar_label: 'Awesome docs'}], - invalidFrontMatters: [[{sidebar_label: ''}, 'is not allowed to be empty']], - }); -}); - -describe('validateShowcaseFrontMatter sidebar_position', () => { - testField({ - prefix: 'sidebar_position', - validFrontMatters: [ - {sidebar_position: -5}, - {sidebar_position: -3.5}, - {sidebar_position: 0}, - {sidebar_position: 5}, - {sidebar_position: 3.5}, - ], - convertibleFrontMatter: [ - [{sidebar_position: '-1.5'}, {sidebar_position: -1.5}], - [{sidebar_position: '1'}, {sidebar_position: 1}], - [{sidebar_position: '1.5'}, {sidebar_position: 1.5}], - ], - }); -}); - -describe('validateShowcaseFrontMatter sidebar_custom_props', () => { - testField({ - prefix: 'sidebar_custom_props', - validFrontMatters: [ - {sidebar_custom_props: {}}, - {sidebar_custom_props: {prop: 'custom', number: 1, boolean: true}}, - ], - invalidFrontMatters: [ - [{sidebar_custom_props: ''}, 'must be of type object'], - ], - }); -}); - -describe('validateShowcaseFrontMatter custom_edit_url', () => { - testField({ - prefix: 'custom_edit_url', - validFrontMatters: [ - // See https://github.com/demisto/content-docs/pull/616#issuecomment-827087566 - {custom_edit_url: ''}, - {custom_edit_url: null}, - {custom_edit_url: 'https://github.com/facebook/docusaurus/markdown.md'}, - {custom_edit_url: '../../api/docs/markdown.md'}, - {custom_edit_url: '@site/api/docs/markdown.md'}, - ], - }); -}); - -describe('validateShowcaseFrontMatter parse_number_prefixes', () => { - testField({ - prefix: 'parse_number_prefixes', - validFrontMatters: [ - {parse_number_prefixes: true}, - {parse_number_prefixes: false}, - ], - convertibleFrontMatter: [ - [{parse_number_prefixes: 'true'}, {parse_number_prefixes: true}], - [{parse_number_prefixes: 'false'}, {parse_number_prefixes: false}], - ], - invalidFrontMatters: [ - [{parse_number_prefixes: 'yes'}, 'must be a boolean'], - [{parse_number_prefixes: 'no'}, 'must be a boolean'], - [{parse_number_prefixes: ''}, 'must be a boolean'], - ], - }); -}); - -describe('validateShowcaseFrontMatter tags', () => { - testField({ - prefix: 'tags', - validFrontMatters: [{}, {tags: undefined}, {tags: ['tag1', 'tag2']}], - convertibleFrontMatter: [[{tags: ['tag1', 42]}, {tags: ['tag1', '42']}]], - invalidFrontMatters: [ - [ - {tags: 42}, - '"tags" does not look like a valid front matter Yaml array.', - ], - [ - {tags: 'tag1, tag2'}, - '"tags" does not look like a valid front matter Yaml array.', - ], - [{tags: [{}]}, '"tags[0]" does not look like a valid tag'], - [{tags: [true]}, '"tags[0]" does not look like a valid tag'], - [ - {tags: ['tag1', {hey: 'test'}]}, - '"tags[1]" does not look like a valid tag', - ], - ], - }); -}); - -describe('toc_min_heading_level', () => { - testField({ - prefix: 'toc_min_heading_level', - validFrontMatters: [ - {}, - {toc_min_heading_level: undefined}, - {toc_min_heading_level: 2}, - {toc_min_heading_level: 3}, - {toc_min_heading_level: 4}, - {toc_min_heading_level: 5}, - {toc_min_heading_level: 6}, - ], - convertibleFrontMatter: [ - [{toc_min_heading_level: '2'}, {toc_min_heading_level: 2}], - ], - invalidFrontMatters: [ - [ - {toc_min_heading_level: 1}, - '"toc_min_heading_level" must be greater than or equal to 2', - ], - [ - {toc_min_heading_level: 7}, - '"toc_min_heading_level" must be less than or equal to 6', - ], - [ - {toc_min_heading_level: 'hello'}, - '"toc_min_heading_level" must be a number', - ], - [ - {toc_min_heading_level: true}, - '"toc_min_heading_level" must be a number', - ], - ], - }); -}); - -describe('toc_max_heading_level', () => { - testField({ - prefix: 'toc_max_heading_level', - validFrontMatters: [ - {}, - {toc_max_heading_level: undefined}, - {toc_max_heading_level: 2}, - {toc_max_heading_level: 3}, - {toc_max_heading_level: 4}, - {toc_max_heading_level: 5}, - {toc_max_heading_level: 6}, - ], - convertibleFrontMatter: [ - [{toc_max_heading_level: '2'}, {toc_max_heading_level: 2}], - ], - invalidFrontMatters: [ - [ - {toc_max_heading_level: 1}, - '"toc_max_heading_level" must be greater than or equal to 2', - ], - [ - {toc_max_heading_level: 7}, - '"toc_max_heading_level" must be less than or equal to 6', - ], - [ - {toc_max_heading_level: 'hello'}, - '"toc_max_heading_level" must be a number', - ], - [ - {toc_max_heading_level: true}, - '"toc_max_heading_level" must be a number', - ], - ], - }); -}); - -describe('toc min/max consistency', () => { - testField({ - prefix: 'toc min/max', - validFrontMatters: [ - {}, - {toc_min_heading_level: undefined, toc_max_heading_level: undefined}, - {toc_min_heading_level: 2, toc_max_heading_level: 2}, - {toc_min_heading_level: 2, toc_max_heading_level: 6}, - {toc_min_heading_level: 2, toc_max_heading_level: 3}, - {toc_min_heading_level: 3, toc_max_heading_level: 3}, - ], - invalidFrontMatters: [ - [ - {toc_min_heading_level: 4, toc_max_heading_level: 3}, - '"toc_min_heading_level" must be less than or equal to ref:toc_max_heading_level', - ], - [ - {toc_min_heading_level: 6, toc_max_heading_level: 2}, - '"toc_min_heading_level" must be less than or equal to ref:toc_max_heading_level', - ], - ], - }); -}); - -describe('validateShowcaseFrontMatter draft', () => { - testField({ - prefix: 'draft', - validFrontMatters: [{draft: true}, {draft: false}], - convertibleFrontMatter: [ - [{draft: 'true'}, {draft: true}], - [{draft: 'false'}, {draft: false}], - ], - invalidFrontMatters: [ - [{draft: 'yes'}, 'must be a boolean'], - [{draft: 'no'}, 'must be a boolean'], - [{draft: ''}, 'must be a boolean'], - ], - }); -}); - -describe('validateShowcaseFrontMatter unlisted', () => { - testField({ - prefix: 'unlisted', - validFrontMatters: [{unlisted: true}, {unlisted: false}], - convertibleFrontMatter: [ - [{unlisted: 'true'}, {unlisted: true}], - [{unlisted: 'false'}, {unlisted: false}], - ], - invalidFrontMatters: [ - [{unlisted: 'yes'}, 'must be a boolean'], - [{unlisted: 'no'}, 'must be a boolean'], - [{unlisted: ''}, 'must be a boolean'], - ], - }); -}); - -describe('validateShowcaseFrontMatter draft XOR unlisted', () => { - testField({ - prefix: 'draft XOR unlisted', - validFrontMatters: [ - {draft: false}, - {unlisted: false}, - {draft: false, unlisted: false}, - {draft: true, unlisted: false}, - {draft: false, unlisted: true}, - ], - invalidFrontMatters: [ - [ - {draft: true, unlisted: true}, - "Can't be draft and unlisted at the same time.", - ], - ], + it('reject invalid frontmatter', () => { + const frontMatter = {}; + expect(() => + validateShowcaseFrontMatter(frontMatter), + ).toThrowErrorMatchingInlineSnapshot( + `""title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, + ); }); }); -describe('validateShowcaseFrontMatter last_update', () => { +describe('validateShowcaseFrontMatter full', () => { testField({ - prefix: 'last_update', + prefix: 'valid full frontmatter', validFrontMatters: [ - {last_update: undefined}, - {last_update: {author: 'test author', date: undefined}}, - {last_update: {author: undefined, date: '1/1/2000'}}, - {last_update: {author: undefined, date: new Date('1/1/2000')}}, - {last_update: {author: 'test author', date: '1/1/2000'}}, - {last_update: {author: 'test author', date: '1995-12-17T03:24:00'}}, - {last_update: {author: undefined, date: 'December 17, 1995 03:24:00'}}, - ], - invalidFrontMatters: [ - [ - {last_update: null}, - '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', - ], - [ - {last_update: {}}, - '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', - ], - [ - {last_update: ''}, - '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', - ], - [ - {last_update: {invalid: 'key'}}, - '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', - ], - [ - {last_update: {author: 'test author', date: 'I am not a date :('}}, - 'must be a valid date', - ], - [ - {last_update: {author: 'test author', date: '2011-10-45'}}, - 'must be a valid date', - ], - [ - {last_update: {author: 'test author', date: '2011-0-10'}}, - 'must be a valid date', - ], - [ - {last_update: {author: 'test author', date: ''}}, - 'must be a valid date', - ], + { + title: 'title', + description: 'description', + preview: 'preview', + source: 'source', + tags: [], + website: 'website', + }, ], }); }); From cdb7c07bdcd78afb8c6759a8d8723bc1fd3619f7 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 29 Mar 2024 20:18:03 +0100 Subject: [PATCH 23/79] consistent file naming --- .../src/__tests__/frontMatter.test.ts | 2 +- .../src/{yaml.ts => frontMatter.ts} | 0 packages/docusaurus-plugin-content-showcase/src/index.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/docusaurus-plugin-content-showcase/src/{yaml.ts => frontMatter.ts} (100%) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts index f4c9a9bdb1d6..6de639727f02 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -6,7 +6,7 @@ */ import {escapeRegexp} from '@docusaurus/utils'; -import {validateShowcaseFrontMatter} from '../yaml'; +import {validateShowcaseFrontMatter} from '../frontMatter'; import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; function testField(params: { diff --git a/packages/docusaurus-plugin-content-showcase/src/yaml.ts b/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts similarity index 100% rename from packages/docusaurus-plugin-content-showcase/src/yaml.ts rename to packages/docusaurus-plugin-content-showcase/src/frontMatter.ts diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 6a80d9d90067..22a014cc9ebe 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -14,7 +14,7 @@ import { } from '@docusaurus/utils'; import Yaml from 'js-yaml'; -import {validateShowcaseFrontMatter} from './yaml'; +import {validateShowcaseFrontMatter} from './frontMatter'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type {PluginOptions, Content} from '@docusaurus/plugin-content-showcase'; import type {ShowcaseContentPaths} from './types'; From 46c57d6cd5691af5656a4e1b3f94899fbe22a24f Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:30:40 +0200 Subject: [PATCH 24/79] wip tags file validation --- .../__snapshots__/index.test.ts.snap | 2 +- .../src/__tests__/options.test.ts | 66 +++++++++++++- .../src/index.ts | 91 +++++++++++++++++-- .../src/options.ts | 17 +++- .../src/plugin-content-showcase.d.ts | 47 +++------- .../src/theme-classic.d.ts | 8 +- website/docusaurus.config.ts | 2 +- .../website => showcase}/clem/clem.mdx | 0 .../showcase/website => showcase}/dino.yaml | 0 .../website => showcase}/ozaki/ozaki.yaml | 0 .../showcase/website => showcase}/seb.yaml | 0 website/showcase/tags.yaml | 61 +++++++++++++ 12 files changed, 245 insertions(+), 49 deletions(-) rename website/{src/showcase/website => showcase}/clem/clem.mdx (100%) rename website/{src/showcase/website => showcase}/dino.yaml (100%) rename website/{src/showcase/website => showcase}/ozaki/ozaki.yaml (100%) rename website/{src/showcase/website => showcase}/seb.yaml (100%) create mode 100644 website/showcase/tags.yaml diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap index f23e1c240f4e..097bb675fed3 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap @@ -2,7 +2,7 @@ exports[`docusaurus-plugin-content-showcase loads simple showcase 1`] = ` { - "website": [ + "items": [ { "description": "World", "preview": "github.com/ozakione.png", diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts index 52d1dd7b7136..591eff07e721 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts @@ -6,7 +6,7 @@ */ import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {validateOptions, DEFAULT_OPTIONS} from '../options'; -import type {Options} from '@docusaurus/plugin-content-pages'; +import type {Options} from '@docusaurus/plugin-content-showcase'; function testValidate(options: Options) { return validateOptions({validate: normalizePluginOptions, options}); @@ -60,4 +60,68 @@ describe('normalizeShowcasePluginOptions', () => { routeBasePath: '/', }); }); + + it('accepts correctly defined tags file options', () => { + const userOptions = { + tags: '@site/showcase/tags.yaml', + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('reject badly defined tags file options', () => { + const userOptions = { + tags: 42, + }; + expect(() => + testValidate( + // @ts-expect-error: bad attributes + userOptions, + ), + ).toThrowErrorMatchingInlineSnapshot( + `""tags" must be one of [string, array]"`, + ); + }); + + it('accepts correctly defined tags object options', () => { + const userOptions = { + tags: [ + { + label: 'foo', + description: { + message: 'bar', + id: 'baz', + }, + color: 'red', + }, + ], + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('reject bedly defined tags object options', () => { + const userOptions = { + tags: [ + { + label: 'foo', + description: { + message: 'bar', + id: 'baz', + }, + color: 42, + }, + ], + }; + expect(() => + testValidate( + // @ts-expect-error: bad attributes + userOptions, + ), + ).toThrowErrorMatchingInlineSnapshot(`""tags[0].color" must be a string"`); + }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 22a014cc9ebe..0559cd2744ba 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -14,9 +14,15 @@ import { } from '@docusaurus/utils'; import Yaml from 'js-yaml'; +import {Joi} from '@docusaurus/utils-validation'; import {validateShowcaseFrontMatter} from './frontMatter'; +import {tagSchema} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; -import type {PluginOptions, Content} from '@docusaurus/plugin-content-showcase'; +import type { + PluginOptions, + ShowcaseItem, + TagOption, +} from '@docusaurus/plugin-content-showcase'; import type {ShowcaseContentPaths} from './types'; export function getContentPathList( @@ -25,10 +31,59 @@ export function getContentPathList( return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } +async function getTagsDefinition( + filePath: string | TagOption[], +): Promise { + if (Array.isArray(filePath)) { + return filePath.map((tag) => tag.label); + } + + const rawYaml = await fs.readFile(filePath, 'utf-8'); + const unsafeYaml: any = Yaml.load(rawYaml); + console.log('unsafeYaml:', unsafeYaml); + + const transformedData = unsafeYaml.tags.map((item: any) => { + const [label] = Object.keys(item); // Extract label from object key + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const {description, color} = item[label]; // Extract description and color + return {label, description, color}; // Create new object with transformed structure + }); + console.log('transformedData:', transformedData); + + const safeYaml = tagSchema.validate(transformedData); + + if (safeYaml.error) { + throw new Error(`Invalid tags.yaml file: ${safeYaml.error.message}`); + } + + const tagLabels = safeYaml.value.map((tag: any) => Object.keys(tag)[0]); + return tagLabels; +} + +function createTagSchema(tags: string[]): Joi.Schema { + return Joi.alternatives().try( + Joi.string().valid(...tags), // Schema for single string + Joi.array().items(Joi.string().valid(...tags)), // Schema for array of strings + ); +} + +function validateFrontMatterTags( + frontMatterTags: string[], + tagListSchema: Joi.Schema, +): void { + const result = tagListSchema.validate(frontMatterTags); + if (result.error) { + throw new Error( + `Front matter contains invalid tags: ${result.error.message}`, + ); + } +} + export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, -): Plugin { +): Plugin { const {siteDir, localizationDir} = context; const contentPaths: ShowcaseContentPaths = { @@ -51,7 +106,7 @@ export default function pluginContentShowcase( // ); // }, - async loadContent(): Promise { + async loadContent(): Promise { const {include} = options; if (!(await fs.pathExists(contentPaths.contentPath))) { @@ -64,6 +119,23 @@ export default function pluginContentShowcase( ignore: options.exclude, }); + const filteredShowcaseFiles = showcaseFiles.filter( + (source) => source !== 'tags.yaml', + ); + + // todo refactor ugly + const tagFilePath = path.join( + await getFolderContainingFile( + getContentPathList(contentPaths), + 'tags.yaml', + ), + 'tags.yaml', + ); + + const tagList = await getTagsDefinition(tagFilePath); + const createdTagSchema = createTagSchema(tagList); + console.log('createdTagSchema:', createdTagSchema.describe()); + async function processShowcaseSourceFile(relativeSource: string) { // Lookup in localized folder in priority const contentPath = await getFolderContainingFile( @@ -72,9 +144,14 @@ export default function pluginContentShowcase( ); const sourcePath = path.join(contentPath, relativeSource); + const rawYaml = await fs.readFile(sourcePath, 'utf-8'); const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; - return validateShowcaseFrontMatter(unsafeYaml); + const yaml = validateShowcaseFrontMatter(unsafeYaml); + + validateFrontMatterTags(yaml.tags, createdTagSchema); + + return yaml; } async function doProcessShowcaseSourceFile(relativeSource: string) { @@ -89,8 +166,8 @@ export default function pluginContentShowcase( } return { - website: await Promise.all( - showcaseFiles.map(doProcessShowcaseSourceFile), + items: await Promise.all( + filteredShowcaseFiles.map(doProcessShowcaseSourceFile), ), }; }, @@ -104,7 +181,7 @@ export default function pluginContentShowcase( const showcaseAllData = await createData( 'showcaseAll.json', - JSON.stringify(content.website), + JSON.stringify(content.items), ); addRoute({ diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index 3eff51e430db..f718d01fd9f8 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -12,18 +12,33 @@ import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', - path: 'src/showcase/website', // Path to data on filesystem, relative to site dir. + path: 'showcase', // Path to data on filesystem, relative to site dir. routeBasePath: '/', // URL Route. include: ['**/*.{yml,yaml}'], // Extensions to include. exclude: GlobExcludeDefault, + tags: '@site/showcase/tags.yaml', }; +export const tagSchema = Joi.array().items( + Joi.object({ + label: Joi.string().required(), + description: Joi.object({ + message: Joi.string().required(), + id: Joi.string().required(), + }).required(), + color: Joi.string().required(), + }), +); + 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), + tags: Joi.alternatives() + .try(Joi.string().default(DEFAULT_OPTIONS.tags), tagSchema) + .default(DEFAULT_OPTIONS.tags), }); export function validateOptions({ diff --git a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index 5c0475f5ffcf..b17055bc80da 100644 --- a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -8,8 +8,13 @@ declare module '@docusaurus/plugin-content-showcase' { import type {LoadContext, Plugin} from '@docusaurus/types'; - export type Assets = { - image?: string; + export type TagOption = { + label: string; + description: { + message: string; + id: string; + }; + color: string; }; export type PluginOptions = { @@ -18,9 +23,10 @@ declare module '@docusaurus/plugin-content-showcase' { routeBasePath: string; include: string[]; exclude: string[]; + tags: string | TagOption[]; }; - export type TagType = + type TagType = | 'favorite' | 'opensource' | 'product' @@ -41,41 +47,14 @@ declare module '@docusaurus/plugin-content-showcase' { readonly tags: TagType[]; }; - export type Content = { - website: { - title: string; - description: string; - preview: string | null; // null = use our serverless screenshot service - website: string; - source: string | null; - sourcePath?: string; - tags: TagType[]; - }[]; + export type ShowcaseItem = { + items: ShowcaseFrontMatter[]; }; export type Options = Partial; - export default function pluginShowcase( + export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; - - export type ShowcaseMetadata = { - /** Path to the Markdown source, with `@site` alias. */ - readonly source: string; - /** - * Used to generate the page h1 heading, tab title, and pagination title. - */ - readonly title: string; - /** Full link including base URL. */ - readonly permalink: string; - /** - * Description used in the meta. Could be an empty string (empty content) - */ - readonly description: string; - /** Front matter, as-is. */ - readonly frontMatter: Content['website'][number] & {[key: string]: unknown}; - /** Tags, normalized. */ - readonly tags: TagType[]; - }; + ): Promise>; } diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index d35e90336d61..3c3e7cb6f092 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -248,9 +248,9 @@ declare module '@theme/BlogPostItems' { } declare module '@theme/ShowcaseDetails' { - import type {Content} from '@docusaurus/plugin-content-showcase'; + import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - export type User = Content['website'][number]; + export type User = ShowcaseItem['website'][number]; export type Props = { content: User; @@ -260,9 +260,9 @@ declare module '@theme/ShowcaseDetails' { } declare module '@theme/Showcase' { - import type {Content} from '@docusaurus/plugin-content-showcase'; + import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - export type User = Content['website'][number]; + export type User = ShowcaseItem['website'][number]; export type Props = { content: User[]; diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index ac82efa21851..74e97dd10b09 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -239,7 +239,7 @@ export default async function createConfigAsync() { ], themes: ['live-codeblock', ...dogfoodingThemeInstances], plugins: [ - 'showcase', + 'content-showcase', [ './src/plugins/changelog/index.js', { diff --git a/website/src/showcase/website/clem/clem.mdx b/website/showcase/clem/clem.mdx similarity index 100% rename from website/src/showcase/website/clem/clem.mdx rename to website/showcase/clem/clem.mdx diff --git a/website/src/showcase/website/dino.yaml b/website/showcase/dino.yaml similarity index 100% rename from website/src/showcase/website/dino.yaml rename to website/showcase/dino.yaml diff --git a/website/src/showcase/website/ozaki/ozaki.yaml b/website/showcase/ozaki/ozaki.yaml similarity index 100% rename from website/src/showcase/website/ozaki/ozaki.yaml rename to website/showcase/ozaki/ozaki.yaml diff --git a/website/src/showcase/website/seb.yaml b/website/showcase/seb.yaml similarity index 100% rename from website/src/showcase/website/seb.yaml rename to website/showcase/seb.yaml diff --git a/website/showcase/tags.yaml b/website/showcase/tags.yaml new file mode 100644 index 000000000000..ef7d2b839b3e --- /dev/null +++ b/website/showcase/tags.yaml @@ -0,0 +1,61 @@ +tags: + - favorite: + label: 'Favorite' + description: + message: 'Our favorite Docusaurus sites that you must absolutely check out!' + id: 'showcase.tag.favorite.description' + color: '#e9669e' + - opensource: + label: 'Open-Source' + description: + message: 'Open-Source Docusaurus sites can be useful for inspiration!' + id: 'showcase.tag.opensource.description' + color: '#39ca30' + - product: + label: 'Product' + description: + message: 'Docusaurus sites associated to a commercial product!' + id: 'showcase.tag.product.description' + color: '#dfd545' + - design: + label: 'Design' + description: + message: 'Beautiful Docusaurus sites, polished and standing out from the initial template!' + id: 'showcase.tag.design.description' + color: '#a44fb7' + - i18n: + label: 'I18n' + description: + message: 'Translated Docusaurus sites using the internationalization support with more than 1 locale.' + id: 'showcase.tag.i18n.description' + color: '#127f82' + - versioning: + label: 'Versioning' + description: + message: 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.' + id: 'showcase.tag.versioning.description' + color: '#fe6829' + - large: + label: 'Large' + description: + message: 'Very large Docusaurus sites, including many more pages than the average!' + id: 'showcase.tag.large.description' + color: '#8c2f00' + - meta: + label: 'Meta' + description: + message: 'Docusaurus sites of Meta (formerly Facebook) projects' + id: 'showcase.tag.meta.description' + color: '#4267b2' + - personal: + label: 'Personal' + description: + message: 'Personal websites, blogs and digital gardens built with Docusaurus' + id: 'showcase.tag.personal.description' + color: '#14cfc3' + - rtl: + label: 'RTL Direction' + description: + message: 'Docusaurus sites using the right-to-left reading direction support.' + id: 'showcase.tag.rtl.description' + color: '#ffcfc3' From 4a221f376805b738370b785afa562dd2f834f2d5 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 1 Apr 2024 13:33:50 +0200 Subject: [PATCH 25/79] wip working tags from yaml --- .../src/__tests__/index.test.ts | 3 + .../src/__tests__/options.test.ts | 14 +- .../src/index.ts | 31 ++--- .../src/options.ts | 3 +- .../src/plugin-content-showcase.d.ts | 12 +- website/showcase/dino.yaml | 2 +- website/showcase/tags.yaml | 121 +++++++++--------- 7 files changed, 92 insertions(+), 94 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts index 6669399f900f..7cebadbab571 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -12,6 +12,9 @@ import {normalizePluginOptions} from '@docusaurus/utils-validation'; import pluginContentPages from '../index'; import {validateOptions} from '../options'; +// todo add test with tags in config +// todo add test with tags in yaml + describe('docusaurus-plugin-content-showcase', () => { it('loads simple showcase', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts index 591eff07e721..0cef7b4711f8 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts @@ -16,6 +16,8 @@ const defaultOptions = { id: 'default', }; +// todo add test that validate and reject tags.yaml file + describe('normalizeShowcasePluginOptions', () => { it('returns default options for undefined user options', () => { expect(testValidate({})).toEqual(defaultOptions); @@ -89,12 +91,14 @@ describe('normalizeShowcasePluginOptions', () => { const userOptions = { tags: [ { - label: 'foo', - description: { - message: 'bar', - id: 'baz', + foo: { + label: 'foo', + description: { + message: 'bar', + id: 'baz', + }, + color: 'red', }, - color: 'red', }, ], }; diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 0559cd2744ba..19e8592b0693 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -31,33 +31,23 @@ export function getContentPathList( return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } -async function getTagsDefinition( - filePath: string | TagOption[], -): Promise { +async function getTagsList(filePath: string | TagOption[]): Promise { if (Array.isArray(filePath)) { - return filePath.map((tag) => tag.label); + return Object.keys(filePath); } const rawYaml = await fs.readFile(filePath, 'utf-8'); - const unsafeYaml: any = Yaml.load(rawYaml); - console.log('unsafeYaml:', unsafeYaml); - - const transformedData = unsafeYaml.tags.map((item: any) => { - const [label] = Object.keys(item); // Extract label from object key - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const {description, color} = item[label]; // Extract description and color - return {label, description, color}; // Create new object with transformed structure - }); - console.log('transformedData:', transformedData); - - const safeYaml = tagSchema.validate(transformedData); + const unsafeYaml = Yaml.load(rawYaml); + const safeYaml = tagSchema.validate(unsafeYaml); if (safeYaml.error) { - throw new Error(`Invalid tags.yaml file: ${safeYaml.error.message}`); + throw new Error( + `There was an error extracting tags: ${safeYaml.error.message}`, + {cause: safeYaml.error}, + ); } - const tagLabels = safeYaml.value.map((tag: any) => Object.keys(tag)[0]); + const tagLabels = Object.keys(safeYaml.value); return tagLabels; } @@ -132,9 +122,8 @@ export default function pluginContentShowcase( 'tags.yaml', ); - const tagList = await getTagsDefinition(tagFilePath); + const tagList = await getTagsList(tagFilePath); const createdTagSchema = createTagSchema(tagList); - console.log('createdTagSchema:', createdTagSchema.describe()); async function processShowcaseSourceFile(relativeSource: string) { // Lookup in localized folder in priority diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index f718d01fd9f8..7443fb61db28 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -19,7 +19,8 @@ export const DEFAULT_OPTIONS: PluginOptions = { tags: '@site/showcase/tags.yaml', }; -export const tagSchema = Joi.array().items( +export const tagSchema = Joi.object().pattern( + Joi.string(), Joi.object({ label: Joi.string().required(), description: Joi.object({ diff --git a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index b17055bc80da..90d031b10fa8 100644 --- a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -9,12 +9,14 @@ declare module '@docusaurus/plugin-content-showcase' { import type {LoadContext, Plugin} from '@docusaurus/types'; export type TagOption = { - label: string; - description: { - message: string; - id: string; + [key: string]: { + label: string; + description: { + message: string; + id: string; + }; + color: string; }; - color: string; }; export type PluginOptions = { diff --git a/website/showcase/dino.yaml b/website/showcase/dino.yaml index 2fa47aaa4348..c723f73c8df9 100644 --- a/website/showcase/dino.yaml +++ b/website/showcase/dino.yaml @@ -3,4 +3,4 @@ description: 'Docusaurus dinooooooo' preview: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Grosser_Panda.JPG/2560px-Grosser_Panda.JPG website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' -tags: ['opensource', 'design'] +tags: ['opensource', 'favorite'] diff --git a/website/showcase/tags.yaml b/website/showcase/tags.yaml index ef7d2b839b3e..53f4be9b4653 100644 --- a/website/showcase/tags.yaml +++ b/website/showcase/tags.yaml @@ -1,61 +1,60 @@ -tags: - - favorite: - label: 'Favorite' - description: - message: 'Our favorite Docusaurus sites that you must absolutely check out!' - id: 'showcase.tag.favorite.description' - color: '#e9669e' - - opensource: - label: 'Open-Source' - description: - message: 'Open-Source Docusaurus sites can be useful for inspiration!' - id: 'showcase.tag.opensource.description' - color: '#39ca30' - - product: - label: 'Product' - description: - message: 'Docusaurus sites associated to a commercial product!' - id: 'showcase.tag.product.description' - color: '#dfd545' - - design: - label: 'Design' - description: - message: 'Beautiful Docusaurus sites, polished and standing out from the initial template!' - id: 'showcase.tag.design.description' - color: '#a44fb7' - - i18n: - label: 'I18n' - description: - message: 'Translated Docusaurus sites using the internationalization support with more than 1 locale.' - id: 'showcase.tag.i18n.description' - color: '#127f82' - - versioning: - label: 'Versioning' - description: - message: 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.' - id: 'showcase.tag.versioning.description' - color: '#fe6829' - - large: - label: 'Large' - description: - message: 'Very large Docusaurus sites, including many more pages than the average!' - id: 'showcase.tag.large.description' - color: '#8c2f00' - - meta: - label: 'Meta' - description: - message: 'Docusaurus sites of Meta (formerly Facebook) projects' - id: 'showcase.tag.meta.description' - color: '#4267b2' - - personal: - label: 'Personal' - description: - message: 'Personal websites, blogs and digital gardens built with Docusaurus' - id: 'showcase.tag.personal.description' - color: '#14cfc3' - - rtl: - label: 'RTL Direction' - description: - message: 'Docusaurus sites using the right-to-left reading direction support.' - id: 'showcase.tag.rtl.description' - color: '#ffcfc3' +favorite: + label: 'Favorite' + description: + message: 'Our favorite Docusaurus sites that you must absolutely check out!' + id: 'showcase.tag.favorite.description' + color: '#e9669e' +opensource: + label: 'Open-Source' + description: + message: 'Open-Source Docusaurus sites can be useful for inspiration!' + id: 'showcase.tag.opensource.description' + color: '#39ca30' +product: + label: 'Product' + description: + message: 'Docusaurus sites associated to a commercial product!' + id: 'showcase.tag.product.description' + color: '#dfd545' +design: + label: 'Design' + description: + message: 'Beautiful Docusaurus sites, polished and standing out from the initial template!' + id: 'showcase.tag.design.description' + color: '#a44fb7' +i18n: + label: 'I18n' + description: + message: 'Translated Docusaurus sites using the internationalization support with more than 1 locale.' + id: 'showcase.tag.i18n.description' + color: '#127f82' +versioning: + label: 'Versioning' + description: + message: 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.' + id: 'showcase.tag.versioning.description' + color: '#fe6829' +large: + label: 'Large' + description: + message: 'Very large Docusaurus sites, including many more pages than the average!' + id: 'showcase.tag.large.description' + color: '#8c2f00' +meta: + label: 'Meta' + description: + message: 'Docusaurus sites of Meta (formerly Facebook) projects' + id: 'showcase.tag.meta.description' + color: '#4267b2' +personal: + label: 'Personal' + description: + message: 'Personal websites, blogs and digital gardens built with Docusaurus' + id: 'showcase.tag.personal.description' + color: '#14cfc3' +rtl: + label: 'RTL Direction' + description: + message: 'Docusaurus sites using the right-to-left reading direction support.' + id: 'showcase.tag.rtl.description' + color: '#ffcfc3' From fc34725b0b5db134e75cc975ddbd52fdd64259dd Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:30:08 +0200 Subject: [PATCH 26/79] prettier --- website/showcase/dino.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/showcase/dino.yaml b/website/showcase/dino.yaml index c723f73c8df9..57db57f1da44 100644 --- a/website/showcase/dino.yaml +++ b/website/showcase/dino.yaml @@ -1,5 +1,5 @@ -title: 'Dinosaur' -description: 'Docusaurus dinooooooo' +title: 'Docusaurus' +description: 'Docusaurus' preview: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Grosser_Panda.JPG/2560px-Grosser_Panda.JPG website: 'https://agile-ts.org/' source: 'https://github.com/agile-ts/documentation' From 6b83e2d153eac81095917e26fb79c45f4bd9d197 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:30:36 +0200 Subject: [PATCH 27/79] wip --- .../src/frontMatter.ts | 12 ++++ .../src/index.ts | 62 +++++++------------ .../src/options.ts | 4 +- 3 files changed, 36 insertions(+), 42 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts b/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts index dc3550f4cd17..8f2ed6dd442a 100644 --- a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts @@ -22,3 +22,15 @@ export function validateShowcaseFrontMatter(frontMatter: { }): ShowcaseFrontMatter { return validateFrontMatter(frontMatter, showcaseFrontMatterSchema); } + +export function validateFrontMatterTags( + frontMatterTags: string[], + tagListSchema: Joi.Schema, +): void { + const result = tagListSchema.validate(frontMatterTags); + if (result.error) { + throw new Error( + `Front matter contains invalid tags: ${result.error.message}`, + ); + } +} diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 19e8592b0693..d96c3e5f41fe 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -8,6 +8,7 @@ import fs from 'fs-extra'; import path from 'path'; import { + aliasedSitePathToRelativePath, getFolderContainingFile, getPluginI18nPath, Globby, @@ -15,7 +16,10 @@ import { import Yaml from 'js-yaml'; import {Joi} from '@docusaurus/utils-validation'; -import {validateShowcaseFrontMatter} from './frontMatter'; +import { + validateFrontMatterTags, + validateShowcaseFrontMatter, +} from './frontMatter'; import {tagSchema} from './options'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { @@ -31,12 +35,22 @@ export function getContentPathList( return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } +function createTagSchema(tags: string[]): Joi.Schema { + return Joi.alternatives().try( + Joi.string().valid(...tags), // Schema for single string + Joi.array().items(Joi.string().valid(...tags)), // Schema for array of strings + ); +} + async function getTagsList(filePath: string | TagOption[]): Promise { - if (Array.isArray(filePath)) { + if (typeof filePath === 'object') { return Object.keys(filePath); } - const rawYaml = await fs.readFile(filePath, 'utf-8'); + const rawYaml = await fs.readFile( + aliasedSitePathToRelativePath(filePath), + 'utf-8', + ); const unsafeYaml = Yaml.load(rawYaml); const safeYaml = tagSchema.validate(unsafeYaml); @@ -51,25 +65,6 @@ async function getTagsList(filePath: string | TagOption[]): Promise { return tagLabels; } -function createTagSchema(tags: string[]): Joi.Schema { - return Joi.alternatives().try( - Joi.string().valid(...tags), // Schema for single string - Joi.array().items(Joi.string().valid(...tags)), // Schema for array of strings - ); -} - -function validateFrontMatterTags( - frontMatterTags: string[], - tagListSchema: Joi.Schema, -): void { - const result = tagListSchema.validate(frontMatterTags); - if (result.error) { - throw new Error( - `Front matter contains invalid tags: ${result.error.message}`, - ); - } -} - export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, @@ -97,32 +92,18 @@ export default function pluginContentShowcase( // }, async loadContent(): Promise { - const {include} = options; - if (!(await fs.pathExists(contentPaths.contentPath))) { return null; } - // const {baseUrl} = siteConfig; + const {include} = options; + const showcaseFiles = await Globby(include, { cwd: contentPaths.contentPath, ignore: options.exclude, }); - const filteredShowcaseFiles = showcaseFiles.filter( - (source) => source !== 'tags.yaml', - ); - - // todo refactor ugly - const tagFilePath = path.join( - await getFolderContainingFile( - getContentPathList(contentPaths), - 'tags.yaml', - ), - 'tags.yaml', - ); - - const tagList = await getTagsList(tagFilePath); + const tagList = await getTagsList(options.tags); const createdTagSchema = createTagSchema(tagList); async function processShowcaseSourceFile(relativeSource: string) { @@ -135,6 +116,7 @@ export default function pluginContentShowcase( const sourcePath = path.join(contentPath, relativeSource); const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + // todo remove as ... because bad practice ? const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; const yaml = validateShowcaseFrontMatter(unsafeYaml); @@ -156,7 +138,7 @@ export default function pluginContentShowcase( return { items: await Promise.all( - filteredShowcaseFiles.map(doProcessShowcaseSourceFile), + showcaseFiles.map(doProcessShowcaseSourceFile), ), }; }, diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index 7443fb61db28..3c972324202c 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -14,8 +14,8 @@ export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', path: 'showcase', // Path to data on filesystem, relative to site dir. routeBasePath: '/', // URL Route. - include: ['**/*.{yml,yaml}'], // Extensions to include. - exclude: GlobExcludeDefault, + include: ['**/*.{yml,yaml}'], + exclude: [...GlobExcludeDefault, 'tags.*'], tags: '@site/showcase/tags.yaml', }; From c3f32ab781d2a32b66babc51a16473ff9c6e546f Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:46:51 +0200 Subject: [PATCH 28/79] wip --- .../docusaurus-plugin-content-showcase/src/frontMatter.ts | 6 +++--- packages/docusaurus-plugin-content-showcase/src/index.ts | 2 +- website/docusaurus.config.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts b/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts index 8f2ed6dd442a..3a1b35a8eda7 100644 --- a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts @@ -29,8 +29,8 @@ export function validateFrontMatterTags( ): void { const result = tagListSchema.validate(frontMatterTags); if (result.error) { - throw new Error( - `Front matter contains invalid tags: ${result.error.message}`, - ); + throw new Error(`Front matter contains invalid tags`, { + cause: result.error, + }); } } diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index d96c3e5f41fe..0d6b33d969f5 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -131,7 +131,7 @@ export default function pluginContentShowcase( } catch (err) { throw new Error( `Processing of page source file path=${relativeSource} failed.`, - {cause: err as Error}, + {cause: err}, ); } } diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 74e97dd10b09..897b24e70565 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -239,7 +239,7 @@ export default async function createConfigAsync() { ], themes: ['live-codeblock', ...dogfoodingThemeInstances], plugins: [ - 'content-showcase', + ['content-showcase', {}], [ './src/plugins/changelog/index.js', { From 8ab6ddac549768a0538fb2bdaf04c726d53c59f5 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 1 Apr 2024 18:06:14 +0200 Subject: [PATCH 29/79] wip --- .../src/showcase/{site.yml => site.yaml} | 0 .../website/src/showcase/tags.yaml | 12 ++++++ .../src/__tests__/index.test.ts | 1 + .../src/__tests__/options.test.ts | 41 ++++++++++--------- .../src/index.ts | 3 ++ 5 files changed, 38 insertions(+), 19 deletions(-) rename packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/{site.yml => site.yaml} (100%) create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/tags.yaml diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yaml similarity index 100% rename from packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml rename to packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yaml diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/tags.yaml b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/tags.yaml new file mode 100644 index 000000000000..dc6e89b9877a --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/tags.yaml @@ -0,0 +1,12 @@ +opensource: + label: "Open-Source" + description: + message: "Open-Source Docusaurus sites can be useful for inspiration!" + id: "showcase.tag.opensource.description" + color: "#39ca30" +meta: + label: "Meta" + description: + message: "Docusaurus sites of Meta (formerly Facebook) projects" + id: "showcase.tag.meta.description" + color: "#4267b2" diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts index 7cebadbab571..f5b22f535f83 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -24,6 +24,7 @@ describe('docusaurus-plugin-content-showcase', () => { validateOptions({ validate: normalizePluginOptions, options: { + // todo broken because we use aliasedPaths path: 'src/showcase', }, }), diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts index 0cef7b4711f8..eea346373211 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts @@ -83,25 +83,25 @@ describe('normalizeShowcasePluginOptions', () => { userOptions, ), ).toThrowErrorMatchingInlineSnapshot( - `""tags" must be one of [string, array]"`, + `""tags" must be one of [string, object]"`, ); }); it('accepts correctly defined tags object options', () => { const userOptions = { - tags: [ - { - foo: { - label: 'foo', - description: { - message: 'bar', - id: 'baz', - }, - color: 'red', + tags: { + favorite: { + label: 'Favorite', + description: { + message: + 'Our favorite Docusaurus sites that you must absolutely check out!', + id: 'showcase.tag.favorite.description', }, + color: '#e9669e', }, - ], + }, }; + // todo fix ts error expect(testValidate(userOptions)).toEqual({ ...defaultOptions, ...userOptions, @@ -110,22 +110,25 @@ describe('normalizeShowcasePluginOptions', () => { it('reject bedly defined tags object options', () => { const userOptions = { - tags: [ - { - label: 'foo', + tags: { + favorite: { + label: 32, description: { - message: 'bar', - id: 'baz', + message: + 'Our favorite Docusaurus sites that you must absolutely check out!', + id: 'showcase.tag.favorite.description', }, - color: 42, + color: '#e9669e', }, - ], + }, }; expect(() => testValidate( // @ts-expect-error: bad attributes userOptions, ), - ).toThrowErrorMatchingInlineSnapshot(`""tags[0].color" must be a string"`); + ).toThrowErrorMatchingInlineSnapshot( + `""tags.favorite.label" must be a string"`, + ); }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 0d6b33d969f5..371774fe2dbf 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -48,6 +48,8 @@ async function getTagsList(filePath: string | TagOption[]): Promise { } const rawYaml = await fs.readFile( + // todo should we use aliasedPath ? + // because it breaks tests showcase/index.test.ts#L27-L28 aliasedSitePathToRelativePath(filePath), 'utf-8', ); @@ -95,6 +97,7 @@ export default function pluginContentShowcase( if (!(await fs.pathExists(contentPaths.contentPath))) { return null; } + console.log('contentPaths:', contentPaths); const {include} = options; From 45af11fd134226efad9b5c9599a7cf4a274e05a2 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:59:26 +0200 Subject: [PATCH 30/79] add tests --- .../__snapshots__/index.test.ts.snap | 18 ++++++ .../src/__tests__/frontMatter.test.ts | 10 ++- .../src/__tests__/index.test.ts | 62 +++++++++++++++++-- .../src/index.ts | 55 ++++++++-------- .../src/options.ts | 2 +- 5 files changed, 113 insertions(+), 34 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap index 097bb675fed3..512898564a27 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap @@ -17,3 +17,21 @@ exports[`docusaurus-plugin-content-showcase loads simple showcase 1`] = ` ], } `; + +exports[`docusaurus-plugin-content-showcase loads simple showcase with tags in options 1`] = ` +{ + "items": [ + { + "description": "World", + "preview": "github.com/ozakione.png", + "source": "https://github.com/facebook/docusaurus", + "tags": [ + "opensource", + "meta", + ], + "title": "Hello", + "website": "https://docusaurus.io/", + }, + ], +} +`; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts index 6de639727f02..ab394f129b14 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -45,7 +45,7 @@ function testField(params: { try { validateShowcaseFrontMatter(frontMatter); throw new Error( - `Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( + `Showcase front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( frontMatter, null, 2, @@ -61,7 +61,7 @@ function testField(params: { }); } -describe('doc front matter schema', () => { +describe('showcase front matter schema', () => { it('accepts valid frontmatter', () => { const frontMatter: ShowcaseFrontMatter = { title: 'title', @@ -97,5 +97,11 @@ describe('validateShowcaseFrontMatter full', () => { website: 'website', }, ], + invalidFrontMatters: [ + [ + {}, + '"title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required', + ], + ], }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts index f5b22f535f83..97bd15c7583c 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -12,9 +12,6 @@ import {normalizePluginOptions} from '@docusaurus/utils-validation'; import pluginContentPages from '../index'; import {validateOptions} from '../options'; -// todo add test with tags in config -// todo add test with tags in yaml - describe('docusaurus-plugin-content-showcase', () => { it('loads simple showcase', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); @@ -24,8 +21,8 @@ describe('docusaurus-plugin-content-showcase', () => { validateOptions({ validate: normalizePluginOptions, options: { - // todo broken because we use aliasedPaths path: 'src/showcase', + tags: 'tags.yaml', }, }), ); @@ -33,4 +30,61 @@ describe('docusaurus-plugin-content-showcase', () => { expect(showcaseMetadata).toMatchSnapshot(); }); + + it('loads simple showcase with tags in options', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const context = await loadContext({siteDir}); + const plugin = pluginContentPages( + context, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/showcase', + tags: { + opensource: { + label: 'Open-Source', + description: { + message: + 'Open-Source Docusaurus sites can be useful for inspiration!', + id: 'showcase.tag.opensource.description', + }, + color: '#39ca30', + }, + meta: { + label: 'Meta', + description: { + message: + 'Docusaurus sites of Meta (formerly Facebook) projects', + id: 'showcase.tag.meta.description', + }, + color: '#4267b2', + }, + }, + }, + }), + ); + const showcaseMetadata = await plugin.loadContent!(); + + expect(showcaseMetadata).toMatchSnapshot(); + }); + + it('throw loading inexistant tags file', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const context = await loadContext({siteDir}); + const plugin = pluginContentPages( + context, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/showcase', + tags: 'wrong.yaml', + }, + }), + ); + await expect( + plugin.loadContent!(), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to read tags file for showcase"`, + ); + }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 371774fe2dbf..7b372479df09 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -8,7 +8,6 @@ import fs from 'fs-extra'; import path from 'path'; import { - aliasedSitePathToRelativePath, getFolderContainingFile, getPluginI18nPath, Globby, @@ -42,31 +41,6 @@ function createTagSchema(tags: string[]): Joi.Schema { ); } -async function getTagsList(filePath: string | TagOption[]): Promise { - if (typeof filePath === 'object') { - return Object.keys(filePath); - } - - const rawYaml = await fs.readFile( - // todo should we use aliasedPath ? - // because it breaks tests showcase/index.test.ts#L27-L28 - aliasedSitePathToRelativePath(filePath), - 'utf-8', - ); - const unsafeYaml = Yaml.load(rawYaml); - const safeYaml = tagSchema.validate(unsafeYaml); - - if (safeYaml.error) { - throw new Error( - `There was an error extracting tags: ${safeYaml.error.message}`, - {cause: safeYaml.error}, - ); - } - - const tagLabels = Object.keys(safeYaml.value); - return tagLabels; -} - export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, @@ -82,6 +56,34 @@ export default function pluginContentShowcase( }), }; + async function getTagsList( + configTags: string | TagOption[], + ): Promise { + if (typeof configTags === 'object') { + return Object.keys(configTags); + } + + const tagsPath = path.resolve(contentPaths.contentPath, configTags); + + try { + const rawYaml = await fs.readFile(tagsPath, 'utf-8'); + const unsafeYaml = Yaml.load(rawYaml); + const safeYaml = tagSchema.validate(unsafeYaml); + + if (safeYaml.error) { + throw new Error( + `There was an error extracting tags: ${safeYaml.error.message}`, + {cause: safeYaml.error}, + ); + } + + const tagLabels = Object.keys(safeYaml.value); + return tagLabels; + } catch (error) { + throw new Error(`Failed to read tags file for showcase`, {cause: error}); + } + } + return { name: 'docusaurus-plugin-content-showcase', @@ -97,7 +99,6 @@ export default function pluginContentShowcase( if (!(await fs.pathExists(contentPaths.contentPath))) { return null; } - console.log('contentPaths:', contentPaths); const {include} = options; diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index 3c972324202c..b4b197f0eab9 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -16,7 +16,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { routeBasePath: '/', // URL Route. include: ['**/*.{yml,yaml}'], exclude: [...GlobExcludeDefault, 'tags.*'], - tags: '@site/showcase/tags.yaml', + tags: 'tags.yaml', }; export const tagSchema = Joi.object().pattern( From a9d6bcf968dd69a2e50891538bdfcf64d771dac4 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:58:53 +0200 Subject: [PATCH 31/79] wip --- .../__fixtures__/website/docusaurus.config.js | 7 -- .../src/__tests__/frontMatter.test.ts | 90 +++---------------- .../src/index.ts | 73 +++++---------- .../src/markdownLoader.ts | 22 ----- .../src/options.ts | 3 +- .../src/plugin-content-showcase.d.ts | 42 ++++----- .../src/tags.ts | 45 ++++++++++ .../src/{frontMatter.ts => validation.ts} | 10 +-- website/docusaurus.config.ts | 7 +- 9 files changed, 107 insertions(+), 192 deletions(-) delete mode 100644 packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/tags.ts rename packages/docusaurus-plugin-content-showcase/src/{frontMatter.ts => validation.ts} (74%) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js index d048d2caf5a1..ae48be19a450 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -11,11 +11,4 @@ module.exports = { url: 'https://your-docusaurus-site.example.com', baseUrl: '/', favicon: 'img/favicon.ico', - markdown: { - parseFrontMatter: async (params) => { - const result = await params.defaultParseFrontMatter(params); - result.frontMatter.custom_frontMatter = 'added by parseFrontMatter'; - return result; - }, - }, }; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts index ab394f129b14..1cc8787eef2c 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -5,65 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import {escapeRegexp} from '@docusaurus/utils'; -import {validateShowcaseFrontMatter} from '../frontMatter'; -import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; - -function testField(params: { - prefix: string; - validFrontMatters: ShowcaseFrontMatter[]; - convertibleFrontMatter?: [ - ConvertibleFrontMatter: {[key: string]: unknown}, - ConvertedFrontMatter: ShowcaseFrontMatter, - ][]; - invalidFrontMatters?: [ - InvalidFrontMatter: {[key: string]: unknown}, - ErrorMessage: string, - ][]; -}) { - // eslint-disable-next-line jest/require-top-level-describe - test(`[${params.prefix}] accept valid values`, () => { - params.validFrontMatters.forEach((frontMatter) => { - expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); - }); - }); - - // eslint-disable-next-line jest/require-top-level-describe - test(`[${params.prefix}] convert valid values`, () => { - params.convertibleFrontMatter?.forEach( - ([convertibleFrontMatter, convertedFrontMatter]) => { - expect(validateShowcaseFrontMatter(convertibleFrontMatter)).toEqual( - convertedFrontMatter, - ); - }, - ); - }); - - // eslint-disable-next-line jest/require-top-level-describe - test(`[${params.prefix}] throw error for values`, () => { - params.invalidFrontMatters?.forEach(([frontMatter, message]) => { - try { - validateShowcaseFrontMatter(frontMatter); - throw new Error( - `Showcase front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( - frontMatter, - null, - 2, - )}`, - ); - } catch (err) { - // eslint-disable-next-line jest/no-conditional-expect - expect((err as Error).message).toMatch( - new RegExp(escapeRegexp(message)), - ); - } - }); - }); -} +import {validateShowcaseItem} from '../validation'; +import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; describe('showcase front matter schema', () => { it('accepts valid frontmatter', () => { - const frontMatter: ShowcaseFrontMatter = { + const frontMatter: ShowcaseItem = { title: 'title', description: 'description', preview: 'preview', @@ -71,37 +18,24 @@ describe('showcase front matter schema', () => { tags: [], website: 'website', }; - expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + expect(validateShowcaseItem(frontMatter)).toEqual(frontMatter); }); it('reject invalid frontmatter', () => { const frontMatter = {}; expect(() => - validateShowcaseFrontMatter(frontMatter), + validateShowcaseItem(frontMatter), ).toThrowErrorMatchingInlineSnapshot( `""title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, ); }); -}); -describe('validateShowcaseFrontMatter full', () => { - testField({ - prefix: 'valid full frontmatter', - validFrontMatters: [ - { - title: 'title', - description: 'description', - preview: 'preview', - source: 'source', - tags: [], - website: 'website', - }, - ], - invalidFrontMatters: [ - [ - {}, - '"title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required', - ], - ], + it('reject invalid frontmatter value', () => { + const frontMatter = {title: 42}; + expect(() => + validateShowcaseItem(frontMatter), + ).toThrowErrorMatchingInlineSnapshot( + `""title" must be a string. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, + ); }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 7b372479df09..193634b7474c 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -13,18 +13,13 @@ import { Globby, } from '@docusaurus/utils'; import Yaml from 'js-yaml'; - import {Joi} from '@docusaurus/utils-validation'; -import { - validateFrontMatterTags, - validateShowcaseFrontMatter, -} from './frontMatter'; -import {tagSchema} from './options'; +import {validateFrontMatterTags, validateShowcaseItem} from './validation'; +import {getTagsList} from './tags'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { PluginOptions, - ShowcaseItem, - TagOption, + ShowcaseItems, } from '@docusaurus/plugin-content-showcase'; import type {ShowcaseContentPaths} from './types'; @@ -35,55 +30,24 @@ export function getContentPathList( } function createTagSchema(tags: string[]): Joi.Schema { - return Joi.alternatives().try( - Joi.string().valid(...tags), // Schema for single string - Joi.array().items(Joi.string().valid(...tags)), // Schema for array of strings - ); + return Joi.array().items(Joi.string().valid(...tags)); // Schema for array of strings } export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, -): Plugin { +): Plugin { const {siteDir, localizationDir} = context; const contentPaths: ShowcaseContentPaths = { contentPath: path.resolve(siteDir, options.path), contentPathLocalized: getPluginI18nPath({ localizationDir, - pluginName: 'docusaurus-plugin-content-pages', + pluginName: 'docusaurus-plugin-content-showcase', pluginId: options.id, }), }; - async function getTagsList( - configTags: string | TagOption[], - ): Promise { - if (typeof configTags === 'object') { - return Object.keys(configTags); - } - - const tagsPath = path.resolve(contentPaths.contentPath, configTags); - - try { - const rawYaml = await fs.readFile(tagsPath, 'utf-8'); - const unsafeYaml = Yaml.load(rawYaml); - const safeYaml = tagSchema.validate(unsafeYaml); - - if (safeYaml.error) { - throw new Error( - `There was an error extracting tags: ${safeYaml.error.message}`, - {cause: safeYaml.error}, - ); - } - - const tagLabels = Object.keys(safeYaml.value); - return tagLabels; - } catch (error) { - throw new Error(`Failed to read tags file for showcase`, {cause: error}); - } - } - return { name: 'docusaurus-plugin-content-showcase', @@ -95,19 +59,24 @@ export default function pluginContentShowcase( // ); // }, - async loadContent(): Promise { + async loadContent(): Promise { if (!(await fs.pathExists(contentPaths.contentPath))) { - return null; + throw new Error( + `The showcase content path does not exist: ${contentPaths.contentPath}`, + ); } const {include} = options; const showcaseFiles = await Globby(include, { cwd: contentPaths.contentPath, - ignore: options.exclude, + ignore: [...options.exclude], }); - const tagList = await getTagsList(options.tags); + const tagList = await getTagsList({ + configTags: options.tags, + configPath: contentPaths.contentPath, + }); const createdTagSchema = createTagSchema(tagList); async function processShowcaseSourceFile(relativeSource: string) { @@ -119,14 +88,14 @@ export default function pluginContentShowcase( const sourcePath = path.join(contentPath, relativeSource); - const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + const data = await fs.readFile(sourcePath, 'utf-8'); // todo remove as ... because bad practice ? - const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; - const yaml = validateShowcaseFrontMatter(unsafeYaml); + const unsafeData = Yaml.load(data) as {[key: string]: unknown}; + const showcaseItem = validateShowcaseItem(unsafeData); - validateFrontMatterTags(yaml.tags, createdTagSchema); + validateFrontMatterTags(showcaseItem.tags, createdTagSchema); - return yaml; + return showcaseItem; } async function doProcessShowcaseSourceFile(relativeSource: string) { @@ -160,7 +129,7 @@ export default function pluginContentShowcase( ); addRoute({ - path: '/showcaseAll', + path: options.routeBasePath, component: '@theme/Showcase', modules: { content: showcaseAllData, diff --git a/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts b/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts deleted file mode 100644 index e5c91b7bf797..000000000000 --- a/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index b4b197f0eab9..39b347020dbe 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -13,8 +13,9 @@ import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', path: 'showcase', // Path to data on filesystem, relative to site dir. - routeBasePath: '/', // URL Route. + routeBasePath: '/showcase', // URL Route. include: ['**/*.{yml,yaml}'], + // todo exclude won't work if user pass a custom file name exclude: [...GlobExcludeDefault, 'tags.*'], tags: 'tags.yaml', }; diff --git a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index 90d031b10fa8..1a822bd5b32b 100644 --- a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -8,15 +8,17 @@ declare module '@docusaurus/plugin-content-showcase' { import type {LoadContext, Plugin} from '@docusaurus/types'; - export type TagOption = { - [key: string]: { - label: string; - description: { - message: string; - id: string; - }; - color: string; + type Tag = { + label: string; + description: { + message: string; + id: string; }; + color: string; + }; + + export type TagsOption = { + [tagName: string]: Tag; }; export type PluginOptions = { @@ -25,32 +27,20 @@ declare module '@docusaurus/plugin-content-showcase' { routeBasePath: string; include: string[]; exclude: string[]; - tags: string | TagOption[]; + tags: string | TagsOption; }; - type TagType = - | 'favorite' - | 'opensource' - | 'product' - | 'design' - | 'i18n' - | 'versioning' - | 'large' - | 'meta' - | 'personal' - | 'rtl'; - - export type ShowcaseFrontMatter = { + export type ShowcaseItem = { readonly title: string; readonly description: string; readonly preview: string | null; // null = use our serverless screenshot service readonly website: string; readonly source: string | null; - readonly tags: TagType[]; + readonly tags: string[]; }; - export type ShowcaseItem = { - items: ShowcaseFrontMatter[]; + export type ShowcaseItems = { + items: ShowcaseItem[]; }; export type Options = Partial; @@ -58,5 +48,5 @@ declare module '@docusaurus/plugin-content-showcase' { export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; + ): Promise>; } diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts new file mode 100644 index 000000000000..8fef1caef42a --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -0,0 +1,45 @@ +/** + * 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 fs from 'fs-extra'; +import path from 'path'; +import Yaml from 'js-yaml'; +import {tagSchema} from './options'; +import type {TagsOption} from '@docusaurus/plugin-content-showcase'; + +// todo extract in another file +export async function getTagsList({ + configTags, + configPath, +}: { + configTags: string | TagsOption; + configPath: string; +}): Promise { + if (typeof configTags === 'object') { + return Object.keys(configTags); + } + + const tagsPath = path.resolve(configPath, configTags); + + try { + const data = await fs.readFile(tagsPath, 'utf-8'); + const unsafeData = Yaml.load(data); + const tags = tagSchema.validate(unsafeData); + + if (tags.error) { + throw new Error( + `There was an error extracting tags: ${tags.error.message}`, + {cause: tags.error}, + ); + } + + const tagLabels = Object.keys(tags.value); + return tagLabels; + } catch (error) { + throw new Error(`Failed to read tags file for showcase`, {cause: error}); + } +} diff --git a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts b/packages/docusaurus-plugin-content-showcase/src/validation.ts similarity index 74% rename from packages/docusaurus-plugin-content-showcase/src/frontMatter.ts rename to packages/docusaurus-plugin-content-showcase/src/validation.ts index 3a1b35a8eda7..70fd10a697d8 100644 --- a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-showcase/src/validation.ts @@ -6,9 +6,9 @@ */ import {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; -import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; +import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; -const showcaseFrontMatterSchema = Joi.object({ +const showcaseItemSchema = Joi.object({ title: Joi.string().required(), description: Joi.string().required(), preview: Joi.string().required(), @@ -17,10 +17,10 @@ const showcaseFrontMatterSchema = Joi.object({ tags: Joi.array().items(Joi.string()).required(), }); -export function validateShowcaseFrontMatter(frontMatter: { +export function validateShowcaseItem(frontMatter: { [key: string]: unknown; -}): ShowcaseFrontMatter { - return validateFrontMatter(frontMatter, showcaseFrontMatterSchema); +}): ShowcaseItem { + return validateFrontMatter(frontMatter, showcaseItemSchema); } export function validateFrontMatterTags( diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 897b24e70565..094cecb3de5a 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -239,7 +239,12 @@ export default async function createConfigAsync() { ], themes: ['live-codeblock', ...dogfoodingThemeInstances], plugins: [ - ['content-showcase', {}], + [ + 'content-showcase', + { + routeBasePath: '/showcaseAll', + }, + ], [ './src/plugins/changelog/index.js', { From 91aa292d26efb4fe4964ff67c7af4065a5161615 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 4 Apr 2024 18:07:04 +0200 Subject: [PATCH 32/79] improve type --- packages/docusaurus-plugin-content-showcase/src/index.ts | 4 +--- packages/docusaurus-plugin-content-showcase/src/validation.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 193634b7474c..bef38394c8f6 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -87,10 +87,8 @@ export default function pluginContentShowcase( ); const sourcePath = path.join(contentPath, relativeSource); - const data = await fs.readFile(sourcePath, 'utf-8'); - // todo remove as ... because bad practice ? - const unsafeData = Yaml.load(data) as {[key: string]: unknown}; + const unsafeData = Yaml.load(data); const showcaseItem = validateShowcaseItem(unsafeData); validateFrontMatterTags(showcaseItem.tags, createdTagSchema); diff --git a/packages/docusaurus-plugin-content-showcase/src/validation.ts b/packages/docusaurus-plugin-content-showcase/src/validation.ts index 70fd10a697d8..e389dfad197e 100644 --- a/packages/docusaurus-plugin-content-showcase/src/validation.ts +++ b/packages/docusaurus-plugin-content-showcase/src/validation.ts @@ -17,9 +17,7 @@ const showcaseItemSchema = Joi.object({ tags: Joi.array().items(Joi.string()).required(), }); -export function validateShowcaseItem(frontMatter: { - [key: string]: unknown; -}): ShowcaseItem { +export function validateShowcaseItem(frontMatter: unknown): ShowcaseItem { return validateFrontMatter(frontMatter, showcaseItemSchema); } From 4cecd0c5dee8a5685e648adee1091973061804aa Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:11:27 +0200 Subject: [PATCH 33/79] wip --- .../src/__tests__/frontMatter.test.ts | 11 ++++--- .../src/index.ts | 29 ++++++++----------- .../src/options.ts | 2 +- .../src/tags.ts | 6 +++- .../src/validation.ts | 20 ++++++++----- website/showcase/clem/clem.mdx | 16 ---------- website/showcase/{dino.yaml => dino.yml} | 0 .../showcase/ozaki/{ozaki.yaml => ozaki.yml} | 0 website/showcase/{seb.yaml => seb.yml} | 0 website/showcase/{tags.yaml => tags.yml} | 0 10 files changed, 35 insertions(+), 49 deletions(-) delete mode 100644 website/showcase/clem/clem.mdx rename website/showcase/{dino.yaml => dino.yml} (100%) rename website/showcase/ozaki/{ozaki.yaml => ozaki.yml} (100%) rename website/showcase/{seb.yaml => seb.yml} (100%) rename website/showcase/{tags.yaml => tags.yml} (100%) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts index 1cc8787eef2c..bd97ead99569 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -8,9 +8,10 @@ import {validateShowcaseItem} from '../validation'; import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; +// todo broken describe('showcase front matter schema', () => { it('accepts valid frontmatter', () => { - const frontMatter: ShowcaseItem = { + const item: ShowcaseItem = { title: 'title', description: 'description', preview: 'preview', @@ -18,24 +19,22 @@ describe('showcase front matter schema', () => { tags: [], website: 'website', }; - expect(validateShowcaseItem(frontMatter)).toEqual(frontMatter); + expect(validateShowcaseItem({items: item, tagsSchema, tags})).toEqual(item); }); - it('reject invalid frontmatter', () => { const frontMatter = {}; expect(() => validateShowcaseItem(frontMatter), ).toThrowErrorMatchingInlineSnapshot( - `""title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, + `"Cannot read properties of undefined (reading 'validate')"`, ); }); - it('reject invalid frontmatter value', () => { const frontMatter = {title: 42}; expect(() => validateShowcaseItem(frontMatter), ).toThrowErrorMatchingInlineSnapshot( - `""title" must be a string. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, + `"Cannot read properties of undefined (reading 'validate')"`, ); }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index bef38394c8f6..54f3e957cebc 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -13,8 +13,7 @@ import { Globby, } from '@docusaurus/utils'; import Yaml from 'js-yaml'; -import {Joi} from '@docusaurus/utils-validation'; -import {validateFrontMatterTags, validateShowcaseItem} from './validation'; +import {validateShowcaseItem} from './validation'; import {getTagsList} from './tags'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { @@ -29,18 +28,16 @@ export function getContentPathList( return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } -function createTagSchema(tags: string[]): Joi.Schema { - return Joi.array().items(Joi.string().valid(...tags)); // Schema for array of strings -} - export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, ): Plugin { const {siteDir, localizationDir} = context; + // todo check for better naming of path: sitePath + const {include, exclude, tags, routeBasePath, path: sitePath} = options; const contentPaths: ShowcaseContentPaths = { - contentPath: path.resolve(siteDir, options.path), + contentPath: path.resolve(siteDir, sitePath), contentPathLocalized: getPluginI18nPath({ localizationDir, pluginName: 'docusaurus-plugin-content-showcase', @@ -66,18 +63,15 @@ export default function pluginContentShowcase( ); } - const {include} = options; - const showcaseFiles = await Globby(include, { cwd: contentPaths.contentPath, - ignore: [...options.exclude], + ignore: [...exclude], }); const tagList = await getTagsList({ - configTags: options.tags, + configTags: tags, configPath: contentPaths.contentPath, }); - const createdTagSchema = createTagSchema(tagList); async function processShowcaseSourceFile(relativeSource: string) { // Lookup in localized folder in priority @@ -88,10 +82,11 @@ export default function pluginContentShowcase( const sourcePath = path.join(contentPath, relativeSource); const data = await fs.readFile(sourcePath, 'utf-8'); - const unsafeData = Yaml.load(data); - const showcaseItem = validateShowcaseItem(unsafeData); - - validateFrontMatterTags(showcaseItem.tags, createdTagSchema); + const item = Yaml.load(data); + const showcaseItem = validateShowcaseItem({ + item, + tags: tagList, + }); return showcaseItem; } @@ -127,7 +122,7 @@ export default function pluginContentShowcase( ); addRoute({ - path: options.routeBasePath, + path: routeBasePath, component: '@theme/Showcase', modules: { content: showcaseAllData, diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index 39b347020dbe..44a1caa58fac 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -17,7 +17,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { include: ['**/*.{yml,yaml}'], // todo exclude won't work if user pass a custom file name exclude: [...GlobExcludeDefault, 'tags.*'], - tags: 'tags.yaml', + tags: 'tags.yml', }; export const tagSchema = Joi.object().pattern( diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index 8fef1caef42a..554b9aa77990 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -8,10 +8,10 @@ import fs from 'fs-extra'; import path from 'path'; import Yaml from 'js-yaml'; +import {Joi} from '@docusaurus/utils-validation'; import {tagSchema} from './options'; import type {TagsOption} from '@docusaurus/plugin-content-showcase'; -// todo extract in another file export async function getTagsList({ configTags, configPath, @@ -43,3 +43,7 @@ export async function getTagsList({ throw new Error(`Failed to read tags file for showcase`, {cause: error}); } } + +export function createTagSchema(tags: string[]): Joi.Schema { + return Joi.array().items(Joi.string().valid(...tags)); // Schema for array of strings +} diff --git a/packages/docusaurus-plugin-content-showcase/src/validation.ts b/packages/docusaurus-plugin-content-showcase/src/validation.ts index e389dfad197e..c459a2646fe0 100644 --- a/packages/docusaurus-plugin-content-showcase/src/validation.ts +++ b/packages/docusaurus-plugin-content-showcase/src/validation.ts @@ -6,6 +6,7 @@ */ import {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; +import {createTagSchema} from './tags'; import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; const showcaseItemSchema = Joi.object({ @@ -17,18 +18,21 @@ const showcaseItemSchema = Joi.object({ tags: Joi.array().items(Joi.string()).required(), }); -export function validateShowcaseItem(frontMatter: unknown): ShowcaseItem { - return validateFrontMatter(frontMatter, showcaseItemSchema); -} +export function validateShowcaseItem({ + item, + tags, +}: { + item: unknown; + tags: string[]; +}): ShowcaseItem { + const tagsSchema = createTagSchema(tags); -export function validateFrontMatterTags( - frontMatterTags: string[], - tagListSchema: Joi.Schema, -): void { - const result = tagListSchema.validate(frontMatterTags); + const result = tagsSchema.validate(tags); if (result.error) { throw new Error(`Front matter contains invalid tags`, { cause: result.error, }); } + + return validateFrontMatter(item, showcaseItemSchema); } diff --git a/website/showcase/clem/clem.mdx b/website/showcase/clem/clem.mdx deleted file mode 100644 index f1969dda067f..000000000000 --- a/website/showcase/clem/clem.mdx +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Clement; -description: Description from frontmatter -preview: https://github.com/ozakione.png -website: https://github.com/ozakione -source: source -tags: - - favorite - - opensource ---- - -# Hello - -- some test - -text diff --git a/website/showcase/dino.yaml b/website/showcase/dino.yml similarity index 100% rename from website/showcase/dino.yaml rename to website/showcase/dino.yml diff --git a/website/showcase/ozaki/ozaki.yaml b/website/showcase/ozaki/ozaki.yml similarity index 100% rename from website/showcase/ozaki/ozaki.yaml rename to website/showcase/ozaki/ozaki.yml diff --git a/website/showcase/seb.yaml b/website/showcase/seb.yml similarity index 100% rename from website/showcase/seb.yaml rename to website/showcase/seb.yml diff --git a/website/showcase/tags.yaml b/website/showcase/tags.yml similarity index 100% rename from website/showcase/tags.yaml rename to website/showcase/tags.yml From 6670b7a3813d44cdbcf1ce4b8db3edec9a6c7a4b Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:23:43 +0200 Subject: [PATCH 34/79] fix types --- .../src/plugin-content-showcase.d.ts | 12 ++++++++++++ .../docusaurus-theme-classic/src/theme-classic.d.ts | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index 1a822bd5b32b..d7cd6ec304d0 100644 --- a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -17,6 +17,18 @@ declare module '@docusaurus/plugin-content-showcase' { color: string; }; + type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; + export type TagsOption = { [tagName: string]: Tag; }; diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 3c3e7cb6f092..9151c327597b 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -250,7 +250,7 @@ declare module '@theme/BlogPostItems' { declare module '@theme/ShowcaseDetails' { import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - export type User = ShowcaseItem['website'][number]; + export type User = ShowcaseItem; export type Props = { content: User; @@ -262,7 +262,7 @@ declare module '@theme/ShowcaseDetails' { declare module '@theme/Showcase' { import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - export type User = ShowcaseItem['website'][number]; + export type User = ShowcaseItem; export type Props = { content: User[]; @@ -278,7 +278,7 @@ declare module '@theme/Showcase/ShowcaseCard' { preview: string | null; // null = use our serverless screenshot service website: string; source: string | null; - tags: TagType[]; + tags: string[]; }; export interface Props { From d9885dbdbb3c25c0056ee07c370a6ff786a8cef0 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:48:33 +0200 Subject: [PATCH 35/79] dynamic validation --- .../src/options.ts | 13 +-------- .../src/tags.ts | 13 ++++++++- .../src/validation.ts | 29 +++++++++---------- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index 44a1caa58fac..5c60814b746c 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -7,6 +7,7 @@ import {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; +import {tagSchema} from './tags'; import type {OptionValidationContext} from '@docusaurus/types'; import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; @@ -20,18 +21,6 @@ export const DEFAULT_OPTIONS: PluginOptions = { tags: 'tags.yml', }; -export const tagSchema = Joi.object().pattern( - Joi.string(), - Joi.object({ - label: Joi.string().required(), - description: Joi.object({ - message: Joi.string().required(), - id: Joi.string().required(), - }).required(), - color: Joi.string().required(), - }), -); - const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath), diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index 554b9aa77990..50f818658bca 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -9,9 +9,20 @@ import fs from 'fs-extra'; import path from 'path'; import Yaml from 'js-yaml'; import {Joi} from '@docusaurus/utils-validation'; -import {tagSchema} from './options'; import type {TagsOption} from '@docusaurus/plugin-content-showcase'; +export const tagSchema = Joi.object().pattern( + Joi.string(), + Joi.object({ + label: Joi.string().required(), + description: Joi.object({ + message: Joi.string().required(), + id: Joi.string().required(), + }).required(), + color: Joi.string().required(), + }), +); + export async function getTagsList({ configTags, configPath, diff --git a/packages/docusaurus-plugin-content-showcase/src/validation.ts b/packages/docusaurus-plugin-content-showcase/src/validation.ts index c459a2646fe0..4ad8ed42d5eb 100644 --- a/packages/docusaurus-plugin-content-showcase/src/validation.ts +++ b/packages/docusaurus-plugin-content-showcase/src/validation.ts @@ -9,14 +9,18 @@ import {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; import {createTagSchema} from './tags'; import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; -const showcaseItemSchema = Joi.object({ - title: Joi.string().required(), - description: Joi.string().required(), - preview: Joi.string().required(), - website: Joi.string().required(), - source: Joi.string().required(), - tags: Joi.array().items(Joi.string()).required(), -}); +const createShowcaseItemSchema = (tags: string[]) => { + const tagsSchema = createTagSchema(tags); + + return Joi.object({ + title: Joi.string().required(), + description: Joi.string().required(), + preview: Joi.string().required(), + website: Joi.string().required(), + source: Joi.string().required(), + tags: tagsSchema, + }); +}; export function validateShowcaseItem({ item, @@ -25,14 +29,7 @@ export function validateShowcaseItem({ item: unknown; tags: string[]; }): ShowcaseItem { - const tagsSchema = createTagSchema(tags); - - const result = tagsSchema.validate(tags); - if (result.error) { - throw new Error(`Front matter contains invalid tags`, { - cause: result.error, - }); - } + const showcaseItemSchema = createShowcaseItemSchema(tags); return validateFrontMatter(item, showcaseItemSchema); } From 9eab6aecfe9f4295b00a258ceaf4cc69b40d758c Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:29:09 +0200 Subject: [PATCH 36/79] move schema creation outside of loadcontent --- .../src/index.ts | 20 ++++++++++--------- .../src/validation.ts | 8 +++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 54f3e957cebc..8602d26f61d2 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -13,7 +13,7 @@ import { Globby, } from '@docusaurus/utils'; import Yaml from 'js-yaml'; -import {validateShowcaseItem} from './validation'; +import {createShowcaseItemSchema, validateShowcaseItem} from './validation'; import {getTagsList} from './tags'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { @@ -28,10 +28,10 @@ export function getContentPathList( return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } -export default function pluginContentShowcase( +export default async function pluginContentShowcase( context: LoadContext, options: PluginOptions, -): Plugin { +): Promise> { const {siteDir, localizationDir} = context; // todo check for better naming of path: sitePath const {include, exclude, tags, routeBasePath, path: sitePath} = options; @@ -45,6 +45,13 @@ export default function pluginContentShowcase( }), }; + const tagList = await getTagsList({ + configTags: tags, + configPath: contentPaths.contentPath, + }); + + const showcaseItemSchema = createShowcaseItemSchema(tagList); + return { name: 'docusaurus-plugin-content-showcase', @@ -68,11 +75,6 @@ export default function pluginContentShowcase( ignore: [...exclude], }); - const tagList = await getTagsList({ - configTags: tags, - configPath: contentPaths.contentPath, - }); - async function processShowcaseSourceFile(relativeSource: string) { // Lookup in localized folder in priority const contentPath = await getFolderContainingFile( @@ -85,7 +87,7 @@ export default function pluginContentShowcase( const item = Yaml.load(data); const showcaseItem = validateShowcaseItem({ item, - tags: tagList, + showcaseItemSchema, }); return showcaseItem; diff --git a/packages/docusaurus-plugin-content-showcase/src/validation.ts b/packages/docusaurus-plugin-content-showcase/src/validation.ts index 4ad8ed42d5eb..58b8ef172e45 100644 --- a/packages/docusaurus-plugin-content-showcase/src/validation.ts +++ b/packages/docusaurus-plugin-content-showcase/src/validation.ts @@ -9,7 +9,7 @@ import {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; import {createTagSchema} from './tags'; import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; -const createShowcaseItemSchema = (tags: string[]) => { +export const createShowcaseItemSchema = (tags: string[]): Joi.ObjectSchema => { const tagsSchema = createTagSchema(tags); return Joi.object({ @@ -24,12 +24,10 @@ const createShowcaseItemSchema = (tags: string[]) => { export function validateShowcaseItem({ item, - tags, + showcaseItemSchema, }: { item: unknown; - tags: string[]; + showcaseItemSchema: Joi.ObjectSchema; }): ShowcaseItem { - const showcaseItemSchema = createShowcaseItemSchema(tags); - return validateFrontMatter(item, showcaseItemSchema); } From 0fdb9d66da361597ee463237261cd19ee8bcc0a6 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:11:01 +0200 Subject: [PATCH 37/79] wip tests --- .../src/__tests__/frontMatter.test.ts | 40 ------- .../src/__tests__/index.test.ts | 102 +++++++++--------- .../src/__tests__/validation.test.ts | 89 +++++++++++++++ .../src/tags.ts | 5 +- 4 files changed, 143 insertions(+), 93 deletions(-) delete mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts deleted file mode 100644 index bd97ead99569..000000000000 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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 {validateShowcaseItem} from '../validation'; -import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - -// todo broken -describe('showcase front matter schema', () => { - it('accepts valid frontmatter', () => { - const item: ShowcaseItem = { - title: 'title', - description: 'description', - preview: 'preview', - source: 'source', - tags: [], - website: 'website', - }; - expect(validateShowcaseItem({items: item, tagsSchema, tags})).toEqual(item); - }); - it('reject invalid frontmatter', () => { - const frontMatter = {}; - expect(() => - validateShowcaseItem(frontMatter), - ).toThrowErrorMatchingInlineSnapshot( - `"Cannot read properties of undefined (reading 'validate')"`, - ); - }); - it('reject invalid frontmatter value', () => { - const frontMatter = {title: 42}; - expect(() => - validateShowcaseItem(frontMatter), - ).toThrowErrorMatchingInlineSnapshot( - `"Cannot read properties of undefined (reading 'validate')"`, - ); - }); -}); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts index 97bd15c7583c..66660d46792e 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -8,82 +8,80 @@ import path from 'path'; import {loadContext} from '@docusaurus/core/src/server/site'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; - +import {fromPartial} from '@total-typescript/shoehorn'; import pluginContentPages from '../index'; import {validateOptions} from '../options'; +import type {PluginOptions} from '@docusaurus/plugin-content-showcase'; + +const loadPluginContent = async (siteDir: string, options: PluginOptions) => { + const context = await loadContext({siteDir}); + const plugin = await pluginContentPages( + context, + validateOptions({ + validate: normalizePluginOptions, + options, + }), + ); + return plugin.loadContent!(); +}; describe('docusaurus-plugin-content-showcase', () => { it('loads simple showcase', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const context = await loadContext({siteDir}); - const plugin = pluginContentPages( - context, - validateOptions({ - validate: normalizePluginOptions, - options: { - path: 'src/showcase', - tags: 'tags.yaml', - }, + const showcaseMetadata = await loadPluginContent( + siteDir, + fromPartial({ + path: 'src/showcase', + tags: 'tags.yaml', }), ); - const showcaseMetadata = await plugin.loadContent!(); expect(showcaseMetadata).toMatchSnapshot(); }); it('loads simple showcase with tags in options', async () => { - const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const context = await loadContext({siteDir}); - const plugin = pluginContentPages( - context, - validateOptions({ - validate: normalizePluginOptions, - options: { - path: 'src/showcase', - tags: { - opensource: { - label: 'Open-Source', - description: { - message: - 'Open-Source Docusaurus sites can be useful for inspiration!', - id: 'showcase.tag.opensource.description', - }, - color: '#39ca30', - }, - meta: { - label: 'Meta', - description: { - message: - 'Docusaurus sites of Meta (formerly Facebook) projects', - id: 'showcase.tag.meta.description', - }, - color: '#4267b2', - }, - }, + const tags = { + opensource: { + label: 'Open-Source', + description: { + message: + 'Open-Source Docusaurus sites can be useful for inspiration!', + id: 'showcase.tag.opensource.description', }, + color: '#39ca30', + }, + meta: { + label: 'Meta', + description: { + message: 'Docusaurus sites of Meta (formerly Facebook) projects', + id: 'showcase.tag.meta.description', + }, + color: '#4267b2', + }, + }; + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const showcaseMetadata = await loadPluginContent( + siteDir, + fromPartial({ + path: 'src/showcase', + tags, }), ); - const showcaseMetadata = await plugin.loadContent!(); expect(showcaseMetadata).toMatchSnapshot(); }); it('throw loading inexistant tags file', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const context = await loadContext({siteDir}); - const plugin = pluginContentPages( - context, - validateOptions({ - validate: normalizePluginOptions, - options: { + await expect(async () => { + await loadPluginContent( + siteDir, + fromPartial({ path: 'src/showcase', tags: 'wrong.yaml', - }, - }), - ); - await expect( - plugin.loadContent!(), - ).rejects.toThrowErrorMatchingInlineSnapshot( + }), + ); + }).rejects.toThrowErrorMatchingInlineSnapshot( `"Failed to read tags file for showcase"`, ); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts new file mode 100644 index 000000000000..cf93a2c56571 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts @@ -0,0 +1,89 @@ +/** + * 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 {createShowcaseItemSchema, validateShowcaseItem} from '../validation'; +import {getTagsList} from '../tags'; +import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; + +const tags = { + favorite: { + label: 'Favorite', + description: { + message: + 'Our favorite Docusaurus sites that you must absolutely check out!', + id: 'showcase.tag.favorite.description', + }, + color: '#e9669e', + }, + opensource: { + label: 'Open-Source', + description: { + message: 'Open-Source Docusaurus sites can be useful for inspiration!', + id: 'showcase.tag.opensource.description', + }, + color: '#39ca30', + }, +}; + +async function prepareSchema() { + const tagList = await getTagsList({ + configTags: tags, + configPath: '', + }); + return createShowcaseItemSchema(tagList); +} + +// todo broken +describe('showcase item schema', () => { + it('accepts valid item', async () => { + const item: ShowcaseItem = { + title: 'title', + description: 'description', + preview: 'preview', + source: 'source', + tags: [], + website: 'website', + }; + const showcaseItemSchema = await prepareSchema(); + expect(validateShowcaseItem({item, showcaseItemSchema})).toEqual(item); + }); + it('reject invalid tags', async () => { + const item: ShowcaseItem = { + title: 'title', + description: 'description', + preview: 'preview', + source: 'source', + tags: ['invalid'], + website: 'website', + }; + const showcaseItemSchema = await prepareSchema(); + expect(() => + validateShowcaseItem({item, showcaseItemSchema}), + ).toThrowErrorMatchingInlineSnapshot( + `""tags[0]" must be one of [favorite, opensource]"`, + ); + }); + it('reject invalid item', async () => { + const item = {}; + const showcaseItemSchema = await prepareSchema(); + expect(() => + validateShowcaseItem({item, showcaseItemSchema}), + ).toThrowErrorMatchingInlineSnapshot( + `""title" is required. "description" is required. "preview" is required. "website" is required. "source" is required"`, + ); + }); + it('reject invalid item value', async () => { + const item = {title: 42}; + const showcaseItemSchema = await prepareSchema(); + + expect(() => + validateShowcaseItem({item, showcaseItemSchema}), + ).toThrowErrorMatchingInlineSnapshot( + `""title" must be a string. "description" is required. "preview" is required. "website" is required. "source" is required"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index 50f818658bca..97bf80f50eb7 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -19,7 +19,10 @@ export const tagSchema = Joi.object().pattern( message: Joi.string().required(), id: Joi.string().required(), }).required(), - color: Joi.string().required(), + color: Joi.string() + // todo doesn't seems to work ??? + .regex(/^#[\dA-Fa-f]{6}$/) + .required(), }), ); From 290cdb4f98dc7cd68795e218b64a52dea78cc0d4 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:37:24 +0200 Subject: [PATCH 38/79] wip tests --- .../src/__tests__/__fixtures__/tags.yml | 12 ++++ .../src/__tests__/tags.test.ts | 55 +++++++++++++++++++ .../src/__tests__/validation.test.ts | 6 +- .../src/options.ts | 1 + .../src/tags.ts | 14 +++-- 5 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml new file mode 100644 index 000000000000..8b22ab180c46 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml @@ -0,0 +1,12 @@ +favorite: + label: "Favorite" + description: + message: "Our favorite Docusaurus sites that you must absolutely check out!" + id: "showcase.tag.favorite.description" + color: "#e9669e" +opensource: + label: "Open-Source" + description: + message: "Open-Source Docusaurus sites can be useful for inspiration!" + id: "showcase.tag.opensource.description" + color: "#39ca30" diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts new file mode 100644 index 000000000000..1f9fa3b3aa90 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts @@ -0,0 +1,55 @@ +/** + * 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 {getTagsList} from '../tags'; + +const tags = { + favorite: { + label: 'Favorite', + description: { + message: + 'Our favorite Docusaurus sites that you must absolutely check out!', + id: 'showcase.tag.favorite.description', + }, + color: '#e9669e', + }, + opensource: { + label: 'Open-Source', + description: { + message: 'Open-Source Docusaurus sites can be useful for inspiration!', + id: 'showcase.tag.opensource.description', + }, + // todo throw an error with `getTagsList tagSchema` + color: '#39c', + }, +}; + +describe('showcase tags', () => { + it('get tag list', async () => { + const tagList = await getTagsList({ + configTags: tags, + configPath: '', + }); + expect(tagList).toEqual(Object.keys(tags)); + }); + + it('get tag list from file', async () => { + const tagList = await getTagsList({ + configTags: './__fixtures__/tags.yml', + configPath: __dirname, + }); + expect(tagList).toEqual(Object.keys(tags)); + }); + + it('error get tag list', async () => { + const tagList = await getTagsList({ + configTags: tags, + configPath: '', + }); + expect(tagList).toEqual(Object.keys(tags)); + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts index cf93a2c56571..65bf07950e9c 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {fromPartial} from '@total-typescript/shoehorn'; import {createShowcaseItemSchema, validateShowcaseItem} from '../validation'; import {getTagsList} from '../tags'; import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; @@ -68,7 +69,7 @@ describe('showcase item schema', () => { ); }); it('reject invalid item', async () => { - const item = {}; + const item: ShowcaseItem = fromPartial({}); const showcaseItemSchema = await prepareSchema(); expect(() => validateShowcaseItem({item, showcaseItemSchema}), @@ -77,7 +78,8 @@ describe('showcase item schema', () => { ); }); it('reject invalid item value', async () => { - const item = {title: 42}; + // @ts-expect-error: title should be a string + const item: ShowcaseItem = fromPartial({title: 42}); const showcaseItemSchema = await prepareSchema(); expect(() => diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index 5c60814b746c..f7c08f71b7a6 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -28,6 +28,7 @@ const PluginOptionSchema = Joi.object({ exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude), id: Joi.string().default(DEFAULT_OPTIONS.id), tags: Joi.alternatives() + // todo ozaki understand this .try(Joi.string().default(DEFAULT_OPTIONS.tags), tagSchema) .default(DEFAULT_OPTIONS.tags), }); diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index 97bf80f50eb7..db8925e88346 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -20,9 +20,12 @@ export const tagSchema = Joi.object().pattern( id: Joi.string().required(), }).required(), color: Joi.string() - // todo doesn't seems to work ??? - .regex(/^#[\dA-Fa-f]{6}$/) - .required(), + .pattern(/^#[\dA-Fa-f]{6}$/) + .required() + .messages({ + 'string.pattern.base': + 'Color must be a hexadecimal color string (e.g., #RRGGBB #rrggbb)', + }), }), ); @@ -34,7 +37,8 @@ export async function getTagsList({ configPath: string; }): Promise { if (typeof configTags === 'object') { - return Object.keys(configTags); + const tags = tagSchema.validate(configTags); + return Object.keys(tags.value); } const tagsPath = path.resolve(configPath, configTags); @@ -47,7 +51,7 @@ export async function getTagsList({ if (tags.error) { throw new Error( `There was an error extracting tags: ${tags.error.message}`, - {cause: tags.error}, + {cause: tags}, ); } From 65affab60866faf92481c8cc984472a665ea7479 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:09:07 +0200 Subject: [PATCH 39/79] wip tests --- .../src/__tests__/__fixtures__/tags.yml | 6 ------ .../src/__tests__/tags.test.ts | 12 +++++++++--- .../docusaurus-plugin-content-showcase/src/tags.ts | 9 +++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml index 8b22ab180c46..6bc8d21dd7fd 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/tags.yml @@ -4,9 +4,3 @@ favorite: message: "Our favorite Docusaurus sites that you must absolutely check out!" id: "showcase.tag.favorite.description" color: "#e9669e" -opensource: - label: "Open-Source" - description: - message: "Open-Source Docusaurus sites can be useful for inspiration!" - id: "showcase.tag.opensource.description" - color: "#39ca30" diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts index 1f9fa3b3aa90..b93b787ba4a6 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts @@ -17,6 +17,9 @@ const tags = { }, color: '#e9669e', }, +}; + +const invalidTags = { opensource: { label: 'Open-Source', description: { @@ -46,10 +49,13 @@ describe('showcase tags', () => { }); it('error get tag list', async () => { - const tagList = await getTagsList({ - configTags: tags, + const tagList = getTagsList({ + configTags: invalidTags, configPath: '', }); - expect(tagList).toEqual(Object.keys(tags)); + + await expect(() => tagList).rejects.toThrowErrorMatchingInlineSnapshot( + `"There was an error extracting tags: Color must be a hexadecimal color string (e.g., #RRGGBB #rrggbb)"`, + ); }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index db8925e88346..ad2c3d8675f3 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -20,6 +20,7 @@ export const tagSchema = Joi.object().pattern( id: Joi.string().required(), }).required(), color: Joi.string() + // todo doesnt work ??? .pattern(/^#[\dA-Fa-f]{6}$/) .required() .messages({ @@ -37,7 +38,15 @@ export async function getTagsList({ configPath: string; }): Promise { if (typeof configTags === 'object') { + console.log('Gettings Tag List and validating'); + tagSchema.describe(); const tags = tagSchema.validate(configTags); + if (tags.error) { + throw new Error( + `There was an error extracting tags: ${tags.error.message}`, + {cause: tags}, + ); + } return Object.keys(tags.value); } From 867b6049193918c864fa1ff8c99603d6efc490a4 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:39:51 +0200 Subject: [PATCH 40/79] wip --- .../src/index.ts | 8 +- .../src/types.ts | 2 +- .../src/__tests__/index.test.ts | 4 +- .../src/__tests__/options.test.ts | 1 - .../src/__tests__/tags.test.ts | 3 +- .../src/__tests__/validation.test.ts | 1 - .../src/index.ts | 93 ++++++------------- .../src/lifecycle/contentLoaded.ts | 35 +++++++ .../src/lifecycle/loadContent.ts | 68 ++++++++++++++ .../src/options.ts | 5 +- .../src/tags.ts | 7 +- 11 files changed, 141 insertions(+), 86 deletions(-) create mode 100644 packages/docusaurus-plugin-content-showcase/src/lifecycle/contentLoaded.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index 7fe81e83af33..d43ec69c1b13 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -26,7 +26,7 @@ import { } from '@docusaurus/utils'; import {validatePageFrontMatter} from './frontMatter'; import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types'; -import type {PagesContentPaths} from './types'; +import type {ShowcaseContentPaths} from './types'; import type { PluginOptions, Metadata, @@ -34,7 +34,9 @@ import type { PageFrontMatter, } from '@docusaurus/plugin-content-pages'; -export function getContentPathList(contentPaths: PagesContentPaths): string[] { +export function getContentPathList( + contentPaths: ShowcaseContentPaths, +): string[] { return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } @@ -47,7 +49,7 @@ export default function pluginContentPages( ): Plugin { const {siteConfig, siteDir, generatedFilesDir, localizationDir} = context; - const contentPaths: PagesContentPaths = { + const contentPaths: ShowcaseContentPaths = { contentPath: path.resolve(siteDir, options.path), contentPathLocalized: getPluginI18nPath({ localizationDir, diff --git a/packages/docusaurus-plugin-content-pages/src/types.ts b/packages/docusaurus-plugin-content-pages/src/types.ts index 2d11492cb04e..457a800cc679 100644 --- a/packages/docusaurus-plugin-content-pages/src/types.ts +++ b/packages/docusaurus-plugin-content-pages/src/types.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -export type PagesContentPaths = { +export type ShowcaseContentPaths = { contentPath: string; contentPathLocalized: string; }; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts index 66660d46792e..0522f31f9b5e 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -9,13 +9,13 @@ import path from 'path'; import {loadContext} from '@docusaurus/core/src/server/site'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {fromPartial} from '@total-typescript/shoehorn'; -import pluginContentPages from '../index'; +import pluginContentShowcase from '../index'; import {validateOptions} from '../options'; import type {PluginOptions} from '@docusaurus/plugin-content-showcase'; const loadPluginContent = async (siteDir: string, options: PluginOptions) => { const context = await loadContext({siteDir}); - const plugin = await pluginContentPages( + const plugin = await pluginContentShowcase( context, validateOptions({ validate: normalizePluginOptions, diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts index eea346373211..4b95f8dfe35b 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts @@ -101,7 +101,6 @@ describe('normalizeShowcasePluginOptions', () => { }, }, }; - // todo fix ts error expect(testValidate(userOptions)).toEqual({ ...defaultOptions, ...userOptions, diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts index b93b787ba4a6..3e756b31e1e4 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/tags.test.ts @@ -26,7 +26,6 @@ const invalidTags = { message: 'Open-Source Docusaurus sites can be useful for inspiration!', id: 'showcase.tag.opensource.description', }, - // todo throw an error with `getTagsList tagSchema` color: '#39c', }, }; @@ -55,7 +54,7 @@ describe('showcase tags', () => { }); await expect(() => tagList).rejects.toThrowErrorMatchingInlineSnapshot( - `"There was an error extracting tags: Color must be a hexadecimal color string (e.g., #RRGGBB #rrggbb)"`, + `"There was an error extracting tags: Color must be a hexadecimal color string (e.g., #14cfc3 #E9669E)"`, ); }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts index 65bf07950e9c..6d6d501eac18 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/validation.test.ts @@ -38,7 +38,6 @@ async function prepareSchema() { return createShowcaseItemSchema(tagList); } -// todo broken describe('showcase item schema', () => { it('accepts valid item', async () => { const item: ShowcaseItem = { diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 8602d26f61d2..933a18acc24d 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -7,14 +7,11 @@ import fs from 'fs-extra'; import path from 'path'; -import { - getFolderContainingFile, - getPluginI18nPath, - Globby, -} from '@docusaurus/utils'; -import Yaml from 'js-yaml'; -import {createShowcaseItemSchema, validateShowcaseItem} from './validation'; +import {getPluginI18nPath} from '@docusaurus/utils'; +import {createShowcaseItemSchema} from './validation'; import {getTagsList} from './tags'; +import {processContentLoaded} from './lifecycle/contentLoaded'; +import {processLoadContent} from './lifecycle/loadContent'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { PluginOptions, @@ -34,14 +31,14 @@ export default async function pluginContentShowcase( ): Promise> { const {siteDir, localizationDir} = context; // todo check for better naming of path: sitePath - const {include, exclude, tags, routeBasePath, path: sitePath} = options; + const {include, exclude, tags, routeBasePath, path: sitePath, id} = options; const contentPaths: ShowcaseContentPaths = { contentPath: path.resolve(siteDir, sitePath), contentPathLocalized: getPluginI18nPath({ localizationDir, pluginName: 'docusaurus-plugin-content-showcase', - pluginId: options.id, + pluginId: id, }), }; @@ -55,13 +52,16 @@ export default async function pluginContentShowcase( return { name: 'docusaurus-plugin-content-showcase', - // todo doesn't work - // getPathsToWatch() { - // const {include} = options; - // return getContentPathList(contentPaths).flatMap((contentPath) => - // include.map((pattern) => `${contentPath}/${pattern}`), - // ); - // }, + // TODO doesn't work + getPathsToWatch() { + console.log( + 'getContentPathList(contentPaths):', + getContentPathList(contentPaths), + ); + return getContentPathList(contentPaths).flatMap((contentPath) => + include.map((pattern) => `${contentPath}/${pattern}`), + ); + }, async loadContent(): Promise { if (!(await fs.pathExists(contentPaths.contentPath))) { @@ -70,45 +70,12 @@ export default async function pluginContentShowcase( ); } - const showcaseFiles = await Globby(include, { - cwd: contentPaths.contentPath, - ignore: [...exclude], + return processLoadContent({ + include, + exclude, + contentPaths, + showcaseItemSchema, }); - - async function processShowcaseSourceFile(relativeSource: string) { - // Lookup in localized folder in priority - const contentPath = await getFolderContainingFile( - getContentPathList(contentPaths), - relativeSource, - ); - - const sourcePath = path.join(contentPath, relativeSource); - const data = await fs.readFile(sourcePath, 'utf-8'); - const item = Yaml.load(data); - const showcaseItem = validateShowcaseItem({ - item, - showcaseItemSchema, - }); - - return showcaseItem; - } - - async function doProcessShowcaseSourceFile(relativeSource: string) { - try { - return await processShowcaseSourceFile(relativeSource); - } catch (err) { - throw new Error( - `Processing of page source file path=${relativeSource} failed.`, - {cause: err}, - ); - } - } - - return { - items: await Promise.all( - showcaseFiles.map(doProcessShowcaseSourceFile), - ), - }; }, async contentLoaded({content, actions}) { @@ -118,19 +85,11 @@ export default async function pluginContentShowcase( const {addRoute, createData} = actions; - const showcaseAllData = await createData( - 'showcaseAll.json', - JSON.stringify(content.items), - ); - - addRoute({ - path: routeBasePath, - component: '@theme/Showcase', - modules: { - content: showcaseAllData, - // img: '@site/src/showcase/website/ozaki/aot.jpg', - }, - exact: true, + await processContentLoaded({ + content, + routeBasePath, + addRoute, + createData, }); }, }; diff --git a/packages/docusaurus-plugin-content-showcase/src/lifecycle/contentLoaded.ts b/packages/docusaurus-plugin-content-showcase/src/lifecycle/contentLoaded.ts new file mode 100644 index 000000000000..c7a401933931 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/lifecycle/contentLoaded.ts @@ -0,0 +1,35 @@ +/** + * 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 {ShowcaseItems} from '@docusaurus/plugin-content-showcase'; +import type {PluginContentLoadedActions} from '@docusaurus/types'; + +export async function processContentLoaded({ + content, + routeBasePath, + addRoute, + createData, +}: { + content: ShowcaseItems; + routeBasePath: string; + addRoute: PluginContentLoadedActions['addRoute']; + createData: PluginContentLoadedActions['createData']; +}): Promise { + const showcaseAllData = await createData( + 'showcaseAll.json', + JSON.stringify(content.items), + ); + + addRoute({ + path: routeBasePath, + component: '@theme/Showcase', + modules: { + content: showcaseAllData, + }, + exact: true, + }); +} diff --git a/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts b/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts new file mode 100644 index 000000000000..bbd404cb615f --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts @@ -0,0 +1,68 @@ +/** + * 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 fs from 'fs-extra'; +import path from 'path'; +import Yaml from 'js-yaml'; +import { + Globby, + getContentPathList, + getFolderContainingFile, +} from '@docusaurus/utils'; +import {validateShowcaseItem} from '../validation'; +import type {ShowcaseContentPaths} from '../types'; +import type {ShowcaseItems} from '@docusaurus/plugin-content-showcase'; + +export async function processLoadContent({ + include, + exclude, + contentPaths, + showcaseItemSchema, +}: { + include: string[]; + exclude: string[]; + contentPaths: ShowcaseContentPaths; + showcaseItemSchema: any; +}): Promise { + const showcaseFiles = await Globby(include, { + cwd: contentPaths.contentPath, + ignore: [...exclude], + }); + + async function processShowcaseSourceFile(relativeSource: string) { + // Lookup in localized folder in priority + const contentPath = await getFolderContainingFile( + getContentPathList(contentPaths), + relativeSource, + ); + + const sourcePath = path.join(contentPath, relativeSource); + const data = await fs.readFile(sourcePath, 'utf-8'); + const item = Yaml.load(data); + const showcaseItem = validateShowcaseItem({ + item, + showcaseItemSchema, + }); + + return showcaseItem; + } + + async function doProcessShowcaseSourceFile(relativeSource: string) { + try { + return await processShowcaseSourceFile(relativeSource); + } catch (err) { + throw new Error( + `Processing of page source file path=${relativeSource} failed.`, + {cause: err}, + ); + } + } + + return { + items: await Promise.all(showcaseFiles.map(doProcessShowcaseSourceFile)), + }; +} diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index f7c08f71b7a6..c92ad6fbd00b 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -12,11 +12,10 @@ import type {OptionValidationContext} from '@docusaurus/types'; import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { - id: 'showcase', path: 'showcase', // Path to data on filesystem, relative to site dir. routeBasePath: '/showcase', // URL Route. include: ['**/*.{yml,yaml}'], - // todo exclude won't work if user pass a custom file name + // TODO exclude won't work if user pass a custom file name exclude: [...GlobExcludeDefault, 'tags.*'], tags: 'tags.yml', }; @@ -26,9 +25,7 @@ const PluginOptionSchema = Joi.object({ 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), tags: Joi.alternatives() - // todo ozaki understand this .try(Joi.string().default(DEFAULT_OPTIONS.tags), tagSchema) .default(DEFAULT_OPTIONS.tags), }); diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index ad2c3d8675f3..198955464273 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -20,12 +20,11 @@ export const tagSchema = Joi.object().pattern( id: Joi.string().required(), }).required(), color: Joi.string() - // todo doesnt work ??? .pattern(/^#[\dA-Fa-f]{6}$/) .required() .messages({ 'string.pattern.base': - 'Color must be a hexadecimal color string (e.g., #RRGGBB #rrggbb)', + 'Color must be a hexadecimal color string (e.g., #14cfc3 #E9669E)', }), }), ); @@ -38,8 +37,6 @@ export async function getTagsList({ configPath: string; }): Promise { if (typeof configTags === 'object') { - console.log('Gettings Tag List and validating'); - tagSchema.describe(); const tags = tagSchema.validate(configTags); if (tags.error) { throw new Error( @@ -72,5 +69,5 @@ export async function getTagsList({ } export function createTagSchema(tags: string[]): Joi.Schema { - return Joi.array().items(Joi.string().valid(...tags)); // Schema for array of strings + return Joi.array().items(Joi.string().valid(...tags)); } From e1c51bf8fd30aad580acf398326f7ed03a036442 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:17:15 +0200 Subject: [PATCH 41/79] wip --- .../src/index.ts | 10 +++------- .../src/lifecycle/loadContent.ts | 14 +++++++++----- .../src/plugin-content-showcase.d.ts | 2 +- .../docusaurus-plugin-content-showcase/src/tags.ts | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 933a18acc24d..133cb0a97348 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -28,7 +28,7 @@ export function getContentPathList( export default async function pluginContentShowcase( context: LoadContext, options: PluginOptions, -): Promise> { +): Promise> { const {siteDir, localizationDir} = context; // todo check for better naming of path: sitePath const {include, exclude, tags, routeBasePath, path: sitePath, id} = options; @@ -37,7 +37,7 @@ export default async function pluginContentShowcase( contentPath: path.resolve(siteDir, sitePath), contentPathLocalized: getPluginI18nPath({ localizationDir, - pluginName: 'docusaurus-plugin-content-showcase', + pluginName: 'docusaurus-plugin-content-pages', pluginId: id, }), }; @@ -54,16 +54,12 @@ export default async function pluginContentShowcase( // TODO doesn't work getPathsToWatch() { - console.log( - 'getContentPathList(contentPaths):', - getContentPathList(contentPaths), - ); return getContentPathList(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`), ); }, - async loadContent(): Promise { + async loadContent(): Promise { if (!(await fs.pathExists(contentPaths.contentPath))) { throw new Error( `The showcase content path does not exist: ${contentPaths.contentPath}`, diff --git a/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts b/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts index bbd404cb615f..aaeade6ea926 100644 --- a/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts +++ b/packages/docusaurus-plugin-content-showcase/src/lifecycle/loadContent.ts @@ -14,8 +14,12 @@ import { getFolderContainingFile, } from '@docusaurus/utils'; import {validateShowcaseItem} from '../validation'; +import type {Joi} from '@docusaurus/utils-validation'; import type {ShowcaseContentPaths} from '../types'; -import type {ShowcaseItems} from '@docusaurus/plugin-content-showcase'; +import type { + PluginOptions, + ShowcaseItems, +} from '@docusaurus/plugin-content-showcase'; export async function processLoadContent({ include, @@ -23,11 +27,11 @@ export async function processLoadContent({ contentPaths, showcaseItemSchema, }: { - include: string[]; - exclude: string[]; + include: PluginOptions['include']; + exclude: PluginOptions['exclude']; contentPaths: ShowcaseContentPaths; - showcaseItemSchema: any; -}): Promise { + showcaseItemSchema: Joi.ObjectSchema; +}): Promise { const showcaseFiles = await Globby(include, { cwd: contentPaths.contentPath, ignore: [...exclude], diff --git a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index d7cd6ec304d0..b7c3516fbb18 100644 --- a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -60,5 +60,5 @@ declare module '@docusaurus/plugin-content-showcase' { export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; + ): Promise>; } diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts index 198955464273..6804c071b4c4 100644 --- a/packages/docusaurus-plugin-content-showcase/src/tags.ts +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -9,7 +9,7 @@ import fs from 'fs-extra'; import path from 'path'; import Yaml from 'js-yaml'; import {Joi} from '@docusaurus/utils-validation'; -import type {TagsOption} from '@docusaurus/plugin-content-showcase'; +import type {PluginOptions} from '@docusaurus/plugin-content-showcase'; export const tagSchema = Joi.object().pattern( Joi.string(), @@ -33,8 +33,8 @@ export async function getTagsList({ configTags, configPath, }: { - configTags: string | TagsOption; - configPath: string; + configTags: PluginOptions['tags']; + configPath: PluginOptions['path']; }): Promise { if (typeof configTags === 'object') { const tags = tagSchema.validate(configTags); From 047b5f0e3dccbdad130150f9b9c22d4b9d724e8d Mon Sep 17 00:00:00 2001 From: sebastien Date: Thu, 11 Apr 2024 15:38:14 +0200 Subject: [PATCH 42/79] versions --- .../docusaurus-plugin-content-showcase/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/docusaurus-plugin-content-showcase/package.json b/packages/docusaurus-plugin-content-showcase/package.json index 16cea13dd5b0..c39e3e955f8e 100644 --- a/packages/docusaurus-plugin-content-showcase/package.json +++ b/packages/docusaurus-plugin-content-showcase/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-content-showcase", - "version": "3.0.0", + "version": "3.2.1", "description": "Showcase plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-content-showcase.d.ts", @@ -18,10 +18,10 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "3.0.0", - "@docusaurus/types": "3.0.0", - "@docusaurus/utils": "3.0.0", - "@docusaurus/utils-validation": "3.0.0", + "@docusaurus/core": "3.2.1", + "@docusaurus/types": "3.2.1", + "@docusaurus/utils": "3.2.1", + "@docusaurus/utils-validation": "3.2.1", "fs-extra": "^11.1.1", "js-yaml": "^4.1.0", "tslib": "^2.6.0", From 3cef20712411be28f44882b6c6410c023ed3711f Mon Sep 17 00:00:00 2001 From: slorber Date: Thu, 11 Apr 2024 13:43:01 +0000 Subject: [PATCH 43/79] refactor: apply lint autofix --- project-words.txt | 6 +++-- yarn.lock | 58 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/project-words.txt b/project-words.txt index 89bd9391d7c7..7edcbabe321b 100644 --- a/project-words.txt +++ b/project-words.txt @@ -23,6 +23,7 @@ autogenerating autohide Autolinks Bartosz +bedly beforeinstallprompt Bhatt Blockquotes @@ -69,7 +70,6 @@ Datagit's dedup devto dingers -dinooooooo Dmitry docsearch Docsify @@ -107,7 +107,6 @@ Flightcontrol's Formik FOUC froms -frontmatter funboxteam gabrielcsapo Gifs @@ -132,6 +131,7 @@ IANAD idempotency Iframes Immer +inexistant Infima infima Infima's @@ -226,6 +226,7 @@ opensearch opensearchdescription opensource optimizt +Orama orama Orta orta @@ -306,6 +307,7 @@ rtcts rtlcss saurus Scaleway +searchbar Sebastien sebastien sebastienlorber diff --git a/yarn.lock b/yarn.lock index a9578f9c90c8..605ac089e9bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1639,7 +1639,7 @@ "@docsearch/css" "3.5.2" algoliasearch "^4.19.1" -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": +"@docusaurus/react-loadable@5.5.2": version "5.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== @@ -13736,7 +13736,7 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.2.0: +react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -13788,11 +13788,27 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" +"react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + react-medium-image-zoom@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/react-medium-image-zoom/-/react-medium-image-zoom-5.1.6.tgz#1ec9dabbc88da664f3aacc03a93cf79cb1b70a23" integrity sha512-0QolPce1vNJsF5HKrGkU1UT6kLNvY9EOnLBqz++LlVnBQduaHLkJlY73ayj3SxY09XWRrnxKDMTHPDkrQYdREw== +react-popper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -15160,7 +15176,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15259,7 +15284,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16484,6 +16516,13 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +warning@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" @@ -16924,7 +16963,7 @@ workbox-window@7.0.0, workbox-window@^7.0.0: "@types/trusted-types" "^2.0.2" workbox-core "7.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -16942,6 +16981,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From c0a652079387c74165a715b82859d498d0e4ca34 Mon Sep 17 00:00:00 2001 From: sebastien Date: Thu, 11 Apr 2024 15:43:05 +0200 Subject: [PATCH 44/79] yarnlock --- yarn.lock | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index a9578f9c90c8..605ac089e9bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1639,7 +1639,7 @@ "@docsearch/css" "3.5.2" algoliasearch "^4.19.1" -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": +"@docusaurus/react-loadable@5.5.2": version "5.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== @@ -13736,7 +13736,7 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.2.0: +react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -13788,11 +13788,27 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" +"react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + react-medium-image-zoom@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/react-medium-image-zoom/-/react-medium-image-zoom-5.1.6.tgz#1ec9dabbc88da664f3aacc03a93cf79cb1b70a23" integrity sha512-0QolPce1vNJsF5HKrGkU1UT6kLNvY9EOnLBqz++LlVnBQduaHLkJlY73ayj3SxY09XWRrnxKDMTHPDkrQYdREw== +react-popper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -15160,7 +15176,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15259,7 +15284,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16484,6 +16516,13 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" +warning@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" @@ -16924,7 +16963,7 @@ workbox-window@7.0.0, workbox-window@^7.0.0: "@types/trusted-types" "^2.0.2" workbox-core "7.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -16942,6 +16981,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 2af5b44159aefb5b5a29ad73f032ce7e35db4ece Mon Sep 17 00:00:00 2001 From: sebastien Date: Thu, 11 Apr 2024 15:55:04 +0200 Subject: [PATCH 45/79] remove popper/tooltip --- .../docusaurus-theme-classic/package.json | 1 - .../src/theme-classic.d.ts | 10 -- .../src/theme/Showcase/ShowcaseCard/index.tsx | 13 +- .../theme/Showcase/ShowcaseTooltip/index.tsx | 145 ------------------ .../ShowcaseTooltip/styles.module.css | 45 ------ .../src/theme/Showcase/index.tsx | 47 +++--- 6 files changed, 22 insertions(+), 239 deletions(-) delete mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx delete mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 9f98d14a686a..253aace61eef 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -42,7 +42,6 @@ "postcss": "^8.4.26", "prism-react-renderer": "^2.3.0", "prismjs": "^1.29.0", - "react-popper": "^2.3.0", "react-router-dom": "^5.3.4", "rtlcss": "^4.1.0", "tslib": "^2.6.0", diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 9151c327597b..87d0a1a2fa06 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -287,16 +287,6 @@ declare module '@theme/Showcase/ShowcaseCard' { export default function ShowcaseCard(props: Props): JSX.Element; } -declare module '@theme/Showcase/ShowcaseTooltip' { - export interface Props { - anchorEl?: HTMLElement | string; - id: string; - text: string; - children: React.ReactElement; - } - - export default function ShowcaseTooltip(props: Props): JSX.Element; -} declare module '@theme/Showcase/ShowcaseTagSelect' { import {type ComponentProps, type ReactNode, type ReactElement} from 'react'; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx index 52a3e49cd899..9dbe5b58e8db 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx @@ -11,7 +11,6 @@ import Link from '@docusaurus/Link'; import Translate, {translate} from '@docusaurus/Translate'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import Heading from '@theme/Heading'; -import Tooltip from '@theme/Showcase/ShowcaseTooltip'; import styles from './styles.module.css'; const Tags: {[type in TagType]: Tag} = { @@ -174,17 +173,7 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) { return ( <> {tagObjectsSorted.map((tagObject, index) => { - const id = `showcase_card_tag_${tagObject.tag}`; - - return ( - - - - ); + return ; })} ); diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx deleted file mode 100644 index 7087fa0e5d2d..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/** - * 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 React, {useEffect, useState, useRef} from 'react'; -import ReactDOM from 'react-dom'; -import {usePopper} from 'react-popper'; -import styles from './styles.module.css'; - -interface Props { - anchorEl?: HTMLElement | string; - id: string; - text: string; - children: React.ReactElement; -} - -export default function Tooltip({ - children, - id, - anchorEl, - text, -}: Props): JSX.Element { - const [open, setOpen] = useState(false); - const [referenceElement, setReferenceElement] = useState( - null, - ); - const [popperElement, setPopperElement] = useState(null); - const [arrowElement, setArrowElement] = useState(null); - const [container, setContainer] = useState(null); - const {styles: popperStyles, attributes} = usePopper( - referenceElement, - popperElement, - { - modifiers: [ - { - name: 'arrow', - options: { - element: arrowElement, - }, - }, - { - name: 'offset', - options: { - offset: [0, 8], - }, - }, - ], - }, - ); - - const timeout = useRef(null); - const tooltipId = `${id}_tooltip`; - - useEffect(() => { - if (anchorEl) { - if (typeof anchorEl === 'string') { - setContainer(document.querySelector(anchorEl)); - } else { - setContainer(anchorEl); - } - } else { - setContainer(document.body); - } - }, [container, anchorEl]); - - useEffect(() => { - const showEvents = ['mouseenter', 'focus']; - const hideEvents = ['mouseleave', 'blur']; - - const handleOpen = () => { - // There is no point in displaying an empty tooltip. - if (text === '') { - return; - } - - // Remove the title ahead of time to avoid displaying - // two tooltips at the same time (native + this one). - referenceElement?.removeAttribute('title'); - - timeout.current = window.setTimeout(() => { - setOpen(true); - }, 400); - }; - - const handleClose = () => { - clearInterval(timeout.current!); - setOpen(false); - }; - - if (referenceElement) { - showEvents.forEach((event) => { - referenceElement.addEventListener(event, handleOpen); - }); - - hideEvents.forEach((event) => { - referenceElement.addEventListener(event, handleClose); - }); - } - - return () => { - if (referenceElement) { - showEvents.forEach((event) => { - referenceElement.removeEventListener(event, handleOpen); - }); - - hideEvents.forEach((event) => { - referenceElement.removeEventListener(event, handleClose); - }); - } - }; - }, [referenceElement, text]); - - return ( - <> - {React.cloneElement(children, { - ref: setReferenceElement, - 'aria-describedby': open ? tooltipId : undefined, - })} - {container - ? ReactDOM.createPortal( - open && ( - - ), - container, - ) - : container} - - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css deleted file mode 100644 index 5500c818b625..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTooltip/styles.module.css +++ /dev/null @@ -1,45 +0,0 @@ -/** - * 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. - */ - -.tooltip { - border-radius: 4px; - padding: 4px 8px; - color: var(--site-color-tooltip); - background: var(--site-color-tooltip-background); - font-size: 0.8rem; - z-index: 500; - line-height: 1.4; - font-weight: 500; - max-width: 300px; - opacity: 0.92; -} - -.tooltipArrow { - visibility: hidden; -} - -.tooltipArrow, -.tooltipArrow::before { - position: absolute; - width: 8px; - height: 8px; - background: inherit; -} - -.tooltipArrow::before { - visibility: visible; - content: ''; - transform: rotate(45deg); -} - -.tooltip[data-popper-placement^='top'] > .tooltipArrow { - bottom: -4px; -} - -.tooltip[data-popper-placement^='bottom'] > .tooltipArrow { - top: -4px; -} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index a79c0c6457f7..a80e6c705049 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -17,7 +17,6 @@ import Layout from '@theme/Layout'; import Heading from '@theme/Heading'; import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import ShowcaseCard from '@theme/Showcase/ShowcaseCard'; -import ShowcaseTooltip from '@theme/Showcase/ShowcaseTooltip'; import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect'; import ShowcaseFilterToggle from '@theme/Showcase/ShowcaseFilterToggle'; import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; @@ -297,36 +296,32 @@ function ShowcaseFilters({users}: {users: Users}) {
      {TagList.map((tag, i) => { - const {label, description, color} = Tags[tag]; + const {label, color} = Tags[tag]; const id = `showcase_checkbox_id_${tag}`; return (
    • - - - ) : ( - - ) - } - /> - + label={label} + // description={description} TODO + icon={ + tag === 'favorite' ? ( + + ) : ( + + ) + } + />
    • ); })} From 8f9408cb01db6f5fc5aefa9786bb4b7f1f51687f Mon Sep 17 00:00:00 2001 From: slorber Date: Thu, 11 Apr 2024 13:59:53 +0000 Subject: [PATCH 46/79] refactor: apply lint autofix --- yarn.lock | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/yarn.lock b/yarn.lock index 605ac089e9bf..ba16cee5a1e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13736,7 +13736,7 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -13801,14 +13801,6 @@ react-medium-image-zoom@^5.1.6: resolved "https://registry.yarnpkg.com/react-medium-image-zoom/-/react-medium-image-zoom-5.1.6.tgz#1ec9dabbc88da664f3aacc03a93cf79cb1b70a23" integrity sha512-0QolPce1vNJsF5HKrGkU1UT6kLNvY9EOnLBqz++LlVnBQduaHLkJlY73ayj3SxY09XWRrnxKDMTHPDkrQYdREw== -react-popper@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" - integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== - dependencies: - react-fast-compare "^3.0.1" - warning "^4.0.2" - react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -16516,13 +16508,6 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -warning@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" - integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== - dependencies: - loose-envify "^1.0.0" - watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" From 81acd878a5622d334ce50f929d1642fc2c59e487 Mon Sep 17 00:00:00 2001 From: sebastien Date: Thu, 11 Apr 2024 16:22:23 +0200 Subject: [PATCH 47/79] showcase plugin client API setup --- .../package.json | 17 +++++++++++++++-- .../src/client/index.ts | 8 ++++++++ .../tsconfig.client.json | 16 ++++++++++++++++ .../tsconfig.json | 3 ++- .../src/theme/Showcase/index.tsx | 3 +++ yarn.lock | 17 +---------------- 6 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 packages/docusaurus-plugin-content-showcase/src/client/index.ts create mode 100644 packages/docusaurus-plugin-content-showcase/tsconfig.client.json diff --git a/packages/docusaurus-plugin-content-showcase/package.json b/packages/docusaurus-plugin-content-showcase/package.json index c39e3e955f8e..fee1f5c8cf89 100644 --- a/packages/docusaurus-plugin-content-showcase/package.json +++ b/packages/docusaurus-plugin-content-showcase/package.json @@ -3,10 +3,23 @@ "version": "3.2.1", "description": "Showcase plugin for Docusaurus.", "main": "lib/index.js", + "sideEffects": false, + "exports": { + "./lib/*": "./lib/*", + "./src/*": "./src/*", + "./client": { + "type": "./lib/client/index.d.ts", + "default": "./lib/client/index.js" + }, + ".": { + "types": "./src/plugin-content-showcase.d.ts", + "default": "./lib/index.js" + } + }, "types": "src/plugin-content-showcase.d.ts", "scripts": { - "build": "tsc", - "watch": "tsc --watch" + "build": "tsc --build", + "watch": "tsc --build --watch" }, "publishConfig": { "access": "public" diff --git a/packages/docusaurus-plugin-content-showcase/src/client/index.ts b/packages/docusaurus-plugin-content-showcase/src/client/index.ts new file mode 100644 index 000000000000..da14bdafcd4e --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/client/index.ts @@ -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. + */ + +export const clientShowcase = 42; diff --git a/packages/docusaurus-plugin-content-showcase/tsconfig.client.json b/packages/docusaurus-plugin-content-showcase/tsconfig.client.json new file mode 100644 index 000000000000..5d06aa818c96 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/tsconfig.client.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": false, + "composite": true, + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo-client", + "moduleResolution": "bundler", + "module": "esnext", + "target": "esnext", + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src/client", "src/*.d.ts"], + "exclude": ["**/__tests__/**"] +} diff --git a/packages/docusaurus-plugin-content-showcase/tsconfig.json b/packages/docusaurus-plugin-content-showcase/tsconfig.json index e16d5c2c5d33..3936df64b7e4 100644 --- a/packages/docusaurus-plugin-content-showcase/tsconfig.json +++ b/packages/docusaurus-plugin-content-showcase/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "../../tsconfig.json", + "references": [{"path": "./tsconfig.client.json"}], "compilerOptions": { "noEmit": false, "incremental": true, @@ -8,5 +9,5 @@ "outDir": "lib" }, "include": ["src"], - "exclude": ["**/__tests__/**"] + "exclude": ["src/client", "**/__tests__/**"] } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index a80e6c705049..834526460fe6 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -12,6 +12,7 @@ import {useHistory, useLocation} from 'react-router-dom'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import Translate, {translate} from '@docusaurus/Translate'; import {usePluralForm} from '@docusaurus/theme-common'; +import {clientShowcase} from '@docusaurus/plugin-content-showcase/client'; import type {User, Props} from '@theme/Showcase'; import Layout from '@theme/Layout'; import Heading from '@theme/Heading'; @@ -447,6 +448,8 @@ export default function Showcase(props: Props): JSX.Element { return ( + {/* eslint-disable-next-line @docusaurus/prefer-docusaurus-heading */} +

      Client showcase API: {clientShowcase}

      {JSON.stringify(props)}
      diff --git a/yarn.lock b/yarn.lock index 605ac089e9bf..ba16cee5a1e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13736,7 +13736,7 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -13801,14 +13801,6 @@ react-medium-image-zoom@^5.1.6: resolved "https://registry.yarnpkg.com/react-medium-image-zoom/-/react-medium-image-zoom-5.1.6.tgz#1ec9dabbc88da664f3aacc03a93cf79cb1b70a23" integrity sha512-0QolPce1vNJsF5HKrGkU1UT6kLNvY9EOnLBqz++LlVnBQduaHLkJlY73ayj3SxY09XWRrnxKDMTHPDkrQYdREw== -react-popper@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" - integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== - dependencies: - react-fast-compare "^3.0.1" - warning "^4.0.2" - react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -16516,13 +16508,6 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -warning@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" - integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== - dependencies: - loose-envify "^1.0.0" - watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" From 0cfdd9d6fddb94569d242dd392f6dfb510a97a55 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:48:51 +0200 Subject: [PATCH 48/79] hooks bypass extreme wip --- .../src/theme-classic.d.ts | 109 +++-- .../theme/Showcase/ClearAllButton/index.tsx | 22 + .../src/theme/Showcase/FavoriteIcon/index.tsx | 37 +- .../Showcase/FavoriteIcon/styles.module.css | 37 +- .../theme/Showcase/OperatorButton/index.tsx | 41 ++ .../styles.module.css | 0 .../src/theme/Showcase/ShowcaseCard/index.tsx | 166 +------ .../Showcase/ShowcaseCard/styles.module.css | 6 +- .../theme/Showcase/ShowcaseCards/index.tsx | 99 ++++ .../Showcase/ShowcaseCards/styles.module.css | 27 ++ .../Showcase/ShowcaseFilterToggle/index.tsx | 101 ---- .../theme/Showcase/ShowcaseFilters/index.tsx | 110 +++++ .../ShowcaseFilters/styles.module.css | 53 +++ .../Showcase/ShowcaseSearchBar/index.tsx | 29 ++ .../ShowcaseSearchBar/styles.module.css | 17 + .../Showcase/ShowcaseTagSelect/index.tsx | 127 ++--- .../ShowcaseTagSelect/styles.module.css | 6 +- .../src/theme/Showcase/_utils.tsx | 99 ++++ .../src/theme/Showcase/index.tsx | 432 +----------------- .../src/theme/Showcase/styles.module.css | 95 ---- .../src/theme/ShowcaseDetails/index.tsx | 21 - 21 files changed, 643 insertions(+), 991 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ClearAllButton/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/OperatorButton/index.tsx rename packages/docusaurus-theme-classic/src/theme/Showcase/{ShowcaseFilterToggle => OperatorButton}/styles.module.css (100%) create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCards/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCards/styles.module.css delete mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/_utils.tsx delete mode 100644 packages/docusaurus-theme-classic/src/theme/Showcase/styles.module.css delete mode 100644 packages/docusaurus-theme-classic/src/theme/ShowcaseDetails/index.tsx diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 87d0a1a2fa06..0d2280fc244a 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -247,28 +247,18 @@ declare module '@theme/BlogPostItems' { export default function BlogPostItem(props: Props): JSX.Element; } -declare module '@theme/ShowcaseDetails' { - import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - - export type User = ShowcaseItem; - - export type Props = { - content: User; - }; - - export default function Showcase(props: Props): JSX.Element; -} - declare module '@theme/Showcase' { - import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; - - export type User = ShowcaseItem; + export default function Showcase(): JSX.Element; +} - export type Props = { - content: User[]; - }; +declare module '@theme/Showcase/FavoriteIcon' { + export interface Props { + className?: string; + style?: ComponentProps<'svg'>['style']; + size: 'small' | 'medium' | 'large'; + } - export default function Showcase(props: Props): JSX.Element; + export default function FavoriteIcon(props: Props): JSX.Element; } declare module '@theme/Showcase/ShowcaseCard' { @@ -287,13 +277,41 @@ declare module '@theme/Showcase/ShowcaseCard' { export default function ShowcaseCard(props: Props): JSX.Element; } -declare module '@theme/Showcase/ShowcaseTagSelect' { - import {type ComponentProps, type ReactNode, type ReactElement} from 'react'; - export interface Props extends ComponentProps<'input'> { - icon: ReactElement>; - label: ReactNode; +declare module '@theme/Showcase/ShowcaseCards' { + export default function ShowcaseCards(): JSX.Element; +} +declare module '@theme/Showcase/ShowcaseTooltip' { + export interface Props { + anchorEl?: HTMLElement | string; + id: string; + text: string; + children: React.ReactElement; + } + + export default function ShowcaseTooltip(props: Props): JSX.Element; +} +declare module '@theme/Showcase/ShowcaseTagSelect' { + import {type ComponentProps, type ReactElement} from 'react'; + + // TODO use from plugin content showcase + type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; + + interface Props extends ComponentProps<'input'> { tag: TagType; + label: string; + description: string; + icon: ReactElement>; } export default function ShowcaseTagSelect(props: Props): JSX.Element; @@ -304,27 +322,32 @@ declare module '@theme/Showcase/ShowcaseFilterToggle' { export default function ShowcaseFilterToggle(): JSX.Element; } -declare module '@theme/Showcase/FavoriteIcon' { - import {type ReactNode, type ComponentProps} from 'react'; - - export type SvgIconProps = ComponentProps<'svg'> & { - viewBox?: string; - size?: 'inherit' | 'small' | 'medium' | 'large'; - color?: - | 'inherit' - | 'primary' - | 'secondary' - | 'success' - | 'error' - | 'warning'; - svgClass?: string; // Class attribute on the child - colorAttr?: string; // Applies a color attribute to the SVG element. - children: ReactNode; // Node passed into the SVG element. - }; +declare module '@theme/Showcase/ShowcaseFilters' { + // TODO use from plugin content showcase + export type TagType = + | 'favorite' + | 'opensource' + | 'product' + | 'design' + | 'i18n' + | 'versioning' + | 'large' + | 'meta' + | 'personal' + | 'rtl'; - export type Props = Omit; + export default function ShowcaseFilters(): JSX.Element; +} - export default function FavoriteIcon(props: Props): JSX.Element; +declare module '@theme/Showcase/OperatorButton' { + export default function OperatorButton(): JSX.Element; +} +declare module '@theme/Showcase/ClearAllButton' { + export default function ClearAllButton(): JSX.Element; +} + +declare module '@theme/Showcase/ShowcaseSearchBar' { + export default function ShowcaseSearchBar(): JSX.Element; } declare module '@theme/BlogPostItem/Container' { diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ClearAllButton/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ClearAllButton/index.tsx new file mode 100644 index 000000000000..372706062ee3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ClearAllButton/index.tsx @@ -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 React, {type ReactNode} from 'react'; +import {useClearQueryString} from '@docusaurus/theme-common'; + +export default function ClearAllButton(): ReactNode { + const clearQueryString = useClearQueryString(); + // TODO translate + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx index 04f0fb4128e3..4ce2b5724230 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/index.tsx @@ -7,36 +7,21 @@ import React from 'react'; import clsx from 'clsx'; -import type {Props, SvgIconProps} from '@theme/Showcase/FavoriteIcon'; -import styles from './styles.module.css'; -function Svg(props: SvgIconProps): JSX.Element { - const { - svgClass, - colorAttr, - children, - color = 'inherit', - size = 'medium', - viewBox = '0 0 24 24', - ...rest - } = props; +import type {Props} from '@theme/Showcase/FavoriteIcon'; +import styles from './styles.module.css'; +export default function FavoriteIcon({ + size, + className, + style, +}: Props): React.ReactNode { return ( - {children} - - ); -} - -export default function FavoriteIcon(props: Props): JSX.Element { - return ( - + viewBox="0 0 24 24" + className={clsx(styles.svg, styles[size], className)} + style={style}> - + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css index db2d48e3ea71..4eda3471db71 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/FavoriteIcon/styles.module.css @@ -5,50 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -.svgIcon { +.svg { user-select: none; + color: #e9669e; width: 1em; height: 1em; display: inline-block; fill: currentColor; - flex-shrink: 0; - color: inherit; } -/* font-size */ .small { - font-size: 1.25rem; + font-size: 1rem; } .medium { - font-size: 1.5rem; + font-size: 1.25rem; } .large { - font-size: 2.185rem; -} - -/* colors */ -.primary { - color: var(--ifm-color-primary); -} - -.secondary { - color: var(--ifm-color-secondary); -} - -.success { - color: var(--ifm-color-success); -} - -.error { - color: var(--ifm-color-error); -} - -.warning { - color: var(--ifm-color-warning); -} - -.inherit { - color: inherit; + font-size: 1.8rem; } diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/OperatorButton/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/OperatorButton/index.tsx new file mode 100644 index 000000000000..96ea29d48c0b --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/OperatorButton/index.tsx @@ -0,0 +1,41 @@ +/** + * 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 React, {useId} from 'react'; +import clsx from 'clsx'; +import {useOperator} from '../../_utils'; + +import styles from './styles.module.css'; + +export default function OperatorButton(): JSX.Element { + const id = useId(); + const [operator, toggleOperator] = useOperator(); + // TODO add translations + return ( + <> + { + if (e.key === 'Enter') { + toggleOperator(); + } + }} + /> + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/OperatorButton/styles.module.css similarity index 100% rename from packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/Showcase/OperatorButton/styles.module.css diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx index 9dbe5b58e8db..dfda04995cbe 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCard/index.tsx @@ -8,159 +8,31 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import Translate, {translate} from '@docusaurus/Translate'; -import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; +import Translate from '@docusaurus/Translate'; +// import Image from '@theme/IdealImage'; +import {Tags, TagList, type TagType} from '@site/src/data/users'; +import {type User} from '@theme/Showcase/ShowcaseCard'; +import {sortBy} from '@site/src/utils/jsUtils'; import Heading from '@theme/Heading'; +import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; import styles from './styles.module.css'; -const Tags: {[type in TagType]: Tag} = { - favorite: { - label: translate({message: 'Favorite'}), - description: translate({ - message: - 'Our favorite Docusaurus sites that you must absolutely check out!', - id: 'showcase.tag.favorite.description', - }), - color: '#e9669e', - }, - - opensource: { - label: translate({message: 'Open-Source'}), - description: translate({ - message: 'Open-Source Docusaurus sites can be useful for inspiration!', - id: 'showcase.tag.opensource.description', - }), - color: '#39ca30', - }, - - product: { - label: translate({message: 'Product'}), - description: translate({ - message: 'Docusaurus sites associated to a commercial product!', - id: 'showcase.tag.product.description', - }), - color: '#dfd545', - }, - - design: { - label: translate({message: 'Design'}), - description: translate({ - message: - 'Beautiful Docusaurus sites, polished and standing out from the initial template!', - id: 'showcase.tag.design.description', - }), - color: '#a44fb7', - }, - - i18n: { - label: translate({message: 'I18n'}), - description: translate({ - message: - 'Translated Docusaurus sites using the internationalization support with more than 1 locale.', - id: 'showcase.tag.i18n.description', - }), - color: '#127f82', - }, - - versioning: { - label: translate({message: 'Versioning'}), - description: translate({ - message: - 'Docusaurus sites using the versioning feature of the docs plugin to manage multiple versions.', - id: 'showcase.tag.versioning.description', - }), - color: '#fe6829', - }, - - large: { - label: translate({message: 'Large'}), - description: translate({ - message: - 'Very large Docusaurus sites, including many more pages than the average!', - id: 'showcase.tag.large.description', - }), - color: '#8c2f00', - }, - - meta: { - label: translate({message: 'Meta'}), - description: translate({ - message: 'Docusaurus sites of Meta (formerly Facebook) projects', - id: 'showcase.tag.meta.description', - }), - color: '#4267b2', // Facebook blue - }, - - personal: { - label: translate({message: 'Personal'}), - description: translate({ - message: - 'Personal websites, blogs and digital gardens built with Docusaurus', - id: 'showcase.tag.personal.description', - }), - color: '#14cfc3', - }, - - rtl: { - label: translate({message: 'RTL Direction'}), - description: translate({ - message: - 'Docusaurus sites using the right-to-left reading direction support.', - id: 'showcase.tag.rtl.description', - }), - color: '#ffcfc3', - }, -}; - -const TagList = Object.keys(Tags) as TagType[]; - -type TagType = - | 'favorite' - | 'opensource' - | 'product' - | 'design' - | 'i18n' - | 'versioning' - | 'large' - | 'meta' - | 'personal' - | 'rtl'; - -type User = { - title: string; - description: string; - preview: string | null; // null = use our serverless screenshot service - website: string; - source: string | null; - tags: TagType[]; -}; - -type Tag = { +function TagItem({ + label, + description, + color, +}: { label: string; description: string; color: string; -}; - -function sortBy( - array: T[], - getter: (item: T) => string | number | boolean, -): T[] { - const sortedArray = [...array]; - sortedArray.sort((a, b) => - // eslint-disable-next-line no-nested-ternary - getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0, - ); - return sortedArray; -} - -const TagComp = React.forwardRef( - ({label, color, description}, ref) => ( -
    • +}) { + return ( +
    • {label.toLowerCase()}
    • - ), -); + ); +} function ShowcaseCardTag({tags}: {tags: TagType[]}) { const tagObjects = tags.map((tag) => ({tag, ...Tags[tag]})); @@ -173,7 +45,7 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) { return ( <> {tagObjectsSorted.map((tagObject, index) => { - return ; + return ; })} ); @@ -182,6 +54,7 @@ function ShowcaseCardTag({tags}: {tags: TagType[]}) { function getCardImage(user: User): string { return ( user.preview ?? + // TODO make it configurable `https://slorber-api-screenshot.netlify.app/${encodeURIComponent( user.website, )}/showcase` @@ -193,6 +66,7 @@ function ShowcaseCard({user}: {user: User}) { return (
    • + {/* TODO change back to ideal image */} {user.title}
      @@ -203,7 +77,7 @@ function ShowcaseCard({user}: {user: User}) { {user.tags.includes('favorite') && ( - + )} {user.source && ( + user.tags.includes('favorite'), +); + +const otherUsers = sortedUsers.filter( + (user) => !user.tags.includes('favorite'), +); + +function HeadingNoResult() { + return ( + + No result + + ); +} + +function HeadingFavorites() { + return ( + + Our favorites + + + ); +} + +function HeadingAllSites() { + return ( + + All sites + + ); +} + +function CardList({heading, items}: {heading?: ReactNode; items: User[]}) { + return ( +
      + {heading} +
        + {items.map((item) => ( + + ))} +
      +
      + ); +} + +function NoResultSection() { + return ( +
      +
      + +
      +
      + ); +} + +export default function ShowcaseCards(): JSX.Element { + const filteredUsers = useFilteredUsers(); + + if (filteredUsers.length === 0) { + return ; + } + + return ( +
      + {filteredUsers.length === sortedUsers.length ? ( + <> +
      + } items={favoriteUsers} /> +
      +
      + } items={otherUsers} /> +
      + + ) : ( + + )} +
      + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCards/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCards/styles.module.css new file mode 100644 index 000000000000..b1512e62d7ca --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseCards/styles.module.css @@ -0,0 +1,27 @@ +/** + * 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. + */ + +.cardList { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +.showcaseFavorite { + padding-top: 2rem; + padding-bottom: 2rem; + background-color: #f6fdfd; +} + +html[data-theme='dark'] .showcaseFavorite { + background-color: #232525; +} + +.headingFavorites { + display: flex; + align-items: center; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx deleted file mode 100644 index ff6616c754f7..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilterToggle/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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 React, {useState, useEffect, useCallback} from 'react'; -import clsx from 'clsx'; -import {useHistory, useLocation} from '@docusaurus/router'; - -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; - -import styles from './styles.module.css'; - -const OperatorQueryKey = 'operator'; - -type UserState = { - scrollTopPosition: number; - focusedElementId: string | undefined; -}; - -function prepareUserState(): UserState | undefined { - if (ExecutionEnvironment.canUseDOM) { - return { - scrollTopPosition: window.scrollY, - focusedElementId: document.activeElement?.id, - }; - } - - return undefined; -} - -function readOperator(search: string): Operator { - return (new URLSearchParams(search).get(OperatorQueryKey) ?? - 'OR') as Operator; -} - -export default function ShowcaseFilterToggle(): JSX.Element { - const id = 'showcase_filter_toggle'; - const location = useLocation(); - const history = useHistory(); - const [operator, setOperator] = useState(false); - useEffect(() => { - setOperator(readOperator(location.search) === 'AND'); - }, [location]); - const toggleOperator = useCallback(() => { - setOperator((o) => !o); - const searchParams = new URLSearchParams(location.search); - searchParams.delete(OperatorQueryKey); - if (!operator) { - searchParams.append(OperatorQueryKey, 'AND'); - } - history.push({ - ...location, - search: searchParams.toString(), - state: prepareUserState(), - }); - }, [operator, location, history]); - - const ClearTag = () => { - history.push({ - ...location, - search: '', - state: prepareUserState(), - }); - }; - - return ( -
      - { - if (e.key === 'Enter') { - toggleOperator(); - } - }} - checked={operator} - /> - - - {/* eslint-disable-next-line @docusaurus/no-untranslated-text */} - -
      - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/index.tsx new file mode 100644 index 000000000000..95aefeabcaaa --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/index.tsx @@ -0,0 +1,110 @@ +/** + * 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 {ReactNode, CSSProperties} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import FavoriteIcon from '@theme/Showcase/FavoriteIcon'; +import {Tags, TagList} from '@site/src/data/users'; +import Heading from '@theme/Heading'; +import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect'; +import {type TagType} from '@theme/Showcase/ShowcaseFilters'; +import OperatorButton from '@theme/Showcase/OperatorButton'; +import ClearAllButton from '@theme/Showcase/ClearAllButton'; +import {useFilteredUsers, useSiteCountPlural} from '../../_utils'; + +import styles from './styles.module.css'; + +function TagCircleIcon({color, style}: {color: string; style?: CSSProperties}) { + return ( + + ); +} + +function ShowcaseTagListItem({tag}: {tag: TagType}) { + const {label, description, color} = Tags[tag]; + return ( +
    • + + ) : ( + + ) + } + /> +
    • + ); +} + +function ShowcaseTagList() { + return ( +
        + {TagList.map((tag) => { + return ; + })} +
      + ); +} + +function HeadingText() { + const filteredUsers = useFilteredUsers(); + const siteCountPlural = useSiteCountPlural(); + return ( +
      + + Filters + + {siteCountPlural(filteredUsers.length)} +
      + ); +} + +function HeadingButtons() { + return ( +
      + + +
      + ); +} + +function HeadingRow() { + return ( +
      + + +
      + ); +} + +export default function ShowcaseFilters(): ReactNode { + return ( +
      + + +
      + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/styles.module.css new file mode 100644 index 000000000000..627b3322a12c --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseFilters/styles.module.css @@ -0,0 +1,53 @@ +/** + * 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. + */ + +.headingRow { + display: flex; + align-items: center; + justify-content: space-between; +} + +.headingText { + display: flex; + align-items: baseline; +} + +.headingText > h2 { + margin-bottom: 0; +} + +.headingText > span { + margin-left: 8px; +} + +.headingButtons { + display: flex; + align-items: center; +} + +.headingButtons > * { + margin-left: 8px; +} + +.tagList { + display: flex; + align-items: center; + flex-wrap: wrap; +} + +.tagListItem { + user-select: none; + white-space: nowrap; + height: 32px; + font-size: 0.8rem; + margin-top: 0.5rem; + margin-right: 0.5rem; +} + +.tagListItem:last-child { + margin-right: 0; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/index.tsx new file mode 100644 index 000000000000..87b62b8ce39b --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/index.tsx @@ -0,0 +1,29 @@ +/** + * 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 ReactNode} from 'react'; +import {translate} from '@docusaurus/Translate'; +import {useSearchName} from '@site/src/pages/showcase/_utils'; +import styles from './styles.module.css'; + +export default function ShowcaseSearchBar(): ReactNode { + const [searchName, setSearchName] = useSearchName(); + return ( +
      + { + setSearchName(e.currentTarget.value); + }} + /> +
      + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/styles.module.css new file mode 100644 index 000000000000..22e60d479b14 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseSearchBar/styles.module.css @@ -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. + */ + +.searchBar { + margin-left: auto; +} + +.searchBar input { + height: 30px; + border-radius: 15px; + padding: 10px; + border: 1px solid gray; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx index 3219a051b789..4063c290ea7e 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/ShowcaseTagSelect/index.tsx @@ -5,127 +5,54 @@ * LICENSE file in the root directory of this source tree. */ -import React, { - useCallback, - useState, - useEffect, - type ComponentProps, - type ReactNode, - type ReactElement, -} from 'react'; -import {useHistory, useLocation} from '@docusaurus/router'; +import React, {useCallback, type ReactNode, useId} from 'react'; +import type {Props} from '@theme/Showcase/ShowcaseTagSelect'; +import {useTags} from '../../_utils'; -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import styles from './styles.module.css'; -type UserState = { - scrollTopPosition: number; - focusedElementId: string | undefined; -}; - -function prepareUserState(): UserState | undefined { - if (ExecutionEnvironment.canUseDOM) { - return { - scrollTopPosition: window.scrollY, - focusedElementId: document.activeElement?.id, - }; - } - - return undefined; -} - -function toggleListItem(list: T[], item: T): T[] { - const itemIndex = list.indexOf(item); - if (itemIndex === -1) { - return list.concat(item); - } - const newList = [...list]; - newList.splice(itemIndex, 1); - return newList; -} - -type TagType = - | 'favorite' - | 'opensource' - | 'product' - | 'design' - | 'i18n' - | 'versioning' - | 'large' - | 'meta' - | 'personal' - | 'rtl'; -interface Props extends ComponentProps<'input'> { - icon: ReactElement>; - label: ReactNode; - tag: TagType; -} - -const TagQueryStringKey = 'tags'; - -function readSearchTags(search: string): TagType[] { - return new URLSearchParams(search).getAll(TagQueryStringKey) as TagType[]; -} +function useTagState(tag: string) { + const [tags, setTags] = useTags(); + const isSelected = tags.includes(tag); + const toggle = useCallback(() => { + setTags((list) => { + return list.includes(tag) + ? list.filter((t) => t !== tag) + : [...list, tag]; + }); + }, [tag, setTags]); -function replaceSearchTags(search: string, newTags: TagType[]) { - const searchParams = new URLSearchParams(search); - searchParams.delete(TagQueryStringKey); - newTags.forEach((tag) => searchParams.append(TagQueryStringKey, tag)); - return searchParams.toString(); + return [isSelected, toggle] as const; } -function ShowcaseTagSelect( - {id, icon, label, tag, ...rest}: Props, - ref: React.ForwardedRef, -) { - const location = useLocation(); - const history = useHistory(); - const [selected, setSelected] = useState(false); - useEffect(() => { - const tags = readSearchTags(location.search); - setSelected(tags.includes(tag)); - }, [tag, location]); - const toggleTag = useCallback(() => { - const tags = readSearchTags(location.search); - const newTags = toggleListItem(tags, tag); - const newSearch = replaceSearchTags(location.search, newTags); - history.push({ - ...location, - search: newSearch, - state: prepareUserState(), - }); - }, [tag, location, history]); +export default function ShowcaseTagSelect({ + icon, + label, + description, + tag, + ...rest +}: Props): ReactNode { + const id = useId(); + const [isSelected, toggle] = useTagState(tag); return ( <> { if (e.key === 'Enter') { - toggleTag(); + toggle(); } }} - onFocus={(e) => { - if (e.relatedTarget) { - e.target.nextElementSibling?.dispatchEvent( - new KeyboardEvent('focus'), - ); - } - }} - onBlur={(e) => { - e.target.nextElementSibling?.dispatchEvent(new KeyboardEvent('blur')); - }} - onChange={toggleTag} - checked={selected} {...rest} /> -