2014-01-27 23:20:50 +01:00
|
|
|
// Parsing routine that can be dropped in to message parsing
|
|
|
|
// and formats code blocks
|
|
|
|
//
|
|
|
|
// This supports arbitrarily nested code blocks as well as
|
|
|
|
// auto-completing code blocks missing a trailing close.
|
|
|
|
|
|
|
|
// See backend fenced_code.py:71 for associated regexp
|
2019-11-02 00:06:25 +01:00
|
|
|
const fencestr = "^(~{3,}|`{3,})" + // Opening Fence
|
2017-06-15 23:39:20 +02:00
|
|
|
"[ ]*" + // Spaces
|
|
|
|
"(" +
|
|
|
|
"\\{?\\.?" +
|
|
|
|
"([a-zA-Z0-9_+-./#]*)" + // Language
|
|
|
|
"\\}?" +
|
|
|
|
"[ ]*" + // Spaces
|
2014-01-27 23:20:50 +01:00
|
|
|
")$";
|
2019-11-02 00:06:25 +01:00
|
|
|
const fence_re = new RegExp(fencestr);
|
2014-01-27 23:20:50 +01:00
|
|
|
|
|
|
|
// Default stashing function does nothing
|
2019-11-02 00:06:25 +01:00
|
|
|
let stash_func = function (text) {
|
2014-01-27 23:20:50 +01:00
|
|
|
return text;
|
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let escape_func = function (text) {
|
2014-02-03 17:13:40 +01:00
|
|
|
return text;
|
|
|
|
};
|
|
|
|
|
2014-01-27 23:20:50 +01:00
|
|
|
function wrap_code(code) {
|
|
|
|
// Trim trailing \n until there's just one left
|
|
|
|
// This mirrors how pygments handles code input
|
|
|
|
code += '\n';
|
|
|
|
while (code.length > 2 && code.substr(code.length - 2) === '\n\n') {
|
|
|
|
code = code.substring(0, code.length - 1);
|
|
|
|
}
|
2016-09-12 20:50:10 +02:00
|
|
|
return '<div class="codehilite"><pre><span></span>' + escape_func(code) + '</pre></div>\n';
|
2014-01-27 23:20:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function wrap_quote(text) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const paragraphs = text.split('\n\n');
|
|
|
|
const quoted_paragraphs = [];
|
2014-01-27 23:20:50 +01:00
|
|
|
// Prefix each quoted paragraph with > at the
|
|
|
|
// beginning of each line
|
|
|
|
_.each(paragraphs, function (paragraph) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const lines = paragraph.split('\n');
|
2014-01-27 23:20:50 +01:00
|
|
|
quoted_paragraphs.push(_.map(
|
2018-05-06 21:43:17 +02:00
|
|
|
_.reject(lines, function (line) { return line === ''; }),
|
|
|
|
function (line) { return '> ' + line; }).join('\n'));
|
2014-01-27 23:20:50 +01:00
|
|
|
});
|
|
|
|
return quoted_paragraphs.join('\n\n');
|
|
|
|
}
|
|
|
|
|
2017-03-20 16:56:39 +01:00
|
|
|
function wrap_tex(tex) {
|
|
|
|
try {
|
|
|
|
return katex.renderToString(tex, {
|
|
|
|
displayMode: true,
|
|
|
|
});
|
|
|
|
} catch (ex) {
|
|
|
|
return '<span class="tex-error">' + escape_func(tex) + '</span>';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-27 23:20:50 +01:00
|
|
|
exports.set_stash_func = function (stash_handler) {
|
|
|
|
stash_func = stash_handler;
|
|
|
|
};
|
|
|
|
|
2014-02-03 17:13:40 +01:00
|
|
|
exports.set_escape_func = function (escape) {
|
|
|
|
escape_func = escape;
|
|
|
|
};
|
|
|
|
|
2014-01-27 23:20:50 +01:00
|
|
|
exports.process_fenced_code = function (content) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const input = content.split('\n');
|
|
|
|
const output = [];
|
|
|
|
const handler_stack = [];
|
|
|
|
let consume_line;
|
2014-01-27 23:20:50 +01:00
|
|
|
|
|
|
|
function handler_for_fence(output_lines, fence, lang) {
|
|
|
|
// lang is ignored except for 'quote', as we
|
|
|
|
// don't do syntax highlighting yet
|
|
|
|
return (function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const lines = [];
|
2014-01-27 23:20:50 +01:00
|
|
|
if (lang === 'quote') {
|
|
|
|
return {
|
|
|
|
handle_line: function (line) {
|
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
|
|
|
consume_line(lines, line);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
done: function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const text = wrap_quote(lines.join('\n'));
|
2014-01-27 23:20:50 +01:00
|
|
|
output_lines.push('');
|
|
|
|
output_lines.push(text);
|
|
|
|
output_lines.push('');
|
|
|
|
handler_stack.pop();
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2014-01-27 23:20:50 +01:00
|
|
|
};
|
|
|
|
}
|
2017-03-20 16:56:39 +01:00
|
|
|
|
|
|
|
if (lang === 'math' || lang === 'tex' || lang === 'latex') {
|
|
|
|
return {
|
|
|
|
handle_line: function (line) {
|
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
2017-11-21 07:21:26 +01:00
|
|
|
lines.push(line);
|
2017-03-20 16:56:39 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
done: function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const text = wrap_tex(lines.join('\n'));
|
|
|
|
const placeholder = stash_func(text, true);
|
2017-03-20 16:56:39 +01:00
|
|
|
output_lines.push('');
|
|
|
|
output_lines.push(placeholder);
|
|
|
|
output_lines.push('');
|
|
|
|
handler_stack.pop();
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-12-02 21:34:35 +01:00
|
|
|
return {
|
|
|
|
handle_line: function (line) {
|
|
|
|
if (line === fence) {
|
|
|
|
this.done();
|
|
|
|
} else {
|
2017-03-20 18:54:00 +01:00
|
|
|
lines.push(util.rtrim(line));
|
2016-12-02 21:34:35 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
done: function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const text = wrap_code(lines.join('\n'));
|
2016-12-02 21:34:35 +01:00
|
|
|
// insert safe HTML that is passed through the parsing
|
2019-11-02 00:06:25 +01:00
|
|
|
const placeholder = stash_func(text, true);
|
2016-12-02 21:34:35 +01:00
|
|
|
output_lines.push('');
|
|
|
|
output_lines.push(placeholder);
|
|
|
|
output_lines.push('');
|
|
|
|
handler_stack.pop();
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2016-12-02 21:34:35 +01:00
|
|
|
};
|
2014-01-27 23:20:50 +01:00
|
|
|
}());
|
|
|
|
}
|
|
|
|
|
|
|
|
function default_hander() {
|
|
|
|
return {
|
|
|
|
handle_line: function (line) {
|
|
|
|
consume_line(output, line);
|
|
|
|
},
|
|
|
|
done: function () {
|
|
|
|
handler_stack.pop();
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2014-01-27 23:20:50 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
consume_line = function consume_line(output_lines, line) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const match = fence_re.exec(line);
|
2014-01-27 23:20:50 +01:00
|
|
|
if (match) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const fence = match[1];
|
|
|
|
const lang = match[3];
|
|
|
|
const handler = handler_for_fence(output_lines, fence, lang);
|
2014-01-27 23:20:50 +01:00
|
|
|
handler_stack.push(handler);
|
|
|
|
} else {
|
|
|
|
output_lines.push(line);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const current_handler = default_hander();
|
2014-01-27 23:20:50 +01:00
|
|
|
handler_stack.push(current_handler);
|
|
|
|
|
|
|
|
_.each(input, function (line) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const handler = handler_stack[handler_stack.length - 1];
|
2014-01-27 23:20:50 +01:00
|
|
|
handler.handle_line(line);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Clean up all trailing blocks by letting them
|
|
|
|
// insert closing fences
|
|
|
|
while (handler_stack.length !== 0) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const handler = handler_stack[handler_stack.length - 1];
|
2014-01-27 23:20:50 +01:00
|
|
|
handler.done();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output.length > 2 && output[output.length - 2] !== '') {
|
|
|
|
output.push('');
|
|
|
|
}
|
|
|
|
|
|
|
|
return output.join('\n');
|
|
|
|
};
|
|
|
|
|
2019-08-26 09:04:34 +02:00
|
|
|
const fence_length_re = /^ {0,3}(`{3,})/gm;
|
|
|
|
exports.get_unused_fence = (content) => {
|
|
|
|
// we only return ``` fences, not ~~~.
|
|
|
|
let length = 3;
|
|
|
|
let match;
|
|
|
|
fence_length_re.lastIndex = 0;
|
|
|
|
while ((match = fence_length_re.exec(content)) !== null) {
|
|
|
|
length = Math.max(length, match[1].length + 1);
|
|
|
|
}
|
|
|
|
return '`'.repeat(length);
|
|
|
|
};
|
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.fenced_code = exports;
|