Skip to content

Migration Guide: Super Editor = 0.2.x to Super Editor 0.3.0

Matt Carroll edited this page May 13, 2023 · 3 revisions

super_editor v0.3.0 introduces major breaking changes so that the logical editor forces all changes through a single pipeline, allowing for undo/redo, tagging, content conversions, and more.

The following describes how to migrate from super_editor v0.2.x to v0.3.0.

From DocumentEditor to Editor

At a high level, any place where a DocumentEditor was used in v0.2.x, a new Editor object should be used in v0.3.0.

Configuring a SuperEditor

In v0.2.x a SuperEditor only required a DocumentEditor:

void initState() {
  super.initState();
  _myDocument = createDocument();
  _myEditor = DocumentEditor(document: _myDocument);
}

Widget build(BuildContext context) {
  return SuperEditor(
    editor: _myEditor,
  );
}

In v0.3.0 a SuperEditor requires an Editor, a Document, and a DocumentComposer.

Creating a new Editor requires a MutableDocument, and a MutableDocumentComposer.

The Editor has a lot of possible configuration for the typical use-case. Therefore, a top-level method is made available to create a typical Editor.

void initState() {
  super.initState();
  _myDocument = createDocument();
  _myComposer = MutableDocumentComposer();
  _myEditor = createDefaultDocumentEditor(document: _myDocument, composer: _myComposer);
}

Widget build(BuildContext context) {
  return SuperEditor(
    editor: _myEditor,
    document: _myDocument,
    composer: _myComposer,
  );
}

EditorCommands to EditRequests and EditCommands

The concept of editor commands existed before v0.3.0, but in v0.2.x those commands could mutate anything at any time. The primary change in v0.3.0 is that all changes need to happen within a single pipeline.

A command in v0.2.x would look something like:

class MyCommand implements EditorCommand {
  MyCommand({
    // args
  });

  // properties

  @override
  void execute(Document document, DocumentEditorTransaction transaction) {
    // command behavior here
  }
}

Also, some commands in v0.2.x might be implemented in a functional way like this:

editor.executeCommand(
  EditorCommandFunction(
    (doc, transaction) {
      // command behavior here
    },
  ),
);

In v0.3.0, a change should typically include an EditRequest and an EditCommand. There's no longer any support for an functional command.

A request/command pair in v0.3.0 should look something like this:

class MyRequest implements EditRequest {
  MyRequest({
    // args
  });

  // properties
}

class MyCommand implements EditCommand {
  MyCommand({
    // args
  });

  // properties

  @override
  void execute(EditContext context, CommandExecutor executor) {
    // command behavior here
  }
}

Implementing EditRequests and EditCommands

In most situations, an editor behavior should include both an EditRequest and an EditCommand.

An EditRequest represents a desire for a change. An Editor maps a given EditRequest to some EditCommand.

class MyRequest implements EditRequest {
  const MyRequest({
    // args
  });

  // properties
}

An EditCommand mutates things to cause a desired change.

class MyCommand implements EditCommand {
  const MyCommand({
    // args
  });

  // properties

  @override
  void execute(EditContext context, CommandExecutor executor) {
    // Most commands will require access to the mutable version of the Document, and the
    // mutable version of the DocumentComposer.
    //
    // You can add your own `Editable`s to the `Editor`'s `EditContext` and access them
    // just like this.
    final document = context.find<MutableDocument>(Editor.documentKey);
    final composer = context.find<MutableComposer>(Editor.composerKey);

    // Mutate the document however you want.

    // Mutate the composer however you want.

    // After making desired changes, report those changes so that reactions and listeners
    // can see what changed.
    executor.logChanges([
      DocumentEdit(
        NodeChangeEvent(someNodeId),
      ),
      SelectionChangeEvent(...),
    ]);

    // You can also run other commands from this command, to re-use atomic behavior.
    executor.executeCommand(
      MyOtherCommand(...),
    );
  }
}

Map EditRequests to EditCommands in the Editor constructor:

Editor(
  requestHandlers: [
    // ...
    (request) => request is MyRequest
      ? MyCommand(
          request.thing1,
          request.thing2,
        )
      : null,
    // ...
  ],
);