從這里進入官網. 能找到這個NB的編輯器是因為公司項目需要一個可視化的cms編輯器,類似微信公眾號編輯文章。可以插入各種卡片,模塊,問題,圖片等等。然后插入的內容還需要能刪除,拖拽等等。所以采用vue開發,兼容vue並兼容拖拽的文本編輯器並不多,所以在github上一番搜索找到了quill這款文本編輯器神器。
先從官方例子里面扒一個圖瞅瞅:

PS:和大多數文本編輯器長得都差不多,如果功能都一樣,那也不用介紹了。 他NB,強大的地方就是所有能看到的,不能看到的功能統統都是一個一個獨立的模塊。全部都是可以替換的。不得不對這段文字進行重點標記。當然其他編輯器的一些幾本功能也統統都有且不僅如此。比如文本的樣式,多媒體文件的上傳,響應鍵盤事件,操作歷史,公式支持等等。點擊查看詳情. 各種自定義的使用說明
比如上圖中的菜單欄可以自定義,對已有的菜單欄定義:繼續從官方例子里面扒圖:

當然,如果插件自帶的功能沒有,比如你要做一個動畫在菜單欄上加一個圖標、選項或者什么的。可以對整個菜單欄進行定義和重寫

下面從項目中的擴展點找2個說明一下這個NB的編輯器,當然他的更多可擴展功能也沒有用上,所以只有看到的官方文檔,才能理解他的可擴展性和靈活性。
修改字體大小選擇,使用自定義的列表和單位(rem)
自帶的字體大小編輯有2個如下。但是顯然不太能支持我們的用法。一開始吧size擴展成了px。但是后來經過測試發現手機端使用的是rem,so。最后改成使用rem。
[{ 'size': ['small', false, 'large', 'huge'] }]
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
//擴展后的字體選擇
[{
// 'size': ['10px', '12px', '14px', '16px', '18px', '20px']
//1/75 *2
//1px =0.026rem
//1rem=36px
'size': ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem']
}]
為了在菜單欄中顯示對應的字體大小。加入css。差不多長這樣,有多少個選項,就加多少個。
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="10px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="10px"]::before {
content: '10px';
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
content: '20px';
font-size: 20px;
}
//默認的樣式
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: '14px';
font-size: 14px;
}
//rem:需要說明一下,在編輯的時候還是顯示px單位,但最終生成的源代碼使用rem,因為編輯是在pc上,並且運營人員也只熟悉px這個單位,對rem沒有概念。
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="0.26rem"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="0.26rem"]::before {
content: '10px';
font-size: 10px;
}
在然后在初始化quill的地方加上下面的js代碼
import Quill from 'quill'
var Size = Quill.import('attributors/style/size');
// Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px'];
Size.whitelist = ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem'];
Quill.register(Size, true);
如此之后,對我們字體大小的選擇就算擴展完畢了,讓我們檢驗一下成果:

當然為了在pc上rem字體能生效,還必須得加上一行。
html {
font-size: 36px;
}
擴展居中,靠右使用樣式,而不是class方式
值得說明的是,樣式的設置等,幾本都有多套策略可以選擇。舉個栗子,官方源代碼。
這是官方的字體方向設置的源代碼。我們可以看到他就有3種方式設置:通過attribute(algin:'right'),通過class(class='ql-align-right'),通過style(style='text-align:right');是不是很靈活,很強大,任君選擇有木有
import Parchment from 'parchment';
let config = {
scope: Parchment.Scope.BLOCK,
whitelist: ['right', 'center', 'justify']
};
let AlignAttribute = new Parchment.Attributor.Attribute('align', 'align', config);
let AlignClass = new Parchment.Attributor.Class('align', 'ql-align', config);
let AlignStyle = new Parchment.Attributor.Style('align', 'text-align', config);
export { AlignAttribute, AlignClass, AlignStyle };
那如何指定使用其他的一種呢?像下面的代碼一樣,如果使用style。則使用 Quill.import('attributors/style/align');替換默認的,如果使用class:則使用 Quill.import('attributors/class/align');
var Align = Quill.import('attributors/style/align');
Align.whitelist = ['right', 'center', 'justify'];
Quill.register(Align, true);
檢驗一下成果:

