This commit is contained in:
ozakione 2024-03-24 22:02:40 +01:00
parent ec35998bde
commit 8d1b174917
13 changed files with 160 additions and 115 deletions

View file

@ -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,
});
},
};
}

View file

@ -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({

View file

@ -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>>;
}

View file

@ -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",

View file

@ -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;
}

View file

@ -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;

View file

@ -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) {

View file

@ -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>
);

View file

@ -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>;
}

View 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']

View file

@ -1,2 +0,0 @@
author: ozaki
title: ozaki

View 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']

View file

@ -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']