@hywax/cms 3.6.1 → 3.7.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 (65) 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/FormSeo.d.vue.ts +4 -4
  9. package/dist/runtime/components/FormSeo.vue +25 -13
  10. package/dist/runtime/components/FormSeo.vue.d.ts +4 -4
  11. package/dist/runtime/components/FormUploraImage.d.vue.ts +7 -9
  12. package/dist/runtime/components/FormUploraImage.vue +70 -46
  13. package/dist/runtime/components/FormUploraImage.vue.d.ts +7 -9
  14. package/dist/runtime/components/UploraImage.d.vue.ts +4 -0
  15. package/dist/runtime/components/UploraImage.vue +13 -2
  16. package/dist/runtime/components/UploraImage.vue.d.ts +4 -0
  17. package/dist/runtime/components/prose/UploraImage.d.vue.ts +4 -7
  18. package/dist/runtime/components/prose/UploraImage.vue +13 -7
  19. package/dist/runtime/components/prose/UploraImage.vue.d.ts +4 -7
  20. package/dist/runtime/components/prose/UploraImageGallery.d.vue.ts +19 -0
  21. package/dist/runtime/components/prose/UploraImageGallery.vue +24 -0
  22. package/dist/runtime/components/prose/UploraImageGallery.vue.d.ts +19 -0
  23. package/dist/runtime/composables/useEditorSuggestions.d.ts +29 -6
  24. package/dist/runtime/composables/useEditorSuggestions.js +7 -3
  25. package/dist/runtime/editor/caution/EditorCaution.d.ts +18 -0
  26. package/dist/runtime/editor/caution/EditorCaution.js +36 -0
  27. package/dist/runtime/editor/caution/EditorCautionNode.d.vue.ts +4 -0
  28. package/dist/runtime/editor/caution/EditorCautionNode.vue +22 -0
  29. package/dist/runtime/editor/caution/EditorCautionNode.vue.d.ts +4 -0
  30. package/dist/runtime/editor/note/EditorNote.d.ts +18 -0
  31. package/dist/runtime/editor/note/EditorNote.js +36 -0
  32. package/dist/runtime/editor/note/EditorNoteNode.d.vue.ts +4 -0
  33. package/dist/runtime/editor/note/EditorNoteNode.vue +22 -0
  34. package/dist/runtime/editor/note/EditorNoteNode.vue.d.ts +4 -0
  35. package/dist/runtime/editor/tip/EditorTip.d.ts +18 -0
  36. package/dist/runtime/editor/tip/EditorTip.js +36 -0
  37. package/dist/runtime/editor/tip/EditorTipNode.d.vue.ts +4 -0
  38. package/dist/runtime/editor/tip/EditorTipNode.vue +22 -0
  39. package/dist/runtime/editor/tip/EditorTipNode.vue.d.ts +4 -0
  40. package/dist/runtime/editor/uplora-image/EditorUploraImage.js +10 -4
  41. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.d.vue.ts +0 -5
  42. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.vue +2 -2
  43. package/dist/runtime/editor/uplora-image/EditorUploraImageNode.vue.d.ts +0 -5
  44. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGallery.d.ts +18 -0
  45. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGallery.js +71 -0
  46. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGalleryNode.d.vue.ts +4 -0
  47. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGalleryNode.vue +93 -0
  48. package/dist/runtime/editor/uplora-image-gallery/EditorUploraImageGalleryNode.vue.d.ts +4 -0
  49. package/dist/runtime/editor/utils/attributes.d.ts +2 -0
  50. package/dist/runtime/editor/{utils.js → utils/attributes.js} +0 -29
  51. package/dist/runtime/editor/utils/createAtomBlockMarkdownSpec.d.ts +10 -0
  52. package/dist/runtime/editor/utils/createAtomBlockMarkdownSpec.js +32 -0
  53. package/dist/runtime/editor/utils/createBlockMarkdownSpec.d.ts +12 -0
  54. package/dist/runtime/editor/utils/createBlockMarkdownSpec.js +99 -0
  55. package/dist/runtime/editor/utils/index.d.ts +3 -0
  56. package/dist/runtime/editor/utils/index.js +3 -0
  57. package/dist/runtime/editor/warning/EditorWarning.d.ts +18 -0
  58. package/dist/runtime/editor/warning/EditorWarning.js +36 -0
  59. package/dist/runtime/editor/warning/EditorWarningNode.d.vue.ts +4 -0
  60. package/dist/runtime/editor/warning/EditorWarningNode.vue +22 -0
  61. package/dist/runtime/editor/warning/EditorWarningNode.vue.d.ts +4 -0
  62. package/dist/runtime/server/utils/pagination.d.ts +4 -0
  63. package/dist/runtime/server/utils/pagination.js +2 -2
  64. package/package.json +1 -1
  65. 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
+ "columns": {
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.7.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.7.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
+ columns: {
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);
@@ -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>;
@@ -9,10 +9,15 @@
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,23 @@ 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
+ model.value = value;
87
+ }
88
+ });
77
89
  const appConfig = useAppConfig();
