Rolling My Own WYSIWYM Editor
Posted by Michael Shepanski, on December 13, 2009

While trying to create a decent comment system for this blog I was faced with a bunch of choices.  It seems everyone has a different idea of the best way to get this done.  In the end I decided to go with a WYSIWYM editor.  This was new to me since StackOverflow started using it.  Basically, its not a full blown WYSIWYG editor, but much simpler, and fit my needs quite well.

I also looked at a few markup languages to use.  The ones in question were textile, markdown, and restructured text.  I decided to go with MarkDown because of the simplicity to add code to a comment.

Why Not Use an Already Exising Editor?

There are currently two implementations of this type of editor that I know of.  And researching each one, I decided neither of seem to fit my bill. Or maybe I am too picky.

StackOverflow's WMD

The first one I thought of naturally was the StackOverflow implementation.  The story behind this editor is quite strange.  It was originally developed by John Fraser.  For reasons unknown to the interwebs, he has fell off the face of the planet around August 2008 according to Jeff Atwood of StackOverflow.  A reverse engineering effort of the code was underway.  And from my research, this effort and code is found in three different locations. This raises two questions in my mind:

The legality of the code seems to be a bit of a big question mark.  The original code was never released with a license, and since the original author is nowhere to be found, we can only assume it's ownership still belongs to him.  There is buzz that it was released under the MIT license, but that is just word of mouth from Jeff Atwood.

How maintainable will the reverse engineered version really be?  I took the code from github as it seems like the most up to date version of the code base to date (Oct, 2008).  I looked through a bit of source and found myself way in over my head.  With regular expressions longer than my widescreen and comments such as "my understanding of how the buttons and callbacks are stored in the array is incomplete." I think I'll leave this effort to the community to continue to maintain. ;)

MarkItUp! ..Dude!

Another implementation of a WYSIWYM editor is the MarkItUp! by a Jay Salvat from France.  At first glance, the website is quite impressive, easy to find what I am looking for, and its still being maintained.  I already feel this is going smoother than the previous version.

I like how he made the code modular and supports more than just the Markdown syntax.  What I didn't like was the implementation of the this in the actual code.  It's a great product and I may recommend it to others, but I wasn't completely thrilled.  Maybe it's the idea of extending regular expressions to prompt the user questions.  While cool and tricky, it's not a path I feel comfortable with.  Also the main engine itself didn't seem to offer many of the features I was looking for as it seems limited to about 8 or so different hard-coded options surrounding the current cursor position.

Moving Forward with My Own Implementation

Maybe I truly wasn't happy with the above implementations, or maybe I simply wanted to see if I could actually pull this off myself.  This is easily one of the bigger JavaScript projects I took on, thanks to jQuery, I feel much less afraid of actually coding in JavaScript.

I tried to keep the implementation modular or expandable as that's what I liked about MarkItUp!  I also tried my best to separate simple textarea logic from that of the actual MarkDown syntax. In the end I came up with a general solution I am happy with.

JQuery Plugin - jquery.wysisym.js

  • $.fn.wysiwym() - Main function extends textarea with surounding HTML and JavaScript.
  • Contains the WysiwymTextarea Object for use in defining a markup language.

WysiwymTextarea Object

This handles all the textarea specific operations and is separated from any one markup language.  It contains the following functions:

  • getLines() - Returns an array of Line objects containing: lineType, text, (bool) inSelection.
  • getNumLines() - Return the total number of lines in the textarea.
  • getLine(lineNum) - Return the text for the specified line number.
  • getSelectionRange() - Return the selection start and end lineNum and position.
  • getSelection() - Returns the current selected text.
  • getSelectionLength() - Return the length of Selection text.
  • selectionHasPrefix(text) - Return true if the current selection has the specified prefix text.
  • selectionHasSuffix(text) - Return true if the current selection has the specified suffix text.
  • prependToSelection(text) - Insert the specified text at at the cursor start position and make it selected.
  • appendToSelection(text) - Insert the specified text at at the cursor end position and make it selected.
  • insertSelectionPrefix(text) - Insert the specified text at at the cursor start position.
  • insertSelectionSuffix(text) - Insert the specified text at at the cursor end position.
  • removeSelectionPrefix(text) - Remove the specified prefix text if it matches with the passed in value.
  • removeSelectionSuffix(text) - Remove the specified suffix text if it matches with the passed in value.
  • selectionIsWrapped(prefix, suffix) - Return True if the selection is wrapped in the specified text.
  • wrapSelection(prefix, suffix) - Wrap the cursor or currently selected text with the specified text.
  • unwrapSelection(prefix, suffix) - Unwrap the selection by removeing the prefix and suffix text specified.
  • lineHasPrefix(lineNum, text) - Return True if the line has the specified prefix.
  • insertLinePrefix(lineNum, text) - Add the Prefix to the specified line.
  • removeLinePrefix(lineNum, text) - Remove the specified prefix text if it matches with the passed in value.
  • insertLineSuffix(lineNum, text) - Append the suffix to the end of the specified line.
  • selectionLinesHavePrefix(text) - Return True if all lines in the selection have the specified prefix.
  • insertSelectionLinesPrefix(text) - Add the Prefix Text to all lines in the selection.
  • removeSelectionLinesPrefix(text) - Add the Prefix Text to all lines in the selection.
  • update() - Update the HTML textarea to reflect all changes made within this class.

