Contenteditable

Rich text editing in the browser!







a short story by Eric Wood
@eric_b_wood
eric@ericwood.org

So...What is it?


Simplest example:
<div contenteditable="true">  <h1>Edit me!</h1></div>






Neat!

Creating a WYSIWYG for SPiceworks

The journey begins...

Let's not reinvent the wheel!


People have tackled this before. Let's use their stuff.

Neat, wysihtml5 looks like a cool library.




Uh oh

Roadblock #1


From our todo list:
"Admins should be able to edit the post's raw HTML"

wysihtml5:
"Stop that. Let ME decide for you. I'll remove those tags, kthx."


(lots of Github threads about it, maintainer is hardheaded and won't budge)

Solution #1

Modify wysihtml5 to make it optional

 if(clean) {  // this is the original method for parsing elements
  // if we want to use it, set "clean" to true (only done for cleaning up pasted stuff)
  returnValue = this.config.parser(htmlOrElement, this.config.parserRules, this.composer.sandbox.getDocument(), this.config.cleanUp);
} else {
  // just return it, don't bother cleaning anything up
  returnValue = htmlOrElement;
}

Copy and Pasting

Users will copy and paste things from EVERYWHERE.

contenteditable will always grab the rich text version from the clipboard.

tl;dr things are going to get messy very quickly

example: http://pastebin.com/DX7wwU85

Ideal solution: paste event

There's a paste event we can take over!

 elem.onpaste = function(event) {  var html = event.clipboardData.getData('html');  var text = event.clipboardData.getData('text');};

Caveats:
  • ZERO support in IE
  • Chrome removes formatting in text version


Hack #1

The "phantom textarea" hack

Hack #1 Caveats

  • VERY timing-dependent (frail)
  • Works great in the lab...not so much in real life
  • It's just...gross...

Hack #2

The "nuclear war" approach

  • paste event:
    • preserve editor contents / caret info
    • NUKE IT
  • (paste event occurs)
  • use wysihtml5 parser to reformat contents
  • grab contents
  • restore previous contents, caret position
  • insert HTML of the cleaned pasted content
  • cry

Hack #2 caveats

  • Dealing with the caret is flaky (at best)
    • Have fun with IE!
  • Overriding caret behavior can ruin UX








(this one never ended up working well cross-browser)

Let's cut our losses

Sure, clipboardData doesn't work in IE, but we can still use it
if(event.clipboardData) {
  event.preventDefault();

  var text;
  var html = event.clipboardData.getData('text/html');

  if(html === '') {
    text = event.clipboardData.getData('text/plain');
  } else {
    text = wysihtml5.dom.htmlToText(html);
  }

  var result = _.map(text.split('\n'), function(line) {
    return '<p>' + line + '</p>';
  });

  result = result.join('\n');
  that.commands.exec('insertHTML', result);
}

HTML to plaintext conversion

Preserving the important stuff

Functions like text() have no regard for whitespace :(

This is a deal breaker!

Implementation

(let the browser do the work for you)

wysihtml5.dom.htmlToText = function(html) {
  var wrapper = $('<div style="display: none;" />');
  wrapper.html(html);
  wrapper.appendTo('body');

  var contents = wrapper.find('*');

  if(!contents.length) {
    return wrapper.text();
  }

  contents.parent().find('br').replaceWith('\n');

  var text = [];
  contents.each(function() {
    var $el = $(this);
    var display = $el.css('display');

    if(display === 'block') {
      text.push($el.text());
    }
  });

  if(!text.length && contents.length) {
    text.push(wrapper.text());
  }

  wrapper.remove();

  text = _.filter(text, function(t) { return /\S/.test(t); });

  return text.join('\n');
};

Made with Slides.com