Многие наверняка сталкивались с тем, что любимый браузер не умеет делать что-то очень простое, но насущно необходимое. Я хочу рассказать, как на коленке за час можно обучить FF4 хорошему. Написание расширений — процесс очень простой и даже временами приятный, но, к сожалению, документация не всегда легко находится, how-to разбросаны по разным углам сайта MDN, внятных рекомендаций мне найти тоже не удалось… Все это порождает (по крайней мере, в моей голове — породило) миф о том, что это трудоемкий процесс, доступный только гуру. Вот этот миф и призвана развеять данная статья. Мы напишем расширение, которое будет править типографику в полях ввода, обращаясь за помощью к сервису «Типограф».
Сразу хочу оговориться: все нижесказанное будет работать только в FF4. Коллеги из Mozilla так переработали API, что проще создать два расширения для обеих версий браузера, чем писать рекомендуемую лапшу вида
Итак.
Для начала — создаем скелет нашего будущего расширения. О нас позаботились, поэтому достаточно просто обратиться за помощью. Заполняем простую формочку — и скачиваем готовый скелет. Мы для нашего примера выбрали расширение без диалога настройки, только с одной кнопкой на тулбаре. Вот, что за нас сделал генератор:
Иконки я рисовать не умею, потому позаимствуем оную у сервиса, который будет делать за нас основную работу (на скриншоте сверху она уже добавлена мною в папку
Для начала исправим надпись на кнопке и всплывающую подсказку в файле
Затем подставим нашу иконку вместо той, что прикатилась к нам по умолчанию:
Теперь осталось только переписать реакцию на нажатие.
Мне удобно использовать типограф следующим образом: если фокус в поле ввода (
Что ж, приступим. Все эти правки попадут в файл
Эту магию я откопал где-то в недрах MDN:
Возвращаем содержимое буфера обмена, либо пустую строку. Комментировать тут, вроде бы, нечего.
Еще нам потребуется отлавливать фокус ввода (этот код в силу его тривиальности я оставлю за кадром).
Ну и, наконец, текст нужно отослать типографскому сервису:
Вот, собственно, и все. Осталось вызвать функцию
И запаковать все это наше барахло в архив:
Весь код примера и получившееся расширение можно взять на народе. XPI — это файл расширения, если его распаковать zip-ом — внутри будет собственно код.
Низкий старт
Сразу хочу оговориться: все нижесказанное будет работать только в FF4. Коллеги из Mozilla так переработали API, что проще создать два расширения для обеих версий браузера, чем писать рекомендуемую лапшу вида
appversion>=4.0
.Итак.
Для начала — создаем скелет нашего будущего расширения. О нас позаботились, поэтому достаточно просто обратиться за помощью. Заполняем простую формочку — и скачиваем готовый скелет. Мы для нашего примера выбрали расширение без диалога настройки, только с одной кнопкой на тулбаре. Вот, что за нас сделал генератор:
Иконки я рисовать не умею, потому позаимствуем оную у сервиса, который будет делать за нас основную работу (на скриншоте сверху она уже добавлена мною в папку
skin
). Итак, что же делать дальше с этим набором файлов?Для начала исправим надпись на кнопке и всплывающую подсказку в файле
/chrome/locale/en-US/overlay.dtd
:<!ENTITY typographToolbarButton.label "Typo!">
<!ENTITY typographToolbarButton.tooltip "Proceed typography of selected text against Lebedev's tool">
Затем подставим нашу иконку вместо той, что прикатилась к нам по умолчанию:
pushd chrome/skin && mv favicon.png toolbar-button.png && popd
Теперь осталось только переписать реакцию на нажатие.
Немного кода
Мне удобно использовать типограф следующим образом: если фокус в поле ввода (
textarea
) и там выделен кусок текста — типографим его. Если же текст не выделен — типографим содержимое буфера обмена. Ну а если фокус где-то еще — ничего не делаем :-). Поэтому нам потребуется научиться работать с буфером обмена (это самая нетривиальная часть). Что ж, приступим. Все эти правки попадут в файл
/chrome/content/overlay.js
.Эту магию я откопал где-то в недрах MDN:
var getClipboard = function (flavor) {
if (!flavor || flavor == "") flavor = "text/unicode";
var trans = Components.classes["@mozilla.org/widget/transferable;1"]
.createInstance(Components.interfaces.nsITransferable);
var clip = Components.classes["@mozilla.org/widget/clipboard;1"]
.getService(Components.interfaces.nsIClipboard);
if (!clip || !trans) return '';
trans.addDataFlavor(flavor);
clip.getData(trans, clip.kGlobalClipboard);
var str = new Object();
var strLength = new Object();
try {
trans.getTransferData(flavor, str, strLength);
if (str) str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
} catch (e) {
// it's OK, skipping
str = null;
}
return str ? str.data.substring(0, strLength.value / 2) : '';
}
Возвращаем содержимое буфера обмена, либо пустую строку. Комментировать тут, вроде бы, нечего.
Еще нам потребуется отлавливать фокус ввода (этот код в силу его тривиальности я оставлю за кадром).
Ну и, наконец, текст нужно отослать типографскому сервису:
typo : function () {
var focused = document.commandDispatcher.focusedElement;
if (!focused || focused.tagName.toLowerCase() != 'textarea') {
return;
}
if (focused.selectionStart != focused.selectionEnd ) {
text = focused.value.substring(focused.selectionStart, focused.selectionEnd);
} else {
text = getClipboard();
}
text = text.replace (/&/g, '&');
text = text.replace (/</g, '<');
text = text.replace (/>/g, '>');
var xmlRequest =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
'<soap:Body>' +
'<ProcessText xmlns="http://typograf.artlebedev.ru/webservices/">' +
'<text>' + text + '</text>' +
'<entityType>' + 3 + '</entityType>' +
'<useBr>' + 0 + '</useBr>' +
'<useP>' + 0 + '</useP>' +
'</ProcessText>' +
'</soap:Body>' +
'</soap:Envelope>';
var req = new XMLHttpRequest();
req.open('POST', 'http://typograf.artlebedev.ru/webservices/typograf.asmx', true);
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
var response = req.responseText;
var re = /<ProcessTextResult>\s*((.|\n)*?)\s*<\/ProcessTextResult>/m;
response = re.exec (response);
response = RegExp.$1;
response = response.replace (/>/g, '>');
response = response.replace (/</g, '<');
response = response.replace (/&/g, '&');
insertText(response, focused);
} else {
// smth went wrong — just skip it
}
}
};
req.send(xmlRequest);
}
Вот, собственно, и все. Осталось вызвать функцию
typo
из обработчика события «кнопка нажата»:onToolbarButtonCommand: function(e) {
typograph.typo();
}
И запаковать все это наше барахло в архив:
#!/bin/bash
if [ -e build ]; then rm -rf build; fi
mkdir -p build
cp -r install.rdf chrome.manifest chrome defaults build
pushd build/chrome >/dev/null && zip -r typograph.jar content/* skin/* locale/* && popd >/dev/null || echo 'Unable to produce jar :-(\n'
pushd build && zip -r typograph-4.0.xpi install.rdf chrome.manifest chrome/* defaults/* && popd >/dev/null || echo 'Unable to produce .xpi :-(\n'
if [ -e dist ]; then rm -rf dist; fi
mkdir -p dist
cp build/typograph-4.0.xpi dist/
Весь код примера и получившееся расширение можно взять на народе. XPI — это файл расширения, если его распаковать zip-ом — внутри будет собственно код.