Spec-Zone.ru › Wagtail

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

На первый взгляд, возможности форматированного текста 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. Объект, известный как реестр функций, служит центральным источником информации о том, как должно работать форматирование текста. К этому объекту можно получить доступ через хук Регистрация функций форматированного текста, который вызывается при запуске для сбора всех определений, относящихся к форматированному тексту:

    # 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

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

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

Этот метод позволяет зарегистрировать пользовательский обработчик, производный от 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 класса django.forms.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 использует соглашение, называемое «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/stable/extending/rich_text_internals.html

Spec-Zone.ru

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