diff --git a/src/components/04_product-service/BasicInfoProduct.vue b/src/components/04_product-service/BasicInfoProduct.vue
index 57999347..d4aed337 100644
--- a/src/components/04_product-service/BasicInfoProduct.vue
+++ b/src/components/04_product-service/BasicInfoProduct.vue
@@ -1,6 +1,7 @@
@@ -209,6 +212,7 @@ watch(
@update:model-value="
(v) => (typeof v === 'string' ? (detail = v) : '')
"
+ @drop="detailEditorImageDrop"
min-height="5rem"
class="q-mt-sm q-mb-xs"
:flat="!readonly"
diff --git a/src/components/04_product-service/BasicInformation.vue b/src/components/04_product-service/BasicInformation.vue
index 77a0c777..ba6fc5c1 100644
--- a/src/components/04_product-service/BasicInformation.vue
+++ b/src/components/04_product-service/BasicInformation.vue
@@ -1,4 +1,5 @@
@@ -88,18 +91,40 @@ defineProps<{
v-model="name"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
- (typeof v === 'string' ? (detail = v) : '')"
- />
+ stack-label
+ dense
+ >
+ (typeof v === 'string' ? (detail = v) : '')
+ "
+ @drop="detailEditorImageDrop"
+ min-height="5rem"
+ class="q-mt-sm q-mb-xs"
+ :flat="!readonly"
+ :readonly="readonly"
+ :toolbar-color="
+ readonly ? 'disabled' : $q.dark.isActive ? 'white' : ''
+ "
+ :toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
+ style="
+ cursor: auto;
+ color: var(--foreground);
+ border-color: var(--surface-3);
+ "
+ :style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
+ />
+
- (typeof v === 'string' ? (serviceDescription = v) : '')
- "
- />
+ stack-label
+ dense
+ >
+ (typeof v === 'string' ? (detail = v) : '')
+ "
+ @drop="detailEditorImageDrop"
+ min-height="5rem"
+ class="q-mt-sm q-mb-xs"
+ :flat="!readonly"
+ :readonly="readonly"
+ :toolbar-color="
+ readonly ? 'disabled' : $q.dark.isActive ? 'white' : ''
+ "
+ :toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
+ style="
+ cursor: auto;
+ color: var(--foreground);
+ border-color: var(--surface-3);
+ "
+ :style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
+ />
+
diff --git a/src/utils/ui.ts b/src/utils/ui.ts
index fccee5dc..d3dec4be 100644
--- a/src/utils/ui.ts
+++ b/src/utils/ui.ts
@@ -1,6 +1,8 @@
+import { ModelRef, Ref } from 'vue';
import { Dark } from 'quasar';
import { setLocale as setDateTimeLocale } from './datetime';
import { i18n } from 'src/boot/i18n';
+import { fileToBase64 } from './file';
export enum Theme {
Light = 'light',
@@ -75,3 +77,58 @@ export function setLang(lang: Lang): Lang {
return lang;
}
+
+/**
+ * This is for use with ContentEditable element and with q-editor
+ */
+export function createEditorImageDrop(ref: Ref | ModelRef) {
+ return async (e: DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const target = e.target;
+
+ if (!target || !(target instanceof HTMLElement)) return;
+
+ const items = e.dataTransfer?.items;
+ const promises: Promise[] = [];
+
+ if (!items) return;
+
+ for (let i = 0; i < items.length; i++) {
+ const file = items[i].getAsFile();
+ if (!file || file.type.indexOf('image') === -1) continue;
+ promises.push(fileToBase64(file));
+ }
+
+ const getCaret = () => {
+ if (target.isContentEditable || document.designMode === 'on') {
+ target.focus();
+ const range = document.getSelection()?.getRangeAt(0);
+ if (!range?.collapsed) return null;
+ const tmp = document.createTextNode('\0');
+ range.insertNode(tmp);
+ const pos = target.innerHTML.indexOf('\0');
+ tmp.parentNode?.removeChild(tmp);
+ return pos;
+ }
+ return null;
+ };
+
+ for (const base64 of await Promise.all(promises)) {
+ const image = document.createElement('img') as HTMLImageElement;
+ const caret = getCaret();
+
+ image.src = base64;
+
+ if (caret) {
+ ref.value =
+ ref.value.substring(0, caret) +
+ image.outerHTML +
+ ref.value.substring(caret, ref.value.length);
+ } else {
+ ref.value += image.outerHTML;
+ }
+ }
+ };
+}