Трюк Javascript для "вставити як звичайний текст" у execCommand


106

У мене є базовий редактор, заснований на execCommandнаступному зразку, представленому тут. Існує три способи вставлення тексту в execCommandобласть:

  • Ctrl+V
  • Клацніть правою кнопкою миші -> Вставити
  • Клацніть правою кнопкою миші -> Вставити як звичайний текст

Я хочу дозволити вставляти лише звичайний текст без розмітки HTML. Як я можу змусити перші дві дії вставити звичайний текст?

Можливе рішення: Я можу придумати - встановити слухача для подій клавіатури для ( Ctrl+ V) та знімати теги HTML перед вставкою.

  1. Це найкраще рішення?
  2. Чи куленебезпечно, щоб уникнути розмітки HTML в пасті?
  3. Як додати слухача до клацання правою кнопкою миші -> Вставити?

5
Як бічна примітка, ви також хочете подбати про те, щоб текст перетягувався в редактор? Це ще один спосіб HTML просочитися до редактора.
pimvdb

1
@pimvdb Ти відповіді був достатній для моєї потреби. Чи просто з цікавості, чи є простий метод уникнути затягнутого витоку?
Googlebot

2
Я думав, що це зробить роботу: jsfiddle.net/HBEzc/2 . Але на Chrome, принаймні, текст завжди вставляється на початку редактора, на жаль.
pimvdb

Тут вам потрібно використовувати буфер обміну api як пояснення. youtube.com/watch?v=Q81HH2Od5oo
Джон Доу

Відповіді:


247

Він перехопить 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
@Ali: Я пропустив щось очевидне. Якщо він 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);
Джейсон Ньюелл

5
Я знайшов insertHTMLі insertTextне працюю в IE11, проте document.execCommand('paste', false, text);працює чудово. Хоча це, схоже, не працює в інших браузерах> _>.
Джеймі Баркер

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 У мене не було цього питання, коли я його створював, проте я більше не працюю в тому місці, де я створив цей код, тому я не міг сказати вам, чи він все ще працює! Не могли б ви описати, як це вставлення двічі?
Джеймі Баркер

2
@Fanky Перегляньте, чи зможете ви створити його тут: jsfiddle.net/v2qbp829 .
Джеймі Баркер

2
Здається, що проблема у мене була через виклик вашого сценарію з файлу, який був завантажений самим сценарієм. Я не можу вставити ані текстові області, ані ввести у вашу скрипку у FF 47.0.1 (можна це зробити в хромі), але я можу вставити в div contentedable, що є ключовим для мене. Дякую!
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.
Каніцид

19

Звичайно, на питання вже відповіли, і тема дуже стара, але я хочу надати своє рішення, оскільки це просто "чисто":

Це всередині моєї пасти-події на моєму вмісту-ділі.

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-поста, якого я вже не міг знайти ...


ОНОВЛЕННЯ 19.11.2014: Інший SO-пост


2
Я думаю , що ви маєте на увазі це повідомлення: stackoverflow.com/questions/21257688 / ...
gfullam

1
Здається, це не працювало для мене в Safari. Можливо, щось не так
Cannicide

8

Жоден із опублікованих відповідей, схоже, не працює між веб-переглядачем або рішення надто складне:

  • Команда insertTextне підтримується IE
  • Використання 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> - це моє поле дів .

Я читаю дані буфера обміну з window.clipboardДані та змінюю текст div на зміст і даю каре.

Я даю тайм-аут для встановлення карети, тому що якщо я не встановлю тайм-аут, карета закінчується дівом.

і ви повинні прочитати буфер обмінуДані в IE11 нижче. Якщо ви цього не зробите, з новою лінією каракаром не обробляється належним чином, тому карет іде не так.

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

Тестували на IE11 та хромі. Він може не працювати на 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>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.