Register

Version 10 TextEditor Changes

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;
}

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)});
});

This migration guide provides an overview of the migration steps related to the following issues: