@finema/core 3.8.0 → 3.9.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 (43) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1 -1
  3. package/dist/runtime/components/FlexDeck/index.vue +79 -79
  4. package/dist/runtime/components/Form/FieldWrapper.vue +13 -13
  5. package/dist/runtime/components/Form/Fields.vue +13 -13
  6. package/dist/runtime/components/Form/InputCheckbox/index.vue +18 -18
  7. package/dist/runtime/components/Form/InputCheckboxGroup/index.vue +21 -21
  8. package/dist/runtime/components/Form/InputCurrency/index.vue +49 -49
  9. package/dist/runtime/components/Form/InputDateTime/index.vue +62 -62
  10. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +56 -56
  11. package/dist/runtime/components/Form/InputNumber/index.vue +20 -20
  12. package/dist/runtime/components/Form/InputSelect/index.vue +46 -46
  13. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +62 -62
  14. package/dist/runtime/components/Form/InputTags/index.vue +54 -54
  15. package/dist/runtime/components/Form/InputTextarea/index.vue +18 -18
  16. package/dist/runtime/components/Form/InputTime/index.vue +38 -38
  17. package/dist/runtime/components/Form/InputToggle/index.vue +17 -17
  18. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +40 -34
  19. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +50 -50
  20. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +50 -50
  21. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +50 -50
  22. package/dist/runtime/components/Form/InputWYSIWYG/EditorImagePasteExtension.d.ts +16 -0
  23. package/dist/runtime/components/Form/InputWYSIWYG/EditorImagePasteExtension.js +106 -0
  24. package/dist/runtime/components/Form/InputWYSIWYG/EditorImageUploadNode.vue +18 -18
  25. package/dist/runtime/components/Form/InputWYSIWYG/EditorLinkPopover.vue +65 -65
  26. package/dist/runtime/components/Form/InputWYSIWYG/README.md +96 -0
  27. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +3 -2
  28. package/dist/runtime/components/Form/fileState/EmptyState.vue +21 -21
  29. package/dist/runtime/components/Form/fileState/FailedState.vue +33 -33
  30. package/dist/runtime/components/Form/fileState/LoadingState.vue +24 -24
  31. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue +75 -75
  32. package/dist/runtime/components/Form/fileState/PreviewModal.vue +23 -23
  33. package/dist/runtime/components/Form/index.vue +5 -5
  34. package/dist/runtime/components/Image.vue +28 -28
  35. package/dist/runtime/components/Log/index.vue +17 -17
  36. package/dist/runtime/components/Table/ColumnDate.vue +1 -1
  37. package/dist/runtime/components/Table/ColumnDateTime.vue +1 -1
  38. package/dist/runtime/components/Table/ColumnImage.vue +4 -4
  39. package/dist/runtime/components/Table/ColumnText.vue +1 -1
  40. package/dist/runtime/components/Table/Pagination.vue +46 -46
  41. package/dist/runtime/components/Table/Simple.vue +16 -16
  42. package/dist/runtime/server/tsconfig.json +3 -3
  43. package/package.json +1 -1
@@ -1,21 +1,21 @@
1
1
  <template>
2
- <FieldWrapper
3
- v-bind="wrapperProps"
4
- label=""
5
- description=""
6
- >
7
- <Switch
8
- :model-value="value"
9
- :disabled="wrapperProps.disabled"
10
- :name="name"
11
- :ui="ui"
12
- :label="label"
13
- :description="description"
14
- :loading="loading"
15
- :loading-icon="loadingIcon"
16
- @update:modelValue="onChange"
17
- />
18
- </FieldWrapper>
2
+ <FieldWrapper
3
+ v-bind="wrapperProps"
4
+ label=""
5
+ description=""
6
+ >
7
+ <Switch
8
+ :model-value="value"
9
+ :disabled="wrapperProps.disabled"
10
+ :name="name"
11
+ :ui="ui"
12
+ :label="label"
13
+ :description="description"
14
+ :loading="loading"
15
+ :loading-icon="loadingIcon"
16
+ @update:modelValue="onChange"
17
+ />
18
+ </FieldWrapper>
19
19
  </template>
