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.
Files changed (313) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/ROADMAP.md +23 -13
  3. package/dist/admin/api/accept-invite.js +1 -5
  4. package/dist/admin/api/invite.js +7 -16
  5. package/dist/admin/client/account/account-page.svelte +20 -50
  6. package/dist/admin/client/account/lang.d.ts +15 -23
  7. package/dist/admin/client/account/lang.js +51 -67
  8. package/dist/admin/client/account/preferences-section.svelte +26 -84
  9. package/dist/admin/client/account/profile-section.svelte +60 -40
  10. package/dist/admin/client/account/schema.d.ts +11 -3
  11. package/dist/admin/client/account/schema.js +25 -16
  12. package/dist/admin/client/account/security-section.svelte +139 -105
  13. package/dist/admin/client/account/sessions-section.svelte +35 -34
  14. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +3 -5
  15. package/dist/admin/client/admin/admin-layout.svelte +3 -2
  16. package/dist/admin/client/admin/admin-preloader.svelte +36 -0
  17. package/dist/admin/client/admin/admin-preloader.svelte.d.ts +18 -0
  18. package/dist/admin/client/admin/dashboard-page.svelte +55 -41
  19. package/dist/admin/client/collection/a11y-score-cell.svelte +45 -0
  20. package/dist/admin/client/collection/a11y-score-cell.svelte.d.ts +6 -0
  21. package/dist/admin/client/collection/bulk-actions-bar.svelte +83 -0
  22. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +9 -0
  23. package/dist/admin/client/collection/collection-entries.svelte +255 -256
  24. package/dist/admin/client/collection/collection-view.svelte.d.ts +4 -3
  25. package/dist/admin/client/collection/collection-view.svelte.js +9 -5
  26. package/dist/admin/client/collection/collection.svelte +22 -12
  27. package/dist/admin/client/collection/data-table.svelte +50 -39
  28. package/dist/admin/client/collection/data-table.svelte.d.ts +1 -0
  29. package/dist/admin/client/collection/date-cell.svelte +7 -5
  30. package/dist/admin/client/collection/date-cell.svelte.d.ts +1 -1
  31. package/dist/admin/client/collection/empty-state.svelte +28 -0
  32. package/dist/admin/client/collection/empty-state.svelte.d.ts +9 -0
  33. package/dist/admin/client/collection/entry-link.svelte +10 -4
  34. package/dist/admin/client/collection/entry-link.svelte.d.ts +1 -0
  35. package/dist/admin/client/collection/grid-view.svelte +21 -23
  36. package/dist/admin/client/collection/grid-view.svelte.d.ts +1 -2
  37. package/dist/admin/client/collection/row-actions.svelte +60 -0
  38. package/dist/admin/client/collection/row-actions.svelte.d.ts +9 -0
  39. package/dist/admin/client/collection/status-badge.svelte +7 -8
  40. package/dist/admin/client/collection/table-pagination.svelte +122 -79
  41. package/dist/admin/client/collection/table-pagination.svelte.d.ts +1 -0
  42. package/dist/admin/client/collection/table-toolbar.svelte +108 -88
  43. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +8 -9
  44. package/dist/admin/client/entry/entry-form.svelte +109 -1
  45. package/dist/admin/client/entry/entry-header.svelte +96 -37
  46. package/dist/admin/client/entry/entry-header.svelte.d.ts +5 -0
  47. package/dist/admin/client/entry/entry.svelte +171 -60
  48. package/dist/admin/client/entry/header/a11y-validator.d.ts +46 -0
  49. package/dist/admin/client/entry/header/a11y-validator.js +311 -0
  50. package/dist/admin/client/entry/header/publish-panel.svelte +373 -131
  51. package/dist/admin/client/entry/header/publish-panel.svelte.d.ts +4 -0
  52. package/dist/admin/client/entry/header/save-indicator.svelte +33 -23
  53. package/dist/admin/client/entry/header/schedule-popover.svelte +1 -1
  54. package/dist/admin/client/entry/header/status-badge.svelte +25 -118
  55. package/dist/admin/client/entry/header/version-history-sheet.svelte +314 -98
  56. package/dist/admin/client/form/form-submission/form-submission.svelte +271 -83
  57. package/dist/admin/client/form/form-submission/submission-field.svelte +12 -12
  58. package/dist/admin/client/form/form-submissions.svelte +421 -139
  59. package/dist/admin/client/form/submission-link.svelte +8 -2
  60. package/dist/admin/client/form/submission-link.svelte.d.ts +1 -0
  61. package/dist/admin/client/form/submission-status-badge.svelte +18 -4
  62. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +1 -0
  63. package/dist/admin/client/login/lang.d.ts +32 -0
  64. package/dist/admin/client/login/lang.js +66 -2
  65. package/dist/admin/client/login/login-form.svelte +237 -95
  66. package/dist/admin/client/login/login-form.svelte.d.ts +2 -17
  67. package/dist/admin/client/login/login-page.svelte +34 -98
  68. package/dist/admin/client/login/reset-password-page.svelte +235 -0
  69. package/dist/admin/client/login/reset-password-page.svelte.d.ts +4 -0
  70. package/dist/admin/client/login/schema.d.ts +15 -0
  71. package/dist/admin/client/login/schema.js +21 -0
  72. package/dist/admin/client/users/accept-invite-page.svelte +166 -37
  73. package/dist/admin/client/users/create-user-dialog.svelte +15 -7
  74. package/dist/admin/client/users/delete-user-dialog.svelte +81 -16
  75. package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +4 -1
  76. package/dist/admin/client/users/edit-user-dialog.svelte +3 -0
  77. package/dist/admin/client/users/invite-user-dialog.svelte +16 -3
  78. package/dist/admin/client/users/lang.d.ts +27 -0
  79. package/dist/admin/client/users/lang.js +64 -10
  80. package/dist/admin/client/users/pending-invitations.svelte +59 -23
  81. package/dist/admin/client/users/users-page.svelte +471 -72
  82. package/dist/admin/components/accessibility/accessibility-overview.svelte +2 -7
  83. package/dist/admin/components/dashboard/a11y-gauge.svelte +90 -0
  84. package/dist/admin/components/dashboard/a11y-gauge.svelte.d.ts +18 -0
  85. package/dist/admin/components/dashboard/accessibility-hub.svelte +13 -12
  86. package/dist/admin/components/dashboard/form-submissions-widget.svelte +71 -113
  87. package/dist/admin/components/dashboard/index.d.ts +4 -2
  88. package/dist/admin/components/dashboard/index.js +4 -2
  89. package/dist/admin/components/dashboard/recent-activity.svelte +53 -75
  90. package/dist/admin/components/dashboard/recent-entries.svelte +94 -0
  91. package/dist/admin/components/dashboard/recent-entries.svelte.d.ts +18 -0
  92. package/dist/admin/components/dashboard/stat-card.svelte +2 -2
  93. package/dist/admin/components/dashboard/tip-of-the-day.svelte +109 -0
  94. package/dist/admin/components/dashboard/tip-of-the-day.svelte.d.ts +3 -0
  95. package/dist/admin/components/dashboard/welcome-header.svelte +45 -0
  96. package/dist/admin/components/dashboard/welcome-header.svelte.d.ts +3 -0
  97. package/dist/admin/components/fields/{array-field.svelte → blocks-field.svelte} +4 -4
  98. package/dist/admin/components/fields/{array-field.svelte.d.ts → blocks-field.svelte.d.ts} +5 -5
  99. package/dist/admin/components/fields/content-field.svelte +27 -0
  100. package/dist/admin/components/fields/content-field.svelte.d.ts +31 -0
  101. package/dist/admin/components/fields/field-renderer.svelte +9 -7
  102. package/dist/admin/components/fields/image-field.svelte +2 -2
  103. package/dist/admin/components/fields/media-field.svelte +2 -2
  104. package/dist/admin/components/fields/seo-field.svelte +205 -25
  105. package/dist/admin/components/fields/simple-array-field.svelte +289 -0
  106. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +30 -0
  107. package/dist/admin/components/fields/slug-field.svelte +3 -2
  108. package/dist/admin/components/fields/standalone-field-renderer.svelte +148 -0
  109. package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +9 -0
  110. package/dist/admin/components/fields/text-field-wrapper.svelte +13 -1
  111. package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
  112. package/dist/admin/components/fields/url-field.svelte +5 -4
  113. package/dist/admin/components/layout/app-sidebar.svelte +27 -24
  114. package/dist/admin/components/layout/lang.d.ts +6 -0
  115. package/dist/admin/components/layout/lang.js +13 -1
  116. package/dist/admin/components/layout/layout-renderer.svelte +352 -0
  117. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +14 -0
  118. package/dist/admin/components/layout/nav-breadcrumbs.svelte +4 -4
  119. package/dist/admin/components/layout/nav-collections.svelte +65 -36
  120. package/dist/admin/components/layout/nav-footer.svelte +31 -0
  121. package/dist/admin/components/layout/nav-footer.svelte.d.ts +18 -0
  122. package/dist/admin/components/layout/nav-forms.svelte +55 -30
  123. package/dist/admin/components/layout/nav-main.svelte +14 -52
  124. package/dist/admin/components/layout/nav-search.svelte +4 -3
  125. package/dist/admin/components/layout/nav-singletons.svelte +59 -17
  126. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +17 -8
  127. package/dist/admin/components/layout/site-header.svelte +74 -13
  128. package/dist/admin/components/media/alt-input.svelte +32 -22
  129. package/dist/admin/components/media/bulk-action-bar.svelte +139 -150
  130. package/dist/admin/components/media/file/file-details.svelte +299 -217
  131. package/dist/admin/components/media/file/file-miniature.svelte +54 -41
  132. package/dist/admin/components/media/file/file-miniature.svelte.d.ts +1 -0
  133. package/dist/admin/components/media/file/file-preview.svelte +1 -1
  134. package/dist/admin/components/media/file-upload.svelte +24 -26
  135. package/dist/admin/components/media/files-list.svelte +112 -40
  136. package/dist/admin/components/media/files-list.svelte.d.ts +2 -0
  137. package/dist/admin/components/media/focal-point-input.svelte +122 -26
  138. package/dist/admin/components/media/media-library.svelte +127 -70
  139. package/dist/admin/components/media/media-search.svelte +6 -6
  140. package/dist/admin/components/media/media-sort.svelte +3 -1
  141. package/dist/admin/components/media/multi-file-summary.svelte +88 -68
  142. package/dist/admin/components/media/tag-combobox.svelte +141 -66
  143. package/dist/admin/components/media/tag-combobox.svelte.d.ts +1 -0
  144. package/dist/admin/components/media/tag-sidebar.svelte +139 -121
  145. package/dist/admin/components/tiptap/FigureNodeView.svelte +144 -15
  146. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +254 -0
  147. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte.d.ts +4 -0
  148. package/dist/admin/components/tiptap/SlashCommandPopup.svelte +212 -0
  149. package/dist/admin/components/tiptap/SlashCommandPopup.svelte.d.ts +8 -0
  150. package/dist/admin/components/tiptap/content-editor.svelte +280 -0
  151. package/dist/admin/components/tiptap/content-editor.svelte.d.ts +9 -0
  152. package/dist/admin/components/tiptap/editor-toolbar.svelte +230 -0
  153. package/dist/admin/components/tiptap/editor-toolbar.svelte.d.ts +16 -0
  154. package/dist/admin/components/tiptap/heading-a11y-plugin.d.ts +2 -0
  155. package/dist/admin/components/tiptap/heading-a11y-plugin.js +67 -0
  156. package/dist/admin/components/tiptap/image-dialog.svelte +172 -11
  157. package/dist/admin/components/tiptap/inline-block-node.d.ts +19 -0
  158. package/dist/admin/components/tiptap/inline-block-node.js +98 -0
  159. package/dist/admin/components/tiptap/link-dialog.svelte +9 -4
  160. package/dist/admin/components/tiptap/slash-command.d.ts +17 -0
  161. package/dist/admin/components/tiptap/slash-command.js +181 -0
  162. package/dist/admin/components/tiptap/structured-content-utils.d.ts +21 -0
  163. package/dist/admin/components/tiptap/structured-content-utils.js +150 -0
  164. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -190
  165. package/dist/admin/email/invite-template.d.ts +8 -0
  166. package/dist/admin/email/invite-template.js +99 -0
  167. package/dist/admin/email/reset-password-template.d.ts +7 -0
  168. package/dist/admin/email/reset-password-template.js +96 -0
  169. package/dist/admin/remote/ai.remote.d.ts +1 -0
  170. package/dist/admin/remote/ai.remote.js +4 -1
  171. package/dist/admin/remote/entry.remote.d.ts +8 -0
  172. package/dist/admin/remote/entry.remote.js +53 -4
  173. package/dist/admin/remote/preview.remote.js +2 -1
  174. package/dist/admin/shared/password-schema.d.ts +5 -0
  175. package/dist/admin/shared/password-schema.js +10 -0
  176. package/dist/admin/styles/admin.css +1530 -151
  177. package/dist/admin/utils/formatDate.d.ts +1 -0
  178. package/dist/admin/utils/formatDate.js +8 -0
  179. package/dist/admin/utils/roleLabel.d.ts +2 -0
  180. package/dist/admin/utils/roleLabel.js +13 -0
  181. package/dist/ai-claude/index.d.ts +2 -0
  182. package/dist/ai-claude/index.js +56 -0
  183. package/dist/cms/runtime/api.d.ts +6 -1
  184. package/dist/cms/runtime/api.js +3 -0
  185. package/dist/cms/runtime/schemas.d.ts +9 -1
  186. package/dist/cms/runtime/schemas.js +8 -0
  187. package/dist/cms/runtime/types.d.ts +82 -10
  188. package/dist/cms/runtime/types.js +4 -0
  189. package/dist/components/ui/accordion/accordion.stories.svelte +39 -0
  190. package/dist/components/ui/accordion/accordion.stories.svelte.d.ts +27 -0
  191. package/dist/components/ui/alert/alert.stories.svelte +53 -0
  192. package/dist/components/ui/alert/alert.stories.svelte.d.ts +27 -0
  193. package/dist/components/ui/alert/alert.svelte +5 -0
  194. package/dist/components/ui/alert/alert.svelte.d.ts +9 -0
  195. package/dist/components/ui/avatar/avatar.stories.svelte +16 -0
  196. package/dist/components/ui/avatar/avatar.stories.svelte.d.ts +27 -0
  197. package/dist/components/ui/badge/badge.stories.svelte +33 -0
  198. package/dist/components/ui/badge/badge.stories.svelte.d.ts +27 -0
  199. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +33 -0
  200. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte.d.ts +27 -0
  201. package/dist/components/ui/button/button.stories.svelte +43 -0
  202. package/dist/components/ui/button/button.stories.svelte.d.ts +27 -0
  203. package/dist/components/ui/button/button.svelte +1 -2
  204. package/dist/components/ui/button/button.svelte.d.ts +0 -3
  205. package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +1 -1
  206. package/dist/components/ui/card/card.stories.svelte +42 -0
  207. package/dist/components/ui/card/card.stories.svelte.d.ts +27 -0
  208. package/dist/components/ui/command/command.stories.svelte +51 -0
  209. package/dist/components/ui/command/command.stories.svelte.d.ts +27 -0
  210. package/dist/components/ui/dialog/dialog.stories.svelte +29 -0
  211. package/dist/components/ui/dialog/dialog.stories.svelte.d.ts +27 -0
  212. package/dist/components/ui/field/field-label.svelte.d.ts +1 -1
  213. package/dist/components/ui/field/field.stories.svelte +21 -0
  214. package/dist/components/ui/field/field.stories.svelte.d.ts +27 -0
  215. package/dist/components/ui/input/input.stories.svelte +40 -0
  216. package/dist/components/ui/input/input.stories.svelte.d.ts +27 -0
  217. package/dist/components/ui/input/input.svelte +2 -4
  218. package/dist/components/ui/item/item-separator.svelte.d.ts +1 -1
  219. package/dist/components/ui/label/label.stories.svelte +20 -0
  220. package/dist/components/ui/label/label.stories.svelte.d.ts +27 -0
  221. package/dist/components/ui/popover/popover.stories.svelte +29 -0
  222. package/dist/components/ui/popover/popover.stories.svelte.d.ts +27 -0
  223. package/dist/components/ui/select/select-group-heading.svelte.d.ts +1 -1
  224. package/dist/components/ui/select/select.stories.svelte +23 -0
  225. package/dist/components/ui/select/select.stories.svelte.d.ts +27 -0
  226. package/dist/components/ui/separator/separator.stories.svelte +24 -0
  227. package/dist/components/ui/separator/separator.stories.svelte.d.ts +27 -0
  228. package/dist/components/ui/sheet/sheet.stories.svelte +29 -0
  229. package/dist/components/ui/sheet/sheet.stories.svelte.d.ts +27 -0
  230. package/dist/components/ui/sidebar/sidebar-group.svelte +3 -3
  231. package/dist/components/ui/sidebar/sidebar-group.svelte.d.ts +2 -2
  232. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +28 -30
  233. package/dist/components/ui/sidebar/sidebar-menu-button.svelte.d.ts +7 -7
  234. package/dist/components/ui/sidebar/sidebar-separator.svelte.d.ts +1 -1
  235. package/dist/components/ui/sidebar/sidebar-trigger.svelte +4 -4
  236. package/dist/components/ui/sonner/sonner.stories.svelte +22 -0
  237. package/dist/components/ui/sonner/sonner.stories.svelte.d.ts +26 -0
  238. package/dist/components/ui/sonner/sonner.svelte +8 -2
  239. package/dist/components/ui/sonner/toast-demo.svelte +29 -0
  240. package/dist/components/ui/sonner/toast-demo.svelte.d.ts +6 -0
  241. package/dist/components/ui/textarea/textarea.stories.svelte +22 -0
  242. package/dist/components/ui/textarea/textarea.stories.svelte.d.ts +27 -0
  243. package/dist/components/ui/textarea/textarea.svelte +0 -2
  244. package/dist/components/ui/toggle/toggle.stories.svelte +22 -0
  245. package/dist/components/ui/toggle/toggle.stories.svelte.d.ts +27 -0
  246. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +17 -0
  247. package/dist/components/ui/toggle-group/toggle-group.stories.svelte.d.ts +27 -0
  248. package/dist/components/ui/tooltip/tooltip.stories.svelte +26 -0
  249. package/dist/components/ui/tooltip/tooltip.stories.svelte.d.ts +27 -0
  250. package/dist/core/fields/fieldSchemaToTs.d.ts +1 -0
  251. package/dist/core/fields/fieldSchemaToTs.js +133 -1
  252. package/dist/core/fields/layoutUtils.d.ts +17 -0
  253. package/dist/core/fields/layoutUtils.js +149 -0
  254. package/dist/core/fields/structuredToHtml.d.ts +9 -0
  255. package/dist/core/fields/structuredToHtml.js +161 -0
  256. package/dist/core/server/entries/operations/create.js +2 -1
  257. package/dist/core/server/entries/operations/get.js +8 -6
  258. package/dist/core/server/entries/operations/update.d.ts +3 -0
  259. package/dist/core/server/entries/operations/update.js +30 -2
  260. package/dist/core/server/fields/queryStructuredContent.d.ts +15 -0
  261. package/dist/core/server/fields/queryStructuredContent.js +65 -0
  262. package/dist/core/server/fields/resolveImageFields.js +51 -2
  263. package/dist/core/server/fields/resolveRelationFields.js +2 -2
  264. package/dist/core/server/fields/resolveRichtextLinks.js +80 -13
  265. package/dist/core/server/fields/resolveUrlFields.js +57 -6
  266. package/dist/core/server/fields/slugResolver.d.ts +10 -0
  267. package/dist/core/server/fields/slugResolver.js +34 -0
  268. package/dist/core/server/generator/fields.js +15 -4
  269. package/dist/core/server/generator/generator.js +3 -2
  270. package/dist/files-local/index.js +126 -64
  271. package/dist/paraglide/.prettierignore +3 -0
  272. package/dist/paraglide/messages/_index.d.ts +36 -0
  273. package/dist/paraglide/messages/_index.js +72 -0
  274. package/dist/paraglide/messages/en.d.ts +5 -0
  275. package/dist/paraglide/messages/en.js +14 -0
  276. package/dist/paraglide/messages/pl.d.ts +5 -0
  277. package/dist/paraglide/messages/pl.js +14 -0
  278. package/dist/paraglide/messages.d.ts +2 -0
  279. package/dist/paraglide/messages.js +4 -0
  280. package/dist/paraglide/registry.d.ts +21 -0
  281. package/dist/paraglide/registry.js +31 -0
  282. package/dist/paraglide/runtime.d.ts +583 -0
  283. package/dist/paraglide/runtime.js +1402 -0
  284. package/dist/paraglide/server.d.ts +67 -0
  285. package/dist/paraglide/server.js +175 -0
  286. package/dist/server/auth.d.ts +5 -0
  287. package/dist/server/auth.js +12 -1
  288. package/dist/sveltekit/components/structured-content.svelte +204 -0
  289. package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
  290. package/dist/sveltekit/config.d.ts +13 -3
  291. package/dist/sveltekit/index.d.ts +3 -0
  292. package/dist/sveltekit/index.js +3 -0
  293. package/dist/sveltekit/server/handle.js +1 -0
  294. package/dist/types/config.d.ts +3 -0
  295. package/dist/types/fields.d.ts +19 -2
  296. package/dist/types/index.d.ts +2 -0
  297. package/dist/types/index.js +2 -0
  298. package/dist/types/layout.d.ts +54 -0
  299. package/dist/types/layout.js +6 -0
  300. package/dist/types/structured-content.d.ts +63 -0
  301. package/dist/types/structured-content.js +1 -0
  302. package/dist/updates/0.1.4/index.d.ts +2 -0
  303. package/dist/updates/0.1.4/index.js +11 -0
  304. package/dist/updates/0.1.5/index.d.ts +2 -0
  305. package/dist/updates/0.1.5/index.js +18 -0
  306. package/dist/updates/0.2.0/index.d.ts +2 -0
  307. package/dist/updates/0.2.0/index.js +11 -0
  308. package/dist/updates/0.2.2/index.d.ts +2 -0
  309. package/dist/updates/0.2.2/index.js +13 -0
  310. package/dist/updates/0.5.0/index.d.ts +2 -0
  311. package/dist/updates/0.5.0/index.js +14 -0
  312. package/dist/updates/index.js +6 -1
  313. 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
+ }