Spec-Zone.ru › Wagtail 3

Внутреннее устройство форматированного текста

На первый взгляд, возможности форматированного текста 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 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.richtext.LinkHandler или wagtail.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.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.rich_text.LinkHandler, и добавить его в список доступных обработчиков ссылок во время преобразования форматированного текста.

# my_app/wagtail_hooks.py

from wagtail 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 import hooks
from wagtail.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.rich_text.EmbedHandler, и добавить его в список обработчиков встраивания, доступных во время преобразования форматированного текста.

# my_app/wagtail_hooks.py

from wagtail 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).

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

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

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

Например, стороннее расширение Wagtail может ввести table в качестве новой функции форматированного текста и предоставить реализации для редактора Draftail (который предоставляет механизм плагинов). В этом случае стороннее расширение не будет знать о вашем пользовательском виджете редактора, и поэтому виджет не будет знать, как обработать идентификатор функции 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.rich_text.features. Возможной отправной точкой для редактора форматированного текста с поддержкой функций будет:

from django.forms import widgets
from wagtail.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 для встроенного редактора), идентификатор функции и объект определения плагина. Этот объект специфичен для виджета редактора и может быть любым произвольным значением, но, как правило, включает в себя определение средств Django формы ссылаясь на JavaScript-код плагина, который затем будет объединён в собственное определение средств виджета редактора, наряду с любыми соответствующими параметрами конфигурации, которые нужно передать при инициализации редактора.

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

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

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

Виджеты редакторов часто не могут работать напрямую с форматом форматированного текста Wagtail и требуют преобразования в собственный внутренний формат. Для Draftail это формат на основе JSON, известный как ContentState (см. Как Draft.js представляет данные форматированного текста). Редакторы, основанные на механизме HTML contentEditable, требуют допустимого HTML, и поэтому Wagtail использует соглашение, называемое «editor 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 см. в Расширение редактора Draftail.

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

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

Spec-Zone.ru

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