Skip to content

Commit

Permalink
docs: handle Card and CardList components in cross-links plugin (medu…
Browse files Browse the repository at this point in the history
  • Loading branch information
shahednasser authored May 16, 2024
1 parent 490586f commit 1160a34
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 116 deletions.
151 changes: 124 additions & 27 deletions www/packages/remark-rehype-plugins/src/cross-project-links.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,142 @@
/* eslint-disable no-case-declarations */
import type { Transformer } from "unified"
import type {
CrossProjectLinksOptions,
ExpressionJsVar,
UnistNode,
UnistNodeWithData,
UnistTree,
} from "./types/index.js"
import { estreeToJs } from "./utils/estree-to-js.js"
import getAttribute from "./utils/get-attribute.js"
import {
isExpressionJsVarLiteral,
isExpressionJsVarObj,
} from "./utils/expression-is-utils.js"

const PROJECT_REGEX = /^!(?<area>[\w-]+)!/

export function crossProjectLinksPlugin({
baseUrl,
projectUrls,
}: CrossProjectLinksOptions): Transformer {
return async (tree) => {
const { visit } = await import("unist-util-visit")
visit(tree as UnistTree, "element", (node: UnistNode) => {
if (node.tagName !== "a" || !node.properties?.href) {
function matchAndFixLinks(
link: string,
{ baseUrl, projectUrls }: CrossProjectLinksOptions
): string {
const projectArea = PROJECT_REGEX.exec(link)

if (!projectArea?.groups?.area) {
return link
}

const actualUrl = link.replace(PROJECT_REGEX, "")

const base =
projectUrls &&
Object.hasOwn(projectUrls, projectArea.groups.area) &&
projectUrls[projectArea.groups.area]?.url
? projectUrls[projectArea.groups.area].url
: baseUrl
const path =
projectUrls &&
Object.hasOwn(projectUrls, projectArea.groups.area) &&
projectUrls[projectArea.groups.area]?.path
? projectUrls[projectArea.groups.area].path
: projectArea.groups.area

return `${base}/${path}${actualUrl}`
}

function linkElmFixer(node: UnistNode, options: CrossProjectLinksOptions) {
if (!node.properties) {
return
}

node.properties.href = matchAndFixLinks(node.properties.href, options)
}

function componentFixer(
node: UnistNodeWithData,
options: CrossProjectLinksOptions
) {
if (!node.name) {
return
}

switch (node.name) {
case "CardList":
const itemsAttribute = getAttribute(node, "items")

if (
!itemsAttribute?.value ||
typeof itemsAttribute.value === "string" ||
!itemsAttribute.value.data?.estree
) {
return
}

const projectArea = PROJECT_REGEX.exec(node.properties.href)
const jsVar = estreeToJs(itemsAttribute.value.data.estree)

if (!projectArea?.groups?.area) {
if (!jsVar) {
return
}

const actualUrl = node.properties.href.replace(PROJECT_REGEX, "")

const base =
projectUrls &&
Object.hasOwn(projectUrls, projectArea.groups.area) &&
projectUrls[projectArea.groups.area]?.url
? projectUrls[projectArea.groups.area].url
: baseUrl
const path =
projectUrls &&
Object.hasOwn(projectUrls, projectArea.groups.area) &&
projectUrls[projectArea.groups.area]?.path
? projectUrls[projectArea.groups.area].path
: projectArea.groups.area

node.properties.href = `${base}/${path}${actualUrl}`
})
const fixProperty = (item: ExpressionJsVar) => {
if (!isExpressionJsVarObj(item)) {
return
}

Object.entries(item).forEach(([key, value]) => {
if (key !== "href" || !isExpressionJsVarLiteral(value)) {
return
}

value.original.value = matchAndFixLinks(
value.original.value as string,
options
)
value.original.raw = JSON.stringify(value.original.value)
})
}

if (Array.isArray(jsVar)) {
jsVar.forEach(fixProperty)
} else {
fixProperty(jsVar)
}
return
case "Card":
const hrefAttribute = getAttribute(node, "href")

if (!hrefAttribute?.value || typeof hrefAttribute.value !== "string") {
return
}

hrefAttribute.value = matchAndFixLinks(hrefAttribute.value, options)

return
}
}

export function crossProjectLinksPlugin(
options: CrossProjectLinksOptions
): Transformer {
return async (tree) => {
const { visit } = await import("unist-util-visit")

visit(
tree as UnistTree,
["element", "mdxJsxFlowElement"],
(node: UnistNode) => {
const isComponent = node.name === "Card" || node.name === "CardList"
const isLink = node.tagName === "a" && node.properties?.href
if (!isComponent && !isLink) {
return
}

if (isComponent) {
componentFixer(node as UnistNodeWithData, options)
}

linkElmFixer(node, options)
}
)
}
}
108 changes: 39 additions & 69 deletions www/packages/remark-rehype-plugins/src/type-list-link-fixer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import path from "path"
import { Transformer } from "unified"
import {
ExpressionJsVar,
TypeListLinkFixerOptions,
UnistNode,
UnistNodeWithData,
UnistTree,
} from "./types/index.js"
import { FixLinkOptions, fixLinkUtil } from "./index.js"
import getAttribute from "./utils/get-attribute.js"
import { estreeToJs } from "./utils/estree-to-js.js"
import {
isExpressionJsVarLiteral,
isExpressionJsVarObj,
} from "./utils/expression-is-utils.js"

const LINK_REGEX = /\[(.*?)\]\((?<link>.*?)\)/gm

Expand All @@ -32,24 +38,34 @@ function matchLinks(
}

function traverseTypes(
types: Record<string, unknown>[],
types: ExpressionJsVar[] | ExpressionJsVar,
linkOptions: Omit<FixLinkOptions, "linkedPath">
) {
return types.map((typeItem) => {
typeItem.type = matchLinks(typeItem.type as string, linkOptions)
typeItem.description = matchLinks(
typeItem.description as string,
if (Array.isArray(types)) {
types.forEach((item) => traverseTypes(item, linkOptions))
} else if (isExpressionJsVarLiteral(types)) {
types.original.value = matchLinks(
types.original.value as string,
linkOptions
)
if (typeItem.children) {
typeItem.children = traverseTypes(
typeItem.children as Record<string, unknown>[],
types.original.raw = JSON.stringify(types.original.value)
} else {
Object.values(types).forEach((value) => {
if (Array.isArray(value) || isExpressionJsVarObj(value)) {
return traverseTypes(value, linkOptions)
}

if (!isExpressionJsVarLiteral(value)) {
return
}

value.original.value = matchLinks(
value.original.value as string,
linkOptions
)
}

return typeItem
})
value.original.raw = JSON.stringify(value.original.value)
})
}
}

export function typeListLinkFixerPlugin(
Expand All @@ -76,22 +92,17 @@ export function typeListLinkFixerPlugin(
""
)
const appsPath = basePath || path.join(file.cwd, "app")
visit(tree as UnistTree, "mdxJsxFlowElement", (node: UnistNode) => {
visit(tree as UnistTree, "mdxJsxFlowElement", (node: UnistNodeWithData) => {
if (node.name !== "TypeList") {
return
}

const typesAttributeIndex = node.attributes?.findIndex(
(attribute) => attribute.name === "types"
)
if (typesAttributeIndex === undefined || typesAttributeIndex === -1) {
return
}
const typesAttribute = node.attributes![typesAttributeIndex]
const typesAttribute = getAttribute(node, "types")

if (
!typesAttribute ||
!(typesAttribute.value as Record<string, unknown>)?.value
typeof typesAttribute.value === "string" ||
!typesAttribute.value.data?.estree
) {
return
}
Expand All @@ -101,56 +112,15 @@ export function typeListLinkFixerPlugin(
appsPath,
}

let newItems: Record<string, unknown>[]

try {
newItems = traverseTypes(
JSON.parse(
(typesAttribute.value as Record<string, unknown>).value as string
) as Record<string, unknown>[],
linkOptions
)
} catch (e) {
// eslint-disable-next-line no-console
console.log(
`[type-list-link-fixer-plugin]: An error occurred while parsing items for page ${file.history[0]}: ${e}`
)
return
}
// let newItems: Record<string, unknown>[]

;(
node.attributes![typesAttributeIndex].value as Record<string, unknown>
).value = JSON.stringify(newItems)
const typesJsVar = estreeToJs(typesAttribute.value.data.estree)

if (
(node as UnistNodeWithData).attributes![typesAttributeIndex].value?.data
?.estree?.body?.length
) {
const oldItems = (node as UnistNodeWithData).attributes[
typesAttributeIndex
].value.data!.estree!.body![0].expression!.elements!

;(node as UnistNodeWithData).attributes[
typesAttributeIndex
].value.data!.estree!.body![0].expression!.elements = newItems.map(
(newItem, index) => {
oldItems[index].properties = oldItems[index].properties.map(
(property) => {
if (Object.hasOwn(newItem, property.key.value)) {
property.value.value = newItem[property.key.value]
property.value.raw = JSON.stringify(
newItem[property.key.value]
)
}

return property
}
)

return oldItems[index]
}
)
if (!typesJsVar) {
return
}

traverseTypes(typesJsVar, linkOptions)
})
}
}
Loading

0 comments on commit 1160a34

Please sign in to comment.