Gh 3795 editor filemanager (#5273)

* Editor: Use constants to represent options for tiny editor

* Editor: Use new base editor component in pages

* Editor: Use new base editor in course intro

* Adapt Form to vue 3 setup script for consistency

* Editor: Use new vase editor in documents

* Editor: Use new vase editor in messages

* Editor: Use new vase editor in message reply

* Editor: use new base editor in group discusssions

* Editor: use new base editor in group discusssion topics

* Editor: use new base editor in terms and condition edition

* Editor: remove duplicated editor

* Clean code and format with prettier

* Editor: Use new base editor component in pages
pull/5293/head
Daniel 2 years ago committed by GitHub
parent a3993697b5
commit 3a3cf9e0fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 52
      assets/vue/components/basecomponents/BaseEditor.vue
  2. 293
      assets/vue/components/basecomponents/BaseTinyEditor.vue
  3. 6
      assets/vue/components/basecomponents/TinyEditorOptions.js
  4. 180
      assets/vue/components/ctoolintro/Form.vue
  5. 37
      assets/vue/components/documents/FormNewDocument.vue
  6. 104
      assets/vue/components/page/Form.vue
  7. 4
      assets/vue/components/usergroup/GroupDiscussionTopics.vue
  8. 4
      assets/vue/components/usergroup/GroupDiscussions.vue
  9. 23
      assets/vue/views/message/MessageCreate.vue
  10. 23
      assets/vue/views/message/MessageReply.vue
  11. 7
      assets/vue/views/page/EditorDemo.vue
  12. 26
      assets/vue/views/terms/TermsEdit.vue

@ -1,52 +0,0 @@
<template>
<div class="base-editor">
<label v-if="title" :for="editorId">{{ title }}</label>
<TinyEditor
:id="editorId"
:model-value="modelValue"
:init="editorConfig"
:required="required"
@update:model-value="updateValue"
@input="updateValue"
/>
<p v-if="helpText" class="help-text">{{ helpText }}</p>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const props = defineProps({
editorId: String,
modelValue: String,
required: Boolean,
editorConfig: Object,
title: String,
helpText: String
})
const emit = defineEmits(['update:modelValue'])
const updateValue = (value) => {
emit('update:modelValue', value)
document.getElementById(props.editorId).value = value
}
const defaultEditorConfig = {
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 280,
toolbar_mode: 'sliding',
autosave_ask_before_unload: true,
plugins: [
'fullpage advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount emoticons',
],
toolbar: 'undo redo | bold italic underline strikethrough | ...',
}
const editorConfig = computed(() => ({
...defaultEditorConfig,
...props.editorConfig
}))
function browser(callback, value, meta) {
}
</script>

@ -1,6 +1,11 @@
<template> <template>
<div class="base-tiny-editor"> <div class="base-tiny-editor">
<label v-if="title" :for="editorId">{{ title }}</label> <label
v-if="title"
:for="editorId"
>
{{ title }}
</label>
<TinyEditor <TinyEditor
:id="editorId" :id="editorId"
:model-value="modelValue" :model-value="modelValue"
@ -9,30 +14,64 @@
@update:model-value="updateValue" @update:model-value="updateValue"
@input="updateValue" @input="updateValue"
/> />
<p v-if="helpText" class="help-text">{{ helpText }}</p> <p
v-if="helpText"
class="help-text"
>
{{ helpText }}
</p>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, watch } from 'vue' import { computed, ref } from "vue"
import TinyEditor from '@tinymce/tinymce-vue' import TinyEditor from "@tinymce/tinymce-vue"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import { useCidReqStore } from "../../store/cidReq" import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from "pinia" import { storeToRefs } from "pinia"
import { useStore } from "vuex" import { useStore } from "vuex"
import { TINYEDITOR_MODE_DOCUMENTS, TINYEDITOR_MODE_PERSONAL_FILES, TINYEDITOR_MODES } from "./TinyEditorOptions"
const props = defineProps({ const props = defineProps({
editorId: String, editorId: {
modelValue: String, type: String,
required: Boolean, required: true,
editorConfig: Object, },
title: String, modelValue: {
helpText: String, type: String,
mode: { type: String, default: 'personal_files' }, required: true,
useFileManager: { type: Boolean, default: false } },
required: {
type: Boolean,
default: false,
},
title: {
type: String,
default: "",
},
editorConfig: {
type: Object,
default: () => {},
},
// A helper text shown below editor
helpText: {
type: String,
default: "",
},
// if true the Chamilo inner file manager will be shown
// if false the system file picker will be shown
useFileManager: {
type: Boolean,
default: false,
},
// change mode when useFileManager=True
mode: {
type: String,
default: TINYEDITOR_MODE_PERSONAL_FILES,
validator: (value) => TINYEDITOR_MODES.includes(value),
},
}) })
const emit = defineEmits(["update:modelValue"])
const emit = defineEmits(['update:modelValue'])
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const parentResourceNodeId = ref(0) const parentResourceNodeId = ref(0)
@ -47,115 +86,183 @@ if (route.params.node) {
} }
const updateValue = (value) => { const updateValue = (value) => {
emit('update:modelValue', value) emit("update:modelValue", value)
} }
const toolbarUndo = "undo redo"
const toolbarFormatText = "bold italic underline strikethrough"
const toolbarInsertMedia = "image media template link"
const toolbarFontConfig = "fontselect fontsizeselect formatselect"
const toolbarAlign = "alignleft aligncenter alignright alignjustify"
const toolbarIndent = "outdent indent"
const toolbarList = "numlist bullist"
const toolbarColor = "forecolor backcolor removeformat"
const toolbarPageBreak = "pagebreak"
const toolbarSpecialSymbols = "charmap emoticons"
const toolbarOther = "fullscreen preview save print"
const toolbarCode = "code codesample"
const toolbarTextDirection = "ltr rtl"
const defaultEditorConfig = { const defaultEditorConfig = {
skin_url: '/build/libs/tinymce/skins/ui/oxide', skin_url: "/build/libs/tinymce/skins/ui/oxide",
content_css: '/build/libs/tinymce/skins/content/default/content.css', content_css: "/build/libs/tinymce/skins/content/default/content.css",
branding: false, branding: false,
relative_urls: false, relative_urls: false,
height: 280, height: 500,
toolbar_mode: 'sliding', toolbar_mode: "sliding",
autosave_ask_before_unload: true, autosave_ask_before_unload: true,
plugins: [ plugins: [
'advlist autolink lists link image charmap print preview anchor', "advlist",
'searchreplace visualblocks code fullscreen', "anchor",
'insertdatetime media table paste wordcount emoticons', "autolink",
"charmap",
"code",
"codesample",
"directionality",
"fullpage",
"fullscreen",
"emoticons",
"image",
"insertdatetime",
"link",
"lists",
"media",
"paste",
"preview",
"print",
"pagebreak",
"save",
"searchreplace",
"table",
"template",
"visualblocks",
"wordcount",
], ],
toolbar: 'undo redo | bold italic underline strikethrough | ...', toolbar:
file_picker_callback: filePickerCallback toolbarUndo +
" | " +
toolbarFormatText +
" | " +
toolbarInsertMedia +
" | " +
toolbarFontConfig +
" | " +
toolbarAlign +
" | " +
toolbarIndent +
" | " +
toolbarList +
" | " +
toolbarColor +
" | " +
toolbarPageBreak +
" | " +
toolbarSpecialSymbols +
" | " +
toolbarOther +
" | " +
toolbarCode +
" | " +
toolbarTextDirection,
file_picker_callback: filePickerCallback,
} }
const editorConfig = computed(() => ({ const editorConfig = computed(() => ({
...defaultEditorConfig, ...defaultEditorConfig,
...props.editorConfig ...props.editorConfig,
})) }))
function filePickerCallback(callback, value, meta) { async function filePickerCallback(callback, value, meta) {
if (!props.useFileManager) { if (!props.useFileManager) {
const input = document.createElement('input'); const input = document.createElement("input")
input.setAttribute('type', 'file'); input.setAttribute("type", "file")
input.style.display = 'none'; input.style.display = "none"
input.onchange = inputFileHandler(callback, input)
input.onchange = () => { document.body.appendChild(input)
const file = input.files[0]; input.click()
const title = file.name;
const comment = '';
const fileType = 'file';
const resourceLinkList = [];
const formData = new FormData();
formData.append('uploadFile', file);
formData.append('title', title);
formData.append('comment', comment);
formData.append('parentResourceNodeId', parentResourceNodeId.value);
formData.append('filetype', fileType);
formData.append('resourceLinkList', resourceLinkList);
fetch('/file-manager/upload-image', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
if (data.location) {
callback(data.location);
} else {
console.error('Failed to upload file');
}
})
.catch(error => console.error('Error uploading file:', error))
.finally(() => document.body.removeChild(input));
};
document.body.appendChild(input);
input.click();
return return
} }
let url; let url = getUrlForTinyEditor(props.mode)
if (props.mode === 'personal_files') { if (meta.filetype === "image") {
url = '/resources/filemanager/personal_list/' + parentResourceNodeId.value; url += "&type=images"
} else if (props.mode === 'documents') {
const cidReqStore = useCidReqStore();
const { course, session } = storeToRefs(cidReqStore);
let nodeId = course.value && course.value.resourceNode ? course.value.resourceNode.id : null;
if (!nodeId) {
console.error('Resource node ID is not available.');
return;
}
let folderParams = Object.entries(route.query).map(([key, value]) => `${key}=${value}`).join('&');
url = router.resolve({ name: "DocumentForHtmlEditor", params: { id: nodeId }, query: route.query }).href;
}
if (meta.filetype === 'image') {
url += "&type=images";
} else { } else {
url += "&type=files"; url += "&type=files"
} }
window.addEventListener("message", function (event) { window.addEventListener("message", function (event) {
var data = event.data; let data = event.data
if (data.url) { if (data.url) {
url = data.url; url = data.url
callback(url); callback(url)
} }
}); })
// tinymce is already in the global scope, set by backend and php
tinymce.activeEditor.windowManager.openUrl({ tinymce.activeEditor.windowManager.openUrl({
url: url, url: url,
title: "File manager", title: "File manager",
onMessage: (api, message) => { onMessage: (api, message) => {
if (message.mceAction === 'fileSelected') { if (message.mceAction === "fileSelected") {
const fileUrl = message.content; const fileUrl = message.content
callback(fileUrl); callback(fileUrl)
api.close(); api.close()
} }
},
})
}
function inputFileHandler(callback, input) {
return () => {
const file = input.files[0]
const title = file.name
const comment = ""
const fileType = "file"
const resourceLinkList = []
const formData = new FormData()
formData.append("uploadFile", file)
formData.append("title", title)
formData.append("comment", comment)
formData.append("parentResourceNodeId", parentResourceNodeId.value)
formData.append("filetype", fileType)
formData.append("resourceLinkList", resourceLinkList)
try {
let response = fetch("/file-manager/upload-image", {
method: "POST",
body: formData,
})
let data = response.json()
if (data.location) {
callback(data.location)
} else {
console.error("Failed to upload file")
}
} catch (error) {
console.error("Error uploading file:", error)
} finally {
document.body.removeChild(input)
} }
}); }
}
function getUrlForTinyEditor(mode) {
if (props.mode === TINYEDITOR_MODE_PERSONAL_FILES) {
return "/resources/filemanager/personal_list/" + parentResourceNodeId.value
} else if (props.mode === TINYEDITOR_MODE_DOCUMENTS) {
const cidReqStore = useCidReqStore()
const { course } = storeToRefs(cidReqStore)
let nodeId = course.value && course.value.resourceNode ? course.value.resourceNode.id : null
if (!nodeId) {
console.error("Resource node ID is not available.")
return
}
return router.resolve({ name: "DocumentForHtmlEditor", params: { id: nodeId }, query: route.query }).href
} else {
console.error(`Mode "${mode}" is not valid. Check valid modes on TinyEditorOptions.js`)
}
} }
</script> </script>

