diff --git a/.changeset/opt-in-reading-time.md b/.changeset/opt-in-reading-time.md new file mode 100644 index 00000000..07e6eaac --- /dev/null +++ b/.changeset/opt-in-reading-time.md @@ -0,0 +1,5 @@ +--- +'@doc-kit/generator-react': minor +--- + +Make reading time opt-in via `showReadingTime` diff --git a/packages/react/src/html/README.md b/packages/react/src/html/README.md index 5dddd652..65190db3 100644 --- a/packages/react/src/html/README.md +++ b/packages/react/src/html/README.md @@ -416,7 +416,9 @@ export default ({ metadata }) => ( - `metadata` {Object} Serialized page metadata — all YAML frontmatter properties plus `addedIn`, `basename`, `path`, and any custom user-defined fields. - `headings` {Array} Pre-computed table of contents heading entries. -- `readingTime` {string} Estimated reading time (e.g. `'5 min read'`). +- `readingTime` {string|undefined} Estimated reading time (e.g. `'5 min read'`). + Only present when the `jsx-ast` generator's `showReadingTime` option is + enabled. - `children` {ComponentChildren} Processed page content. The `Layout` component receives the props above. Custom Layout components can use diff --git a/packages/react/src/html/ui/components/Layout/index.jsx b/packages/react/src/html/ui/components/Layout/index.jsx index 6308aa74..2621d028 100644 --- a/packages/react/src/html/ui/components/Layout/index.jsx +++ b/packages/react/src/html/ui/components/Layout/index.jsx @@ -18,7 +18,7 @@ import SideBar from '#theme/Sidebar'; * main content, meta bar, and footer. Override via `#theme/Layout` in your * configuration's `imports` to customize the entire page structure. * - * @param {{ metadata: import('../../types').SerializedMetadata, headings: Array, readingTime: string, children: import('preact').ComponentChildren }} props + * @param {{ metadata: import('../../types').SerializedMetadata, headings: Array, readingTime?: string, children: import('preact').ComponentChildren }} props */ export default ({ metadata, headings, readingTime, children }) => { const crossLinkItems = navigation.showCrossLinks diff --git a/packages/react/src/html/ui/components/MetaBar/index.jsx b/packages/react/src/html/ui/components/MetaBar/index.jsx index 5f705efd..bc0d5ed8 100644 --- a/packages/react/src/html/ui/components/MetaBar/index.jsx +++ b/packages/react/src/html/ui/components/MetaBar/index.jsx @@ -49,7 +49,7 @@ const HeadingValue = ({ value, stability }) => { /** * MetaBar component that displays table of contents and page metadata - * @param {{ metadata: import('../../types').SerializedMetadata, headings: Array, readingTime: string }} props + * @param {{ metadata: import('../../types').SerializedMetadata, headings: Array, readingTime?: string }} props */ export default ({ metadata, headings = [], readingTime }) => { const editThisPage = editURL?.replace('{path}', metadata.path); diff --git a/packages/react/src/jsx-ast/README.md b/packages/react/src/jsx-ast/README.md index a8fbd040..2969fb78 100644 --- a/packages/react/src/jsx-ast/README.md +++ b/packages/react/src/jsx-ast/README.md @@ -12,6 +12,8 @@ The `jsx-ast` generator converts MDAST (Markdown Abstract Syntax Tree) to JSX AS for `all.html`. **Default:** `true`. - `generateNotFoundPage` {boolean} When `true`, creates a synthetic JSX AST entry for `404.html`. **Default:** `true`. +- `showReadingTime` {boolean} When `true`, computes an estimated reading time + for each page and displays it in the MetaBar. **Default:** `false`. ## Index page diff --git a/packages/react/src/jsx-ast/index.mjs b/packages/react/src/jsx-ast/index.mjs index 31c23f09..ab202680 100644 --- a/packages/react/src/jsx-ast/index.mjs +++ b/packages/react/src/jsx-ast/index.mjs @@ -18,6 +18,7 @@ export default { ref: 'main', generateAllPage: true, generateNotFoundPage: true, + showReadingTime: false, }, hasParallelProcessor: true, diff --git a/packages/react/src/jsx-ast/types.d.ts b/packages/react/src/jsx-ast/types.d.ts index 97c52182..9d630e1a 100644 --- a/packages/react/src/jsx-ast/types.d.ts +++ b/packages/react/src/jsx-ast/types.d.ts @@ -6,6 +6,7 @@ export type Generator = GeneratorMetadata< ref: string; generateAllPage: boolean; generateNotFoundPage: boolean; + showReadingTime: boolean; }, Generate, AsyncGenerator>, ProcessChunk< diff --git a/packages/react/src/jsx-ast/utils/buildContent.mjs b/packages/react/src/jsx-ast/utils/buildContent.mjs index 7b9a7dda..b06b70a9 100644 --- a/packages/react/src/jsx-ast/utils/buildContent.mjs +++ b/packages/react/src/jsx-ast/utils/buildContent.mjs @@ -11,7 +11,6 @@ import { UNIST } from '@doc-kit/core/utils/queries/index.mjs'; import { transformNodesToString } from '@doc-kit/core/utils/unist.mjs'; import { h as createElement } from 'hastscript'; import { slice } from 'mdast-util-slice-markdown'; -import readingTime from 'reading-time'; import remarkParse from 'remark-parse'; import { unified } from 'unified'; import { u as createTree } from 'unist-builder'; @@ -50,6 +49,12 @@ const toPlainText = markdown => unified().use(remarkParse).parse(markdown).children ).trim(); +/** + * + */ +const readingTime = text => + import('reading-time').then(({ default: rt }) => rt(text).text); + /** * Processes lifecycle and change history data into a sorted array of change entries. * @param {import('@doc-kit/core/generators/metadata/types').MetadataEntry} entry - The metadata entry @@ -315,16 +320,20 @@ export const processEntry = entry => { * @param {Array} entries - API documentation metadata entries * @param {Object} metadata - Raw page metadata from the head entry */ -export const createDocumentLayout = (entries, metadata) => { +export const createDocumentLayout = async (entries, metadata) => { // Collapse overloaded function headings into one stable ToC entry, tagging the // underlying headings with compact anchors / overload flags read just below. annotateOverloads(entries); + const { showReadingTime } = getConfig('jsx-ast'); + return createTree('root', [ createJSXElement(JSX_IMPORTS.Layout.name, { metadata, headings: extractHeadings(entries), - readingTime: readingTime(extractTextContent(entries)).text, + readingTime: showReadingTime + ? await readingTime(extractTextContent(entries)) + : undefined, children: entries.map(processEntry), }), ]); @@ -348,7 +357,7 @@ const buildContent = async (metadataEntries, head) => { ]); // Create root document AST with all layout components and processed content - const root = createDocumentLayout(metadataEntries, metadata); + const root = await createDocumentLayout(metadataEntries, metadata); // Run remark processor to transform AST (parse markdown, plugins, etc.) const ast = await remark().run(root);