diff --git a/src/services/import/markdown.js b/src/services/import/markdown.js
index e350cd065..f9cac8eab 100644
--- a/src/services/import/markdown.js
+++ b/src/services/import/markdown.js
@@ -1,10 +1,12 @@
"use strict";
const marked = require("marked");
+const texPlugin = require("./tex").texPlugin;
const htmlSanitizer = require("../html_sanitizer");
const importUtils = require("./utils");
function renderToHtml(content, title) {
+ marked.use(texPlugin());
const html = marked.parse(content, {
mangle: false,
headerIds: false
diff --git a/src/services/import/tex.js b/src/services/import/tex.js
new file mode 100644
index 000000000..0e1bc7e7a
--- /dev/null
+++ b/src/services/import/tex.js
@@ -0,0 +1,156 @@
+const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n$]))\1(?=[\s?!.,:?!。,:]|$)/;
+const alternativeInlineRule = /^\\\((?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n$]))\\\)(?=[\s?!.,:?!。,:]|$)/;
+const blockRule = /^(\${1,2})[\s\n]((?:\\[^]|[^\\])+?)[\s\n]\1(?:\n|$)/;
+const alternativeBlockRule = /^\\\[(\s*)((?:\\[^]|[^\\])+?)(\s*)\\](?:\n|$)/;
+
+function texPlugin(options = {}) {
+ return {
+ extensions: [
+ inlineKatex(options, createRenderer(options, false)),
+ altInlineKatex(options, createRenderer(options, false)),
+ blockKatex(options, createRenderer(options, true)),
+ altBlockKatex(options, createRenderer(options, true))
+ ]
+ };
+}
+
+function createRenderer(options, newlineAfter) {
+ return (token) => {
+ let result = token.text;
+ if (token.displayMode) {
+ // full block mode
+ result = `\\[ ${result} \\]`;
+ } else {
+ // inline block mode
+ result = `\\( ${result} \\)`;
+ }
+
+ if (newlineAfter) {
+ result += '\n';
+ }
+
+ return result;
+ };
+}
+
+function inlineKatex(options, renderer) {
+ return {
+ name: 'inlineKatex',
+ level: 'inline',
+ start(src) {
+ let index;
+ let indexSrc = src;
+
+ while (indexSrc) {
+ index = indexSrc.indexOf('$');
+ if (index === -1) {
+ return;
+ }
+
+ if (index === 0 || indexSrc.charAt(index - 1) === ' ') {
+ const possibleKatex = indexSrc.substring(index);
+
+ if (possibleKatex.match(inlineRule)) {
+ return index;
+ }
+ }
+
+ indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
+ }
+ },
+ tokenizer(src) {
+ const match = src.match(inlineRule);
+ if (match) {
+ return {
+ type: 'inlineKatex',
+ raw: match[0],
+ text: match[2].trim(),
+ displayMode: match[1].length === 2
+ };
+ }
+ },
+ renderer
+ };
+}
+
+function altInlineKatex(options, renderer) {
+ return {
+ name: 'altInlineKatex',
+ level: 'inline',
+ start(src) {
+ let index;
+ let indexSrc = src;
+
+ while (indexSrc) {
+ index = indexSrc.indexOf('\\(');
+ if (index === -1) {
+ return;
+ }
+
+ if (index === 0 || indexSrc.charAt(index - 1) === ' ') {
+ const possibleKatex = indexSrc.substring(index + 1);
+
+ if (possibleKatex.match(alternativeInlineRule)) {
+ return index;
+ }
+ }
+
+ indexSrc = indexSrc.substring(index + 2).replace(/^\\[()]/, '');
+ }
+ },
+ tokenizer(src) {
+ const match = src.match(alternativeInlineRule);
+ if (match) {
+ return {
+ type: 'inlineKatex',
+ raw: match[0],
+ text: match[1].trim(),
+ displayMode: false
+ };
+ }
+ },
+ renderer
+ };
+}
+
+function blockKatex(options, renderer) {
+ return {
+ name: 'blockKatex',
+ level: 'block',
+ tokenizer(src) {
+ const match = src.match(blockRule);
+ if (match) {
+ return {
+ type: 'blockKatex',
+ raw: match[0],
+ text: match[2].trim(),
+ displayMode: match[1].length === 2
+ };
+ }
+ },
+ renderer
+ };
+}
+
+function altBlockKatex(options, renderer) {
+ return {
+ name: 'altBlockKatex',
+ level: 'block',
+ tokenizer(src) {
+ const match = src.match(alternativeBlockRule);
+ if (match) {
+ return {
+ type: 'blockKatex',
+ raw: match[0],
+ text: match[2].trim(),
+ displayMode: true
+ };
+ }
+ },
+ renderer
+ };
+}
+
+module.exports = {
+ texPlugin
+};