@ -0,0 +1,6 @@
// option to show dialog to upload to personal files in Chamilo
export const TINYEDITOR_MODE_PERSONAL_FILES = "personal_files"
// ??
export const TINYEDITOR_MODE_DOCUMENTS = "documents"
export const TINYEDITOR_MODES = [TINYEDITOR_MODE_PERSONAL_FILES, TINYEDITOR_MODE_DOCUMENTS]

@ -1,27 +1,8 @@
<template> <template>
<q-form> <q-form>
<TinyEditor <BaseTinyEditor
id="introText"
v-model="item.introText" v-model="item.introText"
:init="{ editor-id="introText"
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 500,
toolbar_mode: 'sliding',
file_picker_callback: browser,
autosave_ask_before_unload: true,
plugins: [
'fullpage advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount emoticons ' +
extraPlugins,
],
toolbar:
'undo redo | bold italic underline strikethrough | insertfile image media template link | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | code codesample | ltr rtl | ' +
extraPlugins,
}"
required required
/> />
<!-- For extra content--> <!-- For extra content-->
@ -29,131 +10,60 @@
</q-form> </q-form>
</template> </template>
<script> <script setup>
import useVuelidate from "@vuelidate/core"; import useVuelidate from "@vuelidate/core"
import { ref } from "vue"; import { computed, ref, defineExpose } from "vue"
import { usePlatformConfig } from "../../store/platformConfig"; import { usePlatformConfig } from "../../store/platformConfig"
import {useRoute} from "vue-router"; import { useRoute } from "vue-router"
export default { import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
name: "ToolIntroForm",
props: {
values: {
type: Object,
required: true,
},
errors: {
type: Object,
default: () => {},
},
initialValues: {
type: Object,
default: () => {},
},
},
setup() {
const extraPlugins = ref("");
const platformConfigStore = usePlatformConfig(); const props = defineProps({
values: {
if ("true" === platformConfigStore.getSetting("editor.translate_html")) { type: Object,
extraPlugins.value = "translatehtml"; required: true,
}
const route = useRoute();
const parentResourceNodeId = ref(route.query.parentResourceNodeId);
return { v$: useVuelidate(), extraPlugins, parentResourceNodeId};
}, },
data() { errors: {
return { type: Object,
introText: null, default: () => {},
parentResourceNodeId: null,
resourceNode: null,
};
}, },
computed: { initialValues: {
item() { type: Object,
return this.initialValues || this.values; default: () => {},
},
violations() {
return this.errors || {};
},
}, },
methods: { })
browser(callback, value, meta) {
let nodeId = this.parentResourceNodeId;
let folderParams = this.$route.query;
let url = this.$router.resolve({
name: "DocumentForHtmlEditor",
params: { node: nodeId },
query: folderParams,
});
url = url.fullPath;
console.log(url);
if (meta.filetype === "image") {
url = url + "&type=images";
} else {
url = url + "&type=files";
}
console.log(url); const route = useRoute()
const introText = ref(null)
const parentResourceNodeId = ref(route.query.parentResourceNodeId)
const resourceNode = ref(null)
window.addEventListener("message", function (event) { const item = computed(() => {
var data = event.data; return props.initialValues || props.values
if (data.url) { })
url = data.url;
console.log(meta); // {filetype: "image", fieldname: "src"}
callback(url);
}
});
tinymce.activeEditor.windowManager.openUrl( const violations = computed(() => {
{ return props.errors || {}
url: url, // use an absolute path! })
title: "file manager",
/*width: 900,
height: 450,
resizable: 'yes'*/
},
{
oninsert: function (file, fm) {
var url, reg, info;
// URL normalization const extraPlugins = ref("")
url = fm.convAbsUrl(file.url);
// Make file info const platformConfigStore = usePlatformConfig()
info = file.name + " (" + fm.formatSize(file.size) + ")";
// Provide file and text for the link dialog if ("true" === platformConfigStore.getSetting("editor.translate_html")) {
if (meta.filetype === "file") { extraPlugins.value = "translatehtml"
callback(url, { text: info, title: info }); }
}
// Provide image and alt text for the image dialog const validations = {
if (meta.filetype === "image") { item: {
callback(url, { alt: info }); introText: {
} //required,
// Provide alternative source and posted for the media dialog
if (meta.filetype === "media") {
callback(url);
}
},
}
);
return false;
}, },
parentResourceNodeId: {},
resourceNode: {},
}, },
validations: { }
item: {
introText: { const v$ = useVuelidate(validations, { item })
//required,
}, defineExpose({ v$: v$ })
parentResourceNodeId: {},
resourceNode: {},
},
},
};
</script> </script>

