SlideShare una empresa de Scribd logo
1 de 98
Разработка Rich Text Editor:
проблемы и решения
Егор Яковишен, Setka
Setka Editor
• многоколоночная верстка
• гибкая настройка стилей
• адаптивная верстка
• CSS и JS эмбеды
• live preview
• плагин для WordPress
Более 20 млн просмотров
Текстовые редакторы
появились давно…
MS-DOS Editor
Notepad
Microsoft Word
Adobe InDesign
А что в браузере?
<textarea>
TinyMCE
CKEditor
Froala Editor
Есть проблемы :-(
Проблемы
• What You See is not What You Get
• Функций очень мало или слишком много
• Сложно «подружить» редактор с существующим дизайном сайта
• Кроссбраузерность (IE, Safari, …)
• Редакторы создаются как инструменты общего назначения
Проблемы
• Строгая очистка HTML-кода — или наоборот
• Проблемы с SEO
• Верстка только под десктоп
• Поддержка очень старых браузеров или только новых
• …
Итак, вы решили сделать
свой редактор…
Хорошая попытка!
Что предстоит?
• Как редактировать контент?
• Как хранить контент?
• Как быть с CSS?
• Как расширять функциональность редактора?
Как редактировать контент?
Как редактируется контент на странице?
• document.designMode = "on”
• <div contenteditable=“true”></div>
• document.execCommand()
document.designMode = "on"
document.designMode = "on"
contenteditable
<div contenteditable=“true”>
Hello, world
</div>
contenteditable: Chrome, Safari
<div contenteditable=“true”>
Hello,
<div>&nbsp;world</div>
</div>
contenteditable: Firefox
<div contenteditable=“true”>
Hello,
<br>
world
</div>
document.execCommand
// JS
document.execCommand(‘bold’);
document.execCommand(‘fontSize’, false, 7);
<!-- HTML -->
<b>Hello</b>, world
<font size=“7”>Hello</font>, world
Проблемы
• Разные браузеры генерируют разный HTML
• execCommand работает не везде и не всегда (и тоже по-разному)
• Вы не контролируете, что происходит :—(
Проблемы
Альтернативные способы
• <canvas>
• contenteditable + document state
contenteditable + document state
• contenteditable используется только
как интерфейс для отслеживания событий
• все события перехватываются и происходит
изменение document state
• после изменения state обновляется
DOM-представление документа
• controlled input
document и editor state
• Контент (текст, картинки, …)
• Позиция курсора
• Выделение текста
• UI редактора
Draft.js
Google Docs: kix
Google Docs: kix
Google Docs: kix
Как хранить контент?
Хранение в виде HTML: плюсы
• Легко показать в браузере
• Легко изменять без редактора
• Скорее всего, сейчас хранится именно так
• Другой код приучен работать с HTML
Хранение в виде HTML: минусы
• Разные браузеры выдают разный HTML из contenteditable
• Сложнее экспортировать в другие форматы (FB IA, JSON, …)
• Контент тесно связан с оформлением (style, class, …)
Декомпозиция на сущности
• Заголовок
• Параграф
• Картинка
• Врезка
• Комментарий
• …
Структура данных
{
type: “Paragraph”,
content: “this is a text”,
style: [
{
range: [0,3],
format: [“bold”]
}
]
}
Представления
• Plain text
• HTML
• PDF
• JSON, XML
• RSS
• Facebook Instant Articles, Google AMP, Apple News
• А также разный рендеринг сущностей в редакторе и вне его
Workaround
• Хранить данные в том формате, который уже есть (HTML)
• При загрузке в редактор парсить их и работать со своим
форматом
• Удобно в тех случаях, когда нужно работать в устоявшейся
экосистеме (например, CMS с плагинами)
Как работать с
пользовательским вводом?
Клавиатура
Виртуальная клавиатура
Контекстное меню
Copy & Paste
Drag’n’drop
Голосовой ввод
Рукописный ввод
autocorrect и autosuggest
Браузерные API
• У contenteditable нет единого события change
• Selection API
• Clipboard API
• CompositionEvents
• MutationObserver
Selection API
• События selectstart и selectionchange
• window.getSelection()
• Range, anchorNode, offsetNode
Selection API
Clipboard API
• События: copy, cut, paste, beforecopy, beforecut, beforepaste
• document.execCommand(‘copy’)
• Разные типы содержимого
• Что можно сделать?
• изменить содержимое буфера
• запретить вставку
• изменить алгоритм вставки
• Чего нельзя сделать?
• инициировать вставку
Clipboard API
Clipboard API
Clipboard API
Composition Events
• à, è, ù, …
• compositionstart
• compositionupdate
• compositionend
Undo / Redo
• Системный механизм не подойдет
• Храним снэпшоты state
Как быть с CSS?
В чем проблема?
• Редактор — это компонент
• У редактора есть свои стили (UI)
• Редактор живет внутри приложения (CMS)
• У приложения есть свои стили
• Внутри редактора живет пост
• У поста тоже есть свои стили (шрифты, цвета, …)
При этом…
• CSS-правила живут в глобальной области видимости
• Порядок применения правил определяется специфичностью
• Помним про WYSIWYG!
WordPress CSS
#poststuff h2 {
font-size: 14px;
padding: 8px 12px;
margin: 0;
line-height: 1.4;
}
Итак, что изолируем?
• Редактор
• Пост
• CMS
• Шаблон сайта (в некоторых случаях)
Способы изоляции CSS
• CSS reset + БЭМ
• iframe
• Shadow DOM + Custom Elements
Побеждаем специфичность
WordPress CSS:
#poststuff h2 {
font-size: 14px;
}
CSS редактора:
#my-editor h2 {
font-size: 28px;
}
Скользкая дорожка
• инлайн-стили
• !important
CSS reset + БЭМ
• Раздувает размер CSS-файла
• Постоянные войны со специфичностью (своей и чужой)
• Не дает 100% гарантии (на каждый хитрый селектор…)
iframe
• Создает барьер между страницей и редактором (и хорошо, и
плохо)
• Не адаптируется автоматически под размеры содержимого
Shadow DOM + Custom Elements
<my-editor>
#shadow-root
</my-editor>
Shadow DOM + Custom Elements
Shadow DOM + Custom Elements
Адаптивная верстка
• Верстаем на десктопе
• Хотим видеть, как это выглядит на мобилке
• А также менять определенные параметры для мобилки
Как расширять
функциональность?
Расширение функциональности
• Public API
• Система плагинов
• Документация
Public API
• Получение и изменение контента в редакторе
• Система событий
• Интеграция с внешней средой (загрузка файлов, взаимодействие
с CMS API и т.д.)
Система плагинов
• Плагины должны уметь влиять на state редактора
• Плагин — набор actions и reducers
• API для расширения UI редактора
• Примеры плагинов:
• Проверка орфографии
• Автотипограф
• Интеграция с Google Drive
О чем еще надо подумать?
• oembed и санитайзинг HTML
• Контекстное меню
• Горячие клавиши
• Режим редактирования HTML и Custom CSS
• Мета-сущности (комментарии)
• Анимации
• Оффлайн-режим
• Запуск и остановка редактора
Что нас ждет?
Будущее rich text editing
• W3C Input Events
• Web Components
Input Events
• События input и beforeinput
• InputEvent.inputType
• insert
• insertReplacementText
• deleteByCut
• formatBold
Input Events
• InputEvent.data (insert*, format*)
• InputEvent.dataTransfer (text/plain, text/html)
• InputEvent.getTargetRanges()
Итоги
Как редактировать данные?
• Используйте contenteditable для отслеживания событий
• Храните состояние редактора и контента в хранилище
• Не храните состояние в DOM
Как хранить данные?
• Определитесь с сущностями
• Продумайте структуру документа
• Напишите сериализацию и десериализацию
Как быть с CSS?
• Редактор будет жить во внешней среде, которую вы не
контролируете
• Заранее подумайте об изоляции CSS
• Кладите редактор в iframe или очищайте все, что можно
Расширение функциональности
• Модульная архитектура
• API
• Плагины
Хорошие примеры
• Quill (Salesforce, Telegra.ph)
• Draft.js (Facebook)
• Trix (Basecamp)
• ProseMirror, CodeMirror
• Google Docs
• iCloud Pages
Спасибо!
Егор Яковишен
yakovishen@setka.io
Telegram: @yaplusplus
Facebook: Yegor Yakovishen

