Add katex render to Markdown

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez
2022-10-04 11:50:49 +02:00
parent 8e3789b799
commit a016e98169
7 changed files with 142 additions and 78 deletions
+6
View File
@@ -2,6 +2,12 @@ build/
node_modules/
NOTES
# KaTeX files
*.min.css
KaTeX_**.ttf
KaTeX_**.woff
KaTeX_**.woff2
# Integration Tests
cypress/screenshots
+1
View File
@@ -16,6 +16,7 @@
<meta property="og:image:height" content="240" />
<meta property="og:type" content="website" />
<link rel="stylesheet" type="text/css" href="/typography.css" />
<link rel="stylesheet" type="text/css" href="/katex.min.css" />
<link rel="stylesheet" type="text/css" href="/colors.css" />
<link rel="stylesheet" type="text/css" href="/elevations.css" />
<link rel="stylesheet" type="text/css" href="/index.css" />
+96 -48
View File
@@ -6,6 +6,7 @@
"packages": {
"": {
"version": "1.0.0",
"hasInstallScript": true,
"dependencies": {
"@datamodels/identity-profile-basic": "^0.1.2",
"@ethersproject/abstract-provider": "^5.4.0",
@@ -19,8 +20,10 @@
"@types/md5": "^2.3.2",
"@walletconnect/client": "^1.8.0",
"buffer": "^6.0.3",
"dompurify": "^2.4.0",
"ethers": "^5.7.1",
"events": "^3.3.0",
"katex": "^0.16.2",
"lodash": "^4.17.21",
"lru-cache": "^7.14.0",
"marked": "^4.1.0",
@@ -31,13 +34,14 @@
"siwe": "^2.0.5",
"svelte": "^3.50.1",
"svelte-preprocess": "^4.10.7",
"svelte-routing": "^1.6.0",
"xss": "^1.0.14"
"svelte-routing": "^1.6.0"
},
"devDependencies": {
"@rsksmart/mock-web3-provider": "^1.0.1",
"@sveltejs/vite-plugin-svelte": "^1.0.8",
"@tsconfig/svelte": "^3.0.0",
"@types/dompurify": "^2.3.4",
"@types/katex": "^0.14.0",
"@types/lodash": "^4.14.186",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"cypress": "^10.9.0",
@@ -1444,6 +1448,15 @@
"@types/node": "*"
}
},
"node_modules/@types/dompurify": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.3.4.tgz",
"integrity": "sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==",
"dev": true,
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/form-data": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz",
@@ -1459,6 +1472,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/katex": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz",
"integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.186",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
@@ -1527,6 +1546,12 @@
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
"node_modules/@types/trusted-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==",
"dev": true
},
"node_modules/@types/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
@@ -2799,11 +2824,6 @@
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true
},
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
},
"node_modules/cypress": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.9.0.tgz",
@@ -3070,6 +3090,11 @@
"node": ">=6.0.0"
}
},
"node_modules/dompurify": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz",
"integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA=="
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -4947,6 +4972,29 @@
"verror": "1.10.0"
}
},
"node_modules/katex": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.2.tgz",
"integrity": "sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"dependencies": {
"commander": "^8.0.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/katex/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"engines": {
"node": ">= 12"
}
},
"node_modules/keccak": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz",
@@ -7239,26 +7287,6 @@
}
}
},
"node_modules/xss": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
"integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"bin": {
"xss": "bin/xss"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/xss/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -8281,6 +8309,15 @@
"@types/node": "*"
}
},
"@types/dompurify": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.3.4.tgz",
"integrity": "sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==",
"dev": true,
"requires": {
"@types/trusted-types": "*"
}
},
"@types/form-data": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz",
@@ -8296,6 +8333,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/katex": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.14.0.tgz",
"integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==",
"dev": true
},
"@types/lodash": {
"version": "4.14.186",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.186.tgz",
@@ -8364,6 +8407,12 @@
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
"@types/trusted-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==",
"dev": true
},
"@types/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
@@ -9333,11 +9382,6 @@
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true
},
"cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
},
"cypress": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-10.9.0.tgz",
@@ -9555,6 +9599,11 @@
"esutils": "^2.0.2"
}
},
"dompurify": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz",
"integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA=="
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -10918,6 +10967,21 @@
"verror": "1.10.0"
}
},
"katex": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.2.tgz",
"integrity": "sha512-70DJdQAyh9EMsthw3AaQlDyFf54X7nWEUIa5W+rq8XOpEk//w5Th7/8SqFqpvi/KZ2t6MHUj4f9wLmztBmAYQA==",
"requires": {
"commander": "^8.0.0"
},
"dependencies": {
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
}
}
},
"keccak": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz",
@@ -12553,22 +12617,6 @@
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
"requires": {}
},
"xss": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
"integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
"requires": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+6 -2
View File
@@ -4,6 +4,7 @@
"start": "vite",
"serve": "vite preview",
"build": "scripts/build",
"postinstall": "scripts/copy-katex-assets",
"check": "scripts/check",
"format": "npx prettier '**/*.@(ts|js|svelte|json|css|html)' --ignore-path .gitignore --write",
"test:unit": "TZ='UTC' vitest run",
@@ -19,6 +20,8 @@
"@rsksmart/mock-web3-provider": "^1.0.1",
"@sveltejs/vite-plugin-svelte": "^1.0.8",
"@tsconfig/svelte": "^3.0.0",
"@types/dompurify": "^2.3.4",
"@types/katex": "^0.14.0",
"@types/lodash": "^4.14.186",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"cypress": "^10.9.0",
@@ -46,8 +49,10 @@
"@types/md5": "^2.3.2",
"@walletconnect/client": "^1.8.0",
"buffer": "^6.0.3",
"dompurify": "^2.4.0",
"ethers": "^5.7.1",
"events": "^3.3.0",
"katex": "^0.16.2",
"lodash": "^4.17.21",
"lru-cache": "^7.14.0",
"marked": "^4.1.0",
@@ -58,7 +63,6 @@
"siwe": "^2.0.5",
"svelte": "^3.50.1",
"svelte-preprocess": "^4.10.7",
"svelte-routing": "^1.6.0",
"xss": "^1.0.14"
"svelte-routing": "^1.6.0"
}
}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -Eeuo pipefail
cp -r node_modules/katex/dist/katex.min.css public/katex.min.css
cp -r node_modules/katex/dist/fonts/* public/fonts/
+3 -13
View File
@@ -8,7 +8,7 @@
getImageMime,
isUrl,
} from "@app/utils";
import xss, { getDefaultWhiteList } from "xss";
import dompurify from "dompurify";
export let content: string;
export let getImage: (path: string) => Promise<proj.Blob>;
@@ -19,18 +19,8 @@
let container: HTMLElement;
const render = (content: string): string => {
return xss(marked.parse(content), {
whiteList: {
...getDefaultWhiteList(),
img: ["src", "alt", "title"],
audio: ["src"],
video: ["src"],
a: ["href", "name"],
},
stripIgnoreTag: false,
});
};
const render = (content: string): string =>
dompurify.sanitize(marked.parse(content));
onMount(() => {
// Don't underline <a> tags that contain images.
+25 -15
View File
@@ -3,6 +3,7 @@ import type { RouteLocation } from "@app/index";
import md5 from "md5";
import { BigNumber } from "ethers";
import multibase from "multibase";
import katex from "katex";
import multihashes from "multihashes";
import type { TransactionResult } from "@gnosis.pm/safe-core-sdk";
import EthersSafe, { EthersAdapter } from "@gnosis.pm/safe-core-sdk";
@@ -808,33 +809,42 @@ export const unreachable = (value: never): never => {
throw new Error(`Unreachable code: ${value}`);
};
const markdownEmojiTokenizer: marked.TokenizerExtension = {
const emojisMarkedExtension = {
name: "emoji",
level: "inline",
start(src: string) {
return src.indexOf(":");
},
start: (src: string) => src.indexOf(":"),
tokenizer(src: string) {
const rule = /^:([\w+-]+):/;
const match = rule.exec(src);
const match = src.match(/^:([\w+-]+):/);
if (match) {
return {
type: "emoji",
raw: match[0],
emoji: match[1],
text: match[1].trim(),
};
}
},
renderer: (token: marked.Tokens.Generic) =>
`<span>${parseEmoji(token.text)}</span>`,
};
const markdownEmojiRenderer: marked.RendererExtension = {
name: "emoji",
renderer(token: marked.Tokens.Generic) {
return `<span>${parseEmoji(token.emoji)}</span>`;
const katexMarkedExtension = {
name: "katex",
level: "inline",
start: (src: string) => src.indexOf("$"),
tokenizer(src: string) {
const match = src.match(/^\$+([^$\n]+?)\$+/);
if (match) {
return {
type: "katex",
raw: match[0],
text: match[1].trim(),
};
}
},
renderer: (token: marked.Tokens.Generic) =>
katex.renderToString(token.text, {
throwOnError: false,
}),
};
export const markdownExtensions = [
markdownEmojiTokenizer,
markdownEmojiRenderer,
];
export const markdownExtensions = [emojisMarkedExtension, katexMarkedExtension];