diff --git a/enums.ts b/enums.ts index a8e849ef0..144988ffa 100644 --- a/enums.ts +++ b/enums.ts @@ -1,4 +1,5 @@ export enum NodeTypes { + callout = 'callout', codeTabs = 'code-tabs', emoji = 'emoji', i = 'i', diff --git a/index.tsx b/index.tsx index db621edb2..dee60c40f 100644 --- a/index.tsx +++ b/index.tsx @@ -30,7 +30,7 @@ type RunOpts = Omit & { }; type MdastOpts = { - components?: ComponentOpts; + components?: Record; }; export { Components }; @@ -112,12 +112,12 @@ export const html = (text: string, opts = {}) => { unimplemented('html export'); }; -const astProcessor = (opts = {}) => +const astProcessor = (opts: MdastOpts = { components: {} }) => remark() .use(remarkMdx) .use(remarkFrontmatter) .use(remarkPlugins) - .use(readmeComponentsTransformer, { components: opts.components }); + .use(readmeComponentsTransformer({ components: opts.components })); export const mdast: any = (text: string, opts: MdastOpts = {}) => { const processor = astProcessor(opts); diff --git a/package-lock.json b/package-lock.json index f4f7c6f40..cd199ba8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "trim": "^1.0.1", "unified": "^8.4.0", "unist-util-flatmap": "^1.0.0", - "unist-util-visit": "^4.1.1", + "unist-util-visit": "^5.0.0", "util": "^0.12.5" }, "devDependencies": { @@ -63,6 +63,7 @@ "@types/jest": "^29.5.12", "@types/mdast": "^4.0.3", "@types/mdx": "^2.0.12", + "@types/unist": "^3.0.2", "@vitejs/plugin-react": "^4.2.1", "@vitest/ui": "^1.6.0", "babel-jest": "^29.5.0", @@ -4531,33 +4532,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/@mdx-js/mdx/node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@mdx-js/mdx/node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/@mdx-js/react": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", @@ -18957,19 +18931,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-find-and-replace/node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-from-markdown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", @@ -19223,33 +19184,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-to-hast/node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast/node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-to-markdown": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", @@ -19269,33 +19203,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-to-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", @@ -29207,33 +29114,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-remove-position/node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position/node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -29247,13 +29127,13 @@ } }, "node_modules/unist-util-visit": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", - "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", @@ -29261,46 +29141,12 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", - "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents/node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" - }, - "node_modules/unist-util-visit-parents/node_modules/unist-util-is": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", - "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", - "dependencies": { - "@types/unist": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit/node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" - }, - "node_modules/unist-util-visit/node_modules/unist-util-is": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", - "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", "dependencies": { - "@types/unist": "^2.0.0" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", diff --git a/package.json b/package.json index 9885bf9ee..db0673fb6 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "trim": "^1.0.1", "unified": "^8.4.0", "unist-util-flatmap": "^1.0.0", - "unist-util-visit": "^4.1.1", + "unist-util-visit": "^5.0.0", "util": "^0.12.5" }, "peerDependencies": { @@ -85,6 +85,7 @@ "@types/jest": "^29.5.12", "@types/mdast": "^4.0.3", "@types/mdx": "^2.0.12", + "@types/unist": "^3.0.2", "@vitejs/plugin-react": "^4.2.1", "@vitest/ui": "^1.6.0", "babel-jest": "^29.5.0", diff --git a/processor/transform/readme-components.ts b/processor/transform/readme-components.ts index b0f5255b8..381ebdaf9 100644 --- a/processor/transform/readme-components.ts +++ b/processor/transform/readme-components.ts @@ -1,5 +1,8 @@ -import { BlockContent, Paragraph, Root, TableRow } from 'mdast'; -import { MdxJsxFlowElement } from 'mdast-util-mdx'; +import { BlockContent, Code, Image, Paragraph, Parents, Table, TableRow } from 'mdast'; +import { Transform } from 'mdast-util-from-markdown'; + +import { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx'; +import { Callout, CodeTabs } from 'types'; import { visit } from 'unist-util-visit'; const types = { @@ -12,88 +15,104 @@ const types = { td: 'tableCell', }; -const attributes = (jsx: MdxJsxFlowElement) => +const attributes = (jsx: MdxJsxFlowElement | MdxJsxTextElement) => jsx.attributes.reduce((memo, attr) => { - memo[attr.name] = attr.value; + if ('name' in attr) { + memo[attr.name] = attr.value; + } + return memo; - }, {}); + }, {} as T); + +interface Options { + components: Record; +} -const readmeComponents = +const coerceJsxToMd = ({ components = {} } = {}) => - (tree: Root) => { - visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node, index, parent) => { - if (node.name in components) return; - - if (node.name === 'Code') { - const { position } = node; - const { value, lang = null, meta = null } = attributes(node); - - const mdNode = { - lang, - meta, - position, - type: 'code', - value, - data: { - hProperties: { value, lang, meta }, - }, - }; - - parent.children[index] = mdNode; - } else if (node.name === 'Image') { - const { position } = node; - const { alt = '', src, title = null } = attributes(node); - - const mdNode = { - alt, - position, - title, - type: 'image', - url: src, - }; - - parent.children[index] = mdNode; - } else if (node.name === 'Table') { - const { children, position } = node; - const { align = [...new Array(node.children.length)].map(() => null) } = attributes(node); - - const mdNode = { - align, - type: 'table', - position, - children, - }; - - parent.children[index] = mdNode; - } else if (node.name in types) { - const hProperties = attributes(node); - - const mdNode = { - children: node.children, - type: types[node.name], - ...(['tr', 'td'].includes(node.name) - ? {} - : { - data: { - hName: node.name, - ...(Object.keys(hProperties).length ? { hProperties } : {}), - }, - }), - position: node.position, - }; - - parent.children[index] = mdNode; - } - }); - - visit(tree, 'paragraph', (node: Paragraph, index: number, parent: BlockContent | TableRow) => { - if (parent.type !== 'tableRow') return; + (node: MdxJsxFlowElement | MdxJsxTextElement, index: number, parent: Parents) => { + if (node.name in components) return; + + if (node.name === 'Code') { + const { position } = node; + const { value, lang = null, meta = null } = attributes>(node); + + const mdNode: Code = { + lang, + meta, + position, + type: 'code', + value, + data: { + hProperties: { value, lang, meta }, + }, + }; + + parent.children[index] = mdNode; + } else if (node.name === 'Image') { + const { position } = node; + const { alt = '', src, title = null } = attributes & { src: string }>(node); + + const mdNode: Image = { + alt, + position, + title, + type: 'image', + url: src, + }; + + parent.children[index] = mdNode; + } else if (node.name === 'Table') { + const { children, position } = node; + const { align = [...new Array(node.children.length)].map(() => null) } = attributes>(node); + + const mdNode: Table = { + align, + type: 'table', + position, + // @ts-ignore + children, + }; + + parent.children[index] = mdNode; + } else if (node.name in types) { + const hProperties = attributes(node); // @ts-ignore - parent.children.splice(index, 1, ...node.children); - }); - - return tree; + const mdNode: Callout | CodeTabs = { + children: node.children as any, + type: types[node.name], + ...(['tr', 'td'].includes(node.name) + ? {} + : { + data: { + hName: node.name, + ...(Object.keys(hProperties).length ? { hProperties } : {}), + }, + }), + position: node.position, + }; + + parent.children[index] = mdNode; + } }; +const readmeComponents = (opts: Options) => (): Transform => tree => { + // @TODO: unist-util-visit does a really good job with types, **but** it + // can't seem to infer allowing multiple types passed to the visitor + // function. Otherwise, I would have these two function calls be one? + visit(tree, 'mdxJsxFlowElement', coerceJsxToMd(opts)); + visit(tree, 'mdxJsxTextElement', coerceJsxToMd(opts)); + + visit(tree, 'paragraph', (node, index, parent) => { + // @ts-ignore + if (parent.type !== 'tableRow') return; + + // @ts-ignore + parent.children.splice(index, 1, ...node.children); + }); + + return tree; +}; + export default readmeComponents; diff --git a/types.d.ts b/types.d.ts index f890fc74d..df0477167 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,6 +1,14 @@ -import { Code, Data, Literal, Table, Parent } from 'mdast'; +import { Code, Data, Literal, Parent, BlockContent } from 'mdast'; import { NodeTypes } from './enums'; +interface Callout extends Parent { + type: NodeTypes.callout; + children: BlockContent[]; + data: Data & { + hName: 'Callout'; + }; +} + interface CodeTabs extends Parent { type: NodeTypes.codeTabs; children: Code[]; @@ -30,16 +38,17 @@ interface FaEmoji extends Literal { declare module 'mdast' { interface BlockContentMap { + [NodeTypes.callout]: Callout; [NodeTypes.codeTabs]: CodeTabs; } interface PhrasingContentMap { - [NodeTypes.codeTabs]: CodeTabs; [NodeTypes.emoji]: Gemoji; [NodeTypes.i]: FaEmoji; } interface RootContentMap { + [NodeTypes.callout]: Callout; [NodeTypes.codeTabs]: CodeTabs; [NodeTypes.emoji]: Gemoji; [NodeTypes.i]: FaEmoji;