然后在來一個高級一點的。設置字體為粗體
quill默認使用的是strong或者b標簽方式。其實這也是沒有問題的,但是架不住公司的"高級"前端對手機端的所有html標簽都reset了。所有的hx標簽,em,strong等語義標簽全部reset了。所以沒辦法只能使用style的方式來實現。
import Inline from '../blots/inline';
class Bold extends Inline {
static create() {
return super.create();
}
static formats() {
return true;
}
optimize() {
super.optimize();
if (this.domNode.tagName !== this.statics.tagName[0]) {
this.replaceWith(this.statics.blotName);
}
}
}
Bold.blotName = 'bold';
Bold.tagName = ['STRONG', 'B'];
export default Bold;
使用style來實現文字的加粗
import Quill from 'quill'
let Parchment = Quill.import('parchment')
class BoldStyleAttributor extends Parchment.Attributor.Style {
value(domNode) {
let value = super.value(domNode);
return value;
}
add(node, value) {
$(node).css('font-weight', 'bold');
return true;
}
remove(node) {
$(node).css('font-weight', 'normal');
}
}
let BoldStyle = new BoldStyleAttributor('bold', 'font-weight', {
scope: Parchment.Scope.INLINE,
whitelist: [true, false]
});
export default BoldStyle;`
初始化quill的地方加上下面的代碼
./NodeEditText/TextBold”或者“./NodeEditText/TextBold.js”就是上面幾行代碼的js文件路徑。
import MyBold from './NodeEditText/TextBold'
Quill.register("formats/bold", MyBold, true);
檢驗一下成果:

諸如文字的字體啦,斜體啦,都類似寫法。就不一一展開了。官方文檔雖然是英文的,但是耐着性子看,還是能比較方便看懂的,
寫在最后:
能夠快速的自定義這個組件的前提是需要懂他的設計思想,我也只是粗淺的了解使用了一下這個組件,就不做什么總結了
回答一下 @48詹澤娟 的問題,集成到vue,大約是這樣子.
<template>
<div id="quillWrapper">
<div ref="quillContainer" :id="$data.elmId" class="quill-container"></div>
<button v-if="useSaveButton" class="save-button"
@click="saveContent">
{{ buttonText ? buttonText : 'Save Content' }}
</button>
<div v-if="showPreview" ref="livePreview" class="ql-editor"></div>
</div>
</template>
<script>
import Quill from 'quill'
import Parchment from 'parchment';
import MyBold from './NodeEditText/TextBold'
import MyItalic from './NodeEditText/TextItalic'
var defaultToolbar = [
['bold', 'italic'],
[{
'color': []
}],
[{
// 'size': ['10px', '12px', '14px', '16px', '18px', '20px']
//1/75 *2
//1px =0.026rem
//1rem=36px
'size': ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem']
}],
[{
'align': []
}],
['clean'],
];
export default {
name: 'VueEditor',
props: {
editorContent: String,
placeholder: String,
buttonText: String,
useSaveButton: {
type: Boolean,
default () {
return true
}
},
showPreview: {
type: Boolean,
default () {
return false
}
}
},
data: function() {
return {
quill: null,
editor: null,
toolbar: defaultToolbar,
elmId: 'quill-container' + (new Date()).getTime()
}
},
mounted: function() {
const vm = this
var Size = Quill.import('attributors/style/size');
// Size.whitelist = ['10px', '12px', '14px', '16px', '18px', '20px'];
Size.whitelist = ['0.26rem', '0.31rem', '0.37rem', '0.41rem', '0.47rem', '0.52rem'];
Quill.register(Size, true);
var Align = Quill.import('attributors/style/align');
Align.whitelist = ['right', 'center', 'justify'];
Quill.register(Align, true);
// Quill.register(MyBold, true);
Quill.register("formats/bold", MyBold, true);
Quill.register("formats/italic", MyItalic, true);
vm.quill = new Quill(vm.$refs.quillContainer, {
modules: {
toolbar: {
'container': this.toolbar,
}
},
placeholder: this.placeholder ? this.placeholder : '',
theme: 'snow'
});
vm.editor = $(this.$el).find('.ql-editor')[0];
vm.editor.innerHTML = this.editorContent;
if (vm.$refs.livePreview !== undefined || false) {
vm.quill.on('text-change', function() {
vm.$refs.livePreview.innerHTML = vm.editor.innerHTML
vm.$emit('editor-updated', vm.editor.innerHTML)
});
} else {
vm.quill.on('text-change', function() {
vm.$emit('editor-updated', vm.editor.innerHTML)
});
}
var replaceReg = /<(\S*?) [^>]*>.*?<\/\1>|<.*?\/>/gm;
$(vm.editor).on('paste', function(e) {
var text = null;
if (window.clipboardData && clipboardData.setData) {
// IE
text = window.clipboardData.getData('text');
} else {
text = (e.originalEvent || e).clipboardData.getData('text/plain') || prompt('在這里輸入文本');
}
console.log(text);
if (document.body.createTextRange) {
if (document.selection) {
textRange = document.selection.createRange();
} else if (window.getSelection) {
sel = window.getSelection();
var range = sel.getRangeAt(0);
// 創建臨時元素,使得TextRange可以移動到正確的位置
var tempEl = document.createElement("span");
tempEl.innerHTML = "&#FEFF;";
range.deleteContents();
range.insertNode(tempEl);
textRange = document.body.createTextRange();
textRange.moveToElementText(tempEl);
tempEl.parentNode.removeChild(tempEl);
}
textRange.text = text;
textRange.collapse(false);
textRange.select();
} else {
// Chrome之類瀏覽器
document.execCommand("insertText", false, text);
}
e.preventDefault();
console.log('paste:' + text);
});
try {
document.execCommand("AutoUrlDetect", false, false);
} catch (e) {}
},
watch: {
editorContent: function() {
if (this.editor.innerHTML != this.editorContent) {
console.log('set inner html');
this.editor.innerHTML = this.editorContent;
}
}
},
methods: {
saveContent: function(value) {
this.$emit('save-content', this.editor.innerHTML)
}
}
}
</script>
