includio-cms 0.5.0 → 0.5.1

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 (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/ROADMAP.md +8 -0
  3. package/dist/admin/client/admin/dashboard-page.svelte +18 -64
  4. package/dist/admin/client/collection/bulk-actions-bar.svelte +18 -1
  5. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +1 -0
  6. package/dist/admin/client/collection/collection-entries.svelte +25 -1
  7. package/dist/admin/client/collection/row-actions.svelte +13 -4
  8. package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -0
  9. package/dist/admin/client/entry/entry-header.svelte +51 -4
  10. package/dist/admin/client/entry/entry-header.svelte.d.ts +3 -0
  11. package/dist/admin/client/entry/entry.svelte +106 -6
  12. package/dist/admin/client/entry/header/a11y-validator.d.ts +3 -2
  13. package/dist/admin/client/entry/header/a11y-validator.js +50 -9
  14. package/dist/admin/client/entry/header/publish-panel.svelte +164 -4
  15. package/dist/admin/client/entry/header/version-history-sheet.svelte +9 -1
  16. package/dist/admin/components/dashboard/changelog-dialog.svelte +157 -0
  17. package/dist/admin/components/dashboard/changelog-dialog.svelte.d.ts +6 -0
  18. package/dist/admin/components/dashboard/orphaned-entries-notice.svelte +240 -0
  19. package/dist/admin/components/dashboard/orphaned-entries-notice.svelte.d.ts +13 -0
  20. package/dist/admin/components/fields/text-field-wrapper.svelte +134 -2
  21. package/dist/admin/components/layout/nav-footer.svelte +11 -4
  22. package/dist/admin/components/layout/nav-footer.svelte.d.ts +2 -17
  23. package/dist/admin/remote/entry.remote.d.ts +1 -0
  24. package/dist/admin/remote/entry.remote.js +5 -4
  25. package/dist/admin/state/content-language.svelte.d.ts +3 -0
  26. package/dist/admin/state/content-language.svelte.js +8 -0
  27. package/dist/admin/utils/translationStatus.d.ts +17 -0
  28. package/dist/admin/utils/translationStatus.js +134 -0
  29. package/dist/core/server/entries/operations/get.js +2 -1
  30. package/dist/db-postgres/index.js +10 -6
  31. package/dist/types/entries.d.ts +3 -0
  32. package/dist/updates/0.5.1/index.d.ts +2 -0
  33. package/dist/updates/0.5.1/index.js +17 -0
  34. package/dist/updates/index.js +2 -1
  35. package/package.json +1 -1
  36. package/dist/admin/components/dashboard/updates-banner.svelte +0 -170
  37. package/dist/admin/components/dashboard/updates-banner.svelte.d.ts +0 -3
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.5.1 — 2026-02-24
7
+
8
+ Restore archived entries, dashboard redesign, translation fixes
9
+
10
+ ### Added
11
+ - Restore archived entries from collection list (single + bulk)
12
+ - Archived entry page: read-only with restore banner
13
+ - Dashboard: changelog modal, orphaned entries redesign, grid layout
14
+
15
+ ### Fixed
16
+ - Opening archived entry no longer returns 500 (getRawEntry now supports includeArchived)
17
+ - Translation flow: reactive status, switcher UX, dynamic ref, copyFrom crash, panel scroll
18
+ - False "Niezapisane zmiany" alert on entry load
19
+ - Hide translation dots for non-required empty fields
20
+
6
21
  ## 0.5.0 — 2026-02-22
7
22
 
8
23
  Frontend rendering for structured content
package/ROADMAP.md CHANGED
@@ -64,6 +64,14 @@
64
64
  - **0.4.1** — Accessibility layer (ATAG Part B)
65
65
  - **0.5.0** — Frontend rendering
66
66
 
67
+ ## 0.5.1 — Patches & dashboard
68
+
69
+ - [x] `[feature]` `[P1]` Restore archived entries — banner, read-only mode, list actions
70
+ - [x] `[feature]` `[P2]` Dashboard: changelog modal, orphaned entries redesign, grid layout
71
+ - [x] `[fix]` `[P1]` Translation flow — reactive status, switcher UX, dynamic ref, copyFrom crash, panel scroll
72
+ - [x] `[fix]` `[P1]` False "Niezapisane zmiany" on entry load
73
+ - [x] `[fix]` `[P2]` Hide translation dots for non-required empty fields
74
+
67
75
  ## 0.6.0 — Plugin system _(deferred from 0.2.0)_
68
76
 
69
77
  - [ ] `[feature]` `[P0]` Wire plugin hooks into CRUD operations (before/afterCreate, Update, Delete) <!-- files: src/lib/types/plugins.ts, src/lib/core/server/entries/operations/ -->
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import UpdatesBanner from '../../components/dashboard/updates-banner.svelte';
2
+ import OrphanedEntriesNotice from '../../components/dashboard/orphaned-entries-notice.svelte';
3
3
  import WelcomeHeader from '../../components/dashboard/welcome-header.svelte';
4
4
  import RecentEntries from '../../components/dashboard/recent-entries.svelte';
5
5
  import FormSubmissionsWidget from '../../components/dashboard/form-submissions-widget.svelte';
@@ -9,12 +9,8 @@
9
9
  import { sidebarLang } from '../../components/layout/lang.js';
10
10
  import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
11
11
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
12
- import * as Alert from '../../../components/ui/alert/index.js';
13
- import { Button } from '../../../components/ui/button/index.js';
14
12
 
15
13
  import type { InterfaceLanguage } from '../../../types/languages.js';
16
- import AlertTriangleIcon from '@tabler/icons-svelte/icons/alert-triangle';
17
- import TrashIcon from '@tabler/icons-svelte/icons/trash';
18
14
 
19
15
  type OrphanedEntry = { id: string; slug: string; createdAt: string };
20
16
 
@@ -39,7 +35,11 @@
39
35
  }
