Trik Javascript untuk 'paste sebagai teks biasa` di execCommand

106

Saya memiliki editor dasar berdasarkan execCommandcontoh yang diperkenalkan di sini. Ada tiga cara untuk menempelkan teks di dalam execCommandarea:

  • Ctrl+V
  • Klik Kanan -> Tempel
  • Klik Kanan -> Tempel Sebagai Teks Biasa

Saya ingin mengizinkan hanya menempelkan teks biasa tanpa markup HTML. Bagaimana cara memaksa dua tindakan pertama untuk menempelkan Teks Biasa?

Solusi yang Mungkin: Cara yang dapat saya pikirkan adalah menyetel listener untuk acara keyup untuk ( Ctrl+ V) dan menghapus tag HTML sebelum menempel.

  1. Apakah ini solusi terbaik?
  2. Apakah antipeluru untuk menghindari markup HTML dalam bentuk tempel?
  3. Bagaimana cara menambahkan pendengar ke Klik Kanan -> Tempel?
Googlebot
sumber
5
Sebagai catatan tambahan, apakah Anda juga ingin menangani teks yang ditarik ke editor? Itu adalah cara lain HTML dapat bocor ke editor.
pimvdb
1
@pimvdb Jawaban Anda sudah cukup untuk kebutuhan saya. Hanya karena penasaran, adakah metode sederhana untuk menghindari kebocoran yang berkepanjangan juga?
Googlebot
2
Saya pikir ini akan berhasil: jsfiddle.net/HBEzc/2 . Tapi setidaknya di Chrome, teks selalu disisipkan di awal editor, sayangnya.
pimvdb
Anda perlu menggunakan api clipboard sebagai explaiend di sini. youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

Jawaban:

247

Ini akan mencegat pasteacara, membatalkan paste, dan secara manual menyisipkan representasi teks dari papan klip:
http://jsfiddle.net/HBEzc/ . Ini harus yang paling andal:

  • Ini menangkap semua jenis penempelan ( Ctrl+V , menu konteks, dll.)
  • Ini memungkinkan Anda untuk mendapatkan data papan klip langsung sebagai teks, jadi Anda tidak perlu melakukan peretasan buruk untuk mengganti HTML.

Saya tidak yakin dengan dukungan lintas browser.

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.dll
sumber
4
@ Ali: Saya melewatkan sesuatu yang jelas. Jika textberisi HTML (mis. Jika Anda menyalin kode HTML sebagai teks biasa) maka itu akan benar-benar menempelkannya sebagai HTML. Inilah solusinya, tetapi tidak terlalu bagus: jsfiddle.net/HBEzc/3 .
pimvdb
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');menyediakan sedikit lebih banyak kompatibilitas lintas-browser
Duncan Walker
10
Ini merusak fungsionalitas urungkan. (Ctrl + Z)
Rudey
2
Solusi hebat, tetapi ini menyimpang dari perilaku default. Jika pengguna menyalin sesuatu seperti <div></div>itu, konten akan ditambahkan sebagai elemen turunan dari elemen konten yang dapat diedit. Saya memperbaikinya seperti ini:document.execCommand("insertText", false, text);
Jason Newell
5
Saya menemukan insertHTMLdan insertTexttidak berfungsi di IE11, namun document.execCommand('paste', false, text);berfungsi dengan baik. Meskipun itu tampaknya tidak berfungsi di browser lain> _>.
Jamie Barker
39

Saya tidak bisa mendapatkan jawaban yang diterima di sini untuk bekerja di IE jadi saya mencari-cari dan menemukan jawaban ini yang berfungsi di IE11 dan versi terbaru Chrome dan 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
sumber
1
Terima kasih, saya mengalami masalah yang sama ... insertText tidak berfungsi pada IE11 atau FF terbaru :)
HeberLZ
1
Mungkinkah dalam beberapa kasus teks menempel dua kali di Firefox dan Chrome? Menurut saya ..
Fanky
1
@Fanky Saya tidak memiliki masalah itu ketika saya membuatnya, namun saya tidak lagi bekerja di tempat saya membuat kode ini jadi saya tidak bisa memberi tahu Anda apakah itu masih berfungsi! Bisakah Anda menjelaskan bagaimana itu menempelkan dua kali?
Jamie Barker
2
@Fanky Lihat apakah Anda dapat membuatnya kembali di sini: jsfiddle.net/v2qbp829 .
Jamie Barker
2
Tampaknya sekarang masalah yang saya hadapi adalah karena memanggil skrip Anda dari file yang dimuat sendiri oleh skrip. Saya tidak dapat menempelkan ke area teks atau pun input di biola Anda di FF 47.0.1 (dapat melakukannya di chrome), tetapi dapat menempel ke div contenteditable, yang merupakan kuncinya bagi saya. Terima kasih!
Fanky
21

