Astuce Javascript pour 'coller en texte brut' dans execCommand

107

J'ai un éditeur de base basé sur execCommandl'exemple présenté ici. Il existe trois façons de coller du texte dans la execCommandzone:

  • Ctrl+V
  • Clic droit -> Coller
  • Clic droit -> Coller en tant que texte brut

Je souhaite autoriser le collage uniquement de texte brut sans balisage HTML. Comment puis-je forcer les deux premières actions à coller du texte brut?

Solution possible: La façon dont je peux penser est de définir l'auditeur pour les événements de keyup pour ( Ctrl+ V) et de supprimer les balises HTML avant de coller.

  1. Est-ce la meilleure solution?
  2. Est-il à toute épreuve d'éviter tout balisage HTML en pâte?
  3. Comment ajouter un auditeur au clic droit -> Coller?
Googlebot
la source
5
En passant, voulez-vous également vous occuper du texte qui est glissé dans l'éditeur? C'est une autre façon dont HTML peut s'infiltrer dans l'éditeur.
pimvdb
1
@pimvdb Votre réponse était suffisante pour mon besoin. Juste par curiosité, existe-t-il une méthode simple pour éviter également les fuites traînées?
Googlebot
2
J'ai pensé que cela ferait le travail: jsfiddle.net/HBEzc/2 . Mais sur Chrome au moins, le texte est toujours inséré au début de l'éditeur, malheureusement.
pimvdb
Vous devez utiliser l'API du presse-papiers comme expliqué ici. youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

Réponses:

248

Il interceptera l' pasteévénement, annulera le pasteet insérera manuellement la représentation textuelle du presse-papiers:
http://jsfiddle.net/HBEzc/ . Cela devrait être le plus fiable:

  • Il attrape toutes sortes de collages ( Ctrl+ V, menu contextuel, etc.)
  • Il vous permet d'obtenir les données du presse-papiers directement sous forme de texte, vous n'avez donc pas à faire de vilains hacks pour remplacer le HTML.

Je ne suis pas sûr de la prise en charge de plusieurs navigateurs.

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
la source
4
@Ali: J'ai raté quelque chose d'évident. Si textcontient du HTML (par exemple, si vous copiez du code HTML en texte brut), il le collera en fait en HTML. Voici une solution, mais ce n'est pas très joli: jsfiddle.net/HBEzc/3 .
pimvdb
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');offre un peu plus de compatibilité entre navigateurs
Duncan Walker
10
Cela rompt la fonctionnalité d'annulation. (Ctrl + Z)
Rudey
2
Excellente solution, mais cela diverge du comportement par défaut. Si l'utilisateur copie quelque chose comme <div></div>ce contenu sera ajouté en tant qu'élément enfant de l'élément contenteditable. Je l'ai corrigé comme ceci:document.execCommand("insertText", false, text);
Jason Newell
5
J'ai trouvé insertHTMLet insertTextne fonctionne pas dans IE11, mais document.execCommand('paste', false, text);fonctionne très bien. Bien que cela ne semble pas fonctionner dans d'autres navigateurs> _>.
Jamie Barker
39

Je n'ai pas pu obtenir la réponse acceptée ici pour fonctionner dans IE, alors j'ai fait quelques recherches et suis venu à cette réponse qui fonctionne dans IE11 et les dernières versions de Chrome et 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
la source
1
Merci, je luttais avec le même problème ... insertText ne fonctionnait ni sur IE11 ni sur le dernier FF :)
HeberLZ
1
Est-il possible que, dans certains cas, il colle le texte deux fois dans Firefox et Chrome? Il me semble ..
Fanky
1
@Fanky Je n'ai pas eu ce problème lorsque je l'ai créé, mais je ne travaille plus à l'endroit où j'ai créé ce code donc je ne pourrais pas vous dire s'il fonctionne toujours! Pouvez-vous décrire comment il se colle deux fois?
Jamie Barker
2
@Fanky Voyez si vous pouvez le recréer ici: jsfiddle.net/v2qbp829 .
Jamie Barker
2
Il semble maintenant que le problème que j'avais était dû à l'appel de votre script à partir d'un fichier lui-même chargé par un script. Je ne peux pas coller ni dans textarea ni entrer dans votre violon dans FF 47.0.1 (je peux le faire dans chrome), mais je peux coller dans div contenteditable, ce qui est essentiel pour moi. Merci!
Fanky
21

