includio-cms 0.5.2 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/ROADMAP.md +29 -0
- package/dist/admin/api/rest/handler.d.ts +7 -0
- package/dist/admin/api/rest/handler.js +116 -0
- package/dist/admin/api/rest/middleware/apiKey.d.ts +6 -0
- package/dist/admin/api/rest/middleware/apiKey.js +45 -0
- package/dist/admin/api/rest/routes/collections.d.ts +5 -0
- package/dist/admin/api/rest/routes/collections.js +104 -0
- package/dist/admin/api/rest/routes/entries.d.ts +2 -0
- package/dist/admin/api/rest/routes/entries.js +37 -0
- package/dist/admin/api/rest/routes/languages.d.ts +1 -0
- package/dist/admin/api/rest/routes/languages.js +5 -0
- package/dist/admin/api/rest/routes/schema.d.ts +2 -0
- package/dist/admin/api/rest/routes/schema.js +78 -0
- package/dist/admin/api/rest/routes/singletons.d.ts +3 -0
- package/dist/admin/api/rest/routes/singletons.js +60 -0
- package/dist/admin/auth-client.d.ts +7 -7
- package/dist/admin/client/collection/collection-entries.svelte +56 -5
- package/dist/admin/client/collection/data-table.svelte +127 -18
- package/dist/admin/client/collection/data-table.svelte.d.ts +2 -0
- package/dist/admin/client/entry/entry-form.svelte +1 -0
- package/dist/admin/client/entry/entry.svelte +130 -123
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +92 -9
- package/dist/admin/components/fields/blocks-field.svelte +142 -112
- package/dist/admin/components/fields/blocks-field.svelte.d.ts +10 -30
- package/dist/admin/components/fields/boolean-field.svelte +28 -38
- package/dist/admin/components/fields/boolean-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/checkboxes-field.svelte +12 -24
- package/dist/admin/components/fields/checkboxes-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/content-field.svelte +4 -17
- package/dist/admin/components/fields/content-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/date-field.svelte +8 -21
- package/dist/admin/components/fields/date-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/datetime-field.svelte +8 -21
- package/dist/admin/components/fields/datetime-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/field-renderer.svelte +32 -19
- package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -1
- package/dist/admin/components/fields/field-value-bridge.svelte +21 -0
- package/dist/admin/components/fields/field-value-bridge.svelte.d.ts +31 -0
- package/dist/admin/components/fields/fields-form.svelte +13 -10
- package/dist/admin/components/fields/file-field.svelte +12 -27
- package/dist/admin/components/fields/file-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/image-field.svelte +13 -28
- package/dist/admin/components/fields/image-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/media-field.svelte +15 -30
- package/dist/admin/components/fields/media-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/number-field.svelte +6 -20
- package/dist/admin/components/fields/number-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/object-field.svelte +26 -29
- package/dist/admin/components/fields/object-field.svelte.d.ts +11 -31
- package/dist/admin/components/fields/radio-field.svelte +8 -20
- package/dist/admin/components/fields/radio-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/relation-field.svelte +28 -40
- package/dist/admin/components/fields/relation-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/richtext-field.svelte +4 -17
- package/dist/admin/components/fields/richtext-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/select-field.svelte +14 -28
- package/dist/admin/components/fields/select-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/seo-field.svelte +5 -12
- package/dist/admin/components/fields/seo-field.svelte.d.ts +8 -28
- package/dist/admin/components/fields/simple-array-field.svelte +29 -42
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/slug-field.svelte +6 -11
- package/dist/admin/components/fields/slug-field.svelte.d.ts +6 -26
- package/dist/admin/components/fields/text-field-wrapper.svelte +22 -40
- package/dist/admin/components/fields/text-field.svelte +7 -19
- package/dist/admin/components/fields/text-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/url-field-wrapper.svelte +8 -3
- package/dist/admin/components/fields/url-field.svelte +294 -128
- package/dist/admin/components/fields/url-field.svelte.d.ts +5 -27
- package/dist/admin/components/layout/layout-renderer.svelte +8 -6
- package/dist/admin/components/layout/nav-collections.svelte +2 -1
- package/dist/admin/components/layout/nav-forms.svelte +2 -1
- package/dist/admin/components/layout/nav-singletons.svelte +2 -1
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +221 -31
- package/dist/admin/components/tiptap/content-editor.svelte +13 -2
- package/dist/admin/components/tiptap/inline-block-node.d.ts +1 -0
- package/dist/admin/components/tiptap/inline-block-node.js +18 -1
- package/dist/admin/components/tiptap/slash-command.js +2 -3
- package/dist/admin/components/tiptap/standalone-form.d.ts +7 -0
- package/dist/admin/components/tiptap/standalone-form.js +31 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +7 -0
- package/dist/admin/remote/entry.remote.d.ts +9 -1
- package/dist/admin/remote/entry.remote.js +30 -2
- package/dist/admin/styles/admin.css +10 -0
- package/dist/admin/utils/fieldCondition.d.ts +6 -0
- package/dist/admin/utils/fieldCondition.js +20 -0
- package/dist/cli/scaffold/admin.js +8 -0
- package/dist/components/ui/switch/index.d.ts +2 -0
- package/dist/components/ui/switch/index.js +4 -0
- package/dist/components/ui/switch/switch.svelte +26 -0
- package/dist/components/ui/switch/switch.svelte.d.ts +4 -0
- package/dist/core/cms.d.ts +2 -1
- package/dist/core/cms.js +2 -0
- package/dist/core/fields/fieldSchemaToTs.js +15 -3
- package/dist/core/fields/formFieldSchemaToTs.js +22 -6
- package/dist/core/fields/urlUtils.d.ts +14 -0
- package/dist/core/fields/urlUtils.js +21 -0
- package/dist/core/server/entries/operations/get.js +2 -1
- package/dist/core/server/entries/operations/update.d.ts +1 -0
- package/dist/core/server/entries/operations/update.js +5 -1
- package/dist/core/server/fields/populateEntry.js +43 -0
- package/dist/core/server/fields/resolveImageFields.js +33 -1
- package/dist/core/server/fields/resolveRelationFields.js +46 -0
- package/dist/core/server/fields/resolveRichtextLinks.js +15 -1
- package/dist/core/server/fields/resolveUrlFields.js +65 -0
- package/dist/core/server/generator/formFieldSchemaToString.js +40 -9
- package/dist/core/server/generator/formFields.js +2 -0
- package/dist/core/server/generator/generator.js +25 -1
- package/dist/db-postgres/schema/entry.d.ts +17 -0
- package/dist/db-postgres/schema/entry.js +4 -2
- package/dist/schemas/field/url.d.ts +2 -0
- package/dist/schemas/field/url.js +4 -2
- package/dist/server/auth.d.ts +6 -6
- package/dist/sveltekit/server/handle.js +1 -0
- package/dist/types/cms.d.ts +7 -0
- package/dist/types/collections.d.ts +2 -0
- package/dist/types/entries.d.ts +7 -1
- package/dist/types/fields.d.ts +9 -0
- package/dist/types/formFields.d.ts +15 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -0
- package/dist/updates/0.5.3/index.d.ts +2 -0
- package/dist/updates/0.5.3/index.js +19 -0
- package/dist/updates/0.5.4/index.d.ts +2 -0
- package/dist/updates/0.5.4/index.js +15 -0
- package/dist/updates/0.5.5/index.d.ts +2 -0
- package/dist/updates/0.5.5/index.js +20 -0
- package/dist/updates/index.js +4 -1
- package/package.json +7 -1
- package/dist/admin/components/fields/standalone-field-renderer.svelte +0 -148
- package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +0 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getRawCollectionEntryLabel } from '../../utils/entryLabel.js';
|
|
3
3
|
import { getEntryThumbnail } from '../../utils/entryThumbnail.js';
|
|
4
|
+
import { arrayMove } from '../../utils/arrayMove.js';
|
|
4
5
|
import { getRemotes } from '../../../sveltekit/index.js';
|
|
5
6
|
import type { CollectionConfigWithType } from '../../../types/collections.js';
|
|
6
7
|
import DataTable from './data-table.svelte';
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
12
13
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
13
14
|
import { getEntryStatus } from '../entry/utils.js';
|
|
15
|
+
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
14
16
|
import * as AlertDialog from '../../../components/ui/alert-dialog/index.js';
|
|
15
17
|
import { toast } from 'svelte-sonner';
|
|
16
18
|
import StatusBadge from './status-badge.svelte';
|
|
@@ -110,6 +112,7 @@
|
|
|
110
112
|
thumbnail: string | null;
|
|
111
113
|
searchText: string;
|
|
112
114
|
a11yWarnings: number;
|
|
115
|
+
customData: Record<string, unknown>;
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
type Props = {
|
|
@@ -140,8 +143,14 @@
|
|
|
140
143
|
// Is the current filter for archived entries?
|
|
141
144
|
const isArchivedFilter = $derived(viewState.statusFilter === 'archived');
|
|
142
145
|
|
|
146
|
+
const isOrderable = $derived(!!collection.orderable);
|
|
147
|
+
|
|
143
148
|
// Build orderBy from sorting state for server-side mode
|
|
144
149
|
const serverOrderBy = $derived.by(() => {
|
|
150
|
+
// When orderable, always sort by sortOrder
|
|
151
|
+
if (isOrderable) {
|
|
152
|
+
return { column: 'sortOrder' as const, direction: 'asc' as const };
|
|
153
|
+
}
|
|
145
154
|
if (!viewState.sorting.length) return undefined;
|
|
146
155
|
const { id, desc } = viewState.sorting[0];
|
|
147
156
|
if (id === 'createdAt' || id === 'updatedAt') {
|
|
@@ -240,6 +249,23 @@
|
|
|
240
249
|
}),
|
|
241
250
|
size: 140
|
|
242
251
|
},
|
|
252
|
+
// Custom list columns from collection config
|
|
253
|
+
...(collection.listColumns ?? []).map((fieldSlug) => {
|
|
254
|
+
const fieldConfig = collection.fields.find((f) => f.slug === fieldSlug);
|
|
255
|
+
const headerLabel = fieldConfig?.label
|
|
256
|
+
? getLocalizedLabel(fieldConfig.label, interfaceLanguage.current)
|
|
257
|
+
: fieldSlug;
|
|
258
|
+
return {
|
|
259
|
+
id: `custom_${fieldSlug}`,
|
|
260
|
+
header: headerLabel,
|
|
261
|
+
cell: (info: { row: { original: CollectionDataTableRow } }) => {
|
|
262
|
+
const value = info.row.original.customData[fieldSlug];
|
|
263
|
+
return String(value ?? '');
|
|
264
|
+
},
|
|
265
|
+
enableSorting: false,
|
|
266
|
+
size: 120
|
|
267
|
+
} satisfies ColumnDef<CollectionDataTableRow>;
|
|
268
|
+
}),
|
|
243
269
|
{
|
|
244
270
|
id: 'actions',
|
|
245
271
|
header: '',
|
|
@@ -331,6 +357,19 @@
|
|
|
331
357
|
|
|
332
358
|
function mapEntryToRow(entry: RawEntry): CollectionDataTableRow {
|
|
333
359
|
const data = entry.draftVersion?.data || entry.publishedVersion?.data || {};
|
|
360
|
+
const customData: Record<string, unknown> = {};
|
|
361
|
+
if (collection.listColumns) {
|
|
362
|
+
for (const fieldSlug of collection.listColumns) {
|
|
363
|
+
const fieldData = (data as Record<string, unknown>)[fieldSlug];
|
|
364
|
+
// For localized fields, extract current content language value
|
|
365
|
+
if (fieldData && typeof fieldData === 'object' && !Array.isArray(fieldData)) {
|
|
366
|
+
const localized = fieldData as Record<string, unknown>;
|
|
367
|
+
customData[fieldSlug] = localized[contentLanguage.current] ?? Object.values(localized)[0] ?? '';
|
|
368
|
+
} else {
|
|
369
|
+
customData[fieldSlug] = fieldData ?? '';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
334
373
|
return {
|
|
335
374
|
id: entry.id,
|
|
336
375
|
name: getRawCollectionEntryLabel(entry, collection, contentLanguage.current),
|
|
@@ -341,7 +380,8 @@
|
|
|
341
380
|
updatedAt: new Date(entry.updatedAt),
|
|
342
381
|
thumbnail: getEntryThumbnail(data as Record<string, unknown>, collection),
|
|
343
382
|
searchText: getSearchText(entry),
|
|
344
|
-
a11yWarnings: countA11yWarnings(entry)
|
|
383
|
+
a11yWarnings: countA11yWarnings(entry),
|
|
384
|
+
customData
|
|
345
385
|
};
|
|
346
386
|
}
|
|
347
387
|
|
|
@@ -404,6 +444,14 @@
|
|
|
404
444
|
refreshQueries();
|
|
405
445
|
}
|
|
406
446
|
|
|
447
|
+
async function handleReorder(items: CollectionDataTableRow[], fromIndex: number, toIndex: number) {
|
|
448
|
+
if (!isOrderable || items.length === 0) return;
|
|
449
|
+
const reordered = arrayMove(items, fromIndex, toIndex);
|
|
450
|
+
const orderedIds = reordered.map((item) => item.id);
|
|
451
|
+
await remotes.reorderEntriesCommand({ orderedIds });
|
|
452
|
+
refreshQueries();
|
|
453
|
+
}
|
|
454
|
+
|
|
407
455
|
async function handleBulkDelete(items: CollectionDataTableRow[]) {
|
|
408
456
|
const idsToDelete = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
|
|
409
457
|
for (const id of idsToDelete) {
|
|
@@ -439,8 +487,7 @@
|
|
|
439
487
|
? result.total
|
|
440
488
|
: filteredRows.length}
|
|
441
489
|
{@const pageCount = Math.ceil(totalItems / viewState.pageSize)}
|
|
442
|
-
{@const displayItems =
|
|
443
|
-
useServerPagination && !viewState.statusFilter
|
|
490
|
+
{@const displayItems = useServerPagination && !viewState.statusFilter
|
|
444
491
|
? filteredRows
|
|
445
492
|
: filteredRows.slice(
|
|
446
493
|
viewState.pageIndex * viewState.pageSize,
|
|
@@ -474,7 +521,7 @@
|
|
|
474
521
|
<DataTable
|
|
475
522
|
data={displayItems}
|
|
476
523
|
{columns}
|
|
477
|
-
enableSorting
|
|
524
|
+
enableSorting={!isOrderable}
|
|
478
525
|
enableFiltering
|
|
479
526
|
enableSelection
|
|
480
527
|
enablePagination
|
|
@@ -490,12 +537,14 @@
|
|
|
490
537
|
viewState.pageIndex = p.pageIndex;
|
|
491
538
|
}}
|
|
492
539
|
tableRef={(t) => (tableInstance = t)}
|
|
540
|
+
orderable={isOrderable}
|
|
541
|
+
onReorder={(from, to) => handleReorder(displayItems, from, to)}
|
|
493
542
|
/>
|
|
494
543
|
{:else}
|
|
495
544
|
<DataTable
|
|
496
545
|
data={displayItems}
|
|
497
546
|
{columns}
|
|
498
|
-
enableSorting
|
|
547
|
+
enableSorting={!isOrderable}
|
|
499
548
|
enableFiltering
|
|
500
549
|
enableSelection
|
|
501
550
|
enablePagination
|
|
@@ -508,6 +557,8 @@
|
|
|
508
557
|
viewState.pageIndex = p.pageIndex;
|
|
509
558
|
}}
|
|
510
559
|
tableRef={(t) => (tableInstance = t)}
|
|
560
|
+
orderable={isOrderable}
|
|
561
|
+
onReorder={(from, to) => handleReorder(displayItems, from, to)}
|
|
511
562
|
/>
|
|
512
563
|
{/if}
|
|
513
564
|
</div>
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import * as Table from '../../../components/ui/table/index.js';
|
|
16
16
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
17
17
|
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
18
|
+
import { droppable, draggable, dndState } from '@thisux/sveltednd';
|
|
19
|
+
import { flip } from 'svelte/animate';
|
|
18
20
|
|
|
19
21
|
type DataTableProps<TData, TValue> = {
|
|
20
22
|
columns: ColumnDef<TData, TValue>[];
|
|
@@ -35,6 +37,8 @@
|
|
|
35
37
|
pageCount?: number;
|
|
36
38
|
rowCount?: number;
|
|
37
39
|
emptyMessage?: string;
|
|
40
|
+
orderable?: boolean;
|
|
41
|
+
onReorder?: (fromIndex: number, toIndex: number) => void;
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
let {
|
|
@@ -55,9 +59,18 @@
|
|
|
55
59
|
manualPagination: manualPaginationProp = false,
|
|
56
60
|
pageCount: pageCountProp,
|
|
57
61
|
rowCount: rowCountProp,
|
|
58
|
-
emptyMessage
|
|
62
|
+
emptyMessage,
|
|
63
|
+
orderable = false,
|
|
64
|
+
onReorder
|
|
59
65
|
}: DataTableProps<TData, TValue> = $props();
|
|
60
66
|
|
|
67
|
+
let liveRegionMessage = $state('');
|
|
68
|
+
let dropProcessing = false;
|
|
69
|
+
|
|
70
|
+
const matchesReducedMotion = typeof window !== 'undefined'
|
|
71
|
+
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
72
|
+
: false;
|
|
73
|
+
|
|
61
74
|
const interfaceLanguage = useInterfaceLanguage();
|
|
62
75
|
|
|
63
76
|
const defaultEmptyMessages: Record<InterfaceLanguage, string> = {
|
|
@@ -113,10 +126,17 @@
|
|
|
113
126
|
});
|
|
114
127
|
</script>
|
|
115
128
|
|
|
116
|
-
|
|
129
|
+
{#if orderable}
|
|
130
|
+
<div aria-live="polite" aria-atomic="true" class="sr-only">{liveRegionMessage}</div>
|
|
131
|
+
{/if}
|
|
132
|
+
|
|
133
|
+
<Table.Root aria-roledescription={orderable ? 'sortable list' : undefined}>
|
|
117
134
|
<Table.Header class="bg-muted sticky top-0 z-10">
|
|
118
135
|
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
|
119
136
|
<Table.Row class="border-b-0 hover:bg-transparent">
|
|
137
|
+
{#if orderable}
|
|
138
|
+
<Table.Head class="text-[11px] font-bold uppercase tracking-[0.04em] text-muted-foreground h-10 w-10"></Table.Head>
|
|
139
|
+
{/if}
|
|
120
140
|
{#each headerGroup.headers as header (header.id)}
|
|
121
141
|
<Table.Head class="text-[11px] font-bold uppercase tracking-[0.04em] text-muted-foreground h-10">
|
|
122
142
|
{#if !header.isPlaceholder}
|
|
@@ -131,23 +151,112 @@
|
|
|
131
151
|
{/each}
|
|
132
152
|
</Table.Header>
|
|
133
153
|
<Table.Body>
|
|
134
|
-
{#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
{#if orderable}
|
|
155
|
+
{#each table.getRowModel().rows as row, i (row.id)}
|
|
156
|
+
{@const totalRows = table.getRowModel().rows.length}
|
|
157
|
+
<tr
|
|
158
|
+
use:droppable={{
|
|
159
|
+
container: i.toString(),
|
|
160
|
+
callbacks: {
|
|
161
|
+
onDrop: (state) => {
|
|
162
|
+
if (dropProcessing) return;
|
|
163
|
+
dropProcessing = true;
|
|
164
|
+
const fromIndex = parseInt(state.sourceContainer ?? '');
|
|
165
|
+
const toIndex = parseInt(state.targetContainer ?? '');
|
|
166
|
+
if (!isNaN(fromIndex) && !isNaN(toIndex)) {
|
|
167
|
+
onReorder?.(fromIndex, toIndex);
|
|
168
|
+
}
|
|
169
|
+
dndState.isDragging = false;
|
|
170
|
+
dndState.draggedItem = null;
|
|
171
|
+
dndState.sourceContainer = '';
|
|
172
|
+
dndState.targetContainer = null;
|
|
173
|
+
dndState.targetElement = null;
|
|
174
|
+
setTimeout(() => { dropProcessing = false; }, 50);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}}
|
|
178
|
+
animate:flip={{ duration: matchesReducedMotion ? 0 : 200 }}
|
|
179
|
+
data-slot="table-row"
|
|
180
|
+
data-state={row.getIsSelected() ? 'selected' : undefined}
|
|
181
|
+
class="hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors hover:bg-lavender-lighter/50 data-[state=selected]:bg-lavender-lighter/30 {i === totalRows - 1 ? 'border-b-0' : ''}"
|
|
182
|
+
>
|
|
183
|
+
<Table.Cell class="py-3 w-10">
|
|
184
|
+
<div class="flex items-center gap-1">
|
|
185
|
+
<div
|
|
186
|
+
use:draggable={{
|
|
187
|
+
container: i.toString(),
|
|
188
|
+
dragData: { index: i }
|
|
189
|
+
}}
|
|
190
|
+
class="text-muted-foreground hover:text-foreground cursor-grab"
|
|
191
|
+
role="button"
|
|
192
|
+
tabindex="0"
|
|
193
|
+
aria-label={`Drag to reorder, row ${i + 1} of ${totalRows}`}
|
|
194
|
+
aria-roledescription="draggable"
|
|
195
|
+
>
|
|
196
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="flex flex-col">
|
|
199
|
+
{#if i > 0}
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
class="p-0.5 rounded text-muted-foreground hover:text-foreground"
|
|
203
|
+
aria-label={`Move up, row ${i + 1} of ${totalRows}`}
|
|
204
|
+
onclick={() => {
|
|
205
|
+
onReorder?.(i, i - 1);
|
|
206
|
+
liveRegionMessage = `Moved to position ${i} of ${totalRows}`;
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m18 15-6-6-6 6"/></svg>
|
|
210
|
+
</button>
|
|
211
|
+
{/if}
|
|
212
|
+
{#if i < totalRows - 1}
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
215
|
+
class="p-0.5 rounded text-muted-foreground hover:text-foreground"
|
|
216
|
+
aria-label={`Move down, row ${i + 1} of ${totalRows}`}
|
|
217
|
+
onclick={() => {
|
|
218
|
+
onReorder?.(i, i + 1);
|
|
219
|
+
liveRegionMessage = `Moved to position ${i + 2} of ${totalRows}`;
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6"/></svg>
|
|
223
|
+
</button>
|
|
224
|
+
{/if}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
142
227
|
</Table.Cell>
|
|
143
|
-
|
|
144
|
-
|
|
228
|
+
{#each row.getVisibleCells() as cell (cell.id)}
|
|
229
|
+
<Table.Cell class="py-3">
|
|
230
|
+
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
|
231
|
+
</Table.Cell>
|
|
232
|
+
{/each}
|
|
233
|
+
</tr>
|
|
234
|
+
{:else}
|
|
235
|
+
<Table.Row>
|
|
236
|
+
<Table.Cell colspan={columns.length + 1} class="h-24 text-center text-muted-foreground">
|
|
237
|
+
{emptyMessage ?? defaultEmptyMessages[interfaceLanguage.current]}
|
|
238
|
+
</Table.Cell>
|
|
239
|
+
</Table.Row>
|
|
240
|
+
{/each}
|
|
145
241
|
{:else}
|
|
146
|
-
|
|
147
|
-
<Table.
|
|
148
|
-
{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
242
|
+
{#each table.getRowModel().rows as row, i (row.id)}
|
|
243
|
+
<Table.Row
|
|
244
|
+
data-state={row.getIsSelected() && 'selected'}
|
|
245
|
+
class="hover:bg-lavender-lighter/50 data-[state=selected]:bg-lavender-lighter/30 {i === table.getRowModel().rows.length - 1 ? 'border-b-0' : ''}"
|
|
246
|
+
>
|
|
247
|
+
{#each row.getVisibleCells() as cell (cell.id)}
|
|
248
|
+
<Table.Cell class="py-3">
|
|
249
|
+
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
|
250
|
+
</Table.Cell>
|
|
251
|
+
{/each}
|
|
252
|
+
</Table.Row>
|
|
253
|
+
{:else}
|
|
254
|
+
<Table.Row>
|
|
255
|
+
<Table.Cell colspan={columns.length} class="h-24 text-center text-muted-foreground">
|
|
256
|
+
{emptyMessage ?? defaultEmptyMessages[interfaceLanguage.current]}
|
|
257
|
+
</Table.Cell>
|
|
258
|
+
</Table.Row>
|
|
259
|
+
{/each}
|
|
260
|
+
{/if}
|
|
152
261
|
</Table.Body>
|
|
153
262
|
</Table.Root>
|
|
@@ -18,6 +18,8 @@ type DataTableProps<TData, TValue> = {
|
|
|
18
18
|
pageCount?: number;
|
|
19
19
|
rowCount?: number;
|
|
20
20
|
emptyMessage?: string;
|
|
21
|
+
orderable?: boolean;
|
|
22
|
+
onReorder?: (fromIndex: number, toIndex: number) => void;
|
|
21
23
|
};
|
|
22
24
|
declare function $$render<TData, TValue>(): {
|
|
23
25
|
props: DataTableProps<TData, TValue>;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import ArchiveIcon from '@tabler/icons-svelte/icons/archive';
|
|
14
14
|
import XIcon from '@tabler/icons-svelte/icons/x';
|
|
15
15
|
import Button from '../../../components/ui/button/button.svelte';
|
|
16
|
+
import { cn } from '../../../utils.js';
|
|
16
17
|
import { ElementSize, useDebounce } from 'runed';
|
|
17
18
|
import { defaults, superForm, type SuperForm } from 'sveltekit-superforms';
|
|
18
19
|
import { zod4, zod4Client } from 'sveltekit-superforms/adapters';
|
|
@@ -326,9 +327,7 @@
|
|
|
326
327
|
validationErrors = errors;
|
|
327
328
|
|
|
328
329
|
// Scroll to first errored field
|
|
329
|
-
const firstErrorKey = Object.keys(validatedForm.errors).find(
|
|
330
|
-
(k) => k !== '_errors'
|
|
331
|
-
);
|
|
330
|
+
const firstErrorKey = Object.keys(validatedForm.errors).find((k) => k !== '_errors');
|
|
332
331
|
if (firstErrorKey) {
|
|
333
332
|
scrollToIssue(firstErrorKey);
|
|
334
333
|
}
|
|
@@ -540,137 +539,145 @@
|
|
|
540
539
|
});
|
|
541
540
|
|
|
542
541
|
const t = $derived(lang[interfaceLanguage.current]);
|
|
542
|
+
const isHybrid = $derived(hybridContext.mode === 'hybrid' && !!collection.previewUrl);
|
|
543
543
|
</script>
|
|
544
544
|
|
|
545
|
-
<
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
<AlertCircle class="mt-0.5 size-4 shrink-0" />
|
|
566
|
-
<div class="min-w-0 flex-1">
|
|
567
|
-
<p class="font-semibold">{t.cannotPublish}</p>
|
|
568
|
-
<p class="text-xs opacity-80">{t.validationHint}</p>
|
|
569
|
-
<ul class="mt-1 text-xs">
|
|
570
|
-
{#each validationErrors.slice(0, 5) as error}
|
|
571
|
-
<li>— {error}</li>
|
|
572
|
-
{/each}
|
|
573
|
-
</ul>
|
|
574
|
-
</div>
|
|
575
|
-
<button
|
|
576
|
-
type="button"
|
|
577
|
-
onclick={() => (validationErrors = [])}
|
|
578
|
-
class="shrink-0 rounded p-0.5 opacity-60 hover:opacity-100"
|
|
579
|
-
aria-label="Zamknij"
|
|
545
|
+
<div class={isHybrid ? 'flex h-full flex-col overflow-hidden' : ''}>
|
|
546
|
+
<EntryHeader
|
|
547
|
+
{entry}
|
|
548
|
+
version={editingEntry}
|
|
549
|
+
{onSave}
|
|
550
|
+
onSaveDraft={performAutosave}
|
|
551
|
+
{onArchive}
|
|
552
|
+
{saveStatus}
|
|
553
|
+
{isArchived}
|
|
554
|
+
fields={getFieldsFromConfig(collection)}
|
|
555
|
+
getFormData={() => get(form.form)}
|
|
556
|
+
onScrollToIssue={scrollToIssue}
|
|
557
|
+
{translationStatus}
|
|
558
|
+
/>
|
|
559
|
+
|
|
560
|
+
{#if validationErrors.length > 0}
|
|
561
|
+
<div
|
|
562
|
+
role="alert"
|
|
563
|
+
aria-live="assertive"
|
|
564
|
+
class="flex shrink-0 items-start gap-3 border-b border-[var(--error)]/20 bg-[#FDF0F0] px-6 py-3 text-sm text-[var(--error)]"
|
|
580
565
|
>
|
|
581
|
-
<
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
{
|
|
566
|
+
<AlertCircle class="mt-0.5 size-4 shrink-0" />
|
|
567
|
+
<div class="min-w-0 flex-1">
|
|
568
|
+
<p class="font-semibold">{t.cannotPublish}</p>
|
|
569
|
+
<p class="text-xs opacity-80">{t.validationHint}</p>
|
|
570
|
+
<ul class="mt-1 text-xs">
|
|
571
|
+
{#each validationErrors.slice(0, 5) as error}
|
|
572
|
+
<li>— {error}</li>
|
|
573
|
+
{/each}
|
|
574
|
+
</ul>
|
|
575
|
+
</div>
|
|
576
|
+
<button
|
|
577
|
+
type="button"
|
|
578
|
+
onclick={() => (validationErrors = [])}
|
|
579
|
+
class="shrink-0 rounded p-0.5 opacity-60 hover:opacity-100"
|
|
580
|
+
aria-label="Zamknij"
|
|
581
|
+
>
|
|
582
|
+
<XIcon class="size-4" />
|
|
583
|
+
</button>
|
|
584
|
+
</div>
|
|
585
|
+
{/if}
|
|
585
586
|
|
|
586
|
-
{#if isArchived}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
587
|
+
{#if isArchived}
|
|
588
|
+
<div
|
|
589
|
+
role="alert"
|
|
590
|
+
class="flex shrink-0 items-center justify-between gap-3 border-b border-[var(--warning)]/20 bg-[#FDF6EC] px-6 py-3 text-sm"
|
|
591
|
+
>
|
|
592
|
+
<div class="flex items-center gap-2 text-[var(--warning)]">
|
|
593
|
+
<ArchiveIcon class="size-4 shrink-0" />
|
|
594
|
+
<span>{t.archivedBanner}</span>
|
|
595
|
+
</div>
|
|
596
|
+
<Button size="sm" onclick={onRestore}>{t.restore}</Button>
|
|
594
597
|
</div>
|
|
595
|
-
|
|
596
|
-
</div>
|
|
597
|
-
{/if}
|
|
598
|
+
{/if}
|
|
598
599
|
|
|
599
|
-
{#if showDraftBanner}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
>
|
|
603
|
-
<span class="text-[var(--text-secondary)]">{lang[interfaceLanguage.current].newerDraft}</span>
|
|
604
|
-
<button
|
|
605
|
-
type="button"
|
|
606
|
-
class="font-semibold text-[var(--primary)] hover:underline"
|
|
607
|
-
onclick={() => goto(`?version=${draftVersionId}`)}
|
|
600
|
+
{#if showDraftBanner}
|
|
601
|
+
<div
|
|
602
|
+
class="flex shrink-0 items-center justify-between border-b bg-[var(--lavender-lighter)] px-6 py-2 text-sm"
|
|
608
603
|
>
|
|
609
|
-
{lang[interfaceLanguage.current].
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
604
|
+
<span class="text-[var(--text-secondary)]">{lang[interfaceLanguage.current].newerDraft}</span>
|
|
605
|
+
<button
|
|
606
|
+
type="button"
|
|
607
|
+
class="font-semibold text-[var(--primary)] hover:underline"
|
|
608
|
+
onclick={() => goto(`?version=${draftVersionId}`)}
|
|
609
|
+
>
|
|
610
|
+
{lang[interfaceLanguage.current].switchToDraft}
|
|
611
|
+
</button>
|
|
612
|
+
</div>
|
|
613
|
+
{:else if showPublishedBanner}
|
|
614
|
+
<div
|
|
615
|
+
class="flex shrink-0 items-center justify-between border-b bg-[var(--lavender-lighter)] px-6 py-2 text-sm"
|
|
616
|
+
>
|
|
617
|
+
<span class="text-[var(--text-secondary)]"
|
|
618
|
+
>{lang[interfaceLanguage.current].editingDraft}</span
|
|
619
|
+
>
|
|
620
|
+
<button
|
|
621
|
+
type="button"
|
|
622
|
+
class="font-semibold text-[var(--primary)] hover:underline"
|
|
623
|
+
onclick={() => goto(`?version=${entry.publishedVersion!.id}`)}
|
|
624
|
+
>
|
|
625
|
+
{lang[interfaceLanguage.current].switchToPublished}
|
|
626
|
+
</button>
|
|
627
|
+
</div>
|
|
628
|
+
{/if}
|
|
629
|
+
|
|
613
630
|
<div
|
|
614
|
-
class=
|
|
631
|
+
class={cn(
|
|
632
|
+
isArchived && 'pointer-events-none opacity-60',
|
|
633
|
+
isHybrid && 'flex min-h-0 flex-1 overflow-hidden'
|
|
634
|
+
)}
|
|
615
635
|
>
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
{
|
|
638
|
-
<
|
|
639
|
-
{
|
|
640
|
-
{
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
{size}
|
|
644
|
-
bind:el
|
|
636
|
+
{#if isHybrid}
|
|
637
|
+
{#await import('./hybrid/hybrid-layout.svelte')}
|
|
638
|
+
<div class="bg-accent h-full animate-pulse rounded-md"></div>
|
|
639
|
+
{:then { default: HybridLayout }}
|
|
640
|
+
<HybridLayout>
|
|
641
|
+
{#snippet preview()}
|
|
642
|
+
{#await import('./hybrid/hybrid-preview.svelte')}
|
|
643
|
+
<div class="bg-accent h-full animate-pulse rounded-md"></div>
|
|
644
|
+
{:then { default: HybridPreview }}
|
|
645
|
+
<HybridPreview
|
|
646
|
+
{collection}
|
|
647
|
+
{editingEntry}
|
|
648
|
+
bind:previewIframe
|
|
649
|
+
bind:sizePreset
|
|
650
|
+
{size}
|
|
651
|
+
bind:el
|
|
652
|
+
/>
|
|
653
|
+
{:catch}
|
|
654
|
+
<p class="text-destructive p-4 text-sm">Failed to load preview</p>
|
|
655
|
+
{/await}
|
|
656
|
+
{/snippet}
|
|
657
|
+
{#snippet formPanel()}
|
|
658
|
+
<EntryForm
|
|
659
|
+
{form}
|
|
660
|
+
{entry}
|
|
661
|
+
focusedPath={hybridContext.focusedPath}
|
|
662
|
+
onPathSelect={(path) => (hybridContext.focusedPath = path)}
|
|
645
663
|
/>
|
|
646
|
-
{
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
{entry}
|
|
654
|
-
focusedPath={hybridContext.focusedPath}
|
|
655
|
-
onPathSelect={(path) => (hybridContext.focusedPath = path)}
|
|
656
|
-
/>
|
|
657
|
-
{/snippet}
|
|
658
|
-
</HybridLayout>
|
|
659
|
-
{:catch}
|
|
660
|
-
<p class="text-destructive p-4 text-sm">Failed to load layout</p>
|
|
661
|
-
{/await}
|
|
662
|
-
</div>
|
|
663
|
-
{:else if hasLayout(collection)}
|
|
664
|
-
<div class="overflow-y-auto" style="scroll-padding-top: 48px;">
|
|
665
|
-
<EntryForm {form} {entry} />
|
|
666
|
-
</div>
|
|
667
|
-
{:else}
|
|
668
|
-
<div class="flex items-stretch justify-center" style="scroll-padding-top: 48px;">
|
|
669
|
-
<div class="max-w-2xl grow p-4 lg:p-6">
|
|
670
|
-
<div class="bg-card rounded-2xl border p-4 shadow-sm lg:p-6">
|
|
664
|
+
{/snippet}
|
|
665
|
+
</HybridLayout>
|
|
666
|
+
{:catch}
|
|
667
|
+
<p class="text-destructive p-4 text-sm">Failed to load layout</p>
|
|
668
|
+
{/await}
|
|
669
|
+
{:else if hasLayout(collection)}
|
|
670
|
+
<div class="overflow-y-auto" style="scroll-padding-top: 48px;">
|
|
671
671
|
<EntryForm {form} {entry} />
|
|
672
672
|
</div>
|
|
673
|
-
|
|
673
|
+
{:else}
|
|
674
|
+
<div class="flex items-stretch justify-center" style="scroll-padding-top: 48px;">
|
|
675
|
+
<div class="max-w-2xl grow p-4 lg:p-6">
|
|
676
|
+
<div class="bg-card rounded-2xl border p-4 shadow-sm lg:p-6">
|
|
677
|
+
<EntryForm {form} {entry} />
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
{/if}
|
|
674
682
|
</div>
|
|
675
|
-
{/if}
|
|
676
683
|
</div>
|