Получаем список всех узлов с определенным именем тега, лежащих между началом и концом выделения Когда мы применяем к блокам или инлайнам команду форматирования определенным тегом с определенным классом, у нас может получиться много узлов, поэтому нам интересны не только начальный и конечный. Банальный проход по nextSiblingам не подходит, т.к. нам пожет понадобится в процессе обхода подниматься и опускаться по дереву узлов. Посему алгоритм такой - получив ближайшего общего родителя (root), из поддерева его потомков, ограниченного начальным и конечным узлами, выбираем все нужные нам узлы.
функция, скорее всего, нуждается в оптимизации, а то она через глобальную переменную написана...
var global_stage // некрасивая глобальная переменная // bounds — массив [root, start, end] // tag_name — имя тега // остальные аргументы не указываем, используются для рекурсии function find_tags_in_subtree(bounds, tag_name, stage, second){ var root = bounds[root] var start = bounds[start] var end = bounds[end]
Ближайший родитель с нужным тегом На вход даем узел и имя тега. Если узел уже является нужным тегом, если у него нет подходящих родителей или если имя тега пусто, возвращаем этот узел. Иначе возвращаем ближайшего родителя, у которого нужное имя тега.
// Ближайший родитель с нужным тегом function closest_parent_by_tag_name(node, tag_name){ tag_name = tag_name.toLowerCase() var p = node do{ if(tag_name == p.nodeName.toLowerCase() == tag_name) return p }while(p = p.parentNode)
return node } Массив всех узлов с нужным тегом, попавших в выделение function get_selected_tags(editor_window, tag_name){ if(tag_name){ tag_name = tag_name.toLowerCase() }else{ tag_name = } var bounds = get_selection_bounds(editor_window) if(!bounds) return null
В API есть команда formatBlock, ей на вход дается имя блочного тега и она оформляет текущее выделение этим тегом. Например: document.execCommand("formatBlock", false, "<h1>").
Далее все просто — мы применим эту команду и потом выберем из выделения все теги нужного имени (tagName), которым и присвоим нужный className:
// Оформляем выделение нужным блочным тегом с нужным классом function wysiwyg_format_block(iframe_id, tag_name, class_name){ var iframe = document.getElementById(iframe_id) var wysiwyg = iframe.contentWindow.document // Оформляем нужным блочным тегом wysiwyg.execCommand(formatblock, false, < + tag_name + >) // Выбираем из выделения все теги нужного имени и ставим им класс var nodes = get_selected_tags(iframe.contentWindow, tag_name) for(var i = 0; i < nodes.length; i++){ if(class_name){ // Устанавливаем класс nodes[i].className = class_name }else{ // Убираем класс, если он нам не нужен nodes[i].removeAttribute(class) nodes[i].removeAttribute(className) } } iframe.focus() }
Форматирование слов (inline)
На первый взгляд может показаться, что между инлайновыми и блочными элементами какой-то принципиальной разницы нет, что их можно обрабатывать аналогичным образом. На самом деле это не так... Дело в том, что в API визуального редактора нет команды для оформления произвольным строчным тегом. А нам нужно как-то вставить несуществующий пока тег по границам выделения. Есть идея использовать для этого какую-то из команд, вставляющих идеологически вредный, но строковый тег.
Перспективным решением выглядит использование команды ForeColor, которая вставляет <font color=...>. Сам по себе <font> нам в нашем идеологически выдержанном редакторе не нужен абсолютно, но это позволит нам штатным образом создать строковые узлы, которые мы опять же сможем выбрать из выделения и поменять им tagName и className (и убрать атрибут color). Для надежности можно вставлять и потом искать какой-то конкретный цвет, подобранный таким образом, чтобы практически исключить ситуацию, когда он попадется нам в скопированном с другого документа тексте, например, #00ff01 (хотя его все равно бы уничтожил задуманный нами очиститель HTML).
// Магический неиспользуемый цвет var magic_unusual_color=#00f001 // Оформляем выделение нужным строковым (инлайновым) тегом с нужным классом function format_inline(iframe_id, tag_name, class_name){ var iframe = document.getElementById(iframe_id) var wysiwyg = iframe.contentWindow.document // Убираем все существующее форматирование wysiwyg.execCommand(RemoveFormat, false, true) // В MSIE после RemoveFormat остаются span-ы, удалим их тоже clean_nodes(get_selected_tags(iframe.contentWindow, span))
// Если имя тега не указано (применяется, когда мы хотим просто убрать форматирование) if(tag_name!=){ // Вставляем наш <font color> wysiwyg.execCommand(ForeColor, false, magic_unusual_color)
// Заменяем узлы, образованные fontами, на новые с нужным именем и классом var nodes=get_selected_tags(iframe.contentWindow, font) var new_node for(var i=0;i<nodes.length;i++){ if(nodes[i].getAttribute(color) != magic_unusual_color) continue new_node = wysiwyg.createElement(tag_name) if(class_name) new_node.className = class_name new_node.innerHTML = nodes[i].innerHTML nodes[i].parentNode.replaceChild(new_node, nodes[i]) } } iframe.focus() }
// Чистка узлов (удаляем тег, оставляем содержимое) // (Только для MSIE) function clean_nodes(nodes, class_name){ if(!nodes) return var l = nodes.length - 1 for(var i = l ; i >= 0 ; i--){ if(!classname nodes[i].className == class_name){ nodes[i].removeNode(false) } } }
Себе: закроссбраузерить clean_nodes
BUGS: 1: MSIE:пропадают граничные пробелы (попавшие в выделение и крайние в нем. По-видимому, из-за переприсвоения innerHTML) 2: иногда в MSIE при применении инлайн-форматирования на несколько абзацев сразу часть текста остается зеленой (magic_unusual_color). В мозиле, кстати, тоже иногда, но при других обстоятельствах... Может, выбирать все fontы из всего документа, а не только из выделения? // Круглов Работа со списками Имеется в виду работа с нумерованными и маркированными списками. На наше счастье в API уже почти есть (и даже больше чем нужно, но не будем забегать вперед).
Мы хотим преобразовывать абзацы в оба вида списков и обратно, а также управлять вложенностью списков (менять отступы).
Со сменой отступов есть небольшая проблемка — если мы увеличиваем отступ не у списка, вставляется тег <blockquote>. Он нам тут совершенно не нужен. Однако мы, вооружась недавно описанной функцией clean_nodes, его незамедлительно удалим.
// Работа со списками. Передаем на вход одну из команд: // ul, ol, indent, outdent function list(iframe_id, command){ var iframe = document.getElementById(iframe_id) var wysiwyg = iframe.contentWindow.document switch(command){ case ol: wysiwyg.execCommand(InsertOrderedList) break case ul: wysiwyg.execCommand(InsertUnorderedList) break case indent: wysiwyg.execCommand(Indent) // удаляем <BLOCKQUOTE> clean_nodes(get_selected_tags(iframe.contentWindow, blockquote)) break case outdent: wysiwyg.execCommand(Outdent) break