Une solution proche comme pimvdb. Mais cela fonctionne avec FF, Chrome et 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
la source
5
J'aime l' contentaffectation des variables de court-circuit . J'ai trouvé que l'utilisation getData('Text')fonctionne avec plusieurs navigateurs, vous pouvez donc effectuer cette affectation une seule fois comme ceci: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');il vous suffira alors d'utiliser la logique pour la commande coller / insérer entre les navigateurs.
gfullam
6
Je ne pense pas que vous puissiez écrire document.selection.createRange().pasteHTML(content)... juste testé sur IE11 et ça ne marche pas comme ça.
vsync
3
document.execCommand('insertText', false, content)ne fonctionne pas à partir de IE11 et Edge. De plus, les dernières versions de Chrome prennent désormais en charge document.execCommand('paste', false, content), ce qui est plus simple. Ils pourraient être obsolètes insertText.
Cannicide
19

Bien sûr, la question est déjà répondue et le sujet très ancien mais je veux fournir ma solution car elle est simple et propre:

Ceci est dans mon événement de collage sur mon 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'autre partie provient d'un autre article SO que je ne pouvais plus trouver ...


UPDATE 19.11.2014: L'autre SO-post

développeur web
la source
2
Je pense que vous faites référence à ce post: stackoverflow.com/questions/21257688/…
gfullam
1
Cela ne semblait pas fonctionner pour moi dans Safari. Peut-être que quelque chose ne va pas
Cannicide
8

Aucune des réponses publiées ne semble vraiment fonctionner sur plusieurs navigateurs ou la solution est trop compliquée:

  • La commande insertTextn'est pas prise en charge par IE
  • L'utilisation de la pastecommande entraîne une erreur de débordement de pile dans IE11

Ce qui a fonctionné pour moi (IE11, Edge, Chrome et FF) est le suivant:

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

Notez que le gestionnaire de collage personnalisé n'est nécessaire / fonctionne que pour les contenteditablenœuds. Comme les champs textareasimples et les inputchamps simples ne prennent pas du tout en charge le collage de contenu HTML, il n'y a donc rien à faire ici.

dpr
la source
J'ai dû me débarrasser du .originalEventgestionnaire d'événements (ligne 3) pour que cela fonctionne. Ainsi , les regards de ligne complète comme ceci: const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');. Fonctionne dans les derniers Chrome, Safari, Firefox.
Pwdr
3

Firefox ne vous permet pas d'accéder aux données du presse-papiers, vous devrez donc faire un «hack» pour le faire fonctionner. Je n'ai pas été en mesure de trouver une solution complète, mais vous pouvez le corriger pour les pâtes ctrl + v en créant une zone de texte et en la collant à la place:

//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);
}
MinuitTortoise
la source
2

Je travaillais également sur un collage de texte brut et j'ai commencé à détester toutes les erreurs execCommand et getData, alors j'ai décidé de le faire de manière classique et cela fonctionne comme un charme:

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

Le code avec mes notations peut être trouvé ici: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

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

Le code ci-dessus fonctionne pour moi dans IE10 et IE11 et fonctionne maintenant également dans Chrome et Safari. Non testé dans Firefox.

Nikhil Ghuse
la source
1

Dans IE11, execCommand ne fonctionne pas bien. J'utilise le code ci-dessous pour IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> est ma boîte div.

J'ai lu les données du presse-papiers à partir de window.clipboardData et je modifie le textContent de div et donne le signe caret.

Je donne un timeout pour définir le caret, car si je ne règle pas le timeout, un caret va à la fin de div.

et vous devriez lire clipboardData dans IE11 de la manière ci-dessous. Si vous ne le faites pas, le caractère de nouvelle ligne n'est pas géré correctement, donc caret va mal.

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

Testé sur IE11 et chrome. Cela peut ne pas fonctionner sur 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
la source
0

Après avoir longuement cherché et essayé, j'ai trouvé en quelque sorte une solution optimale

ce qu'il est important de garder à l'esprit

// /\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
la source
0

OK car tout le monde essaie de contourner les données du presse-papiers, de vérifier l'événement de pression de touche et d'utiliser execCommand.

J'ai pensé à ça

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
la source