Truco de Javascript para 'pegar como texto sin formato' en execCommand

106

Tengo un editor básico basado en execCommandseguir el ejemplo presentado aquí. Hay tres formas de pegar texto dentro del execCommandárea:

  • Ctrl+V
  • Clic derecho -> Pegar
  • Clic derecho -> Pegar como texto sin formato

Quiero permitir pegar solo texto sin formato sin ningún marcado HTML. ¿Cómo puedo forzar las dos primeras acciones para pegar texto sin formato?

Posible solución: La forma en que puedo pensar es configurar el oyente para los eventos de activación para ( Ctrl+ V) y eliminar las etiquetas HTML antes de pegar.

  1. ¿Es la mejor solución?
  2. ¿Es a prueba de balas para evitar cualquier marcado HTML en la pasta?
  3. ¿Cómo agregar oyente al clic derecho -> Pegar?
Googlebot
fuente
5
Como nota al margen, ¿también quieres encargarte de que el texto se arrastre al editor? Esa es otra forma en que HTML puede filtrarse en el editor.
pimvdb
1
@pimvdb Tu respuesta fue suficiente para mi necesidad. Solo por curiosidad, ¿existe un método simple para evitar las fugas arrastradas también?
Googlebot
2
Pensé que esto haría el trabajo: jsfiddle.net/HBEzc/2 . Pero, al menos en Chrome, el texto siempre se inserta al principio del editor, desafortunadamente.
pimvdb
Debe usar la API del portapapeles como explicación aquí. youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

Respuestas:

247

Interceptará el pasteevento, cancelará pastee insertará manualmente la representación de texto del portapapeles:
http://jsfiddle.net/HBEzc/ . Este debería ser el más confiable:

  • Captura todo tipo de pegado ( Ctrl+ V, menú contextual, etc.)
  • Le permite obtener los datos del portapapeles directamente como texto, por lo que no tiene que hacer trucos desagradables para reemplazar HTML.

Sin embargo, no estoy seguro de la compatibilidad con varios navegadores.

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);
});
pimvdb
fuente
4
@Ali: Me perdí algo obvio. Si textcontiene HTML (por ejemplo, si copia código HTML como texto sin formato), lo pegará como HTML. Aquí hay una solución, pero no es muy bonita: jsfiddle.net/HBEzc/3 .
pimvdb
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');proporciona un poco más de compatibilidad entre navegadores
Duncan Walker
10
Esto rompe la funcionalidad de deshacer. (Ctrl + Z)
Rudey
2
Gran solución, pero difiere del comportamiento predeterminado. Si el usuario copia algo así, <div></div>el contenido se agregará como un elemento secundario del elemento contenteditable. Lo arreglé así:document.execCommand("insertText", false, text);
Jason Newell
5
Encontré insertHTMLy insertTextno trabajo en IE11, sin embargo, document.execCommand('paste', false, text);funciona bien. Aunque eso no parece funcionar en otros navegadores> _>.
Jamie Barker
39

No pude obtener la respuesta aceptada aquí para que funcione en IE, así que busqué un poco y encontré esta respuesta que funciona en IE11 y las últimas versiones de Chrome y 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
fuente
1
Gracias, estaba luchando con el mismo problema ... insertText no funcionaba ni en IE11 ni en el último FF :)
HeberLZ
1
¿Es posible que en algunos casos pegue el texto dos veces tanto en Firefox como en Chrome? Me parece a mí ..
Fanky
1
@Fanky No tenía ese problema cuando lo creé, sin embargo, ya no trabajo en el lugar donde creé este código, ¡así que no podría decirte si todavía funciona! ¿Podría describir cómo se pega dos veces?
Jamie Barker
2
@Fanky Vea si puede volver a crearlo aquí: jsfiddle.net/v2qbp829 .
Jamie Barker
2
Ahora parece que el problema que tuve se debió a llamar a su script desde un archivo que fue cargado por un script. No puedo pegar en textarea ni ingresar en su violín en FF 47.0.1 (puedo hacerlo en Chrome), pero puedo pegar en div contenteditable, que es clave para mí. ¡Gracias!
Fanky
21

