yabwe / words

A humble yet ambitious attempt to build a WYSIWYG editor, backed by JSON, without relying on document.execCommand
Other
19 stars 4 forks source link

A micro-library for building wysiwyg editors? #10

Open sabine opened 8 years ago

sabine commented 8 years ago

I stumbled on this when I read about it in one of the contenteditable issues on medium-editor.

I very much approve of the idea to not use execCommand. Have you looked at https://github.com/basecamp/trix?

From what I understand so far, a reasonable workflow around contenteditable is to

I tried https://github.com/jakiestfu/Medium.js and found it quite interesting, but I believe that the approach of "keeping the HTML content of the contenteditable semantic, simple, and clean" is not the best way of doing things.

To me, it looks like we still need a library that make it easy to build wysiwyg editors (not necessarily only for rich text, mind you) around contenteditable without touching the icky contenteditable parts.

Does that make any sense?

nmielnik commented 8 years ago

@grumpi yeah that definitely makes sense. I've unfortunately allowed this project to go very stale, but the goal here was to use contenteditable only for the 3 cases you listed above.

If there is any use of execCommand() in the repo, it was only supposed to be there temporarily, ultimately the content would be represented by a JSON data structure which would have an algorithm for generating HTML and never allow the browser to make a mess of it!

I have indeed looked at trix, as well as draftjs, prosemirror, and carbon. The last 3 were more along the lines of what I was attempting to accomplish with this project.

nchase commented 8 years ago

Anything that treats contenteditable as I/O is a good idea...

sabine commented 8 years ago

I didn't know about carbon yet. Thanks for pointing it out. I don't think I would have found it otherwise.

If people could agree on a reasonable abstraction around contenteditable (there probably are some reasonable abstractions for different use cases, the question is just what a rather low-level one looks like), I think a micro-library that implements that abstraction would be a useful and fun thing to have. It looks to me like draftjs is one such abstraction - but only for React, and specialized towards rich text editing.

I'm not sure the level of abstraction I'm imagining exists in a meaningful way. It would be quite low-level, providing a foundation for building both rich text editors and tag input fields (like, e.g. https://github.com/loopj/jquery-tokeninput) on contenteditable.

It's really just a vague idea at this point since I have never implemented a WYSIWYG editor before - or done something remotely useful with contenteditable. I'm just seeing this thing and thinking that it looks like I could streamline a good part of my application if I could just get it all to work on contenteditable instead of piecing it together from jQuery plugins.

okiuim commented 8 years ago

Sorry for a potentially stupid question. While it is possible to overwrite any native methods of Javascript objects, would it be possible to hack the native contenteditable and document.execCommand with a superior solution and make all "broken editors" work like magic? Kind of a semi-polyfill if you will.

sabine commented 8 years ago

@okiuim

It looks like the main problem with contenteditable is how it behaves when you input stuff. For example, how, on some browsers, a <span> with styles gets inserted when you press enter in some situations. The easy way to fix that is to intercept all the keypress events and implement what is supposed to happen manually, but that's too naive since it breaks some input methods (https://en.wikipedia.org/wiki/Input_method).

What's done here in the repo with diffing the text might be a better method to deal with people inputting text.

I just saw there's something going on wrt making a "better contenteditable" here: http://w3c.github.io/editing/editing-explainer.html

nmielnik commented 8 years ago

@okiuim that's an interesting way of going about this...I'm not sure if some of the native browser methods actually call document.execCommand (ie CTRL + B for paste, or clicking 'Undo' in the right-click context menu), but I bet you'd be able to work around some of those if you needed to.

At the end of the day though, you'd still need some library (like MediumEditor) to give the users ways to trigger document.execCommand() by clicking buttons in a toolbar/menu, or using keyboard shortcuts etc. However, building out a shim could be a good way of taking the logic that's within all of the contenteditable-based editors out there like MediumEditor and putting it in a place that all editors could use...

nmielnik commented 8 years ago

@grumpi I wanted to help clarify what I was hoping the workflow would be inside of this project:

  1. Use contenteditable as the interface the user interact with. This gives us all basic text-editing, selections, moving the cursor around, etc.
  2. Building a tree structure to represent the text that's being displayed within the contenteditable element. I started with having a parent node for the editor, 'block' nodes to represent chunks of text that are separated by new lines (ie <div>, <p>, etc.), 'word' nodes for chunks of text separated by white space, and char nodes for each character.
  3. After each text change to the editor, doing a quick diff between the displayed text and the text within the tree, and doing a targeted update to the tree.
  4. Each time the user attempts to make a formatting change (bold, underline, header, blockquote, etc.) figuring out what the selection was and either changing the attributes for all the 'char' or word nodes within the tree (for inline formatting, like bold, italic, underline, etc.) or changing the attributes of the 'block' nodes within the tree (header, blockquote, etc.).
  5. Writing a simple process that could convert the tree structure into HTML. Since it's already a tree, this should be a really simple inline traversal.
  6. Try leveraging some existing framework to detect the differences from the generated HTML and the HTML within the editor, and make targeted updates to the displayed HTML (like what React does with the VirtualDOM)

I was able to get through steps 1, 2, and 3, and write tests to cover all the existing code. The next step was to start updating attributes of the nodes within the tree whenever the user attempts to make a formatting change.

I'm not sure if having a level of nodes for 'words' (chunks of text separated by whitespace) is going to be useful/helpful in any way, but I wanted to try to structure the tree like that until I was able to prove that it wasn't helping anything. I'm also not sure if having a full tree structure is useful, but since all the other editors out there just having blocks of strings, and then separately track which indexes within the strings have which formatting, I wanted to try going about it a different way and see if it is better/worse from a performance and simplicity standpoint.