2016-09-22 16:18:12 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2016-11-29 19:56:48 +00:00
|
|
|
import commonmark from 'commonmark';
|
2016-09-22 16:18:12 +00:00
|
|
|
|
|
|
|
/**
|
2017-02-02 11:27:07 +00:00
|
|
|
* Class that wraps commonmark, adding the ability to see whether
|
2016-09-22 16:18:12 +00:00
|
|
|
* a given message actually uses any markdown syntax or whether
|
|
|
|
* it's plain text.
|
|
|
|
*/
|
|
|
|
export default class Markdown {
|
|
|
|
constructor(input) {
|
2017-01-18 18:29:11 +00:00
|
|
|
this.input = input;
|
2017-02-02 11:34:39 +00:00
|
|
|
|
|
|
|
const parser = new commonmark.Parser();
|
|
|
|
this.parsed = parser.parse(this.input);
|
|
|
|
|
2017-01-18 18:29:11 +00:00
|
|
|
this.renderer = new commonmark.HtmlRenderer({safe: false});
|
2016-09-22 16:18:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
isPlainText() {
|
|
|
|
// we determine if the message requires markdown by
|
|
|
|
// running the parser on the tokens with a dummy
|
|
|
|
// rendered and seeing if any of the renderer's
|
|
|
|
// functions are called other than those noted below.
|
2017-02-02 11:27:07 +00:00
|
|
|
// TODO: can't we just examine the output of the parser?
|
2016-09-22 16:18:12 +00:00
|
|
|
let is_plain = true;
|
|
|
|
|
|
|
|
function setNotPlain() {
|
|
|
|
is_plain = false;
|
|
|
|
}
|
|
|
|
|
2016-11-29 19:56:48 +00:00
|
|
|
const dummy_renderer = new commonmark.HtmlRenderer();
|
|
|
|
for (const k of Object.keys(commonmark.HtmlRenderer.prototype)) {
|
2016-09-22 17:57:46 +00:00
|
|
|
dummy_renderer[k] = setNotPlain;
|
2016-09-22 16:18:12 +00:00
|
|
|
}
|
|
|
|
// text and paragraph are just text
|
2017-01-20 14:22:27 +00:00
|
|
|
dummy_renderer.text = function(t) { return t; };
|
|
|
|
dummy_renderer.softbreak = function(t) { return t; };
|
|
|
|
dummy_renderer.paragraph = function(t) { return t; };
|
2016-09-22 16:18:12 +00:00
|
|
|
|
2017-02-02 11:34:39 +00:00
|
|
|
dummy_renderer.render(this.parsed);
|
2016-09-22 16:18:12 +00:00
|
|
|
|
|
|
|
return is_plain;
|
|
|
|
}
|
|
|
|
|
2017-01-19 10:55:36 +00:00
|
|
|
toHTML() {
|
2017-01-18 18:29:11 +00:00
|
|
|
const real_paragraph = this.renderer.paragraph;
|
|
|
|
|
|
|
|
this.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 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.
|
|
|
|
var par = node;
|
|
|
|
while (par.parent) {
|
2017-01-20 14:22:27 +00:00
|
|
|
par = par.parent;
|
2016-11-29 19:56:48 +00:00
|
|
|
}
|
2017-01-18 18:29:11 +00:00
|
|
|
if (par.firstChild != par.lastChild) {
|
|
|
|
real_paragraph.call(this, node, entering);
|
2017-01-17 21:20:05 +00:00
|
|
|
}
|
2017-01-20 14:22:27 +00:00
|
|
|
};
|
2017-01-17 21:20:05 +00:00
|
|
|
|
2017-02-02 11:34:39 +00:00
|
|
|
var rendered = this.renderer.render(this.parsed);
|
2017-01-18 18:29:11 +00:00
|
|
|
|
|
|
|
this.renderer.paragraph = real_paragraph;
|
|
|
|
|
|
|
|
return rendered;
|
|
|
|
}
|
|
|
|
|
2017-02-02 11:27:07 +00:00
|
|
|
/*
|
|
|
|
* Render the mrkdown message to plain text. That is, essentially
|
|
|
|
* just remove any backslashes escaping what would otherwise be
|
|
|
|
* markdown syntax
|
|
|
|
* (to fix https://github.com/vector-im/riot-web/issues/2870)
|
|
|
|
*/
|
2017-01-18 18:29:11 +00:00
|
|
|
toPlaintext() {
|
|
|
|
const real_paragraph = this.renderer.paragraph;
|
|
|
|
|
|
|
|
// The default `out` function only sends the input through an XML
|
|
|
|
// escaping function, which causes messages to be entity encoded,
|
|
|
|
// which we don't want in this case.
|
|
|
|
this.renderer.out = function(s) {
|
|
|
|
// The `lit` function adds a string literal to the output buffer.
|
|
|
|
this.lit(s);
|
2017-01-20 14:22:27 +00:00
|
|
|
};
|
2017-01-18 18:29:11 +00:00
|
|
|
|
|
|
|
this.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 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.
|
|
|
|
var par = node;
|
|
|
|
while (par.parent) {
|
|
|
|
node = par;
|
|
|
|
par = par.parent;
|
|
|
|
}
|
|
|
|
if (node != par.lastChild) {
|
|
|
|
if (!entering) {
|
|
|
|
this.lit('\n\n');
|
2016-12-02 18:58:35 +00:00
|
|
|
}
|
2016-09-22 17:57:46 +00:00
|
|
|
}
|
2017-01-20 14:22:27 +00:00
|
|
|
};
|
2016-09-22 17:57:46 +00:00
|
|
|
|
2017-02-02 11:34:39 +00:00
|
|
|
var rendered = this.renderer.render(this.parsed);
|
2017-01-18 18:29:11 +00:00
|
|
|
|
|
|
|
this.renderer.paragraph = real_paragraph;
|
|
|
|
|
|
|
|
return rendered;
|
2016-09-22 16:18:12 +00:00
|
|
|
}
|
|
|
|
}
|