Commit ece599ff authored by Robert Knight's avatar Robert Knight

Refactor markdown and math rendering

Extract markdown and math rendering out of the editor
component into a separate module and add tests.
parent 8137324b
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
var angular = require('angular'); var angular = require('angular');
var debounce = require('lodash.debounce'); var debounce = require('lodash.debounce');
var katex = require('katex');
var commands = require('../markdown-commands'); var commands = require('../markdown-commands');
var mediaEmbedder = require('../media-embedder'); var mediaEmbedder = require('../media-embedder');
var renderMarkdown = require('../render-markdown');
/** /**
* @ngdoc directive * @ngdoc directive
...@@ -16,7 +16,7 @@ var mediaEmbedder = require('../media-embedder'); ...@@ -16,7 +16,7 @@ var mediaEmbedder = require('../media-embedder');
* the markdown editor. * the markdown editor.
*/ */
// @ngInject // @ngInject
module.exports = function($filter, $sanitize) { module.exports = function($sanitize) {
return { return {
controller: function () {}, controller: function () {},
link: function(scope, elem) { link: function(scope, elem) {
...@@ -132,94 +132,6 @@ module.exports = function($filter, $sanitize) { ...@@ -132,94 +132,6 @@ module.exports = function($filter, $sanitize) {
scope.preview = !scope.preview; scope.preview = !scope.preview;
}; };
var renderInlineMath = function(textToCheck) {
var re = /\\?\\\(|\\?\\\)/g;
var startMath = null;
var endMath = null;
var match;
var indexes = [];
while ((match = re.exec(textToCheck))) {
indexes.push(match.index);
}
for (var i = 0, index; i < indexes.length; i++) {
index = indexes[i];
if (startMath === null) {
startMath = index + 2;
} else {
endMath = index;
}
if (startMath !== null && endMath !== null) {
try {
var math = katex.renderToString(textToCheck.substring(startMath, endMath));
textToCheck = (
textToCheck.substring(0, (startMath - 2)) + math +
textToCheck.substring(endMath + 2)
);
startMath = null;
endMath = null;
return renderInlineMath(textToCheck);
} catch (error) {
$sanitize(textToCheck.substring(startMath, endMath));
}
}
}
return textToCheck;
};
var renderMathAndMarkdown = function(textToCheck) {
var convert = $filter('converter');
var re = /\$\$/g;
var startMath = 0;
var endMath = 0;
var indexes = (function () {
var match;
var result = [];
while ((match = re.exec(textToCheck))) {
result.push(match.index);
}
return result;
})();
indexes.push(textToCheck.length);
var parts = (function () {
var result = [];
/* jshint -W083 */
for (var i = 0, index; i < indexes.length; i++) {
index = indexes[i];
result.push((function () {
if (startMath > endMath) {
endMath = index + 2;
try {
// \\displaystyle tells KaTeX to render the math in display style (full sized fonts).
return katex.renderToString($sanitize("\\displaystyle {" + textToCheck.substring(startMath, index) + "}"));
} catch (error) {
return $sanitize(textToCheck.substring(startMath, index));
}
} else {
startMath = index + 2;
return $sanitize(convert(renderInlineMath(textToCheck.substring(endMath, index))));
}
})());
}
/* jshint +W083 */
return result;
})();
var htmlString = parts.join('');
// Transform the HTML string into a DOM element.
var domElement = document.createElement('div');
domElement.innerHTML = htmlString;
mediaEmbedder.replaceLinksWithEmbeds(domElement);
return domElement.innerHTML;
};
// React to the changes to the input // React to the changes to the input
var handleInputChange = debounce(function () { var handleInputChange = debounce(function () {
scope.$apply(function () { scope.$apply(function () {
...@@ -230,15 +142,16 @@ module.exports = function($filter, $sanitize) { ...@@ -230,15 +142,16 @@ module.exports = function($filter, $sanitize) {
// Re-render the markdown when the view needs updating. // Re-render the markdown when the view needs updating.
scope.$watch('text', function () { scope.$watch('text', function () {
output.innerHTML = renderMathAndMarkdown(scope.text || ''); output.innerHTML = renderMarkdown(scope.text || '', $sanitize);
mediaEmbedder.replaceLinksWithEmbeds(output);
}); });
scope.showEditor = function () { scope.showEditor = function () {
return !scope.readOnly && !scope.preview; return !scope.readOnly && !scope.preview;
}; };
// Exit preview mode when leaving edit mode
scope.$watch('readOnly', function () { scope.$watch('readOnly', function () {
// Exit preview mode when editor stops
scope.preview = false; scope.preview = false;
}); });
......
...@@ -50,18 +50,17 @@ describe('markdown', function () { ...@@ -50,18 +50,17 @@ describe('markdown', function () {
fn(); fn();
}; };
}, },
'../render-markdown': noCallThru(function (markdown, $sanitize) {
return $sanitize('rendered:' + markdown);
}),
'../markdown-commands': { '../markdown-commands': {
convertSelectionToLink: mockFormattingCommand, convertSelectionToLink: mockFormattingCommand,
toggleBlockStyle: mockFormattingCommand, toggleBlockStyle: mockFormattingCommand,
toggleSpanStyle: mockFormattingCommand, toggleSpanStyle: mockFormattingCommand,
LinkType: require('../../markdown-commands').LinkType, LinkType: require('../../markdown-commands').LinkType,
}, }),
}))) }));
.filter('converter', function () {
return function (input) {
return 'rendered:' + input;
};
});
}); });
beforeEach(function () { beforeEach(function () {
...@@ -103,17 +102,6 @@ describe('markdown', function () { ...@@ -103,17 +102,6 @@ describe('markdown', function () {
}); });
}); });
describe('math rendering', function () {
it('should render LaTeX', function () {
var editor = util.createDirective(document, 'markdown', {
readOnly: true,
text: '$$x*2$$',
});
assert.equal(getRenderedHTML(editor),
'rendered:math:\\displaystyle {x*2}rendered:');
});
});
describe('toolbar buttons', function () { describe('toolbar buttons', function () {
it('should apply formatting when clicking toolbar buttons', function () { it('should apply formatting when clicking toolbar buttons', function () {
var editor = util.createDirective(document, 'markdown', { var editor = util.createDirective(document, 'markdown', {
......
'use strict';
var showdown = require('showdown'); var showdown = require('showdown');
function targetBlank(converter) { function targetBlank() {
function filter(text) { function filter(text) {
return text.replace(/<a href=/g, '<a target="_blank" href='); return text.replace(/<a href=/g, '<a target="_blank" href=');
} }
......
'use strict';
var katex = require('katex');
var showdown = require('showdown');
function targetBlank() {
function filter(text) {
return text.replace(/<a href=/g, '<a target="_blank" href=');
}
return [{type: 'output', filter: filter}];
}
function renderMarkdown(html) {
// see https://github.com/showdownjs/showdown#valid-options
var converter = new showdown.Converter({
extensions: [targetBlank],
simplifiedAutoLink: true,
// Since we're using simplifiedAutoLink we also use
// literalMidWordUnderscores because otherwise _'s in URLs get
// transformed into <em>'s.
// See https://github.com/showdownjs/showdown/issues/211
literalMidWordUnderscores: true,
});
return converter.makeHtml(html);
}
function renderInlineMath(textToCheck, $sanitize) {
var re = /\\?\\\(|\\?\\\)/g;
var startMath = null;
var endMath = null;
var match;
var indexes = [];
while ((match = re.exec(textToCheck))) {
indexes.push(match.index);
}
for (var i = 0, index; i < indexes.length; i++) {
index = indexes[i];
if (startMath === null) {
startMath = index + 2;
} else {
endMath = index;
}
if (startMath !== null && endMath !== null) {
try {
var math = katex.renderToString(textToCheck.substring(startMath, endMath));
textToCheck = (
textToCheck.substring(0, (startMath - 2)) + math +
textToCheck.substring(endMath + 2)
);
startMath = null;
endMath = null;
return renderInlineMath(textToCheck);
} catch (error) {
$sanitize(textToCheck.substring(startMath, endMath));
}
}
}
return textToCheck;
}
function renderMathAndMarkdown(textToCheck, $sanitize) {
var re = /\$\$/g;
var startMath = 0;
var endMath = 0;
var indexes = (function () {
var match;
var result = [];
while ((match = re.exec(textToCheck))) {
result.push(match.index);
}
return result;
})();
indexes.push(textToCheck.length);
var parts = (function () {
var result = [];
/* jshint -W083 */
for (var i = 0, index; i < indexes.length; i++) {
index = indexes[i];
result.push((function () {
if (startMath > endMath) {
endMath = index + 2;
try {
// \\displaystyle tells KaTeX to render the math in display style (full sized fonts).
return katex.renderToString($sanitize("\\displaystyle {" + textToCheck.substring(startMath, index) + "}"));
} catch (error) {
return $sanitize(textToCheck.substring(startMath, index));
}
} else {
startMath = index + 2;
return $sanitize(renderMarkdown(renderInlineMath(textToCheck.substring(endMath, index), $sanitize)));
}
})());
}
/* jshint +W083 */
return result;
})();
return parts.join('');
}
module.exports = renderMathAndMarkdown;
'use strict';
var proxyquire = require('proxyquire');
describe('render-markdown', function () {
var render;
var renderMarkdown;
beforeEach(function () {
renderMarkdown = proxyquire('../render-markdown', {
katex: {
renderToString: function (input) {
return 'math:' + input;
},
},
});
render = function (markdown) {
return renderMarkdown(markdown, function (html) { return html; });
};
});
describe('autolinking', function () {
it('should autolink URLs', function () {
assert.equal(render('See this link - http://arxiv.org/article'),
'<p>See this link - <a target="_blank" href="http://arxiv.org/article">' +
'http://arxiv.org/article</a></p>');
});
it('should autolink URLs with _\'s in them correctly', function () {
assert.equal(
render(
'See this https://hypothes.is/stream?q=tag:group_test_needs_card'),
'<p>See this <a target="_blank" ' +
'href="https://hypothes.is/stream?q=tag:group_test_needs_card">' +
'https://hypothes.is/stream?q=tag:group_test_needs_card</a></p>');
});
});
describe('markdown rendering', function () {
it('should render markdown', function () {
assert.equal(render('one **two** three'),
'<p>one <strong>two</strong> three</p>');
});
it('should sanitize the result', function () {
var sanitize = function (html) {
return '<safe>' + html + '</safe>';
};
assert.equal(renderMarkdown('one **two** three', sanitize),
'<safe><p>one <strong>two</strong> three</p></safe>');
});
});
describe('math rendering', function () {
it('should render LaTeX', function () {
assert.equal(render('$$x*2$$'), 'math:\\displaystyle {x*2}');
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment