misc/class
lib/jquery_pnotify, lib/moment, lib/lodash, misc/notification, site/engine, misc/social
if( $.browser.msie && $.browser.version <= 8 ) include('lib/respond'); $._social.__cfg = {"init":[{"service":"basic"},{"fb_app_id":"1997094873850041","service":"fb"},{"vk_app_id":"2978320","service":"vk"},{"service":"twi"}],"like":[{"service":"fb"},{"service":"vk"},{"via":"","channel":"","hash_tag":"","service":"twi"}]}; window._SiteEngine = new classes.SiteEngine( { user_id: 0, controller: 'content_tape', action: 'view', content_css_version: '1459538664', social_enabled: 0} );

Faiwer

Блог web-программиста

Простая тег-кнопка для CKeditor 4

11 марта 2016

Возникла довольно тривиальная задача ― написать для CKeditor-а плагин, который бы добавил кнопку, которая бы обрамляла выбранный текст конкретным тегом. По сути тоже самое, что и привычная нам кнопка bold, но вместо <b/> тег <var/>. Для начала нужно декларировать сам плагин:

CKEDITOR.plugins.add( 'insertvar',
    {
        init(editor){ }
    } );

Метод init будет вызываться всякий раз при создании instance-а редактора. В него пишем:

const style = new CKEDITOR.style({ element: 'var' });
const styleCommand = new CKEDITOR.styleCommand( style );
const command = editor.addCommand( 'insertvar', styleCommand );
editor.attachStyleStateChange(style, state =>
    {
        if(!editor.readOnly)
            command.setState(state);
    });

Все эти методы это стандартное API CKeditor-а для внутренних нужд. Для начала мы определяем новый стиль (CKeditor.style), указывая в нём название тега. Затем декларируем стилевую команду, также стандартными средствами. Там довольно много готовой логики. Добавляем эту команду в реестр всех команд редактора (editor.addCommand). 

Метод editor.attachStyleStateChange вызывается всякий раз, когда указанный нами стиль снимается, устанавливается или просто мы перемещаем курсор в другую позицию. command.setState позволяет нам менять состояние кнопки: активная, если курсор внутри тега, неактивная если снаружи. Также от этого состояния зависит и поведение кнопки при нажатии: либо тег установится, либо снимется.

Ну и финальный штрих:

editor.ui.addButton( 'InsertVar',
{
    label: 'Insert Variable',
    icon: 'about',
    command: 'insertvar',
    toolbar: 'insert,99'
} );

Декларируем команду как кнопку для toolbar-а. Иконку я взял стандартную. Осталось только подключить сам плагин и не забыть добавить нашу кнопку InsertVar в настройки toolbar-а. При желании можно всё дооформить по взрослому: добавить комментарии для сборки, локализацию, отдельную иконку и т.д.

CasperJS, PhantomJS, SlimerJS

10 марта 2016

SlimerJS ― это Firefox для автоматических тестов или же каких-либо подручных задач. PhantomJS ― это webkit браузер без окна, для тех же целей. CasperJS ― это test-кит, обёртка над PhantomJS и SlimerJS. До недавнего времени CasperJS был присмерти, основной разработчик на него забил и никто проектом не занимался. Но судя по коммитам дело сдвинулось с мёртвой точки, и проект скорее жив, чем мёртв. Появилась поддержка свежих версий PhantomJS, которую я так давно ждал. 

PhantomJS

С ним у меня возникло множество проблем. Дело в том, что webkit в нём очень древний. Он не способен переварить less.js, нуждается в CSS-префиксах к flexbox, не знает ничего о Function.Prototype.bind. В общем less приходится билдить в CSS, а для JS пихать es5-shim и babel-polifill. Про ES2015 молчу :-)

Стабильностью не страдает, и регулярно падает с PhantomJS has crashed, без объяснения причин. Плохо дружит с jQuery.IndexedDB, с некоторой периодичностью выкидывая ошибки.

Не имеет собственного окна, что сильно затрудняет отладку тестов или персональных косяков его самого. Но умеет скриншоты, что несколько выручает. А ещё мне пока не удалось завести в нём contenteditable (обновился до 2.1 и завелось).

Касательно работы через CasperJS… Почему-то запускает только 1-ый тест в файле. Много отличий в работе с require по сравнению со SlimerJS. Если ваша тестовая база подбита под SlimerJS, то потребуется много работы напильником, прежде чем тесты хотя бы запустятся.

CSS. Свежие версии фантома умеют зачатки flexbox-а. С префиксом -webkit и сильно устаревшими свойствами. По сути половину не умеет. Насколько я понял, исходя из беглого поиска, там QWebKit 2.2, который сильно устарел. Его возможности примерно равны Safari 5.1. Открыл я в ней проект… поковырялся, понял что безнадёга полнейшая и пришлось от фантома отказаться. Буду ждать обновлений.

В отличии от SlimerJS, фантом умеет падать с правильным exit code, а также умеет работать по file:// протоколу с AJAX запросами, что очень удобно для frontend тестирования.

Отладка. Если кратко ― ад. К примеру console.log для объектов выводит [Object object]. Любит при eval уходить в вечные циклы. Stacktrace-ы выдаёт далеко не всегда. Если запускаемый файл содержит ошибки синтаксиса ― где именно не укажет. Ну и т.д.. Обычно причину приходится искать методом проб и ошибок.

UPD: отдельные граблки это то каким образом casperJS гоняет данные между тестом и страницей. В случае slimerJS, главное чтобы эти данные могли сериализовываться, к примеру, JSON-ом. В случае phantomJS начинаются какие-то чудеса с null и похожими значениями. Разбираться в чём дело я не стал, а критичные к таким багам данные стал вручную перед отправкой перегонять в JSON-строку, а в тесте уже распаковывать.

 

