Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > JavaScript: Общие вопросы > Перехват и обработка события onpaste в DesignMode


Автор: Stampede 12.4.2011, 18:59
Всем привет!

Взываю к коллективной мудрости. Есть следующая задача.

Я http://real-english.ru/blog/introducing-visual-editor.htm визуальный редактор http://nicedit.com/. Выбрал его за компактность, простоту и разборчивый код. Вроде пока все получается, хотя многое приходится затачивать под свои нужды. Сейчас встал вопрос о том, чтобы при копированиии в поле редактирования содержимое буфера очищалось от разметки HTML.

В ходе продолжительных поисков и экспериментов наткнулся вот на такую подсказку: 

Цитата
What you can do is quite involved and a bit of a hack that will work in Firefox 2+, IE 5.5+ and recent WebKit browsers such as Safari 4 or Chrome (untested on older versions). Recent versions of both TinyMCE and CKEditor use this technique:

   1. Detect a ctrl-v / shift-ins event using a keypress event handler
   2. In that handler, save the current user selection, add a textarea element off-screen (say at left -1000px) to the document, turn designMode off and call focus() on the textarea, thus moving the caret and effectively redirecting the paste
   3. Set a very brief timer (say 1 millisecond) in the event handler to call another function that stores the textarea value, removes the textarea from the document, turns designMode back on, restores the user selection and pastes the text in.

Note that this will only work for keyboard paste events and not pastes from the context or edit menus. By the time the paste event fires, it's too late to redirect the caret into the textarea (in some browsers, at least).

Ссылка: http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser


Закодил согласно описанию, получилось вот так:

http://download.real-english.ru/test/paste.html


Остается одна маленькая досадная мелочь: если вклеивать содержимое буфера через меню или перетаскиванием, то "очистительный фильтр", естественно, не срабатывает. Кто подскажет, что здесь можно сделать?

Для удобства привожу полный код страницы:

Код

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<head>
    <title>Test: Paste as plain text</title>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />

<style type="text/css">
* {
    margin: 0px;
    padding: 0px;
}

body {
    width: 400px;
    margin: 0px auto;
}

#form {
    margin: 1em 0px;
}

#pane {
    height: 200px;
    border: 1px solid silver;
}

#area {
/*
position: fixed;
left: -999px;
*/
    width: 400px;
}
</style>

<script type="text/javascript" language="javascript">
// http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser
var pane;
var form;
var doc;
var area;

function init() {
    form = document.getElementById('form');
    pane = document.getElementById('pane');
    pane.onkeydown = test_paste;

    area = document.getElementById('area');
}

function test_paste(evt) {
    evt = evt || window.event;

    var code = (evt.charCode)?
        evt.charCode : ((evt.which)? evt.which : evt.keyCode);


    if ((evt.ctrlKey && code == 86) || (evt.shiftKey && code == 45)) {
        do_paste();
    }
}

var currange;
var cursel;
function do_paste() {
    var sel;
    if (document.all) { // IE
        cursel = document.selection;
        currange = (cursel)? cursel.createRange() : null;
    }
    else {
        cursel = (window.getSelection || document.getSelection) ();
        currange = (cursel)? cursel.getRangeAt(0) : null;
    }

    area.value = '';
    area.focus();
    setTimeout(finish_paste, 1);
}

function finish_paste() {
    if (document.all) {
        area.select();
        pane.focus();
        currange.pasteHTML(area.value);
    }
    else {
        cursel.removeAllRanges();
        cursel.addRange(currange);
        pane.focus();
        document.execCommand('insertHTML', false, area.value);
    }
}

</script>

</head>

<body onload="init();">

<form id="form">
    <div id="pane" contentEditable></div>
</form>

<textarea id="area"></textarea>

<p id="lorem"><b>Lorem Ipsum</b> is simply <i>dummy text</i> of the printing and <u>typesetting industry</u>. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>

</body>

</html>


Заранее спасибо!

Автор: ksnk 12.4.2011, 19:13
Можно перехватывать событие onpaste и сохранять контент в переменной. После небольшого таймаута можно выковырять изменения в контенте простым текстовым сравнением с начала и конца текста с сохраненным значением. 

Правда, вероятно, возможны разные глюки, при вставке некорректного Html, например. При этом броузер будет корректировать контент и сравнение будет глючить.

Возможно, можно выковыривать изменения более разумно, используя сохраненную позицию курсора до вставки и после вставки, хотя с этим надо возится... 

Автор: Котокобра 12.4.2011, 21:24
а что мешает очищать всегда, когда произведена вставка?

Автор: Stampede 12.4.2011, 21:34
Цитата(Котокобра @  12.4.2011,  12:24 Найти цитируемый пост)
а что мешает очищать всегда, когда произведена вставка? 


К содержимому буфера в общем случае не так легко доступиться, поэтому приходится идти на такие вот ухищрения.

Автор: Котокобра 12.4.2011, 22:16
посмотрел пример. я думаю, текст нужно парсить. что-нибудь в этом духе:

Код

<script>
var str = "<i><b>text</b></i>";

var reg = /<\/{0,1}(b|i)>/ig;

str = str.replace(reg, "");

alert(str);

</script>


Автор: Stampede 13.4.2011, 01:09
Котокобра, загвоздка не в том, чтобы руками вычистить разметку (хотя, конечно, это делается не так прямолинейно, как в вашем примере — вспомним хотя бы, что теги могут быть с атрибутами), а в том, чтобы получить содержимое буфера обмена. Firefox, например, скрывает его от программного доступа из соображений безопасности. Потому-то WYSIWYG-редакторы вынуждены делать финт ушами: позволяют браузеру сделать вставку своим внутренними средствами, но при этом перенаправляют вывод в текстовое поле путем изменения фокуса.

Какие еще будут идеи?

Автор: Stampede 13.4.2011, 01:54
Если другого выхода не будет, тупо поставлю на onpaste заглушку с сообщением, чтобы копировали с клавы. Примерно так, http://tinymce.moxiecode.com/tryit/full.php:

user posted image

Но прежде чем капитулировать, хочется все же еще немного повоевать. Так что продолжаем мозговой штурм.

Автор: ksnk 13.4.2011, 08:01
Цитата(Stampede @  13.4.2011,  01:54 Найти цитируемый пост)
Если другого выхода не будет

вобще-то, событие onpaste в Мозиле есть. Убедиться в этом достаточно легко
Код

...

function init() {
    form = document.getElementById('form');
    pane = document.getElementById('pane');
    pane.onkeydown = test_paste;
    pane.onpaste = function (){alert("paste");}; //<<<<<<<<<<<<<<<<<<
    area = document.getElementById('area');
}
...


Цитата(Stampede @  12.4.2011,  21:34 Найти цитируемый пост)
К содержимому буфера в общем случае не так легко доступиться

А в чем проблема? Получить содержимое области редактирования? Получить положение "курсора"?

Автор: Stampede 13.4.2011, 08:25
Цитата(ksnk @  12.4.2011,  23:01 Найти цитируемый пост)
вобще-то, событие onpaste в Мозиле есть. Убедиться в этом достаточно легко


Событие-то есть, но нет простой возможности получить содержимое буфера. Скажем, команды cut/copy/paste, выполняемые по document.execCommand, не дают ничего сделать, пока не подредактируешь http://www.mozilla.org/editor/midasdemo/securityprefs.html.

Существуют хаки в виде флэшевых компонентов для работы с буфером (например, http://code.google.com/p/zeroclipboard/), но нет никакой гарантии, что в будущем Adobe не прикроет эту лавочку.

Впрочем, как вариант можно и попробовать.

Stay tuned!

Автор: ksnk 13.4.2011, 09:45
Цитата(Stampede @  13.4.2011,  08:25 Найти цитируемый пост)
Событие-то есть, но нет простой возможности получить содержимое буфера

А зачем нам буфер? Нам нужно получить изменение.

Вот небольшая модификация "рабочего примера" для демонстрации. Из за теперешнего перехвата клавиатурной вставки срабатывает только на вставку из copy-paste менюшки.
Осталось только прикрутить что-то такое на drag&drop. Но, вроде, даже события такие где-то появились...
Код

/** 
 * вызываем по событию onpaste,
 * запоминаем значение во внутренней переменной
 * через небольшой таймаут - сравниваем изменившееся значение и выводим изменение алертом
 */
function clearContent(el){
  var lastHtml=el.innerHTML||'';
  setTimeout(function(){
    var newHtml = el.innerHTML||'';
    var i =0,j,start=0,finish=newHtml.length-1;
    for(i=0;i< lastHtml.length;i++){
        if(lastHtml.charAt(i)!=newHtml.charAt(i)) {
            start=i; 
            break; 
        }
    }
    for(i=lastHtml.length-1,j=newHtml.length-1;i>start;i--,j--){
        if(lastHtml.charAt(i)!=newHtml.charAt(j)) {
            finish=j; 
            break; 
        }
    }
    alert([newHtml.substring(start,finish+1)]); 
  },1);

}

function init() {
    form = document.getElementById('form');
    pane = document.getElementById('pane');
    pane.onkeydown = test_paste;
    pane.onpaste = function (){clearContent(pane);}; // !!!!!!!!!!
    area = document.getElementById('area');
}


Добавлено через 1 минуту и 10 секунд
Я проверяю на FireFox. сейчас, к сожалению, нет возможности проверить на других броузерах. :(

Автор: Stampede 13.4.2011, 10:58
ksnk, спасибо за деятельное участие в решении вопроса. Метод не без недостатков (например, при большом объеме вклеиваемого форматированного текста будет заметно видимое перемигивание), но в целом "ход вашей мысли мне нравится" © , так что держите плюса.

Автор: Stampede 13.4.2011, 23:34
Ну что я могу сказать: вроде бы получилось. Посмотреть и потестить можно вживую:


http://download.real-english.ru/test/paste1.html


Код, приведенный ksnk, пришлось подправить в части коррекции маркеров начала и конца расхождения до границ тегов. Это, конечно, далеко не окончательый вариант, но без этого вообще вставлялось с ненужными хвостами.

Ну и перемигивание при вставке, действительно, довольно заметное.

Полный код страницы такой:

Код

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>
    <title>Test: Paste as plain text</title>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />

<style type="text/css">
* {
    margin: 0px;
    padding: 0px;
}

body {
    width: 400px;
    margin: 0px auto;
}

#form {
    margin: 1em 0px;
}

#pane {
    height: 200px;
    border: 1px solid silver;
}

#area {
/*
position: fixed;
left: -999px;
*/
    width: 400px;
}
</style>

<script type="text/javascript" language="javascript">
// http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser
var pane;
var form;
var doc;
var area;

function init() {
    form = document.getElementById('form');
    pane = document.getElementById('pane');
    pane.onkeydown = test_paste;
    // pane.onpaste = capture_paste;
    pane.onpaste = function () {clearContent(pane);}; // !!!!!!!!!!

    area = document.getElementById('area');
}

function test_paste(evt) {
    evt = evt || window.event;

    var code = (evt.charCode)?
        evt.charCode : ((evt.which)? evt.which : evt.keyCode);


    if ((evt.ctrlKey && code == 86) || (evt.shiftKey && code == 45)) {
        do_paste();
    }
}

function capture_paste(evt) {
    evt = evt || window.event;

    if (document.all) { // IE
        cursel = document.selection;
        currange = (cursel)? cursel.createRange() : null;
        area.value = window.clipboardData.getData('Text');
    }
    else {
        cursel = (window.getSelection || document.getSelection) ();
        currange = (cursel)? cursel.getRangeAt(0) : null;
        area.value = '';
    }
    finish_paste();

    evt.cancelBubble = true;
    return false;
}

var currange;
var cursel;
function do_paste() {
    if (document.all) { // IE
        cursel = document.selection;
        currange = (cursel)? cursel.createRange() : null;
    }
    else {
        cursel = (window.getSelection || document.getSelection) ();
        currange = (cursel)? cursel.getRangeAt(0) : null;
    }

    area.value = '';
    area.focus();
    setTimeout(finish_paste, 1);
}

function finish_paste() {
    if (document.all) {
        area.select();
        pane.focus();
        currange.pasteHTML(area.value);
    }
    else {
        cursel.removeAllRanges();
        cursel.addRange(currange);
        pane.focus();
        document.execCommand('insertHTML', false, area.value);
    }
}

function clearContent(el){
  var _oldHtml=el.innerHTML||'';
  setTimeout(function(){
    var oldHtml = _oldHtml;
    var newHtml = el.innerHTML||'';
    var tagStart = -1;
    var i = 0, j ,start = 0, finish = newHtml.length-1;
    for(i=0;i< oldHtml.length;i++){
     var c = oldHtml.charAt(i);
     var cc = (i < oldHtml.length - 1)? oldHtml.charAt(i + 1) : '';
     if (tagStart < 0 && c == '<' && isAlpha(cc)) {
         tagStart = i;
     }
     else if (tagStart >= 0 && c == '>') {
         tagStart = -1;
     }
        if(oldHtml.charAt(i)!=newHtml.charAt(i)) {
            start = (tagStart >= 0)? tagStart : i;
            break;
        }
    }

    var tagEnd = -1;
    for(i = oldHtml.length - 1, j = newHtml.length-1; i >= 0; i--, j--){
     var c = newHtml.charAt(j);
     if (c == '>') {
         for (var k = j - 1; k >= 0; k--) {
             c = newHtml.charAt(k);
             if (c == '/') {
                 tagEnd = j;
                 break;
             }
             if (!isAlphaNumeric(c)) {
                 tagEnd = -1;
                 break;
             }
         }
     }
     else if (tagEnd > 0 && c == '<') {
          tagEnd = -1;
     }
        if(oldHtml.charAt(i)!=newHtml.charAt(j)) {
            finish = (tagEnd > 0)? tagEnd + 1 : j;
            break;
        }
    }
    var diff = newHtml.substring(start, finish);
    diff = stripHtml(diff);
    // alert(diff);
    el.innerHTML = newHtml.substring(0, start) + diff + newHtml.substring(finish);
  },1);
}

function stripHtml(str) {
   var tmp = document.createElement("DIV");
   tmp.innerHTML = str;
   return tmp.textContent||tmp.innerText;
}

function isAlpha(s) {
    return /^[a-z]+$/i.test(s);
}

function isNumeric(s) {
    return /^[0-9]+$/.test(s);
}

function isAlphaNumeric(s) {
    return /^[a-z0-9]+$/i.test(s);
}


</script>

</head>

<body onload="init();">

<form id="form">
    <div id="pane" contentEditable></div>
</form>

<textarea id="area"></textarea>

<p id="lorem"><b>Lorem Ipsum</b> is simply <i>dummy text</i> of the printing and <u>typesetting industry</u>. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>

</body>

</html>


Если у кого-то будут еще какие-либо другие предложения — давайте обсудим.

Автор: Embedded 28.4.2011, 17:12
довольно неплохая штука nicedit, имхо tinymce сильно загружен ненужностямии у tiny какие-то странные глюки при копировании/вставке из ie в opera 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)