Spec-Zone.ru › Wagtail 2

Внутренние механизмы форматированного текста

На первый взгляд, возможности Wagtail по работе с форматированным текстом предоставляют редакторам прямой контроль над блоком HTML-контента. На самом деле, для редакторов требуется представление форматированного текста, удалённое на несколько шагов от конечного HTML-вывода, по нескольким причинам:

  • Интерфейс редактора должен отфильтровывать определённые виды нежелательной разметки; это включает вредоносные скрипты, стили шрифтов, скопированные из внешнего текстового процессора, и элементы, которые нарушают валидность или согласованность дизайна сайта (например, страницы обычно резервируют элемент <h1> для заголовка страницы, поэтому не следует разрешать пользователям вставлять свои дополнительные элементы <h1> через форматированный текст).
  • Поля форматированного текста могут указывать аргумент features для дальнейшего ограничения разрешённых элементов в поле — см. Ограничение функций в поле форматированного текста.
  • Принудительное использование подмножества HTML помогает предотвратить попадание презентационной разметки в базу данных, что повышает поддерживаемость сайта и упрощает повторное использование контента сайта (включая, потенциально, генерацию вывода, отличного от HTML, например, LaTeX).
  • Элементы, такие как ссылки на страницы и изображения, должны сохранять метаданные, такие как идентификатор страницы или изображения, которые отсутствуют в конечном представлении HTML.

Это требует, чтобы контент форматированного текста проходил ряд шагов валидации и преобразования; как между интерфейсом редактора и версией, хранящейся в базе данных, так и от представления в базе данных до конечного рендеренного HTML.

По этой причине расширение обработки форматированного текста Wagtail для поддержки нового элемента более сложно, чем просто сказать (например): «включить элемент <blockquote>», так как различные компоненты Wagtail — как на стороне клиента, так и на стороне сервера — должны договориться о том, как обрабатывать эту функцию, включая то, как она должна отображаться в интерфейсе редактора, как она должна представляться в базе данных и (при необходимости) как она должна быть преобразована при рендеринге на фронтенде.

Ниже описаны компоненты, участвующие в обработке форматированного текста Wagtail.

Формат данных

Данные форматированного текста (обрабатываемые с помощью RichTextField и RichTextBlock в StreamField) хранятся в базе данных в формате, похожем, но не идентичном HTML. Например, ссылка на страницу может храниться следующим образом:

<p><a linktype="page" id="3">Contact us</a> for more information.</p>

Здесь атрибут linktype идентифицирует правило, которое будет использоваться для переписывания тега. При рендеринге на шаблоне с помощью фильтра |richtext (см. Форматированный текст (фильтр)) это преобразуется в допустимый HTML:

<p><a href="/contact-us/">Contact us</a> for more information.</p>

В случае RichTextBlock, значение блока является объектом RichText, который выполняет это преобразование автоматически при рендеринге в строку, поэтому фильтр |richtext не нужен.

Аналогично, изображение внутри контента форматированного текста может храниться следующим образом:

<embed embedtype="image" id="10" alt="A pied wagtail" format="left" />

что преобразуется в элемент img при рендеринге:

<img alt="A pied wagtail" class="richtext-image left" height="294" src="/media/images/pied-wagtail.width-500_ENyKffb.jpg" width="500">

Опять же, атрибут embedtype идентифицирует правило, которое будет использоваться для переписывания тега. Все теги, кроме <a linktype="..."> и <embed embedtype="..." />, остаются неизменными в преобразованном HTML.

Для эффективного преобразования посредством подстановки строк к тегам <a linktype="..."> и <embed embedtype="..." /> применяются ряд дополнительных ограничений:

  • Имя тега и атрибуты должны быть в нижнем регистре
  • Значения атрибутов должны быть заключены в двойные кавычки
  • embed элементы должны использовать синтаксис самозакрывающегося тега XML (то есть заканчиваться /> вместо закрывающего </embed> тега)
  • В значениях атрибутов разрешены только следующие HTML-сущности: &lt;, &gt;, &amp; и &quot;

Реестр функций

Любой приложение в вашем проекте может определить расширения для обработки форматированного текста Wagtail, такие как новые правила linktype и embedtype. Объект, известный как реестр функций, служит центральным источником информации о том, как должен работать форматированный текст. К этому объекту можно получить доступ через хук register_rich_text_features, который вызывается при запуске для сбора всех определений, связанных с форматированным текстом:

# my_app/wagtail_hooks.py

from wagtail.core import hooks

@hooks.register('register_rich_text_features')
def register_my_feature(features):
    # add new definitions to 'features' here

Обработчики переписывания

Обработчики переписывания — это классы, которые знают, как преобразовать содержимое тегов форматированного текста, таких как <a linktype="..."> и <embed embedtype="..." /> в HTML фронтенда. Например, класс PageLinkHandler знает, как преобразовать тег форматированного текста <a linktype="page" id="123"> в тег HTML <a href="/path/to/page/123">.

Обработчики переписывания также могут предоставлять другую полезную информацию о тегах форматированного текста. Например, используя соответствующий тег, PageLinkHandler можно использовать для извлечения страницы, на которую ссылаются. Это может быть полезно для кода, который может потребоваться информация об объектах, на которые ссылается форматированный текст.

Вы можете создать пользовательские обработчики переписывания для поддержки собственных новых тегов linktype и embedtype. Новые обработчики должны быть классами Python, которые наследуют от wagtail.core.richtext.LinkHandler или wagtail.core.richtext.EmbedHandler. Ваши новые классы должны переопределить по крайней мере некоторые из следующих методов (перечисленные здесь для LinkHandler, хотя EmbedHandler имеет идентичный сигнатуру):

Ниже приведен пример пользовательского обработчика переписывания, который реализует эти методы для добавления поддержки ссылок на адреса электронной почты пользователей в форматированном тексте. Он поддерживает преобразование тегов форматированного текста, таких как <a linktype="user" username="wagtail"> в допустимый HTML, такой как <a href="mailto:hello@wagtail.org">. Этот пример предполагает, что эквивалентная функциональность фронтенда была добавлена, чтобы пользователи могли вставлять такие ссылки в свой редактор форматированного текста.

from django.contrib.auth import get_user_model
from wagtail.core.rich_text import LinkHandler

class UserLinkHandler(LinkHandler):
    identifier = 'user'

    @staticmethod
    def get_model():
        return get_user_model()

    @classmethod
    def get_instance(cls, attrs):
        model = cls.get_model()
        return model.objects.get(username=attrs['username'])

    @classmethod
    def expand_db_attributes(cls, attrs):
        user = cls.get_instance(attrs)
        return '<a href="mailto:%s">' % user.email

Регистрация обработчиков переписывания

Обработчики переписывания также должны быть зарегистрированы в реестре функций через хук register_rich_text_features. Предоставляются отдельные методы для регистрации обработчиков ссылок и обработчиков встраивания.

Этот метод позволяет зарегистрировать пользовательский обработчик, унаследованный от wagtail.core.rich_text.LinkHandler, и добавить его в список обработчиков ссылок, доступных во время преобразования форматированного текста.

# my_app/wagtail_hooks.py

from wagtail.core import hooks
from my_app.handlers import MyCustomLinkHandler

@hooks.register('register_rich_text_features')
def register_link_handler(features):
    features.register_link_type(MyCustomLinkHandler)

Также можно определить обработчики переписывания ссылок для встроенных ссылок Wagtail external и email, даже если у них нет предварительно определённого linktype. Например, если вы хотите, чтобы внешние ссылки имели атрибут rel="nofollow" для целей SEO:

from django.utils.html import escape
from wagtail.core import hooks
from wagtail.core.rich_text import LinkHandler

