@aphexcms/cms-core 0.1.3 → 0.1.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/dist/api/assets.d.ts +48 -0
- package/dist/api/assets.d.ts.map +1 -0
- package/dist/api/assets.js +52 -0
- package/dist/api/client.d.ts +37 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +125 -0
- package/dist/api/documents.d.ts +56 -0
- package/dist/api/documents.d.ts.map +1 -0
- package/dist/api/documents.js +77 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +5 -0
- package/dist/api/organizations.d.ts +101 -0
- package/dist/api/organizations.d.ts.map +1 -0
- package/dist/api/organizations.js +92 -0
- package/dist/api/types.d.ts +23 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +1 -0
- package/dist/app.d.ts +19 -0
- package/dist/auth/MULTI_TENANCY_PLAN.md +1183 -0
- package/dist/auth/auth-errors.d.ts +7 -0
- package/dist/auth/auth-errors.d.ts.map +1 -0
- package/dist/auth/auth-errors.js +13 -0
- package/dist/auth/auth-hooks.d.ts +6 -0
- package/dist/auth/auth-hooks.d.ts.map +1 -0
- package/dist/auth/auth-hooks.js +108 -0
- package/dist/auth/provider.d.ts +17 -0
- package/dist/auth/provider.d.ts.map +1 -0
- package/dist/auth/provider.js +1 -0
- package/dist/client/index.d.ts +24 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +31 -0
- package/dist/components/AdminApp.svelte +1077 -0
- package/dist/components/AdminApp.svelte.d.ts +24 -0
- package/dist/components/AdminApp.svelte.d.ts.map +1 -0
- package/dist/components/admin/AdminLayout.svelte +115 -0
- package/dist/components/admin/AdminLayout.svelte.d.ts +15 -0
- package/dist/components/admin/AdminLayout.svelte.d.ts.map +1 -0
- package/dist/components/admin/DocumentEditor.svelte +795 -0
- package/dist/components/admin/DocumentEditor.svelte.d.ts +18 -0
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -0
- package/dist/components/admin/DocumentTypesList.svelte +97 -0
- package/dist/components/admin/DocumentTypesList.svelte.d.ts +14 -0
- package/dist/components/admin/DocumentTypesList.svelte.d.ts.map +1 -0
- package/dist/components/admin/ObjectModal.svelte +135 -0
- package/dist/components/admin/ObjectModal.svelte.d.ts +15 -0
- package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -0
- package/dist/components/admin/SchemaField.svelte +171 -0
- package/dist/components/admin/SchemaField.svelte.d.ts +19 -0
- package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ArrayField.svelte +266 -0
- package/dist/components/admin/fields/ArrayField.svelte.d.ts +12 -0
- package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/BooleanField.svelte +35 -0
- package/dist/components/admin/fields/BooleanField.svelte.d.ts +13 -0
- package/dist/components/admin/fields/BooleanField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ImageField.svelte +284 -0
- package/dist/components/admin/fields/ImageField.svelte.d.ts +15 -0
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/NumberField.svelte +82 -0
- package/dist/components/admin/fields/NumberField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/NumberField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ReferenceField.svelte +260 -0
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts +12 -0
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/SlugField.svelte +74 -0
- package/dist/components/admin/fields/SlugField.svelte.d.ts +15 -0
- package/dist/components/admin/fields/SlugField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/StringField.svelte +40 -0
- package/dist/components/admin/fields/StringField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/TextareaField.svelte +40 -0
- package/dist/components/admin/fields/TextareaField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/TextareaField.svelte.d.ts.map +1 -0
- package/dist/components/fields/index.d.ts +9 -0
- package/dist/components/fields/index.d.ts.map +1 -0
- package/dist/components/fields/index.js +9 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +12 -0
- package/dist/components/layout/OrganizationSwitcher.svelte +218 -0
- package/dist/components/layout/OrganizationSwitcher.svelte.d.ts +11 -0
- package/dist/components/layout/OrganizationSwitcher.svelte.d.ts.map +1 -0
- package/dist/components/layout/Sidebar.svelte +88 -0
- package/dist/components/layout/Sidebar.svelte.d.ts +14 -0
- package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/AppSidebar.svelte +63 -0
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +11 -0
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/NavMain.svelte +95 -0
- package/dist/components/layout/sidebar/NavMain.svelte.d.ts +19 -0
- package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/NavSecondary.svelte +69 -0
- package/dist/components/layout/sidebar/NavSecondary.svelte.d.ts +9 -0
- package/dist/components/layout/sidebar/NavSecondary.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/NavUser.svelte +85 -0
- package/dist/components/layout/sidebar/NavUser.svelte.d.ts +9 -0
- package/dist/components/layout/sidebar/NavUser.svelte.d.ts.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +15 -0
- package/dist/db/adapters/index.d.ts +1 -0
- package/dist/db/adapters/index.d.ts.map +1 -0
- package/dist/db/adapters/index.js +4 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +4 -0
- package/dist/db/interfaces/asset.d.ts +51 -0
- package/dist/db/interfaces/asset.d.ts.map +1 -0
- package/dist/db/interfaces/asset.js +1 -0
- package/dist/db/interfaces/document.d.ts +36 -0
- package/dist/db/interfaces/document.d.ts.map +1 -0
- package/dist/db/interfaces/document.js +1 -0
- package/dist/db/interfaces/index.d.ts +73 -0
- package/dist/db/interfaces/index.d.ts.map +1 -0
- package/dist/db/interfaces/index.js +1 -0
- package/dist/db/interfaces/organization.d.ts +27 -0
- package/dist/db/interfaces/organization.d.ts.map +1 -0
- package/dist/db/interfaces/organization.js +1 -0
- package/dist/db/interfaces/schema.d.ts +21 -0
- package/dist/db/interfaces/schema.d.ts.map +1 -0
- package/dist/db/interfaces/schema.js +1 -0
- package/dist/db/interfaces/user.d.ts +15 -0
- package/dist/db/interfaces/user.d.ts.map +1 -0
- package/dist/db/interfaces/user.js +1 -0
- package/dist/db/utils/reference-resolver.d.ts +18 -0
- package/dist/db/utils/reference-resolver.d.ts.map +1 -0
- package/dist/db/utils/reference-resolver.js +80 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +4 -0
- package/dist/email/index.d.ts +2 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +4 -0
- package/dist/email/interfaces/email.d.ts +42 -0
- package/dist/email/interfaces/email.d.ts.map +1 -0
- package/dist/email/interfaces/email.js +1 -0
- package/dist/engine.d.ts +26 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +66 -0
- package/dist/field-validation/rule.d.ts +51 -0
- package/dist/field-validation/rule.d.ts.map +1 -0
- package/dist/field-validation/rule.js +221 -0
- package/dist/field-validation/utils.d.ts +21 -0
- package/dist/field-validation/utils.d.ts.map +1 -0
- package/dist/field-validation/utils.js +66 -0
- package/dist/hooks.d.ts +23 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +96 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/plugins/README.md +154 -0
- package/dist/routes/assets-by-id.d.ts +5 -0
- package/dist/routes/assets-by-id.d.ts.map +1 -0
- package/dist/routes/assets-by-id.js +138 -0
- package/dist/routes/assets-cdn.d.ts +3 -0
- package/dist/routes/assets-cdn.d.ts.map +1 -0
- package/dist/routes/assets-cdn.js +155 -0
- package/dist/routes/assets.d.ts +4 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +94 -0
- package/dist/routes/documents-by-id.d.ts +5 -0
- package/dist/routes/documents-by-id.d.ts.map +1 -0
- package/dist/routes/documents-by-id.js +142 -0
- package/dist/routes/documents-publish.d.ts +4 -0
- package/dist/routes/documents-publish.d.ts.map +1 -0
- package/dist/routes/documents-publish.js +151 -0
- package/dist/routes/documents.d.ts +4 -0
- package/dist/routes/documents.d.ts.map +1 -0
- package/dist/routes/documents.js +131 -0
- package/dist/routes/index.d.ts +6 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +10 -0
- package/dist/routes/organizations-by-id.d.ts +5 -0
- package/dist/routes/organizations-by-id.d.ts.map +1 -0
- package/dist/routes/organizations-by-id.js +187 -0
- package/dist/routes/organizations-invitations.d.ts +4 -0
- package/dist/routes/organizations-invitations.d.ts.map +1 -0
- package/dist/routes/organizations-invitations.js +125 -0
- package/dist/routes/organizations-members.d.ts +5 -0
- package/dist/routes/organizations-members.d.ts.map +1 -0
- package/dist/routes/organizations-members.js +206 -0
- package/dist/routes/organizations-switch.d.ts +3 -0
- package/dist/routes/organizations-switch.d.ts.map +1 -0
- package/dist/routes/organizations-switch.js +53 -0
- package/dist/routes/organizations.d.ts +4 -0
- package/dist/routes/organizations.d.ts.map +1 -0
- package/dist/routes/organizations.js +108 -0
- package/dist/routes/schemas-by-type.d.ts +3 -0
- package/dist/routes/schemas-by-type.d.ts.map +1 -0
- package/dist/routes/schemas-by-type.js +25 -0
- package/dist/routes/schemas.d.ts +3 -0
- package/dist/routes/schemas.d.ts.map +1 -0
- package/dist/routes/schemas.js +11 -0
- package/dist/routes-exports.d.ts +14 -0
- package/dist/routes-exports.d.ts.map +1 -0
- package/dist/routes-exports.js +19 -0
- package/dist/schema-context.svelte.d.ts +10 -0
- package/dist/schema-context.svelte.d.ts.map +1 -0
- package/dist/schema-context.svelte.js +18 -0
- package/dist/schema-utils/cleanup.d.ts +21 -0
- package/dist/schema-utils/cleanup.d.ts.map +1 -0
- package/dist/schema-utils/cleanup.js +80 -0
- package/dist/schema-utils/index.d.ts +4 -0
- package/dist/schema-utils/index.d.ts.map +1 -0
- package/dist/schema-utils/index.js +4 -0
- package/dist/schema-utils/utils.d.ts +30 -0
- package/dist/schema-utils/utils.d.ts.map +1 -0
- package/dist/schema-utils/utils.js +37 -0
- package/dist/schema-utils/validator.d.ts +6 -0
- package/dist/schema-utils/validator.d.ts.map +1 -0
- package/dist/schema-utils/validator.js +45 -0
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +28 -0
- package/dist/services/asset-service.d.ts +86 -0
- package/dist/services/asset-service.d.ts.map +1 -0
- package/dist/services/asset-service.js +187 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +4 -0
- package/dist/storage/adapters/index.d.ts +2 -0
- package/dist/storage/adapters/index.d.ts.map +1 -0
- package/dist/storage/adapters/index.js +2 -0
- package/dist/storage/adapters/local-storage-adapter.d.ts +54 -0
- package/dist/storage/adapters/local-storage-adapter.d.ts.map +1 -0
- package/dist/storage/adapters/local-storage-adapter.js +187 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/interfaces/index.d.ts +2 -0
- package/dist/storage/interfaces/index.d.ts.map +1 -0
- package/dist/storage/interfaces/index.js +2 -0
- package/dist/storage/interfaces/storage.d.ts +91 -0
- package/dist/storage/interfaces/storage.d.ts.map +1 -0
- package/dist/storage/interfaces/storage.js +1 -0
- package/dist/storage/providers/storage.d.ts +43 -0
- package/dist/storage/providers/storage.d.ts.map +1 -0
- package/dist/storage/providers/storage.js +64 -0
- package/dist/types/asset.d.ts +73 -0
- package/dist/types/asset.d.ts.map +1 -0
- package/dist/types/asset.js +2 -0
- package/dist/types/auth.d.ts +50 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +41 -0
- package/dist/types/config.d.ts +47 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +1 -0
- package/dist/types/document.d.ts +34 -0
- package/dist/types/document.d.ts.map +1 -0
- package/dist/types/document.js +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/organization.d.ts +105 -0
- package/dist/types/organization.d.ts.map +1 -0
- package/dist/types/organization.js +3 -0
- package/dist/types/schemas.d.ts +114 -0
- package/dist/types/schemas.d.ts.map +1 -0
- package/dist/types/schemas.js +1 -0
- package/dist/types/sidebar.d.ts +33 -0
- package/dist/types/sidebar.d.ts.map +1 -0
- package/dist/types/sidebar.js +1 -0
- package/dist/types/user.d.ts +14 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +1 -0
- package/dist/utils/content-hash.d.ts +22 -0
- package/dist/utils/content-hash.d.ts.map +1 -0
- package/dist/utils/content-hash.js +67 -0
- package/dist/utils/image-url.d.ts +88 -0
- package/dist/utils/image-url.d.ts.map +1 -0
- package/dist/utils/image-url.js +165 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/slug.d.ts +13 -0
- package/dist/utils/slug.d.ts.map +1 -0
- package/dist/utils/slug.js +30 -0
- package/package.json +11 -41
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* AdminApp - Complete CMS Admin Interface
|
|
4
|
+
* A packaged, reusable Sanity-style admin UI
|
|
5
|
+
*/
|
|
6
|
+
import { Alert, AlertDescription, AlertTitle } from '@aphexcms/ui/shadcn/alert';
|
|
7
|
+
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
8
|
+
import * as Tabs from '@aphexcms/ui/shadcn/tabs';
|
|
9
|
+
import { page } from '$app/state';
|
|
10
|
+
import { goto } from '$app/navigation';
|
|
11
|
+
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
12
|
+
import type { SchemaType } from '../types/index.js';
|
|
13
|
+
import DocumentEditor from './admin/DocumentEditor.svelte';
|
|
14
|
+
import type { DocumentType } from '../types/index.js';
|
|
15
|
+
import { documents } from '../api/index.js';
|
|
16
|
+
|
|
17
|
+
type InitDocumentType = Pick<DocumentType, 'name' | 'title' | 'description'>;
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
schemas: SchemaType[];
|
|
21
|
+
documentTypes: InitDocumentType[];
|
|
22
|
+
schemaError?: { message: string } | null;
|
|
23
|
+
title?: string;
|
|
24
|
+
graphqlSettings?: { endpoint: string; enableGraphiQL: boolean } | null;
|
|
25
|
+
isReadOnly?: boolean;
|
|
26
|
+
activeTab?: { value: 'structure' | 'vision' };
|
|
27
|
+
handleTabChange: (value: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
schemas,
|
|
32
|
+
documentTypes,
|
|
33
|
+
schemaError = null,
|
|
34
|
+
title = 'Aphex CMS',
|
|
35
|
+
graphqlSettings = null,
|
|
36
|
+
isReadOnly = false,
|
|
37
|
+
activeTab = { value: 'structure' } as { value: 'structure' | 'vision' },
|
|
38
|
+
handleTabChange = () => {},
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// Set schema context for child components
|
|
43
|
+
|
|
44
|
+
const hasDocumentTypes = $derived(documentTypes.length > 0);
|
|
45
|
+
|
|
46
|
+
// Client-side routing state
|
|
47
|
+
let currentView = $state<'dashboard' | 'documents' | 'editor'>('dashboard');
|
|
48
|
+
let selectedDocumentType = $state<string | null>(null);
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
let documentsList = $state<any[]>([]);
|
|
52
|
+
let loading = $state(false);
|
|
53
|
+
let error = $state<string | null>(null);
|
|
54
|
+
|
|
55
|
+
// Mobile navigation state (Sanity-style)
|
|
56
|
+
let mobileView = $state<'types' | 'documents' | 'editor'>('types');
|
|
57
|
+
|
|
58
|
+
// Window size reactivity
|
|
59
|
+
let windowWidth = $state(1024); // Default to desktop size
|
|
60
|
+
|
|
61
|
+
// Document editor state (moved before layoutConfig)
|
|
62
|
+
let editingDocumentId = $state<string | null>(null);
|
|
63
|
+
let isCreatingDocument = $state(false);
|
|
64
|
+
|
|
65
|
+
// Editor stack for nested references
|
|
66
|
+
interface EditorStackItem {
|
|
67
|
+
documentId: string;
|
|
68
|
+
documentType: string;
|
|
69
|
+
isCreating: boolean;
|
|
70
|
+
}
|
|
71
|
+
let editorStack = $state<EditorStackItem[]>([]);
|
|
72
|
+
|
|
73
|
+
// Track which editor is currently active/focused (0 = primary, 1+ = stacked)
|
|
74
|
+
let activeEditorIndex = $state<number>(0);
|
|
75
|
+
|
|
76
|
+
// Calculate how many editors can be shown expanded based on available space
|
|
77
|
+
const MIN_EDITOR_WIDTH = 600; // Minimum width for ANY expanded editor
|
|
78
|
+
const COLLAPSED_WIDTH = 60; // Width of collapsed panels
|
|
79
|
+
const TYPES_EXPANDED = 350;
|
|
80
|
+
const DOCS_EXPANDED = 350;
|
|
81
|
+
|
|
82
|
+
let layoutConfig = $derived.by(() => {
|
|
83
|
+
const start = performance.now();
|
|
84
|
+
const totalEditors = (currentView === 'editor' ? 1 : 0) + editorStack.length;
|
|
85
|
+
|
|
86
|
+
if (totalEditors === 0) {
|
|
87
|
+
return {
|
|
88
|
+
totalEditors: 0,
|
|
89
|
+
expandedCount: 0,
|
|
90
|
+
collapsedCount: 0,
|
|
91
|
+
typesCollapsed: false,
|
|
92
|
+
docsCollapsed: false,
|
|
93
|
+
expandedIndices: [] as number[],
|
|
94
|
+
activeIndex: activeEditorIndex,
|
|
95
|
+
typesExpanded: true,
|
|
96
|
+
docsExpanded: true
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Ensure activeEditorIndex is valid (can be -1 for types, -2 for docs)
|
|
101
|
+
const validActiveIndex =
|
|
102
|
+
activeEditorIndex < 0
|
|
103
|
+
? activeEditorIndex
|
|
104
|
+
: Math.max(0, Math.min(activeEditorIndex, totalEditors - 1));
|
|
105
|
+
|
|
106
|
+
// Check if types or docs are active (user clicked on collapsed strip)
|
|
107
|
+
const typesActive = activeEditorIndex === -1;
|
|
108
|
+
const docsActive = activeEditorIndex === -2;
|
|
109
|
+
|
|
110
|
+
// Calculate space requirements
|
|
111
|
+
// If user clicked types/docs, force those panels expanded
|
|
112
|
+
// Otherwise, prioritize editors over panels
|
|
113
|
+
|
|
114
|
+
let typesExpanded = typesActive || true; // Expand types if clicked, or by default
|
|
115
|
+
let docsExpanded = docsActive || true; // Expand docs if clicked, or by default
|
|
116
|
+
let typesWidth = typesExpanded ? TYPES_EXPANDED : COLLAPSED_WIDTH;
|
|
117
|
+
let docsWidth = selectedDocumentType ? (docsExpanded ? DOCS_EXPANDED : COLLAPSED_WIDTH) : 0;
|
|
118
|
+
|
|
119
|
+
// If user explicitly clicked types/docs, keep those panels expanded no matter what
|
|
120
|
+
if (typesActive || docsActive) {
|
|
121
|
+
// Force the clicked panel to stay expanded
|
|
122
|
+
typesExpanded = typesActive ? true : typesExpanded;
|
|
123
|
+
docsExpanded = docsActive ? true : docsExpanded;
|
|
124
|
+
typesWidth = typesActive ? TYPES_EXPANDED : COLLAPSED_WIDTH;
|
|
125
|
+
docsWidth = docsActive ? DOCS_EXPANDED : selectedDocumentType ? COLLAPSED_WIDTH : 0;
|
|
126
|
+
|
|
127
|
+
// Calculate how many editors fit with these panel sizes
|
|
128
|
+
let remainingWidth = windowWidth - typesWidth - docsWidth;
|
|
129
|
+
let maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
|
|
130
|
+
|
|
131
|
+
// Expand as many editors as possible around the last active editor
|
|
132
|
+
if (maxExpandedEditors < 1) maxExpandedEditors = 0;
|
|
133
|
+
|
|
134
|
+
// Build expanded indices for editors
|
|
135
|
+
let expandedIndices: number[] = [];
|
|
136
|
+
if (maxExpandedEditors > 0 && totalEditors > 0) {
|
|
137
|
+
// Start from rightmost editor (most recently opened)
|
|
138
|
+
const lastEditorIndex = totalEditors - 1;
|
|
139
|
+
expandedIndices.push(lastEditorIndex);
|
|
140
|
+
|
|
141
|
+
// Expand editors to the left if space allows
|
|
142
|
+
for (
|
|
143
|
+
let i = lastEditorIndex - 1;
|
|
144
|
+
i >= 0 && expandedIndices.length < maxExpandedEditors;
|
|
145
|
+
i--
|
|
146
|
+
) {
|
|
147
|
+
expandedIndices.push(i);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const expandedCount = expandedIndices.length;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
totalEditors,
|
|
155
|
+
expandedCount,
|
|
156
|
+
collapsedCount: totalEditors - expandedCount,
|
|
157
|
+
typesCollapsed: !typesExpanded,
|
|
158
|
+
docsCollapsed: !docsExpanded,
|
|
159
|
+
expandedIndices,
|
|
160
|
+
activeIndex: validActiveIndex,
|
|
161
|
+
typesExpanded,
|
|
162
|
+
docsExpanded
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Normal mode: prioritize editors over panels
|
|
167
|
+
// Calculate how many editors we can fit with current panel widths
|
|
168
|
+
let remainingWidth = windowWidth - typesWidth - docsWidth;
|
|
169
|
+
let maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
|
|
170
|
+
|
|
171
|
+
// If we can't fit all editors, start collapsing panels
|
|
172
|
+
if (maxExpandedEditors < totalEditors) {
|
|
173
|
+
// Try collapsing docs first
|
|
174
|
+
docsWidth = selectedDocumentType ? COLLAPSED_WIDTH : 0;
|
|
175
|
+
docsExpanded = false;
|
|
176
|
+
remainingWidth = windowWidth - typesWidth - docsWidth;
|
|
177
|
+
maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// If still not enough space, collapse types too
|
|
181
|
+
if (maxExpandedEditors < totalEditors) {
|
|
182
|
+
typesWidth = COLLAPSED_WIDTH;
|
|
183
|
+
typesExpanded = false;
|
|
184
|
+
remainingWidth = windowWidth - typesWidth - docsWidth;
|
|
185
|
+
maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Always expand at least the active editor
|
|
189
|
+
if (maxExpandedEditors < 1) {
|
|
190
|
+
maxExpandedEditors = 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Expand editors around the active one symmetrically
|
|
194
|
+
let expandedIndices: number[] = [validActiveIndex];
|
|
195
|
+
|
|
196
|
+
if (maxExpandedEditors > 1) {
|
|
197
|
+
// How many editors to add on each side
|
|
198
|
+
const slotsToFill = Math.min(maxExpandedEditors - 1, totalEditors - 1);
|
|
199
|
+
|
|
200
|
+
// Expand symmetrically around active editor
|
|
201
|
+
for (let offset = 1; offset <= slotsToFill; offset++) {
|
|
202
|
+
const leftIndex = validActiveIndex - offset;
|
|
203
|
+
const rightIndex = validActiveIndex + offset;
|
|
204
|
+
|
|
205
|
+
// Alternate left and right to maintain symmetry
|
|
206
|
+
if (offset % 2 === 1) {
|
|
207
|
+
// Odd offset: try right first, then left
|
|
208
|
+
if (rightIndex < totalEditors && !expandedIndices.includes(rightIndex)) {
|
|
209
|
+
expandedIndices.push(rightIndex);
|
|
210
|
+
if (expandedIndices.length >= maxExpandedEditors) break;
|
|
211
|
+
}
|
|
212
|
+
if (leftIndex >= 0 && !expandedIndices.includes(leftIndex)) {
|
|
213
|
+
expandedIndices.push(leftIndex);
|
|
214
|
+
if (expandedIndices.length >= maxExpandedEditors) break;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// Even offset: try left first, then right
|
|
218
|
+
if (leftIndex >= 0 && !expandedIndices.includes(leftIndex)) {
|
|
219
|
+
expandedIndices.push(leftIndex);
|
|
220
|
+
if (expandedIndices.length >= maxExpandedEditors) break;
|
|
221
|
+
}
|
|
222
|
+
if (rightIndex < totalEditors && !expandedIndices.includes(rightIndex)) {
|
|
223
|
+
expandedIndices.push(rightIndex);
|
|
224
|
+
if (expandedIndices.length >= maxExpandedEditors) break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const expandedCount = expandedIndices.length;
|
|
231
|
+
|
|
232
|
+
const end = performance.now();
|
|
233
|
+
console.log(
|
|
234
|
+
`[Layout Calc] ${(end - start).toFixed(3)}ms | Editors: ${totalEditors} | Expanded: ${expandedCount} | Window: ${windowWidth}px | Active: ${validActiveIndex} | ExpandedIndices: [${expandedIndices.join(', ')}]`
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
totalEditors,
|
|
239
|
+
expandedCount,
|
|
240
|
+
collapsedCount: totalEditors - expandedCount,
|
|
241
|
+
typesCollapsed: !typesExpanded,
|
|
242
|
+
docsCollapsed: !docsExpanded,
|
|
243
|
+
expandedIndices,
|
|
244
|
+
activeIndex: validActiveIndex,
|
|
245
|
+
typesExpanded,
|
|
246
|
+
docsExpanded
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
let typesPanel = $derived.by(() => {
|
|
251
|
+
if (windowWidth < 620) {
|
|
252
|
+
return mobileView === 'types' ? 'w-full' : 'hidden';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return layoutConfig.typesExpanded ? 'w-[350px]' : 'w-[60px]';
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
let documentsPanelState = $derived.by(() => {
|
|
259
|
+
if (windowWidth < 620) {
|
|
260
|
+
const state = { visible: mobileView === 'documents', width: 'full' };
|
|
261
|
+
console.log('[Mobile Documents Panel]', { windowWidth, mobileView, state });
|
|
262
|
+
return state;
|
|
263
|
+
}
|
|
264
|
+
if (!selectedDocumentType) return { visible: false, width: 'none' };
|
|
265
|
+
|
|
266
|
+
const width = layoutConfig.docsExpanded ? 'normal' : 'compact';
|
|
267
|
+
return { visible: true, width };
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
let primaryEditorState = $derived.by(() => {
|
|
271
|
+
if (windowWidth < 620) {
|
|
272
|
+
return { visible: mobileView === 'editor', expanded: true };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (currentView !== 'editor') return { visible: false, expanded: false };
|
|
276
|
+
|
|
277
|
+
const primaryIndex = 0;
|
|
278
|
+
const isExpanded = layoutConfig.expandedIndices.includes(primaryIndex);
|
|
279
|
+
|
|
280
|
+
return { visible: true, expanded: isExpanded };
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Update window width on resize
|
|
284
|
+
$effect(() => {
|
|
285
|
+
if (typeof window !== 'undefined') {
|
|
286
|
+
windowWidth = window.innerWidth;
|
|
287
|
+
const handleResize = () => {
|
|
288
|
+
windowWidth = window.innerWidth;
|
|
289
|
+
};
|
|
290
|
+
window.addEventListener('resize', handleResize);
|
|
291
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Watch URL params for bookmarkable navigation
|
|
296
|
+
$effect(() => {
|
|
297
|
+
const url = page.url;
|
|
298
|
+
const docType = url.searchParams.get('docType');
|
|
299
|
+
const action = url.searchParams.get('action');
|
|
300
|
+
const docId = url.searchParams.get('docId');
|
|
301
|
+
const stackParam = url.searchParams.get('stack');
|
|
302
|
+
|
|
303
|
+
console.log('[URL Effect] Params:', {
|
|
304
|
+
docType,
|
|
305
|
+
action,
|
|
306
|
+
docId,
|
|
307
|
+
stackParam,
|
|
308
|
+
fullURL: url.toString()
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (action === 'create' && docType) {
|
|
312
|
+
console.log('[URL Effect] Branch: CREATE');
|
|
313
|
+
currentView = 'editor';
|
|
314
|
+
mobileView = 'editor';
|
|
315
|
+
selectedDocumentType = docType;
|
|
316
|
+
isCreatingDocument = true;
|
|
317
|
+
editingDocumentId = null;
|
|
318
|
+
editorStack = [];
|
|
319
|
+
fetchDocuments(docType);
|
|
320
|
+
} else if (docId) {
|
|
321
|
+
console.log('[URL Effect] Branch: EDIT (docId)');
|
|
322
|
+
currentView = 'editor';
|
|
323
|
+
mobileView = 'editor';
|
|
324
|
+
editingDocumentId = docId;
|
|
325
|
+
isCreatingDocument = false;
|
|
326
|
+
|
|
327
|
+
// Parse stack param to restore stacked editors
|
|
328
|
+
if (stackParam) {
|
|
329
|
+
const stackItems = stackParam.split(',').map((item) => {
|
|
330
|
+
const [type, id] = item.split(':');
|
|
331
|
+
return { documentType: type, documentId: id, isCreating: false };
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Only update stack and activeEditorIndex if the stack actually changed
|
|
335
|
+
const stackChanged =
|
|
336
|
+
editorStack.length !== stackItems.length ||
|
|
337
|
+
editorStack.some(
|
|
338
|
+
(item, i) =>
|
|
339
|
+
item.documentId !== stackItems[i]?.documentId ||
|
|
340
|
+
item.documentType !== stackItems[i]?.documentType
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (stackChanged) {
|
|
344
|
+
console.log('[AdminApp] Stack changed, updating editorStack and activeEditorIndex');
|
|
345
|
+
editorStack = stackItems;
|
|
346
|
+
// Set active editor to the last stacked editor
|
|
347
|
+
activeEditorIndex = stackItems.length; // 0 = primary, so stackItems.length is the last stacked editor
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Only reset if there was a stack before
|
|
351
|
+
if (editorStack.length > 0) {
|
|
352
|
+
editorStack = [];
|
|
353
|
+
activeEditorIndex = 0; // Primary editor is active
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (docType) {
|
|
358
|
+
selectedDocumentType = docType;
|
|
359
|
+
if (documentsList.length === 0 || selectedDocumentType !== docType) {
|
|
360
|
+
fetchDocuments(docType);
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
fetchDocumentForEditing(docId);
|
|
364
|
+
}
|
|
365
|
+
} else if (docType) {
|
|
366
|
+
console.log('[URL Effect] Branch: DOCUMENTS (docType only)');
|
|
367
|
+
currentView = 'documents';
|
|
368
|
+
mobileView = 'documents';
|
|
369
|
+
selectedDocumentType = docType;
|
|
370
|
+
editingDocumentId = null;
|
|
371
|
+
isCreatingDocument = false;
|
|
372
|
+
editorStack = [];
|
|
373
|
+
fetchDocuments(docType);
|
|
374
|
+
} else {
|
|
375
|
+
currentView = 'dashboard';
|
|
376
|
+
mobileView = 'types';
|
|
377
|
+
selectedDocumentType = null;
|
|
378
|
+
editingDocumentId = null;
|
|
379
|
+
isCreatingDocument = false;
|
|
380
|
+
editorStack = [];
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Watch orgId changes to refetch documents when switching organizations
|
|
385
|
+
$effect(() => {
|
|
386
|
+
const orgId = page.url.searchParams.get('orgId');
|
|
387
|
+
|
|
388
|
+
// When orgId changes and we have a selected document type, refetch documents
|
|
389
|
+
if (orgId && selectedDocumentType) {
|
|
390
|
+
fetchDocuments(selectedDocumentType);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
async function navigateToDocumentType(docType: string) {
|
|
395
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
396
|
+
params.set('docType', docType);
|
|
397
|
+
params.delete('docId');
|
|
398
|
+
params.delete('action');
|
|
399
|
+
params.delete('stack');
|
|
400
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
401
|
+
mobileView = 'documents';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function navigateToCreateDocument(docType: string) {
|
|
405
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
406
|
+
params.set('docType', docType);
|
|
407
|
+
params.set('action', 'create');
|
|
408
|
+
params.delete('docId');
|
|
409
|
+
params.delete('stack');
|
|
410
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
411
|
+
mobileView = 'editor';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function navigateToEditDocument(docId: string, docType?: string, replace: boolean = false) {
|
|
415
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
416
|
+
params.set('docId', docId);
|
|
417
|
+
if (docType) params.set('docType', docType);
|
|
418
|
+
params.delete('action');
|
|
419
|
+
params.delete('fromDocId');
|
|
420
|
+
params.delete('fromDocType');
|
|
421
|
+
await goto(`/admin?${params.toString()}`, { replaceState: replace });
|
|
422
|
+
mobileView = 'editor';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function navigateBack() {
|
|
426
|
+
// Check if we came from another document (mobile reference navigation)
|
|
427
|
+
const fromDocId = page.url.searchParams.get('fromDocId');
|
|
428
|
+
const fromDocType = page.url.searchParams.get('fromDocType');
|
|
429
|
+
|
|
430
|
+
if (fromDocId && fromDocType) {
|
|
431
|
+
// Navigate back to the document we came from
|
|
432
|
+
await navigateToEditDocument(fromDocId, fromDocType, false);
|
|
433
|
+
} else if (selectedDocumentType) {
|
|
434
|
+
// Navigate back to document list
|
|
435
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
436
|
+
params.set('docType', selectedDocumentType);
|
|
437
|
+
params.delete('docId');
|
|
438
|
+
params.delete('action');
|
|
439
|
+
params.delete('stack');
|
|
440
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
441
|
+
mobileView = 'documents';
|
|
442
|
+
} else {
|
|
443
|
+
// Navigate back to home
|
|
444
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
445
|
+
params.delete('docType');
|
|
446
|
+
params.delete('docId');
|
|
447
|
+
params.delete('action');
|
|
448
|
+
params.delete('stack');
|
|
449
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
450
|
+
mobileView = 'types';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Handle opening reference in new editor panel
|
|
455
|
+
async function handleOpenReference(documentId: string, documentType: string) {
|
|
456
|
+
// On mobile, navigate to the referenced document directly
|
|
457
|
+
// Add fromDocId to track where we came from for proper back navigation
|
|
458
|
+
if (windowWidth < 620) {
|
|
459
|
+
const params = new SvelteURLSearchParams({
|
|
460
|
+
docId: documentId,
|
|
461
|
+
docType: documentType
|
|
462
|
+
});
|
|
463
|
+
// Track the document we're coming from
|
|
464
|
+
if (editingDocumentId) {
|
|
465
|
+
params.set('fromDocId', editingDocumentId);
|
|
466
|
+
if (selectedDocumentType) {
|
|
467
|
+
params.set('fromDocType', selectedDocumentType);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
471
|
+
mobileView = 'editor';
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// On desktop, add to editor stack
|
|
476
|
+
const newStack = [...editorStack, { documentId, documentType, isCreating: false }];
|
|
477
|
+
|
|
478
|
+
// Build stack param string: type1:id1,type2:id2,...
|
|
479
|
+
const stackParam = newStack.map((item) => `${item.documentType}:${item.documentId}`).join(',');
|
|
480
|
+
|
|
481
|
+
// Update URL with new stack
|
|
482
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
483
|
+
params.set('stack', stackParam);
|
|
484
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
485
|
+
|
|
486
|
+
// Set the new editor as active
|
|
487
|
+
activeEditorIndex = newStack.length; // 0 = primary, so stack.length is the new editor
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Close editor from stack
|
|
491
|
+
async function handleCloseStackedEditor(index: number) {
|
|
492
|
+
const newStack = editorStack.slice(0, index);
|
|
493
|
+
|
|
494
|
+
// Update URL
|
|
495
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
496
|
+
if (newStack.length > 0) {
|
|
497
|
+
const stackParam = newStack
|
|
498
|
+
.map((item) => `${item.documentType}:${item.documentId}`)
|
|
499
|
+
.join(',');
|
|
500
|
+
params.set('stack', stackParam);
|
|
501
|
+
} else {
|
|
502
|
+
params.delete('stack');
|
|
503
|
+
}
|
|
504
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
505
|
+
|
|
506
|
+
// Reset active editor to the last one
|
|
507
|
+
activeEditorIndex = Math.min(activeEditorIndex, newStack.length);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Set active editor when clicking on a strip
|
|
511
|
+
function setActiveEditor(index: number) {
|
|
512
|
+
console.log('[AdminApp] setActiveEditor called:', {
|
|
513
|
+
previousIndex: activeEditorIndex,
|
|
514
|
+
newIndex: index,
|
|
515
|
+
editorStackLength: editorStack.length
|
|
516
|
+
});
|
|
517
|
+
activeEditorIndex = index;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function handleAutoSave(documentId: string, title: string) {
|
|
521
|
+
if (documentsList.length > 0) {
|
|
522
|
+
documentsList = documentsList.map((doc) =>
|
|
523
|
+
doc.id === documentId ? { ...doc, title: title } : doc
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async function fetchDocumentForEditing(docId: string) {
|
|
529
|
+
loading = true;
|
|
530
|
+
error = null;
|
|
531
|
+
|
|
532
|
+
try {
|
|
533
|
+
const result = await documents.getById(docId);
|
|
534
|
+
|
|
535
|
+
if (result.success && result.data) {
|
|
536
|
+
const documentType = result.data.type;
|
|
537
|
+
|
|
538
|
+
if (documentsList.length === 0 || selectedDocumentType !== documentType) {
|
|
539
|
+
await fetchDocuments(documentType);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
selectedDocumentType = documentType;
|
|
543
|
+
} else {
|
|
544
|
+
throw new Error(result.error || 'Failed to fetch document');
|
|
545
|
+
}
|
|
546
|
+
} catch (err) {
|
|
547
|
+
console.error('Failed to fetch document:', err);
|
|
548
|
+
error = err instanceof Error ? err.message : 'Failed to load document';
|
|
549
|
+
await goto('/admin', { replaceState: true });
|
|
550
|
+
} finally {
|
|
551
|
+
loading = false;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function fetchDocuments(docType: string) {
|
|
556
|
+
loading = true;
|
|
557
|
+
error = null;
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
const result = await documents.list({ docType, limit: 50 });
|
|
561
|
+
|
|
562
|
+
if (result.success && result.data) {
|
|
563
|
+
// Find schema for preview config
|
|
564
|
+
const schema = schemas.find((s) => s.name === docType);
|
|
565
|
+
const previewConfig = schema?.preview;
|
|
566
|
+
|
|
567
|
+
documentsList = result.data.map((doc: any) => {
|
|
568
|
+
const docData = doc.draftData || doc.publishedData || {};
|
|
569
|
+
|
|
570
|
+
// Use preview config if available
|
|
571
|
+
const title = previewConfig?.select?.title
|
|
572
|
+
? docData[previewConfig.select.title] || `Untitled`
|
|
573
|
+
: docData.title || `Untitled`;
|
|
574
|
+
|
|
575
|
+
const subtitle = previewConfig?.select?.subtitle
|
|
576
|
+
? docData[previewConfig.select.subtitle]
|
|
577
|
+
: undefined;
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
id: doc.id,
|
|
581
|
+
title,
|
|
582
|
+
subtitle,
|
|
583
|
+
status: doc.status,
|
|
584
|
+
publishedAt: doc.publishedAt ? new Date(doc.publishedAt) : null,
|
|
585
|
+
updatedAt: doc.updatedAt ? new Date(doc.updatedAt) : null,
|
|
586
|
+
createdAt: doc.createdAt ? new Date(doc.createdAt) : null,
|
|
587
|
+
hasChanges:
|
|
588
|
+
doc.status === 'published' &&
|
|
589
|
+
doc.draftData !== null &&
|
|
590
|
+
JSON.stringify(doc.draftData) !== JSON.stringify(doc.publishedData)
|
|
591
|
+
};
|
|
592
|
+
});
|
|
593
|
+
} else {
|
|
594
|
+
throw new Error(result.error || 'Failed to fetch documents');
|
|
595
|
+
}
|
|
596
|
+
} catch (err) {
|
|
597
|
+
console.error('Failed to fetch documents:', err);
|
|
598
|
+
error = err instanceof Error ? err.message : 'Failed to load documents';
|
|
599
|
+
documentsList = [];
|
|
600
|
+
} finally {
|
|
601
|
+
loading = false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
</script>
|
|
605
|
+
|
|
606
|
+
<svelte:head>
|
|
607
|
+
<title>{activeTab.value === 'structure' ? 'Content' : 'Vision'} - {title}</title>
|
|
608
|
+
</svelte:head>
|
|
609
|
+
|
|
610
|
+
<div class="flex h-full flex-col overflow-hidden">
|
|
611
|
+
<!-- Mobile breadcrumb navigation (< 620px) -->
|
|
612
|
+
{#if windowWidth < 620}
|
|
613
|
+
<div class="border-border bg-background border-b">
|
|
614
|
+
<div class="flex h-12 items-center px-4">
|
|
615
|
+
{#if mobileView === 'documents' && selectedDocumentType}
|
|
616
|
+
<button
|
|
617
|
+
onclick={async () => {
|
|
618
|
+
mobileView = 'types';
|
|
619
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
620
|
+
params.delete('docType');
|
|
621
|
+
params.delete('docId');
|
|
622
|
+
params.delete('action');
|
|
623
|
+
params.delete('stack');
|
|
624
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
625
|
+
}}
|
|
626
|
+
class="text-muted-foreground hover:text-foreground flex items-center gap-2 text-sm"
|
|
627
|
+
>
|
|
628
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
629
|
+
<path
|
|
630
|
+
stroke-linecap="round"
|
|
631
|
+
stroke-linejoin="round"
|
|
632
|
+
stroke-width="2"
|
|
633
|
+
d="M15 19l-7-7 7-7"
|
|
634
|
+
/>
|
|
635
|
+
</svg>
|
|
636
|
+
Content
|
|
637
|
+
</button>
|
|
638
|
+
<span class="text-muted-foreground mx-2">/</span>
|
|
639
|
+
<span class="text-sm font-medium">
|
|
640
|
+
{(documentTypes.find((t) => t.name === selectedDocumentType)?.title ||
|
|
641
|
+
selectedDocumentType) + 's'}
|
|
642
|
+
</span>
|
|
643
|
+
{:else if mobileView === 'editor'}
|
|
644
|
+
<Button
|
|
645
|
+
onclick={navigateBack}
|
|
646
|
+
variant="ghost"
|
|
647
|
+
class="text-muted-foreground hover:text-foreground text-sm"
|
|
648
|
+
>
|
|
649
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
650
|
+
<path
|
|
651
|
+
stroke-linecap="round"
|
|
652
|
+
stroke-linejoin="round"
|
|
653
|
+
stroke-width="2"
|
|
654
|
+
d="M15 19l-7-7 7-7"
|
|
655
|
+
/>
|
|
656
|
+
</svg>
|
|
657
|
+
</Button>
|
|
658
|
+
<span class="ml-3 text-sm font-medium">
|
|
659
|
+
{selectedDocumentType
|
|
660
|
+
? documentTypes.find((t) => t.name === selectedDocumentType)?.title ||
|
|
661
|
+
selectedDocumentType
|
|
662
|
+
: 'Document'}
|
|
663
|
+
</span>
|
|
664
|
+
{:else}
|
|
665
|
+
<span class="text-sm font-medium">Content</span>
|
|
666
|
+
{/if}
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
{/if}
|
|
670
|
+
|
|
671
|
+
<!-- Main Content -->
|
|
672
|
+
<div class="flex-1 overflow-hidden">
|
|
673
|
+
<Tabs.Root value={activeTab.value} onValueChange={handleTabChange} class="h-full">
|
|
674
|
+
<Tabs.Content value="structure" class="h-full overflow-hidden">
|
|
675
|
+
{#key `${currentView}-${selectedDocumentType}-${editingDocumentId}`}
|
|
676
|
+
<div class={windowWidth < 620 ? 'h-full w-full' : 'flex h-full w-full overflow-hidden'}>
|
|
677
|
+
{#if schemaError}
|
|
678
|
+
<div class="bg-destructive/5 flex flex-1 items-center justify-center p-8">
|
|
679
|
+
<div class="w-full max-w-2xl">
|
|
680
|
+
<Alert variant="destructive">
|
|
681
|
+
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
682
|
+
<path
|
|
683
|
+
stroke-linecap="round"
|
|
684
|
+
stroke-linejoin="round"
|
|
685
|
+
stroke-width="2"
|
|
686
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.704-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
687
|
+
/>
|
|
688
|
+
</svg>
|
|
689
|
+
<AlertTitle>Schema Validation Error</AlertTitle>
|
|
690
|
+
<AlertDescription class="whitespace-pre-line">
|
|
691
|
+
{schemaError.message}
|
|
692
|
+
</AlertDescription>
|
|
693
|
+
</Alert>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
{:else}
|
|
697
|
+
<!-- Types Panel -->
|
|
698
|
+
<div
|
|
699
|
+
class="border-r transition-all duration-200 {windowWidth < 620
|
|
700
|
+
? typesPanel === 'hidden'
|
|
701
|
+
? 'hidden'
|
|
702
|
+
: 'h-full w-screen'
|
|
703
|
+
: typesPanel} {typesPanel === 'hidden'
|
|
704
|
+
? 'hidden'
|
|
705
|
+
: 'block'} h-full overflow-hidden"
|
|
706
|
+
>
|
|
707
|
+
{#if typesPanel === 'w-[60px]'}
|
|
708
|
+
<button
|
|
709
|
+
onclick={() => setActiveEditor(-1)}
|
|
710
|
+
class="hover:bg-muted/30 flex h-full w-full flex-col transition-colors"
|
|
711
|
+
title="Click to expand content types"
|
|
712
|
+
>
|
|
713
|
+
<div class="flex flex-1 items-start justify-center p-2 pt-8 text-left">
|
|
714
|
+
<div
|
|
715
|
+
class="text-foreground rotate-90 transform whitespace-nowrap text-sm font-medium"
|
|
716
|
+
>
|
|
717
|
+
Content
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
</button>
|
|
721
|
+
{:else}
|
|
722
|
+
<div class="h-full overflow-y-auto">
|
|
723
|
+
{#if hasDocumentTypes}
|
|
724
|
+
{#each documentTypes as docType, index (index)}
|
|
725
|
+
<button
|
|
726
|
+
onclick={() => navigateToDocumentType(docType.name)}
|
|
727
|
+
class="hover:bg-muted/50 border-border group flex w-full items-center justify-between border-b p-3 text-left transition-colors first:border-t {selectedDocumentType ===
|
|
728
|
+
docType.name
|
|
729
|
+
? 'bg-muted/50'
|
|
730
|
+
: ''}"
|
|
731
|
+
>
|
|
732
|
+
<div class="flex items-center gap-3">
|
|
733
|
+
<div class="flex h-6 w-6 items-center justify-center">
|
|
734
|
+
<span class="text-muted-foreground">📄</span>
|
|
735
|
+
</div>
|
|
736
|
+
<div>
|
|
737
|
+
<h3 class="text-sm font-medium">{docType.title}s</h3>
|
|
738
|
+
{#if docType.description}
|
|
739
|
+
<p class="text-muted-foreground text-xs">{docType.description}</p>
|
|
740
|
+
{/if}
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
<div
|
|
744
|
+
class="text-muted-foreground group-hover:text-foreground transition-colors"
|
|
745
|
+
>
|
|
746
|
+
<svg
|
|
747
|
+
class="h-4 w-4"
|
|
748
|
+
fill="none"
|
|
749
|
+
viewBox="0 0 24 24"
|
|
750
|
+
stroke="currentColor"
|
|
751
|
+
>
|
|
752
|
+
<path
|
|
753
|
+
stroke-linecap="round"
|
|
754
|
+
stroke-linejoin="round"
|
|
755
|
+
stroke-width="2"
|
|
756
|
+
d="M9 5l7 7-7 7"
|
|
757
|
+
/>
|
|
758
|
+
</svg>
|
|
759
|
+
</div>
|
|
760
|
+
</button>
|
|
761
|
+
{/each}
|
|
762
|
+
{:else}
|
|
763
|
+
<div class="p-6 text-center">
|
|
764
|
+
<div
|
|
765
|
+
class="bg-muted/50 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full"
|
|
766
|
+
>
|
|
767
|
+
<span class="text-muted-foreground text-xl">📄</span>
|
|
768
|
+
</div>
|
|
769
|
+
<h3 class="mb-2 font-medium">No content types found</h3>
|
|
770
|
+
<p class="text-muted-foreground mb-4 text-sm">
|
|
771
|
+
Get started by defining your first schema type
|
|
772
|
+
</p>
|
|
773
|
+
<p class="text-muted-foreground text-xs">
|
|
774
|
+
Add schemas in <code class="bg-muted rounded px-1.5 py-0.5 text-xs"
|
|
775
|
+
>src/lib/schemaTypes/</code
|
|
776
|
+
>
|
|
777
|
+
</p>
|
|
778
|
+
</div>
|
|
779
|
+
{/if}
|
|
780
|
+
</div>
|
|
781
|
+
{/if}
|
|
782
|
+
</div>
|
|
783
|
+
|
|
784
|
+
<!-- Documents Panel -->
|
|
785
|
+
{#if selectedDocumentType}
|
|
786
|
+
<div
|
|
787
|
+
class="flex h-full flex-col overflow-hidden border-r transition-all duration-200
|
|
788
|
+
{!documentsPanelState.visible ? 'hidden' : ''}
|
|
789
|
+
{windowWidth < 620 ? (documentsPanelState.visible ? 'w-screen' : 'hidden') : ''}
|
|
790
|
+
{windowWidth >= 620 && documentsPanelState.width === 'full' ? 'w-full' : ''}
|
|
791
|
+
{windowWidth >= 620 && documentsPanelState.width === 'normal' ? 'w-[350px]' : ''}
|
|
792
|
+
{windowWidth >= 620 && documentsPanelState.width === 'compact' ? 'w-[60px]' : ''}
|
|
793
|
+
{windowWidth >= 620 && documentsPanelState.width === 'flex' ? 'flex-1' : ''}
|
|
794
|
+
"
|
|
795
|
+
>
|
|
796
|
+
{#if documentsPanelState.width === 'compact'}
|
|
797
|
+
<button
|
|
798
|
+
onclick={() => setActiveEditor(-2)}
|
|
799
|
+
class="hover:bg-muted/30 flex h-full w-full flex-col transition-colors"
|
|
800
|
+
title="Click to expand documents list"
|
|
801
|
+
>
|
|
802
|
+
<div class="flex flex-1 items-start justify-center p-2 pt-8 text-left">
|
|
803
|
+
<div class="text-foreground rotate-90 transform text-sm font-medium">
|
|
804
|
+
{(documentTypes.find((t) => t.name === selectedDocumentType)?.title ||
|
|
805
|
+
selectedDocumentType) + 's'}
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
</button>
|
|
809
|
+
{:else}
|
|
810
|
+
<div class="border-border bg-muted/20 border-b p-3">
|
|
811
|
+
<div class="flex items-center justify-between">
|
|
812
|
+
<div class="flex items-center gap-3">
|
|
813
|
+
{#if windowWidth > 620}
|
|
814
|
+
<!-- Desktop: Icon -->
|
|
815
|
+
<div class="flex h-6 w-6 items-center justify-center">
|
|
816
|
+
<span class="text-muted-foreground">📄</span>
|
|
817
|
+
</div>
|
|
818
|
+
{/if}
|
|
819
|
+
<div>
|
|
820
|
+
<h3 class="text-sm font-medium">
|
|
821
|
+
{(documentTypes.find((t) => t.name === selectedDocumentType)?.title ||
|
|
822
|
+
selectedDocumentType) + 's'}
|
|
823
|
+
</h3>
|
|
824
|
+
<p class="text-muted-foreground text-xs">
|
|
825
|
+
{documentsList.length} document{documentsList.length !== 1 ? 's' : ''}
|
|
826
|
+
</p>
|
|
827
|
+
</div>
|
|
828
|
+
</div>
|
|
829
|
+
{#if !isReadOnly}
|
|
830
|
+
<Button
|
|
831
|
+
size="sm"
|
|
832
|
+
variant="ghost"
|
|
833
|
+
onclick={() => navigateToCreateDocument(selectedDocumentType!)}
|
|
834
|
+
class="h-8 w-8 p-0"
|
|
835
|
+
title="Create new document"
|
|
836
|
+
>
|
|
837
|
+
<svg
|
|
838
|
+
class="h-4 w-4"
|
|
839
|
+
fill="none"
|
|
840
|
+
viewBox="0 0 24 24"
|
|
841
|
+
stroke="currentColor"
|
|
842
|
+
>
|
|
843
|
+
<path
|
|
844
|
+
stroke-linecap="round"
|
|
845
|
+
stroke-linejoin="round"
|
|
846
|
+
stroke-width="2"
|
|
847
|
+
d="M12 4v16m8-8H4"
|
|
848
|
+
/>
|
|
849
|
+
</svg>
|
|
850
|
+
</Button>
|
|
851
|
+
{/if}
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
854
|
+
|
|
855
|
+
<div class="flex-1 overflow-y-auto">
|
|
856
|
+
{#if error}
|
|
857
|
+
<div class="p-4">
|
|
858
|
+
<Alert variant="destructive">
|
|
859
|
+
<AlertDescription>{error}</AlertDescription>
|
|
860
|
+
</Alert>
|
|
861
|
+
</div>
|
|
862
|
+
{:else if loading}
|
|
863
|
+
<div class="p-3 text-center">
|
|
864
|
+
<div class="text-muted-foreground text-sm">Loading...</div>
|
|
865
|
+
</div>
|
|
866
|
+
{:else if documentsList.length > 0}
|
|
867
|
+
{#each documentsList as doc, index (index)}
|
|
868
|
+
<button
|
|
869
|
+
onclick={() => navigateToEditDocument(doc.id, selectedDocumentType!)}
|
|
870
|
+
class="hover:bg-muted/50 border-border group flex w-full items-center justify-between border-b p-3 text-left transition-colors"
|
|
871
|
+
>
|
|
872
|
+
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
873
|
+
<div class="flex h-6 w-6 items-center justify-center">
|
|
874
|
+
<span class="text-muted-foreground">📄</span>
|
|
875
|
+
</div>
|
|
876
|
+
<div class="min-w-0 flex-1">
|
|
877
|
+
<h3 class="truncate text-sm font-medium">{doc.title}</h3>
|
|
878
|
+
{#if doc.subtitle}
|
|
879
|
+
<p class="text-muted-foreground truncate text-xs">
|
|
880
|
+
{doc.subtitle}
|
|
881
|
+
</p>
|
|
882
|
+
{:else if doc.slug}
|
|
883
|
+
<p class="text-muted-foreground text-xs">/{doc.slug}</p>
|
|
884
|
+
{:else if doc.status}
|
|
885
|
+
<p class="text-muted-foreground text-xs">{doc.status}</p>
|
|
886
|
+
{/if}
|
|
887
|
+
</div>
|
|
888
|
+
</div>
|
|
889
|
+
<div class="text-muted-foreground text-xs">
|
|
890
|
+
{doc.updatedAt?.toLocaleDateString() || ''}
|
|
891
|
+
</div>
|
|
892
|
+
</button>
|
|
893
|
+
{/each}
|
|
894
|
+
{:else}
|
|
895
|
+
<div class="p-6 text-center">
|
|
896
|
+
<div
|
|
897
|
+
class="bg-muted/50 mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full"
|
|
898
|
+
>
|
|
899
|
+
<span class="text-muted-foreground text-xl">📄</span>
|
|
900
|
+
</div>
|
|
901
|
+
<h3 class="mb-2 font-medium">No documents found</h3>
|
|
902
|
+
<p class="text-muted-foreground text-sm">
|
|
903
|
+
Create your first {selectedDocumentType} document using the + button above
|
|
904
|
+
</p>
|
|
905
|
+
</div>
|
|
906
|
+
{/if}
|
|
907
|
+
</div>
|
|
908
|
+
{/if}
|
|
909
|
+
</div>
|
|
910
|
+
{/if}
|
|
911
|
+
|
|
912
|
+
<!-- Primary Editor Panel -->
|
|
913
|
+
{#if primaryEditorState.visible}
|
|
914
|
+
{#if primaryEditorState.expanded}
|
|
915
|
+
<div
|
|
916
|
+
class="transition-all duration-200 {windowWidth < 620
|
|
917
|
+
? 'w-screen'
|
|
918
|
+
: 'flex-1'} h-full overflow-y-auto"
|
|
919
|
+
style={windowWidth >= 620 ? 'min-width: 0;' : ''}
|
|
920
|
+
>
|
|
921
|
+
<DocumentEditor
|
|
922
|
+
{schemas}
|
|
923
|
+
documentType={selectedDocumentType!}
|
|
924
|
+
documentId={editingDocumentId}
|
|
925
|
+
isCreating={isCreatingDocument}
|
|
926
|
+
onBack={navigateBack}
|
|
927
|
+
onOpenReference={handleOpenReference}
|
|
928
|
+
onSaved={async (docId) => {
|
|
929
|
+
if (selectedDocumentType) {
|
|
930
|
+
await fetchDocuments(selectedDocumentType);
|
|
931
|
+
}
|
|
932
|
+
navigateToEditDocument(docId, selectedDocumentType!);
|
|
933
|
+
}}
|
|
934
|
+
onAutoSaved={handleAutoSave}
|
|
935
|
+
onPublished={async (docId) => {
|
|
936
|
+
if (selectedDocumentType) {
|
|
937
|
+
await fetchDocuments(selectedDocumentType);
|
|
938
|
+
}
|
|
939
|
+
}}
|
|
940
|
+
onDeleted={async () => {
|
|
941
|
+
if (selectedDocumentType) {
|
|
942
|
+
await fetchDocuments(selectedDocumentType);
|
|
943
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
944
|
+
params.set('docType', selectedDocumentType);
|
|
945
|
+
params.delete('docId');
|
|
946
|
+
params.delete('action');
|
|
947
|
+
await goto(`/admin?${params.toString()}`, { replaceState: false });
|
|
948
|
+
} else {
|
|
949
|
+
const orgId = page.url.searchParams.get('orgId');
|
|
950
|
+
const url = orgId ? `/admin?orgId=${orgId}` : '/admin';
|
|
951
|
+
await goto(url, { replaceState: false });
|
|
952
|
+
}
|
|
953
|
+
}}
|
|
954
|
+
{isReadOnly}
|
|
955
|
+
/>
|
|
956
|
+
</div>
|
|
957
|
+
{:else}
|
|
958
|
+
<!-- Collapsed Primary Editor Strip -->
|
|
959
|
+
<button
|
|
960
|
+
onclick={() => setActiveEditor(0)}
|
|
961
|
+
class="hover:bg-muted/50 flex h-full w-[60px] flex-col border-l transition-colors"
|
|
962
|
+
title="Click to expand {selectedDocumentType}"
|
|
963
|
+
>
|
|
964
|
+
<div class="mt-7 flex flex-1 items-start justify-center p-2 pt-8 text-left">
|
|
965
|
+
<div class="text-foreground rotate-90 transform text-sm font-medium">
|
|
966
|
+
{selectedDocumentType
|
|
967
|
+
? selectedDocumentType.charAt(0).toUpperCase() +
|
|
968
|
+
selectedDocumentType.slice(1)
|
|
969
|
+
: ''}
|
|
970
|
+
</div>
|
|
971
|
+
</div>
|
|
972
|
+
</button>
|
|
973
|
+
{/if}
|
|
974
|
+
{/if}
|
|
975
|
+
|
|
976
|
+
<!-- Stacked Reference Editors -->
|
|
977
|
+
{#each editorStack as stackedEditor, index (index)}
|
|
978
|
+
{@const editorIndex = index + 1}
|
|
979
|
+
{@const isExpanded = layoutConfig.expandedIndices.includes(editorIndex)}
|
|
980
|
+
|
|
981
|
+
{#if isExpanded}
|
|
982
|
+
<div
|
|
983
|
+
class="h-full flex-1 overflow-y-auto border-l transition-all duration-200"
|
|
984
|
+
style="min-width: 0;"
|
|
985
|
+
>
|
|
986
|
+
<DocumentEditor
|
|
987
|
+
{schemas}
|
|
988
|
+
documentType={stackedEditor.documentType}
|
|
989
|
+
documentId={stackedEditor.documentId}
|
|
990
|
+
isCreating={stackedEditor.isCreating}
|
|
991
|
+
onBack={() => handleCloseStackedEditor(index)}
|
|
992
|
+
onOpenReference={handleOpenReference}
|
|
993
|
+
onSaved={async (docId) => {}}
|
|
994
|
+
onAutoSaved={() => {}}
|
|
995
|
+
onPublished={async (docId) => {}}
|
|
996
|
+
onDeleted={async () => {
|
|
997
|
+
handleCloseStackedEditor(index);
|
|
998
|
+
}}
|
|
999
|
+
{isReadOnly}
|
|
1000
|
+
/>
|
|
1001
|
+
</div>
|
|
1002
|
+
{:else}
|
|
1003
|
+
<!-- Collapsed Stacked Editor Strip -->
|
|
1004
|
+
<button
|
|
1005
|
+
onclick={() => setActiveEditor(editorIndex)}
|
|
1006
|
+
class="hover:bg-muted/50 flex h-full w-[60px] flex-col border-l transition-colors"
|
|
1007
|
+
title="Click to expand {stackedEditor.documentType}"
|
|
1008
|
+
>
|
|
1009
|
+
<div
|
|
1010
|
+
class="-mt-2 flex h-full flex-1 items-start justify-center p-2 pt-8 text-left"
|
|
1011
|
+
>
|
|
1012
|
+
<div
|
|
1013
|
+
class="text-foreground rotate-90 transform whitespace-nowrap text-sm font-medium"
|
|
1014
|
+
>
|
|
1015
|
+
{stackedEditor.documentType.charAt(0).toUpperCase() +
|
|
1016
|
+
stackedEditor.documentType.slice(1)}
|
|
1017
|
+
</div>
|
|
1018
|
+
</div>
|
|
1019
|
+
</button>
|
|
1020
|
+
{/if}
|
|
1021
|
+
{/each}
|
|
1022
|
+
{/if}
|
|
1023
|
+
</div>
|
|
1024
|
+
{/key}
|
|
1025
|
+
</Tabs.Content>
|
|
1026
|
+
|
|
1027
|
+
{#if graphqlSettings?.enableGraphiQL}
|
|
1028
|
+
<Tabs.Content value="vision" class="m-0 h-full p-0">
|
|
1029
|
+
<div class="bg-muted/10 flex h-full items-center justify-center">
|
|
1030
|
+
<div class="space-y-4 text-center">
|
|
1031
|
+
<div
|
|
1032
|
+
class="bg-primary/10 mx-auto flex h-16 w-16 items-center justify-center rounded-full"
|
|
1033
|
+
>
|
|
1034
|
+
<svg
|
|
1035
|
+
class="text-primary h-8 w-8"
|
|
1036
|
+
fill="none"
|
|
1037
|
+
viewBox="0 0 24 24"
|
|
1038
|
+
stroke="currentColor"
|
|
1039
|
+
>
|
|
1040
|
+
<path
|
|
1041
|
+
stroke-linecap="round"
|
|
1042
|
+
stroke-linejoin="round"
|
|
1043
|
+
stroke-width="2"
|
|
1044
|
+
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
|
|
1045
|
+
/>
|
|
1046
|
+
</svg>
|
|
1047
|
+
</div>
|
|
1048
|
+
|
|
1049
|
+
<div>
|
|
1050
|
+
<h3 class="mb-2 text-lg font-semibold">GraphQL Playground</h3>
|
|
1051
|
+
|
|
1052
|
+
<p class="text-muted-foreground mb-4">Query your CMS data with the GraphQL API</p>
|
|
1053
|
+
|
|
1054
|
+
<a
|
|
1055
|
+
href={graphqlSettings.endpoint}
|
|
1056
|
+
target="_blank"
|
|
1057
|
+
class="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-2 rounded-md px-4 py-2 transition-colors"
|
|
1058
|
+
>
|
|
1059
|
+
Open Playground
|
|
1060
|
+
|
|
1061
|
+
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1062
|
+
<path
|
|
1063
|
+
stroke-linecap="round"
|
|
1064
|
+
stroke-linejoin="round"
|
|
1065
|
+
stroke-width="2"
|
|
1066
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
1067
|
+
/>
|
|
1068
|
+
</svg>
|
|
1069
|
+
</a>
|
|
1070
|
+
</div>
|
|
1071
|
+
</div>
|
|
1072
|
+
</div>
|
|
1073
|
+
</Tabs.Content>
|
|
1074
|
+
{/if}
|
|
1075
|
+
</Tabs.Root>
|
|
1076
|
+
</div>
|
|
1077
|
+
</div>
|