includio-cms 0.5.3 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/ROADMAP.md +33 -0
  3. package/dist/admin/api/rest/handler.d.ts +7 -0
  4. package/dist/admin/api/rest/handler.js +116 -0
  5. package/dist/admin/api/rest/middleware/apiKey.d.ts +6 -0
  6. package/dist/admin/api/rest/middleware/apiKey.js +45 -0
  7. package/dist/admin/api/rest/routes/collections.d.ts +5 -0
  8. package/dist/admin/api/rest/routes/collections.js +104 -0
  9. package/dist/admin/api/rest/routes/entries.d.ts +2 -0
  10. package/dist/admin/api/rest/routes/entries.js +37 -0
  11. package/dist/admin/api/rest/routes/languages.d.ts +1 -0
  12. package/dist/admin/api/rest/routes/languages.js +5 -0
  13. package/dist/admin/api/rest/routes/schema.d.ts +2 -0
  14. package/dist/admin/api/rest/routes/schema.js +78 -0
  15. package/dist/admin/api/rest/routes/singletons.d.ts +3 -0
  16. package/dist/admin/api/rest/routes/singletons.js +60 -0
  17. package/dist/admin/auth-client.d.ts +48 -48
  18. package/dist/admin/client/admin/admin-after-login-layout.svelte +5 -3
  19. package/dist/admin/client/admin/admin-layout.svelte +0 -2
  20. package/dist/admin/client/collection/bulk-actions-bar.svelte +3 -1
  21. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +1 -1
  22. package/dist/admin/client/collection/collection-entries.svelte +334 -166
  23. package/dist/admin/client/collection/collection-page.svelte +6 -3
  24. package/dist/admin/client/collection/data-table.svelte +127 -18
  25. package/dist/admin/client/collection/data-table.svelte.d.ts +2 -0
  26. package/dist/admin/client/collection/row-actions.svelte +3 -1
  27. package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -1
  28. package/dist/admin/client/entry/entry-page.svelte +5 -4
  29. package/dist/admin/client/form/form-config-wrapper.svelte +5 -3
  30. package/dist/admin/client/form/form-page.svelte +5 -5
  31. package/dist/admin/client/form/form-submission/form-submission-page.svelte +30 -32
  32. package/dist/admin/components/accessibility/accessibility-overview.svelte +7 -5
  33. package/dist/admin/components/accessibility/accessibility-overview.svelte.d.ts +2 -17
  34. package/dist/admin/components/dashboard/a11y-gauge.svelte +7 -4
  35. package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +2 -17
  36. package/dist/admin/components/dashboard/accessibility-hub.svelte +7 -4
  37. package/dist/admin/components/dashboard/accessibility-hub.svelte.d.ts +2 -17
  38. package/dist/admin/components/dashboard/form-submissions-widget.svelte +8 -3
  39. package/dist/admin/components/dashboard/form-submissions-widget.svelte.d.ts +2 -17
  40. package/dist/admin/components/dashboard/recent-activity.svelte +8 -3
  41. package/dist/admin/components/dashboard/recent-activity.svelte.d.ts +2 -17
  42. package/dist/admin/components/dashboard/recent-entries.svelte +8 -3
  43. package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +2 -17
  44. package/dist/admin/components/fields/image-field.svelte +21 -20
  45. package/dist/admin/components/fields/media-field.svelte +45 -44
  46. package/dist/admin/components/fields/relation-field.svelte +428 -149
  47. package/dist/admin/components/fields/relation-picker-dialog.svelte +112 -0
  48. package/dist/admin/components/fields/relation-picker-dialog.svelte.d.ts +22 -0
  49. package/dist/admin/components/fields/seo-field.svelte +66 -10
  50. package/dist/admin/components/icons/icon-map.d.ts +4 -0
  51. package/dist/admin/components/icons/icon-map.js +143 -0
  52. package/dist/admin/components/layout/header-actions.svelte +0 -10
  53. package/dist/admin/components/layout/nav-collections.svelte +55 -57
  54. package/dist/admin/components/layout/nav-collections.svelte.d.ts +2 -17
  55. package/dist/admin/components/layout/nav-forms.svelte +49 -49
  56. package/dist/admin/components/layout/nav-forms.svelte.d.ts +2 -17
  57. package/dist/admin/components/layout/nav-search.svelte +15 -22
  58. package/dist/admin/components/layout/nav-singletons.svelte +35 -38
  59. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +2 -17
  60. package/dist/admin/components/media/file/file-preview.svelte +92 -91
  61. package/dist/admin/components/media/file-preview.svelte +17 -18
  62. package/dist/admin/components/media/media-library.svelte +39 -41
  63. package/dist/admin/components/media/media-selector.svelte +8 -8
  64. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +6 -8
  65. package/dist/admin/components/tiptap/content-editor.svelte +2 -2
  66. package/dist/admin/components/tiptap/editor-toolbar.svelte +1 -1
  67. package/dist/admin/components/tiptap/inline-block-node.d.ts +1 -0
  68. package/dist/admin/components/tiptap/inline-block-node.js +12 -1
  69. package/dist/admin/components/tiptap/tiptap-editor.svelte +1 -1
  70. package/dist/admin/remote/entry.remote.d.ts +20 -1
  71. package/dist/admin/remote/entry.remote.js +24 -3
  72. package/dist/admin/remote/invite.d.ts +2 -2
  73. package/dist/cli/scaffold/admin.js +8 -0
  74. package/dist/cms/runtime/remote.d.ts +8 -0
  75. package/dist/cms/runtime/remote.js +7 -0
  76. package/dist/cms/runtime/types.d.ts +1 -1
  77. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  78. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  79. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  80. package/dist/components/ui/sonner/sonner.svelte +1 -2
  81. package/dist/core/cms.d.ts +2 -1
  82. package/dist/core/cms.js +2 -0
  83. package/dist/core/server/entries/operations/get.d.ts +11 -0
  84. package/dist/core/server/entries/operations/get.js +63 -1
  85. package/dist/core/server/entries/operations/update.d.ts +1 -0
  86. package/dist/core/server/entries/operations/update.js +5 -1
  87. package/dist/core/server/fields/resolveUrlFields.js +49 -18
  88. package/dist/db-postgres/schema/entry.d.ts +17 -0
  89. package/dist/db-postgres/schema/entry.js +4 -2
  90. package/dist/server/auth.d.ts +4 -4
  91. package/dist/sveltekit/components/structured-content.svelte +18 -5
  92. package/dist/sveltekit/server/handle.js +1 -0
  93. package/dist/types/cms.d.ts +7 -0
  94. package/dist/types/collections.d.ts +2 -0
  95. package/dist/types/config.d.ts +2 -2
  96. package/dist/types/entries.d.ts +7 -1
  97. package/dist/types/forms.d.ts +2 -0
  98. package/dist/types/index.d.ts +2 -1
  99. package/dist/types/index.js +1 -0
  100. package/dist/updates/0.5.4/index.d.ts +2 -0
  101. package/dist/updates/0.5.4/index.js +15 -0
  102. package/dist/updates/0.5.5/index.d.ts +2 -0
  103. package/dist/updates/0.5.5/index.js +20 -0
  104. package/dist/updates/0.5.6/index.d.ts +2 -0
  105. package/dist/updates/0.5.6/index.js +17 -0
  106. package/dist/updates/0.5.7/index.d.ts +2 -0
  107. package/dist/updates/0.5.7/index.js +14 -0
  108. package/dist/updates/index.js +5 -1
  109. package/package.json +6 -2
@@ -6,29 +6,31 @@
6
6
  import { getRemotes } from '../../../sveltekit/index.js';
7
7
  import { sidebarLang } from './lang.js';
8
8
  import ClipboardIcon from '@tabler/icons-svelte/icons/clipboard-list';
9
+ import { getIcon } from '../icons/icon-map.js';
9
10
  import { page } from '$app/state';
10
11
 
11
12
  const interfaceLanguage = useInterfaceLanguage();
12
13
  const remotes = getRemotes();
13
14
 
14
15
  function isActive(url: string) {
15
- return page.url.pathname.startsWith(url);
16
+ const pathname = page.url.pathname;
17
+ return pathname === url || pathname.startsWith(url + '/');
16
18
  }
17
19
 
18
- async function loadForms() {
19
- const [forms, overview] = await Promise.all([
20
- remotes.getForms(),
21
- remotes.getSubmissionsOverview().catch(() => [] as any[])
22
- ]);
23
- const countMap = new Map((overview as any[]).map((o: any) => [o.slug, o.total as number]));
24
- return forms.map((f: any) => ({
25
- ...f,
26
- submissionCount: countMap.get(f.slug) ?? 0
27
- }));
28
- }
20
+ const formsQuery = $derived(remotes.getForms());
21
+ const overviewQuery = $derived(remotes.getSubmissionsOverview());
22
+
23
+ const countsMap = $derived.by(() => {
24
+ const map = new Map<string, number>();
25
+ if (!overviewQuery.ready || !overviewQuery.current) return map;
26
+ for (const item of overviewQuery.current) {
27
+ if (item.total > 0) map.set(item.slug, item.total);
28
+ }
29
+ return map;
30
+ });
29
31
  </script>