Solusi dekat sebagai pimvdb. Tetapi ini berfungsi dari FF, Chrome dan 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
sumber
5
Saya suka contenttugas variabel hubung singkat . Saya telah menemukan bahwa menggunakan getData('Text')berfungsi lintas browser, jadi Anda dapat membuat tugas itu hanya sekali seperti ini: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');Maka Anda hanya perlu menggunakan logika untuk perintah tempel / sisipkan lintas browser.
gfullam
6
Saya tidak berpikir Anda dapat menulis document.selection.createRange().pasteHTML(content)... baru saja diuji di IE11 dan tidak berfungsi seperti itu.
vsync
3
document.execCommand('insertText', false, content)tidak berfungsi pada IE11 dan Edge. Selain itu, Chrome versi terbaru sekarang mendukung document.execCommand('paste', false, content), yang lebih sederhana. Mereka mungkin mencela insertText.
Cannicide
19

Tentu saja pertanyaannya sudah terjawab dan topiknya sangat tua tetapi saya ingin memberikan solusi saya karena sederhana dan bersih:

Ini ada di dalam paste-event saya di contenteditable-div saya.

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

Bagian lain berasal dari pos-SO lain yang tidak dapat saya temukan lagi ...


UPDATE 19.11.2014: SO-post lainnya

webprogrammer
sumber
2
Saya pikir Anda mengacu pada posting ini: stackoverflow.com/questions/21257688/…
gfullam
1
Sepertinya tidak berhasil untuk saya di Safari. Mungkin ada yang salah
Cannicide
8

Tak satu pun dari jawaban yang diposting tampaknya benar-benar berfungsi lintas browser atau solusinya terlalu rumit:

  • Perintah insertTexttidak didukung oleh IE
  • Menggunakan pastehasil perintah dalam kesalahan stack overflow di IE11

Apa yang berhasil untuk saya (IE11, Edge, Chrome dan FF) adalah sebagai berikut:

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

Perhatikan bahwa penangan tempel khusus hanya diperlukan / berfungsi untuk contenteditablenode. Karena keduanya textareadan inputbidang biasa sama sekali tidak mendukung menempelkan konten HTML, jadi tidak ada yang perlu dilakukan di sini.

dpr
sumber
Saya harus menyingkirkan .originalEventdi event handler (baris 3) agar ini berfungsi. Jadi garis terlihat lengkap seperti ini: const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');. Berfungsi di Chrome, Safari, Firefox terbaru.
Pwdr
3

Firefox tidak mengizinkan Anda untuk mengakses data papan klip, jadi Anda harus membuat 'retasan' agar dapat berfungsi. Saya belum dapat menemukan solusi lengkap, namun Anda dapat memperbaikinya untuk pasta ctrl + v dengan membuat area teks & menempelkannya:

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

Saya juga sedang mengerjakan pasta teks biasa dan saya mulai membenci semua kesalahan execCommand dan getData, jadi saya memutuskan untuk melakukannya dengan cara klasik dan bekerja seperti pesona:

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

Kode dengan notasi saya dapat ditemukan di sini: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript

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

Kode di atas berfungsi untuk saya di IE10 dan IE11 dan sekarang juga berfungsi di Chrome dan Safari. Tidak diuji di Firefox.

Nikhil Ghuse
sumber
1

Di IE11, execCommand tidak berfungsi dengan baik. Saya menggunakan kode di bawah ini untuk IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> adalah kotak div saya.

Saya membaca data clipboard dari window.clipboardData dan memodifikasi textContent div dan memberi tanda sisipan.

Saya memberikan batas waktu untuk menyetel tanda sisipan, karena jika saya tidak menyetel batas waktu, tanda sisipan akan menjadi akhir div.

dan Anda harus membaca clipboardData di IE11 dengan cara di bawah ini. Jika Anda tidak melakukannya, karakter baris baru tidak ditangani dengan benar, jadi tanda sisipan salah.

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

Diuji di IE11 dan chrome. Ini mungkin tidak berfungsi di 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
sumber
0

Setelah menelusuri dan mencoba, saya telah menemukan solusi yang optimal

apa yang penting untuk diingat

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

Oke karena semua orang mencoba untuk mengatasi data clipboard, memeriksa peristiwa penekanan tombol, dan menggunakan execCommand.

Saya memikirkan ini

KODE

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
sumber