The TextEditor
singleton has undergone some changes to support the addition of several new features:
Ansynchronous Enrichment
TextEditor.enrichHTML
now supports an async
parameter to control how it evaluates content links and inline rolls. In v10, this parameter defaults to false
, matching the v9 behaviour. Starting with v11, the default will become true
, but it will still be possible for callers to supply false
if necessary. In v12 the parameter will be removed and enrichHTML
will always evaluate asynchronously.
A corollary to this change is that the editor
Handlebars helper has also been modified to expect pre-enriched content. It will continue to behave as before, accepting and enriching content provided via the legacy invocation until the deprecation period has passed in v12.
Version 9 (Before)
{{editor
content=data.details.biography.value
target="data.details.biography.value"
button=true
owner=owner
editable=editable
rollData=rollData
}}
Version 10 (After)
{{editor
enrichedBiography
target="system.details.biography.value"
button=true
editable=editable
}}
Systems that were previously relying on the editor
helper to enrich content for their sheets should move that operation to the getData
method which is able to execute asynchronously.
async getData(options) {
const data = await super.getData(options);
data.enrichedBiography = await TextEditor.enrichHTML(this.object.system.details.biography.value, {async: true});
return data;
}
UUID Content Links
The content linking system pre-dates the addition of Document UUIDs. In order to improve standardization around Document UUIDs, a new content link format has been implemented.
Version 9 (Before)
@Actor[xCr2shRoWsl76mV6]{Name}
@Compendium[dnd5e.heroes.kfzBL0q1Y7LgGs2x]{Name}
Version 10 (After)
@UUID[Actor.xCr2shRoWsl76mV6]{Name}
@UUID[Compendium.dnd5e.heroes.kfzBL0q1Y7LgGs2x]{Name}
Legacy content links will continue to be supported though the core API will no longer produce them. The new content link format supports all Document types and relative UUIDs, unlike the legacy format. When being enriched in an asynchronous context, the new content link format is also able to link to Embedded Documents belonging to a Document inside a Compendium, e.g.:
@UUID[Compendium.dnd5e.heroes.kfzBL0q1Y7LgGs2x.Item.3Y12TEquZ0Vahjp4]{Item}
Drag Data
Related to the standardization of UUIDs, the core API now always produces consistent DragData
in any context where a Document is being dragged, instead of a custom format for each operation.
// A Document with a UUID.
{
type: "Actor",
uuid: "Actor.xCr2shRoWsl76mV6"
}
// A Document without a UUID (either temporary, or a cloned and modified Document)
{
type: "Actor",
data: {
name: "Name",
type: "character",
// ...
}
}
To align with this standard, systems and modules should produce DragData
that conforms to the above structure. Calling Document#toDragData
will handle this for you.
DragData
received by the core API will be passed to ClientDocumentMixin.fromDropData
. This method will prioritize the data
field if it exists, and construct a Document from it, otherwise it will retrieve the Document from the uuid
field. The importWorld
option has been deprecated—Documents dropped from a Compendium that need to be imported into the world should be created with the normal Document creation API.
ProseMirror Editor
A new rich text editor based on the ProseMirror framework with collaborative editing and auto-save features is available as an alternative to the existing TinyMCE editor. The ProseMirror editor is still being actively developed and does not yet offer feature parity with TinyMCE at time of writing.
Users wishing to use this new editor for editing journal pages may do so by selecting the new 'ProseMirror' sheet. Systems and modules who wish to use this editor in their own sheets can do so by overriding the FormApplication#activateEditor
method and passing the new engine
parameter to TextEditor.create
.
async activateEditor(name, options={}, initialContent="") {
const editor = this.editors[name];
options.engine = "prosemirror";
options.menuPlugin = ProseMirror.ProseMirrorMenu.build(ProseMirror.defaultSchema, {sheet: this});
// The following options are required to enable collaborative editing.
options.document = this;
options.fieldName = name;
options.collaborate = true;
return editor.prosemirror = await TextEditor.create(options, initialContent);
}
The ProseMirror API
Outside of a FormApplication
context, the ProseMirrorEditor class is available to create standalone ProseMirror editor instances.
ProseMirrorEditor.create(
target, // The HTML element that the editor will be mounted to.
content, // The initial editor content as a string.
{
plugins, // Plugins to include with the editor.
menuPlugin, // A reference to the menu plugin.
// The following are required for a collaborative editor.
document, // The Document whose content is being edited.
fieldName, // The field within the Document that is being edited.
collaborate: true
}
);
Some of the default ProseMirror plugins supplied by the core API expect a particular HTML structure for the editor to be injected into. Developers wishing to leverage these default plugins will need to ensure the target
element they provide conforms to the following:
<div class="editor">
<div class="editor-content">
<!-- Target the 'editor-content' element -->
</div>
</div>
Hook
The createProseMirrorEditor
hook is fired whenever a ProseMirror editor is created.
Hooks.on("createProseMirrorEditor", (uuid, plugins, options) => {
// The UUID uniquely identifies this ProseMirror editor and is used as part of the collaborative editing
// infrastructure.
// You may append additional plugins to the editor before it is created:
plugins.myPlugin = MyProseMirrorPlugin.build();
// Or swap out a default plugin with your own plugin or one with different options.
plugins.menu = ProseMirror.ProseMirrorMenu.build(mySchema);
// You may swap out the editor state with different content or a ProseMirror document that uses a different schema.
const parser = ProseMirror.DOMParser.fromSchema(mySchema);
options.state = ProseMirror.EditorState.create({doc: parser.parse(content)});
});
Related Issues
This migration guide provides an overview of the migration steps related to the following issues: