Trucco Javascript per 'incolla come testo normale' in execCommand


106

Ho un editor di base basato sul execCommandseguire l'esempio presentato qui. Esistono tre modi per incollare il testo all'interno execCommanddell'area:

  • Ctrl+V
  • Fare clic con il pulsante destro del mouse -> Incolla
  • Fare clic con il tasto destro -> Incolla come testo normale

Voglio consentire di incollare solo testo normale senza alcun markup HTML. Come posso forzare le prime due azioni a incollare il testo normale?

Possibile soluzione: il modo in cui posso pensare è impostare il listener per gli eventi keyup per ( Ctrl+ V) e rimuovere i tag HTML prima di incollarli.

  1. È la soluzione migliore?
  2. È a prova di proiettile per evitare qualsiasi markup HTML in pasta?
  3. Come aggiungere un ascoltatore al clic destro -> Incolla?

5
Come nota a margine, vuoi anche occuparti del testo che viene trascinato nell'editor? Questo è un altro modo in cui l'HTML può penetrare nell'editor.
pimvdb

1
@pimvdb La tua risposta è stata sufficiente per il mio bisogno. Solo per curiosità, esiste un metodo semplice per evitare anche le perdite trascinate?
Googlebot

2
Ho pensato che questo avrebbe funzionato: jsfiddle.net/HBEzc/2 . Ma almeno su Chrome, il testo è sempre inserito all'inizio dell'editor, purtroppo.
pimvdb

È necessario utilizzare l'API degli appunti come spiegato qui. youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

Risposte:


247

Intercetterà l' pasteevento, annullerà pastee inserirà manualmente la rappresentazione testuale degli appunti:
http://jsfiddle.net/HBEzc/ . Questo dovrebbe essere il più affidabile:

  • Cattura tutti i tipi di incolla ( Ctrl+ V, menu contestuale, ecc.)
  • Ti consente di ottenere i dati degli appunti direttamente come testo, quindi non devi fare brutti hack per sostituire l'HTML.

Non sono sicuro del supporto cross-browser, però.

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

4
@ Ali: mi sono perso qualcosa di ovvio. Se textcontiene HTML (ad esempio se copi il codice HTML come testo normale), lo incollerà effettivamente come HTML. Ecco una soluzione, ma non è molto carina: jsfiddle.net/HBEzc/3 .
pimvdb

14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');fornisce un po 'più di compatibilità cross-browser
Duncan Walker

10
Ciò interrompe la funzionalità di annullamento. (Ctrl + Z)
Rudey

2
Ottima soluzione, ma diverge dal comportamento predefinito. Se l'utente copia qualcosa di simile, <div></div>quel contenuto verrà aggiunto come elemento figlio dell'elemento contenteditable. L'ho risolto in questo modo:document.execCommand("insertText", false, text);
Jason Newell

5
Ho trovato insertHTMLe insertTextnon lavoro in IE11, tuttavia document.execCommand('paste', false, text);funziona bene. Anche se ciò non sembra funzionare in altri browser> _>.
Jamie Barker

39

Non sono riuscito a ottenere la risposta accettata qui per funzionare in IE, quindi ho fatto un po 'di esplorazione e sono arrivato a questa risposta che funziona in IE11 e nelle ultime versioni di Chrome e 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);
    }
});

1
Grazie, stavo affrontando lo stesso problema ... insertText non funzionava né su IE11 né su FF più recente :)
HeberLZ

1
È possibile che in alcuni casi incolli il testo due volte sia in Firefox che in Chrome? A me sembra ..
Fanky

1
@Fanky Non avevo quel problema quando l'ho creato, tuttavia non lavoro più nel luogo in cui ho creato questo codice, quindi non potrei dirti se funziona ancora! Potresti descrivere come si incolla due volte?
Jamie Barker

2
@Fanky Vedi se riesci a ricrearlo qui: jsfiddle.net/v2qbp829 .
Jamie Barker

