@riducms/admin 0.1.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/LICENSE +21 -0
- package/README.md +31 -0
- package/THIRD_PARTY_NOTICES.md +217 -0
- package/package.json +62 -0
- package/src/app/admin-core-view-route.svelte +89 -0
- package/src/app/admin-layout.svelte +457 -0
- package/src/app/admin-navigation-progress.svelte +204 -0
- package/src/app/admin-page-title.svelte +94 -0
- package/src/app/admin-provider-layer.svelte +26 -0
- package/src/app/admin-router-root.svelte +9 -0
- package/src/app/admin-routes.svelte +116 -0
- package/src/app/deferred-redirect.svelte +23 -0
- package/src/app/loading-boundary.svelte +70 -0
- package/src/app/not-found-route.svelte +29 -0
- package/src/app-meta.ts +2 -0
- package/src/app.css +467 -0
- package/src/app.svelte +88 -0
- package/src/assets/ridu-logo-arch.svg +3 -0
- package/src/assets/ridu-word-logo.svg +7 -0
- package/src/components/brand/ridu-logo.svelte +24 -0
- package/src/components/code-block/code-block.svelte +107 -0
- package/src/components/json-tree/json-tree-node.svelte +77 -0
- package/src/components/json-tree/json-viewer.svelte +113 -0
- package/src/components/ui/banner/banner.svelte +41 -0
- package/src/components/ui/banner/index.ts +5 -0
- package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +50 -0
- package/src/components/ui/bulk-action-bar/index.ts +1 -0
- package/src/components/ui/button/index.ts +8 -0
- package/src/components/ui/calendar/calendar-caption.svelte +72 -0
- package/src/components/ui/calendar/calendar-cell.svelte +20 -0
- package/src/components/ui/calendar/calendar-day.svelte +27 -0
- package/src/components/ui/calendar/calendar-grid-row.svelte +13 -0
- package/src/components/ui/calendar/calendar-grid.svelte +17 -0
- package/src/components/ui/calendar/calendar-head-cell.svelte +20 -0
- package/src/components/ui/calendar/calendar-header.svelte +20 -0
- package/src/components/ui/calendar/calendar-heading.svelte +17 -0
- package/src/components/ui/calendar/calendar-month-select.svelte +45 -0
- package/src/components/ui/calendar/calendar-month.svelte +16 -0
- package/src/components/ui/calendar/calendar-months.svelte +20 -0
- package/src/components/ui/calendar/calendar-nav.svelte +20 -0
- package/src/components/ui/calendar/calendar-next-button.svelte +30 -0
- package/src/components/ui/calendar/calendar-prev-button.svelte +30 -0
- package/src/components/ui/calendar/calendar-year-select.svelte +44 -0
- package/src/components/ui/calendar/calendar.svelte +118 -0
- package/src/components/ui/calendar/index.ts +1 -0
- package/src/components/ui/checkbox/checkbox.svelte +35 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/confirmation-dialog/confirmation-dialog.svelte +37 -0
- package/src/components/ui/confirmation-dialog/index.ts +1 -0
- package/src/components/ui/date-value-control/date-value-calendar.svelte +37 -0
- package/src/components/ui/date-value-control/date-value-control-loader.svelte +36 -0
- package/src/components/ui/date-value-control/date-value-control.svelte +177 -0
- package/src/components/ui/date-value-control/index.ts +3 -0
- package/src/components/ui/dialog/dialog-close.svelte +11 -0
- package/src/components/ui/dialog/dialog-content.svelte +52 -0
- package/src/components/ui/dialog/dialog-description.svelte +20 -0
- package/src/components/ui/dialog/dialog-footer.svelte +20 -0
- package/src/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/components/ui/dialog/dialog-overlay.svelte +20 -0
- package/src/components/ui/dialog/dialog-title.svelte +17 -0
- package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/src/components/ui/dialog/dialog.svelte +7 -0
- package/src/components/ui/dialog/index.ts +8 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +27 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +16 -0
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/src/components/ui/dropdown-menu/index.ts +5 -0
- package/src/components/ui/empty-state/empty-state.svelte +34 -0
- package/src/components/ui/empty-state/index.ts +1 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/kbd/index.ts +1 -0
- package/src/components/ui/kbd/kbd.svelte +24 -0
- package/src/components/ui/pagination/index.ts +1 -0
- package/src/components/ui/pagination/pagination.svelte +88 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/popover/popover-content.svelte +29 -0
- package/src/components/ui/popover/popover-trigger.svelte +11 -0
- package/src/components/ui/popover/popover.svelte +7 -0
- package/src/components/ui/radio-group/index.ts +3 -0
- package/src/components/ui/radio-group/radio-card-item.svelte +38 -0
- package/src/components/ui/radio-group/radio-group-item.svelte +17 -0
- package/src/components/ui/radio-group/radio-group.svelte +19 -0
- package/src/components/ui/select/index.ts +4 -0
- package/src/components/ui/select/select-content.svelte +38 -0
- package/src/components/ui/select/select-item.svelte +40 -0
- package/src/components/ui/select/select-scroll-down-button.svelte +23 -0
- package/src/components/ui/select/select-scroll-up-button.svelte +23 -0
- package/src/components/ui/select/select-trigger.svelte +29 -0
- package/src/components/ui/select/select.svelte +11 -0
- package/src/components/ui/sheet/index.ts +6 -0
- package/src/components/ui/sheet/sheet-close.svelte +7 -0
- package/src/components/ui/sheet/sheet-content.svelte +77 -0
- package/src/components/ui/sheet/sheet-description.svelte +17 -0
- package/src/components/ui/sheet/sheet-overlay.svelte +36 -0
- package/src/components/ui/sheet/sheet-title.svelte +17 -0
- package/src/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/src/components/ui/sheet/sheet.svelte +7 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/skeleton/skeleton.svelte +31 -0
- package/src/components/ui/slider/index.ts +3 -0
- package/src/components/ui/slider/slider-content.svelte +59 -0
- package/src/components/ui/slider/slider.svelte +35 -0
- package/src/components/ui/sonner/index.ts +1 -0
- package/src/components/ui/sonner/sonner.svelte +55 -0
- package/src/components/ui/status-indicator/index.ts +5 -0
- package/src/components/ui/status-indicator/status-indicator.svelte +54 -0
- package/src/components/ui/table/index.ts +6 -0
- package/src/components/ui/table/table-body.svelte +20 -0
- package/src/components/ui/table/table-cell.svelte +23 -0
- package/src/components/ui/table/table-head.svelte +23 -0
- package/src/components/ui/table/table-header.svelte +20 -0
- package/src/components/ui/table/table-row.svelte +23 -0
- package/src/components/ui/table/table.svelte +22 -0
- package/src/components/ui/tabs/index.ts +8 -0
- package/src/components/ui/tabs/tabs-content.svelte +17 -0
- package/src/components/ui/tabs/tabs-list.svelte +40 -0
- package/src/components/ui/tabs/tabs-trigger.svelte +23 -0
- package/src/components/ui/tabs/tabs.svelte +19 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/components/ui/tooltip/tooltip-content.svelte +50 -0
- package/src/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/src/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/src/components/ui/tooltip/tooltip.svelte +7 -0
- package/src/core/api/admin-client.ts +45 -0
- package/src/core/forms/field-condition.ts +63 -0
- package/src/core/forms/form-controller.svelte.ts +369 -0
- package/src/core/forms/form-draft-recovery.ts +80 -0
- package/src/core/forms/form-schema.ts +609 -0
- package/src/core/forms/form-validation.ts +750 -0
- package/src/core/i18n/admin-i18n.svelte.ts +364 -0
- package/src/core/i18n/localized-schema.ts +111 -0
- package/src/core/notifications/notification-center.svelte.ts +69 -0
- package/src/core/notifications/validation-toast.svelte +18 -0
- package/src/core/plugins/field-registry.ts +60 -0
- package/src/core/plugins/row-label-registry.ts +62 -0
- package/src/core/preferences/preference-write-queue.ts +107 -0
- package/src/core/routing/admin-paths.ts +137 -0
- package/src/core/runtime/admin-runtime.svelte.ts +625 -0
- package/src/features/auth/account-menu.svelte +159 -0
- package/src/features/auth/account-navigation.svelte +44 -0
- package/src/features/auth/account-route.svelte +395 -0
- package/src/features/auth/account-security-controller.svelte.ts +119 -0
- package/src/features/auth/account-security-route.svelte +460 -0
- package/src/features/auth/auth-brand.svelte +17 -0
- package/src/features/auth/auth-frame.svelte +18 -0
- package/src/features/auth/create-first-user-controller.svelte.ts +220 -0
- package/src/features/auth/create-first-user-route.svelte +174 -0
- package/src/features/auth/forgot-password-route.svelte +78 -0
- package/src/features/auth/login-route.svelte +221 -0
- package/src/features/auth/request-verification-route.svelte +73 -0
- package/src/features/auth/reset-password-route.svelte +107 -0
- package/src/features/auth/verify-email-route.svelte +60 -0
- package/src/features/collections/collection-list-bulk-actions.svelte +143 -0
- package/src/features/collections/collection-list-bulk-editor.svelte +157 -0
- package/src/features/collections/collection-list-controller.svelte.ts +717 -0
- package/src/features/collections/collection-list-filter-popover.svelte +238 -0
- package/src/features/collections/collection-list-preferences-controller.svelte.ts +393 -0
- package/src/features/collections/collection-list-results.svelte +410 -0
- package/src/features/collections/collection-list-route.svelte +951 -0
- package/src/features/collections/collection-list-workspace-toolbar.svelte +409 -0
- package/src/features/collections/collection-table-skeleton.svelte +44 -0
- package/src/features/collections/list-workspace.ts +265 -0
- package/src/features/dashboard/dashboard-route.svelte +141 -0
- package/src/features/development/development-tools.svelte +217 -0
- package/src/features/documents/document-api-view-loader.svelte +31 -0
- package/src/features/documents/document-api-view.svelte +547 -0
- package/src/features/documents/document-controller.svelte.ts +1252 -0
- package/src/features/documents/document-field-partition.ts +18 -0
- package/src/features/documents/document-field-sections.svelte +46 -0
- package/src/features/documents/document-heading.ts +20 -0
- package/src/features/documents/document-route-dirty.ts +43 -0
- package/src/features/documents/document-route-loader.svelte +22 -0
- package/src/features/documents/document-route.svelte +1124 -0
- package/src/features/documents/live-preview-panel.svelte +390 -0
- package/src/features/documents/live-preview.ts +61 -0
- package/src/features/documents/upload-document-preview-loader.svelte +40 -0
- package/src/features/documents/upload-document-preview.svelte +525 -0
- package/src/features/localization/content-locale-switcher.svelte +93 -0
- package/src/features/navigation/admin-breadcrumbs.svelte +124 -0
- package/src/features/navigation/admin-command-menu.svelte +245 -0
- package/src/features/navigation/admin-resource-groups.ts +72 -0
- package/src/features/reference-browser/reference-browser-loader.svelte +15 -0
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +691 -0
- package/src/features/reference-browser/reference-browser.svelte +844 -0
- package/src/features/uploads/bulk-upload-route.svelte +624 -0
- package/src/features/uploads/bulk-upload.ts +39 -0
- package/src/features/uploads/upload-metadata-fields.svelte +112 -0
- package/src/features/versions/version-diff.ts +72 -0
- package/src/features/versions/version-history-route.svelte +581 -0
- package/src/fields/field-clipboard.ts +132 -0
- package/src/fields/field-layout.svelte +60 -0
- package/src/fields/field-layout.ts +57 -0
- package/src/fields/field-messages.svelte +29 -0
- package/src/fields/field-renderer.svelte +152 -0
- package/src/fields/field-shell.svelte +30 -0
- package/src/fields/field-tab-layout.svelte +99 -0
- package/src/fields/join/join-field.svelte +358 -0
- package/src/fields/localized-field-presentation.ts +17 -0
- package/src/fields/nested/nested-field.svelte +602 -0
- package/src/fields/nested/scoped-field.ts +78 -0
- package/src/fields/nested/sortable-row.svelte +32 -0
- package/src/fields/point/point-field.svelte +52 -0
- package/src/fields/radio/radio-field.svelte +34 -0
- package/src/fields/relationship/relationship-field-controller.svelte.ts +245 -0
- package/src/fields/relationship/relationship-field.svelte +258 -0
- package/src/fields/relationship/relationship-lookup-controller.svelte.ts +129 -0
- package/src/fields/relationship/relationship-query.ts +88 -0
- package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +198 -0
- package/src/fields/relationship/relationship-quick-picker.svelte +327 -0
- package/src/fields/relationship/relationship-value.ts +47 -0
- package/src/fields/scalar/date-control-value.ts +60 -0
- package/src/fields/scalar/date-value.ts +26 -0
- package/src/fields/scalar/scalar-field.svelte +137 -0
- package/src/fields/select/select-field.svelte +132 -0
- package/src/fields/select/select-value.ts +14 -0
- package/src/fields/text/slug-field.svelte +87 -0
- package/src/fields/text/slug.ts +38 -0
- package/src/fields/text/text-field.svelte +41 -0
- package/src/fields/ui/ui-field.svelte +12 -0
- package/src/fields/virtual/virtual-field.svelte +28 -0
- package/src/index.ts +49 -0
- package/src/main.ts +12 -0
- package/src/vite-env.d.ts +9 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { SchemaCollection, SchemaField } from "@riducms/protocol";
|
|
2
|
+
import type { AdminI18n } from "@riducms/translations";
|
|
3
|
+
|
|
4
|
+
export const listPageSizes = [10, 25, 50, 100] as const;
|
|
5
|
+
|
|
6
|
+
export type ListPageSize = (typeof listPageSizes)[number];
|
|
7
|
+
export type ListFilterOperator =
|
|
8
|
+
| "equals"
|
|
9
|
+
| "notEquals"
|
|
10
|
+
| "like"
|
|
11
|
+
| "contains"
|
|
12
|
+
| "greaterThan"
|
|
13
|
+
| "greaterThanEqual"
|
|
14
|
+
| "lessThan"
|
|
15
|
+
| "lessThanEqual"
|
|
16
|
+
| "exists";
|
|
17
|
+
|
|
18
|
+
export interface ListFilter {
|
|
19
|
+
field: string;
|
|
20
|
+
operator: ListFilterOperator;
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ListWorkspacePreference {
|
|
25
|
+
columns: string[];
|
|
26
|
+
showStatus: boolean;
|
|
27
|
+
showID: boolean;
|
|
28
|
+
showCreated: boolean;
|
|
29
|
+
showUpdated: boolean;
|
|
30
|
+
limit: ListPageSize;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const textOperators: readonly ListFilterOperator[] = [
|
|
34
|
+
"like",
|
|
35
|
+
"equals",
|
|
36
|
+
"notEquals",
|
|
37
|
+
"contains",
|
|
38
|
+
"exists",
|
|
39
|
+
];
|
|
40
|
+
const orderedOperators: readonly ListFilterOperator[] = [
|
|
41
|
+
"equals",
|
|
42
|
+
"notEquals",
|
|
43
|
+
"greaterThan",
|
|
44
|
+
"greaterThanEqual",
|
|
45
|
+
"lessThan",
|
|
46
|
+
"lessThanEqual",
|
|
47
|
+
"exists",
|
|
48
|
+
];
|
|
49
|
+
const identityOperators: readonly ListFilterOperator[] = ["equals", "notEquals", "exists"];
|
|
50
|
+
|
|
51
|
+
export function listColumnFields(collection: SchemaCollection | undefined, titleName?: string) {
|
|
52
|
+
return (collection?.fields ?? []).flatMap((field) => {
|
|
53
|
+
if (field.name === titleName || field.type === "ui") return [];
|
|
54
|
+
if (field.type === "group") return nestedFields(field, "columns");
|
|
55
|
+
return [field];
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function filterableFields(fields: readonly SchemaField[]) {
|
|
60
|
+
return fields.flatMap((field) => {
|
|
61
|
+
if (field.type === "group" || field.type === "array" || field.type === "blocks") {
|
|
62
|
+
return nestedFields(field, "filters");
|
|
63
|
+
}
|
|
64
|
+
return filterableLeaf(field) ? [field] : [];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function sortableField(field: SchemaField) {
|
|
69
|
+
return (
|
|
70
|
+
field.virtual === undefined &&
|
|
71
|
+
field.join === undefined &&
|
|
72
|
+
field.type !== "array" &&
|
|
73
|
+
field.type !== "blocks" &&
|
|
74
|
+
field.type !== "group" &&
|
|
75
|
+
field.type !== "json" &&
|
|
76
|
+
field.plugin === undefined &&
|
|
77
|
+
field.type !== "point" &&
|
|
78
|
+
field.type !== "code"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function defaultListColumns(
|
|
83
|
+
collection: SchemaCollection | undefined,
|
|
84
|
+
fields: readonly SchemaField[]
|
|
85
|
+
) {
|
|
86
|
+
const byName = new Map(fields.map((field) => [field.path, field.path]));
|
|
87
|
+
for (const field of fields) {
|
|
88
|
+
if (field.path === field.name && !byName.has(field.name)) byName.set(field.name, field.path);
|
|
89
|
+
}
|
|
90
|
+
const configured = (collection?.admin.defaultColumns ?? []).flatMap((name) => {
|
|
91
|
+
const path = byName.get(name);
|
|
92
|
+
return path === undefined ? [] : [path];
|
|
93
|
+
});
|
|
94
|
+
if (configured.length > 0) return configured;
|
|
95
|
+
return fields.slice(0, 2).map((field) => field.path);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function filterOperatorsFor(field: SchemaField): readonly ListFilterOperator[] {
|
|
99
|
+
if (field.type === "number" || field.type === "date") return orderedOperators;
|
|
100
|
+
if (field.type === "text" || field.type === "textarea" || field.type === "email") {
|
|
101
|
+
return textOperators;
|
|
102
|
+
}
|
|
103
|
+
return identityOperators;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function filterOperatorLabel(operator: ListFilterOperator, i18n: AdminI18n) {
|
|
107
|
+
const labels: Record<ListFilterOperator, Parameters<AdminI18n["t"]>[0]> = {
|
|
108
|
+
equals: "collections:operatorEquals",
|
|
109
|
+
notEquals: "collections:operatorNotEquals",
|
|
110
|
+
like: "collections:operatorLike",
|
|
111
|
+
contains: "collections:operatorContains",
|
|
112
|
+
greaterThan: "collections:operatorGreaterThan",
|
|
113
|
+
greaterThanEqual: "collections:operatorGreaterThanEqual",
|
|
114
|
+
lessThan: "collections:operatorLessThan",
|
|
115
|
+
lessThanEqual: "collections:operatorLessThanEqual",
|
|
116
|
+
exists: "collections:operatorExists",
|
|
117
|
+
};
|
|
118
|
+
return i18n.t(labels[operator]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function normalizeListFilters(value: unknown, fields: readonly SchemaField[]): ListFilter[] {
|
|
122
|
+
if (!Array.isArray(value)) return [];
|
|
123
|
+
const byName = new Map(fields.map((field) => [field.path, field]));
|
|
124
|
+
return value.flatMap((candidate) => {
|
|
125
|
+
if (typeof candidate !== "object" || candidate === null) return [];
|
|
126
|
+
const fieldName = Reflect.get(candidate, "field");
|
|
127
|
+
const operator = Reflect.get(candidate, "operator");
|
|
128
|
+
const rawValue = Reflect.get(candidate, "value");
|
|
129
|
+
if (typeof fieldName !== "string" || typeof operator !== "string") return [];
|
|
130
|
+
const field = byName.get(fieldName);
|
|
131
|
+
if (
|
|
132
|
+
field === undefined ||
|
|
133
|
+
!filterOperatorsFor(field).includes(operator as ListFilterOperator)
|
|
134
|
+
) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
field: fieldName,
|
|
140
|
+
operator: operator as ListFilterOperator,
|
|
141
|
+
value:
|
|
142
|
+
typeof rawValue === "string" ||
|
|
143
|
+
typeof rawValue === "number" ||
|
|
144
|
+
typeof rawValue === "boolean"
|
|
145
|
+
? String(rawValue)
|
|
146
|
+
: "",
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function parseListFilters(encoded: string | null, fields: readonly SchemaField[]) {
|
|
153
|
+
if (encoded === null || encoded === "") return [];
|
|
154
|
+
try {
|
|
155
|
+
return normalizeListFilters(JSON.parse(encoded), fields);
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function buildListFilterWhere(
|
|
162
|
+
filters: readonly ListFilter[],
|
|
163
|
+
fields: readonly SchemaField[]
|
|
164
|
+
) {
|
|
165
|
+
const byName = new Map(fields.map((field) => [field.path, field]));
|
|
166
|
+
return filters.flatMap((filter): Record<string, unknown>[] => {
|
|
167
|
+
const field = byName.get(filter.field);
|
|
168
|
+
if (field === undefined) return [];
|
|
169
|
+
if (filter.operator === "exists") {
|
|
170
|
+
return [{ [filter.field]: { exists: filter.value !== "false" } }];
|
|
171
|
+
}
|
|
172
|
+
if (filter.value === "") return [];
|
|
173
|
+
let value: string | number | boolean = filter.value;
|
|
174
|
+
if (field.type === "number") {
|
|
175
|
+
value = Number(filter.value);
|
|
176
|
+
if (!Number.isFinite(value)) return [];
|
|
177
|
+
} else if (field.type === "checkbox") {
|
|
178
|
+
value = filter.value === "true";
|
|
179
|
+
}
|
|
180
|
+
return [{ [filter.field]: { [filter.operator]: value } }];
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function parseListPageSize(value: string | null, fallback: ListPageSize = 25): ListPageSize {
|
|
185
|
+
const parsed = Number(value);
|
|
186
|
+
return listPageSizes.includes(parsed as ListPageSize) ? (parsed as ListPageSize) : fallback;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function normalizeWorkspacePreference(
|
|
190
|
+
value: unknown,
|
|
191
|
+
fields: readonly SchemaField[],
|
|
192
|
+
fallback: ListWorkspacePreference
|
|
193
|
+
): ListWorkspacePreference {
|
|
194
|
+
if (typeof value !== "object" || value === null) return fallback;
|
|
195
|
+
const eligible = new Set(fields.map((field) => field.path));
|
|
196
|
+
const rawColumns = Reflect.get(value, "columns");
|
|
197
|
+
const columns = Array.isArray(rawColumns)
|
|
198
|
+
? rawColumns.filter((name): name is string => typeof name === "string" && eligible.has(name))
|
|
199
|
+
: fallback.columns;
|
|
200
|
+
const showStatus = Reflect.get(value, "showStatus");
|
|
201
|
+
const showID = Reflect.get(value, "showID");
|
|
202
|
+
const showCreated = Reflect.get(value, "showCreated");
|
|
203
|
+
const showUpdated = Reflect.get(value, "showUpdated");
|
|
204
|
+
const rawLimit = Reflect.get(value, "limit");
|
|
205
|
+
return {
|
|
206
|
+
columns,
|
|
207
|
+
showStatus: typeof showStatus === "boolean" ? showStatus : fallback.showStatus,
|
|
208
|
+
showID: typeof showID === "boolean" ? showID : fallback.showID,
|
|
209
|
+
showCreated: typeof showCreated === "boolean" ? showCreated : fallback.showCreated,
|
|
210
|
+
showUpdated: typeof showUpdated === "boolean" ? showUpdated : fallback.showUpdated,
|
|
211
|
+
limit: parseListPageSize(
|
|
212
|
+
typeof rawLimit === "number" ? String(rawLimit) : null,
|
|
213
|
+
fallback.limit
|
|
214
|
+
),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function visibleColumnNames(encoded: string | null, fallback: readonly string[]) {
|
|
219
|
+
if (encoded === null) return [...fallback];
|
|
220
|
+
const names = encoded.split(",").filter(Boolean);
|
|
221
|
+
return [...new Set(names)];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function nestedFields(field: SchemaField, mode: "columns" | "filters"): SchemaField[] {
|
|
225
|
+
if (field.type === "blocks") {
|
|
226
|
+
if (mode === "columns") return [];
|
|
227
|
+
return (field.blocks?.types ?? []).flatMap((block) =>
|
|
228
|
+
block.fields.flatMap((child) =>
|
|
229
|
+
withListLabel(child, `${field.admin.label} > ${block.label}`, mode)
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return (field.nested?.fields ?? []).flatMap((child) =>
|
|
234
|
+
withListLabel(child, field.admin.label, mode)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function withListLabel(
|
|
239
|
+
field: SchemaField,
|
|
240
|
+
prefix: string,
|
|
241
|
+
mode: "columns" | "filters"
|
|
242
|
+
): SchemaField[] {
|
|
243
|
+
const labelled = {
|
|
244
|
+
...field,
|
|
245
|
+
admin: { ...field.admin, label: `${prefix} > ${field.admin.label}` },
|
|
246
|
+
};
|
|
247
|
+
if (field.type === "group" || (mode === "filters" && field.type === "array")) {
|
|
248
|
+
return nestedFields(labelled, mode);
|
|
249
|
+
}
|
|
250
|
+
if (field.type === "blocks") return mode === "filters" ? nestedFields(labelled, mode) : [];
|
|
251
|
+
if (mode === "filters" && !filterableLeaf(field)) return [];
|
|
252
|
+
return [labelled];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function filterableLeaf(field: SchemaField) {
|
|
256
|
+
return (
|
|
257
|
+
field.virtual === undefined &&
|
|
258
|
+
field.join === undefined &&
|
|
259
|
+
field.plugin === undefined &&
|
|
260
|
+
field.type !== "ui" &&
|
|
261
|
+
field.type !== "json" &&
|
|
262
|
+
field.type !== "point" &&
|
|
263
|
+
field.type !== "code"
|
|
264
|
+
);
|
|
265
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Link } from "@hvniel/svelte-router";
|
|
3
|
+
import ArrowRightIcon from "~icons/lucide/arrow-right";
|
|
4
|
+
import PlusIcon from "~icons/lucide/plus";
|
|
5
|
+
import { TooltipContent, TooltipRoot, TooltipTrigger } from "@riducms/ui";
|
|
6
|
+
|
|
7
|
+
import { collectionPath, createDocumentPath, globalPath } from "@admin/core/routing/admin-paths";
|
|
8
|
+
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
9
|
+
import { groupAdminResources } from "@admin/features/navigation/admin-resource-groups";
|
|
10
|
+
|
|
11
|
+
const runtime = getAdminRuntime();
|
|
12
|
+
const manifest = $derived(runtime.manifest);
|
|
13
|
+
const resourceGroups = $derived(
|
|
14
|
+
groupAdminResources(runtime.visibleCollections, runtime.visibleGlobals, {
|
|
15
|
+
collections: runtime.i18n.t("navigation:collections"),
|
|
16
|
+
globals: runtime.i18n.t("navigation:globals"),
|
|
17
|
+
})
|
|
18
|
+
);
|
|
19
|
+
const replacement = $derived(
|
|
20
|
+
runtime.dashboardPanels.find((panel) => panel.position === "replace")
|
|
21
|
+
);
|
|
22
|
+
const beforePanels = $derived(
|
|
23
|
+
runtime.dashboardPanels.filter((panel) => panel.position === "before")
|
|
24
|
+
);
|
|
25
|
+
const afterPanels = $derived(
|
|
26
|
+
runtime.dashboardPanels.filter(
|
|
27
|
+
(panel) => panel.position === undefined || panel.position === "after"
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#if manifest !== undefined && replacement !== undefined}
|
|
33
|
+
<replacement.component {manifest} user={runtime.session?.user} i18n={runtime.i18n} />
|
|
34
|
+
{:else}<section class="w-full px-5 pb-7 sm:px-8 sm:pb-9 lg:px-15">
|
|
35
|
+
{#if manifest !== undefined && beforePanels.length > 0}
|
|
36
|
+
<div class="grid gap-3" aria-label={runtime.i18n.t("dashboard:extensions")}>
|
|
37
|
+
{#each beforePanels as panel (panel.key)}
|
|
38
|
+
<panel.component {manifest} user={runtime.session?.user} i18n={runtime.i18n} />
|
|
39
|
+
{/each}
|
|
40
|
+
</div>
|
|
41
|
+
{/if}
|
|
42
|
+
|
|
43
|
+
<div class="mt-9 grid gap-9">
|
|
44
|
+
{#each resourceGroups as group (group.key)}
|
|
45
|
+
{const groupLabel = $derived(runtime.i18n.text(group.label, group.translations))}
|
|
46
|
+
<section aria-label={groupLabel}>
|
|
47
|
+
<div class="mb-3 flex items-end justify-between gap-4">
|
|
48
|
+
<h2 class="text-[18px] font-medium text-foreground-strong">
|
|
49
|
+
{groupLabel}
|
|
50
|
+
</h2>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="grid grid-cols-[repeat(auto-fill,minmax(min(260px,100%),260px))] gap-2.5">
|
|
53
|
+
{#each group.collections as collection (collection.id)}
|
|
54
|
+
{const pluralLabel = $derived(
|
|
55
|
+
runtime.i18n.text(collection.labels.plural, collection.labels.pluralTranslations)
|
|
56
|
+
)}
|
|
57
|
+
{const singularLabel = $derived(
|
|
58
|
+
runtime.i18n.text(
|
|
59
|
+
collection.labels.singular,
|
|
60
|
+
collection.labels.singularTranslations
|
|
61
|
+
)
|
|
62
|
+
)}
|
|
63
|
+
<article
|
|
64
|
+
class="group relative flex min-h-20 items-center gap-3 rounded-[3px] bg-control px-4 py-3.5 transition-colors hover:bg-control-hover"
|
|
65
|
+
>
|
|
66
|
+
<Link
|
|
67
|
+
class="absolute inset-0 rounded-[3px] focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2"
|
|
68
|
+
to={collectionPath(collection.slug)}
|
|
69
|
+
aria-label={runtime.i18n.t("dashboard:open", {
|
|
70
|
+
label: pluralLabel,
|
|
71
|
+
})}
|
|
72
|
+
/>
|
|
73
|
+
<h3 class="min-w-0 flex-1 text-[14px] font-medium text-foreground-strong">
|
|
74
|
+
{pluralLabel}
|
|
75
|
+
</h3>
|
|
76
|
+
{#if runtime.collectionOperations[collection.slug]?.create}
|
|
77
|
+
<TooltipRoot>
|
|
78
|
+
<TooltipTrigger>
|
|
79
|
+
{#snippet child({ props })}
|
|
80
|
+
<Link
|
|
81
|
+
{...props}
|
|
82
|
+
class="relative z-1 grid size-8 shrink-0 place-items-center rounded-[3px] text-foreground-muted hover:bg-background-layer hover:text-foreground-strong"
|
|
83
|
+
to={createDocumentPath(collection.slug)}
|
|
84
|
+
aria-label={runtime.i18n.t("dashboard:create", {
|
|
85
|
+
label: singularLabel.toLocaleLowerCase(runtime.i18n.language),
|
|
86
|
+
})}
|
|
87
|
+
>
|
|
88
|
+
<PlusIcon class="size-4" />
|
|
89
|
+
</Link>
|
|
90
|
+
{/snippet}
|
|
91
|
+
</TooltipTrigger>
|
|
92
|
+
<TooltipContent>
|
|
93
|
+
{runtime.i18n.t("dashboard:create", {
|
|
94
|
+
label: singularLabel,
|
|
95
|
+
})}
|
|
96
|
+
</TooltipContent>
|
|
97
|
+
</TooltipRoot>
|
|
98
|
+
{:else}
|
|
99
|
+
<ArrowRightIcon
|
|
100
|
+
class="size-4 text-foreground-faint rtl:rotate-180"
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
/>
|
|
103
|
+
{/if}
|
|
104
|
+
</article>
|
|
105
|
+
{/each}
|
|
106
|
+
{#each group.globals as global (global.id)}
|
|
107
|
+
{const globalLabel = $derived(
|
|
108
|
+
runtime.i18n.text(global.labels.singular, global.labels.singularTranslations)
|
|
109
|
+
)}
|
|
110
|
+
<article
|
|
111
|
+
class="group relative flex min-h-20 items-center gap-3 rounded-[3px] bg-control px-4 py-3.5 transition-colors hover:bg-control-hover"
|
|
112
|
+
>
|
|
113
|
+
<Link
|
|
114
|
+
class="absolute inset-0 rounded-[3px] focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2"
|
|
115
|
+
to={globalPath(global.slug)}
|
|
116
|
+
aria-label={runtime.i18n.t("dashboard:open", {
|
|
117
|
+
label: globalLabel,
|
|
118
|
+
})}
|
|
119
|
+
/>
|
|
120
|
+
<h3 class="min-w-0 flex-1 text-[14px] font-medium text-foreground-strong">
|
|
121
|
+
{globalLabel}
|
|
122
|
+
</h3>
|
|
123
|
+
<ArrowRightIcon
|
|
124
|
+
class="size-4 text-foreground-faint rtl:rotate-180"
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
/>
|
|
127
|
+
</article>
|
|
128
|
+
{/each}
|
|
129
|
+
</div>
|
|
130
|
+
</section>
|
|
131
|
+
{/each}
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{#if manifest !== undefined && afterPanels.length > 0}
|
|
135
|
+
<div class="mt-8 grid gap-3" aria-label={runtime.i18n.t("dashboard:extensions")}>
|
|
136
|
+
{#each afterPanels as panel (panel.key)}
|
|
137
|
+
<panel.component {manifest} user={runtime.session?.user} i18n={runtime.i18n} />
|
|
138
|
+
{/each}
|
|
139
|
+
</div>
|
|
140
|
+
{/if}
|
|
141
|
+
</section>{/if}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import BookOpenIcon from "~icons/lucide/book-open";
|
|
3
|
+
import ExternalLinkIcon from "~icons/lucide/external-link";
|
|
4
|
+
import LoaderCircleIcon from "~icons/lucide/loader-circle";
|
|
5
|
+
import { onDestroy } from "svelte";
|
|
6
|
+
|
|
7
|
+
import RiduLogo from "@admin/components/brand/ridu-logo.svelte";
|
|
8
|
+
import { Button } from "@admin/components/ui/button";
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@admin/components/ui/popover";
|
|
10
|
+
import { StatusIndicator } from "@admin/components/ui/status-indicator";
|
|
11
|
+
import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
|
|
12
|
+
import { cn } from "@riducms/ui";
|
|
13
|
+
|
|
14
|
+
type SchemaUpdateNotice = {
|
|
15
|
+
tone: "success" | "error";
|
|
16
|
+
label: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const runtime = getAdminRuntime();
|
|
20
|
+
let updateApplied = $state(false);
|
|
21
|
+
let notice = $state<SchemaUpdateNotice>();
|
|
22
|
+
let popoverOpen = $state(false);
|
|
23
|
+
let recoveryAvailable = $state(runtime.manifestRefreshError !== undefined);
|
|
24
|
+
let refreshObserved = false;
|
|
25
|
+
let noticeTimer: number | undefined;
|
|
26
|
+
const status = $derived(
|
|
27
|
+
runtime.refreshingManifest
|
|
28
|
+
? {
|
|
29
|
+
label: runtime.i18n.t("navigation:applyingSchemaUpdate"),
|
|
30
|
+
tone: "live" as const,
|
|
31
|
+
}
|
|
32
|
+
: runtime.manifestRefreshError !== undefined
|
|
33
|
+
? {
|
|
34
|
+
label: runtime.i18n.t("development:schemaUpdateFailed"),
|
|
35
|
+
tone: "destructive" as const,
|
|
36
|
+
}
|
|
37
|
+
: updateApplied
|
|
38
|
+
? {
|
|
39
|
+
label: runtime.i18n.t("development:upToDate"),
|
|
40
|
+
tone: "success" as const,
|
|
41
|
+
}
|
|
42
|
+
: {
|
|
43
|
+
label: runtime.i18n.t("development:ready"),
|
|
44
|
+
tone: "live" as const,
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
function hideNotice() {
|
|
49
|
+
if (noticeTimer !== undefined) window.clearTimeout(noticeTimer);
|
|
50
|
+
notice = undefined;
|
|
51
|
+
noticeTimer = undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function showNotice(next: SchemaUpdateNotice, duration: number) {
|
|
55
|
+
hideNotice();
|
|
56
|
+
notice = next;
|
|
57
|
+
noticeTimer = window.setTimeout(() => {
|
|
58
|
+
notice = undefined;
|
|
59
|
+
noticeTimer = undefined;
|
|
60
|
+
}, duration);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onDestroy(() => {
|
|
64
|
+
if (noticeTimer !== undefined) window.clearTimeout(noticeTimer);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function retrySchema() {
|
|
68
|
+
if (runtime.loading || runtime.refreshingManifest) return;
|
|
69
|
+
void runtime.refreshManifest();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$effect(() => {
|
|
73
|
+
const refreshing = runtime.refreshingManifest;
|
|
74
|
+
const error = runtime.manifestRefreshError;
|
|
75
|
+
if (refreshing) {
|
|
76
|
+
refreshObserved = true;
|
|
77
|
+
hideNotice();
|
|
78
|
+
updateApplied = false;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!refreshObserved) return;
|
|
82
|
+
refreshObserved = false;
|
|
83
|
+
|
|
84
|
+
if (error !== undefined) {
|
|
85
|
+
recoveryAvailable = true;
|
|
86
|
+
showNotice({ tone: "error", label: runtime.i18n.t("development:schemaUpdateFailed") }, 7_000);
|
|
87
|
+
} else {
|
|
88
|
+
showNotice(
|
|
89
|
+
{ tone: "success", label: runtime.i18n.t("development:schemaUpdateApplied") },
|
|
90
|
+
4_500
|
|
91
|
+
);
|
|
92
|
+
updateApplied = true;
|
|
93
|
+
if (recoveryAvailable) popoverOpen = false;
|
|
94
|
+
recoveryAvailable = false;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<div class="fixed end-3 bottom-3 z-30" data-ridu-development-tools>
|
|
100
|
+
{#if notice !== undefined}
|
|
101
|
+
<div
|
|
102
|
+
class={cn(
|
|
103
|
+
"ridu-popover-enter pointer-events-none absolute end-full bottom-0 me-2 flex h-9 max-w-[calc(100vw-4.5rem)] items-center gap-2 whitespace-nowrap rounded-[8px] border bg-popover px-3 text-[12.5px] font-medium shadow-[var(--shadow-popover)]",
|
|
104
|
+
notice.tone === "error"
|
|
105
|
+
? "border-destructive/30 text-destructive"
|
|
106
|
+
: "border-success/30 text-success"
|
|
107
|
+
)}
|
|
108
|
+
role={notice.tone === "error" ? "alert" : "status"}
|
|
109
|
+
data-ridu-development-toast
|
|
110
|
+
data-tone={notice.tone}
|
|
111
|
+
>
|
|
112
|
+
<span class="size-1.75 shrink-0 rounded-full bg-current" aria-hidden="true"></span>
|
|
113
|
+
<span class="truncate">{notice.label}</span>
|
|
114
|
+
</div>
|
|
115
|
+
{/if}
|
|
116
|
+
|
|
117
|
+
<Popover bind:open={popoverOpen}>
|
|
118
|
+
<PopoverTrigger
|
|
119
|
+
class={cn(
|
|
120
|
+
"relative inline-grid size-9 cursor-pointer place-items-center rounded-full border bg-popover text-popover-foreground shadow-[var(--shadow-popover)] transition-[background-color,border-color,color] duration-150 outline-none hover:bg-control-hover focus-visible:outline-2 focus-visible:outline-ring/70 focus-visible:outline-offset-2",
|
|
121
|
+
runtime.manifestRefreshError !== undefined
|
|
122
|
+
? "border-destructive/55"
|
|
123
|
+
: updateApplied
|
|
124
|
+
? "border-success/55"
|
|
125
|
+
: "border-control-border"
|
|
126
|
+
)}
|
|
127
|
+
aria-label={runtime.i18n.t("development:toolsStatus", { status: status.label })}
|
|
128
|
+
title={runtime.i18n.t("development:tools")}
|
|
129
|
+
data-ridu-development-trigger
|
|
130
|
+
>
|
|
131
|
+
{#if runtime.refreshingManifest}
|
|
132
|
+
<LoaderCircleIcon
|
|
133
|
+
class="size-4 animate-spin text-primary motion-reduce:animate-none"
|
|
134
|
+
aria-hidden="true"
|
|
135
|
+
/>
|
|
136
|
+
{:else}
|
|
137
|
+
<RiduLogo variant="arch" class="h-4 text-foreground" />
|
|
138
|
+
{/if}
|
|
139
|
+
<span
|
|
140
|
+
class={cn(
|
|
141
|
+
"absolute top-0 end-0 size-2.5 rounded-full border-2 border-popover",
|
|
142
|
+
runtime.manifestRefreshError !== undefined
|
|
143
|
+
? "bg-destructive"
|
|
144
|
+
: updateApplied
|
|
145
|
+
? "bg-success"
|
|
146
|
+
: "bg-primary"
|
|
147
|
+
)}
|
|
148
|
+
aria-hidden="true"
|
|
149
|
+
></span>
|
|
150
|
+
</PopoverTrigger>
|
|
151
|
+
|
|
152
|
+
<PopoverContent
|
|
153
|
+
class="w-[min(18rem,calc(100vw-1.5rem))] gap-0 overflow-hidden p-0"
|
|
154
|
+
side="top"
|
|
155
|
+
align="end"
|
|
156
|
+
sideOffset={8}
|
|
157
|
+
role="dialog"
|
|
158
|
+
aria-label={runtime.i18n.t("development:tools")}
|
|
159
|
+
data-ridu-development-panel
|
|
160
|
+
>
|
|
161
|
+
<div class="flex items-center gap-2.5 border-b border-control-border px-3.5 py-3">
|
|
162
|
+
<RiduLogo variant="arch" class="h-4 text-foreground" />
|
|
163
|
+
<p class="text-[13px] font-semibold text-foreground">
|
|
164
|
+
{runtime.i18n.t("development:tools")}
|
|
165
|
+
</p>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="grid gap-2.5 px-3.5 py-3">
|
|
169
|
+
<div class="flex items-center justify-between gap-4">
|
|
170
|
+
<span class="font-mono text-[10px] tracking-[0.12em] text-foreground-faint uppercase">
|
|
171
|
+
{runtime.i18n.t("development:schema")}
|
|
172
|
+
</span>
|
|
173
|
+
<StatusIndicator tone={status.tone} pulse={runtime.refreshingManifest}>
|
|
174
|
+
{status.label}
|
|
175
|
+
</StatusIndicator>
|
|
176
|
+
</div>
|
|
177
|
+
{#if runtime.manifestRefreshError !== undefined}
|
|
178
|
+
<p class="text-[12px] leading-5 text-destructive">
|
|
179
|
+
{runtime.manifestRefreshError}
|
|
180
|
+
</p>
|
|
181
|
+
{/if}
|
|
182
|
+
{#if runtime.manifestRefreshError !== undefined || recoveryAvailable}
|
|
183
|
+
<Button
|
|
184
|
+
class="w-fit"
|
|
185
|
+
variant="outline"
|
|
186
|
+
size="sm"
|
|
187
|
+
aria-disabled={runtime.refreshingManifest}
|
|
188
|
+
aria-busy={runtime.refreshingManifest}
|
|
189
|
+
onclick={retrySchema}
|
|
190
|
+
>
|
|
191
|
+
{runtime.refreshingManifest
|
|
192
|
+
? runtime.i18n.t("navigation:applyingSchemaUpdate")
|
|
193
|
+
: runtime.i18n.t("development:retrySchemaUpdate")}
|
|
194
|
+
</Button>
|
|
195
|
+
{/if}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<a
|
|
199
|
+
class="flex items-center gap-2.5 border-t border-control-border px-3.5 py-3 text-foreground-muted outline-none transition-colors hover:bg-control-hover hover:text-foreground focus-visible:bg-control-hover focus-visible:text-foreground"
|
|
200
|
+
href="https://riducms.com"
|
|
201
|
+
target="_blank"
|
|
202
|
+
rel="noreferrer"
|
|
203
|
+
>
|
|
204
|
+
<BookOpenIcon class="size-3.5 shrink-0" aria-hidden="true" />
|
|
205
|
+
<span class="min-w-0 flex-1">
|
|
206
|
+
<span class="block text-[12.5px] font-medium">
|
|
207
|
+
{runtime.i18n.t("development:documentation")}
|
|
208
|
+
</span>
|
|
209
|
+
<span class="block text-[11px] text-foreground-faint">
|
|
210
|
+
{runtime.i18n.t("development:documentationDescription")}
|
|
211
|
+
</span>
|
|
212
|
+
</span>
|
|
213
|
+
<ExternalLinkIcon class="size-3.5 shrink-0" aria-hidden="true" />
|
|
214
|
+
</a>
|
|
215
|
+
</PopoverContent>
|
|
216
|
+
</Popover>
|
|
217
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getAdminI18n } from "@riducms/plugin";
|
|
3
|
+
|
|
4
|
+
type DocumentAPIViewProps = {
|
|
5
|
+
resourceSlug: string;
|
|
6
|
+
resourceLabel: string;
|
|
7
|
+
documentID?: string;
|
|
8
|
+
globalResource?: boolean;
|
|
9
|
+
fallbackValue: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let props: DocumentAPIViewProps = $props();
|
|
13
|
+
const i18n = getAdminI18n();
|
|
14
|
+
const view = import("@admin/features/documents/document-api-view.svelte");
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
{#await view}
|
|
18
|
+
<section
|
|
19
|
+
class="grid min-h-72 place-items-center"
|
|
20
|
+
aria-busy="true"
|
|
21
|
+
aria-label={i18n.t("documents:loadingAPIView")}
|
|
22
|
+
>
|
|
23
|
+
<p class="text-[12px] text-foreground-faint">{i18n.t("documents:loadingAPIView")}</p>
|
|
24
|
+
</section>
|
|
25
|
+
{:then { default: DocumentAPIView }}
|
|
26
|
+
<DocumentAPIView {...props} />
|
|
27
|
+
{:catch}
|
|
28
|
+
<section class="grid min-h-72 place-items-center" role="alert">
|
|
29
|
+
<p class="text-[12px] text-destructive">{i18n.t("documents:apiViewLoadFailed")}</p>
|
|
30
|
+
</section>
|
|
31
|
+
{/await}
|