• 三种插件开发模式,带你玩废tinymce


    前言

    TinyMCE是一款开源、易用、UI时新、所见即所得的富文本编辑器。是富文本领域中的佼佼者。整体设计和模式,都是非常不错的。其提供的API 极其丰富和强大,简单点 就是专业牛👍,可供广大开发者用户,方便快捷的自行进行扩展或根据实际业务需求进行二次开发。下面我将分享3种方式,带你玩废 tinymce,适合接触过 tinymce 的 小伙伴,如果没有接触过 就随便看看,收藏也不吃亏,保不齐,后面用得上,知道可以这样玩就好了😎

    利用tinymce官方提供的 UI 组件扩展

    tinymce 官方提供还算多的 UI 组件,基本满足大部分应用场景,

    组件名称描述
    addAutocompleter()注册一个新的自动完成组件。当键入时在内容中匹配配置的字符串模式时,将触发自动完成器。Emoticons 和 Charmap 使用自动完成程序。有关创建自动完成器的信息, 可以查阅: UI Components - Autocompleter.
    addButton()注册一个新的工具栏按钮,该按钮在通过键盘导航控件单击或激活时执行命令。有关创建基本工具栏按钮的信息,可以参阅: UI Components - Types of toolbar buttons: Basic button.
    addContextForm()注册一个新的上下文表单项。与上下文菜单项类似,上下文表单是在匹配内容谓词时出现输入表单元素的项。上下文表单的一个示例是使用配置 { link_context_toolbar: true } 时的链接插件。当光标位于链接上时,会出现一个上下文输入表单,允许快速更改 url 字段。有关创建上下文表单的信息,可以参阅 : UI Components - Context forms.
    addContextMenu()注册一个新的上下文菜单部分,该部分仅在匹配内容谓词时出现,例如,光标位于表内。有关创建上下文菜单的信息,可以参阅: UI Components - Context Menu.
    addContextToolbar()注册一个新的上下文工具栏,该工具栏仅在内容谓词匹配时出现,例如光标位于图像元素上。有关创建上下文工具栏的信息, 可以参阅: UI Components - Context Toolbar.
    addGroupToolbarButton()为工具栏注册一个新的组工具栏按钮。呈现一个工具栏按钮,单击该按钮会打开一个浮动工具栏。 注意:组工具栏按钮只能在使用浮动工具栏模式时使用。有关创建组工具栏按钮的信息, 可以参阅: UI Components - Types of toolbar buttons: Group toolbar button.
    addIcon()注册一个新的 SVG 图标,图标名称引用可以由任何组件配置。该图标仅适用于为其配置的编辑器实例
    addMenuButton()注册一个新的菜单按钮。添加单击时打开菜单的工具栏按钮。菜单可以由 addMenuItem、addNestedMenuItem 或 addToggleMenuItem 创建的项目填充。有关创建工具栏菜单按钮的信息, 可以参阅: UI Components - Types of toolbar buttons: Menu button.
    addMenuItem()注册一个新的菜单项,该菜单项在通过键盘导航控件单击或激活时执行命令。有关创建基本菜单项的信息, 可以参阅: UI Components - Custom menu items: Basic menu items.
    addNestedMenuItem()注册一个新菜单项,当单击或被键盘导航控件激活时显示子菜单。子菜单可以由 addMenuItem、addNestedMenuItem 或 addToggleMenuItem 创建的项目填充。有关创建嵌套菜单项的信息, 可以参阅: UI Components - Custom menu items: Nested menu items.
    addSidebar()注册一个新的侧边栏容器。此侧边栏容器附加到编辑器的右侧,可以打开或关闭。注册后,将创建一个具有相同侧边栏名称的新工具栏切换按钮。此外,还有一个 ToggleSidebar 命令和一个“ToggleSidebar”事件,可用于管理侧边栏的打开/关闭状态。tinycomments 插件为其 Ui 组件使用侧边栏。有关创建自定义侧边栏的信息,可以参阅: UI Components - Custom sidebar.
    addSplitButton()为工具栏注册一个新的拆分按钮。高级列表插件使用拆分按钮来简化其功能。有关创建拆分工具栏按钮的信息, 可以参阅: UI Components - Types of toolbar buttons: Split button.
    addToggleButton()为工具栏注册一个新的切换按钮。可以在配置中设置切换按钮状态。有关创建切换工具栏按钮的信息, 可以参阅: UI Components - Types of toolbar buttons: Toggle button.
    addToggleMenuItem()注册一个新的菜单项,其作用类似于切换按钮,在菜单项中显示一个勾号以表示状态。有关创建切换菜单项的信息, 可以参阅: UI Components - Custom menu items: Toggle menu items.

    此外对话框(dialog)也是使用频次非常高的 UI 组件,对话框(dialog)的主体必须是panel(单个面板)或tabpanel(面板的集合)。每个面板都可以包含面板组件,这些组件可以是布局组件,也可以是输入、按钮和文本等基本组件。
    通过提供的 UI 组件 你可以扩展成你想要的大部分组件

    首行缩进
    在这里插入图片描述
    字间距
    在这里插入图片描述
    段落设置
    在这里插入图片描述
    以上可以根据自己的实际业务,发挥自己的奇思妙想 去扩展成自己想要的

    利用iframe引入自定义功能页面

    利用iframe引入自定义功能页面 这种方式 ,自定义页面和tinymce本身相对独立,互不干扰,只需要控制好自定义功能页面 和 tinymce之间的数据通信就好了。
    这种开发模式,主要利用了 tinymce API的 URL 对话框 ( windowManager.openUrl(args: Object))
    URL 对话框配置具有三个主要部分,以匹配对话框 UI 的三个主要部分:

    • 标题: 对话框的标题。这将显示在对话框的标题中。
    • URL: 要在对话框中加载的外部页面的 URL。
    • 按钮:( 可选)显示在对话框页脚中的页脚按钮数组。

    主要配置参数有如下表格所示

    名称类型是否必须描述
    titlestring必需对话框的标题。
    urlstring必需要加载的外部页面的 URL。
    widthnumber可选的对话框的宽度(以像素为单位)。
    heightnumber可选对话框的高度(以像素为单位)。
    buttonsFooterButton[]可选要在对话框的页脚中呈现的一组按钮。
    onAction(dialogApi, details) => void可选单击自定义类型页脚按钮时调用的函数。
    onCancel(dialogApi) => void可选取消对话框时调用的函数。对话框标题的关闭按钮和取消类型的页脚按钮调用此函数。
    onClose() => void可选对话框关闭时调用的函数。对话框标题的关闭按钮、取消类型的页脚按钮和对话框实例 API 的close()方法调用此函数。
    onMessage(dialogApi, details) => void可选从外部页面接收到消息时调用的函数。

    更多选项可前往查看

    还是先上手 先来搭个起手式 举一个小例子 一个不可编辑的输入框组件

    先在创建一个 index.html

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>新窗口title>
    head>
    <body>
      <div>
        <input id="inputID" type="text" value="Hello Five!" placeholder="Hello Five!" oninput="onInputFn()">
      div>
      <script>
        let inputID = document.getElementById('inputID')
        //初始化 如果有值 可以替换掉
        window.parent.openUrlData?inputID.value = window.parent.openUrlData: ''//使用window.parent.openUrlData 调用父页面的数据
        window.parent.openUrlData = inputID.value
      // 更新数据
        function onInputFn(){
        window.parent.openUrlData = inputID.value
       }
      script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    核心代码

    var getOpenUrlValue = function (editor:any) {//获取选中节点的值作为初始值
      return editor.dom.getParent(editor.selection.getNode(), '[data-tp-open-url]')?.innerText
    }
      //@ts-ignore
     window.openUrlData = getOpenUrlValue(editor)||'' // 利用window挂上全局 openUrlData 在iframe的子页面 使用window.parent.openUrlData 调用
      editor.windowManager.openUrl({
    			title: 'openUrl',
                width: 300,
    			url: '/openUrl/index.html',//根据自己的实际路径来
    			buttons: [
    				{
    					type: 'cancel',
    					text: 'Close'
    				},
    				{
    					type: 'custom',
    					text: 'Save',
    					name: 'save',
    					primary: true
    				},
    			],
    			onAction: function (api:any, details:any) {
    				switch (details.name) {
    					case 'save':
    		              //@ts-ignore
    		              editor.insertContent(''+window.openUrlData+'')
    		              api.close();
    						break;
    					default:
    						break;
    				}
    				
    			}
    		});
    }
    
    • 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

    效果如下图所示
    在这里插入图片描述
    当然基本搭好后开始美化,加上上下文菜单 和 按钮状态

    添加按钮核心代码

    //状态选择器适配器 用于更新按钮状态
    const stateSelectorAdapter = function (editor:any, selector:any) {
      return function (buttonApi:any) {
        return editor.selection.selectorChangedWithUnbind(selector.join(','),buttonApi.setActive).unbind;
      };
    }
      editor.ui.registry.addToggleButton('tp-openurl', {
        icon: 'tp-openurl',
        tooltip: 'Open Url',
        onSetup: stateSelectorAdapter(editor, [
          '*[data-tp-open-url]',
        ]),
        onAction: () => Main.create(editor,opt)
      });
    
      var cmd = function (command:string) {
        return function () {
          return editor.execCommand(command);
        };
      };
      //注册删除OpenUrl按钮
      editor.ui.registry.addButton('tpOpenUrlDelete', {
        tooltip: 'Delete',
        onAction: cmd('mceTpOpenUrlDelete'),
        icon: 'table-delete-table',
      });
      //注册编辑OpenUrl按钮
      editor.ui.registry.addButton('tpOpenUrlProps ', {
        tooltip: 'Edit Open Url',
        onAction: cmd('tpOpenUrl'),
        icon: 'tp-openurl',
      });
    //注册编辑OpenUrl 删除逻辑
      editor.addCommand('mceTpOpenurlDelete', function () { 
        try {
          editor.dom.getParent(editor.selection.getNode(), '[data-tp-open-url]').remove() //获得选中节点 删除即可
          editor.focus();
        } catch (error) {
          
        } 
      })
      //上下文菜单配置 (已注册的按钮)
      var openurlToolbar =  'tpOpenUrlProps \| tptpOpenUrlDelete'
      if (openurlToolbar.length > 0) {
        var isOpenurl = function (_openurl:any) {//判断是否是 Openurl 用于触发上下文菜单
            return editor.dom.is(_openurl, '[data-tp-open-url]') && editor.getBody().contains(_openurl);
        };
        //注册上下文菜单
        editor.ui.registry.addContextToolbar('tp-openurl', {
          predicate: isOpenurl,
          items: openurlToolbar,
          scope: 'node',
          position: 'node'
        });
      }
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    请添加图片描述
    以上就是一个简单的小例子 可以自行扩展,例如接入 LogicFlow 流程图框架 ,开发成一个流程图插件,流程图插件,流程图部分是在vue2.0中开发,打包后通过 tinymce API URL 对话框 ( windowManager.openUrl(args: Object))
    引入核心代码

    var getLogicflowValue = function (editor:any) {
      let objectVal = editor.dom.getParent(editor.selection.getNode(), '[data-tp-logicflow]')
           
      if(objectVal){
       
        try {
                    //@ts-ignore
                    window.tpLogicflow.tp_initData = objectVal.firstChild || ''
                  } catch (error) {
                    //@ts-ignore
                    window.tpLogicflow.tp_initData = ''
                  }
     
      }else{
        //@ts-ignore
        window.tpLogicflow.tp_initData =''
      }
     
     };
     
       getLogicflowValue(editor)
      editor.windowManager.openUrl({
           title: data.name,
           size: 'large',
           url: '/logicflow/index.html',
           buttons: [
             {
               type: 'cancel',
               text: 'Close'
             },
             {
               type: 'custom',
               text: 'Save',
               name: 'save',
               primary: true
             },
           ],
           onAction: function (api:any, details:any) {
             switch (details.name) {
               case 'save'
                 //@ts-ignore
                 window.tpLogicflow.saveGraphSVGData().then(res=>{
                   editor.insertContent('+res.url+'"  style="'+res.style+'"/>')
                   api.close();
                 break;
               default:
                 break;
             }
             
           }
         });
    
    • 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
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    请添加图片描述

    利用 webCompontents 硬核扩展

    想知道如何利用 webCompontents 扩展,哪肯定得先了解一下 webCompontents(必须得看 ,了解过的 麻烦再看一遍 😨)

    什么是 Web Components?

    Web Components 是一种 W3C标准 支持的组件化方案,通过它,我们可以编写可复用的 组件,同时,就如同写单页VUE模板,他并非一项单一的技术,而是由三项技术组成:

    Custom elements(自定义元素):一组 JavaScript API,允许您定义 custom elements 及其行为,然后可以在您的用户界面中按照需要使用它们。
    Shadow DOM(影子 DOM):一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被 脚本化样式化,而不用担心与文档的其他部分发生冲突。
    HTML templates(HTML 模板):