Add LaTeX / math input and rendering support (#345)
* Initial display support * Use better colors for error in math parsing * Parse math markdown * Use proper jsx * Better copy support * use css var directly * Remove console.debug call * Lazy load math module * Show fallback while katex is loading
This commit is contained in:
parent
9a22b25564
commit
b7c5902f67
8 changed files with 230 additions and 18 deletions
116
package-lock.json
generated
116
package-lock.json
generated
|
@ -21,10 +21,12 @@
|
||||||
"flux": "^4.0.3",
|
"flux": "^4.0.3",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"html-react-parser": "^1.4.11",
|
"html-react-parser": "^1.4.11",
|
||||||
|
"katex": "^0.15.2",
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"matrix-js-sdk": "^17.0.0",
|
"matrix-js-sdk": "^17.0.0",
|
||||||
"micromark": "^3.0.10",
|
"micromark": "^3.0.10",
|
||||||
"micromark-extension-gfm": "^2.0.1",
|
"micromark-extension-gfm": "^2.0.1",
|
||||||
|
"micromark-extension-math": "^2.0.2",
|
||||||
"micromark-util-chunked": "^1.0.0",
|
"micromark-util-chunked": "^1.0.0",
|
||||||
"micromark-util-resolve-all": "^1.0.0",
|
"micromark-util-resolve-all": "^1.0.0",
|
||||||
"micromark-util-symbol": "^1.0.1",
|
"micromark-util-symbol": "^1.0.1",
|
||||||
|
@ -2585,6 +2587,11 @@
|
||||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/katex": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg=="
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||||
|
@ -2945,6 +2952,8 @@
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
||||||
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
@ -2960,7 +2969,9 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/ajv-keywords": {
|
"node_modules/ajv-keywords": {
|
||||||
"version": "3.5.2",
|
"version": "3.5.2",
|
||||||
|
@ -4170,7 +4181,6 @@
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
|
@ -8359,6 +8369,21 @@
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/katex": {
|
||||||
|
"version": "0.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.15.2.tgz",
|
||||||
|
"integrity": "sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://opencollective.com/katex",
|
||||||
|
"https://github.com/sponsors/katex"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^8.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"katex": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kind-of": {
|
"node_modules/kind-of": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
|
@ -9095,6 +9120,40 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/micromark-extension-math": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/katex": "^0.11.0",
|
||||||
|
"katex": "^0.13.0",
|
||||||
|
"micromark-factory-space": "^1.0.0",
|
||||||
|
"micromark-util-character": "^1.0.0",
|
||||||
|
"micromark-util-symbol": "^1.0.0",
|
||||||
|
"micromark-util-types": "^1.0.0",
|
||||||
|
"uvu": "^0.5.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromark-extension-math/node_modules/katex": {
|
||||||
|
"version": "0.13.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz",
|
||||||
|
"integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==",
|
||||||
|
"funding": [
|
||||||
|
"https://opencollective.com/katex",
|
||||||
|
"https://github.com/sponsors/katex"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^8.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"katex": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/micromark-factory-destination": {
|
"node_modules/micromark-factory-destination": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
||||||
|
@ -15793,6 +15852,11 @@
|
||||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/katex": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg=="
|
||||||
|
},
|
||||||
"@types/mime": {
|
"@types/mime": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||||
|
@ -16107,15 +16171,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {},
|
||||||
"ajv": "^8.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "8.9.0",
|
"version": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
|
||||||
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
@ -16127,7 +16190,9 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -17121,8 +17186,7 @@
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "8.3.0",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"commondir": {
|
"commondir": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -20305,6 +20369,14 @@
|
||||||
"object.assign": "^4.1.2"
|
"object.assign": "^4.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"katex": {
|
||||||
|
"version": "0.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.15.2.tgz",
|
||||||
|
"integrity": "sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ==",
|
||||||
|
"requires": {
|
||||||
|
"commander": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
|
@ -20922,6 +20994,30 @@
|
||||||
"uvu": "^0.5.0"
|
"uvu": "^0.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"micromark-extension-math": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/katex": "^0.11.0",
|
||||||
|
"katex": "^0.13.0",
|
||||||
|
"micromark-factory-space": "^1.0.0",
|
||||||
|
"micromark-util-character": "^1.0.0",
|
||||||
|
"micromark-util-symbol": "^1.0.0",
|
||||||
|
"micromark-util-types": "^1.0.0",
|
||||||
|
"uvu": "^0.5.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"katex": {
|
||||||
|
"version": "0.13.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz",
|
||||||
|
"integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==",
|
||||||
|
"requires": {
|
||||||
|
"commander": "^8.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"micromark-factory-destination": {
|
"micromark-factory-destination": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
|
||||||
|
|
|
@ -27,10 +27,12 @@
|
||||||
"flux": "^4.0.3",
|
"flux": "^4.0.3",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"html-react-parser": "^1.4.11",
|
"html-react-parser": "^1.4.11",
|
||||||
|
"katex": "^0.15.2",
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"matrix-js-sdk": "^17.0.0",
|
"matrix-js-sdk": "^17.0.0",
|
||||||
"micromark": "^3.0.10",
|
"micromark": "^3.0.10",
|
||||||
"micromark-extension-gfm": "^2.0.1",
|
"micromark-extension-gfm": "^2.0.1",
|
||||||
|
"micromark-extension-math": "^2.0.2",
|
||||||
"micromark-util-chunked": "^1.0.0",
|
"micromark-util-chunked": "^1.0.0",
|
||||||
"micromark-util-resolve-all": "^1.0.0",
|
"micromark-util-resolve-all": "^1.0.0",
|
||||||
"micromark-util-symbol": "^1.0.1",
|
"micromark-util-symbol": "^1.0.1",
|
||||||
|
|
33
src/app/atoms/math/Math.jsx
Normal file
33
src/app/atoms/math/Math.jsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import katex from 'katex';
|
||||||
|
import 'katex/dist/katex.min.css';
|
||||||
|
|
||||||
|
import 'katex/dist/contrib/copy-tex';
|
||||||
|
import 'katex/dist/contrib/copy-tex.css';
|
||||||
|
|
||||||
|
const Math = React.memo(({
|
||||||
|
content, throwOnError, errorColor, displayMode,
|
||||||
|
}) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
katex.render(content, ref.current, { throwOnError, errorColor, displayMode });
|
||||||
|
}, [content, throwOnError, errorColor, displayMode]);
|
||||||
|
|
||||||
|
return <span ref={ref} />;
|
||||||
|
});
|
||||||
|
Math.defaultProps = {
|
||||||
|
throwOnError: null,
|
||||||
|
errorColor: null,
|
||||||
|
displayMode: null,
|
||||||
|
};
|
||||||
|
Math.propTypes = {
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
throwOnError: PropTypes.bool,
|
||||||
|
errorColor: PropTypes.string,
|
||||||
|
displayMode: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Math;
|
|
@ -189,7 +189,7 @@ const MessageBody = React.memo(({
|
||||||
let content = null;
|
let content = null;
|
||||||
if (isCustomHTML) {
|
if (isCustomHTML) {
|
||||||
try {
|
try {
|
||||||
content = twemojify(sanitizeCustomHtml(body), undefined, true, false);
|
content = twemojify(sanitizeCustomHtml(body), undefined, true, false, true);
|
||||||
} catch {
|
} catch {
|
||||||
console.error('Malformed custom html: ', body);
|
console.error('Malformed custom html: ', body);
|
||||||
content = twemojify(body, undefined);
|
content = twemojify(body, undefined);
|
||||||
|
|
|
@ -2,8 +2,9 @@ import EventEmitter from 'events';
|
||||||
import { micromark } from 'micromark';
|
import { micromark } from 'micromark';
|
||||||
import { gfm, gfmHtml } from 'micromark-extension-gfm';
|
import { gfm, gfmHtml } from 'micromark-extension-gfm';
|
||||||
import encrypt from 'browser-encrypt-attachment';
|
import encrypt from 'browser-encrypt-attachment';
|
||||||
|
import { math } from 'micromark-extension-math';
|
||||||
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
|
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
|
||||||
import { spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
|
import { mathExtensionHtml, spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
|
||||||
import cons from './cons';
|
import cons from './cons';
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
|
|
||||||
|
@ -85,8 +86,8 @@ function getVideoThumbnail(video, width, height, mimeType) {
|
||||||
|
|
||||||
function getFormattedBody(markdown) {
|
function getFormattedBody(markdown) {
|
||||||
const result = micromark(markdown, {
|
const result = micromark(markdown, {
|
||||||
extensions: [gfm(), spoilerExtension()],
|
extensions: [gfm(), spoilerExtension(), math()],
|
||||||
htmlExtensions: [gfmHtml(), spoilerExtensionHtml],
|
htmlExtensions: [gfmHtml(), spoilerExtensionHtml, mathExtensionHtml],
|
||||||
});
|
});
|
||||||
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
||||||
if (bodyParts === null) return result;
|
if (bodyParts === null) return result;
|
||||||
|
|
|
@ -140,4 +140,59 @@ const spoilerExtensionHtml = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export { inlineExtension, spoilerExtension, spoilerExtensionHtml };
|
const mathExtensionHtml = {
|
||||||
|
enter: {
|
||||||
|
mathFlow() {
|
||||||
|
this.lineEndingIfNeeded();
|
||||||
|
},
|
||||||
|
mathFlowFenceMeta() {
|
||||||
|
this.buffer();
|
||||||
|
},
|
||||||
|
mathText() {
|
||||||
|
this.buffer();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
mathFlow() {
|
||||||
|
const value = this.encode(this.resume().replace(/(?:\r?\n|\r)$/, ''));
|
||||||
|
this.tag('<div data-mx-maths="');
|
||||||
|
this.tag(value);
|
||||||
|
this.tag('"><code>');
|
||||||
|
this.raw(value);
|
||||||
|
this.tag('</code></div>');
|
||||||
|
this.setData('mathFlowOpen');
|
||||||
|
this.setData('slurpOneLineEnding');
|
||||||
|
},
|
||||||
|
mathFlowFence() {
|
||||||
|
// After the first fence.
|
||||||
|
if (!this.getData('mathFlowOpen')) {
|
||||||
|
this.setData('mathFlowOpen', true);
|
||||||
|
this.setData('slurpOneLineEnding', true);
|
||||||
|
this.buffer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mathFlowFenceMeta() {
|
||||||
|
this.resume();
|
||||||
|
},
|
||||||
|
mathFlowValue(token) {
|
||||||
|
this.raw(this.sliceSerialize(token));
|
||||||
|
},
|
||||||
|
mathText() {
|
||||||
|
const value = this.encode(this.resume());
|
||||||
|
this.tag('<span data-mx-maths="');
|
||||||
|
this.tag(value);
|
||||||
|
this.tag('"><code>');
|
||||||
|
this.raw(value);
|
||||||
|
this.tag('</code></span>');
|
||||||
|
},
|
||||||
|
mathTextData(token) {
|
||||||
|
this.raw(this.sliceSerialize(token));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
inlineExtension,
|
||||||
|
spoilerExtension, spoilerExtensionHtml,
|
||||||
|
mathExtensionHtml,
|
||||||
|
};
|
||||||
|
|
|
@ -15,7 +15,8 @@ const urlSchemes = ['https', 'http', 'ftp', 'mailto', 'magnet'];
|
||||||
|
|
||||||
const permittedTagToAttributes = {
|
const permittedTagToAttributes = {
|
||||||
font: ['style', 'data-mx-bg-color', 'data-mx-color', 'color'],
|
font: ['style', 'data-mx-bg-color', 'data-mx-color', 'color'],
|
||||||
span: ['style', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-pill', 'data-mx-ping'],
|
span: ['style', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-maths', 'data-mx-pill', 'data-mx-ping'],
|
||||||
|
div: ['data-mx-maths'],
|
||||||
a: ['name', 'target', 'href', 'rel'],
|
a: ['name', 'target', 'href', 'rel'],
|
||||||
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
|
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
|
||||||
o: ['start'],
|
o: ['start'],
|
||||||
|
|
|
@ -1,17 +1,41 @@
|
||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
import React, { lazy, Suspense } from 'react';
|
||||||
|
|
||||||
import linkifyHtml from 'linkifyjs/html';
|
import linkifyHtml from 'linkifyjs/html';
|
||||||
import parse from 'html-react-parser';
|
import parse from 'html-react-parser';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import { sanitizeText } from './sanitize';
|
import { sanitizeText } from './sanitize';
|
||||||
|
|
||||||
|
const Math = lazy(() => import('../app/atoms/math/Math'));
|
||||||
|
|
||||||
|
const mathOptions = {
|
||||||
|
replace: (node) => {
|
||||||
|
const maths = node.attribs?.['data-mx-maths'];
|
||||||
|
if (maths) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<code>{maths}</code>}>
|
||||||
|
<Math
|
||||||
|
content={maths}
|
||||||
|
throwOnError={false}
|
||||||
|
errorColor="var(--tc-danger-normal)"
|
||||||
|
displayMode={node.name === 'div'}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} text - text to twemojify
|
* @param {string} text - text to twemojify
|
||||||
* @param {object|undefined} opts - options for tweomoji.parse
|
* @param {object|undefined} opts - options for tweomoji.parse
|
||||||
* @param {boolean} [linkify=false] - convert links to html tags (default: false)
|
* @param {boolean} [linkify=false] - convert links to html tags (default: false)
|
||||||
* @param {boolean} [sanitize=true] - sanitize html text (default: true)
|
* @param {boolean} [sanitize=true] - sanitize html text (default: true)
|
||||||
|
* @param {boolean} [maths=false] - render maths (default: false)
|
||||||
* @returns React component
|
* @returns React component
|
||||||
*/
|
*/
|
||||||
export function twemojify(text, opts, linkify = false, sanitize = true) {
|
export function twemojify(text, opts, linkify = false, sanitize = true, maths = false) {
|
||||||
if (typeof text !== 'string') return text;
|
if (typeof text !== 'string') return text;
|
||||||
let content = text;
|
let content = text;
|
||||||
|
|
||||||
|
@ -25,5 +49,5 @@ export function twemojify(text, opts, linkify = false, sanitize = true) {
|
||||||
rel: 'noreferrer noopener',
|
rel: 'noreferrer noopener',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return parse(content);
|
return parse(content, maths ? mathOptions : null);
|
||||||
}
|
}
|
Loading…
Reference in a new issue