docs-i18n 0.6.3 → 0.7.0

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 (169) hide show
  1. package/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
  2. package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
  3. package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
  4. package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
  5. package/admin/app/routeTree.gen.ts +68 -0
  6. package/admin/app/router.tsx +23 -0
  7. package/admin/app/routes/__root.tsx +55 -0
  8. package/admin/app/routes/index.tsx +416 -0
  9. package/{src/admin/ui → admin/app}/styles.css +36 -3
  10. package/admin/package.json +27 -0
  11. package/admin/server/functions/jobs.ts +53 -0
  12. package/admin/server/functions/misc.ts +84 -0
  13. package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
  14. package/admin/server/functions/status.ts +61 -0
  15. package/admin/server/index.ts +35 -0
  16. package/admin/server/init.ts +46 -0
  17. package/{src/admin → admin}/server/services/job-manager.ts +39 -10
  18. package/{src/admin → admin}/server/services/status.ts +6 -6
  19. package/admin/tsconfig.json +19 -0
  20. package/{src/admin → admin}/vite.config.ts +8 -2
  21. package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
  22. package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
  23. package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
  24. package/dist/chunk-L64GJ4OB.js +32 -0
  25. package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
  26. package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
  27. package/dist/chunk-TRURQFP4.js +31 -0
  28. package/dist/cli.js +108 -7
  29. package/dist/index.d.ts +41 -1
  30. package/dist/index.js +92 -3
  31. package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
  32. package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
  33. package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
  34. package/dist/upload-XL6KG6S2.js +132 -0
  35. package/package.json +17 -15
  36. package/template/app/components/BlogArticle.tsx +159 -0
  37. package/template/app/components/BlogList.tsx +88 -0
  38. package/template/app/components/Breadcrumbs.tsx +81 -0
  39. package/template/app/components/Card.tsx +31 -0
  40. package/template/app/components/Doc.tsx +191 -0
  41. package/template/app/components/DocBreadcrumb.tsx +60 -0
  42. package/template/app/components/DocContainer.tsx +13 -0
  43. package/template/app/components/DocTitle.tsx +11 -0
  44. package/template/app/components/DocsLayout.tsx +715 -0
  45. package/template/app/components/Dropdown.tsx +116 -0
  46. package/template/app/components/FallbackBanner.tsx +36 -0
  47. package/template/app/components/Footer.tsx +29 -0
  48. package/template/app/components/FrameworkSelect.tsx +150 -0
  49. package/template/app/components/LibraryCard.tsx +178 -0
  50. package/template/app/components/LocaleSwitcher.tsx +43 -0
  51. package/template/app/components/Navbar.tsx +430 -0
  52. package/template/app/components/PostNotFound.tsx +20 -0
  53. package/template/app/components/SearchButton.tsx +32 -0
  54. package/template/app/components/Select.tsx +103 -0
  55. package/template/app/components/Spinner.tsx +18 -0
  56. package/template/app/components/ThemeProvider.tsx +141 -0
  57. package/template/app/components/ThemeToggle.tsx +31 -0
  58. package/template/app/components/Toc.tsx +86 -0
  59. package/template/app/components/VersionSelect.tsx +118 -0
  60. package/template/app/components/icons/BSkyIcon.tsx +27 -0
  61. package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
  62. package/template/app/components/icons/BrandXIcon.tsx +28 -0
  63. package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
  64. package/template/app/components/icons/CogsIcon.tsx +25 -0
  65. package/template/app/components/icons/DiscordIcon.tsx +24 -0
  66. package/template/app/components/icons/GithubIcon.tsx +24 -0
  67. package/template/app/components/icons/GoogleIcon.tsx +24 -0
  68. package/template/app/components/icons/InstagramIcon.tsx +24 -0
  69. package/template/app/components/icons/NpmIcon.tsx +26 -0
  70. package/template/app/components/icons/YinYangIcon.tsx +26 -0
  71. package/template/app/components/icons/YouTubeIcon.tsx +24 -0
  72. package/template/app/components/markdown/CodeBlock.tsx +254 -0
  73. package/template/app/components/markdown/FileTabs.tsx +58 -0
  74. package/template/app/components/markdown/FrameworkContent.tsx +76 -0
  75. package/template/app/components/markdown/Markdown.tsx +216 -0
  76. package/template/app/components/markdown/MarkdownContent.tsx +89 -0
  77. package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
  78. package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
  79. package/template/app/components/markdown/MarkdownLink.tsx +46 -0
  80. package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
  81. package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
  82. package/template/app/components/markdown/Tabs.tsx +139 -0
  83. package/template/app/components/markdown/index.ts +15 -0
  84. package/template/app/components/ui/Button.tsx +141 -0
  85. package/template/app/components/ui/InlineCode.tsx +16 -0
  86. package/template/app/components/ui/MarkdownImg.tsx +21 -0
  87. package/template/app/config/frameworks.ts +93 -0
  88. package/template/app/contexts/SearchContext.tsx +36 -0
  89. package/template/app/db/index.ts +17 -0
  90. package/template/app/db/schema.ts +74 -0
  91. package/template/app/hooks/useClickOutside.ts +106 -0
  92. package/template/app/routeTree.gen.ts +584 -0
  93. package/template/app/router.tsx +29 -0
  94. package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
  95. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
  96. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
  97. package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
  98. package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
  99. package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
  100. package/template/app/routes/$lang.$project.$version.tsx +69 -0
  101. package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
  102. package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
  103. package/template/app/routes/$lang.$project.docs.tsx +79 -0
  104. package/template/app/routes/$lang.$project.tsx +89 -0
  105. package/template/app/routes/$lang.blog.$.tsx +82 -0
  106. package/template/app/routes/$lang.blog.index.tsx +56 -0
  107. package/template/app/routes/$lang.blog.tsx +26 -0
  108. package/template/app/routes/$lang.docs.$.tsx +100 -0
  109. package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
  110. package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
  111. package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
  112. package/template/app/routes/$lang.docs.index.tsx +20 -0
  113. package/template/app/routes/$lang.docs.tsx +90 -0
  114. package/template/app/routes/$lang.tsx +16 -0
  115. package/template/app/routes/__root.tsx +180 -0
  116. package/template/app/routes/index.tsx +89 -0
  117. package/template/app/site.config.ts +182 -0
  118. package/template/app/styles/app.css +1029 -0
  119. package/template/app/types/index.ts +77 -0
  120. package/template/app/utils/blog.server.ts +193 -0
  121. package/template/app/utils/blog.ts +42 -0
  122. package/template/app/utils/config.ts +120 -0
  123. package/template/app/utils/content-loader.ts +400 -0
  124. package/template/app/utils/dates.ts +29 -0
  125. package/template/app/utils/docs.server.ts +150 -0
  126. package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
  127. package/template/app/utils/markdown/index.ts +2 -0
  128. package/template/app/utils/markdown/installCommand.ts +143 -0
  129. package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
  130. package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
  131. package/template/app/utils/markdown/plugins/helpers.ts +33 -0
  132. package/template/app/utils/markdown/plugins/index.ts +8 -0
  133. package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
  134. package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
  135. package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
  136. package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
  137. package/template/app/utils/markdown/processor.ts +75 -0
  138. package/template/app/utils/site-config.tsx +11 -0
  139. package/template/app/utils/upload.ts +232 -0
  140. package/template/app/utils/useLocalStorage.ts +65 -0
  141. package/template/app/utils/utils.ts +23 -0
  142. package/template/package.json +54 -0
  143. package/template/public/favicon.svg +1 -0
  144. package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
  145. package/template/public/fonts/Inter-latin.woff2 +0 -0
  146. package/template/public/images/frameworks/angular-logo.svg +1 -0
  147. package/template/public/images/frameworks/js-logo.svg +1 -0
  148. package/template/public/images/frameworks/lit-logo.svg +1 -0
  149. package/template/public/images/frameworks/preact-logo.svg +6 -0
  150. package/template/public/images/frameworks/qwik-logo.svg +1 -0
  151. package/template/public/images/frameworks/react-logo.svg +1 -0
  152. package/template/public/images/frameworks/solid-logo.svg +1 -0
  153. package/template/public/images/frameworks/svelte-logo.svg +1 -0
  154. package/template/public/images/frameworks/vue-logo.svg +4 -0
  155. package/template/tsconfig.json +24 -0
  156. package/template/vite.config.ts +43 -0
  157. package/template/wrangler.jsonc +16 -0
  158. package/README.md +0 -161
  159. package/dist/server-73AVSOL5.js +0 -598
  160. package/src/admin/index.html +0 -13
  161. package/src/admin/server/index.ts +0 -138
  162. package/src/admin/server/routes/jobs.ts +0 -113
  163. package/src/admin/server/routes/status.ts +0 -57
  164. package/src/admin/ui/App.tsx +0 -332
  165. package/src/admin/ui/main.tsx +0 -19
  166. /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
  167. /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
  168. /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
  169. /package/{src/admin/ui → admin/app}/lib/flags.ts +0 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Filters framework-specific content blocks from raw markdown.
