Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onpaste event to clear formatting #303

Closed
JackPoint opened this issue Mar 14, 2014 · 43 comments
Closed

Onpaste event to clear formatting #303

JackPoint opened this issue Mar 14, 2014 · 43 comments

Comments

@JackPoint
Copy link

Is it possible to add an on paste event? I wanna remove all styles when someone copy pastes a text to the editor. If there is an event like this, I can try to build that.

Or is there an other (easyer) way to clear formatting when someone paste text from an other editor?

@hackerwins hackerwins self-assigned this Mar 16, 2014
@hackerwins hackerwins added this to the v0.6.0 milestone Mar 16, 2014
hackerwins added a commit that referenced this issue Mar 16, 2014
@hackerwins
Copy link
Member

@JackPoint I added onpaste option. Good luck.

@JackPoint
Copy link
Author

Thanks!

This was referenced Mar 19, 2014
@chongwang87
Copy link

so any example/documentation on how to implement the onpaste method?

@JackPoint
Copy link
Author

@chongwang87 No sorry, I tried but no luck. Perhaps @hackerwins can help us out?

@frol
Copy link

frol commented Jun 29, 2014

I tried to implement real-time cleaning (without extra popup windows like in #446 'Paste from Word'), but no luck too. My problem is that I cannot restore cursor position after deleting/replacing tags. Does anybody know tricks to deal with cursor positioning? I was thinking about manipulating with real DOM on cleaning, but it seems to me that it will work very slow.

@frol
Copy link

frol commented Jun 30, 2014

I found this great answer on stackoverflow from CKEditor core developer: http://stackoverflow.com/a/11290082/1178806
It looks like it will require a lot of work even to port CKEditor's implementation.

@anton164
Copy link

Here's a function I wrote to parse pastes from Word into Summernote. My case is quite specific as I use Summernote as a WYSIWYG editor for Markdown, so some of the parsing isn't helpful for you, e.g. titles -> ## Text, but it could prove helpful as inspiration for some and the list parsing should work.

function parseWordPaste(desc) {
      var that = this;
      if (/<!--StartFragment-->([^]*)<!--EndFragment-->/.test(desc)) {
        // Pasted from word
        // Strip all normal <p> tags, replace with divs
        var result = desc.replace(/<p class="MsoNormal">([\s\S]*?)<\/p>/g, "<div>$1</div>\n");
        // Fix titles
        result = result.replace(/<p class="MsoTitle">([\s\S]*?)<\/p>/g, "## $1");

        var $desc = $('<div>' + result + '</div>');
        $desc.contents().each(function() {
          if (this.nodeType == 8) { // nodeType 8 = comments
            $(this).remove();
          }
        });
        var firstItems = $desc.find('p').filter(function() {
          return /MsoList.*?First/g.test(this.className);
        });

        var lists = [];

        firstItems.each(function() {
          lists.push($(this).nextUntil('.MsoListParagraphCxSpLast').addBack().next().addBack());
        });

        // Add lists with one item
        lists.push($desc.find(".MsoListParagraph"));

        // Change between ordered and un-ordered lists
        if (lists.length != 0) {
          lists.forEach(function(list) {
            if (list.length > 0) {
              if (/[\s\S]*?(Symbol|Wingdings)[\s\S]*?/.test(list.html()))
                var unordered = true;
              else if (/[^0-9]/.test(list.text()[0]))
                var unordered = true;

              list.each(function() {
                if (/[\s\S]*?level[2-9][\s\S]*/.test(this.outerHTML))
                  var nested = true;

                var $this = $(this);
                if (unordered)
                  var newText = $this.text().replace(/[^0-9]([\s\S]*)/, "$1");
                else {
                  var newText = $this.text().replace(/[0-9](\.|\))([\s\S]*)/, "$2");
                }

                $this.html(newText);
                if (nested) {
                  if (unordered)
                    $this.wrapInner('<ul><li>');
                  else
                    $this.wrapInner('<ol><li>');
                }

              });
              list.wrapInner('<li>');
              if (unordered)
                list.wrapAll('<ul>');
              else
                list.wrapAll('<ol>');
              // Filter to make sure that we don't unwrap nested lists
              list.find('li').filter(function() {
                return this.parentNode.tagName == 'P'
              }).unwrap();
            }
          });
        }
        return $desc.html();
      } else return desc;
    }

@faridv
Copy link

faridv commented Aug 7, 2014

@anton164 I'm trying to implement the code you provided in my summernote instance but I don't know how to do it. Can you show me where should I put this method to make it work?

@cjdreiss
Copy link

I managed to auto clean the text on paste now. The biggest pain point was figuring out how to use the onpaste event. The code would be called before the actual text got pasted, so anything I tried to do would be on the "blank" <p><br><p> that is in the window by default.

