@hywax/cms 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.nuxt/cms/{input-uplora-image.ts → form-uplora-image.ts} +2 -9
  2. package/.nuxt/cms/http-statuses.ts +50 -0
  3. package/.nuxt/cms/index.ts +4 -4
  4. package/.nuxt/cms.css +3 -3
  5. package/dist/module.d.mts +5 -2
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +66 -44
  8. package/dist/runtime/components/DatePicker.vue +13 -1
  9. package/dist/runtime/components/FormPanel.vue +2 -0
  10. package/dist/runtime/components/{InputSeo.d.vue.ts → FormSeo.d.vue.ts} +10 -8
  11. package/dist/runtime/components/{InputSeo.vue → FormSeo.vue} +18 -8
  12. package/dist/runtime/components/{InputSeo.vue.d.ts → FormSeo.vue.d.ts} +10 -8
  13. package/dist/runtime/components/{InputSlug.vue.d.ts → FormSlug.d.vue.ts} +8 -7
  14. package/dist/runtime/components/{InputSlug.vue → FormSlug.vue} +15 -6
  15. package/dist/runtime/components/{InputSlug.d.vue.ts → FormSlug.vue.d.ts} +8 -7
  16. package/dist/runtime/components/FormUploraImage.d.vue.ts +43 -0
  17. package/dist/runtime/components/FormUploraImage.vue +172 -0
  18. package/dist/runtime/components/FormUploraImage.vue.d.ts +43 -0
  19. package/dist/runtime/components/TablePreviewLink.vue +1 -2
  20. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.vue +2 -2
  21. package/dist/runtime/server/api/uplora/[id].delete.js +1 -1
  22. package/dist/runtime/server/api/uplora/index.post.js +2 -4
  23. package/dist/runtime/server/utils/http.d.ts +52 -0
  24. package/dist/runtime/server/utils/http.js +92 -0
  25. package/dist/runtime/server/utils/timeout.d.ts +2 -1
  26. package/dist/runtime/server/utils/timeout.js +2 -1
  27. package/dist/runtime/server/utils/validation.js +6 -7
  28. package/dist/runtime/types/index.d.ts +3 -3
  29. package/dist/runtime/types/index.js +3 -3
  30. package/package.json +5 -4
  31. package/.nuxt/cms/http-codes.ts +0 -8
  32. package/dist/runtime/components/InputUploraImage.d.vue.ts +0 -40
  33. package/dist/runtime/components/InputUploraImage.vue +0 -181
  34. package/dist/runtime/components/InputUploraImage.vue.d.ts +0 -40
  35. package/dist/runtime/server/errors/InternalHttpError.d.ts +0 -8
  36. package/dist/runtime/server/errors/InternalHttpError.js +0 -7
  37. package/dist/runtime/server/errors/TimeoutError.d.ts +0 -2
  38. package/dist/runtime/server/errors/TimeoutError.js +0 -2
  39. package/dist/runtime/server/errors/index.d.ts +0 -2
  40. package/dist/runtime/server/errors/index.js +0 -2
  41. package/dist/runtime/server/utils/errors.d.ts +0 -8
  42. package/dist/runtime/server/utils/errors.js +0 -57
  43. package/dist/runtime/server/utils/httpHandler.d.ts +0 -10
  44. package/dist/runtime/server/utils/httpHandler.js +0 -15
  45. /package/.nuxt/cms/{input-seo.ts → form-seo.ts} +0 -0
  46. /package/.nuxt/cms/{input-slug.ts → form-slug.ts} +0 -0
@@ -1,9 +1,7 @@
1
1
  export default {
2
2
  "slots": {
3
- "root": "relative w-full rounded-md overflow-hidden border-1 border-default aspect-3/2 text-sm bg-default",
4
- "image": "",
5
- "imageActions": "absolute top-4 right-4 flex gap-2",
6
- "imageActionsWarning": "absolute top-0 right-0 size-6 bg-linear-to-bl from-error/90 to-transparent rounded-bl-lg text-error flex items-center justify-center",
3
+ "root": "relative w-full gap-1",
4
+ "base": "rounded-md overflow-hidden border-1 border-default bg-default aspect-3/2",
7
5
  "uploader": "flex flex-col items-center justify-center w-full h-full",
8
6
  "uploaderPendingIcon": "size-6 animate-spin",
9
7
  "uploaderIdleButton": "cursor-pointer h-full w-full",
@@ -17,11 +15,6 @@ export default {
17
15
  "true": {
18
16
  "root": "cursor-not-allowed opacity-75"
19
17
  }
20
- },
21
- "uploaded": {
22
- "false": {
23
- "root": "border-dashed"
24
- }
25
18
  }
26
19
  }
27
20
  }