40
36
 
41
37
  async function deleteOrphaned() {
42
- if (!confirm(orphanedLang[interfaceLanguage.current].confirmDelete)) return;
38
+ const msg: Record<InterfaceLanguage, string> = {
39
+ pl: 'Czy na pewno chcesz usunąć te wpisy? Tej operacji nie można cofnąć.',
40
+ en: 'Are you sure you want to delete these entries? This cannot be undone.'
41
+ };
42
+ if (!confirm(msg[interfaceLanguage.current])) return;
43
43
 
44
44
  deletingOrphaned = true;
45
45
  try {
@@ -65,78 +65,32 @@
65
65
  }
66
66
  ];
67
67
  });
68
-
69
- const orphanedLang: Record<
70
- InterfaceLanguage,
71
- { title: string; description: string; deleteBtn: string; confirmDelete: string }
72
- > = {
73
- pl: {
74
- title: 'Znaleziono osirocone wpisy',
75
- description:
76
- 'W bazie danych znajdują się wpisy, których konfiguracja została usunięta. Zalecane jest ich usunięcie.',
77
- deleteBtn: 'Usuń osirocone wpisy',
78
- confirmDelete: 'Czy na pewno chcesz usunąć wszystkie osirocone wpisy? Ta operacja jest nieodwracalna.'
79
- },
80
- en: {
81
- title: 'Orphaned entries found',
82
- description:
83
- 'The database contains entries whose configuration has been removed. It is recommended to delete them.',
84
- deleteBtn: 'Delete orphaned entries',
85
- confirmDelete: 'Are you sure you want to delete all orphaned entries? This operation is irreversible.'
86
- }
87
- };
88
68
  </script>
89
69
 
90
70
  <main class="dash-main">