20
20
 
21
21
  <script setup>
@@ -1,34 +1,34 @@
1
1
  <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div
4
- ref="dropzoneRef"
5
- :class="theme.base()"
6
- >
7
- <div :class="theme.wrapper()">
8
- <!-- Empty State -->
9
- <EmptyState
10
- v-if="uploadState.isEmpty.value"
11
- :theme="theme"
12
- :select-file-label="selectFileLabel"
13
- :select-file-sub-label="selectFileSubLabel"
14
- :placeholder="placeholder"
15
- @open-file="uploadState.handleOpenFile"
16
- />
17
-
18
- <!-- Success State -->
19
- <SuccessState
20
- v-if="uploadState.isSuccess.value"
21
- :theme="theme"
22
- :value="value"
23
- :disabled="wrapperProps.disabled"
24
- :readonly="wrapperProps.readonly"
25
- @preview="uploadState.handlePreview"
26
- @download="handleDownloadFile"
27
- @delete="uploadState.handleDeleteFile"
28
- />
29
- </div>
30
- </div>
31
- </FieldWrapper>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="theme.base()"
6
+ >
7
+ <div :class="theme.wrapper()">
8
+ <!-- Empty State -->
9
+ <EmptyState
10
+ v-if="uploadState.isEmpty.value"
11
+ :theme="theme"
12
+ :select-file-label="selectFileLabel"
13
+ :select-file-sub-label="selectFileSubLabel"
14
+ :placeholder="placeholder"
15
+ @open-file="uploadState.handleOpenFile"
16
+ />
17
+
18
+ <!-- Success State -->
19
+ <SuccessState
20
+ v-if="uploadState.isSuccess.value"
21
+ :theme="theme"
22
+ :value="value"
23
+ :disabled="wrapperProps.disabled"
24
+ :readonly="wrapperProps.readonly"
25
+ @preview="uploadState.handlePreview"
26
+ @download="handleDownloadFile"
27
+ @delete="uploadState.handleDeleteFile"
28
+ />
29
+ </div>
30
+ </div>
31
+ </FieldWrapper>
32
32
  </template>
33
33
 
34
34
  <script setup>
@@ -94,12 +94,18 @@ const theme = computed(
94
94
  disabled: wrapperProps.value.disabled
95
95
  })
96
96
  );
