@questpie/admin 3.5.3 → 3.5.5
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/README.md +8 -0
- package/dist/client/blocks/block-renderer.d.mts +2 -2
- package/dist/client/builder/index.d.mts +1 -1
- package/dist/client/builder/types/collection-types.d.mts +80 -5
- package/dist/client/builder/types/common.d.mts +5 -0
- package/dist/client/builder/types/field-types.d.mts +41 -1
- package/dist/client/builder/view/view.d.mts +3 -2
- package/dist/client/components/admin-link.d.mts +2 -2
- package/dist/client/components/fields/boolean-field.mjs +2 -1
- package/dist/client/components/fields/date-field.mjs +2 -1
- package/dist/client/components/fields/datetime-field.mjs +2 -1
- package/dist/client/components/fields/email-field.mjs +2 -1
- package/dist/client/components/fields/field-utils.d.mts +11 -0
- package/dist/client/components/fields/field-utils.mjs +3 -1
- package/dist/client/components/fields/field-wrapper.mjs +3 -3
- package/dist/client/components/fields/number-field.mjs +2 -1
- package/dist/client/components/fields/object-field.mjs +2 -1
- package/dist/client/components/fields/relation/displays/types.mjs +3 -3
- package/dist/client/components/fields/rich-text-editor/extensions.mjs +2 -1
- package/dist/client/components/fields/rich-text-editor/image-popover.mjs +6 -2
- package/dist/client/components/fields/rich-text-editor/image-upload.mjs +2 -1
- package/dist/client/components/fields/rich-text-editor/index.d.mts +3 -2
- package/dist/client/components/fields/rich-text-editor/index.mjs +4 -3
- package/dist/client/components/fields/select-field.mjs +2 -1
- package/dist/client/components/fields/text-field.mjs +2 -1
- package/dist/client/components/fields/textarea-field.mjs +2 -1
- package/dist/client/components/fields/time-field.mjs +2 -1
- package/dist/client/components/layout/field-layout-renderer.mjs +4 -4
- package/dist/client/components/media/media-grid.mjs +2 -1
- package/dist/client/components/primitives/asset-preview.mjs +4 -2
- package/dist/client/components/primitives/dropzone.d.mts +100 -0
- package/dist/client/components/primitives/field-select-control.mjs +2 -1
- package/dist/client/components/ui/button.d.mts +23 -0
- package/dist/client/components/ui/button.mjs +2 -2
- package/dist/client/components/ui/dropdown-menu.d.mts +49 -0
- package/dist/client/components/ui/dropdown-menu.mjs +22 -1
- package/dist/client/components/ui/popover.mjs +1 -1
- package/dist/client/components/ui/search-input.d.mts +56 -0
- package/dist/client/components/ui/select.mjs +2 -2
- package/dist/client/components/ui/sheet.d.mts +40 -0
- package/dist/client/components/ui/table.d.mts +49 -0
- package/dist/client/components/ui/table.mjs +15 -1
- package/dist/client/components/ui/tooltip.d.mts +21 -0
- package/dist/client/contexts/focus-context.d.mts +2 -2
- package/dist/client/hooks/use-admin-config.mjs +20 -1
- package/dist/client/hooks/use-autosave.mjs +91 -0
- package/dist/client/hooks/use-collection.mjs +65 -23
- package/dist/client/hooks/use-upload.d.mts +40 -0
- package/dist/client/hooks/use-upload.mjs +4 -2
- package/dist/client/i18n/hooks.d.mts +20 -0
- package/dist/client/lib/utils.d.mts +6 -0
- package/dist/client/preview/block-scope-context.d.mts +2 -2
- package/dist/client/preview/preview-banner.d.mts +2 -2
- package/dist/client/preview/preview-field.d.mts +4 -4
- package/dist/client/runtime/provider.mjs +22 -3
- package/dist/client/scope/picker.d.mts +2 -2
- package/dist/client/scope/provider.d.mts +2 -2
- package/dist/client/styles/base.css +22 -18
- package/dist/client/utils/asset-url.mjs +27 -0
- package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
- package/dist/client/views/auth/auth-layout.d.mts +3 -3
- package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
- package/dist/client/views/auth/login-form.d.mts +2 -2
- package/dist/client/views/auth/reset-password-form.d.mts +2 -2
- package/dist/client/views/auth/setup-form.d.mts +2 -2
- package/dist/client/views/collection/auto-form-fields.mjs +4 -4
- package/dist/client/views/collection/cells/shared/asset-thumbnail.d.mts +7 -0
- package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +3 -2
- package/dist/client/views/collection/cells/shared/cell-helpers.mjs +3 -2
- package/dist/client/views/collection/cells/upload-cells.mjs +2 -1
- package/dist/client/views/collection/document-view.d.mts +30 -0
- package/dist/client/views/collection/document-view.mjs +377 -0
- package/dist/client/views/collection/field-context.mjs +3 -2
- package/dist/client/views/collection/field-renderer.mjs +2 -2
- package/dist/client/views/collection/form-view.mjs +14 -80
- package/dist/client/views/collection/list-view.mjs +19 -15
- package/dist/client/views/collection/table-view.mjs +1 -1
- package/dist/client/views/layout/admin-layout-provider.mjs +4 -3
- package/dist/client/views/layout/admin-layout.mjs +107 -20
- package/dist/client/views/layout/admin-router.mjs +19 -3
- package/dist/client/views/layout/admin-sidebar.mjs +50 -6
- package/dist/client/views/layout/admin-view-layout.d.mts +36 -0
- package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
- package/dist/client/views/pages/dashboard-page.d.mts +2 -2
- package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
- package/dist/client/views/pages/invite-page.d.mts +2 -2
- package/dist/client/views/pages/login-page.d.mts +2 -2
- package/dist/client/views/pages/reset-password-page.d.mts +2 -2
- package/dist/client/views/pages/setup-page.d.mts +2 -2
- package/dist/client.d.mts +17 -2
- package/dist/client.mjs +16 -1
- package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
- package/dist/factories.d.mts +2 -2
- package/dist/factories.mjs +2 -2
- package/dist/index.d.mts +17 -3
- package/dist/index.mjs +16 -1
- package/dist/server/augmentation/actions.d.mts +5 -0
- package/dist/server/augmentation/form-layout.d.mts +5 -0
- package/dist/server/augmentation/views.d.mts +4 -1
- package/dist/server/fields/blocks.mjs +4 -1
- package/dist/server/fields/reactive-runtime.mjs +3 -0
- package/dist/server/modules/admin/.generated/module.d.mts +1 -1
- package/dist/server/modules/admin/auth-helpers.mjs +7 -1
- package/dist/server/modules/admin/block/introspection.mjs +28 -4
- package/dist/server/modules/admin/block/prefetch.d.mts +11 -0
- package/dist/server/modules/admin/block/prefetch.mjs +108 -27
- package/dist/server/modules/admin/client/.generated/module.d.mts +68 -67
- package/dist/server/modules/admin/client/.generated/module.mjs +2 -0
- package/dist/server/modules/admin/client/views/collection-document.d.mts +6 -0
- package/dist/server/modules/admin/client/views/collection-document.mjs +10 -0
- package/dist/server/modules/admin/collections/account.d.mts +46 -46
- package/dist/server/modules/admin/collections/admin-locks.d.mts +57 -57
- package/dist/server/modules/admin/collections/admin-preferences.d.mts +42 -42
- package/dist/server/modules/admin/collections/admin-saved-views.d.mts +50 -50
- package/dist/server/modules/admin/collections/apikey.d.mts +79 -71
- package/dist/server/modules/admin/collections/assets.d.mts +42 -42
- package/dist/server/modules/admin/collections/session.d.mts +45 -45
- package/dist/server/modules/admin/collections/user.d.mts +66 -66
- package/dist/server/modules/admin/collections/verification.d.mts +39 -39
- package/dist/server/modules/admin/dto/admin-config.dto.mjs +34 -4
- package/dist/server/modules/admin/factories.mjs +4 -34
- package/dist/server/modules/admin/routes/admin-config.d.mts +3 -2
- package/dist/server/modules/admin/routes/admin-config.mjs +18 -2
- package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
- package/dist/server/modules/admin/routes/execute-action.mjs +10 -4
- package/dist/server/modules/admin/routes/locales.d.mts +2 -2
- package/dist/server/modules/admin/routes/locales.mjs +1 -1
- package/dist/server/modules/admin/routes/preview.d.mts +11 -11
- package/dist/server/modules/admin/routes/preview.mjs +6 -5
- package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
- package/dist/server/modules/admin/routes/reactive.mjs +2 -2
- package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
- package/dist/server/modules/admin/routes/setup.d.mts +7 -7
- package/dist/server/modules/admin/routes/translations.d.mts +4 -4
- package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
- package/dist/server/modules/admin/routes/widget-data.mjs +1 -1
- package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +27 -27
- package/dist/server/plugin.mjs +8 -3
- package/dist/server/proxy-factories.d.mts +8 -1
- package/dist/server/proxy-factories.mjs +33 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -65,6 +65,14 @@ export default adminConfig({
|
|
|
65
65
|
|
|
66
66
|
Collections, globals, routes, and jobs are auto-discovered via file convention. Codegen produces a `.generated/index.ts` with the fully-typed `App` and runtime `app` instance.
|
|
67
67
|
|
|
68
|
+
### Admin Auth Contract
|
|
69
|
+
|
|
70
|
+
`@questpie/admin` expects the app to use `adminModule`'s starter Better Auth model, or an equivalent auth setup that exposes `session.user.role` on the active session.
|
|
71
|
+
|
|
72
|
+
Access to the admin panel and admin RPC routes is granted only when `session.user.role === "admin"`. This includes admin config, content locale callbacks, preview URL/token generation, actions, widgets, and reactive field handlers. Authenticated users with `role: "user"` or a missing role are not admin users. The built-in setup route uses the same contract when checking for and creating the first admin user with `role = "admin"`.
|
|
73
|
+
|
|
74
|
+
If you extend the Better Auth `user` collection, merge the starter user collection instead of replacing it from scratch. Custom user collections must preserve the role field and session role propagation used by the admin guard.
|
|
75
|
+
|
|
68
76
|
### Collection Admin Config
|
|
69
77
|
|
|
70
78
|
Admin metadata, list views, and form views are defined on the collection itself:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BlockContent } from "./types.mjs";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react_jsx_runtime53 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/client/blocks/block-renderer.d.ts
|
|
6
6
|
|
|
@@ -50,6 +50,6 @@ declare function BlockRenderer({
|
|
|
50
50
|
onBlockClick,
|
|
51
51
|
onBlockInsert,
|
|
52
52
|
className
|
|
53
|
-
}: BlockRendererProps):
|
|
53
|
+
}: BlockRendererProps): react_jsx_runtime53.JSX.Element | null;
|
|
54
54
|
//#endregion
|
|
55
55
|
export { BlockRenderer, BlockRendererProps };
|
|
@@ -4,7 +4,7 @@ import { AnyWidgetConfig, WidgetAction, WidgetCardVariant, WidgetComponentProps,
|
|
|
4
4
|
import { DashboardAction, DashboardConfig, DashboardLayoutItem, DashboardSection, DashboardTabConfig, DashboardTabs } from "./types/ui-config.mjs";
|
|
5
5
|
import { AdminState } from "./admin-types.mjs";
|
|
6
6
|
import { Admin, AppAdmin, InferAdminCMS } from "./admin.mjs";
|
|
7
|
-
import { ComponentRegistry, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout } from "./types/field-types.mjs";
|
|
7
|
+
import { ComponentRegistry, DocumentViewConfig, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout } from "./types/field-types.mjs";
|
|
8
8
|
import { QuestpieApp, QuestpieClient } from "questpie/client";
|
|
9
9
|
import { CollectionInfer } from "questpie";
|
|
10
10
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import "../../i18n/types.mjs";
|
|
2
|
-
import { MaybeLazyComponent } from "./common.mjs";
|
|
1
|
+
import { I18nText } from "../../i18n/types.mjs";
|
|
2
|
+
import { IconComponent, MaybeLazyComponent } from "./common.mjs";
|
|
3
|
+
import { ComponentReference } from "../../../server/augmentation/common.mjs";
|
|
3
4
|
import { FilterRule, QuickFilterConfig } from "../../../shared/types/saved-views.types.mjs";
|
|
4
5
|
import "../../../server/augmentation.mjs";
|
|
5
|
-
import "../field/field.mjs";
|
|
6
|
-
import "../admin.mjs";
|
|
6
|
+
import { FieldDefinition } from "../field/field.mjs";
|
|
7
|
+
import { Admin } from "../admin.mjs";
|
|
7
8
|
import { ActionsConfig } from "./action-types.mjs";
|
|
8
9
|
|
|
9
10
|
//#region src/client/builder/types/collection-types.d.ts
|
|
@@ -202,5 +203,79 @@ interface ListViewConfig<TFieldNames extends string = string> {
|
|
|
202
203
|
*/
|
|
203
204
|
actions?: ActionsConfig;
|
|
204
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Preview configuration for live preview in form view
|
|
208
|
+
*/
|
|
209
|
+
interface PreviewConfig {
|
|
210
|
+
/**
|
|
211
|
+
* URL builder function that returns preview URL for current form values
|
|
212
|
+
* @param values - Current form values
|
|
213
|
+
* @param locale - Current content locale
|
|
214
|
+
* @returns Preview URL string
|
|
215
|
+
*/
|
|
216
|
+
url: (values: Record<string, unknown>, locale: string) => string;
|
|
217
|
+
/**
|
|
218
|
+
* Whether preview is enabled (default: true)
|
|
219
|
+
*/
|
|
220
|
+
enabled?: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Position of the preview panel (default: "right")
|
|
223
|
+
*/
|
|
224
|
+
position?: "right" | "bottom";
|
|
225
|
+
/**
|
|
226
|
+
* Default width/height percentage of preview panel (default: 50)
|
|
227
|
+
*/
|
|
228
|
+
defaultWidth?: number;
|
|
229
|
+
/**
|
|
230
|
+
* Minimum width/height percentage (default: 30)
|
|
231
|
+
*/
|
|
232
|
+
minWidth?: number;
|
|
233
|
+
/**
|
|
234
|
+
* Maximum width/height percentage (default: 70)
|
|
235
|
+
*/
|
|
236
|
+
maxWidth?: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Autosave configuration for form view
|
|
240
|
+
*/
|
|
241
|
+
interface AutoSaveConfig {
|
|
242
|
+
/**
|
|
243
|
+
* Whether autosave is enabled
|
|
244
|
+
* @default false
|
|
245
|
+
*/
|
|
246
|
+
enabled?: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Debounce delay in milliseconds before autosave triggers
|
|
249
|
+
* @default 500 (0.5s as specified)
|
|
250
|
+
*/
|
|
251
|
+
debounce?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Show autosave status indicator in form header
|
|
254
|
+
* @default true
|
|
255
|
+
*/
|
|
256
|
+
indicator?: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Warn user before navigating away with unsaved changes
|
|
259
|
+
* @default true
|
|
260
|
+
*/
|
|
261
|
+
preventNavigation?: boolean;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Collection builder state - internal state during building
|
|
265
|
+
*/
|
|
266
|
+
interface CollectionBuilderState<TAdminApp extends Admin<any> = Admin<any>> {
|
|
267
|
+
readonly name: string;
|
|
268
|
+
readonly "~adminApp": TAdminApp;
|
|
269
|
+
/** Display label - supports inline translations */
|
|
270
|
+
readonly label?: I18nText;
|
|
271
|
+
/** Description - supports inline translations */
|
|
272
|
+
readonly description?: I18nText;
|
|
273
|
+
readonly icon?: IconComponent | ComponentReference;
|
|
274
|
+
readonly fields?: Record<string, FieldDefinition>;
|
|
275
|
+
readonly list?: any;
|
|
276
|
+
readonly form?: any;
|
|
277
|
+
readonly preview?: PreviewConfig;
|
|
278
|
+
readonly autoSave?: AutoSaveConfig;
|
|
279
|
+
}
|
|
205
280
|
//#endregion
|
|
206
|
-
export { ListViewConfig };
|
|
281
|
+
export { CollectionBuilderState, ListViewConfig };
|
|
@@ -39,6 +39,11 @@ type BaseFieldProps = {
|
|
|
39
39
|
required?: boolean;
|
|
40
40
|
localized?: boolean;
|
|
41
41
|
locale?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Render the control WITHOUT its own label/description. Used by compact
|
|
44
|
+
* layouts (e.g. Notion-style property rows) that supply the label themselves.
|
|
45
|
+
*/
|
|
46
|
+
hideLabel?: boolean;
|
|
42
47
|
control?: any;
|
|
43
48
|
className?: string;
|
|
44
49
|
};
|
|
@@ -578,6 +578,46 @@ interface FormViewActionsConfig<TItem = any> {
|
|
|
578
578
|
/** Actions shown in dropdown menu (...) */
|
|
579
579
|
secondary?: ActionDefinition<TItem>[];
|
|
580
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Document view configuration — a Notion-style page with a dominant rich-text
|
|
583
|
+
* body and inline-editable property rows.
|
|
584
|
+
*
|
|
585
|
+
* No hardcoded field names: the consumer declares which field is the body, the
|
|
586
|
+
* (optional) title field, and which fields appear as properties. Defaults are
|
|
587
|
+
* data-driven (`properties: "auto"` derives from the schema).
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```ts
|
|
591
|
+
* .form(({ v, f }) => v.collectionDocument({
|
|
592
|
+
* document: {
|
|
593
|
+
* body: f.body,
|
|
594
|
+
* title: f.name,
|
|
595
|
+
* properties: [f.scopeType, f.path, f.createdAt],
|
|
596
|
+
* save: "autosave",
|
|
597
|
+
* },
|
|
598
|
+
* }))
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
interface DocumentViewConfig {
|
|
602
|
+
document: {
|
|
603
|
+
/** The dominant long-form field, rendered with the rich-text editor. */
|
|
604
|
+
body: string;
|
|
605
|
+
/**
|
|
606
|
+
* Property fields shown as inline-editable rows.
|
|
607
|
+
* - `"auto"` (default): all schema fields except body/title, in schema order.
|
|
608
|
+
* - `string[]`: an explicit, ordered list.
|
|
609
|
+
*/
|
|
610
|
+
properties?: "auto" | string[];
|
|
611
|
+
/**
|
|
612
|
+
* Save behavior.
|
|
613
|
+
* - `"autosave"` (default): debounced autosave with a "Saved"/"Saving" indicator.
|
|
614
|
+
* - `"manual"`: a Save affordance with dirty tracking.
|
|
615
|
+
*/
|
|
616
|
+
save?: "autosave" | "manual";
|
|
617
|
+
/** Optional page-title field; falls back to the record id/path. */
|
|
618
|
+
title?: string;
|
|
619
|
+
};
|
|
620
|
+
}
|
|
581
621
|
/**
|
|
582
622
|
* Registry for custom field and widget components
|
|
583
623
|
*/
|
|
@@ -596,4 +636,4 @@ interface ComponentRegistry {
|
|
|
596
636
|
custom?: Record<string, MaybeLazyComponent<any>>;
|
|
597
637
|
}
|
|
598
638
|
//#endregion
|
|
599
|
-
export { ComponentRegistry, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout };
|
|
639
|
+
export { ComponentRegistry, DocumentViewConfig, FieldComponentProps, FieldLayoutItem, FormSidebarConfig, FormViewConfig, SectionLayout, TabConfig, TabsLayout };
|
|
@@ -3,9 +3,10 @@ import { MaybeLazyComponent } from "../types/common.mjs";
|
|
|
3
3
|
//#region src/client/builder/view/view.d.ts
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* View kind discriminant — "list" for collection list pages, "form" for edit/create pages
|
|
6
|
+
* View kind discriminant — "list" for collection list pages, "form" for edit/create pages,
|
|
7
|
+
* "document" for Notion-style document pages (dominant rich-text body + property rows).
|
|
7
8
|
*/
|
|
8
|
-
type ViewKind = "list" | "form";
|
|
9
|
+
type ViewKind = "list" | "form" | "document";
|
|
9
10
|
/**
|
|
10
11
|
* View definition — a registry entry mapping a view name to its component.
|
|
11
12
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CollectionNames, GlobalNames } from "../builder/index.mjs";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react_jsx_runtime48 from "react/jsx-runtime";
|
|
4
4
|
import { QuestpieApp } from "questpie/client";
|
|
5
5
|
|
|
6
6
|
//#region src/client/components/admin-link.d.ts
|
|
@@ -61,6 +61,6 @@ declare function AdminLink<TApp extends QuestpieApp>({
|
|
|
61
61
|
children,
|
|
62
62
|
onClick,
|
|
63
63
|
...rest
|
|
64
|
-
}: AdminLinkProps<TApp>):
|
|
64
|
+
}: AdminLinkProps<TApp>): react_jsx_runtime48.JSX.Element;
|
|
65
65
|
//#endregion
|
|
66
66
|
export { AdminLink };
|
|
@@ -11,7 +11,7 @@ import { Controller } from "react-hook-form";
|
|
|
11
11
|
* Unified boolean field component.
|
|
12
12
|
* Renders as checkbox (default) or switch based on `displayAs` prop.
|
|
13
13
|
*/
|
|
14
|
-
function BooleanField({ name, label, description, required, disabled, localized, locale, control, className, displayAs = "checkbox" }) {
|
|
14
|
+
function BooleanField({ name, label, description, required, disabled, localized, locale, hideLabel, control, className, displayAs = "checkbox" }) {
|
|
15
15
|
return /* @__PURE__ */ jsx(Controller, {
|
|
16
16
|
name,
|
|
17
17
|
control: useResolvedControl(control),
|
|
@@ -23,6 +23,7 @@ function BooleanField({ name, label, description, required, disabled, localized,
|
|
|
23
23
|
disabled,
|
|
24
24
|
localized,
|
|
25
25
|
locale,
|
|
26
|
+
hideLabel,
|
|
26
27
|
error: fieldState.error?.message,
|
|
27
28
|
children: displayAs === "switch" ? /* @__PURE__ */ jsx(ToggleInput, {
|
|
28
29
|
id: name,
|
|
@@ -11,7 +11,7 @@ function parseDateFieldValue(value) {
|
|
|
11
11
|
const date = value instanceof Date ? value : new Date(String(value));
|
|
12
12
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
13
13
|
}
|
|
14
|
-
function DateField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, minDate, maxDate, format }) {
|
|
14
|
+
function DateField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, minDate, maxDate, format }) {
|
|
15
15
|
return /* @__PURE__ */ jsx(Controller, {
|
|
16
16
|
name,
|
|
17
17
|
control: useResolvedControl(control),
|
|
@@ -25,6 +25,7 @@ function DateField({ name, label, description, placeholder, required, disabled,
|
|
|
25
25
|
disabled,
|
|
26
26
|
localized,
|
|
27
27
|
locale,
|
|
28
|
+
hideLabel,
|
|
28
29
|
error: fieldState.error?.message,
|
|
29
30
|
children: /* @__PURE__ */ jsx(DateInput, {
|
|
30
31
|
id: name,
|
|
@@ -11,7 +11,7 @@ function parseDateTimeFieldValue(value) {
|
|
|
11
11
|
const date = value instanceof Date ? value : new Date(String(value));
|
|
12
12
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
13
13
|
}
|
|
14
|
-
function DatetimeField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, minDate, maxDate, format, precision }) {
|
|
14
|
+
function DatetimeField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, minDate, maxDate, format, precision }) {
|
|
15
15
|
return /* @__PURE__ */ jsx(Controller, {
|
|
16
16
|
name,
|
|
17
17
|
control: useResolvedControl(control),
|
|
@@ -25,6 +25,7 @@ function DatetimeField({ name, label, description, placeholder, required, disabl
|
|
|
25
25
|
disabled,
|
|
26
26
|
localized,
|
|
27
27
|
locale,
|
|
28
|
+
hideLabel,
|
|
28
29
|
error: fieldState.error?.message,
|
|
29
30
|
children: /* @__PURE__ */ jsx(DateTimeInput, {
|
|
30
31
|
id: name,
|
|
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
7
7
|
|
|
8
8
|
//#region src/client/components/fields/email-field.tsx
|
|
9
|
-
function EmailField({ name, label, description, placeholder, required, disabled, localized, locale, control, className }) {
|
|
9
|
+
function EmailField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className }) {
|
|
10
10
|
return /* @__PURE__ */ jsx(Controller, {
|
|
11
11
|
name,
|
|
12
12
|
control: useResolvedControl(control),
|
|
@@ -18,6 +18,7 @@ function EmailField({ name, label, description, placeholder, required, disabled,
|
|
|
18
18
|
disabled,
|
|
19
19
|
localized,
|
|
20
20
|
locale,
|
|
21
|
+
hideLabel,
|
|
21
22
|
error: fieldState.error?.message,
|
|
22
23
|
children: /* @__PURE__ */ jsx(TextInput, {
|
|
23
24
|
id: name,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "react-hook-form";
|
|
2
|
+
|
|
3
|
+
//#region src/client/components/fields/field-utils.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sanitize filename for safe storage.
|
|
7
|
+
* Removes special characters, replaces spaces with hyphens.
|
|
8
|
+
*/
|
|
9
|
+
declare function sanitizeFilename(filename: string): string;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { sanitizeFilename };
|
|
@@ -12,7 +12,9 @@ function useResolvedControl(control) {
|
|
|
12
12
|
function sanitizeFilename(filename) {
|
|
13
13
|
const lastDot = filename.lastIndexOf(".");
|
|
14
14
|
const ext = lastDot > 0 ? filename.slice(lastDot) : "";
|
|
15
|
-
|
|
15
|
+
const sanitized = (lastDot > 0 ? filename.slice(0, lastDot) : filename).normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\s+/g, "-").replace(/[^a-zA-Z0-9._-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
16
|
+
const safeExt = ext.toLowerCase().replace(/[^a-z0-9.]/g, "");
|
|
17
|
+
return (sanitized || "file") + safeExt;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Extract columns from collection list config.
|
|
@@ -36,10 +36,10 @@ function FieldLocaleIndicator({ localized, locale }) {
|
|
|
36
36
|
showFlag: false
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
function FieldWrapper({ name, label, description, labelAccessory, required, disabled, readOnly, error, localized, locale, children, fieldPath }) {
|
|
39
|
+
function FieldWrapper({ name, label, description, labelAccessory, required, disabled, readOnly, error, localized, locale, hideLabel, children, fieldPath }) {
|
|
40
40
|
const resolveText = useResolveText();
|
|
41
|
-
const resolvedLabel = label ? resolveText(label)
|
|
42
|
-
const resolvedDescription = description ? resolveText(description)
|
|
41
|
+
const resolvedLabel = hideLabel || !label ? void 0 : resolveText(label);
|
|
42
|
+
const resolvedDescription = hideLabel || !description ? void 0 : resolveText(description);
|
|
43
43
|
return /* @__PURE__ */ jsx(Field, {
|
|
44
44
|
"data-disabled": disabled,
|
|
45
45
|
"data-readonly": !disabled && readOnly,
|
|
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
7
7
|
|
|
8
8
|
//#region src/client/components/fields/number-field.tsx
|
|
9
|
-
function NumberField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, min, max, step, showButtons }) {
|
|
9
|
+
function NumberField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, min, max, step, showButtons }) {
|
|
10
10
|
return /* @__PURE__ */ jsx(Controller, {
|
|
11
11
|
name,
|
|
12
12
|
control: useResolvedControl(control),
|
|
@@ -18,6 +18,7 @@ function NumberField({ name, label, description, placeholder, required, disabled
|
|
|
18
18
|
disabled,
|
|
19
19
|
localized,
|
|
20
20
|
locale,
|
|
21
|
+
hideLabel,
|
|
21
22
|
error: fieldState.error?.message,
|
|
22
23
|
children: /* @__PURE__ */ jsx(NumberInput, {
|
|
23
24
|
id: name,
|
|
@@ -26,7 +26,8 @@ function ObjectFieldPanel({ name, label, description, required, disabled, locali
|
|
|
26
26
|
type: "button",
|
|
27
27
|
variant: "ghost",
|
|
28
28
|
onClick: onToggle,
|
|
29
|
-
className: "h-auto min-h-
|
|
29
|
+
className: "hover:bg-surface-low aria-expanded:bg-surface-low h-auto min-h-12 w-full justify-between rounded-none px-4 py-3 text-left",
|
|
30
|
+
"aria-expanded": !isCollapsed,
|
|
30
31
|
disabled,
|
|
31
32
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
32
33
|
className: "flex min-w-0 items-center gap-2",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatLabel } from "../../../../lib/utils.mjs";
|
|
2
|
+
import { resolveAssetUrl } from "../../../../utils/asset-url.mjs";
|
|
2
3
|
import { DefaultCell } from "../../../../views/collection/cells/primitive-cells.mjs";
|
|
3
4
|
|
|
4
5
|
//#region src/client/components/fields/relation/displays/types.ts
|
|
@@ -31,9 +32,8 @@ function formatCellValue(value) {
|
|
|
31
32
|
function getImageUrl(item, imageField) {
|
|
32
33
|
if (!imageField) return null;
|
|
33
34
|
const imageValue = item[imageField];
|
|
34
|
-
if (typeof imageValue === "string") return imageValue;
|
|
35
|
-
if (imageValue?.url) return imageValue.url;
|
|
36
|
-
if (imageValue?.key) return imageValue.key;
|
|
35
|
+
if (typeof imageValue === "string") return resolveAssetUrl(imageValue) ?? null;
|
|
36
|
+
if (imageValue?.url) return resolveAssetUrl(imageValue.url) ?? null;
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
@@ -55,12 +55,13 @@ const defaultLabels = {
|
|
|
55
55
|
* (i.e. codeBlock disabled, or lowlight already cached). Only returns a
|
|
56
56
|
* `Promise` on the first build that includes codeBlock.
|
|
57
57
|
*/
|
|
58
|
-
function buildExtensions({ features, labels, placeholder, maxCharacters, customExtensions }) {
|
|
58
|
+
function buildExtensions({ features, labels, placeholder, maxCharacters, outputMode, customExtensions }) {
|
|
59
59
|
const base = buildBaseExtensions({
|
|
60
60
|
features,
|
|
61
61
|
labels,
|
|
62
62
|
placeholder,
|
|
63
63
|
maxCharacters,
|
|
64
|
+
outputMode,
|
|
64
65
|
customExtensions
|
|
65
66
|
});
|
|
66
67
|
if (!features.codeBlock) return base;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useTranslation } from "../../../i18n/hooks.mjs";
|
|
2
2
|
import { Button } from "../../ui/button.mjs";
|
|
3
3
|
import { Input } from "../../ui/input.mjs";
|
|
4
|
+
import { resolveAssetUrl } from "../../../utils/asset-url.mjs";
|
|
4
5
|
import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "../../ui/popover.mjs";
|
|
5
6
|
import { MediaPickerDialog } from "../../media/media-picker-dialog.mjs";
|
|
6
7
|
import { useRichTextImageUpload } from "./image-upload.mjs";
|
|
@@ -76,7 +77,7 @@ function ImagePopover({ editor, open, onOpenChange, disabled, onImageUpload, ima
|
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
editor.chain().focus().setImage({
|
|
79
|
-
src: asset.url,
|
|
80
|
+
src: resolveAssetUrl(asset.url) ?? asset.url,
|
|
80
81
|
alt: imageAlt || asset.alt || void 0
|
|
81
82
|
}).run();
|
|
82
83
|
setImageUrl("");
|
|
@@ -95,7 +96,10 @@ function ImagePopover({ editor, open, onOpenChange, disabled, onImageUpload, ima
|
|
|
95
96
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Popover, {
|
|
96
97
|
open,
|
|
97
98
|
onOpenChange,
|
|
98
|
-
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
99
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
100
|
+
nativeButton: false,
|
|
101
|
+
render: /* @__PURE__ */ jsx("div", { className: "sr-only" })
|
|
102
|
+
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
99
103
|
className: "w-80",
|
|
100
104
|
children: [/* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsx(PopoverTitle, { children: t("editor.image") }) }), /* @__PURE__ */ jsxs("div", {
|
|
101
105
|
className: "space-y-3",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useTranslation } from "../../../i18n/hooks.mjs";
|
|
2
2
|
import { sanitizeFilename } from "../field-utils.mjs";
|
|
3
|
+
import { resolveAssetUrl } from "../../../utils/asset-url.mjs";
|
|
3
4
|
import { useUploadCollection } from "../../../hooks/use-upload-collection.mjs";
|
|
4
5
|
import { useUpload } from "../../../hooks/use-upload.mjs";
|
|
5
6
|
import * as React from "react";
|
|
@@ -35,7 +36,7 @@ function useRichTextImageUpload({ imageCollection, onImageUpload }) {
|
|
|
35
36
|
const sanitizedName = sanitizeFilename(file.name);
|
|
36
37
|
const uploadedAsset = await upload(sanitizedName === file.name ? file : new File([file], sanitizedName, { type: file.type }), { to: collection });
|
|
37
38
|
if (!uploadedAsset?.url) throw new Error(t("upload.error"));
|
|
38
|
-
return uploadedAsset.url;
|
|
39
|
+
return resolveAssetUrl(uploadedAsset.url) ?? uploadedAsset.url;
|
|
39
40
|
}, [
|
|
40
41
|
collection,
|
|
41
42
|
collections,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RichTextEditorProps } from "./types.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime33 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/client/components/fields/rich-text-editor/index.d.ts
|
|
5
5
|
|
|
@@ -24,6 +24,7 @@ declare function RichTextEditor({
|
|
|
24
24
|
error,
|
|
25
25
|
localized,
|
|
26
26
|
locale,
|
|
27
|
+
hideLabel,
|
|
27
28
|
extensions,
|
|
28
29
|
preset,
|
|
29
30
|
features,
|
|
@@ -34,6 +35,6 @@ declare function RichTextEditor({
|
|
|
34
35
|
imageCollection,
|
|
35
36
|
enableMediaLibrary,
|
|
36
37
|
outputMode
|
|
37
|
-
}: RichTextEditorProps):
|
|
38
|
+
}: RichTextEditorProps): react_jsx_runtime33.JSX.Element;
|
|
38
39
|
//#endregion
|
|
39
40
|
export { RichTextEditor };
|
|
@@ -66,7 +66,7 @@ function RichTextEditorLoadingSkeleton({ disabled, readOnly }) {
|
|
|
66
66
|
* `useEditor` with an empty extension list which causes the ProseMirror
|
|
67
67
|
* "Schema is missing its top node type" error.
|
|
68
68
|
*/
|
|
69
|
-
function RichTextEditor({ name, value, onChange, disabled, readOnly, label, description, placeholder, required, error, localized, locale, extensions, preset, features, showCharacterCount, maxCharacters, enableImages, onImageUpload, imageCollection, enableMediaLibrary, outputMode }) {
|
|
69
|
+
function RichTextEditor({ name, value, onChange, disabled, readOnly, label, description, placeholder, required, error, localized, locale, hideLabel, extensions, preset, features, showCharacterCount, maxCharacters, enableImages, onImageUpload, imageCollection, enableMediaLibrary, outputMode }) {
|
|
70
70
|
const { t } = useTranslation();
|
|
71
71
|
const resolveText = useResolveText();
|
|
72
72
|
const resolvedLabel = label ? resolveText(label) : void 0;
|
|
@@ -137,7 +137,7 @@ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, desc
|
|
|
137
137
|
className: "space-y-2",
|
|
138
138
|
"data-disabled": disabled || readOnly,
|
|
139
139
|
children: [
|
|
140
|
-
resolvedLabel && /* @__PURE__ */ jsxs("div", {
|
|
140
|
+
!hideLabel && resolvedLabel && /* @__PURE__ */ jsxs("div", {
|
|
141
141
|
className: "flex items-center gap-2",
|
|
142
142
|
children: [/* @__PURE__ */ jsxs(Label, {
|
|
143
143
|
htmlFor: name,
|
|
@@ -193,7 +193,7 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
|
|
|
193
193
|
const [linkOpen, setLinkOpen] = React.useState(false);
|
|
194
194
|
const [imageOpen, setImageOpen] = React.useState(false);
|
|
195
195
|
const [uploadingInlineImage, setUploadingInlineImage] = React.useState(false);
|
|
196
|
-
const lastEmittedValueRef = React.useRef(
|
|
196
|
+
const lastEmittedValueRef = React.useRef(value);
|
|
197
197
|
const editorRef = React.useRef(null);
|
|
198
198
|
const allowImages = features.image && (enableImages ?? true);
|
|
199
199
|
const allowLinks = features.link;
|
|
@@ -312,6 +312,7 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
|
|
|
312
312
|
onUpdate: ({ editor: currentEditor }) => {
|
|
313
313
|
if (disabled || readOnly) return;
|
|
314
314
|
const nextValue = getOutput(currentEditor, outputMode);
|
|
315
|
+
if (isSameValue(nextValue, lastEmittedValueRef.current)) return;
|
|
315
316
|
lastEmittedValueRef.current = nextValue;
|
|
316
317
|
onChange?.(nextValue);
|
|
317
318
|
}
|
|
@@ -23,7 +23,7 @@ function resolveOptionIcons(options) {
|
|
|
23
23
|
icon: resolveIconElement(option.icon)
|
|
24
24
|
} : option);
|
|
25
25
|
}
|
|
26
|
-
function SelectField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, options, loadOptions, multiple, clearable, maxSelections, emptyMessage }) {
|
|
26
|
+
function SelectField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, options, loadOptions, multiple, clearable, maxSelections, emptyMessage }) {
|
|
27
27
|
const resolvedControl = useResolvedControl(control);
|
|
28
28
|
const resolvedOptions = useMemo(() => resolveOptionIcons(options), [options]);
|
|
29
29
|
const resolvedLoadOptions = useMemo(() => {
|
|
@@ -43,6 +43,7 @@ function SelectField({ name, label, description, placeholder, required, disabled
|
|
|
43
43
|
disabled,
|
|
44
44
|
localized,
|
|
45
45
|
locale,
|
|
46
|
+
hideLabel,
|
|
46
47
|
error: fieldState.error?.message,
|
|
47
48
|
children: multiple ? /* @__PURE__ */ jsx(SelectMulti, {
|
|
48
49
|
id: name,
|
|
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
7
7
|
|
|
8
8
|
//#region src/client/components/fields/text-field.tsx
|
|
9
|
-
function TextField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, type = "text", maxLength, autoComplete }) {
|
|
9
|
+
function TextField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, type = "text", maxLength, autoComplete }) {
|
|
10
10
|
return /* @__PURE__ */ jsx(Controller, {
|
|
11
11
|
name,
|
|
12
12
|
control: useResolvedControl(control),
|
|
@@ -18,6 +18,7 @@ function TextField({ name, label, description, placeholder, required, disabled,
|
|
|
18
18
|
disabled,
|
|
19
19
|
localized,
|
|
20
20
|
locale,
|
|
21
|
+
hideLabel,
|
|
21
22
|
error: fieldState.error?.message,
|
|
22
23
|
children: /* @__PURE__ */ jsx(TextInput, {
|
|
23
24
|
id: name,
|
|
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
7
7
|
|
|
8
8
|
//#region src/client/components/fields/textarea-field.tsx
|
|
9
|
-
function TextareaField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, rows, maxLength, autoResize }) {
|
|
9
|
+
function TextareaField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, rows, maxLength, autoResize }) {
|
|
10
10
|
return /* @__PURE__ */ jsx(Controller, {
|
|
11
11
|
name,
|
|
12
12
|
control: useResolvedControl(control),
|
|
@@ -18,6 +18,7 @@ function TextareaField({ name, label, description, placeholder, required, disabl
|
|
|
18
18
|
disabled,
|
|
19
19
|
localized,
|
|
20
20
|
locale,
|
|
21
|
+
hideLabel,
|
|
21
22
|
error: fieldState.error?.message,
|
|
22
23
|
children: /* @__PURE__ */ jsx(TextareaInput, {
|
|
23
24
|
id: name,
|
|
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
7
7
|
|
|
8
8
|
//#region src/client/components/fields/time-field.tsx
|
|
9
|
-
function TimeField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, precision }) {
|
|
9
|
+
function TimeField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, precision }) {
|
|
10
10
|
return /* @__PURE__ */ jsx(Controller, {
|
|
11
11
|
name,
|
|
12
12
|
control: useResolvedControl(control),
|
|
@@ -19,6 +19,7 @@ function TimeField({ name, label, description, placeholder, required, disabled,
|
|
|
19
19
|
disabled,
|
|
20
20
|
localized,
|
|
21
21
|
locale,
|
|
22
|
+
hideLabel,
|
|
22
23
|
error: fieldState.error?.message,
|
|
23
24
|
children: /* @__PURE__ */ jsx(TimeInput, {
|
|
24
25
|
id: name,
|
|
@@ -96,18 +96,18 @@ function SectionRenderer({ section, index, ctx }) {
|
|
|
96
96
|
const value = `section-${index}`;
|
|
97
97
|
return /* @__PURE__ */ jsx(Accordion, {
|
|
98
98
|
defaultValue: section.defaultCollapsed !== true ? [value] : [],
|
|
99
|
-
className: "
|
|
99
|
+
className: "qa-field-layout__section qa-field-layout__section--collapsible panel-surface bg-card overflow-hidden",
|
|
100
100
|
children: /* @__PURE__ */ jsxs(AccordionItem, {
|
|
101
101
|
value,
|
|
102
|
-
className: "border-
|
|
102
|
+
className: "border-none px-0 data-open:bg-transparent",
|
|
103
103
|
children: [/* @__PURE__ */ jsx(AccordionTrigger, {
|
|
104
|
-
className: "hover:no-underline",
|
|
104
|
+
className: "hover:bg-surface-low aria-expanded:bg-surface-low min-h-12 px-4 py-3 hover:no-underline",
|
|
105
105
|
children: /* @__PURE__ */ jsx("span", {
|
|
106
106
|
className: "font-semibold",
|
|
107
107
|
children: ctx.resolveText(section.label, "Section")
|
|
108
108
|
})
|
|
109
109
|
}), /* @__PURE__ */ jsxs(AccordionContent, {
|
|
110
|
-
className: "pt-
|
|
110
|
+
className: "border-border-subtle border-t px-4 pt-3 pb-4",
|
|
111
111
|
children: [section.description && /* @__PURE__ */ jsx("p", {
|
|
112
112
|
className: "text-muted-foreground mb-4 text-sm text-pretty",
|
|
113
113
|
children: ctx.resolveText(section.description, "")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useSafeI18n } from "../../i18n/hooks.mjs";
|
|
2
2
|
import { cn } from "../../lib/utils.mjs";
|
|
3
|
+
import { resolveAssetUrl } from "../../utils/asset-url.mjs";
|
|
3
4
|
import { Skeleton } from "../ui/skeleton.mjs";
|
|
4
5
|
import { Icon } from "@iconify/react";
|
|
5
6
|
import * as React from "react";
|
|
@@ -62,7 +63,7 @@ function MediaGridSkeleton({ columns = 4 }) {
|
|
|
62
63
|
}
|
|
63
64
|
function AssetItem({ asset, selected, selectionMode, onToggle, onClick }) {
|
|
64
65
|
const [imageError, setImageError] = React.useState(false);
|
|
65
|
-
const thumbnailUrl = asset.url;
|
|
66
|
+
const thumbnailUrl = resolveAssetUrl(asset.url);
|
|
66
67
|
const isImageType = isImage(asset.mimeType);
|
|
67
68
|
const showCheckbox = selectionMode !== "none";
|
|
68
69
|
const handleClick = () => {
|