livemark 0.2.0 → 0.7.1

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.
Files changed (348) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +5 -1
  3. package/build/actions/changelog/build.d.ts +14 -0
  4. package/build/actions/changelog/build.js +158 -0
  5. package/build/actions/changelog/build.unit.js +66 -0
  6. package/build/actions/config/define.d.ts +6 -0
  7. package/build/actions/config/define.js +8 -0
  8. package/build/actions/config/define.unit.js +29 -0
  9. package/build/actions/config/load.d.ts +5 -0
  10. package/build/actions/config/load.js +25 -0
  11. package/build/actions/config/load.unit.js +23 -0
  12. package/build/commands/build.d.ts +5 -0
  13. package/build/commands/build.js +14 -0
  14. package/build/commands/escape.d.ts +9 -0
  15. package/build/commands/escape.js +56 -0
  16. package/build/commands/preview.d.ts +5 -0
  17. package/build/commands/preview.js +18 -0
  18. package/build/commands/start.d.ts +5 -0
  19. package/build/commands/start.js +19 -0
  20. package/build/docs/markdown/includes/example.d.ts +58 -0
  21. package/build/docs/markdown/includes/example.js +5 -0
  22. package/build/entrypoints/main.d.ts +2 -0
  23. package/build/entrypoints/main.js +12 -0
  24. package/build/helpers/program.d.ts +2 -0
  25. package/build/helpers/program.js +5 -0
  26. package/build/index.d.ts +1 -4
  27. package/build/index.js +2 -5
  28. package/build/livemark.config.d.ts +58 -0
  29. package/build/livemark.config.js +25 -0
  30. package/build/models/config.d.ts +340 -0
  31. package/build/models/config.js +34 -0
  32. package/build/models/link.d.ts +13 -0
  33. package/build/models/link.js +9 -0
  34. package/build/models/patch.d.ts +21 -0
  35. package/build/models/patch.js +7 -0
  36. package/build/models/section.d.ts +75 -0
  37. package/build/models/section.js +26 -0
  38. package/build/models/theme.d.ts +72 -0
  39. package/build/models/theme.js +73 -0
  40. package/build/program.d.ts +2 -0
  41. package/build/program.js +16 -0
  42. package/build/vite.config.d.ts +2 -0
  43. package/build/vite.config.js +48 -0
  44. package/build/website/assets/logo.svg +117 -0
  45. package/build/website/client.tsx +10 -0
  46. package/build/website/components/Abbr.tsx +17 -0
  47. package/build/website/components/AnsiCode.tsx +15 -0
  48. package/build/website/components/Article.tsx +172 -0
  49. package/build/website/components/BackToTop.tsx +32 -0
  50. package/build/website/components/Banner.tsx +32 -0
  51. package/build/website/components/BlogIndex.tsx +121 -0
  52. package/build/website/components/BlogSidebar.tsx +162 -0
  53. package/build/website/components/Breadcrumbs.tsx +22 -0
  54. package/build/website/components/Callout.tsx +72 -0
  55. package/build/website/components/Card.tsx +48 -0
  56. package/build/website/components/Cards.tsx +8 -0
  57. package/build/website/components/ChangelogIndex.tsx +111 -0
  58. package/build/website/components/ChangelogSidebar.tsx +120 -0
  59. package/build/website/components/CodeBlock.tsx +135 -0
  60. package/build/website/components/CodeTabs.tsx +83 -0
  61. package/build/website/components/Column.tsx +4 -0
  62. package/build/website/components/Columns.tsx +12 -0
  63. package/build/website/components/Content.tsx +7 -0
  64. package/build/website/components/ContentTab.tsx +7 -0
  65. package/build/website/components/ContentTabs.tsx +82 -0
  66. package/build/website/components/DefaultCatchBoundary.tsx +54 -0
  67. package/build/website/components/DefinitionList.tsx +21 -0
  68. package/build/website/components/Details.tsx +30 -0
  69. package/build/website/components/FileTree.tsx +52 -0
  70. package/build/website/components/Footer.tsx +16 -0
  71. package/build/website/components/FootnoteRef.tsx +52 -0
  72. package/build/website/components/Heading.tsx +20 -0
  73. package/build/website/components/InlineBadge.tsx +9 -0
  74. package/build/website/components/InlineIcon.tsx +10 -0
  75. package/build/website/components/InlineToc.tsx +31 -0
  76. package/build/website/components/Layout.tsx +190 -0
  77. package/build/website/components/LinkButton.tsx +31 -0
  78. package/build/website/components/Mermaid.tsx +18 -0
  79. package/build/website/components/MermaidRenderer.tsx +37 -0
  80. package/build/website/components/MiniSidebar.tsx +49 -0
  81. package/build/website/components/NotFound.tsx +27 -0
  82. package/build/website/components/PackageTabs.tsx +74 -0
  83. package/build/website/components/PageToolbar.tsx +75 -0
  84. package/build/website/components/PrevNext.tsx +60 -0
  85. package/build/website/components/Search.tsx +28 -0
  86. package/build/website/components/SearchDialog.tsx +245 -0
  87. package/build/website/components/Section.tsx +24 -0
  88. package/build/website/components/SectionHeader.tsx +35 -0
  89. package/build/website/components/Sidebar.tsx +160 -0
  90. package/build/website/components/SidebarLinks.tsx +70 -0
  91. package/build/website/components/SidebarSections.tsx +71 -0
  92. package/build/website/components/SiteTitle.tsx +21 -0
  93. package/build/website/components/SortableTable.tsx +81 -0
  94. package/build/website/components/SoundCloud.tsx +16 -0
  95. package/build/website/components/TagIndex.tsx +120 -0
  96. package/build/website/components/Theme.tsx +39 -0
  97. package/build/website/components/Toc.tsx +226 -0
  98. package/build/website/components/YouTube.tsx +16 -0
  99. package/build/website/components/ZoomImage.tsx +25 -0
  100. package/build/website/content/article.ts +78 -0
  101. package/build/website/content/tag.ts +29 -0
  102. package/build/website/content-collections.ts +208 -0
  103. package/build/website/elements/accordion.tsx +78 -0
  104. package/build/website/elements/alert-dialog.tsx +185 -0
  105. package/build/website/elements/alert.tsx +74 -0
  106. package/build/website/elements/aspect-ratio.tsx +22 -0
  107. package/build/website/elements/avatar.tsx +107 -0
  108. package/build/website/elements/badge.tsx +51 -0
  109. package/build/website/elements/breadcrumb.tsx +120 -0
  110. package/build/website/elements/button-group.tsx +87 -0
  111. package/build/website/elements/button.tsx +57 -0
  112. package/build/website/elements/calendar.tsx +229 -0
  113. package/build/website/elements/card.tsx +102 -0
  114. package/build/website/elements/carousel.tsx +240 -0
  115. package/build/website/elements/chart.tsx +372 -0
  116. package/build/website/elements/checkbox.tsx +26 -0
  117. package/build/website/elements/collapsible.tsx +19 -0
  118. package/build/website/elements/command.tsx +210 -0
  119. package/build/website/elements/context-menu.tsx +270 -0
  120. package/build/website/elements/dialog.tsx +152 -0
  121. package/build/website/elements/direction.tsx +4 -0
  122. package/build/website/elements/drawer.tsx +127 -0
  123. package/build/website/elements/dropdown-menu.tsx +260 -0
  124. package/build/website/elements/empty.tsx +104 -0
  125. package/build/website/elements/field.tsx +235 -0
  126. package/build/website/elements/hover-card.tsx +49 -0
  127. package/build/website/elements/input-group.tsx +171 -0
  128. package/build/website/elements/input-otp.tsx +84 -0
  129. package/build/website/elements/input.tsx +18 -0
  130. package/build/website/elements/item.tsx +199 -0
  131. package/build/website/elements/kbd.tsx +26 -0
  132. package/build/website/elements/label.tsx +17 -0
  133. package/build/website/elements/menubar.tsx +282 -0
  134. package/build/website/elements/native-select.tsx +56 -0
  135. package/build/website/elements/navigation-menu.tsx +171 -0
  136. package/build/website/elements/pagination.tsx +133 -0
  137. package/build/website/elements/popover.tsx +88 -0
  138. package/build/website/elements/progress.tsx +81 -0
  139. package/build/website/elements/radio-group.tsx +36 -0
  140. package/build/website/elements/resizable.tsx +48 -0
  141. package/build/website/elements/scroll-area.tsx +51 -0
  142. package/build/website/elements/select.tsx +200 -0
  143. package/build/website/elements/separator.tsx +22 -0
  144. package/build/website/elements/sheet.tsx +130 -0
  145. package/build/website/elements/sidebar.tsx +717 -0
  146. package/build/website/elements/skeleton.tsx +13 -0
  147. package/build/website/elements/slider.tsx +57 -0
  148. package/build/website/elements/sonner.tsx +40 -0
  149. package/build/website/elements/spinner.tsx +16 -0
  150. package/build/website/elements/switch.tsx +30 -0
  151. package/build/website/elements/table.tsx +112 -0
  152. package/build/website/elements/tabs.tsx +79 -0
  153. package/build/website/elements/textarea.tsx +16 -0
  154. package/build/website/elements/toggle-group.tsx +87 -0
  155. package/build/website/elements/toggle.tsx +43 -0
  156. package/build/website/elements/tooltip.tsx +67 -0
  157. package/build/website/env.d.ts +14 -0
  158. package/build/website/helpers/article-icon.ts +61 -0
  159. package/build/website/helpers/article.ts +90 -0
  160. package/build/website/helpers/changelog-source.ts +16 -0
  161. package/build/website/helpers/changelog-source.unit.ts +38 -0
  162. package/build/website/helpers/dynamic-icon-resolver.tsx +19 -0
  163. package/build/website/helpers/dynamic-icon.tsx +12 -0
  164. package/build/website/helpers/favicon.ts +7 -0
  165. package/build/website/helpers/model.ts +4 -0
  166. package/build/website/helpers/resolve-asset-path.ts +9 -0
  167. package/build/website/helpers/resolve-includes.ts +131 -0
  168. package/build/website/helpers/resolve-includes.unit.ts +126 -0
  169. package/build/website/helpers/search-text.ts +15 -0
  170. package/build/website/helpers/section.ts +50 -0
  171. package/build/website/helpers/section.unit.ts +71 -0
  172. package/build/website/helpers/style.ts +6 -0
  173. package/build/website/helpers/theme.ts +27 -0
  174. package/build/website/helpers/toc-context.ts +10 -0
  175. package/build/website/helpers/toc.ts +46 -0
  176. package/build/website/helpers/toc.unit.ts +74 -0
  177. package/build/website/hooks/in-view.ts +30 -0
  178. package/build/website/hooks/media.ts +19 -0
  179. package/build/website/models/article.ts +55 -0
  180. package/build/website/models/toc.ts +5 -0
  181. package/build/website/plugins/rehype-inline-code.ts +107 -0
  182. package/build/website/plugins/rehype-task-list-a11y.ts +33 -0
  183. package/build/website/plugins/remark-abbr.ts +46 -0
  184. package/build/website/plugins/remark-abbr.unit.ts +99 -0
  185. package/build/website/plugins/remark-ansi.ts +248 -0
  186. package/build/website/plugins/remark-ansi.unit.ts +197 -0
  187. package/build/website/plugins/remark-badge.ts +50 -0
  188. package/build/website/plugins/remark-badge.unit.ts +96 -0
  189. package/build/website/plugins/remark-button.ts +60 -0
  190. package/build/website/plugins/remark-button.unit.ts +147 -0
  191. package/build/website/plugins/remark-callout.ts +57 -0
  192. package/build/website/plugins/remark-card.ts +80 -0
  193. package/build/website/plugins/remark-code-tabs.ts +87 -0
  194. package/build/website/plugins/remark-columns.ts +66 -0
  195. package/build/website/plugins/remark-custom-heading-id.ts +70 -0
  196. package/build/website/plugins/remark-custom-heading-id.unit.ts +131 -0
  197. package/build/website/plugins/remark-deflist.ts +36 -0
  198. package/build/website/plugins/remark-details.ts +49 -0
  199. package/build/website/plugins/remark-filetree.ts +89 -0
  200. package/build/website/plugins/remark-filetree.unit.ts +137 -0
  201. package/build/website/plugins/remark-github-callout.ts +71 -0
  202. package/build/website/plugins/remark-icon.ts +42 -0
  203. package/build/website/plugins/remark-image.ts +34 -0
  204. package/build/website/plugins/remark-media.ts +62 -0
  205. package/build/website/plugins/remark-media.unit.ts +74 -0
  206. package/build/website/plugins/remark-mermaid.ts +23 -0
  207. package/build/website/plugins/remark-npm.ts +52 -0
  208. package/build/website/plugins/remark-steps.ts +36 -0
  209. package/build/website/plugins/remark-steps.unit.ts +97 -0
  210. package/build/website/plugins/remark-tab.ts +100 -0
  211. package/build/website/plugins/remark-tab.unit.ts +181 -0
  212. package/build/website/plugins/remark-toc.ts +39 -0
  213. package/build/website/plugins/remark-toc.unit.ts +82 -0
  214. package/build/website/plugins/shiki-icon.ts +82 -0
  215. package/build/website/plugins/shiki-line-highlight.ts +174 -0
  216. package/build/website/plugins/vite-livemark.ts +91 -0
  217. package/build/website/routeTree.gen.ts +86 -0
  218. package/build/website/router.tsx +34 -0
  219. package/build/website/routes/$.tsx +92 -0
  220. package/build/website/routes/__root.tsx +97 -0
  221. package/build/website/server.ts +7 -0
  222. package/build/website/styles/general.css +163 -0
  223. package/build/website/styles/markdown.css +214 -0
  224. package/build/website/vite.config.ts +71 -0
  225. package/package.json +120 -12
  226. package/skills/authoring/SKILL.md +97 -0
  227. package/skills/configuration/SKILL.md +120 -0
  228. package/skills/customization/SKILL.md +118 -0
  229. package/build/descriptor/index.d.ts +0 -3
  230. package/build/descriptor/index.js +0 -4
  231. package/build/descriptor/load.d.ts +0 -8
  232. package/build/descriptor/load.js +0 -40
  233. package/build/descriptor/node.d.ts +0 -125
  234. package/build/descriptor/node.js +0 -9
  235. package/build/descriptor/save.d.ts +0 -8
  236. package/build/descriptor/save.js +0 -17
  237. package/build/descriptor/save.spec.js +0 -80
  238. package/build/descriptor/validate.d.ts +0 -9
  239. package/build/descriptor/validate.js +0 -14
  240. package/build/descriptor/validate.spec.js +0 -124
  241. package/build/dialect/Dialect.d.ts +0 -88
  242. package/build/dialect/Dialect.js +0 -2
  243. package/build/dialect/index.d.ts +0 -3
  244. package/build/dialect/index.js +0 -3
  245. package/build/dialect/load.d.ts +0 -4
  246. package/build/dialect/load.js +0 -7
  247. package/build/dialect/save.d.ts +0 -9
  248. package/build/dialect/save.js +0 -13
  249. package/build/dialect/validate.d.ts +0 -1
  250. package/build/dialect/validate.js +0 -4
  251. package/build/metadata/index.d.ts +0 -3
  252. package/build/metadata/index.js +0 -4
  253. package/build/metadata/load.d.ts +0 -10
  254. package/build/metadata/load.js +0 -55
  255. package/build/metadata/node.d.ts +0 -125
  256. package/build/metadata/node.js +0 -9
  257. package/build/metadata/save.d.ts +0 -8
  258. package/build/metadata/save.js +0 -17
  259. package/build/metadata/save.spec.js +0 -80
  260. package/build/metadata/validate.d.ts +0 -9
  261. package/build/metadata/validate.js +0 -14
  262. package/build/metadata/validate.spec.d.ts +0 -1
  263. package/build/metadata/validate.spec.js +0 -124
  264. package/build/package/Contributor.d.ts +0 -21
  265. package/build/package/Contributor.js +0 -2
  266. package/build/package/Package.d.ts +0 -65
  267. package/build/package/Package.js +0 -2
  268. package/build/package/index.d.ts +0 -4
  269. package/build/package/index.js +0 -3
  270. package/build/package/load.d.ts +0 -4
  271. package/build/package/load.js +0 -7
  272. package/build/package/save.d.ts +0 -9
  273. package/build/package/save.js +0 -13
  274. package/build/package/validate.d.ts +0 -1
  275. package/build/package/validate.js +0 -4
  276. package/build/resource/License.d.ts +0 -18
  277. package/build/resource/License.js +0 -2
  278. package/build/resource/Resource.d.ts +0 -67
  279. package/build/resource/Resource.js +0 -2
  280. package/build/resource/Source.d.ts +0 -17
  281. package/build/resource/Source.js +0 -2
  282. package/build/resource/index.d.ts +0 -5
  283. package/build/resource/index.js +0 -2
  284. package/build/resource/load.d.ts +0 -1
  285. package/build/resource/load.js +0 -2
  286. package/build/resource/save.d.ts +0 -9
  287. package/build/resource/save.js +0 -13
  288. package/build/resource/types/index.d.ts +0 -1
  289. package/build/resource/types/index.js +0 -2
  290. package/build/resource/types/table.d.ts +0 -29
  291. package/build/resource/types/table.js +0 -2
  292. package/build/resource/validate.d.ts +0 -1
  293. package/build/resource/validate.js +0 -4
  294. package/build/schema/Field.d.ts +0 -5
  295. package/build/schema/Field.js +0 -2
  296. package/build/schema/Schema.d.ts +0 -55
  297. package/build/schema/Schema.js +0 -2
  298. package/build/schema/fields/any.d.ts +0 -20
  299. package/build/schema/fields/any.js +0 -2
  300. package/build/schema/fields/array.d.ts +0 -32
  301. package/build/schema/fields/array.js +0 -2
  302. package/build/schema/fields/base.d.ts +0 -42
  303. package/build/schema/fields/base.js +0 -2
  304. package/build/schema/fields/boolean.d.ts +0 -28
  305. package/build/schema/fields/boolean.js +0 -2
  306. package/build/schema/fields/date.d.ts +0 -35
  307. package/build/schema/fields/date.js +0 -2
  308. package/build/schema/fields/datetime.d.ts +0 -35
  309. package/build/schema/fields/datetime.js +0 -2
  310. package/build/schema/fields/duration.d.ts +0 -28
  311. package/build/schema/fields/duration.js +0 -2
  312. package/build/schema/fields/geojson.d.ts +0 -26
  313. package/build/schema/fields/geojson.js +0 -2
  314. package/build/schema/fields/geopoint.d.ts +0 -27
  315. package/build/schema/fields/geopoint.js +0 -2
  316. package/build/schema/fields/index.d.ts +0 -16
  317. package/build/schema/fields/index.js +0 -17
  318. package/build/schema/fields/integer.d.ts +0 -37
  319. package/build/schema/fields/integer.js +0 -2
  320. package/build/schema/fields/list.d.ts +0 -36
  321. package/build/schema/fields/list.js +0 -2
  322. package/build/schema/fields/number.d.ts +0 -40
  323. package/build/schema/fields/number.js +0 -2
  324. package/build/schema/fields/object.d.ts +0 -32
  325. package/build/schema/fields/object.js +0 -2
  326. package/build/schema/fields/string.d.ts +0 -40
  327. package/build/schema/fields/string.js +0 -2
  328. package/build/schema/fields/time.d.ts +0 -35
  329. package/build/schema/fields/time.js +0 -2
  330. package/build/schema/fields/year.d.ts +0 -28
  331. package/build/schema/fields/year.js +0 -2
  332. package/build/schema/fields/yearmonth.d.ts +0 -28
  333. package/build/schema/fields/yearmonth.js +0 -2
  334. package/build/schema/index.d.ts +0 -4
  335. package/build/schema/index.js +0 -2
  336. package/build/schema/load.d.ts +0 -1
  337. package/build/schema/load.js +0 -2
  338. package/build/schema/save.d.ts +0 -9
  339. package/build/schema/save.js +0 -13
  340. package/build/schema/validate.d.ts +0 -1
  341. package/build/schema/validate.js +0 -4
  342. package/build/tsconfig.tsbuildinfo +0 -1
  343. package/index.ts +0 -0
  344. package/tsconfig.json +0 -10
  345. package/typedoc.json +0 -4
  346. /package/build/{descriptor/save.spec.d.ts → actions/changelog/build.unit.d.ts} +0 -0
  347. /package/build/{descriptor/validate.spec.d.ts → actions/config/define.unit.d.ts} +0 -0
  348. /package/build/{metadata/save.spec.d.ts → actions/config/load.unit.d.ts} +0 -0
