-
Notifications
You must be signed in to change notification settings - Fork 65
feat(ui): group overloaded functions into tabs #1047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f1c4849
47d88f5
23ae591
2f142ae
e6521be
9a26504
739046d
88170be
23d5927
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { | |||||
| GITHUB_BLOB_URL, | ||||||
| populate, | ||||||
| } from '@doc-kit/core/utils/configuration/templates.mjs'; | ||||||
| import { highlighter } from '@doc-kit/core/utils/highlighter.mjs'; | ||||||
| import { parseInline } from '@doc-kit/core/utils/inline.mjs'; | ||||||
| import { omitKeys } from '@doc-kit/core/utils/misc.mjs'; | ||||||
| import { UNIST } from '@doc-kit/core/utils/queries/index.mjs'; | ||||||
|
|
@@ -15,7 +16,7 @@ import { slice } from 'mdast-util-slice-markdown'; | |||||
| import { u as createTree } from 'unist-builder'; | ||||||
| import { SKIP, visit } from 'unist-util-visit'; | ||||||
|
|
||||||
| import { createJSXElement } from './ast.mjs'; | ||||||
| import { createJSXElement, createAttributeNode } from './ast.mjs'; | ||||||
| import { extractHeadings, extractTextContent } from './buildBarProps.mjs'; | ||||||
| import { annotateOverloads } from './overloads.mjs'; | ||||||
| import { getRemarkRecma as remark } from './remark.mjs'; | ||||||
|
|
@@ -317,6 +318,140 @@ export const processEntry = entry => { | |||||
| return entry.content; | ||||||
| }; | ||||||
|
|
||||||
| /** | ||||||
| * Groups consecutive overloaded function API entries into a single OverloadTabs component. | ||||||
| * @param {Array<import('estree').Node>} processedChildren - The processed JSX AST nodes for the API entries | ||||||
| * @param {Array<import('@doc-kit/core/generators/metadata/types').MetadataEntry>} originalEntries - The original API metadata entries containing the overload flags | ||||||
| * @returns {Array<import('estree').Node>} The final array of layout children with overloads grouped | ||||||
| */ | ||||||
| export const groupOverloadsIntoTabs = (processedChildren, originalEntries) => { | ||||||
| const finalChildren = []; | ||||||
| let activeOverloadGroup = null; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| /** | ||||||
| * Wraps an AST node's children in a styled panel `div` for tab rendering. | ||||||
| * @param {import('estree').Node} rootNode - The root node whose children will be wrapped. | ||||||
| * @returns {import('estree').Node} The new `div` AST node containing the children. | ||||||
| */ | ||||||
| const wrapInDiv = rootNode => { | ||||||
| return createJSXElement('div', { | ||||||
| inline: false, | ||||||
| className: 'overload-panel', | ||||||
| children: rootNode.children || [], | ||||||
| }); | ||||||
| }; | ||||||
|
|
||||||
| /** | ||||||
| * Extracts the raw signature string from an API entry node and removes the signature node from its children. | ||||||
| * @param {import('estree').Node} node - The AST node representing the API entry. | ||||||
| * @returns {string|null} The raw TypeScript signature string, or null if not found. | ||||||
| */ | ||||||
| const extractSignature = node => { | ||||||
| const sigIdx = (node.children || []).findIndex( | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||
| c => | ||||||
| c.properties?.className?.includes('signature') || | ||||||
| c.properties?.class === 'signature' | ||||||
| ); | ||||||
| if (sigIdx !== -1) { | ||||||
| const sigNode = node.children.splice(sigIdx, 1)[0]; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||
| return sigNode.properties?.dataSignatureRaw; | ||||||
| } | ||||||
| return null; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: default to return nothing, so it is undefined. |
||||||
| }; | ||||||
|
|
||||||
| /** | ||||||
| * Finalizes the active overload group by generating a combined signatures block | ||||||
| */ | ||||||
| const pushOverloadGroup = () => { | ||||||
| if (!activeOverloadGroup) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| // Deduplicate signatures and join with a single newline | ||||||
| const uniqueSignatures = [...new Set(activeOverloadGroup.signatures)]; | ||||||
| const combinedSigRaw = uniqueSignatures.join('\n'); | ||||||
|
|
||||||
| const highlighted = highlighter.highlightToHast( | ||||||
| combinedSigRaw, | ||||||
| 'typescript' | ||||||
| ); | ||||||
| const combinedSigNode = createElement('div', { class: 'signature' }, [ | ||||||
| highlighted, | ||||||
| ]); | ||||||
|
|
||||||
| // Push combined signatures | ||||||
| finalChildren.push(combinedSigNode); | ||||||
|
|
||||||
| // Inject properties needed by CodeTabs component | ||||||
| const count = activeOverloadGroup.signatures.length; | ||||||
|
|
||||||
| const languagesArr = []; | ||||||
| const displayNamesArr = []; | ||||||
|
|
||||||
| for (let i = 0; i < count; i++) { | ||||||
| languagesArr.push('overload'); | ||||||
| displayNamesArr.push(`Overload #${i + 1}`); | ||||||
| } | ||||||
|
|
||||||
| activeOverloadGroup.tabsNode.attributes.push( | ||||||
| createAttributeNode('languages', languagesArr.join('|')), | ||||||
| createAttributeNode('displayNames', displayNamesArr.join('|')) | ||||||
| ); | ||||||
|
|
||||||
| // Push the tabs | ||||||
| finalChildren.push(activeOverloadGroup.tabsNode); | ||||||
|
|
||||||
| activeOverloadGroup = null; | ||||||
| }; | ||||||
|
|
||||||
| /** | ||||||
| * Processes a single API entry node belonging to an overload group. | ||||||
| * It extracts its signature and pushes its remaining content into a new tab panel. | ||||||
| * @param {import('estree').Node} node - The AST node to process and add to the active group. | ||||||
| */ | ||||||
| const processOverloadNode = node => { | ||||||
| const sigRaw = extractSignature(node); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. feels like this can be a ternary |
||||||
| if (sigRaw) { | ||||||
| activeOverloadGroup.signatures.push(sigRaw); | ||||||
| } | ||||||
| activeOverloadGroup.tabsNode.children.push(wrapInDiv(node)); | ||||||
| }; | ||||||
|
|
||||||
| for (const [i, current] of processedChildren.entries()) { | ||||||
| if (originalEntries[i].heading?.data?.isOverload) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we simply the if statements or make early return/continue; logic? |
||||||
| if (activeOverloadGroup) { | ||||||
| current.children.shift(); | ||||||
| processOverloadNode(current); | ||||||
| } else { | ||||||
| const last = finalChildren.pop(); | ||||||
| activeOverloadGroup = { | ||||||
| firstHeading: last?.children?.shift?.(), | ||||||
| signatures: [], | ||||||
| tabsNode: createJSXElement(JSX_IMPORTS.CodeTabs.name, { | ||||||
| inline: false, | ||||||
| children: [], | ||||||
| }), | ||||||
| }; | ||||||
| current.children.shift(); | ||||||
|
|
||||||
| processOverloadNode(last); | ||||||
| processOverloadNode(current); | ||||||
|
|
||||||
| if (activeOverloadGroup.firstHeading) { | ||||||
| finalChildren.push(activeOverloadGroup.firstHeading); | ||||||
| } | ||||||
| } | ||||||
| } else { | ||||||
| pushOverloadGroup(); | ||||||
| finalChildren.push(current); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| pushOverloadGroup(); | ||||||
|
|
||||||
| return finalChildren; | ||||||
| }; | ||||||
|
|
||||||
| /** | ||||||
| * Builds the overall document layout tree | ||||||
| * @param {Array<import('@doc-kit/core/generators/metadata/types').MetadataEntry>} entries - API documentation metadata entries | ||||||
|
|
@@ -336,7 +471,7 @@ export const createDocumentLayout = async (entries, metadata) => { | |||||
| readingTime: showReadingTime | ||||||
| ? await readingTime(extractTextContent(entries)) | ||||||
| : undefined, | ||||||
| children: entries.map(processEntry), | ||||||
| children: groupOverloadsIntoTabs(entries.map(processEntry), entries), | ||||||
| }), | ||||||
| ]); | ||||||
| }; | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.