Пишемо правильний online wysiwyg-редактор

Введення і розуміння суті проблеми

Навіщо це потрібно

Примітка 1: Mozilla і Firefox далі будемо об'єднувати ім'ям Gecko (ця назва їх движка).

Примітка 2: Поки Opera 9 не є релізом, тому про неї потім напишемо. Взагалі, реалізація designMode в Opera схожа на реалізацію такого в Gecko.

Але є у нього і серйозний недолік - назвемо його "синдромом Ворда" - переважання візуальності розмітки над логікою, коли, умовно кажучи, заголовок в документі робиться шляхом виставлення великого і жирного шрифту. У нашому проекті ми спробуємо уникнути цього недоліку (або принаймні мінімізувати його).

Яким це повинно бути (Правильний WYSIWYG)

втілення

Так що наша робота буде полягати в реалізації цих додаткових можливостей.

Примітка: Оскільки війна браузерів поки не скінчилася, то ми маємо необхідність для різних браузерів писати різний код. Розрізняти браузери будемо шляхом перевірки, який код вони підтримують, а який - ні. Це дозволить нам не морочитися перевіркою userAgent'ов і версій.

Отже, сформулюємо, що нам потрібно:

  • Оформлення виділення потрібним блоковим тегом (на щастя, є команда formatBlock) з необхідним атрибутом class (а тут вже нічого готового немає) або без оного.
  • Оформлення виділення потрібним рядковим (inline) тегом з класом або без.
  • Присвоєння атрибутів (в основному класів) нетекстової об'єктів - картинкам (їм ще корисно привласнювати src і alt), таблиць, ліній
    .
  • Очищення форматування, що не підходить під задану таблицю стилів (корисно при копіюванні тексту з документів Microsoft Office, інших web-сторінок і т. Д.).

панель редагування

Кросбраузерності панель редагування являє собою document. якому властивість designMode встановлено в "On". Оскільки зазвичай нам не потрібно, щоб редагуванню піддавалося все вміст вікна браузера, зручно укладати цей document у фрейм (звичайний - frame або плаваючий - iframe).

Вирішувати цю проблему будемо так:

Заодно в цей підвантажуємий документ можна вписати подгрузку стилів:

,

Також таблицю стилів можна прив'язати до документа, завантаженому в iframe, шляхом створення методами DOM в його head'е елемента типу . вказує на файл з таблицею стилів.

З привласненням контенту можуть бути деякі проблеми, пов'язані з тим, що присвоювання треба робити після всіляких onload'ов і через деякий таймаут після установки designMode (в MSIE). Можна запропонувати таке рішення:
Через try-catch () намагаємося привласнити innerHTML. якщо не виходить, робимо невеликий setTimeout і пробуємо знову. Практика показує, що навіть при таймауті в 0 мілісекунд зациклення не відбувається. Можна і спочатку робити присвоювання з таймаут.

Примітка 1: Спочатку ми встановлюємо designMode, потім присвоюємо контент.

Почнемо писати код.


Можна додати кнопку для перемикання режимів:

Зараз ми не замислюємося над особливою функціональністю. Можна зробити checkbox, можна зробити перемикаються вкладки "Normal - HTML" і т. Д.

Виділення / Selection

Якщо ми захочемо у зображення вказати клас, ми повинні будемо виділити його об'єктно. Якщо ми хочемо поставити з нього посилання - текстово.

Базовою функцією роботи з виділенням є отримання початкового і кінцевого вузлів виділення. З цієї пари ми зможемо отримати весь набір входять в виділення вузлів потрібних нам типів.

Отримуємо початковий і кінцевий вузли виділення (а так само їх найближчого загального батька)

Примітка: Тут є нетривіальність, пов'язана зі "дивною" реалізацією виділення в MSIE.

Отримуємо список всіх вузлів з певним ім'ям тега, що лежать між початком і кінцем виділення

Коли ми застосовуємо до блокам або інлайн команду форматування певним тегом з певним класом, у нас може вийти багато вузлів, тому нам цікаві не тільки початковий і кінцевий. Банальний прохід по nextSibling'ам не підходить, тому що нам пожет знадобиться в процесі обходу підніматися і опускатися по дереву вузлів. Тому алгоритм такий - отримавши найближчого загального батька (root), з поддерева його нащадків, обмеженого початковим і кінцевим вузлами, вибираємо всі потрібні нам вузли.

функція, швидше за все, потребує оптимізації, а то вона через глобальну змінну написана.

Найближчий батько з потрібним тегом

На вхід даємо вузол і ім'я тега. Якщо вузол уже є необхідною тегом, якщо у нього немає відповідних батьків або якщо ім'я тега порожньо, повертаємо цей вузол. Інакше повертаємо найближчого батька, у якого потрібне ім'я тега.

Масив всіх вузлів з потрібним тегом, що потрапили в виділення

форматування блоків

В API є команда formatBlock. їй на вхід дається ім'я блочного тега і вона оформляє поточне виділення цим тегом.
наприклад:
document.execCommand ( "formatBlock", false, "

").

Далі все просто - ми застосуємо цю команду і потім виберемо з виділення все теги потрібного імені (tagName), яким і дамо потрібний className:

Форматування слів (inline)

Собі: закроссбраузеріть clean_nodes

BUGS:
1: MSIE: пропадають граничні прогалини (потрапили в виділення і крайні в ньому. Мабуть, через перепривласнення innerHTML)
2: іноді в MSIE при застосуванні інлайн-форматування на кілька абзаців відразу частина тексту залишається зеленою (magic_unusual_color). У Мозіль, до речі, теж іноді, але при інших обставинах. Може, вибирати все fontи з усього документа, а не тільки з виділення? // Круглов

Робота зі списками

Мається на увазі робота з нумерований і маркованими списками. На наше щастя в API вже майже є (і навіть більше ніж потрібно, але не будемо забігати вперед).

Ми хочемо перетворювати абзаци в обидва види списків і назад, а також керувати вкладеністю списків (міняти відступи).

Ось список наявних в designMode API команд:

  • InsertOrderedList - вставити
    1. InsertUnorderedList - вставити
      • Indent - збільшити відступ (зробити подсписок)
      • Outdent - зменшити відступ (вийти з подсписка)

      Зі зміною відступів є невелика проблемка - якщо ми збільшуємо відступ не у списку, вставляється тег

      . Він нам тут зовсім не потрібен. Однак ми, озброївшись недавно описаної функцією clean_nodes. його негайно видалимо.

      Чистка коду (-)

      література