30
32
 
31
- {#await loadForms()}
33
+ {#if !formsQuery.ready}
32
34
  <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
33
35
  <Sidebar.GroupLabel>{sidebarLang[interfaceLanguage.current].forms.title}</Sidebar.GroupLabel>
34
36
  <Sidebar.Menu>
@@ -38,40 +40,38 @@
38
40
  </div>
39
41
  </Sidebar.Menu>
40
42
  </Sidebar.Group>
41
- {:then items}
42
- {#if items.length > 0}
43
- <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
44
- <Sidebar.GroupLabel class="group-data-[collapsible=icon]:hidden"
45
- >{sidebarLang[interfaceLanguage.current].forms.title}</Sidebar.GroupLabel
46
- >
47
- <Sidebar.Menu>
48
- {#each items as item (item.slug)}
49
- {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
50
- {@const url = `/admin/forms/${item.slug}`}
51
- {@const active = isActive(url)}
52
- <Sidebar.MenuItem>
53
- <Sidebar.MenuButton isActive={active} tooltipContent={name}>
54
- {#snippet child({ props })}
55
- <a {...props} href={url}>
56
- <ClipboardIcon
57
- class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
58
- />
59
- <span class="flex-1 {active ? 'text-primary' : 'text-muted-foreground'} truncate"
60
- >{name}</span
43
+ {:else if formsQuery.current && formsQuery.current.length > 0}
44
+ <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
45
+ <Sidebar.GroupLabel class="group-data-[collapsible=icon]:hidden"
46
+ >{sidebarLang[interfaceLanguage.current].forms.title}</Sidebar.GroupLabel
47
+ >
48
+ <Sidebar.Menu>
49
+ {#each formsQuery.current as item (item.slug)}
50
+ {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
51
+ {@const url = `/admin/forms/${item.slug}`}
52
+ {@const active = isActive(url)}
53
+ <Sidebar.MenuItem>
54
+ <Sidebar.MenuButton isActive={active} tooltipContent={name}>
55
+ {#snippet child({ props })}
56
+ {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? ClipboardIcon}
57
+ <a {...props} href={url}>
58
+ <ResolvedIcon
59
+ class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
60
+ />
61
+ <span class="flex-1 {active ? 'text-primary' : 'text-muted-foreground'} truncate"
62
+ >{name}</span
63
+ >
64
+ {#if countsMap.get(item.slug)}
65
+ <span
66
+ class="bg-lavender-lighter text-primary inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full px-[5px] text-[10px] font-bold group-data-[collapsible=icon]:hidden"
67
+ >{countsMap.get(item.slug)}</span
61
68
  >
62
- {#if item.submissionCount > 0}
63
- <span
64
- class="bg-lavender-lighter text-primary inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full px-[5px] text-[10px] font-bold group-data-[collapsible=icon]:hidden"
65
- >
66
- {item.submissionCount}
67
- </span>
68
- {/if}
69
- </a>
70
- {/snippet}
71
- </Sidebar.MenuButton>
72
- </Sidebar.MenuItem>
73
- {/each}
74
- </Sidebar.Menu>
75
- </Sidebar.Group>
76
- {/if}
77
- {/await}
69
+ {/if}
70
+ </a>
71
+ {/snippet}
72
+ </Sidebar.MenuButton>
73
+ </Sidebar.MenuItem>
74
+ {/each}
75
+ </Sidebar.Menu>
76
+ </Sidebar.Group>
77
+ {/if}
@@ -1,18 +1,3 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const NavForms: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type NavForms = InstanceType<typeof NavForms>;
1
+ declare const NavForms: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type NavForms = ReturnType<typeof NavForms>;
18
3
  export default NavForms;
@@ -5,6 +5,7 @@
5
5
  import ClipboardIcon from '@tabler/icons-svelte/icons/clipboard-list';
6
6
  import SettingsIcon from '@tabler/icons-svelte/icons/settings';
7
7
  import FolderIcon from '@tabler/icons-svelte/icons/folder';
8
+ import { getIcon } from '../icons/icon-map.js';
8
9
  import * as Command from '../../../components/ui/command/index.js';
9
10
  import { goto } from '$app/navigation';
10
11
  import { getRemotes } from '../../../sveltekit/index.js';
@@ -29,20 +30,17 @@
29
30
  goto(url);
30
31
  }
31
32
 
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
- }
33
+ const singlesQuery = $derived(remotes.getSingles());
34
+ const collectionsQuery = $derived(remotes.getCollections());
35
+ const formsQuery = $derived(remotes.getForms());
41
36
  </script>
42
37
 
43
38
  <svelte:window {onkeydown} />
44
39
 
45
- {#await getData() then { singles, collections, forms }}
40
+ {#if singlesQuery.ready && collectionsQuery.ready && formsQuery.ready}
41
+ {@const singles = singlesQuery.current ?? []}
42
+ {@const collections = collectionsQuery.current ?? []}
43
+ {@const forms = formsQuery.current ?? []}
46
44
  <button
47
45
  onclick={() => (open = true)}
48
46
  class="bg-muted text-text-light hover:border-sidebar-border hover:bg-lavender-lighter hover:text-muted-foreground focus-visible:outline-primary mx-2 mt-2 mb-1 flex min-h-9 cursor-pointer items-center gap-2 rounded-[8px] border border-transparent px-2.5 py-[7px] font-sans text-[13px] transition-all group-data-[collapsible=icon]:mx-1 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-[7px] focus-visible:outline-2 focus-visible:outline-offset-2"
@@ -75,11 +73,8 @@
75
73
  {#each singles as item (item.slug)}
76
74
  {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
77
75
  <Command.Item onSelect={() => navigate(`/admin/entries/${item.slug}`)}>
78
- {#if item.sidebarIcon}
79
- <item.sidebarIcon class="mr-2 size-4" />
80
- {:else}
81
- <SettingsIcon class="mr-2 size-4" />
82
- {/if}
76
+ {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? SettingsIcon}
77
+ <ResolvedIcon class="mr-2 size-4" />
83
78
  {name}
84
79
  </Command.Item>
85
80
  {/each}
@@ -91,11 +86,8 @@
91
86
  {@const name =
92
87
  getLocalizedLabel(item.labels?.plural, interfaceLanguage.current) ?? item.slug}
93
88
  <Command.Item onSelect={() => navigate(`/admin/collections/${item.slug}`)}>
94
- {#if item.sidebarIcon}
95
- <item.sidebarIcon class="mr-2 size-4" />
96
- {:else}
97
- <FolderIcon class="mr-2 size-4" />
98
- {/if}
89
+ {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? FolderIcon}
90
+ <ResolvedIcon class="mr-2 size-4" />
99
91
  {name}
100
92
  </Command.Item>
101
93
  {/each}
@@ -107,7 +99,8 @@
107
99
  {#each forms as item (item.slug)}
108
100
  {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
109
101
  <Command.Item onSelect={() => navigate(`/admin/forms/${item.slug}`)}>
110
- <ClipboardIcon class="mr-2 size-4" />
102
+ {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? ClipboardIcon}
103
+ <ResolvedIcon class="mr-2 size-4" />
111
104
  {name}
112
105
  </Command.Item>
113
106
  {/each}
@@ -115,4 +108,4 @@
115
108
  {/if}
116
109
  </Command.List>
117
110
  </Command.Dialog>
118
- {/await}
111
+ {/if}
@@ -7,16 +7,20 @@
7
7
  import Skeleton from '../../../components/ui/skeleton/skeleton.svelte';
8
8
  import { page } from '$app/state';
9
9
  import SettingsIcon from '@tabler/icons-svelte/icons/settings';
10
+ import { getIcon } from '../icons/icon-map.js';
10
11
 
11
12
  const interfaceLanguage = useInterfaceLanguage();
12
13
  const remotes = getRemotes();
13
14
 
14
15
  function isActive(url: string) {
15
- return page.url.pathname.startsWith(url);
16
+ const pathname = page.url.pathname;
17
+ return pathname === url || pathname.startsWith(url + '/');
16
18
  }
19
+
20
+ const query = $derived(remotes.getSingles());
17
21
  </script>
18
22
 
19
- {#await remotes.getSingles()}
23
+ {#if !query.ready}
20
24
  <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
21
25
  <Sidebar.GroupLabel
22
26
  >{sidebarLang[interfaceLanguage.current].singletons.title}</Sidebar.GroupLabel
@@ -28,39 +32,32 @@
28
32
  </div>
29
33
  </Sidebar.Menu>
30
34
  </Sidebar.Group>
31
- {:then items}
32
- {#if items.length > 0}
33
- <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
34
- <Sidebar.GroupLabel class="group-data-[collapsible=icon]:hidden"
35
- >{sidebarLang[interfaceLanguage.current].singletons.title}</Sidebar.GroupLabel
36
- >
37
- <Sidebar.Menu>
38
- {#each items as item (item)}
39
- {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
40
- {@const url = `/admin/entries/${item.slug}`}
41
- {@const active = isActive(url)}
42
- <Sidebar.MenuItem>
43
- <Sidebar.MenuButton isActive={active} tooltipContent={name}>
44
- {#snippet child({ props })}
45
- <a {...props} href={url}>
46
- {#if item.sidebarIcon}
47
- <item.sidebarIcon
48
- class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
49
- />
50
- {:else}
51
- <SettingsIcon
52
- class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
53
- />
54
- {/if}
55
- <span class="flex-1 truncate {active ? 'text-primary' : 'text-muted-foreground'}"
56
- >{name}</span
57
- >
58
- </a>
59
- {/snippet}
60
- </Sidebar.MenuButton>
61
- </Sidebar.MenuItem>
62
- {/each}
63
- </Sidebar.Menu>
64
- </Sidebar.Group>
65
- {/if}
66
- {/await}
35
+ {:else if query.current && query.current.length > 0}
36
+ <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
37
+ <Sidebar.GroupLabel class="group-data-[collapsible=icon]:hidden"
38
+ >{sidebarLang[interfaceLanguage.current].singletons.title}</Sidebar.GroupLabel
39
+ >
40
+ <Sidebar.Menu>
41
+ {#each query.current as item (item)}
42
+ {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
43
+ {@const url = `/admin/entries/${item.slug}`}
44
+ {@const active = isActive(url)}
45
+ <Sidebar.MenuItem>
46
+ <Sidebar.MenuButton isActive={active} tooltipContent={name}>
47
+ {#snippet child({ props })}
48
+ {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? SettingsIcon}
49
+ <a {...props} href={url}>
50
+ <ResolvedIcon
51
+ class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
52
+ />
53
+ <span class="flex-1 truncate {active ? 'text-primary' : 'text-muted-foreground'}"
54
+ >{name}</span
55
+ >
56
+ </a>
57
+ {/snippet}
58
+ </Sidebar.MenuButton>
59
+ </Sidebar.MenuItem>
60
+ {/each}
61
+ </Sidebar.Menu>
62
+ </Sidebar.Group>
63
+ {/if}
@@ -1,18 +1,3 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const NavSingletons: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type NavSingletons = InstanceType<typeof NavSingletons>;
1
+ declare const NavSingletons: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type NavSingletons = ReturnType<typeof NavSingletons>;
18
3
  export default NavSingletons;
@@ -38,111 +38,112 @@
38
38
  }
39
39
  saving = false;
40
40
  }
41
+
42
+ const query = $derived(remotes.getFileById(fileId));
41
43
  </script>
42
44
 
43
- {#await remotes.getFileById(fileId) then file}
44
- {#if file}
45
- <div class="group flex items-center gap-3 rounded-xl border bg-card p-2 shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md">
46
- <!-- Miniaturka z lightbox -->
47
- <button
48
- type="button"
49
- class="aspect-square w-14 shrink-0 overflow-hidden rounded-lg cursor-pointer hover:ring-2 hover:ring-primary/50 transition-all"
50
- onclick={() => file.type === 'image' && (lightboxOpen = true)}
51
- >
52
- <FileMiniature {file} />
53
- </button>
45
+ {#if query.ready && query.current}
46
+ {@const file = query.current}
47
+ <div class="group flex items-center gap-3 rounded-xl border bg-card p-2 shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md">
48
+ <!-- Miniaturka z lightbox -->
49
+ <button
50
+ type="button"
51
+ class="aspect-square w-14 shrink-0 overflow-hidden rounded-lg cursor-pointer hover:ring-2 hover:ring-primary/50 transition-all"
52
+ onclick={() => file.type === 'image' && (lightboxOpen = true)}
53
+ >
54
+ <FileMiniature {file} />
55
+ </button>
56
+
57
+ <div class="min-w-0 flex-1">
58
+ <div class="flex items-center gap-1">
59
+ <p class="truncate text-sm font-medium flex-1">{file.name}</p>
60
+ <Button
61
+ variant="ghost"
62
+ size="icon"
63
+ class="h-6 w-6 shrink-0"
64
+ onclick={() => {
65
+ altValue = file.alt || '';
66
+ altDialogOpen = true;
67
+ }}
68
+ >
69
+ <Pencil class="h-3 w-3" />
70
+ </Button>
71
+ </div>
72
+ {#if file.size}
73
+ <p class="text-xs text-muted-foreground">{(file.size / 1024).toFixed(1)} KB</p>
74
+ {/if}
75
+ {#if file.alt}
76
+ <p class="text-xs text-muted-foreground truncate" title={file.alt}>{file.alt}</p>
77
+ {/if}
78
+ </div>
54
79
 
55
- <div class="min-w-0 flex-1">
56
- <div class="flex items-center gap-1">
57
- <p class="truncate text-sm font-medium flex-1">{file.name}</p>
58
- <Button
59
- variant="ghost"
60
- size="icon"
61
- class="h-6 w-6 shrink-0"
62
- onclick={() => {
63
- altValue = file.alt || '';
64
- altDialogOpen = true;
65
- }}
66
- >
67
- <Pencil class="h-3 w-3" />
68
- </Button>
80
+ {#if index !== undefined && onDelete}
81
+ <div class="flex shrink-0 items-center gap-1">
82
+ <div use:draggable={{ container: index.toString(), dragData: file }} class="cursor-grab p-1 text-muted-foreground hover:text-foreground">
83
+ <GripVertical class="h-4 w-4" />
69
84
  </div>
70
- {#if file.size}
71
- <p class="text-xs text-muted-foreground">{(file.size / 1024).toFixed(1)} KB</p>
72
- {/if}
73
- {#if file.alt}
74
- <p class="text-xs text-muted-foreground truncate" title={file.alt}>{file.alt}</p>
75
- {/if}
85
+ <Button variant="ghost" size="icon" class="h-7 w-7 text-muted-foreground hover:text-destructive" onclick={() => onDelete(index)}>
86
+ <X class="h-4 w-4" />
87
+ </Button>
76
88
  </div>
89
+ {/if}
90
+ </div>
77
91
 
78
- {#if index !== undefined && onDelete}
79
- <div class="flex shrink-0 items-center gap-1">
80
- <div use:draggable={{ container: index.toString(), dragData: file }} class="cursor-grab p-1 text-muted-foreground hover:text-foreground">
81
- <GripVertical class="h-4 w-4" />
82
- </div>
83
- <Button variant="ghost" size="icon" class="h-7 w-7 text-muted-foreground hover:text-destructive" onclick={() => onDelete(index)}>
84
- <X class="h-4 w-4" />
85
- </Button>
92
+ <!-- Lightbox dialog -->
93
+ {#if file.type === 'image'}
94
+ <Dialog.Root bind:open={lightboxOpen}>
95
+ <Dialog.Content class="max-w-[90vw] max-h-[90vh] p-2 bg-black/95 border-none">
96
+ <div class="checkered-lightbox flex items-center justify-center">
97
+ <img
98
+ src={file.url}
99
+ alt={file.alt || file.name}
100
+ class="max-w-full max-h-[85vh] object-contain"
101
+ />
86
102
  </div>
87
- {/if}
88
- </div>
103
+ </Dialog.Content>
104
+ </Dialog.Root>
105
+ {/if}
89
106
 
90
- <!-- Lightbox dialog -->
91
- {#if file.type === 'image'}
92
- <Dialog.Root bind:open={lightboxOpen}>
93
- <Dialog.Content class="max-w-[90vw] max-h-[90vh] p-2 bg-black/95 border-none">
94
- <div class="checkered-lightbox flex items-center justify-center">
107
+ <!-- Alt text edit dialog -->
108
+ <Dialog.Root bind:open={altDialogOpen}>
109
+ <Dialog.Content class="max-w-lg">
110
+ <Dialog.Header>
111
+ <Dialog.Title>Edytuj tekst alternatywny</Dialog.Title>
112
+ </Dialog.Header>
113
+ <div class="space-y-4 py-4">
114
+ {#if file.type === 'image'}
115
+ <div class="checkered-lightbox rounded-lg overflow-hidden">
95
116
  <img
96
117
  src={file.url}
97
118
  alt={file.alt || file.name}
98
- class="max-w-full max-h-[85vh] object-contain"
99
- />
100
- </div>
101
- </Dialog.Content>
102
- </Dialog.Root>
103
- {/if}
104
-
105
- <!-- Alt text edit dialog -->
106
- <Dialog.Root bind:open={altDialogOpen}>
107
- <Dialog.Content class="max-w-lg">
108
- <Dialog.Header>
109
- <Dialog.Title>Edytuj tekst alternatywny</Dialog.Title>
110
- </Dialog.Header>
111
- <div class="space-y-4 py-4">
112
- {#if file.type === 'image'}
113
- <div class="checkered-lightbox rounded-lg overflow-hidden">
114
- <img
115
- src={file.url}
116
- alt={file.alt || file.name}
117
- class="max-h-48 w-full object-contain"
118
- />
119
- </div>
120
- {/if}
121
- <div class="space-y-2">
122
- <Label for="alt-text">Tekst alternatywny</Label>
123
- <Textarea
124
- id="alt-text"
125
- bind:value={altValue}
126
- placeholder="Opisz zawartość obrazu..."
127
- class="min-h-[120px] resize-none"
119
+ class="max-h-48 w-full object-contain"
128
120
  />
129
- <p class="text-xs text-muted-foreground">
130
- Tekst alternatywny opisuje obraz dla osób niewidomych i wyszukiwarek.
131
- </p>
132
121
  </div>
122
+ {/if}
123
+ <div class="space-y-2">
124
+ <Label for="alt-text">Tekst alternatywny</Label>
125
+ <Textarea
126
+ id="alt-text"
127
+ bind:value={altValue}
128
+ placeholder="Opisz zawartość obrazu..."
129
+ class="min-h-[120px] resize-none"
130
+ />
131
+ <p class="text-xs text-muted-foreground">
132
+ Tekst alternatywny opisuje obraz dla osób niewidomych i wyszukiwarek.
133
+ </p>
133
134
  </div>
134
- <Dialog.Footer>
135
- <Button variant="outline" onclick={() => altDialogOpen = false}>Anuluj</Button>
136
- <Button onclick={() => saveAlt(file)} disabled={saving}>
137
- {saving ? 'Zapisuję...' : 'Zapisz'}
138
- </Button>
139
- </Dialog.Footer>
140
- </Dialog.Content>
141
- </Dialog.Root>
142
- {:else}
143
- <p class="text-sm text-muted-foreground">Plik nie istnieje.</p>
144
- {/if}
145
- {/await}
135
+ </div>
136
+ <Dialog.Footer>
137
+ <Button variant="outline" onclick={() => altDialogOpen = false}>Anuluj</Button>
138
+ <Button onclick={() => saveAlt(file)} disabled={saving}>
139
+ {saving ? 'Zapisuję...' : 'Zapisz'}
140
+ </Button>
141
+ </Dialog.Footer>
142
+ </Dialog.Content>
143
+ </Dialog.Root>
144
+ {:else if query.ready}
145
+ <p class="text-sm text-muted-foreground">Plik nie istnieje.</p>
146
+ {/if}
146
147
 
147
148
  <style>
148
149
  .checkered-lightbox {
@@ -19,25 +19,24 @@
19
19
  </script>
20
20
 
21
21
  <div class="border">
22
- {#await fileQuery}
22
+ {#if !fileQuery.ready}
23
23
  Loading...
24
- {:then file}
25
- {#if file}
26
- {#if file.type === 'image'}
27
- <Image data={file} class={className} />
28
- {:else if file.type === 'video'}
29
- {#if file.thumbnailUrl}
30
- <Image data={{ ...file, url: file.thumbnailUrl }} class={className} />
31
- {:else}
32
- <div class={cn('border-primary flex items-center justify-center border p-4', className)}>
33
- <VideoOff />
34
- </div>
35
- {/if}
24
+ {:else if fileQuery.current}
25
+ {@const file = fileQuery.current}
26
+ {#if file.type === 'image'}
27
+ <Image data={file} class={className} />
28
+ {:else if file.type === 'video'}
29
+ {#if file.thumbnailUrl}
30
+ <Image data={{ ...file, url: file.thumbnailUrl }} class={className} />
31
+ {:else}
32
+ <div class={cn('border-primary flex items-center justify-center border p-4', className)}>
33
+ <VideoOff />
34
+ </div>
36
35
  {/if}
37
- {:else}
38
- <div class={cn('border-primary flex items-center justify-center border p-4', className)}>
39
- <PhotoOff />
40
- </div>
41
36
  {/if}
42
- {/await}
37
+ {:else}
38
+ <div class={cn('border-primary flex items-center justify-center border p-4', className)}>
39
+ <PhotoOff />
40
+ </div>
41
+ {/if}
43
42
  </div>