Una solución cercana como pimvdb. Pero funciona 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);
    }   
});
Adriano Galesso Alves
fuente
5
Me gusta la contentasignación de variables de cortocircuito . Descubrí que el uso de getData('Text')varios navegadores funciona, por lo que podría hacer esa asignación solo una vez como esta: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');Entonces solo tendría que usar la lógica para el comando pegar / insertar entre navegadores.
gfullam
6
No creo que puedas escribir document.selection.createRange().pasteHTML(content)... solo probé en IE11 y no funciona así.
vsync
3
document.execCommand('insertText', false, content)no funciona a partir de IE11 y Edge. Además, las últimas versiones de Chrome ahora son compatibles document.execCommand('paste', false, content), lo que es más simple. Podrían estar desaprobando insertText.
Cannicidio
19

Por supuesto, la pregunta ya está respondida y el tema es muy antiguo, pero quiero brindar mi solución, ya que es simple y limpio:

Esto está dentro de mi paste-event en mi 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);
}

La otra parte es de otra publicación SO que ya no pude encontrar ...


ACTUALIZACIÓN 19.11.2014: El otro SO-post

programador web
fuente
2
Creo que te refieres a esta publicación: stackoverflow.com/questions/21257688/…
gfullam
1
No pareció funcionar para mí en Safari. Quizás algo esté mal
Cannicide
8

Ninguna de las respuestas publicadas parece funcionar en todos los navegadores o la solución es demasiado complicada:

  • El comando insertTextno es compatible con IE
  • El uso del pastecomando da como resultado un error de desbordamiento de pila en IE11

Lo que funcionó para mí (IE11, Edge, Chrome y FF) es lo siguiente:

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

Tenga en cuenta que el controlador de pegado personalizado solo es necesario / funciona para los contenteditablenodos. Como tanto los campos textareasimples como los inputcampos sin formato no permiten pegar contenido HTML en absoluto, no es necesario hacer nada aquí.

dpr
fuente
Tuve que deshacerme del .originalEventcontrolador de eventos (línea 3) para que esto funcione. Por lo que las miradas línea completa de esta manera: const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');. Funciona en los últimos Chrome, Safari, Firefox.
Pwdr
3

Firefox no le permite acceder a los datos del portapapeles, por lo que deberá realizar un "truco" para que funcione. No he podido encontrar una solución completa, sin embargo, puede solucionarlo para las pastas ctrl + v creando un área de texto y pegándola en su lugar:

//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);
}
MedianocheTortuga
fuente
2

También estaba trabajando en una pasta de texto sin formato y comencé a odiar todos los errores execCommand y getData, así que decidí hacerlo de la manera clásica y funciona como un encanto:

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

El código con mis notaciones se puede encontrar aquí: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

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

El código anterior funciona para mí en IE10 e IE11 y ahora también funciona en Chrome y Safari. No probado en Firefox.

Nikhil Ghuse
fuente
1

En IE11, execCommand no funciona bien. Yo uso el siguiente código para IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> es mi caja div.

Leo los datos del portapapeles de window.clipboardData y modifico el textContent de div y le doy un intercalado.

Doy un tiempo de espera para establecer un símbolo de intercalación, porque si no establezco un tiempo de espera, un símbolo de intercalación va al final de div.

y debe leer clipboardData en IE11 de la siguiente manera. Si no lo hace, el carácter de nueva línea no se maneja correctamente, por lo que el símbolo de intercalación sale mal.

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

Probado en IE11 y Chrome. Puede que no funcione en 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
fuente
0

Después de buscar y probar, he encontrado de alguna manera una solución óptima

lo que es importante tener en cuenta

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

Bien, ya que todo el mundo está tratando de trabajar con los datos del portapapeles, verificando el evento de pulsación de tecla y usando execCommand.

Pensé en esto

CÓDIGO

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
fuente