Tinymce官方提供了豐富的插件,足以知足關於通常富文本的需求,也能夠經過進一步的配置,讓其更符合實際的業務場景。可是,若是這還知足不了你的需求,想進一步的定製化呢?css
Tinymce提供了豐富的接口,用以編寫自定義的插件。而且,官方的插件源碼也很是清晰易懂,在寫本身的插件的時候,能夠作一個詳細的參考。html
其實編寫一個插件很簡單,只須要作3件事情。node
由於公司這邊的業務要求時,開發一個功能與微信公衆號文章編輯器基本功能對稱的富文本編輯器。而微信編輯器有一個設置行高的功能,Tinymce官方沒有提供,雖然能夠經過配置格式化樣式進行簡單的開發,但爲了統一體驗,就仿照微信編輯器在工具欄新增了一個能夠設置行距的按鈕。api
import Tinymce from 'tinymce'
Tinymce.PluginManager.add('lineheight', function (editor) {
// 執行方法
const actionFunction = function (editor, val) {
const value = val || editor.getParam('lineheight_default_value', 1.5)
editor.formatter.apply('lineheight', { value })
}
// 命令
editor.addCommand('mceLineHeight', function (ui, value) {
actionFunction(editor, value)
})
// toolbar 按鈕 圖標
editor.ui.registry.addIcon('lineheight', ` <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <path d="M5,5 L19,5 L19,7 L5,7 L5,5 Z M13,9 L19,9 L19,11 L13,11 L13,9 Z M13,13 L19,13 L19,15 L13,15 L13,13 Z M5,17 L19,17 L19,19 L5,19 L5,17 Z M11.07,11.46 L8.91,9.84 L6.75,11.46 L5.79,10.18 L8.43,8.2 C8.71444444,7.98666667 9.10555556,7.98666667 9.39,8.2 L12.03,10.18 L11.07,11.46 Z M8.91,14.16 L11.07,12.54 L12.03,13.82 L9.39,15.8 C9.10555556,16.0133333 8.71444444,16.0133333 8.43,15.8 L5.79,13.82 L6.75,12.54 L8.91,14.16 Z" id="行間距"></path> </svg> `)
// toolbar 按鈕功能
editor.ui.registry.addSplitButton('lineheight', {
tooltip: '行間距',
// 圖標
icon: 'lineheight',
// 初始化
onSetup (api) {
editor.formatter.register({
lineheight: {
selector: 'p,h1,h2,h3,h4,h5,h6,table,td,th,div,ul,ol,li,section,article,header,footer,figcaption',
styles: { 'line-height': '%value' }
}
})
},
// 圖標點擊
onAction (api) {
return editor.execCommand('mceLineHeight')
},
// 列表項點擊
onItemAction (buttonApi, value) {
return editor.execCommand('mceLineHeight', false, value)
},
// 初始化列表
fetch (callback) {
const items = [
{
type: 'choiceitem',
text: '1',
value: 1
},
{
type: 'choiceitem',
text: '1.5',
value: 1.5
},
{
type: 'choiceitem',
text: '1.75',
value: 1.75
},
{
type: 'choiceitem',
text: '2',
value: 2
},
{
type: 'choiceitem',
text: '3',
value: 3
},
{
type: 'choiceitem',
text: '4',
value: 4
},
{
type: 'choiceitem',
text: '5',
value: 5
}
]
callback(items)
}
})
})
複製代碼
這個插件只是對當前內容的一種格式化。比較簡單。微信
插件必定要在tinymce
引入以後,實例初始化以前引入。若是插件出現重名,會優先使用本身項目中的插件。換言之,初始化的時候,會先找已註冊的插件,沒有找到的話,會根據配置,去相對路徑取插件文件。app
import Tinymce from 'tinymce'
import './plugins/lineheight'
Tinymce.init({
selector: '.textarea',
plugins: ['lineheight']
})
複製代碼
由於編輯器自帶的插件不能知足業務需求,因此本身寫了一個預覽插件。cors
import Tinymce from 'tinymce'
Tinymce.PluginManager.add('preview', function (editor) {
const adaptStyle = ` body { width: 375px !important; height: 667px !important; overflow-x: hidden !important; overflow-y: auto !important; margin: 0 !important; padding: 0 !important; font-size: 17px; } .adaptive-screen-width { width: 100% !important; height: auto; } `
const Settings = {
getContentStyle (editor) {
return editor.getParam('content_style', '')
},
shouldUseContentCssCors (editor) {
return editor.getParam('content_css_cors', false, 'boolean')
},
getBodyId (editor) {
let bodyId = editor.settings.body_id || 'tinymce'
if (bodyId.indexOf('=') !== -1) {
bodyId = editor.getParam('body_id', '', 'hash')
bodyId = bodyId[editor.id] || bodyId
}
return bodyId
},
getBodyClass (editor) {
let bodyClass = editor.settings.body_class || ''
if (bodyClass.indexOf('=') !== -1) {
bodyClass = editor.getParam('body_class', '', 'hash')
bodyClass = bodyClass[editor.id] || ''
}
return bodyClass
},
getDirAttr (editor) {
const encode = editor.dom.encode
let directionality = editor.getBody().dir
return directionality ? ' dir="' + encode(directionality) + '"' : ''
}
}
const getPreviewFrame = function (editor) {
// <head>
let headHtml = ''
const encode = editor.dom.encode
const contentStyle = Settings.getContentStyle(editor)
headHtml += `<base href="${encode(editor.documentBaseURI.getURI())}">`
if (contentStyle) {
headHtml += `<style type="text/css">${contentStyle + adaptStyle}</style>`
}
const cors = Settings.shouldUseContentCssCors(editor) ? ' crossorigin="anonymous"' : ''
Array.from(editor.contentCSS).forEach(url => {
headHtml += `<link type="text/css" rel="stylesheet" href="${encode(editor.documentBaseURI.toAbsolute(url))}" ${cors}>`
})
// <body>
const bodyId = Settings.getBodyId(editor)
const bodyClass = Settings.getBodyClass(editor)
const dirAttr = Settings.getDirAttr(editor)
// 禁用點擊事件
const preventClicksOnLinksScript = '<script>document.addEventListener && document.addEventListener("click", function(e) {for (var elm = e.target; elm; elm = elm.parentNode) {if (elm.nodeName === "A") {e.preventDefault();}}}, false);</script> '
// html
const html = ` <!DOCTYPE html> <html lang="zh_cn"> <head>${headHtml}</head> <body id="${encode(bodyId)}" class="mce-content-body ${encode(bodyClass)}" ${dirAttr}> ${editor.getContent()} ${preventClicksOnLinksScript} </body> </html> `
// iframe
return ` <iframe sandbox="allow-scripts allow-same-origin" frameborder="0" width="395" height="667" srcdoc="${encode(html)}"> </iframe> `
}
const getPreviewDialog = function (editor) {
let frame = getPreviewFrame(editor)
const id = 'tinymce-editor-preview-dialog-wrapper'
const closeEvent = ` onclick="document.getElementById('${id}').remove()" `
const dialog = document.createElement('div')
dialog.id = id
dialog.innerHTML = ` <div class="tinymce-editor-preview-dialog-mask"></div> <div class="tinymce-editor-preview-dialog"> <div class="tinymce-editor-preview-dialog-header"> <div class="tinymce-editor-preview-dialog-header-title">預覽</div> <div class="tinymce-editor-preview-dialog-header-close" ${closeEvent} title="關閉"> <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"> <path d="M17.953 7.453L13.422 12l4.531 4.547-1.406 1.406L12 13.422l-4.547 4.531-1.406-1.406L10.578 12 6.047 7.453l1.406-1.406L12 10.578l4.547-4.531z" fill-rule="evenodd"></path> </svg> </div> </div> <div class="tinymce-editor-preview-dialog-body">${frame}</div> <div class="tinymce-editor-preview-dialog-footer"> <div class="tinymce-editor-preview-dialog-footer-btn" ${closeEvent}>關閉</div> </div> </div> `
return dialog
}
const actionFunction = (editor, value) => {
const dialog = getPreviewDialog(editor)
if (document.getElementById(dialog.id)) {
console.warn('當前頁面只能有一個彈窗')
} else {
document.body.appendChild(dialog)
}
}
editor.addCommand('mcePreview', function () {
actionFunction(editor)
})
editor.ui.registry.addButton('preview', {
icon: 'preview',
tooltip: 'Preview',
onAction: function () {
return editor.execCommand('mcePreview')
}
})
})
複製代碼
這個插件根據編輯器官方的預覽插件改寫而成,定製化了一些樣式。如前所述,由於與官方插件重名,因此官方的插件文件將再也不會被加載。這個插件比行高插件稍微複雜了點,可是它沒有對內容進行改動。dom
開發中編輯器