tinymce 编辑器踩坑之旅----------自定义插件

857 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

针对之前写的要更新一下,因为一直在本地开发,所以没想到最后打包会出现问题

这很显然,就是文件路径找不到了。

所以针对之前为了开发需求而来的办法显然是行不通的,就算行得通了,也会给自己留下大坑。

所以针对路径问题,我想了很多办法,显示换个地方放到public目录下面,打包之后还是找不到文件,依旧存在上述截图问题。

既然文件里面是require文件的,所以修改html问价 使用script方式引入是没问题的,但是总觉得在vue项目里面不应该这么干,于是找了老大讨论了一下。

针对vue-cli3官网和tinymce官网的文档来看,其实编辑器和我拆分写组件似乎没什么问题,于是我们可以把之前的js代码直接写成一个function,然后export一下,在使用的组件里面import之后调用一下,再试试打包。

这样一来,直接挂载都不用了,可以直接调用,然后打包,运行成功。

所以项目开发中遇到要自定义插件的话,千万不要投机取巧,按照规范开发,给自己避免大坑。

————————以下是原文章—————— 先贴个官网地址 www.tiny.cloud/docs/

最近开发项目中产品有很多需求,比如说在编辑器里面html代码,本来一开始用的是vue集成的那个编辑器,然后就发现,不能显示table!!!!样式

无奈之下找了找好像tinymce这个编辑器可以满足,但是在此之前没用过,所以去通读了一边官方网址,了解了一些基本东西。

然后开干

最开始么肯定是先安装一下插件

npm install tinymce/tinymce-vue --save npm install tinymce --save 1 2 然后在src文件夹下面创建components文件夹,在此文件夹下面创建我们要用的富文本编辑器的组件文件。

根目录下建一个index.js文件夹,输出公共组件

import Vue from 'vue' const componentList = require.context('.', true, /index.vue/)

const hyphenate = (name) => { return name.replace(/\B([A-Z])/g, '-$1').toLowerCase() }

componentList.keys().forEach(item => { const componentItem = componentList(item).default Vue.component(hyphenate(componentItem.name), componentItem) }) 1 2 3 4 5 6 7 8 9 10 11 tinymce 编辑器代码 ,组件传值什么都是自己封装的,所以根据需求而定 注意,全局这样输出组件名称的时候,文件内的name一定要和文件夹名称同名,否则就会报错

用法就直接在需要的地方注入就可以了

因为产品需求可能需要拔取别的网页下来改东西,所以很多扒下来的页面的样式写在内联或者style标签里面,所以导致了样式不显示!!!

为了解决这个问题翻遍官网,最后终于有一个可以实现,初始化的时候加上下面的代码

custom_elements:"style,html,head,body,title,meta",//允许自定义标签存在 1 只要加上这句,页面就可以正常回显

紧接着就是toolbar工具栏的问题,官网已经说的很清楚,toolbar可以根据自己需求而去配置相关的工具,所以有些需要引入一些插件

然后很有可能它现有的插件功能不是产品想要的,所以你还得改代码。

翻遍官网没有发现可以手动更在toolbar插件的方法,但是可以自定义,所以这个就需要自己动动手指改一下。

首先找到要改动的源码,复制一份放到static文件夹下面,建一个plugins的文件夹

我这里改的是插入链接的方法。

复制好了以后便可以对代码进行更改,于是需要熟读一边源码,不然牵一发动全身,console报错一片红。

自己自定义富文本编辑器插件的时候,需要挂在以下tinymce,不然报错,tinymce is not undefiend

import tinymce from 'tinymce/tinymce' window.tinymce = tinymce 1 2 修改toolbar最重要的几个地方需要记录一下

第一个,引入插件

import '@/static/plugins/tinymce/fishLink'

1 2 使用

自定义一个Dialog对话框,也就是根据工具栏的icon 弹出窗。

var makeDialog = function (settings, onSubmit, editor) { var displayText = settings.anchor.text.map(function () { return { name: 'text', type: 'input', label: '请输入文本,该文本将会被转为超链接;点击后将跳转为您所配置的钓鱼页面' }; }).toArray(); var defaultTarget = Optional.from(getDefaultLinkTarget(editor)); var initialData = getInitialData(settings, defaultTarget); var catalogs = settings.catalogs; var dialogDelta = DialogChanges.init(initialData, catalogs); var body = { type: 'panel', items: flatten([ displayText ]) }; return { title:'Insert Link', size: 'normal', body: body, buttons: [ { type: 'cancel', name: 'cancel', text: 'Cancel' }, { type: 'submit', name: 'save', text: '添加', primary: true } ], initialData: initialData, onChange: function (api, _a) { var name = _a.name; dialogDelta.onChange(api.getData, { name: name }).each(function (newData) { api.setData(newData); }); }, onSubmit: onSubmit }; }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 editor.windowManager.open 打开弹窗 editor.ui.registry.addButton 注册按钮 editor.ui.registry.addMenuItem

组件代码:

// 引入你需要的插件 import 'tinymce/plugins/preview' import 'tinymce/plugins/code' import '@/static/plugins/tinymce/fishLink' import 'tinymce/plugins/codesample' import 'tinymce/plugins/image' import 'tinymce/icons/default/icons.min.js'

