«Сделать невозможное возможным» или как разработчики Creative боролись за асинхронность в Quill

«Сделать невозможное возможным» или как разработчики Creative боролись за асинхронность в Quill
Андрей
frontend Creative

Всем привет! Меня зовут Андрей, я frontend-разработчик компании Creative. Сегодня хочу поделиться с вами довольно интересным кейсом. Однажды заказчик обратился к нам с довольно нетривиальной задачей – создать в редакторе Quill Rich Text Editor блок, который будет появляться при вставке ссылки, отправлять запросы серверу и «доставать» описание, тайтл, превью и иконку страницы. Вроде ничего сложного, но мы столкнулись с двумя большими НО. Об этом и будет моя статья. Поехали!

Если вы уже работали с Quill Rich Text Editor, то наслышаны о его весьма «подробной» документации. Если нет, поясню. Дело в том, что в доках Quill’a чётко не описано, как в нём создавать свои кастомные блоки.

Это оказалось первой проблемой, с которой мы столкнулись. На поиски решения ушло полтора рабочих дня и литр слёз разработчика, но в конечном счёте добились желаемого (иллюстрация 1).

<p><i>Иллюстрация 1</i></p>

Иллюстрация 1

В Quill есть несколько низкоуровневых классов, которые используются в его модулях. На их основе мы пишем свои. Embed – это блок, в который нельзя вводить текст (ссылка, картинка, кнопка). Text – это блок, в который текст как раз вводить можно и нужно. Совмещать эти 2 класса в одном элементе очень сложно, а в некоторых случаях – и вовсе невозможно.

Второй проблемой стала невозможность внедрения нашей функции для получения html-кода страницы, из которого мы сможем забрать нужные нам meta-теги. Выглядит она примерно так (иллюстрация 2).

<i>Иллюстрация 2</i>
Иллюстрация 2

Какое решение этой проблемы приходит в голову первым? Отправить асинхронный запрос на сервер и работать с полученным ответом. Мы это сделали и… ничего не получилось. После провала с простым async/await провели эксперимент и сделали самовызывающуюся функцию (иллюстрация 3).

<i>Иллюстрация 3</i>
Иллюстрация 3

Выше я обратил ваше внимание на том, что static всегда должен возвращать node. Но при асинхронности именно этого и не происходит: код переходит к выполнению другого метода и закономерно выдаёт нам с вами ошибку о том, что ему не с чем работать. Почему так происходит? Потому что он не получил элемент.

Если вы думаете, что можно сделать всё просто через async/await и вытащить return node за пределы асинхронности, то я вас огорчу: мы не успеем получить нужные данные и увидим закономерную ошибку.

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

<i>Иллюстрация 4</i>
Иллюстрация 4

После этих манипуляций при создании блока мы сможем получить все данные, обращаясь к объекту data (иллюстрация 5).

<i>Иллюстрация 5</i>
Иллюстрация 5

Итак, данные успешно получены, сохранены и теперь при следующей отрисовке они будут либо загружаться как новые, либо забираться обратно с помощью data-атрибутов.

Весь кастомный контент Quill’a я советую собирать именно с помощью атрибутов. Это позволит сохранять контент так, как удобно вам, а не редактору :) (иллюстрация 6).

<i>Иллюстрация 6</i>
Иллюстрация 6

В конце этой статьи хочу кратко обозначить две самые распространённые ошибки при работе с Quill Rich Text Editor. Надеюсь, эта информация позволит вам сохранить и время, и нервы:

  • Не стоит создавать блоки, в которых есть эмодзи или другие иконочные шрифты – при редактировании текста на них тоже накладываются изменения.
  • Если вы хотите изменять контент кастомного блока из компонента (в нашем случае это был Vue-компонент), вам нужно прокинуть кастомное событие с уникальным идентификатором блока. P.S.: в случае с React.js все гораздо проще, и можно просто вызвать функцию рендер с нужным нам компонентом. Но всё-таки, имейте этот вариант под рукой)

На этом у меня всё, буду рад ответить на ваши вопросы в комментариях!

11
Начать дискуссию