91
- <UpdatesBanner />
92
-
93
- {#if !loadingOrphaned && orphanedEntries.length > 0}
94
- <Alert.Root
95
- variant="destructive"
96
- class="mb-5 rounded-2xl border-orange-300/50 bg-orange-50 dark:border-orange-500/30 dark:bg-orange-950/60"
97
- >
98
- <AlertTriangleIcon class="text-orange-600 dark:text-orange-400" />
99
- <Alert.Title class="text-orange-800 dark:text-orange-200">
100
- {orphanedLang[interfaceLanguage.current].title} ({orphanedEntries.length})
101
- </Alert.Title>
102
- <Alert.Description class="text-orange-700 dark:text-orange-300">
103
- <p class="mb-3">{orphanedLang[interfaceLanguage.current].description}</p>
104
- <details class="mb-3">
105
- <summary class="cursor-pointer text-sm font-medium">Slugs</summary>
106
- <ul class="mt-2 list-inside list-disc text-sm">
107
- {#each orphanedEntries as entry}
108
- <li><code class="rounded bg-orange-200/50 px-1 dark:bg-orange-800/50">{entry.slug}</code></li>
109
- {/each}
110
- </ul>
111
- </details>
112
- <Button
113
- variant="destructive"
114
- size="sm"
115
- onclick={deleteOrphaned}
116
- disabled={deletingOrphaned}
117
- >
118
- <TrashIcon class="mr-1 h-4 w-4" />
119
- {orphanedLang[interfaceLanguage.current].deleteBtn}
120
- </Button>
121
- </Alert.Description>
122
- </Alert.Root>
71
+ {#if !loadingOrphaned}
72
+ <OrphanedEntriesNotice
73
+ entries={orphanedEntries}
74
+ deleting={deletingOrphaned}
75
+ ondelete={deleteOrphaned}
76
+ />
123
77
  {/if}
124
78
 
125
79
  <WelcomeHeader />
126
80
 
127
- <div class="dashboard-grid">
128
- <div class="dashboard-grid-main">
129
- <div class="content-duo">
81
+ <div class="flex gap-5">
82
+ <div class="flex-1">
83
+ <div class="grid grid-cols-2 gap-5">
130
84
  <RecentEntries />
131
85
  <FormSubmissionsWidget />
132
86
  </div>
133
- <div style="margin-top:20px">
87
+ <div class="mt-5">
134
88
  <RecentActivity />
135
89
  </div>
136
90
  </div>
137
- <div class="dashboard-grid-aside">
91
+ <div class="max-w-80 flex-1 shrink-0">
138
92
  <A11yGauge />
139
- <div style="margin-top:20px">
93
+ <div class="mt-5">
140
94
  <TipOfTheDay />
141
95
  </div>
142
96
  </div>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Archive from '@tabler/icons-svelte/icons/archive';
3
+ import ArchiveOff from '@tabler/icons-svelte/icons/archive-off';
3
4
  import Trash from '@tabler/icons-svelte/icons/trash';
4
5
  import X from '@tabler/icons-svelte/icons/x';
5
6
  import Button from '../../../components/ui/button/button.svelte';
@@ -12,25 +13,29 @@
12
13
  onArchive: () => void;
13
14
  onDelete: () => void;
14
15
  onClear: () => void;
16
+ onRestore?: () => void;
15
17
  };
16
18
 
17
- let { selectedCount, onArchive, onDelete, onClear }: Props = $props();
19
+ let { selectedCount, onArchive, onDelete, onClear, onRestore }: Props = $props();
18
20
 
19
21
  const interfaceLanguage = useInterfaceLanguage();
20
22
 
21
23
  const lang: Record<InterfaceLanguage, {
22
24
  selected: (n: number) => string;
23
25
  archive: string;
26
+ restore: string;
24
27
  delete: string;
25
28
  }> = {
26
29
  en: {
27
30
  selected: (n) => `${n} selected`,
28
31
  archive: 'Archive',
32
+ restore: 'Restore',
29
33
  delete: 'Delete'
30
34
  },
31
35
  pl: {
32
36
  selected: (n) => `Zaznaczono: ${n}`,
33
37
  archive: 'Archiwizuj',
38
+ restore: 'Przywróć',
34
39
  delete: 'Usuń'
35
40
  }
36
41
  };
@@ -52,6 +57,17 @@
52
57
 
53
58
  <div class="h-5 w-px bg-white/20"></div>
54
59
 
60
+ {#if onRestore}
61
+ <Button
62
+ variant="ghost"
63
+ size="sm"
64
+ class="text-white hover:bg-white/10 hover:text-white"
65
+ onclick={onRestore}
66
+ >
67
+ <ArchiveOff class="mr-1.5 size-3.5" />
68
+ {t.restore}
69
+ </Button>
70
+ {:else}
55
71
  <Button
56
72
  variant="ghost"
57
73
  size="sm"
@@ -61,6 +77,7 @@
61
77
  <Archive class="mr-1.5 size-3.5" />
62
78
  {t.archive}
63
79
  </Button>
80
+ {/if}
64
81
  <Button
65
82
  variant="ghost"
66
83
  size="sm"
@@ -3,6 +3,7 @@ type Props = {
3
3
  onArchive: () => void;
4
4
  onDelete: () => void;
5
5
  onClear: () => void;
6
+ onRestore?: () => void;
6
7
  };
7
8
  declare const BulkActionsBar: import("svelte").Component<Props, {}, "">;
8
9
  type BulkActionsBar = ReturnType<typeof BulkActionsBar>;
@@ -47,6 +47,8 @@
47
47
  selectAll: string;
48
48
  selectRow: string;
49
49
  entriesArchived: string;
50
+ entryRestored: string;
51
+ entriesRestored: string;
50
52
  entryDeleted: string;
51
53
  deleteConfirmTitle: string;
52
54
  deleteConfirmDescription: string;
@@ -65,6 +67,8 @@
65
67
  selectAll: 'Select all',
66
68
  selectRow: 'Select row',
67
69
  entriesArchived: 'Entries archived',
70
+ entryRestored: 'Entry restored',
71
+ entriesRestored: 'Entries restored',
68
72
  entryDeleted: 'Entry permanently deleted',
69
73
  deleteConfirmTitle: 'Delete entry permanently?',
70
74
  deleteConfirmDescription:
@@ -83,6 +87,8 @@
83
87
  selectAll: 'Zaznacz wszystkie',
84
88
  selectRow: 'Zaznacz wiersz',
85
89
  entriesArchived: 'Wpisy zarchiwizowane',
90
+ entryRestored: 'Wpis przywrócony',
91
+ entriesRestored: 'Wpisy przywrócone',
86
92
  entryDeleted: 'Wpis trwale usunięty',
87
93
  deleteConfirmTitle: 'Usunąć wpis na stałe?',
88
94
  deleteConfirmDescription: 'Ta akcja jest nieodwracalna. Wpis zostanie trwale usunięty.',
@@ -242,7 +248,8 @@
242
248
  entryUrl: info.row.original.url,
243
249
  entryName: info.row.original.name,
244
250
  onArchive: () => handleArchiveSingle(info.row.original.id),
245
- onDelete: () => handleDelete(info.row.original.id)
251
+ onDelete: () => handleDelete(info.row.original.id),
252
+ ...(isArchivedFilter ? { onRestore: () => handleRestoreSingle(info.row.original.id) } : {})
246
253
  }),
247
254
  enableSorting: false,
248
255
  size: 50
@@ -259,6 +266,12 @@
259
266
  refreshQueries();
260
267
  }
