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.
Files changed (55) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/ROADMAP.md +92 -0
  3. package/dist/admin/client/account/lang.d.ts +0 -5
  4. package/dist/admin/client/account/lang.js +2 -12
  5. package/dist/admin/client/account/preferences-section.svelte +27 -24
  6. package/dist/admin/client/account/profile-section.svelte +11 -15
  7. package/dist/admin/client/account/security-section.svelte +0 -11
  8. package/dist/admin/client/account/sessions-section.svelte +2 -1
  9. package/dist/admin/client/collection/collection-entries.svelte +168 -43
  10. package/dist/admin/client/collection/collection-view.svelte.d.ts +5 -0
  11. package/dist/admin/client/collection/collection-view.svelte.js +22 -5
  12. package/dist/admin/client/collection/collection.svelte +2 -2
  13. package/dist/admin/client/collection/data-table.svelte +15 -3
  14. package/dist/admin/client/collection/data-table.svelte.d.ts +3 -0
  15. package/dist/admin/client/entry/entry.svelte +11 -7
  16. package/dist/admin/client/entry/header/status-badge.svelte +6 -3
  17. package/dist/admin/client/entry/header/version-history-sheet.svelte +6 -3
  18. package/dist/admin/client/form/form-submissions.svelte +3 -26
  19. package/dist/admin/components/dashboard/form-submissions-widget.svelte +2 -12
  20. package/dist/admin/components/dashboard/recent-activity.svelte +2 -12
  21. package/dist/admin/components/fields/array-field.svelte +126 -71
  22. package/dist/admin/components/fields/relation-field.svelte +6 -10
  23. package/dist/admin/components/layout/header-actions.svelte +4 -3
  24. package/dist/admin/components/layout/nav-search.svelte +43 -31
  25. package/dist/admin/components/media/media-library.svelte +17 -6
  26. package/dist/admin/remote/entry.remote.d.ts +10 -1
  27. package/dist/admin/remote/entry.remote.js +16 -4
  28. package/dist/admin/state/interface-language.svelte.d.ts +4 -7
  29. package/dist/admin/state/interface-language.svelte.js +19 -18
  30. package/dist/admin/utils/arrayMove.d.ts +5 -0
  31. package/dist/admin/utils/arrayMove.js +12 -0
  32. package/dist/admin/utils/formatDate.d.ts +1 -0
  33. package/dist/admin/utils/formatDate.js +5 -1
  34. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  35. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  36. package/dist/core/server/entries/operations/get.d.ts +2 -0
  37. package/dist/core/server/entries/operations/get.js +6 -0
  38. package/dist/db-postgres/index.js +26 -1
  39. package/dist/sveltekit/components/hybrid-context.d.ts +4 -0
  40. package/dist/sveltekit/components/hybrid-context.js +9 -0
  41. package/dist/sveltekit/components/hybrid-target.svelte +4 -1
  42. package/dist/sveltekit/components/image.svelte +5 -2
  43. package/dist/sveltekit/components/preview.svelte +3 -0
  44. package/dist/sveltekit/components/video.svelte +4 -1
  45. package/dist/sveltekit/index.d.ts +1 -0
  46. package/dist/sveltekit/index.js +1 -0
  47. package/dist/types/adapters/db.d.ts +3 -1
  48. package/dist/types/entries.d.ts +10 -2
  49. package/dist/types/languages.d.ts +2 -1
  50. package/dist/updates/0.0.69/index.d.ts +2 -0
  51. package/dist/updates/0.0.69/index.js +12 -0
  52. package/dist/updates/0.1.0/index.d.ts +2 -0
  53. package/dist/updates/0.1.0/index.js +25 -0
  54. package/dist/updates/index.js +3 -1
  55. package/package.json +7 -2
@@ -117,6 +117,46 @@
117
117
  let deleteDialogOpen = $state(false);
118
118
  let pendingDeleteId = $state<string | null>(null);
119
119
 
