Search code examples
javascriptjquerysummernote

Replace selected text with insertion in summernote editor


I am trying to create a plugin for summernote v0.8.9.

Find below my minimum viable example:

$(document).ready(function() {
  $('.summernote').summernote({
    height: 300,
    tabsize: 2,
    toolbar: [
      ['insert', ['synonym', 'codeview']]
    ],
  });
});

(function(factory) {
  /* global define */
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else if (typeof module === 'object' && module.exports) {
    // Node/CommonJS
    module.exports = factory(require('jquery'));
  } else {
    // Browser globals
    factory(window.jQuery);
  }
}(function($) {

  // minimal dialog plugin
  $.extend($.summernote.plugins, {
    'synonym': function(context) {
      var self = this;

      var ui = $.summernote.ui;

      var $editor = context.layoutInfo.editor;
      var options = context.options;

      // add context menu button
      context.memo('button.synonym', function() {
        return ui.button({
          contents: '<i class="fa fa-snowflake-o">',
          tooltip: 'Create Synonym',
          click: context.createInvokeHandler('synonym.showDialog')
        }).render();
      });

      self.initialize = function() {
        var $container = options.dialogsInBody ? $(document.body) : $editor;

        var body = '<div class="form-group">' +
          '<label>Element</label>' +
          '<input id="input-element" class="form-control" type="text"/>' +
          '</div>' +
          '<label>Synonym</label>' +
          '<input id="input-synonym" class="form-control" type="text" placeholder="Insert your synonym" />'
        var footer = '<button href="#" class="btn btn-primary ext-synonym-btn">OK</button>';

        self.$dialog = ui.dialog({
          title: 'minimal dialog title',
          fade: options.dialogsFade,
          body: body,
          footer: footer
        }).render().appendTo($container);
      };

      self.destroy = function() {
        self.$dialog.remove();
        self.$dialog = null;
      };

      self.showDialog = function() {
        self
          .openDialog()
          .then(function(data) {
            // [workaround] hide dialog before restore range for IE range focus
            ui.hideDialog(self.$dialog);
            context.invoke('editor.restoreRange');
            self.insertToEditor(data);
            // do something with dialogData
            console.log("dialog returned: ", data)
            // ...
          })
          .fail(function() {
            context.invoke('editor.restoreRange');
          });

      };

      self.openDialog = function() {
        return $.Deferred(function(deferred) {
          var $dialogBtn = self.$dialog.find('.ext-synonym-btn');
          var $elemInput = self.$dialog.find('#input-element')[0];
          var $synonymInput = self.$dialog.find('#input-synonym')[0];

          var selectedText = $.selection()
          $('#input-element').val(selectedText);
          console.log("show dialog: " + selectedText)

          ui.onDialogShown(self.$dialog, function() {
            context.triggerEvent('dialog.shown');

            $dialogBtn
              .click(function(event) {
                event.preventDefault();

                deferred.resolve({
                  element: $elemInput.value,
                  synonym: $synonymInput.value
                });
              });
          });

          ui.onDialogHidden(self.$dialog, function() {
            $dialogBtn.off('click');

            if (deferred.state() === 'pending') {
              deferred.reject();
            }
          });

          ui.showDialog(self.$dialog);
        });
      };

      //text that is written to the editor
      this.insertToEditor = function(data) {
        console.log("synonym: " + data.synonym)

        var $elem = $('<synonym>', {
          words: data.synonym
        });;

        $elem.text(data.element)

        context.invoke('editor.insertNode', $elem[0]);
      };
    }
  });
}));
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-pdapHxIh7EYuwy6K7iE41uXVxGCXY0sAjBzaElYGJUrzwodck3Lx6IE2lA0rFREo" crossorigin="anonymous">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.css" />

<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.selection/1.0.1/jquery.selection.js"></script>

<div class="container">
  <div class="summernote">
    <p>Hello World!</p>
    This text should be replaced by the dialog. </div>
</div>

As you can see above I am using the api function context.invoke('editor.insertNode', $elem[0]);, which basically inserts the element from the dialog window at the beginning of the page.

However, I would like to replace the selected word with the elements that where created in the dialog.

Any suggestions how to do this?

I appreciate your replies!


