includio-cms 0.5.3 → 0.5.7
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 +71 -0
- package/ROADMAP.md +33 -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 +48 -48
- package/dist/admin/client/admin/admin-after-login-layout.svelte +5 -3
- package/dist/admin/client/admin/admin-layout.svelte +0 -2
- package/dist/admin/client/collection/bulk-actions-bar.svelte +3 -1
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +1 -1
- package/dist/admin/client/collection/collection-entries.svelte +334 -166
- package/dist/admin/client/collection/collection-page.svelte +6 -3
- 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/collection/row-actions.svelte +3 -1
- package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -1
- package/dist/admin/client/entry/entry-page.svelte +5 -4
- package/dist/admin/client/form/form-config-wrapper.svelte +5 -3
- package/dist/admin/client/form/form-page.svelte +5 -5
- package/dist/admin/client/form/form-submission/form-submission-page.svelte +30 -32
- package/dist/admin/components/accessibility/accessibility-overview.svelte +7 -5
- package/dist/admin/components/accessibility/accessibility-overview.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/a11y-gauge.svelte +7 -4
- package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/accessibility-hub.svelte +7 -4
- package/dist/admin/components/dashboard/accessibility-hub.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +8 -3
- package/dist/admin/components/dashboard/form-submissions-widget.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/recent-activity.svelte +8 -3
- package/dist/admin/components/dashboard/recent-activity.svelte.d.ts +2 -17
- package/dist/admin/components/dashboard/recent-entries.svelte +8 -3
- package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +2 -17
- package/dist/admin/components/fields/image-field.svelte +21 -20
- package/dist/admin/components/fields/media-field.svelte +45 -44
- package/dist/admin/components/fields/relation-field.svelte +428 -149
- package/dist/admin/components/fields/relation-picker-dialog.svelte +112 -0
- package/dist/admin/components/fields/relation-picker-dialog.svelte.d.ts +22 -0
- package/dist/admin/components/fields/seo-field.svelte +66 -10
- package/dist/admin/components/icons/icon-map.d.ts +4 -0
- package/dist/admin/components/icons/icon-map.js +143 -0
- package/dist/admin/components/layout/header-actions.svelte +0 -10
- package/dist/admin/components/layout/nav-collections.svelte +55 -57
- package/dist/admin/components/layout/nav-collections.svelte.d.ts +2 -17
- package/dist/admin/components/layout/nav-forms.svelte +49 -49
- package/dist/admin/components/layout/nav-forms.svelte.d.ts +2 -17
- package/dist/admin/components/layout/nav-search.svelte +15 -22
- package/dist/admin/components/layout/nav-singletons.svelte +35 -38
- package/dist/admin/components/layout/nav-singletons.svelte.d.ts +2 -17
- package/dist/admin/components/media/file/file-preview.svelte +92 -91
- package/dist/admin/components/media/file-preview.svelte +17 -18
- package/dist/admin/components/media/media-library.svelte +39 -41
- package/dist/admin/components/media/media-selector.svelte +8 -8
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +6 -8
- package/dist/admin/components/tiptap/content-editor.svelte +2 -2
- package/dist/admin/components/tiptap/editor-toolbar.svelte +1 -1
- package/dist/admin/components/tiptap/inline-block-node.d.ts +1 -0
- package/dist/admin/components/tiptap/inline-block-node.js +12 -1
- package/dist/admin/components/tiptap/tiptap-editor.svelte +1 -1
- package/dist/admin/remote/entry.remote.d.ts +20 -1
- package/dist/admin/remote/entry.remote.js +24 -3
- package/dist/admin/remote/invite.d.ts +2 -2
- package/dist/cli/scaffold/admin.js +8 -0
- package/dist/cms/runtime/remote.d.ts +8 -0
- package/dist/cms/runtime/remote.js +7 -0
- package/dist/cms/runtime/types.d.ts +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sonner/sonner.svelte +1 -2
- package/dist/core/cms.d.ts +2 -1
- package/dist/core/cms.js +2 -0
- package/dist/core/server/entries/operations/get.d.ts +11 -0
- package/dist/core/server/entries/operations/get.js +63 -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/resolveUrlFields.js +49 -18
- package/dist/db-postgres/schema/entry.d.ts +17 -0
- package/dist/db-postgres/schema/entry.js +4 -2
- package/dist/server/auth.d.ts +4 -4
- package/dist/sveltekit/components/structured-content.svelte +18 -5
- 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/config.d.ts +2 -2
- package/dist/types/entries.d.ts +7 -1
- package/dist/types/forms.d.ts +2 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -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/0.5.6/index.d.ts +2 -0
- package/dist/updates/0.5.6/index.js +17 -0
- package/dist/updates/0.5.7/index.d.ts +2 -0
- package/dist/updates/0.5.7/index.js +14 -0
- package/dist/updates/index.js +5 -1
- package/package.json +6 -2
|
@@ -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';
|
|
@@ -26,11 +28,13 @@
|
|
|
26
28
|
import EmptyState from './empty-state.svelte';
|
|
27
29
|
import { createCollectionViewState } from './collection-view.svelte.js';
|
|
28
30
|
import type { EntryStatus as EntryStatusType, RawEntry } from '../../../types/entries.js';
|
|
31
|
+
import type { RelationField } from '../../../types/fields.js';
|
|
29
32
|
import {
|
|
30
33
|
validateA11y,
|
|
31
34
|
a11yLangPl,
|
|
32
35
|
a11yLangEn
|
|
33
36
|
} from '../entry/header/a11y-validator.js';
|
|
37
|
+
import Skeleton from '../../../components/ui/skeleton/skeleton.svelte';
|
|
34
38
|
|
|
35
39
|
const remotes = getRemotes();
|
|
36
40
|
const contentLanguage = getContentLanguage();
|
|
@@ -110,6 +114,7 @@
|
|
|
110
114
|
thumbnail: string | null;
|
|
111
115
|
searchText: string;
|
|
112
116
|
a11yWarnings: number;
|
|
117
|
+
customData: Record<string, unknown>;
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
type Props = {
|
|
@@ -128,6 +133,17 @@
|
|
|
128
133
|
let deleteDialogOpen = $state(false);
|
|
129
134
|
let pendingDeleteId = $state<string | null>(null);
|
|
130
135
|
|
|
136
|
+
// Relation fields in listColumns: detect and build lookup
|
|
137
|
+
const relationListFields = $derived(
|
|
138
|
+
(collection.listColumns ?? [])
|
|
139
|
+
.map((slug) => {
|
|
140
|
+
const field = collection.fields.find((f) => f.slug === slug);
|
|
141
|
+
return field?.type === 'relation' ? (field as RelationField) : null;
|
|
142
|
+
})
|
|
143
|
+
.filter((f): f is RelationField => f !== null)
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
|
|
131
147
|
// Find seo field for slug extraction
|
|
132
148
|
const seoField = $derived(collection.fields.find((f) => f.type === 'seo'));
|
|
133
149
|
|
|
@@ -140,8 +156,14 @@
|
|
|
140
156
|
// Is the current filter for archived entries?
|
|
141
157
|
const isArchivedFilter = $derived(viewState.statusFilter === 'archived');
|
|
142
158
|
|
|
159
|
+
const isOrderable = $derived(!!collection.orderable);
|
|
160
|
+
|
|
143
161
|
// Build orderBy from sorting state for server-side mode
|
|
144
162
|
const serverOrderBy = $derived.by(() => {
|
|
163
|
+
// When orderable, always sort by sortOrder
|
|
164
|
+
if (isOrderable) {
|
|
165
|
+
return { column: 'sortOrder' as const, direction: 'asc' as const };
|
|
166
|
+
}
|
|
145
167
|
if (!viewState.sorting.length) return undefined;
|
|
146
168
|
const { id, desc } = viewState.sorting[0];
|
|
147
169
|
if (id === 'createdAt' || id === 'updatedAt') {
|
|
@@ -169,6 +191,20 @@
|
|
|
169
191
|
}
|
|
170
192
|
);
|
|
171
193
|
|
|
194
|
+
// Reactive query
|
|
195
|
+
const entriesQuery = $derived(remotes.getRawEntries(queryParams));
|
|
196
|
+
|
|
197
|
+
// Track relation labels
|
|
198
|
+
let labelLookup = $state<Record<string, string>>({});
|
|
199
|
+
|
|
200
|
+
$effect(() => {
|
|
201
|
+
if (entriesQuery.current) {
|
|
202
|
+
fetchRelationLabels(entriesQuery.current.entries).then((l) => {
|
|
203
|
+
labelLookup = l;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
172
208
|
const columns: ColumnDef<CollectionDataTableRow>[] = $derived([
|
|
173
209
|
{
|
|
174
210
|
id: 'select',
|
|
@@ -240,6 +276,23 @@
|
|
|
240
276
|
}),
|
|
241
277
|
size: 140
|
|
242
278
|
},
|
|
279
|
+
// Custom list columns from collection config
|
|
280
|
+
...(collection.listColumns ?? []).map((fieldSlug) => {
|
|
281
|
+
const fieldConfig = collection.fields.find((f) => f.slug === fieldSlug);
|
|
282
|
+
const headerLabel = fieldConfig?.label
|
|
283
|
+
? getLocalizedLabel(fieldConfig.label, interfaceLanguage.current)
|
|
284
|
+
: fieldSlug;
|
|
285
|
+
return {
|
|
286
|
+
id: `custom_${fieldSlug}`,
|
|
287
|
+
header: headerLabel,
|
|
288
|
+
cell: (info: { row: { original: CollectionDataTableRow } }) => {
|
|
289
|
+
const value = info.row.original.customData[fieldSlug];
|
|
290
|
+
return String(value ?? '');
|
|
291
|
+
},
|
|
292
|
+
enableSorting: false,
|
|
293
|
+
size: 120
|
|
294
|
+
} satisfies ColumnDef<CollectionDataTableRow>;
|
|
295
|
+
}),
|
|
243
296
|
{
|
|
244
297
|
id: 'actions',
|
|
245
298
|
header: '',
|
|
@@ -248,28 +301,35 @@
|
|
|
248
301
|
entryUrl: info.row.original.url,
|
|
249
302
|
entryName: info.row.original.name,
|
|
250
303
|
onArchive: () => handleArchiveSingle(info.row.original.id),
|
|
251
|
-
onDelete: () => handleDelete(info.row.original.id),
|
|
252
|
-
...(isArchivedFilter ? { onRestore: () => handleRestoreSingle(info.row.original.id) } : {})
|
|
304
|
+
...(isArchivedFilter ? { onDelete: () => handleDelete(info.row.original.id), onRestore: () => handleRestoreSingle(info.row.original.id) } : {})
|
|
253
305
|
}),
|
|
254
306
|
enableSorting: false,
|
|
255
307
|
size: 50
|
|
256
308
|
}
|
|
257
309
|
]);
|
|
258
310
|
|
|
259
|
-
function refreshQueries() {
|
|
260
|
-
remotes.getRawEntries(queryParams).refresh();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
311
|
async function handleArchiveSingle(id: string) {
|
|
312
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
313
|
+
...old,
|
|
314
|
+
entries: old.entries.filter((e: RawEntry) => e.id !== id),
|
|
315
|
+
total: old.total - 1
|
|
316
|
+
}));
|
|
264
317
|
await remotes.archiveEntryCommand(id);
|
|
265
318
|
toast.success(lang[interfaceLanguage.current].entriesArchived);
|
|
266
|
-
|
|
319
|
+
await entriesQuery.refresh();
|
|
320
|
+
override.release();
|
|
267
321
|
}
|
|
268
322
|
|
|
269
323
|
async function handleRestoreSingle(id: string) {
|
|
324
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
325
|
+
...old,
|
|
326
|
+
entries: old.entries.filter((e: RawEntry) => e.id !== id),
|
|
327
|
+
total: old.total - 1
|
|
328
|
+
}));
|
|
270
329
|
await remotes.unarchiveEntryCommand(id);
|
|
271
330
|
toast.success(lang[interfaceLanguage.current].entryRestored);
|
|
272
|
-
|
|
331
|
+
await entriesQuery.refresh();
|
|
332
|
+
override.release();
|
|
273
333
|
}
|
|
274
334
|
|
|
275
335
|
function handleDelete(id: string) {
|
|
@@ -279,9 +339,16 @@
|
|
|
279
339
|
|
|
280
340
|
async function confirmDelete() {
|
|
281
341
|
if (!pendingDeleteId) return;
|
|
282
|
-
|
|
342
|
+
const id = pendingDeleteId;
|
|
343
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
344
|
+
...old,
|
|
345
|
+
entries: old.entries.filter((e: RawEntry) => e.id !== id),
|
|
346
|
+
total: old.total - 1
|
|
347
|
+
}));
|
|
348
|
+
await remotes.deleteEntryCommand(id);
|
|
283
349
|
toast.success(lang[interfaceLanguage.current].entryDeleted);
|
|
284
|
-
|
|
350
|
+
await entriesQuery.refresh();
|
|
351
|
+
override.release();
|
|
285
352
|
deleteDialogOpen = false;
|
|
286
353
|
pendingDeleteId = null;
|
|
287
354
|
}
|
|
@@ -329,8 +396,68 @@
|
|
|
329
396
|
return issues.filter((i) => i.type === 'warning').length;
|
|
330
397
|
}
|
|
331
398
|
|
|
332
|
-
function
|
|
399
|
+
async function fetchRelationLabels(entries: RawEntry[]): Promise<Record<string, string>> {
|
|
400
|
+
if (relationListFields.length === 0) return {};
|
|
401
|
+
|
|
402
|
+
const uuidsByCollection = new Map<string, Set<string>>();
|
|
403
|
+
for (const field of relationListFields) {
|
|
404
|
+
uuidsByCollection.set(field.collection, new Set());
|
|
405
|
+
}
|
|
406
|
+
for (const entry of entries) {
|
|
407
|
+
const data = entry.publishedVersion?.data || entry.draftVersion?.data;
|
|
408
|
+
if (!data) continue;
|
|
409
|
+
for (const field of relationListFields) {
|
|
410
|
+
const raw = (data as Record<string, unknown>)[field.slug];
|
|
411
|
+
if (!raw) continue;
|
|
412
|
+
const set = uuidsByCollection.get(field.collection)!;
|
|
413
|
+
if (typeof raw === 'string') set.add(raw);
|
|
414
|
+
if (Array.isArray(raw)) raw.forEach((v) => typeof v === 'string' && set.add(v));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const lookup: Record<string, string> = {};
|
|
419
|
+
await Promise.all(
|
|
420
|
+
Array.from(uuidsByCollection.entries()).map(async ([slug, ids]) => {
|
|
421
|
+
if (ids.size === 0) return;
|
|
422
|
+
const labels = await remotes.getEntryLabels({ slug, ids: Array.from(ids) });
|
|
423
|
+
for (const { id, label } of labels) {
|
|
424
|
+
lookup[id] = label;
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
return lookup;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function mapEntryToRow(entry: RawEntry, lookup: Record<string, string> = {}): CollectionDataTableRow {
|
|
333
432
|
const data = entry.draftVersion?.data || entry.publishedVersion?.data || {};
|
|
433
|
+
// For custom columns, prefer published data (complete) over draft (may be partial)
|
|
434
|
+
const columnData = entry.publishedVersion?.data || entry.draftVersion?.data || {};
|
|
435
|
+
const customData: Record<string, unknown> = {};
|
|
436
|
+
if (collection.listColumns) {
|
|
437
|
+
for (const fieldSlug of collection.listColumns) {
|
|
438
|
+
const fieldData = (columnData as Record<string, unknown>)[fieldSlug];
|
|
439
|
+
const isRelation = relationListFields.some((f) => f.slug === fieldSlug);
|
|
440
|
+
if (isRelation) {
|
|
441
|
+
// Resolve relation UUID(s) to labels
|
|
442
|
+
if (typeof fieldData === 'string') {
|
|
443
|
+
customData[fieldSlug] = lookup[fieldData] ?? '';
|
|
444
|
+
} else if (Array.isArray(fieldData)) {
|
|
445
|
+
customData[fieldSlug] = fieldData
|
|
446
|
+
.map((id) => (typeof id === 'string' ? lookup[id] ?? '' : ''))
|
|
447
|
+
.filter(Boolean)
|
|
448
|
+
.join(', ');
|
|
449
|
+
} else {
|
|
450
|
+
customData[fieldSlug] = '';
|
|
451
|
+
}
|
|
452
|
+
} else if (fieldData && typeof fieldData === 'object' && !Array.isArray(fieldData)) {
|
|
453
|
+
// For localized fields, extract current content language value
|
|
454
|
+
const localized = fieldData as Record<string, unknown>;
|
|
455
|
+
customData[fieldSlug] = localized[contentLanguage.current] ?? Object.values(localized)[0] ?? '';
|
|
456
|
+
} else {
|
|
457
|
+
customData[fieldSlug] = fieldData ?? '';
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
334
461
|
return {
|
|
335
462
|
id: entry.id,
|
|
336
463
|
name: getRawCollectionEntryLabel(entry, collection, contentLanguage.current),
|
|
@@ -341,7 +468,8 @@
|
|
|
341
468
|
updatedAt: new Date(entry.updatedAt),
|
|
342
469
|
thumbnail: getEntryThumbnail(data as Record<string, unknown>, collection),
|
|
343
470
|
searchText: getSearchText(entry),
|
|
344
|
-
a11yWarnings: countA11yWarnings(entry)
|
|
471
|
+
a11yWarnings: countA11yWarnings(entry),
|
|
472
|
+
customData
|
|
345
473
|
};
|
|
346
474
|
}
|
|
347
475
|
|
|
@@ -386,190 +514,230 @@
|
|
|
386
514
|
|
|
387
515
|
async function handleBulkArchive(items: CollectionDataTableRow[]) {
|
|
388
516
|
const idsToArchive = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
|
|
517
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
518
|
+
...old,
|
|
519
|
+
entries: old.entries.filter((e: RawEntry) => !idsToArchive.includes(e.id)),
|
|
520
|
+
total: old.total - idsToArchive.length
|
|
521
|
+
}));
|
|
389
522
|
for (const id of idsToArchive) {
|
|
390
523
|
await remotes.archiveEntryCommand(id);
|
|
391
524
|
}
|
|
392
525
|
toast.success(lang[interfaceLanguage.current].entriesArchived);
|
|
393
526
|
rowSelection = {};
|
|
394
|
-
|
|
527
|
+
await entriesQuery.refresh();
|
|
528
|
+
override.release();
|
|
395
529
|
}
|
|
396
530
|
|
|
397
531
|
async function handleBulkRestore(items: CollectionDataTableRow[]) {
|
|
398
532
|
const idsToRestore = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
|
|
533
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
534
|
+
...old,
|
|
535
|
+
entries: old.entries.filter((e: RawEntry) => !idsToRestore.includes(e.id)),
|
|
536
|
+
total: old.total - idsToRestore.length
|
|
537
|
+
}));
|
|
399
538
|
for (const id of idsToRestore) {
|
|
400
539
|
await remotes.unarchiveEntryCommand(id);
|
|
401
540
|
}
|
|
402
541
|
toast.success(lang[interfaceLanguage.current].entriesRestored);
|
|
403
542
|
rowSelection = {};
|
|
404
|
-
|
|
543
|
+
await entriesQuery.refresh();
|
|
544
|
+
override.release();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function handleReorder(items: CollectionDataTableRow[], fromIndex: number, toIndex: number) {
|
|
548
|
+
if (!isOrderable || items.length === 0) return;
|
|
549
|
+
const reordered = arrayMove(items, fromIndex, toIndex);
|
|
550
|
+
const orderedIds = reordered.map((item) => item.id);
|
|
551
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
552
|
+
...old,
|
|
553
|
+
entries: orderedIds.map((id) => old.entries.find((e: RawEntry) => e.id === id)).filter(Boolean)
|
|
554
|
+
}));
|
|
555
|
+
await remotes.reorderEntriesCommand({ orderedIds });
|
|
556
|
+
await entriesQuery.refresh();
|
|
557
|
+
override.release();
|
|
405
558
|
}
|
|
406
559
|
|
|
407
560
|
async function handleBulkDelete(items: CollectionDataTableRow[]) {
|
|
408
561
|
const idsToDelete = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
|
|
562
|
+
const override = entriesQuery.withOverride((old) => ({
|
|
563
|
+
...old,
|
|
564
|
+
entries: old.entries.filter((e: RawEntry) => !idsToDelete.includes(e.id)),
|
|
565
|
+
total: old.total - idsToDelete.length
|
|
566
|
+
}));
|
|
409
567
|
for (const id of idsToDelete) {
|
|
410
568
|
await remotes.deleteEntryCommand(id);
|
|
411
569
|
}
|
|
412
570
|
toast.success(lang[interfaceLanguage.current].entryDeleted);
|
|
413
571
|
rowSelection = {};
|
|
414
|
-
|
|
572
|
+
await entriesQuery.refresh();
|
|
573
|
+
override.release();
|
|
415
574
|
}
|
|
416
575
|
</script>
|
|
417
576
|
|
|
418
|
-
{#
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
577
|
+
{#if !entriesQuery.ready}
|
|
578
|
+
<div class="space-y-4 p-4">
|
|
579
|
+
<Skeleton class="h-10 w-full" />
|
|
580
|
+
<Skeleton class="h-64 w-full" />
|
|
581
|
+
</div>
|
|
582
|
+
{:else if entriesQuery.current}
|
|
583
|
+
{@const result = entriesQuery.current}
|
|
584
|
+
{@const allRows = result.entries.map((e) => mapEntryToRow(e, labelLookup))}
|
|
585
|
+
{@const filteredRows = (() => {
|
|
586
|
+
let rows = allRows;
|
|
587
|
+
// Client-side status filter (non-archived statuses)
|
|
588
|
+
if (!isArchivedFilter) {
|
|
589
|
+
rows = filterByStatus(rows);
|
|
590
|
+
}
|
|
591
|
+
// Client-side search
|
|
592
|
+
if (searchQuery) {
|
|
593
|
+
rows = rows.filter((item) => item.searchText.includes(searchQuery.toLowerCase()));
|
|
594
|
+
}
|
|
595
|
+
// Client-side sort (if sorting by name)
|
|
596
|
+
if (!useServerPagination) {
|
|
597
|
+
rows = sortItems(rows, viewState.sorting);
|
|
598
|
+
}
|
|
599
|
+
return rows;
|
|
600
|
+
})()}
|
|
601
|
+
{@const totalItems =
|
|
602
|
+
useServerPagination && !viewState.statusFilter && viewState.statusFilter !== 'archived'
|
|
603
|
+
? result.total
|
|
604
|
+
: filteredRows.length}
|
|
605
|
+
{@const pageCount = Math.ceil(totalItems / viewState.pageSize)}
|
|
606
|
+
{@const displayItems = useServerPagination && !viewState.statusFilter
|
|
607
|
+
? filteredRows
|
|
608
|
+
: filteredRows.slice(
|
|
609
|
+
viewState.pageIndex * viewState.pageSize,
|
|
610
|
+
(viewState.pageIndex + 1) * viewState.pageSize
|
|
611
|
+
)}
|
|
612
|
+
|
|
613
|
+
<div class:opacity-60={entriesQuery.loading} class="transition-opacity duration-200">
|
|
614
|
+
<TableToolbar
|
|
615
|
+
{searchQuery}
|
|
616
|
+
onSearchChange={(q) => {
|
|
617
|
+
searchQuery = q;
|
|
618
|
+
viewState.pageIndex = 0;
|
|
619
|
+
}}
|
|
620
|
+
statusFilter={viewState.statusFilter}
|
|
621
|
+
onStatusFilterChange={(f) => (viewState.statusFilter = f)}
|
|
622
|
+
viewMode={viewState.viewMode}
|
|
623
|
+
onViewModeChange={(m) => (viewState.viewMode = m)}
|
|
624
|
+
{onCreateEntry}
|
|
625
|
+
createLabel={addLabel}
|
|
626
|
+
/>
|
|
627
|
+
|
|
628
|
+
{#if totalItems === 0 && !searchQuery}
|
|
629
|
+
<EmptyState
|
|
630
|
+
title={lang[interfaceLanguage.current].emptyTitle}
|
|
631
|
+
description={lang[interfaceLanguage.current].emptyDescription}
|
|
632
|
+
ctaLabel={addLabel}
|
|
633
|
+
onCta={onCreateEntry}
|
|
634
|
+
/>
|
|
635
|
+
{:else if viewState.viewMode === 'list'}
|
|
636
|
+
<div class="bg-card overflow-hidden rounded-xl border shadow-sm">
|
|
637
|
+
{#if useServerPagination && !viewState.statusFilter}
|
|
638
|
+
<DataTable
|
|
639
|
+
data={displayItems}
|
|
640
|
+
{columns}
|
|
641
|
+
enableSorting={!isOrderable}
|
|
642
|
+
enableFiltering
|
|
643
|
+
enableSelection
|
|
644
|
+
enablePagination
|
|
645
|
+
manualPagination={true}
|
|
646
|
+
{pageCount}
|
|
647
|
+
rowCount={totalItems}
|
|
648
|
+
sorting={viewState.sorting}
|
|
649
|
+
onSortingChange={(s) => (viewState.sorting = s)}
|
|
650
|
+
{rowSelection}
|
|
651
|
+
onRowSelectionChange={(s) => (rowSelection = s)}
|
|
652
|
+
pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
|
|
653
|
+
onPaginationChange={(p) => {
|
|
654
|
+
viewState.pageIndex = p.pageIndex;
|
|
655
|
+
}}
|
|
656
|
+
tableRef={(t) => (tableInstance = t)}
|
|
657
|
+
orderable={isOrderable}
|
|
658
|
+
onReorder={(from, to) => handleReorder(displayItems, from, to)}
|
|
659
|
+
/>
|
|
660
|
+
{:else}
|
|
661
|
+
<DataTable
|
|
662
|
+
data={displayItems}
|
|
663
|
+
{columns}
|
|
664
|
+
enableSorting={!isOrderable}
|
|
665
|
+
enableFiltering
|
|
666
|
+
enableSelection
|
|
667
|
+
enablePagination
|
|
668
|
+
sorting={viewState.sorting}
|
|
669
|
+
onSortingChange={(s) => (viewState.sorting = s)}
|
|
670
|
+
{rowSelection}
|
|
671
|
+
onRowSelectionChange={(s) => (rowSelection = s)}
|
|
672
|
+
pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
|
|
673
|
+
onPaginationChange={(p) => {
|
|
674
|
+
viewState.pageIndex = p.pageIndex;
|
|
675
|
+
}}
|
|
676
|
+
tableRef={(t) => (tableInstance = t)}
|
|
677
|
+
orderable={isOrderable}
|
|
678
|
+
onReorder={(from, to) => handleReorder(displayItems, from, to)}
|
|
679
|
+
/>
|
|
680
|
+
{/if}
|
|
681
|
+
</div>
|
|
682
|
+
|
|
683
|
+
<TablePagination
|
|
684
|
+
pageIndex={viewState.pageIndex}
|
|
685
|
+
pageSize={viewState.pageSize}
|
|
686
|
+
{pageCount}
|
|
687
|
+
{totalItems}
|
|
688
|
+
onPageChange={(p) => (viewState.pageIndex = p)}
|
|
689
|
+
onPageSizeChange={(s) => {
|
|
690
|
+
viewState.pageSize = s;
|
|
455
691
|
}}
|
|
456
|
-
statusFilter={viewState.statusFilter}
|
|
457
|
-
onStatusFilterChange={(f) => (viewState.statusFilter = f)}
|
|
458
|
-
viewMode={viewState.viewMode}
|
|
459
|
-
onViewModeChange={(m) => (viewState.viewMode = m)}
|
|
460
|
-
{onCreateEntry}
|
|
461
|
-
createLabel={addLabel}
|
|
462
692
|
/>
|
|
463
|
-
|
|
464
|
-
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
sorting={viewState.sorting}
|
|
485
|
-
onSortingChange={(s) => (viewState.sorting = s)}
|
|
486
|
-
{rowSelection}
|
|
487
|
-
onRowSelectionChange={(s) => (rowSelection = s)}
|
|
488
|
-
pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
|
|
489
|
-
onPaginationChange={(p) => {
|
|
490
|
-
viewState.pageIndex = p.pageIndex;
|
|
491
|
-
}}
|
|
492
|
-
tableRef={(t) => (tableInstance = t)}
|
|
493
|
-
/>
|
|
494
|
-
{:else}
|
|
495
|
-
<DataTable
|
|
496
|
-
data={displayItems}
|
|
497
|
-
{columns}
|
|
498
|
-
enableSorting
|
|
499
|
-
enableFiltering
|
|
500
|
-
enableSelection
|
|
501
|
-
enablePagination
|
|
502
|
-
sorting={viewState.sorting}
|
|
503
|
-
onSortingChange={(s) => (viewState.sorting = s)}
|
|
504
|
-
{rowSelection}
|
|
505
|
-
onRowSelectionChange={(s) => (rowSelection = s)}
|
|
506
|
-
pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
|
|
507
|
-
onPaginationChange={(p) => {
|
|
508
|
-
viewState.pageIndex = p.pageIndex;
|
|
509
|
-
}}
|
|
510
|
-
tableRef={(t) => (tableInstance = t)}
|
|
511
|
-
/>
|
|
512
|
-
{/if}
|
|
513
|
-
</div>
|
|
514
|
-
|
|
515
|
-
<TablePagination
|
|
516
|
-
pageIndex={viewState.pageIndex}
|
|
517
|
-
pageSize={viewState.pageSize}
|
|
518
|
-
{pageCount}
|
|
519
|
-
{totalItems}
|
|
520
|
-
onPageChange={(p) => (viewState.pageIndex = p)}
|
|
521
|
-
onPageSizeChange={(s) => {
|
|
522
|
-
viewState.pageSize = s;
|
|
523
|
-
}}
|
|
524
|
-
/>
|
|
525
|
-
{:else}
|
|
526
|
-
{@const gridItems = displayItems.map((item) => ({
|
|
527
|
-
id: item.id,
|
|
528
|
-
name: item.name,
|
|
529
|
-
slug: item.slug,
|
|
530
|
-
status: item.status,
|
|
531
|
-
url: item.url,
|
|
532
|
-
thumbnail: item.thumbnail,
|
|
533
|
-
updatedAt: item.updatedAt
|
|
534
|
-
}))}
|
|
535
|
-
|
|
536
|
-
<GridView
|
|
537
|
-
items={gridItems}
|
|
538
|
-
enableSelection
|
|
539
|
-
selectedIds={new Set(selectedIndices.map((idx) => displayItems[idx]?.id).filter(Boolean))}
|
|
540
|
-
onSelectionChange={(ids) => {
|
|
541
|
-
const newSelection: RowSelectionState = {};
|
|
542
|
-
for (const id of ids) {
|
|
543
|
-
const idx = displayItems.findIndex((i) => i.id === id);
|
|
544
|
-
if (idx !== -1) {
|
|
545
|
-
newSelection[idx] = true;
|
|
546
|
-
}
|
|
693
|
+
{:else}
|
|
694
|
+
{@const gridItems = displayItems.map((item) => ({
|
|
695
|
+
id: item.id,
|
|
696
|
+
name: item.name,
|
|
697
|
+
slug: item.slug,
|
|
698
|
+
status: item.status,
|
|
699
|
+
url: item.url,
|
|
700
|
+
thumbnail: item.thumbnail,
|
|
701
|
+
updatedAt: item.updatedAt
|
|
702
|
+
}))}
|
|
703
|
+
|
|
704
|
+
<GridView
|
|
705
|
+
items={gridItems}
|
|
706
|
+
enableSelection
|
|
707
|
+
selectedIds={new Set(selectedIndices.map((idx) => displayItems[idx]?.id).filter(Boolean))}
|
|
708
|
+
onSelectionChange={(ids) => {
|
|
709
|
+
const newSelection: RowSelectionState = {};
|
|
710
|
+
for (const id of ids) {
|
|
711
|
+
const idx = displayItems.findIndex((i) => i.id === id);
|
|
712
|
+
if (idx !== -1) {
|
|
713
|
+
newSelection[idx] = true;
|
|
547
714
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
{/if}
|
|
563
|
-
|
|
564
|
-
<BulkActionsBar
|
|
565
|
-
{selectedCount}
|
|
566
|
-
onArchive={() => handleBulkArchive(displayItems)}
|
|
567
|
-
onDelete={() => handleBulkDelete(displayItems)}
|
|
568
|
-
onClear={() => (rowSelection = {})}
|
|
569
|
-
onRestore={isArchivedFilter ? () => handleBulkRestore(displayItems) : undefined}
|
|
715
|
+
}
|
|
716
|
+
rowSelection = newSelection;
|
|
717
|
+
}}
|
|
718
|
+
/>
|
|
719
|
+
|
|
720
|
+
<TablePagination
|
|
721
|
+
pageIndex={viewState.pageIndex}
|
|
722
|
+
pageSize={viewState.pageSize}
|
|
723
|
+
{pageCount}
|
|
724
|
+
{totalItems}
|
|
725
|
+
onPageChange={(p) => (viewState.pageIndex = p)}
|
|
726
|
+
onPageSizeChange={(s) => {
|
|
727
|
+
viewState.pageSize = s;
|
|
728
|
+
}}
|
|
570
729
|
/>
|
|
571
|
-
{/
|
|
572
|
-
|
|
730
|
+
{/if}
|
|
731
|
+
|
|
732
|
+
<BulkActionsBar
|
|
733
|
+
{selectedCount}
|
|
734
|
+
onArchive={() => handleBulkArchive(displayItems)}
|
|
735
|
+
onDelete={isArchivedFilter ? () => handleBulkDelete(displayItems) : undefined}
|
|
736
|
+
onClear={() => (rowSelection = {})}
|
|
737
|
+
onRestore={isArchivedFilter ? () => handleBulkRestore(displayItems) : undefined}
|
|
738
|
+
/>
|
|
739
|
+
</div>
|
|
740
|
+
{/if}
|
|
573
741
|
|
|
574
742
|
<AlertDialog.Root bind:open={deleteDialogOpen}>
|
|
575
743
|
<AlertDialog.Content>
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
let { data }: Props = $props();
|
|
10
10
|
|
|
11
11
|
const collectionSlug = $derived(data?.collectionSlug || page.params.collection || '');
|
|
12
|
+
const query = $derived(remotes.getCollection(collectionSlug));
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
|
-
{#
|
|
15
|
-
<
|
|
16
|
-
{
|
|
15
|
+
{#if query.ready && query.current}
|
|
16
|
+
<div class:opacity-60={query.loading} class="transition-opacity duration-200">
|
|
17
|
+
<Collection collection={query.current} />
|
|
18
|
+
</div>
|
|
19
|
+
{/if}
|