You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							285 lines
						
					
					
						
							7.0 KiB
						
					
					
				
			
		
		
	
	
							285 lines
						
					
					
						
							7.0 KiB
						
					
					
				<template>
 | 
						|
  <div class="base-tiny-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 { computed, ref } from "vue"
 | 
						|
import TinyEditor from "../../components/Editor"
 | 
						|
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"
 | 
						|
 | 
						|
//import contentUiCss from "css-loader!tinymce/skins/ui/oxide/content.css"
 | 
						|
//import contentCss from "css-loader!tinymce/skins/content/default/content.css"
 | 
						|
 | 
						|
const props = defineProps({
 | 
						|
  editorId: {
 | 
						|
    type: String,
 | 
						|
    required: true,
 | 
						|
  },
 | 
						|
  modelValue: {
 | 
						|
    type: String,
 | 
						|
    required: true,
 | 
						|
  },
 | 
						|
  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),
 | 
						|
  },
 | 
						|
  fullPage: {
 | 
						|
    type: Boolean,
 | 
						|
    required: false,
 | 
						|
    default: true,
 | 
						|
  },
 | 
						|
})
 | 
						|
const emit = defineEmits(["update:modelValue"])
 | 
						|
const router = useRouter()
 | 
						|
const route = useRoute()
 | 
						|
const parentResourceNodeId = ref(0)
 | 
						|
 | 
						|
const store = useStore()
 | 
						|
const user = computed(() => store.getters["security/getUser"])
 | 
						|
 | 
						|
// Set the parent node ID based on the user's resource node ID or route parameter
 | 
						|
parentResourceNodeId.value = user.value.resourceNode["id"]
 | 
						|
if (route.params.node) {
 | 
						|
  parentResourceNodeId.value = Number(route.params.node)
 | 
						|
}
 | 
						|
 | 
						|
const updateValue = (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: false,
 | 
						|
  content_css: false,
 | 
						|
  //content_style: contentUiCss.toString() + "\n" + contentCss.toString(),
 | 
						|
  branding: false,
 | 
						|
  relative_urls: false,
 | 
						|
  height: 500,
 | 
						|
  toolbar_mode: "sliding",
 | 
						|
  autosave_ask_before_unload: true,
 | 
						|
  plugins: [
 | 
						|
    "advlist",
 | 
						|
    "anchor",
 | 
						|
    "autolink",
 | 
						|
    "charmap",
 | 
						|
    "code",
 | 
						|
    "codesample",
 | 
						|
    "directionality",
 | 
						|
    "fullscreen",
 | 
						|
    "emoticons",
 | 
						|
    "image",
 | 
						|
    "insertdatetime",
 | 
						|
    "link",
 | 
						|
    "lists",
 | 
						|
    "media",
 | 
						|
    "paste",
 | 
						|
    "preview",
 | 
						|
    "print",
 | 
						|
    "pagebreak",
 | 
						|
    "save",
 | 
						|
    "searchreplace",
 | 
						|
    "table",
 | 
						|
    "template",
 | 
						|
    "visualblocks",
 | 
						|
    "wordcount",
 | 
						|
  ],
 | 
						|
  toolbar:
 | 
						|
    toolbarUndo +
 | 
						|
    " | " +
 | 
						|
    toolbarFormatText +
 | 
						|
    " | " +
 | 
						|
    toolbarInsertMedia +
 | 
						|
    " | " +
 | 
						|
    toolbarFontConfig +
 | 
						|
    " | " +
 | 
						|
    toolbarAlign +
 | 
						|
    " | " +
 | 
						|
    toolbarIndent +
 | 
						|
    " | " +
 | 
						|
    toolbarList +
 | 
						|
    " | " +
 | 
						|
    toolbarColor +
 | 
						|
    " | " +
 | 
						|
    toolbarPageBreak +
 | 
						|
    " | " +
 | 
						|
    toolbarSpecialSymbols +
 | 
						|
    " | " +
 | 
						|
    toolbarOther +
 | 
						|
    " | " +
 | 
						|
    toolbarCode +
 | 
						|
    " | " +
 | 
						|
    toolbarTextDirection,
 | 
						|
  file_picker_callback: filePickerCallback,
 | 
						|
}
 | 
						|
 | 
						|
if (props.fullPage) {
 | 
						|
  defaultEditorConfig.plugins.push("fullpage")
 | 
						|
}
 | 
						|
 | 
						|
const editorConfig = computed(() => ({
 | 
						|
  ...defaultEditorConfig,
 | 
						|
  ...props.editorConfig,
 | 
						|
}))
 | 
						|
 | 
						|
async function filePickerCallback(callback, value, meta) {
 | 
						|
  if (!props.useFileManager) {
 | 
						|
    const input = document.createElement("input")
 | 
						|
    input.setAttribute("type", "file")
 | 
						|
    if ("image" === meta.filetype) {
 | 
						|
      input.accept = "image/*"
 | 
						|
    } else if ("media" === meta.filetype) {
 | 
						|
      input.accept = "audio/*, video/*"
 | 
						|
    }
 | 
						|
    input.style.display = "none"
 | 
						|
    input.onchange = inputFileHandler(callback, input)
 | 
						|
    document.body.appendChild(input)
 | 
						|
    input.click()
 | 
						|
    return
 | 
						|
  }
 | 
						|
 | 
						|
  let url = getUrlForTinyEditor(props.mode)
 | 
						|
  if (meta.filetype === "image") {
 | 
						|
    url += "&type=images"
 | 
						|
  } else {
 | 
						|
    url += "&type=files"
 | 
						|
  }
 | 
						|
 | 
						|
  window.addEventListener("message", function (event) {
 | 
						|
    let data = event.data
 | 
						|
    if (data.url) {
 | 
						|
      url = data.url
 | 
						|
      callback(url)
 | 
						|
    }
 | 
						|
  })
 | 
						|
 | 
						|
  // tinymce is already in the global scope, set by backend and php
 | 
						|
  tinymce.activeEditor.windowManager.openUrl({
 | 
						|
    url: url,
 | 
						|
    title: "File manager",
 | 
						|
    onMessage: (api, message) => {
 | 
						|
      if (message.mceAction === "fileSelected") {
 | 
						|
        const fileUrl = message.content
 | 
						|
        callback(fileUrl)
 | 
						|
        api.close()
 | 
						|
      }
 | 
						|
    },
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
function inputFileHandler(callback, input) {
 | 
						|
  return async () => {
 | 
						|
    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 = await fetch("/file-manager/upload-image", {
 | 
						|
        method: "POST",
 | 
						|
        body: formData,
 | 
						|
      })
 | 
						|
      const { data, location } = await response.json()
 | 
						|
      if (location) {
 | 
						|
        callback(location, { alt: data.title })
 | 
						|
      } 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>
 | 
						|
 |