Трюк с javascript для вставки как обычного текста в execCommand

108

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

  • Ctrl+V
  • Щелкните правой кнопкой мыши -> Вставить
  • Щелкните правой кнопкой мыши -> Вставить как обычный текст

Я хочу разрешить вставку только простого текста без разметки HTML. Как я могу заставить первые два действия вставить обычный текст?

Возможное решение: я могу придумать, как установить прослушиватель событий нажатия клавиш для ( Ctrl+ V) и удалить HTML-теги перед вставкой.

  1. Это лучшее решение?
  2. Является ли это пуленепробиваемым, чтобы избежать разметки HTML при вставке?
  3. Как добавить слушателя в Right Click -> Paste?
Googlebot
источник
5
В качестве примечания, вы также хотите позаботиться о перетаскивании текста в редактор? Это еще один способ проникновения HTML в редактор.
pimvdb
1
@pimvdb Твоего ответа мне хватило. Просто из любопытства, есть ли простой способ избежать перетаскивания?
Googlebot
2
Я думал, что это сработает: jsfiddle.net/HBEzc/2 . Но, по крайней мере, в Chrome, к сожалению, текст всегда вставляется в начало редактора.
pimvdb
Здесь вам нужно использовать API буфера обмена. youtube.com/watch?v=Q81HH2Od5oo
Джон Доу,

Ответы:

249

Он перехватит 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);
});
pimvdb
источник
4
@Ali: Я пропустил кое-что очевидное. Если он textсодержит HTML (например, если вы скопируете HTML-код как обычный текст), он фактически вставит его как HTML. Вот решение, но не очень красивое: jsfiddle.net/HBEzc/3 .
pimvdb
14
var text = (event.originalEvent || event).clipboardData.getData('text/plain');обеспечивает немного большую кроссбраузерность
Дункан Уокер
10
Это нарушает функцию отмены. (Ctrl + Z)
Руди
2
Отличное решение, но это отличается от поведения по умолчанию. Если пользователь копирует что-то подобное, <div></div>это содержимое будет добавлено как дочерний элемент contenteditable элемента. Я исправил это так: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 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

Конечно, на вопрос уже дан ответ, и тема очень старая, но я хочу предоставить свое решение, поскольку оно простое:

Это внутри моего события paste на моем 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-сообщения, которое я больше не мог найти ...


ОБНОВЛЕНИЕ 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-содержимого, здесь ничего делать не нужно.

dpr
источник
Мне пришлось избавиться от .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 и изменяю текстовое содержимое div и ставлю курсор.

Я даю тайм-аут для установки каретки, потому что, если я не устанавливаю тайм-аут, каретка переходит в конец div.

и вы должны прочитать clipboardData в IE11 следующим образом. Если вы этого не сделаете, символ новой строки не будет обрабатываться должным образом, поэтому каретка идет не так.

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>   

Mooga
источник
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>

Арун Шарма
источник