includio-cms 0.5.2 → 0.5.3
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 +19 -0
- package/ROADMAP.md +13 -0
- package/dist/admin/client/entry/entry-form.svelte +1 -0
- package/dist/admin/client/entry/entry.svelte +130 -123
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +92 -9
- package/dist/admin/components/fields/blocks-field.svelte +142 -112
- package/dist/admin/components/fields/blocks-field.svelte.d.ts +10 -30
- package/dist/admin/components/fields/boolean-field.svelte +28 -38
- package/dist/admin/components/fields/boolean-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/checkboxes-field.svelte +12 -24
- package/dist/admin/components/fields/checkboxes-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/content-field.svelte +4 -17
- package/dist/admin/components/fields/content-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/date-field.svelte +8 -21
- package/dist/admin/components/fields/date-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/datetime-field.svelte +8 -21
- package/dist/admin/components/fields/datetime-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/field-renderer.svelte +32 -19
- package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -1
- package/dist/admin/components/fields/field-value-bridge.svelte +21 -0
- package/dist/admin/components/fields/field-value-bridge.svelte.d.ts +31 -0
- package/dist/admin/components/fields/fields-form.svelte +13 -10
- package/dist/admin/components/fields/file-field.svelte +12 -27
- package/dist/admin/components/fields/file-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/image-field.svelte +13 -28
- package/dist/admin/components/fields/image-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/media-field.svelte +15 -30
- package/dist/admin/components/fields/media-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/number-field.svelte +6 -20
- package/dist/admin/components/fields/number-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/object-field.svelte +26 -29
- package/dist/admin/components/fields/object-field.svelte.d.ts +11 -31
- package/dist/admin/components/fields/radio-field.svelte +8 -20
- package/dist/admin/components/fields/radio-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/relation-field.svelte +15 -30
- package/dist/admin/components/fields/relation-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/richtext-field.svelte +4 -17
- package/dist/admin/components/fields/richtext-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/select-field.svelte +14 -28
- package/dist/admin/components/fields/select-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/seo-field.svelte +5 -12
- package/dist/admin/components/fields/seo-field.svelte.d.ts +8 -28
- package/dist/admin/components/fields/simple-array-field.svelte +29 -42
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/slug-field.svelte +6 -11
- package/dist/admin/components/fields/slug-field.svelte.d.ts +6 -26
- package/dist/admin/components/fields/text-field-wrapper.svelte +22 -40
- package/dist/admin/components/fields/text-field.svelte +7 -19
- package/dist/admin/components/fields/text-field.svelte.d.ts +5 -27
- package/dist/admin/components/fields/url-field-wrapper.svelte +8 -3
- package/dist/admin/components/fields/url-field.svelte +294 -128
- package/dist/admin/components/fields/url-field.svelte.d.ts +5 -27
- package/dist/admin/components/layout/layout-renderer.svelte +8 -6
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +221 -31
- package/dist/admin/components/tiptap/content-editor.svelte +13 -2
- package/dist/admin/components/tiptap/inline-block-node.d.ts +1 -0
- package/dist/admin/components/tiptap/inline-block-node.js +18 -1
- package/dist/admin/components/tiptap/slash-command.js +2 -3
- package/dist/admin/components/tiptap/standalone-form.d.ts +7 -0
- package/dist/admin/components/tiptap/standalone-form.js +31 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +7 -0
- package/dist/admin/remote/entry.remote.js +16 -0
- package/dist/admin/styles/admin.css +10 -0
- package/dist/admin/utils/fieldCondition.d.ts +6 -0
- package/dist/admin/utils/fieldCondition.js +20 -0
- package/dist/components/ui/switch/index.d.ts +2 -0
- package/dist/components/ui/switch/index.js +4 -0
- package/dist/components/ui/switch/switch.svelte +26 -0
- package/dist/components/ui/switch/switch.svelte.d.ts +4 -0
- package/dist/core/fields/fieldSchemaToTs.js +15 -3
- package/dist/core/fields/formFieldSchemaToTs.js +22 -6
- package/dist/core/fields/urlUtils.d.ts +14 -0
- package/dist/core/fields/urlUtils.js +21 -0
- package/dist/core/server/fields/populateEntry.js +43 -0
- package/dist/core/server/fields/resolveImageFields.js +33 -1
- package/dist/core/server/fields/resolveRelationFields.js +46 -0
- package/dist/core/server/fields/resolveRichtextLinks.js +15 -1
- package/dist/core/server/fields/resolveUrlFields.js +65 -0
- package/dist/core/server/generator/formFieldSchemaToString.js +40 -9
- package/dist/core/server/generator/formFields.js +2 -0
- package/dist/core/server/generator/generator.js +25 -1
- package/dist/schemas/field/url.d.ts +2 -0
- package/dist/schemas/field/url.js +4 -2
- package/dist/types/fields.d.ts +9 -0
- package/dist/types/formFields.d.ts +15 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/updates/0.5.3/index.d.ts +2 -0
- package/dist/updates/0.5.3/index.js +19 -0
- package/dist/updates/index.js +2 -1
- package/package.json +2 -1
- package/dist/admin/components/fields/standalone-field-renderer.svelte +0 -148
- package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +0 -9
|
@@ -1,34 +1,42 @@
|
|
|
1
|
-
<script lang="ts"
|
|
2
|
-
type T = Record<string, unknown>;
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<script lang="ts" generics="T extends Record<string, unknown>">
|
|
1
|
+
<script lang="ts">
|
|
6
2
|
import type { SeoFieldData, UrlField, UrlFieldData } from '../../../types/fields.js';
|
|
7
|
-
import {
|
|
8
|
-
formFieldProxy,
|
|
9
|
-
type FormFieldProxy,
|
|
10
|
-
type FormPathLeaves,
|
|
11
|
-
type SuperForm
|
|
12
|
-
} from 'sveltekit-superforms';
|
|
13
3
|
import { getRemotes } from '../../../sveltekit/index.js';
|
|
14
4
|
import type { Entry } from '../../../types/entries.js';
|
|
15
|
-
import
|
|
16
|
-
import
|
|
5
|
+
import * as InputGroup from '../../../components/ui/input-group/index.js';
|
|
6
|
+
import Badge from '../../../components/ui/badge/badge.svelte';
|
|
17
7
|
import Checkbox from '../../../components/ui/checkbox/checkbox.svelte';
|
|
8
|
+
import Label from '../../../components/ui/label/label.svelte';
|
|
18
9
|
import { getContentLanguage } from '../../state/content-language.svelte.js';
|
|
19
10
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
20
11
|
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
12
|
+
import { isExternalUrl } from '../../../core/fields/urlUtils.js';
|
|
13
|
+
import GlobeIcon from '@tabler/icons-svelte/icons/world';
|
|
21
14
|
import LinkIcon from '@tabler/icons-svelte/icons/link';
|
|
22
|
-
import
|
|
15
|
+
import TextIcon from '@tabler/icons-svelte/icons/typography';
|
|
23
16
|
import XIcon from '@tabler/icons-svelte/icons/x';
|
|
17
|
+
import ExternalLinkIcon from '@tabler/icons-svelte/icons/external-link';
|
|
18
|
+
import ChevronDown from '@tabler/icons-svelte/icons/chevron-down';
|
|
19
|
+
import ChevronRight from '@tabler/icons-svelte/icons/chevron-right';
|
|
24
20
|
|
|
25
21
|
const remotes = getRemotes();
|
|
26
22
|
const contentLanguage = getContentLanguage();
|
|
27
23
|
const interfaceLanguage = useInterfaceLanguage();
|
|
28
24
|
|
|
29
25
|
const labels = {
|
|
30
|
-
linkText: { en: '
|
|
31
|
-
noTitle: { en: 'No title', pl: 'Bez tytułu' }
|
|
26
|
+
linkText: { en: 'Displayed link text', pl: 'Tekst wyświetlany jako link' },
|
|
27
|
+
noTitle: { en: 'No title', pl: 'Bez tytułu' },
|
|
28
|
+
placeholder: { en: 'Paste URL or search pages...', pl: 'Wklej adres lub szukaj stron...' },
|
|
29
|
+
openNewTab: { en: 'Open in new tab', pl: 'Otwórz w nowej karcie' },
|
|
30
|
+
autoExternal: { en: '(auto for external links)', pl: '(automatycznie dla linków zewnętrznych)' },
|
|
31
|
+
indexing: { en: 'Indexing', pl: 'Indeksowanie' },
|
|
32
|
+
nofollowLabel: { en: 'nofollow', pl: 'nofollow' },
|
|
33
|
+
nofollowDesc: { en: "Don't pass SEO authority. Check for untrusted links.", pl: 'Nie przekazuj autorytetu SEO. Zaznacz dla linków, którym nie chcesz ufać.' },
|
|
34
|
+
sponsoredLabel: { en: 'sponsored', pl: 'sponsored' },
|
|
35
|
+
sponsoredDesc: { en: 'Sponsored or advertising link.', pl: 'Link reklamowy lub sponsorowany.' },
|
|
36
|
+
ugcLabel: { en: 'ugc', pl: 'ugc' },
|
|
37
|
+
ugcDesc: { en: 'User-generated content link (e.g. comment).', pl: 'Link dodany przez użytkownika (np. w komentarzu).' },
|
|
38
|
+
external: { en: 'External', pl: 'Zewnętrzny' },
|
|
39
|
+
internal: { en: 'Internal', pl: 'Wewnętrzny' }
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
type EntryWithSeo = Entry & {
|
|
@@ -40,30 +48,37 @@
|
|
|
40
48
|
|
|
41
49
|
type Props = {
|
|
42
50
|
field: UrlField;
|
|
43
|
-
|
|
44
|
-
path: FormPathLeaves<T, UrlFieldData>;
|
|
51
|
+
value: UrlFieldData;
|
|
45
52
|
};
|
|
46
53
|
|
|
47
|
-
let { field,
|
|
54
|
+
let { field, value = $bindable(), ...props }: Props = $props();
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
let isLinked = $derived(value.id != null && value.id.length > 0);
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
// Current URL for the active language
|
|
59
|
+
let currentUrl = $derived(value.url[contentLanguage.current] ?? '');
|
|
60
|
+
let currentIsExternal = $derived(currentUrl ? isExternalUrl(currentUrl) : false);
|
|
52
61
|
|
|
62
|
+
// Autocomplete state
|
|
63
|
+
let autocompleteSuggestions = $state<EntryWithSeo[]>([]);
|
|
64
|
+
let popoverOpen = $state(false);
|
|
65
|
+
let activeIndex = $state(-1);
|
|
66
|
+
let searchTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
67
|
+
let linkedEntry = $state<(Entry & { data: { seo?: SeoFieldData } }) | null>(null);
|
|
68
|
+
|
|
69
|
+
// Rel collapsible
|
|
70
|
+
let relOpen = $state(false);
|
|
71
|
+
let relNofollow = $derived((value.rel ?? '').includes('nofollow'));
|
|
72
|
+
let relSponsored = $derived((value.rel ?? '').includes('sponsored'));
|
|
73
|
+
let relUgc = $derived((value.rel ?? '').includes('ugc'));
|
|
74
|
+
|
|
75
|
+
// Fetch linked entry when linked
|
|
53
76
|
$effect(() => {
|
|
54
77
|
if (isLinked) {
|
|
55
|
-
fetchLinkedEntry(
|
|
78
|
+
fetchLinkedEntry(value.id).then((entry) => {
|
|
56
79
|
linkedEntry = entry;
|
|
57
80
|
});
|
|
58
81
|
}
|
|
59
|
-
|
|
60
|
-
if (!isLinked && $value.url && Object.values($value.url).some((url) => url.length > 2)) {
|
|
61
|
-
fetchSuggestions($value.url).then((suggestions) => {
|
|
62
|
-
autocompleteSuggestions = suggestions;
|
|
63
|
-
});
|
|
64
|
-
} else if (!isLinked) {
|
|
65
|
-
autocompleteSuggestions = [];
|
|
66
|
-
}
|
|
67
82
|
});
|
|
68
83
|
|
|
69
84
|
async function fetchLinkedEntry(id: string | undefined) {
|
|
@@ -71,135 +86,286 @@
|
|
|
71
86
|
return remotes.getEntry({ id });
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (url.length > 2) {
|
|
83
|
-
const [slugResults, titleResults] = await Promise.all([
|
|
84
|
-
remotes.getEntries({ dataLike: { seo: { slug: url } } }),
|
|
85
|
-
remotes.getEntries({ dataLike: { seo: { title: url } } })
|
|
86
|
-
]) as [EntryWithSeo[], EntryWithSeo[]];
|
|
87
|
-
|
|
88
|
-
suggestions = [...suggestions, ...slugResults, ...titleResults];
|
|
89
|
-
suggestions = suggestions.filter(
|
|
90
|
-
(entry, index, self) => index === self.findIndex((e) => e.id === entry.id)
|
|
91
|
-
);
|
|
92
|
-
suggestions = suggestions.slice(0, 5);
|
|
93
|
-
}
|
|
89
|
+
function searchEntries() {
|
|
90
|
+
if (searchTimeout) clearTimeout(searchTimeout);
|
|
91
|
+
const url = value.url[contentLanguage.current] ?? '';
|
|
92
|
+
if (isLinked || url.length < 3) {
|
|
93
|
+
autocompleteSuggestions = [];
|
|
94
|
+
popoverOpen = false;
|
|
95
|
+
return;
|
|
94
96
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
searchTimeout = setTimeout(async () => {
|
|
98
|
+
const [slugResults, titleResults] = await Promise.all([
|
|
99
|
+
remotes.getEntries({ dataLike: { seo: { slug: url } } }),
|
|
100
|
+
remotes.getEntries({ dataLike: { seo: { title: url } } })
|
|
101
|
+
]) as [EntryWithSeo[], EntryWithSeo[]];
|
|
102
|
+
const combined = [...slugResults, ...titleResults];
|
|
103
|
+
const deduped = combined.filter(
|
|
104
|
+
(entry, index, self) => index === self.findIndex((e) => e.id === entry.id)
|
|
105
|
+
);
|
|
106
|
+
autocompleteSuggestions = deduped.slice(0, 5);
|
|
107
|
+
popoverOpen = autocompleteSuggestions.length > 0;
|
|
108
|
+
activeIndex = -1;
|
|
109
|
+
}, 300);
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
|
|
112
|
+
function linkEntry(entry: EntryWithSeo) {
|
|
113
|
+
value.id = entry.id;
|
|
114
|
+
linkedEntry = entry;
|
|
115
|
+
autocompleteSuggestions = [];
|
|
116
|
+
popoverOpen = false;
|
|
117
|
+
}
|
|
100
118
|
|
|
101
119
|
function unlinkEntry() {
|
|
102
|
-
|
|
120
|
+
value.id = '';
|
|
103
121
|
linkedEntry = null;
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
124
|
+
function handleUrlKeydown(e: KeyboardEvent) {
|
|
125
|
+
if (!popoverOpen || autocompleteSuggestions.length === 0) return;
|
|
126
|
+
|
|
127
|
+
switch (e.key) {
|
|
128
|
+
case 'ArrowDown':
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
activeIndex = (activeIndex + 1) % autocompleteSuggestions.length;
|
|
131
|
+
break;
|
|
132
|
+
case 'ArrowUp':
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
activeIndex = activeIndex <= 0 ? autocompleteSuggestions.length - 1 : activeIndex - 1;
|
|
135
|
+
break;
|
|
136
|
+
case 'Enter':
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
if (activeIndex >= 0 && activeIndex < autocompleteSuggestions.length) {
|
|
139
|
+
linkEntry(autocompleteSuggestions[activeIndex]);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
case 'Escape':
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
popoverOpen = false;
|
|
145
|
+
activeIndex = -1;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function updateRel(token: string, add: boolean) {
|
|
151
|
+
const current = (value.rel ?? '').split(/\s+/).filter(Boolean);
|
|
152
|
+
if (add && !current.includes(token)) {
|
|
153
|
+
current.push(token);
|
|
154
|
+
} else if (!add) {
|
|
155
|
+
const idx = current.indexOf(token);
|
|
156
|
+
if (idx >= 0) current.splice(idx, 1);
|
|
157
|
+
}
|
|
158
|
+
value.rel = current.join(' ');
|
|
109
159
|
}
|
|
110
160
|
</script>
|
|
111
161
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
<LinkIcon class="
|
|
119
|
-
|
|
120
|
-
|
|
162
|
+
<!-- URL input -->
|
|
163
|
+
<div class="relative">
|
|
164
|
+
{#if isLinked && linkedEntry}
|
|
165
|
+
<!-- Linked state: Badge chip -->
|
|
166
|
+
<InputGroup.Root>
|
|
167
|
+
<InputGroup.Addon align="inline-start">
|
|
168
|
+
<LinkIcon class="size-4" />
|
|
169
|
+
</InputGroup.Addon>
|
|
170
|
+
<div class="flex min-w-0 flex-1 items-center gap-2 py-1.5">
|
|
171
|
+
<Badge variant="secondary" class="max-w-full gap-1.5 truncate">
|
|
172
|
+
<span class="truncate font-medium">
|
|
121
173
|
{linkedEntry.data.seo?.title || linkedEntry.id}
|
|
122
174
|
</span>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
175
|
+
{#if linkedEntry.data.seo?.slug}
|
|
176
|
+
<span class="text-muted-foreground truncate text-[10px] font-normal">
|
|
177
|
+
{linkedEntry.data.seo.slug}
|
|
178
|
+
</span>
|
|
179
|
+
{/if}
|
|
180
|
+
</Badge>
|
|
181
|
+
</div>
|
|
182
|
+
<InputGroup.Addon align="inline-end">
|
|
127
183
|
<button
|
|
128
184
|
type="button"
|
|
129
|
-
class="text-muted-foreground hover:text-foreground hover:bg-muted
|
|
185
|
+
class="text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-0.5 transition-colors"
|
|
130
186
|
onclick={unlinkEntry}
|
|
187
|
+
aria-label={getLocalizedLabel({ en: 'Remove link', pl: 'Usuń powiązanie' }, interfaceLanguage.current)}
|
|
131
188
|
>
|
|
132
189
|
<XIcon class="size-3.5" />
|
|
133
190
|
</button>
|
|
191
|
+
</InputGroup.Addon>
|
|
192
|
+
</InputGroup.Root>
|
|
193
|
+
{:else}
|
|
194
|
+
{#each contentLanguage.all as lang}
|
|
195
|
+
<div class={contentLanguage.current === lang ? '' : 'hidden'}>
|
|
196
|
+
<InputGroup.Root>
|
|
197
|
+
<InputGroup.Addon align="inline-start">
|
|
198
|
+
<GlobeIcon class="size-4" />
|
|
199
|
+
</InputGroup.Addon>
|
|
200
|
+
<InputGroup.Input
|
|
201
|
+
bind:value={value.url[lang]}
|
|
202
|
+
type="text"
|
|
203
|
+
placeholder={field.placeholder?.[lang] || getLocalizedLabel(labels.placeholder, interfaceLanguage.current)}
|
|
204
|
+
oninput={searchEntries}
|
|
205
|
+
onkeydown={handleUrlKeydown}
|
|
206
|
+
role="combobox"
|
|
207
|
+
aria-expanded={popoverOpen}
|
|
208
|
+
aria-controls="url-suggestions"
|
|
209
|
+
aria-autocomplete="list"
|
|
210
|
+
aria-activedescendant={activeIndex >= 0 ? `url-suggestion-${activeIndex}` : undefined}
|
|
211
|
+
/>
|
|
212
|
+
{#if currentUrl && contentLanguage.current === lang}
|
|
213
|
+
<InputGroup.Addon align="inline-end">
|
|
214
|
+
{#if currentIsExternal}
|
|
215
|
+
<Badge variant="outline" class="text-[10px]">
|
|
216
|
+
<ExternalLinkIcon class="size-3" />
|
|
217
|
+
{getLocalizedLabel(labels.external, interfaceLanguage.current)}
|
|
218
|
+
</Badge>
|
|
219
|
+
{:else if currentUrl.length > 0}
|
|
220
|
+
<Badge variant="secondary" class="text-[10px]">
|
|
221
|
+
<LinkIcon class="size-3" />
|
|
222
|
+
{getLocalizedLabel(labels.internal, interfaceLanguage.current)}
|
|
223
|
+
</Badge>
|
|
224
|
+
{/if}
|
|
225
|
+
</InputGroup.Addon>
|
|
226
|
+
{/if}
|
|
227
|
+
</InputGroup.Root>
|
|
134
228
|
</div>
|
|
135
|
-
{
|
|
136
|
-
|
|
229
|
+
{/each}
|
|
230
|
+
{/if}
|
|
231
|
+
|
|
232
|
+
<!-- Floating autocomplete dropdown -->
|
|
233
|
+
{#if popoverOpen && autocompleteSuggestions.length > 0}
|
|
234
|
+
<div
|
|
235
|
+
id="url-suggestions"
|
|
236
|
+
role="listbox"
|
|
237
|
+
class="bg-popover text-popover-foreground absolute top-full right-0 left-0 z-50 overflow-hidden rounded-md border shadow-md"
|
|
238
|
+
>
|
|
239
|
+
{#each autocompleteSuggestions as suggestion, i}
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
id="url-suggestion-{i}"
|
|
243
|
+
role="option"
|
|
244
|
+
aria-selected={i === activeIndex}
|
|
245
|
+
class="hover:bg-muted flex w-full items-center gap-2 px-3 py-2 text-left transition-colors {i === activeIndex ? 'bg-muted' : ''} {i < autocompleteSuggestions.length - 1 ? 'border-border/50 border-b' : ''}"
|
|
246
|
+
onclick={() => linkEntry(suggestion)}
|
|
247
|
+
>
|
|
248
|
+
<LinkIcon class="text-muted-foreground size-3.5 shrink-0" />
|
|
249
|
+
<span class="truncate text-sm">
|
|
250
|
+
{suggestion.data.seo?.title || getLocalizedLabel(labels.noTitle, interfaceLanguage.current)}
|
|
251
|
+
</span>
|
|
252
|
+
<span class="text-muted-foreground ml-auto shrink-0 text-xs">
|
|
253
|
+
{suggestion.data.seo?.slug || ''}
|
|
254
|
+
</span>
|
|
255
|
+
</button>
|
|
256
|
+
{/each}
|
|
257
|
+
</div>
|
|
258
|
+
{/if}
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<!-- Options (text, newTab, rel) -->
|
|
262
|
+
{#if (field.text || field.newTab || field.rel) && (field.text && value.text || field.newTab || field.rel)}
|
|
263
|
+
<div class="mt-2 space-y-2">
|
|
264
|
+
<!-- Link text -->
|
|
265
|
+
{#if field.text && value.text}
|
|
137
266
|
{#each contentLanguage.all as lang}
|
|
138
267
|
<div class={contentLanguage.current === lang ? '' : 'hidden'}>
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
268
|
+
<InputGroup.Root>
|
|
269
|
+
<InputGroup.Addon align="inline-start">
|
|
270
|
+
<TextIcon class="size-4" />
|
|
271
|
+
</InputGroup.Addon>
|
|
272
|
+
<InputGroup.Input
|
|
273
|
+
bind:value={value.text[lang]}
|
|
274
|
+
type="text"
|
|
275
|
+
placeholder={getLocalizedLabel(labels.linkText, interfaceLanguage.current)}
|
|
276
|
+
/>
|
|
277
|
+
</InputGroup.Root>
|
|
145
278
|
</div>
|
|
146
279
|
{/each}
|
|
147
280
|
{/if}
|
|
148
281
|
|
|
149
|
-
<!--
|
|
150
|
-
{#if
|
|
151
|
-
<div class="
|
|
152
|
-
{#
|
|
282
|
+
<!-- New tab + rel row -->
|
|
283
|
+
{#if field.newTab || field.rel}
|
|
284
|
+
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 px-1">
|
|
285
|
+
{#if field.newTab}
|
|
286
|
+
<label class="flex cursor-pointer items-center gap-2">
|
|
287
|
+
<Checkbox
|
|
288
|
+
checked={value.newTab ?? false}
|
|
289
|
+
onCheckedChange={(v) => (value.newTab = v === true)}
|
|
290
|
+
/>
|
|
291
|
+
<span class="text-sm">
|
|
292
|
+
{getLocalizedLabel(labels.openNewTab, interfaceLanguage.current)}
|
|
293
|
+
</span>
|
|
294
|
+
{#if currentIsExternal}
|
|
295
|
+
<span class="text-muted-foreground text-xs">
|
|
296
|
+
{getLocalizedLabel(labels.autoExternal, interfaceLanguage.current)}
|
|
297
|
+
</span>
|
|
298
|
+
{/if}
|
|
299
|
+
</label>
|
|
300
|
+
{/if}
|
|
301
|
+
|
|
302
|
+
{#if field.rel}
|
|
153
303
|
<button
|
|
154
304
|
type="button"
|
|
155
|
-
class="hover:
|
|
156
|
-
onclick={() =>
|
|
305
|
+
class="text-muted-foreground hover:text-foreground flex items-center gap-1 text-sm transition-colors"
|
|
306
|
+
onclick={() => (relOpen = !relOpen)}
|
|
307
|
+
aria-expanded={relOpen}
|
|
157
308
|
>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</span>
|
|
309
|
+
{#if relOpen}
|
|
310
|
+
<ChevronDown class="size-3.5" />
|
|
311
|
+
{:else}
|
|
312
|
+
<ChevronRight class="size-3.5" />
|
|
313
|
+
{/if}
|
|
314
|
+
{getLocalizedLabel(labels.indexing, interfaceLanguage.current)}
|
|
165
315
|
</button>
|
|
166
|
-
{/
|
|
316
|
+
{/if}
|
|
167
317
|
</div>
|
|
168
318
|
{/if}
|
|
169
|
-
</div>
|
|
170
319
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
320
|
+
<!-- Rel details (collapsible) -->
|
|
321
|
+
{#if field.rel && relOpen}
|
|
322
|
+
<div class="border-border space-y-3 rounded-lg border px-3 py-3">
|
|
323
|
+
<div>
|
|
324
|
+
<div class="flex items-center gap-2">
|
|
325
|
+
<Checkbox
|
|
326
|
+
id="url-rel-nofollow"
|
|
327
|
+
checked={relNofollow}
|
|
328
|
+
onCheckedChange={(v) => updateRel('nofollow', v === true)}
|
|
329
|
+
/>
|
|
330
|
+
<Label for="url-rel-nofollow" class="cursor-pointer font-normal">
|
|
331
|
+
{getLocalizedLabel(labels.nofollowLabel, interfaceLanguage.current)}
|
|
332
|
+
</Label>
|
|
333
|
+
</div>
|
|
334
|
+
<p class="text-muted-foreground mt-1 ml-6 text-xs">
|
|
335
|
+
{getLocalizedLabel(labels.nofollowDesc, interfaceLanguage.current)}
|
|
336
|
+
</p>
|
|
186
337
|
</div>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
</
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
338
|
+
<div>
|
|
339
|
+
<div class="flex items-center gap-2">
|
|
340
|
+
<Checkbox
|
|
341
|
+
id="url-rel-sponsored"
|
|
342
|
+
checked={relSponsored}
|
|
343
|
+
onCheckedChange={(v) => updateRel('sponsored', v === true)}
|
|
344
|
+
/>
|
|
345
|
+
<Label for="url-rel-sponsored" class="cursor-pointer font-normal">
|
|
346
|
+
{getLocalizedLabel(labels.sponsoredLabel, interfaceLanguage.current)}
|
|
347
|
+
</Label>
|
|
348
|
+
</div>
|
|
349
|
+
<p class="text-muted-foreground mt-1 ml-6 text-xs">
|
|
350
|
+
{getLocalizedLabel(labels.sponsoredDesc, interfaceLanguage.current)}
|
|
351
|
+
</p>
|
|
352
|
+
</div>
|
|
353
|
+
<div>
|
|
354
|
+
<div class="flex items-center gap-2">
|
|
355
|
+
<Checkbox
|
|
356
|
+
id="url-rel-ugc"
|
|
357
|
+
checked={relUgc}
|
|
358
|
+
onCheckedChange={(v) => updateRel('ugc', v === true)}
|
|
359
|
+
/>
|
|
360
|
+
<Label for="url-rel-ugc" class="cursor-pointer font-normal">
|
|
361
|
+
{getLocalizedLabel(labels.ugcLabel, interfaceLanguage.current)}
|
|
362
|
+
</Label>
|
|
363
|
+
</div>
|
|
364
|
+
<p class="text-muted-foreground mt-1 ml-6 text-xs">
|
|
365
|
+
{getLocalizedLabel(labels.ugcDesc, interfaceLanguage.current)}
|
|
366
|
+
</p>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
{/if}
|
|
370
|
+
</div>
|
|
371
|
+
{/if}
|
|
@@ -1,30 +1,8 @@
|
|
|
1
1
|
import type { UrlField, UrlFieldData } from '../../../types/fields.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
field: UrlField;
|
|
6
|
-
form: SuperForm<T>;
|
|
7
|
-
path: FormPathLeaves<T, UrlFieldData>;
|
|
8
|
-
};
|
|
9
|
-
exports: {};
|
|
10
|
-
bindings: "";
|
|
11
|
-
slots: {};
|
|
12
|
-
events: {};
|
|
2
|
+
type Props = {
|
|
3
|
+
field: UrlField;
|
|
4
|
+
value: UrlFieldData;
|
|
13
5
|
};
|
|
14
|
-
declare
|
|
15
|
-
|
|
16
|
-
events(): ReturnType<typeof $$render<T>>['events'];
|
|
17
|
-
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
18
|
-
bindings(): "";
|
|
19
|
-
exports(): {};
|
|
20
|
-
}
|
|
21
|
-
interface $$IsomorphicComponent {
|
|
22
|
-
new <T extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
23
|
-
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
24
|
-
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
25
|
-
<T extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
26
|
-
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
27
|
-
}
|
|
28
|
-
declare const UrlField: $$IsomorphicComponent;
|
|
29
|
-
type UrlField<T extends Record<string, unknown>> = InstanceType<typeof UrlField<T>>;
|
|
6
|
+
declare const UrlField: import("svelte").Component<Props, {}, "value">;
|
|
7
|
+
type UrlField = ReturnType<typeof UrlField>;
|
|
30
8
|
export default UrlField;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { SuperForm } from 'sveltekit-superforms';
|
|
5
5
|
import { isLayoutLeaf, isLayoutBranch } from '../../../types/layout.js';
|
|
6
6
|
import FieldRenderer from '../fields/field-renderer.svelte';
|
|
7
|
+
import { evaluateCondition } from '../../utils/fieldCondition.js';
|
|
7
8
|
import * as Accordion from '../../../components/ui/accordion/index.js';
|
|
8
9
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
9
10
|
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
}: Props = $props();
|
|
29
30
|
|
|
30
31
|
const interfaceLanguage = useInterfaceLanguage();
|
|
32
|
+
const { form: formData } = form;
|
|
31
33
|
|
|
32
34
|
const fieldMap = $derived(new Map(fields.map((f) => [f.slug, f])));
|
|
33
35
|
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
<div class="layout-fields-stack">
|
|
61
63
|
{#each node.fields as slug (slug)}
|
|
62
64
|
{@const field = fieldMap.get(slug)}
|
|
63
|
-
{#if field}
|
|
65
|
+
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
64
66
|
<div
|
|
65
67
|
data-field-path={slug}
|
|
66
68
|
class={cn(
|
|
@@ -117,7 +119,7 @@
|
|
|
117
119
|
<div class="layout-auto-grid">
|
|
118
120
|
{#each node.fields as slug (slug)}
|
|
119
121
|
{@const field = fieldMap.get(slug)}
|
|
120
|
-
{#if field}
|
|
122
|
+
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
121
123
|
<div
|
|
122
124
|
data-field-path={slug}
|
|
123
125
|
class={cn(
|
|
@@ -135,7 +137,7 @@
|
|
|
135
137
|
<div class="layout-fields-stack">
|
|
136
138
|
{#each node.fields as slug (slug)}
|
|
137
139
|
{@const field = fieldMap.get(slug)}
|
|
138
|
-
{#if field}
|
|
140
|
+
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
139
141
|
<div
|
|
140
142
|
data-field-path={slug}
|
|
141
143
|
class={cn(
|
|
@@ -174,7 +176,7 @@
|
|
|
174
176
|
<div class="layout-fields-stack">
|
|
175
177
|
{#each node.fields as slug (slug)}
|
|
176
178
|
{@const field = fieldMap.get(slug)}
|
|
177
|
-
{#if field}
|
|
179
|
+
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
178
180
|
<div
|
|
179
181
|
data-field-path={slug}
|
|
180
182
|
class={cn(
|
|
@@ -208,7 +210,7 @@
|
|
|
208
210
|
{#if isLayoutLeaf(node)}
|
|
209
211
|
{#each node.fields as slug (slug)}
|
|
210
212
|
{@const field = fieldMap.get(slug)}
|
|
211
|
-
{#if field}
|
|
213
|
+
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
212
214
|
<div
|
|
213
215
|
data-field-path={slug}
|
|
214
216
|
class={cn(
|
|
@@ -334,7 +336,7 @@
|
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
/* ═══════════ RESPONSIVE ═══════════ */
|
|
337
|
-
@
|
|
339
|
+
@container (max-width: 768px) {
|
|
338
340
|
.layout-columns {
|
|
339
341
|
grid-template-columns: 1fr !important;
|
|
340
342
|
}
|