@hywax/cms 3.6.1 → 3.8.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 (67) hide show
  1. package/.nuxt/cms/config.ts +0 -3
  2. package/.nuxt/cms/prose/index.ts +2 -1
  3. package/.nuxt/cms/prose/uplora-image-gallery.ts +15 -0
  4. package/.nuxt/cms/uplora-image.ts +12 -1
  5. package/dist/module.json +1 -1
  6. package/dist/module.mjs +34 -4
  7. package/dist/runtime/components/EditorFull.vue +18 -3
  8. package/dist/runtime/components/FormPanel.vue +1 -1
  9. package/dist/runtime/components/FormSeo.d.vue.ts +4 -4
  10. package/dist/runtime/components/FormSeo.vue +28 -15
  11. package/dist/runtime/components/FormSeo.vue.d.ts +4 -4
  12. package/dist/runtime/components/FormSlug.vue +9 -2
  13. package/dist/runtime/components/FormUploraImage.d.vue.ts +7 -9
  14. package/dist/runtime/components/FormUploraImage.vue +73 -48
  15. package/dist/runtime/components/FormUploraImage.vue.d.ts +7 -9
  16. package/dist/runtime/components/UploraImage.d.vue.ts +4 -0
  17. package/dist/runtime/components/UploraImage.vue +13 -2
  18. package/dist/runtime/components/UploraImage.vue.d.ts +4 -0
  19. package/dist/runtime/components/prose/UploraImage.d.vue.ts +4 -7
  20. package/dist/runtime/components/prose/UploraImage.vue +13 -7
  21. package/dist/runtime/components/prose/UploraImage.vue.d.ts +4 -7
  22. package/dist/runtime/components/prose/UploraImageGallery.d.vue.ts +19 -0
  23. package/dist/runtime/components/prose/UploraImageGallery.vue +24 -0
  24. package/dist/runtime/components/prose/UploraImageGallery.vue.d.ts +19 -0
  25. package/dist/runtime/composables/useEditorSuggestions.d.ts +29 -6
  26. package/dist/runtime/composables/useEditorSuggestions.js +7 -3
  27. package/dist/runtime/editor/caution/EditorCaution.d.ts +18 -0
  28. package/dist/runtime/editor/caution/EditorCaution.js +36 -0
  29. package/dist/runtime/editor/caution/EditorCautionNode.d.vue.ts +4 -0
  30. package/dist/runtime/editor/caution/EditorCautionNode.vue +22 -0
  31. package/dist/runtime/editor/caution/EditorCautionNode.vue.d.ts +4 -0
  32. package/dist/runtime/editor/note/EditorNote.d.ts +18 -0
  33. package/dist/runtime/editor/note/EditorNote.js +36 -0
  34. package/dist/runtime/editor/note/EditorNoteNode.d.vue.ts +4 -0
  35. package/dist/runtime/editor/note/EditorNoteNode.vue +22 -0
  36. package/dist/runtime/editor/note/EditorNoteNode.vue.d.ts +4 -0
  37. package/dist/runtime/editor/tip/EditorTip.d.ts +18 -0
  38. package/dist/runtime/editor/tip/EditorTip.js +36 -0
  39. package/dist/runtime/editor/tip/EditorTipNode.d.vue.ts +4 -0
  40. package/dist/runtime/editor/tip/EditorTipNode.vue +22 -0
  41. package/dist/runtime/editor/tip/EditorTipNode.vue.d.ts +4 -0
  42. package/dist/runtime/editor/uplora-image/EditorUploraImage.js +10 -4
  43. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.d.vue.ts +0 -5
  44. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.vue +2 -2
  45. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.vue.d.ts +0 -5
  46. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGallery.d.ts +18 -0
  47. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGallery.js +71 -0
  48. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGalleryNode.d.vue.ts +4 -0
  49. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGalleryNode.vue +93 -0
  50. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGalleryNode.vue.d.ts +4 -0
  51. package/dist/runtime/editor/utils/attributes.d.ts +2 -0
  52. package/dist/runtime/editor/{utils.js → utils/attributes.js} +0 -29
  53. package/dist/runtime/editor/utils/createAtomBlockMarkdownSpec.d.ts +10 -0
  54. package/dist/runtime/editor/utils/createAtomBlockMarkdownSpec.js +32 -0
  55. package/dist/runtime/editor/utils/createBlockMarkdownSpec.d.ts +12 -0
  56. package/dist/runtime/editor/utils/createBlockMarkdownSpec.js +99 -0
  57. package/dist/runtime/editor/utils/index.d.ts +3 -0
  58. package/dist/runtime/editor/utils/index.js +3 -0
  59. package/dist/runtime/editor/warning/EditorWarning.d.ts +18 -0
  60. package/dist/runtime/editor/warning/EditorWarning.js +36 -0
  61. package/dist/runtime/editor/warning/EditorWarningNode.d.vue.ts +4 -0
  62. package/dist/runtime/editor/warning/EditorWarningNode.vue +22 -0
  63. package/dist/runtime/editor/warning/EditorWarningNode.vue.d.ts +4 -0
  64. package/dist/runtime/server/utils/pagination.d.ts +4 -0
  65. package/dist/runtime/server/utils/pagination.js +2 -2
  66. package/package.json +1 -1
  67. package/dist/runtime/editor/utils.d.ts +0 -13