Más contenido relacionado

La actualidad más candente

практическое использование модуля Panels богуцкий виктор
практическое использование модуля Panels богуцкий викторпрактическое использование модуля Panels богуцкий виктор
практическое использование модуля Panels богуцкий виктор
drupalconf
 
сысоев андрей
сысоев андрейсысоев андрей
сысоев андрей
Vlado Sudin
 
сысоев андрей
сысоев андрейсысоев андрей
сысоев андрей
Vlado Sudin
 
BEM — block, element, modification conception
BEM — block, element, modification conceptionBEM — block, element, modification conception
BEM — block, element, modification conception
Vadim Patsev
 
Роман Комаров — «Механизм работы браузера»
Роман Комаров — «Механизм работы браузера»Роман Комаров — «Механизм работы браузера»
Роман Комаров — «Механизм работы браузера»
Yandex
 
Оживление сайтов
Оживление сайтовОживление сайтов
Оживление сайтов
MageCloud
 
Как разработать интернет-магазин и не проспать SEO
Как разработать интернет-магазин и не проспать SEOКак разработать интернет-магазин и не проспать SEO
Как разработать интернет-магазин и не проспать SEO
iSEO
 

La actualidad más candente (13)

практическое использование модуля Panels богуцкий виктор
практическое использование модуля Panels богуцкий викторпрактическое использование модуля Panels богуцкий виктор
практическое использование модуля Panels богуцкий виктор
 