261
268
 
269
+ async function handleRestoreSingle(id: string) {
270
+ await remotes.unarchiveEntryCommand(id);
271
+ toast.success(lang[interfaceLanguage.current].entryRestored);
272
+ refreshQueries();
273
+ }
274
+
262
275
  function handleDelete(id: string) {
263
276
  pendingDeleteId = id;
264
277
  deleteDialogOpen = true;
@@ -381,6 +394,16 @@
381
394
  refreshQueries();
382
395
  }
383
396
 
397
+ async function handleBulkRestore(items: CollectionDataTableRow[]) {
398
+ const idsToRestore = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
399
+ for (const id of idsToRestore) {
400
+ await remotes.unarchiveEntryCommand(id);
401
+ }
402
+ toast.success(lang[interfaceLanguage.current].entriesRestored);
403
+ rowSelection = {};
404
+ refreshQueries();
405
+ }
406
+
384
407
  async function handleBulkDelete(items: CollectionDataTableRow[]) {
385
408
  const idsToDelete = selectedIndices.map((idx) => items[idx]?.id).filter(Boolean);
386
409
  for (const id of idsToDelete) {
@@ -543,6 +566,7 @@
543
566
  onArchive={() => handleBulkArchive(displayItems)}
544
567
  onDelete={() => handleBulkDelete(displayItems)}
545
568
  onClear={() => (rowSelection = {})}
569
+ onRestore={isArchivedFilter ? () => handleBulkRestore(displayItems) : undefined}
546
570
  />
547
571
  {/await}
548
572
  {/key}
@@ -2,6 +2,7 @@
2
2
  import DotsVertical from '@tabler/icons-svelte/icons/dots-vertical';
3
3
  import ExternalLink from '@tabler/icons-svelte/icons/external-link';
4
4
  import Archive from '@tabler/icons-svelte/icons/archive';
5
+ import ArchiveOff from '@tabler/icons-svelte/icons/archive-off';
5
6
  import Trash from '@tabler/icons-svelte/icons/trash';
6
7
  import Button from '../../../components/ui/button/button.svelte';
7
8
  import * as DropdownMenu from '../../../components/ui/dropdown-menu/index.js';
@@ -13,15 +14,16 @@
13
14
  entryName: string;
14
15
  onArchive: () => void;
15
16
  onDelete: () => void;
17
+ onRestore?: () => void;
16
18
  };
17
19
 
18
- let { entryUrl, entryName, onArchive, onDelete }: Props = $props();
20
+ let { entryUrl, entryName, onArchive, onDelete, onRestore }: Props = $props();
19
21
 
20
22
  const interfaceLanguage = useInterfaceLanguage();
21
23
 
22
- const lang: Record<InterfaceLanguage, { open: string; archive: string; delete: string; moreActions: string }> = {
23
- en: { open: 'Open', archive: 'Archive', delete: 'Delete', moreActions: 'More actions for' },
24
- pl: { open: 'Otwórz', archive: 'Archiwizuj', delete: 'Usuń', moreActions: 'Więcej akcji dla' }
24
+ const lang: Record<InterfaceLanguage, { open: string; archive: string; restore: string; delete: string; moreActions: string }> = {
25
+ en: { open: 'Open', archive: 'Archive', restore: 'Restore', delete: 'Delete', moreActions: 'More actions for' },
26
+ pl: { open: 'Otwórz', archive: 'Archiwizuj', restore: 'Przywróć', delete: 'Usuń', moreActions: 'Więcej akcji dla' }
25
27
  };
26
28
 
27
29
  const t = $derived(lang[interfaceLanguage.current]);
@@ -48,10 +50,17 @@
48
50
  {t.open}
49
51
  </DropdownMenu.Item>
50
52
  <DropdownMenu.Separator />
53
+ {#if onRestore}
54
+ <DropdownMenu.Item onclick={onRestore}>
55
+ <ArchiveOff class="mr-2 size-4" />
56
+ {t.restore}
57
+ </DropdownMenu.Item>
58
+ {:else}
51
59
  <DropdownMenu.Item onclick={onArchive}>
52
60
  <Archive class="mr-2 size-4" />
53
61
  {t.archive}
54
62
  </DropdownMenu.Item>
63
+ {/if}
55
64
  <DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={onDelete}>
56
65
  <Trash class="mr-2 size-4" />
57
66
  {t.delete}
@@ -3,6 +3,7 @@ type Props = {
3
3
  entryName: string;
4
4
  onArchive: () => void;
5
5
  onDelete: () => void;
6
+ onRestore?: () => void;
6
7
  };
7
8
  declare const RowActions: import("svelte").Component<Props, {}, "">;
8
9
  type RowActions = ReturnType<typeof RowActions>;
@@ -11,10 +11,12 @@
11
11
  import LayoutSidebar from '@tabler/icons-svelte/icons/layout-sidebar';
12
12
  import SendIcon from '@tabler/icons-svelte/icons/send';
13
13
  import ChevronDownIcon from '@tabler/icons-svelte/icons/chevron-down';
14
+ import LanguageIcon from '@tabler/icons-svelte/icons/language';
14
15
  import * as DropdownMenu from '../../../components/ui/dropdown-menu/index.js';
15
16
  import { hasHybridContext, getHybridContext } from './hybrid/hybrid-context.svelte.js';
16
17
  import { getEntryStatus } from './utils.js';
17
18
  import { onMount } from 'svelte';
19
+ import type { LangStatus } from '../../utils/translationStatus.js';
18
20
 
19
21
  const contentLanguage = getContentLanguage();
20
22
  const interfaceLanguage = useInterfaceLanguage();
@@ -50,7 +52,9 @@
50
52
  onSaveDraft: () => void;
51
53
  onArchive: () => void;
52
54
  saveStatus?: SaveStatus;
55
+ isArchived?: boolean;
53
56
  onScrollToIssue?: (fieldSlug: string, nodePos: number) => void;
57
+ translationStatus?: Record<string, LangStatus> | null;
54
58
  };
55
59
 
56
60
  let {
@@ -62,7 +66,9 @@
62
66
  fields = [],
63
67
  getFormData,
64
68
  saveStatus = 'idle',
65
- onScrollToIssue
69
+ isArchived = false,
70
+ onScrollToIssue,
71
+ translationStatus = null
66
72
  }: Props = $props();
67
73
  let { collection } = entry;
68
74
 
@@ -86,6 +92,31 @@
86
92
  isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform);
87
93
  });
