Add better undo support when inserting formatted text

Right now in Discourse, if you paste plain text into the composer, typical browser behavior via Ctrl+Z will undo that paste and remove the pasted text. However, if you paste formatted text such as this bold text, undo doesn’t work. Similarly, pasting links on top of text to linkify them cannot be undone, nor can the markdown inserted via shortcuts like Ctrl+B or when adding formatting from the composer toolbar options. Ideally, as much of this as possible would be added to the undo stack so Ctrl+Z works.

Let me give a description of what happens to me fairly often.

  1. I copy some text from another website which happens to be a link, bold, a header, etc. (Sometimes this fact registers and sometimes it does not.)
  2. I go to Discourse to paste this text into my already partially written post.
  3. I hit Ctrl+V which inserts the formatted text.
  4. I realize my mistake and hit Ctrl+Z, which does nothing.
  5. I manually remove the formatted text that I inserted by mistake. (Most often it’s the linked version that I pasted by mistake, so not like I just have to remove a # or something like that.)
  6. I hit Ctrl+Shift+V which I should have done the first time to paste the unformatted text.

Obviously this is in part user error, and when I don’t mess up it’s only two steps (copy from other website, paste as plain text in Discourse). But when I do mess up (which is often since I’m used to just hitting Ctrl+V given that most websites don’t do formatted pasting) it would be nice if I could save some time by Ctrl+Z working like normal.

4 Likes

This is generally a native browser behavior, and isn’t something Discourse is doing. Try testing against a plain text box in HTML.

Right, undo doesn’t work by default whenever you use JavaScript to modify an input. But I googled around and found that document.execCommand can insert text while appending to the undo stack.

For example, if you do document.getElementById('myInput').value = 'asd' and then Ctrl+Z, it won’t undo.

But if you do document.execCommand('insertText', false, 'asd') while the cursor is where you want it (which it should be based on the current Discourse workflow), the text is properly inserted and Ctrl+Z will remove the added text as expected.

Basically, I’m wondering if document.execCommand (or some other process if another approach is considered better) can be used to append to the undo stack so Ctrl+Z works in these cases.

3 Likes

No — we intentionally pulled that support from Discourse years ago in favor of letting the browser native text box undo handling work as it should across all standard websites.

I think I must be missing something. What does “letting the browser native text box undo handling work” mean exactly? As far as I can tell, undo for formatted text doesn’t work at all in Discourse, so are you saying that undo not working is standard website behavior?

The reason I’m confused is because I can’t think of a single website (other than things like Microsoft Word where undo works) that supports formatted paste besides Discourse. So I have nothing to compare Discourse to in order to see what is “standard”. If you could point me towards a few websites for comparison that would be really helpful.

2 Likes

See

Press Try it yourself, then enter text in the textbox, pause, then press ctrl+z to undo your actions. Here’s a demo. First we press the Try it yourself button, which results in a HTML <textarea> being displayed in the browser.

image

I enter some text into the textbox. As you can see in the screenshot, I typed

I JUST TYPED THIS TEXT YAYYYY!

image

Now, after typing, I press ctrl+z to undo my typing, and I see this:

image

Note that the text has returned to its previous state, and this was 100% handled by the browser itself, zero JavaScript code is involved.

My understanding is that @seanblue is asking if we can amend the browser APIs we use when we manipulate the textarea to better hint the browser so it can handle undo a bit better. So this would only apply to keyboard shortcuts, and toolbar, upload and stuff like that

I am not against tuning stuff here, but I worry that some off these APIs will require lots of care, there is certainly risk of regressions

Not against experiments here, maybe if the community wish to send through some PRs to show us how it is done

7 Likes

We could definitely do better here. A lot of our toolbar buttons and fancy-paste behaviour directly set the value of the textarea in JavaScript. This totally breaks the native browser undo/redo history.

Instead, whenever we make programmatic modifications to the textarea, we should be using document.execCommand (as @seanblue mentioned). That way, the browser interprets it the same as a user action, and inserts it cleanly into the undo/redo history.

the insertText command, which you can use to programmatically replace text at the cursor while preserving the undo buffer (edit history) in plain textarea and input elements.

10 Likes

But that textbox doesn’t handle formatted text, which isn’t what I’m talking about. I know that browsers handle undoing normally typed and pasted text. My point is that Discourse doesn’t handle undoing pasted formatted text. Follow these steps to see what I’m talking about.

First, open the Discourse composer:

Now copy the following text and paste it into the composer: this is a test

Now hit ctrl+z, and the pasted text is removed. This is identical to the behavior you demonstrated in your post.

Now copy the following text instead and paste it into the composer: this is a great test
Notice that it pastes it with the markdown to italicize “great”.

Now hit ctrl+z and notice that the pasted text is still there. This is what I’ve been talking about.


That’s right. I’m not suggesting handling the undo yourself in JavaScript. I’m suggesting that when you manipulate the textarea, let the browser know so it can undo the change itself when the user hits ctrl+z.

For what it’s worth, 99% of my frustration would be resolved if ctrl+z worked after pasting formatted text. Would it be ideal if every single operation could be undone with ctrl+z? Sure. But most other operations can be undone by repeating the original operation (e.g. doing ctrl+b can both add and remove bold markdown). But with paste, it has the potential to include a significant amount of unexpected markdown, including headers, links, and even tables, which is why it’s so important that undo works in that case.

If we narrowed the scope to just handling undo in the formatted paste case and ignored other shortcuts, toolbar buttons, etc., would that reduce the risk enough to give it a shot?

9 Likes

I see; between your and @david’s explanation, I understand the distinction. I just never really use those buttons in the editor for the most part. I type things in the textbox using my computer’s keyboard (either physical or on-screen), and this is handled seamlessly by the browser.

6 Likes

That’s understandable. I also tend to type the markdown instead of using the toolbar buttons for that, so that aspect isn’t really an issue for me. I only mentioned the toolbar stuff in the OP to point out that it doesn’t just happen when pasting formatted text. Being able to undo the toolbar actions isn’t super important because the user is doing those actions intentionally. But when pasting, the formatting is often incidental and unexpected, so being able to undo that would be very convenient.

6 Likes

Just wanted to follow up on this one and see the likelihood of this being worked on any time in the foreseeable future.

3 Likes

Not scheduled yet, but yes, it appears to do the trick. I think we should change our implementation for both toolbar and stuff like CTRL-B shortcuts and mentions.

It is a pretty involved change though, I would say it would take about 1-3 weeks of work to get this all wired in. There is a lot of surface area:

  • Cut-and-paste images
  • Uploads
  • Bold / Italic
  • Links
  • @mentions
  • #autocomplete

I support this change but am just not sure when we can schedule it… I guess I am ok to slot it for our next release, any objections @codinghorror ?

I do like that you will be able to CTRL-Z all the way back to an empty box instead of just be blocked on your first mention, link, etc…

8 Likes

One nice thing is that (in my opinion) it can reasonably be done incrementally rather than having to release it all at once. Obviously I don’t know if that’ll be the case from a technical perspective, but from a user perspective I think it would be fine. Some logical separations might be:

  • Pasting text that becomes formatted, including things like bold, images, and links
  • Autocompleting mentions, categories/tags, emoji
  • Keyboard shortcuts like Ctrl+B for bold
  • Toolbar actions, like bold, hide details, blur spoilers, etc.

I feel like each of those groups could be done individually without confusing users, and personally that’s the order I’d implement it in.

8 Likes

I am slotting this work for our next release, it means we will get this cleaned up over the next 6 months or so, will not happen overnight, but we will make progress here.

8 Likes

Since it’s been four months I wanted to check in and see how this was coming along. :slight_smile:

2 Likes

Yes I completely hear you, but this is going to take a while longer.

It has kind of snowballed into a complete refactor needed on the composer. Our long term plan is to support a different abstraction layer for the composer which is now glued SUPER hard into it always being a TEXTAREA element.

The first unblocking move is supporting a contenteditable composer that looks and acts like our current text area.

I do not see us starting on this project for 3 more months, cause we have 3 other very large projects in front of it, but I do certainly see us getting a start on this project this year.

2 Likes

No worries, was just looking for an update.

Whoa, I had never even heard of contenteditable until now. Do you mind sharing a brief technical explanation of why this change is needed/desirable? If not that’s fine, I’m just curious.

It is somewhat complicated, but we do want to enter the world of experimenting with rich editors. This would unlock that.

The reason this work is on the critical path is that our entire internals are heavily coupled to a specific implementation (TEXTAREA). We don’t have a single function to interface with the composer, it is more like cut-and-paste 20 different implementations.

What we would like to do is have a little “skeleton” component that says

  • This is how you select text
  • This is how you insert text

And so on… then we can re-implement the skeleton as a contenteditable or an undo friendly implementation of TEXTAREA.

A lot of code though needs to move to allow for this.

2 Likes

I was able to make a little progress here:

This is nowhere near as comprehensive as the long-term work @sam described. But I think it should help in the short-term. This should preserve undo history when pasting rich text, quoting, and when using (most) composer buttons/keyboard-shortcuts.

It’s live on Meta now - shout if you notice any issues.

3 Likes