@@ -0,0 +1,50 @@
1
+ export const httpStatuses = {
2
+ "badRequest": {
3
+ "code": 400,
4
+ "message": "Неверный запрос"
5
+ },
6
+ "unauthorized": {
7
+ "code": 401,
8
+ "message": "Неавторизован"
9
+ },
10
+ "forbidden": {
11
+ "code": 403,
12
+ "message": "Доступ запрещен"
13
+ },
14
+ "notFound": {
15
+ "code": 404,
16
+ "message": "Не найдено"
17
+ },
18
+ "alreadyExists": {
19
+ "code": 409,
20
+ "message": "Уже существует"
21
+ },
22
+ "notAllowed": {
23
+ "code": 405,
24
+ "message": "Не разрешено"
25
+ },
26
+ "internalServerError": {
27
+ "code": 500,
28
+ "message": "Внутренняя ошибка сервера"
29
+ },
30
+ "dbNotDefined": {
31
+ "code": 500,
32
+ "message": "База данных не определена"
33
+ },
34
+ "dbConnectionRefused": {
35
+ "code": 500,
36
+ "message": "Не удалось подключиться к базе данных"
37
+ },
38
+ "dbInsertFailed": {
39
+ "code": 500,
40
+ "message": "Ошибка при добавлении данных в базу данных"
41
+ },
42
+ "badGateway": {
43
+ "code": 502,
44
+ "message": "Ошибка шлюза"
45
+ },
46
+ "serviceUnavailable": {
47
+ "code": 503,
48
+ "message": "Сервис недоступен"
49
+ }
50
+ }
@@ -3,10 +3,10 @@ export { default as ButtonDeleteConfirm } from './button-delete-confirm'
3
3
  export { default as FormPanel } from './form-panel'
4
4
  export { default as FormPanelAsideSection } from './form-panel-aside-section'
5
5
  export { default as FormPanelSection } from './form-panel-section'
6
- export { default as InputSeo } from './input-seo'
7
- export { default as InputUploraImage } from './input-uplora-image'
6
+ export { default as FormSeo } from './form-seo'
7
+ export { default as FormSlug } from './form-slug'
8
+ export { default as FormUploraImage } from './form-uplora-image'
8
9
  export { default as ModalConfirm } from './modal-confirm'
9
10
  export { default as TablePanel } from './table-panel'
10
11
  export { default as TableSearchInput } from './table-search-input'
11
- export { default as UploraImage } from './uplora-image'
12
- export { default as inputSlug } from './input-slug'
12
+ export { default as UploraImage } from './uplora-image'
package/.nuxt/cms.css CHANGED
@@ -4,10 +4,10 @@
4
4
  @source "./cms/form-panel.ts";
5
5
  @source "./cms/editor-full.ts";
6
6
  @source "./cms/form-panel-section.ts";
7
- @source "./cms/input-slug.ts";
8
- @source "./cms/input-seo.ts";
9
- @source "./cms/input-uplora-image.ts";
7
+ @source "./cms/form-slug.ts";
8
+ @source "./cms/form-uplora-image.ts";
10
9
  @source "./cms/uplora-image.ts";
10
+ @source "./cms/form-seo.ts";
11
11
  @source "./cms/table-panel.ts";
12
12
  @source "./cms/modal-confirm.ts";
13
13
  @source "./cms/table-search-input.ts";
