We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
🎛️ Migrate `draftail.initEditor` inline script usage to leverage Stimulus `w-init` controller
> ℹ️ **Part of the [Stimulus 🎛️ RFC 78](https://github.com/wagtail/rfcs/pull/78)**

### Is your proposal related to a problem?

Our current approach for bootstrapping the Draftail editor (non-StreamField usage) relies on inline scripts.

https://github.com/wagtail/wagtail/blob/e03c41250776612468226597a077ba1fc2d9b865/wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html#L1

This approach is not CSP compliant and it would be good to find a better way to achieve the same goals.

### Describe the solution you'd like

We further enhance the existing Stimulus controller `InitController` that allows arbitrary events to be dispatches on the target element.

We will need a way to support arbitrary event `detail` being passed to the init events that are dispatched.

This would be very similar to the solution implemented for the comments initialisation, except that this may be initialised multiple times (so no need for `'once': true`). See https://github.com/wagtail/wagtail/issues/11287 & https://github.com/wagtail/wagtail/pull/11294

https://github.com/wagtail/wagtail/blob/e03c41250776612468226597a077ba1fc2d9b865/client/src/entrypoints/admin/comments.js#L341-L350

#### Part 1 - `InitController` add support for `detail` value

In the [`client/src/controllers/InitController.ts`](https://github.com/wagtail/wagtail/blob/main/client/src/controllers/InitController.ts) file.

* Add a new `detail` [Stimulus value](https://stimulus.hotwired.dev/reference/values) to the controller, this would be used with `data-w-init-detail-value='{"some": "json"}'`.
* Read out the detail value object in the `ready` method, something like `const detail = { ...(this.detailValue || {}) }; // Coerce to object`
* When dispatching both the `ready` event and the custom events, pass the `detail` object to those events.
* Add the removal of this attribute and other attributes in `remove` so that the initial data is not lying around in the DOM, example code below.
* Must have - unit tests added for this new functionality.
* Must have - update JSDoc for controller (including new example), methods and the `declare detailValue: Record<string, unknown>;` statement so that the code is well documented.
* Nice to have - add the ability for the `ready` event to have it's default prevented, returning early to stop the other events & removal.
* Nice to have - re-order the values to be in alpha order, in the `static values = {...` and the `declare` statements.

```ts
  remove() {
    const element = this.element;

    (this.constructor as typeof InitController).classes.forEach((key) => {
      element.removeAttribute(`data-${this.identifier}-${key}-class`);
    });

    Object.keys((this.constructor as typeof InitController).values).forEach(
      (key) => {
        element.removeAttribute(`data-${this.identifier}-${key}-value`);
      },
    );
   /// ... existing stuff 
```

#### Part 2 - Add support for an event listener to instantiate Draftail

In [`client/src/entrypoints/admin/draftail.js`](https://github.com/wagtail/wagtail/blob/main/client/src/entrypoints/admin/draftail.js) we will want to add the event listener to wrap the `initDraftail` function call.

Something like this.

```js
/**
 * Initialize a Draftail editor on a given element when the w-draftail:init event is fired.
 */
document.addEventListener('w-draftail:init', ({ detail = {}, target }) => {
  const id = target.id;
  if (!id) {
    throw new Error('w-draftail:init event must have a target with an id');
  }
  window.draftail.initEditor(`#${id}`, detail, document.currentScript);
});
```

#### Part 3 - Update the Django widget to use the new attributes

See [`wagtail/admin/rich_text/editors/draftail/__init__.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/rich_text/editors/draftail/__init__.py) and [`wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html`](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html).

* Update the template to ONLY render the hidden field, `{% include 'django/forms/widgets/hidden.html' %}` and remove the `<script...` tags. We want to keep this template just in case other developers have customised this template for their own code.
* We will want to update the approach in this Widget to set the Stimulus data attributes and replace the way we are passing options, instead we will pass this as `w-init-detail-value`.
* Python unit tests may need to be updated to reflect the way this new behaviour works.

```
class DraftailRichTextArea(widgets.HiddenInput):
    template_name = "wagtailadmin/widgets/draftail_rich_text_area.html"
    # ...

    def __init__(self, *args, **kwargs):
        # ...
        default_attrs = {
            "data-draftail-input": True,
            # add the Stimulus attributes to the `default_attrs`
            "data-controller": "w-init",
            "data-w-init-event-value": "w-draftail:init",
        }
        attrs = kwargs.get("attrs")
        # ...

    # ... other methods

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context["widget"]["attrs"]["data-w-init-detail-value"] = json.dumps(self.options) # update this line
        return context
```


### Describe alternatives you've considered

* We already have a `data-draftail-input` and may want to leverage that, or maybe change that, but this would require building out a new mutation observer OR changing the documentation. It's probably best to just leave this attribute usage as is and avoid the added code churn. We can revisit this later if needed.
* Maybe a discrete controller specific to Draftail, however, the init controller is so close to doing exactly what we need it makes sense to add one additional value to this instead.

### Additional context

* As noted above, this is very similar to the approach in See https://github.com/wagtail/wagtail/issues/11287 & https://github.com/wagtail/wagtail/pull/11294
* This issue is similar in goals to https://github.com/wagtail/wagtail/issues/11597 but taking an intentionally different approach to just leverage an existing controller.

### Working on this

* Anyone can contribute to this if they are able to work with Stimulus and create Jest tests, plus feel confident in modifying Python tests.
* View our [contributing guidelines](https://docs.wagtail.org/en/latest/contributing/index.html), add a comment to the issue once you’re ready to start.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/admin/rich_text/editors/draftail/__init__.py`
Content:
```
1 import json
2 import warnings
3 
4 from django.forms import Media, widgets
5 from django.urls import reverse_lazy
6 from django.utils.functional import cached_property
7 from django.utils.translation import gettext_lazy
8 
9 from wagtail.admin.rich_text.converters.contentstate import ContentstateConverter
10 from wagtail.admin.staticfiles import versioned_static
11 from wagtail.rich_text import features as feature_registry
12 from wagtail.telepath import register
13 from wagtail.widget_adapters import WidgetAdapter
14 
15 
16 class LazyStringEncoder(json.JSONEncoder):
17     """
18     Add support for lazy strings to the JSON encoder so that URLs and
19     translations can be resolved when rendering the widget only.
20     """
21 
22     lazy_string_types = [type(reverse_lazy("")), type(gettext_lazy(""))]
23 
24     def default(self, obj):
25         if type(obj) in self.lazy_string_types:
26             return str(obj)
27 
28         return json.JSONEncoder.default(self, obj)
29 
30 
31 class DraftailRichTextArea(widgets.HiddenInput):
32     template_name = "wagtailadmin/widgets/draftail_rich_text_area.html"
33     is_hidden = False
34 
35     # this class's constructor accepts a 'features' kwarg
36     accepts_features = True
37 
38     # Draftail has its own commenting
39     show_add_comment_button = False
40 
41     def __init__(self, *args, **kwargs):
42         # note: this constructor will receive an 'options' kwarg taken from the WAGTAILADMIN_RICH_TEXT_EDITORS setting,
43         # but we don't currently recognise any options from there (other than 'features', which is passed here as a separate kwarg)
44         kwargs.pop("options", None)
45         self.options = {}
46         self.plugins = []
47 
48         self.features = kwargs.pop("features", None)
49         if self.features is None:
50             self.features = feature_registry.get_default_features()
51 
52         for feature in self.features:
53             plugin = feature_registry.get_editor_plugin("draftail", feature)
54             if plugin is None:
55                 warnings.warn(
56                     f"Draftail received an unknown feature '{feature}'.",
57                     category=RuntimeWarning,
58                 )
59             else:
60                 plugin.construct_options(self.options)
61                 self.plugins.append(plugin)
62 
63         self.converter = ContentstateConverter(self.features)
64 
65         default_attrs = {"data-draftail-input": True}
66         attrs = kwargs.get("attrs")
67         if attrs:
68             default_attrs.update(attrs)
69         kwargs["attrs"] = default_attrs
70 
71         super().__init__(*args, **kwargs)
72 
73     def format_value(self, value):
74         # Convert database rich text representation to the format required by
75         # the input field
76         value = super().format_value(value)
77 
78         if value is None:
79             value = ""
80 
81         return self.converter.from_database_format(value)
82 
83     def get_context(self, name, value, attrs):
84         context = super().get_context(name, value, attrs)
85         context["widget"]["options_json"] = json.dumps(
86             self.options, cls=LazyStringEncoder
87         )
88         return context
89 
90     def value_from_datadict(self, data, files, name):
91         original_value = super().value_from_datadict(data, files, name)
92         if original_value is None:
93             return None
94         return self.converter.to_database_format(original_value)
95 
96     @cached_property
97     def media(self):
98         media = Media(
99             js=[
100                 versioned_static("wagtailadmin/js/draftail.js"),
101             ],
102             css={"all": [versioned_static("wagtailadmin/css/panels/draftail.css")]},
103         )
104 
105         for plugin in self.plugins:
106             media += plugin.media
107 
108         return media
109 
110 
111 class DraftailRichTextAreaAdapter(WidgetAdapter):
112     js_constructor = "wagtail.widgets.DraftailRichTextArea"
113 
114     def js_args(self, widget):
115         return [
116             widget.options,
117         ]
118 
119 
120 register(DraftailRichTextAreaAdapter(), DraftailRichTextArea)
121
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wagtail/admin/rich_text/editors/draftail/__init__.py b/wagtail/admin/rich_text/editors/draftail/__init__.py
--- a/wagtail/admin/rich_text/editors/draftail/__init__.py
+++ b/wagtail/admin/rich_text/editors/draftail/__init__.py
@@ -62,7 +62,11 @@
 
         self.converter = ContentstateConverter(self.features)
 
-        default_attrs = {"data-draftail-input": True}
+        default_attrs = {
+            "data-draftail-input": True,
+            "data-controller": "w-init",
+            "data-w-init-event-value": "w-draftail:init",
+        }
         attrs = kwargs.get("attrs")
         if attrs:
             default_attrs.update(attrs)
@@ -82,8 +86,9 @@
 
     def get_context(self, name, value, attrs):
         context = super().get_context(name, value, attrs)
-        context["widget"]["options_json"] = json.dumps(
-            self.options, cls=LazyStringEncoder
+        context["widget"]["attrs"]["data-w-init-detail-value"] = json.dumps(
+            self.options,
+            cls=LazyStringEncoder,
         )
         return context

{"golden_diff": "diff --git a/wagtail/admin/rich_text/editors/draftail/__init__.py b/wagtail/admin/rich_text/editors/draftail/__init__.py\n--- a/wagtail/admin/rich_text/editors/draftail/__init__.py\n+++ b/wagtail/admin/rich_text/editors/draftail/__init__.py\n@@ -62,7 +62,11 @@\n \n         self.converter = ContentstateConverter(self.features)\n \n-        default_attrs = {\"data-draftail-input\": True}\n+        default_attrs = {\n+            \"data-draftail-input\": True,\n+            \"data-controller\": \"w-init\",\n+            \"data-w-init-event-value\": \"w-draftail:init\",\n+        }\n         attrs = kwargs.get(\"attrs\")\n         if attrs:\n             default_attrs.update(attrs)\n@@ -82,8 +86,9 @@\n \n     def get_context(self, name, value, attrs):\n         context = super().get_context(name, value, attrs)\n-        context[\"widget\"][\"options_json\"] = json.dumps(\n-            self.options, cls=LazyStringEncoder\n+        context[\"widget\"][\"attrs\"][\"data-w-init-detail-value\"] = json.dumps(\n+            self.options,\n+            cls=LazyStringEncoder,\n         )\n         return context\n", "issue": "\ud83c\udf9b\ufe0f Migrate `draftail.initEditor` inline script usage to leverage Stimulus `w-init` controller\n> \u2139\ufe0f **Part of the [Stimulus \ud83c\udf9b\ufe0f RFC 78](https://github.com/wagtail/rfcs/pull/78)**\r\n\r\n### Is your proposal related to a problem?\r\n\r\nOur current approach for bootstrapping the Draftail editor (non-StreamField usage) relies on inline scripts.\r\n\r\nhttps://github.com/wagtail/wagtail/blob/e03c41250776612468226597a077ba1fc2d9b865/wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html#L1\r\n\r\nThis approach is not CSP compliant and it would be good to find a better way to achieve the same goals.\r\n\r\n### Describe the solution you'd like\r\n\r\nWe further enhance the existing Stimulus controller `InitController` that allows arbitrary events to be dispatches on the target element.\r\n\r\nWe will need a way to support arbitrary event `detail` being passed to the init events that are dispatched.\r\n\r\nThis would be very similar to the solution implemented for the comments initialisation, except that this may be initialised multiple times (so no need for `'once': true`). See https://github.com/wagtail/wagtail/issues/11287 & https://github.com/wagtail/wagtail/pull/11294\r\n\r\nhttps://github.com/wagtail/wagtail/blob/e03c41250776612468226597a077ba1fc2d9b865/client/src/entrypoints/admin/comments.js#L341-L350\r\n\r\n#### Part 1 - `InitController` add support for `detail` value\r\n\r\nIn the [`client/src/controllers/InitController.ts`](https://github.com/wagtail/wagtail/blob/main/client/src/controllers/InitController.ts) file.\r\n\r\n* Add a new `detail` [Stimulus value](https://stimulus.hotwired.dev/reference/values) to the controller, this would be used with `data-w-init-detail-value='{\"some\": \"json\"}'`.\r\n* Read out the detail value object in the `ready` method, something like `const detail = { ...(this.detailValue || {}) }; // Coerce to object`\r\n* When dispatching both the `ready` event and the custom events, pass the `detail` object to those events.\r\n* Add the removal of this attribute and other attributes in `remove` so that the initial data is not lying around in the DOM, example code below.\r\n* Must have - unit tests added for this new functionality.\r\n* Must have - update JSDoc for controller (including new example), methods and the `declare detailValue: Record<string, unknown>;` statement so that the code is well documented.\r\n* Nice to have - add the ability for the `ready` event to have it's default prevented, returning early to stop the other events & removal.\r\n* Nice to have - re-order the values to be in alpha order, in the `static values = {...` and the `declare` statements.\r\n\r\n```ts\r\n  remove() {\r\n    const element = this.element;\r\n\r\n    (this.constructor as typeof InitController).classes.forEach((key) => {\r\n      element.removeAttribute(`data-${this.identifier}-${key}-class`);\r\n    });\r\n\r\n    Object.keys((this.constructor as typeof InitController).values).forEach(\r\n      (key) => {\r\n        element.removeAttribute(`data-${this.identifier}-${key}-value`);\r\n      },\r\n    );\r\n   /// ... existing stuff \r\n```\r\n\r\n#### Part 2 - Add support for an event listener to instantiate Draftail\r\n\r\nIn [`client/src/entrypoints/admin/draftail.js`](https://github.com/wagtail/wagtail/blob/main/client/src/entrypoints/admin/draftail.js) we will want to add the event listener to wrap the `initDraftail` function call.\r\n\r\nSomething like this.\r\n\r\n```js\r\n/**\r\n * Initialize a Draftail editor on a given element when the w-draftail:init event is fired.\r\n */\r\ndocument.addEventListener('w-draftail:init', ({ detail = {}, target }) => {\r\n  const id = target.id;\r\n  if (!id) {\r\n    throw new Error('w-draftail:init event must have a target with an id');\r\n  }\r\n  window.draftail.initEditor(`#${id}`, detail, document.currentScript);\r\n});\r\n```\r\n\r\n#### Part 3 - Update the Django widget to use the new attributes\r\n\r\nSee [`wagtail/admin/rich_text/editors/draftail/__init__.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/rich_text/editors/draftail/__init__.py) and [`wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html`](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/templates/wagtailadmin/widgets/draftail_rich_text_area.html).\r\n\r\n* Update the template to ONLY render the hidden field, `{% include 'django/forms/widgets/hidden.html' %}` and remove the `<script...` tags. We want to keep this template just in case other developers have customised this template for their own code.\r\n* We will want to update the approach in this Widget to set the Stimulus data attributes and replace the way we are passing options, instead we will pass this as `w-init-detail-value`.\r\n* Python unit tests may need to be updated to reflect the way this new behaviour works.\r\n\r\n```\r\nclass DraftailRichTextArea(widgets.HiddenInput):\r\n    template_name = \"wagtailadmin/widgets/draftail_rich_text_area.html\"\r\n    # ...\r\n\r\n    def __init__(self, *args, **kwargs):\r\n        # ...\r\n        default_attrs = {\r\n            \"data-draftail-input\": True,\r\n            # add the Stimulus attributes to the `default_attrs`\r\n            \"data-controller\": \"w-init\",\r\n            \"data-w-init-event-value\": \"w-draftail:init\",\r\n        }\r\n        attrs = kwargs.get(\"attrs\")\r\n        # ...\r\n\r\n    # ... other methods\r\n\r\n    def get_context(self, name, value, attrs):\r\n        context = super().get_context(name, value, attrs)\r\n        context[\"widget\"][\"attrs\"][\"data-w-init-detail-value\"] = json.dumps(self.options) # update this line\r\n        return context\r\n```\r\n\r\n\r\n### Describe alternatives you've considered\r\n\r\n* We already have a `data-draftail-input` and may want to leverage that, or maybe change that, but this would require building out a new mutation observer OR changing the documentation. It's probably best to just leave this attribute usage as is and avoid the added code churn. We can revisit this later if needed.\r\n* Maybe a discrete controller specific to Draftail, however, the init controller is so close to doing exactly what we need it makes sense to add one additional value to this instead.\r\n\r\n### Additional context\r\n\r\n* As noted above, this is very similar to the approach in See https://github.com/wagtail/wagtail/issues/11287 & https://github.com/wagtail/wagtail/pull/11294\r\n* This issue is similar in goals to https://github.com/wagtail/wagtail/issues/11597 but taking an intentionally different approach to just leverage an existing controller.\r\n\r\n### Working on this\r\n\r\n* Anyone can contribute to this if they are able to work with Stimulus and create Jest tests, plus feel confident in modifying Python tests.\r\n* View our [contributing guidelines](https://docs.wagtail.org/en/latest/contributing/index.html), add a comment to the issue once you\u2019re ready to start.\r\n\n", "before_files": [{"content": "import json\nimport warnings\n\nfrom django.forms import Media, widgets\nfrom django.urls import reverse_lazy\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy\n\nfrom wagtail.admin.rich_text.converters.contentstate import ContentstateConverter\nfrom wagtail.admin.staticfiles import versioned_static\nfrom wagtail.rich_text import features as feature_registry\nfrom wagtail.telepath import register\nfrom wagtail.widget_adapters import WidgetAdapter\n\n\nclass LazyStringEncoder(json.JSONEncoder):\n    \"\"\"\n    Add support for lazy strings to the JSON encoder so that URLs and\n    translations can be resolved when rendering the widget only.\n    \"\"\"\n\n    lazy_string_types = [type(reverse_lazy(\"\")), type(gettext_lazy(\"\"))]\n\n    def default(self, obj):\n        if type(obj) in self.lazy_string_types:\n            return str(obj)\n\n        return json.JSONEncoder.default(self, obj)\n\n\nclass DraftailRichTextArea(widgets.HiddenInput):\n    template_name = \"wagtailadmin/widgets/draftail_rich_text_area.html\"\n    is_hidden = False\n\n    # this class's constructor accepts a 'features' kwarg\n    accepts_features = True\n\n    # Draftail has its own commenting\n    show_add_comment_button = False\n\n    def __init__(self, *args, **kwargs):\n        # note: this constructor will receive an 'options' kwarg taken from the WAGTAILADMIN_RICH_TEXT_EDITORS setting,\n        # but we don't currently recognise any options from there (other than 'features', which is passed here as a separate kwarg)\n        kwargs.pop(\"options\", None)\n        self.options = {}\n        self.plugins = []\n\n        self.features = kwargs.pop(\"features\", None)\n        if self.features is None:\n            self.features = feature_registry.get_default_features()\n\n        for feature in self.features:\n            plugin = feature_registry.get_editor_plugin(\"draftail\", feature)\n            if plugin is None:\n                warnings.warn(\n                    f\"Draftail received an unknown feature '{feature}'.\",\n                    category=RuntimeWarning,\n                )\n            else:\n                plugin.construct_options(self.options)\n                self.plugins.append(plugin)\n\n        self.converter = ContentstateConverter(self.features)\n\n        default_attrs = {\"data-draftail-input\": True}\n        attrs = kwargs.get(\"attrs\")\n        if attrs:\n            default_attrs.update(attrs)\n        kwargs[\"attrs\"] = default_attrs\n\n        super().__init__(*args, **kwargs)\n\n    def format_value(self, value):\n        # Convert database rich text representation to the format required by\n        # the input field\n        value = super().format_value(value)\n\n        if value is None:\n            value = \"\"\n\n        return self.converter.from_database_format(value)\n\n    def get_context(self, name, value, attrs):\n        context = super().get_context(name, value, attrs)\n        context[\"widget\"][\"options_json\"] = json.dumps(\n            self.options, cls=LazyStringEncoder\n        )\n        return context\n\n    def value_from_datadict(self, data, files, name):\n        original_value = super().value_from_datadict(data, files, name)\n        if original_value is None:\n            return None\n        return self.converter.to_database_format(original_value)\n\n    @cached_property\n    def media(self):\n        media = Media(\n            js=[\n                versioned_static(\"wagtailadmin/js/draftail.js\"),\n            ],\n            css={\"all\": [versioned_static(\"wagtailadmin/css/panels/draftail.css\")]},\n        )\n\n        for plugin in self.plugins:\n            media += plugin.media\n\n        return media\n\n\nclass DraftailRichTextAreaAdapter(WidgetAdapter):\n    js_constructor = \"wagtail.widgets.DraftailRichTextArea\"\n\n    def js_args(self, widget):\n        return [\n            widget.options,\n        ]\n\n\nregister(DraftailRichTextAreaAdapter(), DraftailRichTextArea)\n", "path": "wagtail/admin/rich_text/editors/draftail/__init__.py"}], "after_files": [{"content": "import json\nimport warnings\n\nfrom django.forms import Media, widgets\nfrom django.urls import reverse_lazy\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy\n\nfrom wagtail.admin.rich_text.converters.contentstate import ContentstateConverter\nfrom wagtail.admin.staticfiles import versioned_static\nfrom wagtail.rich_text import features as feature_registry\nfrom wagtail.telepath import register\nfrom wagtail.widget_adapters import WidgetAdapter\n\n\nclass LazyStringEncoder(json.JSONEncoder):\n    \"\"\"\n    Add support for lazy strings to the JSON encoder so that URLs and\n    translations can be resolved when rendering the widget only.\n    \"\"\"\n\n    lazy_string_types = [type(reverse_lazy(\"\")), type(gettext_lazy(\"\"))]\n\n    def default(self, obj):\n        if type(obj) in self.lazy_string_types:\n            return str(obj)\n\n        return json.JSONEncoder.default(self, obj)\n\n\nclass DraftailRichTextArea(widgets.HiddenInput):\n    template_name = \"wagtailadmin/widgets/draftail_rich_text_area.html\"\n    is_hidden = False\n\n    # this class's constructor accepts a 'features' kwarg\n    accepts_features = True\n\n    # Draftail has its own commenting\n    show_add_comment_button = False\n\n    def __init__(self, *args, **kwargs):\n        # note: this constructor will receive an 'options' kwarg taken from the WAGTAILADMIN_RICH_TEXT_EDITORS setting,\n        # but we don't currently recognise any options from there (other than 'features', which is passed here as a separate kwarg)\n        kwargs.pop(\"options\", None)\n        self.options = {}\n        self.plugins = []\n\n        self.features = kwargs.pop(\"features\", None)\n        if self.features is None:\n            self.features = feature_registry.get_default_features()\n\n        for feature in self.features:\n            plugin = feature_registry.get_editor_plugin(\"draftail\", feature)\n            if plugin is None:\n                warnings.warn(\n                    f\"Draftail received an unknown feature '{feature}'.\",\n                    category=RuntimeWarning,\n                )\n            else:\n                plugin.construct_options(self.options)\n                self.plugins.append(plugin)\n\n        self.converter = ContentstateConverter(self.features)\n\n        default_attrs = {\n            \"data-draftail-input\": True,\n            \"data-controller\": \"w-init\",\n            \"data-w-init-event-value\": \"w-draftail:init\",\n        }\n        attrs = kwargs.get(\"attrs\")\n        if attrs:\n            default_attrs.update(attrs)\n        kwargs[\"attrs\"] = default_attrs\n\n        super().__init__(*args, **kwargs)\n\n    def format_value(self, value):\n        # Convert database rich text representation to the format required by\n        # the input field\n        value = super().format_value(value)\n\n        if value is None:\n            value = \"\"\n\n        return self.converter.from_database_format(value)\n\n    def get_context(self, name, value, attrs):\n        context = super().get_context(name, value, attrs)\n        context[\"widget\"][\"attrs\"][\"data-w-init-detail-value\"] = json.dumps(\n            self.options,\n            cls=LazyStringEncoder,\n        )\n        return context\n\n    def value_from_datadict(self, data, files, name):\n        original_value = super().value_from_datadict(data, files, name)\n        if original_value is None:\n            return None\n        return self.converter.to_database_format(original_value)\n\n    @cached_property\n    def media(self):\n        media = Media(\n            js=[\n                versioned_static(\"wagtailadmin/js/draftail.js\"),\n            ],\n            css={\"all\": [versioned_static(\"wagtailadmin/css/panels/draftail.css\")]},\n        )\n\n        for plugin in self.plugins:\n            media += plugin.media\n\n        return media\n\n\nclass DraftailRichTextAreaAdapter(WidgetAdapter):\n    js_constructor = \"wagtail.widgets.DraftailRichTextArea\"\n\n    def js_args(self, widget):\n        return [\n            widget.options,\n        ]\n\n\nregister(DraftailRichTextAreaAdapter(), DraftailRichTextArea)\n", "path": "wagtail/admin/rich_text/editors/draftail/__init__.py"}]}