includio-cms 0.0.56 → 0.0.58
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/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
- package/dist/admin/client/admin/dashboard-page.svelte +10 -47
- package/dist/admin/client/collection/archived-actions.svelte +14 -2
- package/dist/admin/client/collection/collection-entries.svelte +289 -56
- package/dist/admin/client/collection/collection-entries.svelte.d.ts +2 -0
- package/dist/admin/client/collection/collection-view.svelte.d.ts +15 -0
- package/dist/admin/client/collection/collection-view.svelte.js +66 -0
- package/dist/admin/client/collection/collection.svelte +14 -10
- package/dist/admin/client/collection/data-table.svelte +76 -5
- package/dist/admin/client/collection/data-table.svelte.d.ts +13 -1
- package/dist/admin/client/collection/date-cell.svelte +25 -0
- package/dist/admin/client/collection/date-cell.svelte.d.ts +7 -0
- package/dist/admin/client/collection/grid-view.svelte +100 -0
- package/dist/admin/client/collection/grid-view.svelte.d.ts +20 -0
- package/dist/admin/client/collection/selection-cell.svelte +20 -0
- package/dist/admin/client/collection/selection-cell.svelte.d.ts +9 -0
- package/dist/admin/client/collection/sortable-header.svelte +42 -0
- package/dist/admin/client/collection/sortable-header.svelte.d.ts +29 -0
- package/dist/admin/client/collection/status-badge.svelte +40 -0
- package/dist/admin/client/collection/status-badge.svelte.d.ts +7 -0
- package/dist/admin/client/collection/table-pagination.svelte +136 -0
- package/dist/admin/client/collection/table-pagination.svelte.d.ts +11 -0
- package/dist/admin/client/collection/table-toolbar.svelte +156 -0
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +16 -0
- package/dist/admin/client/entry/entry-header.svelte +17 -40
- package/dist/admin/client/entry/entry-header.svelte.d.ts +3 -1
- package/dist/admin/client/entry/entry-page-skeleton.svelte +36 -0
- package/dist/admin/client/entry/entry-page-skeleton.svelte.d.ts +18 -0
- package/dist/admin/client/entry/entry-page.svelte +4 -1
- package/dist/admin/client/entry/entry.svelte +99 -70
- package/dist/admin/client/entry/header/save-indicator.svelte +58 -0
- package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +7 -0
- package/dist/admin/client/entry/header/schedule-popover.svelte +127 -0
- package/dist/admin/client/entry/header/schedule-popover.svelte.d.ts +6 -0
- package/dist/admin/client/entry/header/status-badge.svelte +150 -0
- package/dist/admin/client/entry/header/status-badge.svelte.d.ts +8 -0
- package/dist/admin/client/entry/header/version-history-sheet.svelte +225 -0
- package/dist/admin/client/entry/header/version-history-sheet.svelte.d.ts +8 -0
- package/dist/admin/client/entry/hybrid/hybrid-context.svelte.js +7 -1
- package/dist/admin/client/form/form-config-wrapper.svelte +32 -0
- package/dist/admin/client/form/form-config-wrapper.svelte.d.ts +7 -0
- package/dist/admin/client/form/form-page.svelte +8 -24
- package/dist/admin/client/form/form-submission/form-submission-page.svelte +80 -13
- package/dist/admin/client/form/form-submission/form-submission-page.svelte.d.ts +6 -1
- package/dist/admin/client/form/form-submission/form-submission.svelte +224 -6
- package/dist/admin/client/form/form-submission/submission-field.svelte +104 -0
- package/dist/admin/client/form/form-submission/submission-field.svelte.d.ts +8 -0
- package/dist/admin/client/form/form-submissions.svelte +257 -21
- package/dist/admin/client/form/submission-link.svelte +3 -2
- package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
- package/dist/admin/client/form/submission-status-badge.svelte +27 -0
- package/dist/admin/client/form/submission-status-badge.svelte.d.ts +6 -0
- package/dist/admin/components/dashboard/accessibility-hub.svelte +146 -0
- package/dist/admin/components/dashboard/accessibility-hub.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +152 -0
- package/dist/admin/components/dashboard/form-submissions-widget.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/index.d.ts +4 -0
- package/dist/admin/components/dashboard/index.js +4 -0
- package/dist/admin/components/dashboard/recent-activity.svelte +112 -0
- package/dist/admin/components/dashboard/recent-activity.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/stat-card.svelte +43 -0
- package/dist/admin/components/dashboard/stat-card.svelte.d.ts +14 -0
- package/dist/admin/components/fields/array-field.svelte +32 -7
- package/dist/admin/components/fields/array-field.svelte.d.ts +1 -0
- package/dist/admin/components/fields/block-picker-modal.svelte +97 -0
- package/dist/admin/components/fields/block-picker-modal.svelte.d.ts +9 -0
- package/dist/admin/components/fields/field-renderer.svelte +4 -3
- package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/fields/fields-form.svelte +50 -28
- package/dist/admin/components/fields/image-field.svelte +7 -3
- package/dist/admin/components/fields/object-field.svelte +18 -3
- package/dist/admin/components/fields/object-field.svelte.d.ts +1 -0
- package/dist/admin/components/fields/relation-field.svelte +59 -55
- package/dist/admin/components/fields/relation-field.svelte.d.ts +6 -6
- package/dist/admin/components/media/file/file-preview.svelte +44 -23
- package/dist/admin/components/media/media-selector.svelte +47 -31
- package/dist/admin/remote/entry.remote.d.ts +9 -1
- package/dist/admin/remote/entry.remote.js +51 -8
- package/dist/admin/remote/form.remote.d.ts +19 -0
- package/dist/admin/remote/form.remote.js +45 -1
- package/dist/admin/utils/entryThumbnail.d.ts +2 -0
- package/dist/admin/utils/entryThumbnail.js +79 -0
- package/dist/admin/utils/formatDate.d.ts +3 -0
- package/dist/admin/utils/formatDate.js +94 -0
- package/dist/components/ui/alert-dialog/alert-dialog-action.svelte +23 -0
- package/dist/components/ui/alert-dialog/alert-dialog-action.svelte.d.ts +9 -0
- package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte +23 -0
- package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte.d.ts +9 -0
- package/dist/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
- package/dist/components/ui/alert-dialog/alert-dialog-content.svelte.d.ts +10 -0
- package/dist/components/ui/alert-dialog/alert-dialog-description.svelte +22 -0
- package/dist/components/ui/alert-dialog/alert-dialog-description.svelte.d.ts +9 -0
- package/dist/components/ui/alert-dialog/alert-dialog-footer.svelte +19 -0
- package/dist/components/ui/alert-dialog/alert-dialog-footer.svelte.d.ts +5 -0
- package/dist/components/ui/alert-dialog/alert-dialog-title.svelte +22 -0
- package/dist/components/ui/alert-dialog/alert-dialog-title.svelte.d.ts +9 -0
- package/dist/components/ui/alert-dialog/index.d.ts +11 -0
- package/dist/components/ui/alert-dialog/index.js +13 -0
- package/dist/core/server/entries/operations/get.js +3 -1
- package/dist/core/server/entries/operations/update.d.ts +2 -1
- package/dist/core/server/entries/operations/update.js +17 -0
- package/dist/core/server/fields/resolveImageFields.js +2 -1
- package/dist/core/server/fields/resolveUrlFields.js +2 -1
- package/dist/core/server/forms/submissions/operations/create.d.ts +7 -1
- package/dist/core/server/forms/submissions/operations/create.js +8 -2
- package/dist/core/server/forms/submissions/operations/delete.d.ts +2 -0
- package/dist/core/server/forms/submissions/operations/delete.js +9 -0
- package/dist/core/server/forms/submissions/operations/export.d.ts +1 -0
- package/dist/core/server/forms/submissions/operations/export.js +40 -0
- package/dist/core/server/forms/submissions/operations/update.d.ts +2 -0
- package/dist/core/server/forms/submissions/operations/update.js +4 -0
- package/dist/core/server/generator/generator.js +7 -2
- package/dist/core/server/media/operations/uploadFile.js +5 -1
- package/dist/db-postgres/index.js +15 -2
- package/dist/db-postgres/schema/formSubmission.d.ts +51 -0
- package/dist/db-postgres/schema/formSubmission.js +5 -2
- package/dist/sveltekit/components/image.svelte +16 -12
- package/dist/types/adapters/db.d.ts +11 -1
- package/dist/types/entries.d.ts +1 -0
- package/dist/types/fields.d.ts +2 -0
- package/dist/types/forms.d.ts +3 -0
- package/package.json +1 -1
- package/dist/admin/client/form/data-table.svelte +0 -56
- package/dist/admin/client/form/data-table.svelte.d.ts +0 -29
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import AccessibilityHub from '../../components/dashboard/accessibility-hub.svelte';
|
|
3
|
+
import FormSubmissionsWidget from '../../components/dashboard/form-submissions-widget.svelte';
|
|
4
|
+
import RecentActivity from '../../components/dashboard/recent-activity.svelte';
|
|
3
5
|
import { sidebarLang } from '../../components/layout/lang.js';
|
|
4
6
|
import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
|
|
5
7
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
@@ -7,7 +9,6 @@
|
|
|
7
9
|
import { Button } from '../../../components/ui/button/index.js';
|
|
8
10
|
|
|
9
11
|
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
10
|
-
import InfoCircleIcon from '@tabler/icons-svelte/icons/info-circle';
|
|
11
12
|
import AlertTriangleIcon from '@tabler/icons-svelte/icons/alert-triangle';
|
|
12
13
|
import TrashIcon from '@tabler/icons-svelte/icons/trash';
|
|
13
14
|
|
|
@@ -61,17 +62,6 @@
|
|
|
61
62
|
];
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
const alertLang: Record<InterfaceLanguage, { title: string; description: string }> = {
|
|
65
|
-
pl: {
|
|
66
|
-
title: 'Kokpit jest w trakcie budowy',
|
|
67
|
-
description: 'Oczekuj na więcej funkcji w nadchodzących aktualizacjach.'
|
|
68
|
-
},
|
|
69
|
-
en: {
|
|
70
|
-
title: 'The dashboard is under construction',
|
|
71
|
-
description: 'Expect more features in upcoming updates.'
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
65
|
const orphanedLang: Record<
|
|
76
66
|
InterfaceLanguage,
|
|
77
67
|
{ title: string; description: string; deleteBtn: string; confirmDelete: string }
|
|
@@ -93,27 +83,12 @@
|
|
|
93
83
|
};
|
|
94
84
|
</script>
|
|
95
85
|
|
|
96
|
-
<div class="
|
|
97
|
-
|
|
98
|
-
<div
|
|
99
|
-
class="pointer-events-none absolute inset-0 opacity-30 dark:opacity-10"
|
|
100
|
-
style="background-image: radial-gradient(circle, oklch(0.5 0.02 285) 1px, transparent 1px); background-size: 24px 24px;"
|
|
101
|
-
></div>
|
|
102
|
-
|
|
103
|
-
<!-- Floating orbs -->
|
|
104
|
-
<div
|
|
105
|
-
class="animate-float-slow pointer-events-none absolute -top-20 right-10 h-64 w-64 rounded-full bg-primary/10 blur-3xl"
|
|
106
|
-
></div>
|
|
107
|
-
<div
|
|
108
|
-
class="animate-float-slow pointer-events-none absolute bottom-20 left-10 h-48 w-48 rounded-full bg-primary/15 blur-2xl"
|
|
109
|
-
style="animation-delay: -5s;"
|
|
110
|
-
></div>
|
|
111
|
-
|
|
112
|
-
<div class="relative z-10">
|
|
86
|
+
<div class="min-h-[calc(100vh-var(--header-height))] bg-slate-50 p-4 dark:bg-slate-950 md:p-6">
|
|
87
|
+
<div class="mx-auto max-w-7xl space-y-6">
|
|
113
88
|
{#if !loadingOrphaned && orphanedEntries.length > 0}
|
|
114
89
|
<Alert.Root
|
|
115
90
|
variant="destructive"
|
|
116
|
-
class="
|
|
91
|
+
class="rounded-2xl border-orange-300/50 bg-orange-50 dark:border-orange-500/30 dark:bg-orange-950/60"
|
|
117
92
|
>
|
|
118
93
|
<AlertTriangleIcon class="text-orange-600 dark:text-orange-400" />
|
|
119
94
|
<Alert.Title class="text-orange-800 dark:text-orange-200">
|
|
@@ -142,23 +117,11 @@
|
|
|
142
117
|
</Alert.Root>
|
|
143
118
|
{/if}
|
|
144
119
|
|
|
145
|
-
<
|
|
146
|
-
class="glass-panel mb-4 rounded-2xl border-slate-200/50 bg-white/80 backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/60 md:mb-6"
|
|
147
|
-
>
|
|
148
|
-
<InfoCircleIcon />
|
|
149
|
-
<Alert.Title>{alertLang[interfaceLanguage.current].title}</Alert.Title>
|
|
150
|
-
<Alert.Description>{alertLang[interfaceLanguage.current].description}</Alert.Description>
|
|
151
|
-
</Alert.Root>
|
|
120
|
+
<AccessibilityHub />
|
|
152
121
|
|
|
153
|
-
<div class="
|
|
154
|
-
<
|
|
155
|
-
<
|
|
156
|
-
href="/admin/demo/hybrid-editor"
|
|
157
|
-
class="glass-card rounded-2xl p-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl"
|
|
158
|
-
>
|
|
159
|
-
<h3 class="font-medium">Hybrid Editor Demo</h3>
|
|
160
|
-
<p class="text-muted-foreground text-sm">Try the visual editing experience</p>
|
|
161
|
-
</a>
|
|
122
|
+
<div class="grid gap-6 lg:grid-cols-2">
|
|
123
|
+
<FormSubmissionsWidget />
|
|
124
|
+
<RecentActivity />
|
|
162
125
|
</div>
|
|
163
126
|
</div>
|
|
164
127
|
</div>
|
|
@@ -9,9 +9,21 @@
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
let { restoreLabel, deleteLabel, onRestore, onDelete }: Props = $props();
|
|
12
|
+
|
|
13
|
+
function handleRestore(e: MouseEvent) {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
e.stopPropagation();
|
|
16
|
+
onRestore();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleDelete(e: MouseEvent) {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
e.stopPropagation();
|
|
22
|
+
onDelete();
|
|
23
|
+
}
|
|
12
24
|
</script>
|
|
13
25
|
|
|
14
26
|
<div class="flex gap-2">
|
|
15
|
-
<Button size="sm" variant="outline" onclick={
|
|
16
|
-
<Button size="sm" variant="destructive" onclick={
|
|
27
|
+
<Button size="sm" variant="outline" onclick={handleRestore}>{restoreLabel}</Button>
|
|
28
|
+
<Button size="sm" variant="destructive" onclick={handleDelete}>{deleteLabel}</Button>
|
|
17
29
|
</div>
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Plus from '@tabler/icons-svelte/icons/plus';
|
|
3
|
+
import Button from '../../../components/ui/button/button.svelte';
|
|
2
4
|
import { getRawCollectionEntryLabel } from '../../utils/entryLabel.js';
|
|
5
|
+
import { getEntryThumbnail } from '../../utils/entryThumbnail.js';
|
|
3
6
|
import { getRemotes } from '../../../sveltekit/index.js';
|
|
4
7
|
import type { CollectionConfigWithType } from '../../../types/collections.js';
|
|
5
8
|
import DataTable from './data-table.svelte';
|
|
6
9
|
import { getContentLanguage } from '../../state/content-language.svelte.js';
|
|
7
|
-
import type { ColumnDef } from '@tanstack/table-core';
|
|
10
|
+
import type { ColumnDef, RowSelectionState, SortingState, Table } from '@tanstack/table-core';
|
|
8
11
|
import { renderComponent } from '../../../components/ui/data-table/render-helpers.js';
|
|
9
12
|
import EntryLink from './entry-link.svelte';
|
|
10
13
|
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
11
14
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
12
15
|
import { getEntryStatus } from '../entry/utils.js';
|
|
13
16
|
import * as Tabs from '../../../components/ui/tabs/index.js';
|
|
17
|
+
import * as AlertDialog from '../../../components/ui/alert-dialog/index.js';
|
|
14
18
|
import { toast } from 'svelte-sonner';
|
|
15
19
|
import ArchivedActions from './archived-actions.svelte';
|
|
20
|
+
import StatusBadge from './status-badge.svelte';
|
|
21
|
+
import SortableHeader from './sortable-header.svelte';
|
|
22
|
+
import DateCell from './date-cell.svelte';
|
|
23
|
+
import SelectionCell from './selection-cell.svelte';
|
|
24
|
+
import TableToolbar from './table-toolbar.svelte';
|
|
25
|
+
import TablePagination from './table-pagination.svelte';
|
|
26
|
+
import GridView from './grid-view.svelte';
|
|
27
|
+
import { createCollectionViewState } from './collection-view.svelte.js';
|
|
28
|
+
import type { EntryStatus, RawEntry } from '../../../types/entries.js';
|
|
16
29
|
|
|
17
30
|
const remotes = getRemotes();
|
|
18
31
|
const contentLanguage = getContentLanguage();
|
|
@@ -31,6 +44,12 @@
|
|
|
31
44
|
delete: string;
|
|
32
45
|
entryRestored: string;
|
|
33
46
|
entryDeleted: string;
|
|
47
|
+
selectAll: string;
|
|
48
|
+
selectRow: string;
|
|
49
|
+
entriesArchived: string;
|
|
50
|
+
deleteConfirmTitle: string;
|
|
51
|
+
deleteConfirmDescription: string;
|
|
52
|
+
cancel: string;
|
|
34
53
|
}
|
|
35
54
|
> = {
|
|
36
55
|
en: {
|
|
@@ -43,7 +62,13 @@
|
|
|
43
62
|
restore: 'Restore',
|
|
44
63
|
delete: 'Delete',
|
|
45
64
|
entryRestored: 'Entry restored',
|
|
46
|
-
entryDeleted: 'Entry permanently deleted'
|
|
65
|
+
entryDeleted: 'Entry permanently deleted',
|
|
66
|
+
selectAll: 'Select all',
|
|
67
|
+
selectRow: 'Select row',
|
|
68
|
+
entriesArchived: 'Entries archived',
|
|
69
|
+
deleteConfirmTitle: 'Delete entry permanently?',
|
|
70
|
+
deleteConfirmDescription: 'This action cannot be undone. The entry will be permanently deleted.',
|
|
71
|
+
cancel: 'Cancel'
|
|
47
72
|
},
|
|
48
73
|
pl: {
|
|
49
74
|
name: 'Nazwa',
|
|
@@ -55,23 +80,70 @@
|
|
|
55
80
|
restore: 'Przywróć',
|
|
56
81
|
delete: 'Usuń',
|
|
57
82
|
entryRestored: 'Wpis przywrócony',
|
|
58
|
-
entryDeleted: 'Wpis trwale usunięty'
|
|
83
|
+
entryDeleted: 'Wpis trwale usunięty',
|
|
84
|
+
selectAll: 'Zaznacz wszystkie',
|
|
85
|
+
selectRow: 'Zaznacz wiersz',
|
|
86
|
+
entriesArchived: 'Wpisy zarchiwizowane',
|
|
87
|
+
deleteConfirmTitle: 'Usunąć wpis na stałe?',
|
|
88
|
+
deleteConfirmDescription: 'Ta akcja jest nieodwracalna. Wpis zostanie trwale usunięty.',
|
|
89
|
+
cancel: 'Anuluj'
|
|
59
90
|
}
|
|
60
91
|
};
|
|
61
92
|
|
|
62
93
|
interface CollectionDataTableRow {
|
|
63
94
|
id: string;
|
|
64
95
|
name: string;
|
|
65
|
-
status:
|
|
96
|
+
status: EntryStatus;
|
|
66
97
|
createdAt: Date;
|
|
67
98
|
updatedAt: Date;
|
|
68
99
|
url: string;
|
|
100
|
+
thumbnail: string | null;
|
|
101
|
+
searchText: string;
|
|
69
102
|
}
|
|
70
103
|
|
|
104
|
+
type Props = {
|
|
105
|
+
collection: CollectionConfigWithType;
|
|
106
|
+
onCreateEntry: () => void;
|
|
107
|
+
addLabel: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
let { collection, onCreateEntry, addLabel }: Props = $props();
|
|
111
|
+
|
|
112
|
+
const viewState = createCollectionViewState(collection.slug);
|
|
113
|
+
|
|
114
|
+
let searchQuery = $state('');
|
|
115
|
+
let rowSelection = $state<RowSelectionState>({});
|
|
116
|
+
let tableInstance = $state<Table<CollectionDataTableRow> | null>(null);
|
|
117
|
+
let deleteDialogOpen = $state(false);
|
|
118
|
+
let pendingDeleteId = $state<string | null>(null);
|
|
119
|
+
|
|
71
120
|
const columns: ColumnDef<CollectionDataTableRow>[] = $derived([
|
|
121
|
+
{
|
|
122
|
+
id: 'select',
|
|
123
|
+
header: ({ table }) =>
|
|
124
|
+
renderComponent(SelectionCell, {
|
|
125
|
+
checked: table.getIsAllPageRowsSelected(),
|
|
126
|
+
indeterminate: table.getIsSomePageRowsSelected(),
|
|
127
|
+
onCheckedChange: (value: boolean) => table.toggleAllPageRowsSelected(value),
|
|
128
|
+
ariaLabel: lang[interfaceLanguage.current].selectAll
|
|
129
|
+
}),
|
|
130
|
+
cell: ({ row }) =>
|
|
131
|
+
renderComponent(SelectionCell, {
|
|
132
|
+
checked: row.getIsSelected(),
|
|
133
|
+
onCheckedChange: (value: boolean) => row.toggleSelected(value),
|
|
134
|
+
ariaLabel: lang[interfaceLanguage.current].selectRow
|
|
135
|
+
}),
|
|
136
|
+
enableSorting: false,
|
|
137
|
+
enableHiding: false
|
|
138
|
+
},
|
|
72
139
|
{
|
|
73
140
|
accessorKey: 'name',
|
|
74
|
-
header:
|
|
141
|
+
header: ({ column }) =>
|
|
142
|
+
renderComponent(SortableHeader<CollectionDataTableRow>, {
|
|
143
|
+
column,
|
|
144
|
+
label: lang[interfaceLanguage.current].name,
|
|
145
|
+
sorting: viewState.sorting
|
|
146
|
+
}),
|
|
75
147
|
cell: (info) => {
|
|
76
148
|
return renderComponent(EntryLink, {
|
|
77
149
|
name: info.row.original.name,
|
|
@@ -81,21 +153,40 @@
|
|
|
81
153
|
},
|
|
82
154
|
{
|
|
83
155
|
accessorKey: 'status',
|
|
84
|
-
header: 'Status'
|
|
156
|
+
header: 'Status',
|
|
157
|
+
cell: (info) =>
|
|
158
|
+
renderComponent(StatusBadge, {
|
|
159
|
+
status: info.row.original.status
|
|
160
|
+
}),
|
|
161
|
+
enableSorting: false
|
|
85
162
|
},
|
|
86
163
|
{
|
|
87
164
|
accessorKey: 'createdAt',
|
|
88
|
-
header:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
165
|
+
header: ({ column }) =>
|
|
166
|
+
renderComponent(SortableHeader<CollectionDataTableRow>, {
|
|
167
|
+
column,
|
|
168
|
+
label: lang[interfaceLanguage.current].createdAt,
|
|
169
|
+
sorting: viewState.sorting
|
|
170
|
+
}),
|
|
171
|
+
cell: (info) =>
|
|
172
|
+
renderComponent(DateCell, {
|
|
173
|
+
date: info.row.original.createdAt,
|
|
174
|
+
format: viewState.dateFormat
|
|
175
|
+
})
|
|
92
176
|
},
|
|
93
177
|
{
|
|
94
178
|
accessorKey: 'updatedAt',
|
|
95
|
-
header:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
179
|
+
header: ({ column }) =>
|
|
180
|
+
renderComponent(SortableHeader<CollectionDataTableRow>, {
|
|
181
|
+
column,
|
|
182
|
+
label: lang[interfaceLanguage.current].updatedAt,
|
|
183
|
+
sorting: viewState.sorting
|
|
184
|
+
}),
|
|
185
|
+
cell: (info) =>
|
|
186
|
+
renderComponent(DateCell, {
|
|
187
|
+
date: info.row.original.updatedAt,
|
|
188
|
+
format: viewState.dateFormat
|
|
189
|
+
})
|
|
99
190
|
}
|
|
100
191
|
]);
|
|
101
192
|
|
|
@@ -112,16 +203,20 @@
|
|
|
112
203
|
{
|
|
113
204
|
accessorKey: 'createdAt',
|
|
114
205
|
header: lang[interfaceLanguage.current].createdAt,
|
|
115
|
-
cell: (info) =>
|
|
116
|
-
|
|
117
|
-
|
|
206
|
+
cell: (info) =>
|
|
207
|
+
renderComponent(DateCell, {
|
|
208
|
+
date: info.row.original.createdAt,
|
|
209
|
+
format: viewState.dateFormat
|
|
210
|
+
})
|
|
118
211
|
},
|
|
119
212
|
{
|
|
120
213
|
accessorKey: 'updatedAt',
|
|
121
214
|
header: lang[interfaceLanguage.current].updatedAt,
|
|
122
|
-
cell: (info) =>
|
|
123
|
-
|
|
124
|
-
|
|
215
|
+
cell: (info) =>
|
|
216
|
+
renderComponent(DateCell, {
|
|
217
|
+
date: info.row.original.updatedAt,
|
|
218
|
+
format: viewState.dateFormat
|
|
219
|
+
})
|
|
125
220
|
},
|
|
126
221
|
{
|
|
127
222
|
id: 'actions',
|
|
@@ -137,12 +232,6 @@
|
|
|
137
232
|
}
|
|
138
233
|
]);
|
|
139
234
|
|
|
140
|
-
type Props = {
|
|
141
|
-
collection: CollectionConfigWithType;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
let { collection }: Props = $props();
|
|
145
|
-
|
|
146
235
|
let activeTab = $state('active');
|
|
147
236
|
|
|
148
237
|
async function handleRestore(id: string) {
|
|
@@ -152,55 +241,199 @@
|
|
|
152
241
|
remotes.getRawEntries({ slug: collection.slug }).refresh();
|
|
153
242
|
}
|
|
154
243
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
244
|
+
function handleDelete(id: string) {
|
|
245
|
+
pendingDeleteId = id;
|
|
246
|
+
deleteDialogOpen = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function confirmDelete() {
|
|
250
|
+
if (!pendingDeleteId) return;
|
|
251
|
+
await remotes.deleteEntryCommand(pendingDeleteId);
|
|
158
252
|
toast.success(lang[interfaceLanguage.current].entryDeleted);
|
|
159
253
|
remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }).refresh();
|
|
254
|
+
deleteDialogOpen = false;
|
|
255
|
+
pendingDeleteId = null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getSearchText(entry: RawEntry): string {
|
|
259
|
+
const data = entry.draftVersion?.data || entry.publishedVersion?.data;
|
|
260
|
+
if (!data) return '';
|
|
261
|
+
return extractTextValues(data).join(' ').toLowerCase();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function extractTextValues(obj: unknown): string[] {
|
|
265
|
+
const values: string[] = [];
|
|
266
|
+
|
|
267
|
+
if (typeof obj === 'string') {
|
|
268
|
+
values.push(obj);
|
|
269
|
+
} else if (Array.isArray(obj)) {
|
|
270
|
+
for (const item of obj) {
|
|
271
|
+
values.push(...extractTextValues(item));
|
|
272
|
+
}
|
|
273
|
+
} else if (obj && typeof obj === 'object') {
|
|
274
|
+
for (const value of Object.values(obj)) {
|
|
275
|
+
values.push(...extractTextValues(value));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return values;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function mapEntryToRow(entry: RawEntry): CollectionDataTableRow {
|
|
283
|
+
const data = entry.draftVersion?.data || entry.publishedVersion?.data || {};
|
|
284
|
+
return {
|
|
285
|
+
id: entry.id,
|
|
286
|
+
name: getRawCollectionEntryLabel(entry, collection, contentLanguage.current),
|
|
287
|
+
url: `/admin/entries/${entry.id}`,
|
|
288
|
+
status: getEntryStatus(entry),
|
|
289
|
+
createdAt: new Date(entry.createdAt),
|
|
290
|
+
updatedAt: new Date(entry.updatedAt),
|
|
291
|
+
thumbnail: getEntryThumbnail(data as Record<string, unknown>, collection),
|
|
292
|
+
searchText: getSearchText(entry)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const selectedIndices = $derived(Object.keys(rowSelection).filter((k) => rowSelection[k]).map(Number));
|
|
297
|
+
const selectedCount = $derived(selectedIndices.length);
|
|
298
|
+
|
|
299
|
+
function sortItems<T extends CollectionDataTableRow>(items: T[], sorting: SortingState): T[] {
|
|
300
|
+
if (!sorting.length) return items;
|
|
301
|
+
const { id, desc } = sorting[0];
|
|
302
|
+
return [...items].sort((a, b) => {
|
|
303
|
+
const aVal = a[id as keyof CollectionDataTableRow];
|
|
304
|
+
const bVal = b[id as keyof CollectionDataTableRow];
|
|
305
|
+
if (aVal instanceof Date && bVal instanceof Date) {
|
|
306
|
+
return desc ? bVal.getTime() - aVal.getTime() : aVal.getTime() - bVal.getTime();
|
|
307
|
+
}
|
|
308
|
+
if (typeof aVal === 'string' && typeof bVal === 'string') {
|
|
309
|
+
return desc ? bVal.localeCompare(aVal) : aVal.localeCompare(bVal);
|
|
310
|
+
}
|
|
311
|
+
return 0;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function handleBulkArchive(items: CollectionDataTableRow[]) {
|
|
316
|
+
const idsToArchive = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
|
|
317
|
+
for (const id of idsToArchive) {
|
|
318
|
+
await remotes.archiveEntryCommand(id);
|
|
319
|
+
}
|
|
320
|
+
toast.success(lang[interfaceLanguage.current].entriesArchived);
|
|
321
|
+
rowSelection = {};
|
|
322
|
+
remotes.getRawEntries({ slug: collection.slug }).refresh();
|
|
323
|
+
remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }).refresh();
|
|
160
324
|
}
|
|
161
325
|
</script>
|
|
162
326
|
|
|
163
327
|
<Tabs.Root bind:value={activeTab} class="w-full">
|
|
164
|
-
<
|
|
165
|
-
<Tabs.
|
|
166
|
-
|
|
167
|
-
|
|
328
|
+
<div class="flex items-center justify-between border-b border-slate-200/50 px-4 py-3 dark:border-white/10">
|
|
329
|
+
<Tabs.List class="h-auto gap-1 bg-transparent p-0">
|
|
330
|
+
<Tabs.Trigger value="active" class="rounded-lg px-3 py-1.5 text-sm font-medium data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 dark:data-[state=active]:bg-slate-800 dark:data-[state=active]:text-white">{lang[interfaceLanguage.current].active}</Tabs.Trigger>
|
|
331
|
+
<Tabs.Trigger value="archived" class="rounded-lg px-3 py-1.5 text-sm font-medium data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 dark:data-[state=active]:bg-slate-800 dark:data-[state=active]:text-white">{lang[interfaceLanguage.current].archived}</Tabs.Trigger>
|
|
332
|
+
</Tabs.List>
|
|
333
|
+
<Button type="button" onclick={onCreateEntry} class="btn-gradient" size="sm">
|
|
334
|
+
<Plus class="size-4" /> {addLabel}
|
|
335
|
+
</Button>
|
|
336
|
+
</div>
|
|
168
337
|
|
|
169
|
-
<Tabs.Content value="active">
|
|
338
|
+
<Tabs.Content value="active" class="mt-0">
|
|
170
339
|
{#await remotes.getRawEntries({ slug: collection.slug }) then entries}
|
|
171
340
|
{@const items = entries
|
|
172
341
|
.filter((entry) => entry.archivedAt === null)
|
|
173
|
-
.map(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
342
|
+
.map(mapEntryToRow)}
|
|
343
|
+
{@const filteredItems = searchQuery
|
|
344
|
+
? items.filter((item) => item.searchText.includes(searchQuery.toLowerCase()))
|
|
345
|
+
: items}
|
|
346
|
+
{@const totalItems = filteredItems.length}
|
|
347
|
+
{@const pageCount = Math.ceil(totalItems / viewState.pageSize)}
|
|
348
|
+
|
|
349
|
+
<TableToolbar
|
|
350
|
+
{searchQuery}
|
|
351
|
+
onSearchChange={(q) => (searchQuery = q)}
|
|
352
|
+
viewMode={viewState.viewMode}
|
|
353
|
+
onViewModeChange={(m) => (viewState.viewMode = m)}
|
|
354
|
+
dateFormat={viewState.dateFormat}
|
|
355
|
+
onDateFormatChange={(f) => (viewState.dateFormat = f)}
|
|
356
|
+
{selectedCount}
|
|
357
|
+
onBulkArchive={() => handleBulkArchive(sortItems(filteredItems, viewState.sorting))}
|
|
358
|
+
/>
|
|
359
|
+
|
|
360
|
+
{#if viewState.viewMode === 'list'}
|
|
361
|
+
<DataTable
|
|
362
|
+
data={sortItems(filteredItems, viewState.sorting)}
|
|
363
|
+
{columns}
|
|
364
|
+
enableSorting
|
|
365
|
+
enableFiltering
|
|
366
|
+
enableSelection
|
|
367
|
+
enablePagination
|
|
368
|
+
sorting={viewState.sorting}
|
|
369
|
+
onSortingChange={(s) => (viewState.sorting = s)}
|
|
370
|
+
{rowSelection}
|
|
371
|
+
onRowSelectionChange={(s) => (rowSelection = s)}
|
|
372
|
+
pagination={{ pageIndex: 0, pageSize: viewState.pageSize }}
|
|
373
|
+
tableRef={(t) => (tableInstance = t)}
|
|
374
|
+
/>
|
|
375
|
+
|
|
376
|
+
<TablePagination
|
|
377
|
+
pageIndex={tableInstance?.getState().pagination.pageIndex ?? 0}
|
|
378
|
+
pageSize={viewState.pageSize}
|
|
379
|
+
{pageCount}
|
|
380
|
+
{totalItems}
|
|
381
|
+
onPageChange={(p) => tableInstance?.setPageIndex(p)}
|
|
382
|
+
onPageSizeChange={(s) => {
|
|
383
|
+
viewState.pageSize = s;
|
|
384
|
+
tableInstance?.setPageSize(s);
|
|
385
|
+
}}
|
|
386
|
+
/>
|
|
387
|
+
{:else}
|
|
388
|
+
<GridView
|
|
389
|
+
items={sortItems(filteredItems, viewState.sorting).map((item) => ({
|
|
390
|
+
id: item.id,
|
|
391
|
+
name: item.name,
|
|
392
|
+
status: item.status,
|
|
393
|
+
url: item.url,
|
|
394
|
+
thumbnail: item.thumbnail,
|
|
395
|
+
updatedAt: item.updatedAt
|
|
396
|
+
}))}
|
|
397
|
+
dateFormat={viewState.dateFormat}
|
|
398
|
+
enableSelection
|
|
399
|
+
selectedIds={new Set(selectedIndices.map((idx) => sortItems(filteredItems, viewState.sorting)[idx]?.id).filter(Boolean))}
|
|
400
|
+
onSelectionChange={(ids) => {
|
|
401
|
+
const sorted = sortItems(filteredItems, viewState.sorting);
|
|
402
|
+
const newSelection: RowSelectionState = {};
|
|
403
|
+
for (const id of ids) {
|
|
404
|
+
const idx = sorted.findIndex((i) => i.id === id);
|
|
405
|
+
if (idx !== -1) {
|
|
406
|
+
newSelection[idx] = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
rowSelection = newSelection;
|
|
410
|
+
}}
|
|
411
|
+
/>
|
|
412
|
+
{/if}
|
|
185
413
|
{/await}
|
|
186
414
|
</Tabs.Content>
|
|
187
415
|
|
|
188
|
-
<Tabs.Content value="archived">
|
|
416
|
+
<Tabs.Content value="archived" class="mt-0">
|
|
189
417
|
{#await remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }) then entries}
|
|
190
418
|
{@const items = entries.map((entry) => ({
|
|
191
|
-
|
|
192
|
-
collection: entry.slug,
|
|
193
|
-
name: getRawCollectionEntryLabel(entry, collection, contentLanguage.current),
|
|
194
|
-
url: `/admin/entries/${entry.id}`,
|
|
195
|
-
status: getEntryStatus(entry),
|
|
196
|
-
createdAt: entry.createdAt,
|
|
197
|
-
updatedAt: entry.updatedAt,
|
|
419
|
+
...mapEntryToRow(entry),
|
|
198
420
|
onRestore: () => handleRestore(entry.id),
|
|
199
421
|
onDelete: () => handleDelete(entry.id)
|
|
200
422
|
}))}
|
|
201
|
-
|
|
423
|
+
<div class="p-4">
|
|
202
424
|
<DataTable data={items} columns={archivedColumns} />
|
|
203
|
-
|
|
425
|
+
</div>
|
|
204
426
|
{/await}
|
|
205
427
|
</Tabs.Content>
|
|
206
428
|
</Tabs.Root>
|
|
429
|
+
|
|
430
|
+
<AlertDialog.Root bind:open={deleteDialogOpen}>
|
|
431
|
+
<AlertDialog.Content>
|
|
432
|
+
<AlertDialog.Title>{lang[interfaceLanguage.current].deleteConfirmTitle}</AlertDialog.Title>
|
|
433
|
+
<AlertDialog.Description>{lang[interfaceLanguage.current].deleteConfirmDescription}</AlertDialog.Description>
|
|
434
|
+
<AlertDialog.Footer>
|
|
435
|
+
<AlertDialog.Cancel>{lang[interfaceLanguage.current].cancel}</AlertDialog.Cancel>
|
|
436
|
+
<AlertDialog.Action onclick={confirmDelete}>{lang[interfaceLanguage.current].delete}</AlertDialog.Action>
|
|
437
|
+
</AlertDialog.Footer>
|
|
438
|
+
</AlertDialog.Content>
|
|
439
|
+
</AlertDialog.Root>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CollectionConfigWithType } from '../../../types/collections.js';
|
|
2
2
|
type Props = {
|
|
3
3
|
collection: CollectionConfigWithType;
|
|
4
|
+
onCreateEntry: () => void;
|
|
5
|
+
addLabel: string;
|
|
4
6
|
};
|
|
5
7
|
declare const CollectionEntries: import("svelte").Component<Props, {}, "">;
|
|
6
8
|
type CollectionEntries = ReturnType<typeof CollectionEntries>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SortingState } from '@tanstack/table-core';
|
|
2
|
+
export type ViewMode = 'list' | 'grid';
|
|
3
|
+
export type DateFormat = 'relative' | 'absolute';
|
|
4
|
+
export interface CollectionViewState {
|
|
5
|
+
viewMode: ViewMode;
|
|
6
|
+
dateFormat: DateFormat;
|
|
7
|
+
pageSize: number;
|
|
8
|
+
sorting: SortingState;
|
|
9
|
+
}
|
|
10
|
+
export declare function createCollectionViewState(collectionSlug: string): {
|
|
11
|
+
viewMode: ViewMode;
|
|
12
|
+
dateFormat: DateFormat;
|
|
13
|
+
pageSize: number;
|
|
14
|
+
sorting: SortingState;
|
|
15
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const DEFAULT_STATE = {
|
|
2
|
+
viewMode: 'list',
|
|
3
|
+
dateFormat: 'relative',
|
|
4
|
+
pageSize: 10,
|
|
5
|
+
sorting: []
|
|
6
|
+
};
|
|
7
|
+
function getStorageKey(collectionSlug) {
|
|
8
|
+
return `includio-collection-${collectionSlug}-view`;
|
|
9
|
+
}
|
|
10
|
+
function loadState(collectionSlug) {
|
|
11
|
+
if (typeof window === 'undefined')
|
|
12
|
+
return DEFAULT_STATE;
|
|
13
|
+
try {
|
|
14
|
+
const stored = localStorage.getItem(getStorageKey(collectionSlug));
|
|
15
|
+
if (stored) {
|
|
16
|
+
const parsed = JSON.parse(stored);
|
|
17
|
+
return { ...DEFAULT_STATE, ...parsed };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// ignore
|
|
22
|
+
}
|
|
23
|
+
return DEFAULT_STATE;
|
|
24
|
+
}
|
|
25
|
+
function saveState(collectionSlug, state) {
|
|
26
|
+
if (typeof window === 'undefined')
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(getStorageKey(collectionSlug), JSON.stringify(state));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// ignore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function createCollectionViewState(collectionSlug) {
|
|
36
|
+
let state = $state(loadState(collectionSlug));
|
|
37
|
+
$effect(() => {
|
|
38
|
+
saveState(collectionSlug, state);
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
get viewMode() {
|
|
42
|
+
return state.viewMode;
|
|
43
|
+
},
|
|
44
|
+
set viewMode(value) {
|
|
45
|
+
state = { ...state, viewMode: value };
|
|
46
|
+
},
|
|
47
|
+
get dateFormat() {
|
|
48
|
+
return state.dateFormat;
|
|
49
|
+
},
|
|
50
|
+
set dateFormat(value) {
|
|
51
|
+
state = { ...state, dateFormat: value };
|
|
52
|
+
},
|
|
53
|
+
get pageSize() {
|
|
54
|
+
return state.pageSize;
|
|
55
|
+
},
|
|
56
|
+
set pageSize(value) {
|
|
57
|
+
state = { ...state, pageSize: value };
|
|
58
|
+
},
|
|
59
|
+
get sorting() {
|
|
60
|
+
return state.sorting;
|
|
61
|
+
},
|
|
62
|
+
set sorting(value) {
|
|
63
|
+
state = { ...state, sorting: value };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|