feat: auto generated author page

This commit is contained in:
Hossein Mehrabi
2023-06-07 03:10:21 +03:30
parent b23eecc912
commit 090813fdea
12 changed files with 382 additions and 7 deletions
@@ -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<DocMetadataProps> = ({
...props
}) => {
const { date, authors } = useDocMetadata()
const { content: { authorPage } = {} } = useDocThemeOptions()
return (
<div className={clsx(className, 'mdx-doc-metadata')} {...(props as any)}>
@@ -19,7 +22,17 @@ export const DocMetadata: React.FC<DocMetadataProps> = ({
{authors && authors.length > 0 && (
<>
<Typography variant="body2">
by {authors.map((author) => author!.name).join(', ')}
by{' '}
{authors.map((author, index) => (
<React.Fragment key={author!.key}>
{authorPage ? (
<Link to={`author/${author!.key}`}>{author!.name}</Link>
) : (
author!.name
)}
{index < authors.length - 1 && ', '}
</React.Fragment>
))}
</Typography>
</>
)}
@@ -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
}
@@ -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;
}
}
@@ -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<AuthorPageProps> = ({
data: { author, docs },
}) => {
return (
<div className={styles.root}>
<Typography className={styles.name} variant="h3">
{author.name}
</Typography>
<div className={styles.links}>
{author.github && (
<Typography
variant="body2"
component="a"
target="_blank"
href={`https://github.com/${author.github}`}
>
Github
</Typography>
)}
{author.twitter && (
<Typography
variant="body2"
component="a"
target="_blank"
href={`https://twitter.com/${author.twitter}`}
>
Twitter
</Typography>
)}
{author.website && (
<Typography
variant="body2"
component="a"
target="_blank"
href={author.website}
>
Website
</Typography>
)}
</div>
<div className={styles.docs}>
{docs.map((doc, index) => (
<div className={styles.doc} key={index}>
{doc.frontMatter.date && (
<Typography
className={styles.date}
variant="body2"
component="div"
>
{formatDate(new Date(doc.frontMatter.date), 'MMM d yyyy')}
</Typography>
)}
<Link className={styles.title} href={doc.permalink}>
{doc.title}
</Link>
<Typography className={styles.description} variant="body1">
{doc.description}
</Typography>
</div>
))}
</div>
</div>
)
}
export default AuthorPage
@@ -0,0 +1 @@
export * from './AuthorPage'
@@ -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 = {
+7 -2
View File
@@ -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<undefined> {
): Plugin<any> {
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 }
@@ -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<string, any[]> = {}
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,
})
}
}
}
@@ -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`,
)
}
@@ -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] ?? {}
}
@@ -1,6 +1,7 @@
import { Joi } from '@docusaurus/utils-validation'
import {
Author,
AuthorPageConfig,
DocConfig,
DocContent,
DocSidebarConfig,
@@ -16,6 +17,12 @@ const schema = Joi.object<ThemeOptions>({
hide: Joi.bool().default(true),
}).default({}),
content: Joi.object<DocContent>({
authorPage: Joi.alternatives(
Joi.boolean(),
Joi.object<AuthorPageConfig>({
sidebar: Joi.string().optional(),
}),
).default({}),
authors: Joi.array()
.items(
Joi.object<Author>({
+61 -3
View File
@@ -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"