Эх… достать бы коды старой версии Etherpad, там в реальном времени подсветка синтаксиса в WYSIWYG работала и не тормозила.
Я писал модуль форматирование для такого редактора. Было условие, чтобы на выходе получался красивый валидный код, делал я это следующим образом:
1. Рекурсивно проходил по всему дереву или отдельной его ветке и запоминал ссылки на все непустые текстовые узлы, собирал со всех их inline-родителей computedStyle, вытаскивал из него только поддерживаемые редактором стили, получался двумерный массив объектов:
[
[
{node: textNode, style: {color: "#f00", "font-weight": bold}},
{node: textNode, style: {…}},
{node: textNode, style: {…}},
],
[
{node: textNode, style: {…}},
…
]
]
из такого кода:
<div>
text1
<ul>
<li>text2</li>
<li>text3</li>
</ul>
text4<b>text5</b>text6
</div>
получался вот такой массив:
[
[{node: text1, style: {}}],
[{node: text2, style: {}}],
[{node: text3, style: {}}],
[{node: text4, style: {}}, {node: text5, style: {}}, {node: text6, style: {}}]
]
При первом проходе блочные элементы трогать не будем, оптимизация затрагивает только текстовые узлы и inline-элементы, поэтому и получаем двумерный массив, как бы уменьшаем многомерность задачи.
Внутри какого блока проводить оптимизацию, определяем с помощью range.getAncestorContainer/parentElement, чтобы всегда весь document.body не перестраивать.
Там правда еще много возни было с приведением значений стилей к единому виду, например font-weight может быть bold, 400 или 900, Opera возвращает некоторые значения в кавычках…
2. С помощью конечного автомата, на основе собранных стилей, собираем новый HTML-код (сводим многомерный алгоритм к линейному), проводя объединение одинаковых стилей, создавая <span style="…"> вместо невалидных <U> и т.д. Должен получится одномерный массив кусков HTML-кода.
3. Заменяем старые узлы на новые, полученные из кусочков HTML-кода. Использовать innerHTML не получится, так как, например, для верхнего div, заменив 1-й участок (text1), мы потеряем все остальные, поэтому создаем функцию:
var parseHTML = function () {
var node = document.createElement("div"); // тут дорога каждая миллисекунда
var fragment = document.createDocumentFragment(); // узлы будут переносится в дерево
return function (htmlCode) {
node.innerHTML = htmlCode;
while (node.firstChild) fragment.appendChild(node.firstChild);
return fragment;
};
}();
Например, заменяем отрезок, полученный из text4–text6: для этого нужно получить самого верхнего inline-родителя узла text4 (если такого нет, то берем сам узел text4), вставить перед ним новый fragment, а затем удаляем все ненужные старые узлы text4–text6 и их inline-родителей.
Вот так вот громоздко и сложно, но эффективно
Оптимизировать блочные элементы можно регулярными выражениями.
Возвращать курсор (каретку) в нужное место лучше всего следующим образом:
1. Так как мы уничтожаем старые узлы, то никакой связи с range-объетакми не остается и они нам не помогут, поэтому просто при помощи execCommand вставляем на месте каретки <font face="fake"> (потому что работает везде).
2. Заменяем <font face="fake"> на какой-нибудь элемент с классом.
3. В алгоритме, описанном выше, делаем специальные условия, чтобы этот узел со специальным классом остался в нужном месте.
4. После преобразования кода, находим этот элемент, ставим курсор в него selectNodeContent/moveToElementText.
5. Сдвигаем курсор setStart/moveStart.
6. Удаляем служебный элемент.
В твоей задаче конечный автомат, собирающий новый HTML-код, должен еще и подсвечивать необходимые слова. Но ведь они могут быть "разорваны" на несколько текстовых узлов. Вот с этим хз что делать. Наверное, тогда не в конечном автомате это делать, а сначала оптимизировать код, потом найти эти слова и обернуть в теги для подсветки. Хотя тоже не факт, что пол слова как-нибудь не оформлено по другому было… В общем писать модуль поиска слов мне не досталось, но знаю, что там ппц)))
В IE множественные выделения не работают, поэтому только тегами получится подсвечивать. Разрывать части слов (текстовых узлов) опять же удобнее с помощью execCommand("fontFace", "fake"), предварительно выделив область.
Исходников не осталось, да показывать я их не в праве бы был, проект коммерческий был со всеми вытекающими, хотя он вроде так и не вышел)