mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-24 03:58:49 +02:00
wip
This commit is contained in:
parent
ec35998bde
commit
8d1b174917
13 changed files with 160 additions and 115 deletions
|
@ -36,28 +36,33 @@ export default function pluginContentShowcase(
|
|||
// },
|
||||
|
||||
async loadContent(): Promise<Content> {
|
||||
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,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<PluginOptions>({
|
|||
});
|
||||
|
||||
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({
|
||||
|
|
|
@ -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<Plugin<LoadedContent | null>>;
|
||||
): Promise<Plugin<Content | null>>;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<T>(list: T[], item: T): T[] {
|
||||
const itemIndex = list.indexOf(item);
|
||||
if (itemIndex === -1) {
|
||||
|
|
|
@ -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<T>(
|
||||
array: T[],
|
||||
getter: (item: T) => string | number | boolean,
|
||||
|
@ -168,17 +162,14 @@ function sortBy<T>(
|
|||
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 (
|
||||
<section className="margin-top--lg margin-bottom--lg text--center">
|
||||
|
@ -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<UserState>();
|
||||
const [operator, setOperator] = useState<Operator>('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 (
|
||||
<section className="container margin-top--l margin-bottom--lg">
|
||||
|
@ -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 (
|
||||
<section className="margin-top--lg margin-bottom--xl">
|
||||
{filteredUsers.length === sortedUsers.length ? (
|
||||
{filteredUsers.length === sortUsers(users).length ? (
|
||||
<>
|
||||
<div className={styles.showcaseFavorite}>
|
||||
<div className="container">
|
||||
|
@ -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 (
|
||||
<Layout title="Showcase">
|
||||
<div>{JSON.stringify(props)}</div>
|
||||
<main className="margin-vert--lg">
|
||||
<ShowcaseHeader />
|
||||
<ShowcaseFilters />
|
||||
<ShowcaseFilters users={users} />
|
||||
<div
|
||||
style={{display: 'flex', marginLeft: 'auto'}}
|
||||
className="container">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<ShowcaseCards />
|
||||
<ShowcaseCards users={users} />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
@ -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 <div>Your friends are : {props.content.author}</div>;
|
||||
}
|
6
website/src/showcase/website/dino.yaml
Normal file
6
website/src/showcase/website/dino.yaml
Normal file
|
@ -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']
|
|
@ -1,2 +0,0 @@
|
|||
author: ozaki
|
||||
title: ozaki
|
6
website/src/showcase/website/ozaki/ozaki.yaml
Normal file
6
website/src/showcase/website/ozaki/ozaki.yaml
Normal file
|
@ -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']
|
|
@ -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']
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue