เคล็ดลับ Javascript สำหรับ "วางเป็นข้อความธรรมดา" ใน execCommand


107

ฉันมีเครื่องมือแก้ไขพื้นฐานตามexecCommandตัวอย่างที่แนะนำที่นี่ มีสามวิธีในการวางข้อความภายในexecCommandพื้นที่:

  • Ctrl+V
  • คลิกขวา -> วาง
  • คลิกขวา -> วางเป็นข้อความธรรมดา

ฉันต้องการอนุญาตให้วางเฉพาะข้อความธรรมดาโดยไม่ต้องมาร์กอัป HTML ฉันจะบังคับให้สองการกระทำแรกวางข้อความธรรมดาได้อย่างไร

วิธีแก้ไขที่เป็นไปได้:วิธีที่ฉันคิดได้คือการตั้งค่า Listener สำหรับเหตุการณ์การกดแป้นสำหรับ ( Ctrl+ V) และแถบแท็ก HTML ก่อนวาง

  1. เป็นทางออกที่ดีที่สุดหรือไม่?
  2. มันกันกระสุนเพื่อหลีกเลี่ยงมาร์กอัป HTML ใด ๆ ในการวางหรือไม่
  3. วิธีเพิ่ม Listener ในคลิกขวา -> Paste?

5
ในฐานะบันทึกด้านข้างคุณต้องการดูแลข้อความที่ถูกลากไปยังตัวแก้ไขด้วยหรือไม่? นั่นเป็นอีกวิธีหนึ่งที่ HTML สามารถรั่วไหลเข้าสู่ตัวแก้ไขได้
pimvdb

1
@pimvdb คุณตอบได้เพียงพอสำหรับความต้องการของฉัน ด้วยความอยากรู้อยากเห็นมีวิธีง่ายๆในการหลีกเลี่ยงการรั่วไหลด้วยหรือไม่?
Googlebot

2
ฉันคิดว่านี้จะทำผลงานได้: jsfiddle.net/HBEzc/2 แต่อย่างน้อยใน Chrome ข้อความจะถูกแทรกที่จุดเริ่มต้นของโปรแกรมแก้ไขเสมอ
pimvdb

คุณต้องใช้ clipboard api เป็น Explaiend ที่นี่ youtube.com/watch?v=Q81HH2Od5oo
Johne Doe

คำตอบ:


248

มันจะสกัดกั้นpasteเหตุการณ์ยกเลิกpasteและตนเองแทรกการแสดงข้อความของคลิปบอร์ด: http://jsfiddle.net/HBEzc/
สิ่งนี้ควรเชื่อถือได้มากที่สุด:

  • มันจับทุกชนิดของการวาง ( Ctrl+ V, เมนูบริบท ฯลฯ )
  • ช่วยให้คุณรับข้อมูลคลิปบอร์ดเป็นข้อความได้โดยตรงดังนั้นคุณจึงไม่ต้องทำการแฮ็กที่น่าเกลียดเพื่อแทนที่ HTML

ฉันไม่แน่ใจในการรองรับข้ามเบราว์เซอร์

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

4
@ อาลี: ฉันพลาดอะไรบางอย่างที่ชัดเจน หากtextมี HTML (เช่นถ้าคุณคัดลอกโค้ด HTML เป็นข้อความธรรมดา) มันจะวางเป็น HTML นี่เป็นวิธีการแก้ปัญหากับมัน แต่มันก็ไม่ได้เป็นอย่างสวย: jsfiddle.net/HBEzc/3
pimvdb

14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');เพิ่มความเข้ากันได้ข้ามเบราว์เซอร์อีกเล็กน้อย
Duncan Walker

10
ซึ่งจะทำให้ฟังก์ชันการเลิกทำแตก (Ctrl + Z)
Rudey

2
ทางออกที่ดี แต่สิ่งนี้แตกต่างจากพฤติกรรมเริ่มต้น หากผู้ใช้คัดลอก<div></div>เนื้อหาที่คล้ายกันจะถูกเพิ่มเป็นองค์ประกอบลูกขององค์ประกอบที่แก้ไขได้ ฉันแก้ไขแบบนี้:document.execCommand("insertText", false, text);
Jason Newell

5
ฉันพบinsertHTMLและinsertTextไม่ทำงานใน IE11 แต่ก็document.execCommand('paste', false, text);ใช้ได้ดี แม้ว่าจะดูเหมือนจะใช้ไม่ได้ในเบราว์เซอร์อื่น> _>
Jamie Barker

39

ฉันไม่สามารถรับคำตอบที่ยอมรับได้ที่นี่เพื่อทำงานใน IE ดังนั้นฉันจึงทำการสำรวจและได้รับคำตอบนี้ซึ่งใช้ได้กับ IE11 และ Chrome และ 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);
    }
});

1
ขอบคุณฉันกำลังแก้ไขปัญหาเดียวกันนี้ ... insertText ไม่ทำงานบน IE11 หรือ FF ล่าสุด :)
HeberLZ

1
เป็นไปได้ไหมว่าในบางกรณีจะวางข้อความสองครั้งทั้งใน Firefox และ Chrome ดูเหมือนว่าฉัน ..
Fanky

1
@ Fanky ฉันไม่มีปัญหานั้นเมื่อฉันสร้างมันขึ้นมา แต่ฉันไม่ได้ทำงานในสถานที่ที่ฉันสร้างโค้ดนี้อีกต่อไปดังนั้นฉันจึงไม่สามารถบอกคุณได้ว่ามันยังใช้งานได้หรือไม่! คุณช่วยอธิบายวิธีการวางสองครั้งได้ไหม
Jamie Barker

2
@Fanky ดูว่าคุณสามารถสร้างใหม่ได้ที่นี่นะ: jsfiddle.net/v2qbp829
Jamie Barker

2
ตอนนี้ดูเหมือนว่าปัญหาที่ฉันพบเกิดจากการเรียกสคริปต์ของคุณจากไฟล์ที่สคริปต์โหลด ฉันไม่สามารถวางลงในพื้นที่ว่างหรือใส่ในซอของคุณใน FF 47.0.1 (สามารถทำได้ใน chrome) แต่สามารถวางลงใน div contenteditable ซึ่งเป็นกุญแจสำคัญสำหรับฉัน ขอบคุณ!
Fanky

21

ทางออกที่ใกล้เคียงกับ pimvdb แต่มันใช้งานได้กับ FF, Chrome และ 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);
    }   
});

5
ฉันชอบการcontentกำหนดตัวแปรลัดวงจร ฉันพบว่าการใช้getData('Text')งานข้ามเบราว์เซอร์ดังนั้นคุณสามารถทำการมอบหมายได้เพียงครั้งเดียวดังนี้: var content = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');จากนั้นคุณจะต้องใช้ตรรกะสำหรับคำสั่งวาง / แทรกข้ามเบราว์เซอร์เท่านั้น
gfullam

6
ฉันไม่คิดว่าคุณจะเขียนได้document.selection.createRange().pasteHTML(content)... เพิ่งทดสอบบน IE11 และมันไม่ได้ผลเช่นนั้น
vsync

3
document.execCommand('insertText', false, content)ไม่ทำงานใน IE11 และ Edge นอกจากนี้ Chrome เวอร์ชันล่าสุดยังรองรับdocument.execCommand('paste', false, content)ซึ่งง่ายกว่า insertTextพวกเขาอาจจะเลิก
Cannicide

19

แน่นอนว่าคำถามนั้นได้รับคำตอบแล้วและหัวข้อนั้นเก่ามาก แต่ฉันต้องการให้คำตอบของฉันเพราะมันง่ายสะอาด:

สิ่งนี้อยู่ในเหตุการณ์การวางของฉันบน 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);
}

ส่วนอื่นมาจาก SO-post อื่นที่ฉันไม่พบอีกต่อไป ...


อัปเดต 19.11.2014: SO-post อื่น ๆ


2
ฉันคิดว่าคุณกำลังอ้างถึงโพสต์นี้: stackoverflow.com/questions/21257688/…
gfullam

1
ดูเหมือนจะไม่ได้ผลสำหรับฉันใน Safari อาจมีบางอย่างผิดปกติ
Cannicide

8

ไม่มีคำตอบใดที่โพสต์ไว้ดูเหมือนว่าจะทำงานข้ามเบราว์เซอร์หรือวิธีแก้ปัญหาซับซ้อนเกินไป:

  • insertTextIE ไม่รองรับคำสั่งนี้
  • การใช้pasteคำสั่งทำให้เกิดข้อผิดพลาดสแตกล้นใน IE11

สิ่งที่ใช้ได้ผลสำหรับฉัน (IE11, Edge, Chrome และ FF) มีดังต่อไปนี้:

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

โปรดทราบว่าตัวจัดการการวางแบบกำหนดเองจำเป็น / ใช้งานได้กับcontenteditableโหนดเท่านั้น เนื่องจากทั้งสองช่องtextareaและinputช่องธรรมดาไม่รองรับการวางเนื้อหา HTML เลยจึงไม่จำเป็นต้องทำอะไรที่นี่


ฉันต้องกำจัด.originalEventในตัวจัดการเหตุการณ์ (บรรทัดที่ 3) เพื่อให้สิ่งนี้ใช้งานได้ const text = ev.clipboardData ? ev.clipboardData.getData('text/plain') : window.clipboardData.getData('Text');ดังนั้นลักษณะสายสมบูรณ์เช่นนี้ ทำงานใน Chrome, Safari, Firefox ล่าสุด
Pwdr

3

Firefox ไม่อนุญาตให้คุณเข้าถึงข้อมูลคลิปบอร์ดดังนั้นคุณจะต้องทำการ 'แฮ็ก' เพื่อให้ใช้งานได้ ฉันไม่สามารถหาโซลูชันที่สมบูรณ์ได้ แต่คุณสามารถแก้ไขได้สำหรับการวาง ctrl + v โดยการสร้างพื้นที่ข้อความและวางลงในสิ่งนั้นแทน:

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

2

ฉันกำลังทำงานกับการวางข้อความธรรมดาและฉันเริ่มเกลียดข้อผิดพลาด execCommand และ getData ทั้งหมดดังนั้นฉันจึงตัดสินใจทำแบบคลาสสิกและมันก็ใช้งานได้เหมือนมีเสน่ห์:

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

รหัสพร้อมสัญลักษณ์ของฉันมีอยู่ที่นี่: http://www.albertmartin.de/blog/code.php/20/plain-text-paste-with-javascript


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

โค้ดด้านบนใช้ได้กับฉันใน IE10 และ IE11 และตอนนี้ยังใช้งานได้ใน Chrome และ Safari ไม่ได้ทดสอบใน Firefox


1

ใน IE11 execCommand ทำงานได้ไม่ดี ฉันใช้โค้ดด้านล่างสำหรับ IE11 <div class="wmd-input" id="wmd-input-md" contenteditable=true> คือกล่อง div ของฉัน

ฉันอ่านข้อมูลคลิปบอร์ดจาก window.clipboardData และแก้ไข textContent ของ div และให้คาเร็ต

ฉันให้หมดเวลาสำหรับการตั้งค่าคาเร็ตเพราะถ้าฉันไม่กำหนดระยะหมดเวลาเครื่องหมายคาเร็ตจะไปสิ้นสุดที่ div

และคุณควรอ่าน clipboardData ใน IE11 ด้วยวิธีด้านล่าง หากคุณไม่ทำเช่นนั้นคาแร็คเตอร์ของ newline จะไม่ได้รับการจัดการอย่างถูกต้องดังนั้นคาเร็ตจึงผิดพลาด

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

ทดสอบกับ IE11 และ chrome มันอาจไม่ทำงานบน 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);

0

หลังจากค้นหาและลองแล้วฉันพบวิธีแก้ปัญหาที่ดีที่สุด

สิ่งสำคัญที่ต้องจำไว้

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


0

ตกลงเนื่องจากทุกคนพยายามแก้ไขข้อมูลคลิปบอร์ดตรวจสอบเหตุการณ์การกดแป้นพิมพ์และใช้ execCommand

ฉันนึกถึงสิ่งนี้

รหัส

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>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.