Last active
November 30, 2024 08:35
-
-
Save bettysteger/d7f2b1a52bb1c23a0c24f3a9ff5832d9 to your computer and use it in GitHub Desktop.
Vue 3 SFC for Editor.js using v-model with own custom vue component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="editorjs" ref="htmlelement"></div> | |
</template> | |
<script setup> | |
import EditorJS from '@editorjs/editorjs'; | |
import EmbedTool from '@editorjs/embed'; | |
import ListTool from '@editorjs/list'; | |
import ImageTool from '@editorjs/image'; | |
import VideoTool from './editorjs/video.js'; | |
import { onMounted, onUnmounted, ref, watch } from 'vue'; | |
const htmlelement = ref(null); | |
const props = defineProps(['modelValue', 'placeholder']) | |
const emit = defineEmits(['update:modelValue']) | |
let editor; | |
let updatingModel = false; | |
// model -> view | |
function modelToView() { | |
if (!props.modelValue) { return; } | |
if (typeof props.modelValue === 'string') { | |
editor.blocks.renderFromHTML(props.modelValue); | |
return; | |
} | |
editor.render(props.modelValue); | |
} | |
// view -> model | |
function viewToModel(api, event) { | |
updatingModel = true; | |
editor.save().then((outputData) => { | |
console.log(event, 'Saving completed: ', outputData) | |
emit('update:modelValue', outputData); | |
}).catch((error) => { | |
console.log(event, 'Saving failed: ', error) | |
}).finally(() => { | |
updatingModel = false; | |
}) | |
} | |
onMounted(() => { | |
editor = new EditorJS({ | |
holder: htmlelement.value, | |
placeholder: props.placeholder, | |
inlineToolbar: ['bold', 'italic', 'link'], | |
tools: { | |
embed: EmbedTool, | |
list: ListTool, | |
image: ImageTool, | |
video: VideoTool, | |
}, | |
minHeight: 'auto', | |
data: props.modelValue, | |
onReady: modelToView, | |
onChange: viewToModel, | |
}) | |
}) | |
watch(() => props.modelValue, () => { | |
if (!updatingModel) { | |
modelToView() | |
} | |
}) | |
onUnmounted(() => { | |
editor.destroy() | |
}) | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Video Block Tool, based on Image Tool | |
import ImageTool from '@editorjs/image'; | |
// renders custom Videojs Vue component | |
import { render, h } from 'vue' | |
import Videojs from '../Videojs.vue' | |
const IconVideo = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 489.2 489.2"><path d="M439.6,0h-390C22.2,0,0,22.2,0,49.6v390c0,27.4,22.2,49.6,49.6,49.6h390c27.4,0,49.6-22.2,49.6-49.6V49.7 C489.3,22.3,467,0,439.6,0z M300.6,47.8h42.5v42.5h-42.5V47.8z M223.4,47.8h42.5v42.5h-42.5V47.8L223.4,47.8z M146.1,47.8h42.5 v42.5h-42.5V47.8z M111.3,441.6H68.8v-42.5h42.5V441.6z M111.3,90.3H68.8V47.8h42.5V90.3z M188.6,441.6h-42.5v-42.5h42.5V441.6z M265.8,441.6h-42.5v-42.5h42.5V441.6z M343.1,441.6h-42.5v-42.5h42.5V441.6z M352.5,256.7l-163.1,94.2c-9.2,5.3-20.8-1.3-20.8-12 V150.5c0-10.7,11.6-17.3,20.8-12l163.1,94.2C361.8,238,361.8,251.4,352.5,256.7z M420.4,441.6h-42.5v-42.5h42.5V441.6z M420.4,90.3 h-42.5V47.8h42.5V90.3z"/></svg>' | |
export default class Video extends ImageTool { | |
static get toolbox() { | |
return { title: 'Video', icon: IconVideo }; | |
} | |
/** | |
* @param {object} tool - tool properties got from editor.js | |
* @param {ImageToolData} tool.data - previously saved data | |
* @param {ImageConfig} tool.config - user config for Tool | |
* @param {object} tool.api - Editor.js API | |
* @param {boolean} tool.readOnly - read-only mode flag | |
* @param {BlockAPI|{}} tool.block - current Block API | |
*/ | |
constructor({ data, config, api, readOnly, block }) { | |
config.buttonContent = config.buttonContent || `${IconVideo} ${api.i18n.t('Select a Video')}`; | |
config.types = config.types || 'video/*'; | |
super({ data, config, api, readOnly, block }); | |
} | |
// original render() method from image tool is used | |
// render() { | |
// } | |
/** | |
* Set new video file (override default method) | |
* | |
* @private | |
* | |
* @param {object} file - uploaded file data | |
*/ | |
set image(file) { | |
this._data.file = file || {}; | |
if (file && file.url) { | |
// this.ui.fillImage(file.url); | |
this.fillVideo(file.url); | |
} | |
} | |
/** | |
* Shows a video | |
* | |
* @param {string} url - video source | |
* @returns {void} | |
*/ | |
fillVideo(url) { | |
const vueComponent = h(Videojs, { | |
src: url, | |
class: this.ui.CSS.imageEl, | |
'data-mutation-free': 'true', | |
onLoadeddata: () => { | |
this.ui.toggleStatus('filled'); | |
if (this.ui.nodes.imagePreloader) { | |
this.ui.nodes.imagePreloader.style.backgroundImage = ''; | |
} | |
} | |
}); | |
render(vueComponent, this.ui.nodes.imageContainer); | |
} | |
static get pasteConfig() { | |
return { | |
tags: [{ | |
videojs: { src: true }, | |
video: { src: true } | |
}], | |
patterns: { | |
video: /https?:\/\/\S+\.(mov|ogv|webm|mp4|m4v|m3u8)(\?[a-z0-9=]*)?$/i, | |
}, | |
files: { | |
mimeTypes: [ 'video/*' ], | |
}, | |
}; | |
} | |
async onPaste(event) { | |
switch (event.type) { | |
case 'tag': { | |
const video = event.detail.data; | |
// need getAttribute for custom HTML element videojs | |
this.uploadUrl(video.getAttribute('src')); | |
break; | |
} | |
case 'pattern': { | |
const url = event.detail.data; | |
this.uploadUrl(url); | |
break; | |
} | |
case 'file': { | |
const file = event.detail.file; | |
this.uploadFile(file); | |
break; | |
} | |
} | |
} | |
destroy() { | |
render(null, this.ui.nodes.imageContainer); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<video class="video-js" controls playsinline preload="auto" crossorigin="anonymous" ref="videoEl"> | |
<source :src="currentSrc"> | |
<p class="vjs-no-js"> | |
To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a> | |
</p> | |
</video> | |
</template> | |
<script setup> | |
/** | |
* VideoJs component | |
* @see https://github.com/videojs/video.js | |
*/ | |
import videojs from 'video.js' | |
import { ref, onMounted, onUnmounted } from 'vue' | |
// declare a ref to hold the element reference | |
// the name must match template ref value | |
const videoEl = ref(null) | |
let player; | |
const props = defineProps({ | |
src: { | |
type: String, | |
required: true | |
}, | |
poster: String, | |
subtitleUrls: Object, | |
mandatory: Boolean, | |
}) | |
const emit = defineEmits(['loadeddata', 'end']) | |
const currentSrc = ref(props.src) | |
/** | |
* Manually setup video.js player | |
*/ | |
onMounted(() => { | |
const options = { | |
fluid: true, | |
poster: props.poster, | |
playbackRates: [0.5, 0.75, 1, 1.25, 1.5] | |
}; | |
player = videojs(videoEl.value, options); | |
player.on('loadeddata', (e) => emit('loadeddata', e)); | |
player.on('ended', function () { | |
emit('end', props.src); | |
if(player.isFullscreen()) { | |
player.exitFullscreen(); | |
} | |
}); | |
}) | |
/** | |
* Destroys the video player and does any necessary cleanup. | |
* @see https://docs.videojs.com/player | |
*/ | |
onUnmounted(() => { | |
if (player) { | |
player.dispose(); | |
} | |
}) | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment