diff --git a/package.json b/package.json index e427c9b..e7b5b82 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,12 @@ "lint": "next lint" }, "dependencies": { + "@bytemd/plugin-breaks": "^1.21.0", + "@bytemd/plugin-frontmatter": "^1.21.0", "@bytemd/plugin-gfm": "^1.21.0", "@bytemd/plugin-highlight": "^1.21.0", + "@bytemd/plugin-math": "^1.21.0", + "@bytemd/plugin-mermaid": "^1.21.0", "@bytemd/react": "^1.21.0", "@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-dropdown-menu": "^2.1.5", @@ -19,6 +23,7 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-toast": "^1.2.5", "@radix-ui/react-toggle": "^1.0.3", "@tiptap/extension-image": "^2.2.4", "@tiptap/extension-link": "^2.2.4", @@ -32,6 +37,7 @@ "lucide-react": "^0.474.0", "marked": "^15.0.6", "next": "14.1.0", + "next-themes": "^0.4.4", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwind-merge": "^2.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a11136..daa8bbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,24 @@ importers: .: dependencies: + '@bytemd/plugin-breaks': + specifier: ^1.21.0 + version: 1.21.0(bytemd@1.21.0) + '@bytemd/plugin-frontmatter': + specifier: ^1.21.0 + version: 1.21.0(bytemd@1.21.0) '@bytemd/plugin-gfm': specifier: ^1.21.0 version: 1.21.0(bytemd@1.21.0) '@bytemd/plugin-highlight': specifier: ^1.21.0 version: 1.21.0(bytemd@1.21.0) + '@bytemd/plugin-math': + specifier: ^1.21.0 + version: 1.21.0(bytemd@1.21.0) + '@bytemd/plugin-mermaid': + specifier: ^1.21.0 + version: 1.21.0(bytemd@1.21.0) '@bytemd/react': specifier: ^1.21.0 version: 1.21.0(react@18.3.1) @@ -38,6 +50,9 @@ importers: '@radix-ui/react-tabs': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toast': + specifier: ^1.2.5 + version: 1.2.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-toggle': specifier: ^1.0.3 version: 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -77,6 +92,9 @@ importers: next: specifier: 14.1.0 version: 14.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: + specifier: ^0.4.4 + version: 0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.2.0 version: 18.3.1 @@ -124,6 +142,19 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@braintree/sanitize-url@6.0.4': + resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + + '@bytemd/plugin-breaks@1.21.0': + resolution: {integrity: sha512-bDGTsePltjdBFOks1fjJOWAQs/8ZtkCWAOo/ogtW4ZTphouAt2iYpLFy6udsHyofECo2U6Sr4tgCwcxDUgv/4A==} + peerDependencies: + bytemd: ^1.5.0 + + '@bytemd/plugin-frontmatter@1.21.0': + resolution: {integrity: sha512-P+qwPRSwHOwADsJtA+vtv98bnWjRd4V7eYIc9XJsDDj8dDQByXk7ikcgGqgtosBfHlHclP4DSr7ppwFixYbKQw==} + peerDependencies: + bytemd: ^1.5.0 + '@bytemd/plugin-gfm@1.21.0': resolution: {integrity: sha512-ZlrLa+Nl80gUDeC1hTnyRDfgJU3DGQVjQvX9rIIitUCler+KsAiagEnng6S/W2SZNpv+f8eWpVNL8KA8X3d7Tg==} peerDependencies: @@ -134,6 +165,16 @@ packages: peerDependencies: bytemd: ^1.5.0 + '@bytemd/plugin-math@1.21.0': + resolution: {integrity: sha512-SkKTuPX8D+3x3aVRfZbCGg52mUDar54zn/cbnPY7RYqGUQRz0tSXVApC4mJxfzWpxvPnbjkmy742NVriDUoFrw==} + peerDependencies: + bytemd: ^1.5.0 + + '@bytemd/plugin-mermaid@1.21.0': + resolution: {integrity: sha512-DwjvogJPPY0m3glGOX95+DdNrbRThbw8nfz9Kj7mu3oqq5D2Hr+0rePlNBUq9RaffnbrNEhgkDJS2/8YmtykuA==} + peerDependencies: + bytemd: ^1.5.0 + '@bytemd/react@1.21.0': resolution: {integrity: sha512-WYb+XlFHWraK9KWuNUlWMZoKlI55wDj4388E40zCsxaN/O6UUnCGdrE+6ZiVGPN3ZiqfQ1iceGIzmJ2PiB2T9w==} peerDependencies: @@ -524,6 +565,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toast@1.2.5': + resolution: {integrity: sha512-ZzUsAaOx8NdXZZKcFNDhbSlbsCUy8qQWmzTdgrlrhhZAOx2ofLtKrBDW9fkqhFvXgmtv560Uj16pkLkqML7SHA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toggle@1.1.1': resolution: {integrity: sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==} peerDependencies: @@ -776,6 +830,15 @@ packages: '@types/codemirror@5.60.15': resolution: {integrity: sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==} + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.8': + resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -785,6 +848,12 @@ packages: '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/linkify-it@5.0.0': resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} @@ -986,6 +1055,17 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -1001,10 +1081,164 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.31.0: + resolution: {integrity: sha512-zDGn1K/tfZwEnoGOcHc0H4XazqAAXAuDpcYw9mUnUjATjqljyCNGJv8uEvbvxGaGHaVshxMecyl6oc6uKzRfbw==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.10: + resolution: {integrity: sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -1020,6 +1254,9 @@ packages: defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1037,12 +1274,18 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dompurify@3.1.6: + resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} electron-to-chromium@1.5.88: resolution: {integrity: sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw==} + elkjs@0.9.3: + resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1082,6 +1325,9 @@ packages: fastq@1.18.0: resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -1094,6 +1340,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -1175,12 +1425,23 @@ packages: resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1238,9 +1499,20 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + katex@0.16.21: + resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -1249,6 +1521,9 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1314,6 +1589,9 @@ packages: mdast-util-from-markdown@1.3.1: resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + mdast-util-frontmatter@1.0.1: + resolution: {integrity: sha512-JjA2OjxRqAa8wEG8hloD0uTU0kdn8kbtOWpPP94NBkfAlbxn4S8gCGf/9DwFtEeGPXrDcNXdiDjVaRdUFqYokw==} + mdast-util-gfm-autolink-literal@1.0.3: resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} @@ -1332,6 +1610,12 @@ packages: mdast-util-gfm@2.0.2: resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} + mdast-util-math@2.0.2: + resolution: {integrity: sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==} + + mdast-util-newline-to-break@1.0.0: + resolution: {integrity: sha512-491LcYv3gbGhhCrLoeALncQmega2xPh+m3gbsIhVsOX4sw85+ShLFPvPyibxc1Swx/6GtzxgVodq+cGa/47ULg==} + mdast-util-phrasing@3.0.1: resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} @@ -1354,9 +1638,15 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@10.9.3: + resolution: {integrity: sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw==} + micromark-core-commonmark@1.1.0: resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + micromark-extension-frontmatter@1.1.1: + resolution: {integrity: sha512-m2UH9a7n3W8VAH9JO9y01APpPKmNNNs71P0RbknEmYSaZU5Ghogv38BYO94AI5Xw6OYfxZRdHZZ2nYjs/Z+SZQ==} + micromark-extension-gfm-autolink-literal@1.0.5: resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} @@ -1378,6 +1668,9 @@ packages: micromark-extension-gfm@2.0.3: resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} + micromark-extension-math@2.1.2: + resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} + micromark-factory-destination@1.1.0: resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} @@ -1473,6 +1766,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + next-themes@0.4.4: + resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@14.1.0: resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==} engines: {node: '>=18.17.0'} @@ -1499,6 +1798,9 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1750,9 +2052,18 @@ packages: rehype-stringify@9.0.4: resolution: {integrity: sha512-Uk5xu1YKdqobe5XpSskwPvo1XeHUUucWEQSl8hTrXt5selvca1e8K1EZ37E6YoZ4BT8BCqCdVfQW7OfHfthtVQ==} + remark-breaks@3.0.3: + resolution: {integrity: sha512-C7VkvcUp1TPUc2eAYzsPdaUh8Xj4FSbQnYA5A9f80diApLZscTDeG7efiWP65W8hV2sEy3JuGVU0i6qr5D8Hug==} + + remark-frontmatter@4.0.1: + resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} + remark-gfm@3.0.1: resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} + remark-math@5.1.1: + resolution: {integrity: sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==} + remark-parse@10.0.2: resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} @@ -1772,12 +2083,18 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -1785,6 +2102,9 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -1863,6 +2183,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.5: + resolution: {integrity: sha512-K7npNOKGRYuhAFFzkzMGfxFDpN6gDwf8hcMiE+uveTVbBgm93HrNP3ZDUpKqzZ4pG7TP6fmb+EMAQPjq9FqqvA==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1905,6 +2228,10 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -1981,6 +2308,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -2008,6 +2339,9 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-worker@1.3.0: + resolution: {integrity: sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2039,6 +2373,21 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@braintree/sanitize-url@6.0.4': {} + + '@bytemd/plugin-breaks@1.21.0(bytemd@1.21.0)': + dependencies: + bytemd: 1.21.0 + remark-breaks: 3.0.3 + + '@bytemd/plugin-frontmatter@1.21.0(bytemd@1.21.0)': + dependencies: + '@types/js-yaml': 4.0.9 + bytemd: 1.21.0 + js-yaml: 4.1.0 + remark-frontmatter: 4.0.1 + vfile: 5.3.7 + '@bytemd/plugin-gfm@1.21.0(bytemd@1.21.0)': dependencies: bytemd: 1.21.0 @@ -2051,6 +2400,20 @@ snapshots: bytemd: 1.21.0 highlight.js: 11.11.1 + '@bytemd/plugin-math@1.21.0(bytemd@1.21.0)': + dependencies: + '@types/katex': 0.16.7 + bytemd: 1.21.0 + katex: 0.16.21 + remark-math: 5.1.1 + + '@bytemd/plugin-mermaid@1.21.0(bytemd@1.21.0)': + dependencies: + bytemd: 1.21.0 + mermaid: 10.9.3 + transitivePeerDependencies: + - supports-color + '@bytemd/react@1.21.0(react@18.3.1)': dependencies: bytemd: 1.21.0 @@ -2424,6 +2787,26 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-toast@1.2.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-toggle@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2685,6 +3068,14 @@ snapshots: dependencies: '@types/tern': 0.23.9 + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.8': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-time@3.0.4': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -2695,6 +3086,10 @@ snapshots: dependencies: '@types/unist': 2.0.11 + '@types/js-yaml@4.0.9': {} + + '@types/katex@0.16.7': {} + '@types/linkify-it@5.0.0': {} '@types/lodash-es@4.17.12': @@ -2900,6 +3295,14 @@ snapshots: commander@4.1.1: {} + commander@7.2.0: {} + + commander@8.3.0: {} + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + crelt@1.0.6: {} cross-spawn@7.0.6: @@ -2912,8 +3315,189 @@ snapshots: csstype@3.1.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.31.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.31.0 + + cytoscape@3.31.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + 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) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + 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 + + d3-zoom@3.0.0: + 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) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + 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.1 + 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.1.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 + + dagre-d3-es@7.0.10: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + data-uri-to-buffer@4.0.1: {} + dayjs@1.11.13: {} + debug@4.4.0: dependencies: ms: 2.1.3 @@ -2926,6 +3510,10 @@ snapshots: dependencies: clone: 1.0.4 + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + dequal@2.0.3: {} detect-node-es@1.1.0: {} @@ -2936,10 +3524,14 @@ snapshots: dlv@1.1.3: {} + dompurify@3.1.6: {} + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.88: {} + elkjs@0.9.3: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -2980,6 +3572,10 @@ snapshots: dependencies: reusify: 1.0.4 + fault@2.0.1: + dependencies: + format: 0.2.2 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -2994,6 +3590,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + format@0.2.2: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -3109,10 +3707,18 @@ snapshots: human-signals@4.3.1: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} inherits@2.0.4: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3153,16 +3759,28 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + jsonfile@6.1.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + katex@0.16.21: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + kleur@3.0.3: {} kleur@4.1.5: {} + layout-base@1.0.2: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -3241,6 +3859,12 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-frontmatter@1.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + micromark-extension-frontmatter: 1.1.1 + mdast-util-gfm-autolink-literal@1.0.3: dependencies: '@types/mdast': 3.0.15 @@ -3285,6 +3909,17 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-math@2.0.2: + dependencies: + '@types/mdast': 3.0.15 + longest-streak: 3.1.0 + mdast-util-to-markdown: 1.5.0 + + mdast-util-newline-to-break@1.0.0: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-find-and-replace: 2.2.2 + mdast-util-phrasing@3.0.1: dependencies: '@types/mdast': 3.0.15 @@ -3322,6 +3957,31 @@ snapshots: merge2@1.4.1: {} + mermaid@10.9.3: + dependencies: + '@braintree/sanitize-url': 6.0.4 + '@types/d3-scale': 4.0.8 + '@types/d3-scale-chromatic': 3.1.0 + cytoscape: 3.31.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.31.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.10 + dayjs: 1.11.13 + dompurify: 3.1.6 + elkjs: 0.9.3 + katex: 0.16.21 + khroma: 2.1.0 + lodash-es: 4.17.21 + mdast-util-from-markdown: 1.3.1 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.3.5 + ts-dedent: 2.2.0 + uuid: 9.0.1 + web-worker: 1.3.0 + transitivePeerDependencies: + - supports-color + micromark-core-commonmark@1.1.0: dependencies: decode-named-character-reference: 1.0.2 @@ -3341,6 +4001,13 @@ snapshots: micromark-util-types: 1.1.0 uvu: 0.5.6 + micromark-extension-frontmatter@1.1.1: + dependencies: + fault: 2.0.1 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + micromark-extension-gfm-autolink-literal@1.0.5: dependencies: micromark-util-character: 1.2.0 @@ -3399,6 +4066,16 @@ snapshots: micromark-util-combine-extensions: 1.1.0 micromark-util-types: 1.1.0 + micromark-extension-math@2.1.2: + dependencies: + '@types/katex': 0.16.7 + katex: 0.16.21 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + micromark-factory-destination@1.1.0: dependencies: micromark-util-character: 1.2.0 @@ -3540,6 +4217,11 @@ snapshots: nanoid@3.3.8: {} + next-themes@0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + next@14.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.1.0 @@ -3575,6 +4257,8 @@ snapshots: node-releases@2.0.19: {} + non-layered-tidy-tree-layout@2.0.2: {} + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -3863,6 +4547,19 @@ snapshots: hast-util-to-html: 8.0.4 unified: 10.1.2 + remark-breaks@3.0.3: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-newline-to-break: 1.0.0 + unified: 10.1.2 + + remark-frontmatter@4.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-frontmatter: 1.0.1 + micromark-extension-frontmatter: 1.1.1 + unified: 10.1.2 + remark-gfm@3.0.1: dependencies: '@types/mdast': 3.0.15 @@ -3872,6 +4569,13 @@ snapshots: transitivePeerDependencies: - supports-color + remark-math@5.1.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-math: 2.0.2 + micromark-extension-math: 2.1.2 + unified: 10.1.2 + remark-parse@10.0.2: dependencies: '@types/mdast': 3.0.15 @@ -3900,18 +4604,24 @@ snapshots: reusify@1.0.4: {} + robust-predicates@3.0.2: {} + rope-sequence@1.3.4: {} run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + sade@1.8.1: dependencies: mri: 1.2.0 safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -3976,6 +4686,8 @@ snapshots: client-only: 0.0.1 react: 18.3.1 + stylis@4.3.5: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -4041,6 +4753,8 @@ snapshots: trough@2.2.0: {} + ts-dedent@2.2.0: {} + ts-interface-checker@0.1.13: {} tslib@2.8.1: {} @@ -4115,6 +4829,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@9.0.1: {} + uvu@0.5.6: dependencies: dequal: 2.0.3 @@ -4149,6 +4865,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-worker@1.3.0: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/src/app/globals.css b/src/app/globals.css index de4fd08..08a7c68 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -108,41 +108,162 @@ --tw-prose-quotes: #666666; } -/* 预览内容样式 */ +/* 预览内容样式优化 */ .preview-content { - word-break: break-all; + word-break: break-word; white-space: pre-wrap; + font-size: 15px; + line-height: 1.75; + color: hsl(var(--foreground)); + padding: 1.5rem; + max-width: 100%; + margin: 0 auto; +} + +.preview-content h1, +.preview-content h2, +.preview-content h3, +.preview-content h4, +.preview-content h5, +.preview-content h6 { + font-weight: 600; + line-height: 1.25; + margin-top: 2em; + margin-bottom: 1em; + color: hsl(var(--foreground)); +} + +.preview-content h1 { + font-size: 1.5em; +} + +.preview-content h2 { + font-size: 1.3em; +} + +.preview-content h3 { + font-size: 1.1em; +} + +.preview-content p { + margin: 1.2em 0; + color: hsl(var(--foreground)); } .preview-content img { max-width: 100%; height: auto; - margin: 1em auto; + margin: 1.5em auto; display: block; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; +} + +.preview-content img:hover { + transform: scale(1.02); } .preview-content pre { white-space: pre-wrap; word-wrap: break-word; + background: hsl(var(--muted)); + border-radius: 6px; + padding: 1em; + margin: 1.5em 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + line-height: 1.6; + overflow-x: auto; } -/* 优化预览区域滚动效果 */ +.preview-content code { + background: hsl(var(--muted)); + padding: 0.2em 0.4em; + border-radius: 3px; + font-size: 0.9em; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; +} + +.preview-content blockquote { + margin: 1.5em 0; + padding: 0.8em 1em; + border-left: 4px solid hsl(var(--primary)); + background: hsl(var(--muted)); + border-radius: 0 4px 4px 0; + color: hsl(var(--muted-foreground)); +} + +.preview-content ul, +.preview-content ol { + margin: 1.2em 0; + padding-left: 1.5em; + color: hsl(var(--foreground)); +} + +.preview-content li { + margin: 0.5em 0; +} + +.preview-content table { + width: 100%; + margin: 1.5em 0; + border-collapse: collapse; +} + +.preview-content th, +.preview-content td { + padding: 0.75em; + border: 1px solid hsl(var(--border)); +} + +.preview-content th { + background: hsl(var(--muted)); + font-weight: 600; +} + +/* 预览区域滚动条优化 */ .preview-container { scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.2) transparent; + scrollbar-color: hsl(var(--muted-foreground)) hsl(var(--muted)); } .preview-container::-webkit-scrollbar { - width: 6px; + width: 8px; + height: 8px; } .preview-container::-webkit-scrollbar-track { - background: transparent; + background: hsl(var(--muted)); + border-radius: 4px; } .preview-container::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.2); - border-radius: 3px; + background-color: hsl(var(--muted-foreground)); + border-radius: 4px; + border: 2px solid hsl(var(--muted)); +} + +.preview-container::-webkit-scrollbar-thumb:hover { + background-color: hsl(var(--foreground)); +} + +/* 预览区域响应式布局 */ +@media (max-width: 768px) { + .preview-content { + padding: 0.75rem; + font-size: 14px; + } + + .preview-content img { + margin: 1em auto; + } + + .preview-content pre { + margin: 1em 0; + padding: 0.75em; + font-size: 0.85em; + } } /* 模板预览卡片样式 */ @@ -173,31 +294,558 @@ opacity: 1; } -/* ByteMD 编辑器样式 */ -.bytemd-preview { - @apply prose max-w-none p-4; +/* ByteMD 编辑器样式优化 */ +.bytemd { + height: 100% !important; + border: none !important; + background: transparent !important; + display: flex !important; + flex-direction: column !important; } -.prose-modern .bytemd-preview { - --tw-prose-body: #374151; - --tw-prose-headings: #111827; - --tw-prose-links: #2563eb; - --tw-prose-code: #374151; - --tw-prose-quotes: #4b5563; +.bytemd-toolbar { + background: #fff !important; + border-bottom: 1px solid #e5e7eb !important; + padding: 8px !important; + position: sticky !important; + top: 0 !important; + z-index: 10 !important; } -.prose-elegant .bytemd-preview { - --tw-prose-body: #4a5568; - --tw-prose-headings: #2d3748; - --tw-prose-links: #4299e1; - --tw-prose-code: #4a5568; - --tw-prose-quotes: #718096; +.bytemd-toolbar-left { + display: flex !important; + flex-wrap: wrap !important; + gap: 4px !important; } -.prose-minimal .bytemd-preview { - --tw-prose-body: #1a1a1a; - --tw-prose-headings: #000000; - --tw-prose-links: #0066cc; - --tw-prose-code: #1a1a1a; - --tw-prose-quotes: #666666; +.bytemd-toolbar-right { + display: flex !important; + gap: 4px !important; +} + +.bytemd-toolbar-icon { + padding: 6px !important; + margin: 0 !important; + border-radius: 4px !important; + color: #4b5563 !important; + transition: all 0.2s ease !important; +} + +.bytemd-toolbar-icon:hover { + background: #f3f4f6 !important; + color: #111827 !important; +} + +.bytemd-toolbar-icon.active { + background: #f3f4f6 !important; + color: #2563eb !important; +} + +.bytemd-status { + background: #fff !important; + border-top: 1px solid #e5e7eb !important; + padding: 4px 12px !important; + color: #6b7280 !important; + font-size: 12px !important; + position: sticky !important; + bottom: 0 !important; + z-index: 10 !important; +} + +.bytemd-editor { + background: #fff !important; + flex: 1 !important; + overflow: auto !important; + padding: 0 16px !important; +} + +.CodeMirror { + height: 100% !important; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important; + font-size: 14px !important; + line-height: 1.6 !important; + color: #374151 !important; + padding: 16px 0 !important; +} + +.CodeMirror-lines { + padding: 0 !important; +} + +.CodeMirror-line { + padding: 0 !important; +} + +.CodeMirror-cursor { + border-left: 2px solid #2563eb !important; +} + +.CodeMirror-selected { + background: rgba(37, 99, 235, 0.1) !important; +} + +/* Markdown 语法高亮 */ +.cm-s-default .cm-header { + color: #1e40af !important; + font-weight: 600 !important; +} + +.cm-s-default .cm-quote { + color: #059669 !important; + font-style: italic !important; +} + +.cm-s-default .cm-link { + color: #2563eb !important; + text-decoration: none !important; +} + +.cm-s-default .cm-url { + color: #6b7280 !important; +} + +.cm-s-default .cm-code, +.cm-s-default .cm-comment { + color: #dc2626 !important; + background: rgba(0, 0, 0, 0.05) !important; + padding: 2px 4px !important; + border-radius: 3px !important; +} + +.cm-s-default .cm-strong { + color: #111827 !important; + font-weight: 600 !important; +} + +.cm-s-default .cm-em { + color: #4b5563 !important; + font-style: italic !important; +} + +/* 编辑器滚动条优化 */ +.bytemd-editor ::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.bytemd-editor ::-webkit-scrollbar-track { + background: transparent; +} + +.bytemd-editor ::-webkit-scrollbar-thumb { + background: #d1d5db; + border-radius: 3px; +} + +.bytemd-editor ::-webkit-scrollbar-thumb:hover { + background: #9ca3af; +} + +/* 编辑器暗色主题支持 */ +.dark .bytemd { + background: hsl(var(--background)) !important; +} + +.dark .bytemd-toolbar { + background: hsl(var(--background)) !important; + border-color: hsl(var(--border)) !important; +} + +.dark .bytemd-toolbar-icon { + color: hsl(var(--muted-foreground)) !important; +} + +.dark .bytemd-toolbar-icon:hover { + background: hsl(var(--accent)) !important; + color: hsl(var(--accent-foreground)) !important; +} + +.dark .bytemd-toolbar-icon.active { + background: hsl(var(--accent)) !important; + color: hsl(var(--accent-foreground)) !important; +} + +.dark .bytemd-status { + background: hsl(var(--background)) !important; + border-color: hsl(var(--border)) !important; + color: hsl(var(--muted-foreground)) !important; +} + +.dark .bytemd-editor { + background: hsl(var(--background)) !important; +} + +.dark .CodeMirror { + color: hsl(var(--foreground)) !important; + background: hsl(var(--background)) !important; +} + +.dark .CodeMirror-selected { + background: hsl(var(--accent))/.2 !important; +} + +.dark .CodeMirror-line::selection, +.dark .CodeMirror-line > span::selection, +.dark .CodeMirror-line > span > span::selection { + background: hsl(var(--accent))/.2 !important; +} + +.dark .cm-s-default .cm-header { + color: hsl(var(--primary)) !important; +} + +.dark .cm-s-default .cm-quote { + color: hsl(var(--accent-foreground)) !important; +} + +.dark .cm-s-default .cm-link { + color: hsl(var(--primary)) !important; +} + +.dark .cm-s-default .cm-url { + color: hsl(var(--muted-foreground)) !important; +} + +.dark .cm-s-default .cm-code, +.dark .cm-s-default .cm-comment { + color: hsl(var(--destructive)) !important; + background: hsl(var(--muted)) !important; + border-radius: 3px; + padding: 0 3px; +} + +.dark .cm-s-default .cm-strong { + color: hsl(var(--foreground)) !important; +} + +.dark .cm-s-default .cm-em { + color: hsl(var(--muted-foreground)) !important; +} + +/* 编辑器容器样式优化 */ +.editor-container { + display: flex; + flex-direction: column; + height: 100%; +} + +.editor-container .bytemd { + flex: 1; + display: flex; + flex-direction: column; + height: 100% !important; +} + +.editor-container .bytemd-toolbar { + flex-shrink: 0; +} + +.editor-container .bytemd-editor { + flex: 1; + overflow: auto; + height: auto !important; +} + +/* 预览容器样式优化 */ +.preview-container { + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.preview-container .preview-header { + flex-shrink: 0; +} + +.preview-container .preview-content { + padding: 20px; + overflow-wrap: break-word; + font-size: 15px; + line-height: 1.75; + color: #333; + height: 100%; + overflow-y: auto; +} + + + + + +.editor-container .bytemd-editor::-webkit-scrollbar-thumb, +.preview-container .preview-content::-webkit-scrollbar-thumb, +.CodeMirror-vscrollbar::-webkit-scrollbar-thumb, +.CodeMirror-hscrollbar::-webkit-scrollbar-thumb { + background-color: hsl(var(--muted-foreground)); + border-radius: 4px; + border: 2px solid hsl(var(--muted)); +} + +.editor-container .bytemd-editor::-webkit-scrollbar-thumb:hover, +.preview-container .preview-content::-webkit-scrollbar-thumb:hover, +.CodeMirror-vscrollbar::-webkit-scrollbar-thumb:hover, +.CodeMirror-hscrollbar::-webkit-scrollbar-thumb:hover { + background-color: hsl(var(--foreground)); +} + +/* 暗黑模式下的滚动条样式 */ +.dark .editor-container .bytemd-editor, +.dark .preview-container .preview-content, +.dark .CodeMirror-vscrollbar, +.dark .CodeMirror-hscrollbar { + scrollbar-color: hsl(var(--muted-foreground)) hsl(var(--muted)); +} + +.dark .editor-container .bytemd-editor::-webkit-scrollbar-track, +.dark .preview-container .preview-content::-webkit-scrollbar-track, +.dark .CodeMirror-vscrollbar::-webkit-scrollbar-track, +.dark .CodeMirror-hscrollbar::-webkit-scrollbar-track { + background: hsl(var(--muted)); +} + +.dark .editor-container .bytemd-editor::-webkit-scrollbar-thumb, +.dark .preview-container .preview-content::-webkit-scrollbar-thumb, +.dark .CodeMirror-vscrollbar::-webkit-scrollbar-thumb, +.dark .CodeMirror-hscrollbar::-webkit-scrollbar-thumb { + background-color: hsl(var(--muted-foreground)); + border: 2px solid hsl(var(--muted)); +} + +.dark .editor-container .bytemd-editor::-webkit-scrollbar-thumb:hover, +.dark .preview-container .preview-content::-webkit-scrollbar-thumb:hover, +.dark .CodeMirror-vscrollbar::-webkit-scrollbar-thumb:hover, +.dark .CodeMirror-hscrollbar::-webkit-scrollbar-thumb:hover { + background-color: hsl(var(--foreground)); +} + +/* 编辑器内部滚动条样式 */ +.CodeMirror-vscrollbar, +.CodeMirror-hscrollbar { + display: block !important; +} + +.CodeMirror-vscrollbar { + width: 8px !important; +} + +.CodeMirror-hscrollbar { + height: 8px !important; +} + +/* 隐藏默认滚动条 */ +.CodeMirror-scroll { + margin-right: -8px !important; + margin-bottom: -8px !important; +} + +/* 暗黑模式下的预览内容样式 */ +.dark .preview-content { + color: hsl(var(--foreground)); +} + +.dark .preview-content h1, +.dark .preview-content h2, +.dark .preview-content h3, +.dark .preview-content h4, +.dark .preview-content h5, +.dark .preview-content h6 { + color: hsl(var(--foreground)); +} + +.dark .preview-content p { + color: hsl(var(--foreground)); +} + +.dark .preview-content pre { + background: hsl(var(--muted)); + border: 1px solid hsl(var(--border)); +} + +.dark .preview-content code { + background: hsl(var(--muted)); + color: hsl(var(--accent-foreground)); +} + +.dark .preview-content blockquote { + background: hsl(var(--muted)); + border-left-color: hsl(var(--primary)); + color: hsl(var(--muted-foreground)); +} + +.dark .preview-content img { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.dark .preview-content th { + background: hsl(var(--muted)); +} + +.dark .preview-content td, +.dark .preview-content th { + border-color: hsl(var(--border)); +} + +/* 暗黑模式下的编辑器样式 */ +.dark .bytemd { + background: hsl(var(--background)) !important; +} + +.dark .bytemd-toolbar { + background: hsl(var(--background)) !important; + border-color: hsl(var(--border)) !important; +} + +.dark .bytemd-toolbar-icon { + color: hsl(var(--muted-foreground)) !important; +} + +.dark .bytemd-toolbar-icon:hover { + background: hsl(var(--accent)) !important; + color: hsl(var(--accent-foreground)) !important; +} + +.dark .bytemd-toolbar-icon.active { + background: hsl(var(--accent)) !important; + color: hsl(var(--accent-foreground)) !important; +} + +.dark .bytemd-status { + background: hsl(var(--background)) !important; + border-color: hsl(var(--border)) !important; + color: hsl(var(--muted-foreground)) !important; +} + +.dark .bytemd-editor { + background: hsl(var(--background)) !important; +} + +.dark .CodeMirror { + color: hsl(var(--foreground)) !important; + background: hsl(var(--background)) !important; +} + +.dark .CodeMirror-cursor { + border-left-color: hsl(var(--primary)) !important; +} + +.dark .CodeMirror-selected { + background: hsl(var(--accent))/.2 !important; +} + +.dark .CodeMirror-line::selection, +.dark .CodeMirror-line > span::selection, +.dark .CodeMirror-line > span > span::selection { + background: hsl(var(--accent))/.2 !important; +} + +.dark .cm-s-default .cm-header { + color: hsl(var(--primary)) !important; +} + +.dark .cm-s-default .cm-quote { + color: hsl(var(--accent-foreground)) !important; +} + +.dark .cm-s-default .cm-link { + color: hsl(var(--primary)) !important; +} + +.dark .cm-s-default .cm-url { + color: hsl(var(--muted-foreground)) !important; +} + +.dark .cm-s-default .cm-code, +.dark .cm-s-default .cm-comment { + color: hsl(var(--destructive)) !important; + background: hsl(var(--muted)) !important; + border-radius: 3px; + padding: 0 3px; +} + +.dark .cm-s-default .cm-strong { + color: hsl(var(--foreground)) !important; +} + +.dark .cm-s-default .cm-em { + color: hsl(var(--muted-foreground)) !important; +} + +/* 暗黑模式下的滚动条样式 */ +.dark .editor-container .bytemd-editor::-webkit-scrollbar, +.dark .preview-container ::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.dark .editor-container .bytemd-editor::-webkit-scrollbar-track, +.dark .preview-container ::-webkit-scrollbar-track { + background: hsl(var(--background)); + border-radius: 4px; +} + +.dark .editor-container .bytemd-editor::-webkit-scrollbar-thumb, +.dark .preview-container ::-webkit-scrollbar-thumb { + background: hsl(var(--muted)); + border-radius: 4px; + border: 2px solid hsl(var(--background)); +} + +.dark .editor-container .bytemd-editor::-webkit-scrollbar-thumb:hover, +.dark .preview-container ::-webkit-scrollbar-thumb:hover { + background: hsl(var(--muted-foreground)); +} + +/* 暗黑模式下的UI元素样式 */ +.dark .template-preview-card { + background: hsl(var(--background)); + border-color: hsl(var(--border)); +} + +.dark .template-preview-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.dark .template-preview-card::before { + background: linear-gradient(to right, hsl(var(--primary)), hsl(var(--primary-foreground))); +} + +/* 暗黑模式下的分割线样式 */ +.dark .h-px { + background: hsl(var(--border)); +} + +/* 暗黑模式下的阴影样式 */ +.dark .shadow-sm { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3); +} + +/* 暗黑模式下的选择框样式 */ +.dark select { + background-color: hsl(var(--background)); + border-color: hsl(var(--border)); + color: hsl(var(--foreground)); +} + +.dark select:focus { + border-color: hsl(var(--primary)); + box-shadow: 0 0 0 2px hsl(var(--primary)/.2); +} + +.dark select option { + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); +} + +/* 暗黑模式下的按钮悬停效果 */ +.dark .hover\:bg-muted\/90:hover { + background-color: hsl(var(--muted)/.9); +} + +.dark .hover\:bg-primary\/90:hover { + background-color: hsl(var(--primary)/.9); +} + +/* 暗黑模式下的加载动画 */ +.dark .animate-spin { + color: hsl(var(--primary)); } \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 486c91b..5f33c5c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,8 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { cn } from "@/lib/utils"; +import { Toaster } from "@/components/ui/toaster"; +import { ThemeProvider } from "@/components/theme-provider"; const inter = Inter({ subsets: ["latin"] }); @@ -16,12 +18,20 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + + {children} + + ); diff --git a/src/app/wechat/page.tsx b/src/app/wechat/page.tsx index 23518cf..e9ba8a6 100644 --- a/src/app/wechat/page.tsx +++ b/src/app/wechat/page.tsx @@ -7,14 +7,6 @@ export default function WechatPage() {
-
-

- 微信公众号编辑器 -

-

- 编辑内容,预览效果,一键复制 -

-
diff --git a/src/components/editor/WechatEditor.tsx b/src/components/editor/WechatEditor.tsx index 4a3f084..bc04f49 100644 --- a/src/components/editor/WechatEditor.tsx +++ b/src/components/editor/WechatEditor.tsx @@ -3,39 +3,188 @@ import { Editor } from '@bytemd/react' import gfm from '@bytemd/plugin-gfm' import highlight from '@bytemd/plugin-highlight' -import { useState } from 'react' +import breaks from '@bytemd/plugin-breaks' +import frontmatter from '@bytemd/plugin-frontmatter' +import math from '@bytemd/plugin-math' +import mermaid from '@bytemd/plugin-mermaid' +import { useState, useCallback, useEffect, useRef, useMemo } from 'react' import { templates } from '@/config/wechat-templates' import { cn } from '@/lib/utils' -import { Copy, Smartphone } from 'lucide-react' +import { Copy, Smartphone, Loader2, Save } from 'lucide-react' import { WechatStylePicker } from '../template/WechatStylePicker' import { StyleConfigDialog } from './StyleConfigDialog' import { convertToWechat } from '@/lib/markdown' import { type RendererOptions } from '@/lib/markdown' import { TemplateManager } from '../template/TemplateManager' +import { useToast } from '@/components/ui/use-toast' +import { ToastAction } from '@/components/ui/toast' import 'bytemd/dist/index.css' +import 'highlight.js/styles/github.css' +import 'katex/dist/katex.css' +import type { BytemdPlugin } from 'bytemd' -const plugins = [ - gfm(), - highlight(), -] +const PREVIEW_SIZES = { + small: { width: '360px', label: '小屏' }, + medium: { width: '390px', label: '中屏' }, + large: { width: '420px', label: '大屏' }, + full: { width: '100%', label: '全屏' }, +} as const + +type PreviewSize = keyof typeof PREVIEW_SIZES + +const AUTO_SAVE_DELAY = 3000 // 自动保存延迟(毫秒) export default function WechatEditor() { + const { toast } = useToast() + const autoSaveTimerRef = useRef() + const editorRef = useRef(null) + const previewRef = useRef(null) const [value, setValue] = useState('') const [selectedTemplate, setSelectedTemplate] = useState('') const [showPreview, setShowPreview] = useState(true) const [styleOptions, setStyleOptions] = useState({}) + const [previewSize, setPreviewSize] = useState('medium') + const [isConverting, setIsConverting] = useState(false) + const [isDraft, setIsDraft] = useState(false) - const handleEditorChange = (v: any) => { - - if (typeof v === 'string') { - setValue(v) - } else if (v?.value && typeof v.value === 'string') { - setValue(v.value) - } else { - console.warn('Unexpected editor value:', v) - setValue('') + // 防止滚动事件循环的标志 + const isScrolling = useRef(false) + + // 同步滚动处理 + const handleScroll = useCallback((event: Event) => { + const source = event.target + // 检查是否是编辑器滚动 + const isEditor = source instanceof Element && source.closest('.editor-container') + + if (!editorRef.current) return + + const editorElement = editorRef.current.querySelector('.bytemd-editor') + if (!editorElement) return + + // 防止滚动事件循环 + if (isScrolling.current) return + isScrolling.current = true + + try { + if (isEditor) { + const sourceScrollTop = (source as Element).scrollTop + const sourceMaxScroll = (source as Element).scrollHeight - (source as Element).clientHeight + const percentage = sourceScrollTop / sourceMaxScroll + + const windowMaxScroll = document.documentElement.scrollHeight - window.innerHeight + window.scrollTo({ + top: percentage * windowMaxScroll, + behavior: 'auto' + }) + } else { + const windowScrollTop = window.scrollY + const windowMaxScroll = document.documentElement.scrollHeight - window.innerHeight + const percentage = windowScrollTop / windowMaxScroll + + const targetScrollTop = percentage * (editorElement.scrollHeight - editorElement.clientHeight) + editorElement.scrollTop = targetScrollTop + } + } finally { + // 确保在下一帧重置标志 + requestAnimationFrame(() => { + isScrolling.current = false + }) } - } + }, []) + + // 添加滚动事件监听 + useEffect(() => { + const editorElement = editorRef.current?.querySelector('.bytemd-editor') + + if (editorElement) { + editorElement.addEventListener('scroll', handleScroll, { passive: true }) + window.addEventListener('scroll', handleScroll, { passive: true }) + + return () => { + editorElement.removeEventListener('scroll', handleScroll) + window.removeEventListener('scroll', handleScroll) + } + } + }, [handleScroll]) + + // 自动保存处理 + const handleEditorChange = useCallback((v: string) => { + setValue(v) + setIsDraft(true) + + // 清除之前的定时器 + if (autoSaveTimerRef.current) { + clearTimeout(autoSaveTimerRef.current) + } + + // 设置新的自动保存定时器 + autoSaveTimerRef.current = setTimeout(() => { + localStorage.setItem('wechat_editor_draft', v) + toast({ + description: "内容已自动保存", + duration: 2000 + }) + }, AUTO_SAVE_DELAY) + }, [toast]) + + // 手动保存 + const handleSave = useCallback(() => { + try { + localStorage.setItem('wechat_editor_content', value) + setIsDraft(false) + toast({ + title: "保存成功", + description: "内容已保存到本地", + duration: 3000 + }) + } catch (error) { + toast({ + variant: "destructive", + title: "保存失败", + description: "无法保存内容,请检查浏览器存储空间", + action: 重试, + }) + } + }, [value, toast]) + + // 加载已保存的内容 + useEffect(() => { + const draftContent = localStorage.getItem('wechat_editor_draft') + const savedContent = localStorage.getItem('wechat_editor_content') + + if (draftContent) { + setValue(draftContent) + setIsDraft(true) + toast({ + description: "已恢复未保存的草稿", + action: 放弃草稿, + duration: 5000, + }) + } else if (savedContent) { + setValue(savedContent) + } + }, [toast]) + + // 清理自动保存定时器 + useEffect(() => { + return () => { + if (autoSaveTimerRef.current) { + clearTimeout(autoSaveTimerRef.current) + } + } + }, []) + + // 监听快捷键保存事件 + useEffect(() => { + const handleSaveShortcut = (e: CustomEvent) => { + handleSave() + } + + window.addEventListener('bytemd-save', handleSaveShortcut as EventListener) + return () => { + window.removeEventListener('bytemd-save', handleSaveShortcut as EventListener) + } + }, [handleSave]) const getPreviewContent = () => { if (!value) return '' @@ -67,146 +216,401 @@ export default function WechatEditor() { const copyContent = () => { const content = getPreviewContent() navigator.clipboard.writeText(content) - .then(() => alert('内容已复制到剪贴板')) - .catch(err => console.error('复制失败:', err)) + .then(() => toast({ + title: "复制成功", + description: "已复制源代码到剪贴板", + duration: 2000 + })) + .catch(err => toast({ + variant: "destructive", + title: "复制失败", + description: "无法访问剪贴板,请检查浏览器权限", + action: 重试, + })) } const handleTemplateChange = () => { setValue(value) } - const handleCopy = () => { + const handleCopy = async () => { const previewContent = document.querySelector('.preview-content') - if (!previewContent) return - - // 创建一个临时容器来保持样式 - const tempDiv = document.createElement('div') - tempDiv.innerHTML = previewContent.innerHTML - - // 复制计算后的样式 - const styles = window.getComputedStyle(previewContent) - const importantStyles = { - 'font-family': styles.fontFamily, - 'font-size': styles.fontSize, - 'color': styles.color, - 'line-height': styles.lineHeight, - 'text-align': styles.textAlign, - 'white-space': styles.whiteSpace, - 'margin': styles.margin, - 'padding': styles.padding + if (!previewContent) { + toast({ + variant: "destructive", + title: "复制失败", + description: "未找到预览内容", + duration: 2000 + }) + return } - - // 应用样式到临时容器 - Object.assign(tempDiv.style, importantStyles) - - // 将临时容器添加到文档中(隐藏) - tempDiv.style.position = 'fixed' - tempDiv.style.left = '-9999px' - document.body.appendChild(tempDiv) - - // 创建选区并复制 - const range = document.createRange() - range.selectNode(tempDiv) - const selection = window.getSelection() - if (selection) { - selection.removeAllRanges() - selection.addRange(range) + + try { + // 创建一个临时容器 + const tempDiv = document.createElement('div') - try { - document.execCommand('copy') - alert('预览内容(带格式)已复制到剪贴板') - } catch (err) { - console.error('复制失败:', err) + // 使用与预览相同的转换逻辑 + const template = templates.find(t => t.id === selectedTemplate) + + const mergedOptions = { + ...styleOptions, + ...(template?.options || {}) + } + // 使用相同的转换逻辑获取内容 + const html = convertToWechat(value, mergedOptions) + let finalHtml = html + + if (template?.transform) { + try { + const transformed = template.transform(html) + if (transformed && typeof transformed === 'object') { + const result = transformed as { html?: string; content?: string } + if (result.html) finalHtml = result.html + else if (result.content) finalHtml = result.content + else finalHtml = JSON.stringify(transformed) + } else { + finalHtml = transformed || html + } + } catch (error) { + console.error('Template transformation error:', error) + finalHtml = html + } } - selection.removeAllRanges() + // 设置转换后的内容 + tempDiv.innerHTML = finalHtml + + // 应用模板样式类 + if (template) { + tempDiv.className = template.styles + } + alert(tempDiv.innerHTML) + + // 使用 Blob 和 Clipboard API 复制 + const blob = new Blob([tempDiv.innerHTML], { type: 'text/html' }) + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/html': blob + }) + ]) + + toast({ + title: "复制成功", + description: template + ? "已复制预览内容(使用当前模板样式)" + : "已复制预览内容(无样式)", + duration: 2000 + }) + } catch (err) { + toast({ + variant: "destructive", + title: "复制失败", + description: "无法访问剪贴板,请检查浏览器权限", + action: 重试, + }) + console.error('Copy error:', err) } - - // 清理临时元素 - document.body.removeChild(tempDiv) } + // 创建编辑器插件 + const createEditorPlugin = useCallback((): BytemdPlugin => { + const applyTemplateStyles = (markdownBody: Element, selectedTemplateId: string, options: RendererOptions) => { + const template = templates.find(t => t.id === selectedTemplateId) + if (template) { + markdownBody.classList.add(template.styles) + + // 应用模板的样式配置 + const mergedOptions = { + ...options, + ...(template.options || {}) + } + + // 应用标题样式 + const headings = markdownBody.querySelectorAll('h1, h2, h3, h4, h5, h6') + headings.forEach(heading => { + const level = heading.tagName.toLowerCase() + const style = mergedOptions.block?.[level as keyof RendererOptions['block']] + if (style && heading instanceof HTMLElement) { + Object.assign(heading.style, style) + } + }) + + // 应用段落样式 + const paragraphs = markdownBody.querySelectorAll('p') + paragraphs.forEach(p => { + if (mergedOptions.block?.p && p instanceof HTMLElement) { + Object.assign(p.style, mergedOptions.block.p) + } + }) + + // 应用引用样式 + const blockquotes = markdownBody.querySelectorAll('blockquote') + blockquotes.forEach(quote => { + if (mergedOptions.block?.blockquote && quote instanceof HTMLElement) { + Object.assign(quote.style, mergedOptions.block.blockquote) + } + }) + + // 应用代码块样式 + const codeBlocks = markdownBody.querySelectorAll('pre code') + codeBlocks.forEach(code => { + if (mergedOptions.block?.code_pre && code.parentElement instanceof HTMLElement) { + Object.assign(code.parentElement.style, mergedOptions.block.code_pre) + } + }) + + // 应用行内代码样式 + const inlineCodes = markdownBody.querySelectorAll(':not(pre) > code') + inlineCodes.forEach(code => { + if (mergedOptions.inline?.codespan && code instanceof HTMLElement) { + Object.assign(code.style, mergedOptions.inline.codespan) + } + }) + } + } + + return { + actions: [ + { + title: '保存', + icon: '', + handler: { + type: 'action', + click: (ctx: any) => { + // 触发自定义事件 + const event = new CustomEvent('bytemd-save', { detail: ctx.editor.getValue() }) + window.dispatchEvent(event) + } + } + }, + { + title: '复制预览内容', + icon: '', + handler: { + type: 'action', + click: async (ctx: any) => { + const previewContent = ctx.preview?.querySelector('.markdown-body') + if (!previewContent) { + toast({ + variant: "destructive", + title: "复制失败", + description: "未找到预览内容", + duration: 2000 + }) + return + } + + try { + // 创建一个临时容器 + const tempDiv = document.createElement('div') + tempDiv.innerHTML = previewContent.innerHTML + + // 使用 Blob 和 Clipboard API 复制 + const blob = new Blob([tempDiv.innerHTML], { type: 'text/html' }) + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/html': blob + }) + ]) + + toast({ + title: "复制成功", + description: "已复制预览内容(包含样式)", + duration: 2000 + }) + } catch (err) { + toast({ + variant: "destructive", + title: "复制失败", + description: "无法访问剪贴板,请检查浏览器权限", + action: 重试, + }) + console.error('Copy error:', err) + } + } + } + } + ], + viewerEffect({ markdownBody }) { + // 图片加载优化 + const images = markdownBody.querySelectorAll('img') + images.forEach(img => { + if (img instanceof HTMLImageElement) { + img.style.opacity = '0' + img.style.transition = 'opacity 0.3s ease' + img.onload = () => img.style.opacity = '1' + img.onerror = () => { + img.style.opacity = '1' + img.style.filter = 'grayscale(1)' + img.title = '图片加载失败' + } + } + }) + + // 链接优化 + const links = markdownBody.querySelectorAll('a') + links.forEach(link => { + if (link instanceof HTMLAnchorElement) { + link.target = '_blank' + link.rel = 'noopener noreferrer' + } + }) + + // 应用模板样式 + if (selectedTemplate) { + applyTemplateStyles(markdownBody, selectedTemplate, styleOptions) + } + } + } + }, [selectedTemplate, styleOptions]) + + // 使用创建的插件 + const plugins = useMemo(() => [ + gfm(), // 使用默认配置 + breaks(), + frontmatter(), + math({ + // 配置数学公式渲染 + katexOptions: { + throwOnError: false, + output: 'html' + } + }), + mermaid({ + // 配置 Mermaid 图表渲染 + theme: 'default' + }), + highlight({ + // 配置代码高亮 + init: (hljs) => { + // 可以在这里注册额外的语言 + } + }), + createEditorPlugin() + ], [createEditorPlugin]) + return ( -
-
-
-
- - - - -
- -
- - +
+
+
+
+
+ +
+ +
+ +
+ +
+
+ + + +
-
-
t.id === selectedTemplate)?.styles - )}> -
- -
+
+
t.id === selectedTemplate)?.styles + )} + > + { + return [] + }} + />
- + {showPreview && ( -
-
-
+
t.id === selectedTemplate)?.styles + )} + > +
+
预览效果
+
+ +
-
-
-
t.id === selectedTemplate)?.styles - )}> -
-
+ +
+
+ {isConverting ? ( +
+ +
+ ) : ( +
t.id === selectedTemplate)?.styles + )}> +
+
+ )}
diff --git a/src/components/nav/MainNav.tsx b/src/components/nav/MainNav.tsx index 72a4b35..3420570 100644 --- a/src/components/nav/MainNav.tsx +++ b/src/components/nav/MainNav.tsx @@ -3,6 +3,7 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' import { cn } from '@/lib/utils' +import { ThemeToggle } from '@/components/theme-toggle' const navigation = [ { name: '微信公众号', href: '/wechat' }, @@ -13,7 +14,7 @@ export function MainNav() { const pathname = usePathname() return ( -
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx new file mode 100644 index 0000000..c1b72a8 --- /dev/null +++ b/src/components/theme-provider.tsx @@ -0,0 +1,10 @@ +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" + +type ThemeProviderProps = Parameters[0] + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} \ No newline at end of file diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx new file mode 100644 index 0000000..7faed24 --- /dev/null +++ b/src/components/theme-toggle.tsx @@ -0,0 +1,40 @@ +"use client" + +import * as React from "react" +import { Moon, Sun } from "lucide-react" +import { useTheme } from "next-themes" + +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +export function ThemeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + 浅色 + + setTheme("dark")}> + 深色 + + setTheme("system")}> + 系统 + + + + ) +} \ No newline at end of file diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx new file mode 100644 index 0000000..d0f4d75 --- /dev/null +++ b/src/components/ui/toast.tsx @@ -0,0 +1,132 @@ +'use client' + +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full", + { + variants: { + variant: { + default: "border bg-background", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} \ No newline at end of file diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx new file mode 100644 index 0000000..adf09c8 --- /dev/null +++ b/src/components/ui/toaster.tsx @@ -0,0 +1,35 @@ +'use client' + +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" +import { useToast } from "@/components/ui/use-toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} \ No newline at end of file diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts new file mode 100644 index 0000000..6634612 --- /dev/null +++ b/src/components/ui/use-toast.ts @@ -0,0 +1,193 @@ +'use client' + +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_VALUE + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } \ No newline at end of file