This onpaste function will allow you to call your cleaning code after the text is pasted.

$('.your_selector').summernote({
//your other options here...
onpaste: function(e) {
            var thisNote = $(this);
            var updatePastedText = function(someNote){
                var original = someNote.code();
                var cleaned = CleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
                someNote.code('').html(cleaned); //this sets the displayed content editor to the cleaned pasted code.
            };
            setTimeout(function () {
                //this kinda sucks, but if you don't do a setTimeout, 
                //the function is called before the text is really pasted.
                updatePastedText(thisNote);
            }, 10);


        }
});

As for the function to clean it, I used the function here: http://patisserie.keensoftware.com/en/pages/remove-word-formatting-from-rich-text-editor-with-javascript

function CleanPastedHTML(input) {
  // 1. remove line breaks / Mso classes
  var stringStripper = /(\n|\r| class=(")?Mso[a-zA-Z]+(")?)/g;
  var output = input.replace(stringStripper, ' ');
  // 2. strip Word generated HTML comments
  var commentSripper = new RegExp('<!--(.*?)-->','g');
  var output = output.replace(commentSripper, '');
  var tagStripper = new RegExp('<(/)*(meta|link|span|\\?xml:|st1:|o:|font)(.*?)>','gi');
  // 3. remove tags leave content if any
  output = output.replace(tagStripper, '');
  // 4. Remove everything in between and including tags '<style(.)style(.)>'
  var badTags = ['style', 'script','applet','embed','noframes','noscript'];

  for (var i=0; i< badTags.length; i++) {
    tagStripper = new RegExp('<'+badTags[i]+'.*?'+badTags[i]+'(.*?)>', 'gi');
    output = output.replace(tagStripper, '');
  }
  // 5. remove attributes ' style="..."'
  var badAttributes = ['style', 'start'];
  for (var i=0; i< badAttributes.length; i++) {
    var attributeStripper = new RegExp(' ' + badAttributes[i] + '="(.*?)"','gi');
    output = output.replace(attributeStripper, '');
  }
  return output;
}

@marcusmalmberg
Copy link

@cjdreiss solution works really good 👍. I modified it to remove img and div tags also, if the user copied the text from a webpage.

@CristinaNicolae
Copy link

This is what I was looking for! Many thanks. But I have a question. How do I do to remove every formatting except bold, italic and underline when I paste something?

@septembermd
Copy link

This is basically what we can do binding paste event on summernote element. I think that here we need an option to manipulate with pasted content before it had been inserted into the editor.
ex if I want to remove any styling I could do smth like:
onpaste: function(content) { return $(content).text(); }

@yamilramilev
Copy link

For clear all html tags i use this code:

var editor=$(".summernote");
    editor.summernote({
        onpaste: function(content) {
            setTimeout(function () {
                editor.code(content.target.textContent);
            }, 10);
        }
    });

@septembermd
Copy link

My working solution

var $editor = $('.summernote');

$editor.summernote({
    onpaste: function (e) {
        var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

        e.preventDefault();

        document.execCommand('insertText', false, bufferText);
    }
});

@unpollito
Copy link

