![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Понадобилось вот прикрутить к <textarea>
фичу Indent/Unindent block. Достаточно простую: выделяем блок, жмём клавишу — в начале каждой строчки в блоке добавляется/удаляется пробел.
Для простоты предполагаем, что браузер реализует стандарты DOM в части выделения :)
Обвязка:
function textarea_keydown(event)
{
// window.status = event.keyCode;
if (event.altKey && !event.ctrlKey && !event.shiftKey)
{
switch (event.keyCode)
{
case 109: // Alt+-
textarea_unindent(this);
break;
case 107: // Alt+=
textarea_indent(this);
break;
}
}
}
Эту функцию будем вешать на событие onKeyDown
жертвы, например, так:
function nodelist_foreach(nodelist, action)
{
for (var i = 0; i < nodelist.length; ++i)
{
action(nodelist[i]);
}
}
nodelist_foreach(document.getElementsByTagName('textarea'), function(textarea)
{
textarea.addEventListener('keydown', textarea_keydown, false);
});
Теперь надо реализовать собственно рабочие функции. Начнём с textarea_unindent
. Понятно, что нам понадобится текущее выделение, а также текст до и после него.
var value = textarea.value;
var before = value.substring(0, textarea.selectionStart);
var text = value.substring(textarea.selectionStart, textarea.selectionEnd);
var after = value.substring(textarea.selectionEnd);
Собственно unindent — это удаление пробела в начале каждой строки.
text = text.replace(/^ /gm, '');
Теперь соберём обратно текст из частей.
textarea.value = before + text + after;
Итак, что у нас получилось:
function textarea_unindent(textarea)
{
var value = textarea.value;
var before = value.substring(0, textarea.selectionStart);
var text = value.substring(textarea.selectionStart, textarea.selectionEnd);
var after = value.substring(textarea.selectionEnd);
text = text.replace(/^ /gm, '');
textarea.value = before + text + after;
}
Да, но так пропадает выделение. Надо его восстановить.
textarea.setSelectionRange(before.length, before.length + text.length);
И ещё, если текст длинный, то скролл прыгает в начало. Ну правильно — мы же заменяем весь текст.
var scrollTop = textarea.scrollTop;
var scrollLeft = textarea.scrollLeft;
//…
textarea.scrollLeft = scrollLeft;
textarea.scrollTop = scrollTop;
Вот теперь хорошо. Принимаемся за indent и обнаруживаем, что, по сути, будет-то там то же самое, только замена наоборот — начало каждой строки на пробел.
Стало быть, надо вырефакторить общее в отдельную функцию.
function textarea_replace(textarea, what, withWhat)
{
var scrollTop = textarea.scrollTop;
var scrollLeft = textarea.scrollLeft;
var value = textarea.value;
var before = value.substring(0, textarea.selectionStart);
var text = value.substring(textarea.selectionStart, textarea.selectionEnd);
var after = value.substring(textarea.selectionEnd);
text = text.replace(what, withWhat);
textarea.value = before + text + after;
textarea.setSelectionRange(before.length, before.length + text.length);
textarea.scrollLeft = scrollLeft;
textarea.scrollTop = scrollTop;
}
function textarea_unindent(textarea)
{
textarea_replace(textarea, /^ /gm, '');
}
function textarea_indent(textarea)
{
textarea_replace(textarea, /^/gm, ' ');
}
Всё бы хорошо, только, когда выделяешь целую строку вместе с CRLF’ом, то регексп считает, что в конце есть ещё одна пустая строка и добавляет к ней пробел тоже. Из-за этого indent’ится строчка сразу за блоком. Это не есть здорово.
function textarea_indent(textarea)
{
textarea_replace(textarea, /^(?!$)/gm, ' ');
}
Вот так лучше. Заодно не трогаем пустые строки.
Итого, финальный код:
function textarea_replace(textarea, what, withWhat)
{
var scrollTop = textarea.scrollTop;
var scrollLeft = textarea.scrollLeft;
var value = textarea.value;
var before = value.substring(0, textarea.selectionStart);
var text = value.substring(textarea.selectionStart, textarea.selectionEnd);
var after = value.substring(textarea.selectionEnd);
text = text.replace(what, withWhat);
textarea.value = before + text + after;
textarea.setSelectionRange(before.length, before.length + text.length);
textarea.scrollLeft = scrollLeft;
textarea.scrollTop = scrollTop;
}
function textarea_unindent(textarea)
{
textarea_replace(textarea, /^ /gm, '');
}
function textarea_indent(textarea)
{
textarea_replace(textarea, /^(?!$)/gm, ' ');
}
function textarea_keydown(event)
{
// window.status = event.keyCode;
if (event.altKey && !event.ctrlKey && !event.shiftKey)
{
switch (event.keyCode)
{
case 109: // Alt+-
textarea_unindent(this);
break;
case 107: // Alt+=
textarea_indent(this);
break;
}
}
}
function nodelist_foreach(nodelist, action)
{
for (var i = 0; i < nodelist.length; ++i)
{
action(nodelist[i]);
}
}
nodelist_foreach(document.getElementsByTagName('textarea'), function(textarea)
{
textarea.addEventListener('keydown', textarea_keydown, false);
});
Чёрт возьми, оно мне нравится. Я, пожалуй, поставлю GreaseMonkey, и пусть привязывает это ко всем <textarea>
вообще.