Solution

  • You are on the right track. It seems you have to save the selection via:

    context.invoke('editor.saveRange');   // note line 94 in the snippet
    

    and then use:

    context.invoke('editor.insertText', data.element); // note line 133 in the snippet
    

    I am inserting data.element but you obviously could put there the synonym or whatever :)

    Their documentation is not great but with some trial and error it works.

    $(document).ready(function() {
      $('.summernote').summernote({
        height: 300,
        tabsize: 2,
        toolbar: [
          ['insert', ['synonym', 'codeview']]
        ],
      });
    });
    
    (function(factory) {
      /* global define */
      if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
      } else if (typeof module === 'object' && module.exports) {
        // Node/CommonJS
        module.exports = factory(require('jquery'));
      } else {
        // Browser globals
        factory(window.jQuery);
      }
    }(function($) {
    
      // minimal dialog plugin
      $.extend($.summernote.plugins, {
        'synonym': function(context) {
          var self = this;
    
          var ui = $.summernote.ui;
    
          var $editor = context.layoutInfo.editor;
          var options = context.options;
    
          // add context menu button
          context.memo('button.synonym', function() {
            return ui.button({
              contents: '<i class="fa fa-snowflake-o">',
              tooltip: 'Create Synonym',
              click: context.createInvokeHandler('synonym.showDialog')
            }).render();
          });
    
          self.initialize = function() {
            var $container = options.dialogsInBody ? $(document.body) : $editor;
    
            var body = '<div class="form-group">' +
              '<label>Element</label>' +
              '<input id="input-element" class="form-control" type="text"/>' +
              '</div>' +
              '<label>Synonym</label>' +
              '<input id="input-synonym" class="form-control" type="text" placeholder="Insert your synonym" />'
            var footer = '<button href="#" class="btn btn-primary ext-synonym-btn">OK</button>';
    
            self.$dialog = ui.dialog({
              title: 'minimal dialog title',
              fade: options.dialogsFade,
              body: body,
              footer: footer
            }).render().appendTo($container);
          };
    
          self.destroy = function() {
            self.$dialog.remove();
            self.$dialog = null;
          };
    
          self.showDialog = function() {
            self
              .openDialog()
              .then(function(data) {
                // [workaround] hide dialog before restore range for IE range focus
                ui.hideDialog(self.$dialog);
                context.invoke('editor.restoreRange');
                self.insertToEditor(data);
                // do something with dialogData
                console.log("dialog returned: ", data)
                // ...
              })
              .fail(function() {
                context.invoke('editor.restoreRange');
              });
    
          };
    
          self.openDialog = function() {
            return $.Deferred(function(deferred) {
              var $dialogBtn = self.$dialog.find('.ext-synonym-btn');
              var $elemInput = self.$dialog.find('#input-element')[0];
              var $synonymInput = self.$dialog.find('#input-synonym')[0];
    
              var selectedText = $.selection()
              $('#input-element').val(selectedText);
              context.invoke('editor.saveRange');
              console.log("show dialog: " + selectedText)
    
              ui.onDialogShown(self.$dialog, function() {
                context.triggerEvent('dialog.shown');
    
                $dialogBtn
                  .click(function(event) {
                    event.preventDefault();
    
                    deferred.resolve({
                      element: $elemInput.value,
                      synonym: $synonymInput.value
                    });
                  });
              });
    
              ui.onDialogHidden(self.$dialog, function() {
                $dialogBtn.off('click');
    
                if (deferred.state() === 'pending') {
                  deferred.reject();
                }
              });
    
              ui.showDialog(self.$dialog);
            });
          };
    
          //text that is written to the editor
          this.insertToEditor = function(data) {
            console.log("synonym: " + data.synonym)
    
            var $elem = $('<synonym>', {
              words: data.synonym
            });;
    
            $elem.text(data.element)           
    
            context.invoke('editor.insertText', data.element);
          };
        }
      });
    }));
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-pdapHxIh7EYuwy6K7iE41uXVxGCXY0sAjBzaElYGJUrzwodck3Lx6IE2lA0rFREo" crossorigin="anonymous">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.css" />
    
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.selection/1.0.1/jquery.selection.js"></script>
    
    <div class="container">
      <div class="summernote">
        <p>Hello World!</p>
        This text should be replaced by the dialog. </div>
    </div>