@septembermd The issue with your solution is that, at least on Firefox, it basically removes all images and text formats. :( The getData('Text') method seems to return plaintext alone.

@septembermd
Copy link

@davidmartingonzalez in my case this is what I expected

@bphamous
Copy link

@cjdreiss solution also worked well for me, thank you!

@andre1810
Copy link

+1

@stalker780
Copy link

Summernote 0.61 Paste Text Plugin
based on video plugin

(function ($) {
    var tmpl = $.summernote.renderer.getTemplate();
    var range = $.summernote.core.range;
    var dom = $.summernote.core.dom;

    var showPasteDialog = function ($editable, $dialog, text) {
        return $.Deferred(function (deferred) {
            var $pasteDialog = $dialog.find('.note-paste-dialog');

            var $pasteText = $pasteDialog.find('.note-paste-text'),
                $pasteBtn = $pasteDialog.find('.note-paste-btn');

            $pasteDialog.one('shown.bs.modal', function () {
                $pasteText.val(text).on('input', function () {
                    toggleBtn($pasteBtn, $pasteText.val());
                }).trigger('focus');

                $pasteBtn.click(function (event) {
                    event.preventDefault();
                    deferred.resolve($pasteText.val());
                    $pasteDialog.modal('hide');
                });
            }).one('hidden.bs.modal', function () {
                $pasteText.off('input');
                $pasteBtn.off('click');

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


    $.summernote.addPlugin({
        name: 'pastetext',
        buttons: {
            pastetext: function (lang) {
                return tmpl.iconButton('fa fa-paste', {
                    event: 'showPasteDialog',
                    title: lang.pastetext.title,
                    hide: true
                });
            }
        },
        dialogs: {
            pastetext: function (lang) {
                var body = '<div class="form-group row-fluid">' +
                    '<textarea rows="13" class="note-paste-text form-control span12" style=""></textarea>' +
                    '</div>';
                var footer = '<button href="#" class="btn btn-primary note-paste-btn disabled" disabled>' + lang.pastetext.insert + '</button>';
                return tmpl.dialog('note-paste-dialog', lang.pastetext.title, body, footer);
            }
        },
        events: {
            showPasteDialog: function (event, editor, layoutInfo) {
                var $dialog = layoutInfo.dialog(),
                    $editable = layoutInfo.editable(),
                    text = '';

                editor.saveRange($editable);

                showPasteDialog($editable, $dialog, text).then(function (paste_text) {
                    editor.restoreRange($editable);
                    document.execCommand('insertText', false, paste_text);
                }).fail(function () {
                    editor.restoreRange($editable);
                });
            }
        },
        langs: {
            'en-US': {
                pastetext: {
                    title: 'Paste Text',
                    insert: 'OK'
                }
            }
        }
    });
}));

$('.summernote').summernote({
    height: 300,
    toolbar: [['font',['pastetext']]]
});

@timocouckuyt
Copy link

@septembermd 's solution works gloriously in Chrome, but doenst in FF. Just try to paste in 1 word. Nothing'll show up.

EDIT

onpaste: function (e) {
    var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

    e.preventDefault();

    setTimeout( function(){
        document.execCommand( 'insertText', false, bufferText );
    }, 10 );
}

The setTimeout should do the trick.

@aleksandarcrvc
Copy link

Is there any working example for IE8?

@heyfranks
Copy link

omg Many thanks.

@aliemre
Copy link

aliemre commented Oct 5, 2015

At new version the plugin, consider to use onPaste instead of onpaste.

@hackerwins hackerwins modified the milestones: v0.7.1, v0.7.2 Dec 31, 2015
@hackerwins hackerwins modified the milestone: v0.7.2 Jan 13, 2016
@AaronLlanos
Copy link

why not add a feature $('#summernote').summernote('code', {stripTags: true}); or $('#summernote').summernote('text');

@dusthazard
Copy link

dusthazard commented Jul 1, 2016

Working version for me:

        $(".js-summernote").on("summernote.paste",function(e,ne) {

          var bufferText = ((ne.originalEvent || ne).clipboardData || window.clipboardData).getData('Text');
          ne.preventDefault();
          document.execCommand('insertText', false, bufferText);

        });

Combo of @AliPnhyn and @septembermd - many thanks. Works in ff and chrome

EDIT: When pasting images, the ALT tag comes through as text, but the image doesn't load - works well in my use case.

@daveteu
Copy link

daveteu commented Jul 1, 2016

Seems to be able to still paste images in chrome using @stepembermd answer. Any help? i tried if (!bufferText) return true;

PS: think e.preventDefault doesn't work for chrome on paste event

@robertmain
Copy link

I found this to be very helpful: https://github.com/StudioJunkyard/summernote-cleaner

@ElchinIsmayilov
Copy link

another solution that worked for me.
i did not check if anyone had this solution. of course this is temporary

var $this = $('#body'); $this.summernote({ height:'300px', fontNames: ['Nunito Sans, sans-serif'], callbacks: { onPaste: function(e) { setTimeout( function(){ console.log($this.summernote('code')); $('.note-editable').children().css("font-family", ""); console.log($this.summernote('code')); }, 10); } }

@lqez lqez changed the title Onpase event to clear formatting Onpaste event to clear formatting Nov 15, 2017
@lqez
Copy link
Member

lqez commented Nov 15, 2017

Please use external plugins for now.

We have to discuss whether we have to implement own and independent document model. But we don't have a plan right now. So I'm closing this. Thanks.

@lqez lqez closed this as completed Nov 15, 2017
@jayasuryasakamuri
Copy link

In the new version this code worked for me perfectly

$('#summernote').summernote({ minHeight: 75, maxHeight: 300, focus: true, callbacks: { onPaste: function (e) { var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); e.preventDefault(); document.execCommand('insertText', false, bufferText); } } });

@hiteshaggarwal
Copy link

Check here for a working plugin.
https://gist.github.com/hiteshaggarwal/388cd3fae7331aa0415c63e0a883e8f5

@audas
Copy link

audas commented Aug 11, 2018

@cjdreiss

These plugins and scripts are either stripping out all styling - which we want to keep - along with the microsoft styling - or they are simply leaving it in.

The style tags are required to maintain HTML - so stripping them out is not what I'm after.

Basically on paste I just look for any instance of "mso-" the common microsoft tag - then pass a confirmation to the tagStripper functions / plugins to strip styling since it is a microsoft style - and then alert the users.

@AnujShr
Copy link

AnujShr commented Aug 28, 2018

It is also stripping the normal break line from Ms-word and pasting all in one line. Any solution for that?

@audas
Copy link

audas commented Aug 28, 2018

@AnujShr

I just used an onPaste even as part of the init - then called it after a slight delay.
From there I used a custom stripping function.

Formatting is not working on Github - sorry.

callbacks : { onPaste: function(e) { var thisNote = $(this); var updatePastedText = function(someNote){ var original = someNote.summernote('code'); var microsoft=false; if (original.indexOf("mso-") >= 0)microsoft=true; var cleaned = CleanPastedHTML(original,microsoft); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML. someNote.summernote('code',cleaned); //this sets the displayed content editor to the cleaned pasted code. if(microsoft) alert("Pasted content contained Microsoft Word formatting and has been removed.") }; setTimeout(function () { updatePastedText(thisNote); }, 10); },

`

function CleanPastedHTML(input,is_ms) {
// 1. remove line breaks / Mso classes
var stringStripper = /(\n|\r| class=(")?Mso[a-zA-Z]+(")?)/g;
var output = input.replace(stringStripper, ' ');
// 2. strip Word generated HTML comments
var commentSripper = new RegExp('','g');
var output = output.replace(commentSripper, '');
// var tagStripper = new RegExp('<(/)(meta|link|span|\?xml:|st1:|o:|font)(.?)>','gi');
var tagStripper = new RegExp('<(/)(meta|\?xml:|st1:|o:)(.?)>','gi');
// 3. remove tags leave content if any
output = output.replace(tagStripper, '');
// 4. Remove everything in between and including tags '<style(.)style(.)>'
var badTags = ['style', 'script','applet','embed','noframes','noscript'];

  for (var i=0; i< badTags.length; i++) {
    tagStripper = new RegExp('<'+badTags[i]+'.*?'+badTags[i]+'(.*?)>', 'gi');
    output = output.replace(tagStripper, '');
  }
  // 5. remove attributes ' style="..."'
  var badAttributes = ['start'];
  if(is_ms)badAttributes = ['style','start'];
  for (var i=0; i< badAttributes.length; i++) {
    var attributeStripper = new RegExp(' ' + badAttributes[i] + '="(.*?)"','gi');
    output = output.replace(attributeStripper, '');
  }
  
  return output;
}

`

@IAmShafqatAli
Copy link

@septembermd 's solution works gloriously in Chrome, but doenst in FF. Just try to paste in 1 word. Nothing'll show up.

EDIT

onpaste: function (e) {
    var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

    e.preventDefault();

    setTimeout( function(){
        document.execCommand( 'insertText', false, bufferText );
    }, 10 );
}

The setTimeout should do the trick.
Your solution for FF worked like a charm :) Thank you so much.

@IAmShafqatAli
Copy link

My working solution

var $editor = $('.summernote');

$editor.summernote({
    onpaste: function (e) {
        var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

        e.preventDefault();

        document.execCommand('insertText', false, bufferText);
    }
});

Your solution is working perfectly, <3

Worked by changing
var bufferText = (e.originalEvent || e.clipboardData || window.clipboardData).getData('text/html');
to
var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

@fmagrosoto
Copy link

fmagrosoto commented Aug 31, 2021

This is a very clever solution. Watch out with callbacks method and the event name, is onPaste (with camel case).

const editor = $('#cuerpo')
    editor.summernote({
      height: 500,
      callbacks: {
        onPaste: function (e) {
          console.log('paste it')
          const bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text')
          e.preventDefault()
          setTimeout(function () {
            document.execCommand('insertText', false, bufferText)
          }, 10)
        }
      }
    })

Reference:
https://summernote.org/deep-dive/#callbacks

@fmagrosoto
Copy link

My working solution

var $editor = $('.summernote');

$editor.summernote({
    onpaste: function (e) {
        var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

        e.preventDefault();

        document.execCommand('insertText', false, bufferText);
    }
});

Your solution is working perfectly, <3

Worked by changing
var bufferText = (e.originalEvent || e.clipboardData || window.clipboardData).getData('text/html');
to
var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');

Watch out and be careful with the event name, is onPaste with camel case. And all the method must inside a callbacks method.

@ProsantaChaki
Copy link

By using summernote.paste event we can track the paste in the editor and then text can be formatted or unnecessary style can be removed.

 $('#id').on('summernote.paste', function(e, ne) {
        //get the text
        let inputText = ((ne.originalEvent || ne).clipboardData || window.clipboardData).getData('Text');
        // block default behavior
        ne.preventDefault();
        //modify paste text as plain text
        let modifiedText = inputText .replace(/\r?\n/g, '<br>');
        //put it back in editor
        document.execCommand('insertHtml', false, modifiedText );
    })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests