Какое-то время назад, находясь под впечатлением от некоторых глав книги «Ководство», я решил внимательнее относиться к форматированию текста в материалах, которые публикую. Всё началось с того, что я сохранил в отдельный текстовый файл знак длинного тире и символы кавычек-ёлочек. (Точнее, всё началось с того, что я стал строить предложения таким образом, чтобы избегать случаев, когда требуется использовать эти символы. Но эта пора быстро прошла.) Каждый раз, создавая новый материал, я открывал этот файл и методом «copy-paste» вставлял в текст нужные символы. Эта период закончился тоже довольно-таки быстро. И дело не в том, что мне надоело расставлять «все эти значки, кавычки и неразрывные пробелы вручную» — просто я наткнулся на «Типограф», разработанный «Студией Лебедева». Этот инструмент позволяет добавить текст в специальную форму на сайте студии и получить приведённый в порядок текст одним кликом мыши.
Я пользуюсь «Типографом» уже около года, и до этого момента меня процесс работы устраивал. Я конечно был в курсе, что есть соответствующий веб-сервис, работающий с языком WSDL, использующим протокол SOAP на базе XML-формата, но руки до внедрения этого сервиса на сайт никак не доходили. Сделаю лирическое отступление и признаюсь: они и не дошли бы, не задумай я написать данное руководство.
Из предыдущего абзаца можно сделать вывод, что я даже представления не имею, как всё это сделать. И это будет очень правильный вывод — я действительно пока что не знаю, как «добавить в редактор материала кнопку, которая будет „типографить“ выделенный текст». Однако, раз уж вы читаете данное руководство, значит у меня что-то получилось.
Исследование вопроса
Мой небольшой жизненный опыт всегда диктует одно: «Если компания производит качественную соль, это не значит, что и спички она будет выпускать отменного качества». Опираясь на это, я решил не использовать сразу же WSDL-сервис студии Лебедева, а задумал подробнее исследовать рынок веб-сервисов для экранной типографики — тех, которые могут принять запрос с текстом необработанным, вернув мне текст обработанный. И, прочитав несколько обзоров и просмотрев одну публичную порку типографов сводную таблицу с результатами тестирования типографов, я решил остановиться на сервисе «Типограф» Евгения Лепёшкина.
На странице с описанием представлен пример того, как можно организовать работу с этим сервисом с помощью PHP. Найдя в этом смысле точку опоры и принимая во внимание главную идею данного руководства («добавить в редактор материала кнопку, которая будет „типографить“ выделенный текст»), можно попробовать разделить работу на несколько этапов:
Первым этапом будет разработка PHP-файла, который позволит обращаться к веб-сервису, получая от него обработанный с помощью типографики текст. (Хотя это и становится понятно из повествования, я отдельно упомяну, что для успешного завершения этого мероприятия у вас должна быть подключена услуга «Возможность использования PHP-скриптов».)
Вторым этапом станет добавление на панель редактора материалов кнопки, которая будет «типографить» выделенный текст. Сюда можно отнести не только создание внешнего вида этой кнопки, но и всю механику процесса отправки запроса PHP-скрипту, получение результата и вставку данных в текст.
Реализация
В разделе, посвящённом исследованию вопроса, было намечено два этапа работы. Как следует обдумав их, можно приступать к реализации.
Этап первый. Разработка PHP-файла
Зайдя по FTP на PHP-раздел своего сайта, в папке «scripts» можно создать отдельную папку «typograf». В ней создаётся файл typograf.php. Таким образом, получится вызывать его на исполнение, обращаясь по адресу: «http://мой.сайт/php/typograf/example-utf.php».
Изучив руководство по взаимодействию с веб-сервисом «Типографа», можно заметить, что отправка запроса и получение данных происходит с помощью сокета, который открывается функцией fsockopen(). Такой вариант на серверах Ucoz использовать не получится. Некоторое время назад, разрабатывая скрипт генерации XML-файла, подходящего для отправки на сервис Яндекс.Новости, я впервые столкнулся с тем, что эта функция не работает на хостинге Ucoz. Но можно вместо этого использовать библиотеку cURL, хотя я стараюсь её не использовать, когда это возможно (эта библиотека иногда ведёт себя странным образом, прерывая работу скриптов на ровном месте, однако, это справедливо для скриптов, в процессе работы которых HTTP-запросы выполняются несколько раз; в нашем случае во время работы скрипта cURL-запрос будет выполняться один раз, так что надо надеяться на стабильность работы).
После портирования скрипта для использования с cURL, содержимое файла typograf.php будет выглядеть следующим образом:
Листинг №1. Файл typograf.php - <?
- if ($_POST['text'])
- {
- $text = html_entity_decode(preg_replace('/\%u([a-f0-9]{4})/i', '&#x$1;', $_POST['text']), ENT_NOQUOTES, 'UTF-8');
- $text = urldecode($text);
- $text = stripslashes($text);
-
- $typoSettings = <<<MYXML
- < ?xml version="1.0" encoding="UTF-8" ?>
- <preferences>
- <paragraph insert="0"></paragraph>
- <acronym insert="0"></acronym>
- </preferences>
- MYXML;
-
- $postBody = 'chr=UTF-8&text=' . urlencode($text) . '&xml=' . urlencode($typoSettings);
-
- $host = 'http://www.typograf.ru/webservice/';
-
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $host);
- curl_setopt($ch, CURLOPT_FAILONERROR, 1);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 9);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
- $typografResponse = curl_exec($ch);
- curl_close($ch);
-
- print $typografResponse;
- }
- ?>
Обратите внимание на пробел во второй позиции строки 9. В тексте программы его быть не должно!
Вы наверняка заметили предыдущий абзац, выполненный в ненавязчивом стиле? Дело в том, что браузеры могут неадекватно реагировать на сочетание «<» и «?xml», записанные без пробела, даже если угловая скобка записывается в виде «<». Поэтому в листингах приходится ставить между ними пробелы, иначе браузер будет пытаться воспринимать следующий за этим текст как XML-файл.
Строки 4-5 были добавлены в этот листинг в процессе работы над вторым этапом реализации — «Внедрение кнопки в редактор материала». Дело в том, что (если забежать немного вперёд) отправка POST-запроса из JavaScript с JQuery — это очень нежный вопрос, и при малейшей ошибке при подготовке этого запроса начинаются капризы: от отправки в запросе строки, которую PHP-скрипт никогда не поймёт, до просто отказа вообще что-нибудь отправлять. Чтобы избежать таких капризов, в JavaScript был использован метод escape(), который конвертирует строку в формат Unicode, и только после этого она отправлялась POST-запросом. Как следует из описания функции escape(), все символы, числовые значения которых меньше 255, кодируются как %xx, где xx — шестнадцатеричное представление соответствующего символа. Если значение кода символа больше 255, он кодируется в вид %uxxxx, где xxxx — шестнадцатеричный код символа. Однако, в PHP нет стандартной функции, которая могла бы преобразовать подобные последовательности символов в нормальную строку. Функция urldecode() подходит только для формата %xx, а функция html_entity_decode() работает только с кодами вида «», где FFFF соответствует последовательности xxxx из %uxxxx. Поэтому и было решено изобрести подобный двухпроходный фильтр, как представлено в строках 4-5 листинга № 1. На первом шаге каждый xxxx-символ конвертировался в «»-формат и преобразовывался в обычный символ. На втором шаге в обычные символы конвертировался формат %xx.
Ещё один момент, на который стоило бы обратить внимание — переменная typoSettings (если кого-то удивляет способ, которым она определяется, это называется «Heredoc-синтаксис»). Веб-сервис «Типографа», который был выбран для использования, позволяет в запросе указать настройки. Среди них могут быть такие, как: «не добавлять знаки параграфа к тексту», «не добавлять в текст акронимы» и т.д. (Именно эти два параметра и задаются в листинге №1. Запрет на вставку параграфов сделан для того, чтобы была возможность, например, типографить только пару слов из предложения. Если такая настройка не будет задана, эти два слова превратятся в новый абзац.) Настройки должны представлять из себя корректный XML-файл. Для примера, ниже идёт листинг, который соответствует настройкам по умолчанию для веб-сервиса.
Листинг №2. Настройки веб-сервиса - < ?xml version="1.0" encoding="UTF-8" ?>
- <preferences>
- <tags delete="0">1</tags>
- <paragraph insert="1">
- <start><![CDATA[<p>]]></start>
- <end><![CDATA[</p>]]></end>
- </paragraph>
- <newline insert="1"><![CDATA[<br />]]></newline>
- <cmsNewLine valid="0" />
- <dos-text delete="0" />
- <nowraped insert="1" nonbsp="0" length="0">
- <start><![CDATA[<nobr>]]></start>
- <end><![CDATA[</nobr>]]></end>
- </nowraped>
- <hanging-punct insert="0" />
- <hanging-line delete="0" />
- <minus-sign><![CDATA[–]]></minus-sign>
- <hyphen insert="0" length="0" />
- <acronym insert="1"></acronym>
- <symbols type="0" />
- <link target="" class="" />
- </preferences>
Расшифровываются эти настройки примерно так (цитата из описания на сайте «Типографа»):
- tags (теги) — значения: 0 — не расставлять; 1 — расставлять. Атрибут delete — значения: 0 — не удалять; 1 — удалять до типографирования; 2 — удалять после типографирования.
- paragraph (параграфы) — атрибут insert: 1 — ставить; 0 — не ставить. start/end теги задают внешний вид обрамления параграфа, начальные и конечные теги соответственно (могут быть пустыми).
- newline — перевод строки. Атрибут insert: 1 — ставить; 0 — не ставить. Внутри тега пишутся теги перевода строки.
- dos-text — удаляет одинарные переводы строк и переносы. Атрибут delete: 0 — не удалять; 1 — удалять.
- nowraped — неразрывные конструкции. Атрибут insert: 1 — ставить; 0 — не ставить. Атрибут nonbsp: 0 — не использовать неразрывные конструкции вместо (неразрывного пробела); 1 — наоборот. Атрибут length: не объединять в неразрывные конструкции слова, написанные через дефис, с общей длинной больше N знаков. Если 0 то не используется. start/end аналогично параграфам.
- hanging-punct — висячая пунктуация. Атрибут insert: 1 — использовать; 0 — не использовать.
- hanging-line — висячие строки. Атрибут delete: 1 — удалять; 0 — не удалять.
- minus-sign — указывает какой символ использовать вместо знака минус: — – или −.
- acronym — выделять сокращения. Атрибут insert: 1 — выделять; 0 — не выделять.
- symbols — как выводить типографированный текст. Атрибут type: 0 — буквенными символами ( ); 1 — числовыми ( ).
- link — добавляет дополнительные атрибуты к ссылкам
Настройки можно ставить в любом порядке. Количество настроек можно сокращать и использовать только необходимые, остальные настройки будут браться по умолчанию.
На этом можно завершить первый этап работы, так как «серверная» сторона готова и корректно функционирует: принимаются POST-запросы, которые передаются на веб-сервис «Типографа». Полученные данные возвращаются в формате JSON.
Этап второй. Внедрение кнопки в редактор материала
Рисунок 1. Принцип работы будет примерно такой
Разрабатывая идею и пересматривая первый сезон доктора хауса проводя исследования, я представлял себе что-то похожее на рисунок 1. PHP-часть уже готова, так что задача решена уже на целых 5%. Осталась какая-то малость — всего 95% работы.
В первую очередь придётся ещё немного времени провести в исследованиях. На этот раз понадобиться понять, каким образом можно получить доступ к панели кнопок, чтобы добавить ещё одну — свою, после чего прописать её функционал.
Все исследования будут проводиться в редакторе модуля «Новости сайта». Внедрение кнопки и функций, отвечающих за её поведение будет происходить в шаблоне. Не обязательно использовать только модуль «Новости сайта». Подойдёт любой. Для доступа к шаблону, отвечающего за внешний вид страницы редактирования материала, на панели Ucoz нужно выбрать пункт меню «Дизайн» → «Управление дизайном страницы» (в этот момент нужно находиться на странице редактирования материала). Чтобы обезопасить себя от появления будущего кода на остальных страницах сайта, имеет смысл добавлять его в следующей конструкции:
Листинг №3 <?if($PAGE_ID$=='edit' || $PAGE_ID$=='add')?>
<!-- Этот код будет появляться толька на страницах добавления или редактирования материала -->
<?endif?>
Это имеет смысл, так как в некоторых модулях страница редактирования использует не свой личный шаблон. Например, в модуле «Новости сайта» страница редактирования материала использует шаблон «Страница архива материалов».
Просматривая DOM-содержимое страницы редактирования, можно найти HTML-код, описывающий отдельную кнопку. Для кнопки, которая, к примеру, делает выделенный текст полужирным, этот код выглядит так:
Листинг №4 <table align="left" cellspacing="0" cellpadding="0" style="margin:0px"> <tbody> <tr> <td style="text-align:left;padding:0px;padding-right:0px;VERTICAL-ALIGN: top;margin-left:0;margin-right:1px;margin-bottom:1px;width:23px;height:25px;" unselectable="on"> <div onmouseup="$msUp(event, 'oEditbrief', 'btnBoldoEditbrief')" onmousedown="$msDown(event, 'oEditbrief', 'btnBoldoEditbrief')" onmouseout="$msOut(event, 'oEditbrief', 'btnBoldoEditbrief')" onmouseover="$msOver(event, 'oEditbrief', 'btnBoldoEditbrief')" style="position:absolute;clip:rect(0px 23px 25px 0px);" id="btnBoldoEditbrief"> <img title="Жирный" alt="Жирный" style="position: relative; top: 0px; left: 0px;" src="http://мой.сайт/panel/editor/icons/btnBold.gif" onmousedown="if(event.preventDefault) event.preventDefault();" unselectable="on"> </div> </td> </tr> </tbody> </table>
С одной стороны, это выглядит довольно-таки громоздко. С другой — именно в таком формате я и буду добавлять свою кнопку. Как говорится: «В чужой монастырь со своим уставом не лезут».
Тег <img>, описаный в этом листинге, представляет из себя спрайт, определённые части которого видны на экране в зависимости от конкретной ситуации. Части спрайта — картинки, размером 23 пикселя по ширине и 25 пикселей по высоте. Ориентируясь на это, я создал свой спрайт, который представлен ниже. В нём предусмотрены состояния: «обычный вид», «мышь наведена на кнопку», «кнопка мыши нажата», «кнопка отпущена» и «запрос обрабатывается». Так получилось, что второе сверху изображение не используется, но я его оставил на будущее. До этого планировалось, что оно будет обозначать состояние «мышь наведена на кнопку».
Рисунок 2. Дизайн будущей кнопки
Настала пора определиться с тем, что и куда нужно добавлять.
Во-первых, при загрузке страницы редактирования (если используется WYSIWYG-редактор) происходит инициализация классов для редактора и добавление table-элемента с идентификатором «idAreaoEdit*», который хранит в себе панель инструментов и редактируемую область. Для области с кратким описанием создаётся класс oEditbrief и таблица с идентификатором «idAreaoEditbrief». Для области с полным текстом материала — класс oEditmessage и таблица с идентификатором «idAreaoEditmessage». Используя адресацию в стиле JQuery, можно определить, где хранится содержимое верхней панели с кнопками, нижней панели с кнопками и редактируемой области.
Листинг №5. Адресация $('div#oEditbrief table.istoolbar tr td table tr:eq(0) td').html() //Краткое описание - верхняя панель с кнопками $('div#oEditbrief table.istoolbar tr td table tr:eq(1) td').html() //Краткое описание - нижняя панель с кнопками $('iframe#idContentoEditbrief').html() //Краткое описание - редактируемая область $('div#oEditmessage table.istoolbar tr td table tr:eq(0) td').html() //Полный текст материала - верхняя панель с кнопками $('div#oEditmessage table.istoolbar tr td table tr:eq(1) td').html() //Полный текст материала - нижняя панель с кнопками $('iframe#idContentoEditmessage').html() //Полный текст материала - редактируемая область
Я решил добавить кнопку для типографики в верхнюю панель с кнопками. При этом, описывать я её буду похожим на листинг №4 образом.