includio-cms 0.1.3 → 0.5.0
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 +76 -0
- package/ROADMAP.md +23 -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 +55 -41
- 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 +83 -0
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +9 -0
- package/dist/admin/client/collection/collection-entries.svelte +255 -256
- 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 +60 -0
- package/dist/admin/client/collection/row-actions.svelte.d.ts +9 -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 +96 -37
- package/dist/admin/client/entry/entry-header.svelte.d.ts +5 -0
- package/dist/admin/client/entry/entry.svelte +171 -60
- package/dist/admin/client/entry/header/a11y-validator.d.ts +46 -0
- package/dist/admin/client/entry/header/a11y-validator.js +311 -0
- package/dist/admin/client/entry/header/publish-panel.svelte +373 -131
- 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 +314 -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/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/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 +13 -1
- 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 +31 -0
- package/dist/admin/components/layout/nav-footer.svelte.d.ts +18 -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 +8 -0
- package/dist/admin/remote/entry.remote.js +53 -4
- 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/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/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 +8 -6
- 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/files-local/index.js +126 -64
- package/dist/paraglide/.prettierignore +3 -0
- package/dist/paraglide/messages/_index.d.ts +36 -0
- package/dist/paraglide/messages/_index.js +72 -0
- 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/paraglide/messages.d.ts +2 -0
- package/dist/paraglide/messages.js +4 -0
- package/dist/paraglide/registry.d.ts +21 -0
- package/dist/paraglide/registry.js +31 -0
- package/dist/paraglide/runtime.d.ts +583 -0
- package/dist/paraglide/runtime.js +1402 -0
- package/dist/paraglide/server.d.ts +67 -0
- package/dist/paraglide/server.js +175 -0
- 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/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.4/index.d.ts +2 -0
- package/dist/updates/0.1.4/index.js +11 -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/index.js +6 -1
- package/package.json +17 -10
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { walkNodes } from '../../../components/tiptap/structured-content-utils.js';
|
|
2
|
+
const GENERIC_LINK_TEXTS = new Set([
|
|
3
|
+
'kliknij tutaj',
|
|
4
|
+
'kliknij',
|
|
5
|
+
'tutaj',
|
|
6
|
+
'czytaj więcej',
|
|
7
|
+
'więcej',
|
|
8
|
+
'dowiedz się więcej',
|
|
9
|
+
'sprawdź',
|
|
10
|
+
'link',
|
|
11
|
+
'here',
|
|
12
|
+
'click here',
|
|
13
|
+
'click',
|
|
14
|
+
'read more',
|
|
15
|
+
'more',
|
|
16
|
+
'learn more',
|
|
17
|
+
'check',
|
|
18
|
+
'this'
|
|
19
|
+
]);
|
|
20
|
+
/** Count images (figure/image nodes) without alt text. */
|
|
21
|
+
function checkImagesWithoutAlt(doc) {
|
|
22
|
+
let count = 0;
|
|
23
|
+
walkNodes(doc.content, (node) => {
|
|
24
|
+
if (node.type === 'figure' || node.type === 'image') {
|
|
25
|
+
const alt = node.attrs?.alt;
|
|
26
|
+
if (!alt || !alt.trim()) {
|
|
27
|
+
count++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return count;
|
|
32
|
+
}
|
|
33
|
+
/** Check heading hierarchy — returns true if headings are properly ordered.
|
|
34
|
+
* Rules:
|
|
35
|
+
* - First heading should be H2 (H1 is the page title)
|
|
36
|
+
* - Going deeper: can only increase by 1 (H2→H3 ok, H2→H4 not ok)
|
|
37
|
+
* - Going up: can jump back freely (H3→H2 ok)
|
|
38
|
+
* - But cannot go deeper than previous minimum at that nesting level
|
|
39
|
+
* i.e. H3→H2→H4 is invalid (skips H3 after establishing it)
|
|
40
|
+
*/
|
|
41
|
+
function checkHeadingHierarchy(doc) {
|
|
42
|
+
const levels = [];
|
|
43
|
+
walkNodes(doc.content, (node) => {
|
|
44
|
+
if (node.type === 'heading' && node.attrs?.level) {
|
|
45
|
+
levels.push(node.attrs.level);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (levels.length === 0)
|
|
49
|
+
return true;
|
|
50
|
+
// First heading should be H2 (H1 is reserved for page title)
|
|
51
|
+
if (levels[0] > 2)
|
|
52
|
+
return false;
|
|
53
|
+
for (let i = 1; i < levels.length; i++) {
|
|
54
|
+
// Going deeper: can only increase by 1 level
|
|
55
|
+
if (levels[i] > levels[i - 1] + 1) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/** Collect text content of a node (recursively). */
|
|
62
|
+
function collectText(node) {
|
|
63
|
+
let text = node.text ?? '';
|
|
64
|
+
if (node.content) {
|
|
65
|
+
for (const child of node.content) {
|
|
66
|
+
text += collectText(child);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
/** Count links with generic/non-descriptive anchor text. */
|
|
72
|
+
function checkGenericLinkTexts(doc) {
|
|
73
|
+
let count = 0;
|
|
74
|
+
walkNodes(doc.content, (node) => {
|
|
75
|
+
if (!node.marks)
|
|
76
|
+
return;
|
|
77
|
+
const hasLink = node.marks.some((m) => m.type === 'link');
|
|
78
|
+
if (hasLink) {
|
|
79
|
+
const text = collectText(node).trim().toLowerCase();
|
|
80
|
+
if (text && GENERIC_LINK_TEXTS.has(text)) {
|
|
81
|
+
count++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return count;
|
|
86
|
+
}
|
|
87
|
+
export function getHeadingIssuePositions(doc) {
|
|
88
|
+
const issues = [];
|
|
89
|
+
if (!doc.content)
|
|
90
|
+
return issues;
|
|
91
|
+
let pos = 1; // ProseMirror positions: doc starts at 0, first child at 1
|
|
92
|
+
let seenH2 = false;
|
|
93
|
+
for (const node of doc.content) {
|
|
94
|
+
if (node.type === 'heading' && node.attrs?.level) {
|
|
95
|
+
const level = node.attrs.level;
|
|
96
|
+
if (level === 2) {
|
|
97
|
+
seenH2 = true;
|
|
98
|
+
}
|
|
99
|
+
else if (level === 3 && !seenH2) {
|
|
100
|
+
issues.push({
|
|
101
|
+
pos,
|
|
102
|
+
message: 'Dodaj H2 przed tą podsekcją'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else if (level > 3 && !seenH2) {
|
|
106
|
+
issues.push({
|
|
107
|
+
pos,
|
|
108
|
+
message: 'Pierwszy nagłówek powinien być H2'
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Advance position: each block node = open tag + content size + close tag
|
|
113
|
+
pos += nodeSize(node);
|
|
114
|
+
}
|
|
115
|
+
return issues;
|
|
116
|
+
}
|
|
117
|
+
/** Calculate ProseMirror node size (used for position tracking). */
|
|
118
|
+
function nodeSize(node) {
|
|
119
|
+
if (node.type === 'text') {
|
|
120
|
+
return (node.text ?? '').length;
|
|
121
|
+
}
|
|
122
|
+
// Block/inline nodes: open + content + close = 2 + content size
|
|
123
|
+
let size = 2;
|
|
124
|
+
if (node.content) {
|
|
125
|
+
for (const child of node.content) {
|
|
126
|
+
size += nodeSize(child);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return size;
|
|
130
|
+
}
|
|
131
|
+
/** Find position of first image without alt in TipTap JSON doc. */
|
|
132
|
+
export function findFirstImageWithoutAlt(doc) {
|
|
133
|
+
if (!doc.content)
|
|
134
|
+
return null;
|
|
135
|
+
let pos = 1;
|
|
136
|
+
for (const node of doc.content) {
|
|
137
|
+
if (node.type === 'figure' || node.type === 'image') {
|
|
138
|
+
const alt = node.attrs?.alt;
|
|
139
|
+
if (!alt || !alt.trim())
|
|
140
|
+
return pos;
|
|
141
|
+
}
|
|
142
|
+
pos += nodeSize(node);
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/** Find position of first heading hierarchy issue in TipTap JSON doc. */
|
|
147
|
+
export function findFirstHeadingIssue(doc) {
|
|
148
|
+
const issues = getHeadingIssuePositions(doc);
|
|
149
|
+
return issues.length > 0 ? issues[0].pos : null;
|
|
150
|
+
}
|
|
151
|
+
/** Find position of first generic link in TipTap JSON doc. */
|
|
152
|
+
export function findFirstGenericLink(doc) {
|
|
153
|
+
if (!doc.content)
|
|
154
|
+
return null;
|
|
155
|
+
let pos = 1;
|
|
156
|
+
function walk(nodes, basePos) {
|
|
157
|
+
let p = basePos;
|
|
158
|
+
for (const node of nodes) {
|
|
159
|
+
if (node.marks?.some((m) => m.type === 'link')) {
|
|
160
|
+
const text = (node.text ?? '').trim().toLowerCase();
|
|
161
|
+
if (text && GENERIC_LINK_TEXTS.has(text))
|
|
162
|
+
return p;
|
|
163
|
+
}
|
|
164
|
+
if (node.content) {
|
|
165
|
+
const found = walk(node.content, p + 1);
|
|
166
|
+
if (found !== null)
|
|
167
|
+
return found;
|
|
168
|
+
}
|
|
169
|
+
p += nodeSize(node);
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return walk(doc.content, pos);
|
|
174
|
+
}
|
|
175
|
+
/** Try to extract a StructuredContentDoc from a value.
|
|
176
|
+
* Handles both direct doc format and language-keyed format { en: doc, pl: doc }. */
|
|
177
|
+
function tryExtractDoc(val) {
|
|
178
|
+
if (!val || typeof val !== 'object')
|
|
179
|
+
return [];
|
|
180
|
+
const obj = val;
|
|
181
|
+
// Direct doc: { type: 'doc', content: [...] }
|
|
182
|
+
if (obj.type === 'doc' && Array.isArray(obj.content)) {
|
|
183
|
+
return [obj];
|
|
184
|
+
}
|
|
185
|
+
// Language-keyed: { en: { type: 'doc', ... }, pl: { type: 'doc', ... } }
|
|
186
|
+
const docs = [];
|
|
187
|
+
for (const langVal of Object.values(obj)) {
|
|
188
|
+
if (langVal && typeof langVal === 'object') {
|
|
189
|
+
const langObj = langVal;
|
|
190
|
+
if (langObj.type === 'doc' && Array.isArray(langObj.content)) {
|
|
191
|
+
docs.push(langObj);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return docs;
|
|
196
|
+
}
|
|
197
|
+
/** Extract StructuredContentDoc values from entry data for content/richtext fields. */
|
|
198
|
+
function extractDocs(data, fields) {
|
|
199
|
+
const result = [];
|
|
200
|
+
for (const field of fields) {
|
|
201
|
+
if (field.type !== 'content' && field.type !== 'richtext')
|
|
202
|
+
continue;
|
|
203
|
+
const val = data[field.slug];
|
|
204
|
+
const docs = tryExtractDoc(val);
|
|
205
|
+
for (const doc of docs) {
|
|
206
|
+
result.push({ doc, fieldSlug: field.slug });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
export const a11yLangPl = {
|
|
212
|
+
imagesWithoutAlt: (n) => `${n} ${n === 1 ? 'obraz' : n < 5 ? 'obrazy' : 'obrazów'} bez opisu alternatywnego`,
|
|
213
|
+
headingsOk: 'Hierarchia nagłówków poprawna',
|
|
214
|
+
headingsSkipped: 'Nagłówki pomijają poziomy (np. H2 → H4)',
|
|
215
|
+
genericLinks: (n) => `${n} ${n === 1 ? 'link' : n < 5 ? 'linki' : 'linków'} z generycznym tekstem`,
|
|
216
|
+
allImagesHaveAlt: 'Wszystkie obrazy mają opis alternatywny',
|
|
217
|
+
noGenericLinks: 'Linki mają opisowe teksty'
|
|
218
|
+
};
|
|
219
|
+
export const a11yLangEn = {
|
|
220
|
+
imagesWithoutAlt: (n) => `${n} image${n === 1 ? '' : 's'} without alt text`,
|
|
221
|
+
headingsOk: 'Heading hierarchy is correct',
|
|
222
|
+
headingsSkipped: 'Headings skip levels (e.g. H2 → H4)',
|
|
223
|
+
genericLinks: (n) => `${n} link${n === 1 ? '' : 's'} with generic text`,
|
|
224
|
+
allImagesHaveAlt: 'All images have alt text',
|
|
225
|
+
noGenericLinks: 'Links have descriptive text'
|
|
226
|
+
};
|
|
227
|
+
/** Run a11y validation on entry data and return issues list. */
|
|
228
|
+
export function validateA11y(data, fields, lang = a11yLangPl) {
|
|
229
|
+
const entries = extractDocs(data, fields);
|
|
230
|
+
const issues = [];
|
|
231
|
+
// Images without alt
|
|
232
|
+
let totalMissingAlt = 0;
|
|
233
|
+
let firstMissingAltPos = null;
|
|
234
|
+
let firstMissingAltField;
|
|
235
|
+
for (const { doc, fieldSlug } of entries) {
|
|
236
|
+
totalMissingAlt += checkImagesWithoutAlt(doc);
|
|
237
|
+
if (firstMissingAltPos === null) {
|
|
238
|
+
const pos = findFirstImageWithoutAlt(doc);
|
|
239
|
+
if (pos !== null) {
|
|
240
|
+
firstMissingAltPos = pos;
|
|
241
|
+
firstMissingAltField = fieldSlug;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (totalMissingAlt > 0) {
|
|
246
|
+
issues.push({
|
|
247
|
+
type: 'warning',
|
|
248
|
+
message: lang.imagesWithoutAlt(totalMissingAlt),
|
|
249
|
+
fieldSlug: firstMissingAltField,
|
|
250
|
+
firstNodePos: firstMissingAltPos ?? undefined
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else if (entries.length > 0) {
|
|
254
|
+
issues.push({ type: 'success', message: lang.allImagesHaveAlt });
|
|
255
|
+
}
|
|
256
|
+
// Heading hierarchy
|
|
257
|
+
if (entries.length > 0) {
|
|
258
|
+
let allHeadingsOk = true;
|
|
259
|
+
let firstHeadingIssuePos = null;
|
|
260
|
+
let firstHeadingIssueField;
|
|
261
|
+
for (const { doc, fieldSlug } of entries) {
|
|
262
|
+
if (!checkHeadingHierarchy(doc)) {
|
|
263
|
+
allHeadingsOk = false;
|
|
264
|
+
if (firstHeadingIssuePos === null) {
|
|
265
|
+
const pos = findFirstHeadingIssue(doc);
|
|
266
|
+
if (pos !== null) {
|
|
267
|
+
firstHeadingIssuePos = pos;
|
|
268
|
+
firstHeadingIssueField = fieldSlug;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (allHeadingsOk) {
|
|
274
|
+
issues.push({ type: 'success', message: lang.headingsOk });
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
issues.push({
|
|
278
|
+
type: 'warning',
|
|
279
|
+
message: lang.headingsSkipped,
|
|
280
|
+
fieldSlug: firstHeadingIssueField,
|
|
281
|
+
firstNodePos: firstHeadingIssuePos ?? undefined
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Generic link text
|
|
286
|
+
let totalGenericLinks = 0;
|
|
287
|
+
let firstGenericLinkPos = null;
|
|
288
|
+
let firstGenericLinkField;
|
|
289
|
+
for (const { doc, fieldSlug } of entries) {
|
|
290
|
+
totalGenericLinks += checkGenericLinkTexts(doc);
|
|
291
|
+
if (firstGenericLinkPos === null) {
|
|
292
|
+
const pos = findFirstGenericLink(doc);
|
|
293
|
+
if (pos !== null) {
|
|
294
|
+
firstGenericLinkPos = pos;
|
|
295
|
+
firstGenericLinkField = fieldSlug;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (totalGenericLinks > 0) {
|
|
300
|
+
issues.push({
|
|
301
|
+
type: 'warning',
|
|
302
|
+
message: lang.genericLinks(totalGenericLinks),
|
|
303
|
+
fieldSlug: firstGenericLinkField,
|
|
304
|
+
firstNodePos: firstGenericLinkPos ?? undefined
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else if (entries.length > 0) {
|
|
308
|
+
issues.push({ type: 'success', message: lang.noGenericLinks });
|
|
309
|
+
}
|
|
310
|
+
return issues;
|
|
311
|
+
}
|