сысоев андрей
сысоев андрейсысоев андрей
сысоев андрей
 
сысоев андрей
сысоев андрейсысоев андрей
сысоев андрей
 
Основные операции с текстом
Основные операции с текстомОсновные операции с текстом
Основные операции с текстом
 
BEM — block, element, modification conception
BEM — block, element, modification conceptionBEM — block, element, modification conception
BEM — block, element, modification conception
 
Роман Комаров — «Механизм работы браузера»
Роман Комаров — «Механизм работы браузера»Роман Комаров — «Механизм работы браузера»
Роман Комаров — «Механизм работы браузера»
 
Mobile web apps
Mobile web appsMobile web apps
Mobile web apps
 
Оживление сайтов
Оживление сайтовОживление сайтов
Оживление сайтов
 
MS Word 2013 от новичка до профессионала. Занятие 2. Базовые возможности по р...
MS Word 2013 от новичка до профессионала. Занятие 2. Базовые возможности по р...MS Word 2013 от новичка до профессионала. Занятие 2. Базовые возможности по р...
MS Word 2013 от новичка до профессионала. Занятие 2. Базовые возможности по р...
 
Компонентный подход: скучно, неинтересно, бесперспективно
Компонентный подход: скучно, неинтересно, бесперспективноКомпонентный подход: скучно, неинтересно, бесперспективно
Компонентный подход: скучно, неинтересно, бесперспективно
 
Верстка писем. Часть 2
Верстка писем. Часть 2Верстка писем. Часть 2
Верстка писем. Часть 2
 
Как разработать интернет-магазин и не проспать SEO
Как разработать интернет-магазин и не проспать SEOКак разработать интернет-магазин и не проспать SEO
Как разработать интернет-магазин и не проспать SEO
 
