includio-cms 0.1.4 → 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 (296) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/ROADMAP.md +18 -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 -260
  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/server/auth.d.ts +5 -0
  272. package/dist/server/auth.js +12 -1
  273. package/dist/sveltekit/components/structured-content.svelte +204 -0
  274. package/dist/sveltekit/components/structured-content.svelte.d.ts +21 -0
  275. package/dist/sveltekit/config.d.ts +13 -3
  276. package/dist/sveltekit/index.d.ts +3 -0
  277. package/dist/sveltekit/index.js +3 -0
  278. package/dist/sveltekit/server/handle.js +1 -0
  279. package/dist/types/config.d.ts +3 -0
  280. package/dist/types/fields.d.ts +19 -2
  281. package/dist/types/index.d.ts +2 -0
  282. package/dist/types/index.js +2 -0
  283. package/dist/types/layout.d.ts +54 -0
  284. package/dist/types/layout.js +6 -0
  285. package/dist/types/structured-content.d.ts +63 -0
  286. package/dist/types/structured-content.js +1 -0
  287. package/dist/updates/0.1.5/index.d.ts +2 -0
  288. package/dist/updates/0.1.5/index.js +18 -0
  289. package/dist/updates/0.2.0/index.d.ts +2 -0
  290. package/dist/updates/0.2.0/index.js +11 -0
  291. package/dist/updates/0.2.2/index.d.ts +2 -0
  292. package/dist/updates/0.2.2/index.js +13 -0
  293. package/dist/updates/0.5.0/index.d.ts +2 -0
  294. package/dist/updates/0.5.0/index.js +14 -0
  295. package/dist/updates/index.js +5 -1
  296. package/package.json +16 -9
@@ -4,23 +4,27 @@
4
4
 
5
5
  <script lang="ts" generics="T extends Record<string, unknown>">
6
6
  import {
7
- formFieldProxy,
8
- type FormFieldProxy,
9
7
  type FormPathLeaves,
10
8
  type SuperForm
11
9
  } from 'sveltekit-superforms';
12
10
  import FieldRenderer from './field-renderer.svelte';
13
11
  import { joinPath } from '../../utils/objectPath.js';
12
+ import { getAtPath, setAtPath } from '../../utils/objectPath.js';
14
13
  import type {
15
14
  Field,
16
15
  ImageField,
17
- ObjectFieldData,
18
16
  SeoField,
19
17
  SeoFieldData,
20
18
  TextField
21
19
  } from '../../../types/fields.js';
22
- // import { onMount } from 'svelte';
23
- import * as Item from '../../../components/ui/item/index.js';
20
+ import { untrack } from 'svelte';
21
+ import slugify from '../../imports/slugify.js';
22
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
23
+ import { getContentLanguage } from '../../state/content-language.svelte.js';
24
+ import { getLocalizedLabel } from '../../utils/collectionLabel.js';
25
+
26
+ const interfaceLanguage = useInterfaceLanguage();
27
+ const contentLanguage = getContentLanguage();
24
28
 
