為了後續方便文章編輯與製作,在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>
完成!👏👏👏