[whatwg] Fixing undo on the Web - UndoManager and Transaction

On Tue, Jul 26, 2011 at 11:34 PM, Ryosuke Niwa <rniwa at webkit.org> wrote:
> Hi all,
> In the last couple of weeks, I've been working with developers of CKEditor,
> TinyMCE, and Google Docs to come up with new API for undo and redo.
> Why? Because undo and redo are broken on the Web today. Whenever Web apps
> try to add a custom editing operation without using execCommand or do a "fix
> up" after browser executes a user editing action, user agents get confused
> by DOM mutations made by the apps and won't be able to undo or redo user
> editing actions and execCommand. This forces Web apps to re-implement undo
> and redo themselves, and in fact, many rich text editors store innerHTML of
> a contenteditable element as a string in their internal undo transaction
> history (a.k.a undo stack).
> Also, there's no way for Web apps to add new undo item and populate undo and
> redo menus on user agent's native UI. ?In addition, if an editor app has a
> widget with input/textarea, then the undo stack of the editor gets confused
> when the widget goes away because the undo transaction history exists only
> per document.
> In order to solve above and numerous other problems, we've come to a
> conclusion that we need to?add UndoManager and Transaction.
> UndoManager is an interface for managing undo transaction history. ?It
> exists on a document and an element with the undoscope content attribute.
> ?UndoManager applies new transaction (i.e. make undoable DOM changes) and
> manage them. ?The main purpose of UndoManager is to communicate the list of
> undoable items with the user agent so that the user agent can provide a
> native UI (e.g. populating menu items with them).
> A transaction is a collection of DOM mutations that can be applied,
> unapplied, or reapplied. UndoManager manages transactions and execute
> unapply and reapply upon undo and redo respectively.
> There are two types of DOM transactions:
>
> Managed transaction - the app supplies apply() and the user agent
> automatically takes care of undo and redo. It is compatible with user
> editing actions and editing commands, and allows Web apps to easily add new
> editing operations or fix up DOM after user editing actions or editing
> commands and still have the user agent manage the undo and redo.
> Manual transaction - the app supplies apply(), unapply(), and reapply()
> and?the app takes the full control of undo and redo. However, it is NOT
> compatible with user editing actions, editing commands, or managed
> transactions, meaning that, the user agents won't be able to undo or redo
> them. This transaction is useful for apps such as a?collaborative editor
> that implements domain-specific undo or redo.
>
> You can see more concrete definitions of UndoManager and Transaction at:
> https://rniwa.com/editing/undomanager.html and see a list of uses cases
> at?https://rniwa.com/editing/undomanager-usecases.html. ?The documents are
> incomplete and I need your feedback in order to refine details.

This is awesome and much more thought through than my proposal!

I really do like the undoscope attribute, that is something that is
definitely needed. But I think it should simply be a boolean
attribute, a'la disabled for example. It does seem undefined how it
interacts with the contenteditable attribute? I was thinking that
contenteditable would create a undoscope, unless there's an element
higher up in the tree which has an explicit undoscope attribute.

It does however seem to me that the proposal is unnecessarily complex
in a few areas:

Why is there a need for a 'reapply' action? How is it different from
the 'apply' action?

What is the purpose of the UndoManager.replace function and it's
replaceGroup argument? In general I'm not sure I fully understand
"transaction groups". Are they different from simply a set of
transactions which have been merged together such that they are
done/undone together?

Why expose explicit Transaction objects. What value does that provide?

Here is my updated proposal, which will surely need to be revised once
I understand the questions above better:

interface UndoManager {
  transaction(Function callback, optional in DOMString label, optional
in Boolean merge);
  manualTransaction(Function apply, Function unapply, optional in
DOMString label, optional in boolean merge);

  readonly attribute unsigned long position;
  readonly attribute unsigned long length;
  DOMString? labelAt(in unsigned long index);

  void undo();
  void redo();

  void clearUndo();
  void clearRedo();

  readonly Node host;
};

/ Jonas

Received on Friday, 29 July 2011 17:08:37 UTC