Javascript-Trick für 'Einfügen als einfacher Text' in execCommand

106

Ich habe einen grundlegenden Editor, der darauf basiert, execCommanddem hier vorgestellten Beispiel zu folgen. Es gibt drei Möglichkeiten, Text in den execCommandBereich einzufügen :

  • Ctrl+V
  • Rechtsklick -> Einfügen
  • Rechtsklick -> Als Nur-Text einfügen

Ich möchte zulassen, dass nur einfacher Text ohne HTML-Markup eingefügt wird. Wie kann ich die ersten beiden Aktionen zwingen, einfachen Text einzufügen?

Mögliche Lösung: Ich kann mir vorstellen, den Listener für Keyup-Ereignisse für ( Ctrl+ V) festzulegen und HTML-Tags vor dem Einfügen zu entfernen .

  1. Ist es die beste Lösung?
  2. Ist es kugelsicher, HTML-Markups beim Einfügen zu vermeiden?
  3. Wie füge ich einen Listener zu Rechtsklick -> Einfügen hinzu?
Googlebot
quelle
5
Möchten Sie als Randnotiz auch dafür sorgen, dass Text in den Editor gezogen wird? Auf diese Weise kann HTML auch in den Editor gelangen.
Pimvdb
1
@pimvdb Deine Antwort war genug für meine Bedürfnisse. Gibt es aus Neugier eine einfache Methode, um auch schleppende Leckagen zu vermeiden?
Googlebot
2
Ich dachte, das würde den Job machen: jsfiddle.net/HBEzc/2 . Aber zumindest in Chrome wird der Text leider immer am Anfang des Editors eingefügt.
Pimvdb
Sie müssen die API der Zwischenablage als Erklärung hier verwenden. youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

Antworten:

246

Es wird das pasteEreignis abfangen, das Ereignis abbrechen pasteund die Textdarstellung der Zwischenablage manuell einfügen:
http://jsfiddle.net/HBEzc/ . Dies sollte am zuverlässigsten sein:

  • Es fängt alle Arten des Einfügens ab ( Ctrl+ V, Kontextmenü usw.)
  • Sie können die Daten der Zwischenablage direkt als Text abrufen, sodass Sie keine hässlichen Hacks durchführen müssen, um HTML zu ersetzen.

Ich bin mir jedoch nicht sicher, ob Cross-Browser unterstützt wird.

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
quelle
4
@Ali: Ich habe etwas Offensichtliches verpasst. Wenn es textHTML enthält (z. B. wenn Sie HTML-Code als einfachen Text kopieren), wird es tatsächlich als HTML eingefügt. Hier ist eine Lösung dafür, aber sie ist nicht sehr hübsch: jsfiddle.net/HBEzc/3 .
Pimvdb
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');bietet etwas mehr Cross-Browser-Kompatibilität
Duncan Walker
10
Dies unterbricht die Rückgängig-Funktionalität. (Strg + Z)
Rudey
2
Tolle Lösung, aber dies weicht vom Standardverhalten ab. Wenn der Benutzer so etwas kopiert <div></div>, wird der Inhalt als untergeordnetes Element des inhaltsbearbeitbaren Elements hinzugefügt. Ich habe es so behoben:document.execCommand("insertText", false, text);
Jason Newell
5
Ich habe IE11 gefunden insertHTMLund insertTextarbeite nichtdocument.execCommand('paste', false, text); funktioniert . Obwohl das dann in anderen Browsern nicht zu funktionieren scheint> _>.
Jamie Barker
39

Ich konnte die akzeptierte Antwort hier nicht bekommen, um im IE zu arbeiten, also habe ich ein bisschen herumgesucht und bin zu dieser Antwort gekommen, die in IE11 und den neuesten Versionen von Chrome und Firefox funktioniert.

$('[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
quelle
1
Vielen Dank, ich hatte mit dem gleichen Problem zu kämpfen ... insertText funktionierte weder mit IE11 noch mit dem neuesten FF :)
HeberLZ
1
Ist es möglich, dass der Text in einigen Fällen sowohl in Firefox als auch in Chrome zweimal eingefügt wird? Scheint mir ..
Fanky
1
@Fanky Ich hatte dieses Problem nicht, als ich es erstellt habe, aber ich arbeite nicht mehr an dem Ort, an dem ich diesen Code erstellt habe, sodass ich Ihnen nicht sagen kann, ob es noch funktioniert! Können Sie beschreiben, wie es zweimal eingefügt wird?
Jamie Barker
2
@Fanky Sehen Sie, ob Sie es hier neu erstellen können: jsfiddle.net/v2qbp829 .
Jamie Barker
2
Scheint nun, dass das Problem, das ich hatte, darauf zurückzuführen war, dass ich Ihr Skript aus einer Datei aufgerufen habe, die selbst von einem Skript geladen wurde. Ich kann in FF 47.0.1 weder in Textbereich einfügen noch in Ihre Geige eingeben (kann dies in Chrome tun), aber ich kann in div contenteditable einfügen, was für mich der Schlüssel ist. Vielen Dank!
Fanky
21

Eine enge Lösung als pimvdb. Aber es funktioniert mit FF, Chrome und 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
quelle
5
Ich mag die Kurzschlussvariablenzuordnung content. Ich habe festgestellt, dass die Verwendung getData('Text')browserübergreifend funktioniert, sodass Sie diese Zuweisung nur einmal wie folgt vornehmen können: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');Dann müssten Sie nur die Logik für den browserübergreifenden Befehl zum Einfügen / Einfügen verwenden.
Gfullam
6
Ich glaube nicht, dass du schreiben kannst document.selection.createRange().pasteHTML(content)... nur auf IE11 getestet und es funktioniert nicht so.
vsync
3
document.execCommand('insertText', false, content)funktioniert nicht ab IE11 und Edge. Außerdem unterstützen jetzt die neuesten Versionen von Chrome document.execCommand('paste', false, content), was einfacher ist. Sie könnten veraltet sein insertText.
Cannicide
19

Natürlich ist die Frage bereits beantwortet und das Thema sehr alt, aber ich möchte meine Lösung anbieten, da es einfach und sauber ist:

Dies ist in meinem Einfügeereignis auf meinem 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);
}

Der else-Teil stammt aus einem anderen SO-Post, den ich nicht mehr finden konnte ...


UPDATE 19.11.2014: Der andere SO-Beitrag

Webprogrammierer
quelle
2
Ich denke, Sie beziehen sich auf diesen Beitrag: stackoverflow.com/questions/21257688/…
gfullam
1
Schien in Safari nicht für mich zu funktionieren. Vielleicht stimmt etwas nicht
Cannicide
8

Keine der veröffentlichten Antworten scheint wirklich browserübergreifend zu funktionieren, oder die Lösung ist zu kompliziert:

  • Der Befehl insertText wird vom IE nicht unterstützt
  • Die Verwendung des pasteBefehls führt zu einem Stapelüberlauffehler in IE11

Was für mich (IE11, Edge, Chrome und FF) funktioniert hat, ist Folgendes:

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

Beachten Sie, dass der benutzerdefinierte Einfüge-Handler nur für contenteditableKnoten benötigt wird / funktioniert . Da sowohl textareaeinfache als auch einfache inputFelder das Einfügen von HTML-Inhalten überhaupt nicht unterstützen, muss hier nichts unternommen werden.

dpr
quelle
Ich musste .originalEventden Event-Handler (Zeile 3) loswerden , damit dies funktioniert. Die komplette Linie sieht also so aus : const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');. Funktioniert in den neuesten Versionen von Chrome, Safari und Firefox.
Pwdr
3

In Firefox können Sie nicht auf die Daten der Zwischenablage zugreifen, daher müssen Sie einen "Hack" durchführen, damit sie funktionieren. Ich konnte keine vollständige Lösung finden, aber Sie können sie für Strg + V-Einfügungen beheben, indem Sie stattdessen einen Textbereich erstellen und einfügen:

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

Ich habe auch an einer einfachen Texteinfügung gearbeitet und angefangen, alle execCommand- und getData-Fehler zu hassen. Deshalb habe ich mich für die klassische Methode entschieden und es funktioniert wie ein Zauber:

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

Den Code mit meinen Notationen finden Sie hier: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

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

Der obige Code funktioniert für mich in IE10 und IE11 und jetzt auch in Chrome und Safari. Nicht in Firefox getestet.

Nikhil Ghuse
quelle
1

In IE11 funktioniert execCommand nicht gut. Ich benutze den folgenden Code für IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> ist meine Div-Box.

Ich lese Zwischenablagedaten aus window.clipboardData und ändere den textContent von div und gebe caret.

Ich gebe eine Zeitüberschreitung für das Einstellen von Caret, denn wenn ich keine Zeitüberschreitung einstelle, geht ein Caret zum Ende von div.

und Sie sollten clipboardData in IE11 wie folgt lesen. Wenn Sie dies nicht tun, wird Newline Caracter nicht richtig behandelt, sodass Caret schief geht.

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

Getestet auf IE11 und Chrom. Es funktioniert möglicherweise nicht auf 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
quelle
0

Nach langem Suchen und Ausprobieren habe ich irgendwie eine optimale Lösung gefunden

Was ist wichtig zu beachten

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

OK, da alle versuchen, die Daten der Zwischenablage zu umgehen, das Tastendruckereignis zu überprüfen und execCommand zu verwenden.

Ich habe daran gedacht

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
quelle