export default { name: 'tinyEditor', components: { Editor }, props: { defaultValue: { type: String, default: '' }, disabled: { type: Boolean, default: false }, toolbar:{ type: String, default: '' } }, watch: { //这是设置的传入值,在富文本里需要回显的内容 defaultValue (newValue) { this.value = newValue }, }, data () { return { value: this.defaultValue, fishLink:'', //这是初始化的配置 init: { visual: false, resize: 'both', language_url: '/tinymce/langs/zh_CN.js', // 语言包的路径 language: 'zh_CN', // 语言 convert_urls: false, skin_url: '/tinymce/skins/ui/oxide', // skin路径 height: 400, // 编辑器高度 branding: false, // 是否禁用“Powered by TinyMCE” menubar: false, // 顶部菜单栏显示, keep_styles: true, custom_elements:"style,html,head,body,title,meta",//自定义允许标签存在 plugins: [ 'image code link codesample ' ], toolbar: this.toolbar, paste_data_images: true, // 图片是否可粘贴 elementpath: false, content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }', images_upload_handler: (blobInfo, success, failure) => { if (blobInfo.blob().size / 1024 / 1024 > 5) { failure('上传失败,图片大小请控制在 5M 以内') } else { const formData = new FormData() formData.append('file', blobInfo.blob(), blobInfo.filename()) upload(formData).then(res => { if (res.errorCode === "00000") { if (res.data) { // this.$message.success('图片上传成功') success('/'+ res.data.fileSavePath) } } }) } } } } }, mounted () { tinymce.init({}) }, methods: { getContent () { return this.value } } }

.tox { .tox-editor-container .tox-tbtn{ background-color: transparent; &.tox-tbtn--enabled{ background-color: #EFEFEF; svg { fill: @primary-color; transition: fill .3s; } } &:hover{ cursor: pointer; background-color: #EFEFEF; svg { fill: @primary-color; transition: fill .3s; } } &:active{ background-color: transparent; } } .tox-dialog-wrap{ .tox-dialog{ max-width: 560px; position: relative; background-color: #fff; background-clip: padding-box; border: 0; border-radius: 4px; box-shadow: 0 4px 12px rgb(0 0 0 / 15%); pointer-events: auto; } .tox-dialog__header{ padding: 16px 24px; color: rgba(0,0,0,.65); background: #fff; border-bottom: 1px solid #e8e8e8; border-radius: 4px 4px 0 0; .tox-dialog__title { margin: 0; color: rgba(0,0,0,.85); font-weight: 500; font-size: 16px; line-height: 22px; word-wrap: break-word; } .tox-button--naked{ position: absolute; top: 0; right: 0; z-index: 10; padding: 0; color: rgba(0,0,0,.45); font-weight: 700; line-height: 1; text-decoration: none; background: transparent; border: 0; outline: 0; &:hover{ cursor: pointer; background-color: transparent; } transition: color .3s; .tox-icon{ display: block; width: 56px; height: 56px; font-size: 16px; font-style: normal; line-height: 56px; text-align: center; text-transform: none; text-rendering: auto; svg{ display: inline-block; color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -.125em; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased } } } } .tox-button.tox-button--icon{ vertical-align: 8px; background-color: transparent !important; border-color: transparent !important; &:hover{ background-color: transparent; border-color: transparent; .tox-lock-icon__lock{ color: @primary-color; } } } .tox-dialog__body-content{ // min-height: 245px; .tox-button--secondary{ color: #fff; background: @primary-color; line-height: 1.499; position: relative; display: inline-block; font-weight: 400; white-space: nowrap; text-align: center; background-image: none; box-shadow: 0 2px 0 rgb(0 0 0 / 2%); cursor: pointer; transition: all .3s cubic-bezier(.645,.045,.355,1); -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; touch-action: manipulation; height: 32px; padding: 0 15px; font-size: 14px; border-radius: 4px; &:hover{ color: rgba(222,222,222,.65); background: @primary-color; } } .tox-dropzone p{ font-size: 14px; font-variant: tabular-nums; line-height: 1.5; list-style: none; color: #666; font-feature-settings: "tnum"; } .tox-label{ margin-top: 10px; margin-bottom: 16px; overflow: hidden; white-space: nowrap; text-align: left; font-size: 14px; vertical-align: middle; color: rgba(0,0,0,.85); } .tox-textfield{ box-sizing: border-box; margin-bottom: 8px; font-variant: tabular-nums; list-style: none; font-feature-settings: "tnum"; position: relative; display: inline-block; width: 100%; height: 32px; padding: 4px 11px; color: rgba(0,0,0,.65); font-size: 14px; line-height: 1.5; background-color: #fff; background-image: none; border: 1px solid #d9d9d9; border-radius: 4px; transition: all .3s; } } .tox-dialog__body-nav-item--active, .tox-dialog__body-nav-item--active:focus, .tox-dialog__body-nav-item:focus{ border-color: transparent; color: @primary-color; background: transparent; } .tox-dialog__body-nav-item:hover{ cursor: pointer; color: @primary-color; transition: color .3s; } .tox-dialog__footer{ padding: 10px 16px; text-align: right; background: transparent; border-top: 1px solid #e8e8e8; border-radius: 0 0 4px 4px; .tox-dialog__footer-end{ .tox-button{ line-height: 1.499; position: relative; display: inline-block; font-weight: 400; white-space: nowrap; text-align: center; background-image: none; box-shadow: 0 2px 0 rgb(0 0 0 / 2%); cursor: pointer; transition: all .3s cubic-bezier(.645,.045,.355,1); -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; touch-action: manipulation; height: 32px; padding: 0 15px; font-size: 14px; border-radius: 4px; color: #fff; background-color: @primary-color; border-color: @primary-color; &.tox-button--secondary{ color: rgba(0,0,0,.65); background-color: #fff; border: 1px solid #d9d9d9; text-shadow: 0 -1px 0 rgb(0 0 0 / 12%); box-shadow: 0 2px 0 rgb(0 0 0 / 5%); } } } } } } .tox-tinymce{ max-width: 115%; }