78
90
  const schema = z.object({
79
- title: z.string().min(1),
80
- description: z.string().min(1)
91
+ title: z.string(),
92
+ description: z.string()
81
93
  });
82
- const { title, description } = useSeoStats(model);
94
+ const { title, description } = useSeoStats(() => state.value);
83
95
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formSeo || {} })());
84
96
  </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,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>;
@@ -8,17 +8,34 @@
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,72 @@ 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
+ model.value = value;
152
+ }
153
+ });
130
154
  const appConfig = useAppConfig();
131
155
  const schema = z.object({
132
156
  image: z.string().min(1),
133
157
  alt: z.string().min(1),
158
+ width: z.number(),
159
+ height: z.number(),
134
160
  lqip: z.string().min(1).optional(),
135
- color: z.string().min(1).optional()
161
+ color: z.string().min(1).optional(),
162
+ aspect: z.union([z.literal("horizontal"), z.literal("vertical"), z.literal("dynamic")]).optional()
136
163
  });
137
164
  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();
165
+ const aspectItems = [
166
+ { value: "horizontal", label: "\u0413\u043E\u0440\u0438\u0437\u043E\u043D\u0442\u0430\u043B\u044C\u043D\u044B\u0439" },
167
+ { value: "vertical", label: "\u0412\u0435\u0440\u0442\u0438\u043A\u0430\u043B\u044C\u043D\u044B\u0439" },
168
+ { value: "dynamic", label: "\u0414\u0438\u043D\u0430\u043C\u0438\u0447\u0435\u0441\u043A\u0438\u0439" }
169
+ ];
140
170
  const { open, execute: uploadExecute, status: uploadStatus, reset: resetUpload, onUploaded } = useUploraUpload({
141
171
  accept: "image/*"
142
172
  });
143
173
  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
174
  function resetState() {
153
- modelValue.value = {
175
+ resetUpload();
176
+ state.value = {
154
177
  image: void 0,
178
+ width: void 0,
179
+ height: void 0,
155
180
  alt: void 0,
156
181
  lqip: void 0,
157
- color: void 0
182
+ color: void 0,
183
+ aspect: void 0
158
184
  };
159
- emitFormChange();
160
- emitFormInput();
161
185
  }
162
186
  onUploaded((file) => {
163
- modelValue.value = {
164
- ...modelValue.value,
187
+ state.value = {
165
188
  image: file.id,
189
+ width: file.width,
190
+ height: file.height,
191
+ alt: void 0,
166
192
  lqip: file.lqip,
167
- color: file.color
193
+ color: file.color,
194
+ aspect: void 0
168
195
  };
169
- emit("upload", modelValue.value);
170
- emitFormChange();
171
- emitFormInput();
196
+ emit("upload", state.value);
172
197
  });
173
198
  onDeleted(() => {
174
- resetUpload();
175
199
  resetState();
176
200
  emit("delete");
177
201
  });
178
202
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.formUploraImage || {} })({
179
- disabled: disabled.value
203
+ disabled: props.disabled
180
204
  }));
181
205
  </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>;
@@ -8,7 +8,7 @@
8
8
  backgroundImage: lqip ? `url(${lqip})` : void 0
9
9
  }"
10
10
  />
11
- <picture :class="ui.picture({ class: props.ui?.picture })">
11
+ <picture :class="ui.picture({ class: props.ui?.picture })" :style="{ aspectRatio }">
12
12
  <template v-if="sources.length">
13
13
  <source
14
14
  v-for="(source, key) in sources"
@@ -47,6 +47,9 @@ const props = defineProps({
47
47
  sizes: { type: Array, required: false },
48
48
  lqip: { type: String, required: false },
49
49
  color: { type: String, required: false },
50
+ aspect: { type: String, required: false, default: "horizontal" },
51
+ width: { type: [Number, String], required: false },
52
+ height: { type: [Number, String], required: false },
50
53
  loading: { type: String, required: false, default: "lazy" },
51
54
  preload: { type: [Boolean, Object], required: false },
52
55
  nonce: { type: String, required: false },
@@ -90,6 +93,12 @@ const imageAttrs = computed(() => ({
90
93
  ...props.imgAttrs || {},
91
94
  ...import.meta.server ? { onerror: "this.setAttribute('data-error', 1)" } : {}
92
95
  }));
96
+ const aspectRatio = computed(() => {
97
+ if (props.aspect === "dynamic" && props.width && props.height) {
98
+ return Number.parseInt(props.width) / Number(props.height);
99
+ }
100
+ return void 0;
101
+ });
93
102
  onMounted(() => {
94
103
  if (!imageRef.value) {
95
104
  return;
@@ -108,5 +117,7 @@ onMounted(() => {
108
117
  emit("error", event);
109
118
  };
110
119
  });
111
- const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.uploraImage || {} })());
120
+ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.uploraImage || {} })({
121
+ aspect: props.aspect
122
+ }));
112
123
  </script>
@@ -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>;