2
Sembra ora che il problema che ho avuto fosse dovuto alla chiamata del tuo script da un file che è stato a sua volta caricato da uno script. Non posso incollare né l'area di testo né l'input nel tuo violino in FF 47.0.1 (posso farlo in chrome), ma posso incollare in div contenteditable, che è la chiave per me. Grazie!
Fanky

21

Una soluzione vicina come pimvdb. Ma funziona con FF, Chrome e 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
Mi piace l' contentassegnazione della variabile di cortocircuito . Ho scoperto che l'uso di getData('Text')cross-browser funziona, quindi potresti fare quell'assegnazione solo una volta in questo modo: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');Quindi dovresti usare solo la logica per il comando incolla / inserisci cross-browser.
gfullam

6
Non penso che tu possa scrivere document.selection.createRange().pasteHTML(content)... solo testato su IE11 e non funziona così.
vsync

3
document.execCommand('insertText', false, content)non funziona a partire da IE11 e Edge. Inoltre, ora supportano le ultime versioni di Chrome document.execCommand('paste', false, content), il che è più semplice. Potrebbero essere deprecanti insertText.
Cannicidio

19

Ovviamente la domanda ha già una risposta e l'argomento è molto vecchio ma voglio fornire la mia soluzione in quanto è semplice e pulita:

Questo è all'interno del mio evento paste sul mio 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);
}

L'altra parte proviene da un altro post SO che non sono riuscito a trovare più ...


AGGIORNAMENTO 19.11.2014: L'altro SO-post


2
Credo che lei si riferisce a questo post: stackoverflow.com/questions/21257688/...
gfullam

1
Non sembra funzionare per me in Safari. Forse qualcosa non va
Cannicide

8

Nessuna delle risposte pubblicate sembra davvero funzionare su tutti i browser o la soluzione è troppo complicata:

  • Il comando insertTextnon è supportato da IE
  • L'utilizzo del pastecomando genera un errore di overflow dello stack in IE11

Quello che ha funzionato per me (IE11, Edge, Chrome e FF) è il seguente:

$("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>

Nota che il gestore di incolla personalizzato è necessario / funziona solo per i contenteditablenodi. Poiché entrambi i campi textareae i inputcampi normali non supportano affatto l'incollaggio di contenuto HTML, non è necessario fare nulla qui.


Ho dovuto sbarazzarmene .originalEventnel gestore eventi (riga 3) per farlo funzionare. Così gli sguardi linea completa in questo modo: const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');. Funziona con le ultime versioni di Chrome, Safari, Firefox.
Pwdr

3

Firefox non ti consente di accedere ai dati degli appunti, quindi dovrai fare un "hack" per farlo funzionare. Non sono stato in grado di trovare una soluzione completa, tuttavia puoi risolverla per incolla ctrl + v creando un'area di testo e incollandola invece:

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

Stavo anche lavorando su una pasta di testo semplice e ho iniziato a odiare tutti gli errori execCommand e getData, quindi ho deciso di farlo nel modo classico e funziona a meraviglia:

$('#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);
});

Il codice con le mie annotazioni può essere trovato qui: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript


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

Il codice sopra funziona per me in IE10 e IE11 e ora funziona anche in Chrome e Safari. Non testato in Firefox.


1

In IE11, execCommand non funziona bene. Uso il codice seguente per IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> è la mia casella div.

Ho letto i dati degli appunti da window.clipboardData e modifico il textContent del div e do il cursore.

Dò timeout per l'impostazione del cursore, perché se non imposto il timeout, un cursore va alla fine del div.

e dovresti leggere clipboardData in IE11 nel modo seguente. Se non lo fai, il carattere di nuova riga non viene gestito correttamente, quindi il cursore va storto.

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

Testato su IE11 e Chrome. Potrebbe non funzionare su 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);

0

Dopo lunghe ricerche e tentativi ho trovato in qualche modo una soluzione ottimale

cosa è importante tenere a mente

// /\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>   


0

OK perché tutti stanno cercando di aggirare i dati degli appunti, controllando l'evento di pressione dei tasti e usando execCommand.

Ho pensato a questo

CODICE

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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.