From 090813fdeab8a8f08f863ce3da876ee488ae1970 Mon Sep 17 00:00:00 2001 From: Hossein Mehrabi Date: Wed, 7 Jun 2023 03:10:21 +0330 Subject: [PATCH] feat: auto generated author page --- .../mdx/DocMetadata/DocMetadata.tsx | 15 ++- .../src/client/lib/useThemeOptions.ts | 4 +- .../theme/AuthorPage/AuthorPage.module.scss | 50 ++++++++ .../client/theme/AuthorPage/AuthorPage.tsx | 88 ++++++++++++++ .../src/client/theme/AuthorPage/index.ts | 1 + .../src/client/types/theme.types.ts | 5 + packages/logos-docusaurus-theme/src/index.ts | 9 +- .../src/server/utils/author.utils.ts | 109 ++++++++++++++++++ .../src/server/utils/doc.utils.ts | 21 ++++ .../src/server/utils/option.utils.ts | 16 +++ .../src/server/utils/validateOptions.ts | 7 ++ yarn.lock | 64 +++++++++- 12 files changed, 382 insertions(+), 7 deletions(-) create mode 100644 packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.module.scss create mode 100644 packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.tsx create mode 100644 packages/logos-docusaurus-theme/src/client/theme/AuthorPage/index.ts create mode 100644 packages/logos-docusaurus-theme/src/server/utils/author.utils.ts create mode 100644 packages/logos-docusaurus-theme/src/server/utils/doc.utils.ts create mode 100644 packages/logos-docusaurus-theme/src/server/utils/option.utils.ts diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/DocMetadata/DocMetadata.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/DocMetadata/DocMetadata.tsx index ae37b3f..2db6b85 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/DocMetadata/DocMetadata.tsx +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/DocMetadata/DocMetadata.tsx @@ -1,6 +1,8 @@ import { Typography } from '@acid-info/lsd-react' +import Link from '@docusaurus/Link' import clsx from 'clsx' import React from 'react' +import { useDocThemeOptions } from '../../../lib/useThemeOptions' import './DocMetadata.scss' import { useDocMetadata } from './useDocMetadata' @@ -12,6 +14,7 @@ export const DocMetadata: React.FC = ({ ...props }) => { const { date, authors } = useDocMetadata() + const { content: { authorPage } = {} } = useDocThemeOptions() return (
@@ -19,7 +22,17 @@ export const DocMetadata: React.FC = ({ {authors && authors.length > 0 && ( <> - by {authors.map((author) => author!.name).join(', ')} + by{' '} + {authors.map((author, index) => ( + + {authorPage ? ( + {author!.name} + ) : ( + author!.name + )} + {index < authors.length - 1 && ', '} + + ))} )} diff --git a/packages/logos-docusaurus-theme/src/client/lib/useThemeOptions.ts b/packages/logos-docusaurus-theme/src/client/lib/useThemeOptions.ts index df9bbcf..cb29a2c 100644 --- a/packages/logos-docusaurus-theme/src/client/lib/useThemeOptions.ts +++ b/packages/logos-docusaurus-theme/src/client/lib/useThemeOptions.ts @@ -13,5 +13,7 @@ export const useThemeOptions = (): ThemeOptions => { export const useDocThemeOptions = (): DocConfig => { const activePlugin = useActivePlugin() const themeOptions = useThemeOptions() - return activePlugin ? themeOptions?.docs?.[activePlugin?.pluginId] ?? {} : {} + return ( + activePlugin ? themeOptions?.docs?.[activePlugin?.pluginId] ?? {} : {} + ) as DocConfig } diff --git a/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.module.scss b/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.module.scss new file mode 100644 index 0000000..83cca3f --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.module.scss @@ -0,0 +1,50 @@ +@use '../../css/utils'; + +.root { + max-width: 80%; +} + +.links { + padding-top: 0.5rem; + + display: flex; + flex-direction: row; + + & > a:not(:last-child) { + &::after { + content: '•'; + display: inline-block; + margin-inline: 0.75rem; + } + } +} + +.docs { + margin-top: 1rem; + + display: flex; + flex-direction: column; + gap: 1rem; +} + +.date { + margin-bottom: 0.5rem; + font-style: italic; + opacity: 0.8; +} + +.title { + display: block; + text-decoration: underline; +} + +.description { + display: block; + margin-top: 0.25rem; +} + +@include utils.responsive('lg', 'down') { + .root { + max-width: unset; + } +} diff --git a/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.tsx b/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.tsx new file mode 100644 index 0000000..951ab3c --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/AuthorPage.tsx @@ -0,0 +1,88 @@ +import { Typography } from '@acid-info/lsd-react' +import Link from '@docusaurus/Link' +import formatDate from 'date-fns/format' +import React from 'react' +import { Author } from '../../types/theme.types' +import styles from './AuthorPage.module.scss' + +export type AuthorPageProps = { + data: { + author: Author + docs: { + id: string + title: string + description: string + permalink: string + frontMatter: { + date?: string + } + }[] + } +} + +export const AuthorPage: React.FC = ({ + data: { author, docs }, +}) => { + return ( +
+ + {author.name} + +
+ {author.github && ( + + Github + + )} + {author.twitter && ( + + Twitter + + )} + {author.website && ( + + Website + + )} +
+
+ {docs.map((doc, index) => ( +
+ {doc.frontMatter.date && ( + + {formatDate(new Date(doc.frontMatter.date), 'MMM d yyyy')} + + )} + + {doc.title} + + + {doc.description} + +
+ ))} +
+
+ ) +} + +export default AuthorPage diff --git a/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/index.ts b/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/index.ts new file mode 100644 index 0000000..a85a280 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/theme/AuthorPage/index.ts @@ -0,0 +1 @@ +export * from './AuthorPage' diff --git a/packages/logos-docusaurus-theme/src/client/types/theme.types.ts b/packages/logos-docusaurus-theme/src/client/types/theme.types.ts index 46823ca..5251ccb 100644 --- a/packages/logos-docusaurus-theme/src/client/types/theme.types.ts +++ b/packages/logos-docusaurus-theme/src/client/types/theme.types.ts @@ -8,8 +8,13 @@ export type Author = { website?: string } +export type AuthorPageConfig = { + sidebar?: string +} + export type DocContent = { authors?: Author[] + authorPage?: AuthorPageConfig | boolean } export type DocSidebarConfig = { diff --git a/packages/logos-docusaurus-theme/src/index.ts b/packages/logos-docusaurus-theme/src/index.ts index 1087028..d6f178f 100644 --- a/packages/logos-docusaurus-theme/src/index.ts +++ b/packages/logos-docusaurus-theme/src/index.ts @@ -2,11 +2,12 @@ import type { LoadContext, Plugin } from '@docusaurus/types' import _ from 'lodash' import path from 'path' import type { ThemeOptions } from './client/types/theme.types' +import { createAuthorRoutes } from './server/utils/author.utils' export default function logosTheme( context: LoadContext, options: ThemeOptions, -): Plugin { +): Plugin { const clientModules: string[] = [ path.resolve(__dirname, './client/css/custom.scss'), ] @@ -42,8 +43,12 @@ export default function logosTheme( path.resolve(__dirname, '../src/client/theme'), getClientModules: () => clientModules, + + async contentLoaded(args) { + await createAuthorRoutes(context, args) + }, } } -export type { ThemeOptions } export { validateOptions } from './server/utils/validateOptions' +export type { ThemeOptions } diff --git a/packages/logos-docusaurus-theme/src/server/utils/author.utils.ts b/packages/logos-docusaurus-theme/src/server/utils/author.utils.ts new file mode 100644 index 0000000..da5d5fb --- /dev/null +++ b/packages/logos-docusaurus-theme/src/server/utils/author.utils.ts @@ -0,0 +1,109 @@ +import { LoadedContent } from '@docusaurus/plugin-content-docs' +import { + AllContent, + LoadContext, + PluginContentLoadedActions, +} from '@docusaurus/types' +import { docuHash } from '@docusaurus/utils' +import path from 'path' +import { ensureTrailingSlash } from '../../client/lib/string.utils' +import { getVersionMetadataPath } from './doc.utils' +import { getDocConfig } from './option.utils' + +export const createAuthorRoutes = async ( + context: LoadContext, + args: { + allContent: AllContent + actions: PluginContentLoadedActions + }, +) => { + const docs = args.allContent['docusaurus-plugin-content-docs'] ?? {} + + const docContents = Object.entries(docs) + + for (const [id, loadedContent] of docContents) { + const { loadedVersions } = loadedContent as LoadedContent + + for (const loadedVersion of loadedVersions) { + const { path: versionPath } = loadedVersion + + const versionMetadataPath = getVersionMetadataPath( + context, + id, + loadedVersion, + ) + + const conf = getDocConfig(context, id) + const authorPageConf = conf?.content?.authorPage + if (!authorPageConf) return + + const authors = conf?.content?.authors ?? [] + + const authorDocs: Record = {} + + loadedVersion.docs.forEach((doc) => { + const { author } = doc.frontMatter as any + const docAuthors = !author + ? [] + : Array.isArray(author) + ? author + : [author] + const validAuthors = docAuthors.map((authorKey) => + authors.find((a) => a.key === authorKey), + ) + validAuthors.forEach((author) => { + if (!author) return + + authorDocs[author.key] = [ + ...(authorDocs[author.key] ?? []), + { + id: doc.id, + title: doc.title, + description: doc.description, + frontMatter: doc.frontMatter, + permalink: doc.permalink, + }, + ] + }) + }) + + const routes = await Promise.all( + authors.map(async (author) => { + const dataPath = await args.actions.createData( + `${docuHash( + `author-${author.key}-${loadedVersion.versionName}`, + )}.json`, + JSON.stringify({ author, docs: authorDocs[author.key] ?? [] }), + ) + + return { + path: `${ensureTrailingSlash(versionPath)}author/${author.key}`, + component: path.resolve( + __dirname, + '../../client/theme/AuthorPage/AuthorPage', + ), + exact: true, + sidebar: + typeof authorPageConf === 'boolean' + ? null + : authorPageConf!.sidebar, + modules: { + data: dataPath, + }, + } + }), + ) + + args.actions.addRoute({ + path: `${ensureTrailingSlash(versionPath)}author`, + exact: false, + component: '@theme/DocPage', + routes: routes ?? [], + modules: { + versionMetadata: versionMetadataPath, + }, + priority: 1000, + }) + } + } +} diff --git a/packages/logos-docusaurus-theme/src/server/utils/doc.utils.ts b/packages/logos-docusaurus-theme/src/server/utils/doc.utils.ts new file mode 100644 index 0000000..d61cbc5 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/server/utils/doc.utils.ts @@ -0,0 +1,21 @@ +import { LoadedVersion } from '@docusaurus/plugin-content-docs' +import { LoadContext } from '@docusaurus/types' +import { docuHash } from '@docusaurus/utils' +import path from 'path' + +export const getVersionMetadataPath = ( + context: LoadContext, + pluginId: string, + loadedVersion: LoadedVersion, +) => { + const pluginDataDirRoot = path.join( + context.generatedFilesDir, + 'docusaurus-plugin-content-docs', + ) + const dataDir = path.join(pluginDataDirRoot, pluginId) + + return path.join( + dataDir, + `${docuHash(`version-${loadedVersion.versionName}-metadata-prop`)}.json`, + ) +} diff --git a/packages/logos-docusaurus-theme/src/server/utils/option.utils.ts b/packages/logos-docusaurus-theme/src/server/utils/option.utils.ts new file mode 100644 index 0000000..33c3823 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/server/utils/option.utils.ts @@ -0,0 +1,16 @@ +import { LoadContext } from '@docusaurus/types' +import _ from 'lodash' +import type { DocConfig, ThemeOptions } from '../../client/types/theme.types' + +export const getDocConfig = ( + context: LoadContext, + pluginId: string, +): DocConfig => { + const themeOptions = _.get( + context, + 'siteConfig.customFields.logos-docusaurus-theme', + {}, + ) as ThemeOptions + + return themeOptions?.docs?.[pluginId] ?? {} +} diff --git a/packages/logos-docusaurus-theme/src/server/utils/validateOptions.ts b/packages/logos-docusaurus-theme/src/server/utils/validateOptions.ts index 2d5f409..789acd6 100644 --- a/packages/logos-docusaurus-theme/src/server/utils/validateOptions.ts +++ b/packages/logos-docusaurus-theme/src/server/utils/validateOptions.ts @@ -1,6 +1,7 @@ import { Joi } from '@docusaurus/utils-validation' import { Author, + AuthorPageConfig, DocConfig, DocContent, DocSidebarConfig, @@ -16,6 +17,12 @@ const schema = Joi.object({ hide: Joi.bool().default(true), }).default({}), content: Joi.object({ + authorPage: Joi.alternatives( + Joi.boolean(), + Joi.object({ + sidebar: Joi.string().optional(), + }), + ).default({}), authors: Joi.array() .items( Joi.object({ diff --git a/yarn.lock b/yarn.lock index 9bcd7ad..98cdc05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3399,6 +3399,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/katex@^0.11.0": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5" + integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg== + "@types/lodash@^4.14.186": version "4.14.195" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" @@ -5582,7 +5587,7 @@ commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -commander@^8.3.0: +commander@^8.0.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -8997,6 +9002,11 @@ hast-util-from-parse5@^6.0.0: vfile-location "^3.2.0" web-namespaces "^1.0.0" +hast-util-is-element@1.1.0, hast-util-is-element@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425" + integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ== + hast-util-parse-selector@^2.0.0: version "2.2.5" resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" @@ -9029,6 +9039,15 @@ hast-util-to-parse5@^6.0.0: xtend "^4.0.0" zwitch "^1.0.0" +hast-util-to-text@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-2.0.1.tgz#04f2e065642a0edb08341976084aa217624a0f8b" + integrity sha512-8nsgCARfs6VkwH2jJU9b8LNTuR4700na+0h3PqCaEk4MAnMDeu5P0tP8mjk9LLNGxIeQRLbiDbZVw6rku+pYsQ== + dependencies: + hast-util-is-element "^1.0.0" + repeat-string "^1.0.0" + unist-util-find-after "^3.0.0" + hastscript@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" @@ -10840,6 +10859,13 @@ just-diff@^6.0.0: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285" integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA== +katex@^0.13.0: + version "0.13.24" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.24.tgz#fe55455eb455698cb24b911a353d16a3c855d905" + integrity sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w== + dependencies: + commander "^8.0.0" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -14379,6 +14405,26 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" +rehype-katex@5: + version "5.0.0" + resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-5.0.0.tgz#b556f24fde918f28ba1cb642ea71c7e82f3373d7" + integrity sha512-ksSuEKCql/IiIadOHiKRMjypva9BLhuwQNascMqaoGLDVd0k2NlE2wMvgZ3rpItzRKCd6vs8s7MFbb8pcR0AEg== + dependencies: + "@types/katex" "^0.11.0" + hast-util-to-text "^2.0.0" + katex "^0.13.0" + rehype-parse "^7.0.0" + unified "^9.0.0" + unist-util-visit "^2.0.0" + +rehype-parse@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-7.0.1.tgz#58900f6702b56767814afc2a9efa2d42b1c90c57" + integrity sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw== + dependencies: + hast-util-from-parse5 "^6.0.0" + parse5 "^6.0.0" + relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" @@ -14398,6 +14444,11 @@ remark-footnotes@2.0.0: resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== +remark-math@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/remark-math/-/remark-math-3.0.1.tgz#85a02a15b15cad34b89a27244d4887b3a95185bb" + integrity sha512-epT77R/HK0x7NqrWHdSV75uNLwn8g9qTyMqCRCDujL0vj/6T6+yhdrR7mjELWtkse+Fw02kijAaBuVcHBor1+Q== + remark-mdx@1.6.22: version "1.6.22" resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" @@ -14479,7 +14530,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== -repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.0.0, repeat-string@^1.5.4, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== @@ -16748,7 +16799,7 @@ unified@9.2.0: trough "^1.0.0" vfile "^4.0.0" -unified@^9.2.2: +unified@^9.0.0, unified@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== @@ -16818,6 +16869,13 @@ unist-builder@2.0.3, unist-builder@^2.0.0: resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== +unist-util-find-after@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-3.0.0.tgz#5c65fcebf64d4f8f496db46fa8fd0fbf354b43e6" + integrity sha512-ojlBqfsBftYXExNu3+hHLfJQ/X1jYY/9vdm4yZWjIbf0VuWF6CRufci1ZyoD/wV2TYMKxXUoNuoqwy+CkgzAiQ== + dependencies: + unist-util-is "^4.0.0" + unist-util-generated@^1.0.0: version "1.1.6" resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b"