88
94
  const shortcutLabel = $derived(isMac ? '⌘S' : 'Ctrl+S');
95
+
96
+ function statusDotClass(status: LangStatus | undefined): string {
97
+ if (!status) return 'bg-[var(--text-light)]';
98
+ if (status.status === 'complete') return 'bg-[var(--success)]';
99
+ if (status.status === 'partial') return 'bg-[var(--warning)]';
100
+ return 'bg-[var(--text-light)]';
101
+ }
102
+
103
+ function statusLabel(lang: string, status: LangStatus | undefined): string {
104
+ if (!status) return lang.toUpperCase();
105
+ if (status.status === 'complete') return `${lang.toUpperCase()} \u2713`;
106
+ if (status.status === 'partial') return `${lang.toUpperCase()} \u26A0`;
107
+ return lang.toUpperCase();
108
+ }
109
+
110
+ function statusTooltip(lang: string, status: LangStatus | undefined): string {
111
+ if (!status) return lang.toUpperCase();
112
+ if (status.status === 'complete') return `${lang.toUpperCase()}: 100%`;
113
+ const missing = status.missingFields.map((f) => f.label).join(', ');
114
+ return `${lang.toUpperCase()}: ${status.percentage}% — ${interfaceLanguage.current === 'pl' ? 'brakuje' : 'missing'}: ${missing}`;
115
+ }
116
+
117
+ const isNonDefaultLang = $derived(
118
+ contentLanguage.all.length > 1 && contentLanguage.current !== contentLanguage.all[0]
119
+ );
89
120
  </script>