3
+ *
4
+ * Handles two types of blocks:
5
+ *
6
+ * 1. Framework blocks:
7
+ * <!-- ::start:framework -->
8
+ * # React
9
+ * React-specific content...
10
+ * # Vue
11
+ * Vue-specific content...
12
+ * <!-- ::end:framework -->
13
+ *
14
+ * 2. Package manager tabs (variant="package-manager"):
15
+ * <!-- ::start:tabs variant="package-manager" mode="local-install" -->
16
+ * react: create-tsrouter-app@latest my-app
17
+ * solid: create-tsrouter-app@latest my-app --framework solid
18
+ * <!-- ::end:tabs -->
19
+ */
20
+
21
+ import {
22
+ getInstallCommand,
23
+ type PackageManager,
24
+ type InstallMode,
25
+ } from './installCommand'
26
+
27
+ type FilterOptions = {
28
+ framework?: string
29
+ packageManager?: PackageManager
30
+ /** Whether to keep the surrounding comment markers when filtering. Defaults to false. */
31
+ keepMarkers?: boolean
32
+ }
33
+
34
+ /**
35
+ * Filters framework-specific content and package-manager tabs from raw markdown.
36
+ * If no framework is specified, returns markdown unchanged.
37
+ */
38
+ export function filterFrameworkContent(
39
+ markdown: string,
40
+ options: FilterOptions,
41
+ ): string {
42
+ const { framework, packageManager, keepMarkers = false } = options
43
+
44
+ if (!framework) {
45
+ return markdown
46
+ }
47
+
48
+ const normalizedFramework = framework.toLowerCase()
49
+
50
+ // First pass: filter framework blocks
51
+ let result = filterFrameworkBlocks(markdown, normalizedFramework, keepMarkers)
52
+
53
+ // Second pass: filter package-manager tabs
54
+ result = filterPackageManagerTabs(
55
+ result,
56
+ normalizedFramework,
57
+ packageManager,
58
+ keepMarkers,
59
+ )
60
+
61
+ return result
62
+ }
63
+
64
+ /**
65
+ * Filters <!-- ::start:framework --> blocks.
66
+ */
67
+ function filterFrameworkBlocks(
68
+ markdown: string,
69
+ framework: string,
70
+ keepMarkers: boolean,
71
+ ): string {
72
+ const blockRegex =
73
+ /(<!--\s*::start:framework\s*-->)([\s\S]*?)(<!--\s*::end:framework\s*-->)/gi
74
+
75
+ return markdown.replace(
76
+ blockRegex,
77
+ (
78
+ _match,
79
+ startComment: string,
80
+ blockContent: string,
81
+ endComment: string,
82
+ ) => {
83
+ const sections = splitByFrameworkHeadings(blockContent)
84
+ const frameworkSection = sections.find((s) => s.framework === framework)
85
+
86
+ if (!frameworkSection) {
87
+ return ''
88
+ }
89
+
90
+ const content = frameworkSection.content.trim()
91
+ if (keepMarkers) {
92
+ return `${startComment}\n${content}\n${endComment}`
93
+ }
94
+ return content
95
+ },
96
+ )
97
+ }
98
+
99
+ /**
100
+ * Filters <!-- ::start:tabs variant="package-manager" --> blocks.
101
+ * If framework matches and packageManager is provided, outputs the command.
102
+ * Otherwise returns block as-is.
103
+ */
104
+ function filterPackageManagerTabs(
105
+ markdown: string,
106
+ framework: string,
107
+ packageManager: PackageManager | undefined,
108
+ keepMarkers: boolean,
109
+ ): string {
110
+ // Match tabs blocks with attributes
111
+ const blockRegex =
112
+ /(<!--\s*::start:tabs\s+([^>]*?)-->)([\s\S]*?)(<!--\s*::end:tabs\s*-->)/gi
113
+
114
+ return markdown.replace(
115
+ blockRegex,
116
+ (
117
+ fullMatch,
118
+ startComment: string,
119
+ attrs: string,
120
+ blockContent: string,
121
+ endComment: string,
122
+ ) => {
123
+ // Parse attributes to check variant
124
+ const variant = parseAttribute(attrs, 'variant')
125
+ if (variant !== 'package-manager' && variant !== 'package-managers') {
126
+ return fullMatch
127
+ }
128
+
129
+ // Parse framework lines
130
+ const frameworkPackages = parseFrameworkLines(blockContent)
131
+ const packages = frameworkPackages[framework]
132
+
133
+ // If no match for framework, return as-is
134
+ if (!packages || packages.length === 0) {
135
+ return fullMatch
136
+ }
137
+
138
+ // If no package manager specified, return as-is
139
+ if (!packageManager) {
140
+ return fullMatch
141
+ }
142
+
143
+ // Generate command based on mode
144
+ const mode = (parseAttribute(attrs, 'mode') as InstallMode) || 'install'
145
+ const commands = getInstallCommand(packageManager, packages, mode)
146
+ const commandText = `\`\`\`sh\n${commands.join('\n')}\n\`\`\``
147
+
148
+ // Return with or without comment markers based on keepMarkers option
149
+ if (keepMarkers) {
150
+ return `${startComment}\n${commandText}\n${endComment}`
151
+ }
152
+ return commandText
153
+ },
154
+ )
155
+ }
156
+
157
+ /**
158
+ * Parse a single attribute from an attribute string.
159
+ * e.g., parseAttribute('variant="package-manager" mode="install"', 'variant') => 'package-manager'
160
+ */
161
+ function parseAttribute(attrs: string, name: string): string | undefined {
162
+ const regex = new RegExp(`${name}=["']([^"']+)["']`, 'i')
163
+ const match = attrs.match(regex)
164
+ return match?.[1]
165
+ }
166
+
167
+ /**
168
+ * Parse framework lines like "react: @tanstack/react-query" from block content.
169
+ * Returns { framework: [[packages]] } structure.
170
+ */
171
+ function parseFrameworkLines(content: string): Record<string, string[][]> {
172
+ const result: Record<string, string[][]> = {}
173
+ const lines = content.split('\n')
174
+
175
+ for (const line of lines) {
176
+ const trimmed = line.trim()
177
+ if (!trimmed) continue
178
+
179
+ const colonIndex = trimmed.indexOf(':')
180
+ if (colonIndex === -1) continue
181
+
182
+ const framework = trimmed.slice(0, colonIndex).trim().toLowerCase()
183
+ const packagesStr = trimmed.slice(colonIndex + 1).trim()
184
+ const packages = packagesStr.split(/\s+/).filter(Boolean)
185
+
186
+ if (!framework || packages.length === 0) continue
187
+
188
+ if (result[framework]) {
189
+ result[framework].push(packages)
190
+ } else {
191
+ result[framework] = [packages]
192
+ }
193
+ }
194
+
195
+ return result
196
+ }
197
+
198
+ type FrameworkSection = {
199
+ framework: string
200
+ content: string
201
+ }
202
+
203
+ /**
204
+ * Splits block content by H1 framework headers.
205
+ */
206
+ function splitByFrameworkHeadings(blockContent: string): FrameworkSection[] {
207
+ const sections: FrameworkSection[] = []
208
+ const headerRegex = /^#\s+(\w+)\s*$/gm
209
+ let lastIndex = 0
210
+ let lastFramework: string | null = null
211
+ let match: RegExpExecArray | null
212
+
213
+ while ((match = headerRegex.exec(blockContent)) !== null) {
214
+ if (lastFramework !== null) {
215
+ sections.push({
216
+ framework: lastFramework,
217
+ content: blockContent.slice(lastIndex, match.index),
218
+ })
219
+ }
220
+
221
+ lastFramework = match[1].toLowerCase()
222
+ lastIndex = match.index + match[0].length
223
+ }
224
+
225
+ if (lastFramework !== null) {
226
+ sections.push({
227
+ framework: lastFramework,
228
+ content: blockContent.slice(lastIndex),
229
+ })
230
+ }
231
+
232
+ return sections
233
+ }
@@ -0,0 +1,2 @@
1
+ export { renderMarkdown } from './processor'
2
+ export type { MarkdownHeading, MarkdownRenderResult } from './processor'
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Shared types and utility for generating package manager install commands.
3
+ * Used by both server-side markdown filtering and client-side PackageManagerTabs.
4
+ */
5
+
6
+ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
7
+
8
+ export const PACKAGE_MANAGERS: PackageManager[] = ['npm', 'pnpm', 'yarn', 'bun']
9
+
10
+ export type InstallMode =
11
+ | 'install'
12
+ | 'dev-install'
13
+ | 'local-install'
14
+ | 'create'
15
+ | 'custom'
16
+
17
+ /**
18
+ * Get package manager from query parameter, defaulting to 'npm' if not specified or invalid.
19
+ */
20
+ export function getPackageManager(
21
+ pm: string | null | undefined,
22
+ ): PackageManager {
23
+ if (pm && PACKAGE_MANAGERS.includes(pm as PackageManager)) {
24
+ return pm as PackageManager
25
+ }
26
+ return 'npm' // default
27
+ }
28
+
29
+ /**
30
+ * Generate install command(s) for a package manager.
31
+ * Each group of packages becomes one command line.
32
+ */
33
+ export function getInstallCommand(
34
+ packageManager: PackageManager,
35
+ packageGroups: string[][],
36
+ mode: InstallMode,
37
+ ): string[] {
38
+ const commands: string[] = []
39
+
40
+ if (mode === 'custom') {
41
+ for (const packages of packageGroups) {
42
+ const pkgStr = packages.join(' ')
43
+ switch (packageManager) {
44
+ case 'npm':
45
+ commands.push(`npm ${pkgStr}`)
46
+ break
47
+ case 'pnpm':
48
+ commands.push(`pnpm ${pkgStr}`)
49
+ break
50
+ case 'yarn':
51
+ commands.push(`yarn ${pkgStr}`)
52
+ break
53
+ case 'bun':
54
+ commands.push(`bun ${pkgStr}`)
55
+ break
56
+ }
57
+ }
58
+ return commands
59
+ }
60
+
61
+ if (mode === 'create') {
62
+ for (const packages of packageGroups) {
63
+ const pkgStr = packages.join(' ')
64
+ switch (packageManager) {
65
+ case 'npm':
66
+ commands.push(`npm create ${pkgStr}`)
67
+ break
68
+ case 'pnpm':
69
+ commands.push(`pnpm create ${pkgStr}`)
70
+ break
71
+ case 'yarn':
72
+ commands.push(`yarn create ${pkgStr}`)
73
+ break
74
+ case 'bun':
75
+ commands.push(`bun create ${pkgStr}`)
76
+ break
77
+ }
78
+ }
79
+ return commands
80
+ }
81
+
82
+ if (mode === 'local-install') {
83
+ for (const packages of packageGroups) {
84
+ const pkgStr = packages.join(' ')
85
+ switch (packageManager) {
86
+ case 'npm':
87
+ commands.push(`npx ${pkgStr}`)
88
+ break
89
+ case 'pnpm':
90
+ commands.push(`pnpx ${pkgStr}`)
91
+ break
92
+ case 'yarn':
93
+ commands.push(`yarn dlx ${pkgStr}`)
94
+ break
95
+ case 'bun':
96
+ commands.push(`bunx ${pkgStr}`)
97
+ break
98
+ }
99
+ }
100
+ return commands
101
+ }
102
+
103
+ if (mode === 'dev-install') {
104
+ for (const packages of packageGroups) {
105
+ const pkgStr = packages.join(' ')
106
+ switch (packageManager) {
107
+ case 'npm':
108
+ commands.push(`npm i -D ${pkgStr}`)
109
+ break
110
+ case 'pnpm':
111
+ commands.push(`pnpm add -D ${pkgStr}`)
112
+ break
113
+ case 'yarn':
114
+ commands.push(`yarn add -D ${pkgStr}`)
115
+ break
116
+ case 'bun':
117
+ commands.push(`bun add -d ${pkgStr}`)
118
+ break
119
+ }
120
+ }
121
+ return commands
122
+ }
123
+
124
+ // install mode (default)
125
+ for (const packages of packageGroups) {
126
+ const pkgStr = packages.join(' ')
127
+ switch (packageManager) {
128
+ case 'npm':
129
+ commands.push(`npm i ${pkgStr}`)
130
+ break
131
+ case 'pnpm':
132
+ commands.push(`pnpm add ${pkgStr}`)
133
+ break
134
+ case 'yarn':
135
+ commands.push(`yarn add ${pkgStr}`)
136
+ break
137
+ case 'bun':
138
+ commands.push(`bun add ${pkgStr}`)
139
+ break
140
+ }
141
+ }
142
+ return commands
143
+ }
@@ -0,0 +1,104 @@
1
+ import { visit } from 'unist-util-visit'
2
+ import { toString } from 'hast-util-to-string'
3
+
4
+ import { isHeading } from './helpers'
5
+
6
+ export type MarkdownHeading = {
7
+ id: string
8
+ text: string
9
+ level: number
10
+ framework?: string
11
+ }
12
+
13
+ type HastElement = {
14
+ type: string
15
+ tagName: string
16
+ properties?: Record<string, unknown>
17
+ children?: unknown[]
18
+ }
19
+
20
+ type HastRoot = {
21
+ type: 'root'
22
+ children: unknown[]
23
+ }
24
+
25
+ type VFileData = {
26
+ data: Record<string, unknown>
27
+ }
28
+
29
+ const isTabsAncestor = (ancestor: HastElement) => {
30
+ if (ancestor.type !== 'element') {
31
+ return false
32
+ }
33
+
34
+ if (ancestor.tagName !== 'md-comment-component') {
35
+ return false
36
+ }
37
+
38
+ const component = ancestor.properties?.['data-component']
39
+ return typeof component === 'string' && component.toLowerCase() === 'tabs'
40
+ }
41
+
42
+ const isFrameworkPanelAncestor = (ancestor: HastElement) => {
43
+ if (ancestor.type !== 'element') {
44
+ return false
45
+ }
46
+
47
+ return ancestor.tagName === 'md-framework-panel'
48
+ }
49
+
50
+ export function rehypeCollectHeadings(initialHeadings?: MarkdownHeading[]) {
51
+ const headings = initialHeadings ?? []
52
+
53
+ return function collectHeadings(tree: HastRoot, file?: VFileData) {
54
+ visit(tree as any, 'element', (node: HastElement, _index, ancestors) => {
55
+ if (!isHeading(node)) {
56
+ return
57
+ }
58
+
59
+ if (Array.isArray(ancestors)) {
60
+ const insideTabs = ancestors.some((ancestor) =>
61
+ isTabsAncestor(ancestor as HastElement),
62
+ )
63
+ if (insideTabs) {
64
+ return
65
+ }
66
+ }
67
+
68
+ const id =
69
+ typeof node.properties?.id === 'string' ? node.properties.id : ''
70
+ if (!id) {
71
+ return
72
+ }
73
+
74
+ let currentFramework: string | undefined
75
+
76
+ const headingDataFramework = node.properties?.['data-framework']
77
+ if (typeof headingDataFramework === 'string') {
78
+ currentFramework = headingDataFramework
79
+ } else if (Array.isArray(ancestors)) {
80
+ const frameworkPanel = ancestors.find((ancestor) =>
81
+ isFrameworkPanelAncestor(ancestor as HastElement),
82
+ ) as HastElement | undefined
83
+
84
+ if (frameworkPanel) {
85
+ const dataFramework = frameworkPanel.properties?.['data-framework']
86
+ if (typeof dataFramework === 'string') {
87
+ currentFramework = dataFramework
88
+ }
89
+ }
90
+ }
91
+
92
+ headings.push({
93
+ id,
94
+ level: Number(node.tagName.substring(1)),
95
+ text: toString(node as any).trim(),
96
+ framework: currentFramework,
97
+ })
98
+ })
99
+
100
+ if (file) {
101
+ file.data.headings = headings
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,57 @@
1
+ import type { Root } from 'hast'
2
+ import { visit } from 'unist-util-visit'
3
+
4
+ export function extractCodeMeta() {
5
+ return (tree: Root) => {
6
+ visit(tree, 'element', (node: any) => {
7
+ if (node && node.tagName === 'pre') {
8
+ const codeChild = Array.isArray(node.children)
9
+ ? node.children[0]
10
+ : undefined
11
+
12
+ const metaString =
13
+ (codeChild &&
14
+ ((codeChild.data && codeChild.data.meta) ||
15
+ (codeChild.properties && codeChild.properties.metastring))) ||
16
+ undefined
17
+
18
+ let filename: string | undefined = undefined
19
+ let framework: string | undefined = undefined
20
+
21
+ if (metaString && typeof metaString === 'string') {
22
+ const marker = 'title="'
23
+ const idx = metaString.indexOf(marker)
24
+
25
+ if (idx !== -1) {
26
+ const rest = metaString.slice(idx + marker.length)
27
+ const end = rest.indexOf('"')
28
+
29
+ if (end !== -1) {
30
+ filename = rest.slice(0, end)
31
+ }
32
+ }
33
+
34
+ // Extract framework attribute
35
+ const frameworkMarker = 'framework="'
36
+ const frameworkIdx = metaString.indexOf(frameworkMarker)
37
+
38
+ if (frameworkIdx !== -1) {
39
+ const rest = metaString.slice(frameworkIdx + frameworkMarker.length)
40
+ const end = rest.indexOf('"')
41
+
42
+ if (end !== -1) {
43
+ framework = rest.slice(0, end).toLowerCase()
44
+ }
45
+ }
46
+ }
47
+
48
+ node.properties = {
49
+ ...(node.properties || {}),
50
+ 'data-filename': filename,
51
+ ...(filename ? { 'data-code-title': filename } : {}),
52
+ ...(framework ? { 'data-framework': framework } : {}),
53
+ }
54
+ }
55
+ })
56
+ }
57
+ }
@@ -0,0 +1,33 @@
1
+ import { isElement } from 'hast-util-is-element'
2
+
3
+ type HastElement = {
4
+ type: string
5
+ tagName: string
6
+ properties?: Record<string, unknown>
7
+ children?: unknown[]
8
+ }
9
+
10
+ export const normalizeComponentName = (name: string) => name.toLowerCase()
11
+
12
+ export const slugify = (value: string, fallback: string) => {
13
+ if (!value) {
14
+ return fallback
15
+ }
16
+
17
+ return (
18
+ value
19
+ .trim()
20
+ .toLowerCase()
21
+ .replace(/[^a-z0-9\s-]/g, '')
22
+ .replace(/\s+/g, '-')
23
+ .replace(/-+/g, '-')
24
+ .replace(/^-|-$/g, '')
25
+ .slice(0, 64) || fallback
26
+ )
27
+ }
28
+
29
+ export const isHeading = (node: unknown): node is HastElement =>
30
+ isElement(node as any) && /^h[1-6]$/.test((node as HastElement).tagName)
31
+
32
+ export const headingLevel = (node: HastElement) =>
33
+ Number(node.tagName.substring(1))
@@ -0,0 +1,8 @@
1
+ export { rehypeParseCommentComponents } from './parseCommentComponents'
2
+ export { rehypeTransformCommentComponents } from './transformCommentComponents'
3
+ export {
4
+ rehypeTransformFrameworkComponents,
5
+ transformFrameworkComponent,
6
+ } from './transformFrameworkComponent'
7
+ export { transformTabsComponent } from './transformTabsComponent'
8
+ export { type MarkdownHeading, rehypeCollectHeadings } from './collectHeadings'
@@ -0,0 +1,103 @@
1
+ import { unified } from 'unified'
2
+ import rehypeParse from 'rehype-parse'
3
+ import { SKIP, visit } from 'unist-util-visit'
4
+
5
+ const COMPONENT_PREFIX = '::'
6
+ const START_PREFIX = '::start:'
7
+ const END_PREFIX = '::end:'
8
+
9
+ const componentParser = unified().use(rehypeParse, { fragment: true })
10
+
11
+ const normalizeComponentName = (name: string) => name.toLowerCase()
12
+
13
+ function parseDescriptor(descriptor: string) {
14
+ const tree = componentParser.parse(`<${descriptor} />`)
15
+ const node = tree.children[0]
16
+ if (!node || node.type !== 'element') {
17
+ return null
18
+ }
19
+
20
+ const component = node.tagName
21
+ const attributes: Record<string, string> = {}
22
+ const properties = node.properties ?? {}
23
+ for (const [key, value] of Object.entries(properties)) {
24
+ if (Array.isArray(value)) {
25
+ attributes[key] = value.join(' ')
26
+ } else if (value != null) {
27
+ attributes[key] = String(value)
28
+ }
29
+ }
30
+
31
+ return { component, attributes }
32
+ }
33
+
34
+ const isCommentNode = (value: unknown) =>
35
+ Boolean(
36
+ value &&
37
+ typeof value === 'object' &&
38
+ 'type' in value &&
39
+ value.type === 'comment',
40
+ )
41
+
42
+ export const rehypeParseCommentComponents = () => {
43
+ return (tree: any) => {
44
+ visit(tree, 'comment', (node, index, parent) => {
45
+ if (!isCommentNode(node) || parent == null || typeof index !== 'number') {
46
+ return
47
+ }
48
+
49
+ const trimmed = node.value.trim()
50
+ if (!trimmed.startsWith(COMPONENT_PREFIX)) {
51
+ return
52
+ }
53
+
54
+ const isBlock = trimmed.startsWith(START_PREFIX)
55
+ const descriptor = isBlock
56
+ ? trimmed.slice(START_PREFIX.length)
57
+ : trimmed.slice(COMPONENT_PREFIX.length)
58
+
59
+ const parsed = parseDescriptor(descriptor)
60
+ if (!parsed) {
61
+ return
62
+ }
63
+
64
+ const componentName = parsed.component
65
+ const element = {
66
+ type: 'element',
67
+ tagName: 'md-comment-component',
68
+ properties: {
69
+ 'data-component': componentName,
70
+ 'data-attributes': JSON.stringify(parsed.attributes ?? {}),
71
+ },
72
+ children: [],
73
+ }
74
+
75
+ if (!isBlock) {
76
+ parent.children.splice(index, 1, element)
77
+ return [SKIP, index]
78
+ }
79
+
80
+ let endIndex = -1
81
+ for (let cursor = index + 1; cursor < parent.children.length; cursor++) {
82
+ const candidate = parent.children[cursor]
83
+ if (
84
+ isCommentNode(candidate) &&
85
+ candidate.value.trim().toLowerCase() ===
86
+ `${END_PREFIX}${normalizeComponentName(componentName)}`
87
+ ) {
88
+ endIndex = cursor
89
+ break
90
+ }
91
+ }
92
+
93
+ if (endIndex === -1) {
94
+ parent.children.splice(index, 1, element)
95
+ return [SKIP, index]
96
+ }
97
+
98
+ element.children = parent.children.slice(index + 1, endIndex)
99
+ parent.children.splice(index, endIndex - index + 1, element)
100
+ return [SKIP, index]
101
+ })
102
+ }
103
+ }
@@ -0,0 +1,23 @@
1
+ import { visit } from 'unist-util-visit'
2
+
3
+ import { normalizeComponentName } from './helpers'
4
+ import { transformTabsComponent } from './transformTabsComponent'
5
+
6
+ export const rehypeTransformCommentComponents = () => {
7
+ return (tree: any) => {
8
+ visit(tree, 'element', (node) => {
9
+ if (node.tagName !== 'md-comment-component') {
10
+ return
11
+ }
12
+
13
+ const component = String(node.properties?.['data-component'] ?? '')
14
+ switch (normalizeComponentName(component)) {
15
+ case 'tabs':
16
+ transformTabsComponent(node)
17
+ break
18
+ default:
19
+ break
20
+ }
21
+ })
22
+ }
23
+ }