diff --git a/docs/advanced_topics/customisation/index.rst b/docs/advanced_topics/customisation/index.rst index da2438032..0d044f6a5 100644 --- a/docs/advanced_topics/customisation/index.rst +++ b/docs/advanced_topics/customisation/index.rst @@ -6,6 +6,7 @@ Customising Wagtail :maxdepth: 2 page_editing_interface + rich_text_internals extending_draftail extending_hallo admin_templates diff --git a/docs/advanced_topics/customisation/page_editing_interface.rst b/docs/advanced_topics/customisation/page_editing_interface.rst index 9d3d5eb1b..fb149856c 100644 --- a/docs/advanced_topics/customisation/page_editing_interface.rst +++ b/docs/advanced_topics/customisation/page_editing_interface.rst @@ -86,23 +86,9 @@ The feature identifiers provided on a default Wagtail installation are as follow * ``embed`` - embedded media (see :ref:`embedded_content`) -Adding new features to this list is generally a two step process: - - * Create a plugin that extends the editor with a new toolbar button or other control(s) to manage the rich text formatting of the feature. - * Create conversion or whitelist rules to define how content from the editor should be filtered or transformed before storage, and front-end HTML output. - -Both of these steps are performed through the ``register_rich_text_features`` hook (see :ref:`admin_hooks`). The hook function is triggered on startup, and receives a *feature registry* object as its argument; this object keeps track of the behaviours associated with each feature identifier. - -To have a feature active by default (i.e. on ``RichTextFields`` that do not define an explicit ``features`` list), add it to the ``default_features`` list on the ``features`` object: - -.. code-block:: python - - @hooks.register('register_rich_text_features') - def register_blockquote_feature(features): - features.default_features.append('h6') - The process for creating new features is described in the following pages: +* :doc:`./rich_text_internals` * :doc:`./extending_draftail` * :doc:`./extending_hallo` diff --git a/docs/advanced_topics/customisation/rich_text_internals.rst b/docs/advanced_topics/customisation/rich_text_internals.rst new file mode 100644 index 000000000..75529afe9 --- /dev/null +++ b/docs/advanced_topics/customisation/rich_text_internals.rst @@ -0,0 +1,205 @@ +Rich text internals +=================== + +At first glance, Wagtail's rich text capabilities appear to give editors direct control over a block of HTML content. In reality, it's necessary to give editors a representation of rich text content that is several steps removed from the final HTML output, for several reasons: + + * The editor interface needs to filter out certain kinds of unwanted markup; this includes malicious scripting, font styles pasted from an external word processor, and elements which would break the validity or consistency of the site design (for example, pages will generally reserve the ``
`` element", since various components of Wagtail - both client and server-side - need to agree on how to handle that feature, including how it should be exposed in the editor interface, how it should be represented within the database, and (if appropriate) how it should be translated when rendered on the front-end. + +The components involved in Wagtail's rich text handling are described below. + + +Data format +----------- + +Rich text data (as handled by :ref:`RichTextField`, and ``RichTextBlock`` within :doc:`StreamField `) is stored in the database in a format that is similar, but not identical, to HTML. For example, a link to a page might be stored as: + +.. code-block:: html + + Contact us for more information.
+ +Here, the ``linktype`` attribute identifies a rule that shall be used to rewrite the tag. When rendered on a template through the ``|richtext`` filter (see :ref:`rich-text-filter`), this is converted into valid HTML: + +.. code-block:: html + +Contact us for more information.
+ +In the case of ``RichTextBlock``, the block's value is a ``RichText`` object which performs this conversion automatically when rendered as a string, so the ``|richtext`` filter is not necessary. + +Likewise, an image inside rich text content might be stored as: + +.. code-block:: html + + + +which is converted into an ``img`` element when rendered: + +.. code-block:: html + ++ +Again, the ``embedtype`` attribute identifies a rule that shall be used to rewrite the tag. All tags other than ```` and ```` are left unchanged in the converted HTML. + +A number of additional constraints apply to ```` and ```` tags, to allow the conversion to be performed efficiently via string replacement: + + * The tag name and attributes must be lower-case + * Attribute values must be quoted with double-quotes + * ``embed`` elements must use XML self-closing tag syntax (i.e. end in ``/>`` instead of a closing ```` tag) + * The only HTML entities permitted in attribute values are ``<``, ``>``, ``&`` and ``"`` + + +The feature registry +-------------------- + +Any app within your project can define extensions to Wagtail's rich text handling, such as new ``linktype`` and ``embedtype`` rules. An object known as the *feature registry* serves as a central source of truth about how rich text should behave. This object can be accessed through the :ref:`register_rich_text_features` hook, which is called on startup to gather all definitions relating to rich text: + +.. code-block:: python + + # 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 + + +Link rewrite handlers +--------------------- + +.. method:: FeatureRegistry.register_link_type(linktype, handler) + +The ``register_link_type`` method allows you to define a function to be called when an ```` tag with a given ``linktype`` attribute is encountered. This function receives a dictionary of attributes from the original ```` tag, and returns a string to replace that opening tag (which must be a valid HTML ```` tag). The link element content and closing ```` tag is left unchanged. + +.. code-block:: python + + from django.utils.html import escape + from wagtail.core import hooks + from myapp.models import Report + + def report_link_handler(attrs): + # Handle a link of the form `` + try: + report = Report.objects.get(id=attrs['id']) + except (Report.DoesNotExist, KeyError): + return "" + + return '' % escape(report.url) + + + @hooks.register('register_rich_text_features') + def register_report_link(features): + features.register_link_type('report', report_link_handler) + + +Embed rewrite handlers +---------------------- + +.. method:: FeatureRegistry.register_embed_type(embedtype, handler) + +The ``register_embed_type`` method allows you to define a function to be called when an ```` tag with a given ``embedtype`` attribute is encountered. This function receives a dictionary of attributes from the original ``