@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,547 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getAdminI18n } from "@riducms/plugin";
|
|
3
|
+
import { onDestroy } from "svelte";
|
|
4
|
+
import CheckIcon from "~icons/lucide/check";
|
|
5
|
+
import CopyIcon from "~icons/lucide/copy";
|
|
6
|
+
import ExternalLinkIcon from "~icons/lucide/external-link";
|
|
7
|
+
import RefreshCwIcon from "~icons/lucide/refresh-cw";
|
|
8
|
+
|
|
9
|
+
import CodeBlock from "@admin/components/code-block/code-block.svelte";
|
|
10
|
+
import JsonViewer from "@admin/components/json-tree/json-viewer.svelte";
|
|
11
|
+
import { Banner } from "@admin/components/ui/banner";
|
|
12
|
+
import { Button } from "@admin/components/ui/button";
|
|
13
|
+
import { Checkbox } from "@admin/components/ui/checkbox";
|
|
14
|
+
import { Input } from "@admin/components/ui/input";
|
|
15
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@admin/components/ui/tabs";
|
|
16
|
+
|
|
17
|
+
type APIAction = "list" | "find" | "create" | "update" | "duplicate" | "delete";
|
|
18
|
+
const collectionActions: readonly {
|
|
19
|
+
key: APIAction;
|
|
20
|
+
method: "GET" | "POST" | "PATCH" | "DELETE";
|
|
21
|
+
}[] = [
|
|
22
|
+
{ key: "list", method: "GET" },
|
|
23
|
+
{ key: "find", method: "GET" },
|
|
24
|
+
{ key: "create", method: "POST" },
|
|
25
|
+
{ key: "update", method: "PATCH" },
|
|
26
|
+
{ key: "duplicate", method: "POST" },
|
|
27
|
+
{ key: "delete", method: "DELETE" },
|
|
28
|
+
];
|
|
29
|
+
const i18n = getAdminI18n();
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
resourceSlug,
|
|
33
|
+
resourceLabel,
|
|
34
|
+
documentID,
|
|
35
|
+
globalResource = false,
|
|
36
|
+
fallbackValue,
|
|
37
|
+
}: {
|
|
38
|
+
resourceSlug: string;
|
|
39
|
+
resourceLabel: string;
|
|
40
|
+
documentID?: string;
|
|
41
|
+
globalResource?: boolean;
|
|
42
|
+
fallbackValue: unknown;
|
|
43
|
+
} = $props();
|
|
44
|
+
|
|
45
|
+
let depth = $state(2);
|
|
46
|
+
let authenticated = $state(true);
|
|
47
|
+
let requestRevision = $state(0);
|
|
48
|
+
let loading = $state(false);
|
|
49
|
+
// Begin with a non-reactive form snapshot until a live request becomes available.
|
|
50
|
+
// svelte-ignore state_referenced_locally
|
|
51
|
+
let responseValue = $state.raw<unknown>($state.snapshot(fallbackValue));
|
|
52
|
+
let responseStatus = $state<number>();
|
|
53
|
+
let responseStatusText = $state("");
|
|
54
|
+
let responseDuration = $state<number>();
|
|
55
|
+
let responseSize = $state<number>();
|
|
56
|
+
let requestError = $state<string>();
|
|
57
|
+
let example = $state<"fetch" | "sdk" | "curl">("fetch");
|
|
58
|
+
let apiAction = $state<APIAction>("find");
|
|
59
|
+
let copied = $state<"url" | "example">();
|
|
60
|
+
let copyResetTimer: ReturnType<typeof setTimeout> | undefined;
|
|
61
|
+
|
|
62
|
+
onDestroy(() => clearTimeout(copyResetTimer));
|
|
63
|
+
|
|
64
|
+
const availableActions = $derived(
|
|
65
|
+
globalResource
|
|
66
|
+
? collectionActions.filter((action) => action.key === "find" || action.key === "update")
|
|
67
|
+
: collectionActions
|
|
68
|
+
);
|
|
69
|
+
const selectedAction = $derived(
|
|
70
|
+
availableActions.find((action) => action.key === apiAction) ?? availableActions[0]!
|
|
71
|
+
);
|
|
72
|
+
const runnableAction = $derived(apiAction === "list" || apiAction === "find");
|
|
73
|
+
const liveRequest = $derived(
|
|
74
|
+
runnableAction && (apiAction === "list" || globalResource || documentID !== undefined)
|
|
75
|
+
);
|
|
76
|
+
const collectionEndpoint = $derived(`/api/collections/${encodeURIComponent(resourceSlug)}`);
|
|
77
|
+
const documentEndpoint = $derived(
|
|
78
|
+
`${collectionEndpoint}/${documentID === undefined ? ":id" : encodeURIComponent(documentID)}`
|
|
79
|
+
);
|
|
80
|
+
const endpointPath = $derived.by(() => {
|
|
81
|
+
if (globalResource) return `/api/globals/${encodeURIComponent(resourceSlug)}`;
|
|
82
|
+
if (apiAction === "list" || apiAction === "create") return collectionEndpoint;
|
|
83
|
+
if (apiAction === "duplicate") return `${documentEndpoint}/duplicate`;
|
|
84
|
+
return documentEndpoint;
|
|
85
|
+
});
|
|
86
|
+
const requestURL = $derived.by(() => {
|
|
87
|
+
if (endpointPath.includes(":id")) return "";
|
|
88
|
+
const url = new URL(endpointPath, window.location.origin);
|
|
89
|
+
if (runnableAction) url.searchParams.set("depth", String(depth));
|
|
90
|
+
return url.href;
|
|
91
|
+
});
|
|
92
|
+
const requestExample = $derived(requestExampleFor(example));
|
|
93
|
+
|
|
94
|
+
function requestExampleFor(exampleKind: typeof example) {
|
|
95
|
+
if (apiAction !== "find") return actionExample(exampleKind);
|
|
96
|
+
if (exampleKind === "sdk") {
|
|
97
|
+
if (globalResource)
|
|
98
|
+
return `const settings = await client.global(${JSON.stringify(resourceSlug)}, { depth: ${depth} });`;
|
|
99
|
+
return `const document = await client.find(\n ${JSON.stringify(resourceSlug)},\n ${JSON.stringify(documentID ?? "document-id")},\n { depth: ${depth} },\n);`;
|
|
100
|
+
}
|
|
101
|
+
if (exampleKind === "curl") {
|
|
102
|
+
return `curl ${authenticated ? "-H 'Authorization: Bearer <api-key>' \\\n " : ""}${JSON.stringify(requestURL || endpointPath)}`;
|
|
103
|
+
}
|
|
104
|
+
return `const response = await fetch(${JSON.stringify(requestURL || endpointPath)}, {\n headers: { Accept: "application/json" },\n credentials: ${JSON.stringify(authenticated ? "include" : "omit")},\n});\n\nif (!response.ok) throw new Error(\`Request failed: \${response.status}\`);\nconst { doc } = await response.json();`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
$effect(() => {
|
|
108
|
+
requestRevision;
|
|
109
|
+
const url = requestURL;
|
|
110
|
+
const includeSession = authenticated;
|
|
111
|
+
if (!liveRequest || url === "") {
|
|
112
|
+
responseValue = $state.snapshot(fallbackValue);
|
|
113
|
+
responseStatus = undefined;
|
|
114
|
+
responseStatusText = "";
|
|
115
|
+
responseDuration = undefined;
|
|
116
|
+
responseSize = undefined;
|
|
117
|
+
requestError = undefined;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const abort = new AbortController();
|
|
121
|
+
execute(url, includeSession, abort.signal);
|
|
122
|
+
return () => abort.abort();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
async function execute(url: string, includeSession: boolean, signal: AbortSignal) {
|
|
126
|
+
loading = true;
|
|
127
|
+
requestError = undefined;
|
|
128
|
+
const startedAt = performance.now();
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetch(url, {
|
|
131
|
+
headers: { Accept: "application/json" },
|
|
132
|
+
credentials: includeSession ? "include" : "omit",
|
|
133
|
+
signal,
|
|
134
|
+
});
|
|
135
|
+
const encoded = await response.text();
|
|
136
|
+
if (signal.aborted) return;
|
|
137
|
+
responseStatus = response.status;
|
|
138
|
+
responseStatusText = response.statusText;
|
|
139
|
+
responseDuration = Math.round(performance.now() - startedAt);
|
|
140
|
+
responseSize = new TextEncoder().encode(encoded).byteLength;
|
|
141
|
+
let body: unknown = encoded;
|
|
142
|
+
try {
|
|
143
|
+
body = encoded === "" ? null : JSON.parse(encoded);
|
|
144
|
+
} catch {
|
|
145
|
+
// Non-JSON failures remain inspectable in the response viewer.
|
|
146
|
+
}
|
|
147
|
+
responseValue = documentFromEnvelope(body);
|
|
148
|
+
if (!response.ok) requestError = errorMessage(body, response);
|
|
149
|
+
} catch (cause) {
|
|
150
|
+
if (signal.aborted) return;
|
|
151
|
+
responseDuration = Math.round(performance.now() - startedAt);
|
|
152
|
+
responseStatus = undefined;
|
|
153
|
+
responseStatusText = "";
|
|
154
|
+
responseSize = undefined;
|
|
155
|
+
requestError = cause instanceof Error ? cause.message : i18n.t("documents:apiRequestFailed");
|
|
156
|
+
responseValue = { error: requestError };
|
|
157
|
+
} finally {
|
|
158
|
+
if (!signal.aborted) loading = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function documentFromEnvelope(value: unknown) {
|
|
163
|
+
return isRecord(value) && "doc" in value ? value.doc : value;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function errorMessage(value: unknown, response: Response) {
|
|
167
|
+
if (isRecord(value) && typeof value.message === "string") return value.message;
|
|
168
|
+
if (isRecord(value) && isRecord(value.error) && typeof value.error.message === "string") {
|
|
169
|
+
return value.error.message;
|
|
170
|
+
}
|
|
171
|
+
return `${response.status} ${response.statusText}`.trim();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
175
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function actionExample(language: "fetch" | "sdk" | "curl") {
|
|
179
|
+
if (language === "sdk") return sdkActionExample();
|
|
180
|
+
if (language === "curl") return curlActionExample();
|
|
181
|
+
return fetchActionExample();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function sdkActionExample() {
|
|
185
|
+
if (globalResource) {
|
|
186
|
+
return [
|
|
187
|
+
`const settings = await client.updateGlobal(${JSON.stringify(resourceSlug)}, {`,
|
|
188
|
+
' siteName: "Updated value",',
|
|
189
|
+
"});",
|
|
190
|
+
].join("\n");
|
|
191
|
+
}
|
|
192
|
+
const slug = JSON.stringify(resourceSlug);
|
|
193
|
+
const id = JSON.stringify(documentID ?? "document-id");
|
|
194
|
+
switch (apiAction) {
|
|
195
|
+
case "list":
|
|
196
|
+
return [
|
|
197
|
+
`const page = await client.list(${slug}, {`,
|
|
198
|
+
" page: 1,",
|
|
199
|
+
" limit: 10,",
|
|
200
|
+
` depth: ${depth},`,
|
|
201
|
+
"});",
|
|
202
|
+
].join("\n");
|
|
203
|
+
case "create":
|
|
204
|
+
return `const document = await client.create(${slug}, {\n title: "New ${resourceLabel}",\n});`;
|
|
205
|
+
case "update":
|
|
206
|
+
return `const document = await client.update(${slug}, ${id}, {\n title: "Updated ${resourceLabel}",\n});`;
|
|
207
|
+
case "duplicate":
|
|
208
|
+
return `const copy = await client.duplicate(${slug}, ${id}, {\n title: "Copy of ${resourceLabel}",\n});`;
|
|
209
|
+
case "delete":
|
|
210
|
+
return `const result = await client.delete(${slug}, ${id});`;
|
|
211
|
+
default:
|
|
212
|
+
return "";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function fetchActionExample() {
|
|
217
|
+
const hasBody = apiAction === "create" || apiAction === "update" || apiAction === "duplicate";
|
|
218
|
+
const resultName = apiAction === "list" ? "page" : apiAction === "delete" ? "result" : "doc";
|
|
219
|
+
const lines = [
|
|
220
|
+
`const response = await fetch(${JSON.stringify(requestURL || endpointPath)}, {`,
|
|
221
|
+
` method: ${JSON.stringify(selectedAction.method)},`,
|
|
222
|
+
" headers: {",
|
|
223
|
+
' Accept: "application/json",',
|
|
224
|
+
];
|
|
225
|
+
if (hasBody) lines.push(' "Content-Type": "application/json",');
|
|
226
|
+
lines.push(" },", ` credentials: ${JSON.stringify(authenticated ? "include" : "omit")},`);
|
|
227
|
+
if (hasBody) lines.push(' body: JSON.stringify({ title: "Example value" }),');
|
|
228
|
+
lines.push(
|
|
229
|
+
"});",
|
|
230
|
+
"",
|
|
231
|
+
"if (!response.ok) throw new Error(`Request failed: ${response.status}`);"
|
|
232
|
+
);
|
|
233
|
+
lines.push(
|
|
234
|
+
apiAction === "list" || apiAction === "delete"
|
|
235
|
+
? `const ${resultName} = await response.json();`
|
|
236
|
+
: `const { ${resultName} } = await response.json();`
|
|
237
|
+
);
|
|
238
|
+
return lines.join("\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function curlActionExample() {
|
|
242
|
+
const hasBody = apiAction === "create" || apiAction === "update" || apiAction === "duplicate";
|
|
243
|
+
const lines = [`curl -X ${selectedAction.method} \\`, " -H 'Accept: application/json' \\"];
|
|
244
|
+
if (authenticated) lines.push(" -H 'Authorization: Bearer <api-key>' \\");
|
|
245
|
+
if (hasBody) {
|
|
246
|
+
lines.push(
|
|
247
|
+
" -H 'Content-Type: application/json' \\",
|
|
248
|
+
' -d \'{"title":"Example value"}\' \\'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
lines.push(` ${JSON.stringify(requestURL || endpointPath)}`);
|
|
252
|
+
return lines.join("\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function setDepth(event: Event) {
|
|
256
|
+
const next = Number((event.currentTarget as HTMLInputElement).value);
|
|
257
|
+
depth = Number.isFinite(next) ? Math.min(5, Math.max(0, Math.round(next))) : 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function copy(kind: "url" | "example", value: string) {
|
|
261
|
+
await navigator.clipboard.writeText(value);
|
|
262
|
+
copied = kind;
|
|
263
|
+
clearTimeout(copyResetTimer);
|
|
264
|
+
copyResetTimer = window.setTimeout(() => {
|
|
265
|
+
if (copied === kind) copied = undefined;
|
|
266
|
+
}, 1_500);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function formatBytes(value: number | undefined) {
|
|
270
|
+
if (value === undefined) return "";
|
|
271
|
+
return value < 1_024
|
|
272
|
+
? i18n.formatNumber(value, { style: "unit", unit: "byte" })
|
|
273
|
+
: i18n.formatNumber(value / 1_024, {
|
|
274
|
+
maximumFractionDigits: 1,
|
|
275
|
+
style: "unit",
|
|
276
|
+
unit: "kilobyte",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function actionLabel(action: APIAction) {
|
|
281
|
+
switch (action) {
|
|
282
|
+
case "list":
|
|
283
|
+
return i18n.t("documents:apiListSearch");
|
|
284
|
+
case "find":
|
|
285
|
+
return i18n.t("documents:apiView");
|
|
286
|
+
case "create":
|
|
287
|
+
return i18n.t("documents:apiCreate");
|
|
288
|
+
case "update":
|
|
289
|
+
return i18n.t("documents:apiUpdate");
|
|
290
|
+
case "duplicate":
|
|
291
|
+
return i18n.t("documents:duplicate");
|
|
292
|
+
case "delete":
|
|
293
|
+
return i18n.t("documents:delete");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<section class="grid gap-6" aria-labelledby="api-heading">
|
|
299
|
+
<header class="grid gap-2">
|
|
300
|
+
<p class="font-mono text-[9.5px] tracking-[0.15em] text-foreground-faint uppercase">
|
|
301
|
+
{i18n.t("documents:liveRESTExplorer")}
|
|
302
|
+
</p>
|
|
303
|
+
<h2
|
|
304
|
+
id="api-heading"
|
|
305
|
+
class="text-[28px] font-semibold leading-tight tracking-[-0.02em] text-foreground-strong"
|
|
306
|
+
>
|
|
307
|
+
{i18n.t("documents:api")}
|
|
308
|
+
</h2>
|
|
309
|
+
<p class="max-w-2xl text-[13px] leading-5 text-foreground-muted">
|
|
310
|
+
{i18n.t("documents:apiDescription", {
|
|
311
|
+
label: resourceLabel.toLocaleLowerCase(i18n.language),
|
|
312
|
+
})}
|
|
313
|
+
</p>
|
|
314
|
+
</header>
|
|
315
|
+
|
|
316
|
+
<div class="grid gap-5 xl:grid-cols-[minmax(0,1.65fr)_minmax(340px,0.85fr)] xl:items-start">
|
|
317
|
+
<div class="grid min-w-0 gap-4">
|
|
318
|
+
<div class="overflow-hidden rounded-[4px] border border-control-border bg-background">
|
|
319
|
+
<div
|
|
320
|
+
class="flex min-w-0 flex-wrap items-center gap-2 border-b border-control-border bg-control px-3 py-2.5"
|
|
321
|
+
>
|
|
322
|
+
<span
|
|
323
|
+
class={[
|
|
324
|
+
"rounded-[3px] px-2 py-1 font-mono text-[10px] font-semibold",
|
|
325
|
+
selectedAction.method === "GET" && "bg-success/12 text-success",
|
|
326
|
+
selectedAction.method === "POST" && "bg-primary/12 text-primary",
|
|
327
|
+
selectedAction.method === "PATCH" && "bg-warning/12 text-warning",
|
|
328
|
+
selectedAction.method === "DELETE" && "bg-destructive/12 text-destructive",
|
|
329
|
+
]}
|
|
330
|
+
>
|
|
331
|
+
{selectedAction.method}
|
|
332
|
+
</span>
|
|
333
|
+
<code class="min-w-0 flex-1 truncate text-[11.5px] text-foreground-muted">
|
|
334
|
+
{endpointPath || i18n.t("documents:apiAvailableAfterSave")}
|
|
335
|
+
</code>
|
|
336
|
+
{#if requestURL !== ""}
|
|
337
|
+
<Button variant="ghost" size="xs" onclick={() => copy("url", requestURL)}>
|
|
338
|
+
{#if copied === "url"}<CheckIcon class="size-3" />
|
|
339
|
+
{i18n.t("documents:copied")}{:else}<CopyIcon class="size-3" />
|
|
340
|
+
{i18n.t("documents:copyURL")}{/if}
|
|
341
|
+
</Button>
|
|
342
|
+
<Button
|
|
343
|
+
variant="ghost"
|
|
344
|
+
size="icon-xs"
|
|
345
|
+
href={requestURL}
|
|
346
|
+
target="_blank"
|
|
347
|
+
aria-label={i18n.t("documents:openAPIURL")}
|
|
348
|
+
tooltip={i18n.t("documents:openAPIURL")}
|
|
349
|
+
>
|
|
350
|
+
<ExternalLinkIcon class="size-3.5" />
|
|
351
|
+
</Button>
|
|
352
|
+
{/if}
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<div
|
|
356
|
+
class="grid gap-4 border-b border-control-border px-4 py-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-end"
|
|
357
|
+
>
|
|
358
|
+
<div class="flex flex-wrap items-end gap-5">
|
|
359
|
+
{#if runnableAction && !globalResource}
|
|
360
|
+
<label class="grid gap-1.5" for="ridu-api-depth">
|
|
361
|
+
<span class="text-[11.5px] font-medium text-foreground-muted">
|
|
362
|
+
{i18n.t("documents:apiDepth")}
|
|
363
|
+
</span>
|
|
364
|
+
<Input
|
|
365
|
+
id="ridu-api-depth"
|
|
366
|
+
class="w-20 font-mono"
|
|
367
|
+
type="number"
|
|
368
|
+
min="0"
|
|
369
|
+
max="5"
|
|
370
|
+
step="1"
|
|
371
|
+
value={depth}
|
|
372
|
+
oninput={setDepth}
|
|
373
|
+
/>
|
|
374
|
+
</label>
|
|
375
|
+
{/if}
|
|
376
|
+
<label
|
|
377
|
+
class="flex min-h-10 cursor-pointer items-center gap-2 pb-0.5"
|
|
378
|
+
for="ridu-api-authenticated"
|
|
379
|
+
>
|
|
380
|
+
<Checkbox id="ridu-api-authenticated" bind:checked={authenticated} />
|
|
381
|
+
<span class="text-[12.5px] text-foreground-muted">
|
|
382
|
+
{i18n.t("documents:apiAuthenticated")}
|
|
383
|
+
</span>
|
|
384
|
+
</label>
|
|
385
|
+
</div>
|
|
386
|
+
<Button
|
|
387
|
+
variant="outline"
|
|
388
|
+
size="sm"
|
|
389
|
+
disabled={!liveRequest || loading}
|
|
390
|
+
onclick={() => (requestRevision += 1)}
|
|
391
|
+
>
|
|
392
|
+
<RefreshCwIcon class={["size-3.5", loading && "animate-spin"]} />
|
|
393
|
+
{loading
|
|
394
|
+
? i18n.t("documents:apiRequesting")
|
|
395
|
+
: runnableAction
|
|
396
|
+
? i18n.t("documents:apiRunRequest")
|
|
397
|
+
: i18n.t("documents:apiExampleOnly")}
|
|
398
|
+
</Button>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
<div
|
|
402
|
+
class="flex min-h-10 flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2 text-[10.5px] text-foreground-faint"
|
|
403
|
+
>
|
|
404
|
+
{#if responseStatus !== undefined}
|
|
405
|
+
<strong class={responseStatus < 400 ? "text-success" : "text-destructive"}>
|
|
406
|
+
{i18n.formatNumber(responseStatus, { useGrouping: false })}
|
|
407
|
+
{responseStatusText}
|
|
408
|
+
</strong>
|
|
409
|
+
{/if}
|
|
410
|
+
{#if responseDuration !== undefined}<span>
|
|
411
|
+
{i18n.formatNumber(responseDuration, {
|
|
412
|
+
style: "unit",
|
|
413
|
+
unit: "millisecond",
|
|
414
|
+
})}
|
|
415
|
+
</span>{/if}
|
|
416
|
+
{#if responseSize !== undefined}<span>{formatBytes(responseSize)}</span>{/if}
|
|
417
|
+
<span>
|
|
418
|
+
{authenticated
|
|
419
|
+
? i18n.t("documents:apiCurrentSession")
|
|
420
|
+
: i18n.t("documents:apiPublicRequest")}
|
|
421
|
+
</span>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
{const requestBanner = $derived(
|
|
426
|
+
!runnableAction
|
|
427
|
+
? {
|
|
428
|
+
message: i18n.t("documents:apiMutationDescription"),
|
|
429
|
+
}
|
|
430
|
+
: !liveRequest
|
|
431
|
+
? { message: i18n.t("documents:apiRunnableAfterSave") }
|
|
432
|
+
: requestError === undefined
|
|
433
|
+
? undefined
|
|
434
|
+
: { message: requestError, tone: "destructive" as const }
|
|
435
|
+
)}
|
|
436
|
+
|
|
437
|
+
{#if requestBanner !== undefined}
|
|
438
|
+
<Banner tone={requestBanner.tone}>{requestBanner.message}</Banner>
|
|
439
|
+
{/if}
|
|
440
|
+
|
|
441
|
+
<JsonViewer class="xl:h-[calc(100vh-9rem)] xl:min-h-[620px]" value={responseValue} />
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<aside class="grid min-w-0 gap-4 xl:sticky xl:top-4">
|
|
445
|
+
<nav
|
|
446
|
+
class="overflow-hidden rounded-[4px] border border-control-border bg-background"
|
|
447
|
+
aria-label={i18n.t("documents:apiActions")}
|
|
448
|
+
>
|
|
449
|
+
<p
|
|
450
|
+
class="border-b border-control-border bg-control px-3 py-2 font-mono text-[9.5px] tracking-[0.12em] text-foreground-faint uppercase"
|
|
451
|
+
>
|
|
452
|
+
{i18n.t("documents:apiActions")}
|
|
453
|
+
</p>
|
|
454
|
+
<div class="grid grid-cols-2 gap-px bg-control-border p-px xl:grid-cols-1">
|
|
455
|
+
{#each availableActions as action (action.key)}
|
|
456
|
+
<button
|
|
457
|
+
type="button"
|
|
458
|
+
class={[
|
|
459
|
+
"flex min-h-10 items-center gap-2.5 bg-background px-3 text-start text-[12.5px] outline-none transition-colors hover:bg-control focus-visible:bg-control",
|
|
460
|
+
apiAction === action.key && "bg-control text-foreground-strong",
|
|
461
|
+
]}
|
|
462
|
+
onclick={() => (apiAction = action.key)}
|
|
463
|
+
aria-current={apiAction === action.key ? "page" : undefined}
|
|
464
|
+
>
|
|
465
|
+
<span
|
|
466
|
+
class={[
|
|
467
|
+
"w-12 font-mono text-[9px] font-semibold",
|
|
468
|
+
action.method === "GET" && "text-success",
|
|
469
|
+
action.method === "POST" && "text-primary",
|
|
470
|
+
action.method === "PATCH" && "text-warning",
|
|
471
|
+
action.method === "DELETE" && "text-destructive",
|
|
472
|
+
]}
|
|
473
|
+
>
|
|
474
|
+
{action.method}
|
|
475
|
+
</span>
|
|
476
|
+
<span>{actionLabel(action.key)}</span>
|
|
477
|
+
</button>
|
|
478
|
+
{/each}
|
|
479
|
+
</div>
|
|
480
|
+
</nav>
|
|
481
|
+
<Tabs
|
|
482
|
+
bind:value={example}
|
|
483
|
+
class="overflow-hidden rounded-[4px] border border-control-border"
|
|
484
|
+
aria-labelledby="api-example-heading"
|
|
485
|
+
>
|
|
486
|
+
<div
|
|
487
|
+
class="flex flex-wrap items-center justify-between gap-3 border-b border-control-border bg-control px-3 py-2"
|
|
488
|
+
>
|
|
489
|
+
<TabsList
|
|
490
|
+
variant="line"
|
|
491
|
+
class="h-auto gap-1 rounded-none border-0 bg-transparent p-0"
|
|
492
|
+
aria-label={i18n.t("documents:apiExampleLanguage")}
|
|
493
|
+
>
|
|
494
|
+
{#each ["fetch", "sdk", "curl"] as option}
|
|
495
|
+
<TabsTrigger
|
|
496
|
+
value={option}
|
|
497
|
+
class={[
|
|
498
|
+
"h-auto flex-none rounded-[3px] px-2.5 py-1.5 text-[11.5px] capitalize",
|
|
499
|
+
"data-[state=active]:bg-background data-[state=active]:shadow-sm",
|
|
500
|
+
]}
|
|
501
|
+
>
|
|
502
|
+
{option === "sdk" ? "Ridu SDK" : option}
|
|
503
|
+
</TabsTrigger>
|
|
504
|
+
{/each}
|
|
505
|
+
</TabsList>
|
|
506
|
+
<Button variant="ghost" size="xs" onclick={() => copy("example", requestExample)}>
|
|
507
|
+
{#if copied === "example"}<CheckIcon class="size-3" />
|
|
508
|
+
{i18n.t("documents:copied")}{:else}<CopyIcon class="size-3" />
|
|
509
|
+
{i18n.t("documents:copy")}{/if}
|
|
510
|
+
</Button>
|
|
511
|
+
</div>
|
|
512
|
+
<h3 id="api-example-heading" class="sr-only">
|
|
513
|
+
{i18n.t("documents:apiRequestExample")}
|
|
514
|
+
</h3>
|
|
515
|
+
<TabsContent value="fetch" class="mt-0">
|
|
516
|
+
<CodeBlock value={requestExampleFor("fetch")} language="javascript" />
|
|
517
|
+
</TabsContent>
|
|
518
|
+
<TabsContent value="sdk" class="mt-0">
|
|
519
|
+
<CodeBlock value={requestExampleFor("sdk")} language="javascript" />
|
|
520
|
+
</TabsContent>
|
|
521
|
+
<TabsContent value="curl" class="mt-0">
|
|
522
|
+
<CodeBlock value={requestExampleFor("curl")} language="bash" />
|
|
523
|
+
</TabsContent>
|
|
524
|
+
</Tabs>
|
|
525
|
+
|
|
526
|
+
<details class="group rounded-[4px] border border-control-border px-4 py-3">
|
|
527
|
+
<summary class="cursor-pointer text-[12.5px] font-medium text-foreground outline-none">
|
|
528
|
+
{i18n.t("documents:apiRequestParameters")}
|
|
529
|
+
</summary>
|
|
530
|
+
<div
|
|
531
|
+
class="mt-3 grid gap-3 border-t border-control-border pt-3 text-[12px] leading-5 text-foreground-muted sm:grid-cols-2"
|
|
532
|
+
>
|
|
533
|
+
{#if runnableAction}
|
|
534
|
+
<p>
|
|
535
|
+
<code class="text-foreground">depth</code>
|
|
536
|
+
{i18n.t("documents:apiDepthDescription")}
|
|
537
|
+
</p>
|
|
538
|
+
{/if}
|
|
539
|
+
<p>
|
|
540
|
+
<code class="text-foreground">authenticated</code>
|
|
541
|
+
{i18n.t("documents:apiAuthenticatedDescription")}
|
|
542
|
+
</p>
|
|
543
|
+
</div>
|
|
544
|
+
</details>
|
|
545
|
+
</aside>
|
|
546
|
+
</div>
|
|
547
|
+
</section>
|