@@ -0,0 +1,147 @@
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 { transformButtonDirectives } from "./remark-button.ts"
5
+
6
+ function makeLeafDirective(
7
+ name: string,
8
+ attributes: Record<string, string>,
9
+ children: LeafDirective["children"] = [],
10
+ ): LeafDirective {
11
+ return {
12
+ type: "leafDirective" as LeafDirective["type"],
13
+ name,
14
+ attributes,
15
+ children,
16
+ }
17
+ }
18
+
19
+ function makeTree(...children: Root["children"]): Root {
20
+ return { type: "root", children }
21
+ }
22
+
23
+ function process(tree: Root) {
24
+ transformButtonDirectives(tree)
25
+ return tree
26
+ }
27
+
28
+ describe("transformButtonDirectives", () => {
29
+ it("should convert ::button with bracket text to LinkButton JSX element", () => {
30
+ const tree = process(
31
+ makeTree(
32
+ makeLeafDirective("button", { href: "/start" }, [
33
+ { type: "text", value: "Get Started" },
34
+ ]),
35
+ ),
36
+ )
37
+ const node = tree.children[0]
38
+ expect(node).toMatchObject({
39
+ type: "mdxJsxFlowElement",
40
+ name: "LinkButton",
41
+ attributes: [
42
+ { type: "mdxJsxAttribute", name: "href", value: "/start" },
43
+ { type: "mdxJsxAttribute", name: "label", value: "Get Started" },
44
+ ],
45
+ })
46
+ })
47
+
48
+ it("should convert ::button with label attribute to LinkButton JSX element", () => {
49
+ const tree = process(
50
+ makeTree(
51
+ makeLeafDirective("button", { href: "/docs", label: "Read Docs" }),
52
+ ),
53
+ )
54
+ const node = tree.children[0]
55
+ expect(node).toMatchObject({
56
+ type: "mdxJsxFlowElement",
57
+ name: "LinkButton",
58
+ attributes: [
59
+ { type: "mdxJsxAttribute", name: "href", value: "/docs" },
60
+ { type: "mdxJsxAttribute", name: "label", value: "Read Docs" },
61
+ ],
62
+ })
63
+ })
64
+
65
+ it("should prefer bracket text over label attribute", () => {
66
+ const tree = process(
67
+ makeTree(
68
+ makeLeafDirective("button", { href: "/x", label: "Fallback" }, [
69
+ { type: "text", value: "Primary" },
70
+ ]),
71
+ ),
72
+ )
73
+ const node = tree.children[0]
74
+ expect(node).toMatchObject({
75
+ type: "mdxJsxFlowElement",
76
+ name: "LinkButton",
77
+ attributes: [
78
+ { type: "mdxJsxAttribute", name: "href", value: "/x" },
79
+ { type: "mdxJsxAttribute", name: "label", value: "Primary" },
80
+ ],
81
+ })
82
+ })
83
+
84
+ it("should pass variant attribute when provided", () => {
85
+ const tree = process(
86
+ makeTree(
87
+ makeLeafDirective("button", { href: "/a", variant: "outline" }, [
88
+ { type: "text", value: "Click" },
89
+ ]),
90
+ ),
91
+ )
92
+ const node = tree.children[0]
93
+ expect(node).toMatchObject({
94
+ type: "mdxJsxFlowElement",
95
+ name: "LinkButton",
96
+ attributes: [
97
+ { type: "mdxJsxAttribute", name: "href", value: "/a" },
98
+ { type: "mdxJsxAttribute", name: "label", value: "Click" },
99
+ { type: "mdxJsxAttribute", name: "variant", value: "outline" },
100
+ ],
101
+ })
102
+ })
103
+
104
+ it("should pass size attribute when provided", () => {
105
+ const tree = process(
106
+ makeTree(
107
+ makeLeafDirective("button", { href: "/a", size: "lg" }, [
108
+ { type: "text", value: "Big" },
109
+ ]),
110
+ ),
111
+ )
112
+ const node = tree.children[0]
113
+ expect(node).toMatchObject({
114
+ type: "mdxJsxFlowElement",
115
+ name: "LinkButton",
116
+ attributes: [
117
+ { type: "mdxJsxAttribute", name: "href", value: "/a" },
118
+ { type: "mdxJsxAttribute", name: "label", value: "Big" },
119
+ { type: "mdxJsxAttribute", name: "size", value: "lg" },
120
+ ],
121
+ })
122
+ })
123
+
124
+ it("should skip button directive without href", () => {
125
+ const tree = process(
126
+ makeTree(makeLeafDirective("button", { label: "No Link" })),
127
+ )
128
+ const node = tree.children[0]
129
+ expect(node).toMatchObject({ type: "leafDirective", name: "button" })
130
+ })
131
+
132
+ it("should skip button directive without label or bracket text", () => {
133
+ const tree = process(
134
+ makeTree(makeLeafDirective("button", { href: "/no-label" })),
135
+ )
136
+ const node = tree.children[0]
137
+ expect(node).toMatchObject({ type: "leafDirective", name: "button" })
138
+ })
139
+
140
+ it("should ignore unrelated leaf directives", () => {
141
+ const tree = process(
142
+ makeTree(makeLeafDirective("video", { type: "youtube", id: "abc" })),
143
+ )
144
+ const node = tree.children[0]
145
+ expect(node).toMatchObject({ type: "leafDirective", name: "video" })
146
+ })
147
+ })
@@ -0,0 +1,57 @@
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
+ const calloutTypes = new Set(["note", "tip", "info", "warning", "danger"])
7
+
8
+ /** Remark plugin that converts container directives into Callout JSX elements */
9
+ export const remarkCallout: Plugin<[], Root> = () => {
10
+ return tree => {
11
+ visit(
12
+ tree,
13
+ "containerDirective",
14
+ (node: ContainerDirective, index, parent) => {
15
+ if (!calloutTypes.has(node.name) || index === undefined || !parent)
16
+ return
17
+
18
+ const labelParagraph = node.children.find(
19
+ (child): child is Paragraph =>
20
+ child.type === "paragraph" && !!child.data?.directiveLabel,
21
+ )
22
+
23
+ const title = labelParagraph
24
+ ? {
25
+ type: "mdxJsxAttribute" as const,
26
+ name: "title",
27
+ value: labelParagraph.children
28
+ .map(c => ("value" in c ? String(c.value) : ""))
29
+ .join(""),
30
+ }
31
+ : undefined
32
+
33
+ const body = node.children.filter(
34
+ child => !(child.type === "paragraph" && child.data?.directiveLabel),
35
+ )
36
+
37
+ const jsxNode = {
38
+ type: "mdxJsxFlowElement" as const,
39
+ name: "Callout",
40
+ attributes: [
41
+ {
42
+ type: "mdxJsxAttribute" as const,
43
+ name: "type",
44
+ value: node.name,
45
+ },
46
+ ...(title ? [title] : []),
47
+ ],
48
+ children: body,
49
+ data: { _mdxExplicitJsx: true },
50
+ }
51
+
52
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
53
+ parent.children.splice(index, 1, jsxNode)
54
+ },
55
+ )
56
+ }
57
+ }
@@ -0,0 +1,80 @@
1
+ import type { Root } 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
+ /** Remark plugin that converts :::card directives into Card/Cards JSX elements */
7
+ export const remarkCard: Plugin<[], Root> = () => {
8
+ return tree => {
9
+ visit(
10
+ tree,
11
+ "containerDirective",
12
+ (node: ContainerDirective, index, parent) => {
13
+ if (node.name !== "card" || index === undefined || !parent) return
14
+
15
+ const title = node.attributes?.title ?? ""
16
+ const href = node.attributes?.href
17
+ const icon = node.attributes?.icon
18
+
19
+ const attributes = [
20
+ { type: "mdxJsxAttribute" as const, name: "title", value: title },
21
+ ...(href
22
+ ? [{ type: "mdxJsxAttribute" as const, name: "href", value: href }]
23
+ : []),
24
+ ...(icon
25
+ ? [{ type: "mdxJsxAttribute" as const, name: "icon", value: icon }]
26
+ : []),
27
+ ]
28
+
29
+ const jsxNode = {
30
+ type: "mdxJsxFlowElement" as const,
31
+ name: "Card",
32
+ attributes,
33
+ children: node.children,
34
+ data: { _mdxExplicitJsx: true },
35
+ }
36
+
37
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
38
+ parent.children[index] = jsxNode
39
+ return SKIP
40
+ },
41
+ )
42
+
43
+ const children = tree.children
44
+ let i = 0
45
+ while (i < children.length) {
46
+ const node = children[i]
47
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
48
+ if (node?.type !== "mdxJsxFlowElement" || node.name !== "Card") {
49
+ i++
50
+ continue
51
+ }
52
+
53
+ let j = i + 1
54
+ while (j < children.length) {
55
+ const next = children[j]
56
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
57
+ if (next?.type !== "mdxJsxFlowElement" || next.name !== "Card") break
58
+ j++
59
+ }
60
+
61
+ if (j - i < 2) {
62
+ i++
63
+ continue
64
+ }
65
+
66
+ const group = children.slice(i, j)
67
+ const wrapper = {
68
+ type: "mdxJsxFlowElement" as const,
69
+ name: "Cards",
70
+ attributes: [],
71
+ children: group,
72
+ data: { _mdxExplicitJsx: true },
73
+ }
74
+
75
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
76
+ children.splice(i, j - i, wrapper)
77
+ i++
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,87 @@
1
+ import type { Code, Root } from "mdast"
2
+ import type { Plugin } from "unified"
3
+
4
+ const TAB_PATTERN = /\btab="([^"]+)"/
5
+ const SYNC_PATTERN = /\bsync="([^"]+)"/
6
+
7
+ /** Remark plugin that groups consecutive code blocks with tab="name" meta into a CodeTabs component */
8
+ export const remarkCodeTabs: Plugin<[], Root> = () => {
9
+ return tree => {
10
+ const children = tree.children
11
+ let i = 0
12
+
13
+ while (i < children.length) {
14
+ const node = children[i]
15
+ if (node?.type !== "code" || !node.meta) {
16
+ i++
17
+ continue
18
+ }
19
+
20
+ const firstMatch = TAB_PATTERN.exec(node.meta)
21
+ if (!firstMatch) {
22
+ i++
23
+ continue
24
+ }
25
+
26
+ const group: { name: string; code: Code }[] = [
27
+ { name: firstMatch[1]!, code: node },
28
+ ]
29
+
30
+ let j = i + 1
31
+ while (j < children.length) {
32
+ const next = children[j]
33
+ if (next?.type !== "code" || !next.meta) break
34
+ const nextMatch = TAB_PATTERN.exec(next.meta)
35
+ if (!nextMatch) break
36
+ group.push({ name: nextMatch[1]!, code: next })
37
+ j++
38
+ }
39
+
40
+ if (group.length < 2) {
41
+ i++
42
+ continue
43
+ }
44
+
45
+ const firstMeta = group[0]!.code.meta!
46
+ const syncMatch = SYNC_PATTERN.exec(firstMeta)
47
+
48
+ for (const item of group) {
49
+ item.code.meta =
50
+ item.code
51
+ .meta!.replace(TAB_PATTERN, "")
52
+ .replace(SYNC_PATTERN, "")
53
+ .trim() || null
54
+ }
55
+
56
+ const codeTabsAttrs: {
57
+ type: "mdxJsxAttribute"
58
+ name: string
59
+ value: string
60
+ }[] = [
61
+ {
62
+ type: "mdxJsxAttribute",
63
+ name: "tabs",
64
+ value: JSON.stringify(group.map(g => g.name)),
65
+ },
66
+ ]
67
+ if (syncMatch) {
68
+ codeTabsAttrs.push({
69
+ type: "mdxJsxAttribute",
70
+ name: "sync",
71
+ value: syncMatch[1]!,
72
+ })
73
+ }
74
+
75
+ const jsxNode = {
76
+ type: "mdxJsxFlowElement",
77
+ name: "CodeTabs",
78
+ attributes: codeTabsAttrs,
79
+ children: group.map(g => g.code),
80
+ }
81
+
82
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
83
+ children.splice(i, group.length, jsxNode)
84
+ i++
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,66 @@
1
+ import type { Root } 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
+ /** Remark plugin that converts consecutive :::column directives into a Columns grid */
7
+ export const remarkColumns: Plugin<[], Root> = () => {
8
+ return tree => {
9
+ visit(
10
+ tree,
11
+ "containerDirective",
12
+ (node: ContainerDirective, index, parent) => {
13
+ if (node.name !== "column" || index === undefined || !parent) return
14
+
15
+ const jsxNode = {
16
+ type: "mdxJsxFlowElement" as const,
17
+ name: "Column",
18
+ attributes: [],
19
+ children: node.children,
20
+ data: { _mdxExplicitJsx: true },
21
+ }
22
+
23
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
24
+ parent.children[index] = jsxNode
25
+ return SKIP
26
+ },
27
+ )
28
+
29
+ const children = tree.children
30
+ let i = 0
31
+ while (i < children.length) {
32
+ const node = children[i]
33
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
34
+ if (node?.type !== "mdxJsxFlowElement" || node.name !== "Column") {
35
+ i++
36
+ continue
37
+ }
38
+
39
+ let j = i + 1
40
+ while (j < children.length) {
41
+ const next = children[j]
42
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
43
+ if (next?.type !== "mdxJsxFlowElement" || next.name !== "Column") break
44
+ j++
45
+ }
46
+
47
+ if (j - i < 2) {
48
+ i++
49
+ continue
50
+ }
51
+
52
+ const group = children.slice(i, j)
53
+ const wrapper = {
54
+ type: "mdxJsxFlowElement" as const,
55
+ name: "Columns",
56
+ attributes: [],
57
+ children: group,
58
+ data: { _mdxExplicitJsx: true },
59
+ }
60
+
61
+ // @ts-expect-error mdxJsxFlowElement is not in mdast types
62
+ children.splice(i, j - i, wrapper)
63
+ i++
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,70 @@
1
+ import type { Heading, Root } from "mdast"
2
+ import type { Plugin } from "unified"
3
+ import { SKIP, visit } from "unist-util-visit"
4
+
5
+ declare module "mdast" {
6
+ interface HeadingData {
7
+ hProperties?: Record<string, string | number | boolean | null | undefined>
8
+ }
9
+ }
10
+
11
+ const CUSTOM_ID_PATTERN = /\s*\[#([^\]]+)\]\s*$/
12
+ const TOC_HIDDEN_PATTERN = /\s*\[!toc\]\s*$/
13
+ const TOC_ONLY_PATTERN = /\s*\[toc\]\s*$/
14
+
15
+ /** Walk a mdast tree and process heading annotations: `[#id]`, `[!toc]`, and `[toc]` */
16
+ export function transformHeadingAnnotations(tree: Root) {
17
+ visit(tree, "heading", (node: Heading, index, parent) => {
18
+ const lastChild = node.children[node.children.length - 1]
19
+ if (!lastChild || lastChild.type !== "text") return undefined
20
+
21
+ if (TOC_ONLY_PATTERN.test(lastChild.value)) {
22
+ if (parent && index !== undefined) {
23
+ parent.children.splice(index, 1)
24
+ return [SKIP, index] as const
25
+ }
26
+ return undefined
27
+ }
28
+
29
+ if (TOC_HIDDEN_PATTERN.test(lastChild.value)) {
30
+ lastChild.value = lastChild.value.replace(TOC_HIDDEN_PATTERN, "")
31
+ if (lastChild.value === "" && node.children.length > 1) {
32
+ node.children.pop()
33
+ }
34
+ node.data = node.data ?? {}
35
+ node.data.hProperties = {
36
+ ...node.data.hProperties,
37
+ "data-toc-hidden": true,
38
+ }
39
+ return undefined
40
+ }
41
+
42
+ const match = CUSTOM_ID_PATTERN.exec(lastChild.value)
43
+ if (!match) return undefined
44
+
45
+ const customId = match[1]
46
+ if (!customId) return undefined
47
+
48
+ lastChild.value = lastChild.value.replace(CUSTOM_ID_PATTERN, "")
49
+
50
+ if (lastChild.value === "" && node.children.length > 1) {
51
+ node.children.pop()
52
+ }
53
+
54
+ node.data = node.data ?? {}
55
+ node.data.hProperties = { ...node.data.hProperties, id: customId }
56
+ return undefined
57
+ })
58
+ }
59
+
60
+ /** @deprecated Use transformHeadingAnnotations instead */
61
+ export const transformCustomHeadingIds = transformHeadingAnnotations
62
+
63
+ /** Remark plugin that processes heading annotations: `[#custom-id]`, `[!toc]`, and `[toc]` */
64
+ const remarkCustomHeadingId: Plugin<[], Root> = () => {
65
+ return tree => {
66
+ transformHeadingAnnotations(tree)
67
+ }
68
+ }
69
+
70
+ export default remarkCustomHeadingId
@@ -0,0 +1,131 @@
1
+ import type { Heading, Root, Text } from "mdast"
2
+ import { describe, expect, it } from "vite-plus/test"
3
+ import {
4
+ transformCustomHeadingIds,
5
+ transformHeadingAnnotations,
6
+ } from "./remark-custom-heading-id.ts"
7
+
8
+ function makeHeading(depth: 1 | 2 | 3 | 4 | 5 | 6, text: string): Heading {
9
+ return { type: "heading", depth, children: [{ type: "text", value: text }] }
10
+ }
11
+
12
+ function makeBoldHeading(boldText: string, trailingText: string): Heading {
13
+ return {
14
+ type: "heading",
15
+ depth: 2,
16
+ children: [
17
+ { type: "strong", children: [{ type: "text", value: boldText }] },
18
+ { type: "text", value: trailingText },
19
+ ],
20
+ }
21
+ }
22
+
23
+ function makeTree(...headings: Heading[]): Root {
24
+ return { type: "root", children: headings }
25
+ }
26
+
27
+ function findHeading(tree: Root, index: number): Heading {
28
+ const node = tree.children[index]
29
+ if (!node || node.type !== "heading") throw new Error("No heading at index")
30
+ return node
31
+ }
32
+
33
+ function headingText(heading: Heading): string {
34
+ return heading.children
35
+ .filter((c): c is Text => c.type === "text")
36
+ .map(c => c.value)
37
+ .join("")
38
+ }
39
+
40
+ describe("transformHeadingAnnotations", () => {
41
+ function process(tree: Root) {
42
+ transformHeadingAnnotations(tree)
43
+ return tree
44
+ }
45
+
46
+ it("should set a custom id from [#custom-id] syntax", () => {
47
+ const tree = process(makeTree(makeHeading(2, "My Heading [#custom-id]")))
48
+ const heading = findHeading(tree, 0)
49
+ expect(heading.data?.hProperties).toEqual({ id: "custom-id" })
50
+ expect(headingText(heading)).toBe("My Heading")
51
+ })
52
+
53
+ it("should leave headings without custom ids unchanged", () => {
54
+ const tree = process(makeTree(makeHeading(2, "Normal Heading")))
55
+ const heading = findHeading(tree, 0)
56
+ expect(heading.data).toBeUndefined()
57
+ expect(headingText(heading)).toBe("Normal Heading")
58
+ })
59
+
60
+ it("should handle different heading depths", () => {
61
+ const tree = process(
62
+ makeTree(
63
+ makeHeading(3, "Deep Heading [#deep]"),
64
+ makeHeading(4, "Deeper Heading [#deeper]"),
65
+ ),
66
+ )
67
+
68
+ const h3 = findHeading(tree, 0)
69
+ expect(h3.data?.hProperties).toEqual({ id: "deep" })
70
+ expect(h3.depth).toBe(3)
71
+
72
+ const h4 = findHeading(tree, 1)
73
+ expect(h4.data?.hProperties).toEqual({ id: "deeper" })
74
+ expect(h4.depth).toBe(4)
75
+ })
76
+
77
+ it("should handle ids with hyphens and numbers", () => {
78
+ const tree = process(
79
+ makeTree(makeHeading(2, "Section 1 [#section-1-intro]")),
80
+ )
81
+ const heading = findHeading(tree, 0)
82
+ expect(heading.data?.hProperties).toEqual({ id: "section-1-intro" })
83
+ expect(headingText(heading)).toBe("Section 1")
84
+ })
85
+
86
+ it("should preserve inline formatting before the id", () => {
87
+ const tree = process(
88
+ makeTree(makeBoldHeading("Bold", " heading [#bold-heading]")),
89
+ )
90
+ const heading = findHeading(tree, 0)
91
+ expect(heading.data?.hProperties).toEqual({ id: "bold-heading" })
92
+ expect(heading.children.some(c => c.type === "strong")).toBe(true)
93
+ expect(headingText(heading)).toBe(" heading")
94
+ })
95
+
96
+ it("should add data-toc-hidden for [!toc] headings", () => {
97
+ const tree = process(makeTree(makeHeading(2, "Hidden Heading [!toc]")))
98
+ const heading = findHeading(tree, 0)
99
+ expect(heading.data?.hProperties).toEqual({ "data-toc-hidden": true })
100
+ expect(headingText(heading)).toBe("Hidden Heading")
101
+ })
102
+
103
+ it("should remove [toc] headings from the tree", () => {
104
+ const tree = process(
105
+ makeTree(
106
+ makeHeading(2, "Visible Heading"),
107
+ makeHeading(2, "TOC Only Heading [toc]"),
108
+ makeHeading(3, "Another Visible"),
109
+ ),
110
+ )
111
+ expect(tree.children).toHaveLength(2)
112
+ expect(headingText(findHeading(tree, 0))).toBe("Visible Heading")
113
+ expect(headingText(findHeading(tree, 1))).toBe("Another Visible")
114
+ })
115
+
116
+ it("should remove consecutive [toc] headings correctly", () => {
117
+ const tree = process(
118
+ makeTree(
119
+ makeHeading(2, "First [toc]"),
120
+ makeHeading(2, "Second [toc]"),
121
+ makeHeading(2, "Kept"),
122
+ ),
123
+ )
124
+ expect(tree.children).toHaveLength(1)
125
+ expect(headingText(findHeading(tree, 0))).toBe("Kept")
126
+ })
127
+
128
+ it("should be available as transformCustomHeadingIds for backward compatibility", () => {
129
+ expect(transformCustomHeadingIds).toBe(transformHeadingAnnotations)
130
+ })
131
+ })
@@ -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
+ }