為了後續方便文章編輯與製作,在admin下實作了文章編輯器
當前文章編輯器的package多來自於對Quill的修改
vue-quill-editor亦同,因此許多function的客製化修改可參考Quill的API文件說明
安裝
可參見vue-quill-editor Github的指引,沒啥問題
npm install vue-quill-editor --save # or yarn add vue-quill-editor
與Nuxt整合
在/plugins下創建一個vue-quill-editor.js (名稱可自訂)
// in ~/plugins/vue-quill-editor.js import Vue from 'vue' import VueQuillEditor from 'vue-quill-editor' Vue.use(VueQuillEditor)
修改nuxt.config.js 的plugins,添加此js file
plugins: [ { src: '~plugins/vue-quill-editor.js', ssr: false }, ],
ssr: false
避免雜七雜八的套件問題,反正文本編輯器不需要SEO優化Ovo
創建Component
這部分可獨立建一個客製化component(建議),或直接寫在page file內(不建議,很亂)
~/components/ui/AppEditor.vue
<template> <section> <client-only> <quill-editor ref="editor" v-model.lazy="editedContent" :options="editorOption" class="editor--border relative" @change="debounceTextChange" /> </client-only> </section> </template>
ref="editor"
設定在之後客製化圖片上傳功能會用到
v-model.lazy
避免打字時過快同步而lag
:option
很重要,所有文本編輯器的功能都寫在這一項
@change="debounceTextChange"
很重要,用來將變動後的文本內容emit至parent component
接著是<script>
(這裡import 'vue-debounce'將於另外一篇文章說明)
<script> import { debounce } from 'vue-debounce' export default { // 如果是編輯文章,則會從parent收到文章當前的content props: { content: { type: String, default: () => '', }, }, data() { return { editedContent: this.content, // 所有文本編輯器功能設定均寫在editorOption editorOption: { theme: 'snow', // 可換 modules: { toolbar: { // container這裡是個大坑,[]表分群 container: [ ['bold', 'italic', 'underline', 'strike', 'code'], ['blockquote', 'code-block'], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ list: 'ordered' }, { list: 'bullet' }], [{ script: 'sub' }, { script: 'super' }], [{ indent: '-1' }, { indent: '+1' }], [{ size: ['small', false, 'large', 'huge'] }], [{ color: [] }, { background: [] }], [{ align: [] }], ['clean'], ['link', 'image', 'video'], ], // 客製化圖片上傳功能用的 handlers: { image: this.uploadImage, }, }, }, }, } }, methods: { debounceTextChange: debounce(function () { //don't use arrow function this.$emit('text-change', this.editedContent) }, 3000), // 後面再補上uploadImage說明如何客製化圖片上傳功能 }, } </script>
上述container所設定出來的成果如圖
這邊我只選了我會用到的功能項目,可自行增減
設定全域CSS
在nuxt.config.js內設定snow主題的css (第2項)
css: [ '~assets/css/main.scss', 'quill/dist/quill.snow.css', ],
客製化圖片上傳功能
若沒特別需求,前述已完成最基本的editor component建置
quill editor預設圖片上傳為base64形式
為了使DOM更簡潔以及管理上傳的圖片
我選擇以firebase作為上傳儲存的database,客製化function uploadImage()如下
uploadImage() { if (!process.client) { return } let fileInput = this.$el.querySelector('input.ql-image[type=file]') if (fileInput == null) { fileInput = document.createElement('input') fileInput.setAttribute('type', 'file') fileInput.setAttribute( 'accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' ) fileInput.classList.add('ql-image') fileInput.addEventListener('change', () => { if (fileInput.files != null && fileInput.files[0] != null) { // customize image upload function const file = fileInput.files[0] const fileName = file.name.split('.').slice(0, -1).join('.') //without .png .jpg .svg // 使用nuxt firebase module,圖儲存在store,URL儲存在Realtime Database this.$fire.storage .ref('images/' + file.name) .put(file) .then((response) => { console.log('===image upload succeeded===') response.ref.getDownloadURL().then((downloadURL) => { this.$fire.database .ref('images') .child(fileName) .update({ imageUrl: downloadURL }) .then(() => { console.log('===imageUrl stored in RTDB successfully===') // Quill API提供之當前編輯器內選取的範圍,用來插入連結 let range = this.$refs.editor.quill.getSelection() this.$refs.editor.quill.insertEmbed( range.index, 'image', downloadURL ) }) .catch((err) => console.log(err)) }) }) .catch((err) => console.log(err)) } }) let container = this.$el.querySelector('.ql-toolbar.ql-snow') container.appendChild(fileInput) } fileInput.click() },
BONUS: Fix editor tool bar
預設tool bar在文本過長產生scrollbar時,並不會固定於視窗上方
這會使得編輯起來很沒效率
欲固定之,需使用到CSS deep selector
程式碼如下/deep/ .ql-toolbar
(順便加上邊框 .editor--border
)
<style lang="scss" scoped> .quill-editor { min-height: 300px; max-height: 70vh; overflow-y: auto; /deep/ .ql-toolbar { position: sticky; top: 0; left: 0; background-color: #fff; z-index: 10; } /deep/ .ql-editor { min-height: 300px; font-family: Roboto; font-size: 1rem; } } .editor--border { border: 1px solid #ccc; } </style>
完成!👏👏👏