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,89 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { SquarePen } from 'lucide-react'
|
|
3
|
+
import { twMerge } from 'tailwind-merge'
|
|
4
|
+
import { Markdown } from './Markdown'
|
|
5
|
+
import { Button } from '../ui/Button'
|
|
6
|
+
|
|
7
|
+
type MarkdownContentProps = {
|
|
8
|
+
title: string
|
|
9
|
+
repo: string
|
|
10
|
+
branch: string
|
|
11
|
+
filePath: string
|
|
12
|
+
/** Pre-rendered HTML markup (from renderMarkdown). If not provided, rawContent will be rendered. */
|
|
13
|
+
htmlMarkup?: string
|
|
14
|
+
/** Raw markdown content to render. Used if htmlMarkup is not provided. */
|
|
15
|
+
rawContent?: string
|
|
16
|
+
/** Optional render function for raw markdown to HTML */
|
|
17
|
+
renderMarkdown?: (raw: string) => { markup: string; headings: any[] }
|
|
18
|
+
/** Additional elements to render in the title bar (e.g., width toggle button) */
|
|
19
|
+
titleBarActions?: React.ReactNode
|
|
20
|
+
/** Additional class names for the prose container */
|
|
21
|
+
proseClassName?: string
|
|
22
|
+
/** Ref for the markdown container (used for heading intersection observer in docs) */
|
|
23
|
+
containerRef?: React.RefObject<HTMLDivElement | null>
|
|
24
|
+
/** Current framework for filtering markdown content */
|
|
25
|
+
currentFramework?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function MarkdownContent({
|
|
29
|
+
title,
|
|
30
|
+
repo,
|
|
31
|
+
branch,
|
|
32
|
+
filePath,
|
|
33
|
+
htmlMarkup,
|
|
34
|
+
rawContent,
|
|
35
|
+
renderMarkdown,
|
|
36
|
+
titleBarActions,
|
|
37
|
+
proseClassName,
|
|
38
|
+
containerRef,
|
|
39
|
+
}: MarkdownContentProps) {
|
|
40
|
+
const markdownElement = htmlMarkup ? (
|
|
41
|
+
<Markdown htmlMarkup={htmlMarkup} />
|
|
42
|
+
) : rawContent ? (
|
|
43
|
+
<Markdown rawContent={rawContent} renderMarkdown={renderMarkdown} />
|
|
44
|
+
) : null
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
{title ? (
|
|
49
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
50
|
+
<h1
|
|
51
|
+
className={`flex gap-4 items-center flex-wrap text-xl md:text-2xl font-black`}
|
|
52
|
+
>
|
|
53
|
+
{title}
|
|
54
|
+
</h1>
|
|
55
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
56
|
+
{titleBarActions}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
<div className="h-4" />
|
|
61
|
+
<div className="h-px bg-gray-500 opacity-20" />
|
|
62
|
+
<div className="h-4" />
|
|
63
|
+
<div
|
|
64
|
+
ref={containerRef}
|
|
65
|
+
className={twMerge(
|
|
66
|
+
'prose prose-gray dark:prose-invert max-w-none',
|
|
67
|
+
'[font-size:16px]',
|
|
68
|
+
'styled-markdown-content',
|
|
69
|
+
proseClassName,
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
{markdownElement}
|
|
73
|
+
</div>
|
|
74
|
+
<div className="h-12" />
|
|
75
|
+
<div className="w-full h-px bg-gray-500 opacity-30" />
|
|
76
|
+
<div className="flex py-4">
|
|
77
|
+
<Button
|
|
78
|
+
variant="ghost"
|
|
79
|
+
size="xs"
|
|
80
|
+
as="a"
|
|
81
|
+
href={`https://github.com/${repo}/edit/${branch}/${filePath}`}
|
|
82
|
+
>
|
|
83
|
+
<SquarePen className="w-3.5 h-3.5" />
|
|
84
|
+
Edit on GitHub
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
</>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { domToReact, Element } from 'html-react-parser'
|
|
3
|
+
import type { HTMLReactParserOptions } from 'html-react-parser'
|
|
4
|
+
|
|
5
|
+
// Helper to resolve different module shapes (named export vs default)
|
|
6
|
+
function resolveModuleDefault(mod: any, key: string): React.ComponentType<any> {
|
|
7
|
+
if (!mod) return undefined as any
|
|
8
|
+
if (mod[key] && typeof mod[key] === 'function') return mod[key]
|
|
9
|
+
if (mod.default) {
|
|
10
|
+
if (mod.default[key] && typeof mod.default[key] === 'function')
|
|
11
|
+
return mod.default[key]
|
|
12
|
+
if (typeof mod.default === 'function') return mod.default
|
|
13
|
+
}
|
|
14
|
+
if (typeof mod === 'function') return mod
|
|
15
|
+
return (mod as any).default ?? (mod as any)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FrameworkContent = React.lazy<React.ComponentType<any>>(() =>
|
|
19
|
+
import('./FrameworkContent').then((mod) => ({
|
|
20
|
+
default: resolveModuleDefault(mod, 'FrameworkContent'),
|
|
21
|
+
})),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
export function handleFrameworkComponent(
|
|
25
|
+
domNode: Element,
|
|
26
|
+
attributes: Record<string, any>,
|
|
27
|
+
options: HTMLReactParserOptions,
|
|
28
|
+
) {
|
|
29
|
+
const frameworkMeta = domNode.attribs['data-framework-meta']
|
|
30
|
+
if (!frameworkMeta) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const { codeBlocksByFramework } = JSON.parse(frameworkMeta)
|
|
36
|
+
const availableFrameworks = JSON.parse(
|
|
37
|
+
domNode.attribs['data-available-frameworks'] || '[]',
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const panelElements = domNode.children?.filter(
|
|
41
|
+
(child): child is Element =>
|
|
42
|
+
child instanceof Element && child.name === 'md-framework-panel',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// Build panelsByFramework map
|
|
46
|
+
const panelsByFramework: Record<string, React.ReactNode> = {}
|
|
47
|
+
panelElements?.forEach((panel) => {
|
|
48
|
+
const fw = panel.attribs['data-framework']
|
|
49
|
+
if (fw) {
|
|
50
|
+
panelsByFramework[fw] = domToReact(panel.children as any, options)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<React.Suspense fallback={<div>Loading...</div>}>
|
|
56
|
+
<FrameworkContent
|
|
57
|
+
codeBlocksByFramework={codeBlocksByFramework}
|
|
58
|
+
availableFrameworks={availableFrameworks}
|
|
59
|
+
panelsByFramework={panelsByFramework}
|
|
60
|
+
/>
|
|
61
|
+
</React.Suspense>
|
|
62
|
+
)
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
export type MarkdownHeading = {
|
|
4
|
+
depth: number
|
|
5
|
+
slug: string
|
|
6
|
+
text: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const MarkdownHeadingContext = React.createContext<{
|
|
10
|
+
headings: MarkdownHeading[]
|
|
11
|
+
setHeadings(headings: MarkdownHeading[]): void
|
|
12
|
+
}>({
|
|
13
|
+
headings: [],
|
|
14
|
+
setHeadings: () => undefined,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export function MarkdownHeadingProvider({
|
|
18
|
+
children,
|
|
19
|
+
}: {
|
|
20
|
+
children: React.ReactNode
|
|
21
|
+
}) {
|
|
22
|
+
const [headings, setHeadings] = React.useState<MarkdownHeading[]>([])
|
|
23
|
+
|
|
24
|
+
const value = React.useMemo(() => ({ headings, setHeadings }), [headings])
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<MarkdownHeadingContext.Provider value={value}>
|
|
28
|
+
{children}
|
|
29
|
+
</MarkdownHeadingContext.Provider>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useMarkdownHeadings() {
|
|
34
|
+
return React.useContext(MarkdownHeadingContext)
|
|
35
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
import type { HTMLProps } from 'react'
|
|
3
|
+
|
|
4
|
+
function isRelativeLink(link: string) {
|
|
5
|
+
return (
|
|
6
|
+
!link.startsWith(`/`) &&
|
|
7
|
+
!link.startsWith('http://') &&
|
|
8
|
+
!link.startsWith('https://') &&
|
|
9
|
+
!link.startsWith('//') &&
|
|
10
|
+
!link.startsWith('#') &&
|
|
11
|
+
!link.startsWith('mailto:')
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function MarkdownLink({
|
|
16
|
+
href: hrefProp,
|
|
17
|
+
...rest
|
|
18
|
+
}: HTMLProps<HTMLAnchorElement>) {
|
|
19
|
+
if (!isRelativeLink(hrefProp ?? '')) {
|
|
20
|
+
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
|
21
|
+
return <a {...rest} href={hrefProp} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const [hrefWithoutHash, hash] = hrefProp?.split('#') ?? []
|
|
25
|
+
let hrefWithoutMd = hrefWithoutHash.replace('.md', '')
|
|
26
|
+
|
|
27
|
+
// Force relative links to resolve one level higher
|
|
28
|
+
if (hrefWithoutMd.startsWith('../')) {
|
|
29
|
+
hrefWithoutMd = hrefWithoutMd.replace(/^\.\.\//gm, '../../')
|
|
30
|
+
} else if (hrefWithoutMd.startsWith('./')) {
|
|
31
|
+
hrefWithoutMd = hrefWithoutMd.replace(/^\.\//gm, '../')
|
|
32
|
+
} else {
|
|
33
|
+
hrefWithoutMd = `../${hrefWithoutMd}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Link
|
|
38
|
+
{...rest}
|
|
39
|
+
unsafeRelative="path"
|
|
40
|
+
to={hrefWithoutMd}
|
|
41
|
+
hash={hash}
|
|
42
|
+
preload={undefined}
|
|
43
|
+
ref={undefined}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { domToReact, Element } from 'html-react-parser'
|
|
3
|
+
import type { HTMLReactParserOptions } from 'html-react-parser'
|
|
4
|
+
|
|
5
|
+
// Helper to resolve different module shapes (named export vs default)
|
|
6
|
+
function resolveModuleDefault(mod: any, key: string): React.ComponentType<any> {
|
|
7
|
+
if (!mod) return undefined as any
|
|
8
|
+
if (mod[key] && typeof mod[key] === 'function') return mod[key]
|
|
9
|
+
if (mod.default) {
|
|
10
|
+
if (mod.default[key] && typeof mod.default[key] === 'function')
|
|
11
|
+
return mod.default[key]
|
|
12
|
+
if (typeof mod.default === 'function') return mod.default
|
|
13
|
+
}
|
|
14
|
+
if (typeof mod === 'function') return mod
|
|
15
|
+
return (mod as any).default ?? (mod as any)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Tabs = React.lazy<React.ComponentType<any>>(() =>
|
|
19
|
+
import('./Tabs').then((mod) => ({
|
|
20
|
+
default: resolveModuleDefault(mod, 'Tabs'),
|
|
21
|
+
})),
|
|
22
|
+
)
|
|
23
|
+
const PackageManagerTabs = React.lazy<React.ComponentType<any>>(() =>
|
|
24
|
+
import('./PackageManagerTabs').then((mod) => ({
|
|
25
|
+
default: resolveModuleDefault(mod, 'PackageManagerTabs'),
|
|
26
|
+
})),
|
|
27
|
+
)
|
|
28
|
+
const FileTabs = React.lazy<React.ComponentType<any>>(() =>
|
|
29
|
+
import('./FileTabs').then((mod) => ({
|
|
30
|
+
default: resolveModuleDefault(mod, 'FileTabs'),
|
|
31
|
+
})),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
export function handleTabsComponent(
|
|
35
|
+
domNode: Element,
|
|
36
|
+
attributes: Record<string, any>,
|
|
37
|
+
options: HTMLReactParserOptions,
|
|
38
|
+
) {
|
|
39
|
+
const pmMeta = domNode.attribs['data-package-manager-meta']
|
|
40
|
+
|
|
41
|
+
// Handle package-manager variant
|
|
42
|
+
if (pmMeta) {
|
|
43
|
+
try {
|
|
44
|
+
const { packagesByFramework, mode } = JSON.parse(pmMeta)
|
|
45
|
+
const frameworks = Object.keys(packagesByFramework)
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<React.Suspense fallback={<div>Loading...</div>}>
|
|
49
|
+
<PackageManagerTabs
|
|
50
|
+
packagesByFramework={packagesByFramework}
|
|
51
|
+
mode={mode}
|
|
52
|
+
frameworks={frameworks}
|
|
53
|
+
/>
|
|
54
|
+
</React.Suspense>
|
|
55
|
+
)
|
|
56
|
+
} catch {
|
|
57
|
+
// Fall through to default tabs if parsing fails
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if this is files variant
|
|
62
|
+
const filesMeta = domNode.attribs['data-files-meta']
|
|
63
|
+
if (filesMeta) {
|
|
64
|
+
try {
|
|
65
|
+
const tabs = attributes.tabs || []
|
|
66
|
+
|
|
67
|
+
const panelElements = domNode.children?.filter(
|
|
68
|
+
(child): child is Element =>
|
|
69
|
+
child instanceof Element && child.name === 'md-tab-panel',
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const children = panelElements?.map((panel) =>
|
|
73
|
+
domToReact(panel.children as any, options),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<React.Suspense fallback={<div>Loading...</div>}>
|
|
78
|
+
<FileTabs tabs={tabs} children={children as any} />
|
|
79
|
+
</React.Suspense>
|
|
80
|
+
)
|
|
81
|
+
} catch {
|
|
82
|
+
// Fall through to default tabs if parsing fails
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle default tabs variant
|
|
87
|
+
const tabs = attributes.tabs
|
|
88
|
+
|
|
89
|
+
if (!tabs || !Array.isArray(tabs)) {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const panelElements = domNode.children?.filter(
|
|
94
|
+
(child): child is Element =>
|
|
95
|
+
child instanceof Element && child.name === 'md-tab-panel',
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
const children = panelElements?.map((panel) => {
|
|
99
|
+
const result = domToReact(panel.children as any, options)
|
|
100
|
+
// Wrap in fragment to ensure it's a single React node
|
|
101
|
+
return <>{result}</>
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<React.Suspense fallback={<div>Loading...</div>}>
|
|
106
|
+
<Tabs tabs={tabs} children={children as any} />
|
|
107
|
+
</React.Suspense>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useParams } from '@tanstack/react-router'
|
|
2
|
+
import { create } from 'zustand'
|
|
3
|
+
import { Tabs, type TabDefinition } from './Tabs'
|
|
4
|
+
import { CodeBlock } from './CodeBlock'
|
|
5
|
+
import {
|
|
6
|
+
getInstallCommand,
|
|
7
|
+
PACKAGE_MANAGERS,
|
|
8
|
+
type PackageManager,
|
|
9
|
+
type InstallMode,
|
|
10
|
+
} from '../../utils/markdown/installCommand'
|
|
11
|
+
|
|
12
|
+
// Use zustand for cross-component synchronization
|
|
13
|
+
// This ensures all PackageManagerTabs instances on the page stay in sync
|
|
14
|
+
const usePackageManagerStore = create<{
|
|
15
|
+
packageManager: PackageManager
|
|
16
|
+
setPackageManager: (pm: PackageManager) => void
|
|
17
|
+
}>((set) => ({
|
|
18
|
+
packageManager:
|
|
19
|
+
typeof document !== 'undefined'
|
|
20
|
+
? (localStorage.getItem('packageManager') as PackageManager) || 'npm'
|
|
21
|
+
: 'npm',
|
|
22
|
+
setPackageManager: (pm: PackageManager) => {
|
|
23
|
+
localStorage.setItem('packageManager', pm)
|
|
24
|
+
set({ packageManager: pm })
|
|
25
|
+
},
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
// Simple localStorage-based framework preference (replaces useCurrentUserQuery auth dependency)
|
|
29
|
+
function useLocalFrameworkPreference(): string | undefined {
|
|
30
|
+
if (typeof document === 'undefined') return undefined
|
|
31
|
+
return localStorage.getItem('framework') || undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type PackageManagerTabsProps = {
|
|
35
|
+
packagesByFramework: Record<string, string[][]>
|
|
36
|
+
mode: InstallMode
|
|
37
|
+
frameworks: string[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function PackageManagerTabs({
|
|
41
|
+
packagesByFramework,
|
|
42
|
+
mode,
|
|
43
|
+
}: PackageManagerTabsProps) {
|
|
44
|
+
const { packageManager: storedPackageManager, setPackageManager } =
|
|
45
|
+
usePackageManagerStore()
|
|
46
|
+
|
|
47
|
+
const { framework: paramsFramework } = useParams({ strict: false })
|
|
48
|
+
const localFramework = useLocalFrameworkPreference()
|
|
49
|
+
|
|
50
|
+
const actualFramework = (paramsFramework ||
|
|
51
|
+
localFramework ||
|
|
52
|
+
'react') as string
|
|
53
|
+
|
|
54
|
+
const normalizedFramework = actualFramework.toLowerCase()
|
|
55
|
+
const packageGroups = packagesByFramework[normalizedFramework]
|
|
56
|
+
|
|
57
|
+
// Hide component if current framework not in package list
|
|
58
|
+
if (!packageGroups || packageGroups.length === 0) {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Use stored package manager if valid, otherwise default to first one
|
|
63
|
+
const selectedPackageManager = PACKAGE_MANAGERS.includes(storedPackageManager)
|
|
64
|
+
? storedPackageManager
|
|
65
|
+
: PACKAGE_MANAGERS[0]
|
|
66
|
+
|
|
67
|
+
// Generate tabs for each package manager
|
|
68
|
+
const tabs: TabDefinition[] = PACKAGE_MANAGERS.map((pm) => ({
|
|
69
|
+
slug: pm,
|
|
70
|
+
name: pm,
|
|
71
|
+
headers: [],
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
// Generate children (command blocks) for each package manager
|
|
75
|
+
const children = PACKAGE_MANAGERS.map((pm) => {
|
|
76
|
+
const commands = getInstallCommand(pm, packageGroups, mode)
|
|
77
|
+
const commandText = commands.join('\n')
|
|
78
|
+
return (
|
|
79
|
+
<CodeBlock key={pm}>
|
|
80
|
+
<code className="language-bash">{commandText}</code>
|
|
81
|
+
</CodeBlock>
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="package-manager-tabs">
|
|
87
|
+
<Tabs
|
|
88
|
+
tabs={tabs}
|
|
89
|
+
children={children}
|
|
90
|
+
activeSlug={selectedPackageManager}
|
|
91
|
+
onTabChange={(slug) => setPackageManager(slug as PackageManager)}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { useParams } from '@tanstack/react-router'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
export type FrameworkOption = {
|
|
5
|
+
label: string
|
|
6
|
+
value: string
|
|
7
|
+
logo: string
|
|
8
|
+
color: string
|
|
9
|
+
fontColor: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TabDefinition = {
|
|
13
|
+
slug: string
|
|
14
|
+
name: string
|
|
15
|
+
headers?: Array<string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type TabsProps = {
|
|
19
|
+
tabs?: Array<TabDefinition>
|
|
20
|
+
children?: Array<React.ReactNode> | React.ReactNode
|
|
21
|
+
activeSlug?: string
|
|
22
|
+
onTabChange?: (slug: string) => void
|
|
23
|
+
/** Optional framework options for showing logos in tab buttons */
|
|
24
|
+
frameworkOptions?: FrameworkOption[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Tabs({
|
|
28
|
+
tabs: tabsProp = [],
|
|
29
|
+
children: childrenProp,
|
|
30
|
+
activeSlug: controlledActiveSlug,
|
|
31
|
+
onTabChange,
|
|
32
|
+
frameworkOptions = [],
|
|
33
|
+
}: TabsProps) {
|
|
34
|
+
const id = React.useId()
|
|
35
|
+
const childrenArray = React.Children.toArray(childrenProp)
|
|
36
|
+
|
|
37
|
+
const params = useParams({ strict: false })
|
|
38
|
+
const framework = params?.framework ?? undefined
|
|
39
|
+
|
|
40
|
+
const [internalActiveSlug, setInternalActiveSlug] = React.useState(() => {
|
|
41
|
+
const match = framework
|
|
42
|
+
? tabsProp.find((tab) => tab.slug === framework)
|
|
43
|
+
: undefined
|
|
44
|
+
return match?.slug ?? tabsProp[0]?.slug ?? ''
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const activeSlug = controlledActiveSlug ?? internalActiveSlug
|
|
48
|
+
const setActiveSlug = React.useCallback(
|
|
49
|
+
(slug: string) => {
|
|
50
|
+
if (onTabChange) {
|
|
51
|
+
onTabChange(slug)
|
|
52
|
+
} else {
|
|
53
|
+
setInternalActiveSlug(slug)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[onTabChange],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if (tabsProp.length === 0) return null
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="my-4">
|
|
63
|
+
<div className="not-prose flex items-center justify-start gap-2 rounded-t-md border-1 border-b-none border-gray-200 dark:border-gray-800 overflow-x-auto overflow-y-hidden bg-white dark:bg-gray-950">
|
|
64
|
+
{tabsProp.map((tab) => {
|
|
65
|
+
return (
|
|
66
|
+
<Tab
|
|
67
|
+
key={`${id}-${tab.slug}`}
|
|
68
|
+
id={id}
|
|
69
|
+
tab={tab}
|
|
70
|
+
activeSlug={activeSlug}
|
|
71
|
+
setActiveSlug={setActiveSlug}
|
|
72
|
+
frameworkOptions={frameworkOptions}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
})}
|
|
76
|
+
</div>
|
|
77
|
+
<div
|
|
78
|
+
className={`border border-gray-500/20 rounded-b-md bg-gray-100 dark:bg-gray-900`}
|
|
79
|
+
>
|
|
80
|
+
{childrenArray.map((child, index) => {
|
|
81
|
+
const tab = tabsProp[index]
|
|
82
|
+
if (!tab) return null
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
key={`${id}-${tab.slug}`}
|
|
86
|
+
data-tab={tab.slug}
|
|
87
|
+
hidden={tab.slug !== activeSlug}
|
|
88
|
+
className="max-w-none flex flex-col gap-2 text-base"
|
|
89
|
+
>
|
|
90
|
+
{child}
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
})}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const Tab = React.memo(
|
|
100
|
+
({
|
|
101
|
+
tab,
|
|
102
|
+
activeSlug,
|
|
103
|
+
setActiveSlug,
|
|
104
|
+
frameworkOptions = [],
|
|
105
|
+
}: {
|
|
106
|
+
id?: string
|
|
107
|
+
tab: TabDefinition
|
|
108
|
+
activeSlug: string
|
|
109
|
+
setActiveSlug: (slug: string) => void
|
|
110
|
+
frameworkOptions?: FrameworkOption[]
|
|
111
|
+
}) => {
|
|
112
|
+
const option = React.useMemo(
|
|
113
|
+
() =>
|
|
114
|
+
frameworkOptions.find(
|
|
115
|
+
(o) =>
|
|
116
|
+
o.value === tab.slug.toLowerCase() ||
|
|
117
|
+
o.label.toLowerCase() === tab.name.toLowerCase(),
|
|
118
|
+
),
|
|
119
|
+
[tab.slug, tab.name, frameworkOptions],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<button
|
|
124
|
+
aria-label={tab.name}
|
|
125
|
+
title={tab.name}
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={() => setActiveSlug(tab.slug)}
|
|
128
|
+
className={`inline-flex items-center justify-center gap-2 px-3 py-1.5 -mb-[1px] border-b-2 text-sm font-bold transition-colors hover:bg-gray-100 dark:hover:bg-gray-800 rounded-t-md overflow-y-none ${
|
|
129
|
+
activeSlug === tab.slug
|
|
130
|
+
? 'border-current text-current bg-gray-100 dark:bg-gray-900'
|
|
131
|
+
: 'border-transparent text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200'
|
|
132
|
+
}`}
|
|
133
|
+
>
|
|
134
|
+
{option && <img src={option.logo} alt="" className="w-4 h-4 -ml-1" />}
|
|
135
|
+
<span>{tab.name}</span>
|
|
136
|
+
</button>
|
|
137
|
+
)
|
|
138
|
+
},
|
|
139
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { CodeBlock } from './CodeBlock'
|
|
2
|
+
export { Markdown } from './Markdown'
|
|
3
|
+
export { MarkdownContent } from './MarkdownContent'
|
|
4
|
+
export { MarkdownLink } from './MarkdownLink'
|
|
5
|
+
export { Tabs, type TabDefinition, type TabsProps, type FrameworkOption } from './Tabs'
|
|
6
|
+
export { FileTabs, type FileTabDefinition, type FileTabsProps } from './FileTabs'
|
|
7
|
+
export { PackageManagerTabs } from './PackageManagerTabs'
|
|
8
|
+
export { FrameworkContent } from './FrameworkContent'
|
|
9
|
+
export { handleTabsComponent } from './MarkdownTabsHandler'
|
|
10
|
+
export { handleFrameworkComponent } from './MarkdownFrameworkHandler'
|
|
11
|
+
export {
|
|
12
|
+
MarkdownHeadingProvider,
|
|
13
|
+
useMarkdownHeadings,
|
|
14
|
+
type MarkdownHeading,
|
|
15
|
+
} from './MarkdownHeadingContext'
|