betterstart-cli 0.0.107 → 0.0.109
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/dist/assets/adapters/next/templates/init/admin-globals.css +2 -62
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-header.tsx +2 -19
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-providers.tsx +4 -7
- package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-user-menu.tsx +0 -1
- package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table-pagination.tsx +3 -1
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/copyable-code-block.tsx +2 -5
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-code-mirror.tsx +4 -5
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +0 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/edit-media-dialog-content.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx +3 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +89 -78
- package/dist/assets/adapters/next/templates/init/components/shared/search-input.tsx +5 -1
- package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +2 -11
- package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor.ts +1 -2
- package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +42 -37
- package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +95 -71
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +7 -7
- package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +88 -69
- package/dist/assets/adapters/next/templates/init/pages/settings/forms/form-notifications-drawer.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +26 -8
- package/dist/assets/adapters/next/templates/init/pages/users/change-password-dialog.tsx +8 -10
- package/dist/assets/adapters/next/templates/init/pages/users/create-user-dialog.tsx +8 -5
- package/dist/assets/adapters/next/templates/init/pages/users/delete-user-dialog.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/pages/users/edit-role-dialog.tsx +76 -33
- package/dist/assets/shared-assets/react-admin/custom/content-editor/main-toolbar-content.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/custom/content-editor/mobile-toolbar-content.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/selection-bubble-menu.tsx +1 -5
- package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +3 -15
- package/dist/assets/shared-assets/react-admin/custom/content-editor/table-bubble-menu.tsx +0 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/blockquote-button/blockquote-button.tsx +2 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/code-block-button/code-block-button.tsx +2 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/color-highlight-button/color-highlight-button.tsx +2 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/link-popover/link-control-popover.tsx +2 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/mark-button/mark-button.tsx +2 -1
- package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/undo-redo-button/undo-redo-button.tsx +2 -1
- package/dist/assets/shared-assets/react-admin/ui/avatar.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/badge.tsx +5 -3
- package/dist/assets/shared-assets/react-admin/ui/button.tsx +4 -4
- package/dist/assets/shared-assets/react-admin/ui/calendar.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/chart.tsx +5 -16
- package/dist/assets/shared-assets/react-admin/ui/checkbox.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/combobox.tsx +12 -2
- package/dist/assets/shared-assets/react-admin/ui/context-menu.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +24 -21
- package/dist/assets/shared-assets/react-admin/ui/field.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +9 -3
- package/dist/assets/shared-assets/react-admin/ui/input-otp.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/input.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/kbd.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/menubar.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/native-select.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/radio-group.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/select.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/sonner.tsx +2 -5
- package/dist/assets/shared-assets/react-admin/ui/switch.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/ui/tabs.tsx +3 -3
- package/dist/assets/shared-assets/react-admin/ui/textarea.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/toggle.tsx +1 -1
- package/dist/{chunk-OYU73Q2W.js → chunk-PSDUGFMD.js} +2 -2
- package/dist/{chunk-OYU73Q2W.js.map → chunk-PSDUGFMD.js.map} +1 -1
- package/dist/cli.js +240 -193
- package/dist/cli.js.map +1 -1
- package/dist/{read-namespaced-template-EIH2NPDJ.js → read-namespaced-template-TSATYQPP.js} +2 -2
- package/package.json +1 -1
- package/dist/assets/adapters/next/templates/init/hooks/use-admin-theme.tsx +0 -72
- package/dist/assets/adapters/next/templates/init/utils/theme/system.ts +0 -6
- /package/dist/{read-namespaced-template-EIH2NPDJ.js.map → read-namespaced-template-TSATYQPP.js.map} +0 -0
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
@plugin "@tailwindcss/typography";
|
|
7
7
|
|
|
8
|
-
@custom-variant dark (&:is(.dark *));
|
|
9
|
-
|
|
10
8
|
@theme inline {
|
|
11
9
|
--color-background: var(--background);
|
|
12
10
|
--color-foreground: var(--foreground);
|
|
@@ -97,9 +95,10 @@
|
|
|
97
95
|
--accent-foreground: oklch(0.15 0 0);
|
|
98
96
|
--destructive: oklch(0.577 0.245 27.325);
|
|
99
97
|
--warning: oklch(0.625 0.165 57.4);
|
|
98
|
+
--warning-foreground: oklch(0.625 0.165 57.4);
|
|
100
99
|
--border: oklch(0.93 0 0);
|
|
101
100
|
--highlight: oklch(0.9 0.004 286.32);
|
|
102
|
-
--input: oklch(0.
|
|
101
|
+
--input: oklch(0.93 0 0);
|
|
103
102
|
--ring: oklch(0.708 0 0);
|
|
104
103
|
--chart-1: oklch(0.845 0.143 164.978);
|
|
105
104
|
--chart-2: oklch(0.696 0.17 162.48);
|
|
@@ -174,65 +173,6 @@
|
|
|
174
173
|
--tt-color-highlight-pink-contrast: rgba(225, 136, 179, 0.27);
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
.dark {
|
|
178
|
-
--background: oklch(0.144 0 0);
|
|
179
|
-
--foreground: oklch(0.985 0 0);
|
|
180
|
-
--card: oklch(0.15 0 0);
|
|
181
|
-
--card-foreground: oklch(0.985 0 0);
|
|
182
|
-
--popover: oklch(0.15 0 0);
|
|
183
|
-
--popover-foreground: oklch(0.985 0 0);
|
|
184
|
-
--primary: oklch(0.922 0 0);
|
|
185
|
-
--primary-foreground: oklch(0.15 0 0);
|
|
186
|
-
--secondary: oklch(0.269 0 0);
|
|
187
|
-
--secondary-foreground: oklch(0.985 0 0);
|
|
188
|
-
--muted: oklch(0.269 0 0);
|
|
189
|
-
--muted-foreground: oklch(0.708 0 0);
|
|
190
|
-
--accent: oklch(0.269 0 0);
|
|
191
|
-
--accent-foreground: oklch(0.985 0 0);
|
|
192
|
-
--destructive: oklch(0.704 0.191 22.216);
|
|
193
|
-
--warning: oklch(0.75 0.2 90);
|
|
194
|
-
--warning-foreground: oklch(0.625 0.165 57.4);
|
|
195
|
-
--border: oklch(1 0 0 / 10%);
|
|
196
|
-
--highlight: oklch(1 0 0 / 10%);
|
|
197
|
-
--input: oklch(1 0 0 / 15%);
|
|
198
|
-
--ring: oklch(0.556 0 0);
|
|
199
|
-
--chart-1: oklch(0.845 0.143 164.978);
|
|
200
|
-
--chart-2: oklch(0.696 0.17 162.48);
|
|
201
|
-
--chart-3: oklch(0.596 0.145 163.225);
|
|
202
|
-
--chart-4: oklch(0.508 0.118 165.612);
|
|
203
|
-
--chart-5: oklch(0.432 0.095 166.913);
|
|
204
|
-
--sidebar: oklch(0.15 0 0);
|
|
205
|
-
--sidebar-foreground: oklch(0.985 0 0);
|
|
206
|
-
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
207
|
-
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
208
|
-
--sidebar-accent: oklch(0.269 0 0);
|
|
209
|
-
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
210
|
-
--sidebar-border: oklch(1 0 0 / 10%);
|
|
211
|
-
--sidebar-ring: oklch(0.556 0 0);
|
|
212
|
-
--tt-bg-color: var(--background);
|
|
213
|
-
--tt-bg-color-contrast: var(--border);
|
|
214
|
-
--tt-color-highlight-yellow: #6b6524;
|
|
215
|
-
--tt-color-highlight-green: #509568;
|
|
216
|
-
--tt-color-highlight-blue: #6e92aa;
|
|
217
|
-
--tt-color-highlight-purple: #583e74;
|
|
218
|
-
--tt-color-highlight-red: #743e42;
|
|
219
|
-
--tt-color-highlight-gray: rgb(47, 47, 47);
|
|
220
|
-
--tt-color-highlight-brown: rgb(74, 50, 40);
|
|
221
|
-
--tt-color-highlight-orange: rgb(92, 59, 35);
|
|
222
|
-
--tt-color-highlight-pink: rgb(78, 44, 60);
|
|
223
|
-
--tt-color-highlight-yellow-contrast: #58531e;
|
|
224
|
-
--tt-color-highlight-green-contrast: #47855d;
|
|
225
|
-
--tt-color-highlight-blue-contrast: #5e86a1;
|
|
226
|
-
--tt-color-highlight-purple-contrast: #4c3564;
|
|
227
|
-
--tt-color-highlight-red-contrast: #643539;
|
|
228
|
-
--tt-color-highlight-gray-contrast: rgba(255, 255, 255, 0.094);
|
|
229
|
-
--tt-color-highlight-brown-contrast: rgba(184, 101, 69, 0.25);
|
|
230
|
-
--tt-color-highlight-orange-contrast: rgba(233, 126, 37, 0.2);
|
|
231
|
-
--tt-color-highlight-pink-contrast: rgba(220, 76, 145, 0.22);
|
|
232
|
-
--shadow-small: 0px 1px 2px #00000029;
|
|
233
|
-
--shadow-medium: 0px 2px 2px #00000052, 0px 8px 8px -8px #00000029;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
176
|
@layer base {
|
|
237
177
|
* {
|
|
238
178
|
@apply border-border outline-ring/50;
|
|
@@ -1,31 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Button } from '@admin/components/ui/button'
|
|
4
3
|
import { SidebarTrigger, useSidebar } from '@admin/components/ui/sidebar'
|
|
5
|
-
import { useTheme } from '@admin/hooks/use-admin-theme'
|
|
6
|
-
import { Moon, Sun } from 'lucide-react'
|
|
7
4
|
|
|
8
5
|
export function AdminHeader() {
|
|
9
|
-
const { theme, setTheme } = useTheme()
|
|
10
6
|
const { state } = useSidebar()
|
|
11
7
|
|
|
12
8
|
return (
|
|
13
9
|
<header className="sticky top-0 z-50 flex h-14 w-full shrink-0 items-center gap-2 border-b border-border">
|
|
14
|
-
<div className="flex w-full flex-1 items-center
|
|
15
|
-
|
|
16
|
-
{state === 'collapsed' && <SidebarTrigger />}
|
|
17
|
-
</div>
|
|
18
|
-
<div className="ml-auto flex items-center gap-2">
|
|
19
|
-
<Button
|
|
20
|
-
variant="ghost"
|
|
21
|
-
size="icon"
|
|
22
|
-
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
|
23
|
-
>
|
|
24
|
-
<Sun className="size-4 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
|
|
25
|
-
<Moon className="absolute size-4 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
|
|
26
|
-
<span className="sr-only">Toggle theme</span>
|
|
27
|
-
</Button>
|
|
28
|
-
</div>
|
|
10
|
+
<div className="flex w-full flex-1 items-center gap-2 px-5">
|
|
11
|
+
{state === 'collapsed' && <SidebarTrigger />}
|
|
29
12
|
</div>
|
|
30
13
|
</header>
|
|
31
14
|
)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { Toaster } from '@admin/components/ui/sonner'
|
|
4
4
|
import { TooltipProvider } from '@admin/components/ui/tooltip'
|
|
5
|
-
import { AdminThemeProvider } from '@admin/hooks/use-admin-theme'
|
|
6
5
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
7
6
|
import { useState } from 'react'
|
|
8
7
|
|
|
@@ -22,12 +21,10 @@ export function AdminProviders({ children }: { children: React.ReactNode }) {
|
|
|
22
21
|
|
|
23
22
|
return (
|
|
24
23
|
<QueryClientProvider client={queryClient}>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</TooltipProvider>
|
|
30
|
-
</AdminThemeProvider>
|
|
24
|
+
<TooltipProvider>
|
|
25
|
+
{children}
|
|
26
|
+
<Toaster position="top-right" richColors />
|
|
27
|
+
</TooltipProvider>
|
|
31
28
|
</QueryClientProvider>
|
|
32
29
|
)
|
|
33
30
|
}
|
|
@@ -70,7 +70,9 @@ export function DataTablePagination<TData>(props: DataTablePaginationProps<TData
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
const handlePageSizeChange = React.useCallback(
|
|
73
|
-
(value: string) => {
|
|
73
|
+
(value: string | null) => {
|
|
74
|
+
if (value === null) return
|
|
75
|
+
|
|
74
76
|
React.startTransition(() => {
|
|
75
77
|
if (value === 'all') {
|
|
76
78
|
setPageSize(-1)
|
package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/copyable-code-block.tsx
CHANGED
|
@@ -5,7 +5,6 @@ import type { DevModeCodeLanguage, DevModeSnippetCodeTab } from './dev-mode-type
|
|
|
5
5
|
import { Button } from '@admin/components/ui/button'
|
|
6
6
|
import { Card, CardContent, CardHeader } from '@admin/components/ui/card'
|
|
7
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@admin/components/ui/tabs'
|
|
8
|
-
import { useTheme } from '@admin/hooks/use-admin-theme'
|
|
9
8
|
import { useCopyToClipboard } from '@admin/hooks/use-copy-to-clipboard'
|
|
10
9
|
import { Check, Copy } from 'lucide-react'
|
|
11
10
|
import { PlainCodeFallback } from './plain-code-fallback'
|
|
@@ -21,8 +20,6 @@ interface CopyableCodeBlockProps {
|
|
|
21
20
|
|
|
22
21
|
export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeBlockProps) {
|
|
23
22
|
const [copy, isCopied] = useCopyToClipboard()
|
|
24
|
-
const { resolvedTheme } = useTheme()
|
|
25
|
-
const codeTheme = resolvedTheme === 'dark' ? 'dark' : 'light'
|
|
26
23
|
const fallbackTab: DevModeSnippetCodeTab = {
|
|
27
24
|
id: 'code',
|
|
28
25
|
label,
|
|
@@ -68,7 +65,7 @@ export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeB
|
|
|
68
65
|
<TabsContent key={tab.id} value={tab.id} className="min-h-0 overflow-auto">
|
|
69
66
|
<CardContent className="h-full overflow-auto p-0">
|
|
70
67
|
<React.Suspense fallback={<PlainCodeFallback code={tab.code} />}>
|
|
71
|
-
<LazyCodeMirror code={tab.code} language={tab.language}
|
|
68
|
+
<LazyCodeMirror code={tab.code} language={tab.language} />
|
|
72
69
|
</React.Suspense>
|
|
73
70
|
</CardContent>
|
|
74
71
|
</TabsContent>
|
|
@@ -94,7 +91,7 @@ export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeB
|
|
|
94
91
|
</CardHeader>
|
|
95
92
|
<CardContent className="overflow-auto p-0">
|
|
96
93
|
<React.Suspense fallback={<PlainCodeFallback code={code} />}>
|
|
97
|
-
<LazyCodeMirror code={code} language={language}
|
|
94
|
+
<LazyCodeMirror code={code} language={language} />
|
|
98
95
|
</React.Suspense>
|
|
99
96
|
</CardContent>
|
|
100
97
|
</>
|
package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-code-mirror.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import type { DevModeCodeLanguage
|
|
4
|
+
import type { DevModeCodeLanguage } from './dev-mode-types'
|
|
5
5
|
import { javascript } from '@codemirror/lang-javascript'
|
|
6
6
|
import { json } from '@codemirror/lang-json'
|
|
7
7
|
import { EditorView } from '@codemirror/view'
|
|
8
|
-
import {
|
|
8
|
+
import { githubLight } from '@uiw/codemirror-theme-github'
|
|
9
9
|
import CodeMirror from '@uiw/react-codemirror'
|
|
10
10
|
|
|
11
11
|
const codeMirrorSetup = {
|
|
@@ -43,10 +43,9 @@ const compactCodeTheme = EditorView.theme({
|
|
|
43
43
|
interface DevModeCodeMirrorProps {
|
|
44
44
|
code: string
|
|
45
45
|
language: DevModeCodeLanguage
|
|
46
|
-
theme: DevModeCodeTheme
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
export default function DevModeCodeMirror({ code, language
|
|
48
|
+
export default function DevModeCodeMirror({ code, language }: DevModeCodeMirrorProps) {
|
|
50
49
|
const extensions = React.useMemo(
|
|
51
50
|
() =>
|
|
52
51
|
language === 'json'
|
|
@@ -62,7 +61,7 @@ export default function DevModeCodeMirror({ code, language, theme }: DevModeCode
|
|
|
62
61
|
readOnly
|
|
63
62
|
basicSetup={codeMirrorSetup}
|
|
64
63
|
extensions={extensions}
|
|
65
|
-
theme={
|
|
64
|
+
theme={githubLight}
|
|
66
65
|
className="flex-1 [&>.cm-editor]:h-full"
|
|
67
66
|
/>
|
|
68
67
|
)
|
package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-pagination.tsx
CHANGED
|
@@ -41,7 +41,9 @@ export function MediaGridPagination({
|
|
|
41
41
|
const perPage = 'Rows per page'
|
|
42
42
|
|
|
43
43
|
const handlePageSizeChange = React.useCallback(
|
|
44
|
-
(value: string) => {
|
|
44
|
+
(value: string | null) => {
|
|
45
|
+
if (value === null) return
|
|
46
|
+
|
|
45
47
|
React.startTransition(() => {
|
|
46
48
|
onPageSizeChange(Number(value))
|
|
47
49
|
onPageChange(1)
|
package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { UploadFileResult } from '@admin/types'
|
|
5
|
+
import type { FieldErrors } from 'react-hook-form'
|
|
5
6
|
import { Card, CardContent } from '@admin/components/ui/card'
|
|
7
|
+
import { Form } from '@admin/components/ui/form'
|
|
6
8
|
import {
|
|
7
9
|
InputGroup,
|
|
8
10
|
InputGroupAddon,
|
|
@@ -15,19 +17,29 @@ import { getMediaUrlPlaceholder } from '@admin/utils/media/get-media-url-placeho
|
|
|
15
17
|
import { cn } from '@admin/utils/shared/cn'
|
|
16
18
|
import { isValidUrl } from '@admin/utils/validation/is-valid-url'
|
|
17
19
|
import { Upload } from 'lucide-react'
|
|
20
|
+
import { useForm } from 'react-hook-form'
|
|
21
|
+
import { toast } from 'sonner'
|
|
18
22
|
|
|
19
23
|
interface MediaUrlImporterProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
24
|
onImportFromUrl: (url: string) => Promise<UploadFileResult>
|
|
21
25
|
accept?: string
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
interface MediaUrlFormValues {
|
|
29
|
+
url: string
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUrlImporterProps) {
|
|
25
|
-
const [urlValue, setUrlValue] = React.useState('')
|
|
26
|
-
const [urlError, setUrlError] = React.useState<string | null>(null)
|
|
27
33
|
const [isImporting, startImportTransition] = React.useTransition()
|
|
28
34
|
const mountedRef = React.useRef(true)
|
|
29
35
|
const importInFlightRef = React.useRef(false)
|
|
30
36
|
const importRequestIdRef = React.useRef(0)
|
|
37
|
+
const form = useForm<MediaUrlFormValues>({
|
|
38
|
+
defaultValues: { url: '' },
|
|
39
|
+
mode: 'onSubmit',
|
|
40
|
+
reValidateMode: 'onChange'
|
|
41
|
+
})
|
|
42
|
+
const urlField = form.register('url', { required: 'Enter a direct media URL to import.' })
|
|
31
43
|
|
|
32
44
|
React.useEffect(() => {
|
|
33
45
|
mountedRef.current = true
|
|
@@ -42,93 +54,92 @@ export function MediaUrlImporter({ onImportFromUrl, accept, className }: MediaUr
|
|
|
42
54
|
const urlHelpText = getMediaUrlHelpText(accept)
|
|
43
55
|
const urlPlaceholder = getMediaUrlPlaceholder(accept)
|
|
44
56
|
|
|
45
|
-
const handleImport = React.useCallback(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const nextUrl = urlValue.trim()
|
|
49
|
-
|
|
50
|
-
if (!nextUrl) {
|
|
51
|
-
setUrlError('Enter a direct media URL to import.')
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!isValidUrl(nextUrl)) {
|
|
56
|
-
setUrlError('Enter a valid HTTP or HTTPS URL.')
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
setUrlError(null)
|
|
61
|
-
const importRequestId = importRequestIdRef.current + 1
|
|
62
|
-
importRequestIdRef.current = importRequestId
|
|
63
|
-
importInFlightRef.current = true
|
|
57
|
+
const handleImport = React.useCallback(
|
|
58
|
+
(values: MediaUrlFormValues) => {
|
|
59
|
+
if (isImporting || importInFlightRef.current) return
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const result = await onImportFromUrl(nextUrl)
|
|
61
|
+
const nextUrl = values.url.trim()
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!result.success) {
|
|
74
|
-
setUrlError(result.error ?? 'Failed to import media from URL.')
|
|
75
|
-
return
|
|
76
|
-
}
|
|
63
|
+
if (!isValidUrl(nextUrl)) {
|
|
64
|
+
toast.error('Enter a valid HTTP or HTTPS URL.')
|
|
65
|
+
return
|
|
66
|
+
}
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
const importRequestId = importRequestIdRef.current + 1
|
|
69
|
+
importRequestIdRef.current = importRequestId
|
|
70
|
+
importInFlightRef.current = true
|
|
71
|
+
|
|
72
|
+
startImportTransition(async () => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await onImportFromUrl(nextUrl)
|
|
75
|
+
|
|
76
|
+
if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
toast.error(result.error ?? 'Failed to import media from URL.')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
form.reset()
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (!mountedRef.current || importRequestIdRef.current !== importRequestId) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
toast.error(error instanceof Error ? error.message : 'Failed to import media from URL.')
|
|
92
|
+
} finally {
|
|
93
|
+
if (importRequestIdRef.current === importRequestId) {
|
|
94
|
+
importInFlightRef.current = false
|
|
95
|
+
}
|
|
82
96
|
}
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
[form, isImporting, onImportFromUrl]
|
|
100
|
+
)
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
}, [isImporting, onImportFromUrl, urlValue])
|
|
102
|
+
const handleInvalid = (errors: FieldErrors<MediaUrlFormValues>) => {
|
|
103
|
+
const message = errors.url?.message
|
|
104
|
+
toast.error(typeof message === 'string' ? message : 'Enter a direct media URL to import.')
|
|
105
|
+
}
|
|
92
106
|
|
|
93
107
|
return (
|
|
94
108
|
<Card className={cn('flex items-center justify-center', className)}>
|
|
95
109
|
<CardContent className="h-full w-full">
|
|
96
110
|
<div className="flex h-full w-full flex-col items-center justify-center gap-3 text-center">
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
type="url"
|
|
100
|
-
name="url"
|
|
101
|
-
aria-label="Media URL"
|
|
102
|
-
value={urlValue}
|
|
103
|
-
placeholder={urlPlaceholder}
|
|
111
|
+
<Form {...form}>
|
|
112
|
+
<form
|
|
104
113
|
autoComplete="off"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
114
|
+
noValidate
|
|
115
|
+
onSubmit={form.handleSubmit(handleImport, handleInvalid)}
|
|
116
|
+
className="w-full"
|
|
117
|
+
>
|
|
118
|
+
<InputGroup className="mx-auto max-w-2xl bg-background">
|
|
119
|
+
<InputGroupInput
|
|
120
|
+
type="url"
|
|
121
|
+
aria-label="Media URL"
|
|
122
|
+
placeholder={urlPlaceholder}
|
|
123
|
+
autoComplete="off"
|
|
124
|
+
spellCheck={false}
|
|
125
|
+
{...urlField}
|
|
126
|
+
disabled={isImporting}
|
|
127
|
+
/>
|
|
128
|
+
<InputGroupAddon align="inline-end">
|
|
129
|
+
<InputGroupButton
|
|
130
|
+
type="submit"
|
|
131
|
+
variant="outline"
|
|
132
|
+
disabled={isImporting}
|
|
133
|
+
aria-busy={isImporting}
|
|
134
|
+
className="h-full px-5"
|
|
135
|
+
>
|
|
136
|
+
{isImporting ? <Spinner /> : <Upload />}
|
|
137
|
+
{isImporting ? 'Importing…' : 'Import'}
|
|
138
|
+
</InputGroupButton>
|
|
139
|
+
</InputGroupAddon>
|
|
140
|
+
</InputGroup>
|
|
141
|
+
</form>
|
|
142
|
+
</Form>
|
|
132
143
|
|
|
133
144
|
<div className="flex w-full flex-col items-center text-center">
|
|
134
145
|
<p className="text-sm text-muted-foreground">{urlHelpText}</p>
|
|
@@ -40,7 +40,11 @@ export function SearchInput({
|
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<Form {...form}>
|
|
43
|
-
<form
|
|
43
|
+
<form
|
|
44
|
+
autoComplete="off"
|
|
45
|
+
onSubmit={form.handleSubmit(onSubmit)}
|
|
46
|
+
className="flex w-full items-center"
|
|
47
|
+
>
|
|
44
48
|
<FormField
|
|
45
49
|
control={form.control}
|
|
46
50
|
name="search"
|
|
@@ -9,7 +9,6 @@ import type { QueryClient } from '@tanstack/react-query'
|
|
|
9
9
|
import type { ReactCodeMirrorRef } from '@uiw/react-codemirror'
|
|
10
10
|
import type { Root } from 'react-dom/client'
|
|
11
11
|
import { MediaField } from '@admin/components/custom/media-field'
|
|
12
|
-
import { useTheme } from '@admin/hooks/use-admin-theme'
|
|
13
12
|
import { applyCursorChange } from '@admin/utils/editor/apply-cursor-change'
|
|
14
13
|
import { applyCursorChangeIfAvailable } from '@admin/utils/editor/apply-cursor-change-if-available'
|
|
15
14
|
import { createMarkdownTableText } from '@admin/utils/editor/create-markdown-table-text'
|
|
@@ -33,11 +32,12 @@ import { redo, redoDepth, undo, undoDepth } from '@codemirror/commands'
|
|
|
33
32
|
import { markdown } from '@codemirror/lang-markdown'
|
|
34
33
|
import { Decoration, EditorView, WidgetType } from '@codemirror/view'
|
|
35
34
|
import { QueryClientProvider, useQueryClient } from '@tanstack/react-query'
|
|
36
|
-
import {
|
|
35
|
+
import { githubLightInit } from '@uiw/codemirror-theme-github'
|
|
37
36
|
import { NuqsAdapter } from 'nuqs/adapters/react'
|
|
38
37
|
import { createRoot } from 'react-dom/client'
|
|
39
38
|
|
|
40
39
|
const ADMIN_MONO_FONT_FAMILY = 'var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace'
|
|
40
|
+
const editorTheme = githubLightInit({ settings: { fontFamily: ADMIN_MONO_FONT_FAMILY } })
|
|
41
41
|
|
|
42
42
|
type MarkdownMediaGalleryWidgetProps = {
|
|
43
43
|
widgetId: number
|
|
@@ -150,15 +150,6 @@ export function useContentEditorSourceMode({
|
|
|
150
150
|
const [isMarkdownLinkActive, setIsMarkdownLinkActive] = React.useState(false)
|
|
151
151
|
const [canUndoMarkdownChange, setCanUndoMarkdownChange] = React.useState(false)
|
|
152
152
|
const [canRedoMarkdownChange, setCanRedoMarkdownChange] = React.useState(false)
|
|
153
|
-
const { resolvedTheme } = useTheme()
|
|
154
|
-
const editorTheme = React.useMemo(
|
|
155
|
-
() =>
|
|
156
|
-
resolvedTheme === 'dark'
|
|
157
|
-
? githubDarkInit({ settings: { fontFamily: ADMIN_MONO_FONT_FAMILY } })
|
|
158
|
-
: githubLightInit({ settings: { fontFamily: ADMIN_MONO_FONT_FAMILY } }),
|
|
159
|
-
[resolvedTheme]
|
|
160
|
-
)
|
|
161
|
-
|
|
162
153
|
const valueRef = React.useRef(value)
|
|
163
154
|
valueRef.current = value
|
|
164
155
|
const onChangeRef = React.useRef(onChange)
|
|
@@ -99,8 +99,7 @@ export function useContentEditor({
|
|
|
99
99
|
autocomplete: 'off',
|
|
100
100
|
autocorrect: 'off',
|
|
101
101
|
autocapitalize: 'off',
|
|
102
|
-
class:
|
|
103
|
-
'simple-editor content-editor-prose prose prose-sm max-w-none dark:prose-invert focus:outline-none'
|
|
102
|
+
class: 'simple-editor content-editor-prose prose prose-sm max-w-none focus:outline-none'
|
|
104
103
|
},
|
|
105
104
|
handleKeyDown: (_view, event) => handleSlashKeyDown(event)
|
|
106
105
|
},
|