includio-cms 0.1.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/ROADMAP.md +26 -13
- package/dist/admin/api/accept-invite.js +1 -5
- package/dist/admin/api/invite.js +7 -16
- package/dist/admin/client/account/account-page.svelte +20 -50
- package/dist/admin/client/account/lang.d.ts +15 -23
- package/dist/admin/client/account/lang.js +51 -67
- package/dist/admin/client/account/preferences-section.svelte +26 -84
- package/dist/admin/client/account/profile-section.svelte +60 -40
- package/dist/admin/client/account/schema.d.ts +11 -3
- package/dist/admin/client/account/schema.js +25 -16
- package/dist/admin/client/account/security-section.svelte +139 -105
- package/dist/admin/client/account/sessions-section.svelte +35 -34
- package/dist/admin/client/admin/admin-after-login-layout-content.svelte +3 -5
- package/dist/admin/client/admin/admin-layout.svelte +3 -2
- package/dist/admin/client/admin/admin-preloader.svelte +36 -0
- package/dist/admin/client/admin/admin-preloader.svelte.d.ts +18 -0
- package/dist/admin/client/admin/dashboard-page.svelte +35 -67
- package/dist/admin/client/collection/a11y-score-cell.svelte +45 -0
- package/dist/admin/client/collection/a11y-score-cell.svelte.d.ts +6 -0
- package/dist/admin/client/collection/bulk-actions-bar.svelte +100 -0
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +10 -0
- package/dist/admin/client/collection/collection-entries.svelte +277 -258
- package/dist/admin/client/collection/collection-view.svelte.d.ts +4 -3
- package/dist/admin/client/collection/collection-view.svelte.js +9 -5
- package/dist/admin/client/collection/collection.svelte +22 -12
- package/dist/admin/client/collection/data-table.svelte +50 -39
- package/dist/admin/client/collection/data-table.svelte.d.ts +1 -0
- package/dist/admin/client/collection/date-cell.svelte +7 -5
- package/dist/admin/client/collection/date-cell.svelte.d.ts +1 -1
- package/dist/admin/client/collection/empty-state.svelte +28 -0
- package/dist/admin/client/collection/empty-state.svelte.d.ts +9 -0
- package/dist/admin/client/collection/entry-link.svelte +10 -4
- package/dist/admin/client/collection/entry-link.svelte.d.ts +1 -0
- package/dist/admin/client/collection/grid-view.svelte +21 -23
- package/dist/admin/client/collection/grid-view.svelte.d.ts +1 -2
- package/dist/admin/client/collection/row-actions.svelte +69 -0
- package/dist/admin/client/collection/row-actions.svelte.d.ts +10 -0
- package/dist/admin/client/collection/status-badge.svelte +7 -8
- package/dist/admin/client/collection/table-pagination.svelte +122 -79
- package/dist/admin/client/collection/table-pagination.svelte.d.ts +1 -0
- package/dist/admin/client/collection/table-toolbar.svelte +108 -88
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +8 -9
- package/dist/admin/client/entry/entry-form.svelte +109 -1
- package/dist/admin/client/entry/entry-header.svelte +143 -37
- package/dist/admin/client/entry/entry-header.svelte.d.ts +8 -0
- package/dist/admin/client/entry/entry.svelte +272 -61
- package/dist/admin/client/entry/header/a11y-validator.d.ts +47 -0
- package/dist/admin/client/entry/header/a11y-validator.js +352 -0
- package/dist/admin/client/entry/header/publish-panel.svelte +531 -129
- package/dist/admin/client/entry/header/publish-panel.svelte.d.ts +4 -0
- package/dist/admin/client/entry/header/save-indicator.svelte +33 -23
- package/dist/admin/client/entry/header/schedule-popover.svelte +1 -1
- package/dist/admin/client/entry/header/status-badge.svelte +25 -118
- package/dist/admin/client/entry/header/version-history-sheet.svelte +322 -98
- package/dist/admin/client/form/form-submission/form-submission.svelte +271 -83
- package/dist/admin/client/form/form-submission/submission-field.svelte +12 -12
- package/dist/admin/client/form/form-submissions.svelte +421 -139
- package/dist/admin/client/form/submission-link.svelte +8 -2
- package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
- package/dist/admin/client/form/submission-status-badge.svelte +18 -4
- package/dist/admin/client/form/submission-status-badge.svelte.d.ts +1 -0
- package/dist/admin/client/login/lang.d.ts +32 -0
- package/dist/admin/client/login/lang.js +66 -2
- package/dist/admin/client/login/login-form.svelte +237 -95
- package/dist/admin/client/login/login-form.svelte.d.ts +2 -17
- package/dist/admin/client/login/login-page.svelte +34 -98
- package/dist/admin/client/login/reset-password-page.svelte +235 -0
- package/dist/admin/client/login/reset-password-page.svelte.d.ts +4 -0
- package/dist/admin/client/login/schema.d.ts +15 -0
- package/dist/admin/client/login/schema.js +21 -0
- package/dist/admin/client/users/accept-invite-page.svelte +166 -37
- package/dist/admin/client/users/create-user-dialog.svelte +15 -7
- package/dist/admin/client/users/delete-user-dialog.svelte +81 -16
- package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +4 -1
- package/dist/admin/client/users/edit-user-dialog.svelte +3 -0
- package/dist/admin/client/users/invite-user-dialog.svelte +16 -3
- package/dist/admin/client/users/lang.d.ts +27 -0
- package/dist/admin/client/users/lang.js +64 -10
- package/dist/admin/client/users/pending-invitations.svelte +59 -23
- package/dist/admin/client/users/users-page.svelte +471 -72
- package/dist/admin/components/accessibility/accessibility-overview.svelte +2 -7
- package/dist/admin/components/dashboard/a11y-gauge.svelte +90 -0
- package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/accessibility-hub.svelte +13 -12
- package/dist/admin/components/dashboard/changelog-dialog.svelte +157 -0
- package/dist/admin/components/dashboard/changelog-dialog.svelte.d.ts +6 -0
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +71 -113
- package/dist/admin/components/dashboard/index.d.ts +4 -2
- package/dist/admin/components/dashboard/index.js +4 -2
- package/dist/admin/components/dashboard/orphaned-entries-notice.svelte +240 -0
- package/dist/admin/components/dashboard/orphaned-entries-notice.svelte.d.ts +13 -0
- package/dist/admin/components/dashboard/recent-activity.svelte +53 -75
- package/dist/admin/components/dashboard/recent-entries.svelte +94 -0
- package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +18 -0
- package/dist/admin/components/dashboard/stat-card.svelte +2 -2
- package/dist/admin/components/dashboard/tip-of-the-day.svelte +109 -0
- package/dist/admin/components/dashboard/tip-of-the-day.svelte.d.ts +3 -0
- package/dist/admin/components/dashboard/welcome-header.svelte +45 -0
- package/dist/admin/components/dashboard/welcome-header.svelte.d.ts +3 -0
- package/dist/admin/components/fields/{array-field.svelte → blocks-field.svelte} +4 -4
- package/dist/admin/components/fields/{array-field.svelte.d.ts → blocks-field.svelte.d.ts} +5 -5
- package/dist/admin/components/fields/content-field.svelte +27 -0
- package/dist/admin/components/fields/content-field.svelte.d.ts +31 -0
- package/dist/admin/components/fields/field-renderer.svelte +9 -7
- package/dist/admin/components/fields/image-field.svelte +2 -2
- package/dist/admin/components/fields/media-field.svelte +2 -2
- package/dist/admin/components/fields/seo-field.svelte +205 -25
- package/dist/admin/components/fields/simple-array-field.svelte +289 -0
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +30 -0
- package/dist/admin/components/fields/slug-field.svelte +3 -2
- package/dist/admin/components/fields/standalone-field-renderer.svelte +148 -0
- package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +9 -0
- package/dist/admin/components/fields/text-field-wrapper.svelte +147 -3
- package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
- package/dist/admin/components/fields/url-field.svelte +5 -4
- package/dist/admin/components/layout/app-sidebar.svelte +27 -24
- package/dist/admin/components/layout/lang.d.ts +6 -0
- package/dist/admin/components/layout/lang.js +13 -1
- package/dist/admin/components/layout/layout-renderer.svelte +352 -0
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +14 -0
- package/dist/admin/components/layout/nav-breadcrumbs.svelte +4 -4
- package/dist/admin/components/layout/nav-collections.svelte +65 -36
- package/dist/admin/components/layout/nav-footer.svelte +38 -0
- package/dist/admin/components/layout/nav-footer.svelte.d.ts +3 -0
- package/dist/admin/components/layout/nav-forms.svelte +55 -30
- package/dist/admin/components/layout/nav-main.svelte +14 -52
- package/dist/admin/components/layout/nav-search.svelte +4 -3
- package/dist/admin/components/layout/nav-singletons.svelte +59 -17
- package/dist/admin/components/layout/nav-singletons.svelte.d.ts +17 -8
- package/dist/admin/components/layout/site-header.svelte +74 -13
- package/dist/admin/components/media/alt-input.svelte +32 -22
- package/dist/admin/components/media/bulk-action-bar.svelte +139 -150
- package/dist/admin/components/media/file/file-details.svelte +299 -217
- package/dist/admin/components/media/file/file-miniature.svelte +54 -41
- package/dist/admin/components/media/file/file-miniature.svelte.d.ts +1 -0
- package/dist/admin/components/media/file/file-preview.svelte +1 -1
- package/dist/admin/components/media/file-upload.svelte +24 -26
- package/dist/admin/components/media/files-list.svelte +112 -40
- package/dist/admin/components/media/files-list.svelte.d.ts +2 -0
- package/dist/admin/components/media/focal-point-input.svelte +122 -26
- package/dist/admin/components/media/media-library.svelte +127 -70
- package/dist/admin/components/media/media-search.svelte +6 -6
- package/dist/admin/components/media/media-sort.svelte +3 -1
- package/dist/admin/components/media/multi-file-summary.svelte +88 -68
- package/dist/admin/components/media/tag-combobox.svelte +141 -66
- package/dist/admin/components/media/tag-combobox.svelte.d.ts +1 -0
- package/dist/admin/components/media/tag-sidebar.svelte +139 -121
- package/dist/admin/components/tiptap/FigureNodeView.svelte +144 -15
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +254 -0
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte.d.ts +4 -0
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte +212 -0
- package/dist/admin/components/tiptap/SlashCommandPopup.svelte.d.ts +8 -0
- package/dist/admin/components/tiptap/content-editor.svelte +280 -0
- package/dist/admin/components/tiptap/content-editor.svelte.d.ts +9 -0
- package/dist/admin/components/tiptap/editor-toolbar.svelte +230 -0
- package/dist/admin/components/tiptap/editor-toolbar.svelte.d.ts +16 -0
- package/dist/admin/components/tiptap/heading-a11y-plugin.d.ts +2 -0
- package/dist/admin/components/tiptap/heading-a11y-plugin.js +67 -0
- package/dist/admin/components/tiptap/image-dialog.svelte +172 -11
- package/dist/admin/components/tiptap/inline-block-node.d.ts +19 -0
- package/dist/admin/components/tiptap/inline-block-node.js +98 -0
- package/dist/admin/components/tiptap/link-dialog.svelte +9 -4
- package/dist/admin/components/tiptap/slash-command.d.ts +17 -0
- package/dist/admin/components/tiptap/slash-command.js +181 -0
- package/dist/admin/components/tiptap/structured-content-utils.d.ts +21 -0
- package/dist/admin/components/tiptap/structured-content-utils.js +150 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -190
- package/dist/admin/email/invite-template.d.ts +8 -0
- package/dist/admin/email/invite-template.js +99 -0
- package/dist/admin/email/reset-password-template.d.ts +7 -0
- package/dist/admin/email/reset-password-template.js +96 -0
- package/dist/admin/remote/ai.remote.d.ts +1 -0
- package/dist/admin/remote/ai.remote.js +4 -1
- package/dist/admin/remote/entry.remote.d.ts +9 -0
- package/dist/admin/remote/entry.remote.js +58 -8
- package/dist/admin/remote/preview.remote.js +2 -1
- package/dist/admin/shared/password-schema.d.ts +5 -0
- package/dist/admin/shared/password-schema.js +10 -0
- package/dist/admin/state/content-language.svelte.d.ts +3 -0
- package/dist/admin/state/content-language.svelte.js +8 -0
- package/dist/admin/styles/admin.css +1530 -151
- package/dist/admin/utils/formatDate.d.ts +1 -0
- package/dist/admin/utils/formatDate.js +8 -0
- package/dist/admin/utils/roleLabel.d.ts +2 -0
- package/dist/admin/utils/roleLabel.js +13 -0
- package/dist/admin/utils/translationStatus.d.ts +17 -0
- package/dist/admin/utils/translationStatus.js +134 -0
- package/dist/ai-claude/index.d.ts +2 -0
- package/dist/ai-claude/index.js +56 -0
- package/dist/cms/runtime/api.d.ts +6 -1
- package/dist/cms/runtime/api.js +3 -0
- package/dist/cms/runtime/schemas.d.ts +9 -1
- package/dist/cms/runtime/schemas.js +8 -0
- package/dist/cms/runtime/types.d.ts +82 -10
- package/dist/cms/runtime/types.js +4 -0
- package/dist/components/ui/accordion/accordion.stories.svelte +39 -0
- package/dist/components/ui/accordion/accordion.stories.svelte.d.ts +27 -0
- package/dist/components/ui/alert/alert.stories.svelte +53 -0
- package/dist/components/ui/alert/alert.stories.svelte.d.ts +27 -0
- package/dist/components/ui/alert/alert.svelte +5 -0
- package/dist/components/ui/alert/alert.svelte.d.ts +9 -0
- package/dist/components/ui/avatar/avatar.stories.svelte +16 -0
- package/dist/components/ui/avatar/avatar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/badge/badge.stories.svelte +33 -0
- package/dist/components/ui/badge/badge.stories.svelte.d.ts +27 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +33 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte.d.ts +27 -0
- package/dist/components/ui/button/button.stories.svelte +43 -0
- package/dist/components/ui/button/button.stories.svelte.d.ts +27 -0
- package/dist/components/ui/button/button.svelte +1 -2
- package/dist/components/ui/button/button.svelte.d.ts +0 -3
- package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
- package/dist/components/ui/card/card.stories.svelte +42 -0
- package/dist/components/ui/card/card.stories.svelte.d.ts +27 -0
- package/dist/components/ui/command/command.stories.svelte +51 -0
- package/dist/components/ui/command/command.stories.svelte.d.ts +27 -0
- package/dist/components/ui/dialog/dialog.stories.svelte +29 -0
- package/dist/components/ui/dialog/dialog.stories.svelte.d.ts +27 -0
- package/dist/components/ui/field/field-label.svelte.d.ts +1 -1
- package/dist/components/ui/field/field.stories.svelte +21 -0
- package/dist/components/ui/field/field.stories.svelte.d.ts +27 -0
- package/dist/components/ui/input/input.stories.svelte +40 -0
- package/dist/components/ui/input/input.stories.svelte.d.ts +27 -0
- package/dist/components/ui/input/input.svelte +2 -4
- package/dist/components/ui/item/item-separator.svelte.d.ts +1 -1
- package/dist/components/ui/label/label.stories.svelte +20 -0
- package/dist/components/ui/label/label.stories.svelte.d.ts +27 -0
- package/dist/components/ui/popover/popover.stories.svelte +29 -0
- package/dist/components/ui/popover/popover.stories.svelte.d.ts +27 -0
- package/dist/components/ui/select/select-group-heading.svelte.d.ts +1 -1
- package/dist/components/ui/select/select.stories.svelte +23 -0
- package/dist/components/ui/select/select.stories.svelte.d.ts +27 -0
- package/dist/components/ui/separator/separator.stories.svelte +24 -0
- package/dist/components/ui/separator/separator.stories.svelte.d.ts +27 -0
- package/dist/components/ui/sheet/sheet.stories.svelte +29 -0
- package/dist/components/ui/sheet/sheet.stories.svelte.d.ts +27 -0
- package/dist/components/ui/sidebar/sidebar-group.svelte +3 -3
- package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +2 -2
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte +28 -30
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +7 -7
- package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-trigger.svelte +4 -4
- package/dist/components/ui/sonner/sonner.stories.svelte +22 -0
- package/dist/components/ui/sonner/sonner.stories.svelte.d.ts +26 -0
- package/dist/components/ui/sonner/sonner.svelte +8 -2
- package/dist/components/ui/sonner/toast-demo.svelte +29 -0
- package/dist/components/ui/sonner/toast-demo.svelte.d.ts +6 -0
- package/dist/components/ui/textarea/textarea.stories.svelte +22 -0
- package/dist/components/ui/textarea/textarea.stories.svelte.d.ts +27 -0
- package/dist/components/ui/textarea/textarea.svelte +0 -2
- package/dist/components/ui/toggle/toggle.stories.svelte +22 -0
- package/dist/components/ui/toggle/toggle.stories.svelte.d.ts +27 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte +17 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte +26 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte.d.ts +27 -0
- package/dist/core/fields/fieldSchemaToTs.d.ts +1 -0
- package/dist/core/fields/fieldSchemaToTs.js +133 -1
- package/dist/core/fields/layoutUtils.d.ts +17 -0
- package/dist/core/fields/layoutUtils.js +149 -0
- package/dist/core/fields/structuredToHtml.d.ts +9 -0
- package/dist/core/fields/structuredToHtml.js +161 -0
- package/dist/core/server/entries/operations/create.js +2 -1
- package/dist/core/server/entries/operations/get.js +10 -7
- package/dist/core/server/entries/operations/update.d.ts +3 -0
- package/dist/core/server/entries/operations/update.js +30 -2
- package/dist/core/server/fields/queryStructuredContent.d.ts +15 -0
- package/dist/core/server/fields/queryStructuredContent.js +65 -0
- package/dist/core/server/fields/resolveImageFields.js +51 -2
- package/dist/core/server/fields/resolveRelationFields.js +2 -2
- package/dist/core/server/fields/resolveRichtextLinks.js +80 -13
- package/dist/core/server/fields/resolveUrlFields.js +57 -6
- package/dist/core/server/fields/slugResolver.d.ts +10 -0
- package/dist/core/server/fields/slugResolver.js +34 -0
- package/dist/core/server/generator/fields.js +15 -4
- package/dist/core/server/generator/generator.js +3 -2
- package/dist/db-postgres/index.js +10 -6
- package/dist/files-local/index.js +126 -64
- package/dist/server/auth.d.ts +5 -0
- package/dist/server/auth.js +12 -1
- package/dist/sveltekit/components/structured-content.svelte +204 -0
- package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
- package/dist/sveltekit/config.d.ts +13 -3
- package/dist/sveltekit/index.d.ts +3 -0
- package/dist/sveltekit/index.js +3 -0
- package/dist/sveltekit/server/handle.js +1 -0
- package/dist/types/config.d.ts +3 -0
- package/dist/types/entries.d.ts +3 -0
- package/dist/types/fields.d.ts +19 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/layout.d.ts +54 -0
- package/dist/types/layout.js +6 -0
- package/dist/types/structured-content.d.ts +63 -0
- package/dist/types/structured-content.js +1 -0
- package/dist/updates/0.1.5/index.d.ts +2 -0
- package/dist/updates/0.1.5/index.js +18 -0
- package/dist/updates/0.2.0/index.d.ts +2 -0
- package/dist/updates/0.2.0/index.js +11 -0
- package/dist/updates/0.2.2/index.d.ts +2 -0
- package/dist/updates/0.2.2/index.js +13 -0
- package/dist/updates/0.5.0/index.d.ts +2 -0
- package/dist/updates/0.5.0/index.js +14 -0
- package/dist/updates/0.5.1/index.d.ts +2 -0
- package/dist/updates/0.5.1/index.js +17 -0
- package/dist/updates/index.js +6 -1
- package/package.json +16 -9
- package/dist/admin/components/dashboard/updates-banner.svelte +0 -170
- package/dist/admin/components/dashboard/updates-banner.svelte.d.ts +0 -3
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const VOID_TAGS = new Set(['hr', 'br', 'img']);
|
|
2
|
+
function escapeHtml(str) {
|
|
3
|
+
return str
|
|
4
|
+
.replace(/&/g, '&')
|
|
5
|
+
.replace(/</g, '<')
|
|
6
|
+
.replace(/>/g, '>')
|
|
7
|
+
.replace(/"/g, '"');
|
|
8
|
+
}
|
|
9
|
+
function resolveUrl(url, baseUrl) {
|
|
10
|
+
if (!baseUrl || !url)
|
|
11
|
+
return url;
|
|
12
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//'))
|
|
13
|
+
return url;
|
|
14
|
+
return `${baseUrl.replace(/\/$/, '')}${url.startsWith('/') ? '' : '/'}${url}`;
|
|
15
|
+
}
|
|
16
|
+
function renderMarks(text, marks, options) {
|
|
17
|
+
if (!marks?.length)
|
|
18
|
+
return escapeHtml(text);
|
|
19
|
+
let html = escapeHtml(text);
|
|
20
|
+
for (const mark of marks) {
|
|
21
|
+
switch (mark.type) {
|
|
22
|
+
case 'bold':
|
|
23
|
+
html = `<strong>${html}</strong>`;
|
|
24
|
+
break;
|
|
25
|
+
case 'italic':
|
|
26
|
+
html = `<em>${html}</em>`;
|
|
27
|
+
break;
|
|
28
|
+
case 'underline':
|
|
29
|
+
html = `<u>${html}</u>`;
|
|
30
|
+
break;
|
|
31
|
+
case 'strike':
|
|
32
|
+
html = `<s>${html}</s>`;
|
|
33
|
+
break;
|
|
34
|
+
case 'code':
|
|
35
|
+
html = `<code>${html}</code>`;
|
|
36
|
+
break;
|
|
37
|
+
case 'highlight':
|
|
38
|
+
html = `<mark>${html}</mark>`;
|
|
39
|
+
break;
|
|
40
|
+
case 'link': {
|
|
41
|
+
const attrs = mark.attrs ?? {};
|
|
42
|
+
const href = resolveUrl(attrs.href ?? '', options.baseUrl);
|
|
43
|
+
const target = attrs.target;
|
|
44
|
+
const title = attrs.title;
|
|
45
|
+
const ariaLabel = attrs['aria-label'];
|
|
46
|
+
let attrStr = ` href="${escapeHtml(href)}"`;
|
|
47
|
+
if (target === '_blank') {
|
|
48
|
+
attrStr += ` target="_blank" rel="noopener noreferrer"`;
|
|
49
|
+
}
|
|
50
|
+
else if (target) {
|
|
51
|
+
attrStr += ` target="${escapeHtml(target)}"`;
|
|
52
|
+
}
|
|
53
|
+
if (title)
|
|
54
|
+
attrStr += ` title="${escapeHtml(title)}"`;
|
|
55
|
+
if (ariaLabel)
|
|
56
|
+
attrStr += ` aria-label="${escapeHtml(ariaLabel)}"`;
|
|
57
|
+
html = `<a${attrStr}>${html}</a>`;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return html;
|
|
63
|
+
}
|
|
64
|
+
function renderNode(node, options) {
|
|
65
|
+
const children = node.content?.map((c) => renderNode(c, options)).join('') ?? '';
|
|
66
|
+
switch (node.type) {
|
|
67
|
+
case 'doc':
|
|
68
|
+
return children;
|
|
69
|
+
case 'text':
|
|
70
|
+
return renderMarks(node.text ?? '', node.marks, options);
|
|
71
|
+
case 'paragraph':
|
|
72
|
+
return `<p>${children}</p>`;
|
|
73
|
+
case 'heading': {
|
|
74
|
+
const level = node.attrs?.level ?? 2;
|
|
75
|
+
return `<h${level}>${children}</h${level}>`;
|
|
76
|
+
}
|
|
77
|
+
case 'blockquote':
|
|
78
|
+
return `<blockquote>${children}</blockquote>`;
|
|
79
|
+
case 'bulletList':
|
|
80
|
+
return `<ul>${children}</ul>`;
|
|
81
|
+
case 'orderedList':
|
|
82
|
+
return `<ol>${children}</ol>`;
|
|
83
|
+
case 'listItem':
|
|
84
|
+
return `<li>${children}</li>`;
|
|
85
|
+
case 'codeBlock': {
|
|
86
|
+
const lang = node.attrs?.language;
|
|
87
|
+
const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : '';
|
|
88
|
+
return `<pre><code${langAttr}>${children}</code></pre>`;
|
|
89
|
+
}
|
|
90
|
+
case 'horizontalRule':
|
|
91
|
+
return '<hr>';
|
|
92
|
+
case 'hardBreak':
|
|
93
|
+
return '<br>';
|
|
94
|
+
case 'figure': {
|
|
95
|
+
const attrs = node.attrs ?? {};
|
|
96
|
+
const src = resolveUrl(attrs.src ?? '', options.baseUrl);
|
|
97
|
+
const alt = escapeHtml(attrs.alt ?? '');
|
|
98
|
+
const caption = attrs.caption;
|
|
99
|
+
const width = attrs.width;
|
|
100
|
+
const height = attrs.height;
|
|
101
|
+
let imgAttrs = ` src="${escapeHtml(src)}" alt="${alt}"`;
|
|
102
|
+
if (width)
|
|
103
|
+
imgAttrs += ` width="${width}"`;
|
|
104
|
+
if (height)
|
|
105
|
+
imgAttrs += ` height="${height}"`;
|
|
106
|
+
let html = `<figure><img${imgAttrs}>`;
|
|
107
|
+
if (caption)
|
|
108
|
+
html += `<figcaption>${escapeHtml(caption)}</figcaption>`;
|
|
109
|
+
html += '</figure>';
|
|
110
|
+
return html;
|
|
111
|
+
}
|
|
112
|
+
case 'image': {
|
|
113
|
+
const attrs = node.attrs ?? {};
|
|
114
|
+
const src = resolveUrl(attrs.src ?? '', options.baseUrl);
|
|
115
|
+
const alt = escapeHtml(attrs.alt ?? '');
|
|
116
|
+
const width = attrs.width;
|
|
117
|
+
const height = attrs.height;
|
|
118
|
+
let imgAttrs = ` src="${escapeHtml(src)}" alt="${alt}"`;
|
|
119
|
+
if (width)
|
|
120
|
+
imgAttrs += ` width="${width}"`;
|
|
121
|
+
if (height)
|
|
122
|
+
imgAttrs += ` height="${height}"`;
|
|
123
|
+
return `<img${imgAttrs}>`;
|
|
124
|
+
}
|
|
125
|
+
case 'video': {
|
|
126
|
+
const attrs = node.attrs ?? {};
|
|
127
|
+
const src = resolveUrl(attrs.src ?? '', options.baseUrl);
|
|
128
|
+
const poster = attrs.poster;
|
|
129
|
+
const width = attrs.width;
|
|
130
|
+
const height = attrs.height;
|
|
131
|
+
let videoAttrs = ' controls';
|
|
132
|
+
if (poster)
|
|
133
|
+
videoAttrs += ` poster="${escapeHtml(resolveUrl(poster, options.baseUrl))}"`;
|
|
134
|
+
if (width)
|
|
135
|
+
videoAttrs += ` width="${width}"`;
|
|
136
|
+
if (height)
|
|
137
|
+
videoAttrs += ` height="${height}"`;
|
|
138
|
+
return `<video${videoAttrs}><source src="${escapeHtml(src)}"></video>`;
|
|
139
|
+
}
|
|
140
|
+
case 'table':
|
|
141
|
+
return `<table>${children}</table>`;
|
|
142
|
+
case 'tableRow':
|
|
143
|
+
return `<tr>${children}</tr>`;
|
|
144
|
+
case 'tableCell':
|
|
145
|
+
return `<td>${children}</td>`;
|
|
146
|
+
case 'tableHeader':
|
|
147
|
+
return `<th>${children}</th>`;
|
|
148
|
+
case 'inlineBlock': {
|
|
149
|
+
const attrs = node.attrs ?? {};
|
|
150
|
+
const blockType = attrs.blockType;
|
|
151
|
+
const blockData = attrs.blockData ?? {};
|
|
152
|
+
return options.inlineBlock?.(blockType, blockData) ?? '';
|
|
153
|
+
}
|
|
154
|
+
default:
|
|
155
|
+
return children;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Convert a StructuredContentDoc to an HTML string. */
|
|
159
|
+
export function structuredToHtml(doc, options = {}) {
|
|
160
|
+
return renderNode(doc, options);
|
|
161
|
+
}
|
|
@@ -4,6 +4,7 @@ import { entryTypes } from '../../../../types/entries.js';
|
|
|
4
4
|
import z from 'zod';
|
|
5
5
|
import { getDbEntryOrThrow } from './get.js';
|
|
6
6
|
import { generateZodSchemaFromFields } from '../../../fields/fieldSchemaToTs.js';
|
|
7
|
+
import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
|
|
7
8
|
export const createEntrySchema = z.object({
|
|
8
9
|
slug: z.string(),
|
|
9
10
|
type: z.enum(entryTypes)
|
|
@@ -32,7 +33,7 @@ export const createEntryVersion = async (data, options) => {
|
|
|
32
33
|
const entry = await getDbEntryOrThrow({ id: data.entryId });
|
|
33
34
|
const config = getCMS().getBySlug(entry.slug);
|
|
34
35
|
const languages = getCMS().languages;
|
|
35
|
-
const schema = generateZodSchemaFromFields(config
|
|
36
|
+
const schema = generateZodSchemaFromFields(getFieldsFromConfig(config), languages);
|
|
36
37
|
const parsedData = schema.safeParse(data.data);
|
|
37
38
|
if (!parsedData.success) {
|
|
38
39
|
throw Error('Invalid data: ' + JSON.stringify(parsedData.error.flatten()));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getCMS } from '../../../cms.js';
|
|
2
2
|
import { populateEntryData } from '../../fields/populateEntry.js';
|
|
3
|
+
import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
|
|
3
4
|
// Helper function to transform string values to localized objects
|
|
4
5
|
function transformDataValuesToLocalized(dataValues, defaultLanguage) {
|
|
5
6
|
const result = {};
|
|
@@ -70,16 +71,17 @@ export const getRawEntries = async (options) => {
|
|
|
70
71
|
const scheduledVersion = publishedOrScheduledVersion && isScheduled
|
|
71
72
|
? publishedOrScheduledVersion
|
|
72
73
|
: null;
|
|
73
|
-
// Draft = latest version by versionNumber
|
|
74
|
-
const
|
|
75
|
-
.
|
|
74
|
+
// Draft = latest non-published version by versionNumber
|
|
75
|
+
const draftVersion = versions
|
|
76
|
+
.filter((v) => v.id !== entry.publishedVersionId)
|
|
77
|
+
.sort((a, b) => b.versionNumber - a.versionNumber)[0] || null;
|
|
76
78
|
return {
|
|
77
79
|
...entry,
|
|
78
80
|
collection: getCMS().getBySlug(entry.slug),
|
|
79
81
|
versions: versions.sort((a, b) => b.versionNumber - a.versionNumber),
|
|
80
82
|
publishedVersion,
|
|
81
83
|
scheduledVersion,
|
|
82
|
-
draftVersion
|
|
84
|
+
draftVersion
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
catch {
|
|
@@ -92,7 +94,8 @@ export const getRawEntries = async (options) => {
|
|
|
92
94
|
export const getRawEntry = async (options) => {
|
|
93
95
|
const [entry] = await getRawEntries({
|
|
94
96
|
...options,
|
|
95
|
-
ids: options.id ? [options.id] : undefined
|
|
97
|
+
ids: options.id ? [options.id] : undefined,
|
|
98
|
+
includeArchived: options.includeArchived
|
|
96
99
|
});
|
|
97
100
|
return entry || null;
|
|
98
101
|
};
|
|
@@ -237,7 +240,7 @@ export const getEntries = async (options = {}) => {
|
|
|
237
240
|
const config = getCMS().getBySlug(dbEntry.slug);
|
|
238
241
|
return {
|
|
239
242
|
...dbEntry,
|
|
240
|
-
data: await populateEntryData(version.data, config
|
|
243
|
+
data: await populateEntryData(version.data, getFieldsFromConfig(config), language),
|
|
241
244
|
status,
|
|
242
245
|
publishedAt: dbEntry.publishedAt,
|
|
243
246
|
populated: true
|
|
@@ -295,7 +298,7 @@ export const getEntryVersion = async (options) => {
|
|
|
295
298
|
const config = getCMS().getBySlug(dbEntry.slug);
|
|
296
299
|
return {
|
|
297
300
|
...dbEntry,
|
|
298
|
-
data: await populateEntryData(dbEntryVersion.data, config
|
|
301
|
+
data: await populateEntryData(dbEntryVersion.data, getFieldsFromConfig(config), language)
|
|
299
302
|
};
|
|
300
303
|
}
|
|
301
304
|
catch (error) {
|
|
@@ -17,4 +17,7 @@ export declare const updateEntryVersion: (id: string, data: Partial<DbEntryVersi
|
|
|
17
17
|
export declare const updateEntryVersionCommandTypes: readonly ["draft", "published-now", "published-scheduled", "cancel-published"];
|
|
18
18
|
export type UpdateEntryVersionCommandType = (typeof updateEntryVersionCommandTypes)[number];
|
|
19
19
|
export declare const pruneOldDraftVersions: (entryId: string) => Promise<void>;
|
|
20
|
+
export declare const upsertDraftVersion: (entryId: string, data: Record<string, unknown>, options?: {
|
|
21
|
+
skipValidation?: boolean;
|
|
22
|
+
}) => Promise<DbEntryVersion>;
|
|
20
23
|
export declare const unpublishEntry: (entryId: string) => Promise<void>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getCMS } from '../../../cms.js';
|
|
2
2
|
import { generateZodSchemaFromFields } from '../../../fields/fieldSchemaToTs.js';
|
|
3
|
+
import { getFieldsFromConfig } from '../../../fields/layoutUtils.js';
|
|
3
4
|
import z from 'zod';
|
|
4
|
-
import { getDbEntryOrThrow, getDbEntryVersionOrThrow } from './get.js';
|
|
5
|
+
import { getDbEntryOrThrow, getDbEntryVersionOrThrow, getDbEntryVersions } from './get.js';
|
|
6
|
+
import { createEntryVersion } from './create.js';
|
|
5
7
|
export const updateEntrySchema = z.object({
|
|
6
8
|
availableLocales: z.array(z.string()).optional(),
|
|
7
9
|
archivedAt: z.date().nullable().optional(),
|
|
@@ -57,7 +59,7 @@ export const updateEntryVersion = async (id, data) => {
|
|
|
57
59
|
const entry = await getDbEntryOrThrow({ id: version.entryId });
|
|
58
60
|
const config = getCMS().getBySlug(entry.slug);
|
|
59
61
|
const languages = getCMS().languages;
|
|
60
|
-
const schema = generateZodSchemaFromFields(config
|
|
62
|
+
const schema = generateZodSchemaFromFields(getFieldsFromConfig(config), languages);
|
|
61
63
|
const parsedData = schema.safeParse(filteredData.data);
|
|
62
64
|
if (!parsedData.success) {
|
|
63
65
|
throw Error('Invalid data: ' + parsedData.error.flatten());
|
|
@@ -102,6 +104,32 @@ export const pruneOldDraftVersions = async (entryId) => {
|
|
|
102
104
|
await Promise.all(toDelete.map((v) => getCMS().databaseAdapter.deleteEntryVersion({ id: v.id })));
|
|
103
105
|
}
|
|
104
106
|
};
|
|
107
|
+
export const upsertDraftVersion = async (entryId, data, options) => {
|
|
108
|
+
const entry = await getDbEntryOrThrow({ id: entryId });
|
|
109
|
+
const versions = await getDbEntryVersions({ entryIds: [entryId] });
|
|
110
|
+
// Sort desc by versionNumber, find latest non-published draft
|
|
111
|
+
const sorted = versions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
112
|
+
const latestDraft = sorted.find((v) => v.id !== entry.publishedVersionId);
|
|
113
|
+
if (latestDraft) {
|
|
114
|
+
// Compare data — if identical, skip
|
|
115
|
+
const existingData = JSON.stringify(latestDraft.data);
|
|
116
|
+
const newData = JSON.stringify(data);
|
|
117
|
+
if (existingData === newData) {
|
|
118
|
+
return latestDraft;
|
|
119
|
+
}
|
|
120
|
+
// Different data — update in place
|
|
121
|
+
const updated = await getCMS().databaseAdapter.updateEntryVersion({
|
|
122
|
+
id: latestDraft.id,
|
|
123
|
+
data: {
|
|
124
|
+
data: data,
|
|
125
|
+
createdAt: new Date()
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return updated;
|
|
129
|
+
}
|
|
130
|
+
// No draft exists — create new version
|
|
131
|
+
return createEntryVersion({ entryId, data }, options);
|
|
132
|
+
};
|
|
105
133
|
export const unpublishEntry = async (entryId) => {
|
|
106
134
|
await updateEntry(entryId, {
|
|
107
135
|
publishedVersionId: null,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SCNode, StructuredContentDoc, SCInlineBlockAttrs } from '../../../types/structured-content.js';
|
|
2
|
+
export interface MediaRef {
|
|
3
|
+
type: 'image' | 'video';
|
|
4
|
+
src: string;
|
|
5
|
+
alt?: string;
|
|
6
|
+
mediaId?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Extract block-level nodes, optionally filtered by type. */
|
|
9
|
+
export declare function extractBlocks(doc: StructuredContentDoc, type?: string): SCNode[];
|
|
10
|
+
/** Extract inline block nodes, optionally filtered by blockType. */
|
|
11
|
+
export declare function extractInlineBlocks(doc: StructuredContentDoc, blockType?: string): SCInlineBlockAttrs[];
|
|
12
|
+
/** Extract all plain text from doc (useful for excerpts, search indexing). */
|
|
13
|
+
export declare function extractText(doc: StructuredContentDoc): string;
|
|
14
|
+
/** Extract media references from figure/video/image nodes. */
|
|
15
|
+
export declare function extractMediaRefs(doc: StructuredContentDoc): MediaRef[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { walkNodes } from '../../../admin/components/tiptap/structured-content-utils.js';
|
|
2
|
+
/** Extract block-level nodes, optionally filtered by type. */
|
|
3
|
+
export function extractBlocks(doc, type) {
|
|
4
|
+
const result = [];
|
|
5
|
+
walkNodes(doc.content, (node) => {
|
|
6
|
+
if (type ? node.type === type : !['doc', 'text'].includes(node.type)) {
|
|
7
|
+
result.push(node);
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
/** Extract inline block nodes, optionally filtered by blockType. */
|
|
13
|
+
export function extractInlineBlocks(doc, blockType) {
|
|
14
|
+
const result = [];
|
|
15
|
+
walkNodes(doc.content, (node) => {
|
|
16
|
+
if (node.type !== 'inlineBlock' || !node.attrs)
|
|
17
|
+
return;
|
|
18
|
+
const attrs = node.attrs;
|
|
19
|
+
if (blockType ? attrs.blockType === blockType : true) {
|
|
20
|
+
result.push(attrs);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
/** Extract all plain text from doc (useful for excerpts, search indexing). */
|
|
26
|
+
export function extractText(doc) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
walkNodes(doc.content, (node) => {
|
|
29
|
+
if (node.type === 'text' && node.text) {
|
|
30
|
+
parts.push(node.text);
|
|
31
|
+
}
|
|
32
|
+
else if (node.type === 'hardBreak') {
|
|
33
|
+
parts.push('\n');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return parts.join('');
|
|
37
|
+
}
|
|
38
|
+
/** Extract media references from figure/video/image nodes. */
|
|
39
|
+
export function extractMediaRefs(doc) {
|
|
40
|
+
const refs = [];
|
|
41
|
+
walkNodes(doc.content, (node) => {
|
|
42
|
+
if (node.type === 'figure' || node.type === 'image') {
|
|
43
|
+
const attrs = node.attrs;
|
|
44
|
+
if (attrs?.src) {
|
|
45
|
+
refs.push({
|
|
46
|
+
type: 'image',
|
|
47
|
+
src: attrs.src,
|
|
48
|
+
alt: attrs.alt ?? undefined,
|
|
49
|
+
mediaId: attrs['data-media-id'] ?? undefined
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (node.type === 'video') {
|
|
54
|
+
const attrs = node.attrs;
|
|
55
|
+
if (attrs?.src) {
|
|
56
|
+
refs.push({
|
|
57
|
+
type: 'video',
|
|
58
|
+
src: attrs.src,
|
|
59
|
+
mediaId: attrs['data-media-id'] ?? undefined
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return refs;
|
|
65
|
+
}
|
|
@@ -3,6 +3,7 @@ const PUBLIC_URL = env.PUBLIC_URL ?? '';
|
|
|
3
3
|
import { getCMS } from '../../cms.js';
|
|
4
4
|
import z from 'zod';
|
|
5
5
|
import { getImageStyles } from './utils/imageStyles.js';
|
|
6
|
+
import { extractMediaIds as extractMediaIdsFromDoc, walkMediaNodes, cloneDoc } from '../../../admin/components/tiptap/structured-content-utils.js';
|
|
6
7
|
export async function resolveMediaFields(data, fields) {
|
|
7
8
|
const mediaIds = [];
|
|
8
9
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -39,7 +40,7 @@ export async function resolveMediaFields(data, fields) {
|
|
|
39
40
|
case 'object':
|
|
40
41
|
collectIds(val.data, field.fields);
|
|
41
42
|
break;
|
|
42
|
-
case '
|
|
43
|
+
case 'blocks':
|
|
43
44
|
if (Array.isArray(val)) {
|
|
44
45
|
val.forEach((item) => {
|
|
45
46
|
// Find the object definition for this item based on its slug
|
|
@@ -50,6 +51,17 @@ export async function resolveMediaFields(data, fields) {
|
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
break;
|
|
54
|
+
case 'content': {
|
|
55
|
+
// Content field is localized: { lang: StructuredContentDoc }
|
|
56
|
+
if (typeof val === 'object' && val !== null) {
|
|
57
|
+
for (const doc of Object.values(val)) {
|
|
58
|
+
if (doc && typeof doc === 'object' && doc.type === 'doc') {
|
|
59
|
+
mediaIds.push(...extractMediaIdsFromDoc(doc));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
}
|
|
55
67
|
};
|
|
@@ -178,7 +190,7 @@ export async function resolveMediaFields(data, fields) {
|
|
|
178
190
|
data: await resolveValues(val.data, field.fields)
|
|
179
191
|
};
|
|
180
192
|
break;
|
|
181
|
-
case '
|
|
193
|
+
case 'blocks':
|
|
182
194
|
result[field.slug] = await Promise.all(
|
|
183
195
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
184
196
|
val.map(async (item) => {
|
|
@@ -194,6 +206,43 @@ export async function resolveMediaFields(data, fields) {
|
|
|
194
206
|
return item;
|
|
195
207
|
}));
|
|
196
208
|
break;
|
|
209
|
+
case 'content': {
|
|
210
|
+
// Content field is localized: { lang: StructuredContentDoc }
|
|
211
|
+
if (typeof val === 'object' && val !== null) {
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
const resolved = {};
|
|
214
|
+
for (const [lang, doc] of Object.entries(val)) {
|
|
215
|
+
if (doc && typeof doc === 'object' && doc.type === 'doc') {
|
|
216
|
+
const cloned = cloneDoc(doc);
|
|
217
|
+
walkMediaNodes(cloned, (node) => {
|
|
218
|
+
const mediaId = node.attrs?.['data-media-id'];
|
|
219
|
+
if (!mediaId)
|
|
220
|
+
return;
|
|
221
|
+
const mediaFile = mediaMap[mediaId];
|
|
222
|
+
if (!mediaFile)
|
|
223
|
+
return;
|
|
224
|
+
node.attrs.src = mediaFile.url;
|
|
225
|
+
if (mediaFile.width)
|
|
226
|
+
node.attrs.width = mediaFile.width;
|
|
227
|
+
if (mediaFile.height)
|
|
228
|
+
node.attrs.height = mediaFile.height;
|
|
229
|
+
if (mediaFile.alt && !node.attrs.alt)
|
|
230
|
+
node.attrs.alt = mediaFile.alt;
|
|
231
|
+
node.attrs._media = {
|
|
232
|
+
data: mediaFile,
|
|
233
|
+
...(mediaFile.type === 'image' ? { blurDataUrl: mediaFile.blurDataUrl } : {})
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
resolved[lang] = cloned;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
resolved[lang] = doc;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
result[field.slug] = resolved;
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
197
246
|
default:
|
|
198
247
|
result[field.slug] = val;
|
|
199
248
|
}
|
|
@@ -26,7 +26,7 @@ export async function resolveRelationFields(data, fields, language) {
|
|
|
26
26
|
case 'object':
|
|
27
27
|
collectIds(val.data, field.fields);
|
|
28
28
|
break;
|
|
29
|
-
case '
|
|
29
|
+
case 'blocks':
|
|
30
30
|
if (Array.isArray(val)) {
|
|
31
31
|
val.forEach((item) => {
|
|
32
32
|
// Find the object definition for this item based on its slug
|
|
@@ -78,7 +78,7 @@ export async function resolveRelationFields(data, fields, language) {
|
|
|
78
78
|
data: resolveValues(val.data, field.fields)
|
|
79
79
|
};
|
|
80
80
|
break;
|
|
81
|
-
case '
|
|
81
|
+
case 'blocks':
|
|
82
82
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
83
|
result[field.slug] = val.map((item) => {
|
|
84
84
|
// Find the object definition for this item based on its slug
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getCMS } from '../../cms.js';
|
|
2
|
+
import { extractEntryIds as extractEntryIdsFromDoc, extractMediaIds as extractMediaIdsFromDoc, cloneDoc, walkLinkMarks, walkInlineBlockNodes } from '../../../admin/components/tiptap/structured-content-utils.js';
|
|
3
|
+
import { getEntrySlugPath, getSlugFromEntryData } from './slugResolver.js';
|
|
2
4
|
const ENTRY_ID_RE = /data-entry-id="([0-9a-f-]{36})"/g;
|
|
3
5
|
function extractEntryIds(html) {
|
|
4
6
|
const ids = [];
|
|
@@ -7,15 +9,47 @@ function extractEntryIds(html) {
|
|
|
7
9
|
}
|
|
8
10
|
return ids;
|
|
9
11
|
}
|
|
10
|
-
function resolveHtml(html,
|
|
12
|
+
function resolveHtml(html, slugMap) {
|
|
11
13
|
return html.replace(/<a\s([^>]*data-entry-id="([0-9a-f-]{36})"[^>]*)>/g, (match, _attrs, id) => {
|
|
12
|
-
const
|
|
13
|
-
const slug = entryData?.seo?.slug;
|
|
14
|
+
const slug = slugMap[id];
|
|
14
15
|
if (!slug)
|
|
15
16
|
return match;
|
|
16
17
|
return match.replace(/href="[^"]*"/, `href="${slug}"`);
|
|
17
18
|
});
|
|
18
19
|
}
|
|
20
|
+
/** Extract entry IDs from inlineBlock blockData relation fields. */
|
|
21
|
+
function extractEntryIdsFromInlineBlocks(doc) {
|
|
22
|
+
const ids = [];
|
|
23
|
+
walkInlineBlockNodes(doc, (node) => {
|
|
24
|
+
const data = node.attrs?.blockData;
|
|
25
|
+
if (!data || typeof data !== 'object')
|
|
26
|
+
return;
|
|
27
|
+
for (const val of Object.values(data)) {
|
|
28
|
+
if (typeof val === 'string' && val.length === 36)
|
|
29
|
+
ids.push(val);
|
|
30
|
+
if (Array.isArray(val)) {
|
|
31
|
+
for (const item of val) {
|
|
32
|
+
if (typeof item === 'string' && item.length === 36)
|
|
33
|
+
ids.push(item);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
function resolveContentDoc(doc, slugMap) {
|
|
41
|
+
const cloned = cloneDoc(doc);
|
|
42
|
+
walkLinkMarks(cloned, (mark) => {
|
|
43
|
+
const entryId = mark.attrs?.['data-entry-id'];
|
|
44
|
+
if (!entryId)
|
|
45
|
+
return;
|
|
46
|
+
const slug = slugMap[entryId];
|
|
47
|
+
if (slug && mark.attrs) {
|
|
48
|
+
mark.attrs.href = slug;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return cloned;
|
|
52
|
+
}
|
|
19
53
|
export async function resolveRichtextLinks(data, fields, language) {
|
|
20
54
|
const entriesIds = [];
|
|
21
55
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -39,10 +73,22 @@ export async function resolveRichtextLinks(data, fields, language) {
|
|
|
39
73
|
}
|
|
40
74
|
break;
|
|
41
75
|
}
|
|
76
|
+
case 'content': {
|
|
77
|
+
// Content field is localized: { lang: StructuredContentDoc, ... }
|
|
78
|
+
if (typeof val === 'object' && val !== null) {
|
|
79
|
+
for (const doc of Object.values(val)) {
|
|
80
|
+
if (doc && typeof doc === 'object' && doc.type === 'doc') {
|
|
81
|
+
entriesIds.push(...extractEntryIdsFromDoc(doc));
|
|
82
|
+
entriesIds.push(...extractEntryIdsFromInlineBlocks(doc));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
42
88
|
case 'object':
|
|
43
89
|
collectIds(val.data, field.fields);
|
|
44
90
|
break;
|
|
45
|
-
case '
|
|
91
|
+
case 'blocks':
|
|
46
92
|
if (Array.isArray(val)) {
|
|
47
93
|
val.forEach((item) => {
|
|
48
94
|
const objectDef = field.of.find((objDef) => objDef.slug === item.slug);
|
|
@@ -71,15 +117,20 @@ export async function resolveRichtextLinks(data, fields, language) {
|
|
|
71
117
|
const versions = await db.getEntryVersions({
|
|
72
118
|
ids: publishedVersionIds
|
|
73
119
|
});
|
|
74
|
-
// Build map: entryId →
|
|
75
|
-
const
|
|
120
|
+
// Build map: entryId → collectionSlug for config lookup
|
|
121
|
+
const entryConfigSlugMap = {};
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
entryConfigSlugMap[entry.id] = entry.slug;
|
|
124
|
+
}
|
|
125
|
+
// Build map: entryId → resolved slug string
|
|
126
|
+
const slugMap = {};
|
|
76
127
|
for (const version of versions) {
|
|
77
128
|
const rawData = version.data;
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const slug =
|
|
129
|
+
const configSlug = entryConfigSlugMap[version.entryId];
|
|
130
|
+
const slugPath = configSlug ? getEntrySlugPath(configSlug) : 'seo.slug';
|
|
131
|
+
const slug = getSlugFromEntryData(rawData, slugPath, language);
|
|
81
132
|
if (slug) {
|
|
82
|
-
|
|
133
|
+
slugMap[version.entryId] = slug;
|
|
83
134
|
}
|
|
84
135
|
}
|
|
85
136
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -99,12 +150,28 @@ export async function resolveRichtextLinks(data, fields, language) {
|
|
|
99
150
|
const resolved = {};
|
|
100
151
|
for (const [lang, html] of Object.entries(val)) {
|
|
101
152
|
resolved[lang] =
|
|
102
|
-
typeof html === 'string' ? resolveHtml(html,
|
|
153
|
+
typeof html === 'string' ? resolveHtml(html, slugMap) : html;
|
|
103
154
|
}
|
|
104
155
|
result[field.slug] = resolved;
|
|
105
156
|
}
|
|
106
157
|
else if (typeof val === 'string') {
|
|
107
|
-
result[field.slug] = resolveHtml(val,
|
|
158
|
+
result[field.slug] = resolveHtml(val, slugMap);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'content': {
|
|
163
|
+
if (typeof val === 'object' && val !== null) {
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
165
|
+
const resolved = {};
|
|
166
|
+
for (const [lang, doc] of Object.entries(val)) {
|
|
167
|
+
if (doc && typeof doc === 'object' && doc.type === 'doc') {
|
|
168
|
+
resolved[lang] = resolveContentDoc(doc, slugMap);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
resolved[lang] = doc;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
result[field.slug] = resolved;
|
|
108
175
|
}
|
|
109
176
|
break;
|
|
110
177
|
}
|
|
@@ -114,7 +181,7 @@ export async function resolveRichtextLinks(data, fields, language) {
|
|
|
114
181
|
data: resolveValues(val.data, field.fields)
|
|
115
182
|
};
|
|
116
183
|
break;
|
|
117
|
-
case '
|
|
184
|
+
case 'blocks':
|
|
118
185
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
186
|
result[field.slug] = val.map((item) => {
|
|
120
187
|
const objectDef = field.of.find((objDef) => objDef.slug === item.slug);
|