• Stars
    star
    1,616
  • Rank 27,791 (Top 0.6 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 9 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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

Try out the demo!

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

Look and feel is meant to blend into your designs

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 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 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.

Note that the mode saved by storage is always preferred over the default mode.

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. 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 will recover focus, and whatever changes where made to the chunks object will be applied to the editor. All commands performed by woofmark work this way, so please take a look at the source code if you want to implement your own commands.

editor.addCommandButton(id, combo?, fn)

Adds a button to the editor using an id and an event handler. When the button is pressed, fn(e, mode, chunks) will be called with the same arguments as the ones passed if using editor.addCommand(combo, fn).

You can optionally pass in a combo, in which case editor.addCommand(combo, fn) will be called, in addition to creating the command button.

editor.runCommand(fn)

If you just want to run a command without setting up a keyboard shortcut or a button, you can use this method. Note that there won't be any e event argument in this case, you'll only get chunks, mode passed to fn. You can still run the command asynchronously using this.async(). Note that the argument order chunks, mode is the reverse of that passed to addCommand (mode, chunks).

editor.parseMarkdown()

This is the same method passed as an option.

editor.parseHTML()

This is the same method passed as an option.

editor.destroy()

Destroys the editor instance, removing all event handlers. The editor is reverted to markdown mode, and assigned the proper Markdown source code if needed. Then we go back to being a plain old and dull <textarea> element.

editor.value(text)

If optional Markdown string text is provided, it is used to overwrite the current editor content, parsing into HTML if necessary. Regardless of whether text is provided, value() returns the current Markdown value for the editor.

editor.editable

If options.wysiwyg then this will be the contentEditable <div>. Otherwise it'll be set to null.

editor.mode

The current mode for the editor. Can be markdown, html, or wysiwyg.

editor.setMode(mode)

Sets the current mode of the editor.

editor.showLinkDialog()

Shows the insert link dialog as if the button to insert a link had been clicked.

editor.showImageDialog()

Shows the insert image dialog as if the button to insert a image had been clicked.

editor.showAttachmentDialog()

Shows the insert attachment dialog as if the button to insert a attachment had been clicked.

editor.history

Exposes a few methods to gain insight into the operation history for the editor instance.

editor.history.undo()

Undo the last operation.

editor.history.redo()

Re-applies the most recently undone operation.

editor.history.canUndo()

Returns a boolean value indicating whether there are any operations left to undo.

editor.history.canRedo()

Returns a boolean value indicating whether there are any operations left to redo.

chunks

Please ignore undocumented functionality in the chunks object.

Describes the current state of the editor. This is the context you get on command event handlers such as the method passed to editor.runCommand.

Modifying the values in a chunks object during a command will result in changes to the user input in such a way that Undo and Redo can be taken care of automatically on your behalf, basically by going back to the previous (or next) chunks state in the editor's internal history.

chunks.selection

The currently selected piece of text in the editor, regardless of input mode.

chunks.before

The text that comes before chunks.selection in the editor.

chunks.after

The text that comes after chunks.selection in the editor.

chunks.scrollTop

The current scrollTop for the element. Useful to restore later in action history navigation.

chunks.trim(remove?)

Moves whitespace on either end of chunks.selection to chunks.before and chunks.after respectively. If remove has been set to true, the whitespace in the selection is discarded instead.

woofmark.strings

To enable localization, woofmark.strings exposes all user-facing messages used in woofmark. Make sure not to replace woofmark.strings with a new object, as a reference to it is cached during module load.

Usage with horsey

See banksy to integrate horsey into woofmark.

License

MIT

More Repositories

1

dragula

👌 Drag and drop so simple it hurts
JavaScript
21,766
star
2

es6

🌟 ES6 Overview in 350 Bullet Points
4,328
star
3

rome

📆 Customizable date (and time) picker. Opt-in UI, no jQuery!
JavaScript
2,913
star
4

js

🎨 A JavaScript Quality Guide
2,874
star
5

fuzzysearch

🔮 Tiny and blazing-fast fuzzy search in JavaScript
JavaScript
2,698
star
6

promisees

📨 Promise visualization playground for the adventurous
JavaScript
1,202
star
7

horsey

🐴 Progressive and customizable autocomplete component
JavaScript
1,163
star
8

css

🎨 CSS: The Good Parts
992
star
9

react-dragula

👌 Drag and drop so simple it hurts
JavaScript
990
star
10

contra

🏄 Asynchronous flow control with a functional taste to it
JavaScript
771
star
11

shots

🔫 pull down the entire Internet into a single animated gif.
JavaScript
725
star
12

insignia

🔖 Customizable tag input. Progressive. No non-sense!
JavaScript
673
star
13

campaign

💌 Compose responsive email templates easily, fill them with models, and send them out.
JavaScript
641
star
14

perfschool

🌊 Navigate the #perfmatters salt marsh waters in this NodeSchool workshopper
CSS
629
star
15

local-storage

🛅 A simplified localStorage API that just works
JavaScript
522
star
16

angularjs-dragula

👌 Drag and drop so simple it hurts
HTML
510
star
17

reads

📚 A list of physical books I own and read
486
star
18

insane

😾 Lean and configurable whitelist-oriented HTML sanitizer
JavaScript
438
star
19

hget

👏 Render websites in plain text from your terminal
HTML
334
star
20

hit-that

✊ Render beautiful pixel perfect representations of websites in your terminal
JavaScript
331
star
21

swivel

Message passing between ServiceWorker and pages made simple
JavaScript
293
star
22

hash-sum

🎊 Blazing fast unique hash generator
JavaScript
292
star
23

dominus

💉 Lean DOM Manipulation
JavaScript
277
star
24

trunc-html

📐 truncate html by text length
JavaScript
220
star
25

grunt-ec2

📦 Create, deploy to, and shutdown Amazon EC2 instances
JavaScript
190
star
26

beautify-text

✒️ Automated typographic quotation and punctuation marks
JavaScript
185
star
27

sixflix

🎬 Detects whether a host environment supports ES6. Algorithm by Netflix.
JavaScript
174
star
28

twitter-for-github

🐥 Twitter handles for GitHub
JavaScript
146
star
29

awesome-badges

🏆 Awesome, badges!
JavaScript
124
star
30

prop-tc39

Scraping microservice for TC39 proposals 😸
JavaScript
107
star
31

megamark

😻 Markdown with easy tokenization, a fast highlighter, and a lean HTML sanitizer
JavaScript
102
star
32

diferente

User-friendly virtual DOM diffing
JavaScript
95
star
33

domador

😼 Dependency-free and lean DOM parser that outputs Markdown
JavaScript
83
star
34

proposal-undefined-coalescing-operator

Undefined Coalescing Operator proposal for ECMAScript
76
star
35

dotfiles

💠 Yay! @bevacqua does dotfiles \o/
Shell
75
star
36

assignment

😿 Assign property objects onto other objects, recursively
JavaScript
73
star
37

sektor

📍 A slim alternative to jQuery's Sizzle
JavaScript
65
star
38

map-tag

🏷 Map template literal expression interpolations with ease.
JavaScript
65
star
39

unbox

Unbox a node application with a well-designed build-oriented approach in minutes
JavaScript
61
star
40

hint

Awesome tooltips at your fingertips
JavaScript
60
star
41

but

🛰 But expands your functional horizons to the edge of the universe
JavaScript
59
star
42

kanye

Smash your keyboards with ease
JavaScript
55
star
43

correcthorse

See XKCD for reference
JavaScript
52
star
44

easymap

🗺 simplified use of Google Maps API to render a bunch of markers.
JavaScript
52
star
45

flickr-cats

A demo page using the Flickr API, ServiceWorker, and plain JavaScript
HTML
49
star
46

ruta3

Route matcher devised for shared rendering JavaScript applications
JavaScript
46
star
47

poser

📯 Create clean arrays, or anything else, which you can safely extend
JavaScript
45
star
48

hubby

👨 Hubby is a lowly attempt to describe public GitHub activity in natural language
JavaScript
45
star
49

baal

🐳 Automated, autoscaled, zero-downtime, immutable deployments using plain old bash, Packer, nginx, Node.js, and AWS. Made easy.
Shell
44
star
50

lipstick

💄 sticky sessions for Node.js clustering done responsibly
JavaScript
43
star
51

crossvent

🌏 Cross-platform browser event handling
JavaScript
41
star
52

lazyjs

The minimalist JavaScript loader
JavaScript
39
star
53

spritesmith-cli

😳 Adds a CLI to the spritesmith module
JavaScript
38
star
54

gulp-jsfuck

Fuck JavaScript and obfuscate it using only 6 characters ()+[]!
JavaScript
37
star
55

measly

A measly wrapper around XHR to help you contain your requests
JavaScript
36
star
56

keynote-extractor

🎁 Extract Keynote presentations to JSON and Markdown using a simple script.
AppleScript
35
star
57

hyperterm-working-directory

🖥👷📂 Adds a default working directory setting. Opens new tabs using that working directory.
JavaScript
34
star
58

gitcanvas

🏛 Use your GitHub account's commit history as a canvas. Express the artist in you!
JavaScript
34
star
59

cave

Remove critical CSS from your stylesheet after inlining it in your pages
JavaScript
33
star
60

scrape-metadata

📜 HTML metadata scraper
JavaScript
31
star
61

feeds

🍎 RSS feeds I follow and maintain
31
star
62

suchjs

Provides essential jQuery-like methods for your evergreen browser, in under 200 lines of code. Such small.
JavaScript
30
star
63

ponyedit

An interface between contentEditable and your UI
JavaScript
29
star
64

grunt-grunt

Spawn Grunt tasks in other Gruntfiles easily from a Grunt task
JavaScript
29
star
65

ultramarked

Marked with built-in syntax highlighting and input sanitizing that doesn't encode all HTML.
JavaScript
28
star
66

sell

💰 Cross-browser text input selection made simple
JavaScript
28
star
67

icons

Free icon sets gathered around the open web
27
star
68

insert-rule

Insert rules into a stylesheet programatically with a simple API
JavaScript
26
star
69

hose

Redirect any domain to localhost for convenience or productivity!
JavaScript
26
star
70

ponymark

Next-generation PageDown fork
JavaScript
25
star
71

omnibox

Fast url parsing with a tiny footprint and extensive browser support
JavaScript
25
star
72

estimate

Calculate remaining reading time estimates in real-time
JavaScript
24
star
73

seleccion

💵 A getSelection polyfill and a setSelection ranch dressing
JavaScript
24
star
74

node-emoji-random

Creates a random emoji string. This is as useless as it gets.
JavaScript
24
star
75

bullseye

🎯 Attach elements onto their target
JavaScript
23
star
76

vectorcam

🎥 Record gifs out of <svg> elements painlessly
JavaScript
22
star
77

paqui

Dead simple, packager-agnostic package management solution for front-end component developers
JavaScript
22
star
78

sluggish

🐍 Sluggish slug generator that works universally
JavaScript
22
star
79

grunt-ngdoc

Grunt task for generating documentation using AngularJS' @ngdoc comments
JavaScript
20
star
80

ftco

⚡ Browser extension that unshortens t.co links in TweetDeck and Twitter
JavaScript
19
star
81

jadum

💍 A lean Jade compiler that understands Browserify and reuses partials
JavaScript
19
star
82

trunc-text

📏 truncate text by length, doesn't cut words
JavaScript
16
star
83

flexarea

Pretty flexible areas!
JavaScript
16
star
84

music-manager

📻 Manages a list of favorite artists and opens playlists on youtube.
JavaScript
16
star
85

grunt-integration

Run Integration Tests using Selenium, Mocha, a Server, and a Browser
JavaScript
15
star
86

apartment

🏡 Remove undesirable properties from a piece of css
JavaScript
14
star
87

mongotape

Run integration tests using mongoose and tape
JavaScript
14
star
88

rehearsal

Persist standard input to a file, then simulate real-time program execution.
JavaScript
13
star
89

queso

Turn a plain object into a query string
JavaScript
13
star
90

bitfin

🏦 Finance utility for Bitstamp
JavaScript
13
star
91

grunt-spriting-example

An example on how to seamlessly use spritesheets with Grunt.
12
star
92

pandora-box

🐼 What will it be?
JavaScript
12
star
93

artists

🎤 Big list of artists pulled from Wikipedia.
JavaScript
11
star
94

reaver

Minimal asset hashing CLI and API
JavaScript
11
star
95

atoa

Creates a true array based on `arraylike`, starting at `startIndex`.
JavaScript
10
star
96

twitter-leads

🐦 Pull list of leads from a Twitter Ads Lead Generation Card
JavaScript
10
star
97

BridgeStack

.NET StackExchange API v2.0 client library wrapper
C#
10
star
98

ama

📖 A repository to ask @bevacqua anything.
10
star
99

virtual-host

Create virtual, self-contained `connect` or `express` applications using a very simple API.
JavaScript
10
star
100

banksy

🌇 Street art between woofmark and horsey
JavaScript
10
star