', '**Hello**\n\n**world**', 'strong with two newlines'],
35 | ]);
36 | });
37 |
38 | test('code', function() {
39 | runTestCases([
40 | ['print()', '`print()`']
41 | ]);
42 | });
43 |
44 | test('headings', function() {
45 | runTestCases([
46 | ['Hello world
', '# Hello world', 'h1'],
47 | ['Hello world
', '### Hello world', 'h3'],
48 | ['Hello world
', '###### Hello world', 'h6'],
49 | ['Hello world
', '#### _Hello_ world', 'h4 with child'],
50 | ['Hello world', 'Hello world', 'invalid heading']
51 | ]);
52 | });
53 |
54 | test('horizontal rules', function() {
55 | runTestCases([
56 | ['
', '* * *', 'hr'],
57 | ['
', '* * *', 'open/closed hr']
58 | ]);
59 | });
60 |
61 | test('line breaks', function() {
62 | runTestCases([
63 | ['Hello
world', 'Hello \nworld']
64 | ]);
65 | });
66 |
67 | test('images', function() {
68 | runTestCases([
69 | ['
', '', 'img with no alt'],
70 | ['
', '', 'img with relative src'],
71 | ['
', '', 'img with alt'],
72 | ['
', '', 'img no src']
73 | ]);
74 | });
75 |
76 | test('anchors', function() {
77 | runTestCases([
78 | ['About us', '[About us](http://example.com/about)', 'a'],
79 | ['About us', '[About us](http://example.com/about "About this company")', 'a with title'],
80 | ['About us', 'About us', 'a with no src'],
81 | ['About us', '[About us](http://example.com/about)', 'with a span']
82 | ]);
83 | });
84 |
85 | test('pre/code blocks', function() {
86 | runTestCases([
87 | [
88 | ['def hello_world',
89 | ' # 42 < 9001',
90 | ' "Hello world!"',
91 | 'end
'].join('\n'),
92 |
93 | [' def hello_world',
94 | ' # 42 < 9001',
95 | ' "Hello world!"',
96 | ' end'].join('\n')
97 | ],
98 | [
99 | ['def foo',
100 | ' # 42 < 9001',
101 | ' \'Hello world!\'',
102 | 'end
',
103 | 'next:
',
104 | 'def bar',
105 | ' # 42 < 9001',
106 | ' \'Hello world!\'',
107 | 'end
'].join('\n'),
108 |
109 | [' def foo',
110 | ' # 42 < 9001',
111 | ' \'Hello world!\'',
112 | ' end',
113 | '',
114 | 'next:',
115 | '',
116 | ' def bar',
117 | ' # 42 < 9001',
118 | ' \'Hello world!\'',
119 | ' end'].join('\n'),
120 |
121 | 'Multiple pre/code blocks'
122 | ],
123 | ['preformatted
', 'preformatted
', 'Plain pre']
124 | ]);
125 | });
126 |
127 | test('lists', function() {
128 | runTestCases([
129 | ['1986. What a great season.', '1986\\. What a great season.', 'ol triggers are escaped'],
130 | ['\n\t- Hello world
\n\t- Foo bar
\n
', '1. Hello world\n2. Foo bar', 'ol'],
131 | ['\n\t- Hello world
\n\t- Foo bar
\n
', '* Hello world\n* Foo bar', 'ul'],
132 | [
133 | ['',
134 | ' - Hello world
',
135 | ' - Lorem ipsum
',
136 | '
',
137 | '',
138 | ' - Hello world
',
139 | ' - Lorem ipsum
',
140 | '
'].join('\n'),
141 |
142 | ['* Hello world',
143 | '* Lorem ipsum',
144 | '',
145 | '* Hello world',
146 | '* Lorem ipsum'].join('\n'),
147 |
148 | 'Multiple uls'
149 | ],
150 | [
151 | '',
152 | '* Hello world\n\n* Lorem ipsum',
153 | 'ul with p'
154 | ],
155 | [
156 | ['',
157 | ' - ',
158 | '
This is a list item with two paragraphs.
',
159 | ' Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
',
160 | ' ',
161 | ' - ',
162 | '
Suspendisse id sem consectetuer libero luctus adipiscing.
',
163 | ' ',
164 | '
'].join('\n'),
165 |
166 | ['1. This is a list item with two paragraphs.',
167 | '',
168 | ' Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.',
169 | '',
170 | '2. Suspendisse id sem consectetuer libero luctus adipiscing.'].join('\n'),
171 |
172 | 'ol with multiple ps'
173 | ],
174 | [
175 | ['',
176 | ' - This is a list item at root level
',
177 | ' - This is another item at root level
',
178 | ' - ',
179 | '
',
180 | ' - This is a nested list item
',
181 | ' - This is another nested list item
',
182 | ' - ',
183 | '
',
184 | ' - This is a deeply nested list item
',
185 | ' - This is another deeply nested list item
',
186 | ' - This is a third deeply nested list item
',
187 | '
',
188 | ' ',
189 | '
',
190 | ' ',
191 | ' - This is a third item at root level
',
192 | '
'].join('\n'),
193 |
194 | ['* This is a list item at root level',
195 | '* This is another item at root level',
196 | '* * This is a nested list item',
197 | ' * This is another nested list item',
198 | ' * * This is a deeply nested list item',
199 | ' * This is another deeply nested list item',
200 | ' * This is a third deeply nested list item',
201 | '* This is a third item at root level'].join('\n'),
202 |
203 | 'Nested uls'
204 | ],
205 | [
206 | ['',
207 | ' - This is a list item at root level
',
208 | ' - This is another item at root level
',
209 | ' - ',
210 | '
',
211 | ' - This is a nested list item
',
212 | ' - This is another nested list item
',
213 | ' - ',
214 | '
',
215 | ' - This is a deeply nested list item
',
216 | ' - This is another deeply nested list item
',
217 | ' - This is a third deeply nested list item
',
218 | '
',
219 | ' ',
220 | '
',
221 | ' ',
222 | ' - This is a third item at root level
',
223 | '
'].join('\n'),
224 |
225 | ['* This is a list item at root level',
226 | '* This is another item at root level',
227 | '* 1. This is a nested list item',
228 | ' 2. This is another nested list item',
229 | ' 3. * This is a deeply nested list item',
230 | ' * This is another deeply nested list item',
231 | ' * This is a third deeply nested list item',
232 | '* This is a third item at root level'].join('\n'),
233 |
234 | 'Nested ols'
235 | ],
236 | [
237 | [''].join('\n'),
245 |
246 | ['* A list item with a blockquote:',
247 | '',
248 | ' > This is a blockquote inside a list item.'].join('\n'),
249 |
250 | 'ul with blockquote'
251 | ]
252 | ]);
253 | });
254 |
255 | test('blockquotes', function() {
256 | runTestCases([
257 | [
258 | ['',
259 | ' This is a blockquote with two paragraphs.
',
260 | '',
261 | ' Donec sit amet nisl.
',
262 | '
'].join('\n'),
263 |
264 | ['> This is a blockquote with two paragraphs.',
265 | '> ',
266 | '> Donec sit amet nisl.'].join('\n'),
267 |
268 | 'blockquote with two ps'
269 | ],
270 | [
271 | ['',
272 | ' This is the first level of quoting.
',
273 | '',
274 | ' ',
275 | ' This is nested blockquote.
',
276 | '
',
277 | '',
278 | ' Back to the first level.
',
279 | '
'].join('\n'),
280 |
281 | ['> This is the first level of quoting.',
282 | '> ',
283 | '> > This is nested blockquote.',
284 | '> ',
285 | '> Back to the first level.'].join('\n'),
286 |
287 | 'Nested blockquotes'
288 | ],
289 | [
290 | ['',
291 | ' This is a header.
',
292 | ' ',
293 | ' - This is the first list item.
',
294 | ' - This is the second list item.
',
295 | '
',
296 | ' Here\'s some example code:
',
297 | ' return 1 < 2 ? shell_exec(\'echo $input | $markdown_script\') : 0;
',
298 | '
'].join('\n'),
299 |
300 | ['> ## This is a header.',
301 | '> ',
302 | '> 1. This is the first list item.',
303 | '> 2. This is the second list item.',
304 | '> ',
305 | '> Here\'s some example code:',
306 | '> ',
307 | '> return 1 < 2 ? shell_exec(\'echo $input | $markdown_script\') : 0;'].join('\n'),
308 |
309 | 'html in blockquote'
310 | ]
311 | ]);
312 | });
313 |
314 | test('block-level', function () {
315 | runTestCases([
316 | ['Hello
world
', 'Hello
\n\nworld
', 'divs separated by \\n\\n'],
317 | ['hello
', '_hello_
']
318 | ]);
319 | });
320 |
321 | test('comments', function () {
322 | equal(toMarkdown(''), '', 'comments removed');
323 | });
324 |
325 | test('leading/trailing whitespace', function() {
326 | runTestCases([
327 | [
328 | 'I need more spaces!
',
329 | 'I [need](http://example.com) [more](http://www.example.com) spaces!',
330 | 'Whitespace between inline elements'
331 | ],
332 | ['\n Header text', '# Header text', 'Leading whitespace in h1'],
333 | [
334 | ['',
335 | ' - Chapter One',
336 | '
',
337 | ' - Section One
',
338 | ' - Section Two
',
339 | ' - Section Three
',
340 | '
',
341 | ' ',
342 | ' - Chapter Two
',
343 | ' - Chapter Three
',
344 | '
'].join('\n'),
345 |
346 | ['1. Chapter One',
347 | ' 1. Section One',
348 | ' 2. Section Two',
349 | ' 3. Section Three',
350 | '2. Chapter Two',
351 | '3. Chapter Three'].join('\n'),
352 |
353 | 'Trailing whitespace in li'
354 | ],
355 | [
356 | ['',
357 | ' - ', // Multilined
358 | ' Foo ',
359 | '
',
360 | ' - ', // Bizarre formatting
361 | ' Bar
',
362 | ' - Baz
',
363 | '
',
364 | '',
365 | ' - Hello',
366 | ' world',
367 | '
',
368 | '
'].join('\n'),
369 |
370 | ['* Foo',
371 | '* **Bar**',
372 | '* Baz',
373 | '',
374 | '1. Hello world'].join('\n')
375 | ],
376 | [
377 | 'Hello world. Foo bar ',
378 | 'Hello world. _Foo_ **bar**',
379 | 'Whitespace in inline elements'
380 | ],
381 | [
382 | '
Hello world.
',
383 | '#  Hello world.',
384 | 'Whitespace and void elements'
385 | ]
386 | ]);
387 | });
388 |
389 | test('blank', function () {
390 | runTestCases([
391 | ['
', '', 'Blank div'],
392 | ['', '', 'Blank em'],
393 | ['
', '', 'Blank strong with br'],
394 | ['', '[](#foo)', 'Blank a'],
395 | ]);
396 | });
397 |
398 | test('custom converters', function() {
399 | var html, converter, md = '*Hello world*';
400 | var replacement = function (innerHTML) {
401 | return '*' + innerHTML + '*';
402 | };
403 |
404 | html = 'Hello world';
405 | converter = {
406 | filter: 'span',
407 | replacement: replacement
408 | };
409 | equal(toMarkdown(html, {converters: [converter]}), md, 'Custom filter string');
410 |
411 | html = 'Hello world';
412 | converter = {
413 | filter: ['span'],
414 | replacement: replacement
415 | };
416 | equal(toMarkdown(html, {converters: [converter]}), md, 'Custom filter array');
417 |
418 | html = 'Hello world';
419 | converter = {
420 | filter: function (node) {
421 | return node.tagName === 'SPAN' && /italic/i.test(node.style.fontStyle);
422 | },
423 | replacement: replacement
424 | };
425 | equal(toMarkdown(html, {converters: [converter]}), md, 'Custom filter function');
426 | });
427 |
428 | test('invalid input', function () {
429 | throws(function () { toMarkdown(null); }, /null is not a string/, 'null input');
430 | throws(function () { toMarkdown(void(0)); }, /undefined is not a string/, 'undefined input');
431 |
432 | throws(function () { toMarkdown(null); }, function (e) {
433 | return e.name === 'TypeError';
434 | }, 'error type');
435 | });
436 |
437 | asyncTest('img[onerror]', 1, function () {
438 | start();
439 | equal(toMarkdown('>\'>">
'), '>\'>">', 'We expect img[onerror] functions not to run');
440 | });
441 |
--------------------------------------------------------------------------------
/dist/to-markdown.js:
--------------------------------------------------------------------------------
1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toMarkdown = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1) {
84 | newDoc.documentElement.innerHTML = string;
85 | }
86 | else {
87 | newDoc.body.innerHTML = string;
88 | }
89 | return newDoc;
90 | };
91 | return Parser;
92 | }
93 |
94 | var HtmlParser = canParseHtml() ? _window.DOMParser : createHtmlParser();
95 |
96 | function htmlToDom(string) {
97 | var tree = new HtmlParser().parseFromString(string, 'text/html');
98 | collapse(tree, isBlock);
99 | return tree;
100 | }
101 |
102 | /*
103 | * Flattens DOM tree into single array
104 | */
105 |
106 | function bfsOrder(node) {
107 | var inqueue = [node],
108 | outqueue = [],
109 | elem, children, i;
110 |
111 | while (inqueue.length > 0) {
112 | elem = inqueue.shift();
113 | outqueue.push(elem);
114 | children = elem.childNodes;
115 | for (i = 0 ; i < children.length; i++) {
116 | if (children[i].nodeType === 1) { inqueue.push(children[i]); }
117 | }
118 | }
119 | outqueue.shift();
120 | return outqueue;
121 | }
122 |
123 | /*
124 | * Contructs a Markdown string of replacement text for a given node
125 | */
126 |
127 | function getContent(node) {
128 | var text = '';
129 | for (var i = 0; i < node.childNodes.length; i++) {
130 | if (node.childNodes[i].nodeType === 1) {
131 | text += node.childNodes[i]._replacement;
132 | }
133 | else if (node.childNodes[i].nodeType === 3) {
134 | text += node.childNodes[i].data;
135 | }
136 | else { continue; }
137 | }
138 | return text;
139 | }
140 |
141 | /*
142 | * Returns the HTML string of an element with its contents converted
143 | */
144 |
145 | function outer(node, content) {
146 | return node.cloneNode(false).outerHTML.replace('><', '>'+ content +'<');
147 | }
148 |
149 | function canConvert(node, filter) {
150 | if (typeof filter === 'string') {
151 | return filter === node.nodeName.toLowerCase();
152 | }
153 | if (Array.isArray(filter)) {
154 | return filter.indexOf(node.nodeName.toLowerCase()) !== -1;
155 | }
156 | else if (typeof filter === 'function') {
157 | return filter.call(toMarkdown, node);
158 | }
159 | else {
160 | throw new TypeError('`filter` needs to be a string, array, or function');
161 | }
162 | }
163 |
164 | function isFlankedByWhitespace(side, node) {
165 | var sibling, regExp, isFlanked;
166 |
167 | if (side === 'left') {
168 | sibling = node.previousSibling;
169 | regExp = / $/;
170 | }
171 | else {
172 | sibling = node.nextSibling;
173 | regExp = /^ /;
174 | }
175 |
176 | if (sibling) {
177 | if (sibling.nodeType === 3) {
178 | isFlanked = regExp.test(sibling.nodeValue);
179 | }
180 | else if(sibling.nodeType === 1 && !isBlock(sibling)) {
181 | isFlanked = regExp.test(sibling.textContent);
182 | }
183 | }
184 | return isFlanked;
185 | }
186 |
187 | function flankingWhitespace(node) {
188 | var leading = '', trailing = '';
189 |
190 | if (!isBlock(node)) {
191 | var hasLeading = /^[ \r\n\t]/.test(node.innerHTML),
192 | hasTrailing = /[ \r\n\t]$/.test(node.innerHTML);
193 |
194 | if (hasLeading && !isFlankedByWhitespace('left', node)) {
195 | leading = ' ';
196 | }
197 | if (hasTrailing && !isFlankedByWhitespace('right', node)) {
198 | trailing = ' ';
199 | }
200 | }
201 |
202 | return { leading: leading, trailing: trailing };
203 | }
204 |
205 | /*
206 | * Finds a Markdown converter, gets the replacement, and sets it on
207 | * `_replacement`
208 | */
209 |
210 | function process(node) {
211 | var replacement, content = getContent(node);
212 |
213 | // Remove blank nodes
214 | if (!isVoid(node) && !/A/.test(node.nodeName) && /^\s*$/i.test(content)) {
215 | node._replacement = '';
216 | return;
217 | }
218 |
219 | for (var i = 0; i < converters.length; i++) {
220 | var converter = converters[i];
221 |
222 | if (canConvert(node, converter.filter)) {
223 | if (typeof converter.replacement !== 'function') {
224 | throw new TypeError(
225 | '`replacement` needs to be a function that returns a string'
226 | );
227 | }
228 |
229 | var whitespace = flankingWhitespace(node);
230 |
231 | if (whitespace.leading || whitespace.trailing) {
232 | content = trim(content);
233 | }
234 | replacement = whitespace.leading +
235 | converter.replacement.call(toMarkdown, content, node) +
236 | whitespace.trailing;
237 | break;
238 | }
239 | }
240 |
241 | node._replacement = replacement;
242 | }
243 |
244 | toMarkdown = function (input, options) {
245 | options = options || {};
246 |
247 | if (typeof input !== 'string') {
248 | throw new TypeError(input + ' is not a string');
249 | }
250 |
251 | // Escape potential ol triggers
252 | input = input.replace(/(\d+)\. /g, '$1\\. ');
253 |
254 | var clone = htmlToDom(input).body,
255 | nodes = bfsOrder(clone),
256 | output;
257 |
258 | converters = mdConverters.slice(0);
259 | if (options.gfm) {
260 | converters = gfmConverters.concat(converters);
261 | }
262 |
263 | if (options.converters) {
264 | converters = options.converters.concat(converters);
265 | }
266 |
267 | // Process through nodes in reverse (so deepest child elements are first).
268 | for (var i = nodes.length - 1; i >= 0; i--) {
269 | process(nodes[i]);
270 | }
271 | output = getContent(clone);
272 |
273 | return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '')
274 | .replace(/\n\s+\n/g, '\n\n')
275 | .replace(/\n{3,}/g, '\n\n');
276 | };
277 |
278 | toMarkdown.isBlock = isBlock;
279 | toMarkdown.isVoid = isVoid;
280 | toMarkdown.trim = trim;
281 | toMarkdown.outer = outer;
282 |
283 | module.exports = toMarkdown;
284 |
285 | },{"./lib/gfm-converters":2,"./lib/md-converters":3,"collapse-whitespace":4,"jsdom":7}],2:[function(require,module,exports){
286 | 'use strict';
287 |
288 | function cell(content, node) {
289 | var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node);
290 | var prefix = ' ';
291 | if (index === 0) { prefix = '| '; }
292 | return prefix + content + ' |';
293 | }
294 |
295 | var highlightRegEx = /highlight highlight-(\S+)/;
296 |
297 | module.exports = [
298 | {
299 | filter: 'br',
300 | replacement: function () {
301 | return '\n';
302 | }
303 | },
304 | {
305 | filter: ['del', 's', 'strike'],
306 | replacement: function (content) {
307 | return '~~' + content + '~~';
308 | }
309 | },
310 |
311 | {
312 | filter: function (node) {
313 | return node.type === 'checkbox' && node.parentNode.nodeName === 'LI';
314 | },
315 | replacement: function (content, node) {
316 | return (node.checked ? '[x]' : '[ ]') + ' ';
317 | }
318 | },
319 |
320 | {
321 | filter: ['th', 'td'],
322 | replacement: function (content, node) {
323 | return cell(content, node);
324 | }
325 | },
326 |
327 | {
328 | filter: 'tr',
329 | replacement: function (content, node) {
330 | var borderCells = '';
331 | var alignMap = { left: ':--', right: '--:', center: ':-:' };
332 |
333 | if (node.parentNode.nodeName === 'THEAD') {
334 | for (var i = 0; i < node.childNodes.length; i++) {
335 | var align = node.childNodes[i].attributes.align;
336 | var border = '---';
337 |
338 | if (align) { border = alignMap[align.value] || border; }
339 |
340 | borderCells += cell(border, node.childNodes[i]);
341 | }
342 | }
343 | return '\n' + content + (borderCells ? '\n' + borderCells : '');
344 | }
345 | },
346 |
347 | {
348 | filter: 'table',
349 | replacement: function (content) {
350 | return '\n\n' + content + '\n\n';
351 | }
352 | },
353 |
354 | {
355 | filter: ['thead', 'tbody', 'tfoot'],
356 | replacement: function (content) {
357 | return content;
358 | }
359 | },
360 |
361 | // Fenced code blocks
362 | {
363 | filter: function (node) {
364 | return node.nodeName === 'PRE' &&
365 | node.firstChild &&
366 | node.firstChild.nodeName === 'CODE';
367 | },
368 | replacement: function(content, node) {
369 | return '\n\n```\n' + node.firstChild.textContent + '\n```\n\n';
370 | }
371 | },
372 |
373 | // Syntax-highlighted code blocks
374 | {
375 | filter: function (node) {
376 | return node.nodeName === 'PRE' &&
377 | node.parentNode.nodeName === 'DIV' &&
378 | highlightRegEx.test(node.parentNode.className);
379 | },
380 | replacement: function (content, node) {
381 | var language = node.parentNode.className.match(highlightRegEx)[1];
382 | return '\n\n```' + language + '\n' + node.textContent + '\n```\n\n';
383 | }
384 | },
385 |
386 | {
387 | filter: function (node) {
388 | return node.nodeName === 'DIV' &&
389 | highlightRegEx.test(node.className);
390 | },
391 | replacement: function (content) {
392 | return '\n\n' + content + '\n\n';
393 | }
394 | }
395 | ];
396 |
397 | },{}],3:[function(require,module,exports){
398 | 'use strict';
399 |
400 | module.exports = [
401 | {
402 | filter: 'p',
403 | replacement: function (content) {
404 | return '\n\n' + content + '\n\n';
405 | }
406 | },
407 |
408 | {
409 | filter: 'br',
410 | replacement: function () {
411 | return ' \n';
412 | }
413 | },
414 |
415 | {
416 | filter: ['h1', 'h2', 'h3', 'h4','h5', 'h6'],
417 | replacement: function(content, node) {
418 | var hLevel = node.nodeName.charAt(1);
419 | var hPrefix = '';
420 | for(var i = 0; i < hLevel; i++) {
421 | hPrefix += '#';
422 | }
423 | return '\n\n' + hPrefix + ' ' + content + '\n\n';
424 | }
425 | },
426 |
427 | {
428 | filter: 'hr',
429 | replacement: function () {
430 | return '\n\n* * *\n\n';
431 | }
432 | },
433 |
434 | {
435 | filter: ['em', 'i'],
436 | replacement: function (content) {
437 | return '_' + content + '_';
438 | }
439 | },
440 |
441 | {
442 | filter: ['strong', 'b'],
443 | replacement: function (content) {
444 | return '**' + content + '**';
445 | }
446 | },
447 |
448 | // Inline code
449 | {
450 | filter: function (node) {
451 | var hasSiblings = node.previousSibling || node.nextSibling;
452 | var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
453 |
454 | return node.nodeName === 'CODE' && !isCodeBlock;
455 | },
456 | replacement: function(content) {
457 | return '`' + content + '`';
458 | }
459 | },
460 |
461 | {
462 | filter: function (node) {
463 | return node.nodeName === 'A' && node.getAttribute('href');
464 | },
465 | replacement: function(content, node) {
466 | var titlePart = node.title ? ' "'+ node.title +'"' : '';
467 | return '[' + content + '](' + node.getAttribute('href') + titlePart + ')';
468 | }
469 | },
470 |
471 | {
472 | filter: 'img',
473 | replacement: function(content, node) {
474 | var alt = node.alt || '';
475 | var src = node.getAttribute('src') || '';
476 | var title = node.title || '';
477 | var titlePart = title ? ' "'+ title +'"' : '';
478 | return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '';
479 | }
480 | },
481 |
482 | // Code blocks
483 | {
484 | filter: function (node) {
485 | return node.nodeName === 'PRE' && node.firstChild.nodeName === 'CODE';
486 | },
487 | replacement: function(content, node) {
488 | return '\n\n ' + node.firstChild.textContent.replace(/\n/g, '\n ') + '\n\n';
489 | }
490 | },
491 |
492 | {
493 | filter: 'blockquote',
494 | replacement: function (content) {
495 | content = this.trim(content);
496 | content = content.replace(/\n{3,}/g, '\n\n');
497 | content = content.replace(/^/gm, '> ');
498 | return '\n\n' + content + '\n\n';
499 | }
500 | },
501 |
502 | {
503 | filter: 'li',
504 | replacement: function (content, node) {
505 | content = content.replace(/^\s+/, '').replace(/\n/gm, '\n ');
506 | var prefix = '* ';
507 | var parent = node.parentNode;
508 | var index = Array.prototype.indexOf.call(parent.children, node) + 1;
509 |
510 | prefix = /ol/i.test(parent.nodeName) ? index + '. ' : '* ';
511 | return prefix + content;
512 | }
513 | },
514 |
515 | {
516 | filter: ['ul', 'ol'],
517 | replacement: function (content, node) {
518 | var strings = [];
519 | for (var i = 0; i < node.childNodes.length; i++) {
520 | strings.push(node.childNodes[i]._replacement);
521 | }
522 |
523 | if (/li/i.test(node.parentNode.nodeName)) {
524 | return '\n' + strings.join('\n');
525 | }
526 | return '\n\n' + strings.join('\n') + '\n\n';
527 | }
528 | },
529 |
530 | {
531 | filter: function (node) {
532 | return this.isBlock(node);
533 | },
534 | replacement: function (content, node) {
535 | return '\n\n' + this.outer(node, content) + '\n\n';
536 | }
537 | },
538 |
539 | // Anything else!
540 | {
541 | filter: function () {
542 | return true;
543 | },
544 | replacement: function (content, node) {
545 | return this.outer(node, content);
546 | }
547 | }
548 | ];
549 | },{}],4:[function(require,module,exports){
550 | 'use strict';
551 |
552 | var voidElements = require('void-elements');
553 | Object.keys(voidElements).forEach(function (name) {
554 | voidElements[name.toUpperCase()] = 1;
555 | });
556 |
557 | var blockElements = {};
558 | require('block-elements').forEach(function (name) {
559 | blockElements[name.toUpperCase()] = 1;
560 | });
561 |
562 | /**
563 | * isBlockElem(node) determines if the given node is a block element.
564 | *
565 | * @param {Node} node
566 | * @return {Boolean}
567 | */
568 | function isBlockElem(node) {
569 | return !!(node && blockElements[node.nodeName]);
570 | }
571 |
572 | /**
573 | * isVoid(node) determines if the given node is a void element.
574 | *
575 | * @param {Node} node
576 | * @return {Boolean}
577 | */
578 | function isVoid(node) {
579 | return !!(node && voidElements[node.nodeName]);
580 | }
581 |
582 | /**
583 | * whitespace(elem [, isBlock]) removes extraneous whitespace from an
584 | * the given element. The function isBlock may optionally be passed in
585 | * to determine whether or not an element is a block element; if none
586 | * is provided, defaults to using the list of block elements provided
587 | * by the `block-elements` module.
588 | *
589 | * @param {Node} elem
590 | * @param {Function} blockTest
591 | */
592 | function collapseWhitespace(elem, isBlock) {
593 | if (!elem.firstChild || elem.nodeName === 'PRE') return;
594 |
595 | if (typeof isBlock !== 'function') {
596 | isBlock = isBlockElem;
597 | }
598 |
599 | var prevText = null;
600 | var prevVoid = false;
601 |
602 | var prev = null;
603 | var node = next(prev, elem);
604 |
605 | while (node !== elem) {
606 | if (node.nodeType === 3) {
607 | // Node.TEXT_NODE
608 | var text = node.data.replace(/[ \r\n\t]+/g, ' ');
609 |
610 | if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') {
611 | text = text.substr(1);
612 | }
613 |
614 | // `text` might be empty at this point.
615 | if (!text) {
616 | node = remove(node);
617 | continue;
618 | }
619 |
620 | node.data = text;
621 | prevText = node;
622 | } else if (node.nodeType === 1) {
623 | // Node.ELEMENT_NODE
624 | if (isBlock(node) || node.nodeName === 'BR') {
625 | if (prevText) {
626 | prevText.data = prevText.data.replace(/ $/, '');
627 | }
628 |
629 | prevText = null;
630 | prevVoid = false;
631 | } else if (isVoid(node)) {
632 | // Avoid trimming space around non-block, non-BR void elements.
633 | prevText = null;
634 | prevVoid = true;
635 | }
636 | } else {
637 | node = remove(node);
638 | continue;
639 | }
640 |
641 | var nextNode = next(prev, node);
642 | prev = node;
643 | node = nextNode;
644 | }
645 |
646 | if (prevText) {
647 | prevText.data = prevText.data.replace(/ $/, '');
648 | if (!prevText.data) {
649 | remove(prevText);
650 | }
651 | }
652 | }
653 |
654 | /**
655 | * remove(node) removes the given node from the DOM and returns the
656 | * next node in the sequence.
657 | *
658 | * @param {Node} node
659 | * @return {Node} node
660 | */
661 | function remove(node) {
662 | var next = node.nextSibling || node.parentNode;
663 |
664 | node.parentNode.removeChild(node);
665 |
666 | return next;
667 | }
668 |
669 | /**
670 | * next(prev, current) returns the next node in the sequence, given the
671 | * current and previous nodes.
672 | *
673 | * @param {Node} prev
674 | * @param {Node} current
675 | * @return {Node}
676 | */
677 | function next(prev, current) {
678 | if (prev && prev.parentNode === current || current.nodeName === 'PRE') {
679 | return current.nextSibling || current.parentNode;
680 | }
681 |
682 | return current.firstChild || current.nextSibling || current.parentNode;
683 | }
684 |
685 | module.exports = collapseWhitespace;
686 |
687 | },{"block-elements":5,"void-elements":6}],5:[function(require,module,exports){
688 | /**
689 | * This file automatically generated from `build.js`.
690 | * Do not manually edit.
691 | */
692 |
693 | module.exports = [
694 | "address",
695 | "article",
696 | "aside",
697 | "audio",
698 | "blockquote",
699 | "canvas",
700 | "dd",
701 | "div",
702 | "dl",
703 | "fieldset",
704 | "figcaption",
705 | "figure",
706 | "footer",
707 | "form",
708 | "h1",
709 | "h2",
710 | "h3",
711 | "h4",
712 | "h5",
713 | "h6",
714 | "header",
715 | "hgroup",
716 | "hr",
717 | "main",
718 | "nav",
719 | "noscript",
720 | "ol",
721 | "output",
722 | "p",
723 | "pre",
724 | "section",
725 | "table",
726 | "tfoot",
727 | "ul",
728 | "video"
729 | ];
730 |
731 | },{}],6:[function(require,module,exports){
732 | /**
733 | * This file automatically generated from `pre-publish.js`.
734 | * Do not manually edit.
735 | */
736 |
737 | module.exports = {
738 | "area": true,
739 | "base": true,
740 | "br": true,
741 | "col": true,
742 | "embed": true,
743 | "hr": true,
744 | "img": true,
745 | "input": true,
746 | "keygen": true,
747 | "link": true,
748 | "menuitem": true,
749 | "meta": true,
750 | "param": true,
751 | "source": true,
752 | "track": true,
753 | "wbr": true
754 | };
755 |
756 | },{}],7:[function(require,module,exports){
757 |
758 | },{}]},{},[1])(1)
759 | });
--------------------------------------------------------------------------------