@@ -1,6 +1,3 @@
1
- interface CmsConfig {
2
- }
3
-
4
1
  const cmsConfig = {
5
2
  "name": "cms",
6
3
  "formats": {
@@ -1 +1,2 @@
1
- export { default as uploraImage } from './uplora-image'
1
+ export { default as uploraImage } from './uplora-image'
2
+ export { default as uploraImageGallery } from './uplora-image-gallery'
@@ -0,0 +1,15 @@
1
+ export default {
2
+ "slots": {
3
+ "base": "grid gap-4"
4
+ },
5
+ "variants": {
6
+ "layout": {
7
+ "two-columns": {
8
+ "base": "grid-cols-2"
9
+ },
10
+ "three-columns": {
11
+ "base": "grid-cols-3"
12
+ }
13
+ }
14
+ }
15
+ }
@@ -2,7 +2,18 @@ export default {
2
2
  "slots": {
3
3
  "root": "relative grid grid-cols-[100%] grid-rows-[100%] overflow-hidden",
4
4
  "lqip": "bg-cover bg-no-repeat bg-center col-[1] row-[1]",
5
- "picture": "aspect-3/2 col-[1] row-[1]",
5
+ "picture": "col-[1] row-[1]",
6
6
  "img": "w-full h-full block object-cover"
7
+ },
8
+ "variants": {
9
+ "aspect": {
10
+ "horizontal": {
11
+ "picture": "aspect-3/2"
12
+ },
13
+ "vertical": {
14
+ "picture": "aspect-2/3"
15
+ },
16
+ "dynamic": {}
17
+ }
7
18
  }
8
19
  }
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "3.6.1",
3
+ "version": "3.8.0",
4
4
  "configKey": "cms",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
package/dist/module.mjs CHANGED
@@ -7,7 +7,7 @@ import { pascalCase, kebabCase, camelCase } from 'scule';
7
7
  import { globSync } from 'tinyglobby';
8
8
 
9
9
  const name = "@hywax/cms";
10
- const version = "3.6.1";
10
+ const version = "3.8.0";
11
11
 
12
12
  function createContext(options, nuxt) {
13
13
  const { resolve } = createResolver(import.meta.url);
@@ -181,6 +181,7 @@ const icons = {
181
181
  strikethrough: "i-lucide-strikethrough",
182
182
  code: "i-lucide-code",
183
183
  image: "i-lucide-image",
184
+ images: "i-lucide-images",
184
185
  editLine: "i-lucide-pen-line",
185
186
  save: "i-lucide-cloud-check",
186
187
  toc: "i-lucide-text-align-start"
@@ -356,7 +357,8 @@ async function prepareMergeConfigs({ nuxt, options, resolve, detectedComponents:
356
357
  nuxt.options.mdc = defu(nuxt.options.mdc || {}, {
357
358
  components: {
358
359
  map: {
359
- "uplora-image": "ProseUploraImage"
360
+ "uplora-image": "ProseUploraImage",
361
+ "uplora-image-gallery": "ProseUploraImageGallery"
360
362
  }
361
363
  }
362
364
  });
@@ -563,8 +565,19 @@ const uploraImage$1 = {
563
565
  slots: {
564
566
  root: "relative grid grid-cols-[100%] grid-rows-[100%] overflow-hidden",
565
567
  lqip: "bg-cover bg-no-repeat bg-center col-[1] row-[1]",
566
- picture: "aspect-3/2 col-[1] row-[1]",
568
+ picture: "col-[1] row-[1]",
567
569
  img: "w-full h-full block object-cover"
570
+ },
571
+ variants: {
572
+ aspect: {
573
+ horizontal: {
574
+ picture: "aspect-3/2"
575
+ },
576
+ vertical: {
577
+ picture: "aspect-2/3"
578
+ },
579
+ dynamic: {}
580
+ }
568
581
  }
569
582
  };
570
583
 
@@ -601,9 +614,26 @@ const uploraImage = {
601
614
  }
602
615
  };
603
616
 
617
+ const uploraImageGallery = {
618
+ slots: {
619
+ base: "grid gap-4"
620
+ },
621
+ variants: {
622
+ layout: {
623
+ "two-columns": {
624
+ base: "grid-cols-2"
625
+ },
626
+ "three-columns": {
627
+ base: "grid-cols-3"
628
+ }
629
+ }
630
+ }
631
+ };
632
+
604
633
  const themeProse = {
605
634
  __proto__: null,
606
- uploraImage: uploraImage
635
+ uploraImage: uploraImage,
636
+ uploraImageGallery: uploraImageGallery
607
637
  };
608
638
 
609
639
  async function getAppTemplates({ options, resolve, nuxt, detectedComponents: contextDetectedComponents }) {
@@ -16,8 +16,8 @@
16
16
  v-bind="forwardedEditor"
17
17
  class="min-h-screen"
18
18
  content-type="markdown"
19
- placeholder="Введите '/' для выбора блока..."
20
19
  autofocus
20
+ :placeholder="{ placeholder: '\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442...', includeChildren: true }"
21
21
  :extensions="extensions"
22
22
  :handlers="customHandlers"
23
23
  >
@@ -64,7 +64,7 @@
64
64
  :editor="editor"
65
65
  :items="bubbleToolbarItems"
66
66
  :should-show="({ view, state }) => {
67
- if (editor.isActive('uplora-image')) {
67
+ if (editor.isActive('uplora-image') || editor.isActive('uplora-image-gallery')) {
68
68
  return false;
69
69
  }
70
70
  const { selection } = state;
@@ -92,7 +92,12 @@ import { computed } from "vue";
92
92
  import { useEditorDragHandle } from "../composables/useEditorDragHandle";
93
93
  import { useEditorSuggestions } from "../composables/useEditorSuggestions";
94
94
  import { useEditorToolbar } from "../composables/useEditorToolbar";
95
+ import { Caution as CautionExtension, CautionHandlers } from "../editor/caution/EditorCaution";
96
+ import { Note as NoteExtension, NoteHandlers } from "../editor/note/EditorNote";
97
+ import { Tip as TipExtension, TipHandlers } from "../editor/tip/EditorTip";
98
+ import { UploraImageGallery as UploraImageGalleryExtension, UploraImageGalleryHandlers } from "../editor/uplora-image-gallery/EditorUploraImageGallery";
95
99
  import { UploraImage as UploraImageExtension, UploraImageHandlers } from "../editor/uplora-image/EditorUploraImage";
100
+ import { Warning as WarningExtension, WarningHandlers } from "../editor/warning/EditorWarning";
96
101
  import CEditorLinkPopover from "./EditorLinkPopover.vue";
97
102
  </script>
98
103
 
@@ -143,11 +148,21 @@ const forwardedTextarea = useForwardPropsEmits(reactivePick(props, "modelValue")
143
148
  const appConfig = useAppConfig();
144
149
  const extensions = computed(() => [
145
150
  UploraImageExtension,
151
+ UploraImageGalleryExtension,
152
+ NoteExtension,
153
+ TipExtension,
154
+ WarningExtension,
155
+ CautionExtension,
146
156
  ...props.extensions || []
147
157
  ]);
148
158
  const customHandlers = computed(() => ({
149
159
  ...props.customHandlers || {},
150
- ...UploraImageHandlers
160
+ ...UploraImageHandlers,
161
+ ...UploraImageGalleryHandlers,
162
+ ...NoteHandlers,
163
+ ...TipHandlers,
164
+ ...WarningHandlers,
165
+ ...CautionHandlers
151
166
  }));
152
167
  const appendToBody = false ? () => document.body : void 0;
153
168
  const { items: suggestionItems } = useEditorSuggestions(customHandlers);
@@ -29,7 +29,7 @@
29
29
  :state="state"
30
30
  :schema="schema"
31
31
  :class="ui.form({ class: props.ui?.form })"
32
- :validate-on="['change']"
32
+ :validate-on="['input']"
33
33
  @submit="submitHandler"
34
34
  >
35
35
  <div :class="ui.body({ class: props.ui?.body })">
@@ -10,13 +10,13 @@ export interface FormSeoProps {
10
10
  declare const _default: typeof __VLS_export;
11
11
  export default _default;
12
12
  declare const __VLS_export: import("vue").DefineComponent<FormSeoProps & {
13
- modelValue?: SEO;
13
+ modelValue?: SEO | null;
14
14
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
- "update:modelValue": (value: SEO) => any;
15
+ "update:modelValue": (value: SEO | null | undefined) => any;
16
16
  }, string, import("vue").PublicProps, Readonly<FormSeoProps & {
17
- modelValue?: SEO;
17
+ modelValue?: SEO | null;
18
18
  }> & Readonly<{
19
- "onUpdate:modelValue"?: ((value: SEO) => any) | undefined;
19
+ "onUpdate:modelValue"?: ((value: SEO | null | undefined) => any) | undefined;
20
20
  }>, {
21
21
  name: string;
22
22
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -3,16 +3,21 @@
3
3
  :name="name"
4
4
  :schema="schema"
5
5
  :class="ui.root({ class: [props.ui?.root, props.class] })"
6
- :validate-on="['change']"
6
+ :validate-on="['input']"
7
7
  nested
8
8
  >
9
9
  <UFormField
10
10
  name="title"
11
11
  label="Заголовок"
12
- :hint="`${model.title.length}/60`"
12
+ :hint="`${state.title.length}/60`"
13
13
  :ui="{ hint: 'text-xs' }"
14
14
  >
15
- <UInput v-model="model.title" placeholder="Введите заголовок..." class="w-full" />
15
+ <UInput
16
+ :model-value="state.title"
17
+ placeholder="Введите заголовок..."
18
+ class="w-full"
19
+ @update:model-value="state = { ...state, title: $event }"
20
+ />
16
21
 
17
22
  <template #help>
18
23
  <p class="text-xs mb-2">
@@ -29,14 +34,15 @@
29
34
  <UFormField
30
35
  name="description"
31
36
  label="Описание"
32
- :hint="`${model.description.length}/160`"
37
+ :hint="`${state.description.length}/160`"
33
38
  :ui="{ hint: 'text-xs' }"
34
39
  >
35
40
  <UTextarea
36
- v-model="model.description"
41
+ :model-value="state.description"
37
42
  placeholder="Введите описание..."
38
43
  class="w-full"
39
44
  autoresize
45
+ @update:model-value="state = { ...state, description: $event }"
40
46
  />
41
47
 
42
48
  <template #help>
@@ -68,17 +74,24 @@ const props = defineProps({
68
74
  name: { type: String, required: false, default: "seo" },
69
75
  ui: { type: null, required: false }
70
76
  });
71
- const model = defineModel({ type: Object, ...{
72
- default: () => ({
73
- title: "",
74
- description: ""
75
- })
76
- } });
77
+ const model = defineModel({ type: [Object, null] });
78
+ const state = computed({
79
+ get: () => {
80
+ return model.value ?? {
81
+ title: "",
82
+ description: ""
83
+ };
84
+ },
85
+ set: (value) => {
86
+ const isEmpty = Object.values(value).every((value2) => !value2);
87
+ model.value = isEmpty ? null : value;
88
+ }
89
+ });
77
90
  const appConfig = useAppConfig();
78
91
  const schema = z.object({
79
- title: z.string().min(1),
80
- description: z.string().min(1)
81
- });
82
- const { title, description } = useSeoStats(model);
92
+ title: z.string(),
93
+ description: z.string()
94
+ }).nullable();
95
+ const { title, description } = useSeoStats(() => state.value);
83
96
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formSeo || {} })());
84
97
  </script>
@@ -10,13 +10,13 @@ export interface FormSeoProps {
10
10
  declare const _default: typeof __VLS_export;
11
11
  export default _default;
12
12
  declare const __VLS_export: import("vue").DefineComponent<FormSeoProps & {
13
- modelValue?: SEO;
13
+ modelValue?: SEO | null;
14
14
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
- "update:modelValue": (value: SEO) => any;
15
+ "update:modelValue": (value: SEO | null | undefined) => any;
16
16
  }, string, import("vue").PublicProps, Readonly<FormSeoProps & {
17
- modelValue?: SEO;
17
+ modelValue?: SEO | null;
18
18
  }> & Readonly<{
19
- "onUpdate:modelValue"?: ((value: SEO) => any) | undefined;
19
+ "onUpdate:modelValue"?: ((value: SEO | null | undefined) => any) | undefined;
20
20
  }>, {
21
21
  name: string;
22
22
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,8 +1,9 @@
1
1
  <template>
2
2
  <UForm
3
+ ref="formRef"
3
4
  :schema="schema"
4
5
  :class="ui.root({ class: [props.ui?.root, props.class] })"
5
- :validate-on="['change']"
6
+ :validate-on="['input']"
6
7
  nested
7
8
  >
8
9
  <UFormField
@@ -45,7 +46,7 @@
45
46
  <script>
46
47
  import theme from "#build/cms/form-slug";
47
48
  import { useAppConfig } from "#imports";
48
- import { computed, ref, watch } from "vue";
49
+ import { computed, ref, useTemplateRef, watch } from "vue";
49
50
  import { z } from "zod";
50
51
  import { tv } from "../tv";
51
52
  import { slugify } from "../utils/slugify";
@@ -69,6 +70,7 @@ const schema = z.object({
69
70
  title: z.string().min(1),
70
71
  slug: z.string().min(1)
71
72
  });
73
+ const formRef = useTemplateRef("formRef");
72
74
  const isRegenerate = ref(props.regenerate);
73
75
  watch(title, () => {
74
76
  if (!isRegenerate.value) {
@@ -76,5 +78,10 @@ watch(title, () => {
76
78
  }
77
79
  slug.value = slugify(title.value);
78
80
  });
81
+ watch(slug, () => {
82
+ if (formRef.value?.getErrors("slug")) {
83
+ formRef.value?.clear("slug");
84
+ }
85
+ });
79
86
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formSlug || {} })());
80
87
  </script>
@@ -1,16 +1,13 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { ComponentConfig } from '../types';
3
+ import type { UploraImageProps } from './UploraImage.vue';
3
4
  import theme from '#build/cms/form-uplora-image';
4
5
  type FormUploraImage = ComponentConfig<typeof theme, AppConfig, 'formUploraImage'>;
5
- export interface FormUploraImageModelValue {
6
+ export interface FormUploraImageModelValue extends Pick<UploraImageProps, 'alt' | 'lqip' | 'color' | 'aspect' | 'width' | 'height'> {
6
7
  image?: string;
7
- alt?: string;
8
- color?: string;
9
- lqip?: string;
10
8
  }
11
9
  export interface FormUploraImageProps {
12
10
  showExtensions?: boolean;
13
- id?: string;
14
11
  name?: string;
15
12
  label?: string;
16
13
  disabled?: boolean;
@@ -26,18 +23,19 @@ export interface FormUploraImageEmits {
26
23
  declare const _default: typeof __VLS_export;
27
24
  export default _default;
28
25
  declare const __VLS_export: import("vue").DefineComponent<FormUploraImageProps & {
29
- modelValue?: FormUploraImageModelValue;
26
+ modelValue?: FormUploraImageModelValue | null;
30
27
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
31
28
  delete: () => any;
32
- "update:modelValue": (value: FormUploraImageModelValue) => any;
29
+ "update:modelValue": (value: FormUploraImageModelValue | null | undefined) => any;
33
30
  upload: (args_0: FormUploraImageModelValue) => any;
34
31
  }, string, import("vue").PublicProps, Readonly<FormUploraImageProps & {
35
- modelValue?: FormUploraImageModelValue;
32
+ modelValue?: FormUploraImageModelValue | null;
36
33
  }> & Readonly<{
37
34
  onDelete?: (() => any) | undefined;
38
- "onUpdate:modelValue"?: ((value: FormUploraImageModelValue) => any) | undefined;
35
+ "onUpdate:modelValue"?: ((value: FormUploraImageModelValue | null | undefined) => any) | undefined;
39
36
  onUpload?: ((args_0: FormUploraImageModelValue) => any) | undefined;
40
37
  }>, {
38
+ name: string;
41
39
  showExtensions: boolean;
42
40
  nested: boolean;
43
41
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -3,22 +3,39 @@
3
3
  :name="name"
4
4
  :schema="schema"
5
5
  :class="ui.root({ class: [props.ui?.root, props.class] })"
6
- :validate-on="['change']"
6
+ :validate-on="['input']"
7
7
  :nested="nested"
8
8
  >
9
9
  <UFormField name="image" :label="label">
10
10
  <div :class="ui.base({ class: [props.ui?.base] })">
11
- <template v-if="modelValue.image">
12
- <UPopover :content="{ align: 'center', side: 'top', sideOffset: 8 }" :ui="{ content: 'p-1' }">
11
+ <template v-if="state.image">
12
+ <UPopover
13
+ :content="{ align: 'center', side: 'top', sideOffset: 8 }"
14
+ :ui="{ content: 'p-1' }"
15
+ mode="click"
16
+ >
13
17
  <CUploraImage
14
- :image="modelValue.image"
15
- :alt="modelValue.alt"
16
- :color="modelValue.color"
17
- :lqip="modelValue.lqip"
18
+ :image="state.image"
19
+ :alt="state.alt"
20
+ :color="state.color"
21
+ :lqip="state.lqip"
22
+ :aspect="state.aspect"
23
+ :width="state.width"
24
+ :height="state.height"
18
25
  />
19
26
 
20
27
  <template #content>
21
28
  <div class="flex items-center gap-0.5">
29
+ <USelect
30
+ variant="ghost"
31
+ color="neutral"
32
+ size="sm"
33
+ :items="aspectItems"
34
+ :model-value="state.aspect"
35
+ default-value="horizontal"
36
+ @update:model-value="state = { ...state, aspect: $event === 'horizontal' ? void 0 : $event }"
37
+ />
38
+
22
39
  <UButton
23
40
  color="neutral"
24
41
  size="sm"
@@ -33,13 +50,13 @@
33
50
  variant="ghost"
34
51
  label=""
35
52
  :icon="appConfig.ui.icons.trash"
36
- @confirm="deleteExecute(modelValue.image)"
53
+ @confirm="deleteExecute(state.image)"
37
54
  />
38
55
  </div>
39
56
  </template>
40
57
  </UPopover>
41
58
  </template>
42
- <div v-else :id="id" :class="ui.uploader({ class: [props.ui?.uploader] })">
59
+ <div v-else :class="ui.uploader({ class: [props.ui?.uploader] })">
43
60
  <template v-if="uploadStatus === 'pending'">
44
61
  <UIcon :name="appConfig.ui.icons.loading" :class="ui.uploaderPendingIcon({ class: [props.ui?.uploaderPendingIcon] })" />
45
62
  </template>
@@ -48,7 +65,6 @@
48
65
  type="button"
49
66
  :class="ui.uploaderIdleButton({ class: [props.ui?.uploaderIdleButton] })"
50
67
  :disabled="disabled"
51
- v-bind="ariaAttrs"
52
68
  @click="open"
53
69
  >
54
70
  <UAvatar :icon="appConfig.ui.icons.image" size="lg" />
@@ -85,12 +101,12 @@
85
101
  </UFormField>
86
102
  <UFormField name="alt">
87
103
  <UInput
88
- v-model="modelValue.alt"
89
104
  placeholder="Введите описание..."
90
105
  variant="none"
91
106
  size="xs"
107
+ :model-value="state.alt"
92
108
  :ui="{ base: 'text-center text-toned' }"
93
- @update:model-value="onAltInput"
109
+ @update:model-value="state = { ...state, alt: $event || void 0 }"
94
110
  />
95
111
  </UFormField>
96
112
  </UForm>
@@ -98,9 +114,9 @@
98
114
 
99
115
  <script>
100
116
  import theme from "#build/cms/form-uplora-image";
101
- import { useAppConfig, useFormField, useUploraDelete, useUploraUpload } from "#imports";
117
+ import { useAppConfig, useUploraDelete, useUploraUpload } from "#imports";
102
118
  import { imagesExtensions } from "@uplora/formats";
103
- import { computed, useId } from "vue";
119
+ import { computed } from "vue";
104
120
  import { z } from "zod";
105
121
  import { tv } from "../tv";
106
122
  import CButtonDeleteConfirm from "./ButtonDeleteConfirm.vue";
@@ -109,8 +125,7 @@ import CButtonDeleteConfirm from "./ButtonDeleteConfirm.vue";
109
125
  <script setup>
110
126
  const props = defineProps({
111
127
  showExtensions: { type: Boolean, required: false, default: true },
112
- id: { type: String, required: false },
113
- name: { type: String, required: false },
128
+ name: { type: String, required: false, default: "image" },
114
129
  label: { type: String, required: false },
115
130
  disabled: { type: Boolean, required: false },
116
131
  as: { type: null, required: false },
@@ -119,63 +134,73 @@ const props = defineProps({
119
134
  ui: { type: null, required: false }
120
135
  });
121
136
  const emit = defineEmits(["upload", "delete"]);
122
- const modelValue = defineModel({ type: Object, ...{
123
- default: () => ({
124
- image: void 0,
125
- alt: void 0,
126
- lqip: void 0,
127
- color: void 0
128
- })
129
- } });
137
+ const model = defineModel({ type: [Object, null] });
138
+ const state = computed({
139
+ get: () => {
140
+ return model.value ?? {
141
+ image: void 0,
142
+ width: void 0,
143
+ height: void 0,
144
+ alt: void 0,
145
+ lqip: void 0,
146
+ color: void 0,
147
+ aspect: void 0
148
+ };
149
+ },
150
+ set: (value) => {
151
+ const isEmpty = Object.values(value).every((value2) => !value2);
152
+ model.value = isEmpty ? null : value;
153
+ }
154
+ });
130
155
  const appConfig = useAppConfig();
131
156
  const schema = z.object({
132
157
  image: z.string().min(1),
133
158
  alt: z.string().min(1),
159
+ width: z.number(),
160
+ height: z.number(),
134
161
  lqip: z.string().min(1).optional(),
135
- color: z.string().min(1).optional()
136
- });
162
+ color: z.string().min(1).optional(),
163
+ aspect: z.union([z.literal("horizontal"), z.literal("vertical"), z.literal("dynamic")]).optional()
164
+ }).nullable();
137
165
  const extensions = computed(() => imagesExtensions.filter((extension) => extension !== "jpeg").join(", "));
138
- const { id: _id, disabled, emitFormChange, emitFormInput, ariaAttrs } = useFormField(props);
139
- const id = _id.value ?? useId();
166
+ const aspectItems = [
167
+ { value: "horizontal", label: "\u0413\u043E\u0440\u0438\u0437\u043E\u043D\u0442\u0430\u043B\u044C\u043D\u044B\u0439" },
168
+ { value: "vertical", label: "\u0412\u0435\u0440\u0442\u0438\u043A\u0430\u043B\u044C\u043D\u044B\u0439" },
169
+ { value: "dynamic", label: "\u0414\u0438\u043D\u0430\u043C\u0438\u0447\u0435\u0441\u043A\u0438\u0439" }
170
+ ];
140
171
  const { open, execute: uploadExecute, status: uploadStatus, reset: resetUpload, onUploaded } = useUploraUpload({
141
172
  accept: "image/*"
142
173
  });
143
174
  const { execute: deleteExecute, onDeleted } = useUploraDelete();
144
- function onAltInput(alt) {
145
- modelValue.value = {
146
- ...modelValue.value,
147
- alt: alt || void 0
148
- };
149
- emitFormChange();
150
- emitFormInput();
151
- }
152
175
  function resetState() {
153
- modelValue.value = {
176
+ resetUpload();
177
+ state.value = {
154
178
  image: void 0,
179
+ width: void 0,
180
+ height: void 0,
155
181
  alt: void 0,
156
182
  lqip: void 0,
157
- color: void 0
183
+ color: void 0,
184
+ aspect: void 0
158
185
  };
159
- emitFormChange();
160
- emitFormInput();
161
186
  }
162
187
  onUploaded((file) => {
163
- modelValue.value = {
164
- ...modelValue.value,
188
+ state.value = {
165
189
  image: file.id,
190
+ width: file.width,
191
+ height: file.height,
192
+ alt: void 0,
166
193
  lqip: file.lqip,
167
- color: file.color
194
+ color: file.color,
195
+ aspect: void 0
168
196
  };
169
- emit("upload", modelValue.value);
170
- emitFormChange();
171
- emitFormInput();
197
+ emit("upload", state.value);
172
198
  });
173
199
  onDeleted(() => {
174
- resetUpload();
175
200
  resetState();
176
201
  emit("delete");
177
202
  });
178
203
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formUploraImage || {} })({
179
- disabled: disabled.value
204
+ disabled: props.disabled
180
205
  }));
181
206
  </script>
@@ -1,16 +1,13 @@
1
1
  import type { AppConfig } from '@nuxt/schema';
2
2
  import type { ComponentConfig } from '../types';
3
+ import type { UploraImageProps } from './UploraImage.vue';
3
4
  import theme from '#build/cms/form-uplora-image';
4
5
  type FormUploraImage = ComponentConfig<typeof theme, AppConfig, 'formUploraImage'>;
5
- export interface FormUploraImageModelValue {
6
+ export interface FormUploraImageModelValue extends Pick<UploraImageProps, 'alt' | 'lqip' | 'color' | 'aspect' | 'width' | 'height'> {
6
7
  image?: string;
7
- alt?: string;
8
- color?: string;
9
- lqip?: string;
10
8
  }
11
9
  export interface FormUploraImageProps {
12
10
  showExtensions?: boolean;
13
- id?: string;
14
11
  name?: string;
15
12
  label?: string;
16
13
  disabled?: boolean;
@@ -26,18 +23,19 @@ export interface FormUploraImageEmits {
26
23
  declare const _default: typeof __VLS_export;
27
24
  export default _default;
28
25
  declare const __VLS_export: import("vue").DefineComponent<FormUploraImageProps & {
29
- modelValue?: FormUploraImageModelValue;
26
+ modelValue?: FormUploraImageModelValue | null;
30
27
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
31
28
  delete: () => any;
32
- "update:modelValue": (value: FormUploraImageModelValue) => any;
29
+ "update:modelValue": (value: FormUploraImageModelValue | null | undefined) => any;
33
30
  upload: (args_0: FormUploraImageModelValue) => any;
34
31
  }, string, import("vue").PublicProps, Readonly<FormUploraImageProps & {
35
- modelValue?: FormUploraImageModelValue;
32
+ modelValue?: FormUploraImageModelValue | null;
36
33
  }> & Readonly<{
37
34
  onDelete?: (() => any) | undefined;
38
- "onUpdate:modelValue"?: ((value: FormUploraImageModelValue) => any) | undefined;
35
+ "onUpdate:modelValue"?: ((value: FormUploraImageModelValue | null | undefined) => any) | undefined;
39
36
  onUpload?: ((args_0: FormUploraImageModelValue) => any) | undefined;
40
37
  }>, {
38
+ name: string;
41
39
  showExtensions: boolean;
42
40
  nested: boolean;
43
41
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -11,6 +11,9 @@ export interface UploraImageProps {
11
11
  sizes?: ImageSize[];
12
12
  lqip?: string;
13
13
  color?: string;
14
+ aspect?: 'horizontal' | 'vertical' | 'dynamic';
15
+ width?: number | string;
16
+ height?: number | string;
14
17
  loading?: 'lazy' | 'eager';
15
18
  preload?: boolean | {
16
19
  fetchPriority: 'auto' | 'high' | 'low';
@@ -34,4 +37,5 @@ declare const __VLS_export: import("vue").DefineComponent<UploraImageProps, {},
34
37
  onLoad?: ((args_0: Event) => any) | undefined;
35
38
  }>, {
36
39
  loading: "lazy" | "eager";
40
+ aspect: "horizontal" | "vertical" | "dynamic";
37
41
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;