120
+ // Determine if we need fetch-all fallback (search active or sorting by name)
121
+ const isSortingByName = $derived(
122
+ viewState.sorting.length > 0 && viewState.sorting[0].id === 'name'
123
+ );
124
+ const useServerPagination = $derived(!searchQuery && !isSortingByName);
125
+
126
+ // Build orderBy from sorting state for server-side mode
127
+ const serverOrderBy = $derived.by(() => {
128
+ if (!viewState.sorting.length) return undefined;
129
+ const { id, desc } = viewState.sorting[0];
130
+ if (id === 'createdAt' || id === 'updatedAt') {
131
+ return { column: id as 'createdAt' | 'updatedAt', direction: (desc ? 'desc' : 'asc') as 'asc' | 'desc' };
132
+ }
133
+ return undefined;
134
+ });
135
+
136
+ // Server-side query params
137
+ const activeQueryParams = $derived(
138
+ useServerPagination
139
+ ? {
140
+ slug: collection.slug,
141
+ limit: viewState.pageSize,
142
+ offset: viewState.pageIndex * viewState.pageSize,
143
+ orderBy: serverOrderBy
144
+ }
145
+ : { slug: collection.slug }
146
+ );
147
+
148
+ const archivedQueryParams = $derived(
149
+ useServerPagination
150
+ ? {
151
+ slug: collection.slug,
152
+ onlyArchived: true as const,
153
+ limit: viewState.pageSize,
154
+ offset: viewState.pageIndex * viewState.pageSize,
155
+ orderBy: serverOrderBy
156
+ }
157
+ : { slug: collection.slug, onlyArchived: true as const }
158
+ );
159
+
120
160
  const columns: ColumnDef<CollectionDataTableRow>[] = $derived([
121
161
  {
122
162
  id: 'select',
@@ -232,13 +272,15 @@
232
272
  }
233
273
  ]);
234
274
 
235
- let activeTab = $state('active');
275
+ function refreshQueries() {
276
+ remotes.getRawEntries(activeQueryParams).refresh();
277
+ remotes.getRawEntries(archivedQueryParams).refresh();
278
+ }
236
279
 
237
280
  async function handleRestore(id: string) {
238
281
  await remotes.unarchiveEntryCommand(id);
239
282
  toast.success(lang[interfaceLanguage.current].entryRestored);
240
- remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }).refresh();
241
- remotes.getRawEntries({ slug: collection.slug }).refresh();
283
+ refreshQueries();
242
284
  }
243
285
 
244
286
  function handleDelete(id: string) {
@@ -250,7 +292,7 @@
250
292
  if (!pendingDeleteId) return;
251
293
  await remotes.deleteEntryCommand(pendingDeleteId);
252
294
  toast.success(lang[interfaceLanguage.current].entryDeleted);
253
- remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }).refresh();
295
+ refreshQueries();
254
296
  deleteDialogOpen = false;
255
297
  pendingDeleteId = null;
256
298
  }
@@ -319,12 +361,18 @@
319
361
  }
320
362
  toast.success(lang[interfaceLanguage.current].entriesArchived);
321
363
  rowSelection = {};
322
- remotes.getRawEntries({ slug: collection.slug }).refresh();
323
- remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }).refresh();
364
+ refreshQueries();
324
365
  }
366
+
325
367
  </script>
326
368
 
327
- <Tabs.Root bind:value={activeTab} class="w-full">
369
+ <Tabs.Root
370
+ value={viewState.activeTab}
371
+ onValueChange={(v) => {
372
+ if (v === 'active' || v === 'archived') viewState.activeTab = v;
373
+ }}
374
+ class="w-full"
375
+ >
328
376
  <div class="flex items-center justify-between border-b border-slate-200/50 px-4 py-3 dark:border-white/10">
329
377
  <Tabs.List class="h-auto gap-1 bg-transparent p-0">
330
378
  <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>
@@ -336,57 +384,94 @@
336
384
  </div>
337
385
 
338
386
  <Tabs.Content value="active" class="mt-0">