Основы языка HTML
Основы языка HTMLОсновы языка HTML
Основы языка HTML
 

Similar a Разработка Rich Text Editor: проблемы и решения / Егор Яковишен (Setka)

Yaremchuk - Drupal CodeLobster
Yaremchuk - Drupal CodeLobsterYaremchuk - Drupal CodeLobster
Yaremchuk - Drupal CodeLobster
Andrii Podanenko
 
Javascript-фреймворки:
 должен остаться только один
Javascript-фреймворки:
 должен остаться только одинJavascript-фреймворки:
 должен остаться только один
Javascript-фреймворки:
 должен остаться только один
Sergey Xek
 
Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)
Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)
Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)
Ontico
 
2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один
2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один
2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один
HappyDev
 
Как создать тему для магазина на платформе InSales?
Как создать тему для магазина на платформе InSales?Как создать тему для магазина на платформе InSales?
Как создать тему для магазина на платформе InSales?
InSales
 
Построение собственного JS SDK — зачем и как?
Построение собственного JS SDK — зачем и как?Построение собственного JS SDK — зачем и как?
Построение собственного JS SDK — зачем и как?
buranLcme
 
SECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только один
SECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только одинSECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только один
SECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только один
SECON
 

Similar a Разработка Rich Text Editor: проблемы и решения / Егор Яковишен (Setka) (20)

Управляем сайтом: Быстро. Просто. Эффективно.
Управляем сайтом: Быстро. Просто. Эффективно.Управляем сайтом: Быстро. Просто. Эффективно.
Управляем сайтом: Быстро. Просто. Эффективно.
 
Экскурс в мир WEB разработки
Экскурс в мир WEB разработкиЭкскурс в мир WEB разработки
Экскурс в мир WEB разработки
 
Yaremchuk - Drupal CodeLobster
Yaremchuk - Drupal CodeLobsterYaremchuk - Drupal CodeLobster
Yaremchuk - Drupal CodeLobster
 
TК°Conf. Организация разработки Frontend. Виталий Слободин.
TК°Conf. Организация разработки Frontend. Виталий Слободин.TК°Conf. Организация разработки Frontend. Виталий Слободин.
TК°Conf. Организация разработки Frontend. Виталий Слободин.
 
Javascript-фреймворки:
 должен остаться только один
Javascript-фреймворки:
 должен остаться только одинJavascript-фреймворки:
 должен остаться только один
Javascript-фреймворки:
 должен остаться только один
 
Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)
Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)
Javascript-фреймворки: должен остаться только один / Аверин Сергей (Acronis)
 
SEO для разработчика сайта
SEO для разработчика сайтаSEO для разработчика сайта
SEO для разработчика сайта
 
Введение во фронтенд-разработку
Введение во фронтенд-разработкуВведение во фронтенд-разработку
Введение во фронтенд-разработку
 
2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один
2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один
2015-12-05 Сергей Аверин - Javascript-фреймворки: должен остаться только один
 
CMS
CMSCMS
CMS
 
Разработка мобильного и веб интерфейса для Caché
Разработка мобильного и веб интерфейса для CachéРазработка мобильного и веб интерфейса для Caché
Разработка мобильного и веб интерфейса для Caché
 
Как создать тему для магазина на платформе InSales?
Как создать тему для магазина на платформе InSales?Как создать тему для магазина на платформе InSales?
Как создать тему для магазина на платформе InSales?
 
Иван Карев — Клиентская оптимизация
Иван Карев — Клиентская оптимизацияИван Карев — Клиентская оптимизация
Иван Карев — Клиентская оптимизация
 
Web and mobile development for intersystems caché, Eduard Lebedyuk
Web and mobile development for intersystems caché, Eduard LebedyukWeb and mobile development for intersystems caché, Eduard Lebedyuk
Web and mobile development for intersystems caché, Eduard Lebedyuk
 
Построение собственного JS SDK — зачем и как?
Построение собственного JS SDK — зачем и как?Построение собственного JS SDK — зачем и как?
Построение собственного JS SDK — зачем и как?
 
SECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только один
SECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только одинSECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только один
SECON'2016. Сергей Аверин. Javascript-фреймворки:
 должен остаться только один
 
SECON'2016. Аверин Сергей, Javascript-фреймворки:
 должен остаться только один
SECON'2016. Аверин Сергей, Javascript-фреймворки:
 должен остаться только одинSECON'2016. Аверин Сергей, Javascript-фреймворки:
 должен остаться только один
SECON'2016. Аверин Сергей, Javascript-фреймворки:
 должен остаться только один
 
A.pleshkov
A.pleshkovA.pleshkov
A.pleshkov
 
YaC 2013 Notes
YaC 2013 NotesYaC 2013 Notes
YaC 2013 Notes
 
Конференция SEO-ПРАКТИКУМ — Севальнев — SEO и разработка сайта
Конференция SEO-ПРАКТИКУМ — Севальнев — SEO и разработка сайтаКонференция SEO-ПРАКТИКУМ — Севальнев — SEO и разработка сайта
Конференция SEO-ПРАКТИКУМ — Севальнев — SEO и разработка сайта
 

Más de Ontico

Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Ontico
 

Más de Ontico (20)

One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...
One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...
One-cloud — система управления дата-центром в Одноклассниках / Олег Анастасье...
 
Масштабируя DNS / Артем Гавриченков (Qrator Labs)
Масштабируя DNS / Артем Гавриченков (Qrator Labs)Масштабируя DNS / Артем Гавриченков (Qrator Labs)
Масштабируя DNS / Артем Гавриченков (Qrator Labs)
 
Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)
Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)
Создание BigData-платформы для ФГУП Почта России / Андрей Бащенко (Luxoft)
 
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
Готовим тестовое окружение, или сколько тестовых инстансов вам нужно / Алекса...
 
Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...
Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...
Новые технологии репликации данных в PostgreSQL / Александр Алексеев (Postgre...
 
PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)
PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)
PostgreSQL Configuration for Humans / Alvaro Hernandez (OnGres)
 
Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...
Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...
Inexpensive Datamasking for MySQL with ProxySQL — Data Anonymization for Deve...
 
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
Опыт разработки модуля межсетевого экранирования для MySQL / Олег Брославский...
 
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
ProxySQL Use Case Scenarios / Alkin Tezuysal (Percona)
 
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)MySQL Replication — Advanced Features / Петр Зайцев (Percona)
MySQL Replication — Advanced Features / Петр Зайцев (Percona)
 
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
Внутренний open-source. Как разрабатывать мобильное приложение большим количе...
 
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
Подробно о том, как Causal Consistency реализовано в MongoDB / Михаил Тюленев...
 
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
Балансировка на скорости проводов. Без ASIC, без ограничений. Решения NFWare ...
 
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
Перехват трафика — мифы и реальность / Евгений Усков (Qrator Labs)
 
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
И тогда наверняка вдруг запляшут облака! / Алексей Сушков (ПЕТЕР-СЕРВИС)
 
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
Как мы заставили Druid работать в Одноклассниках / Юрий Невиницин (OK.RU)
 
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
Разгоняем ASP.NET Core / Илья Вербицкий (WebStoating s.r.o.)
 
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...100500 способов кэширования в Oracle Database или как достичь максимальной ск...
100500 способов кэширования в Oracle Database или как достичь максимальной ск...
 
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
Apache Ignite Persistence: зачем Persistence для In-Memory, и как он работает...
 
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
Механизмы мониторинга баз данных: взгляд изнутри / Дмитрий Еманов (Firebird P...
 

Разработка Rich Text Editor: проблемы и решения / Егор Яковишен (Setka)