includio-cms 0.7.2 → 0.13.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.
- package/CHANGELOG.md +128 -0
- package/ROADMAP.md +54 -2
- package/dist/admin/api/generate-styles.d.ts +2 -0
- package/dist/admin/api/generate-styles.js +32 -0
- package/dist/admin/api/handler.js +33 -0
- package/dist/admin/api/media-gc.js +10 -4
- package/dist/admin/api/rest/handler.js +17 -0
- package/dist/admin/api/rest/routes/collections.js +25 -13
- package/dist/admin/api/rest/routes/entries.d.ts +1 -1
- package/dist/admin/api/rest/routes/entries.js +10 -10
- package/dist/admin/api/rest/routes/media.d.ts +2 -0
- package/dist/admin/api/rest/routes/media.js +9 -0
- package/dist/admin/api/rest/routes/schema.d.ts +5 -0
- package/dist/admin/api/rest/routes/schema.js +152 -0
- package/dist/admin/api/rest/routes/singletons.d.ts +1 -1
- package/dist/admin/api/rest/routes/singletons.js +8 -7
- package/dist/admin/api/rest/routes/upload.d.ts +2 -0
- package/dist/admin/api/rest/routes/upload.js +28 -0
- package/dist/admin/api/upload.js +13 -0
- package/dist/admin/client/collection/collection-entries.svelte +35 -13
- package/dist/admin/client/entry/entry.svelte +21 -23
- package/dist/admin/client/entry/header/a11y-validator.js +2 -2
- package/dist/admin/client/entry/header/publish-panel.svelte +33 -85
- package/dist/admin/client/entry/header/status-badge.svelte +2 -2
- package/dist/admin/client/entry/header/version-history-sheet.svelte +9 -9
- package/dist/admin/client/entry/header/visibility.svelte +16 -10
- package/dist/admin/client/entry/utils.d.ts +3 -0
- package/dist/admin/client/entry/utils.js +22 -4
- package/dist/admin/client/form/form-submission/form-submission-page.svelte +4 -1
- package/dist/admin/client/form/form-submission/submission-field.svelte +10 -0
- package/dist/admin/client/index.d.ts +1 -0
- package/dist/admin/client/index.js +1 -0
- package/dist/admin/client/maintenance/maintenance-page.svelte +146 -2
- package/dist/admin/client/users/users-page.svelte +5 -6
- package/dist/admin/client/users/users-page.svelte.d.ts +1 -4
- package/dist/admin/components/fields/block-picker-modal.svelte +13 -4
- package/dist/admin/components/fields/blocks-field.svelte +40 -19
- package/dist/admin/components/fields/field-renderer.svelte +4 -8
- package/dist/admin/components/fields/object-field.svelte +7 -12
- package/dist/admin/components/fields/select-field.svelte +8 -2
- package/dist/admin/components/fields/seo-field.svelte +40 -93
- package/dist/admin/components/fields/simple-array-field.svelte +27 -16
- package/dist/admin/components/fields/text-field-wrapper.svelte +52 -197
- package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
- package/dist/admin/components/fields/url-field-wrapper.svelte +15 -25
- package/dist/admin/components/fields/url-field.svelte +61 -72
- package/dist/admin/components/layout/layout-renderer.svelte +10 -4
- package/dist/admin/components/media/file-preview.svelte +10 -1
- package/dist/admin/components/media/file-upload.svelte +5 -1
- package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
- package/dist/admin/components/media/files-list.svelte +12 -3
- package/dist/admin/components/media/media-library.svelte +109 -37
- package/dist/admin/components/media/media-selector.svelte +90 -16
- package/dist/admin/components/media/tag-sidebar.svelte +10 -6
- package/dist/admin/components/media/tag-sidebar.svelte.d.ts +7 -2
- package/dist/admin/components/tiptap/FigureNodeView.svelte +15 -10
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +53 -94
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte +8 -3
- package/dist/admin/components/tiptap/editor-toolbar.svelte +28 -23
- package/dist/admin/components/tiptap/image-dialog.svelte +12 -7
- package/dist/admin/components/tiptap/inline-block-node.js +6 -5
- package/dist/admin/components/tiptap/lang.d.ts +77 -0
- package/dist/admin/components/tiptap/lang.js +170 -0
- package/dist/admin/components/tiptap/link-dialog.svelte +31 -28
- package/dist/admin/components/tiptap/slash-command.js +27 -23
- package/dist/admin/components/tiptap/table-dialog.svelte +9 -4
- package/dist/admin/components/tiptap/video-dialog.svelte +6 -1
- package/dist/admin/remote/email.remote.d.ts +1 -0
- package/dist/admin/remote/email.remote.js +5 -0
- package/dist/admin/remote/entry.remote.d.ts +2 -5
- package/dist/admin/remote/entry.remote.js +23 -28
- package/dist/admin/remote/index.d.ts +1 -0
- package/dist/admin/remote/index.js +1 -0
- package/dist/admin/remote/media.remote.d.ts +15 -0
- package/dist/admin/remote/media.remote.js +18 -2
- package/dist/admin/remote/preview.remote.js +3 -1
- package/dist/admin/utils/entryLabel.js +9 -6
- package/dist/admin/utils/translationStatus.js +1 -2
- package/dist/cli/scaffold/admin.js +34 -2
- package/dist/cms/runtime/api.d.ts +16 -12
- package/dist/cms/runtime/api.js +7 -6
- package/dist/cms/runtime/remote.js +2 -2
- package/dist/cms/runtime/schemas.d.ts +1 -1
- package/dist/cms/runtime/schemas.js +1 -1
- package/dist/cms/runtime/types.d.ts +118 -112
- package/dist/cms/runtime/types.js +0 -12
- package/dist/core/cms.d.ts +3 -1
- package/dist/core/cms.js +30 -0
- package/dist/core/fields/fieldSchemaToTs.js +9 -15
- package/dist/core/fields/formFieldSchemaToTs.js +7 -0
- package/dist/core/server/entries/operations/create.js +10 -4
- package/dist/core/server/entries/operations/get.d.ts +1 -0
- package/dist/core/server/entries/operations/get.js +186 -191
- package/dist/core/server/entries/operations/update.d.ts +6 -7
- package/dist/core/server/entries/operations/update.js +20 -38
- package/dist/core/server/fields/populateEntry.js +16 -52
- package/dist/core/server/fields/resolveImageFields.js +69 -120
- package/dist/core/server/fields/resolveRelationFields.js +30 -51
- package/dist/core/server/fields/resolveRichtextLinks.js +46 -100
- package/dist/core/server/fields/resolveTypographyOrphans.bench.d.ts +1 -0
- package/dist/core/server/fields/resolveTypographyOrphans.bench.js +87 -0
- package/dist/core/server/fields/resolveTypographyOrphans.d.ts +3 -0
- package/dist/core/server/fields/resolveTypographyOrphans.js +128 -0
- package/dist/core/server/fields/resolveUrlFields.js +47 -56
- package/dist/core/server/fields/utils/fixOrphans.d.ts +5 -0
- package/dist/core/server/fields/utils/fixOrphans.js +12 -0
- package/dist/core/server/fields/utils/imageStyles.d.ts +4 -2
- package/dist/core/server/fields/utils/imageStyles.js +41 -25
- package/dist/core/server/fields/utils/resolveMedia.js +1 -6
- package/dist/core/server/forms/submissions/operations/delete.js +26 -2
- package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +2 -0
- package/dist/core/server/forms/submissions/utils/parseMultipart.js +75 -0
- package/dist/core/server/generator/fields.d.ts +6 -0
- package/dist/core/server/generator/fields.js +43 -5
- package/dist/core/server/generator/formFieldSchemaToString.js +10 -0
- package/dist/core/server/generator/formFields.js +1 -0
- package/dist/core/server/generator/generator.js +98 -30
- package/dist/core/server/media/operations/getFiles.d.ts +5 -0
- package/dist/core/server/media/operations/getFiles.js +6 -0
- package/dist/core/server/media/operations/uploadPrivateFile.d.ts +4 -0
- package/dist/core/server/media/operations/uploadPrivateFile.js +8 -0
- package/dist/core/server/media/styles/operations/batchGenerateStyles.d.ts +16 -0
- package/dist/core/server/media/styles/operations/batchGenerateStyles.js +144 -0
- package/dist/db-postgres/index.js +303 -37
- package/dist/db-postgres/schema/entry.d.ts +0 -94
- package/dist/db-postgres/schema/entry.js +0 -6
- package/dist/db-postgres/schema/entryVersion.d.ts +17 -0
- package/dist/db-postgres/schema/entryVersion.js +1 -0
- package/dist/entity/index.d.ts +9 -4
- package/dist/entity/index.js +24 -24
- package/dist/files-local/index.js +43 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/sveltekit/components/preview.svelte +2 -326
- package/dist/sveltekit/components/preview.svelte.d.ts +5 -16
- package/dist/sveltekit/server/index.d.ts +2 -1
- package/dist/sveltekit/server/index.js +2 -1
- package/dist/sveltekit/server/preview.js +4 -7
- package/dist/types/adapters/db.d.ts +15 -1
- package/dist/types/adapters/files.d.ts +6 -0
- package/dist/types/cms.d.ts +5 -0
- package/dist/types/entries.d.ts +54 -18
- package/dist/types/fields.d.ts +14 -24
- package/dist/types/formFields.d.ts +7 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/layout.d.ts +0 -1
- package/dist/types/structured-content.d.ts +5 -0
- package/dist/updates/0.10.0/index.d.ts +2 -0
- package/dist/updates/0.10.0/index.js +15 -0
- package/dist/updates/0.11.0/index.d.ts +2 -0
- package/dist/updates/0.11.0/index.js +12 -0
- package/dist/updates/0.12.0/index.d.ts +2 -0
- package/dist/updates/0.12.0/index.js +12 -0
- package/dist/updates/0.13.0/index.d.ts +2 -0
- package/dist/updates/0.13.0/index.js +10 -0
- package/dist/updates/0.13.1/index.d.ts +2 -0
- package/dist/updates/0.13.1/index.js +20 -0
- package/dist/updates/0.7.3/index.d.ts +2 -0
- package/dist/updates/0.7.3/index.js +10 -0
- package/dist/updates/0.8.0/index.d.ts +2 -0
- package/dist/updates/0.8.0/index.js +18 -0
- package/dist/updates/0.8.0/migrate.d.ts +2 -0
- package/dist/updates/0.8.0/migrate.js +101 -0
- package/dist/updates/0.9.0/index.d.ts +2 -0
- package/dist/updates/0.9.0/index.js +38 -0
- package/dist/updates/index.js +9 -1
- package/package.json +7 -6
- package/dist/admin/components/fields/image-field.svelte +0 -198
- package/dist/admin/components/fields/image-field.svelte.d.ts +0 -8
- package/dist/admin/components/fields/richtext-field.svelte +0 -13
- package/dist/admin/components/fields/richtext-field.svelte.d.ts +0 -8
- package/dist/admin/components/tiptap.svelte +0 -11
- package/dist/admin/components/tiptap.svelte.d.ts +0 -6
- package/dist/core/server/entries/utils/getEntryTranslation.d.ts +0 -1
- package/dist/core/server/entries/utils/getEntryTranslation.js +0 -18
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
import InfoCircle from '@tabler/icons-svelte/icons/info-circle';
|
|
17
17
|
import Shield from '@tabler/icons-svelte/icons/shield';
|
|
18
18
|
import CircleCheck from '@tabler/icons-svelte/icons/circle-check';
|
|
19
|
-
import {
|
|
19
|
+
import { getEntryStatusForLang } from '../utils.js';
|
|
20
20
|
import { validateA11y, a11yLangPl, a11yLangEn, type A11yIssue } from './a11y-validator.js';
|
|
21
21
|
import { getContentLanguage } from '../../../state/content-language.svelte.js';
|
|
22
|
-
import { computeTranslationStatus, type LangStatus } from '../../../utils/translationStatus.js';
|
|
23
22
|
import LanguageIcon from '@tabler/icons-svelte/icons/language';
|
|
24
23
|
|
|
25
24
|
const lang: Record<
|
|
@@ -131,19 +130,13 @@
|
|
|
131
130
|
let dateValue = $state('');
|
|
132
131
|
let timeValue = $state('');
|
|
133
132
|
let a11yIssues = $state<A11yIssue[]>([]);
|
|
134
|
-
let translationStatus = $state<Record<string, LangStatus> | null>(null);
|
|
135
133
|
|
|
136
|
-
|
|
134
|
+
// Status is per current language
|
|
135
|
+
const entryStatus = $derived(getEntryStatusForLang(entry, contentLanguage.current));
|
|
137
136
|
const t = $derived(lang[interfaceLanguage.current]);
|
|
138
137
|
|
|
139
138
|
const hasA11yWarnings = $derived(a11yIssues.some((i) => i.type === 'warning'));
|
|
140
|
-
const
|
|
141
|
-
translationStatus != null &&
|
|
142
|
-
Object.values(translationStatus).some((s) => s.status !== 'complete')
|
|
143
|
-
);
|
|
144
|
-
const showTranslationSection = $derived(
|
|
145
|
-
contentLanguage.all.length > 1 && translationStatus != null
|
|
146
|
-
);
|
|
139
|
+
const showLangStatusSection = $derived(contentLanguage.all.length > 1);
|
|
147
140
|
|
|
148
141
|
const scheduleLabel = $derived.by(() => {
|
|
149
142
|
switch (entryStatus) {
|
|
@@ -173,8 +166,9 @@
|
|
|
173
166
|
}
|
|
174
167
|
|
|
175
168
|
function setDefaultValues() {
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
const scheduledVersion = entry.scheduledVersions[contentLanguage.current];
|
|
170
|
+
if (entryStatus === 'scheduled' && scheduledVersion?.publishedAt) {
|
|
171
|
+
const scheduled = new Date(scheduledVersion.publishedAt);
|
|
178
172
|
dateValue = scheduled.toISOString().slice(0, 10);
|
|
179
173
|
timeValue = `${String(scheduled.getHours()).padStart(2, '0')}:${String(scheduled.getMinutes()).padStart(2, '0')}`;
|
|
180
174
|
} else {
|
|
@@ -229,20 +223,10 @@
|
|
|
229
223
|
a11yIssues = validateA11y(data, fields, a11yLang, contentLanguage.all);
|
|
230
224
|
}
|
|
231
225
|
|
|
232
|
-
function runTranslationCheck() {
|
|
233
|
-
if (contentLanguage.all.length <= 1 || !fields.length) {
|
|
234
|
-
translationStatus = null;
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const data = getFormData ? getFormData() : version.data;
|
|
238
|
-
translationStatus = computeTranslationStatus(data, fields, contentLanguage.all);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
226
|
$effect(() => {
|
|
242
227
|
if (open) {
|
|
243
228
|
setDefaultValues();
|
|
244
229
|
runA11yValidation();
|
|
245
|
-
runTranslationCheck();
|
|
246
230
|
}
|
|
247
231
|
});
|
|
248
232
|
</script>
|
|
@@ -284,92 +268,56 @@
|
|
|
284
268
|
</div>
|
|
285
269
|
</div>
|
|
286
270
|
|
|
287
|
-
{#if entryStatus === 'published' && entry.publishedAt}
|
|
271
|
+
{#if entryStatus === 'published' && entry.publishedVersions[contentLanguage.current]?.publishedAt}
|
|
288
272
|
<div class="publish-detail-row">
|
|
289
273
|
<span>{t.publishedAt}</span>
|
|
290
|
-
<span class="tabular-nums">{formatDateTime(entry.publishedAt)}</span>
|
|
274
|
+
<span class="tabular-nums">{formatDateTime(entry.publishedVersions[contentLanguage.current]?.publishedAt)}</span>
|
|
291
275
|
</div>
|
|
292
276
|
{/if}
|
|
293
277
|
|
|
294
|
-
{#if entryStatus === 'scheduled' && entry.publishedAt}
|
|
278
|
+
{#if entryStatus === 'scheduled' && entry.publishedVersions[contentLanguage.current]?.publishedAt}
|
|
295
279
|
<div class="publish-detail-row">
|
|
296
280
|
<span>{t.scheduledFor}</span>
|
|
297
|
-
<span class="tabular-nums">{formatDateTime(entry.publishedAt)}</span>
|
|
281
|
+
<span class="tabular-nums">{formatDateTime(entry.publishedVersions[contentLanguage.current]?.publishedAt)}</span>
|
|
298
282
|
</div>
|
|
299
283
|
{/if}
|
|
300
284
|
|
|
301
|
-
{#if entry.
|
|
285
|
+
{#if entry.publishedVersions[contentLanguage.current] && entry.publishedVersions[contentLanguage.current].id !== version.id}
|
|
302
286
|
<div class="publish-detail-row">
|
|
303
287
|
<span>{t.publishedVersion}</span>
|
|
304
|
-
<span class="tabular-nums">{formatDateTime(entry.
|
|
288
|
+
<span class="tabular-nums">{formatDateTime(entry.publishedVersions[contentLanguage.current].createdAt)}</span>
|
|
305
289
|
</div>
|
|
306
290
|
{/if}
|
|
307
291
|
</div>
|
|
308
292
|
|
|
309
|
-
<!--
|
|
310
|
-
{#if
|
|
293
|
+
<!-- Per-language status section -->
|
|
294
|
+
{#if showLangStatusSection}
|
|
311
295
|
<div class="sheet-section">
|
|
312
296
|
<div class="sheet-section-title">
|
|
313
297
|
<LanguageIcon class="size-3.5" />
|
|
314
298
|
{t.translations}
|
|
315
299
|
</div>
|
|
316
300
|
|
|
317
|
-
{#each contentLanguage.all as
|
|
318
|
-
{@const
|
|
319
|
-
{
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
<div
|
|
329
|
-
class="translation-progress-fill {status.status === 'complete' ? 'complete' : status.status === 'partial' ? 'partial' : ''}"
|
|
330
|
-
style="width: {status.percentage}%"
|
|
331
|
-
></div>
|
|
332
|
-
</div>
|
|
301
|
+
{#each contentLanguage.all as langCode}
|
|
302
|
+
{@const langPub = entry.publishedVersions[langCode]}
|
|
303
|
+
{@const langSch = entry.scheduledVersions[langCode]}
|
|
304
|
+
{@const langDraft = entry.draftVersions[langCode]}
|
|
305
|
+
{@const langStatus = langPub ? 'published' : langSch ? 'scheduled' : 'draft'}
|
|
306
|
+
<div class="translation-lang-row">
|
|
307
|
+
<div class="translation-lang-header">
|
|
308
|
+
<span class="translation-lang-label">{langCode.toUpperCase()}</span>
|
|
309
|
+
<span class="translation-lang-pct {langStatus === 'published' ? 'complete' : langStatus === 'scheduled' ? 'partial' : 'empty'}">
|
|
310
|
+
{t.statusLabels[langStatus]}
|
|
311
|
+
</span>
|
|
333
312
|
</div>
|
|
334
|
-
|
|
313
|
+
<div class="translation-progress-track">
|
|
314
|
+
<div
|
|
315
|
+
class="translation-progress-fill {langStatus === 'published' ? 'complete' : langStatus === 'scheduled' ? 'partial' : ''}"
|
|
316
|
+
style="width: {langStatus === 'published' ? 100 : langStatus === 'scheduled' ? 66 : langDraft ? 33 : 0}%"
|
|
317
|
+
></div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
335
320
|
{/each}
|
|
336
|
-
|
|
337
|
-
{#if hasIncompleteTranslations}
|
|
338
|
-
{#each contentLanguage.all as lang}
|
|
339
|
-
{@const status = translationStatus[lang]}
|
|
340
|
-
{#if status && status.missingFields.length > 0}
|
|
341
|
-
<div class="translation-missing">
|
|
342
|
-
<div class="translation-missing-title">{t.missingFieldsLabel(lang)}</div>
|
|
343
|
-
{#each status.missingFields as field}
|
|
344
|
-
{#if onScrollToIssue}
|
|
345
|
-
<button
|
|
346
|
-
type="button"
|
|
347
|
-
class="a11y-item warning a11y-item-clickable"
|
|
348
|
-
onclick={() => {
|
|
349
|
-
contentLanguage.current = lang;
|
|
350
|
-
open = false;
|
|
351
|
-
setTimeout(() => onScrollToIssue!(field.slug, 0), 200);
|
|
352
|
-
}}
|
|
353
|
-
>
|
|
354
|
-
<div class="a11y-item-icon">
|
|
355
|
-
<AlertTriangle class="size-4" />
|
|
356
|
-
</div>
|
|
357
|
-
<span class="a11y-item-text">{field.label}</span>
|
|
358
|
-
</button>
|
|
359
|
-
{:else}
|
|
360
|
-
<div class="a11y-item warning">
|
|
361
|
-
<div class="a11y-item-icon">
|
|
362
|
-
<AlertTriangle class="size-4" />
|
|
363
|
-
</div>
|
|
364
|
-
<span class="a11y-item-text">{field.label}</span>
|
|
365
|
-
</div>
|
|
366
|
-
{/if}
|
|
367
|
-
{/each}
|
|
368
|
-
</div>
|
|
369
|
-
{/if}
|
|
370
|
-
{/each}
|
|
371
|
-
<p class="a11y-hint">{t.translationsHint}</p>
|
|
372
|
-
{/if}
|
|
373
321
|
</div>
|
|
374
322
|
{/if}
|
|
375
323
|
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
|
|
46
46
|
let { entry, version }: Props = $props();
|
|
47
47
|
|
|
48
|
-
const isPublishedVersion = $derived(entry.
|
|
49
|
-
const isScheduledVersion = $derived(entry.
|
|
48
|
+
const isPublishedVersion = $derived(entry.publishedVersions[version.lang]?.id === version.id);
|
|
49
|
+
const isScheduledVersion = $derived(entry.scheduledVersions[version.lang]?.id === version.id);
|
|
50
50
|
|
|
51
51
|
const currentStatus = $derived.by((): EntryVersionStatus => {
|
|
52
52
|
if (isPublishedVersion) return 'published';
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function getDotClass(version: DbEntryVersion): string {
|
|
92
|
-
const isPublished = entry.
|
|
93
|
-
const isScheduled = entry.
|
|
92
|
+
const isPublished = entry.publishedVersions[version.lang]?.id === version.id && entryStatus === 'published';
|
|
93
|
+
const isScheduled = entry.publishedVersions[version.lang]?.id === version.id && entryStatus === 'scheduled';
|
|
94
94
|
const isCurrent = version.id === currentVersionId;
|
|
95
95
|
|
|
96
96
|
if (isPublished) return 'dot-published';
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
// Always keep current version
|
|
110
110
|
if (version.id === currentVersionId) return true;
|
|
111
111
|
// Always keep published/scheduled
|
|
112
|
-
if (entry.
|
|
112
|
+
if (entry.publishedVersions[version.lang]?.id === version.id) return true;
|
|
113
113
|
// Keep versions with actual data changes vs previous
|
|
114
114
|
const prevVersion = sortedVersions[idx + 1];
|
|
115
115
|
if (!prevVersion) return true; // first version
|
|
@@ -132,8 +132,8 @@
|
|
|
132
132
|
const groups: Map<string, DbEntryVersion[]> = new Map();
|
|
133
133
|
|
|
134
134
|
for (const version of timelineVersions) {
|
|
135
|
-
const isPublished = entry.
|
|
136
|
-
const isScheduled = entry.
|
|
135
|
+
const isPublished = entry.publishedVersions[version.lang]?.id === version.id && entryStatus === 'published';
|
|
136
|
+
const isScheduled = entry.publishedVersions[version.lang]?.id === version.id && entryStatus === 'scheduled';
|
|
137
137
|
|
|
138
138
|
let key: string;
|
|
139
139
|
if (isPublished) {
|
|
@@ -193,12 +193,12 @@
|
|
|
193
193
|
<div class="vh-item-left">
|
|
194
194
|
<span class="vh-time">{formatTime(currentVersion.createdAt)}</span>
|
|
195
195
|
<Badge class="vh-badge-editing">{t.editing}</Badge>
|
|
196
|
-
{#if entry.
|
|
196
|
+
{#if entry.publishedVersions[currentVersion.lang]?.id === currentVersion.id && entryStatus === 'published'}
|
|
197
197
|
<Badge class="vh-badge-published">
|
|
198
198
|
<SquareCheckFilled class="size-2.5 mr-0.5" />
|
|
199
199
|
{t.published}
|
|
200
200
|
</Badge>
|
|
201
|
-
{:else if entry.
|
|
201
|
+
{:else if entry.publishedVersions[currentVersion.lang]?.id === currentVersion.id && entryStatus === 'scheduled'}
|
|
202
202
|
<Badge class="vh-badge-scheduled">
|
|
203
203
|
{t.scheduled}
|
|
204
204
|
</Badge>
|
|
@@ -233,8 +233,8 @@
|
|
|
233
233
|
<div class="vh-timeline-line"></div>
|
|
234
234
|
|
|
235
235
|
{#each versions as version}
|
|
236
|
-
{@const isPublished = entry.
|
|
237
|
-
{@const isScheduled = entry.
|
|
236
|
+
{@const isPublished = entry.publishedVersions[version.lang]?.id === version.id && entryStatus === 'published'}
|
|
237
|
+
{@const isScheduled = entry.publishedVersions[version.lang]?.id === version.id && entryStatus === 'scheduled'}
|
|
238
238
|
{@const prevVersion = sortedVersions.find(
|
|
239
239
|
(v) => v.versionNumber === version.versionNumber - 1
|
|
240
240
|
)}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getContentLanguage } from '../../../state/content-language.svelte.js';
|
|
2
3
|
import { useInterfaceLanguage } from '../../../state/interface-language.svelte.js';
|
|
3
4
|
import type { RawEntry } from '../../../../types/entries.js';
|
|
4
5
|
import type { InterfaceLanguage } from '../../../../types/languages.js';
|
|
5
6
|
import ClockFilled from '@tabler/icons-svelte/icons/clock-filled';
|
|
6
7
|
import FileFilled from '@tabler/icons-svelte/icons/file-filled';
|
|
7
8
|
import SquareCheckFilled from '@tabler/icons-svelte/icons/square-check-filled';
|
|
8
|
-
import {
|
|
9
|
+
import { getEntryStatusForLang } from '../utils.js';
|
|
9
10
|
|
|
10
11
|
const lang: Record<
|
|
11
12
|
InterfaceLanguage,
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
const interfaceLanguage = useInterfaceLanguage();
|
|
38
|
+
const contentLanguage = getContentLanguage();
|
|
37
39
|
|
|
38
40
|
type Props = {
|
|
39
41
|
entry: RawEntry;
|
|
@@ -41,34 +43,38 @@
|
|
|
41
43
|
|
|
42
44
|
let { entry }: Props = $props();
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const currentLang = $derived(contentLanguage.current);
|
|
47
|
+
const entryStatus = $derived(getEntryStatusForLang(entry, currentLang));
|
|
48
|
+
const publishedVersion = $derived(entry.publishedVersions[currentLang]);
|
|
49
|
+
const scheduledVersion = $derived(entry.scheduledVersions[currentLang]);
|
|
50
|
+
const draftVersion = $derived(entry.draftVersions[currentLang]);
|
|
45
51
|
</script>
|
|
46
52
|
|
|
47
53
|
<div class="flex items-center gap-1 text-sm">
|
|
48
54
|
<span class="text-muted-foreground"> {lang[interfaceLanguage.current].status}:</span>
|
|
49
55
|
|
|
50
|
-
{#if entryStatus === 'published' &&
|
|
56
|
+
{#if entryStatus === 'published' && publishedVersion}
|
|
51
57
|
<span class="flex items-center gap-1">
|
|
52
58
|
<SquareCheckFilled />
|
|
53
|
-
{lang[interfaceLanguage.current].published} (v{
|
|
59
|
+
{lang[interfaceLanguage.current].published} (v{publishedVersion.versionNumber}, {publishedVersion.publishedAt ? new Date(publishedVersion.publishedAt).toLocaleString(
|
|
54
60
|
interfaceLanguage.current
|
|
55
|
-
)})</span
|
|
61
|
+
) : ''})</span
|
|
56
62
|
>
|
|
57
63
|
{/if}
|
|
58
64
|
|
|
59
|
-
{#if entryStatus === 'scheduled' &&
|
|
65
|
+
{#if entryStatus === 'scheduled' && scheduledVersion}
|
|
60
66
|
<span class="flex items-center gap-1">
|
|
61
67
|
<ClockFilled />
|
|
62
|
-
{lang[interfaceLanguage.current].scheduled} v{
|
|
68
|
+
{lang[interfaceLanguage.current].scheduled} v{scheduledVersion.versionNumber}
|
|
63
69
|
{lang[interfaceLanguage.current].on}
|
|
64
|
-
{
|
|
70
|
+
{scheduledVersion.publishedAt ? new Date(scheduledVersion.publishedAt).toLocaleString(interfaceLanguage.current) : ''})</span
|
|
65
71
|
>
|
|
66
72
|
{/if}
|
|
67
73
|
|
|
68
|
-
{#if entryStatus === 'draft' &&
|
|
74
|
+
{#if entryStatus === 'draft' && draftVersion}
|
|
69
75
|
<span class="flex items-center gap-1">
|
|
70
76
|
<FileFilled />
|
|
71
|
-
{lang[interfaceLanguage.current].draft} (v{
|
|
77
|
+
{lang[interfaceLanguage.current].draft} (v{draftVersion.versionNumber})</span
|
|
72
78
|
>
|
|
73
79
|
{/if}
|
|
74
80
|
</div>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { DbEntryVersion, EntryStatus, EntryVersionStatus, RawEntry } from '../../../types/entries.js';
|
|
2
2
|
export declare function getEntryVersionStatus(version: DbEntryVersion): EntryVersionStatus;
|
|
3
|
+
/** Get entry status for a specific language */
|
|
4
|
+
export declare function getEntryStatusForLang(entry: RawEntry, lang: string): EntryStatus;
|
|
5
|
+
/** Get overall entry status — published if any lang is published */
|
|
3
6
|
export declare function getEntryStatus(entry: RawEntry): EntryStatus;
|
|
@@ -7,15 +7,33 @@ export function getEntryVersionStatus(version) {
|
|
|
7
7
|
}
|
|
8
8
|
return 'published';
|
|
9
9
|
}
|
|
10
|
+
/** Get entry status for a specific language */
|
|
11
|
+
export function getEntryStatusForLang(entry, lang) {
|
|
12
|
+
if (entry.archivedAt) {
|
|
13
|
+
return 'archived';
|
|
14
|
+
}
|
|
15
|
+
const publishedVersion = entry.publishedVersions[lang];
|
|
16
|
+
const scheduledVersion = entry.scheduledVersions[lang];
|
|
17
|
+
if (publishedVersion) {
|
|
18
|
+
return 'published';
|
|
19
|
+
}
|
|
20
|
+
if (scheduledVersion) {
|
|
21
|
+
return 'scheduled';
|
|
22
|
+
}
|
|
23
|
+
return 'draft';
|
|
24
|
+
}
|
|
25
|
+
/** Get overall entry status — published if any lang is published */
|
|
10
26
|
export function getEntryStatus(entry) {
|
|
11
27
|
if (entry.archivedAt) {
|
|
12
28
|
return 'archived';
|
|
13
29
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return 'scheduled';
|
|
17
|
-
}
|
|
30
|
+
const hasPublished = Object.values(entry.publishedVersions).some((v) => v != null);
|
|
31
|
+
if (hasPublished) {
|
|
18
32
|
return 'published';
|
|
19
33
|
}
|
|
34
|
+
const hasScheduled = Object.values(entry.scheduledVersions).some((v) => v != null);
|
|
35
|
+
if (hasScheduled) {
|
|
36
|
+
return 'scheduled';
|
|
37
|
+
}
|
|
20
38
|
return 'draft';
|
|
21
39
|
}
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
|
|
47
47
|
const formSubmissionQuery = $derived(submissionId ? remotes.getFormSubmission(submissionId) : null);
|
|
48
48
|
|
|
49
|
+
let markedAsRead = false;
|
|
50
|
+
|
|
49
51
|
$effect(() => {
|
|
50
52
|
const submission = formSubmissionQuery?.current;
|
|
51
53
|
if (submission) {
|
|
@@ -65,7 +67,8 @@
|
|
|
65
67
|
];
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
if (!submission.read) {
|
|
70
|
+
if (!submission.read && !markedAsRead) {
|
|
71
|
+
markedAsRead = true;
|
|
69
72
|
remotes.updateFormSubmission({ id: submission.id, read: true });
|
|
70
73
|
}
|
|
71
74
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import Phone from '@tabler/icons-svelte/icons/phone';
|
|
6
6
|
import MessageSquare from '@tabler/icons-svelte/icons/message';
|
|
7
7
|
import FileText from '@tabler/icons-svelte/icons/file-text';
|
|
8
|
+
import Download from '@tabler/icons-svelte/icons/download';
|
|
8
9
|
import CheckCircle from '@tabler/icons-svelte/icons/circle-check';
|
|
9
10
|
import CircleX from '@tabler/icons-svelte/icons/circle-x';
|
|
10
11
|
import Hash from '@tabler/icons-svelte/icons/hash';
|
|
@@ -28,11 +29,13 @@
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
const isBoolean = $derived(typeof value === 'boolean');
|
|
32
|
+
const isFile = $derived(fieldType === 'file' && typeof value === 'string' && value.length > 0);
|
|
31
33
|
const isEmail = $derived(fieldType === 'email' || (typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)));
|
|
32
34
|
const isPhone = $derived(fieldType === 'phone' || (typeof value === 'string' && /^[\d\s\-+()]+$/.test(value) && String(value).length >= 9));
|
|
33
35
|
|
|
34
36
|
const iconType = $derived.by(() => {
|
|
35
37
|
if (isBoolean) return value ? 'check' : 'x';
|
|
38
|
+
if (isFile) return 'download';
|
|
36
39
|
if (isEmail) return 'mail';
|
|
37
40
|
if (isPhone) return 'phone';
|
|
38
41
|
if (fieldType === 'textarea') return 'message';
|
|
@@ -64,6 +67,8 @@
|
|
|
64
67
|
<Hash class="size-4 text-muted-foreground" />
|
|
65
68
|
{:else if iconType === 'calendar'}
|
|
66
69
|
<Calendar class="size-4 text-muted-foreground" />
|
|
70
|
+
{:else if iconType === 'download'}
|
|
71
|
+
<Download class="size-4 text-muted-foreground" />
|
|
67
72
|
{:else}
|
|
68
73
|
<FileText class="size-4 text-muted-foreground" />
|
|
69
74
|
{/if}
|
|
@@ -81,6 +86,11 @@
|
|
|
81
86
|
<span class="text-red-600">Nie</span>
|
|
82
87
|
{/if}
|
|
83
88
|
</div>
|
|
89
|
+
{:else if isFile}
|
|
90
|
+
<a href={String(value)} target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 text-primary hover:underline">
|
|
91
|
+
<Download class="size-4" />
|
|
92
|
+
Download
|
|
93
|
+
</a>
|
|
84
94
|
{:else if isEmail}
|
|
85
95
|
<a href="mailto:{value}" class="text-primary hover:underline break-all">{displayValue()}</a>
|
|
86
96
|
{:else if isPhone}
|
|
@@ -9,5 +9,6 @@ export { default as FormPage } from './form/form-page.svelte';
|
|
|
9
9
|
export { default as FormSubmissionPage } from './form/form-submission/form-submission-page.svelte';
|
|
10
10
|
export { default as UsersPage } from './users/users-page.svelte';
|
|
11
11
|
export { default as AcceptInvitePage } from './users/accept-invite-page.svelte';
|
|
12
|
+
export { default as ResetPasswordPage } from './login/reset-password-page.svelte';
|
|
12
13
|
export { default as MaintenancePage } from './maintenance/maintenance-page.svelte';
|
|
13
14
|
export { default as MediaSelector } from '../components/media/media-selector.svelte';
|
|
@@ -9,5 +9,6 @@ export { default as FormPage } from './form/form-page.svelte';
|
|
|
9
9
|
export { default as FormSubmissionPage } from './form/form-submission/form-submission-page.svelte';
|
|
10
10
|
export { default as UsersPage } from './users/users-page.svelte';
|
|
11
11
|
export { default as AcceptInvitePage } from './users/accept-invite-page.svelte';
|
|
12
|
+
export { default as ResetPasswordPage } from './login/reset-password-page.svelte';
|
|
12
13
|
export { default as MaintenancePage } from './maintenance/maintenance-page.svelte';
|
|
13
14
|
export { default as MediaSelector } from '../components/media/media-selector.svelte';
|
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
import FileSearch from '@tabler/icons-svelte/icons/file-search';
|
|
6
6
|
import Photo from '@tabler/icons-svelte/icons/photo';
|
|
7
7
|
import AlertTriangle from '@tabler/icons-svelte/icons/alert-triangle';
|
|
8
|
+
import PlayerPlay from '@tabler/icons-svelte/icons/player-play';
|
|
9
|
+
import PlayerStop from '@tabler/icons-svelte/icons/player-stop';
|
|
10
|
+
import CircleCheck from '@tabler/icons-svelte/icons/circle-check';
|
|
8
11
|
import Button from '../../../components/ui/button/button.svelte';
|
|
9
12
|
import * as Card from '../../../components/ui/card/index.js';
|
|
10
13
|
import { toast } from 'svelte-sonner';
|
|
11
14
|
|
|
12
15
|
interface GcReport {
|
|
13
16
|
imageStylesCount: number;
|
|
17
|
+
processableImagesCount: number;
|
|
18
|
+
expectedStylesCount: number;
|
|
19
|
+
missingStylesCount: number;
|
|
14
20
|
orphanedDiskFiles: string[];
|
|
15
21
|
missingDiskRecords: { table: string; id: string; url: string }[];
|
|
16
22
|
}
|
|
@@ -20,6 +26,18 @@
|
|
|
20
26
|
let purging = $state(false);
|
|
21
27
|
let cleaningOrphans = $state(false);
|
|
22
28
|
|
|
29
|
+
// Batch generation state
|
|
30
|
+
let generating = $state(false);
|
|
31
|
+
let genTotal = $state(0);
|
|
32
|
+
let genProcessed = $state(0);
|
|
33
|
+
let genCreated = $state(0);
|
|
34
|
+
let genSkipped = $state(0);
|
|
35
|
+
let genCurrentFile = $state('');
|
|
36
|
+
let genErrors = $state(0);
|
|
37
|
+
let genAbort: AbortController | null = null;
|
|
38
|
+
|
|
39
|
+
let genPercent = $derived(genTotal > 0 ? Math.round((genProcessed / genTotal) * 100) : 0);
|
|
40
|
+
|
|
23
41
|
async function loadReport() {
|
|
24
42
|
loading = true;
|
|
25
43
|
try {
|
|
@@ -63,6 +81,77 @@
|
|
|
63
81
|
}
|
|
64
82
|
}
|
|
65
83
|
|
|
84
|
+
async function startBatchGenerate() {
|
|
85
|
+
generating = true;
|
|
86
|
+
genTotal = 0;
|
|
87
|
+
genProcessed = 0;
|
|
88
|
+
genCreated = 0;
|
|
89
|
+
genSkipped = 0;
|
|
90
|
+
genCurrentFile = '';
|
|
91
|
+
genErrors = 0;
|
|
92
|
+
genAbort = new AbortController();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const res = await fetch('/admin/api/generate-styles', {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
signal: genAbort.signal
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!res.ok) throw new Error('Failed to start');
|
|
101
|
+
if (!res.body) throw new Error('No response body');
|
|
102
|
+
|
|
103
|
+
const reader = res.body.getReader();
|
|
104
|
+
const decoder = new TextDecoder();
|
|
105
|
+
let buffer = '';
|
|
106
|
+
|
|
107
|
+
while (true) {
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done) break;
|
|
110
|
+
|
|
111
|
+
buffer += decoder.decode(value, { stream: true });
|
|
112
|
+
const chunks = buffer.split('\n\n');
|
|
113
|
+
buffer = chunks.pop() || '';
|
|
114
|
+
|
|
115
|
+
for (const chunk of chunks) {
|
|
116
|
+
if (!chunk.startsWith('data: ')) continue;
|
|
117
|
+
const event = JSON.parse(chunk.slice(6));
|
|
118
|
+
|
|
119
|
+
genTotal = event.total ?? genTotal;
|
|
120
|
+
genProcessed = event.processed ?? genProcessed;
|
|
121
|
+
genCreated = event.created ?? genCreated;
|
|
122
|
+
genSkipped = event.skipped ?? genSkipped;
|
|
123
|
+
genCurrentFile = event.currentFile ?? genCurrentFile;
|
|
124
|
+
|
|
125
|
+
if (event.type === 'error') {
|
|
126
|
+
genErrors++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (event.type === 'done') {
|
|
130
|
+
const parts = [`Przetworzono ${genTotal} obrazów`];
|
|
131
|
+
if (genCreated > 0) parts.push(`utworzono ${genCreated} styli`);
|
|
132
|
+
if (genSkipped > 0) parts.push(`pominięto ${genSkipped} (już istnieją)`);
|
|
133
|
+
if (genErrors > 0) parts.push(`${genErrors} błędów`);
|
|
134
|
+
toast.success(parts.join(', '));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (e) {
|
|
139
|
+
if (e instanceof DOMException && e.name === 'AbortError') {
|
|
140
|
+
toast.info(`Przerwano po ${genProcessed}/${genTotal} obrazów`);
|
|
141
|
+
} else {
|
|
142
|
+
toast.error('Błąd podczas generowania styli');
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
generating = false;
|
|
146
|
+
genAbort = null;
|
|
147
|
+
await loadReport();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cancelBatchGenerate() {
|
|
152
|
+
genAbort?.abort();
|
|
153
|
+
}
|
|
154
|
+
|
|
66
155
|
$effect(() => {
|
|
67
156
|
loadReport();
|
|
68
157
|
});
|
|
@@ -94,14 +183,69 @@
|
|
|
94
183
|
</Card.Description>
|
|
95
184
|
</Card.Header>
|
|
96
185
|
<Card.Content>
|
|
97
|
-
<p class="mb-
|
|
186
|
+
<p class="mb-1 text-3xl font-bold" style="color: var(--primary);">
|
|
98
187
|
{report.imageStylesCount}
|
|
188
|
+
<span class="text-base font-normal" style="color: var(--muted-foreground);">/ {report.expectedStylesCount}</span>
|
|
189
|
+
</p>
|
|
190
|
+
<p class="mb-4 text-xs" style="color: var(--muted-foreground);">
|
|
191
|
+
{report.processableImagesCount} obrazów, {report.expectedStylesCount} oczekiwanych styli
|
|
99
192
|
</p>
|
|
193
|
+
|
|
194
|
+
<!-- Batch generate -->
|
|
195
|
+
{#if generating}
|
|
196
|
+
<div class="mb-4">
|
|
197
|
+
<div class="mb-1 flex items-center justify-between text-xs" style="color: var(--muted-foreground);">
|
|
198
|
+
<span>{genProcessed}/{genTotal} obrazów</span>
|
|
199
|
+
<span>{genPercent}%</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="h-2 w-full overflow-hidden rounded-full" style="background: var(--muted, #e5e7eb);">
|
|
202
|
+
<div
|
|
203
|
+
class="h-full rounded-full transition-all duration-300"
|
|
204
|
+
style="width: {genPercent}%; background: var(--primary);"
|
|
205
|
+
></div>
|
|
206
|
+
</div>
|
|
207
|
+
<p class="mt-1 text-xs" style="color: var(--muted-foreground);">
|
|
208
|
+
Utworzono: {genCreated}, pominięto: {genSkipped}{genErrors > 0 ? `, błędów: ${genErrors}` : ''}
|
|
209
|
+
</p>
|
|
210
|
+
<p class="mt-0.5 truncate text-xs" style="color: var(--muted-foreground);">
|
|
211
|
+
{genCurrentFile}
|
|
212
|
+
</p>
|
|
213
|
+
<Button
|
|
214
|
+
variant="outline"
|
|
215
|
+
size="sm"
|
|
216
|
+
onclick={cancelBatchGenerate}
|
|
217
|
+
class="mt-2"
|
|
218
|
+
>
|
|
219
|
+
<PlayerStop class="size-4" />
|
|
220
|
+
Anuluj
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
223
|
+
{:else if report.missingStylesCount > 0}
|
|
224
|
+
<div class="mb-3">
|
|
225
|
+
<p class="mb-2 text-sm" style="color: var(--warning, #C4893A);">
|
|
226
|
+
Brakuje {report.missingStylesCount} styli — generowanie odbywa się przy wyświetleniu, co może spowalniać aplikację.
|
|
227
|
+
</p>
|
|
228
|
+
<Button
|
|
229
|
+
variant="default"
|
|
230
|
+
size="sm"
|
|
231
|
+
onclick={startBatchGenerate}
|
|
232
|
+
>
|
|
233
|
+
<PlayerPlay class="size-4" />
|
|
234
|
+
Generuj brakujące style
|
|
235
|
+
</Button>
|
|
236
|
+
</div>
|
|
237
|
+
{:else}
|
|
238
|
+
<div class="mb-3 flex items-center gap-1.5 text-sm" style="color: var(--success, #3A8A5C);">
|
|
239
|
+
<CircleCheck class="size-4" />
|
|
240
|
+
Wszystkie style wygenerowane
|
|
241
|
+
</div>
|
|
242
|
+
{/if}
|
|
243
|
+
|
|
100
244
|
<Button
|
|
101
245
|
variant="destructive"
|
|
102
246
|
size="sm"
|
|
103
247
|
onclick={purgeStyles}
|
|
104
|
-
disabled={purging || report.imageStylesCount === 0}
|
|
248
|
+
disabled={purging || generating || report.imageStylesCount === 0}
|
|
105
249
|
>
|
|
106
250
|
{#if purging}
|
|
107
251
|
<Loader2 class="size-4 animate-spin" />
|
|
@@ -32,12 +32,7 @@
|
|
|
32
32
|
import UserSessionsSheet from './user-sessions-sheet.svelte';
|
|
33
33
|
import InviteUserDialog from './invite-user-dialog.svelte';
|
|
34
34
|
import PendingInvitations from './pending-invitations.svelte';
|
|
35
|
-
|
|
36
|
-
type Props = {
|
|
37
|
-
emailConfigured?: boolean;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
let { emailConfigured = false }: Props = $props();
|
|
35
|
+
import { getRemotes } from '../../helpers/index.js';
|
|
41
36
|
|
|
42
37
|
type User = {
|
|
43
38
|
id: string;
|
|
@@ -47,6 +42,10 @@
|
|
|
47
42
|
createdAt: Date;
|
|
48
43
|
};
|
|
49
44
|
|
|
45
|
+
const remotes = getRemotes();
|
|
46
|
+
const emailQuery = $derived(remotes.getEmailConfigured());
|
|
47
|
+
const emailConfigured = $derived(emailQuery.data === true);
|
|
48
|
+
|
|
50
49
|
const interfaceLanguage = useInterfaceLanguage();
|
|
51
50
|
const lang = $derived(usersLang[interfaceLanguage.current]);
|
|
52
51
|
const session = authClient.useSession();
|