25
29
  type Props = {
26
30
  field: SeoField;
@@ -30,31 +34,89 @@
30
34
 
31
35
  let { field, form, path, ...props }: Props = $props();
32
36
 
37
+ const { form: formData } = form;
38
+
39
+ const labels = {
40
+ slug: {
41
+ label: { en: 'URL Slug', pl: 'Slug URL' },
42
+ description: {
43
+ en: 'Page address shown in the browser bar',
44
+ pl: 'Adres strony widoczny w pasku przeglądarki'
45
+ }
46
+ },
47
+ canonicalUrl: {
48
+ label: { en: 'Canonical URL', pl: 'Adres kanoniczny' },
49
+ description: {
50
+ en: 'If this content appears at another address, enter the original URL',
51
+ pl: 'Jeśli ta treść pojawia się pod innym adresem, podaj oryginalny URL'
52
+ }
53
+ },
54
+ title: {
55
+ label: { en: 'SEO Title', pl: 'Tytuł SEO' },
56
+ description: {
57
+ en: 'Appears in search results and browser tabs (50–60 characters)',
58
+ pl: 'Widoczny w wynikach wyszukiwania i zakładce przeglądarki (50–60 znaków)'
59
+ }
60
+ },
61
+ description: {
62
+ label: { en: 'Description', pl: 'Opis' },
63
+ description: {
64
+ en: 'Short summary for search engines (120–160 characters)',
65
+ pl: 'Krótkie podsumowanie dla wyszukiwarek (120–160 znaków)'
66
+ }
67
+ },
68
+ keywords: {
69
+ label: { en: 'Keywords', pl: 'Słowa kluczowe' },
70
+ description: {
71
+ en: 'Comma-separated list of keywords',
72
+ pl: 'Lista słów kluczowych oddzielonych przecinkami'
73
+ }
74
+ },
75
+ ogImage: {
76
+ label: { en: 'Social media image', pl: 'Obraz w mediach społecznościowych' },
77
+ description: {
78
+ en: 'Image displayed when sharing this page on social media',
79
+ pl: 'Obraz wyświetlany przy udostępnianiu strony w mediach społecznościowych'
80
+ }
81
+ },
82
+ customCode: {
83
+ label: { en: 'Custom code', pl: 'Własny kod' },
84
+ description: {
85
+ en: 'Additional HTML inserted into the page <head>',
86
+ pl: 'Dodatkowy HTML wstawiany do sekcji <head> strony'
87
+ }
88
+ }
89
+ };
90
+
33
91
  const slugField: TextField = {
34
92
  type: 'text',
35
93
  slug: 'slug',
36
- label: 'Slug',
94
+ label: labels.slug.label,
95
+ description: labels.slug.description,
37
96
  required: true
38
97
  };
39
98
 
40
99
  const canonicalUrlField: TextField = {
41
100
  type: 'text',
42
101
  slug: 'canonicalUrl',
43
- label: 'Canonical Url',
102
+ label: labels.canonicalUrl.label,
103
+ description: labels.canonicalUrl.description,
44
104
  required: false
45
105
  };
46
106
 
47
107
  const titleField: TextField = {
48
108
  type: 'text',
49
109
  slug: 'title',
50
- label: 'Title',
110
+ label: labels.title.label,
111
+ description: labels.title.description,
51
112
  required: true
52
113
  };
53
114
 
54
115
  const descriptionField: TextField = {
55
116
  type: 'text',
56
117
  slug: 'description',
57
- label: 'Description',
118
+ label: labels.description.label,
119
+ description: labels.description.description,
58
120
  required: false,
59
121
  multiline: true
60
122
  };
@@ -62,7 +124,8 @@
62
124
  const keyWordsField: TextField = {
63
125
  type: 'text',
64
126
  slug: 'keywords',
65
- label: 'Keywords',
127
+ label: labels.keywords.label,
128
+ description: labels.keywords.description,
66
129
  required: false,
67
130
  multiline: true
68
131
  };
@@ -70,14 +133,16 @@
70
133
  const ogImageField: ImageField = {
71
134
  type: 'image',
72
135
  slug: 'ogImage',
73
- label: 'Open Graph Image',
136
+ label: labels.ogImage.label,
137
+ description: labels.ogImage.description,
74
138
  required: false
75
139
  };
76
140
 
77
141
  const customCodeField: TextField = {
78
142
  type: 'text',
79
143
  slug: 'customCode',
80
- label: 'Custom Code',
144
+ label: labels.customCode.label,
145
+ description: labels.customCode.description,
81
146
  required: false,
82
147
  multiline: true
83
148
  };
@@ -91,19 +156,134 @@
91
156
  ogImageField,
92
157
  customCodeField
93
158
  ];