package/dist/module.d.mts CHANGED
@@ -34,9 +34,12 @@ interface CMSCoreOptions {
34
34
  */
35
35
  unovis?: boolean;
36
36
  /**
37
- * HTTP codes
37
+ * HTTP statuses
38
38
  */
39
- httpCodes?: Record<string, string>;
39
+ httpStatuses?: Record<string, {
40
+ code: number;
41
+ message: string;
42
+ }>;
40
43
  /**
41
44
  * Component detection
42
45
  */
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "2.1.0",
3
+ "version": "3.0.1",
4
4
  "configKey": "cms",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
package/dist/module.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import { createResolver, addComponentsDir, addPlugin, addImports, addImportsDir, addServerImports, addServerImportsDir, addServerHandler, addTypeTemplate, addTemplate, addServerTemplate, updateTemplates, getLayerDirectories, logger, defineNuxtModule } from '@nuxt/kit';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { dirname, join } from 'pathe';
4
- import { snakeCase, pascalCase, kebabCase } from 'scule';
5
4
  import { readFile, writeFile } from 'node:fs/promises';
6
5
  import { defu } from 'defu';
6
+ import { pascalCase, kebabCase } from 'scule';
7
7
  import { globSync } from 'tinyglobby';
8
8
 
9
9
  const name = "@hywax/cms";
10
- const version = "2.1.0";
10
+ const version = "3.0.1";
11
11
 
12
12
  function createContext(options, nuxt) {
13
13
  const { resolve } = createResolver(import.meta.url);
@@ -30,15 +30,55 @@ const defaultModuleOptions = {
30
30
  envPrefix: "APP_",
31
31
  unovis: false,
32
32
  componentDetection: true,
33
- httpCodes: {
34
- badRequest: "400: \u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0437\u0430\u043F\u0440\u043E\u0441",
35
- unauthorized: "401: \u041D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D",
36
- forbidden: "403: \u0414\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D",
37
- notFound: "404: \u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
38
- requestTimeout: "408: \u0412\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0438\u0441\u0442\u0435\u043A\u043B\u043E",
39
- internalServerError: "500: \u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
40
- badGateway: "502: \u041E\u0448\u0438\u0431\u043A\u0430 \u0448\u043B\u044E\u0437\u0430",
41
- serviceUnavailable: "503: \u0421\u0435\u0440\u0432\u0438\u0441 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"
33
+ httpStatuses: {
34
+ badRequest: {
35
+ code: 400,
36
+ message: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0437\u0430\u043F\u0440\u043E\u0441"
37
+ },
38
+ unauthorized: {
39
+ code: 401,
40
+ message: "\u041D\u0435\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D"
41
+ },
42
+ forbidden: {
43
+ code: 403,
44
+ message: "\u0414\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D"
45
+ },
46
+ notFound: {
47
+ code: 404,
48
+ message: "\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E"
49
+ },
50
+ alreadyExists: {
51
+ code: 409,
52
+ message: "\u0423\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442"
53
+ },
54
+ notAllowed: {
55
+ code: 405,
56
+ message: "\u041D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u043E"
57
+ },
58
+ internalServerError: {
59
+ code: 500,
60
+ message: "\u0412\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430"
61
+ },
62
+ dbNotDefined: {
63
+ code: 500,
64
+ message: "\u0411\u0430\u0437\u0430 \u0434\u0430\u043D\u043D\u044B\u0445 \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u0430"
65
+ },
66
+ dbConnectionRefused: {
67
+ code: 500,
68
+ message: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043A \u0431\u0430\u0437\u0435 \u0434\u0430\u043D\u043D\u044B\u0445"
69
+ },
70
+ dbInsertFailed: {
71
+ code: 500,
72
+ message: "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043D\u043D\u044B\u0445"
73
+ },
74
+ badGateway: {
75
+ code: 502,
76
+ message: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0448\u043B\u044E\u0437\u0430"
77
+ },
78
+ serviceUnavailable: {
79
+ code: 503,
80
+ message: "\u0421\u0435\u0440\u0432\u0438\u0441 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"
81
+ }
42
82
  },
43
83
  formats: {
44
84
  serverDateTime: "YYYY-MM-DD HH:mm:ss"
@@ -51,17 +91,9 @@ const defaultModuleOptions = {
51
91
  }
52
92
  };
53
93
 
54
- function transformHttpCodes(httpCodes) {
55
- return Object.entries(httpCodes).map(([code, value]) => ({
56
- code: `HTTP_CODE_${snakeCase(code).toUpperCase()}`,
57
- value
58
- }));
59
- }
60
-
61
94
  function prepareAutoImports({ resolve, options, nuxt }) {
62
95
  const cmsConfigPath = resolve(nuxt.options.buildDir, "cms/config");
63
- const httpCodesPath = resolve(nuxt.options.buildDir, "cms/http-codes");
64
- const httpCodesImports = transformHttpCodes(options.httpCodes).map(({ code }) => ({ name: code, from: httpCodesPath }));
96
+ const httpStatusesPath = resolve(nuxt.options.buildDir, "cms/http-statuses");
65
97
  addComponentsDir({
66
98
  path: resolve("./runtime/components/prose"),
67
99
  prefix: "Prose",
@@ -77,7 +109,6 @@ function prepareAutoImports({ resolve, options, nuxt }) {
77
109
  addPlugin(resolve("./runtime/plugins/api"));
78
110
  addPlugin(resolve("./runtime/plugins/zod"));
79
111
  addImports([
80
- ...httpCodesImports,
81
112
  { name: "getCmsConfig", from: cmsConfigPath }
82
113
  ]);
83
114
  addImportsDir([
@@ -85,7 +116,6 @@ function prepareAutoImports({ resolve, options, nuxt }) {
85
116
  resolve("./runtime/composables")
86
117
  ]);
87
118
  addServerImports([
88
- ...httpCodesImports,
89
119
  { name: "getCmsConfig", from: cmsConfigPath }
90
120
  ]);
91
121
  addServerImportsDir([
@@ -93,7 +123,7 @@ function prepareAutoImports({ resolve, options, nuxt }) {
93
123
  resolve("./runtime/server/utils")
94
124
  ]);
95
125
  nuxt.options.nitro.alias ||= {};
96
- nuxt.options.nitro.alias["#cms/http-codes"] = httpCodesPath;
126
+ nuxt.options.nitro.alias["#cms/http-statuses"] = httpStatusesPath;
97
127
  nuxt.options.alias["#cms"] = resolve("./runtime");
98
128
  }
99
129
 
@@ -146,7 +176,8 @@ const icons = {
146
176
  strikethrough: "i-lucide-strikethrough",
147
177
  code: "i-lucide-code",
148
178
  image: "i-lucide-image",
149
- editLine: "i-lucide-pen-line"
179
+ editLine: "i-lucide-pen-line",
180
+ save: "i-lucide-cloud-check"
150
181
  };
151
182
 
152
183
  async function buildComponentDependencyGraph(componentDir, componentPattern) {
@@ -437,24 +468,22 @@ const formPanelSection = {
437
468
  }
438
469
  };
439
470
 
440
- const inputSeo = {
471
+ const formSeo = {
441
472
  slots: {
442
473
  root: "flex flex-col gap-4"
443
474
  }
444
475
  };
445
476
 
446
- const inputSlug = {
477
+ const formSlug = {
447
478
  slots: {
448
479
  root: "flex flex-col gap-4"
449
480
  }
450
481
  };
451
482
 
452
- const inputUploraImage = {
483
+ const formUploraImage = {
453
484
  slots: {
454
- root: "relative w-full rounded-md overflow-hidden border-1 border-default aspect-3/2 text-sm bg-default",
455
- image: "",
456
- imageActions: "absolute top-4 right-4 flex gap-2",
457
- imageActionsWarning: "absolute top-0 right-0 size-6 bg-linear-to-bl from-error/90 to-transparent rounded-bl-lg text-error flex items-center justify-center",
485
+ root: "relative w-full gap-1",
486
+ base: "rounded-md overflow-hidden border-1 border-default bg-default aspect-3/2",
458
487
  uploader: "flex flex-col items-center justify-center w-full h-full",
459
488
  uploaderPendingIcon: "size-6 animate-spin",
460
489
  uploaderIdleButton: "cursor-pointer h-full w-full",
@@ -468,11 +497,6 @@ const inputUploraImage = {
468
497
  true: {
469
498
  root: "cursor-not-allowed opacity-75"
470
499
  }
471
- },
472
- uploaded: {
473
- false: {
474
- root: "border-dashed"
475
- }
476
500
  }
477
501
  }
478
502
  };
@@ -515,13 +539,13 @@ const theme = {
515
539
  FormPanel: formPanel,
516
540
  FormPanelAsideSection: formPanelAsideSection,
517
541
  FormPanelSection: formPanelSection,
518
- InputSeo: inputSeo,
519
- InputUploraImage: inputUploraImage,
542
+ FormSeo: formSeo,
543
+ FormSlug: formSlug,
544
+ FormUploraImage: formUploraImage,
520
545
  ModalConfirm: modalConfirm,
521
546
  TablePanel: tablePanel,
522
547
  TableSearchInput: tableSearchInput,
523
- UploraImage: uploraImage$1,
524
- inputSlug: inputSlug
548
+ UploraImage: uploraImage$1
525
549
  };
526
550
 
527
551
  const themeEditor = {
@@ -694,12 +718,10 @@ export {}
694
718
  `
695
719
  });
696
720
  templates.push({
697
- filename: "cms/http-codes.ts",
721
+ filename: "cms/http-statuses.ts",
698
722
  write: true,
699
723
  getContents: () => {
700
- const httpCodes = transformHttpCodes(options.httpCodes);
701
- const content = httpCodes.map(({ code, value }) => `export const ${code} = '${value}'`).join("\n");
702
- return `${content}
724
+ return `export const httpStatuses = ${JSON.stringify(options.httpStatuses, null, 2)}
703
725
  `;
704
726
  }
705
727
  });
@@ -12,7 +12,19 @@
12
12
  :size="size"
13
13
  v-bind="ariaAttrs"
14
14
  >
15
- <span class="truncate">{{ textValue }} </span>
15
+ <span class="truncate">{{ textValue }}</span>
16
+
17
+ <template #trailing>
18
+ <UButton
19
+ :icon="appConfig.ui.icons.close"
20
+ as="div"
21
+ variant="link"
22
+ size="sm"
23
+ color="neutral"
24
+ class="p-0 ms-auto"
25
+ @click.prevent.stop="$emit('update:modelValue', void 0)"
26
+ />
27
+ </template>
16
28
  </UButton>
17
29
 
18
30
  <template #content>
@@ -15,6 +15,7 @@
15
15
  <UButton
16
16
  type="submit"
17
17
  label="Сохранить"
18
+ :icon="appConfig.ui.icons.save"
18
19
  :form="formId"
19
20
  :loading="loading"
20
21
  loading-auto
@@ -28,6 +29,7 @@
28
29
  :state="state"
29
30
  :schema="schema"
30
31
  :class="ui.form({ class: props.ui?.form })"
32
+ :validate-on="['change']"
31
33
  @submit="submitHandler"
32
34
  >
33
35
  <div :class="ui.body({ class: props.ui?.body })">
@@ -1,20 +1,22 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { ComponentConfig, SEO } from '../types';
3
- import theme from '#build/cms/input-seo';
4
- type InputSeo = ComponentConfig<typeof theme, AppConfig, 'inputSeo'>;
5
- export interface InputSeoProps {
6
- as?: any;
3
+ import theme from '#build/cms/form-seo';
4
+ type FormSeo = ComponentConfig<typeof theme, AppConfig, 'formSeo'>;
5
+ export interface FormSeoProps {
7
6
  class?: any;
8
- ui?: InputSeo['slots'];
7
+ name?: string;
8
+ ui?: FormSeo['slots'];
9
9
  }
10
10
  declare const _default: typeof __VLS_export;
11
11
  export default _default;
12
- declare const __VLS_export: import("vue").DefineComponent<InputSeoProps & {
12
+ declare const __VLS_export: import("vue").DefineComponent<FormSeoProps & {
13
13
  modelValue?: SEO;
14
14
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
15
  "update:modelValue": (value: SEO) => any;
16
- }, string, import("vue").PublicProps, Readonly<InputSeoProps & {
16
+ }, string, import("vue").PublicProps, Readonly<FormSeoProps & {
17
17
  modelValue?: SEO;
18
18
  }> & Readonly<{
19
19
  "onUpdate:modelValue"?: ((value: SEO) => any) | undefined;
20
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ }>, {
21
+ name: string;
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,7 +1,13 @@
1
1
  <template>
2
- <Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
2
+ <UForm
3
+ :name="name"
4
+ :schema="schema"
5
+ :class="ui.root({ class: [props.ui?.root, props.class] })"
6
+ :validate-on="['change']"
7
+ nested
8
+ >
3
9
  <UFormField
4
- name="seo.title"
10
+ name="title"
5
11
  label="Заголовок"
6
12
  :hint="`${model.title.length}/60`"
7
13
  :ui="{ hint: 'text-xs' }"
@@ -21,7 +27,7 @@
21
27
  </UFormField>
22
28
 
23
29
  <UFormField
24
- name="seo.description"
30
+ name="description"
25
31
  label="Описание"
26
32
  :hint="`${model.description.length}/160`"
27
33
  :ui="{ hint: 'text-xs' }"
@@ -44,22 +50,22 @@
44
50
  />
45
51
  </template>
46
52
  </UFormField>
47
- </Primitive>
53
+ </UForm>
48
54
  </template>
49
55
 
50
56
  <script>
51
- import theme from "#build/cms/input-seo";
57
+ import theme from "#build/cms/form-seo";
52
58
  import { useAppConfig } from "#imports";
53
- import { Primitive } from "reka-ui";
54
59
  import { computed } from "vue";
60
+ import { z } from "zod";
55
61
  import { useSeoStats } from "../composables/useSeoStats";
56
62
  import { tv } from "../tv";
57
63
  </script>
58
64
 
59
65
  <script setup>
60
66
  const props = defineProps({
61
- as: { type: null, required: false },
62
67
  class: { type: null, required: false },
68
+ name: { type: String, required: false, default: "seo" },
63
69
  ui: { type: null, required: false }
64
70
  });
65
71
  const model = defineModel({ type: Object, ...{
@@ -69,6 +75,10 @@ const model = defineModel({ type: Object, ...{
69
75
  })
70
76
  } });
71
77
  const appConfig = useAppConfig();
78
+ const schema = z.object({
79
+ title: z.string().min(1),
80
+ description: z.string().min(1)
81
+ });
72
82
  const { title, description } = useSeoStats(model);
73
- const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.inputSeo || {} })());
83
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formSeo || {} })());
74
84
  </script>
@@ -1,20 +1,22 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { ComponentConfig, SEO } from '../types';
3
- import theme from '#build/cms/input-seo';
4
- type InputSeo = ComponentConfig<typeof theme, AppConfig, 'inputSeo'>;
5
- export interface InputSeoProps {
6
- as?: any;
3
+ import theme from '#build/cms/form-seo';
4
+ type FormSeo = ComponentConfig<typeof theme, AppConfig, 'formSeo'>;
5
+ export interface FormSeoProps {
7
6
  class?: any;
8
- ui?: InputSeo['slots'];
7
+ name?: string;
8
+ ui?: FormSeo['slots'];
9
9
  }
10
10
  declare const _default: typeof __VLS_export;
11
11
  export default _default;
12
- declare const __VLS_export: import("vue").DefineComponent<InputSeoProps & {
12
+ declare const __VLS_export: import("vue").DefineComponent<FormSeoProps & {
13
13
  modelValue?: SEO;
14
14
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
15
  "update:modelValue": (value: SEO) => any;
16
- }, string, import("vue").PublicProps, Readonly<InputSeoProps & {
16
+ }, string, import("vue").PublicProps, Readonly<FormSeoProps & {
17
17
  modelValue?: SEO;
18
18
  }> & Readonly<{
19
19
  "onUpdate:modelValue"?: ((value: SEO) => any) | undefined;
20
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ }>, {
21
+ name: string;
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,33 +1,34 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { InputProps } from '@nuxt/ui';
3
3
  import type { ComponentConfig } from '../types';
4
- import theme from '#build/cms/input-slug';
5
- type InputSlug = ComponentConfig<typeof theme, AppConfig, 'inputSlug'>;
6
- export interface InputSlugProps {
4
+ import theme from '#build/cms/form-slug';
5
+ type FormSlug = ComponentConfig<typeof theme, AppConfig, 'formSlug'>;
6
+ export interface FormSlugProps {
7
+ name?: string;
7
8
  regenerate?: boolean;
8
9
  label?: string;
9
10
  titleKey?: string;
10
11
  slugKey?: string;
11
12
  inputProps?: Omit<InputProps, 'modelModifiers'>;
12
- as?: any;
13
13
  class?: any;
14
- ui?: InputSlug['slots'];
14
+ ui?: FormSlug['slots'];
15
15
  }
16
16
  declare const _default: typeof __VLS_export;
17
17
  export default _default;
18
- declare const __VLS_export: import("vue").DefineComponent<InputSlugProps & {
18
+ declare const __VLS_export: import("vue").DefineComponent<FormSlugProps & {
19
19
  title?: string;
20
20
  slug?: string;
21
21
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
22
22
  "update:title": (value: string | undefined) => any;
23
23
  "update:slug": (value: string | undefined) => any;
24
- }, string, import("vue").PublicProps, Readonly<InputSlugProps & {
24
+ }, string, import("vue").PublicProps, Readonly<FormSlugProps & {
25
25
  title?: string;
26
26
  slug?: string;
27
27
  }> & Readonly<{
28
28
  "onUpdate:title"?: ((value: string | undefined) => any) | undefined;
29
29
  "onUpdate:slug"?: ((value: string | undefined) => any) | undefined;
30
30
  }>, {
31
+ name: string;
31
32
  titleKey: string;
32
33
  slugKey: string;
33
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
2
+ <UForm
3
+ :schema="schema"
4
+ :class="ui.root({ class: [props.ui?.root, props.class] })"
5
+ :validate-on="['change']"
6
+ nested
7
+ >
3
8
  <UFormField
4
9
  :label="label"
5
10
  :name="titleKey"
@@ -34,32 +39,36 @@
34
39
  </template>
35
40
  </UInput>
36
41
  </UFormField>
37
- </Primitive>
42
+ </UForm>
38
43
  </template>
39
44
 
40
45
  <script>
41
- import theme from "#build/cms/input-slug";
46
+ import theme from "#build/cms/form-slug";
42
47
  import { useAppConfig } from "#imports";
43
- import { Primitive } from "reka-ui";
44
48
  import { computed, ref, watch } from "vue";
49
+ import { z } from "zod";
45
50
  import { tv } from "../tv";
46
51
  import { slugify } from "../utils/slugify";
47
52
  </script>
48
53
 
49
54
  <script setup>
50
55
  const props = defineProps({
56
+ name: { type: String, required: false, default: "slug" },
51
57
  regenerate: { type: Boolean, required: false },
52
58
  label: { type: String, required: false },
53
59
  titleKey: { type: String, required: false, default: "title" },
54
60
  slugKey: { type: String, required: false, default: "slug" },
55
61
  inputProps: { type: Object, required: false },
56
- as: { type: null, required: false },
57
62
  class: { type: null, required: false },
58
63
  ui: { type: null, required: false }
59
64
  });
60
65
  const title = defineModel("title", { type: String });
61
66
  const slug = defineModel("slug", { type: String });
62
67
  const appConfig = useAppConfig();
68
+ const schema = z.object({
69
+ title: z.string().min(1),
70
+ slug: z.string().min(1)
71
+ });
63
72
  const isRegenerate = ref(props.regenerate);
64
73
  watch(title, () => {
65
74
  if (!isRegenerate.value) {
@@ -67,5 +76,5 @@ watch(title, () => {
67
76
  }
68
77
  slug.value = slugify(title.value);
69
78
  });
70
- const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.inputSlug || {} })());
79
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formSlug || {} })());
71
80
  </script>
@@ -1,33 +1,34 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { InputProps } from '@nuxt/ui';
3
3
  import type { ComponentConfig } from '../types';
4
- import theme from '#build/cms/input-slug';
5
- type InputSlug = ComponentConfig<typeof theme, AppConfig, 'inputSlug'>;
6
- export interface InputSlugProps {
4
+ import theme from '#build/cms/form-slug';
5
+ type FormSlug = ComponentConfig<typeof theme, AppConfig, 'formSlug'>;
6
+ export interface FormSlugProps {
7
+ name?: string;
7
8
  regenerate?: boolean;
8
9
  label?: string;
9
10
  titleKey?: string;
10
11
  slugKey?: string;
11
12
  inputProps?: Omit<InputProps, 'modelModifiers'>;
12
- as?: any;
13
13
  class?: any;
14
- ui?: InputSlug['slots'];
14
+ ui?: FormSlug['slots'];
15
15
  }
16
16
  declare const _default: typeof __VLS_export;
17
17
  export default _default;
18
- declare const __VLS_export: import("vue").DefineComponent<InputSlugProps & {
18
+ declare const __VLS_export: import("vue").DefineComponent<FormSlugProps & {
19
19
  title?: string;
20
20
  slug?: string;
21
21
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
22
22
  "update:title": (value: string | undefined) => any;
23
23
  "update:slug": (value: string | undefined) => any;
24
- }, string, import("vue").PublicProps, Readonly<InputSlugProps & {
24
+ }, string, import("vue").PublicProps, Readonly<FormSlugProps & {
25
25
  title?: string;
26
26
  slug?: string;
27
27
  }> & Readonly<{
28
28
  "onUpdate:title"?: ((value: string | undefined) => any) | undefined;
29
29
  "onUpdate:slug"?: ((value: string | undefined) => any) | undefined;
30
30
  }>, {
31
+ name: string;
31
32
  titleKey: string;
32
33
  slugKey: string;
33
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;