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,141 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { twMerge } from 'tailwind-merge'
|
|
3
|
+
|
|
4
|
+
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'icon'
|
|
5
|
+
|
|
6
|
+
type ButtonColor =
|
|
7
|
+
| 'blue'
|
|
8
|
+
| 'green'
|
|
9
|
+
| 'red'
|
|
10
|
+
| 'orange'
|
|
11
|
+
| 'purple'
|
|
12
|
+
| 'gray'
|
|
13
|
+
| 'emerald'
|
|
14
|
+
| 'cyan'
|
|
15
|
+
| 'yellow'
|
|
16
|
+
|
|
17
|
+
type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'icon-sm' | 'icon-md'
|
|
18
|
+
|
|
19
|
+
type ButtonRounded = 'none' | 'md' | 'lg' | 'full'
|
|
20
|
+
|
|
21
|
+
type ButtonOwnProps<TElement extends React.ElementType = 'button'> = {
|
|
22
|
+
as?: TElement
|
|
23
|
+
children: React.ReactNode
|
|
24
|
+
variant?: ButtonVariant
|
|
25
|
+
color?: ButtonColor
|
|
26
|
+
size?: ButtonSize
|
|
27
|
+
rounded?: ButtonRounded
|
|
28
|
+
className?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ButtonProps<TElement extends React.ElementType = 'button'> =
|
|
32
|
+
ButtonOwnProps<TElement> &
|
|
33
|
+
Omit<
|
|
34
|
+
React.ComponentPropsWithoutRef<TElement>,
|
|
35
|
+
keyof ButtonOwnProps<TElement>
|
|
36
|
+
>
|
|
37
|
+
|
|
38
|
+
type ButtonComponent = <TElement extends React.ElementType = 'button'>(
|
|
39
|
+
props: ButtonProps<TElement>,
|
|
40
|
+
) => React.ReactNode
|
|
41
|
+
|
|
42
|
+
const primaryColorStyles: Record<ButtonColor, string> = {
|
|
43
|
+
blue: 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700',
|
|
44
|
+
green: 'bg-green-600 text-white border-green-600 hover:bg-green-700',
|
|
45
|
+
red: 'bg-red-600 text-white border-red-600 hover:bg-red-700',
|
|
46
|
+
orange: 'bg-orange-600 text-white border-orange-600 hover:bg-orange-700',
|
|
47
|
+
purple: 'bg-purple-600 text-white border-purple-600 hover:bg-purple-700',
|
|
48
|
+
gray: 'bg-gray-600 text-white border-gray-600 hover:bg-gray-700',
|
|
49
|
+
emerald: 'bg-emerald-500 text-white border-emerald-500 hover:bg-emerald-600',
|
|
50
|
+
cyan: 'bg-cyan-500 text-white border-cyan-500 hover:bg-cyan-600',
|
|
51
|
+
yellow: 'bg-yellow-400 text-black border-yellow-400 hover:bg-yellow-500',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const iconColorStyles: Record<ButtonColor, string> = {
|
|
55
|
+
blue: 'text-blue-600 hover:bg-blue-100 dark:hover:bg-blue-900/30',
|
|
56
|
+
green: 'text-green-600 hover:bg-green-100 dark:hover:bg-green-900/30',
|
|
57
|
+
red: 'text-red-600 hover:bg-red-100 dark:hover:bg-red-900/30',
|
|
58
|
+
orange: 'text-orange-600 hover:bg-orange-100 dark:hover:bg-orange-900/30',
|
|
59
|
+
purple: 'text-purple-600 hover:bg-purple-100 dark:hover:bg-purple-900/30',
|
|
60
|
+
gray: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700',
|
|
61
|
+
emerald: 'text-emerald-600 hover:bg-emerald-100 dark:hover:bg-emerald-900/30',
|
|
62
|
+
cyan: 'text-cyan-600 hover:bg-cyan-100 dark:hover:bg-cyan-900/30',
|
|
63
|
+
yellow: 'text-yellow-600 hover:bg-yellow-100 dark:hover:bg-yellow-900/30',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const variantStyles: Record<ButtonVariant, string> = {
|
|
67
|
+
primary: 'border font-medium',
|
|
68
|
+
secondary:
|
|
69
|
+
'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 border-transparent font-medium',
|
|
70
|
+
ghost:
|
|
71
|
+
'border border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 font-medium',
|
|
72
|
+
icon: 'border-transparent',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sizeStyles: Record<ButtonSize, string> = {
|
|
76
|
+
xs: 'px-2 py-1.5 text-xs',
|
|
77
|
+
sm: 'px-3 py-1 text-sm',
|
|
78
|
+
md: 'px-4 py-2 text-sm',
|
|
79
|
+
lg: 'px-6 py-3 text-base',
|
|
80
|
+
'icon-sm': 'p-1.5',
|
|
81
|
+
'icon-md': 'p-2',
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const roundedStyles: Record<ButtonRounded, string> = {
|
|
85
|
+
none: 'rounded-none',
|
|
86
|
+
md: 'rounded-md',
|
|
87
|
+
lg: 'rounded-lg',
|
|
88
|
+
full: 'rounded-full',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const baseStyles =
|
|
92
|
+
'inline-flex items-center justify-center gap-2 cursor-pointer transition-colors disabled:opacity-50 disabled:cursor-not-allowed'
|
|
93
|
+
|
|
94
|
+
function getDefaultSize(variant: ButtonVariant): ButtonSize {
|
|
95
|
+
if (variant === 'icon') return 'icon-md'
|
|
96
|
+
return 'md'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getDefaultRounded(size: ButtonSize): ButtonRounded {
|
|
100
|
+
if (size === 'xs' || size === 'sm') return 'md'
|
|
101
|
+
if (size === 'icon-sm' || size === 'icon-md') return 'lg'
|
|
102
|
+
return 'lg'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const Button: ButtonComponent = ({
|
|
106
|
+
as,
|
|
107
|
+
children,
|
|
108
|
+
variant = 'primary',
|
|
109
|
+
color = 'blue',
|
|
110
|
+
size,
|
|
111
|
+
rounded,
|
|
112
|
+
className,
|
|
113
|
+
...props
|
|
114
|
+
}) => {
|
|
115
|
+
const Component = as || 'button'
|
|
116
|
+
const resolvedSize = size ?? getDefaultSize(variant)
|
|
117
|
+
const resolvedRounded = rounded ?? getDefaultRounded(resolvedSize)
|
|
118
|
+
|
|
119
|
+
const colorStyles =
|
|
120
|
+
variant === 'primary'
|
|
121
|
+
? primaryColorStyles[color]
|
|
122
|
+
: variant === 'icon'
|
|
123
|
+
? iconColorStyles[color]
|
|
124
|
+
: ''
|
|
125
|
+
|
|
126
|
+
return React.createElement(
|
|
127
|
+
Component,
|
|
128
|
+
{
|
|
129
|
+
className: twMerge(
|
|
130
|
+
baseStyles,
|
|
131
|
+
variantStyles[variant],
|
|
132
|
+
sizeStyles[resolvedSize],
|
|
133
|
+
roundedStyles[resolvedRounded],
|
|
134
|
+
colorStyles,
|
|
135
|
+
className,
|
|
136
|
+
),
|
|
137
|
+
...props,
|
|
138
|
+
},
|
|
139
|
+
children,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { HTMLProps } from 'react'
|
|
3
|
+
|
|
4
|
+
export const InlineCode = React.memo(function InlineCode({
|
|
5
|
+
className,
|
|
6
|
+
...rest
|
|
7
|
+
}: HTMLProps<HTMLElement>) {
|
|
8
|
+
return (
|
|
9
|
+
<span
|
|
10
|
+
className={`border border-gray-500/20 bg-gray-500/10 rounded px-1 py-0.5${
|
|
11
|
+
className ? ` ${className}` : ''
|
|
12
|
+
}`}
|
|
13
|
+
{...rest}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { HTMLProps } from 'react'
|
|
3
|
+
|
|
4
|
+
export const MarkdownImg = React.memo(function MarkdownImg({
|
|
5
|
+
alt,
|
|
6
|
+
src,
|
|
7
|
+
className,
|
|
8
|
+
children: _,
|
|
9
|
+
...props
|
|
10
|
+
}: HTMLProps<HTMLImageElement>) {
|
|
11
|
+
return (
|
|
12
|
+
<img
|
|
13
|
+
{...props}
|
|
14
|
+
src={src}
|
|
15
|
+
alt={alt ?? ''}
|
|
16
|
+
className={`max-w-full h-auto rounded-lg shadow-md ${className ?? ''}`}
|
|
17
|
+
loading="lazy"
|
|
18
|
+
decoding="async"
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export type Framework =
|
|
2
|
+
| 'angular'
|
|
3
|
+
| 'lit'
|
|
4
|
+
| 'preact'
|
|
5
|
+
| 'qwik'
|
|
6
|
+
| 'react'
|
|
7
|
+
| 'solid'
|
|
8
|
+
| 'svelte'
|
|
9
|
+
| 'vanilla'
|
|
10
|
+
| 'vue'
|
|
11
|
+
|
|
12
|
+
export type FrameworkOption = {
|
|
13
|
+
label: string
|
|
14
|
+
value: string
|
|
15
|
+
logo: string
|
|
16
|
+
color: string
|
|
17
|
+
fontColor: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Framework logo URLs — these are imported from the tanstack.com image assets.
|
|
21
|
+
// In production, replace these with actual SVG imports or CDN URLs.
|
|
22
|
+
// For now, we use placeholder paths that match the tanstack.com convention.
|
|
23
|
+
export const frameworkOptions: FrameworkOption[] = [
|
|
24
|
+
{
|
|
25
|
+
label: 'React',
|
|
26
|
+
value: 'react',
|
|
27
|
+
logo: '/images/frameworks/react-logo.svg',
|
|
28
|
+
color: 'bg-blue-500',
|
|
29
|
+
fontColor: 'text-sky-500',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Preact',
|
|
33
|
+
value: 'preact',
|
|
34
|
+
logo: '/images/frameworks/preact-logo.svg',
|
|
35
|
+
color: 'bg-purple-500',
|
|
36
|
+
fontColor: 'text-purple-500',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'Vue',
|
|
40
|
+
value: 'vue',
|
|
41
|
+
logo: '/images/frameworks/vue-logo.svg',
|
|
42
|
+
color: 'bg-green-500',
|
|
43
|
+
fontColor: 'text-green-500',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'Angular',
|
|
47
|
+
value: 'angular',
|
|
48
|
+
logo: '/images/frameworks/angular-logo.svg',
|
|
49
|
+
color: 'bg-red-500',
|
|
50
|
+
fontColor: 'text-fuchsia-500',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: 'Solid',
|
|
54
|
+
value: 'solid',
|
|
55
|
+
logo: '/images/frameworks/solid-logo.svg',
|
|
56
|
+
color: 'bg-blue-600',
|
|
57
|
+
fontColor: 'text-blue-600',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'Lit',
|
|
61
|
+
value: 'lit',
|
|
62
|
+
logo: '/images/frameworks/lit-logo.svg',
|
|
63
|
+
color: 'bg-emerald-500',
|
|
64
|
+
fontColor: 'text-emerald-500',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'Svelte',
|
|
68
|
+
value: 'svelte',
|
|
69
|
+
logo: '/images/frameworks/svelte-logo.svg',
|
|
70
|
+
color: 'bg-orange-600',
|
|
71
|
+
fontColor: 'text-orange-600',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Qwik',
|
|
75
|
+
value: 'qwik',
|
|
76
|
+
logo: '/images/frameworks/qwik-logo.svg',
|
|
77
|
+
color: 'bg-indigo-500',
|
|
78
|
+
fontColor: 'text-indigo-500',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: 'Vanilla',
|
|
82
|
+
value: 'vanilla',
|
|
83
|
+
logo: '/images/frameworks/js-logo.svg',
|
|
84
|
+
color: 'bg-yellow-500',
|
|
85
|
+
fontColor: 'text-yellow-500',
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
export function getFrameworkOptions(frameworkStrs: Framework[]) {
|
|
90
|
+
return frameworkOptions.filter((d) =>
|
|
91
|
+
frameworkStrs.includes(d.value as Framework),
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
interface SearchContextType {
|
|
4
|
+
isOpen: boolean
|
|
5
|
+
openSearch: () => void
|
|
6
|
+
closeSearch: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SearchContext = React.createContext<SearchContextType | undefined>(
|
|
10
|
+
undefined,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export function SearchProvider({ children }: { children: React.ReactNode }) {
|
|
14
|
+
const [isOpen, setIsOpen] = React.useState(false)
|
|
15
|
+
|
|
16
|
+
const value = React.useMemo(
|
|
17
|
+
() => ({
|
|
18
|
+
isOpen,
|
|
19
|
+
openSearch: () => setIsOpen(true),
|
|
20
|
+
closeSearch: () => setIsOpen(false),
|
|
21
|
+
}),
|
|
22
|
+
[isOpen],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<SearchContext.Provider value={value}>{children}</SearchContext.Provider>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useSearchContext() {
|
|
31
|
+
const context = React.useContext(SearchContext)
|
|
32
|
+
if (context === undefined) {
|
|
33
|
+
throw new Error('useSearch must be used within a SearchProvider')
|
|
34
|
+
}
|
|
35
|
+
return context
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database connection factory for D1.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { createDb } from '~/db'
|
|
6
|
+
* const db = createDb(env.DB)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { drizzle } from 'drizzle-orm/d1'
|
|
10
|
+
import * as schema from './schema'
|
|
11
|
+
|
|
12
|
+
export function createDb(d1: D1Database) {
|
|
13
|
+
return drizzle(d1, { schema })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type Db = ReturnType<typeof createDb>
|
|
17
|
+
export { schema }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle ORM schema for D1 database.
|
|
3
|
+
*
|
|
4
|
+
* Matches the existing SQLite schema from src/core/cache.ts:
|
|
5
|
+
* - sources: EN source texts keyed by MD5 hash
|
|
6
|
+
* - source_files: maps sources to file locations per version
|
|
7
|
+
* - translations: translated texts per language and key
|
|
8
|
+
*
|
|
9
|
+
* Plus a new table for production content serving:
|
|
10
|
+
* - content: full markdown documents for D1-based serving
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { sqliteTable, text, integer, primaryKey, index } from 'drizzle-orm/sqlite-core'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* EN source texts keyed by MD5 hash.
|
|
17
|
+
* Matches: CREATE TABLE sources (key TEXT PRIMARY KEY, text TEXT, type TEXT)
|
|
18
|
+
*/
|
|
19
|
+
export const sources = sqliteTable('sources', {
|
|
20
|
+
key: text('key').primaryKey(),
|
|
21
|
+
text: text('text').notNull(),
|
|
22
|
+
type: text('type').notNull().default('paragraph'),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maps source keys to file locations per version.
|
|
27
|
+
* Matches: CREATE TABLE source_files (key TEXT, file TEXT, line INTEGER, version TEXT)
|
|
28
|
+
*/
|
|
29
|
+
export const sourceFiles = sqliteTable(
|
|
30
|
+
'source_files',
|
|
31
|
+
{
|
|
32
|
+
key: text('key').notNull(),
|
|
33
|
+
file: text('file').notNull(),
|
|
34
|
+
line: integer('line').notNull(),
|
|
35
|
+
version: text('version').notNull().default('latest'),
|
|
36
|
+
},
|
|
37
|
+
(table) => [
|
|
38
|
+
primaryKey({ columns: [table.version, table.key, table.file, table.line] }),
|
|
39
|
+
index('idx_source_files_version').on(table.version),
|
|
40
|
+
index('idx_source_files_file').on(table.version, table.file),
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Translated texts per language and key.
|
|
46
|
+
* Matches: CREATE TABLE translations (lang TEXT, key TEXT, value TEXT, created_at INTEGER, updated_at INTEGER)
|
|
47
|
+
*/
|
|
48
|
+
export const translations = sqliteTable(
|
|
49
|
+
'translations',
|
|
50
|
+
{
|
|
51
|
+
lang: text('lang').notNull(),
|
|
52
|
+
key: text('key').notNull(),
|
|
53
|
+
value: text('value').notNull(),
|
|
54
|
+
createdAt: integer('created_at'),
|
|
55
|
+
updatedAt: integer('updated_at'),
|
|
56
|
+
},
|
|
57
|
+
(table) => [
|
|
58
|
+
primaryKey({ columns: [table.lang, table.key] }),
|
|
59
|
+
index('idx_translations_lang').on(table.lang),
|
|
60
|
+
],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Full markdown documents for production content serving via D1.
|
|
65
|
+
* This is new — not in the original cache.ts schema.
|
|
66
|
+
*/
|
|
67
|
+
export const content = sqliteTable('content', {
|
|
68
|
+
path: text('path').primaryKey(),
|
|
69
|
+
body: text('body').notNull(),
|
|
70
|
+
project: text('project').notNull(),
|
|
71
|
+
version: text('version').notNull(),
|
|
72
|
+
lang: text('lang').notNull(),
|
|
73
|
+
updatedAt: integer('updated_at'),
|
|
74
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
type UseClickOutsideOptions = {
|
|
4
|
+
/** Whether the click-outside detection is active */
|
|
5
|
+
enabled: boolean
|
|
6
|
+
/** Callback when a click outside is detected */
|
|
7
|
+
onClickOutside: () => void
|
|
8
|
+
/** Whether to also close on Escape key press (default: true) */
|
|
9
|
+
closeOnEscape?: boolean
|
|
10
|
+
/** Additional refs to consider as "inside" */
|
|
11
|
+
additionalRefs?: React.RefObject<HTMLElement | null>[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook to detect clicks outside of a referenced element.
|
|
16
|
+
* Returns a ref to attach to the element you want to detect clicks outside of.
|
|
17
|
+
*/
|
|
18
|
+
export function useClickOutside<T extends HTMLElement = HTMLElement>({
|
|
19
|
+
enabled,
|
|
20
|
+
onClickOutside,
|
|
21
|
+
closeOnEscape = true,
|
|
22
|
+
additionalRefs = [],
|
|
23
|
+
}: UseClickOutsideOptions): React.RefObject<T | null> {
|
|
24
|
+
const ref = React.useRef<T>(null)
|
|
25
|
+
const touchStartRef = React.useRef<{
|
|
26
|
+
x: number
|
|
27
|
+
y: number
|
|
28
|
+
outside: boolean
|
|
29
|
+
} | null>(null)
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (!enabled) return
|
|
33
|
+
|
|
34
|
+
const isInsideRefs = (target: Node) => {
|
|
35
|
+
if (ref.current?.contains(target)) return true
|
|
36
|
+
for (const additionalRef of additionalRefs) {
|
|
37
|
+
if (additionalRef.current?.contains(target)) return true
|
|
38
|
+
}
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Mouse: handle on mousedown for immediate response
|
|
43
|
+
const handleMouseDown = (event: MouseEvent) => {
|
|
44
|
+
if ((event as any).sourceCapabilities?.firesTouchEvents) return
|
|
45
|
+
|
|
46
|
+
if (!isInsideRefs(event.target as Node)) {
|
|
47
|
+
onClickOutside()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Touch: only close if tap started AND ended outside
|
|
52
|
+
const handleTouchStart = (event: TouchEvent) => {
|
|
53
|
+
const touch = event.touches[0]
|
|
54
|
+
if (touch) {
|
|
55
|
+
const target = event.target as Node
|
|
56
|
+
const startedOutside = !isInsideRefs(target)
|
|
57
|
+
touchStartRef.current = {
|
|
58
|
+
x: touch.clientX,
|
|
59
|
+
y: touch.clientY,
|
|
60
|
+
outside: startedOutside,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handleTouchEnd = (event: TouchEvent) => {
|
|
66
|
+
const start = touchStartRef.current
|
|
67
|
+
touchStartRef.current = null
|
|
68
|
+
|
|
69
|
+
if (!start) return
|
|
70
|
+
if (!start.outside) return
|
|
71
|
+
|
|
72
|
+
const touch = event.changedTouches[0]
|
|
73
|
+
if (!touch) return
|
|
74
|
+
|
|
75
|
+
const dx = Math.abs(touch.clientX - start.x)
|
|
76
|
+
const dy = Math.abs(touch.clientY - start.y)
|
|
77
|
+
if (dx > 10 || dy > 10) return
|
|
78
|
+
|
|
79
|
+
onClickOutside()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const handleEscape = (event: KeyboardEvent) => {
|
|
83
|
+
if (event.key === 'Escape') {
|
|
84
|
+
onClickOutside()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
document.addEventListener('mousedown', handleMouseDown)
|
|
89
|
+
document.addEventListener('touchstart', handleTouchStart, { passive: true })
|
|
90
|
+
document.addEventListener('touchend', handleTouchEnd)
|
|
91
|
+
if (closeOnEscape) {
|
|
92
|
+
document.addEventListener('keydown', handleEscape)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
document.removeEventListener('mousedown', handleMouseDown)
|
|
97
|
+
document.removeEventListener('touchstart', handleTouchStart)
|
|
98
|
+
document.removeEventListener('touchend', handleTouchEnd)
|
|
99
|
+
if (closeOnEscape) {
|
|
100
|
+
document.removeEventListener('keydown', handleEscape)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}, [enabled, onClickOutside, closeOnEscape, additionalRefs])
|
|
104
|
+
|
|
105
|
+
return ref
|
|
106
|
+
}
|