livemark 0.0.0-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +3 -0
- package/build/actions/config/define.d.ts +6 -0
- package/build/actions/config/define.js +8 -0
- package/build/actions/config/define.unit.d.ts +1 -0
- package/build/actions/config/define.unit.js +34 -0
- package/build/actions/config/load.d.ts +5 -0
- package/build/actions/config/load.js +22 -0
- package/build/actions/config/load.unit.d.ts +1 -0
- package/build/actions/config/load.unit.js +23 -0
- package/build/commands/build.d.ts +5 -0
- package/build/commands/build.js +13 -0
- package/build/commands/preview.d.ts +5 -0
- package/build/commands/preview.js +18 -0
- package/build/commands/start.d.ts +5 -0
- package/build/commands/start.js +19 -0
- package/build/docs/markdown/includes/example.d.ts +9 -0
- package/build/docs/markdown/includes/example.js +5 -0
- package/build/entrypoints/main.d.ts +2 -0
- package/build/entrypoints/main.js +12 -0
- package/build/helpers/program.d.ts +2 -0
- package/build/helpers/program.js +5 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +2 -0
- package/build/livemark.config.d.ts +9 -0
- package/build/livemark.config.js +5 -0
- package/build/models/config.d.ts +33 -0
- package/build/models/config.js +14 -0
- package/build/program.d.ts +2 -0
- package/build/program.js +14 -0
- package/build/vite.config.d.ts +2 -0
- package/build/vite.config.js +48 -0
- package/build/website/client.tsx +10 -0
- package/build/website/components/Abbr.tsx +17 -0
- package/build/website/components/AnsiCode.tsx +15 -0
- package/build/website/components/Banner.tsx +32 -0
- package/build/website/components/Breadcrumbs.tsx +19 -0
- package/build/website/components/Callout.tsx +72 -0
- package/build/website/components/Cards.tsx +66 -0
- package/build/website/components/CodeBlock.tsx +118 -0
- package/build/website/components/CodeTabs.tsx +83 -0
- package/build/website/components/Columns.tsx +17 -0
- package/build/website/components/Content.tsx +7 -0
- package/build/website/components/ContentTab.tsx +7 -0
- package/build/website/components/ContentTabs.tsx +82 -0
- package/build/website/components/DefaultCatchBoundary.tsx +54 -0
- package/build/website/components/DefinitionList.tsx +21 -0
- package/build/website/components/Details.tsx +30 -0
- package/build/website/components/FileTree.tsx +52 -0
- package/build/website/components/Footer.tsx +9 -0
- package/build/website/components/FootnoteRef.tsx +52 -0
- package/build/website/components/Heading.tsx +20 -0
- package/build/website/components/InlineBadge.tsx +9 -0
- package/build/website/components/InlineIcon.tsx +21 -0
- package/build/website/components/InlineToc.tsx +31 -0
- package/build/website/components/Layout.tsx +220 -0
- package/build/website/components/LinkButton.tsx +31 -0
- package/build/website/components/Mermaid.tsx +37 -0
- package/build/website/components/NotFound.tsx +27 -0
- package/build/website/components/PackageTabs.tsx +74 -0
- package/build/website/components/PageToolbar.tsx +65 -0
- package/build/website/components/PrevNext.tsx +52 -0
- package/build/website/components/SearchDialog.tsx +245 -0
- package/build/website/components/Section.tsx +24 -0
- package/build/website/components/SectionHeader.tsx +35 -0
- package/build/website/components/SiteTitle.tsx +16 -0
- package/build/website/components/SortableTable.tsx +81 -0
- package/build/website/components/SoundCloud.tsx +16 -0
- package/build/website/components/Theme.tsx +40 -0
- package/build/website/components/Toc.tsx +99 -0
- package/build/website/components/YouTube.tsx +16 -0
- package/build/website/components/ZoomImage.tsx +30 -0
- package/build/website/content-collections.ts +181 -0
- package/build/website/elements/accordion.tsx +78 -0
- package/build/website/elements/alert-dialog.tsx +185 -0
- package/build/website/elements/alert.tsx +74 -0
- package/build/website/elements/aspect-ratio.tsx +22 -0
- package/build/website/elements/avatar.tsx +107 -0
- package/build/website/elements/badge.tsx +51 -0
- package/build/website/elements/breadcrumb.tsx +120 -0
- package/build/website/elements/button-group.tsx +87 -0
- package/build/website/elements/button.tsx +57 -0
- package/build/website/elements/calendar.tsx +229 -0
- package/build/website/elements/card.tsx +102 -0
- package/build/website/elements/carousel.tsx +240 -0
- package/build/website/elements/chart.tsx +372 -0
- package/build/website/elements/checkbox.tsx +26 -0
- package/build/website/elements/collapsible.tsx +19 -0
- package/build/website/elements/command.tsx +210 -0
- package/build/website/elements/context-menu.tsx +270 -0
- package/build/website/elements/dialog.tsx +152 -0
- package/build/website/elements/direction.tsx +4 -0
- package/build/website/elements/drawer.tsx +127 -0
- package/build/website/elements/dropdown-menu.tsx +260 -0
- package/build/website/elements/empty.tsx +104 -0
- package/build/website/elements/field.tsx +235 -0
- package/build/website/elements/hover-card.tsx +49 -0
- package/build/website/elements/input-group.tsx +171 -0
- package/build/website/elements/input-otp.tsx +84 -0
- package/build/website/elements/input.tsx +18 -0
- package/build/website/elements/item.tsx +199 -0
- package/build/website/elements/kbd.tsx +26 -0
- package/build/website/elements/label.tsx +17 -0
- package/build/website/elements/menubar.tsx +282 -0
- package/build/website/elements/native-select.tsx +56 -0
- package/build/website/elements/navigation-menu.tsx +171 -0
- package/build/website/elements/pagination.tsx +133 -0
- package/build/website/elements/popover.tsx +88 -0
- package/build/website/elements/progress.tsx +81 -0
- package/build/website/elements/radio-group.tsx +36 -0
- package/build/website/elements/resizable.tsx +48 -0
- package/build/website/elements/scroll-area.tsx +51 -0
- package/build/website/elements/select.tsx +200 -0
- package/build/website/elements/separator.tsx +22 -0
- package/build/website/elements/sheet.tsx +130 -0
- package/build/website/elements/sidebar.tsx +717 -0
- package/build/website/elements/skeleton.tsx +13 -0
- package/build/website/elements/slider.tsx +57 -0
- package/build/website/elements/sonner.tsx +40 -0
- package/build/website/elements/spinner.tsx +16 -0
- package/build/website/elements/switch.tsx +30 -0
- package/build/website/elements/table.tsx +112 -0
- package/build/website/elements/tabs.tsx +79 -0
- package/build/website/elements/textarea.tsx +16 -0
- package/build/website/elements/toggle-group.tsx +87 -0
- package/build/website/elements/toggle.tsx +43 -0
- package/build/website/elements/tooltip.tsx +67 -0
- package/build/website/env.d.ts +10 -0
- package/build/website/globals.d.ts +8 -0
- package/build/website/helpers/article-icon.ts +59 -0
- package/build/website/helpers/articles.ts +24 -0
- package/build/website/helpers/resolve-includes.ts +131 -0
- package/build/website/helpers/resolve-includes.unit.ts +126 -0
- package/build/website/helpers/search-text.ts +15 -0
- package/build/website/helpers/style.ts +6 -0
- package/build/website/helpers/theme.ts +27 -0
- package/build/website/helpers/toc-context.ts +10 -0
- package/build/website/helpers/toc.ts +51 -0
- package/build/website/helpers/toc.unit.ts +74 -0
- package/build/website/hooks/in-view.ts +30 -0
- package/build/website/hooks/media.ts +19 -0
- package/build/website/models/article.ts +40 -0
- package/build/website/plugins/rehype-inline-code.ts +107 -0
- package/build/website/plugins/remark-abbr.ts +46 -0
- package/build/website/plugins/remark-abbr.unit.ts +99 -0
- package/build/website/plugins/remark-ansi.ts +247 -0
- package/build/website/plugins/remark-ansi.unit.ts +197 -0
- package/build/website/plugins/remark-badge.ts +50 -0
- package/build/website/plugins/remark-badge.unit.ts +96 -0
- package/build/website/plugins/remark-button.ts +60 -0
- package/build/website/plugins/remark-button.unit.ts +147 -0
- package/build/website/plugins/remark-callout.ts +57 -0
- package/build/website/plugins/remark-card.ts +80 -0
- package/build/website/plugins/remark-code-tabs.ts +87 -0
- package/build/website/plugins/remark-columns.ts +66 -0
- package/build/website/plugins/remark-custom-heading-id.ts +70 -0
- package/build/website/plugins/remark-custom-heading-id.unit.ts +131 -0
- package/build/website/plugins/remark-deflist.ts +36 -0
- package/build/website/plugins/remark-details.ts +49 -0
- package/build/website/plugins/remark-filetree.ts +89 -0
- package/build/website/plugins/remark-filetree.unit.ts +137 -0
- package/build/website/plugins/remark-github-callout.ts +71 -0
- package/build/website/plugins/remark-icon.ts +42 -0
- package/build/website/plugins/remark-image.ts +25 -0
- package/build/website/plugins/remark-media.ts +62 -0
- package/build/website/plugins/remark-media.unit.ts +74 -0
- package/build/website/plugins/remark-mermaid.ts +23 -0
- package/build/website/plugins/remark-npm.ts +52 -0
- package/build/website/plugins/remark-steps.ts +36 -0
- package/build/website/plugins/remark-steps.unit.ts +97 -0
- package/build/website/plugins/remark-tab.ts +100 -0
- package/build/website/plugins/remark-tab.unit.ts +181 -0
- package/build/website/plugins/remark-toc.ts +39 -0
- package/build/website/plugins/remark-toc.unit.ts +82 -0
- package/build/website/plugins/shiki-icon.ts +82 -0
- package/build/website/plugins/shiki-line-highlight.ts +174 -0
- package/build/website/public/logo.svg +72 -0
- package/build/website/routeTree.gen.ts +86 -0
- package/build/website/router.tsx +34 -0
- package/build/website/routes/$.tsx +149 -0
- package/build/website/routes/__root.tsx +84 -0
- package/build/website/routes/index.tsx +13 -0
- package/build/website/server.ts +7 -0
- package/build/website/styles/general.css +347 -0
- package/build/website/vite.config.ts +36 -0
- package/entrypoints/main.ts +12 -0
- package/package.json +120 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Root, Parent } from "mdast"
|
|
2
|
+
import type { Plugin } from "unified"
|
|
3
|
+
import { visit, SKIP } from "unist-util-visit"
|
|
4
|
+
|
|
5
|
+
/** Remark plugin that converts defList nodes into DefinitionList/DefinitionTerm/DefinitionDetail JSX elements */
|
|
6
|
+
export const remarkDeflist: Plugin<[], Root> = () => {
|
|
7
|
+
return tree => {
|
|
8
|
+
visit(tree, "defList", (node: Parent, index, parent) => {
|
|
9
|
+
if (index === undefined || !parent) return
|
|
10
|
+
|
|
11
|
+
const children = node.children.map(child => {
|
|
12
|
+
const name =
|
|
13
|
+
child.type === "defListTerm" ? "DefinitionTerm" : "DefinitionDetail"
|
|
14
|
+
return {
|
|
15
|
+
type: "mdxJsxFlowElement" as const,
|
|
16
|
+
name,
|
|
17
|
+
attributes: [],
|
|
18
|
+
children: (child as Parent).children,
|
|
19
|
+
data: { _mdxExplicitJsx: true },
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const jsxNode = {
|
|
24
|
+
type: "mdxJsxFlowElement" as const,
|
|
25
|
+
name: "DefinitionList",
|
|
26
|
+
attributes: [],
|
|
27
|
+
children,
|
|
28
|
+
data: { _mdxExplicitJsx: true },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
32
|
+
parent.children.splice(index, 1, jsxNode)
|
|
33
|
+
return SKIP
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Paragraph, Root } from "mdast"
|
|
2
|
+
import type { ContainerDirective } from "mdast-util-directive"
|
|
3
|
+
import type { Plugin } from "unified"
|
|
4
|
+
import { visit } from "unist-util-visit"
|
|
5
|
+
|
|
6
|
+
/** Remark plugin that converts :::details directives into collapsible details/summary elements */
|
|
7
|
+
export const remarkDetails: Plugin<[], Root> = () => {
|
|
8
|
+
return tree => {
|
|
9
|
+
visit(
|
|
10
|
+
tree,
|
|
11
|
+
"containerDirective",
|
|
12
|
+
(node: ContainerDirective, index, parent) => {
|
|
13
|
+
if (node.name !== "details" || index === undefined || !parent) return
|
|
14
|
+
|
|
15
|
+
const labelParagraph = node.children.find(
|
|
16
|
+
(child): child is Paragraph =>
|
|
17
|
+
child.type === "paragraph" && !!child.data?.directiveLabel,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const summary = labelParagraph
|
|
21
|
+
? labelParagraph.children
|
|
22
|
+
.map(c => ("value" in c ? String(c.value) : ""))
|
|
23
|
+
.join("")
|
|
24
|
+
: "Details"
|
|
25
|
+
|
|
26
|
+
const body = node.children.filter(
|
|
27
|
+
child => !(child.type === "paragraph" && child.data?.directiveLabel),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const jsxNode = {
|
|
31
|
+
type: "mdxJsxFlowElement" as const,
|
|
32
|
+
name: "Details",
|
|
33
|
+
attributes: [
|
|
34
|
+
{
|
|
35
|
+
type: "mdxJsxAttribute" as const,
|
|
36
|
+
name: "summary",
|
|
37
|
+
value: summary,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
children: body,
|
|
41
|
+
data: { _mdxExplicitJsx: true },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
45
|
+
parent.children.splice(index, 1, jsxNode)
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { List, ListItem, Paragraph, Root, Text } from "mdast"
|
|
2
|
+
import type { ContainerDirective } from "mdast-util-directive"
|
|
3
|
+
import type { Plugin } from "unified"
|
|
4
|
+
import { visit, SKIP } from "unist-util-visit"
|
|
5
|
+
|
|
6
|
+
interface TreeItem {
|
|
7
|
+
name: string
|
|
8
|
+
isDir: boolean
|
|
9
|
+
children?: TreeItem[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Extract tree items from a mdast list node */
|
|
13
|
+
export function parseList(list: List): TreeItem[] {
|
|
14
|
+
const items: TreeItem[] = []
|
|
15
|
+
|
|
16
|
+
for (const listItem of list.children) {
|
|
17
|
+
if (listItem.type !== "listItem") continue
|
|
18
|
+
const item = parseListItem(listItem)
|
|
19
|
+
if (item) items.push(item)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return items
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseListItem(listItem: ListItem): TreeItem | undefined {
|
|
26
|
+
const paragraph = listItem.children.find(
|
|
27
|
+
(child): child is Paragraph => child.type === "paragraph",
|
|
28
|
+
)
|
|
29
|
+
if (!paragraph) return undefined
|
|
30
|
+
|
|
31
|
+
const textContent = paragraph.children
|
|
32
|
+
.filter((c): c is Text => c.type === "text")
|
|
33
|
+
.map(c => c.value)
|
|
34
|
+
.join("")
|
|
35
|
+
.trim()
|
|
36
|
+
|
|
37
|
+
if (!textContent) return undefined
|
|
38
|
+
|
|
39
|
+
const isDir = textContent.endsWith("/")
|
|
40
|
+
const name = isDir ? textContent.slice(0, -1) : textContent
|
|
41
|
+
|
|
42
|
+
const nestedList = listItem.children.find(
|
|
43
|
+
(child): child is List => child.type === "list",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const children = nestedList ? parseList(nestedList) : undefined
|
|
47
|
+
|
|
48
|
+
return { name, isDir, children }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Remark plugin that converts :::filetree directives into FileTree JSX elements */
|
|
52
|
+
export const remarkFiletree: Plugin<[], Root> = () => {
|
|
53
|
+
return tree => {
|
|
54
|
+
visit(
|
|
55
|
+
tree,
|
|
56
|
+
"containerDirective",
|
|
57
|
+
(node: ContainerDirective, index, parent) => {
|
|
58
|
+
if (node.name !== "filetree" || index === undefined || !parent) return
|
|
59
|
+
|
|
60
|
+
const lists = node.children.filter(
|
|
61
|
+
(child): child is List => child.type === "list",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const treeItems: TreeItem[] = []
|
|
65
|
+
for (const list of lists) {
|
|
66
|
+
treeItems.push(...parseList(list))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const jsxNode = {
|
|
70
|
+
type: "mdxJsxFlowElement" as const,
|
|
71
|
+
name: "FileTree",
|
|
72
|
+
attributes: [
|
|
73
|
+
{
|
|
74
|
+
type: "mdxJsxAttribute" as const,
|
|
75
|
+
name: "tree",
|
|
76
|
+
value: JSON.stringify(treeItems),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
children: [],
|
|
80
|
+
data: { _mdxExplicitJsx: true },
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
84
|
+
parent.children[index] = jsxNode
|
|
85
|
+
return SKIP
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { List, ListItem, Paragraph, Text } from "mdast"
|
|
2
|
+
import { describe, expect, it } from "vite-plus/test"
|
|
3
|
+
import { parseList } from "./remark-filetree.ts"
|
|
4
|
+
|
|
5
|
+
function text(value: string): Text {
|
|
6
|
+
return { type: "text", value }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function paragraph(...children: Text[]): Paragraph {
|
|
10
|
+
return { type: "paragraph", children }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function listItem(...children: (Paragraph | List)[]): ListItem {
|
|
14
|
+
return { type: "listItem", children, spread: false }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function list(...children: ListItem[]): List {
|
|
18
|
+
return { type: "list", children, ordered: false, spread: false }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("parseList", () => {
|
|
22
|
+
it("should parse flat file entries", () => {
|
|
23
|
+
const input = list(
|
|
24
|
+
listItem(paragraph(text("package.json"))),
|
|
25
|
+
listItem(paragraph(text("tsconfig.json"))),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(parseList(input)).toEqual([
|
|
29
|
+
{ name: "package.json", isDir: false },
|
|
30
|
+
{ name: "tsconfig.json", isDir: false },
|
|
31
|
+
])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("should detect directories by trailing slash", () => {
|
|
35
|
+
const input = list(
|
|
36
|
+
listItem(paragraph(text("src/"))),
|
|
37
|
+
listItem(paragraph(text("README.md"))),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(parseList(input)).toEqual([
|
|
41
|
+
{ name: "src", isDir: true },
|
|
42
|
+
{ name: "README.md", isDir: false },
|
|
43
|
+
])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("should parse nested directory structures", () => {
|
|
47
|
+
const input = list(
|
|
48
|
+
listItem(
|
|
49
|
+
paragraph(text("src/")),
|
|
50
|
+
list(
|
|
51
|
+
listItem(paragraph(text("index.ts"))),
|
|
52
|
+
listItem(
|
|
53
|
+
paragraph(text("components/")),
|
|
54
|
+
list(listItem(paragraph(text("Button.tsx")))),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(parseList(input)).toEqual([
|
|
61
|
+
{
|
|
62
|
+
name: "src",
|
|
63
|
+
isDir: true,
|
|
64
|
+
children: [
|
|
65
|
+
{ name: "index.ts", isDir: false },
|
|
66
|
+
{
|
|
67
|
+
name: "components",
|
|
68
|
+
isDir: true,
|
|
69
|
+
children: [{ name: "Button.tsx", isDir: false }],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("should skip list items without text content", () => {
|
|
77
|
+
const emptyItem: ListItem = {
|
|
78
|
+
type: "listItem",
|
|
79
|
+
children: [],
|
|
80
|
+
spread: false,
|
|
81
|
+
}
|
|
82
|
+
const input = list(emptyItem, listItem(paragraph(text("file.ts"))))
|
|
83
|
+
|
|
84
|
+
expect(parseList(input)).toEqual([{ name: "file.ts", isDir: false }])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("should trim whitespace from names", () => {
|
|
88
|
+
const input = list(listItem(paragraph(text(" file.ts "))))
|
|
89
|
+
|
|
90
|
+
expect(parseList(input)).toEqual([{ name: "file.ts", isDir: false }])
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("should handle deeply nested structures", () => {
|
|
94
|
+
const input = list(
|
|
95
|
+
listItem(
|
|
96
|
+
paragraph(text("a/")),
|
|
97
|
+
list(
|
|
98
|
+
listItem(
|
|
99
|
+
paragraph(text("b/")),
|
|
100
|
+
list(
|
|
101
|
+
listItem(
|
|
102
|
+
paragraph(text("c/")),
|
|
103
|
+
list(listItem(paragraph(text("deep.ts")))),
|
|
104
|
+
),
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
expect(parseList(input)).toEqual([
|
|
112
|
+
{
|
|
113
|
+
name: "a",
|
|
114
|
+
isDir: true,
|
|
115
|
+
children: [
|
|
116
|
+
{
|
|
117
|
+
name: "b",
|
|
118
|
+
isDir: true,
|
|
119
|
+
children: [
|
|
120
|
+
{
|
|
121
|
+
name: "c",
|
|
122
|
+
isDir: true,
|
|
123
|
+
children: [{ name: "deep.ts", isDir: false }],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("should return undefined children for directories without nested list", () => {
|
|
133
|
+
const input = list(listItem(paragraph(text("empty-dir/"))))
|
|
134
|
+
|
|
135
|
+
expect(parseList(input)).toEqual([{ name: "empty-dir", isDir: true }])
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Blockquote, Paragraph, Root, Text } from "mdast"
|
|
2
|
+
import type { Plugin } from "unified"
|
|
3
|
+
import { visit } from "unist-util-visit"
|
|
4
|
+
|
|
5
|
+
const typeMap: Record<string, string> = {
|
|
6
|
+
NOTE: "note",
|
|
7
|
+
TIP: "tip",
|
|
8
|
+
WARNING: "warning",
|
|
9
|
+
CAUTION: "danger",
|
|
10
|
+
IMPORTANT: "info",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const calloutPattern = /^\[!(\w+)\]\s*/
|
|
14
|
+
|
|
15
|
+
/** Remark plugin that converts GitHub-style blockquote callouts into Callout JSX elements */
|
|
16
|
+
export const remarkGithubCallout: Plugin<[], Root> = () => {
|
|
17
|
+
return tree => {
|
|
18
|
+
visit(tree, "blockquote", (node: Blockquote, index, parent) => {
|
|
19
|
+
if (!parent || index === undefined || node.children.length === 0) return
|
|
20
|
+
|
|
21
|
+
const first = node.children[0]
|
|
22
|
+
if (first?.type !== "paragraph" || first.children.length === 0) return
|
|
23
|
+
|
|
24
|
+
const firstInline = first.children[0]
|
|
25
|
+
if (firstInline?.type !== "text") return
|
|
26
|
+
|
|
27
|
+
const match = calloutPattern.exec(firstInline.value)
|
|
28
|
+
if (!match) return
|
|
29
|
+
|
|
30
|
+
const rawType = match[1]
|
|
31
|
+
if (!rawType) return
|
|
32
|
+
|
|
33
|
+
const calloutType = typeMap[rawType]
|
|
34
|
+
if (!calloutType) return
|
|
35
|
+
|
|
36
|
+
const remainingText = firstInline.value.slice(match[0].length)
|
|
37
|
+
|
|
38
|
+
const updatedFirstChildren = [
|
|
39
|
+
...(remainingText
|
|
40
|
+
? [{ type: "text" as const, value: remainingText } satisfies Text]
|
|
41
|
+
: []),
|
|
42
|
+
...first.children.slice(1),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
const body =
|
|
46
|
+
updatedFirstChildren.length > 0
|
|
47
|
+
? [
|
|
48
|
+
{ ...first, children: updatedFirstChildren } satisfies Paragraph,
|
|
49
|
+
...node.children.slice(1),
|
|
50
|
+
]
|
|
51
|
+
: node.children.slice(1)
|
|
52
|
+
|
|
53
|
+
const jsxNode = {
|
|
54
|
+
type: "mdxJsxFlowElement" as const,
|
|
55
|
+
name: "Callout",
|
|
56
|
+
attributes: [
|
|
57
|
+
{
|
|
58
|
+
type: "mdxJsxAttribute" as const,
|
|
59
|
+
name: "type",
|
|
60
|
+
value: calloutType,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
children: body,
|
|
64
|
+
data: { _mdxExplicitJsx: true },
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
68
|
+
parent.children.splice(index, 1, jsxNode)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Root } from "mdast"
|
|
2
|
+
import type { TextDirective } from "mdast-util-directive"
|
|
3
|
+
import type { Plugin } from "unified"
|
|
4
|
+
import { visit } from "unist-util-visit"
|
|
5
|
+
|
|
6
|
+
/** Remark plugin that converts :icon text directives into InlineIcon JSX elements */
|
|
7
|
+
export const remarkIcon: Plugin<[], Root> = () => {
|
|
8
|
+
return tree => {
|
|
9
|
+
visit(tree, "textDirective", (node: TextDirective, index, parent) => {
|
|
10
|
+
if (node.name !== "icon" || index === undefined || !parent) return
|
|
11
|
+
|
|
12
|
+
const name = node.attributes?.name
|
|
13
|
+
if (!name) return
|
|
14
|
+
|
|
15
|
+
const className = node.attributes?.className
|
|
16
|
+
|
|
17
|
+
const attributes = [
|
|
18
|
+
{ type: "mdxJsxAttribute" as const, name: "name", value: name },
|
|
19
|
+
...(className
|
|
20
|
+
? [
|
|
21
|
+
{
|
|
22
|
+
type: "mdxJsxAttribute" as const,
|
|
23
|
+
name: "className",
|
|
24
|
+
value: className,
|
|
25
|
+
},
|
|
26
|
+
]
|
|
27
|
+
: []),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const jsxNode = {
|
|
31
|
+
type: "mdxJsxTextElement" as const,
|
|
32
|
+
name: "InlineIcon",
|
|
33
|
+
attributes,
|
|
34
|
+
children: [],
|
|
35
|
+
data: { _mdxExplicitJsx: true },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// @ts-expect-error mdxJsxTextElement is not in mdast types
|
|
39
|
+
parent.children[index] = jsxNode
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { dirname, relative, resolve } from "node:path"
|
|
2
|
+
import type { Image, Root } from "mdast"
|
|
3
|
+
import type { Plugin } from "unified"
|
|
4
|
+
import { visit } from "unist-util-visit"
|
|
5
|
+
|
|
6
|
+
/** Remark plugin that resolves relative image paths to URLs serveable by Vite */
|
|
7
|
+
export const remarkImage: Plugin<
|
|
8
|
+
[{ filePath: string; root: string }],
|
|
9
|
+
Root
|
|
10
|
+
> = ({ filePath, root }) => {
|
|
11
|
+
const fileDir = dirname(filePath)
|
|
12
|
+
|
|
13
|
+
return tree => {
|
|
14
|
+
visit(tree, "image", (node: Image) => {
|
|
15
|
+
if (
|
|
16
|
+
node.url.startsWith("http") ||
|
|
17
|
+
node.url.startsWith("/") ||
|
|
18
|
+
node.url.startsWith("data:")
|
|
19
|
+
)
|
|
20
|
+
return
|
|
21
|
+
const absolute = resolve(fileDir, node.url)
|
|
22
|
+
node.url = "/" + relative(root, absolute)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Root } from "mdast"
|
|
2
|
+
import type { LeafDirective } from "mdast-util-directive"
|
|
3
|
+
import type { Plugin } from "unified"
|
|
4
|
+
import { visit } from "unist-util-visit"
|
|
5
|
+
|
|
6
|
+
/** Walk a mdast tree and convert media leaf directives into JSX embed elements */
|
|
7
|
+
export function transformMediaDirectives(tree: Root) {
|
|
8
|
+
visit(tree, "leafDirective", (node: LeafDirective, index, parent) => {
|
|
9
|
+
if (
|
|
10
|
+
(node.name !== "video" && node.name !== "audio") ||
|
|
11
|
+
index === undefined ||
|
|
12
|
+
!parent
|
|
13
|
+
)
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
const type = node.attributes?.type
|
|
17
|
+
if (!type) return
|
|
18
|
+
|
|
19
|
+
if (node.name === "video" && type === "youtube") {
|
|
20
|
+
const id = node.attributes?.id
|
|
21
|
+
if (!id) return
|
|
22
|
+
|
|
23
|
+
const jsxNode = {
|
|
24
|
+
type: "mdxJsxFlowElement" as const,
|
|
25
|
+
name: "YouTube",
|
|
26
|
+
attributes: [
|
|
27
|
+
{ type: "mdxJsxAttribute" as const, name: "id", value: id },
|
|
28
|
+
],
|
|
29
|
+
children: [],
|
|
30
|
+
data: { _mdxExplicitJsx: true },
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
34
|
+
parent.children.splice(index, 1, jsxNode)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (node.name === "audio" && type === "soundcloud") {
|
|
38
|
+
const url = node.attributes?.url
|
|
39
|
+
if (!url) return
|
|
40
|
+
|
|
41
|
+
const jsxNode = {
|
|
42
|
+
type: "mdxJsxFlowElement" as const,
|
|
43
|
+
name: "SoundCloud",
|
|
44
|
+
attributes: [
|
|
45
|
+
{ type: "mdxJsxAttribute" as const, name: "url", value: url },
|
|
46
|
+
],
|
|
47
|
+
children: [],
|
|
48
|
+
data: { _mdxExplicitJsx: true },
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
52
|
+
parent.children.splice(index, 1, jsxNode)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Remark plugin that converts leaf directives into media embed JSX elements */
|
|
58
|
+
export const remarkMedia: Plugin<[], Root> = () => {
|
|
59
|
+
return tree => {
|
|
60
|
+
transformMediaDirectives(tree)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Root } from "mdast"
|
|
2
|
+
import type { LeafDirective } from "mdast-util-directive"
|
|
3
|
+
import { describe, expect, it } from "vite-plus/test"
|
|
4
|
+
import { transformMediaDirectives } from "./remark-media.ts"
|
|
5
|
+
|
|
6
|
+
function makeLeafDirective(
|
|
7
|
+
name: string,
|
|
8
|
+
attributes: Record<string, string>,
|
|
9
|
+
): LeafDirective {
|
|
10
|
+
return {
|
|
11
|
+
type: "leafDirective" as LeafDirective["type"],
|
|
12
|
+
name,
|
|
13
|
+
attributes,
|
|
14
|
+
children: [],
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeTree(...children: Root["children"]): Root {
|
|
19
|
+
return { type: "root", children }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function process(tree: Root) {
|
|
23
|
+
transformMediaDirectives(tree)
|
|
24
|
+
return tree
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("transformMediaDirectives", () => {
|
|
28
|
+
it("should convert video youtube directive to YouTube JSX element", () => {
|
|
29
|
+
const tree = process(
|
|
30
|
+
makeTree(makeLeafDirective("video", { type: "youtube", id: "abc123" })),
|
|
31
|
+
)
|
|
32
|
+
const node = tree.children[0]
|
|
33
|
+
expect(node).toMatchObject({
|
|
34
|
+
type: "mdxJsxFlowElement",
|
|
35
|
+
name: "YouTube",
|
|
36
|
+
attributes: [{ type: "mdxJsxAttribute", name: "id", value: "abc123" }],
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("should convert audio soundcloud directive to SoundCloud JSX element", () => {
|
|
41
|
+
const url = "https://soundcloud.com/artist/track"
|
|
42
|
+
const tree = process(
|
|
43
|
+
makeTree(makeLeafDirective("audio", { type: "soundcloud", url })),
|
|
44
|
+
)
|
|
45
|
+
const node = tree.children[0]
|
|
46
|
+
expect(node).toMatchObject({
|
|
47
|
+
type: "mdxJsxFlowElement",
|
|
48
|
+
name: "SoundCloud",
|
|
49
|
+
attributes: [{ type: "mdxJsxAttribute", name: "url", value: url }],
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("should skip video directive without type", () => {
|
|
54
|
+
const tree = process(makeTree(makeLeafDirective("video", { id: "abc123" })))
|
|
55
|
+
const node = tree.children[0]
|
|
56
|
+
expect(node).toMatchObject({ type: "leafDirective", name: "video" })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("should skip audio directive without url", () => {
|
|
60
|
+
const tree = process(
|
|
61
|
+
makeTree(makeLeafDirective("audio", { type: "soundcloud" })),
|
|
62
|
+
)
|
|
63
|
+
const node = tree.children[0]
|
|
64
|
+
expect(node).toMatchObject({ type: "leafDirective", name: "audio" })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("should ignore unrelated leaf directives", () => {
|
|
68
|
+
const tree = process(
|
|
69
|
+
makeTree(makeLeafDirective("include", { file: "test.md" })),
|
|
70
|
+
)
|
|
71
|
+
const node = tree.children[0]
|
|
72
|
+
expect(node).toMatchObject({ type: "leafDirective", name: "include" })
|
|
73
|
+
})
|
|
74
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Root } from "mdast"
|
|
2
|
+
import type { Plugin } from "unified"
|
|
3
|
+
|
|
4
|
+
/** Remark plugin that converts mermaid code blocks into Mermaid components */
|
|
5
|
+
export const remarkMermaid: Plugin<[], Root> = () => {
|
|
6
|
+
return tree => {
|
|
7
|
+
const children = tree.children
|
|
8
|
+
for (let i = 0; i < children.length; i++) {
|
|
9
|
+
const node = children[i]
|
|
10
|
+
if (node?.type !== "code" || node.lang !== "mermaid") continue
|
|
11
|
+
|
|
12
|
+
const jsxNode = {
|
|
13
|
+
type: "mdxJsxFlowElement" as const,
|
|
14
|
+
name: "Mermaid",
|
|
15
|
+
attributes: [],
|
|
16
|
+
children: [{ type: "text" as const, value: node.value }],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
20
|
+
children[i] = jsxNode
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Root } from "mdast"
|
|
2
|
+
import type { Plugin } from "unified"
|
|
3
|
+
import convert from "npm-to-yarn"
|
|
4
|
+
|
|
5
|
+
type Manager = "npm" | "pnpm" | "yarn" | "bun"
|
|
6
|
+
|
|
7
|
+
const managers: Manager[] = ["npm", "pnpm", "yarn", "bun"]
|
|
8
|
+
|
|
9
|
+
interface MdxJsxAttribute {
|
|
10
|
+
type: "mdxJsxAttribute"
|
|
11
|
+
name: string
|
|
12
|
+
value: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface MdxJsxFlowElement {
|
|
16
|
+
type: "mdxJsxFlowElement"
|
|
17
|
+
name: string
|
|
18
|
+
attributes: MdxJsxAttribute[]
|
|
19
|
+
children: []
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// @ts-expect-error tsgo does not resolve npm-to-yarn default export correctly
|
|
23
|
+
const convertCommand: (s: string, to: string) => string = convert
|
|
24
|
+
|
|
25
|
+
/** Remark plugin that converts npm code blocks into PackageTabs components */
|
|
26
|
+
export const remarkNpm: Plugin<[], Root> = () => {
|
|
27
|
+
return tree => {
|
|
28
|
+
const children = tree.children
|
|
29
|
+
for (let i = 0; i < children.length; i++) {
|
|
30
|
+
const node = children[i]
|
|
31
|
+
if (node?.type !== "code") continue
|
|
32
|
+
if (node.lang !== "npm" && node.lang !== "package-install") continue
|
|
33
|
+
|
|
34
|
+
const command = node.value.trim()
|
|
35
|
+
const attributes: MdxJsxAttribute[] = managers.map(pm => ({
|
|
36
|
+
type: "mdxJsxAttribute" as const,
|
|
37
|
+
name: pm,
|
|
38
|
+
value: convertCommand(command, pm),
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
const jsxNode: MdxJsxFlowElement = {
|
|
42
|
+
type: "mdxJsxFlowElement",
|
|
43
|
+
name: "PackageTabs",
|
|
44
|
+
attributes,
|
|
45
|
+
children: [],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// @ts-expect-error mdxJsxFlowElement is not in mdast types
|
|
49
|
+
children[i] = jsxNode
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|