includio-cms 0.0.68 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +181 -0
- package/ROADMAP.md +92 -0
- package/dist/admin/client/account/lang.d.ts +0 -5
- package/dist/admin/client/account/lang.js +2 -12
- package/dist/admin/client/account/preferences-section.svelte +27 -24
- package/dist/admin/client/account/profile-section.svelte +11 -15
- package/dist/admin/client/account/security-section.svelte +0 -11
- package/dist/admin/client/account/sessions-section.svelte +2 -1
- package/dist/admin/client/collection/collection-entries.svelte +168 -43
- package/dist/admin/client/collection/collection-view.svelte.d.ts +5 -0
- package/dist/admin/client/collection/collection-view.svelte.js +22 -5
- package/dist/admin/client/collection/collection.svelte +2 -2
- package/dist/admin/client/collection/data-table.svelte +15 -3
- package/dist/admin/client/collection/data-table.svelte.d.ts +3 -0
- package/dist/admin/client/entry/entry.svelte +11 -7
- package/dist/admin/client/entry/header/status-badge.svelte +6 -3
- package/dist/admin/client/entry/header/version-history-sheet.svelte +6 -3
- package/dist/admin/client/form/form-submissions.svelte +3 -26
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +2 -12
- package/dist/admin/components/dashboard/recent-activity.svelte +2 -12
- package/dist/admin/components/fields/array-field.svelte +126 -71
- package/dist/admin/components/fields/relation-field.svelte +6 -10
- package/dist/admin/components/layout/header-actions.svelte +4 -3
- package/dist/admin/components/layout/nav-search.svelte +43 -31
- package/dist/admin/components/media/media-library.svelte +17 -6
- package/dist/admin/remote/entry.remote.d.ts +10 -1
- package/dist/admin/remote/entry.remote.js +16 -4
- package/dist/admin/state/interface-language.svelte.d.ts +4 -7
- package/dist/admin/state/interface-language.svelte.js +19 -18
- package/dist/admin/utils/arrayMove.d.ts +5 -0
- package/dist/admin/utils/arrayMove.js +12 -0
- package/dist/admin/utils/formatDate.d.ts +1 -0
- package/dist/admin/utils/formatDate.js +5 -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/core/server/entries/operations/get.d.ts +2 -0
- package/dist/core/server/entries/operations/get.js +6 -0
- package/dist/db-postgres/index.js +26 -1
- package/dist/sveltekit/components/hybrid-context.d.ts +4 -0
- package/dist/sveltekit/components/hybrid-context.js +9 -0
- package/dist/sveltekit/components/hybrid-target.svelte +4 -1
- package/dist/sveltekit/components/image.svelte +5 -2
- package/dist/sveltekit/components/preview.svelte +3 -0
- package/dist/sveltekit/components/video.svelte +4 -1
- package/dist/sveltekit/index.d.ts +1 -0
- package/dist/sveltekit/index.js +1 -0
- package/dist/types/adapters/db.d.ts +3 -1
- package/dist/types/entries.d.ts +10 -2
- package/dist/types/languages.d.ts +2 -1
- package/dist/updates/0.0.69/index.d.ts +2 -0
- package/dist/updates/0.0.69/index.js +12 -0
- package/dist/updates/0.1.0/index.d.ts +2 -0
- package/dist/updates/0.1.0/index.js +25 -0
- package/dist/updates/index.js +3 -1
- package/package.json +7 -2
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
import RequiredLabel from './required-label.svelte';
|
|
31
31
|
import { cn } from '../../../utils.js';
|
|
32
32
|
import BlockPickerModal from './block-picker-modal.svelte';
|
|
33
|
+
import GripVertical from '@tabler/icons-svelte/icons/grip-vertical';
|
|
34
|
+
import { droppable, draggable, dndState } from '@thisux/sveltednd';
|
|
35
|
+
import { flip } from 'svelte/animate';
|
|
36
|
+
import { arrayMove } from '../../utils/arrayMove.js';
|
|
33
37
|
|
|
34
38
|
const contentLanguage = getContentLanguage();
|
|
35
39
|
const interfaceLanguage = useInterfaceLanguage();
|
|
@@ -119,6 +123,17 @@
|
|
|
119
123
|
});
|
|
120
124
|
}
|
|
121
125
|
|
|
126
|
+
let dropProcessing = false;
|
|
127
|
+
|
|
128
|
+
function moveItem(from: number, to: number) {
|
|
129
|
+
if (!$value || from === to) return;
|
|
130
|
+
$value = arrayMove($value, from, to);
|
|
131
|
+
|
|
132
|
+
tick().then(() => {
|
|
133
|
+
openAndCloseOthers(to);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
function removeItem(index: number) {
|
|
123
138
|
if (!$value) return;
|
|
124
139
|
|
|
@@ -208,80 +223,120 @@
|
|
|
208
223
|
<Accordion.Root type="multiple" class="w-full space-y-4" bind:value={accordionOpenState}>
|
|
209
224
|
{#if $value && $value.length > 0}
|
|
210
225
|
{#each $value as item, index (item._id ?? index)}
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
226
|
+
<div
|
|
227
|
+
use:droppable={{
|
|
228
|
+
container: index.toString(),
|
|
229
|
+
callbacks: {
|
|
230
|
+
onDrop: (state) => {
|
|
231
|
+
if (dropProcessing) return;
|
|
232
|
+
dropProcessing = true;
|
|
233
|
+
|
|
234
|
+
const dragIndex = parseInt(state.sourceContainer ?? '');
|
|
235
|
+
const dropIndex = parseInt(state.targetContainer ?? '');
|
|
236
|
+
if (!isNaN(dragIndex) && !isNaN(dropIndex)) {
|
|
237
|
+
moveItem(dragIndex, dropIndex);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// {#key index} destroys the old draggable before dragend fires,
|
|
241
|
+
// leaving isDragging stuck at true. Reset manually.
|
|
242
|
+
dndState.isDragging = false;
|
|
243
|
+
dndState.draggedItem = null;
|
|
244
|
+
dndState.sourceContainer = '';
|
|
245
|
+
dndState.targetContainer = null;
|
|
246
|
+
dndState.targetElement = null;
|
|
247
|
+
|
|
248
|
+
setTimeout(() => { dropProcessing = false; }, 50);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}}
|
|
252
|
+
animate:flip={{ duration: 200 }}
|
|
253
|
+
>
|
|
254
|
+
{#key index}
|
|
255
|
+
{#if $value[index].data && $value[index].slug}
|
|
256
|
+
{@const item = $value[index]}
|
|
257
|
+
{@const objectField = field.of.find((option) => option.slug === item.slug)}
|
|
258
|
+
|
|
259
|
+
{#if objectField}
|
|
260
|
+
<Accordion.Item value={index.toString()} class="border-0" data-depth={depth + 1}>
|
|
261
|
+
<Accordion.Trigger
|
|
262
|
+
class="items-center border px-4 text-base font-normal data-[state=open]:rounded-b-none dark:bg-slate-800/30 dark:hover:bg-slate-700/40 dark:border-white/[0.08]"
|
|
263
|
+
>
|
|
264
|
+
<div class="flex grow items-center justify-between gap-4">
|
|
265
|
+
<div class="flex items-center gap-4">
|
|
266
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
267
|
+
<div
|
|
268
|
+
use:draggable={{ container: index.toString(), dragData: { id: item._id } }}
|
|
269
|
+
class="cursor-grab text-muted-foreground hover:text-foreground"
|
|
270
|
+
onmousedown={(e) => e.stopPropagation()}
|
|
271
|
+
onclick={(e) => e.stopPropagation()}
|
|
248
272
|
>
|
|
249
|
-
|
|
250
|
-
</
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
</
|
|
254
|
-
</
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
<GripVertical class="h-4 w-4" />
|
|
274
|
+
</div>
|
|
275
|
+
<span>{index < 10 ? '0' : ''}{index + 1}</span>
|
|
276
|
+
<Badge variant="outline">{getLocalizedLabel(objectField.label, interfaceLanguage.current) || objectField.slug}</Badge>
|
|
277
|
+
<span>{getAccordionLabel($value[index])}</span>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<DropdownMenu.Root>
|
|
281
|
+
<DropdownMenu.Trigger
|
|
282
|
+
class="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
|
283
|
+
>
|
|
284
|
+
{#snippet child({ props })}
|
|
285
|
+
<Button variant="ghost" size="icon" {...props}>
|
|
286
|
+
<DotsVerticalIcon />
|
|
287
|
+
<span class="sr-only">Open menu</span>
|
|
288
|
+
</Button>
|
|
289
|
+
{/snippet}
|
|
290
|
+
</DropdownMenu.Trigger>
|
|
291
|
+
<DropdownMenu.Content align="end" class="w-32">
|
|
292
|
+
<DropdownMenu.Item onclick={() => duplicateItem(index)}>
|
|
293
|
+
Duplicate
|
|
294
|
+
</DropdownMenu.Item>
|
|
295
|
+
<DropdownMenu.Item onclick={() => moveItemUp(index)} disabled={index === 0}>
|
|
296
|
+
Move up
|
|
297
|
+
</DropdownMenu.Item>
|
|
298
|
+
<DropdownMenu.Item
|
|
299
|
+
onclick={() => moveItemDown(index)}
|
|
300
|
+
disabled={$value && index === $value.length - 1}
|
|
301
|
+
>
|
|
302
|
+
Move down
|
|
303
|
+
</DropdownMenu.Item>
|
|
304
|
+
<DropdownMenu.Item variant="destructive" onclick={() => removeItem(index)}>
|
|
305
|
+
Delete
|
|
306
|
+
</DropdownMenu.Item>
|
|
307
|
+
</DropdownMenu.Content>
|
|
308
|
+
</DropdownMenu.Root>
|
|
309
|
+
</div>
|
|
310
|
+
</Accordion.Trigger>
|
|
311
|
+
<Accordion.Content
|
|
312
|
+
class="space-y-4 rounded-b-md border border-t-0 dark:bg-slate-900/30 dark:shadow-[inset_0_2px_4px_rgb(0_0_0/0.1)] dark:border-white/[0.08]"
|
|
313
|
+
style="padding: {Math.max(4, 16 - depth * 3)}px;"
|
|
314
|
+
>
|
|
315
|
+
{@const itemPath = joinPath(path, index)}
|
|
316
|
+
<div data-field-path={itemPath}>
|
|
317
|
+
<FieldRenderer
|
|
318
|
+
objectFieldType="inline"
|
|
319
|
+
field={objectField}
|
|
320
|
+
form={form as SuperForm<Record<string, unknown>>}
|
|
321
|
+
path={itemPath as FormPathLeaves<T, ObjectFieldData>}
|
|
322
|
+
{focusedPath}
|
|
323
|
+
{flashingPath}
|
|
324
|
+
depth={depth + 1}
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
</Accordion.Content>
|
|
328
|
+
</Accordion.Item>
|
|
329
|
+
{:else}
|
|
330
|
+
<p class="text-red-500">
|
|
331
|
+
Invalid field configuration. Unknown slug:
|
|
332
|
+
{$value[index].slug}
|
|
333
|
+
</p>
|
|
334
|
+
{/if}
|
|
276
335
|
{:else}
|
|
277
|
-
<p class="text-red-500">
|
|
278
|
-
Invalid field configuration. Unknown slug:
|
|
279
|
-
{$value[index].slug}
|
|
280
|
-
</p>
|
|
336
|
+
<p class="text-red-500">Invalid field configuration. Index: {index}</p>
|
|
281
337
|
{/if}
|
|
282
|
-
{
|
|
283
|
-
|
|
284
|
-
{/if}
|
|
338
|
+
{/key}
|
|
339
|
+
</div>
|
|
285
340
|
{/each}
|
|
286
341
|
{/if}
|
|
287
342
|
</Accordion.Root>
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
import type { InterfaceLanguage } from '../../../types/languages.js';
|
|
28
28
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
29
29
|
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
30
|
-
import { droppable } from '@thisux/sveltednd';
|
|
31
|
-
import {
|
|
30
|
+
import { droppable, draggable } from '@thisux/sveltednd';
|
|
31
|
+
import { arrayMove } from '../../utils/arrayMove.js';
|
|
32
32
|
import { flip } from 'svelte/animate';
|
|
33
33
|
import { fade } from 'svelte/transition';
|
|
34
34
|
|
|
@@ -195,15 +195,11 @@
|
|
|
195
195
|
container: index.toString(),
|
|
196
196
|
callbacks: {
|
|
197
197
|
onDrop: (state) => {
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const dropIndex = parseInt(targetContainer ?? '0');
|
|
198
|
+
const dragIndex = parseInt(state.sourceContainer ?? '');
|
|
199
|
+
const dropIndex = parseInt(state.targetContainer ?? '');
|
|
201
200
|
|
|
202
|
-
if (dragIndex
|
|
203
|
-
|
|
204
|
-
const [moved] = arr.splice(dragIndex, 1);
|
|
205
|
-
arr.splice(dropIndex, 0, moved);
|
|
206
|
-
$value = [...arr];
|
|
201
|
+
if (!isNaN(dragIndex) && !isNaN(dropIndex)) {
|
|
202
|
+
$value = [...arrayMove(getArrayValue(), dragIndex, dropIndex)];
|
|
207
203
|
}
|
|
208
204
|
}
|
|
209
205
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { toggleMode } from 'mode-watcher';
|
|
3
3
|
import Sun from '@tabler/icons-svelte/icons/sun';
|
|
4
4
|
import Moon from '@tabler/icons-svelte/icons/moon';
|
|
5
|
-
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
5
|
+
import { useInterfaceLanguage, locales } from '../../state/interface-language.svelte.js';
|
|
6
6
|
|
|
7
7
|
const interfaceLanguage = useInterfaceLanguage();
|
|
8
8
|
|
|
9
9
|
function toggleLanguage() {
|
|
10
|
-
|
|
10
|
+
const idx = locales.indexOf(interfaceLanguage.current);
|
|
11
|
+
interfaceLanguage.current = locales[(idx + 1) % locales.length];
|
|
11
12
|
}
|
|
12
13
|
</script>
|
|
13
14
|
|
|
@@ -23,6 +24,6 @@
|
|
|
23
24
|
onclick={toggleLanguage}
|
|
24
25
|
class="text-foreground/70 hover:text-foreground flex size-8 items-center justify-center rounded-md border border-black/10 text-xs font-semibold transition-colors hover:bg-black/5 dark:border-white/10 dark:hover:bg-white/10"
|
|
25
26
|
>
|
|
26
|
-
{interfaceLanguage.current
|
|
27
|
+
{interfaceLanguage.current.toUpperCase()}
|
|
27
28
|
</button>
|
|
28
29
|
</div>
|
|
@@ -28,34 +28,49 @@
|
|
|
28
28
|
open = false;
|
|
29
29
|
goto(url);
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
async function getData() {
|
|
33
|
+
const [singles, collections, forms] = await Promise.all([
|
|
34
|
+
remotes.getSingles(),
|
|
35
|
+
remotes.getCollections(),
|
|
36
|
+
remotes.getForms()
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
return { singles, collections, forms };
|
|
40
|
+
}
|
|
31
41
|
</script>
|
|
32
42
|
|
|
33
43
|
<svelte:window {onkeydown} />
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
45
|
+
{#await getData() then { singles, collections, forms }}
|
|
46
|
+
<button
|
|
47
|
+
onclick={() => (open = true)}
|
|
48
|
+
class="text-muted-foreground mx-2 flex w-[calc(100%-1rem)] items-center gap-2 rounded-md border px-2.5 py-1.5 text-sm transition-colors hover:bg-black/5 dark:border-white/10 dark:hover:bg-white/10"
|
|
49
|
+
>
|
|
50
|
+
<SearchIcon class="size-4 shrink-0" />
|
|
51
|
+
<span class="flex-1 truncate text-left"
|
|
52
|
+
>{sidebarLang[interfaceLanguage.current].search.placeholder}</span
|
|
53
|
+
>
|
|
54
|
+
<kbd
|
|
55
|
+
class="bg-muted text-muted-foreground shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium"
|
|
56
|
+
>⌘K</kbd
|
|
57
|
+
>
|
|
58
|
+
</button>
|
|
43
59
|
|
|
44
|
-
<Command.Dialog bind:open title={sidebarLang[interfaceLanguage.current].search.placeholder}>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
<Command.Dialog bind:open title={sidebarLang[interfaceLanguage.current].search.placeholder}>
|
|
61
|
+
<Command.Input placeholder={sidebarLang[interfaceLanguage.current].search.placeholder} />
|
|
62
|
+
<Command.List>
|
|
63
|
+
<Command.Empty>{sidebarLang[interfaceLanguage.current].search.noResults}</Command.Empty>
|
|
48
64
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{#await remotes.getSingles() then singles}
|
|
65
|
+
<Command.Group heading={sidebarLang[interfaceLanguage.current].main.platform}>
|
|
66
|
+
<Command.Item onSelect={() => navigate('/admin')}>
|
|
67
|
+
<DashboardIcon class="mr-2 size-4" />
|
|
68
|
+
{sidebarLang[interfaceLanguage.current].main.dashboard}
|
|
69
|
+
</Command.Item>
|
|
70
|
+
<Command.Item onSelect={() => navigate('/admin/media')}>
|
|
71
|
+
<CameraIcon class="mr-2 size-4" />
|
|
72
|
+
{sidebarLang[interfaceLanguage.current].main.media}
|
|
73
|
+
</Command.Item>
|
|
59
74
|
{#each singles as item (item.slug)}
|
|
60
75
|
{@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
|
|
61
76
|
<Command.Item onSelect={() => navigate(`/admin/entries/${item.slug}`)}>
|
|
@@ -67,14 +82,13 @@
|
|
|
67
82
|
{name}
|
|
68
83
|
</Command.Item>
|
|
69
84
|
{/each}
|
|
70
|
-
|
|
71
|
-
</Command.Group>
|
|
85
|
+
</Command.Group>
|
|
72
86
|
|
|
73
|
-
{#await remotes.getCollections() then collections}
|
|
74
87
|
{#if collections.length > 0}
|
|
75
88
|
<Command.Group heading={sidebarLang[interfaceLanguage.current].collections.title}>
|
|
76
89
|
{#each collections as item (item.slug)}
|
|
77
|
-
{@const name =
|
|
90
|
+
{@const name =
|
|
91
|
+
getLocalizedLabel(item.labels?.plural, interfaceLanguage.current) ?? item.slug}
|
|
78
92
|
<Command.Item onSelect={() => navigate(`/admin/collections/${item.slug}`)}>
|
|
79
93
|
{#if item.sidebarIcon}
|
|
80
94
|
<item.sidebarIcon class="mr-2 size-4" />
|
|
@@ -86,9 +100,7 @@
|
|
|
86
100
|
{/each}
|
|
87
101
|
</Command.Group>
|
|
88
102
|
{/if}
|
|
89
|
-
{/await}
|
|
90
103
|
|
|
91
|
-
{#await remotes.getForms() then forms}
|
|
92
104
|
{#if forms.length > 0}
|
|
93
105
|
<Command.Group heading={sidebarLang[interfaceLanguage.current].forms.title}>
|
|
94
106
|
{#each forms as item (item.slug)}
|
|
@@ -100,6 +112,6 @@
|
|
|
100
112
|
{/each}
|
|
101
113
|
</Command.Group>
|
|
102
114
|
{/if}
|
|
103
|
-
|
|
104
|
-
</Command.
|
|
105
|
-
|
|
115
|
+
</Command.List>
|
|
116
|
+
</Command.Dialog>
|
|
117
|
+
{/await}
|
|
@@ -65,6 +65,10 @@
|
|
|
65
65
|
})
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
+
const allFilesQuery = $derived(
|
|
69
|
+
remotes.getMediaFiles({ data: { mimeTypes: accept?.split(',') } })
|
|
70
|
+
);
|
|
71
|
+
|
|
68
72
|
let tagsQuery = $derived(remotes.getMediaTags());
|
|
69
73
|
|
|
70
74
|
// Multi-select with shift/ctrl
|
|
@@ -99,6 +103,7 @@
|
|
|
99
103
|
await remotes.deleteMediaFile(currentFile.id);
|
|
100
104
|
toast.success(lang[interfaceLanguage.current].fileDeletedToast);
|
|
101
105
|
filesQuery.refresh();
|
|
106
|
+
allFilesQuery.refresh();
|
|
102
107
|
currentFile = null;
|
|
103
108
|
}
|
|
104
109
|
}
|
|
@@ -109,6 +114,7 @@
|
|
|
109
114
|
const tags = (await tagsQuery) as MediaTag[];
|
|
110
115
|
currentFile = { ...currentFile, tags: tags.filter((t) => tagIds.includes(t.id)) };
|
|
111
116
|
await filesQuery.refresh();
|
|
117
|
+
await allFilesQuery.refresh();
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
@@ -133,12 +139,14 @@
|
|
|
133
139
|
await remotes.updateMediaTag({ id, name, color });
|
|
134
140
|
await tagsQuery.refresh();
|
|
135
141
|
await filesQuery.refresh();
|
|
142
|
+
await allFilesQuery.refresh();
|
|
136
143
|
}
|
|
137
144
|
|
|
138
145
|
async function handleDeleteTag(id: string) {
|
|
139
146
|
await remotes.deleteMediaTag(id);
|
|
140
147
|
await tagsQuery.refresh();
|
|
141
148
|
await filesQuery.refresh();
|
|
149
|
+
await allFilesQuery.refresh();
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
// Bulk operations
|
|
@@ -146,6 +154,7 @@
|
|
|
146
154
|
const fileIds = selectedFileIds;
|
|
147
155
|
await remotes.bulkSetMediaFileTags({ fileIds, tagIds });
|
|
148
156
|
await filesQuery.refresh();
|
|
157
|
+
await allFilesQuery.refresh();
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
async function handleBulkDelete() {
|
|
@@ -154,6 +163,7 @@
|
|
|
154
163
|
selectedFileIds = [];
|
|
155
164
|
currentFile = null;
|
|
156
165
|
await filesQuery.refresh();
|
|
166
|
+
await allFilesQuery.refresh();
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
onMount(() => {
|
|
@@ -168,10 +178,10 @@
|
|
|
168
178
|
<div class="flex h-full" bind:this={dropZoneRef}>
|
|
169
179
|
<!-- Tag sidebar -->
|
|
170
180
|
<div class="w-48 shrink-0 border-r p-3">
|
|
171
|
-
{#await tagsQuery then tags}
|
|
181
|
+
{#await Promise.all([tagsQuery, allFilesQuery]) then [tags, allFiles]}
|
|
172
182
|
<TagSidebar
|
|
173
183
|
{tags}
|
|
174
|
-
files={
|
|
184
|
+
files={allFiles}
|
|
175
185
|
activeFilter={activeTagFilter}
|
|
176
186
|
onFilterChange={(f) => (activeTagFilter = f)}
|
|
177
187
|
onCreateTag={handleCreateTag}
|
|
@@ -187,7 +197,7 @@
|
|
|
187
197
|
<MediaSearch bind:value={searchQuery} />
|
|
188
198
|
<MediaSort />
|
|
189
199
|
<FileUpload
|
|
190
|
-
onUpload={() => filesQuery.refresh()}
|
|
200
|
+
onUpload={() => { filesQuery.refresh(); allFilesQuery.refresh(); }}
|
|
191
201
|
{accept}
|
|
192
202
|
bind:dropZoneRef
|
|
193
203
|
/>
|
|
@@ -234,6 +244,7 @@
|
|
|
234
244
|
onReplace={(updated) => {
|
|
235
245
|
currentFile = updated;
|
|
236
246
|
filesQuery.refresh();
|
|
247
|
+
allFilesQuery.refresh();
|
|
237
248
|
}}
|
|
238
249
|
{onTagUpdate}
|
|
239
250
|
{onNameUpdate}
|
|
@@ -256,15 +267,15 @@
|
|
|
256
267
|
|
|
257
268
|
<!-- Bulk action bar -->
|
|
258
269
|
{#if selectedFileIds.length > 0}
|
|
259
|
-
{#await tagsQuery then tags}
|
|
270
|
+
{#await Promise.all([tagsQuery, allFilesQuery]) then [tags, allFiles]}
|
|
260
271
|
<BulkActionBar
|
|
261
272
|
selectedCount={selectedFileIds.length}
|
|
262
|
-
totalCount={
|
|
273
|
+
totalCount={allFiles.length}
|
|
263
274
|
{tags}
|
|
264
275
|
onBulkTag={handleBulkTag}
|
|
265
276
|
onBulkDelete={handleBulkDelete}
|
|
266
277
|
onClear={() => (selectedFileIds = [])}
|
|
267
|
-
onSelectAll={() => {}}
|
|
278
|
+
onSelectAll={() => { selectedFileIds = allFiles.map((f) => f.id); }}
|
|
268
279
|
onCreateTag={handleCreateTag}
|
|
269
280
|
onUpdateTagColor={async (id, color) => {
|
|
270
281
|
const tag = tags.find((t) => t.id === id);
|
|
@@ -3,7 +3,16 @@ export declare const getRawEntries: import("@sveltejs/kit").RemoteQueryFunction<
|
|
|
3
3
|
ids?: string[] | undefined;
|
|
4
4
|
slug?: string | undefined;
|
|
5
5
|
onlyArchived?: boolean | undefined;
|
|
6
|
-
|
|
6
|
+
limit?: number | undefined;
|
|
7
|
+
offset?: number | undefined;
|
|
8
|
+
orderBy?: {
|
|
9
|
+
column: "createdAt" | "updatedAt";
|
|
10
|
+
direction: "asc" | "desc";
|
|
11
|
+
} | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
entries: RawEntry[];
|
|
14
|
+
total: number;
|
|
15
|
+
}>;
|
|
7
16
|
export declare const getEntries: import("@sveltejs/kit").RemoteQueryFunction<{
|
|
8
17
|
ids?: string[] | undefined;
|
|
9
18
|
dataValues?: Record<string, unknown> | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { command, query } from '$app/server';
|
|
2
2
|
import { createEntry as createEntryOperation, createEntrySchema, createEntryVersion } from '../../core/server/entries/operations/create.js';
|
|
3
|
-
import { getRawEntries as getRawEntriesOperation, getRawEntry as getRawEntryOperation, getRawEntryOrThrow, getDbEntry, getDbEntryOrThrow, getEntries as getEntriesOperation, getEntry as getEntryOperation, getEntryVersion as getEntryVersionOperation } from '../../core/server/entries/operations/get.js';
|
|
3
|
+
import { getRawEntries as getRawEntriesOperation, countRawEntries as countRawEntriesOperation, getRawEntry as getRawEntryOperation, getRawEntryOrThrow, getDbEntry, getDbEntryOrThrow, getEntries as getEntriesOperation, getEntry as getEntryOperation, getEntryVersion as getEntryVersionOperation } from '../../core/server/entries/operations/get.js';
|
|
4
4
|
import { getCMS } from '../../core/cms.js';
|
|
5
5
|
import { pruneOldDraftVersions, unpublishEntry, updateEntry, updateEntrySchema, updateEntryVersionCommandTypes } from '../../core/server/entries/operations/update.js';
|
|
6
6
|
import z from 'zod';
|
|
@@ -9,10 +9,22 @@ import { entryStatuses } from '../../types/entries.js';
|
|
|
9
9
|
export const getRawEntries = query(z.object({
|
|
10
10
|
ids: z.array(z.string().uuid()).optional(),
|
|
11
11
|
slug: z.string().optional(),
|
|
12
|
-
onlyArchived: z.boolean().optional()
|
|
12
|
+
onlyArchived: z.boolean().optional(),
|
|
13
|
+
limit: z.number().int().positive().optional(),
|
|
14
|
+
offset: z.number().int().nonnegative().optional(),
|
|
15
|
+
orderBy: z
|
|
16
|
+
.object({
|
|
17
|
+
column: z.enum(['createdAt', 'updatedAt']),
|
|
18
|
+
direction: z.enum(['asc', 'desc'])
|
|
19
|
+
})
|
|
20
|
+
.optional()
|
|
13
21
|
}), async (input) => {
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
const { limit, offset, orderBy, ...filterOptions } = input;
|
|
23
|
+
const [entries, total] = await Promise.all([
|
|
24
|
+
getRawEntriesOperation(input),
|
|
25
|
+
countRawEntriesOperation(filterOptions)
|
|
26
|
+
]);
|
|
27
|
+
return { entries, total };
|
|
16
28
|
});
|
|
17
29
|
export const getEntries = query(z.object({
|
|
18
30
|
ids: z.array(z.string().uuid()).optional(),
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { InterfaceLanguage as InterfaceLanguageType } from '../../types/languages.js';
|
|
2
|
-
export declare
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
set current(value: InterfaceLanguageType);
|
|
7
|
-
}
|
|
8
|
-
export declare function useInterfaceLanguage(): InterfaceLanguage;
|
|
2
|
+
export declare function useInterfaceLanguage(): {
|
|
3
|
+
current: InterfaceLanguageType;
|
|
4
|
+
};
|
|
5
|
+
export { locales } from '../../paraglide/runtime.js';
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { PersistedState } from 'runed';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { locales, baseLocale } from '../../paraglide/runtime.js';
|
|
3
|
+
function detectDefaultLocale() {
|
|
4
|
+
if (typeof navigator === 'undefined')
|
|
5
|
+
return baseLocale;
|
|
6
|
+
const langs = navigator.languages ?? [navigator.language];
|
|
7
|
+
for (const lang of langs) {
|
|
8
|
+
const base = lang.split('-')[0].toLowerCase();
|
|
9
|
+
if (locales.includes(base))
|
|
10
|
+
return base;
|
|
9
11
|
}
|
|
12
|
+
return baseLocale;
|
|
13
|
+
}
|
|
14
|
+
const state = new PersistedState('interface-language', detectDefaultLocale());
|
|
15
|
+
const interfaceLanguage = {
|
|
10
16
|
get current() {
|
|
11
|
-
return
|
|
12
|
-
}
|
|
17
|
+
return state.current;
|
|
18
|
+
},
|
|
13
19
|
set current(value) {
|
|
14
|
-
|
|
20
|
+
state.current = value;
|
|
15
21
|
}
|
|
16
|
-
}
|
|
22
|
+
};
|
|
17
23
|
export function useInterfaceLanguage() {
|
|
18
|
-
|
|
19
|
-
return getInterfaceLanguage();
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
setInterfaceLanguage(new InterfaceLanguage());
|
|
23
|
-
return getInterfaceLanguage();
|
|
24
|
-
}
|
|
24
|
+
return interfaceLanguage;
|
|
25
25
|
}
|
|
26
|
+
export { locales } from '../../paraglide/runtime.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move an element from one index to another via splice, returning a new array.
|
|
3
|
+
* Returns the original array reference (unchanged) when `from === to`.
|
|
4
|
+
*/
|
|
5
|
+
export function arrayMove(arr, from, to) {
|
|
6
|
+
if (from === to)
|
|
7
|
+
return arr;
|
|
8
|
+
const copy = [...arr];
|
|
9
|
+
const [moved] = copy.splice(from, 1);
|
|
10
|
+
copy.splice(to, 0, moved);
|
|
11
|
+
return copy;
|
|
12
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { InterfaceLanguage } from '../../types/languages.js';
|
|
2
|
+
export declare function toLocaleCode(lang: string): string;
|
|
2
3
|
export declare function formatRelativeDate(date: Date | string, lang?: InterfaceLanguage): string;
|
|
3
4
|
export declare function formatAbsoluteDate(date: Date | string, lang?: InterfaceLanguage): string;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export function toLocaleCode(lang) {
|
|
2
|
+
const map = { pl: 'pl-PL', en: 'en-US' };
|
|
3
|
+
return map[lang] ?? lang;
|
|
4
|
+
}
|
|
1
5
|
const units = [
|
|
2
6
|
{ unit: 'year', ms: 365 * 24 * 60 * 60 * 1000 },
|
|
3
7
|
{ unit: 'month', ms: 30 * 24 * 60 * 60 * 1000 },
|
|
@@ -84,7 +88,7 @@ export function formatRelativeDate(date, lang = 'en') {
|
|
|
84
88
|
}
|
|
85
89
|
export function formatAbsoluteDate(date, lang = 'en') {
|
|
86
90
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
87
|
-
return d.toLocaleString(lang
|
|
91
|
+
return d.toLocaleString(toLocaleCode(lang), {
|
|
88
92
|
year: 'numeric',
|
|
89
93
|
month: 'short',
|
|
90
94
|
day: 'numeric',
|