From 86b1e9d74a0b3fc93b9ba700cff6ec3e74816498 Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 13 Apr 2023 22:49:12 +0800 Subject: [PATCH] feat: support mermid in edit mode Signed-off-by: Innei --- package.json | 1 + pnpm-lock.yaml | 404 +++++++++++++++++++++++++- src/components/common/PageContent.tsx | 2 - src/components/ui/Editor.tsx | 1 + src/components/ui/Mermaid.tsx | 44 +++ src/hooks/useLifecycle.ts | 11 + src/hooks/useMermaid.ts | 33 --- src/lib/load-script.ts | 45 --- src/markdown/index.ts | 2 + src/markdown/remark-mermaid.ts | 2 +- src/markdown/sanitize-schema.ts | 1 + 11 files changed, 464 insertions(+), 82 deletions(-) create mode 100644 src/components/ui/Mermaid.tsx create mode 100644 src/hooks/useLifecycle.ts delete mode 100644 src/hooks/useMermaid.ts delete mode 100644 src/lib/load-script.ts diff --git a/package.json b/package.json index 84e7dc2d..48a050d9 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "lottie-react": "^2.4.0", "mdast-util-to-string": "3.2.0", "mdast-util-toc": "6.1.1", + "mermaid": "10.1.0", "nanoid": "4.0.2", "next": "13.2.4", "next-i18next": "^13.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0118fafd..fd973c47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,6 +148,9 @@ dependencies: mdast-util-toc: specifier: 6.1.1 version: 6.1.1 + mermaid: + specifier: 10.1.0 + version: 10.1.0(react-dom@18.2.0)(react@18.2.0) nanoid: specifier: 4.0.2 version: 4.0.2 @@ -1617,6 +1620,10 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + /@braintree/sanitize-url@6.0.2: + resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==} + dev: false + /@bret/truthy@1.0.1: resolution: {integrity: sha512-mFVhqy/yrh+BZwzqQQptGcTq5SJ+T0uhWQ/hmBkU0RFV3YN2UkcDhxvAzUuMDibOi6s66YM01BUGAi1y/92QGw==} dependencies: @@ -2861,6 +2868,17 @@ packages: '@pedrouid/environment': 1.0.1 dev: false + /@khanacademy/simple-markdown@0.8.6(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mAUlR9lchzfqunR89pFvNI51jQKsMpJeWYsYWw0DQcUXczn/T/V6510utgvm7X0N3zN87j1SvuKk8cMbl9IAFw==} + peerDependencies: + react: 16.14.0 + react-dom: 16.14.0 + dependencies: + '@types/react': 18.0.35 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@ledgerhq/connect-kit-loader@1.0.2: resolution: {integrity: sha512-TQ21IjcZOw/scqypaVFY3jHVqI7X7Hta3qN/us6FvTol3AY06UmrhhXGww0E9xHmAbdX241ddwXEiMBSQZFr9g==} dev: false @@ -6176,7 +6194,6 @@ packages: /commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} - dev: true /commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} @@ -6254,6 +6271,18 @@ packages: requiresBuild: true dev: false + /cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + dependencies: + layout-base: 1.0.2 + dev: false + + /cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + dependencies: + layout-base: 2.0.1 + dev: false + /cosmiconfig@6.0.0: resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} engines: {node: '>=8'} @@ -6371,6 +6400,280 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /cytoscape-cose-bilkent@4.1.0(cytoscape@3.23.0): + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + dependencies: + cose-base: 1.0.3 + cytoscape: 3.23.0 + dev: false + + /cytoscape-fcose@2.2.0(cytoscape@3.23.0): + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + dependencies: + cose-base: 2.2.0 + cytoscape: 3.23.0 + dev: false + + /cytoscape@3.23.0: + resolution: {integrity: sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==} + engines: {node: '>=0.10'} + dependencies: + heap: 0.2.7 + lodash: 4.17.21 + dev: false + + /d3-array@3.2.3: + resolution: {integrity: sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + dev: false + + /d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dev: false + + /d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + dev: false + + /d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + dependencies: + delaunator: 5.0.0 + dev: false + + /d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + dev: false + + /d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + dev: false + + /d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + dependencies: + d3-dsv: 3.0.1 + dev: false + + /d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + dev: false + + /d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-geo@3.1.0: + resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + dev: false + + /d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + dev: false + + /d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + dev: false + + /d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + dev: false + + /d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + dev: false + + /d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + dev: false + + /d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + dev: false + + /d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.1.0 + dev: false + + /d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + + /d3-transition@3.0.1(d3-selection@3.0.0): + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + dev: false + + /d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dev: false + + /d3@7.8.4: + resolution: {integrity: sha512-q2WHStdhiBtD8DMmhDPyJmXUxr6VWRngKyiJ5EfXMxPw+tqT6BhNjhJZ4w3BHsNm3QoVfZLY8Orq/qPFczwKRA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + dev: false + /d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} dependencies: @@ -6378,6 +6681,13 @@ packages: type: 1.2.0 dev: false + /dagre-d3-es@7.0.10: + resolution: {integrity: sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==} + dependencies: + d3: 7.8.4 + lodash-es: 4.17.21 + dev: false + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -6527,6 +6837,12 @@ packages: rimraf: 2.7.1 dev: true + /delaunator@5.0.0: + resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==} + dependencies: + robust-predicates: 3.0.1 + dev: false + /delay@5.0.0: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} @@ -6632,6 +6948,10 @@ packages: csstype: 3.1.2 dev: false + /dompurify@2.4.5: + resolution: {integrity: sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA==} + dev: false + /dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -6675,6 +6995,10 @@ packages: /electron-to-chromium@1.4.332: resolution: {integrity: sha512-c1Vbv5tuUlBFp0mb3mCIjw+REEsgthRgNE8BlbEDKmvzb8rxjcVki6OkQP83vLN34s0XCxpSkq7AZNep1a6xhw==} + /elkjs@0.8.2: + resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} + dev: false + /elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} dependencies: @@ -8082,6 +8406,10 @@ packages: hasBin: true dev: false + /heap@0.2.7: + resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + dev: false + /hey-listen@1.0.8: resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} dev: false @@ -8153,6 +8481,13 @@ packages: '@babel/runtime': 7.21.0 dev: false + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /idb-keyval@6.2.0: resolution: {integrity: sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==} dependencies: @@ -8219,6 +8554,11 @@ packages: side-channel: 1.0.4 dev: true + /internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -8754,6 +9094,10 @@ packages: resolution: {integrity: sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==} dev: false + /khroma@2.0.0: + resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==} + dev: false + /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -8861,6 +9205,14 @@ packages: language-subtag-registry: 0.3.22 dev: true + /layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + dev: false + + /layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + dev: false + /leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -9322,6 +9674,31 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + /mermaid@10.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-LYekSMNJygI1VnMizAPUddY95hZxOjwZxr7pODczILInO0dhQKuhXeu4sargtnuTwCilSuLS7Uiq/Qn7HTVrmA==} + dependencies: + '@braintree/sanitize-url': 6.0.2 + '@khanacademy/simple-markdown': 0.8.6(react-dom@18.2.0)(react@18.2.0) + cytoscape: 3.23.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.23.0) + cytoscape-fcose: 2.2.0(cytoscape@3.23.0) + d3: 7.8.4 + dagre-d3-es: 7.0.10 + dayjs: 1.11.7 + dompurify: 2.4.5 + elkjs: 0.8.2 + khroma: 2.0.0 + lodash-es: 4.17.21 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.1.3 + ts-dedent: 2.2.0 + uuid: 9.0.0 + web-worker: 1.2.0 + transitivePeerDependencies: + - react + - react-dom + dev: false + /micromark-core-commonmark@1.0.6: resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==} dependencies: @@ -9993,6 +10370,10 @@ packages: dev: false optional: true + /non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + dev: false + /nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -11394,6 +11775,10 @@ packages: bn.js: 5.2.1 dev: false + /robust-predicates@3.0.1: + resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} + dev: false + /rollup-plugin-terser@7.0.2(rollup@2.79.1): resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser @@ -11440,6 +11825,10 @@ packages: dependencies: queue-microtask: 1.2.3 + /rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + dev: false + /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -11490,6 +11879,10 @@ packages: engines: {node: '>=10'} dev: false + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -12348,6 +12741,11 @@ packages: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: false + /ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + dev: false + /ts-essentials@9.3.1(typescript@5.0.4): resolution: {integrity: sha512-9CChSvQMyVRo29Vb1A2jbs+LKo3d/bAf+ndSaX0T8cEiy/HChVaRN/HY5DqUryZ8hZ6uol9bEgCnGmnDbwBR9Q==} peerDependencies: @@ -13088,6 +13486,10 @@ packages: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} dev: false + /web-worker@1.2.0: + resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} + dev: false + /web3-core-helpers@1.9.0: resolution: {integrity: sha512-NeJzylAp9Yj9xAt2uTT+kyug3X0DLnfBdnAcGZuY6HhoNPDIfQRA9CkJjLngVRlGTLZGjNp9x9eR+RyZQgUlXg==} engines: {node: '>=8.0.0'} diff --git a/src/components/common/PageContent.tsx b/src/components/common/PageContent.tsx index c4df6ba9..1a1468fa 100644 --- a/src/components/common/PageContent.tsx +++ b/src/components/common/PageContent.tsx @@ -3,7 +3,6 @@ import { useCodeCopy } from "~/hooks/useCodeCopy" import { renderPageContent } from "~/markdown" import { PostToc } from "~/components/site/PostToc" import { MutableRefObject, useMemo, useRef } from "react" -import { useMermaid } from "~/hooks/useMermaid" export const PageContent: React.FC<{ content?: string @@ -35,7 +34,6 @@ export const PageContent: React.FC<{ return null } }, [content, parsedContent]) - useMermaid($articleRef, inParsedContent?.element) return (
(null) const isDark = useMediaStore((state) => state.isDark) useCodeMirrorAutoToggleTheme(editor, isDark) + useEffect(() => { setExtensions([ markdown(), diff --git a/src/components/ui/Mermaid.tsx b/src/components/ui/Mermaid.tsx new file mode 100644 index 00000000..cc128e8c --- /dev/null +++ b/src/components/ui/Mermaid.tsx @@ -0,0 +1,44 @@ +import { FC, useEffect, useState } from "react" +import { useTranslation } from "react-i18next" +import { useMediaStore } from "~/hooks/useDarkMode" +import { useIsUnmounted } from "~/hooks/useLifecycle" + +export const Mermaid: FC<{ + children: [string] +}> = (props) => { + const [loading, setLoading] = useState(true) + const [svg, setSvg] = useState("") + const isUnmounted = useIsUnmounted() + + const isDark = useMediaStore((state) => state.isDark) + + useEffect(() => { + import("mermaid").then(async (mo) => { + const mermaid = mo.default + mermaid.initialize({ + theme: isDark ? "dark" : "default", + }) + }) + }, [isDark]) + + useEffect(() => { + import("mermaid").then(async (mo) => { + const mermaid = mo.default + const result = await mermaid.render("mermaid", props.children[0]) + + if (isUnmounted()) return + setSvg(result.svg) + setLoading(false) + }) + }, [props.children]) + + const { t } = useTranslation("common") + + return loading ? ( +
+ Mermaid {t("Loading")}... +
+ ) : ( +
+ ) +} diff --git a/src/hooks/useLifecycle.ts b/src/hooks/useLifecycle.ts new file mode 100644 index 00000000..1739d26b --- /dev/null +++ b/src/hooks/useLifecycle.ts @@ -0,0 +1,11 @@ +import { useEffect, useRef } from "react" + +export const useIsUnmounted = () => { + const isUnmounted = useRef(false) + useEffect(() => { + return () => { + isUnmounted.current = true + } + }, []) + return () => isUnmounted.current +} diff --git a/src/hooks/useMermaid.ts b/src/hooks/useMermaid.ts deleted file mode 100644 index 22d07e2e..00000000 --- a/src/hooks/useMermaid.ts +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useEffect, useLayoutEffect } from "react" -import { loadScript } from "~/lib/load-script" - -declare global { - interface Window { - mermaid: any - } -} - -export const useMermaid = ( - contentRef: React.MutableRefObject, - deps: any, -) => { - useEffect(() => { - if (contentRef.current) { - const mermaidEls = contentRef.current.querySelectorAll(".mermaid") - - if (mermaidEls.length === 0) return - - loadScript( - "https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/mermaid/8.9.0/mermaid.min.js", - ).then(() => { - if (window.mermaid) { - window.mermaid.initialize({ - theme: "default", - startOnLoad: false, - }) - window.mermaid.init(undefined, ".mermaid") - } - }) - } - }, [contentRef, deps]) -} diff --git a/src/lib/load-script.ts b/src/lib/load-script.ts deleted file mode 100644 index 8d7fc4cd..00000000 --- a/src/lib/load-script.ts +++ /dev/null @@ -1,45 +0,0 @@ -const isLoadScriptMap: Record = {} -const loadingQueueMap: Record = {} -export function loadScript(url: string) { - return new Promise((resolve, reject) => { - const status = isLoadScriptMap[url] - if (status === "loaded") { - return resolve(null) - } else if (status === "loading") { - loadingQueueMap[url] = !loadingQueueMap[url] - ? [[resolve, reject]] - : [...loadingQueueMap[url], [resolve, reject]] - return - } - - const script = document.createElement("script") - script.src = url - script.crossOrigin = "anonymous" - - isLoadScriptMap[url] = "loading" - script.onload = function () { - isLoadScriptMap[url] = "loaded" - resolve(null) - if (loadingQueueMap[url]) { - loadingQueueMap[url].forEach(([resolve, reject]) => { - resolve(null) - }) - delete loadingQueueMap[url] - } - } - - script.onerror = function (e) { - // this.onload = null here is necessary - // because even IE9 works not like others - this.onerror = this.onload = null - delete isLoadScriptMap[url] - loadingQueueMap[url].forEach(([resolve, reject]) => { - reject(e) - }) - delete loadingQueueMap[url] - reject(e) - } - - document.head.appendChild(script) - }) -} diff --git a/src/markdown/index.ts b/src/markdown/index.ts index 7fa4397c..98ad5fcc 100644 --- a/src/markdown/index.ts +++ b/src/markdown/index.ts @@ -32,6 +32,7 @@ import rehypeKatex from "rehype-katex" import rehypeInferDescriptionMeta from "rehype-infer-description-meta" import remarkBreaks from "remark-breaks" import { remarkMermaid } from "./remark-mermaid" +import { Mermaid } from "~/components/ui/Mermaid" export type MarkdownEnv = { excerpt: string @@ -150,6 +151,7 @@ export const renderPageContent = ( img: Image, anchor: Element, mention: Mention, + mermaid: Mermaid, } as any, }) .use(() => (tree) => { diff --git a/src/markdown/remark-mermaid.ts b/src/markdown/remark-mermaid.ts index 0168155d..3d3a2514 100644 --- a/src/markdown/remark-mermaid.ts +++ b/src/markdown/remark-mermaid.ts @@ -7,7 +7,7 @@ export const remarkMermaid: Plugin, Root> = () => (tree, file) => { if (node.type === "code" && node.lang === "mermaid") { // @ts-ignore node.type = "html" - node.value = `
${node.value}
` + node.value = `${node.value}` } }) } diff --git a/src/markdown/sanitize-schema.ts b/src/markdown/sanitize-schema.ts index 9123f555..290c539b 100644 --- a/src/markdown/sanitize-schema.ts +++ b/src/markdown/sanitize-schema.ts @@ -14,6 +14,7 @@ const scheme = { "mention", "audio", "source", + "mermaid", ], attributes: { ...defaultSchema.attributes,