Truque de Javascript para 'colar como texto simples' em execCommand

106

Eu tenho um editor básico baseado em execCommandseguir o exemplo apresentado aqui. Existem três maneiras de colar texto dentro da execCommandárea:

  • Ctrl+V
  • Clique com o botão direito -> Colar
  • Clique com o botão direito -> Colar como texto simples

Quero permitir a colagem apenas de texto sem formatação, sem nenhuma marcação HTML. Como posso forçar as duas primeiras ações a colar Texto Simples?

Solução possível: a maneira que posso imaginar é definir o ouvinte para eventos de ativação para ( Ctrl+ V) e remover as tags HTML antes de colar.

  1. É a melhor solução?
  2. É à prova de balas evitar qualquer marcação HTML na colagem?
  3. Como adicionar ouvinte ao botão direito -> colar?
Googlebot
fonte
5
Como observação lateral, você também deseja cuidar do texto sendo arrastado para o editor? Essa é outra maneira que o HTML pode vazar para o editor.
pimvdb
1
@pimvdb Sua resposta foi suficiente para minha necessidade. Só por curiosidade, existe um método simples para evitar o vazamento arrastado também?
Googlebot de
2
Achei que isso daria certo: jsfiddle.net/HBEzc/2 . Mas pelo menos no Chrome, o texto é sempre inserido no início do editor, infelizmente.
pimvdb
Você precisa usar a API da área de transferência como explicação aqui. youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

Respostas:

247

Ele irá interceptar o pasteevento, cancelar o pastee inserir manualmente a representação de texto da área de transferência:
http://jsfiddle.net/HBEzc/ . Este deve ser o mais confiável:

  • Ele captura todos os tipos de colagem ( Ctrl+ V, menu de contexto, etc.)
  • Ele permite que você obtenha os dados da área de transferência diretamente como texto, para que você não precise fazer hacks feios para substituir o HTML.

Não tenho certeza do suporte cross-browser, no entanto.

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
fonte
4
@Ali: Perdi algo óbvio. Se textcontiver HTML (por exemplo, se você copiar o código HTML como texto simples), ele irá realmente colá-lo como HTML. Aqui está uma solução para isso, mas não é muito bonita: jsfiddle.net/HBEzc/3 .
pimvdb de
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');oferece um pouco mais de compatibilidade entre navegadores
Duncan Walker
10
Isso quebra a funcionalidade de desfazer. (Ctrl + Z)
Rudey
2
Ótima solução, mas diverge do comportamento padrão. Se o usuário copiar algo assim, <div></div>o conteúdo será adicionado como um elemento filho do elemento contenteditable. Eu consertei assim:document.execCommand("insertText", false, text);
Jason Newell
5
Eu encontrei insertHTMLe insertTextnão trabalho no IE11, mas document.execCommand('paste', false, text);funciona bem. Embora isso pareça não funcionar em outros navegadores> _>.
Jamie Barker
39

Não consegui obter a resposta aceita aqui para funcionar no IE, então fiz algumas pesquisas e cheguei a esta resposta que funciona no IE11 e nas versões mais recentes do 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);
    }
});
Jamie Barker
fonte
1
Obrigado, eu estava lutando com o mesmo problema ... insertText não estava funcionando no IE11 nem no FF mais recente :)
HeberLZ
1
É possível que, em alguns casos, cole o texto duas vezes no Firefox e no Chrome? Parece-me ..
Fanky
1
@Fanky Não tive esse problema quando o criei, no entanto, não trabalho mais no local onde criei este código, então não posso dizer se ele ainda funciona! Você poderia descrever como ele está colando duas vezes?
Jamie Barker
2
@Fanky Veja se você pode recriá-lo aqui: jsfiddle.net/v2qbp829 .
Jamie Barker
2
Parece agora que o problema que tive foi devido a chamar seu script de um arquivo que foi carregado por um script. Não consigo colar em textarea nem inserir em seu fiddle no FF 47.0.1 (posso fazer isso no cromo), mas posso colar em div contenteditable, que é fundamental para mim. Obrigado!
Fanky
21

