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.
- package/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
- package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
- package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
- package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
- package/admin/app/routeTree.gen.ts +68 -0
- package/admin/app/router.tsx +23 -0
- package/admin/app/routes/__root.tsx +55 -0
- package/admin/app/routes/index.tsx +416 -0
- package/{src/admin/ui → admin/app}/styles.css +36 -3
- package/admin/package.json +27 -0
- package/admin/server/functions/jobs.ts +53 -0
- package/admin/server/functions/misc.ts +84 -0
- package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
- package/admin/server/functions/status.ts +61 -0
- package/admin/server/index.ts +35 -0
- package/admin/server/init.ts +46 -0
- package/{src/admin → admin}/server/services/job-manager.ts +39 -10
- package/{src/admin → admin}/server/services/status.ts +6 -6
- package/admin/tsconfig.json +19 -0
- package/{src/admin → admin}/vite.config.ts +8 -2
- package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
- package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
- package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
- package/dist/chunk-L64GJ4OB.js +32 -0
- package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
- package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
- package/dist/chunk-TRURQFP4.js +31 -0
- package/dist/cli.js +108 -7
- package/dist/index.d.ts +41 -1
- package/dist/index.js +92 -3
- package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
- package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
- package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
- package/dist/upload-XL6KG6S2.js +132 -0
- package/package.json +17 -15
- package/template/app/components/BlogArticle.tsx +159 -0
- package/template/app/components/BlogList.tsx +88 -0
- package/template/app/components/Breadcrumbs.tsx +81 -0
- package/template/app/components/Card.tsx +31 -0
- package/template/app/components/Doc.tsx +191 -0
- package/template/app/components/DocBreadcrumb.tsx +60 -0
- package/template/app/components/DocContainer.tsx +13 -0
- package/template/app/components/DocTitle.tsx +11 -0
- package/template/app/components/DocsLayout.tsx +715 -0
- package/template/app/components/Dropdown.tsx +116 -0
- package/template/app/components/FallbackBanner.tsx +36 -0
- package/template/app/components/Footer.tsx +29 -0
- package/template/app/components/FrameworkSelect.tsx +150 -0
- package/template/app/components/LibraryCard.tsx +178 -0
- package/template/app/components/LocaleSwitcher.tsx +43 -0
- package/template/app/components/Navbar.tsx +430 -0
- package/template/app/components/PostNotFound.tsx +20 -0
- package/template/app/components/SearchButton.tsx +32 -0
- package/template/app/components/Select.tsx +103 -0
- package/template/app/components/Spinner.tsx +18 -0
- package/template/app/components/ThemeProvider.tsx +141 -0
- package/template/app/components/ThemeToggle.tsx +31 -0
- package/template/app/components/Toc.tsx +86 -0
- package/template/app/components/VersionSelect.tsx +118 -0
- package/template/app/components/icons/BSkyIcon.tsx +27 -0
- package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
- package/template/app/components/icons/BrandXIcon.tsx +28 -0
- package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
- package/template/app/components/icons/CogsIcon.tsx +25 -0
- package/template/app/components/icons/DiscordIcon.tsx +24 -0
- package/template/app/components/icons/GithubIcon.tsx +24 -0
- package/template/app/components/icons/GoogleIcon.tsx +24 -0
- package/template/app/components/icons/InstagramIcon.tsx +24 -0
- package/template/app/components/icons/NpmIcon.tsx +26 -0
- package/template/app/components/icons/YinYangIcon.tsx +26 -0
- package/template/app/components/icons/YouTubeIcon.tsx +24 -0
- package/template/app/components/markdown/CodeBlock.tsx +254 -0
- package/template/app/components/markdown/FileTabs.tsx +58 -0
- package/template/app/components/markdown/FrameworkContent.tsx +76 -0
- package/template/app/components/markdown/Markdown.tsx +216 -0
- package/template/app/components/markdown/MarkdownContent.tsx +89 -0
- package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
- package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
- package/template/app/components/markdown/MarkdownLink.tsx +46 -0
- package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
- package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
- package/template/app/components/markdown/Tabs.tsx +139 -0
- package/template/app/components/markdown/index.ts +15 -0
- package/template/app/components/ui/Button.tsx +141 -0
- package/template/app/components/ui/InlineCode.tsx +16 -0
- package/template/app/components/ui/MarkdownImg.tsx +21 -0
- package/template/app/config/frameworks.ts +93 -0
- package/template/app/contexts/SearchContext.tsx +36 -0
- package/template/app/db/index.ts +17 -0
- package/template/app/db/schema.ts +74 -0
- package/template/app/hooks/useClickOutside.ts +106 -0
- package/template/app/routeTree.gen.ts +584 -0
- package/template/app/router.tsx +29 -0
- package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
- package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
- package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
- package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
- package/template/app/routes/$lang.$project.$version.tsx +69 -0
- package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
- package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
- package/template/app/routes/$lang.$project.docs.tsx +79 -0
- package/template/app/routes/$lang.$project.tsx +89 -0
- package/template/app/routes/$lang.blog.$.tsx +82 -0
- package/template/app/routes/$lang.blog.index.tsx +56 -0
- package/template/app/routes/$lang.blog.tsx +26 -0
- package/template/app/routes/$lang.docs.$.tsx +100 -0
- package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
- package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
- package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
- package/template/app/routes/$lang.docs.index.tsx +20 -0
- package/template/app/routes/$lang.docs.tsx +90 -0
- package/template/app/routes/$lang.tsx +16 -0
- package/template/app/routes/__root.tsx +180 -0
- package/template/app/routes/index.tsx +89 -0
- package/template/app/site.config.ts +182 -0
- package/template/app/styles/app.css +1029 -0
- package/template/app/types/index.ts +77 -0
- package/template/app/utils/blog.server.ts +193 -0
- package/template/app/utils/blog.ts +42 -0
- package/template/app/utils/config.ts +120 -0
- package/template/app/utils/content-loader.ts +400 -0
- package/template/app/utils/dates.ts +29 -0
- package/template/app/utils/docs.server.ts +150 -0
- package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
- package/template/app/utils/markdown/index.ts +2 -0
- package/template/app/utils/markdown/installCommand.ts +143 -0
- package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
- package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
- package/template/app/utils/markdown/plugins/helpers.ts +33 -0
- package/template/app/utils/markdown/plugins/index.ts +8 -0
- package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
- package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
- package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
- package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
- package/template/app/utils/markdown/processor.ts +75 -0
- package/template/app/utils/site-config.tsx +11 -0
- package/template/app/utils/upload.ts +232 -0
- package/template/app/utils/useLocalStorage.ts +65 -0
- package/template/app/utils/utils.ts +23 -0
- package/template/package.json +54 -0
- package/template/public/favicon.svg +1 -0
- package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
- package/template/public/fonts/Inter-latin.woff2 +0 -0
- package/template/public/images/frameworks/angular-logo.svg +1 -0
- package/template/public/images/frameworks/js-logo.svg +1 -0
- package/template/public/images/frameworks/lit-logo.svg +1 -0
- package/template/public/images/frameworks/preact-logo.svg +6 -0
- package/template/public/images/frameworks/qwik-logo.svg +1 -0
- package/template/public/images/frameworks/react-logo.svg +1 -0
- package/template/public/images/frameworks/solid-logo.svg +1 -0
- package/template/public/images/frameworks/svelte-logo.svg +1 -0
- package/template/public/images/frameworks/vue-logo.svg +4 -0
- package/template/tsconfig.json +24 -0
- package/template/vite.config.ts +43 -0
- package/template/wrangler.jsonc +16 -0
- package/README.md +0 -161
- package/dist/server-73AVSOL5.js +0 -598
- package/src/admin/index.html +0 -13
- package/src/admin/server/index.ts +0 -138
- package/src/admin/server/routes/jobs.ts +0 -113
- package/src/admin/server/routes/status.ts +0 -57
- package/src/admin/ui/App.tsx +0 -332
- package/src/admin/ui/main.tsx +0 -19
- /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
- /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
- /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
- /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,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
|
+
}
|