docs: add util to generate clean markdown for a file (#11303)
This commit is contained in:
@@ -28,7 +28,9 @@
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/mdx": "^3.1.0",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-mdx": "^3.1.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"to-vfile": "^8.0.0",
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ExpressionJsVarLiteral,
|
||||
LiteralExpression,
|
||||
ObjectExpression,
|
||||
} from "../types/index.js"
|
||||
} from "types"
|
||||
|
||||
export function estreeToJs(estree: Estree) {
|
||||
// TODO improve on this utility. Currently it's implemented to work
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExpressionJsVarLiteral, ExpressionJsVarObj } from "../types/index.js"
|
||||
import { ExpressionJsVarLiteral, ExpressionJsVarObj } from "types"
|
||||
|
||||
export function isExpressionJsVarLiteral(
|
||||
expression: unknown
|
||||
136
www/packages/docs-utils/src/get-clean-md.ts
Normal file
136
www/packages/docs-utils/src/get-clean-md.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import remarkMdx from "remark-mdx"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkStringify from "remark-stringify"
|
||||
import { read } from "to-vfile"
|
||||
import { UnistNode, UnistNodeWithData, UnistTree } from "types"
|
||||
import { Plugin, Transformer, unified } from "unified"
|
||||
import { SKIP } from "unist-util-visit"
|
||||
import type { VFile } from "vfile"
|
||||
import {
|
||||
parseCard,
|
||||
parseCardList,
|
||||
parseCodeTabs,
|
||||
parseDetails,
|
||||
parseNote,
|
||||
parsePrerequisites,
|
||||
parseSourceCodeLink,
|
||||
parseTable,
|
||||
parseTabs,
|
||||
parseTypeList,
|
||||
parseWorkflowDiagram,
|
||||
} from "./utils/parse-elms.js"
|
||||
|
||||
const parseComponentsPlugin = (): Transformer => {
|
||||
return async (tree) => {
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
let pageTitle = ""
|
||||
|
||||
visit(
|
||||
tree as UnistTree,
|
||||
["mdxJsxFlowElement", "element", "mdxjsEsm", "heading"],
|
||||
(node: UnistNode, index, parent) => {
|
||||
if (typeof index !== "number" || !parent) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
node.type === "mdxjsEsm" &&
|
||||
node.value?.startsWith("export const metadata = ") &&
|
||||
node.data &&
|
||||
"estree" in node.data
|
||||
) {
|
||||
const regexMatch = /title: (?<title>.+),?/.exec(node.value)
|
||||
if (regexMatch?.groups?.title) {
|
||||
pageTitle = regexMatch.groups.title
|
||||
.replace(/,$/, "")
|
||||
.replaceAll(/\$\{.+\}/g, "")
|
||||
.replaceAll(/^['"`]/g, "")
|
||||
.replaceAll(/['"`]$/g, "")
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
if (node.type === "heading") {
|
||||
if (
|
||||
node.depth === 1 &&
|
||||
node.children?.length &&
|
||||
node.children[0].value === "metadata.title"
|
||||
) {
|
||||
node.children[0] = {
|
||||
type: "text",
|
||||
value: pageTitle,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (
|
||||
node.type === "mdxjsEsm" ||
|
||||
node.name === "Feedback" ||
|
||||
node.name === "ChildDocs" ||
|
||||
node.name === "DetailsList"
|
||||
) {
|
||||
parent?.children.splice(index, 1)
|
||||
return [SKIP, index]
|
||||
}
|
||||
switch (node.name) {
|
||||
case "Card":
|
||||
return parseCard(node, index, parent)
|
||||
case "CardList":
|
||||
return parseCardList(node as UnistNodeWithData, index, parent)
|
||||
case "CodeTabs":
|
||||
return parseCodeTabs(node as UnistNodeWithData, index, parent)
|
||||
case "Details":
|
||||
return parseDetails(node as UnistNodeWithData, index, parent)
|
||||
case "Note":
|
||||
return parseNote(node, index, parent)
|
||||
case "Prerequisites":
|
||||
return parsePrerequisites(node as UnistNodeWithData, index, parent)
|
||||
case "SourceCodeLink":
|
||||
return parseSourceCodeLink(node as UnistNodeWithData, index, parent)
|
||||
case "Table":
|
||||
return parseTable(node as UnistNodeWithData, index, parent)
|
||||
case "Tabs":
|
||||
return parseTabs(node as UnistNodeWithData, index, parent)
|
||||
case "TypeList":
|
||||
return parseTypeList(node as UnistNodeWithData, index, parent)
|
||||
case "WorkflowDiagram":
|
||||
return parseWorkflowDiagram(
|
||||
node as UnistNodeWithData,
|
||||
index,
|
||||
parent
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const getParsedAsString = (file: VFile): string => {
|
||||
return file.toString().replaceAll(/^([\s]*)\* /gm, "$1- ")
|
||||
}
|
||||
|
||||
export const getCleanMd = async (
|
||||
filePath: string,
|
||||
plugins?: {
|
||||
before?: Plugin[]
|
||||
after?: Plugin[]
|
||||
}
|
||||
): Promise<string> => {
|
||||
if (!filePath.endsWith(".md") && !filePath.endsWith(".mdx")) {
|
||||
return ""
|
||||
}
|
||||
const unifier = unified().use(remarkParse).use(remarkMdx).use(remarkStringify)
|
||||
|
||||
plugins?.before?.forEach((plugin) => {
|
||||
unifier.use(...(Array.isArray(plugin) ? plugin : [plugin]))
|
||||
})
|
||||
|
||||
unifier.use(parseComponentsPlugin)
|
||||
|
||||
plugins?.after?.forEach((plugin) => {
|
||||
unifier.use(...(Array.isArray(plugin) ? plugin : [plugin]))
|
||||
})
|
||||
|
||||
const parsed = await unifier.process(await read(filePath))
|
||||
|
||||
return getParsedAsString(parsed)
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
export * from "./estree-to-js.js"
|
||||
export * from "./expression-is-utils.js"
|
||||
export * from "./find-title.js"
|
||||
export * from "./get-clean-md.js"
|
||||
export * from "./get-file-slug-sync.js"
|
||||
export * from "./get-file-slug.js"
|
||||
export * from "./get-front-matter.js"
|
||||
|
||||
597
www/packages/docs-utils/src/utils/parse-elms.ts
Normal file
597
www/packages/docs-utils/src/utils/parse-elms.ts
Normal file
@@ -0,0 +1,597 @@
|
||||
import { ExpressionJsVar, UnistNode, UnistNodeWithData, UnistTree } from "types"
|
||||
import { SKIP, VisitorResult } from "unist-util-visit"
|
||||
import { estreeToJs } from "../estree-to-js.js"
|
||||
import {
|
||||
isExpressionJsVarLiteral,
|
||||
isExpressionJsVarObj,
|
||||
} from "../expression-is-utils.js"
|
||||
|
||||
export const parseCard = (
|
||||
node: UnistNode,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
let title: string | undefined,
|
||||
text: string | undefined,
|
||||
href: string | undefined
|
||||
node.attributes?.some((attr) => {
|
||||
if (title && text && href) {
|
||||
return true
|
||||
}
|
||||
if (attr.name === "title") {
|
||||
title = attr.value as string
|
||||
} else if (attr.name === "text") {
|
||||
text = attr.value as string
|
||||
} else if (attr.name === "href") {
|
||||
href = attr.value as string
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (!title || !href) {
|
||||
return
|
||||
}
|
||||
parent?.children.splice(index, 1, {
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "link",
|
||||
url: href,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: title,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
value: `: ${text}`,
|
||||
},
|
||||
],
|
||||
})
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseCardList = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const items = node.attributes?.find((attr) => attr.name === "items")
|
||||
if (!items || typeof items.value === "string" || !items.value.data?.estree) {
|
||||
return
|
||||
}
|
||||
|
||||
const itemsJsVar = estreeToJs(items.value.data.estree)
|
||||
|
||||
if (!itemsJsVar || !Array.isArray(itemsJsVar)) {
|
||||
return
|
||||
}
|
||||
|
||||
const listItems = itemsJsVar
|
||||
.map((item) => {
|
||||
if (
|
||||
!isExpressionJsVarObj(item) ||
|
||||
!("text" in item) ||
|
||||
!("link" in item) ||
|
||||
!isExpressionJsVarLiteral(item.text) ||
|
||||
!isExpressionJsVarLiteral(item.link)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
type: "listItem",
|
||||
children: [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "link",
|
||||
url: `#${item.link.data}`,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: item.text.data,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as UnistNode[]
|
||||
|
||||
parent?.children.splice(index, 1, {
|
||||
type: "list",
|
||||
ordered: false,
|
||||
spread: false,
|
||||
children: listItems,
|
||||
})
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseCodeTabs = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const tabs = node.children?.filter((child) => child.name === "CodeTab")
|
||||
if (!tabs) {
|
||||
return
|
||||
}
|
||||
|
||||
const children: UnistNode[] = []
|
||||
|
||||
tabs.forEach((tab) => {
|
||||
const label = tab.attributes?.find((attr) => attr.name === "label")
|
||||
const code = tab.children?.find((child) => child.type === "code")
|
||||
|
||||
if (!label || !code) {
|
||||
return
|
||||
}
|
||||
|
||||
children.push({
|
||||
type: "mdxJsxFlowElement",
|
||||
name: "details",
|
||||
children: [
|
||||
{
|
||||
type: "mdxJsxFlowElement",
|
||||
name: "summary",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: (label.value as string) || "summary",
|
||||
},
|
||||
],
|
||||
},
|
||||
code,
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
parent?.children.splice(index, 1, ...children)
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseDetails = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const summary = node.attributes?.find(
|
||||
(attr) => attr.name === "summaryContent"
|
||||
)
|
||||
|
||||
parent?.children.splice(index, 1, {
|
||||
type: "mdxJsxFlowElement",
|
||||
name: "details",
|
||||
children: [
|
||||
{
|
||||
type: "mdxJsxFlowElement",
|
||||
name: "summary",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: (summary?.value as string) || "Details",
|
||||
},
|
||||
],
|
||||
},
|
||||
...(node.children || []),
|
||||
],
|
||||
})
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseNote = (
|
||||
node: UnistNode,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
parent.children?.splice(index, 1, ...(node.children || []))
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parsePrerequisites = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const items = node.attributes?.find((attr) => attr.name === "items")
|
||||
if (!items || typeof items.value === "string" || !items.value.data?.estree) {
|
||||
return
|
||||
}
|
||||
|
||||
const itemsJsVar = estreeToJs(items.value.data.estree)
|
||||
|
||||
if (!itemsJsVar || !Array.isArray(itemsJsVar)) {
|
||||
return
|
||||
}
|
||||
|
||||
const listItems = itemsJsVar
|
||||
.map((item) => {
|
||||
if (
|
||||
!isExpressionJsVarObj(item) ||
|
||||
!("text" in item) ||
|
||||
!("link" in item) ||
|
||||
!isExpressionJsVarLiteral(item.text) ||
|
||||
!isExpressionJsVarLiteral(item.link)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
type: "listItem",
|
||||
children: [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "link",
|
||||
url: `${item.link.data}`,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: item.text.data,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as UnistNode[]
|
||||
|
||||
parent?.children.splice(
|
||||
index,
|
||||
1,
|
||||
{
|
||||
type: "heading",
|
||||
depth: 3,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Prerequisites",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "list",
|
||||
ordered: false,
|
||||
spread: false,
|
||||
children: listItems,
|
||||
}
|
||||
)
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseSourceCodeLink = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const link = node.attributes?.find((attr) => attr.name === "link")
|
||||
if (!link) {
|
||||
return
|
||||
}
|
||||
|
||||
parent?.children.splice(index, 1, {
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "link",
|
||||
url: link?.value as string,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Source code",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseTable = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const headerNode = node.children?.find(
|
||||
(child) => child.name === "Table.Header"
|
||||
)
|
||||
const bodyNode = node.children?.find((child) => child.name === "Table.Body")
|
||||
|
||||
let nodeText = ``
|
||||
let headerCellsCount = 0
|
||||
|
||||
headerNode?.children?.forEach((headerRow, rowIndex) => {
|
||||
if (rowIndex > 0) {
|
||||
nodeText += `\n`
|
||||
}
|
||||
const childCells =
|
||||
headerRow.children?.length === 1 &&
|
||||
headerRow.children[0].type === "paragraph"
|
||||
? headerRow.children[0].children
|
||||
: headerRow.children
|
||||
headerCellsCount = childCells?.length || 0
|
||||
childCells?.forEach((headerCell) => {
|
||||
if (headerCell.name !== "Table.HeaderCell") {
|
||||
return
|
||||
}
|
||||
nodeText += `|`
|
||||
nodeText += formatNodeText(getTextNode(headerCell))
|
||||
})
|
||||
nodeText += `|`
|
||||
})
|
||||
|
||||
nodeText += `\n|${new Array(headerCellsCount).fill(`---`).join(`|`)}|`
|
||||
|
||||
bodyNode?.children?.forEach((bodyRow) => {
|
||||
nodeText += `\n`
|
||||
bodyRow.children?.forEach((bodyCell) => {
|
||||
nodeText += `|`
|
||||
nodeText += formatNodeText(getTextNode(bodyCell))
|
||||
})
|
||||
nodeText += `|`
|
||||
})
|
||||
|
||||
parent.children?.splice(index, 1, {
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: nodeText,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export const parseTabs = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
if ((node.children?.length || 0) < 2) {
|
||||
return
|
||||
}
|
||||
|
||||
const tabs: UnistNode[] = []
|
||||
|
||||
node.children![0].children?.forEach((tabList) => {
|
||||
tabList.children?.forEach((tabTrigger, index) => {
|
||||
const tabContentNode = node.children?.[1].children?.[index]
|
||||
const tabLabel = formatNodeText(getTextNode(tabTrigger))
|
||||
const tabContent = tabContentNode?.children || []
|
||||
if (!tabLabel || !tabContent) {
|
||||
return
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
type: "mdxJsxFlowElement",
|
||||
name: "details",
|
||||
children: [
|
||||
{
|
||||
type: "mdxJsxFlowElement",
|
||||
name: "summary",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: tabLabel,
|
||||
},
|
||||
],
|
||||
},
|
||||
...tabContent,
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
parent.children?.splice(index, 1, ...tabs)
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseTypeList = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const types = node.attributes?.find((attr) => attr.name === "types")
|
||||
if (!types || typeof types.value === "string" || !types.value.data?.estree) {
|
||||
return
|
||||
}
|
||||
|
||||
const typesJsVar = estreeToJs(types.value.data.estree)
|
||||
|
||||
if (!typesJsVar || !Array.isArray(typesJsVar)) {
|
||||
return
|
||||
}
|
||||
|
||||
const generateTypeListItems = (
|
||||
typesJsVar: ExpressionJsVar[]
|
||||
): UnistNode[] => {
|
||||
const listItems: UnistNode[] = []
|
||||
|
||||
typesJsVar.forEach((item) => {
|
||||
if (!isExpressionJsVarObj(item)) {
|
||||
return
|
||||
}
|
||||
|
||||
const typeName = isExpressionJsVarLiteral(item.name) ? item.name.data : ""
|
||||
const itemType = isExpressionJsVarLiteral(item.type) ? item.type.data : ""
|
||||
const itemDescription = isExpressionJsVarLiteral(item.description)
|
||||
? item.description.data
|
||||
: ""
|
||||
|
||||
if (!typeName || !itemType) {
|
||||
return
|
||||
}
|
||||
|
||||
const itemListChildren = item.children || []
|
||||
const children: UnistNode[] = [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: `${typeName}: (${itemType}) ${itemDescription}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
if (Array.isArray(itemListChildren) && itemListChildren.length) {
|
||||
children.push(...generateTypeListItems(itemListChildren))
|
||||
}
|
||||
|
||||
listItems.push({
|
||||
type: "listItem",
|
||||
children,
|
||||
})
|
||||
})
|
||||
|
||||
return listItems
|
||||
}
|
||||
|
||||
const listItems = generateTypeListItems(typesJsVar)
|
||||
|
||||
parent?.children.splice(index, 1, {
|
||||
type: "list",
|
||||
ordered: false,
|
||||
spread: false,
|
||||
children: listItems,
|
||||
})
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
export const parseWorkflowDiagram = (
|
||||
node: UnistNodeWithData,
|
||||
index: number,
|
||||
parent: UnistTree
|
||||
): VisitorResult => {
|
||||
const worflowItems = node.attributes?.find((attr) => attr.name === "workflow")
|
||||
if (
|
||||
!worflowItems ||
|
||||
typeof worflowItems.value === "string" ||
|
||||
!worflowItems.value.data?.estree
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const workflowJsVar = estreeToJs(worflowItems.value.data.estree)
|
||||
|
||||
if (
|
||||
!isExpressionJsVarObj(workflowJsVar) ||
|
||||
!("steps" in workflowJsVar) ||
|
||||
!Array.isArray(workflowJsVar.steps)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const generateWorkflowItems = (
|
||||
jsVarItems: ExpressionJsVar[]
|
||||
): UnistNode[] => {
|
||||
const listItems: UnistNode[] = []
|
||||
|
||||
jsVarItems.forEach((item) => {
|
||||
if (!isExpressionJsVarObj(item)) {
|
||||
return
|
||||
}
|
||||
|
||||
const stepName = isExpressionJsVarLiteral(item.name) ? item.name.data : ""
|
||||
const stepDescription = isExpressionJsVarLiteral(item.description)
|
||||
? item.description.data
|
||||
: ""
|
||||
const stepLink = isExpressionJsVarLiteral(item.link)
|
||||
? (item.link.data as string)
|
||||
: `#${stepName}`
|
||||
|
||||
if (!stepName) {
|
||||
return
|
||||
}
|
||||
|
||||
const stepChildren = item.steps || []
|
||||
const children: UnistNode[] = [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [
|
||||
{
|
||||
type: "link",
|
||||
url: stepLink,
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: `${stepName}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
value: `: ${stepDescription}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
if (Array.isArray(stepChildren) && stepChildren.length) {
|
||||
children.push(...generateWorkflowItems(stepChildren))
|
||||
}
|
||||
|
||||
listItems.push({
|
||||
type: "listItem",
|
||||
children,
|
||||
})
|
||||
})
|
||||
|
||||
return listItems
|
||||
}
|
||||
|
||||
const listItems = generateWorkflowItems(workflowJsVar.steps)
|
||||
|
||||
parent?.children.splice(index, 1, {
|
||||
type: "list",
|
||||
ordered: false,
|
||||
spread: false,
|
||||
children: listItems,
|
||||
})
|
||||
return [SKIP, index]
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
const getTextNode = (node: UnistNode): UnistNode | undefined => {
|
||||
let textNode: UnistNode | undefined
|
||||
node.children?.some((child) => {
|
||||
if (textNode) {
|
||||
return true
|
||||
}
|
||||
if (child.type === "text") {
|
||||
textNode = child
|
||||
} else if (child.type === "paragraph") {
|
||||
textNode = getTextNode(child)
|
||||
} else if (child.value) {
|
||||
textNode = child
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return textNode
|
||||
}
|
||||
|
||||
const formatNodeText = (node?: UnistNode): string => {
|
||||
if (!node) {
|
||||
return ""
|
||||
}
|
||||
if (node.type === "inlineCode") {
|
||||
return `\`${node.value}\``
|
||||
} else if (node.type === "code") {
|
||||
return `\`\`\`${"lang" in node ? node.lang : "ts"}\n${node.value}\n\`\`\``
|
||||
} else if (node.type === "link") {
|
||||
return `[${node.children?.[0].value}](${node.url})`
|
||||
}
|
||||
|
||||
return node.value || ""
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { UnistNode, UnistTree } from "types"
|
||||
import { Transformer } from "unified"
|
||||
|
||||
type Options = {
|
||||
url: string
|
||||
}
|
||||
|
||||
export function addUrlToRelativeLink(options: Options): Transformer {
|
||||
return async (tree) => {
|
||||
const { visit } = await import("unist-util-visit")
|
||||
|
||||
visit(tree as UnistTree, "link", (node: UnistNode) => {
|
||||
if (!node.url || !node.url.startsWith("/")) {
|
||||
return
|
||||
}
|
||||
|
||||
node.url = `${options.url}${node.url}`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,12 @@ import type {
|
||||
UnistNode,
|
||||
UnistNodeWithData,
|
||||
UnistTree,
|
||||
} from "./types/index.js"
|
||||
} from "types"
|
||||
import type { VFile } from "vfile"
|
||||
import { parseCrossProjectLink } from "./utils/cross-project-link-utils.js"
|
||||
import { SlugChange } from "types"
|
||||
import getAttribute from "./utils/get-attribute.js"
|
||||
import { estreeToJs } from "./utils/estree-to-js.js"
|
||||
import { estreeToJs } from "docs-utils"
|
||||
import { performActionOnLiteral } from "./utils/perform-action-on-literal.js"
|
||||
import { MD_LINK_REGEX } from "./constants.js"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Transformer } from "unified"
|
||||
import type { CloudinaryConfig, UnistNode, UnistTree } from "./types/index.js"
|
||||
import type { CloudinaryConfig, UnistNode, UnistTree } from "types"
|
||||
|
||||
const cloudinaryImageRegex =
|
||||
/^https:\/\/res.cloudinary.com\/.*\/upload\/v[0-9]+\/(?<imageId>.*)$/
|
||||
|
||||
@@ -4,8 +4,8 @@ import type {
|
||||
UnistNode,
|
||||
UnistNodeWithData,
|
||||
UnistTree,
|
||||
} from "./types/index.js"
|
||||
import { estreeToJs } from "./utils/estree-to-js.js"
|
||||
} from "types"
|
||||
import { estreeToJs } from "docs-utils"
|
||||
import getAttribute from "./utils/get-attribute.js"
|
||||
import { performActionOnLiteral } from "./utils/perform-action-on-literal.js"
|
||||
|
||||
@@ -48,6 +48,14 @@ function linkElmFixer(node: UnistNode, options: CrossProjectLinksOptions) {
|
||||
node.properties.href = matchAndFixLinks(node.properties.href, options)
|
||||
}
|
||||
|
||||
function linkNodeFixer(node: UnistNode, options: CrossProjectLinksOptions) {
|
||||
if (!node.url) {
|
||||
return
|
||||
}
|
||||
|
||||
node.url = matchAndFixLinks(node.url, options)
|
||||
}
|
||||
|
||||
function componentFixer(
|
||||
node: UnistNodeWithData,
|
||||
options: CrossProjectLinksOptions
|
||||
@@ -128,20 +136,23 @@ export function crossProjectLinksPlugin(
|
||||
|
||||
visit(
|
||||
tree as UnistTree,
|
||||
["element", "mdxJsxFlowElement"],
|
||||
["element", "mdxJsxFlowElement", "link"],
|
||||
(node: UnistNode) => {
|
||||
const isComponent =
|
||||
node.name && allowedComponentNames.includes(node.name)
|
||||
const isLink = node.tagName === "a" && node.properties?.href
|
||||
if (!isComponent && !isLink) {
|
||||
const isLinkElm = node.tagName === "a" && node.properties?.href
|
||||
const isLinkNode = node.type === "link" && node.url
|
||||
if (!isComponent && !isLinkElm && !isLinkNode) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isComponent) {
|
||||
componentFixer(node as UnistNodeWithData, options)
|
||||
} else if (isLinkElm) {
|
||||
linkElmFixer(node, options)
|
||||
} else {
|
||||
linkNodeFixer(node, options)
|
||||
}
|
||||
|
||||
linkElmFixer(node, options)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./add-url-to-relative-link.js"
|
||||
export * from "./broken-link-checker.js"
|
||||
export * from "./cloudinary-img.js"
|
||||
export * from "./cross-project-links.js"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "path"
|
||||
import type { Transformer } from "unified"
|
||||
import type { LocalLinkOptions, UnistNode, UnistTree } from "./types/index.js"
|
||||
import type { LocalLinkOptions, UnistNode, UnistTree } from "types"
|
||||
import { fixLinkUtil } from "./index.js"
|
||||
|
||||
export function localLinksRehypePlugin(options: LocalLinkOptions): Transformer {
|
||||
@@ -25,16 +25,28 @@ export function localLinksRehypePlugin(options: LocalLinkOptions): Transformer {
|
||||
""
|
||||
)
|
||||
const appsPath = basePath || path.join(file.cwd, "app")
|
||||
visit(tree as UnistTree, "element", (node: UnistNode) => {
|
||||
if (node.tagName !== "a" || !node.properties?.href?.match(/page\.mdx?/)) {
|
||||
return
|
||||
}
|
||||
visit(tree as UnistTree, ["element", "link"], (node: UnistNode) => {
|
||||
if (node.tagName === "a") {
|
||||
if (!node.properties?.href?.match(/page\.mdx?/)) {
|
||||
return
|
||||
}
|
||||
|
||||
node.properties.href = fixLinkUtil({
|
||||
currentPageFilePath,
|
||||
linkedPath: node.properties.href,
|
||||
appsPath,
|
||||
})
|
||||
node.properties.href = fixLinkUtil({
|
||||
currentPageFilePath,
|
||||
linkedPath: node.properties.href,
|
||||
appsPath,
|
||||
})
|
||||
} else if (node.type === "link") {
|
||||
if (!node.url?.match(/page\.mdx?/)) {
|
||||
return
|
||||
}
|
||||
|
||||
node.url = fixLinkUtil({
|
||||
currentPageFilePath,
|
||||
linkedPath: node.url,
|
||||
appsPath,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "path"
|
||||
import type { Transformer } from "unified"
|
||||
import type { RawSidebarItem } from "types"
|
||||
import type { UnistTree } from "./types/index.js"
|
||||
import type { UnistTree } from "types"
|
||||
|
||||
export function pageNumberRehypePlugin({
|
||||
sidebar,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transformer } from "unified"
|
||||
import { ComponentLinkFixerOptions } from "./types/index.js"
|
||||
import { ComponentLinkFixerOptions } from "types"
|
||||
import { componentLinkFixer } from "./utils/component-link-fixer.js"
|
||||
|
||||
export function prerequisitesLinkFixerPlugin(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transformer } from "unified"
|
||||
import { UnistNode } from "./types/index.js"
|
||||
import { UnistNode } from "types"
|
||||
|
||||
const ALLOWED_NODE_NAMES = ["note", "tip", "info", "warning"]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transformer } from "unified"
|
||||
import { ComponentLinkFixerOptions } from "./types/index.js"
|
||||
import { ComponentLinkFixerOptions } from "types"
|
||||
import { componentLinkFixer } from "./utils/component-link-fixer.js"
|
||||
|
||||
export function typeListLinkFixerPlugin(
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import path from "path"
|
||||
import { Transformer } from "unified"
|
||||
import { UnistNodeWithData, UnistTree } from "../types/index.js"
|
||||
import { UnistNodeWithData, UnistTree, ComponentLinkFixerOptions } from "types"
|
||||
import { FixLinkOptions, fixLinkUtil } from "../index.js"
|
||||
import getAttribute from "../utils/get-attribute.js"
|
||||
import { estreeToJs } from "../utils/estree-to-js.js"
|
||||
import { ComponentLinkFixerOptions } from "../types/index.js"
|
||||
import { estreeToJs } from "docs-utils"
|
||||
import { performActionOnLiteral } from "./perform-action-on-literal.js"
|
||||
import { MD_LINK_REGEX } from "../constants.js"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UnistNodeWithData } from "../types/index.js"
|
||||
import { UnistNodeWithData } from "types"
|
||||
|
||||
export default function getAttribute(
|
||||
node: UnistNodeWithData,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { ExpressionJsVar, ExpressionJsVarLiteral } from "../types/index.js"
|
||||
import {
|
||||
isExpressionJsVarLiteral,
|
||||
isExpressionJsVarObj,
|
||||
} from "./expression-is-utils.js"
|
||||
import { ExpressionJsVar, ExpressionJsVarLiteral } from "types"
|
||||
import { isExpressionJsVarLiteral, isExpressionJsVarObj } from "docs-utils"
|
||||
|
||||
export const performActionOnLiteral = (
|
||||
item: ExpressionJsVar[] | ExpressionJsVar,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transformer } from "unified"
|
||||
import { ComponentLinkFixerOptions } from "./types/index.js"
|
||||
import { ComponentLinkFixerOptions } from "types"
|
||||
import { componentLinkFixer } from "./utils/component-link-fixer.js"
|
||||
|
||||
export function workflowDiagramLinkFixerPlugin(
|
||||
|
||||
@@ -5,6 +5,7 @@ export * from "./frontmatter.js"
|
||||
export * from "./general.js"
|
||||
export * from "./menu.js"
|
||||
export * from "./navigation.js"
|
||||
export * from "./remark-rehype.js"
|
||||
export * from "./navigation-dropdown.js"
|
||||
export * from "./sidebar.js"
|
||||
export * from "./tags.js"
|
||||
|
||||
@@ -14,6 +14,10 @@ export interface UnistNode extends Node {
|
||||
type?: string
|
||||
}[]
|
||||
children?: UnistNode[]
|
||||
ordered?: boolean
|
||||
url?: string
|
||||
spread?: boolean
|
||||
depth?: number
|
||||
}
|
||||
|
||||
export type ArrayExpression = {
|
||||
@@ -1583,7 +1583,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mdx-js/mdx@npm:^3.0.1":
|
||||
"@mdx-js/mdx@npm:^3.0.1, @mdx-js/mdx@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "@mdx-js/mdx@npm:3.1.0"
|
||||
dependencies:
|
||||
@@ -7196,8 +7196,10 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "docs-utils@workspace:packages/docs-utils"
|
||||
dependencies:
|
||||
"@mdx-js/mdx": ^3.1.0
|
||||
"@types/node": ^20.11.20
|
||||
remark-frontmatter: ^5.0.0
|
||||
remark-mdx: ^3.1.0
|
||||
remark-parse: ^11.0.0
|
||||
remark-stringify: ^11.0.0
|
||||
rimraf: ^5.0.5
|
||||
@@ -13297,6 +13299,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"remark-mdx@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "remark-mdx@npm:3.1.0"
|
||||
dependencies:
|
||||
mdast-util-mdx: ^3.0.0
|
||||
micromark-extension-mdxjs: ^3.0.0
|
||||
checksum: 247800fa8561624bdca5776457c5965d99e5e60080e80262c600fe12ddd573862e029e39349e1e36e4c3bf79c8e571ecf4d3d2d8c13485b758391fb500e24a1a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"remark-parse@npm:^10.0.0, remark-parse@npm:^10.0.2":
|
||||
version: 10.0.2
|
||||
resolution: "remark-parse@npm:10.0.2"
|
||||
|
||||
Reference in New Issue
Block a user