118

I have a basic editor based on execCommand following the sample introduced here. There are three ways to paste text within the execCommand area:

  • Ctrl+V
  • Right Click -> Paste
  • Right Click -> Paste As Plain Text

I want to allow pasting only plain text without any HTML markup. How can I force the first two actions to paste Plain Text?

Possible Solution: The way I can think of is to set listener for keyup events for (Ctrl+V) and strip HTML tags before paste.

  1. Is it the best solution?
  2. Is it bulletproof to avoid any HTML markup in paste?
  3. How to add listener to Right Click -> Paste?
Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
Googlebot
  • 13,096
  • 38
  • 113
  • 210
  • 5
    As a side note, do you also want to take care of text being dragged into the editor? That is another way HTML can leak into the editor. – pimvdb Aug 19 '12 at 16:38
  • 1
    @pimvdb You answer was enough for my need. Just out of curiosity, is there a simple method to avoid dragged leakage too? – Googlebot Aug 19 '12 at 17:32
  • 2
    I thought this would do the job: http://jsfiddle.net/HBEzc/2/. But on Chrome at least, the text is always inserted at the beginning of the editor, unfortunately. – pimvdb Aug 19 '12 at 18:07
  • You need to use clipboard api as explaiend here. https://www.youtube.com/watch?v=Q81HH2Od5oo – Johne Doe Apr 12 '19 at 10:08

11 Answers11

267

It will intercept the paste event, cancel the paste, and manually insert the text representation of the clipboard:
http://jsfiddle.net/HBEzc/. This should be the most reliable:

  • It catches all kinds of pasting (Ctrl+V, context menu, etc.)
  • It allows you to get the clipboard data directly as text, so you don't have to do ugly hacks to replace HTML.

I'm not sure of cross-browser support, though.

editor.addEventListener("paste", function(e) {
    // cancel paste
    e.preventDefault();

    // get text representation of clipboard
    var text = (e.originalEvent || e).clipboardData.getData('text/plain');

    // insert text manually
    document.execCommand("insertHTML", false, text);
});
Quinn Keaveney
  • 777
  • 11
  • 31
pimvdb
  • 141,012
  • 68
  • 291
  • 345
  • 4
    @Ali: I missed something obvious. If `text` contains HTML (e.g. if you copy HTML code as plain text) then it will actually paste it as HTML. Here's a solution to it, but it's not very pretty: http://jsfiddle.net/HBEzc/3/. – pimvdb Aug 20 '12 at 08:51
  • This does not work in Chrome/Chromium: clipboardData.getData comes back as empty – btk Apr 22 '13 at 21:26
  • from http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser there is a similar solution that is claimed to work in IE (8,9,10 and 11), it uses getData("text") instead of getData("text/plain") – alzclarke May 01 '14 at 09:32
  • 14
    `var text = (event.originalEvent || event).clipboardData.getData('text/plain');` provides a bit more cross-browser compatibility – Duncan Walker Sep 15 '14 at 16:23
  • this is a great code snippet. text area in WebKit doesn't has a bug where pasting 'large' amounts of data is extremely slow. This code makes it work as it should. thanks! – gypsyDev Oct 08 '14 at 05:57
  • 10
    This breaks the undo functionality. (Ctrl+Z) – Rudey Jun 09 '15 at 13:36
  • 3
    Great solution, but this diverges from the default behavior. If the user copies something like `
    ` that content will be added as a child element of the contenteditable element. I fixed it like this: `document.execCommand("insertText", false, text);`
    – Jason Newell Aug 24 '15 at 00:37
  • It replaces new lines with nothing. Hence all new lines are vanished. – ahmadalibaloch Nov 30 '15 at 12:32
  • 5
    I found `insertHTML` and `insertText` don't work in IE11, however `document.execCommand('paste', false, text);` works just fine. Although that then doesn't seem to work in other browsers >_>. – Jamie Barker Jan 19 '16 at 11:48
  • 1
    Jamie Barker's answer it's the correct one as it provides support for IE11 – HeberLZ Mar 24 '16 at 00:18
  • 1
    both `insertHTML` and `insertText` don't work in firefox (within textarea) – paradite Jul 26 '16 at 07:14
  • 1
    @paradite please see my BUG-ticket https://bugzilla.mozilla.org/show_bug.cgi?id=1220696 – webprogrammer Jul 28 '16 at 09:21
  • @webprogrammer thanks. I coded a fix for `textarea` here: https://gist.github.com/paradite/f66862dbfb61001c8dd09680747eec06 using `value`, `selectionStart ` and `selectionEnd ` attributes. – paradite Jul 28 '16 at 09:30
  • This doesn't preserve newlines – Cybernetic Oct 31 '18 at 03:33
  • 2
    Use `return false;` instead of `e.preventDefault();` to keep `undo` (Ctrl+Z) working – Oleg Nov 08 '18 at 10:38
  • `execCommand` is now [obsolete](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) – RA. Apr 04 '20 at 23:41
  • Great answer and was very helpful :) Copying and pasting something like `

    hello world

    ` this will break it though
    – petermir Jun 08 '20 at 07:15