@ -10,34 +10,16 @@
<div class="field"> <div class="field">
<div class="p-float-label"> <div class="p-float-label">
<div class="html-editor-container"> <div class="html-editor-container">
<TinyEditor <BaseTinyEditor
v-if=" v-if="
(item.resourceNode && (
item.resourceNode.resourceFile && item.resourceNode
item.resourceNode.resourceFile.text) || && item.resourceNode.resourceFile
item.newDocument && item.resourceNode.resourceFile.text
" )
id="item_content" || item.newDocument"
v-model="item.contentFile" v-model="item.contentFile"
:init="{ editor-id="item_content"
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 500,
toolbar_mode: 'sliding',
file_picker_callback: browser,
autosave_ask_before_unload: true,
plugins: [
'fullpage advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount emoticons ' +
extraPlugins,
],
toolbar:
'undo redo | bold italic underline strikethrough | insertfile image media template link | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | code codesample | ltr rtl | ' +
extraPlugins,
}"
required required
/> />
</div> </div>
@ -56,10 +38,11 @@ import { required } from "@vuelidate/validators";
import { ref } from "vue"; import { ref } from "vue";
import { usePlatformConfig } from "../../store/platformConfig"; import { usePlatformConfig } from "../../store/platformConfig";
import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue" import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue"
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
export default { export default {
name: "DocumentsForm", name: "DocumentsForm",
components: { BaseInputTextWithVuelidate }, components: { BaseTinyEditor, BaseInputTextWithVuelidate },
props: { props: {
values: { values: {
type: Object, type: Object,

@ -40,44 +40,9 @@
/> />
<div class="field"> <div class="field">
<TinyEditor <BaseTinyEditor
id="item_content"
v-model="v$.item.content.$model" v-model="v$.item.content.$model"
:init="{ editor-id="item_content"
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 500,
toolbar_mode: 'sliding',
file_picker_callback: function(callback, value, meta) {
if (meta.filetype === 'image') {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.onchange = function() {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function(e) {
// Esta es la URL de la imagen que se pasará al editor
callback(e.target.result, {
alt: file.name
});
};
reader.readAsDataURL(file);
};
input.click();
}
},
autosave_ask_before_unload: true,
plugins: [
'fullpage advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount '
],
toolbar: 'undo redo | bold italic underline strikethrough | insertfile image media template link | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | code codesample | ltr rtl | ' + extraPlugins,
}
"
required required
/> />
</div> </div>
@ -95,35 +60,33 @@
</template> </template>
<script setup> <script setup>
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from "vue"
import BaseInputText from "../basecomponents/BaseInputText.vue"; import BaseInputText from "../basecomponents/BaseInputText.vue"
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"; import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import BaseDropdown from "../basecomponents/BaseDropdown.vue"; import BaseDropdown from "../basecomponents/BaseDropdown.vue"
import useVuelidate from '@vuelidate/core'; import useVuelidate from "@vuelidate/core"
import { required } from '@vuelidate/validators'; import { required } from "@vuelidate/validators"
import isEmpty from 'lodash/isEmpty'; import isEmpty from "lodash/isEmpty"
import { useI18n } from 'vue-i18n'; import { useI18n } from "vue-i18n"
import pageCategoryService from "../../services/pageCategoryService" import pageCategoryService from "../../services/pageCategoryService"
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
default: () => {}, default: () => {},
} },
}); })
const emit = defineEmits([ const emit = defineEmits(["update:modelValue", "submit"])
'update:modelValue',
'submit',
]);
const { t } = useI18n(); const { t } = useI18n()
let locales = ref(window.languages); let locales = ref(window.languages)
let categories = ref([]); let categories = ref([])
const findAllPageCategories = async () => categories.value = await pageCategoryService.findAll() const findAllPageCategories = async () => (categories.value = await pageCategoryService.findAll())
findAllPageCategories() findAllPageCategories()
@ -131,17 +94,17 @@ watch(
() => props.modelValue, () => props.modelValue,
(newValue) => { (newValue) => {
if (!newValue) { if (!newValue) {
return; return
} }
if (!isEmpty(newValue.category) && !isEmpty(newValue.category['@id'])) { if (!isEmpty(newValue.category) && !isEmpty(newValue.category["@id"])) {
emit('update:modelValue', { emit("update:modelValue", {
...newValue, ...newValue,
category: newValue.category['@id'] category: newValue.category["@id"],
}); })
} }
} },
); )
const validations = { const validations = {
item: { item: {
@ -160,19 +123,16 @@ const validations = {
category: { category: {
required, required,
}, },
} },
}; }
const v$ = useVuelidate( const v$ = useVuelidate(validations, { item: computed(() => props.modelValue) })
validations,
{ item: computed(() => props.modelValue) }
);
function btnSaveOnClick () { function btnSaveOnClick() {
const item = { ...props.modelValue, ...v$.value.item.$model }; const item = { ...props.modelValue, ...v$.value.item.$model }
emit('update:modelValue', item) emit("update:modelValue", item)
emit('submit', item) emit("submit", item)
} }
</script> </script>

@ -27,7 +27,7 @@
<Dialog header="Reply/Edit Message" v-model:visible="showMessageDialog" modal closable> <Dialog header="Reply/Edit Message" v-model:visible="showMessageDialog" modal closable>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<BaseInputText v-if="isEditMode" id="title" :label="t('Title')" v-model="messageTitle" :isInvalid="titleError" /> <BaseInputText v-if="isEditMode" id="title" :label="t('Title')" v-model="messageTitle" :isInvalid="titleError" />
<BaseEditor editorId="messageEditor" v-model="messageContent" title="Message" /> <BaseTinyEditor v-model="messageContent" editor-id="messageEditor" title="Message" />
<BaseFileUploadMultiple v-model="files" :label="t('Add files')" accept="image/png, image/jpeg" /> <BaseFileUploadMultiple v-model="files" :label="t('Add files')" accept="image/png, image/jpeg" />
<BaseButton type="button" :label="t('Send message')" icon="save" @click="handleSubmit" class="mt-8" /> <BaseButton type="button" :label="t('Send message')" icon="save" @click="handleSubmit" class="mt-8" />
</form> </form>
@ -43,8 +43,8 @@ import MessageItem from "./MessageItem.vue"
import BaseButton from "../basecomponents/BaseButton.vue" import BaseButton from "../basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import BaseInputText from "../basecomponents/BaseInputText.vue" import BaseInputText from "../basecomponents/BaseInputText.vue"
import BaseEditor from "../basecomponents/BaseEditor.vue"
import BaseFileUploadMultiple from "../basecomponents/BaseFileUploadMultiple.vue" import BaseFileUploadMultiple from "../basecomponents/BaseFileUploadMultiple.vue"
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()

@ -34,7 +34,7 @@
<Dialog header="Create Thread" v-model:visible="showCreateThreadDialog" modal closable> <Dialog header="Create Thread" v-model:visible="showCreateThreadDialog" modal closable>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<BaseInputText id="title" label="Title" v-model="title" :isInvalid="titleError" /> <BaseInputText id="title" label="Title" v-model="title" :isInvalid="titleError" />
<BaseEditor editorId="messageEditor" v-model="message" title="Message" /> <BaseTinyEditor v-model="message" editor-id="messageEditor" title="Message" />
<BaseFileUploadMultiple v-model="files" label="Add files" accept="image/png, image/jpeg" /> <BaseFileUploadMultiple v-model="files" label="Add files" accept="image/png, image/jpeg" />
<BaseButton type="button" label="Send message" icon="save" @click="handleSubmit" class="mt-8" /> <BaseButton type="button" label="Send message" icon="save" @click="handleSubmit" class="mt-8" />
</form> </form>
@ -50,8 +50,8 @@ import { useFormatDate } from "../../composables/formatDate"
import { useSocialInfo } from "../../composables/useSocialInfo" import { useSocialInfo } from "../../composables/useSocialInfo"
import BaseButton from "../basecomponents/BaseButton.vue" import BaseButton from "../basecomponents/BaseButton.vue"
import BaseInputText from "../basecomponents/BaseInputText.vue" import BaseInputText from "../basecomponents/BaseInputText.vue"
import BaseEditor from "../basecomponents/BaseEditor.vue"
import BaseFileUploadMultiple from "../basecomponents/BaseFileUploadMultiple.vue" import BaseFileUploadMultiple from "../basecomponents/BaseFileUploadMultiple.vue"
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
const route = useRoute() const route = useRoute()
const discussions = ref([]) const discussions = ref([])

@ -33,27 +33,7 @@
/> />
<div class="field"> <div class="field">
<TinyEditor <BaseTinyEditor v-model="item.content" editor-id="message" required />
v-model="item.content"
:init="{
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 500,
toolbar_mode: 'sliding',
file_picker_callback: browser,
autosave_ask_before_unload: true,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount emoticons',
],
toolbar:
'undo redo | bold italic underline strikethrough | insertfile image media template link | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | code codesample | ltr rtl',
}"
required
/>
</div> </div>
<BaseButton <BaseButton
@ -82,6 +62,7 @@ import userService from "../../services/user"
import BaseUserAvatar from "../../components/basecomponents/BaseUserAvatar.vue" import BaseUserAvatar from "../../components/basecomponents/BaseUserAvatar.vue"
import { useNotification } from "../../composables/notification" import { useNotification } from "../../composables/notification"
import { capitalize } from "lodash" import { capitalize } from "lodash"
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue"
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()

