diff --git a/app/src/interfaces/wysiwyg/index.ts b/app/src/interfaces/wysiwyg/index.ts
index 114f3b3f36..982549efdf 100644
--- a/app/src/interfaces/wysiwyg/index.ts
+++ b/app/src/interfaces/wysiwyg/index.ts
@@ -272,5 +272,15 @@ export default defineInterface(({ i18n }) => ({
},
},
},
+ {
+ field: 'imageToken',
+ name: i18n.t('interfaces.markdown.imageToken'),
+ type: 'string',
+ meta: {
+ note: i18n.t('interfaces.markdown.imageToken_label'),
+ width: 'full',
+ interface: 'text-input',
+ },
+ },
],
}));
diff --git a/app/src/interfaces/wysiwyg/tinymce-overrides.css b/app/src/interfaces/wysiwyg/tinymce-overrides.css
index 1771e23acc..590088ecbb 100644
--- a/app/src/interfaces/wysiwyg/tinymce-overrides.css
+++ b/app/src/interfaces/wysiwyg/tinymce-overrides.css
@@ -175,7 +175,11 @@ body.dark .tox .tox-toolbar__overflow {
/* Modal */
.tox .tox-dialog-wrap__backdrop {
- background-color: rgba(0, 0, 0, 0.75);
+ background-color: var(--v-overlay-color);
+}
+
+.tox.tox-tinymce-aux {
+ z-index: 400;
}
.tox .tox-dialog {
diff --git a/app/src/interfaces/wysiwyg/wysiwyg.vue b/app/src/interfaces/wysiwyg/wysiwyg.vue
index 3579ce3062..db29b5776e 100644
--- a/app/src/interfaces/wysiwyg/wysiwyg.vue
+++ b/app/src/interfaces/wysiwyg/wysiwyg.vue
@@ -8,6 +8,17 @@
@onFocusIn="setFocus(true)"
@onFocusOut="setFocus(false)"
/>
+
+
+ {{ $t('upload_from_device') }}
+
+
+
+
+ {{ $t('cancel') }}
+
+
+
@@ -37,6 +48,9 @@ import Editor from '@tinymce/tinymce-vue';
import getEditorStyles from './get-editor-styles';
+import { getPublicURL } from '@/utils/get-root-path';
+import { addTokenToURL } from '@/api';
+
type CustomFormat = {
title: string;
inline: string;
@@ -89,9 +103,16 @@ export default defineComponent({
type: Boolean,
default: true,
},
+ imageToken: {
+ type: String,
+ default: undefined,
+ },
},
setup(props, { emit }) {
const editorElement = ref(null);
+ const imageUploadHandler = ref(null);
+
+ const _imageDialogOpen = computed(() => !!imageUploadHandler.value);
const _value = computed({
get() {
@@ -131,11 +152,56 @@ export default defineComponent({
extended_valid_elements: 'audio[loop],source',
toolbar: toolbarString,
style_formats: styleFormats,
+ file_picker_types: 'image media',
+ file_picker_callback: setImageUploadHandler,
+ urlconverter_callback: urlConverter,
...(props.tinymceOverrides || {}),
};
});
- return { editorElement, editorOptions, _value, setFocus };
+ return {
+ editorElement,
+ editorOptions,
+ _value,
+ setFocus,
+ onImageUpload,
+ unsetImageUploadHandler,
+ _imageDialogOpen,
+ };
+
+ function onImageUpload(file: Record) {
+ if (imageUploadHandler.value) imageUploadHandler.value(file);
+ unsetImageUploadHandler();
+ }
+
+ function setImageUploadHandler(cb: CallableFunction, value: any, meta: Record) {
+ imageUploadHandler.value = (result: Record) => {
+ if (meta.filetype === 'image' && !/^image\//.test(result.type)) return;
+
+ const imageUrl = getPublicURL() + 'assets/' + result.id;
+
+ cb(imageUrl, {
+ alt: result.title,
+ title: result.title,
+ width: (result.width || '').toString(),
+ height: (result.height || '').toString(),
+ });
+ };
+ }
+
+ function urlConverter(url: string, node: string) {
+ if (url && props.imageToken && ['img', 'source', 'poster', 'audio'].includes(node)) {
+ const baseUrl = getPublicURL() + 'assets/';
+ if (url.includes(baseUrl)) {
+ url = addTokenToURL(url, props.imageToken);
+ }
+ }
+ return url;
+ }
+
+ function unsetImageUploadHandler() {
+ imageUploadHandler.value = null;
+ }
function setFocus(val: boolean) {
if (editorElement.value == null) return;