w3c / editing

Specs and explainers maintained by the editing task force
http://w3c.github.io/editing/
Other
190 stars 40 forks source link

Removal of browser built-in Undo stack functionality from contenteditable #150

Open johanneswilm opened 7 years ago

johanneswilm commented 7 years ago

The subject of removing the undo manager functionality from contenteditable by default came up at the Editing Taskforce F2F meeting on 2016-09-22 at TPAC in Lisboa, Portugal. It was pointed out that the browser's undo stack would be entirely useless once the JS editor interrupt the default behavior even in just a few limited cases. No-one present opposed the idea of removing the browser's undo functionality for contenteditable and replacing it by a way for the JS to enable/disable the menu entries for redo and undo + giving the ability to listen for redo/undo being triggered via beforeinput events.

Everyone agreed to turn this functionality of by default. (given that there seems to be no authoritative spec that says that undo IS provided, we may only need to spec that undo is turned off be default).

In addition, the following JS editor projects were contacted and all of them responded that the browser's undo stack and default undo behavior was either useless or harmful:

The following applications were investigated and it was determined that they use their own undo stacks (a separate stack for each input field):

Two working contenteditable webapps were found that makes use of the browser's undo stack:

[1] https://github.com/facebook/draft-js/blob/67c5e69499e3b0c149ce83b004872afdf4180463/src/component/handlers/edit/commands/keyCommandUndo.js#L24-L27

(This issue is updated as responses from developers arrive.)

Edit 2019-11-27: Added TypeIt.org

rniwa commented 7 years ago

Disabling undo/redo is possible but enabling undo/redo would require each app inserting an undo item into the browser's undo stack (NSUndoManager) on Safari (both Mac & iOS). This is precisely why the old undo manager spec, which Gecko implemented at some point, had an explicit mechanism to insert and remove undo entries.

johanneswilm commented 7 years ago

@rniwa On iOS/Safari, what are the ways that undo/redo can be activated? The issue of having to have an item in the undo stack in order to have the menu enabled is something that is part of the OS, somehow?

Is there absolutely no way that Safari could take care of inserting this token item into the undo stack when there is focus on the contenteditable element and undo/redo is to be enabled and remove that item again when moving the focus? That would be preferable, as that way the JS editor developer doesn't have to program something specific just for Safari.

The browser would still take care of undo-handling of textareas and input=text elements.

rniwa commented 7 years ago

Could Safari take care of inserting this token item into the undo stack when there is focus on the contenteditable element and undo/redo is to be enabled, and then somehow also empty the entire undo/redo stack when it has to be disabled?

Not really. Clearing undo stack is possible but then we'd have no way of inserting undo items back. I investigated this problem four years ago. Please go ahead and read old discussions on public-webapps.

Web apps really need to be using browser's undo stack. Also, talking about enabling / disabling menu item is extremely shortsighted approach. There is a reason some operating system provide a high-level abstraction for undo/redo, and any approach that relies on some existent UI tends to be not forward compatible. For example, how is an assistant technology supposed to expose undo/redo stacks if the only thing the browser knows is that there is something undoable? We need to be able to describe to the user what undo does.

johanneswilm commented 7 years ago

Web apps really need to be using browser's undo stack. Also, talking about enabling / disabling menu item is extremely shortsighted approach. There is a reason some operating system provide a high-level abstraction for undo/redo, and any approach that relies on some existent UI tends to be not forward compatible.

The issue here is that the browser's undo-stack is unusable in the case of all editors that use even just a little bit of JavaScript to manipulate the DOM due to user intentions. Given that raw contenteditable doesn't work, this includes all existing editors. Some editors are reporting that they need to "fight" the browser in it's attempt to undo itself. Others are apparently intercepting the keyboard shortcuts and the menu items are just not working.

So the question is not about throwing out a technology that is working. It is about removing something that is entirely unusable and replacing it with something that reduces the number of unused/non-working menu entries.

For example, how is an assistant technology supposed to expose undo/redo stacks if the only thing the browser knows is that there is something undoable?

That may be a good point. Do we have specific requirements for what has to be known for these technologies? Is it more than a title for what the next redo or undo would do?

We need to be able to describe to the user what undo does.

This is part of the Apple platform requirements or of assistant technologies?

johanneswilm commented 7 years ago

Having the webapp add and remove items from a browser-held undo history may work, if they can add something like a dictionary of their own data. Changing the DOM automatically is not something the browser should be engaged in.

Also, it needs to be able to add and remove great numbers of items to the undo and redo stack at arbitrary positions. For use cases such as;

User A and B are writing together in a collaborative editor. Their document is 3 paragraphs long. User A types 3 words in paragraph one, 10 words in paragraph two, and 4 words in paragraph three.User B then deletes paragraph two. The undo stack for User A should now contain 7 items, and that of User B should contain 1 item. If User B undos once, the undo stack of User A should contain 17 items, and that of User B should contain 0 items.

johanneswilm commented 7 years ago

Testing: Undo/redo browser native menu items don't work at all in Google Docs and Office 365 on Safari/Mac OS X.

Do these applications work with assistant technologies?

johanneswilm commented 7 years ago

@rniwa I have been looking through old discussions, but all I can find is a system whereby the browser changes the DOM and automatically applies DOM changes. That seems to be what we want to get away from now.

rniwa commented 7 years ago

The issue here is that the browser's undo-stack is unusable in the case of all editors that use even just a little bit of JavaScript to manipulate the DOM due to user intentions.

The issue is well understood. The problem is that your proposed solution is not implementable.

Also, it needs to be able to add and remove great numbers of items to the undo and redo stack at arbitrary positions.

Removing and inserting arbitrary number of items is possible but not at arbitrary positions for the same reason we can't just enable/disable undo/redo. In order to do that kind of manipulation, one has to remove all entries up to the entry to be removed, and re-insert all succeeding entries.

@rniwa I have been looking through old discussions, but all I can find is a system whereby the browser changes the DOM and automatically applies DOM changes. That seems to be what we want to get away from now.

The old undo manager API did support automatically rolling DOM changes, but also provided a mechanism to insert an entry without any DOM mutations that get automatically unapplied & reapplied. Basically, you just create a DOM transaction, don't do anything inside the transaction, and insert it to the undo manager. Because a transaction can be associated with any JS data, and undo / redo events will be fired upon unapplying and reapplying those transactions, scripts can do whatever they want to do upon receiving those events.

johanneswilm commented 7 years ago

The issue is well understood. The problem is that your proposed solution is not implementable.

You seem to be the first browser person saying this, so I would like to find out what exactly is hindering this from happening. Maybe it's just browser developers who have to rethink about how some things can be put together. Or maybe we need to reformulate this slightly to get around restrictions browser makers face.

Removing and inserting arbitrary number of items is possible but not at arbitrary positions for the same reason we can't just enable/disable undo/redo. In order to do that kind of manipulation, one has to remove all entries up to the entry to be removed, and re-insert all succeeding entries.

Ok, well that means it's kind of useless. What exactly is hindering browsers from creating two new menu entries that have nothing to do with the existing undo/redo functionality that are labeled "undo" and "redo" which are shown whenever the selection is in a contenteditable area?

The old undo manager API did support automatically rolling DOM changes, but also provided a mechanism to insert an entry without any DOM mutations that get automatically unapplied & reapplied.

I've had a look at it. So far I have not been able to find any JavaScript project that would seem to have any use of it. It would just mean they have to spend a lot of time implementing something that gives them no advantage.

I think we need to look at what advantages assistant technologies have from using the current native undo stack, and then look at how we can give access to that to the JavaScript undo stacks editors are using anyway.

rniwa commented 7 years ago

The issue is well understood. The problem is that your proposed solution is not implementable.

You seem to be the first browser person saying this, so I would like to find out what exactly is hindering this from happening. Maybe it's just browser developers who have to rethink about how some things can be put together. Or maybe we need to reformulate this slightly to get around restrictions browser makers face.

We've gone though that exercise four years ago. I find your dismissal attitude with regards to all these discussion extremely offensive. I'm gonna stop following this discussion because it's really affecting my well being at this point.

johanneswilm commented 7 years ago

I am not trying todismiss anything. I looked through the old spec and I searched the email list. Maybe you could provide me with a link? I think what's new now is that we actually have JS editor and browser developers come together and try to find a solution for the problems editors run into. That's fairly new. Now we just need to find away around the hacks editor developers run into.

johanneswilm commented 7 years ago

Another problem with the undo manager proposal seems to be that it only allows one, global undo stack. This is not how applications like Google Docs and Office 365 work as of today. The same is true for all the other editor projects as they all have their own undo stack.

johanneswilm commented 7 years ago

@rniwa: What part exactly is the impossible? Is it

A) Allowing JS to disable the undo/redo menu entries? As I understood your proposal, this wasn't an issue.

B) Enabling the undo/redo items if JS requests this. If the menu entries are directly linked to the OS, this could be done by adding fake items there if the stacks are empty and removing them when the editor loses focus. Given that the global undo stack is broken anyway, this shouldn't matter, should it?

C) Triggering a beforeinput event for undo/redo?

D) Not changing the DOM be default if beforeinput isn't canceled? This seems like it would be a minor issue.

rniwa commented 7 years ago

B. Adding a fake item, etc... doesn't work because of the way it integrates with the rest of the browser.

Mutating DOM is not necessary at all. It was merely a convenience feature we wanted to add at the time.

The bottom line is, the only way we can support something like this would be JS authors pushing and popping an entry into and from the native undo stack we manage. We can provide an API, etc... to associate and dissociate arbitrary JS data.

The thing is I've stated our requirements. I'm not going into details of what is and what is not possible with the native API because such is an outside the scope of the standardization process. I'm simply giving an implementation feedback that what you proposed is not implementable, and I've proposed an alternative, which gives full control of DOM mutations and allows authors to remove/add their own undo entries so I don't know what else I can say.

johanneswilm commented 7 years ago

Ok, but if I understand the alternative correctly it:

But how about this: If you implement that undomanager, it should allow JS to push one fake undo step and one fake redo step when the focus is on the editor and then to consume those when the focus moves away, right? So it would basically be the same thing with the only difference being that JS needs to add 1-3 extra lines of code for Safari. I think that should work!

johanneswilm commented 7 years ago

I guess this can be done even now, as long as Safari supports execCommand, by executing two commands, then undoing one, then putting the DOM back to its initial state by manipulating it directly in JS. Now both menus should be enabled. It's a bit of a hack, but it would be a solution that would make beforeinput useful for undo/redo immediately when beforeinput has been implemented in Safari. Solving it all in a non-hackish way could be a next step.

rniwa commented 7 years ago

Imposes having a global undo stack for all input fields on one page, which is not what webapps currently do, not even those by browser vendors such as Google or Microsoft.

Chrome certainly shares a single undo stack for the entire page. You can test this by typing text into a text field across iframe boundaries: https://jsfiddle.net/buny13mz/

Changing this behavior in regular case (i.e. ones that doesn't invoke custom editor) is unacceptable because it breaks a key browser UX.

Doesn't work with collaborative editors where an arbitrary number of items have to be added or removed from some point in the past.

When we designed the old Undo Manager API, we specifically worked with Google Docs team to ensure their undo worked with the API so this assertion is false.

If you implement that undomanager, it should allow JS to push one fake undo step and one fake redo step when the focus is on the editor and then to consume those when the focus moves away, right? So it would basically be the same thing with the only difference being that JS needs to add 1-3 extra lines of code for Safari.

Not quite. This would still break semantics of the shared undo stack across documents. So you'd have to insert a fake undo/redo for the collaborative undo/redo part at the beginning, and then insert new undo/redo entry as the current client modifies content. You'd just ignore callbacks and pop more entires when undo/redo fires on things that were supposed to be removed earlier. You don't have to actually remove it from the browser's undo stack as they happen.

johanneswilm commented 7 years ago

Imposes having a global undo stack for all input fields on one page, which is not what webapps currently do, not even those by browser vendors such as Google or Microsoft.

Chrome certainly shares a single undo stack for the entire page. You can test this by typing text into a text field across iframe boundaries: https://jsfiddle.net/buny13mz/

Yes, but that's the undo stack noone uses for richtext beyond demo purposes. If you go to actual applications Google has made, like Gmail or Google Docs, you'll see that they have implemented it with a different undo stack for each input field.

Changing this behavior in regular case (i.e. ones that doesn't invoke custom editor) is unacceptable because it breaks a key browser UX.

But every time contenteditable is involved, a custom editor will be involved. And all of them create their own undo stack.

The place where the global undo stack still may be used is if you have a series of input text fields in a form. For that case it's fine. I am only talking about contenteditable here.

Doesn't work with collaborative editors where an arbitrary number of items have to be added or removed from some point in the past.

When we designed the old Undo Manager API, we specifically worked with Google Docs team to ensure their undo worked with the API so this assertion is false.

But if I understood you correctly, you said that it cannot include add and remove undo steps at an arbitrary point in the past? (usecase: https://github.com/w3c/editing/issues/150#issuecomment-249390872 )

Not quite. This would still break semantics of the shared undo stack across documents.

The global undo stack is already broken as it is -- in all of the web apps, because it's global, because changes are added via JS, etc. . As long as contenteditable adds items to this global undo stack, it will be broken. That's what this issue was about. Your undo manager may still be useful for other areas - form filling, textareas, SVG editing, etc, so I'm not suggesting you stop working on it. It's just not the solution for richtext editing.

I figured out how to make sure that both undo and redo are enabled in Chrome and Firefox. It doesn't quite work in Safari, because Safari seems to join several edit operations into a single item in the undo stack, but with a few hours of trial and error, I'm sure JS developers could find a solution there as well:

<!DOCTYPE html>
<html>
<head>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
  // Enable undo and redo menu entries
  var undoEl = document.createElement('div');
  undoEl.setAttribute('contenteditable','true');
  undoEl.innerHTML = "b";
  var undoElText = undoEl.firstChild;
  document.body.appendChild(undoEl);
  var r = document.createRange();
  r.setStart(undoElText, 0);
  r.setEnd(undoElText, 1);
  var s = window.getSelection();
  s.removeAllRanges();
  s.addRange(r);
  document.execCommand('bold');
  document.execCommand('bold');
  document.execCommand('undo');
  document.body.removeChild(undoEl);
});
</script>
</head>
<body>
<div contenteditable="true">
<h1>Title</h1>
<p>Text...</p>
</div>
</body>
</html>
johanneswilm commented 7 years ago

Not quite. This would still break semantics of the shared undo stack across documents.

The global undo stack is already broken as it is -- in all of the web apps, because it's global, because changes are added via JS, etc. . As long as contenteditable adds items to this global undo stack, it will be broken.

The other option---and this is what was proposed by Chrome and Edge representatives at the F2F---is to exclude contenteditable from the global history and add a way to enable/disable the undo/redo menu entries so that the editor can receive the corresponding beforeinput event.

That is of course a much cleaner solution as it doesn't interfere with the global undo stack used by other fields. I opened this issue primarily to figure out if there is actually no use for the global undo stack at all. And there I found the draft-js issue which probably needs further investigation.

Now if Safari cannot do what was proposed by the other browser vendors, then we need a solution for that. At this early stage, it maybe enough to find a JS hack to get around the disabled undo/redo menu items. At the next stage, I would think it would make sense to find some less hacky solution, but we can talk about it at that time.

chaals commented 7 years ago

The problem with not integrating into the global undo stack is that users have historically been given only one stack. And their memory of what they have done doesn't usually distinguish between different input fields.

This is changing, because as Johannes says, editing systems are making a custom undo stack per editable region.

There are also systems that offer more information about what "undo" will actually do - which is good because it isn't often very clear. In particular, it is sometimes very hard to know what can be undone, and what cannot.

It would be good to get telemetry from editor apps as well as browsers on how often, and how far, people actually use undo…

johanneswilm commented 7 years ago

The problem with not integrating into the global undo stack is that users have historically been given only one stack. And their memory of what they have done doesn't usually distinguish between different input fields.

True, although we may have to go a while back. Before working on this, I wasn't aware that there was something like a global undo stack as I had only ever had to deal with undo stacks per editable element (an exception being editable elements contained in other editable elements). Maybe we could talk to Google Docs, Office 365, Gmail & others who "voluntarily" chose to switch toward having multiple undo stacks and find out how long time ago they made this decision?

There are also systems that offer more information about what "undo" will actually do - which is good because it isn't often very clear. In particular, it is sometimes very hard to know what can be undone, and what cannot.

This is also where assistant technologies come in? Do we have a description ok what exactly they need? For example: do they need a written description and possibly type for the next possible undo or redo step, or do they need to go even further back?

It would be good to get telemetry from editor apps as well as browsers on how often, and how far, people actually use undo…

I think we have already taken one step in the right direction: focusing on editing apps and their developers. Going all the way to the end user would make the process of figuring out what we need to provide even more perfect. But it may not be without problems.

I wonder what type of data actually exists on this. The first post here is a result of me sending out a simple one-question survey to the various editor apps. Additionally, I looked at the source code or tried the app in the case of the editor apps where I couldn't get a response or didn't know who to contact.

Maybe we should design a more complex survey which also includes questions about their end users and how much they know about them. They may not be willing to share much more than their final conclusion though, given that they are competing with oneanother.

Of course, with every new survey there is the chance that people don't bother answering us.

johanneswilm commented 7 years ago

I just tried iCloud Pages on Safari/Mac OS X, and it also maintains different undo stacks for each input window (font size, comment, main doc). The browser's redo/undo menu entries are not working (same as Google Docs and Ms Office 365). Instead it provides undo/redo buttons in the apps UI that switch whenever the user switches from one input box to the next.

@rniwa I'm trying to understand Apple's overall UX policy in this area. Is the rule you mention about having one global undo stack something that only applies for pages where the editor is not the main element? Wouldn't it be good to provide menu entries that would allow iCloud Pages to function as it does now, but with working Undo/Redo buttons? It Apple's policy requires having one global undo stack on some types of pages, should we maybe look for a solution that allows for both a global undo stack and one that has separate undo stacks?

rniwa commented 7 years ago

Wouldn't it be good to provide menu entries that would allow iCloud Pages to function as it does now, but with working Undo/Redo buttons? It Apple's policy requires having one global undo stack on some types of pages, should we maybe look for a solution that allows for both a global undo stack and one that has separate undo stacks?

Right, this is likely to be implementation-dependent behavior like focus and selection. The key here is to allow browser vendors to implement either behavior and let web authors choose which behaviors they want. This is why the old undo manager API had undoscope attribute.

The only problem with the old API was that being able to add a new undoscope via a content attribute made things way too complicated for implementors. I think a better solution would be adding a flag on ShadowRoot. Since builtin text fields are modeled like they have their own shadow root, any UA that uses a separate undo stack per text field (e.g. Gecko) can be modeled using this way.

johanneswilm commented 7 years ago

Right, this is likely to be implementation-dependent behavior like focus and selection.

Implementation as in "webpage app implementation" or as in "UA implementation"? As far as I can tell, all these JS editors on all the platforms, have separate undo stacks for each input field.

We may need to look at that a bit more in details: Some of these apps are "forced" to have a separate undo stack, because the current one is useless. This would be the case for the smaller editors that can be one of several components on a webpage. The other ones "voluntarily" have separate undo stacks. This would be the case of the bigger webapps that fill the entire page with various input boxes which they all all control. They could have created one global undo stack for all their input fields, but instead they chose to have separate undo stacks for each input field - this is the case with the various products of Microsoft, Apple, Google, etc. .

The big question is here: Would there be simple a richtext editor that would prefer the global undo stack, if they could? From their responses, I haven't come across such an editor project, but that doesn't mean they don't exist.

In any case, we should plan for the separate undo stack, because this seems to be the main usecase and defacto standard as of now.

If the undo manager can be adjusted to accommodate for local undo stacks and be implementable -- that's great. The question is then just what the advantage for the web developer would be to use this undo manager. If the web developer can add two "fake" edit operations to an undo stack that is local to just one element, so that the undo/redo buttons are shown, and then intercepts all edit operations with beforeinput, that should work without contaminating the global undo stack, right?

Personally I wouldn't mind using a built-in undo manager. But as soon as it restricts me in any way even with a more exotic operation, I would likely away from it rather immediately, at least if there is no perceived advantage with using it.

rniwa commented 7 years ago

In any case, we should plan for the separate undo stack, because this seems to be the main use case and defacto standard as of now.

We're fine with having that as an option but forcing that on all contenteditable content is definitely not okay for us.

johanneswilm commented 7 years ago

In any case, we should plan for the separate undo stack, because this seems to be the main use case and defacto standard as of now.

We're fine with having that as an option but forcing that on all contenteditable content is definitely not okay for us.

Great! Then we are in full agreement.

Then we just need to figure out how to do it in combination with the beforeinput system and the inputTypes undo and redo. Say we simply combine beforeinput with a future undomanager that always for the insertion of arbitrary JS objects to be stored with the undo entry. Undert these circumstances, what kind of data should we get in the beforeinput events for undo and redo? Should we simply get access to the item in the undo stack that would be applied if we don't preventDefault it?

And can we have an intermediate solution until we get the undo manager so that we can ensure that undo and redo always are enabled when we need them to? Or should we just say that JS developers simply need to use a hack as I posted above, in the meantime?

rniwa commented 7 years ago

Again, the problem here is that there is no intermediate solution for us. We can't arbitrarily enable undo/redo menu item in Safari without having the capability to push/pop undo stack items.

waterplea commented 5 years ago

A much better solution to removing native Undo/Redo from contenteditable would be endorsing what I just uncovered IE/Edge had for ages:

document.execCommand('ms-beginUndoUnit');
contentEditableElement.innerHTML = '';
document.execCommand('ms-endUndoUnit');

This way you can define particular manipulations as a singular Undo unit.

johanneswilm commented 5 years ago

@waterplea That is very interesting! Undo not recognizing changes made by other parts of JavaScript is only one part of the problem though. The other part is that it has one global undo stack for all the elements on the same page. That goes against how every app I have been to find works. Undo stacks work on a per element basis in reality. It's a really odd choice to have the entire page share the same undo stack, which is probably why noone has thought of testing for this.

waterplea commented 5 years ago

Well, what I did to circumvent that was creating an iframe with a designMode = 'on' document inside. This way I have a separate Undo stack for an editor.

Reinmar commented 5 years ago

I don't think it would work with real-time collaborative editing (where you can only undo your changes). Such a way of communicating with the browser could help, but something more straightforward would be better.

rniwa commented 5 years ago

Well, what I did to circumvent that was creating an iframe with a designMode = 'on' document inside. This way I have a separate Undo stack for an editor.

Note that WebKit famously (?) does not have a undo stack per document. Safari's undo stack is shared across all the documents. In general, the behavior about undo/redo isn't really standardized and diverges across platforms.

A while ago, Google proposed on a real UndoManager API (I say Google because I was working at Google at the time) but it didn't get much traction due to its immense complexity, etc...

@whsieh and I are experimenting with something simpler where you can just register a function for undo / redo. I think real-time collaborating editing is a challenging case regardless because what you may have been previously undoable may no longer become undoable at some point.

However, we believe having some good undo / redo on the Web is important since undo / redo is one of the most frequently used feature for any real productivity app, and not having any browser integration would mean that assistant technology (for accessibility) can't even recognize that there is something undoable. It's a freaking embarrassment that we're in such a broken state in 2019.

Reinmar commented 5 years ago

@whsieh and I are experimenting with something simpler where you can just register a function for undo / redo.

Sounds interesting! Could you share some more details?

I think real-time collaborating editing is a challenging case regardless because what you may have been previously undoable may no longer become undoable at some point.

Typically, a rich-text editor which supports operational transformation or other real-time collaborative engine will have its own undo manager and know what can be undone (usually, the current user's changes only). So as long as the decision is in JS editor's "hands" we should be fine.

johanneswilm commented 5 years ago

Typically, a rich-text editor which supports operational transformation or other real-time collaborative engine will have its own undo manager and know what can be undone (usually, the current user's changes only).

Additionally, the undoing may not correspond exactly to what had been changed in the first place. So for example if you have something like:

User 1 writes a word:

The fish are exppensive|.

User 2 corrects the word:

The fish are ex~p~pensive.

User 1 undoes his latest change. This can either be entirely forbidden, as it interferes with the changes of user 2, or it could for example be to delete the new word:

The fish are ~expensive~.

What is possible is a decision that has to be taken fairly close to the final app user / person who runs the website as there are many factors to that decide what makes most sense. It can have to do with the kinds of users and the kinds of texts one is dealing with.

So as long as the decision is in JS editor's "hands" we should be fine.

Agreed. Very important that we leave the decision to JS as otherwise it will likely just be ignored/overridden as is the case today.

whsieh commented 5 years ago

@whsieh and I are experimenting with something simpler where you can just register a function for undo / redo.

Sounds interesting! Could you share some more details?

Sure! Here's a high-level overview; however, please note that the UndoManager prototype is currently only enabled in some first party Apple applications, and is not available in Safari quite yet.

There's a couple of main parts to this API: UndoManager and UndoItem. The UndoManager is exposed as a readonly attribute on Document, and has a method, addItem, that can be used to add an undoable action to the platform undo stack. This undoable action is backed by an UndoItem, which may be constructed using a label string and callbacks that are invoked when the user triggers undo or redo.

interface UndoManager {
    void addItem(UndoItem item)
};

...and UndoItem is defined as such:

interface UndoItem {
    Constructor(UndoItemInit initDict);
    readonly attribute DOMString label;
};

dictionary UndoItemInit {
    required DOMString label; // Determines the string to display in platform undo UI (for instance, the menu bar on macOS).
    required VoidCallback undo; // Invoked when the user triggers undo.
    required VoidCallback redo; // Invoked when the user triggers redo.
};

The hope is to make it easier for web content to define custom undoable actions using DOM API, in a way that:

(1) doesn't require maintaining a custom notion of an undo stack in script, and... (2) maintains better platform integration by influencing the undo label name in platform UI, as well as being agnostic to all platform-specific methods of triggering undo and redo (e.g. shaking the device on iOS, and selecting actions in the menu bar on macOS).

johanneswilm commented 5 years ago

Here's a high-level overview

This looks generally good. The one case it doesn't seem to be able to handle is when a specific action becomes un-undoable after the fact, right? Also, you keep the global undo stack for all apps on the same page which goes against the logic of all richtext editing web apps we have been able to find. This second point may not be that important though if it really can be made to work reliably in most cases where is only one big editor on a page and maybe 1-2 small input elements. We'd have to see how end users respond to a global undo stack like that.

waterplea commented 5 years ago

@rniwa you are right, Safari does not isolate undo stack per document. That's unfortunate. Thankfully the audience of my app rarely uses Safari so sometimes graceful degradation works for me. As for Chrome — iframe isolates Undo stack well on it, as it does on Firefox and IE/Edge.

I'm building a humble WYSIWYG editor, mostly basic editing and no collaboration. With those poorly documented IE commands for defining undo item I've managed to create native polyfill for insertHTML/insertText. Accessibility is my main concern, that's why I dance around native Undo stack rather than making my own like more mature editors do. After all, you can redefine undo actions (at least on Mac I believe) so there's no secure way of making your own undo stack work 'natively' that I know of. Add to that other ways of triggering mentioned above.

dmonad commented 5 years ago

I recently stumbled on this problem and came up with a method to trick the browser to use ProseMirrors undo manager. I posted my solution in the ProseMirror forum, but it can certainly applied to other editors as well. https://discuss.prosemirror.net/t/native-undo-history/1823 My solution currently works in Chrome, Chromium, Firefox, and Safari. I think the method works in all browsers that support inputType on Input events.

tszynalski commented 4 years ago

@johanneswilm For what it's worth, I run a simple, fairly popular web editor for typing foreign characters. It's just contenteditable with execCommand, and currently no programmatic DOM manipulation. I use the native undo stack and it works fine. I wouldn't want to see it gone!

I agree things get ugly quick when you start to combine native editing operations with direct DOM modifications (been there, done that), but currently I have no need for anything beyond Bold/Italic/Underline/Subscript/Superscript.

johanneswilm commented 4 years ago

That sounds fascinating. Could you paste a link to that service here?

On Wed, Nov 27, 2019, 07:01 tszynalski notifications@github.com wrote:

@johanneswilm https://github.com/johanneswilm For what it's worth, I run a simple, fairly popular web editor for typing foreign characters. It's just contenteditable with execCommand, and currently no programmatic DOM manipulation. I use the native undo stack and it works fine. I wouldn't want to see it gone!

I agree things get ugly quick when you start to combine native editing operations with direct DOM modifications (been there, done that), but currently I have no need for anything beyond Bold/Italic/Underline/Subscript/Superscript.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/w3c/editing/issues/150?email_source=notifications&email_token=AAERMOBIGMNUQXVTHRMS5ETQVYEKPA5CNFSM4CQW2SH2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFIMW2Y#issuecomment-558943083, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAERMOHZF3H3PXOVEJWM5NDQVYEKPANCNFSM4CQW2SHQ .

tszynalski commented 4 years ago

@johanneswilm https://www.typeit.org

johanneswilm commented 4 years ago

@tszynalski It seems like your editor has no cleanup mechanism, so if I paste something from an arbitrary website, it gives me content which is partially non-editable, it throws JavaScript errors, etc. . Is that intentional?

This looks like the kind of editors blogging platforms like Joomla used about a decade ago. I think this can work for a professional web developer who knows how to clean up their content. The issue with not cleaning up the CSS is more with less professional users who publish it directly to their website like that and then break the website. In your case that is probably not as much an issue any more given that all the blogging platforms have editors that clean up content, so the way users get the content out from your editor is to copy and then paste in the other editor where it is being cleaned up.

Does that sound right to you, @tszynalski ? Is there anything you get from execCommand that you could not get if you were to switch to a JavaScript editing framework instead?

johanneswilm commented 4 years ago

@tszynalski And on Android you are using a textarea instead of contenteditable? What's the background for that?

tszynalski commented 4 years ago

@johanneswilm I'm not sure we should discuss my design decisions at length here -- this is, after all, a place for discussing Web standards. I wrote my comment, because I wanted to point out that some people do in fact use execCommand(). In my case, it's a good solution despite various shortcomings, some of which you have brought up. A third-party JS editor would probably work, too, but it would be much heavier and slower, while not adding much value for my particular use case.

If you would like to discuss my site in detail, you can also email me at the address found on www.typeit.org.

johanneswilm commented 4 years ago

Well, yes, of course we should list your editor especially if it has a substantial user base and is used in production. But the questions are because part of what we are doing is try to find out if we are covering all use cases with the standards we are working on. ExecCommand is nor among these because we currently believe that JavaScript libraries are better suited for this. Yet you are still using execCommand so then the question for us is if we overlooked something and if maybe there is a usecase where execCommand is needed anyway.

On Wed, Nov 27, 2019, 14:18 tszynalski notifications@github.com wrote:

@johanneswilm https://github.com/johanneswilm I'm not sure we should discuss my design decisions at length here -- this is, after all, a place for discussing Web standards. I wrote my comment, because I wanted to point out that some people do in fact use execCommand(). In my case, it's a good solution despite various shortcomings, some of which you have brought up. A third-party JS editor would probably work, too, but it would be much heavier and slower, while not adding much value for my particular use case.

If you would like to discuss my site in detail, you can also email me at the address found on www.typeit.org.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/w3c/editing/issues/150?email_source=notifications&email_token=AAERMOG52C7OJ4TJUDGVDY3QVZXSTA5CNFSM4CQW2SH2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFJPBQI#issuecomment-559083713, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAERMOD6XJE5243BRRVNXI3QVZXSTANCNFSM4CQW2SHQ .

tszynalski commented 4 years ago

It's like with <input type=range>. You can totally replicate this functionality using a third-party JS library. But it's faster and lighter to have a native widget provided by the browser.

It's not that I'm a fan of contenteditable and execCommand() in particular. If there was a better native "textarea with formatting", preferably with standardized underlying HTML, I would be all over it, and I'm sure many other projects would also find it useful. One idea is forum/blog comments (like the one I'm typing right now). You don't need a full-fledged MS Word clone with images and tables for that. But boldface/italics/links would be nice. Right now the prevailing solution is Markdown. You need to know the markup codes. Separate boxes for writing and for previewing. Works OK for developers, but normal users would probably prefer something WYSIWYG, don't you think?

PS. My site gets ~800,000 visits per month.

dmonad commented 4 years ago

I think @tszynalski is right in the regard that we shouldn't just disable native undo functionality for all sites. For instance, there are many users of old versions of the google closure editor or old CKEditor versions. They don't have their own undo-stack implementation yet. Disabling native undo-redo by default will definitely break huge chunks of the web.

Instead, it should be possible to disable & intercept events of the native undo stack if needed.

dmonad commented 4 years ago

@johanneswilm I don't think that anyone really needs execCommand. It's a bad feature now that we have all these methods to intercept key-events. But as a browser vendor I also wouldn't just disable this feature and accept frustrating my users.

johanneswilm commented 4 years ago

@dmonad @tszynalski The proposal of removing the undo stack altogether for contenteditable has been superseded by the undo manager proposal since [1]. This ticket was just not closed yet as I was waiting for the undo manager proposal to be updated and put in the right place. But maybe I should so we don't need to debate this any more.

Sorry for not mentioning that earlier, but that's the reason I'm more interested in the question of whether or not we have found an actual usecase for execCommand here. It sounds like that's not actually the case, right?

[1] https://github.com/w3c/editing/issues/209

johanneswilm commented 4 years ago

@tszynalski I think the problem is that even a mini-execCommand has many of the same issues that regular execCommand has, which is largely that four large browser engines don't have richtext contentediting as their top priority so bugs don't get fixed for long periods of time, while there are JavaScript frameworks out there that do have it as their priority so those libraries do get fixed and they need a way of dealing with input without having to wait on browser makers for too long.

And who is to decide what goes into a mini version? Bold and italic is something probably all can agree on. But I see you also have underline, sup and sub. Then someone else may argue they also need font color, etc. and then very quickly we are back at what we have now. Execution speed is generally not much of an issue because human typing is limited, so running it in JavaScript is not much of a problem in that regard.

Maybe the solution to @tszynalski's issue of not wanting the user to have to download an entire JS library when visiting his site is to cache the library somehow and use it across sites? Basically what sites used to do with jQuery a few years ago.