@ -31,27 +31,7 @@
</div> </div>
<div class="field"> <div class="field">
<TinyEditor <BaseTinyEditor v-model="item.content" editor-id="message" required />
v-model="item.content"
:init="{
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 500,
toolbar_mode: 'sliding',
file_picker_callback: browser,
autosave_ask_before_unload: true,
plugins: [
'fullpage advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount emoticons',
],
toolbar:
'undo redo | bold italic underline strikethrough | insertfile image media template link | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | code codesample | ltr rtl',
}"
required
/>
</div> </div>
<BaseButton <BaseButton
@ -78,6 +58,7 @@ import { useI18n } from "vue-i18n"
import { useSecurityStore } from "../../store/securityStore" import { useSecurityStore } from "../../store/securityStore"
import { useNotification } from "../../composables/notification" import { useNotification } from "../../composables/notification"
import { formatDateTimeFromISO } from "../../utils/dates" import { formatDateTimeFromISO } from "../../utils/dates"
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue"
const item = ref({}) const item = ref({})
const store = useStore() const store = useStore()

@ -13,12 +13,13 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from "vue"
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue" import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue"
import { TINYEDITOR_MODE_PERSONAL_FILES } from "../../components/basecomponents/TinyEditorOptions"
const editorContent = ref('') const editorContent = ref("")
// Here you decide the mode, 'personal_files' or 'documents' // Here you decide the mode, 'personal_files' or 'documents'
const editorMode = ref('personal_files') const editorMode = ref(TINYEDITOR_MODE_PERSONAL_FILES)
// Decide if you want to use the file manager or not // Decide if you want to use the file manager or not
const useFileManager = ref(true) const useFileManager = ref(true)
</script> </script>

