javascript - 如何在 Summernote 中插入占位符元素?

标签 javascript jquery iframe summernote

我正在为 Summernote 所见即所得编辑器(版本 0.8.1)开发一个插件,以将 iframe 元素插入代码中。

使用提供的示例,我设法在菜单中获得了插件按钮,它打开了一个对话框,我可以在其中输入 URL 和标题。给源码加个iframe Tag是没问题的,但这不是我想要的。

我想在代码中插入一个占位符,其标记类似于(或类似于):

<div class="ext-iframe-subst" data-src="http://www.test.example" data-title="iframe title"><span>iframe URL: http://www.test.example</span></div>

现在,summernote 允许我编辑跨度的内容,但我想要一个占位符,它不能在编辑器中修改。

如何在具有以下属性的编辑器中插入占位符:
  • 它可以作为单个 block 进行编辑(可以通过单个删除来删除)
  • 单击时,我可以打开类似于链接或图像弹出框的弹出框来调整大小 f.i。
  • 内部内容不可修改

  • 这是我到目前为止所拥有的:
    // Extends plugins for adding iframes.
    //  - plugin is external module for customizing.
    $.extend($.summernote.plugins, {
      /**
       * @param {Object} context - context object has status of editor.
       */
      'iframe': function (context) {
        var self = this;
    
        // ui has renders to build ui elements.
        //  - you can create a button with `ui.button`
        var ui = $.summernote.ui;
    
        var $editor = context.layoutInfo.editor;
        var options = context.options;
        var lang = options.langInfo;
    
        // add context menu button
        context.memo('button.iframe', function () {
          return ui.button({
            contents: '<i class="fa fa-newspaper-o"/>',
            tooltip: lang.iframe.iframe,
            click: context.createInvokeHandler('iframe.show')
          }).render();
        });
    
    
        // This events will be attached when editor is initialized.
        this.events = {
          // This will be called after modules are initialized.
          'summernote.init': function (we, e) {
            console.log('IFRAME initialized', we, e);
          },
          // This will be called when user releases a key on editable.
          'summernote.keyup': function (we, e) {
            console.log('IFRAME keyup', we, e);
          }
        };
    
        // This method will be called when editor is initialized by $('..').summernote();
        // You can create elements for plugin
        this.initialize = function () {
          var $container = options.dialogsInBody ? $(document.body) : $editor;
    
          var body = '<div class="form-group row-fluid">' +
              '<label>' + lang.iframe.url + '</label>' +
              '<input class="ext-iframe-url form-control" type="text" />' +
              '<label>' + lang.iframe.title + '</label>' +
              '<input class="ext-iframe-title form-control" type="text" />' +
              '<label>' + lang.iframe.alt + '</label>' +
              '<textarea class="ext-iframe-alt form-control" placeholder="' + lang.iframe.alttext + '" rows=""10""></textarea>' +
              '</div>';
          var footer = '<button href="#" class="btn btn-primary ext-iframe-btn disabled" disabled>' + lang.iframe.insert + '</button>';
    
          this.$dialog = ui.dialog({
            title: lang.iframe.insert,
            fade: options.dialogsFade,
            body: body,
            footer: footer
          }).render().appendTo($container);
        };
    
        // This methods will be called when editor is destroyed by $('..').summernote('destroy');
        // You should remove elements on `initialize`.
        this.destroy = function () {
          this.$dialog.remove();
          this.$dialog = null;
        };
    
    
        this.bindEnterKey = function ($input, $btn) {
          $input.on('keypress', function (event) {
            if (event.keyCode === 13) { //key.code.ENTER) {
              $btn.trigger('click');
            }
          });
        };
    
    
    
        this.createIframeNode = function (data) {
          var $iframeSubst = $('<div class="ext-iframe-subst"><span>' + lang.iframe.iframe + '</span></div>');
    
          $iframeSubst.attr("data-src", data.url).attr("data-title", data.title);
    
          return $iframeSubst[0];
        };
    
    
        this.show = function () {
          var text = context.invoke('editor.getSelectedText');
          context.invoke('editor.saveRange');
    
          console.log("iframe.getInfo: " + text);
    
          this
            .showIframeDialog(text)
            .then(function (data) {
              // [workaround] hide dialog before restore range for IE range focus
              ui.hideDialog(self.$dialog);
              context.invoke('editor.restoreRange');
    
              // build node
              var $node = self.createIframeNode(data);
    
              if ($node) {
                // insert iframe node
                context.invoke('editor.insertNode', $node);
              }
            })
            .fail(function () {
              context.invoke('editor.restoreRange');
            });
    
        };
    
        this.showIframeDialog = function (text) {
          return $.Deferred(function (deferred) {
            var $iframeUrl = self.$dialog.find('.ext-iframe-url');
            var $iframeTitle = self.$dialog.find('.ext-iframe-title');
            var $iframeBtn = self.$dialog.find('.ext-iframe-btn');
    
            ui.onDialogShown(self.$dialog, function () {
              context.triggerEvent('dialog.shown');
    
              $iframeUrl.val(text).on('input', function () {
                ui.toggleBtn($iframeBtn, $iframeUrl.val());
              }).trigger('focus');
    
              $iframeBtn.click(function (event) {
                event.preventDefault();
    
                deferred.resolve({ url: $iframeUrl.val(), title: $iframeTitle.val() });
              });
    
              self.bindEnterKey($iframeUrl, $iframeBtn);
            });
    
            ui.onDialogHidden(self.$dialog, function () {
              $iframeUrl.off('input');
              $iframeBtn.off('click');
    
              if (deferred.state() === 'pending') {
                deferred.reject();
              }
            });
    
            ui.showDialog(self.$dialog);
          });
        };
    
    
      }
    });
    
    // add localization texts
    $.extend($.summernote.lang['en-US'], {
        iframe: {
          iframe: 'iframe',
          url: 'iframe URL',
          title: 'title',
          insert: 'insert iframe',
          alt: 'Text alternative',
          alttext: 'you should provide a text alternative for the content in this iframe.',
          test: 'Test'
        }
    });
    

    最佳答案

    您可以使用 contenteditable 您的 span 上的属性元素,它将工作并保持 iframe编辑器中的插件 HTML,当按下 Del 或 Backspace 键时,它将删除整个 block 。
    Github repository 中有一些演示插件并且有一个演示了对话框和弹出框编辑的用法,您可以检查逻辑和代码here .
    createIframeNode我们创建元素并设置数据属性

    this.createIframeNode = function (data) {
      var $iframeSubst = $(
        '<div class="ext-iframe-subst"><span contenteditable="false">' +
        lang.iframe.url + ': ' + data.url +
        '</span></div>'
      );
    
      $iframeSubst
        .attr("data-src", data.url)
        .attr("data-title", data.title);
    
      return $iframeSubst[0];
    };
    
    我们还创建了 currentEditing变量以在弹出菜单弹出时保存光标下的元素,因此 opoup 对话框将知道我们正在编辑一个元素而不是创建一个新元素。
    updateIframeNode我们正在使用 currentEditing要更新的元素
    这里我们只重新创建 span元素,因为 currentEditing是实际的 div.ext-iframe-subst然后我们更新数据属性:
    this.updateIframeNode = function (data) {
      $(currentEditing).html(
        '<span contenteditable="false">' +
        lang.iframe.url + ': ' + data.url +
        '</span>'
      )
    
      $(currentEditing)
        .attr("data-src", data.url)
        .attr("data-title", data.title);
    }
    
    完整的工作插件
    运行代码片段并尝试使用带有方形图标的按钮插入 iframe。您可以编辑现有的 iFrame 元素,并且 block 会一起删除。

    /**
     * @param {Object} context - context object has status of editor.
     */
    var iframePlugin = function (context) {
      var self = this;
    
      // ui has renders to build ui elements.
      //  - you can create a button with `ui.button`
      var ui = $.summernote.ui;
      var dom = $.summernote.dom;
    
      var $editor = context.layoutInfo.editor;
      var currentEditing = null;
      var options = context.options;
      var lang = options.langInfo;
    
      // add context menu button
      context.memo('button.iframe', function () {
        return ui.button({
          contents: '<i class="note-icon-frame"/>',
          tooltip: lang.iframe.iframe,
          click: (event) => {
            currentEditing = null;
            context.createInvokeHandler('iframe.show')(event);
          }
        }).render();
      });
    
      context.memo('button.iframeDialog', function () {
        return ui.button({
          contents: '<i class="note-icon-frame"/>',
          tooltip: lang.iframe.iframe,
          click: (event) => {
            context.createInvokeHandler('iframe.show')(event);
            // currentEditing
          }
        }).render();
      });
    
    
      // This events will be attached when editor is initialized.
      this.events = {
        // This will be called after modules are initialized.
        'summernote.init': function (we, e) {
          $('data.ext-iframe', e.editable).each(function() { self.setContent($(this)); });
        },
        // This will be called when user releases a key on editable.
        'summernote.keyup summernote.mouseup summernote.change summernote.scroll': function() {
          self.update();
        },
        'summernote.dialog.shown': function() {
          self.hidePopover();
        },
      };
    
      // This method will be called when editor is initialized by $('..').summernote();
      // You can create elements for plugin
      this.initialize = function () {
        var $container = options.dialogsInBody ? $(document.body) : $editor;
    
        var body = '<div class="form-group row-fluid">' +
            '<label>' + lang.iframe.url + '</label>' +
            '<input class="ext-iframe-url form-control" type="text" />' +
            '<label>' + lang.iframe.title + '</label>' +
            '<input class="ext-iframe-title form-control" type="text" />' +
            // '<label>' + lang.iframe.alt + '</label>' +
            // '<textarea class="ext-iframe-alt form-control" placeholder="' + lang.iframe.alttext + '" rows=""10""></textarea>' +
            '</div>';
        var footer = '<button href="#" class="btn btn-primary ext-iframe-btn disabled" disabled>' + lang.iframe.insertOrUpdate + '</button>';
    
        this.$dialog = ui.dialog({
          title: lang.iframe.insert,
          fade: options.dialogsFade,
          body: body,
          footer: footer
        }).render().appendTo($container);
    
        // create popover
        this.$popover = ui.popover({
          className: 'ext-iframe-popover',
        }).render().appendTo('body');
        var $content = self.$popover.find('.popover-content');
    
        context.invoke('buttons.build', $content, options.popover.iframe);
      };
    
      // This methods will be called when editor is destroyed by $('..').summernote('destroy');
      // You should remove elements on `initialize`.
      this.destroy = function () {
        self.$popover.remove();
        self.$popover = null;
        self.$dialog.remove();
        self.$dialog = null;
      };
    
    
      this.bindEnterKey = function ($input, $btn) {
        $input.on('keypress', function (event) {
          if (event.keyCode === 13) { //key.code.ENTER) {
            $btn.trigger('click');
          }
        });
      };
    
      self.update = function() {
        // Prevent focusing on editable when invoke('code') is executed
        if (!context.invoke('editor.hasFocus')) {
          self.hidePopover();
          return;
        }
    
        var rng = context.invoke('editor.createRange');
        var visible = false;
        var $data = $(rng.sc).closest('div.ext-iframe-subst');
    
        if ($data.length) {
          currentEditing = $data[0];
          var pos = dom.posFromPlaceholder(currentEditing);
          const containerOffset = $(options.container).offset();
          pos.top -= containerOffset.top;
          pos.left -= containerOffset.left;
    
          self.$popover.css({
            display: 'block',
            left: pos.left,
            top: pos.top,
          });
    
          // save editor target to let size buttons resize the container
          context.invoke('editor.saveTarget', currentEditing);
    
          visible = true;
        }
    
        // hide if not visible
        if (!visible) {
          self.hidePopover();
        }
      };
    
      self.hidePopover = function() {
        self.$popover.hide();
      };
    
      this.createIframeNode = function (data) {
        var $iframeSubst = $(
          '<div class="ext-iframe-subst"><span contenteditable="false">' +
          lang.iframe.url + ': ' + data.url +
          '</span></div>'
        );
    
        $iframeSubst.attr("data-src", data.url).attr("data-title", data.title);
        return $iframeSubst[0];
      };
    
      this.updateIframeNode = function (data) {
        $(currentEditing).html(
          '<span contenteditable="false">' +
          lang.iframe.url + ': ' + data.url +
          '</span>'
        )
    
        $(currentEditing).attr("data-src", data.url).attr("data-title", data.title);
      }
    
      this.show = function () {
        var text = context.invoke('editor.getSelectedText');
        context.invoke('editor.saveRange');
    
        this
          .showIframeDialog(text)
          .then(function (data) {
            // [workaround] hide dialog before restore range for IE range focus
            ui.hideDialog(self.$dialog);
            context.invoke('editor.restoreRange');
    
            if (currentEditing) {
              self.updateIframeNode(data);
            } else {
              // build node
              var $node = self.createIframeNode(data);
    
              if ($node) {
                // insert iframe node
                context.invoke('editor.insertNode', $node);
              }
            }
          })
          .fail(function () {
            context.invoke('editor.restoreRange');
          });
      };
    
      this.showIframeDialog = function (text) {
        return $.Deferred(function (deferred) {
          var $iframeUrl = self.$dialog.find('.ext-iframe-url');
          var $iframeTitle = self.$dialog.find('.ext-iframe-title');
          var $iframeBtn = self.$dialog.find('.ext-iframe-btn');
    
          ui.onDialogShown(self.$dialog, function () {
            context.triggerEvent('dialog.shown');
    
            var dataSrc = currentEditing ? $(currentEditing).attr('data-src') : '';
            var dataTitle = currentEditing ? $(currentEditing).attr('data-title') : '';
    
            $iframeTitle.val(dataTitle);
            $iframeUrl.val(dataSrc).on('input', function () {
              ui.toggleBtn($iframeBtn, $iframeUrl.val());
            }).trigger('focus');
    
            $iframeBtn.click(function (event) {
              event.preventDefault();
    
              deferred.resolve({ url: $iframeUrl.val(), title: $iframeTitle.val() });
            });
    
            self.bindEnterKey($iframeUrl, $iframeBtn);
          });
    
          ui.onDialogHidden(self.$dialog, function () {
            $iframeUrl.off('input');
            $iframeBtn.off('click');
    
            if (deferred.state() === 'pending') {
              deferred.reject();
            }
          });
    
          ui.showDialog(self.$dialog);
        });
      };
    }
    
    // Extends plugins for adding iframes.
    //  - plugin is external module for customizing.
    $.extend(true, $.summernote, {
      plugins: {
        iframe: iframePlugin,
      },
      options: {
        popover: {
          iframe: [
            ['iframe', ['iframeDialog']],
          ],
        },
      },
      lang: {
        'en-US': {
          iframe: {
            iframe: 'iframe',
            url: 'iframe URL',
            title: 'title',
            insertOrUpdate: 'insert/update iframe',
            alt: 'Text alternative',
            alttext: 'you should provide a text alternative for the content in this iframe.',
            test: 'Test',
          },
        },
      },
    });
    
    $(document).ready(function() {
      $('#editor').summernote({
        height: 200,
        toolbar: [
          ['operation', ['undo', 'redo']],
          ['style', ['bold', 'italic', 'underline']],
          ['color', ['color']],       
          ['insert', ['iframe', 'link','picture', 'hr']],
          ['view', ['codeview']],
         ],
      });
    });
    <!-- include libraries(jQuery, bootstrap) -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    
    <!-- include summernote css/js -->
    <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
      
    <div id="editor">Hello Summernote</div>

    关于javascript - 如何在 Summernote 中插入占位符元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35895445/

    相关文章:

    javascript - 如何使用jquery获取div值?

    javascript - 如何确定变量是 'undefined' 还是 'null' ?

    javascript - 托管支付字段如何更改父 div 类?

    css - Chrome 变换矩阵 iframe 渲染故障

    javascript - 如何将 React 应用程序与 aframe VR 应用程序连接

    javascript替换问号

    javascript - 在 JavaScript 中嵌套 if else

    php - jQuery $.ajax 发布到 PHP 文件不起作用

    Cordova ,内容安全策略 : within iframe getting error as deviceready has not fired after 5 seconds

    javascript - 如何在 ReactJS + Redux 中检查数组中的每个对象?