WysiwymMarkdown Object

With a proper implementation of the above textarea object.  It's much easier manage and create a MarkDown object to keep track of changes and updates to the actual text in the text area.  The object is pretty simple with the following functions:

  • Definitions for each Button calling one of the callback functions below.
  • buttonLink() - Wraps the Selection with the specified markdown link syntax, [This link](http://example.net/).
  • genericSpan() - Wraps the Selection with the specified text defined on the button.
  • genericLine() - Starts the selected lines with the specified text defined on the button.
  • assertBlockWrap() - Asserts the selected lines all contain the specified prefix.
  • autoIndent() - Auto indent the next line with the prefix from the previous line.

Conclusion

While quite not as many features as the StackOverflow version, and not as pretty looking as the MarkItUp version, I am feel like I have a final result I am very happy with.  And of course, because I thought of it as something I most likely wouldn't be able to accomplish, I am quite proud of the outcome.

As with any new project there will be many kinks to work out.  It's a nice feeling to be one of only three implementations of wysiwym editors out there.

Download

This is all currently wrapped in a Django Application.  And with initial help from Brandon Konkle, I was able to add the Ajax goodness to the built in Django Comment System.  You can download version 1.0 below.  Maybe someday in the near future I'll get it added to Google Code.

4 Comments
January 14, 2010 at 05:01 a.m.

That was an inspiring post, Great advice to use a WYSIWYM Editor Thanks for bringing this up

harald
January 19, 2010 at 10:01 a.m.

great post ... i'm looking into the same for several weeks now. your post is a good source for inspiration. thanks!

July 9, 2010 at 06:07 a.m.

First of all, thanks, this was awesome. I like it better than wmd because:

  • code is waaaaay better, the documentation for setting it up wasn't great but I figured it out pretty easy by just looking at the code
  • it auto-bullets. win. I'm using it in a site where that will be used a lot.

To share my experiences, this is all it took to implement outside of a django/comment environment (using Rails, although that doesn't matter):

$('#recipe_directions').wysiwym(WysiwymMarkdown, 'wysiwymComment');
function markupPreviewLoop() {
// Update the Comment Markup
var markupText = $('#directions').val().escapeHTML();
if (markupText == '') { markupText = " "; }
if (typeof(previousMarkupText) == "undefined" || markupText != previousMarkupText) {
    var converter = new Showdown.converter();
    var markupHtml = converter.makeHtml(markupText);
    $('#directions_preview').html(markupHtml);
    previousMarkupText = markupText;
  }
  setTimeout(markupPreviewLoop, 100);
}
markupPreviewLoop();

Note that this went in my $(document).ready(function() {... block.

Again, nice work.

Also, a design detail I'm trying out that I haven't seen people use is putting the preview above the textarea. It feels like a wysiwyg, although it might be a little weird... I end up watching the page and not the textarea as I type until I have a formatting problem.

Michael Shepanski
August 3, 2010 at 09:08 p.m.

@Woody Peterson - Thanks for the comments. I actually just started on a new layout for this site. I felt I never posted here because I always secreted hated the way my website looks.

How is this related? -- I am going to be going through this code again, and attempting to turn the wysiwym portion into a more official jQuery plugin, with much better documentation and some demos to help set it up. Something that has never been my strong suit.

I think I also like the idea of putting the live preview above for the single reason that it is where it will actually end up after posting the comment. Perhaps a different background or warning flag will be needed to remind the user that their post was not in fact posted yet.

Thanks for the feedback, keep an eye out for an official code repo and some better docs in the next month or so.

Leave a Comment

Michael Shepanski

I am a software engineer working in the Boston area. I love Python, and I love tinkering with code during my spare time.