class NoFollowExternalLinkHandler(LinkHandler):
    identifier = 'external'

    @classmethod
    def expand_db_attributes(cls, attrs):
        href = attrs["href"]
        return '<a href="%s" rel="nofollow">' % escape(href)

@hooks.register('register_rich_text_features')
def register_external_link(features):
    features.register_link_type(NoFollowExternalLinkHandler)

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

Этот метод позволяет зарегистрировать пользовательский обработчик, унаследованный от wagtail.core.rich_text.EmbedHandler, и добавить его в список обработчиков встраивания, доступных во время преобразования форматированного текста.

# my_app/wagtail_hooks.py

from wagtail.core import hooks
from my_app.handlers import MyCustomEmbedHandler

@hooks.register('register_rich_text_features')
def register_embed_handler(features):
    features.register_embed_type(MyCustomEmbedHandler)

Виджеты редактора

Интерфейс редактора, используемый в полях форматированного текста, можно настроить с помощью настройки WAGTAILADMIN_RICH_TEXT_EDITORS. Wagtail предоставляет две реализации редактора: wagtail.admin.rich_text.DraftailRichTextArea (редактор Draftail на основе Draft.js) и wagtail.admin.rich_text.HalloRichTextArea (устаревшая, основанная на Hallo.js).

Возможно создание собственной реализации редактора форматированного текста. Как минимум, редактор форматированного текста — это подкласс Django Widget, конструктор которого принимает ключевой аргумент options (словарь параметров конфигурации редактора, полученный из поля OPTIONS в поле WAGTAILADMIN_RICH_TEXT_EDITORS), и который потребляет и производит строковые данные в формате, похожем на HTML, описанном выше.

Как правило, виджет форматированного текста также получает список features , передаваемый либо из RichTextField / RichTextBlock или опции features в WAGTAILADMIN_RICH_TEXT_EDITORS, который определяет доступные функции в данном экземпляре редактора (см. Ограничение функций в поле форматированного текста). Чтобы включить поддержку функций, установите атрибут accepts_features = True в вашем классе виджета; тогда конструктор виджета получит список функций в качестве ключевого аргумента features.

Существует стандартный набор известных идентификаторов функций, как указано в Ограничение функций в поле форматированного текста, но это не исчерпывающий список; идентификаторы функций определяются только по соглашению, и каждый виджет редактора сам определяет, какие функции он будет распознавать и адаптировать своё поведение в соответствии с этим. Отдельные виджеты редактора могут реализовывать меньше или больше функций, чем по умолчанию, как встроенную функциональность, или через механизм плагинов, если у виджета редактора есть такой механизм.

Например, стороннее расширение Wagtail может ввести table как новую функцию форматированного текста и предоставить реализации для редакторов Draftail и Hallo (обе предоставляют механизм плагинов). В этом случае стороннее расширение не будет знать о вашем пользовательском виджете редактора, и поэтому виджет не будет знать, как обработать идентификатор функции table . Виджеты редактора должны игнорировать любые идентификаторы функций, которые они не распознают.

Атрибут default_features реестра функций — это список идентификаторов функций, который используется всякий раз, когда явный список функций не предоставлен в RichTextField / RichTextBlock или WAGTAILADMIN_RICH_TEXT_EDITORS. Этот список можно изменить в хуке register_rich_text_features для включения новых функций по умолчанию и получить его, вызвав get_default_features().

@hooks.register('register_rich_text_features')
def make_h1_default(features):
    features.default_features.append('h1')

Вне хука register_rich_text_features — например, внутри класса виджета — реестр функций можно импортировать как объект wagtail.core.rich_text.features. Возможная отправная точка для редактора богатого текста с поддержкой функций:

from django.forms import widgets
from wagtail.core.rich_text import features

class CustomRichTextArea(widgets.TextArea):
    accepts_features = True

    def __init__(self, *args, **kwargs):
        self.options = kwargs.pop('options', None)

        self.features = kwargs.pop('features', None)
        if self.features is None:
            self.features = features.get_default_features()

        super().__init__(*args, **kwargs)

Плагины редактора

Редакторы богатого текста часто предоставляют механизм плагинов, позволяющий расширять редактор новыми функциями. Метод register_editor_plugin предоставляет стандартный способ для хуков register_rich_text_features определять плагины, которые будут подключаться к редактору при включении определенной функции богатого текста.

register_editor_plugin принимает имя редактора (строка, уникально идентифицирующая виджет редактора — Wagtail использует идентификаторы draftail и hallo для своих встроенных редакторов), идентификатор функции и объект определения плагина. Этот объект специфичен для виджета редактора и может иметь любое произвольное значение, но обычно включает определение медиа Django-формы, ссылающееся на JavaScript-код плагина — который затем будет объединён в собственное определение медиа виджета редактора — вместе со всеми соответствующими параметрами конфигурации, которые необходимо передать при создании редактора.

Внутри виджета редактора определение плагина для данной функции можно получить через метод get_editor_plugin, передав собственный идентификатор строки редактора и идентификатор функции. Это вернёт None , если не зарегистрирован соответствующий плагин.

Подробности о форматах плагинов для встроенных редакторов Wagtail см. в Расширение редактора Draftail и Расширение редактора Hallo.

Конвертеры форматов

Редакторы виджетов часто не могут напрямую работать с форматом богатого текста Wagtail и требуют преобразования в свой собственный родной формат. Для Draftail это формат на основе JSON, известный как ContentState (см. Как Draft.js представляет данные богатого текста). Hallo.js и другие редакторы, основанные на механизме contentEditable HTML, требуют допустимого HTML, поэтому Wagtail использует соглашение, называемое «HTML редактора», где дополнительные данные, необходимые для элементов ссылок и встроек, хранятся в атрибутах data-, например: <a href="/contact-us/" data-linktype="page" data-id="3">Contact us</a>.

Wagtail предоставляет два вспомогательных класса, wagtail.admin.rich_text.converters.contentstate.ContentstateConverter и wagtail.admin.rich_text.converters.editor_html.EditorHTMLConverter, для выполнения преобразований между форматом богатого текста и родными форматами редактора. Эти классы независимы от любого виджета редактора и отличаются от процесса переписывания, который происходит при отображении богатого текста на шаблоне.

Оба класса принимают список features в качестве аргумента конструктора и реализуют два метода, from_database_format(data), который преобразует данные Wagtail богатого текста в формат редактора, и to_database_format(data), который преобразует данные редактора в формат богатого текста Wagtail.

Как и в случае с плагинами редактора, поведение класса конвертера может меняться в зависимости от списка функций, переданного ему. В частности, он может применять правила белого списка, чтобы гарантировать, что вывод содержит только HTML-элементы, соответствующие текущему активному набору функций. Реестр функций предоставляет метод register_converter_rule для того, чтобы хуки register_rich_text_features могли определять правила преобразования, которые будут активированы при включении данной функции.

register_editor_plugin принимает имя конвертера (строка, уникально идентифицирующая класс конвертера — Wagtail использует идентификаторы contentstate и editorhtml), идентификатор функции и объект определения правила. Этот объект специфичен для конвертера и может иметь любое произвольное значение.

Подробности о формате определения правил для конвертеров contentstate и editorhtml см. соответственно в Расширение редактора Draftail и Расширение редактора Hallo.

Внутри класса конвертера определение правила для данной функции можно получить через метод get_converter_rule, передав собственный идентификатор конвертера и идентификатор функции. Это вернёт None , если не зарегистрировано соответствующее правило.

  • Предыдущая Настройка представлений редактирования/создания групп
  • Следующая Расширение редактора Draftail

Содержание страницы

  • Внутренности богатого текста
    • Формат данных
    • Реестр функций
    • Обработчики переписывания
    • Регистрация обработчиков переписывания
    • Виджеты редактора
    • Плагины редактора
    • Конвертеры форматов

© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/v2.16.3/extending/rich_text_internals.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API