90
121
 
91
122
  <div
@@ -103,24 +134,39 @@
103
134
  {#if contentLanguage.all.length > 1}
104
135
  <div class="border-border bg-muted inline-flex overflow-hidden rounded-lg border">
105
136
  {#each contentLanguage.all as lang}
137
+ {@const langStatus = translationStatus?.[lang]}
106
138
  <button
107
139
  type="button"
108
140
  role="tab"
109
141
  aria-selected={lang === contentLanguage.current}
110
- class="px-2.5 py-1 text-xs font-semibold transition-colors {lang ===
142
+ title={statusTooltip(lang, langStatus)}
143
+ class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-semibold transition-colors {lang ===
111
144
  contentLanguage.current
112
- ? 'text-primary bg-white shadow-sm'
145
+ ? 'text-primary bg-white shadow-sm ring-1 ring-primary/20'
113
146
  : 'text-muted-foreground hover:text-foreground bg-transparent'}"
114
147
  onclick={() => (contentLanguage.current = lang)}
115
148
  >
116
- {lang.toUpperCase()}
149
+ <span class="size-2 shrink-0 rounded-full {statusDotClass(langStatus)}"></span>
150
+ {statusLabel(lang, langStatus)}
117
151
  </button>
118
152
  {/each}
119
153
  </div>
120
154
 
155
+ <!-- Reference mode toggle -->
156
+ <Button
157
+ variant="ghost"
158
+ size="icon-sm"
159
+ class={contentLanguage.referenceMode ? 'text-primary bg-[var(--lavender-lighter)]' : ''}
160
+ onclick={() => (contentLanguage.referenceMode = !contentLanguage.referenceMode)}
161
+ title={interfaceLanguage.current === 'pl' ? 'Tryb referencyjny' : 'Reference mode'}
162
+ >
163
+ <LanguageIcon class="size-4" />
164
+ </Button>
165
+
121
166
  <div class="bg-border mx-1 h-5 w-px shrink-0"></div>
122
167
  {/if}
123
168
 
169
+ {#if !isArchived}
124
170
  <!-- Split button: Publish + Save Draft -->
125
171
  <div class="inline-flex items-stretch rounded-lg shadow-[0_1px_3px_rgba(91,74,158,0.3)]">
126
172
  <button
@@ -155,6 +201,7 @@
155
201
  </div>
156
202
 
157
203
  <div class="bg-border mx-1 h-5 w-px shrink-0"></div>
204
+ {/if}
158
205
 
159
206
  <!-- Panel triggers + Hybrid toggle -->
160
207
  {#await import('./header/publish-panel.svelte') then { default: PublishPanel }}
@@ -1,6 +1,7 @@
1
1
  import type { DbEntryVersion, RawEntry } from '../../../types/entries.js';
2
2
  import type { UpdateEntryVersionCommandType } from '../../../core/server/entries/operations/update.js';
3
3
  import type { Field } from '../../../types/fields.js';
4
+ import type { LangStatus } from '../../utils/translationStatus.js';
4
5
  type SaveStatus = 'idle' | 'saving' | 'saved' | 'unsaved' | 'error';
5
6
  type Props = {
6
7
  entry: RawEntry;
@@ -11,7 +12,9 @@ type Props = {
11
12
  onSaveDraft: () => void;
12
13
  onArchive: () => void;
13
14
  saveStatus?: SaveStatus;
15
+ isArchived?: boolean;
14
16
  onScrollToIssue?: (fieldSlug: string, nodePos: number) => void;
17
+ translationStatus?: Record<string, LangStatus> | null;
15
18
  };
16
19
  declare const EntryHeader: import("svelte").Component<Props, {}, "">;
17
20
  type EntryHeader = ReturnType<typeof EntryHeader>;