159
+
160
+ // Helper: read a value from formData, handling both localized (object) and plain (string) values
161
+ function readSourceValue(sourcePath: string): string | undefined {
162
+ const raw = ($formData as Record<string, unknown>)[sourcePath];
163
+ if (typeof raw === 'string') return raw;
164
+ if (raw && typeof raw === 'object') {
165
+ return (raw as Record<string, string>)[contentLanguage.current];
166
+ }
167
+ return undefined;
168
+ }
169
+
170
+ // Helper: read value at a dot-path from formData
171
+ function readPath(dotPath: string): string | undefined {
172
+ const val = getAtPath($formData as Record<string, unknown>, dotPath);
173
+ return typeof val === 'string' ? val : undefined;
174
+ }
175
+
176
+ // Character count for current language
177
+ let titleLength = $derived.by(() => {
178
+ const val = readPath(joinPath(String(path), 'title', contentLanguage.current));
179
+ return val?.length ?? 0;
180
+ });
181
+ let descLength = $derived.by(() => {
182
+ const val = readPath(joinPath(String(path), 'description', contentLanguage.current));
183
+ return val?.length ?? 0;
184
+ });
185
+
186
+ function charHintClass(len: number, min: number, max: number): string {
187
+ if (len === 0) return 'text-muted-foreground';
188
+ if (len >= min && len <= max) return 'text-success';
189
+ if (len < min) return 'text-warning';
190
+ return 'text-destructive';
191
+ }
192
+
193
+ // Auto-gen: track last auto-generated values per language
194
+ let lastAutoSlugs: Record<string, string> = {};
195
+ let lastAutoTitles: Record<string, string> = {};
196
+
197
+ // slugSource → auto-gen seo.slug per language
198
+ $effect(() => {
199
+ if (!field.slugSource) return;
200
+ const sourceRaw = ($formData as Record<string, unknown>)[field.slugSource];
201
+ if (!sourceRaw) return;
202
+
203
+ untrack(() => {
204
+ const pairs: [string, string][] =
205
+ typeof sourceRaw === 'string'
206
+ ? [[contentLanguage.current, sourceRaw]]
207
+ : typeof sourceRaw === 'object'
208
+ ? Object.entries(sourceRaw as Record<string, string>).filter(([, v]) => typeof v === 'string' && v)
209
+ : [];
210
+
211
+ let changed = false;
212
+ for (const [lang, text] of pairs) {
213
+ const targetPath = joinPath(String(path), 'slug', lang);
214
+ const current = getAtPath($formData as Record<string, unknown>, targetPath) as string | undefined;
215
+ if (!current || current === lastAutoSlugs[lang]) {
216
+ const newSlug = slugify(String(text), { lower: true, strict: true, trim: true });
217
+ if (newSlug !== current) {
218
+ setAtPath($formData as Record<string, unknown>, targetPath, newSlug);
219
+ changed = true;
220
+ }
221
+ lastAutoSlugs[lang] = newSlug;
222
+ }
223
+ }
224
+ if (changed) $formData = $formData;
225
+ });
226
+ });
227
+
228
+ // titleSource → auto-fill seo.title per language
229
+ $effect(() => {
230
+ if (!field.titleSource) return;
231
+ const sourceRaw = ($formData as Record<string, unknown>)[field.titleSource];
232
+ if (!sourceRaw) return;
233
+
234
+ untrack(() => {
235
+ const pairs: [string, string][] =
236
+ typeof sourceRaw === 'string'
237
+ ? [[contentLanguage.current, sourceRaw]]
238
+ : typeof sourceRaw === 'object'
239
+ ? Object.entries(sourceRaw as Record<string, string>).filter(([, v]) => typeof v === 'string' && v)
240
+ : [];
241
+
242
+ let changed = false;
243
+ for (const [lang, text] of pairs) {
244
+ const targetPath = joinPath(String(path), 'title', lang);
245
+ const current = getAtPath($formData as Record<string, unknown>, targetPath) as string | undefined;
246
+ if (!current || current === lastAutoTitles[lang]) {
247
+ if (text !== current) {
248
+ setAtPath($formData as Record<string, unknown>, targetPath, text);
249
+ changed = true;
250
+ }
251
+ lastAutoTitles[lang] = text;
252
+ }
253
+ }
254
+ if (changed) $formData = $formData;
255
+ });
256
+ });
94
257
  </script>
