SkillAgentSearch skills...

Woofmark

:dog2: Barking up the DOM tree. A modular, progressive, and beautiful Markdown and HTML editor

Install / Use

/learn @bevacqua/Woofmark
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

woofmark

Barking up the DOM tree. A modular, progressive, and beautiful Markdown and HTML editor

Browser support includes every sane browser and IE9+.

Demo

[![woofmark-stompflow][3]][4]

Try out the [demo][4]!

Features

  • Small and focused
  • Progressive, enhance a raw <textarea>
  • Markdown, HTML, and WYSIWYG input modes
  • Text selection persists even across input modes!
  • Built in Undo and Redo
  • Entirely customizable styles
  • Bring your own parsers

<sub>Look and feel is meant to blend into your designs</sub>

Install

You can get it on npm.

npm install woofmark --save

Or bower, too.

bower install woofmark --save

woofmark.find(textarea)

Returns an editor object associated with a woofmark instance, or null if none exists for the textarea yet. When woofmark(textarea, options?) is called, woofmark.find will be used to look up an existing instance, which gets immediately returned.

woofmark(textarea, options?)

Adds rich editing capabilities to a textarea element. Returns an editor object.

options.parseMarkdown

A method that's called by woofmark whenever it needs to parse Markdown into HTML. This way, editing user input is decoupled from a Markdown parser. We suggest you use [megamark][1] to parse Markdown. This parser is used whenever the editor switches from Markdown mode into HTML or WYSIWYG mode.

woofmark(textarea, {
  parseMarkdown: require('megamark')
});

For optimal consistency, your parseMarkdown method should match whatever Markdown parsing you do on the server-side.

options.parseHTML

A method that's called by woofmark whenever it needs to parse HTML or a DOM tree into Markdown. This way, editing user input is decoupled from a DOM parser. We suggest you use [domador][2] to parse HTML and DOM. This parser is used whenever the editor switches to Markdown mode, and also when .value() is called while in the HTML or WYSIWYG modes.

woofmark(textarea, {
  parseHTML: require('domador')
});

If you're implementing your own parseHTML method, note that woofmark will call parseHTML with either a DOM element or a Markdown string.

