【问题标题】:Can I use Polymer elements / WebComponents with TinyMCE?我可以在 TinyMCE 中使用 Polymer 元素/WebComponents 吗?
【发布时间】:2016-08-03 02:56:43
【问题描述】:

我正在尝试为教学内容构建一个自定义 TinyMCE 编辑器,该编辑器将允许将某些块包装为“活动”。一个内容块中会有多个活动,因此它们会将 ID 作为主键等。

我的挑战是实现一个允许这样做的插件——理想情况下,我会使用短代码,但它们很容易出错。我正在研究使用将通过 Polymer 呈现的自定义 HTML 标签——可以这样做吗?

【问题讨论】:

    标签: javascript tinymce polymer web-component


    【解决方案1】:

    大约 4 小时后我完全解决了。

    TinyMCE 编辑器需要初始化为支持自定义元素,如下所示:

    {
    ...
        extended_valid_elements : 'module-activity',
        custom_elements : 'module-activity',
        init_instance_callback: function(editor) {
            registerCustomWebComponents(tinymce.activeEditor.dom.doc);
        },
    ...
    }
    

    registerCustomWebComponents 的样子:

    function registerCustomWebComponents(doc) {
      doc.registerElement('module-activity', ModuleActivityHTMLElement);  
    }
    

    我最终定义了自定义 HTML 元素,然后定义了一个 React 组件,而不是将 HTMl 构建为字符串。

    class ModuleActivity extends React.Component {
      constructor(props) {
        super(props);
        this.openActivityEdit = this.openActivityEdit.bind(this);
      }
    
      openActivityEdit() {
    
      }
    
      render() {
        return <div>
          <h3>Module Activity</h3>
          <button onClick={this.openActivityEdit}>Edit</button>
          <div dangerouslySetInnerHTML={{__html: this.props.contentHtml }} />
        </div>;
      }
    }
    
    
    class ModuleActivityHTMLElement extends HTMLElement {
      attachedCallback() {
        let self = this;
        var mountPoint = document.createElement('div');
        this.createShadowRoot().appendChild(mountPoint);
        ReactDOM.render(<ModuleActivity contentHtml={self.innerHTML}/>, mountPoint);
      }
    }
    

    【讨论】:

    • 能否将示例放在 codepen.io 或其他地方?
    【解决方案2】:

    这里的答案是不要在聚合物中使用 tinymce,tinymce 严重依赖文档根目录,shadow dom 会破坏它。

    但就像生活中的一切一样,一切都不会丢失......

    在您的聚合物模板中使用一个对象,让该对象加载 tinymce 并解决文档根问题。一旦以这种方式加载,您就可以从对象访问 tinymce。

    创建一个 HTML 文件来加载 tinymce

    <!DOCTYPE html>
    <html>
    
    <head>
        <script src="https://cloud.tinymce.com/stable/tinymce.min.js"></script>
    
        <style>
            html { height: 96%; }
            body { height: 96%; }
        </style>
    </head>
    
    <body>
        <textarea>Loading...</textarea>
        <script>
            var ta = document.querySelector('textarea');
            ta.tinymce = tinymce;
            var editorChangeHandler;
            tinymce.init({ selector: 'textarea', height: document.body.scrollHeight - 100, setup: function (editor) {
                editor.on('Paste Change input Undo Redo', function () {
                    if (editorChangeHandler) { clearTimeout(editorChangeHandler); }
                    editorChangeHandler = setTimeout(function () {
                        ta.dispatchEvent(new CustomEvent('save'));
                    }, 2000);
                });
            } });
        </script>
    </body>
    
    </html>
    

    现在你只需要在你的组件模板中添加一个对象,使用对象的data属性来加载html。

    加载后,您可以访问它,查询对象 dom 并获取 textarea,添加事件侦听器以保存自定义事件并预先设置内容,调整高度。我只测试了同一个域,因此请注意这可能会破坏跨域,但无论如何您都希望与其他组件一起提供服务。

    将您的对象添加到组件模板中

    <object id="editor" type="text/html" data="/src/lib/tinymce/tinymce.html"></object>
    

    还有一些预加载、抓取东西、设置高度和捕捉保存的方法

    ready() {
        super.ready();
    
        // wait five seconds before capturing input
        var interval = setInterval(() => {
            if (!this.$.editor.contentDocument.body) return;
            let ta = this.$.editor.contentDocument.body.querySelector('textarea');
            if (!ta || !ta.tinymce || !ta.tinymce.activeEditor) return;
    
            // clear interval now loaded
            window.clearInterval(interval);
    
            setTimeout(() => {
                // resize on window change
                window.addEventListener('resize', this._updateEditorSize.bind(this));
    
                // pre load
                ta.tinymce.activeEditor.setContent(this.element.value);
    
                // catch updates every few seconds, this will then have a 4 second debounce on save too naturally
                ta.addEventListener('save', (ev) => {
                    this.set('element.value', ta.tinymce.activeEditor.getContent({ format: 'raw' }));
                });
            }, 250);
        }, 250);
    }
    

    这是聚合物 3 和 tinymce 的工作场景,可快速加载、自动调整大小并从对象捕获保存事件,我不必更改 tinymce 的默认设置。您还可以将此方法用于其他方式来绕过某些嵌入式应用程序的 shadow dom。

    【讨论】:

      【解决方案3】:

      @liamzebedee 提供的解决方案有点过时,这里是一个带有 ES6 模块和标准 Web 组件的解决方案 au goût du jour

      与上面代码的主要区别是在定义 Web 组件的脚本的 TinyMCE &lt;iframe/&gt; 中注入。否则,接受的标签将保持惰性。

      首先,这是与TinyMCE初始化相关的一段代码:

      const name = 'custom-element';
      const attribute = 'view-mode';
      const insertTag = '<custom-element view-mode="editing"></custom-element>';
      const definitionFile = './custom-element.js';
      
      tinymce.PluginManager.add(name, function(editor, url) {
          editor.ui.registry.addButton(name, {
              text: name,
              onAction: () => editor.insertContent(insertTag)
          });
      });
      
      tinymce.init({
          // ...
      
          custom_elements: name, // just the custom Web element names
          extended_valid_elements: `${name}[${attribute}]`, // names+attributes
      
          init_instance_callback: function(editor) {
              const edDoc = editor.getDoc();
              const scriptTag = edDoc.createElement('script');
              scriptTag.src = definitionFile;
              scriptTag.type = 'module';
              // Injection of the definition file in the <iframe/> document
              edDoc.querySelector('head').appendChild(scriptTag);
          }
      });
      

      对应的(最小的)Web组件定义为:

      export class SimpleNumber extends HTMLElement {
      
          constructor() {
              super();
      
              // Add a simple <input/> field in the ShadowRoot
              this.attachShadow({mode: 'open'}).innerHTML = `
                  <style>
                      :host { display:inline-block; }
                      input { border: 1px solid blue; font-size: 12pt; text-align: right; }
                  </style>
                  <input type="number" min="0" max="10" value="0" />
              `;
      
              // Provide some feedback when the  <input/> field value changes
              this.shadowRoot.querySelector('input').addEventListener('change', (event) => {
                  const field = event.target;
                  field.style.backgroundColor = field.value === '3' ? 'lightgreen' : 'orange';
              });
          }
      }
      
      if (window.customElements && !window.customElements.get('simple-number')){
          window.customElements.define('simple-number', SimpleNumber);
      }
      

      请注意,我无法在 codepen.io 上提供运行示例(例如),因为我不能为自定义元素定义(需要注入 @ 987654324@)…

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-04
        • 2018-08-14
        • 2017-03-28
        • 1970-01-01
        • 1970-01-01
        • 2015-09-16
        • 2015-07-03
        • 1970-01-01
        相关资源
        最近更新 更多