SlimerJS

В отличии от фантома не умеет работать без окна. В Linux эту проблему можно решить, к примеру, так: xvfb-run %команда%, без каких-либо побочных эффектов. 

Очень многие вещи, такие как require, понимает не так, как их понимает PhantomJS. По сути то тут, то там, приходит ставить условие phantom.casperEngine === 'slimerjs', что сильно поело нервов.

В отличии от фантома умеет кое-что и из ES2015, и никакие shim-es5 ему не нужны. Без труда переварил less.js. Отлично справился с flexbox-ом. 

Пока не умеет опции --web-security=no, что не позволяет нормально тестировать по file:// протоколу, т.к. не проходят AJAX запросы.

Всегда завершает скрипт с кодом 0, что создаёт проблемы в определении, провалился ли тест, или был успешен, к примеру, из grunt-задачи.

Отладка примерно столь же ужасная, что и в фантоме. Тоже умеет скриншоты.

 

CasperJS

Располагает интересным API. К примеру может загружать файл в input[type=file], умеет [contenteditable] и курсоры. Умеет дожидаться появления или исчезновения HTMLElement-а по его selector-у и многое другое.

Но требует слишком много возни с подключением скриптов, сами тесты приходится писать без ES2015 возможностей. Располагает ужасной отладкой. API не связанное с самими web-страницами очень проблематично работает, к тому же по разному в зависимости от движка. К примеру, подключить к тесту какой-нибудь lodash может оказаться непростой задачей.

Test-kit очень сильно отличается от привычных мне подходов (таких как в mocha). Я так и не понял, как удобно организовать большие и сложные тесты. Вывод в консоль в итоге напоминает какой-то хаос.

 

Резюмируя

Инструментарий есть, но он ужасен. Требует очень большого и длительного внимания. Работает весьма нестабильно. По сути это сплошная головная боль. А сами проекты почти не развиваются. Увы.

Плагин для grunt, оказался чуть менее, чем никаким, ввиду чего пришлось писать собственную реализацию, которая пока тоже далека от желаемой.

String.split и RegExp

9 марта 2016

При написании тестов столкнулся с интересной проблемой. Метод String.split работал не так, как я от него ожидал. Я долго возился с различными вариантами жадного и ленивого поиска, пока наконец не понял, что дело не в них. Перейду сразу к сути:

'aaaa'.split(/a+/); // ["", ""]
'aaaa'.split(/(a+)/); // ["", "aaaa", ""] WAT?
'aaaa'.split(/(?:a+)/); // ["", ""]

Полез в дебри документации:

Если разделитель является регулярным выражением, содержащим подгруппы, то каждый раз при сопоставлении с разделителем, результаты (включая те, что не определены) захвата подгруппы будут помещаться внутрь выходного массива. Однако, не все браузеры поддерживают эту возможность. 

Всё просто. Если знаешь. 

CLI-скрипты и stdout

5 марта 2016

При написании подручных консольных криптов неизбежно встаёт вопрос, а как запускать внешнюю программу так, чтобы не потерять её вывод. Ситуация осложняется тем, что вывод может быть сложнее, нежели просто груда текста. К примеру ffmpeg рисует что-то вроде progressbar-а в поледней строке выдачи. Помимо прочего не хочется терять цвета. 

Стандартная библиотека предоставляет ряд методов для запуска сторонних программ. Я перебрал несколько из них, но всё как-то мимо. К примеру при ручном перенаправлении spawn-а за счёт pipe в process.stdout выдачи я не добился. При ручной обработке on('data', нет цветной выдачи и затирания уже отрисованного текста. Ну и т.д. 

Как обычно великий stackoverflow помог:

require('child_process').spawn('ffmpeg',
	[
		'-i', fname,
		'-f', ext,
		'-vcodec', vcodec,
		'-acodec', acodec,
		'-ss', fromS,
		'-to', endS,
		result
	], { stdio: 'inherit' });

Весь рецепт в третьем параметре ― { stdio: 'inherit' }

UPD0. Помимо цветного вывода и затирания текста прекрасно работает пользовательский ввод (пример: ffmpeg спрашивает перезаписать ли сущ-ий файл). Получается, что stdin тоже пробрасывается.

IE8 и babel

3 марта 2016

В одном из проектов требуется на уровне ТЗ поддержка Internet Explorer 8. Спасибо, что не 7 :) Дошли руки до перевода кодовой базы на es6. Воткнул как обычно preset es2015, потестил, вроде всё работает, но дёрнул меня чёрт проверить и в старом ослике. Подохло там всё. Причём глюк настолько странный, что я невольно задвигал бровями. Перейду сразу к сути:

function init(){ console.log(1); }
var a = { init: function(){ console.log(2); } };
init();

Результат, конечно же, 1. Однако babel превратит сий код в:

"use strict";

function init() {
  console.log(1);
}
var a = { init: function init() {
    console.log(2);
  } };
init();

Ключевое отличие ― именование метода. Теперь он тоже init. Зачем? Не ко мне вопрос. Может быть для упрощения отладки. Но с этого момента старый ишак возвращает 2. Оказывается это старый баг тогдашнего jscript-а.

Лечение: добавляем: plugins: ["transform-jscript"] к настройкам babel-я. Ну и соответственно тащим этот плагин. С этого момента babel будет генерировать вот таких вот монстров:

"use strict";

function init() {
  console.log(1);
}
var a = { init: function() {
    function init(){ console.log(2); }
    init();
  } };
init();

Очень бредово, если честно. Ведь можно было сохранить изначальный код в первозданном виде.