43

I couldn't get the accepted answer here to work in IE so I did some scouting around and came to this answer which works in IE11 and the latest versions of Chrome and Firefox.

$('[contenteditable]').on('paste', function(e) {
    e.preventDefault();
    var text = '';
    if (e.clipboardData || e.originalEvent.clipboardData) {
      text = (e.originalEvent || e).clipboardData.getData('text/plain');
    } else if (window.clipboardData) {
      text = window.clipboardData.getData('Text');
    }
    if (document.queryCommandSupported('insertText')) {
      document.execCommand('insertText', false, text);
    } else {
      document.execCommand('paste', false, text);
    }
});
Jamie Barker
  • 7,735
  • 3
  • 26
  • 62
  • 1
    Thank you, i was truggling with that same issue... insertText wasn't working on neither IE11 nor latest FF :) – HeberLZ Mar 24 '16 at 00:16
  • 1
    Is it possible that it in some cases pastes the text twice in both Firefox and Chrome? Seems to me.. – Fanky Jul 22 '16 at 14:31
  • 1
    @Fanky I didn't have that issue when I created it, however I no longer work at the place where I created this code so I couldn't tell you if it still works! Could you describe how it's pasting twice? – Jamie Barker Jul 22 '16 at 14:38
  • 2
    @Fanky See if you can re-create it on here: https://jsfiddle.net/v2qbp829/. – Jamie Barker Jul 22 '16 at 15:41
  • I cannot paste in the box at the fiddle..does nothing - in FF 47.0.1 – Fanky Jul 22 '16 at 15:43
  • @Fanky What browser and version are you using? – Jamie Barker Jul 22 '16 at 15:45
  • 2
    Seems now that the problem I had was due to calling your script from a file which was itself loaded by a script. I can't paste into neither textarea nor input in your fiddle in FF 47.0.1 (can do it in chrome), but can paste into div contenteditable, which is key for me. Thanks! – Fanky Jul 22 '16 at 16:34
  • @PascalLindelauf I rolled back your edit because my answer does not target IE10. [Microsoft do no support it themselves](https://www.microsoft.com/en-gb/windowsforbusiness/end-of-ie-support) so I refuse also to put any code out into the wild that suggests you should support it, because quite frankly you shouldn't. You're basically telling people it is okay to be using a browser that is a security risk. – Jamie Barker Nov 23 '17 at 13:14
  • This seems to work in IE11 at the first glance only. Actually IE reports a "Stack overflow" error. This can easily observed in the above fiddle: https://jsfiddle.net/v2qbp829 – dpr Jan 29 '18 at 15:00
  • @dpr Right at this very moment (30/01/2018 10:45am GMT) it looks like JSFiddle isn't working in IE at all for any fiddles. Just to be sure I've [created a codepen](https://codepen.io/anon/pen/MQYyBX) with the same code and it's seemingly working fine in IE there. – Jamie Barker Jan 30 '18 at 10:51
  • @JamieBarker, if you open the dev console in IE, you will notice the stack overflow error, in the code pen as well. Without the console it looks like it's working, but actually it's producing an error. – dpr Jan 30 '18 at 10:54
  • @dpr So there is. It appears to be working correctly while producing the console error at least. See if you can work out how to fix it if you like ;) – Jamie Barker Jan 30 '18 at 14:53
  • 1
    Use `return false;` instead of `e.preventDefault();` to keep `undo` (Ctrl+Z) working – Oleg Nov 08 '18 at 10:38
23

A close solution as pimvdb. But it's working of FF, Chrome and IE 9:

editor.addEventListener("paste", function(e) {
    e.preventDefault();

    if (e.clipboardData) {
        content = (e.originalEvent || e).clipboardData.getData('text/plain');

        document.execCommand('insertText', false, content);
    }
    else if (window.clipboardData) {
        content = window.clipboardData.getData('Text');

        document.selection.createRange().pasteHTML(content);
    }   
});
  • 5
    I like the short-circuit `content` variable assignment. I have found that using `getData('Text')` works cross-browser, so you could make that assignment just once like this: `var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');` Then you'd only have to use logic for cross-browser paste/insert command. – gfullam Nov 20 '14 at 07:20
  • 6
    I don't think you can write `document.selection.createRange().pasteHTML(content)`... just tested on IE11 and it doesn't work like that. – vsync Feb 15 '15 at 16:52
  • 3
    `document.execCommand('insertText', false, content)` does not work as of IE11 and Edge. Also, latest versions of Chrome now support `document.execCommand('paste', false, content)`, which is simpler. They might be deprecating `insertText`. – Cannicide Feb 01 '17 at 21:45
19

Of course the question is already answered and the topic very old but I want to provide my solution as it is simple an clean:

This is inside my paste-event on my contenteditable-div.

var text = '';
var that = $(this);

if (e.clipboardData)
    text = e.clipboardData.getData('text/plain');
else if (window.clipboardData)
    text = window.clipboardData.getData('Text');
else if (e.originalEvent.clipboardData)
    text = $('<div></div>').text(e.originalEvent.clipboardData.getData('text'));

if (document.queryCommandSupported('insertText')) {
    document.execCommand('insertHTML', false, $(text).html());
    return false;
}
else { // IE > 7
    that.find('*').each(function () {
         $(this).addClass('within');
    });

    setTimeout(function () {
          // nochmal alle durchlaufen
          that.find('*').each(function () {
               // wenn das element keine klasse 'within' hat, dann unwrap
               // http://api.jquery.com/unwrap/
               $(this).not('.within').contents().unwrap();
          });
    }, 1);
}

The else-part is from another SO-post I couldn't find anymore...


UPDATE 19.11.2014: The other SO-post

Community
  • 1
  • 1
webprogrammer
  • 782
  • 14
  • 26
  • 2
    I think you are referring to this post: http://stackoverflow.com/questions/21257688/paste-rich-text-into-content-editable-div-and-only-keep-bold-and-italics-formatt – gfullam Nov 19 '14 at 16:16
  • 1
    Didn't seem to work for me in Safari. Maybe something is wrong – Cannicide Feb 01 '17 at 21:42
9

None of the posted answers really seems to work cross browser or the solution is over complicated:

  • The command insertText is not supported by IE
  • Using the paste command results in stack overflow error in IE11

What worked for me (IE11, Edge, Chrome and FF) is the following:

$("div[contenteditable=true]").off('paste').on('paste', function(e) {
    e.preventDefault();
    var text = e.originalEvent.clipboardData ? e.originalEvent.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');
    _insertText(text);
});

function _insertText(text) { 
    // use insertText command if supported
    if (document.queryCommandSupported('insertText')) {
        document.execCommand('insertText', false, text);
    }
    // or insert the text content at the caret's current position
    // replacing eventually selected content
    else {
        var range = document.getSelection().getRangeAt(0);
        range.deleteContents();
        var textNode = document.createTextNode(text);
        range.insertNode(textNode);
        range.selectNodeContents(textNode);
        range.collapse(false);

        var selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    }
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<textarea name="t1"></textarea>
<div style="border: 1px solid;" contenteditable="true">Edit me!</div>
<input />
</body>

Note that the custom paste handler is only needed/working for contenteditable nodes. As both textarea and plain input fields don't support pasting HTML content at all, so nothing needs to be done here.

dpr
  • 8,675
  • 3
  • 32
  • 57
  • 1
    I had to get rid of `.originalEvent` in the event handler (line 3) to get this to work. So the complete line looks like this: `const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');`. Works in latest Chrome, Safari, Firefox. – Pwdr Dec 10 '19 at 09:15
  • need to replace `var text = e.originalEvent.clipboardData ? e.originalEvent.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');` with `var text = (e.originalEvent || e).clipboardData.getData('text/plain');` – Mohsen Newtoa Sep 24 '20 at 21:19
3

Firefox does not allow you to access the clipboard data so you'll need to make a 'hack' to get it to work. I've not been able to find a complete solution, however you can fix it for ctrl+v pastes by creating a textarea & pasting to that instead:

//Test if browser has the clipboard object
if (!window.Clipboard)
{
    /*Create a text area element to hold your pasted text
    Textarea is a good choice as it will make anything added to it in to plain text*/           
    var paster = document.createElement("textarea");
    //Hide the textarea
    paster.style.display = "none";              
    document.body.appendChild(paster);
    //Add a new keydown event tou your editor
    editor.addEventListener("keydown", function(e){

        function handlePaste()
        {
            //Get the text from the textarea
            var pastedText = paster.value;
            //Move the cursor back to the editor
            editor.focus();
            //Check that there is a value. FF throws an error for insertHTML with an empty string
            if (pastedText !== "") document.execCommand("insertHTML", false, pastedText);
            //Reset the textarea
            paster.value = "";
        }

        if (e.which === 86 && e.ctrlKey)
        {
            //ctrl+v => paste
            //Set the focus on your textarea
            paster.focus();
            //We need to wait a bit, otherwise FF will still try to paste in the editor => settimeout
            window.setTimeout(handlePaste, 1);
        }

    }, false);
}
else //Pretty much the answer given by pimvdb above
{
    //Add listener for paster to force paste-as-plain-text
    editor.addEventListener("paste", function(e){

        //Get the plain text from the clipboard
        var plain = (!!e.clipboardData)? e.clipboardData.getData("text/plain") : window.clipboardData.getData("Text");
            //Stop default paste action
        e.preventDefault();
        //Paste plain text
        document.execCommand("insertHTML", false, plain);

    }, false);
}
2

I was also working on a plain text paste and I started to hate all the execCommand and getData errors, so I decided to do it the classic way and it works like a charm:

$('#editor').bind('paste', function(){
    var before = document.getElementById('editor').innerHTML;
    setTimeout(function(){
        var after = document.getElementById('editor').innerHTML;
        var pos1 = -1;
        var pos2 = -1;
        for (var i=0; i<after.length; i++) {
            if (pos1 == -1 && before.substr(i, 1) != after.substr(i, 1)) pos1 = i;
            if (pos2 == -1 && before.substr(before.length-i-1, 1) != after.substr(after.length-i-1, 1)) pos2 = i;
        }
        var pasted = after.substr(pos1, after.length-pos2-pos1);
        var replace = pasted.replace(/<[^>]+>/g, '');
        var replaced = after.substr(0, pos1)+replace+after.substr(pos1+pasted.length);
        document.getElementById('editor').innerHTML = replaced;
    }, 100);
});

The code with my notations can be found here: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

Albert
  • 71
  • 1
  • 2
1
function PasteString() {
    var editor = document.getElementById("TemplateSubPage");
    editor.focus();
  //  editor.select();
    document.execCommand('Paste');
}

function CopyString() {
    var input = document.getElementById("TemplateSubPage");
    input.focus();
   // input.select();
    document.execCommand('Copy');
    if (document.selection || document.textSelection) {
        document.selection.empty();
    } else if (window.getSelection) {
        window.getSelection().removeAllRanges();
    }
}

Above code works for me in IE10 and IE11 and now also works in Chrome and Safari. Not tested in Firefox.

Cannicide
  • 2,530
  • 1
  • 12
  • 28
Nikhil Ghuse
  • 1,218
  • 12
  • 29
1

In IE11, execCommand doesn't work well. I use below code for IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> is my div box.

I read clipboard data from window.clipboardData and modify div's textContent and give caret.

I give timeout for setting caret, because if I don't set timeout, a caret goes to end of div.

and you should read clipboardData in IE11 in below way. If you don't do it, newline caracter is not handled properly, so caret goes wrong.

var tempDiv = document.createElement("div");
tempDiv.textContent = window.clipboardData.getData("text");
var text = tempDiv.textContent;

Tested on IE11 and chrome. It may not work on IE9

document.getElementById("wmd-input-md").addEventListener("paste", function (e) {
    if (!e.clipboardData) {
        //For IE11
        e.preventDefault();
        e.stopPropagation();
        var tempDiv = document.createElement("div");
        tempDiv.textContent = window.clipboardData.getData("text");
        var text = tempDiv.textContent;
        var selection = document.getSelection();
        var start = selection.anchorOffset > selection.focusOffset ? selection.focusOffset : selection.anchorOffset;
        var end = selection.anchorOffset > selection.focusOffset ? selection.anchorOffset : selection.focusOffset;                    
        selection.removeAllRanges();

        setTimeout(function () {    
            $(".wmd-input").text($(".wmd-input").text().substring(0, start)
              + text
              + $(".wmd-input").text().substring(end));
            var range = document.createRange();
            range.setStart(document.getElementsByClassName("wmd-input")[0].firstChild, start + text.length);
            range.setEnd(document.getElementsByClassName("wmd-input")[0].firstChild, start + text.length);

            selection.addRange(range);
        }, 1);
    } else {                
        //For Chrome
        e.preventDefault();
        var text = e.clipboardData.getData("text");

        var selection = document.getSelection();
        var start = selection.anchorOffset > selection.focusOffset ? selection.focusOffset : selection.anchorOffset;
        var end = selection.anchorOffset > selection.focusOffset ? selection.anchorOffset : selection.focusOffset;

        $(this).text($(this).text().substring(0, start)
          + text
          + $(this).text().substring(end));

        var range = document.createRange();
        range.setStart($(this)[0].firstChild, start + text.length);
        range.setEnd($(this)[0].firstChild, start + text.length);
        selection.removeAllRanges();
        selection.addRange(range);
    }
}, false);
Lee Hojin
  • 79
  • 5
0

After along search and trying I have found somehow kind of optimal solution

what is important to keep in mind

// /\x0D/g return key ASCII
window.document.execCommand('insertHTML', false, text.replace('/\x0D/g', "\\n"))


and give the css style white-space: pre-line //for displaying

var contenteditable = document.querySelector('[contenteditable]')
            contenteditable.addEventListener('paste', function(e){
                let text = ''
                contenteditable.classList.remove('empty')                
                e.preventDefault()
                text = (e.originalEvent || e).clipboardData.getData('text/plain')
                e.clipboardData.setData('text/plain', '')                 
                window.document.execCommand('insertHTML', false, text.replace('/\x0D/g', "\\n"))// /\x0D/g return ASCII
        })
#input{
  width: 100%;
  height: 100px;
  border: 1px solid black;
  white-space: pre-line; 
}
<div id="input"contenteditable="true">
        <p>
        </p>
</div>   
mooga
  • 2,543
  • 4
  • 19
  • 33
0

OK as everybody is trying to work around clipboard data, checking keypress event, and using execCommand.

I thought of this

CODE

handlePastEvent=()=>{
    document.querySelector("#new-task-content-1").addEventListener("paste",function(e)
    {
        
        setTimeout(function(){
            document.querySelector("#new-task-content-1").innerHTML=document.querySelector("#new-task-content-1").innerText.trim();
        },1);
    });

}
handlePastEvent();
<div contenteditable="true" id="new-task-content-1">You cann't paste HTML here</div>
Arun Sharma
  • 417
  • 8
  • 14