Metadata-Version: 2.4
Name: django-prose-editor
Version: 0.11b3
Summary: Prose editor for the Django admin based on ProseMirror
Project-URL: Documentation, https://django-prose-editor.readthedocs.io/
Project-URL: Homepage, https://github.com/matthiask/django-prose-editor/
Author-email: Matthias Kestenholz <mk@feinheit.ch>
License: BSD-3-Clause
License-File: LICENSE
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.10
Requires-Dist: django-js-asset>=3.1.2
Requires-Dist: django>=4.2
Provides-Extra: sanitize
Requires-Dist: nh3; extra == 'sanitize'
Provides-Extra: tests
Requires-Dist: asgiref; extra == 'tests'
Requires-Dist: coverage; extra == 'tests'
Requires-Dist: nh3; extra == 'tests'
Requires-Dist: pytest; extra == 'tests'
Requires-Dist: pytest-asyncio; extra == 'tests'
Requires-Dist: pytest-cov; extra == 'tests'
Requires-Dist: pytest-django; extra == 'tests'
Requires-Dist: pytest-playwright; extra == 'tests'
Description-Content-Type: text/x-rst

===================
django-prose-editor
===================

Prose editor for the Django admin based on ProseMirror and Tiptap. `Announcement blog post <https://406.ch/writing/django-prose-editor-prose-editing-component-for-the-django-admin/>`__.


About rich text editors
=======================

Copied from the `django-content-editor documentation <https://django-content-editor.readthedocs.io/en/latest/>`__.

We have been struggling with rich text editors for a long time. To be honest, I do not think it was a good idea to add that many features to the rich text editor. Resizing images uploaded into a rich text editor is a real pain, and what if you’d like to reuse these images or display them using a lightbox script or something similar? You have to resort to writing loads of JavaScript code which will only work on one browser. You cannot really filter the HTML code generated by the user to kick out ugly HTML code generated by copy-pasting from word. The user will upload 10mb JPEGs and resize them to 50x50 pixels in the rich text editor.

All of this convinced me that offering the user a rich text editor with too much capabilities is a really bad idea. The rich text editor in FeinCMS only has bold, italic, bullets, link and headlines activated (and the HTML code button, because that’s sort of inevitable – sometimes the rich text editor messes up and you cannot fix it other than going directly into the HTML code. Plus, if someone really knows what they are doing, I’d still like to give them the power to shot their own foot).

If this does not seem convincing you can always add your own rich text plugin with a different configuration (or just override the rich text editor initialization template in your own project). We do not want to force our world view on you, it’s just that we think that in this case, more choice has the bigger potential to hurt than to help.


Installation
============

The first step is to ensure that you have an activated virtualenv for your
current project, using something like ``. .venv/bin/activate``.

Install the package into your environment:

.. code-block:: shell

    pip install django-prose-editor

To include nh3 as optional (but recommended!) dependency for sanitized HTML,
install the extra "sanitize":

.. code-block:: shell

    pip install django-prose-editor[sanitize]

Add ``django_prose_editor`` to ``INSTALLED_APPS``:

.. code-block:: python

    INSTALLED_APPS = [
        # ...
        "django_prose_editor",
    ]

Add the importmap by adding the ``js_asset.context_processors.importmap``
context processor and inserting ``{{ importmap }}`` somewhere in your base
template, above all other scripts.

Replace ``models.TextField`` with ``ProseEditorField`` where appropriate:

.. code-block:: python

    from django_prose_editor.fields import ProseEditorField

    class Project(models.Model):
        description = ProseEditorField()

Note! No migrations will be generated when switching from and to
``models.TextField``. That's by design. Those migrations are mostly annoying.


Configuration
=============

ProseMirror does a really good job of only allowing content which conforms to a
particular scheme. Of course users can submit what they want, they are not
constrainted by the HTML widgets you're using. You should still always sanitize
the HTML submitted on the server side.

Using Configurable Sanitization (Recommended)
---------------------------------------------

The recommended approach is to use the extensions mechanism for configuring the
prose editor field which automatically synchronizes editor extensions with
sanitization rules:

.. code-block:: python

    from django_prose_editor.fields import ProseEditorField

    content = ProseEditorField(
        extensions={
            "Bold": True,
            "Italic": True,
            "BulletList": True,
            "Link": True,
        },
        # sanitize=True is the default when using extensions
    )

This ensures that the HTML sanitization rules exactly match what the editor
allows, preventing inconsistencies between editing capabilities and allowed
output. Note that you need the nh3 library for this which is automatically
installed when you specify the requirement as
``django-prose-editor[sanitize]``.

Old Approach
------------

For backward compatibility, you can still use the legacy
``SanitizedProseEditorField``, although this approach is now discouraged:

.. code-block:: python

    from django_prose_editor.sanitized import SanitizedProseEditorField

    description = SanitizedProseEditorField()

You should be aware that this uses the default configuration of the nh3
sanitizer which does prevent XSS but allows many HTML elements and attributes.

I'd be interested to know why the extensions mechanism isn't sufficient for
your requirements.

Convenience
===========

Sometimes it may be useful to show an excerpt of the HTML field; the
``ProseEditorField`` automatically adds a ``get_*_excerpt`` method to models
which returns the truncated and stripped beginning of your HTML field's
content. The name would be ``Project.get_description_excerpt`` in the example
above.


Customization
=============

The editor can be customized in several ways:

1. Using the new extensions mechanism with ``ProseEditorField`` (recommended).
   For the new configuration language, see the ``configuration_language.rst``
   documentation.
2. Using the ``config`` parameter to include/exclude specific extensions
   (legacy approach)
3. Creating custom presets for more advanced customization

Note that the ``ProseEditorField`` automatically uses the extension mechanism
except if you initialize it with the legacy ``config`` dictionary.


Simple customization with extensions
------------------------------------

.. code-block:: python

    from django_prose_editor.fields import ProseEditorField

    class Article(models.Model):
        content = ProseEditorField(
            extensions={
                "HardBreak": True,
                "Bold": True,
                "Italic": True,
                "BulletList": True,
                "OrderedList": True,
                "HorizontalRule": True,
                "Link": True,
                "Table": True,
                "History": True,
                "HTML": True,  # Enable HTML editing
                "Typographic": True,  # Highlight typographic characters
            },
            # sanitize=True,  # It's the default.
        )

Some extensions also support additional configuration, for example:

.. code-block:: python

    extensions={
        # ...
        "Link": {"enableTarget": False},  # Disable the 'open in new window' checkbox
        "Heading": {"levels": [1, 2, 3]},  # Offer a subset of H1-H6
        # ...
    }

Available extension types include:

* Text formatting: ``Bold``, ``Italic``, ``Strike``, ``Subscript``, ``Superscript``, ``Underline`` (all enabled by default)
* Lists: ``BulletList``, ``OrderedList``, ``ListItem`` (enabled by default)
* Structure: ``Blockquote``, ``Heading``, ``HorizontalRule`` (enabled by default)
* Links: ``Link`` (enabled by default)
* Tables: ``Table``, ``TableRow``, ``TableHeader``, ``TableCell`` (opt-in only, not enabled by default)


Simple Customization with Config (Legacy)
-----------------------------------------

For basic customization, you can use the ``config`` parameter to specify which
extensions should be enabled (this is the legacy approach):

.. code-block:: python

    from django_prose_editor.fields import ProseEditorField

    class Article(models.Model):
        content = ProseEditorField(
            config={
                "types": [
                    "Bold",
                    "Italic",
                    "Strike",
                    "BulletList",
                    "OrderedList",
                    "HorizontalRule",
                    "Link",
                    "Table",  # Enables full table support
                ],
                "history": True,
                "html": True,
                "typographic": True,
            }
        )

All extension names now use the Tiptap names (e.g., ``Bold``, ``Italic``,
``BulletList``, ``HorizontalRule``). For backward compatibility, the following legacy
ProseMirror-style names are still supported:

* Legacy node names: ``bullet_list`` → ``BulletList``, ``ordered_list`` →
  ``OrderedList``, ``horizontal_rule`` → ``HorizontalRule``
* Legacy mark names: ``strong`` → ``Bold``, ``em`` → ``Italic``,
  ``strikethrough`` → ``Strike``, ``sub`` → ``Subscript``, ``sup`` → ``Superscript``,
  ``link`` → ``Link``


Customization with JavaScript bundlers
======================================

If you're using a bundler such as esbuild, rspack or webpack you have to ensure
that the django-prose-editor JavaScript library is treated as an external. In
the case of rspack this means adding the following lines to your rspack
configuration:

.. code-block:: javascript

    module.exports = {
        // ...
        experiments: { outputModule: true },
        externals: {
            "django-prose-editor/editor": "module django-prose-editor/editor",
        },
    }

This makes rspack emit ES modules and preserves imports of
``django-prose-editor/editor`` in the output instead of trying to bundle the
library.


Usage outside the Django admin
==============================

The prose editor can easily be used outside the Django admin. The form field
respectively the widget includes the necessary CSS and JavaScript:

.. code-block:: python

    from django_prose_editor.fields import ProseEditorFormField

    class Form(forms.Form):
        text = ProseEditorFormField()

Or maybe you want to use ``django_prose_editor.widgets.ProseEditorWidget``, but
why make it more complicated than necessary.

If you're rendering the form in a template you have to include the form media:

.. code-block:: html+django

    <form method="post">
      {% csrf_token %}
      {{ form.media }}  {# This is the important line! #}

      {{ form.errors }} {# Always makes sense #}
      {{ form.as_div }}
      <button type="submit">send</button>
    </form>

Note that the form media isn't django-prose-editor specific, that's a Django
feature.

The django-prose-editor CSS uses the following CSS custom properties.

* ``--prose-editor-background``
* ``--prose-editor-foreground``
* ``--prose-editor-border-color``
* ``--prose-editor-active-color``
* ``--prose-editor-disabled-color``

If you do not set them, they get their value from the following properties that
are defined in the Django admin's CSS:

* ``--border-color``
* ``--body-fg``
* ``--body-bg``
* ``--primary``

You should set these properties with appropriate values to use
django-prose-editor outside the admin in your site.

In addition, you may optionally set a ``--prose-editor-typographic`` property
to control the color of typographic characters when shown.


Development
===========

For the best development experience:

1. Install django-prose-editor in editable mode in your project:

   .. code-block:: shell

       pip install -e /path/to/django-prose-editor

2. Run ``yarn && yarn dev`` in the django-prose-editor directory to watch for
   asset changes.

When using ``yarn dev``:

- The generated CSS and JavaScript is not minified, making it easier to debug.
- Source maps are generated to help identify exactly where in the source code
  an error occurs.
- The watcher will rebuild files automatically when you make changes.

Source maps are generated in development mode (``yarn dev``) for easier
debugging, but not included in production builds to keep the package size
manageable. The JavaScript in this project is quite extensive, so source maps
would significantly increase the distribution size.

The pre-commit configuration includes a hook that prevents committing files
with source map references, ensuring that development artifacts don't make it
into the repository.

Browser Testing with Playwright
-------------------------------

This project uses Playwright for browser-based testing of the prose editor.

To run the browser tests:

1. Install the dependencies:

   .. code-block:: shell

       pip install -e ".[tests]"
       playwright install

2. Run the tests using tox:

   .. code-block:: shell

       tox -e playwright

   Or directly with pytest:

   .. code-block:: shell

       pytest tests/testapp/test_prose_editor_e2e.py -v --browser chromium

Code Style and Linting
----------------------

This project uses pre-commit hooks to enforce coding style guidelines. We use
Ruff for Python linting and formatting, Biome for JavaScript/TypeScript linting
and formatting and a few other hooks.

To set up pre-commit using uv:

.. code-block:: shell

    uv tool install pre-commit
    pre-commit install

Pre-commit will automatically check your code for style issues when you commit
changes.
