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

Posted

技术标签:

【中文标题】如何在 Summernote 中插入占位符元素?【英文标题】:How to insert a placeholder element in Summernote? 【发布时间】:2016-06-24 01:19:37 【问题描述】:

我正在为 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 允许我编辑 span 的内容,但我想要一个无法在编辑器中修改的占位符。

如何在编辑器中插入一个占位符,它具有以下属性:

可作为单个块进行编辑(可以通过单个删除来删除) 点击后,我可以打开类似于链接或图像弹出框的弹出框来调整大小 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'
    
);

【问题讨论】:

只需在 Summernote 对象中添加“占位符”选项即可。 【参考方案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>

【讨论】:

“占位符”并不是指您可以在输入和文本区域中拥有的占位符属性。想想一个邮件合并文档和你合并它的数据。在文档中放置占位符以标记每个数据文件应插入的位置。【参考方案2】:

您可以在您的span 元素上使用contenteditable 属性,它会起作用并将iframe 插件html 保留在编辑器中,当点击Del 或Backspace 键。

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 变量来在弹出菜单弹出时保存光标下的元素,以便弹出对话框知道我们正在编辑一个元素而不是创建一个新元素。

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);

完整的工作插件

运行代码 sn-p 并尝试使用带有方形图标的按钮插入 iframe。您可以编辑现有的 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>

【讨论】:

【参考方案3】:

默认情况下,summernote 创建它的编辑器 div 包含内容

/p> 所以如果你清空summernote onload 那么问题就会得到解决。

有删除内容的代码:

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

【讨论】:

以上是关于如何在 Summernote 中插入占位符元素?的主要内容,如果未能解决你的问题,请参考以下文章

一页中的多个 Summernote 所见即所得编辑器具有相同的占位符

Mysql占位符插入

如何在 UITextView 中插入占位符? [复制]

如何使用具有任意数量元素的占位符创建 python 字符串

如何错误检查包含带有占位符 %s 的 SQL 查询的函数

python向mysql插入数据一直报TypeError: must be real number,not str