95
258
 
96
- <Item.Root variant="outline">
97
- <Item.Content>
98
- <Item.Title class="mb-4 text-lg">SEO</Item.Title>
99
- <div class="space-y-4">
100
- {#each fields as f}
101
- <FieldRenderer
102
- field={f}
103
- form={form as SuperForm<Record<string, unknown>>}
104
- path={joinPath(path, f.slug)}
105
- />
106
- {/each}
259
+ <div class="space-y-4">
260
+ {#each fields as f}
261
+ <div>
262
+ <FieldRenderer
263
+ field={f}
264
+ form={form as SuperForm<Record<string, unknown>>}
265
+ path={joinPath(path, f.slug)}
266
+ />
267
+ {#if f.slug === 'title'}
268
+ <p class="mt-1 text-xs {charHintClass(titleLength, 50, 60)}">
269
+ {titleLength}/60
270
+ {#if titleLength > 0 && titleLength < 50}
271
+ — {getLocalizedLabel({ en: 'a bit short', pl: 'trochę za krótko' }, interfaceLanguage.current)}
272
+ {:else if titleLength > 60}
273
+ — {getLocalizedLabel({ en: 'too long', pl: 'za długo' }, interfaceLanguage.current)}
274
+ {/if}
275
+ </p>
276
+ {/if}
277
+ {#if f.slug === 'description'}
278
+ <p class="mt-1 text-xs {charHintClass(descLength, 120, 160)}">
279
+ {descLength}/160
280
+ {#if descLength > 0 && descLength < 120}
281
+ — {getLocalizedLabel({ en: 'a bit short', pl: 'trochę za krótko' }, interfaceLanguage.current)}
282
+ {:else if descLength > 160}
283
+ — {getLocalizedLabel({ en: 'too long', pl: 'za długo' }, interfaceLanguage.current)}
284
+ {/if}
285
+ </p>
286
+ {/if}
107
287
  </div>
108
- </Item.Content>
109
- </Item.Root>
288
+ {/each}
289
+ </div>
@@ -0,0 +1,289 @@
1
+ <script lang="ts" module>
2
+ type T = Record<string, unknown>;
3
+ </script>
4
+
5
+ <script lang="ts" generics="T extends Record<string, unknown>">
6
+ import Button from '../../../components/ui/button/button.svelte';
7
+ import Input from '../../../components/ui/input/input.svelte';
8
+ import {
9
+ formFieldProxy,
10
+ type FormFieldProxy,
11
+ type FormPathLeaves,
12
+ type SuperForm
13
+ } from 'sveltekit-superforms';
14
+ import type { ArrayField, UrlFieldData } from '../../../types/fields.js';
15
+ import { onMount } from 'svelte';
16
+ import CirclePlus from '@tabler/icons-svelte/icons/circle-plus';
17
+ import X from '@tabler/icons-svelte/icons/x';
18
+ import { getContentLanguage } from '../../state/content-language.svelte.js';
19
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
20
+
21
+ const contentLanguage = getContentLanguage();
22
+ const interfaceLanguage = useInterfaceLanguage();
23
+
24
+ type Props = {
25
+ field: ArrayField;
26
+ form: SuperForm<T>;
27
+ path: FormPathLeaves<T>;
28
+ };
29
+
30
+ let { field, form, path, ...props }: Props = $props();
31
+
32
+ const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<unknown[] | undefined>;
33
+
34
+ onMount(() => {
35
+ if (!$value || !Array.isArray($value)) {
36
+ $value = [];
37
+ }
38
+ });
39
+
40
+ const atMax = $derived(field.maxItems !== undefined && ($value?.length ?? 0) >= field.maxItems);
41
+
42
+ // --- Text ---
43
+ let textInput = $state('');
44
+
45
+ function addTextItem() {
46
+ if (!textInput.trim() || atMax) return;
47
+ if (field.localized) {
48
+ const item: Record<string, string> = {};
49
+ item[contentLanguage.current] = textInput.trim();
50
+ $value = [...($value ?? []), item];
51
+ } else {
52
+ $value = [...($value ?? []), textInput.trim()];
53
+ }
54
+ textInput = '';
55
+ }
56
+
57
+ function handleTextKeydown(e: KeyboardEvent) {
58
+ if (e.key === 'Enter') {
59
+ e.preventDefault();
60
+ addTextItem();
61
+ }
62
+ }
63
+
64
+ // --- Number ---
65
+ let numberInput = $state('');
66
+
67
+ function addNumberItem() {
68
+ const num = Number(numberInput);
69
+ if (numberInput === '' || isNaN(num) || atMax) return;
70
+ $value = [...($value ?? []), num];
71
+ numberInput = '';
72
+ }
73
+
74
+ function handleNumberKeydown(e: KeyboardEvent) {
75
+ if (e.key === 'Enter') {
76
+ e.preventDefault();
77
+ addNumberItem();
78
+ }
79
+ }
80
+
81
+ // --- URL ---
82
+ function addUrlItem() {
83
+ if (atMax) return;
84
+ const item: UrlFieldData = { url: {}, text: {}, newTab: false };
85
+ $value = [...($value ?? []), item];
86
+ }
87
+
88
+ // --- Common ---
89
+ function removeItem(index: number) {
90
+ if (!$value) return;
91
+ $value = $value.filter((_, i) => i !== index);
92
+ }
93
+
94
+ function getTextDisplay(item: unknown): string {
95
+ if (typeof item === 'string') return item;
96
+ if (typeof item === 'object' && item !== null) {
97
+ const rec = item as Record<string, string>;
98
+ return rec[contentLanguage.current] || Object.values(rec)[0] || '';
99
+ }
100
+ return String(item);
101
+ }
102
+ </script>
103
+
104
+ {#if field.of === 'text'}
105
+ <!-- Tag chips for text items -->
106
+ <div class="space-y-3">
107
+ {#if $value && $value.length > 0}
108
+ <div class="flex flex-wrap gap-2">
109
+ {#each $value as item, index}
110
+ <span
111
+ class="bg-[#EEEAF8] text-[#1A1A2E] inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium"
112
+ >
113
+ {#if field.localized}
114
+ <!-- Localized: show editable input per current lang -->
115
+ <input
116
+ type="text"
117
+ class="bg-transparent border-none outline-none w-auto min-w-[4ch] text-sm"
118
+ style="width: {getTextDisplay(item).length + 1}ch"
119
+ value={getTextDisplay(item)}
120
+ oninput={(e) => {
121
+ const val = e.currentTarget.value;
122
+ if (typeof item === 'object' && item !== null) {
123
+ const rec = { ...(item as Record<string, string>) };
124
+ rec[contentLanguage.current] = val;
125
+ const arr = [...($value ?? [])];
126
+ arr[index] = rec;
127
+ $value = arr;
128
+ }
129
+ }}
130
+ />
131
+ {:else}
132
+ {item}
133
+ {/if}
134
+ <button
135
+ type="button"
136
+ class="text-[#555566] hover:text-[#C44B4B] transition-colors"
137
+ onclick={() => removeItem(index)}
138
+ aria-label="Usuń element"
139
+ >
140
+ <X class="h-3.5 w-3.5" />
141
+ </button>
142
+ </span>
143
+ {/each}
144
+ </div>
145
+ {/if}
146
+
147
+ <div class="flex items-center gap-2">
148
+ <Input
149
+ type="text"
150
+ placeholder="Wpisz i naciśnij Enter..."
151
+ bind:value={textInput}
152
+ onkeydown={handleTextKeydown}
153
+ disabled={atMax}
154
+ class="max-w-xs"
155
+ />
156
+ <Button size="sm" type="button" variant="outline" disabled={atMax || !textInput.trim()} onclick={addTextItem}>
157
+ <CirclePlus class="h-4 w-4" />
158
+ Dodaj
159
+ </Button>
160
+ </div>
161
+
162
+ {#if field.maxItems !== undefined}
163
+ <p class="text-xs {atMax ? 'text-destructive' : 'text-muted-foreground'}">
164
+ {$value?.length ?? 0} / {field.maxItems}
165
+ </p>
166
+ {/if}
167
+ </div>
168
+
169
+ {:else if field.of === 'number'}
170
+ <!-- Tag chips for number items -->
171
+ <div class="space-y-3">
172
+ {#if $value && $value.length > 0}
173
+ <div class="flex flex-wrap gap-2">
174
+ {#each $value as item, index}
175
+ <span
176
+ class="bg-[#EEEAF8] text-[#1A1A2E] inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium tabular-nums"
177
+ >
178
+ {item}
179
+ <button
180
+ type="button"
181
+ class="text-[#555566] hover:text-[#C44B4B] transition-colors"
182
+ onclick={() => removeItem(index)}
183
+ aria-label="Usuń element"
184
+ >
185
+ <X class="h-3.5 w-3.5" />
186
+ </button>
187
+ </span>
188
+ {/each}
189
+ </div>
190
+ {/if}
191
+
192
+ <div class="flex items-center gap-2">
193
+ <Input
194
+ type="number"
195
+ placeholder="Wpisz liczbę..."
196
+ bind:value={numberInput}
197
+ onkeydown={handleNumberKeydown}
198
+ disabled={atMax}
199
+ class="max-w-[160px]"
200
+ />
201
+ <Button size="sm" type="button" variant="outline" disabled={atMax || numberInput === ''} onclick={addNumberItem}>
202
+ <CirclePlus class="h-4 w-4" />
203
+ Dodaj
204
+ </Button>
205
+ </div>
206
+
207
+ {#if field.maxItems !== undefined}
208
+ <p class="text-xs {atMax ? 'text-destructive' : 'text-muted-foreground'}">
209
+ {$value?.length ?? 0} / {field.maxItems}
210
+ </p>
211
+ {/if}
212
+ </div>
213
+
214
+ {:else if field.of === 'url'}
215
+ <!-- Compact list for URL items -->
216
+ <div class="space-y-3">
217
+ {#if $value && $value.length > 0}
218
+ <div class="space-y-2">
219
+ {#each $value as item, index}
220
+ {@const urlItem = item as UrlFieldData}
221
+ <div class="flex items-start gap-2 rounded-xl border border-[#E2DFF0] bg-[#F4F2FA] p-3">
222
+ <div class="flex-1 space-y-2">
223
+ <Input
224
+ type="url"
225
+ placeholder="URL..."
226
+ value={urlItem.url?.[contentLanguage.current] ?? ''}
227
+ oninput={(e) => {
228
+ const val = e.currentTarget.value;
229
+ const updated = { ...urlItem, url: { ...(urlItem.url ?? {}), [contentLanguage.current]: val } };
230
+ const arr = [...($value ?? [])];
231
+ arr[index] = updated;
232
+ $value = arr;
233
+ }}
234
+ />
235
+ <div class="flex items-center gap-2">
236
+ <Input
237
+ type="text"
238
+ placeholder="Tekst linku..."
239
+ class="flex-1"
240
+ value={urlItem.text?.[contentLanguage.current] ?? ''}
241
+ oninput={(e) => {
242
+ const val = e.currentTarget.value;
243
+ const updated = { ...urlItem, text: { ...(urlItem.text ?? {}), [contentLanguage.current]: val } };
244
+ const arr = [...($value ?? [])];
245
+ arr[index] = updated;
246
+ $value = arr;
247
+ }}
248
+ />
249
+ <label class="flex items-center gap-1.5 text-xs text-[#555566] whitespace-nowrap">
250
+ <input
251
+ type="checkbox"
252
+ checked={urlItem.newTab ?? false}
253
+ onchange={(e) => {
254
+ const updated = { ...urlItem, newTab: e.currentTarget.checked };
255
+ const arr = [...($value ?? [])];
256
+ arr[index] = updated;
257
+ $value = arr;
258
+ }}
259
+ class="accent-[#5B4A9E]"
260
+ />
261
+ Nowa karta
262
+ </label>
263
+ </div>
264
+ </div>
265
+ <button
266
+ type="button"
267
+ class="mt-1.5 text-[#555566] hover:text-[#C44B4B] transition-colors"
268
+ onclick={() => removeItem(index)}
269
+ aria-label="Usuń link"
270
+ >
271
+ <X class="h-4 w-4" />
272
+ </button>
273
+ </div>
274
+ {/each}
275
+ </div>
276
+ {/if}
277
+
278
+ <Button size="sm" type="button" variant="outline" disabled={atMax} onclick={addUrlItem}>
279
+ <CirclePlus class="h-4 w-4" />
280
+ Dodaj link
281
+ </Button>
282
+
283
+ {#if field.maxItems !== undefined}
284
+ <p class="text-xs {atMax ? 'text-destructive' : 'text-muted-foreground'}">
285
+ {$value?.length ?? 0} / {field.maxItems}
286
+ </p>
287
+ {/if}
288
+ </div>
289
+ {/if}
@@ -0,0 +1,30 @@
1
+ import { type FormPathLeaves, type SuperForm } from 'sveltekit-superforms';
2
+ import type { ArrayField } from '../../../types/fields.js';
3
+ declare function $$render<T extends Record<string, unknown>>(): {
4
+ props: {
5
+ field: ArrayField;
6
+ form: SuperForm<T>;
7
+ path: FormPathLeaves<T>;
8
+ };
9
+ exports: {};
10
+ bindings: "";
11
+ slots: {};
12
+ events: {};
13
+ };
14
+ declare class __sveltets_Render<T extends Record<string, unknown>> {
15
+ props(): ReturnType<typeof $$render<T>>['props'];
16
+ events(): ReturnType<typeof $$render<T>>['events'];
17
+ slots(): ReturnType<typeof $$render<T>>['slots'];
18
+ bindings(): "";
19
+ exports(): {};
20
+ }
21
+ interface $$IsomorphicComponent {
22
+ new <T extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
23
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
24
+ } & ReturnType<__sveltets_Render<T>['exports']>;
25
+ <T extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
26
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
27
+ }
28
+ declare const SimpleArrayField: $$IsomorphicComponent;
29
+ type SimpleArrayField<T extends Record<string, unknown>> = InstanceType<typeof SimpleArrayField<T>>;
30
+ export default SimpleArrayField;
@@ -22,13 +22,14 @@
22
22
 
23
23
  let { field, form, path, ...props }: Props = $props();
24
24
 
25
- let pattern = field.pattern;
25
+ let effectivePattern = field.pattern || (field.sourceField ? `{${field.sourceField}}` : undefined);
26
+ let pattern = effectivePattern;
26
27
 
27
28
  const { form: formData } = form;
28
29
 
29
30
  const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<string | undefined>;
30
31
 
31
- let patternKeys = field.pattern ? extractPatternKeys(field.pattern) : [];
32
+ let patternKeys = effectivePattern ? extractPatternKeys(effectivePattern) : [];
32
33
 
33
34
  function extractPatternKeys(pattern: string): string[] {
34
35
  const matches = [...pattern.matchAll(/{([^}]+)}/g)];