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

5
我正在为Summernote所使用的所见即所得编辑器(版本0.8.1)开发一个插件,以便将iframe元素插入到代码中。
通过使用提供的示例,我成功地在菜单中添加了插件按钮,并打开了一个对话框,在其中可以输入URL和标题。将iframe标签添加到源中并不是问题,但这不是我想要的。
我想要在代码中插入一个占位符,其标记类似于(或类似于):
<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允许我编辑span的内容,但我希望能够插入一个占位符,它不能在编辑器中修改。
我该如何在编辑器中插入一个具有以下特点的占位符:
- 它作为单一块进行编辑(可以通过单个删除删除) - 单击时,我可以打开类似于链接或图像弹出窗口的弹出窗口以调整大小 - 内部内容不可编辑
以下是我到目前为止所拥有的:
// 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'
    }
});

只需在summernote对象中添加“placeholder”选项即可。 - Mahendra Garg
3个回答

2
你可以在元素上使用contenteditable属性,它将起作用并保留编辑器中的iframe插件HTML,并在按下DelBackspace键时删除整个块。 Github存储库中有一些演示插件,其中一个演示了对话框和弹出式编辑的用法,你可以在这里查看逻辑和代码。
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 变量,以保存鼠标下方的元素,以便弹出菜单时,弹出对话框将知道我们正在编辑一个元素而不是创建一个新元素。
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);
}

完全可用的插件

运行代码片段并尝试使用带有方形图标的按钮插入iframes。您可以编辑现有的iFrame元素,该块将全部删除。

/**
 * @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>


0
默认情况下,当Summernote创建其编辑器div时,它包含以下内容< /p>< br>/p>。因此,如果您在加载时清空Summernote,则问题将得到解决。

有代码可用于删除内容:

      $(function () {
        $('#summernote').summernote({
            inheritPlaceholder: true,
            placeholder: 'Enter your Inquiry here...',
        });
        $('#summernote').summernote('code', ''); //This line remove summercontent when load
    });

-1

尝试使用

$(document).ready(function() {
        $('#summernote').summernote({
            placeholder: 'Hello stand alone ui',
            tabsize: 2,
            height: 120
        });
});
<!-- 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="summernote"></div>


我所说的“占位符”并不是指您可以在输入框和文本区域中使用的占位符属性。想象一下邮件合并文档和您要合并的数据。在文档中,您放置占位符来标记应插入每个数据字段的位置。 - Majid Fouladpour

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接