diff --git a/packages/codehike/package.json b/packages/codehike/package.json index f8eb0164..6573b123 100644 --- a/packages/codehike/package.json +++ b/packages/codehike/package.json @@ -57,6 +57,7 @@ "diff": "^5.1.0", "estree-util-visit": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-to-markdown": "^2.1.2", "unist-util-visit": "^5.0.0" }, "devDependencies": { diff --git a/packages/codehike/src/mdx/1.0.transform-hikes.ts b/packages/codehike/src/mdx/1.0.transform-hikes.ts index 26c74006..9a90efad 100644 --- a/packages/codehike/src/mdx/1.0.transform-hikes.ts +++ b/packages/codehike/src/mdx/1.0.transform-hikes.ts @@ -1,10 +1,47 @@ import { Root } from "mdast" -import { MdxJsxFlowElement } from "mdast-util-mdx-jsx" +import { MdxJsxAttribute, MdxJsxFlowElement } from "mdast-util-mdx-jsx" import { visit } from "unist-util-visit" import { isHikeElement, listToSection } from "./1.1.remark-list-to-section.js" import { sectionToAttribute } from "./1.2.remark-section-to-attribute.js" import { CodeHikeConfig } from "./config.js" +/** + * Determines whether Markdown is enabled for the given MDX JSX element. + * + * This function checks for the presence of a `markdownEnabled` attribute: + * - If no attribute is found, it returns `false`. + * - If the attribute is present in shorthand form (e.g. ``), it returns `true`. + * - If the attribute is an MDX expression (e.g. ``), it checks if the raw expression text is + * literally `"true"`. + */ +export function isMarkdownEnabled(node: MdxJsxFlowElement): boolean { + // Look for the "markdownEnabled" attribute within the node’s attributes. + const markdownEnabledAttr = node.attributes.find( + (attr): attr is MdxJsxAttribute => + attr.type === "mdxJsxAttribute" && attr.name === "markdownEnabled", + ) + + if (!markdownEnabledAttr) return false + + // Shorthand () implies true. + if (markdownEnabledAttr.value === null) return true + + // If the attribute value is an object, it indicates an MDX expression + // (e.g. markdownEnabled={true}). The `.value` property on this object is the + // raw string representation of the expression, so we check if it’s + // literally "true". + if ( + typeof markdownEnabledAttr.value === "object" && + markdownEnabledAttr.value.type === "mdxJsxAttributeValueExpression" + ) { + return markdownEnabledAttr.value.value.trim() === "true" + } + + return false +} + export async function transformAllHikes(root: Root, config: CodeHikeConfig) { let tree = wrapInHike(root) @@ -42,8 +79,10 @@ async function transformRemarkHike( node: MdxJsxFlowElement, config: CodeHikeConfig, ) { + const markdownEnabled = isMarkdownEnabled(node) + const section = await listToSection(node, config) - const { children, attributes } = sectionToAttribute(section) + const { children, attributes } = sectionToAttribute(section, markdownEnabled) node.children = children node.attributes.push(...attributes) diff --git a/packages/codehike/src/mdx/1.2.remark-section-to-attribute.ts b/packages/codehike/src/mdx/1.2.remark-section-to-attribute.ts index accbc374..f18f9dbc 100644 --- a/packages/codehike/src/mdx/1.2.remark-section-to-attribute.ts +++ b/packages/codehike/src/mdx/1.2.remark-section-to-attribute.ts @@ -1,15 +1,15 @@ import { MdxJsxAttribute, MdxJsxFlowElement } from "mdast-util-mdx-jsx" -import { - HikeContent, - HikeSection, - JSXChild, -} from "./1.1.remark-list-to-section.js" +import { toMarkdown } from "mdast-util-to-markdown" +import { HikeSection, JSXChild } from "./1.1.remark-list-to-section.js" import { getObjectAttribute } from "./estree.js" -export function sectionToAttribute(root: HikeSection) { +export function sectionToAttribute( + root: HikeSection, + markdownEnabled: boolean, +) { const children: JSXChild[] = getSectionContainers(root, "") - const serializableTree = getSerializableNode(root, "") + const serializableTree = getSerializableNode(root, "", markdownEnabled) return { children, @@ -23,7 +23,11 @@ export function sectionToAttribute(root: HikeSection) { } } -function getSerializableNode(section: HikeSection, path: string) { +function getSerializableNode( + section: HikeSection, + path: string, + markdownEnabled: boolean = false, +) { const newPath = path ? [path, section.name].join(".") : section.name const node: any = { children: newPath, @@ -33,10 +37,21 @@ function getSerializableNode(section: HikeSection, path: string) { section.children.forEach((child) => { if (child.type === "content") { + if (markdownEnabled) { + // If Markdown is enabled, convert paragraph nodes into Markdown text + // and accumulate them in the `node.markdown` property. + if (child.value.type === "paragraph") { + if (node.markdown == null) { + node.markdown = toMarkdown(child.value) + } else { + node.markdown += toMarkdown(child.value) + } + } + } return } if (child.type === "section") { - const childNode = getSerializableNode(child, newPath) + const childNode = getSerializableNode(child, newPath, markdownEnabled) if (child.multi) { node[child.name] = node[child.name] || [] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90afdf7a..71237a32 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -198,6 +198,9 @@ importers: mdast-util-mdx-jsx: specifier: ^3.0.0 version: 3.0.0 + mdast-util-to-markdown: + specifier: ^2.1.2 + version: 2.1.2 unist-util-visit: specifier: ^5.0.0 version: 5.0.0 @@ -3050,6 +3053,9 @@ packages: mdast-util-to-markdown@2.1.0: resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -3840,10 +3846,11 @@ packages: shikiji-core@0.9.19: resolution: {integrity: sha512-AFJu/vcNT21t0e6YrfadZ+9q86gvPum6iywRyt1OtIPjPFe25RQnYJyxHQPMLKCCWA992TPxmEmbNcOZCAJclw==} + deprecated: Deprecated, use @shikijs/core instead shikiji@0.9.19: resolution: {integrity: sha512-Kw2NHWktdcdypCj1GkKpXH4o6Vxz8B8TykPlPuLHOGSV8VkhoCLcFOH4k19K4LXAQYRQmxg+0X/eM+m2sLhAkg==} - deprecated: Shikiji is merged back to Shiki v1.0, please migrate over to get the latest updates + deprecated: Deprecated, use shiki instead side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -7609,7 +7616,7 @@ snapshots: '@types/mdast': 4.0.3 devlop: 1.1.0 mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7622,7 +7629,7 @@ snapshots: ccount: 2.0.1 devlop: 1.1.0 mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.1 stringify-entities: 4.0.3 unist-util-remove-position: 5.0.0 @@ -7637,7 +7644,7 @@ snapshots: mdast-util-mdx-expression: 2.0.0 mdast-util-mdx-jsx: 3.0.0 mdast-util-mdxjs-esm: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7648,7 +7655,7 @@ snapshots: '@types/mdast': 4.0.3 devlop: 1.1.0 mdast-util-from-markdown: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -7679,6 +7686,18 @@ snapshots: unist-util-visit: 5.0.0 zwitch: 2.0.4 + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.3 + '@types/unist': 3.0.2 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.0.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.3