Этап 1. Знакомимся с редактором Summernote
Этап 4. Автоматизируем добавление кнопок
Этап 5. Обрабатываем нажатия кнопок
Этап 6. Обрабатываем вставку из других редакторов (компрессия стилей)
Этап 7 Возвращаем совместимость с Linux
По поиску легковесный редактор WYSIWYG (или по-нашему: ЧТВТТИП), наткнулся на интересный сайт, на котором было все, что нужно: кнопка Скачать и действующий пример внедрения на страницу:
Как выяснилось уже позже, у данного решения есть еще несколько преимуществ:
1. действительно легковесное решение - требует только jquery и bootstrap из стандартных библиотек, плюс 300 Кб основного года. Оговорюсь здесь, что требуется свой шрифт и встроить его оказалось не так просто, но эта проблема уже решена.
2. легко расширяется панель инструментов, где много кнопок уже предусмотрено, включая кнопки отмены/повтора, но не выводится по умолчанию
3. полностью рабочая вставка из других редакторов (выяснилось, что с ним в 1С необязательно нажимать Ctrl+V, чтобы вставить форматированный текст)
4. два режима работы: ненавязчивая всплывающая панель (пользователь даже не узнает о существовании редактора, пока не вздумает выделить что-то или нажать правой кнопкой) и обычная панель редактора.
Внедрение шрифта, когда все уже одним текстовым макетом
Итак, решение сдернуто с сайта, внедрено в текстовый макет 1С, подгружаемый при открытии формы, дома все работает, а на работе... Ограничения сети, другая версия 1С. Да 1С развиваются, как хотят, и в версии 8.3.24 они внедрили использование шрифтов формата woff/woff2, а версии 8.3.23 - нет, поэтому шрифт нужно конвертировать в ttf и тогда внедрить его в макет страницы можно в бинарном виде:
МакетШрифта = ПолучитьМакет("МакетШрифта");
ТекстHTML = СтрЗаменить(ТекстHTML, "summernote.woff2", "data:application/x-font-ttf;charset=utf-8;base64,"
+ СтрЗаменить(СтрЗаменить(Base64Строка(МакетШрифта), Символы.ПС, ""), Символы.ВК, ""));
1С зачем-то вставляет переносы в строку Base64, но зная это, легко исправить.
Что характерно, в корпоративной среде подгрузка скриптов по ссылкам показала себя очень медленной и нестабильной, решение с уже внедренными в макет скриптами и стилями выдает мгновенную загрузку
Этап 3. Добавляем свои кнопки
Редактировать текст, выставляя стили, шрифты, таблицы и списки уже можно. Но хочется чего-то своего! Здесь авторы позаботились о достаточно простой интеграции дополнительных кнопок, все просто:
- Добавляем кнопку:
this.context.memo("button.HtButtonClass", function() { return t.button({ className: "HtButtonClass", contents: "HtButtonContent", tooltip: t.lang.HtButtonGroup.HtButtonClass, click: t.context.createInvokeHandler("sync1c.HtButtonMethod") }).render() }),
2. Описываем подсказку:
HtButtonGroup: {
HtButtonClass: "HtButtonTooltip"
},
3. Описываем метод, выполняемый при нажатии:
{
key: "HtButtonMethod",
value: function() {
this.$editor.find(".note-editable").focus()
}
},
4. И теперь можно добавить кнопку непосредственно на панель:
["HtButtonGroup", ["HtButtonClass"]],
Всё! После нажатия на кнопку Форматирование (я добавил, легко убирается) ваша новая кнопка появилась:
Этап 4. А если это просто, почему бы не автоматизировать?
Это делалось, конечно, скорее шутки ради, просьба не пинать, но регулярные выражения и поиск нужных мест кода постарался сделать универсальным, чтобы искал и в сжатом модуле (minify) и распрекрасном (prettify), но вполне возможно, что при вашем изменении скрипта что-то пойдет не так, кнопка не добавится или перестанет выполнять код, тут уж отладчик и regex101.com вам в помощь (нужно выбирать Java 8 Flavor), если хочется автоматизации. Просто лишаться возможностей отладки и минификации одновременно (сперва ей активно пользовался) не хочется, поэтому просто расставить флаги %МетодКнопки%, %ПодсказкаКнопки% и прочее не решился, так что остался при регулярных выражениях и ими доволен.
Здесь приведу только регулярные выражения и, собственно, снимок соответствующего справочника:
ШаблонМетода = "{(.|\n){0,60}key.{1,10}HtButtonMethod(.|\n){1,300}}, ?\{";
ШаблонКнопки = "this\.context\.memo\(""button\.HtButtonClass"",(.|\n){2,1800}t\.context\.createInvokeHandler\(""sync1c\.HtButtonMethod""\)(.|\n){2,200}}\),";
ШаблонПанели = "\[""HtButtonGroup"", ?\[.[^]]{1,1000}]],";
Подсказку искать совсем просто: там сочетание "НазваниеГруппы:", так что можно обойтись без шаблона. А вот и справочник КнопкиРедактораГТ:
Что видно из формы: кнопка с названием Кнопка03, входит в группу AlsoGroup, отображается как иконка высотой 22 пикселя, при нажатии вылетает предупреждение о том, что пора запастись зонтиками, затем вставляет специальную метку для вставки чего-то уже со стороны 1С (PasteFromClipboard) и ставит фокус на поле редактора (снимает его с кнопки, в браузере незаметно, а в 1С очень). Пометка (выделение кнопки) не используется. И все, теперь остается только добавить кнопку на форму при инициализации текста поля HTML и убрать рыбу, к которой цеплялись при добавлении:
НоваяКнопка = Справочники.КнопкиРедактораГТ.НайтиПоКоду("Кнопка03").ПолучитьОбъект();
НоваяКнопка.ДобавитьВСкрипт(ТекстHTML);
НоваяКнопка.ПочиститьРыбу(ТекстHTML);
Этап 5. Обрабатываем нажатия кнопок
Кнопка будет на панели (видно на КПДВ), осталось только обработать вставку и сжатие изображения со стороны 1С:
Кстати, движок Webkit ну совершенно игнорирует ... попытки сжатия изображения своим посредством (методы ToBlob и ToDataUrl) и там, где обычные браузеры успешно сжимают на порядок и больше, сжатие тем же способом в 1С не дает ничего. Да. К счастью же, есть метод ПолучитьКартинку у объекта ОбрабатываемаяКартинка и здесь можно поиграться с параметрами изображения, чтобы сжать их. Убиваем сразу трех зайцев. Исправляем недочет движка 1С и... исправляем еще один недочет платформы - во встроенном в движок окне выбора файла изображения кнопка и надпись не переведены на русский язык, а в Учебной версии и вовсе утрачены тексты, да и окно, несмотря на указанный тип accept="image/*", дает на выбор все файлы, а вот диалог настраивается...
В этом коде есть интересный момент пляски с меткой, сделанной со стороны JavaScript. Зачем она нужна? Чтобы передвинуть курсор мыши строго после текущего положения курсора и вставка, разумеется, что произошла именно в то же место. Дело в том, что со стороны 1С мы максимум что можем - это определить координаты курсора мыши, но обрабатывать их в отношении к тексту на экране просто бессмысленно, нужна позиция именно в тексте с точностью до строки и символа. Это нам дает только JS, которая умеет с этим работать и то с серьезными ограничениями (то, что мы видим, не обязательно то, что оно есть, поэтому для выделения двух соседних слов есть специальная команда, а сами - попробуйте-ка в форматированном тексте!)
В общем, без хитростей не обойтись. И вторая хитрость здесь в том, что появляется скрытый элемент, который выделяется, а затем удаляется: дело просто в том, что если ничего не выделить, что при изменении свойства innerHTML у основного элемента - редактора позиция курсора уйдет в неопознанную область в самом начале, куда не попасть курсором вручную (залипает в спрайтах, не иначе). И если пользователь, не дай бог, решит еще раз воспользоваться вставкой, не передвинув щелчком курсор мыши ... слететь может все, включая панельку и рабочие скрипты. Поэтому в сложных случаях подставляем скрытый элемент, на который после изменения innerHTML и переходим, либо, если позволяет логика, просто выделяем весь текст, а пользователь пусть делает как знает:
ЭлементыФормы.ПолеHTMLОписанияЗадачи.Документ.defaultView.SelectAllText();
Разумеется, эта простенькая процедура уже размещена в модуле скрипта, но если вы очень смелы, можете попробовать вызвать ее тело прямо от defaultView или даже Документа, там не сложно: document.execCommand("selectAll").
Вышеупомянутую пугалку убирать не буду, потому что момент важный, но как выяснилось, обходится еще легче: догадались? Все тот же focus(), который я ставил в конце вызова, нужно поставить перед выполнением основного кода, например, вставки, и тогда все пройдет отлично независимо от положения курсора перед вызовом функции!
Этап 6 Вставка из Word/Excel
Наверное, все причастные к теме редакторов в курсе, как много лишнего (в плане стилей) вставляют в скрытом виде при копировании указанные выше приложения. Объем данных более чем на порядок превышает количество символов, но оказывается с этим можно бороться, и Word, а особенно, Excel, изрядно нам сами в этом помогают, назначая разным стилям разные классы. Увы, далеко не качественно назначаются классы, слишком много остается зоопарка стилей среди класса, но идея хорошая, подхватываем ее, создавая уникальный класс для каждого стиля и, естественно, фиксируем это в коде вместо стилей для каждого тэга:
Надеюсь, сжатие кому-нибудь да пригодится - на случайно скачанном сложном файле из Word'a показал 13 769 байт строки вместо 30 229 до сжатия стилей (только на них!). Конечно, текста еще раз в 5 меньше, но вы попробуйте такое текстом создать:
Этап 7 Возвращение совместимости с Linux
Здесь чуда не случится, но что замечено/проделано:
- линуксовая версия WebKit щепетильнее относится к употреблению ключевого слова let, которое по примеру источника я опрометчиво поставил вместо var. Было время, когда я и сам разбирался в тонкостях этих двух ключевых слов, но
потом мне прострелили колено... © - WebKit на линукс не знает обратных кавычек ``, которые в обычном мире давно обозначают обычный текстовый шаблон
- addEventListener - с ним большие сложности, никаких "() =>" нельзя, а bind полной заменой не служит (по моему поверхностному, но упрямому, подтвержденному практикой мнению). Если кто-то может разубедить, будет здорово, но в применении к данному решению не очень важно, поскольку все выше приведенные конструкции просто не позволяют коду основного скрипта нормально инициализироваться и окно с кнопками просто не появится, но когда вы это исправите:
- не отображаются подсказки
- внезапно, для строки нет функции includes, ее просто нет, так что используем indexOf по полной!, пока ее тоже не убрали(
- естественно, на linux регулярные выражения, используемые мной при подготовке текста ГТР доступны только с версии 8.3.23, а меня интересует совместимость с 8.2.13, поэтому регулярные выражения 1С я лишь попробовал, нашел, что они работают исправно, но с одним ограничением: они более чувствительны или скорее требовательны к экранированию специальных символов и там где обычная регулярка отловит } в тексте именно как фигурную скобку, регулярное выражение 1С вылетит с ошибкой, пока вы не сделаете \}, чтобы показать, что это просто фигурная скобка и ничего более. Но я это только на 8.3.23 проверял, на 24 и далее поведения не знаю.
В общем, если нужен линукс, то организовать это не сложно, возможно и с ограничениями, как и везде на линукс. Кстати, если кто не знал: на управляемых формах 1С нажатие клавиш Ctrl+Alt+Shift+F12, что на линукс, что на windows приводит к отображению горячо любимой всеми причастными к веб-разработке Консоли разработчика. Применительно к данному случаю консоль нужно вызывать (повторюсь - на управляемых формах), предварительно щелкнув в область за пределами редактируемого сейчас кода, снизу, например, по крайней мере, так срабатывает стабильнее.
Этап 8. Заключительный
Итак, код исправно работает, оболочка редактора, написанная на JavaScript, успешно встроена в 1С. Время радоваться! Но нет. Есть здесь капля дегтя в бочке меда. За две-три недели экспериментов встречено минимум два случая, когда на месте панели не оказывалось ничего. Вплоть до перезагрузки 1С, когда все опять свистело и вертелось. Но пятнышко-то ... осталось. Одну из возможных причин помогает проработать эксперимент с управляемыми формами. У них не все так кристально, как с обычными, поскольку нет метода УстановитьТекст, который заново инициализирует элемент управления, запуская встроенные стили и скрипты или я плохо старался его найти (но мне и не надо, хм, а здесь используется альтернативный подход). Так вот, при перезаписи свойства innerHtml у основного элемента (в замену такового метода) скрипт инициализации редактора сам не запускается. Поэтому было предусмотрено окно, в котором наивному пользователю будет предложено поработать рычагом, он же дергалка, он же "кривой стартер":
Попутно, обнаружен и более быстрый (окно с этими кнопками не успевает появиться) метод инициализации: подписка на событие load вместо стандартной инициализации методом jquery. Так что, возможно, картинка выше никогда не появится, но практика и время покажет.
На этом все, базу и основные файлы тоже выкладываю: экспериментируйте, пользуйтесь, радуйтесь, как я радовался при проработке данного перекрестного проекта. Действительно было очень интересно, не думаю, что мои порывы в технологии web на этом закончены, они все ближе, даже на таких, казалось бы устаревших проектах как УПП 1.3. Все самое интересное впереди!
Этап 9 Регулярные выражения
Считаю, эта статья была бы неполной без некоторого экскурса в регулярные выражения, потому что здесь без них никуда. Так что попытаюсь упорядочить свои полученные знания, да и кому-то пригодится, может. Заодно обновлюсь на 23 версию со встроенными регулярками (СтрНайтиВсеПоРегулярномуВыражению, СтрНайтиПоРегулярномуВыражению и (Ух-ты!) СтрЗаменитьПоРегулярномуВыражению)
Задача 1. Ищем последовательность с открывающим и закрывающим символом. Поиск атрибута html со значением (если вас просто интересует значение атрибута, никто не запрещает получить его через свойство Attributes любого элемента, но здесь атрибуты нужно обработать все и произвольно).
Пример текста: <p class="MsoNormaldop0"><span class="class1dop6">
Вызов: Совпадения = СтрНайтиВсеПоРегулярномуВыражению(ТекстHTML, "class=""[^""]*", Истина, Истина);
Объясняю: ищем объявление атрибута, поэтому начало - class=" (двойные кавычки для экранирования строк 1С, на сайте regex101.com, например, их экранировать не нужно), далее мы знаем, что объявление заканчивается теми же кавычками, поэтому включаем фразу ([^""]*), которая означает - квадратные скобки для объявления набора символов, в котором доступно все кроме (^) кавычек (") неограниченное число раз (*). Итогом два значения: class="MsoNormaldop0 и class="class1dop6, где значение удобно брать, начиная с кавычек.
Задача 1.1 Закрепляем. Убираем все открытия и закрытия тэгов, чтобы скрыть их предпросмотра
Пример текста: <p class="MsoNormaldop0" align="center">Должность (специальность, профессия)<o:p></o:p></p>
Вызов: Описание = СтрЗаменитьПоРегулярномуВыражению(Описание, "<[^>]*>", "", Истина, Истина);
Объясняю: тег открывается "<" и закрывается ">", поэтому ищем открытие тега, затем неограниченное число любого символа кроме ">" ([^>]*) и сам символ закрытия. Результатом вызова будет строка: Должность (специальность, профессия). При этом последнее закрытие в нашем случае может быть неполным, поэтому дополняем вызовом с таким шаблоном: "<[^>]*$", в котором последний символ ($) означает конец строки. Так <p class="MsoNormaldop0" align="c будет заменен на пустую строку.
Задача 2. Поиск буквенных последовательностей. Например, поиск объявления стиля в формате css.
Пример текста: td.class1dop13 { border: 1px solid #000000; padding: 0cm 0.05cm }
Вызов: Совпадения = СтрНайтиВсеПоРегулярномуВыражению(ТекстHTML, "\w*\.[a-zA-Z]{2,}\w*[^\}]*\}", Истина, Истина);
Объясняю: (\w*) - последовательность букв, цифр или знака подчеркивания (в нашем случае тег), далее точка, где обратный слэш используется для экранирования (\.), далее класс, то есть две и более букв ([a-zA-Z]{2,}), где {2,} означает от 2 до бесконечности, затем опять цифры\буквы (\w*) и вообще все, включая перенос строки, но кроме закрывающей фигурной скобки, которую тоже экранируем ([^\}]*), ну и сама, экранированная, скобка. Без экранирования, напоминаю, выпадает ошибка 10301 (U_REGEX_RULE_SYNTAX). В результате приводимая строка будет выбрана в таком же виде. Если нужны только цифры, используйте \d.
Задача 3. Учимся считать - это нужно! Например, при поиске объявления метода кнопки
Пример текста: {
key: "HtButtonMethod",
value: function() {
this.$editor.find(".note-editable").focus(), CheckItModificated()
}
}, {
Вызов: ШаблонМетода = "\{(.|\n){0,60}key.{1,10}HtButtonMethod(.|\n){1,300}\}, ?\{";
Совпадения = СтрНайтиВсеПоРегулярномуВыражению(ТекстHTML, ШаблонМетода, Истина, Истина);
Объясняю: открывающая скобка (\{), затем любые символы или перенос строки ((.|\n){0,60}), внимание, в количестве до 60 символов.
Зачем это? А если символов будет 61? Придется ставить по 61 символ, потому что если поставить *, то будет ошибка 10311 (U_REGEX_STACK_OVERFLOW), да и обычные регулярки тоже работать не будут. Это общее поведение. Причем здесь есть интересный момент: вы можете поставить несколько блоков {0,Число}, но если их общая сумма символов перевалит за 2000, то сайт regex101, например, выдаст engine error, а 1С - нет, справляется, но тогда в эти 2000 символов может попасть еще несколько методов, с открывающими и закрывающими скобками, да и тупит она при этом знатно, так что берем по минимуму.
Далее key и до 10 любых символов, ибо (.{1,10}), затем ... , потом один или 0 пробелов ( ?) - делаем для учета минификации\украшения кода, ну и закрывающая скобка (\{), все!