97
- const handleDownloadFile = () => {
98
- if (value.value?.url && value.value?.name) {
97
+ const handleDownloadFile = async () => {
98
+ const fileValue = value.value;
99
+ if (fileValue instanceof File) {
100
+ const blobUrl = URL.createObjectURL(fileValue);
99
101
  const a = document.createElement("a");
100
- a.href = value.value.url;
101
- a.download = value.value.name;
102
+ a.href = blobUrl;
103
+ a.download = fileValue.name;
104
+ document.body.appendChild(a);
102
105
  a.click();
106
+ a.remove();
107
+ URL.revokeObjectURL(blobUrl);
108
+ return;
103
109
  }
104
110
  };
105
111
  </script>
@@ -1,54 +1,54 @@
1
1
  <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div
4
- ref="dropzoneRef"
5
- :class="theme.base()"
6
- >
7
- <div :class="theme.wrapper()">
8
- <!-- Empty State -->
9
- <EmptyState
10
- v-if="uploadState.isEmpty.value"
11
- :theme="theme"
12
- :select-file-label="selectFileLabel"
13
- :select-file-sub-label="selectFileSubLabel"
14
- :placeholder="placeholder"
15
- @open-file="uploadState.handleOpenFile"
16
- />
17
-
18
- <!-- Loading State -->
19
- <LoadingState
20
- v-if="uploadState.isUploading.value"
21
- :theme="theme"
22
- :selected-file="uploadState.selectedFile.value"
23
- :percent="uploadState.percent.value"
24
- :uploading-label="uploadingLabel"
25
- />
26
-
27
- <!-- Success State -->
28
- <SuccessState
29
- v-if="uploadState.isSuccess.value"
30
- :theme="theme"
31
- :value="value"
32
- :disabled="wrapperProps.disabled"
33
- :readonly="wrapperProps.readonly"
34
- @preview="uploadState.handlePreview"
35
- @download="handleDownloadFile"
36
- @delete="uploadState.handleDeleteFile"
37
- />
38
-
39
- <!-- Failed State -->
40
- <FailedState
41
- v-if="uploadState.isError.value"
42
- :theme="theme"
43
- :selected-file="uploadState.selectedFile.value"
44
- :upload-failed-label="uploadFailedLabel"
45
- :retry-label="retryLabel"
46
- @retry="uploadState.handleRetryUpload"
47
- @delete="uploadState.handleDeleteFile"
48
- />
49
- </div>
50
- </div>
51
- </FieldWrapper>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="theme.base()"
6
+ >
7
+ <div :class="theme.wrapper()">
8
+ <!-- Empty State -->
9
+ <EmptyState
10
+ v-if="uploadState.isEmpty.value"
11
+ :theme="theme"
12
+ :select-file-label="selectFileLabel"
13
+ :select-file-sub-label="selectFileSubLabel"
14
+ :placeholder="placeholder"
15
+ @open-file="uploadState.handleOpenFile"
16
+ />
17
+
18
+ <!-- Loading State -->
19
+ <LoadingState
20
+ v-if="uploadState.isUploading.value"
21
+ :theme="theme"
22
+ :selected-file="uploadState.selectedFile.value"
23
+ :percent="uploadState.percent.value"
24
+ :uploading-label="uploadingLabel"
25
+ />
26
+
27
+ <!-- Success State -->
28
+ <SuccessState
29
+ v-if="uploadState.isSuccess.value"
30
+ :theme="theme"
31
+ :value="value"
32
+ :disabled="wrapperProps.disabled"
33
+ :readonly="wrapperProps.readonly"
34
+ @preview="uploadState.handlePreview"
35
+ @download="handleDownloadFile"
36
+ @delete="uploadState.handleDeleteFile"
37
+ />
38
+
39
+ <!-- Failed State -->
40
+ <FailedState
41
+ v-if="uploadState.isError.value"
42
+ :theme="theme"
43
+ :selected-file="uploadState.selectedFile.value"
44
+ :upload-failed-label="uploadFailedLabel"
45
+ :retry-label="retryLabel"
46
+ @retry="uploadState.handleRetryUpload"
47
+ @delete="uploadState.handleDeleteFile"
48
+ />
49
+ </div>
50
+ </div>
51
+ </FieldWrapper>
52
52
  </template>
53
53
 
54
54
  <script setup>
@@ -1,54 +1,54 @@
1
1
  <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div
4
- ref="dropzoneRef"
5
- :class="theme.base()"
6
- >
7
- <div :class="theme.wrapper()">
8
- <!-- Empty State -->
9
- <EmptyState
10
- :theme="theme"
11
- :select-file-label="selectFileLabel"
12
- :select-file-sub-label="selectFileSubLabel"
13
- :placeholder="placeholder"
14
- @open-file="uploadState.handleOpenFile"
15
- />
16
- </div>
17
- </div>
18
- <!-- Multiple Files State -->
19
- <MultipleFilesState
20
- v-if="!uploadState.isEmpty.value"
21
- :theme="theme"
22
- :file-items="uploadState.fileItems.value"
23
- :disabled="wrapperProps.disabled"
24
- :readonly="wrapperProps.readonly"
25
- :upload-failed-label="uploadFailedLabel"
26
- :retry-label="retryLabel"
27
- @preview="uploadState.handlePreview"
28
- @download="handleDownloadFile"
29
- @delete="uploadState.handleDeleteFile"
30
- @retry="uploadState.handleRetryUpload"
31
- />
32
-
33
- <!-- Success State -->
34
- <div
35
- v-for="(item, index) in value"
36
- :key="index"
37
- :class="theme.multipleFilesWrapper()"
38
- >
39
- <div :class="theme.fileItemWrapper()">
40
- <SuccessState
41
- :theme="theme"
42
- :value="item"
43
- :disabled="wrapperProps.disabled"
44
- :readonly="wrapperProps.readonly"
45
- @preview="uploadState.handlePreview(item)"
46
- @download="handleDownloadFile(item)"
47
- @delete="handleDeleteFile(index, item)"
48
- />
49
- </div>
50
- </div>
51
- </FieldWrapper>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="theme.base()"
6
+ >
7
+ <div :class="theme.wrapper()">
8
+ <!-- Empty State -->
9
+ <EmptyState
10
+ :theme="theme"
11
+ :select-file-label="selectFileLabel"
12
+ :select-file-sub-label="selectFileSubLabel"
13
+ :placeholder="placeholder"
14
+ @open-file="uploadState.handleOpenFile"
15
+ />
16
+ </div>
17
+ </div>
18
+ <!-- Multiple Files State -->
19
+ <MultipleFilesState
20
+ v-if="!uploadState.isEmpty.value"
21
+ :theme="theme"
22
+ :file-items="uploadState.fileItems.value"
23
+ :disabled="wrapperProps.disabled"
24
+ :readonly="wrapperProps.readonly"
25
+ :upload-failed-label="uploadFailedLabel"
26
+ :retry-label="retryLabel"
27
+ @preview="uploadState.handlePreview"
28
+ @download="handleDownloadFile"
29
+ @delete="uploadState.handleDeleteFile"
30
+ @retry="uploadState.handleRetryUpload"
31
+ />
32
+
33
+ <!-- Success State -->
34
+ <div
35
+ v-for="(item, index) in value"
36
+ :key="index"
37
+ :class="theme.multipleFilesWrapper()"
38
+ >
39
+ <div :class="theme.fileItemWrapper()">
40
+ <SuccessState
41
+ :theme="theme"
42
+ :value="item"
43
+ :disabled="wrapperProps.disabled"
44
+ :readonly="wrapperProps.readonly"
45
+ @preview="uploadState.handlePreview(item)"
46
+ @download="handleDownloadFile(item)"
47
+ @delete="handleDeleteFile(index, item)"
48
+ />
49
+ </div>
50
+ </div>
51
+ </FieldWrapper>
52
52
  </template>
53
53
 
54
54
  <script setup>
@@ -1,54 +1,54 @@
1
1
  <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div
4
- ref="dropzoneRef"
5
- :class="theme.base()"
6
- >
7
- <div :class="theme.wrapper()">
8
- <!-- Empty State -->
9
- <EmptyState
10
- v-if="uploadState.isEmpty.value"
11
- :theme="theme"
12
- :select-file-label="selectFileLabel"
13
- :select-file-sub-label="selectFileSubLabel"
14
- :placeholder="placeholder"
15
- @open-file="uploadState.handleOpenFile"
16
- />
17
-
18
- <!-- Loading State -->
19
- <LoadingState
20
- v-if="uploadState.isUploading.value"
21
- :theme="theme"
22
- :selected-file="uploadState.selectedFile.value"
23
- :percent="uploadState.percent.value"
24
- :uploading-label="uploadingLabel"
25
- />
26
-
27
- <!-- Success State -->
28
- <SuccessState
29
- v-if="uploadState.isSuccess.value"
30
- :theme="theme"
31
- :value="value"
32
- :disabled="wrapperProps.disabled"
33
- :readonly="wrapperProps.readonly"
34
- @preview="uploadState.handlePreview"
35
- @download="handleDownloadFile"
36
- @delete="uploadState.handleDeleteFile"
37
- />
38
-
39
- <!-- Failed State -->
40
- <FailedState
41
- v-if="uploadState.isError.value"
42
- :theme="theme"
43
- :selected-file="uploadState.selectedFile.value"
44
- :upload-failed-label="uploadFailedLabel"
45
- :retry-label="retryLabel"
46
- @retry="uploadState.handleRetryUpload"
47
- @delete="uploadState.handleDeleteFile"
48
- />
49
- </div>
50
- </div>
51
- </FieldWrapper>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="theme.base()"
6
+ >
7
+ <div :class="theme.wrapper()">
8
+ <!-- Empty State -->
9
+ <EmptyState
10
+ v-if="uploadState.isEmpty.value"
11
+ :theme="theme"
12
+ :select-file-label="selectFileLabel"
13
+ :select-file-sub-label="selectFileSubLabel"
14
+ :placeholder="placeholder"
15
+ @open-file="uploadState.handleOpenFile"
16
+ />
17
+
18
+ <!-- Loading State -->
19
+ <LoadingState
20
+ v-if="uploadState.isUploading.value"
21
+ :theme="theme"
22
+ :selected-file="uploadState.selectedFile.value"
23
+ :percent="uploadState.percent.value"
24
+ :uploading-label="uploadingLabel"
25
+ />
26
+
27
+ <!-- Success State -->
28
+ <SuccessState
29
+ v-if="uploadState.isSuccess.value"
30
+ :theme="theme"
31
+ :value="value"
32
+ :disabled="wrapperProps.disabled"
33
+ :readonly="wrapperProps.readonly"
34
+ @preview="uploadState.handlePreview"
35
+ @download="handleDownloadFile"
36
+ @delete="uploadState.handleDeleteFile"
37
+ />
38
+
39
+ <!-- Failed State -->
40
+ <FailedState
41
+ v-if="uploadState.isError.value"
42
+ :theme="theme"
43
+ :selected-file="uploadState.selectedFile.value"
44
+ :upload-failed-label="uploadFailedLabel"
45
+ :retry-label="retryLabel"
46
+ @retry="uploadState.handleRetryUpload"
47
+ @delete="uploadState.handleDeleteFile"
48
+ />
49
+ </div>
50
+ </div>
51
+ </FieldWrapper>
52
52
  </template>
53
53
 
54
54
  <script setup>
@@ -0,0 +1,16 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import type { AxiosRequestConfig } from 'axios';
3
+ export interface ImagePasteOptions {
4
+ requestOptions?: Omit<AxiosRequestConfig, 'baseURL'> & {
5
+ baseURL: string;
6
+ };
7
+ uploadPathURL?: string;
8
+ bodyKey?: string;
9
+ responseURL?: string;
10
+ responsePath?: string;
11
+ responseName?: string;
12
+ responseSize?: string;
13
+ responseID?: string;
14
+ maxSize?: number;
15
+ }
16
+ export declare const ImagePaste: Extension<ImagePasteOptions, any>;
@@ -0,0 +1,106 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
3
+ import { useUploadLoader } from "#core/composables/useUpload";
4
+ import { _get } from "#core/utils/lodash";
5
+ export const ImagePaste = Extension.create({
6
+ name: "imagePaste",
7
+ addOptions() {
8
+ return {
9
+ bodyKey: "file",
10
+ responseURL: "url",
11
+ responsePath: "path",
12
+ responseName: "name",
13
+ responseSize: "size",
14
+ responseID: "id"
15
+ };
16
+ },
17
+ addProseMirrorPlugins() {
18
+ const options = this.options;
19
+ return [
20
+ new Plugin({
21
+ key: new PluginKey("imagePaste"),
22
+ props: {
23
+ handlePaste: async (view, event) => {
24
+ const items = Array.from(event.clipboardData?.items || []);
25
+ const imageItems = items.filter((item) => item.type.includes("image"));
26
+ if (imageItems.length === 0) {
27
+ return false;
28
+ }
29
+ event.preventDefault();
30
+ for (const item of imageItems) {
31
+ const file = item.getAsFile();
32
+ if (!file) continue;
33
+ if (options.maxSize && file.size > options.maxSize * 1024) {
34
+ console.warn(`Image size (${file.size} bytes) exceeds maximum allowed size (${options.maxSize} KB)`);
35
+ continue;
36
+ }
37
+ if (!options.requestOptions) {
38
+ console.warn("ImagePaste: requestOptions is not configured");
39
+ continue;
40
+ }
41
+ const {
42
+ schema
43
+ } = view.state;
44
+ const pos = view.state.selection.from;
45
+ if (schema.nodes.imageUpload) {
46
+ const transaction = view.state.tr.insert(pos, schema.nodes.imageUpload.create());
47
+ view.dispatch(transaction);
48
+ }
49
+ const request = {
50
+ requestOptions: options.requestOptions,
51
+ pathURL: options.uploadPathURL
52
+ };
53
+ const uploadLoader = useUploadLoader(request);
54
+ const formData = new FormData();
55
+ const bodyKey = options.bodyKey || "file";
56
+ formData.append(bodyKey, file);
57
+ await uploadLoader.run({
58
+ data: formData
59
+ });
60
+ if (uploadLoader.status.value.isSuccess && uploadLoader.data.value) {
61
+ const responseURL = options.responseURL || "url";
62
+ const url = _get(uploadLoader.data.value, responseURL);
63
+ if (!url) {
64
+ console.error("ImagePaste: Could not find URL in response", uploadLoader.data.value);
65
+ continue;
66
+ }
67
+ const currentState = view.state;
68
+ let placeholderPos = -1;
69
+ currentState.doc.descendants((node, nodePos) => {
70
+ if (node.type.name === "imageUpload" && placeholderPos === -1) {
71
+ placeholderPos = nodePos;
72
+ return false;
73
+ }
74
+ });
75
+ if (placeholderPos !== -1 && schema.nodes.image) {
76
+ const imageNode = schema.nodes.image.create({
77
+ src: url
78
+ });
79
+ let tr = currentState.tr.delete(placeholderPos, placeholderPos + 1).insert(placeholderPos, imageNode);
80
+ const resolvedPos = tr.doc.resolve(placeholderPos + imageNode.nodeSize);
81
+ tr = tr.setSelection(TextSelection.near(resolvedPos));
82
+ view.dispatch(tr);
83
+ }
84
+ } else if (uploadLoader.status.value.isError) {
85
+ console.error("ImagePaste: Upload failed", uploadLoader.status.value.errorData);
86
+ const currentState = view.state;
87
+ let placeholderPos = -1;
88
+ currentState.doc.descendants((node, nodePos) => {
89
+ if (node.type.name === "imageUpload" && placeholderPos === -1) {
90
+ placeholderPos = nodePos;
91
+ return false;
92
+ }
93
+ });
94
+ if (placeholderPos !== -1) {
95
+ const tr = currentState.tr.delete(placeholderPos, placeholderPos + 1);
96
+ view.dispatch(tr);
97
+ }
98
+ }
99
+ }
100
+ return true;
101
+ }
102
+ }
103
+ })
104
+ ];
105
+ }
106
+ });
@@ -1,22 +1,22 @@
1
1
  <template>
2
- <NodeViewWrapper>
3
- <FileUpload
4
- v-model="file"
5
- accept="image/*"
6
- label="Upload an image"
7
- description="SVG, PNG, JPG or GIF"
8
- :preview="false"
9
- class="min-h-48"
10
- >
11
- <template #leading>
12
- <Avatar
13
- :icon="loading ? 'i-lucide-loader-circle' : 'i-lucide-image'"
14
- size="xl"
15
- :ui="{ icon: [loading && 'animate-spin'] }"
16
- />
17
- </template>
18
- </FileUpload>
19
- </NodeViewWrapper>
2
+ <NodeViewWrapper>
3
+ <FileUpload
4
+ v-model="file"
5
+ accept="image/*"
6
+ label="Upload an image"
7
+ description="SVG, PNG, JPG or GIF"
8
+ :preview="false"
9
+ class="min-h-48"
10
+ >
11
+ <template #leading>
12
+ <Avatar
13
+ :icon="loading ? 'i-lucide-loader-circle' : 'i-lucide-image'"
14
+ size="xl"
15
+ :ui="{ icon: [loading && 'animate-spin'] }"
16
+ />
17
+ </template>
18
+ </FileUpload>
19
+ </NodeViewWrapper>
20
20
  </template>
21
21
 
22
22
  <script setup>