Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add katex render to Markdown
Sebastian Martinez committed 3 years ago
commit a016e981699d54ca369bbaae310dacdbdd3950cc
parent 8e3789b799b184161f7417fcb8c3657d0439ba0c
7 files changed +142 -78
modified .gitignore
@@ -2,6 +2,12 @@ build/
node_modules/
NOTES

+
# KaTeX files
+
*.min.css
+
KaTeX_**.ttf
+
KaTeX_**.woff
+
KaTeX_**.woff2
+

# Integration Tests
cypress/screenshots

modified index.html
@@ -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" />
modified package-lock.json
@@ -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",
modified package.json
@@ -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"
  }
}
added scripts/copy-katex-assets
@@ -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/
modified src/Markdown.svelte
@@ -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.
modified src/utils.ts
@@ -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];