@ -1,6 +1,11 @@
< template >
< div class = "base-tiny-editor" >
< label v-if ="title" :for ="editorId" > {{ title }} < / label >
< label
v - if = "title"
: for = "editorId"
>
{ { title } }
< / label >
< TinyEditor
: id = "editorId"
: model - value = "modelValue"
@ -9,30 +14,64 @@
@ update : model - value = "updateValue"
@ input = "updateValue"
/ >
< p v-if ="helpText" class="help-text" > {{ helpText }} < / p >
< p
v - if = "helpText"
class = "help-text"
>
{ { helpText } }
< / p >
< / div >
< / template >
< script setup >
import { ref , computed , watch } from 'vue'
import TinyEditor from '@tinymce/tinymce-vue'
import { computed , ref } from "vue"
import TinyEditor from "@tinymce/tinymce-vue"
import { useRoute , useRouter } from "vue-router"
import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from "pinia"
import { useStore } from "vuex"
import { TINYEDITOR _MODE _DOCUMENTS , TINYEDITOR _MODE _PERSONAL _FILES , TINYEDITOR _MODES } from "./TinyEditorOptions"
const props = defineProps ( {
editorId : String ,
modelValue : String ,
required : Boolean ,
editorConfig : Object ,
title : String ,
helpText : String ,
mode : { type : String , default : 'personal_files' } ,
useFileManager : { type : Boolean , default : false }
editorId : {
type : String ,
required : true ,
} ,
modelValue : {
type : String ,
required : true ,
} ,
required : {
type : Boolean ,
default : false ,
} ,
title : {
type : String ,
default : "" ,
} ,
editorConfig : {
type : Object ,
default : ( ) => { } ,
} ,
/ / A h e l p e r t e x t s h o w n b e l o w e d i t o r
helpText : {
type : String ,
default : "" ,
} ,
/ / i f t r u e t h e C h a m i l o i n n e r f i l e m a n a g e r w i l l b e s h o w n
/ / i f f a l s e t h e s y s t e m f i l e p i c k e r w i l l b e s h o w n
useFileManager : {
type : Boolean ,
default : false ,
} ,
/ / c h a n g e m o d e w h e n u s e F i l e M a n a g e r = T r u e
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 route = useRoute ( )
const parentResourceNodeId = ref ( 0 )
@ -47,115 +86,183 @@ if (route.params.node) {
}
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 = {
skin _url : '/build/libs/tinymce/skins/ui/oxide' ,
content _css : '/build/libs/tinymce/skins/content/default/content.css' ,
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' ,
height : 50 0,
toolbar _mode : "sliding" ,
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' ,
"advlist" ,
"anchor" ,
"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 | ...' ,
file _picker _callback : filePickerCallback
toolbar :
toolbarUndo +
" | " +
toolbarFormatText +
" | " +
toolbarInsertMedia +
" | " +
toolbarFontConfig +
" | " +
toolbarAlign +
" | " +
toolbarIndent +
" | " +
toolbarList +
" | " +
toolbarColor +
" | " +
toolbarPageBreak +
" | " +
toolbarSpecialSymbols +
" | " +
toolbarOther +
" | " +
toolbarCode +
" | " +
toolbarTextDirection ,
file _picker _callback : filePickerCallback ,
}
const editorConfig = computed ( ( ) => ( {
... defaultEditorConfig ,
... props . editorConfig
... props . editorConfig ,
} ) )
function filePickerCallback ( callback , value , meta ) {
async function filePickerCallback ( callback , value , meta ) {
if ( ! props . useFileManager ) {
const input = document . createElement ( 'input' ) ;
input . setAttribute ( 'type' , 'file' ) ;
input . style . display = 'none' ;
input . onchange = ( ) => {
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 ) ;
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 ( ) ;
const input = document . createElement ( "input" )
input . setAttribute ( "type" , "file" )
input . style . display = "none"
input . onchange = inputFileHandler ( callback , input )
document . body . appendChild ( input )
input . click ( )
return
}
let url ;
if ( props . mode === 'personal_files' ) {
url = '/resources/filemanager/personal_list/' + parentResourceNodeId . value ;
} 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" ;
let url = getUrlForTinyEditor ( props . mode )
if ( meta . filetype === "image" ) {
url += "&type=images"
} else {
url += "&type=files" ;
url += "&type=files"
}
window . addEventListener ( "message" , function ( event ) {
var data = event . data ;
let data = event . data
if ( data . url ) {
url = data . url ;
callback ( url ) ;
url = data . url
callback ( url )
}
} ) ;
} )
/ / t i n y m c e i s a l r e a d y i n t h e g l o b a l s c o p e , s e t b y b a c k e n d a n d p h p
tinymce . activeEditor . windowManager . openUrl ( {
url : url ,
title : "File manager" ,
onMessage : ( api , message ) => {
if ( message . mceAction === 'fileSelected' ) {
const fileUrl = message . content ;
callback ( fileUrl ) ;
api . close ( ) ;
if ( message . mceAction === "fileSelected" ) {
const fileUrl = message . content
callback ( fileUrl )
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 >