Uma solução próxima como pimvdb. Mas está funcionando no 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
fonte
5
Gosto da contentatribuição de variáveis ​​de curto-circuito . Descobri que usar getData('Text')o navegador cruzado funciona, então você poderia fazer essa atribuição apenas uma vez, assim: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');Então você só teria que usar a lógica para o comando colar / inserir do navegador cruzado.
gfullam de
6
Eu não acho que você pode escrever document.selection.createRange().pasteHTML(content)... apenas testei no IE11 e não funciona assim.
vsync
3
document.execCommand('insertText', false, content)não funciona a partir do IE11 e do Edge. Além disso, as versões mais recentes do Chrome agora são compatíveis document.execCommand('paste', false, content), o que é mais simples. Eles podem estar obsoletos insertText.
Cannicídio de
19

Claro que a pergunta já foi respondida e o tópico é muito antigo, mas quero fornecer minha solução, pois é simples e limpa:

Isso está dentro do meu evento de colagem em meu div contenteditable.

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

A outra parte é de outro SO-post que não consegui encontrar mais ...


ATUALIZAÇÃO 19.11.2014: O outro SO-post

webprogrammer
fonte
2
Acho que você está se referindo a esta postagem: stackoverflow.com/questions/21257688/…
gfullam
1
Não pareceu funcionar para mim no Safari. Talvez algo esteja errado
Cannicídio
8

Nenhuma das respostas postadas realmente parece funcionar em qualquer navegador ou a solução é muito complicada:

  • O comando insertTextnão é compatível com o IE
  • Usar o pastecomando resulta em erro de estouro de pilha no IE11

O que funcionou para mim (IE11, Edge, Chrome e FF) é o seguinte:

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

Observe que o manipulador de colagem personalizado é necessário / funcionando apenas para contenteditablenós. Como os campos textareae os inputcampos simples não suportam a colagem de conteúdo HTML, nada precisa ser feito aqui.

dpr
fonte
Eu tive que me livrar do .originalEventmanipulador de eventos (linha 3) para fazer isso funcionar. Então os olhares linha completa assim: const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');. Funciona na versão mais recente do Chrome, Safari, Firefox.
Pwdr de
3

O Firefox não permite que você acesse os dados da área de transferência, então você precisará fazer um 'hack' para fazê-lo funcionar. Não consegui encontrar uma solução completa, no entanto, você pode corrigi-lo para ctrl + v pastas criando uma área de texto e colando nela:

//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);
}
MidnightTortoise
fonte
2

Eu também estava trabalhando em uma pasta de texto simples e comecei a odiar todos os erros execCommand e getData, então decidi fazer da maneira clássica e funciona perfeitamente:

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

O código com minhas anotações pode ser encontrado aqui: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

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

O código acima funciona para mim no IE10 e no IE11 e agora também funciona no Chrome e Safari. Não testado no Firefox.

Nikhil Ghuse
fonte
1

No IE11, execCommand não funciona bem. Eu uso o código abaixo para o IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> é minha caixa div.

Eu leio os dados da área de transferência de window.clipboardData, modifico o textContent do div e dou o cursor.

Eu dou tempo limite para definir o acento circunflexo, porque se eu não definir o tempo limite, um acento circunflexo vai para o final de div.

e você deve ler clipboardData no IE11 da maneira abaixo. Se você não fizer isso, o caractere de nova linha não será tratado corretamente, então o cursor dá errado.

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

Testado no IE11 e Chrome. Pode não funcionar no 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
fonte
0

Depois de pesquisar e tentar, encontrei de alguma forma a solução ideal

o que é importante ter em 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>   

mooga
fonte
0

OK, pois todos estão tentando contornar os dados da área de transferência, verificando o evento de pressionamento de tecla e usando execCommand.

Eu pensei nisso

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
fonte