339
- {#await remotes.getRawEntries({ slug: collection.slug }) then entries}
340
- {@const items = entries
341
- .filter((entry) => entry.archivedAt === null)
342
- .map(mapEntryToRow)}
343
- {@const filteredItems = searchQuery
344
- ? items.filter((item) => item.searchText.includes(searchQuery.toLowerCase()))
345
- : items}
346
- {@const totalItems = filteredItems.length}
387
+ {#await remotes.getRawEntries(activeQueryParams) then result}
388
+ {@const allRows = result.entries.map(mapEntryToRow)}
389
+ {@const items = useServerPagination
390
+ ? allRows
391
+ : (() => {
392
+ let filtered = searchQuery
393
+ ? allRows.filter((item) => item.searchText.includes(searchQuery.toLowerCase()))
394
+ : allRows;
395
+ return sortItems(filtered, viewState.sorting);
396
+ })()}
397
+ {@const totalItems = useServerPagination ? result.total : items.length}
347
398
  {@const pageCount = Math.ceil(totalItems / viewState.pageSize)}
348
399
 
349
400
  <TableToolbar
350
401
  {searchQuery}
351
- onSearchChange={(q) => (searchQuery = q)}
402
+ onSearchChange={(q) => {
403
+ searchQuery = q;
404
+ viewState.pageIndex = 0;
405
+ }}
352
406
  viewMode={viewState.viewMode}
353
407
  onViewModeChange={(m) => (viewState.viewMode = m)}
354
408
  dateFormat={viewState.dateFormat}
355
409
  onDateFormatChange={(f) => (viewState.dateFormat = f)}
356
410
  {selectedCount}
357
- onBulkArchive={() => handleBulkArchive(sortItems(filteredItems, viewState.sorting))}
411
+ onBulkArchive={() => handleBulkArchive(items)}
358
412
  />
359
413
 
360
414
  {#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
- />
415
+ {#if useServerPagination}
416
+ <DataTable
417
+ data={items}
418
+ {columns}
419
+ enableSorting
420
+ enableFiltering
421
+ enableSelection
422
+ enablePagination
423
+ manualPagination={true}
424
+ pageCount={pageCount}
425
+ rowCount={totalItems}
426
+ sorting={viewState.sorting}
427
+ onSortingChange={(s) => (viewState.sorting = s)}
428
+ {rowSelection}
429
+ onRowSelectionChange={(s) => (rowSelection = s)}
430
+ pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
431
+ onPaginationChange={(p) => {
432
+ viewState.pageIndex = p.pageIndex;
433
+ }}
434
+ tableRef={(t) => (tableInstance = t)}
435
+ />
436
+ {:else}
437
+ <DataTable
438
+ data={items}
439
+ {columns}
440
+ enableSorting
441
+ enableFiltering
442
+ enableSelection
443
+ enablePagination
444
+ sorting={viewState.sorting}
445
+ onSortingChange={(s) => (viewState.sorting = s)}
446
+ {rowSelection}
447
+ onRowSelectionChange={(s) => (rowSelection = s)}
448
+ pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
449
+ onPaginationChange={(p) => {
450
+ viewState.pageIndex = p.pageIndex;
451
+ }}
452
+ tableRef={(t) => (tableInstance = t)}
453
+ />
454
+ {/if}
375
455
 
376
456
  <TablePagination
377
- pageIndex={tableInstance?.getState().pagination.pageIndex ?? 0}
457
+ pageIndex={viewState.pageIndex}
378
458
  pageSize={viewState.pageSize}
379
459
  {pageCount}
380
460
  {totalItems}
381
- onPageChange={(p) => tableInstance?.setPageIndex(p)}
461
+ onPageChange={(p) => (viewState.pageIndex = p)}
382
462
  onPageSizeChange={(s) => {
383
463
  viewState.pageSize = s;
384
- tableInstance?.setPageSize(s);
385
464
  }}
386
465
  />
387
466
  {:else}
467
+ {@const displayItems = useServerPagination
468
+ ? items
469
+ : items.slice(
470
+ viewState.pageIndex * viewState.pageSize,
471
+ (viewState.pageIndex + 1) * viewState.pageSize
472
+ )}
388
473
  <GridView
389
- items={sortItems(filteredItems, viewState.sorting).map((item) => ({
474
+ items={displayItems.map((item) => ({
390
475
  id: item.id,
391
476
  name: item.name,
392
477
  status: item.status,
@@ -396,12 +481,11 @@
396
481
  }))}
397
482
  dateFormat={viewState.dateFormat}
398
483
  enableSelection
399
- selectedIds={new Set(selectedIndices.map((idx) => sortItems(filteredItems, viewState.sorting)[idx]?.id).filter(Boolean))}
484
+ selectedIds={new Set(selectedIndices.map((idx) => items[idx]?.id).filter(Boolean))}
400
485
  onSelectionChange={(ids) => {
401
- const sorted = sortItems(filteredItems, viewState.sorting);
402
486
  const newSelection: RowSelectionState = {};
403
487
  for (const id of ids) {
404
- const idx = sorted.findIndex((i) => i.id === id);
488
+ const idx = items.findIndex((i) => i.id === id);
405
489
  if (idx !== -1) {
406
490
  newSelection[idx] = true;
407
491
  }
@@ -409,20 +493,61 @@
409
493
  rowSelection = newSelection;
410
494
  }}
411
495
  />
496
+
497
+ <TablePagination
498
+ pageIndex={viewState.pageIndex}
499
+ pageSize={viewState.pageSize}
500
+ {pageCount}
501
+ {totalItems}
502
+ onPageChange={(p) => (viewState.pageIndex = p)}
503
+ onPageSizeChange={(s) => {
504
+ viewState.pageSize = s;
505
+ }}
506
+ />
412
507
  {/if}
413
508
  {/await}
414
509
  </Tabs.Content>
415
510
 
416
511
  <Tabs.Content value="archived" class="mt-0">
417
- {#await remotes.getRawEntries({ slug: collection.slug, onlyArchived: true }) then entries}
418
- {@const items = entries.map((entry) => ({
512
+ {#await remotes.getRawEntries(archivedQueryParams) then result}
513
+ {@const items = result.entries.map((entry) => ({
419
514
  ...mapEntryToRow(entry),
420
515
  onRestore: () => handleRestore(entry.id),
421
516
  onDelete: () => handleDelete(entry.id)
422
517
  }))}
423
- <div class="p-4">
424
- <DataTable data={items} columns={archivedColumns} />
425
- </div>
518
+ {@const totalItems = result.total}
519
+ {@const pageCount = Math.ceil(totalItems / viewState.pageSize)}
520
+
521
+ {#if useServerPagination}
522
+ <div class="p-4">
523
+ <DataTable
524
+ data={items}
525
+ columns={archivedColumns}
526
+ manualPagination={true}
527
+ pageCount={pageCount}
528
+ rowCount={totalItems}
529
+ enablePagination
530
+ pagination={{ pageIndex: viewState.pageIndex, pageSize: viewState.pageSize }}
531
+ onPaginationChange={(p) => {
532
+ viewState.pageIndex = p.pageIndex;
533
+ }}
534
+ />
535
+ </div>
536
+ <TablePagination
537
+ pageIndex={viewState.pageIndex}
538
+ pageSize={viewState.pageSize}
539
+ {pageCount}
540
+ {totalItems}
541
+ onPageChange={(p) => (viewState.pageIndex = p)}
542
+ onPageSizeChange={(s) => {
543
+ viewState.pageSize = s;
544
+ }}
545
+ />
546
+ {:else}
547
+ <div class="p-4">
548
+ <DataTable data={items} columns={archivedColumns} />
549
+ </div>
550
+ {/if}
426
551
  {/await}
427
552
  </Tabs.Content>
428
553
  </Tabs.Root>
@@ -1,15 +1,20 @@
1
1
  import type { SortingState } from '@tanstack/table-core';
2
2
  export type ViewMode = 'list' | 'grid';
3
3
  export type DateFormat = 'relative' | 'absolute';
4
+ export type ActiveTab = 'active' | 'archived';
4
5
  export interface CollectionViewState {
5
6
  viewMode: ViewMode;
6
7
  dateFormat: DateFormat;
7
8
  pageSize: number;
8
9
  sorting: SortingState;
10
+ pageIndex: number;
11
+ activeTab: ActiveTab;
9
12
  }
10
13
  export declare function createCollectionViewState(collectionSlug: string): {
11
14
  viewMode: ViewMode;
12
15
  dateFormat: DateFormat;
13
16
  pageSize: number;
14
17
  sorting: SortingState;
18
+ pageIndex: number;
19
+ activeTab: ActiveTab;
15
20
  };
@@ -2,7 +2,9 @@ const DEFAULT_STATE = {
2
2
  viewMode: 'list',
3
3
  dateFormat: 'relative',
4
4
  pageSize: 10,
5
- sorting: []
5
+ sorting: [],
6
+ pageIndex: 0,
7
+ activeTab: 'active'
6
8
  };
7
9
  function getStorageKey(collectionSlug) {
8
10
  return `includio-collection-${collectionSlug}-view`;
@@ -42,25 +44,40 @@ export function createCollectionViewState(collectionSlug) {
42
44
  return state.viewMode;
43
45
  },
44
46
  set viewMode(value) {
45
- state = { ...state, viewMode: value };
47
+ state.viewMode = value;
46
48
  },
47
49
  get dateFormat() {
48
50
  return state.dateFormat;
49
51
  },
50
52
  set dateFormat(value) {
51
- state = { ...state, dateFormat: value };
53
+ state.dateFormat = value;
52
54
  },
53
55
  get pageSize() {
54
56
  return state.pageSize;
55
57
  },
56
58
  set pageSize(value) {
57
- state = { ...state, pageSize: value };
59
+ state.pageSize = value;
60
+ state.pageIndex = 0;
58
61
  },
59
62
  get sorting() {
60
63
  return state.sorting;
61
64
  },
62
65
  set sorting(value) {
63
- state = { ...state, sorting: value };
66
+ state.sorting = value;
67
+ state.pageIndex = 0;
68
+ },
69
+ get pageIndex() {
70
+ return state.pageIndex;
71
+ },
72
+ set pageIndex(value) {
73
+ state.pageIndex = value;
74
+ },
75
+ get activeTab() {
76
+ return state.activeTab;
77
+ },
78
+ set activeTab(value) {
79
+ state.activeTab = value;
80
+ state.pageIndex = 0;
64
81
  }
65
82
  };
66
83
  }
@@ -45,8 +45,8 @@
45
45
  });
46
46
 
47
47
  $effect(() => {
48
- remotes.getRawEntries({ slug: collection.slug }).then((entries) => {
49
- entryCount = entries.filter((e) => e.archivedAt === null).length;
48
+ remotes.getRawEntries({ slug: collection.slug }).then(({ total }) => {
49
+ entryCount = total;
50
50
  });
51
51
  });
52
52
  </script>
@@ -29,6 +29,9 @@
29
29
  pagination?: PaginationState;
30
30
  onPaginationChange?: (pagination: PaginationState) => void;
31
31
  tableRef?: (table: TableType<TData>) => void;
32
+ manualPagination?: boolean;
33
+ pageCount?: number;
34
+ rowCount?: number;
32
35
  };
33
36
 
34
37
  let {
@@ -45,7 +48,10 @@
45
48
  onRowSelectionChange,
46
49
  pagination = { pageIndex: 0, pageSize: 10 },
47
50
  onPaginationChange,
48
- tableRef
51
+ tableRef,
52
+ manualPagination: manualPaginationProp = false,
53
+ pageCount: pageCountProp,
54
+ rowCount: rowCountProp
49
55
  }: DataTableProps<TData, TValue> = $props();
50
56
 
51
57
  const table = createSvelteTable({
@@ -81,8 +87,14 @@
81
87
  onPaginationChange?.(newPagination);
82
88
  },
83
89
  globalFilterFn: 'includesString',
84
- manualPagination: false,
85
- manualSorting: true
90
+ manualPagination: manualPaginationProp,
91
+ manualSorting: true,
92
+ get pageCount() {
93
+ return pageCountProp;
94
+ },
95
+ get rowCount() {
96
+ return rowCountProp;
97
+ }
86
98
  });
87
99
 
88
100
  $effect(() => {
@@ -14,6 +14,9 @@ type DataTableProps<TData, TValue> = {
14
14
  pagination?: PaginationState;
15
15
  onPaginationChange?: (pagination: PaginationState) => void;
16
16
  tableRef?: (table: TableType<TData>) => void;
17
+ manualPagination?: boolean;
18
+ pageCount?: number;
19
+ rowCount?: number;
17
20
  };
18
21
  declare function $$render<TData, TValue>(): {
19
22
  props: DataTableProps<TData, TValue>;
@@ -74,6 +74,8 @@
74
74
  saveDraftToast: string;
75
75
  publishToast: string;
76
76
  scheduledToast: string;
77
+ saveFailed: string;
78
+ cannotPublish: string;
77
79
  }
78
80
  > = {
79
81
  en: {
@@ -81,14 +83,18 @@
81
83
  saveToast: 'Entry saved successfully',
82
84
  saveDraftToast: 'Draft saved successfully',
83
85
  publishToast: 'Entry published successfully',
84
- scheduledToast: 'Entry scheduled successfully'
86
+ scheduledToast: 'Entry scheduled successfully',
87
+ saveFailed: 'Save failed',
88
+ cannotPublish: 'Cannot publish'
85
89
  },
86
90
  pl: {
87
91
  entryArchived: 'Wpis został zarchiwizowany pomyślnie',
88
92
  saveToast: 'Wpis został pomyślnie zapisany',
89
93
  saveDraftToast: 'Wersja robocza została pomyślnie zapisana',
90
94
  publishToast: 'Wpis został pomyślnie opublikowany',
91
- scheduledToast: 'Wpis został zaplanowany pomyślnie'
95
+ scheduledToast: 'Wpis został zaplanowany pomyślnie',
96
+ saveFailed: 'Błąd zapisu',
97
+ cannotPublish: 'Nie można opublikować'
92
98
  }
93
99
  };
94
100
 
@@ -199,7 +205,7 @@
199
205
  } catch (e) {
200
206
  console.error('Save error:', e);
201
207
  saveStatus = 'error';
202
- toast.error(interfaceLanguage.current === 'pl' ? 'Błąd zapisu' : 'Save failed', {
208
+ toast.error(lang[interfaceLanguage.current].saveFailed, {
203
209
  description: e instanceof Error ? e.message : undefined
204
210
  });
205
211
  }
@@ -234,16 +240,14 @@
234
240
  } catch (e) {
235
241
  console.error('Publish error:', e);
236
242
  saveStatus = 'error';
237
- toast.error(interfaceLanguage.current === 'pl' ? 'Błąd zapisu' : 'Save failed', {
243
+ toast.error(lang[interfaceLanguage.current].saveFailed, {
238
244
  description: e instanceof Error ? e.message : undefined
239
245
  });
240
246
  }
241
247
  } else {
242
248
  const errors = flattenErrors(validatedForm.errors, collection.fields);
243
249
  toast.error(
244
- interfaceLanguage.current === 'pl'
245
- ? 'Nie można opublikować'
246
- : 'Cannot publish',
250
+ lang[interfaceLanguage.current].cannotPublish,
247
251
  {
248
252
  description: errors.slice(0, 3).join('\n') + (errors.length > 3 ? '\n...' : ''),
249
253
  duration: 5000
@@ -17,6 +17,7 @@
17
17
  publishedBy: string;
18
18
  createdAt: string;
19
19
  scheduledFor: string;
20
+ publishedVersion: string;
20
21
  }
21
22
  > = {
22
23
  en: {
@@ -29,7 +30,8 @@
29
30
  publishedAt: 'Published at',
30
31
  publishedBy: 'Published by',
31
32
  createdAt: 'Created at',
32
- scheduledFor: 'Scheduled for'
33
+ scheduledFor: 'Scheduled for',
34
+ publishedVersion: 'Published version'
33
35
  },
34
36
  pl: {
35
37
  status: {
@@ -41,7 +43,8 @@
41
43
  publishedAt: 'Opublikowano',
42
44
  publishedBy: 'Opublikował',
43
45
  createdAt: 'Utworzono',
44
- scheduledFor: 'Zaplanowano na'
46
+ scheduledFor: 'Zaplanowano na',
47
+ publishedVersion: 'Opublikowana wersja'
45
48
  }
46
49
  };
47
50
 
@@ -152,7 +155,7 @@
152
155
  {#if entry.publishedVersion && entry.publishedVersion.id !== version.id}
153
156
  <div class="border-t pt-2 mt-2">
154
157
  <div class="text-muted-foreground text-xs">
155
- {interfaceLanguage.current === 'pl' ? 'Opublikowana wersja' : 'Published version'}: v{entry.publishedVersion.versionNumber}
158
+ {lang[interfaceLanguage.current].publishedVersion}: v{entry.publishedVersion.versionNumber}
156
159
  </div>
157
160
  </div>
158
161
  {/if}
@@ -22,6 +22,7 @@
22
22
  draft: string;
23
23
  changedFields: string;
24
24
  noChanges: string;
25
+ versionsCount: string;
25
26
  }
26
27
  > = {
27
28
  en: {
@@ -33,7 +34,8 @@
33
34
  scheduled: 'Scheduled',
34
35
  draft: 'Draft',
35
36
  changedFields: 'Changed fields',
36
- noChanges: 'No changes'
37
+ noChanges: 'No changes',
38
+ versionsCount: 'versions'
37
39
  },
38
40
  pl: {
39
41
  history: 'Historia wersji',
@@ -44,7 +46,8 @@
44
46
  scheduled: 'Zaplanowana',
45
47
  draft: 'Szkic',
46
48
  changedFields: 'Zmienione pola',
47
- noChanges: 'Brak zmian'
49
+ noChanges: 'Brak zmian',
50
+ versionsCount: 'wersji'
48
51
  }
49
52
  };
50
53
 
@@ -153,7 +156,7 @@
153
156
  <Sheet.Header>
154
157
  <Sheet.Title>{lang[interfaceLanguage.current].history}</Sheet.Title>
155
158
  <Sheet.Description>
156
- {sortedVersions.length} {interfaceLanguage.current === 'pl' ? 'wersji' : 'versions'}
159
+ {sortedVersions.length} {lang[interfaceLanguage.current].versionsCount}
157
160
  </Sheet.Description>
158
161
  </Sheet.Header>
159
162
 
@@ -10,6 +10,7 @@
10
10
  import { getLocalizedLabel } from '../../utils/collectionLabel.js';
11
11
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
12
12
  import type { InterfaceLanguage } from '../../../types/languages.js';
13
+ import { formatRelativeDate as formatRelativeDateUtil, formatAbsoluteDate as formatAbsoluteDateUtil } from '../../utils/formatDate.js';
13
14
  import type { DateFormat } from '../collection/collection-view.svelte.js';
14
15
  import Checkbox from '../../../components/ui/checkbox/checkbox.svelte';
15
16
  import { getRemotes } from '../../context/remotes.js';
@@ -188,35 +189,11 @@
188
189
  });
189
190
 
190
191
  function formatRelativeDate(date: Date): string {
191
- const now = new Date();
192
- const diff = now.getTime() - date.getTime();
193
- const minutes = Math.floor(diff / 60000);
194
- const hours = Math.floor(diff / 3600000);
195
- const days = Math.floor(diff / 86400000);
196
- const weeks = Math.floor(days / 7);
197
- const months = Math.floor(days / 30);
198
-
199
- if (minutes < 1) return interfaceLanguage.current === 'pl' ? 'teraz' : 'now';
200
- if (minutes < 60)
201
- return interfaceLanguage.current === 'pl' ? `${minutes} min temu` : `${minutes} min ago`;
202
- if (hours < 24)
203
- return interfaceLanguage.current === 'pl' ? `${hours} godz. temu` : `${hours}h ago`;
204
- if (days < 7) return interfaceLanguage.current === 'pl' ? `${days} dni temu` : `${days}d ago`;
205
- if (weeks < 5)
206
- return interfaceLanguage.current === 'pl' ? `${weeks} tyg. temu` : `${weeks}w ago`;
207
- if (months < 12)
208
- return interfaceLanguage.current === 'pl' ? `${months} mies. temu` : `${months}mo ago`;
209
- return formatAbsoluteDate(date);
192
+ return formatRelativeDateUtil(date, interfaceLanguage.current);
210
193
  }
211
194
 
212
195
  function formatAbsoluteDate(date: Date): string {
213
- return date.toLocaleString(interfaceLanguage.current === 'pl' ? 'pl-PL' : 'en-US', {
214
- year: 'numeric',
215
- month: '2-digit',
216
- day: '2-digit',
217
- hour: '2-digit',
218
- minute: '2-digit'
219
- });
196
+ return formatAbsoluteDateUtil(date, interfaceLanguage.current);
220
197
  }
221
198
 
222
199
  async function handleBulkDelete() {
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
3
+ import { formatRelativeDate as formatRelativeDateUtil } from '../../utils/formatDate.js';
3
4
  import * as Card from '../../../components/ui/card/index.js';
4
5
  import { Badge } from '../../../components/ui/badge/index.js';
5
6
  import { Button } from '../../../components/ui/button/index.js';
@@ -45,18 +46,7 @@
45
46
  };
46
47
 
47
48
  function formatRelativeDate(date: Date): string {
48
- const now = new Date();
49
- const diff = now.getTime() - date.getTime();
50
- const minutes = Math.floor(diff / 60000);
51
- const hours = Math.floor(diff / 3600000);
52
- const days = Math.floor(diff / 86400000);
53
-
54
- if (minutes < 1) return interfaceLanguage.current === 'pl' ? 'teraz' : 'now';
55
- if (minutes < 60)
56
- return interfaceLanguage.current === 'pl' ? `${minutes} min` : `${minutes}m`;
57
- if (hours < 24)
58
- return interfaceLanguage.current === 'pl' ? `${hours} godz.` : `${hours}h`;
59
- return interfaceLanguage.current === 'pl' ? `${days} dni` : `${days}d`;
49
+ return formatRelativeDateUtil(date, interfaceLanguage.current);
60
50
  }
61
51
  </script>
62
52
 
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
3
+ import { formatRelativeDate as formatRelativeDateUtil } from '../../utils/formatDate.js';
3
4
  import * as Card from '../../../components/ui/card/index.js';
4
5
  import Skeleton from '../../../components/ui/skeleton/skeleton.svelte';
5
6
  import { getRemotes } from '../../../sveltekit/index.js';
@@ -34,18 +35,7 @@
34
35
  };
35
36
 
36
37
  function formatRelativeDate(date: Date): string {
37
- const now = new Date();
38
- const diff = now.getTime() - date.getTime();
39
- const minutes = Math.floor(diff / 60000);
40
- const hours = Math.floor(diff / 3600000);
41
- const days = Math.floor(diff / 86400000);
42
-
43
- if (minutes < 1) return interfaceLanguage.current === 'pl' ? 'teraz' : 'now';
44
- if (minutes < 60)
45
- return interfaceLanguage.current === 'pl' ? `${minutes} min temu` : `${minutes}m ago`;
46
- if (hours < 24)
47
- return interfaceLanguage.current === 'pl' ? `${hours} godz. temu` : `${hours}h ago`;
48
- return interfaceLanguage.current === 'pl' ? `${days} dni temu` : `${days}d ago`;
38
+ return formatRelativeDateUtil(date, interfaceLanguage.current);
49
39
  }
50
40
  </script>
51
41