@ -24,16 +24,12 @@
<div v-if="termsLoaded"> <div v-if="termsLoaded">
<form @submit.prevent="saveTerms"> <form @submit.prevent="saveTerms">
<BaseEditor <BaseTinyEditor
:editorId="'item_content'"
v-model="termData.content" v-model="termData.content"
editor-id="item_content"
:title="t('Personal Data Collection')" :title="t('Personal Data Collection')"
> :help-text="t('Why do we collect this data?')"
<template #help-text> />
<p>{{ t('Why do we collect this data?') }}</p>
</template>
</BaseEditor>
<BaseRadioButtons <BaseRadioButtons
:options="typeOptions" :options="typeOptions"
@ -48,10 +44,12 @@
<!-- Extra fields --> <!-- Extra fields -->
<div v-for="field in extraFields" :key="field.id" class="extra-field"> <div v-for="field in extraFields" :key="field.id" class="extra-field">
<component :is="getFieldComponent(field.type)" v-bind="field.props" @update:modelValue="field.props.modelValue = $event"> <component
<template v-if="field.type === 'editor'" #help-text> :is="getFieldComponent(field.type)"
<p>{{ field.props.helpText }}</p> v-bind="field.props"
</template> :help-text="field.type === 'editor' ? field.props.helpText : '' "
@update:model-value="field.props.modelValue = $event"
>
</component> </component>
</div> </div>
@ -81,10 +79,10 @@ import Message from "primevue/message"
import BaseDropdown from "../../components/basecomponents/BaseDropdown.vue" import BaseDropdown from "../../components/basecomponents/BaseDropdown.vue"
import BaseRadioButtons from "../../components/basecomponents/BaseRadioButtons.vue" import BaseRadioButtons from "../../components/basecomponents/BaseRadioButtons.vue"
import BaseInputText from "../../components/basecomponents/BaseInputText.vue" import BaseInputText from "../../components/basecomponents/BaseInputText.vue"
import BaseEditor from "../../components/basecomponents/BaseEditor.vue"
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import languageService from "../../services/languageService" import languageService from "../../services/languageService"
import legalService from "../../services/legalService" import legalService from "../../services/legalService"
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue"
const { t } = useI18n() const { t } = useI18n()
@ -169,7 +167,7 @@ function getFieldComponent(type) {
const componentMap = { const componentMap = {
text: BaseInputText, text: BaseInputText,
select: BaseDropdown, select: BaseDropdown,
editor: BaseEditor, editor: BaseTinyEditor,
// Add more mappings as needed // Add more mappings as needed
} }
return componentMap[type] || 'div' return componentMap[type] || 'div'

Loading…
Cancel
Save