While the parseHTML method will never map HTML back to the original Markdown in 100% cases, (because you can't really know if the original source was plain HTML or Markdown), it should strive to detokenize whatever special tokens you may allow in parseMarkdown, so that the user isn't met with inconsistent output when switching between the different editing modes.

A test of sufficiently good-citizen behavior can be found below. This is code for "Once an input Markdown string is parsed into HTML and back into Markdown, any further back-and-forth conversions should return the same output." Ensuring consistent back-and-forth is ensuring humans aren't confused when switching modes in the editor.

var parsed = parseHTML(parseMarkdown(original));
assert.equal(parseHTML(parseMarkdown(parsed)), parsed);

As an example, consider the following piece of Markdown:

Hey @bevacqua I _love_ [woofmark](https://github.com/bevacqua/woofmark)!

Without any custom Markdown hooks, it would translate to HTML similar to the following:

<p>Hey @bevacqua I <em>love</em> <a href="https://github.com/bevacqua/woofmark">woofmark</a>!</p>

However, suppose we were to add a tokenizer in our megamark configuration, like below:

woofmark(textarea, {
  parseMarkdown: function (input) {
    return require('megamark')(input, {
      tokenizers: [{
        token: /(^|\s)@([A-z]+)\b/g,
        transform: function (all, separator, id) {
          return separator + '<a href="/users/' + id + '">@' + id + '</a>';
        }
      }]
    });
  },
  parseHTML: require('domador')
});

Our HTML output would now look slightly different.

<p>Hey <a href="/users/bevacqua">@bevacqua</a> I <em>love</em> <a href="https://github.com/bevacqua/woofmark">woofmark</a>!</p>

The problem is that parseHTML doesn't know about the tokenizer, so if you were to convert the HTML back into Markdown, you'd get:

Hey [@bevacqua](/users/bevacqua) I _love_ [woofmark](https://github.com/bevacqua/woofmark)!

The solution is to let parseHTML "know" about the tokenizer, so to speak. In the example below, domador is now aware that links that start with @ should be converted back into something like @bevacqua.

woofmark(textarea, {
  parseMarkdown: function (input) {
    return require('megamark')(input, {
      tokenizers: [{
        token: /(^|\s)@([A-z]+)\b/g,
        transform: function (all, separator, id) {
          return separator + '<a href="/users/' + id + '">@' + id + '</a>';
        }
      }]
    });
  },
  parseHTML: function (input) {
    return require('domador')(input, {
      transform: function (el) {
        if (el.tagName === 'A' && el.innerHTML[0] === '@') {
          return el.innerHTML;
        }
      }
    });
  }
});

This kind of nudge to the Markdown compiler is particularly useful in simpler use cases where you'd want to preserve HTML elements entirely when they have CSS classes, as well.

Preserving Selection Across Input Modes

Note that both megamark and domador support a special option called markers, needed to preserve selection across input modes. Unless your parseHTML function supports this option, you'll lose that functionality when providing your own custom parsing functions. That's one of the reasons we strongly recommend using megamark and domador.

options.fencing

Prefers to wrap code blocks in "fences" (GitHub style) instead of indenting code blocks using four spaces. Defaults to true.

options.markdown

Enables Markdown user input mode. Defaults to true.

options.html

Enables HTML user input mode. Defaults to true.

options.wysiwyg

Enables WYSIWYG user input mode. Defaults to true.

options.defaultMode

Sets the default mode for the editor.

options.storage

Enables this particular instance woofmark to remember the user's preferred input mode. If enabled, the type of input mode will be persisted across browser refreshes using localStorage. You can pass in true if you'd like all instances to share the same localStorage property name, but you can also pass in the property name you want to use, directly. Useful for grouping preferences as you see fit.

<sub>Note that the mode saved by storage is always preferred over the default mode.</sub>

options.render.modes

This option can be set to a method that determines how to fill the Markdown, HTML, and WYSIWYG mode buttons. The method will be called once for each of them.

Example
woofmark(textarea, {
  render: {
    modes: function (button, id) {
      button.className = 'woofmark-mode-' + id;
    }
  }
});

options.render.commands

Same as options.render.modes but for command buttons. Called once on each button.

options.images

If you wish to set up file uploads, in addition to letting the user just paste a link to an image (which is always enabled), you can configure options.images like below.

{
  // http method to use, defaults to PUT
  method: 'PUT',

  // endpoint where the images will be uploaded to, required
  url: '/uploads',

  // image field key, passed to bureaucracy, which defaults to 'uploads'
  fieldKey: 'uploads',

  // optional additional form submission data, passed to `bureaucracy`
  formData: { description: 'A new image' },

  // optional options for `xhr` upload request, passed to `bureaucracy`
  xhrOptions: { headers: {} },

  // optional text describing the kind of files that can be uploaded
  restriction: 'GIF, JPG, and PNG images',

  // should return whether `e.dataTransfer.files[i]` is valid, defaults to a `true` operation
  validate: function isItAnImageFile (file) {
    return /^image\/(gif|png|p?jpe?g)$/i.test(file.type);
  }
}

woofmark expects a JSON response including a results property that's an array describing the success of each file upload. Each file's entry should include an href and a title:

{
  results: [
    { href: '/images/new.jpg', title: 'New image' }
  ]
}

For more information on file uploads, see bureaucracy.

options.attachments

Virtually the same as images, except an anchor <a> tag will be used instead of an image <img> tag.

To set the formatting of the inserted attachment link, set options.mergeHtmlAndAttachment; the default follows this format:

function mergeHtmlAndAttachment (chunks, link) {
  var linkText = chunks.selection || link.title;
  return {
    before: chunks.before,
    selection: '<a href="' + link.href + '">' + linkText + '</a>',
    after: chunks.after,
  };
}

editor

The editor API allows you to interact with woofmark editor instances. This is what you get back from woofmark(textarea, options) or woofmark.find(textarea).

editor.addCommand(combo, fn)

Binds a keyboard key combination such as cmd+shift+b to a method using [kanye][5]. Please note that you should always use cmd rather than ctrl. In non-OSX environments it'll be properly mapped to ctrl. When the combo is entered, fn(e, mode, chunks) will be called.

  • e is the original event object
  • mode can be markdown, html, or wysiwyg
  • chunks is a chunks object, describing the current state of the editor

In addition, fn is given a this context similar to that of Grunt tasks, where you can choose to do nothing and the command is assumed to be synchronous, or you can call this.async() and get back a done callback like in the example below.

editor.addCommand('cmd+j', function jump (e, mode, chunks) {
  var done = this.async();
  // TODO: async operation
  done();
});

When the command finishes, the editor

Related Skills

View on GitHub
GitHub Stars1.6k
CategoryDevelopment
Updated8d ago
Forks64

Languages

JavaScript

Security Score

100/100

Audited on Mar 24, 2026

No findings