Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Replace marked with commonmark
Browse files Browse the repository at this point in the history
Marked has some annoying bugs, and the author is inactive, so replace it
with commonmark.js, which is the reference JavaScript implementation of
CommonMark.  CommonMark is also preferable since it has a specification,
and a conformance test suite to make sure that parsers are correct.

Signed-off-by: Johannes Löthberg <johannes@kyriasis.com>
  • Loading branch information
kyrias committed Nov 29, 2016
1 parent 03bd4b1 commit 4d29264
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 70 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3",
"lodash": "^4.13.1",
"marked": "^0.3.5",
"commonmark": "^0.27.0",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1",
"q": "^1.4.1",
Expand Down
92 changes: 23 additions & 69 deletions src/Markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import marked from 'marked';

// marked only applies the default options on the high
// level marked() interface, so we do it here.
const marked_options = Object.assign({}, marked.defaults, {
gfm: true,
tables: true,
breaks: true,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false,
xhtml: true, // return self closing tags (ie. <br /> not <br>)
});
import commonmark from 'commonmark';

/**
* Class that wraps marked, adding the ability to see whether
Expand All @@ -36,16 +23,7 @@ const marked_options = Object.assign({}, marked.defaults, {
*/
export default class Markdown {
constructor(input) {
const lexer = new marked.Lexer(marked_options);
this.tokens = lexer.lex(input);
}

_copyTokens() {
// copy tokens (the parser modifies its input arg)
const tokens_copy = this.tokens.slice();
// it also has a 'links' property, because this is javascript
// and why wouldn't you have an array that also has properties?
return Object.assign(tokens_copy, this.tokens);
this.input = input
}

isPlainText() {
Expand All @@ -64,65 +42,41 @@ export default class Markdown {
is_plain = false;
}

const dummy_renderer = {};
for (const k of Object.keys(marked.Renderer.prototype)) {
const dummy_renderer = new commonmark.HtmlRenderer();
for (const k of Object.keys(commonmark.HtmlRenderer.prototype)) {
dummy_renderer[k] = setNotPlain;
}
// text and paragraph are just text
dummy_renderer.text = function(t){return t;}
dummy_renderer.paragraph = function(t){return t;}
dummy_renderer.text = function(t) { return t; }
dummy_renderer.paragraph = function(t) { return t; }

// ignore links where text is just the url:
// this ignores plain URLs that markdown has
// detected whilst preserving markdown syntax links
dummy_renderer.link = function(href, title, text) {
if (text != href) {
is_plain = false;
}
}

const dummy_options = Object.assign({}, marked_options, {
renderer: dummy_renderer,
});
const dummy_parser = new marked.Parser(dummy_options);
dummy_parser.parse(this._copyTokens());
const dummy_parser = new commonmark.Parser();
dummy_renderer.render(dummy_parser.parse(this.input));

return is_plain;
}

toHTML() {
const real_renderer = new marked.Renderer();
real_renderer.link = function(href, title, text) {
// prevent marked from turning plain URLs
// into links, because its algorithm is fairly
// poor. Let's send plain URLs rather than
// badly linkified ones (the linkifier Vector
// uses on message display is way better, eg.
// handles URLs with closing parens at the end).
if (text == href) {
return href;
}
return marked.Renderer.prototype.link.apply(this, arguments);
}
const parser = new commonmark.Parser();

real_renderer.paragraph = (text) => {
// The tokens at the top level are the 'blocks', so if we
// have more than one, there are multiple 'paragraphs'.
// If there is only one top level token, just return the
const renderer = new commonmark.HtmlRenderer({safe: true});
const real_paragraph = renderer.paragraph;
renderer.paragraph = function(node, entering) {
// If there is only one top level node, just return the
// bare text: it's a single line of text and so should be
// 'inline', rather than necessarily wrapped in its own
// p tag. If, however, we have multiple tokens, each gets
// 'inline', rather than unnecessarily wrapped in its own
// p tag. If, however, we have multiple nodes, each gets
// its own p tag to keep them as separate paragraphs.
if (this.tokens.length == 1) {
return text;
var par = node;
while (par.parent) {
par = par.parent
}
if (par.firstChild != par.lastChild) {
real_paragraph.bind(this)(node, entering);
}
return '<p>' + text + '</p>';
}

const real_options = Object.assign({}, marked_options, {
renderer: real_renderer,
});
const real_parser = new marked.Parser(real_options);
return real_parser.parse(this._copyTokens());
var parsed = parser.parse(this.input);
return renderer.render(parsed);
}
}

0 comments on commit 4d29264

Please sign in to comment.