mirror of
https://github.com/ershisan99/md-preview-desktop.git
synced 2025-12-17 20:59:25 +00:00
chore: replace local components with mdx-components lib
feat: directory selection working feat: theme toggle working
This commit is contained in:
@@ -24,10 +24,11 @@
|
||||
"@electron-toolkit/utils": "^2.0.0",
|
||||
"@fontsource/roboto": "^5.0.8",
|
||||
"@it-incubator/md-bundler": "0.0.8",
|
||||
"@it-incubator/mdx-components": "0.0.3",
|
||||
"@it-incubator/ui-kit": "0.2.17",
|
||||
"@it-incubator/mdx-components": "0.0.4",
|
||||
"@it-incubator/ui-kit": "0.2.18",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.1",
|
||||
"esbuild": "^0.19.3",
|
||||
"mdx-bundler": "^9.2.1",
|
||||
|
||||
131
pnpm-lock.yaml
generated
131
pnpm-lock.yaml
generated
@@ -18,17 +18,20 @@ dependencies:
|
||||
specifier: 0.0.8
|
||||
version: 0.0.8
|
||||
'@it-incubator/mdx-components':
|
||||
specifier: 0.0.3
|
||||
version: 0.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(esbuild@0.19.3)(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: 0.0.4
|
||||
version: 0.0.4(@types/react-dom@18.2.7)(@types/react@18.2.21)(esbuild@0.19.3)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@it-incubator/ui-kit':
|
||||
specifier: 0.2.17
|
||||
version: 0.2.17(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react-toastify@9.1.3)(react@18.2.0)
|
||||
specifier: 0.2.18
|
||||
version: 0.2.18(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react-toastify@9.1.3)(react@18.2.0)
|
||||
builtin-modules:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
chokidar:
|
||||
specifier: ^3.5.3
|
||||
version: 3.5.3
|
||||
electron-store:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
electron-updater:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.4
|
||||
@@ -1224,8 +1227,8 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@it-incubator/mdx-components@0.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(esbuild@0.19.3)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-S8L7MtXkgPgTCclmMxgeHUxk/6a3W6cbzZX9Q4mXfvnBaSVh2SJrVqq9YdxQQkEK+0q2Vu04BoTLyruD5ewunw==}
|
||||
/@it-incubator/mdx-components@0.0.4(@types/react-dom@18.2.7)(@types/react@18.2.21)(esbuild@0.19.3)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-GEqGlg/5u9Tu1xahabRMEKv2g/FxMEhWhMmdIYclJDCqTJfguUkmIWeYjKgJVgJFqxeuJQNpzd6D850RuviEJQ==}
|
||||
peerDependencies:
|
||||
react: '>=18.0.2'
|
||||
react-dom: '>=18.0.2'
|
||||
@@ -1269,8 +1272,8 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@it-incubator/ui-kit@0.2.17(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react-toastify@9.1.3)(react@18.2.0):
|
||||
resolution: {integrity: sha512-x3V3dqh2HY/wJZJ8hhO0c+5C0hiUxz/dhdkWoaDrqqaiAUGb+laj7uzh8SFJ7noWwXT4uFWX57cPnlzvdUqieA==}
|
||||
/@it-incubator/ui-kit@0.2.18(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react-toastify@9.1.3)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xmDIEKCfkiBFVg+k1k5065Cw08/9fL39LkdgzyJ7nSbYENVW4gLw1m57jOuh0PvzzVOk1qTkF869Z9rgbPfm6Q==}
|
||||
peerDependencies:
|
||||
react: '>=18.0.2'
|
||||
react-dom: '>=18.0.2'
|
||||
@@ -2943,6 +2946,17 @@ packages:
|
||||
indent-string: 4.0.0
|
||||
dev: false
|
||||
|
||||
/ajv-formats@2.1.1(ajv@8.12.0):
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
dependencies:
|
||||
ajv: 8.12.0
|
||||
dev: false
|
||||
|
||||
/ajv-keywords@3.5.2(ajv@6.12.6):
|
||||
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
|
||||
peerDependencies:
|
||||
@@ -2967,7 +2981,6 @@ packages:
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
uri-js: 4.4.1
|
||||
dev: true
|
||||
|
||||
/ansi-escapes@4.3.2:
|
||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||
@@ -3187,6 +3200,11 @@ packages:
|
||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
/atomically@1.7.0:
|
||||
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
dev: false
|
||||
|
||||
/attr-accept@2.2.2:
|
||||
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3658,6 +3676,22 @@ packages:
|
||||
typedarray: 0.0.6
|
||||
dev: false
|
||||
|
||||
/conf@10.2.0:
|
||||
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
ajv: 8.12.0
|
||||
ajv-formats: 2.1.1(ajv@8.12.0)
|
||||
atomically: 1.7.0
|
||||
debounce-fn: 4.0.0
|
||||
dot-prop: 6.0.1
|
||||
env-paths: 2.2.1
|
||||
json-schema-typed: 7.0.3
|
||||
onetime: 5.1.2
|
||||
pkg-up: 3.1.0
|
||||
semver: 7.5.4
|
||||
dev: false
|
||||
|
||||
/config-file-ts@0.2.4:
|
||||
resolution: {integrity: sha512-cKSW0BfrSaAUnxpgvpXPLaaW/umg4bqg4k3GO1JqlRfpx+d5W0GDXznCMkWotJQek5Mmz1MJVChQnz3IVaeMZQ==}
|
||||
dependencies:
|
||||
@@ -3766,6 +3800,13 @@ packages:
|
||||
'@babel/runtime': 7.22.15
|
||||
dev: false
|
||||
|
||||
/debounce-fn@4.0.0:
|
||||
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
mimic-fn: 3.1.0
|
||||
dev: false
|
||||
|
||||
/debounce@1.2.1:
|
||||
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
|
||||
dev: false
|
||||
@@ -3983,6 +4024,13 @@ packages:
|
||||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dot-prop@6.0.1:
|
||||
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
is-obj: 2.0.0
|
||||
dev: false
|
||||
|
||||
/dotenv-expand@5.1.0:
|
||||
resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==}
|
||||
dev: true
|
||||
@@ -4034,6 +4082,13 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/electron-store@8.1.0:
|
||||
resolution: {integrity: sha512-2clHg/juMjOH0GT9cQ6qtmIvK183B39ZXR0bUoPwKwYHJsEF3quqyDzMFUAu+0OP8ijmN2CbPRAelhNbWUbzwA==}
|
||||
dependencies:
|
||||
conf: 10.2.0
|
||||
type-fest: 2.19.0
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.523:
|
||||
resolution: {integrity: sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==}
|
||||
|
||||
@@ -4723,7 +4778,6 @@ packages:
|
||||
|
||||
/fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-diff@1.3.0:
|
||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
||||
@@ -4815,6 +4869,13 @@ packages:
|
||||
resolution: {integrity: sha512-wRkO8crYqjaTvCnqEfQGuV8LOp4JO0Ctjn6qROGPcradK+6jQ7giLMGLnKlNxQm6dEdYD3/TBABQ7Xi/5ZhWcg==}
|
||||
dev: false
|
||||
|
||||
/find-up@3.0.0:
|
||||
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
locate-path: 3.0.0
|
||||
dev: false
|
||||
|
||||
/find-up@4.1.0:
|
||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5770,6 +5831,11 @@ packages:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
/is-obj@2.0.0:
|
||||
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/is-obj@3.0.0:
|
||||
resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -5970,7 +6036,10 @@ packages:
|
||||
|
||||
/json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
dev: true
|
||||
|
||||
/json-schema-typed@7.0.3:
|
||||
resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
|
||||
dev: false
|
||||
|
||||
/json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
@@ -6064,6 +6133,14 @@ packages:
|
||||
/lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
/locate-path@3.0.0:
|
||||
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
p-locate: 3.0.0
|
||||
path-exists: 3.0.0
|
||||
dev: false
|
||||
|
||||
/locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7090,6 +7167,11 @@ packages:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
/mimic-fn@3.1.0:
|
||||
resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -7365,6 +7447,13 @@ packages:
|
||||
yocto-queue: 0.1.0
|
||||
dev: true
|
||||
|
||||
/p-locate@3.0.0:
|
||||
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
dev: false
|
||||
|
||||
/p-locate@4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7440,6 +7529,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/path-exists@3.0.0:
|
||||
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7495,6 +7589,13 @@ packages:
|
||||
find-up: 4.1.0
|
||||
dev: false
|
||||
|
||||
/pkg-up@3.1.0:
|
||||
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
find-up: 3.0.0
|
||||
dev: false
|
||||
|
||||
/plist@3.1.0:
|
||||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||
engines: {node: '>=10.4.0'}
|
||||
@@ -7642,7 +7743,6 @@ packages:
|
||||
/punycode@2.3.0:
|
||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/querystring@0.2.0:
|
||||
resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==}
|
||||
@@ -8046,7 +8146,6 @@ packages:
|
||||
/require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/resolve-alpn@1.2.1:
|
||||
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||
@@ -8898,6 +8997,11 @@ packages:
|
||||
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/type-fest@2.19.0:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/typed-array-buffer@1.0.0:
|
||||
resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -9110,7 +9214,6 @@ packages:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
punycode: 2.3.0
|
||||
dev: true
|
||||
|
||||
/url@0.10.3:
|
||||
resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const CODE_BLOCK_FILENAME_REGEX = /filename="([^"]+)"/
|
||||
@@ -3,11 +3,14 @@ import path from 'node:path'
|
||||
import { join } from 'path'
|
||||
|
||||
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
|
||||
import { bundleMdx } from '@it-incubator/md-bundler'
|
||||
import { bundleMdx, generateToc } from '@it-incubator/md-bundler'
|
||||
import { BrowserWindow, app, ipcMain, shell } from 'electron'
|
||||
import Store from 'electron-store'
|
||||
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
|
||||
const chokidar = require('chokidar')
|
||||
const store = new Store()
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
@@ -35,6 +38,11 @@ function createWindow(): void {
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
mainWindow.webContents.on('will-navigate', (event, url) => {
|
||||
event.preventDefault()
|
||||
shell.openExternal(url)
|
||||
})
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
@@ -79,8 +87,10 @@ app.on('window-all-closed', () => {
|
||||
})
|
||||
|
||||
let watcher: any = null
|
||||
let currentContent: any = null
|
||||
|
||||
function setupWatcher(filePath: string) {
|
||||
store.set('lastFilePath', filePath)
|
||||
// Close the existing watcher if it exists
|
||||
if (watcher) {
|
||||
watcher.close()
|
||||
@@ -101,13 +111,18 @@ function setupWatcher(filePath: string) {
|
||||
// Send file content to renderer
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const bundled = await bundleMdx(content)
|
||||
const toc = await generateToc(content, {})
|
||||
const newContent = { ...bundled, fileName: path, toc }
|
||||
|
||||
mainWindow.webContents.send('file-changed', { ...bundled, fileName: path })
|
||||
currentContent = newContent
|
||||
|
||||
mainWindow.webContents.send('current-content', newContent)
|
||||
}
|
||||
})
|
||||
await shell.openPath(path)
|
||||
// await shell.openPath(path)
|
||||
}
|
||||
|
||||
bundleAndSend(filePath)
|
||||
// Add your event listeners
|
||||
watcher
|
||||
.on('add', async (path: string) => {
|
||||
@@ -119,15 +134,147 @@ function setupWatcher(filePath: string) {
|
||||
.on('unlink', (path: string) => console.warn(`File ${path} has been removed`))
|
||||
}
|
||||
|
||||
function prepareAndSendDir(dir: string) {
|
||||
const files = fs.readdirSync(dir)
|
||||
const dirName = path.basename(dir)
|
||||
const data = [
|
||||
{
|
||||
children: getFilesRecursive(
|
||||
dir,
|
||||
['.md', '.mdx'],
|
||||
['node_modules', 'README.md'],
|
||||
true,
|
||||
dir + '/'
|
||||
),
|
||||
name: dirName,
|
||||
path: dir,
|
||||
type: FsEntryType.Directory,
|
||||
},
|
||||
]
|
||||
|
||||
// Send the list of files to the renderer process
|
||||
mainWindow?.webContents.send('directory-contents', { data, dir, dirName, files })
|
||||
store.set('lastOpenDir', dir)
|
||||
}
|
||||
|
||||
ipcMain.on('dropped-file', (event, arg) => {
|
||||
console.warn('Dropped File(s):', arg)
|
||||
event.returnValue = `Received ${arg.length} paths.` // Synchronous reply
|
||||
|
||||
// Assuming the user only dropped one file, update the watcher to watch that file.
|
||||
event.returnValue = `Received ${arg.length} paths.`
|
||||
if (!mainWindow) {
|
||||
throw new Error('mainWindow is not defined')
|
||||
}
|
||||
if (arg.length > 0) {
|
||||
setupWatcher(arg[0])
|
||||
const pathToCheck = arg[0]
|
||||
|
||||
if (fs.statSync(pathToCheck).isDirectory()) {
|
||||
// If it's a directory, get the list of files
|
||||
prepareAndSendDir(pathToCheck)
|
||||
} else {
|
||||
setupWatcher(pathToCheck)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('get-current-content', event => {
|
||||
event.reply('current-content', currentContent)
|
||||
})
|
||||
ipcMain.on('get-current-dir', () => {
|
||||
const lastOpenDir = store.get('lastOpenDir') as string | undefined
|
||||
|
||||
if (!lastOpenDir) {
|
||||
return
|
||||
}
|
||||
prepareAndSendDir(lastOpenDir)
|
||||
})
|
||||
ipcMain.on('open-file', (_event, filePath) => {
|
||||
setupWatcher(filePath)
|
||||
})
|
||||
const lastFilePath = store.get('lastFilePath') as string | undefined
|
||||
const lastOpenDir = store.get('lastOpenDir') as string | undefined
|
||||
|
||||
if (lastOpenDir) {
|
||||
prepareAndSendDir(lastOpenDir)
|
||||
}
|
||||
|
||||
// Initially setup watcher for 'hello.md'
|
||||
setupWatcher(path.resolve(__dirname, '../../../hello.md'))
|
||||
setupWatcher(lastFilePath ?? path.resolve(__dirname, '../../../hello.md'))
|
||||
|
||||
function getFilesRecursive(
|
||||
directory: string,
|
||||
allowedExtensions: string[] = [],
|
||||
ignoredPaths: string[] = [],
|
||||
includeParent = true,
|
||||
prefix = ''
|
||||
): FileOrDirectory[] {
|
||||
const fileList: FileOrDirectory[] = []
|
||||
|
||||
const filesAndDirs = fs.readdirSync(directory)
|
||||
|
||||
for (const fileOrDir of filesAndDirs) {
|
||||
const absolutePath = path.join(directory, fileOrDir)
|
||||
const relativePath = path.join(prefix, fileOrDir)
|
||||
|
||||
// Skip dotfiles and dot directories
|
||||
if (fileOrDir.startsWith('.')) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip ignored files and directories
|
||||
if (ignoredPaths.some(ignoredPath => absolutePath.includes(ignoredPath))) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (fs.statSync(absolutePath).isDirectory()) {
|
||||
const nestedFiles = getFilesRecursive(
|
||||
absolutePath,
|
||||
allowedExtensions,
|
||||
ignoredPaths,
|
||||
includeParent,
|
||||
relativePath + '/'
|
||||
)
|
||||
|
||||
fileList.push({
|
||||
children: nestedFiles,
|
||||
name: fileOrDir,
|
||||
path: relativePath,
|
||||
type: FsEntryType.Directory,
|
||||
})
|
||||
} else {
|
||||
const extension = path.extname(fileOrDir).toLowerCase()
|
||||
|
||||
// Check the file has an allowed extension
|
||||
if (
|
||||
allowedExtensions.length === 0 ||
|
||||
allowedExtensions.map(e => e.toLowerCase()).includes(extension)
|
||||
) {
|
||||
fileList.push({
|
||||
name: fileOrDir,
|
||||
path: relativePath,
|
||||
type: FsEntryType.File,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileList
|
||||
}
|
||||
|
||||
enum FsEntryType {
|
||||
Directory = 'directory',
|
||||
File = 'file',
|
||||
}
|
||||
|
||||
type File = {
|
||||
name: string
|
||||
path: string
|
||||
type: FsEntryType.File
|
||||
}
|
||||
|
||||
type Directory = {
|
||||
children: Array<FileOrDirectory>
|
||||
name: string
|
||||
path: string
|
||||
type: FsEntryType.Directory
|
||||
}
|
||||
|
||||
type FileOrDirectory = Directory | File
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { CODE_BLOCK_FILENAME_REGEX } from './constants.js'
|
||||
function visit(node, tagNames, handler) {
|
||||
if (tagNames.includes(node.tagName)) {
|
||||
handler(node)
|
||||
|
||||
return
|
||||
}
|
||||
if ('children' in node) {
|
||||
for (const n of node.children) {
|
||||
visit(n, tagNames, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const parseMeta =
|
||||
({ defaultShowCopyCode }) =>
|
||||
tree => {
|
||||
visit(tree, ['pre'], preEl => {
|
||||
const [codeEl] = preEl.children
|
||||
|
||||
// Add default language `text` for code-blocks without languages
|
||||
codeEl.properties.className ||= ['language-text']
|
||||
const meta = codeEl.data?.meta
|
||||
|
||||
preEl.__nextra_filename = meta?.match(CODE_BLOCK_FILENAME_REGEX)?.[1]
|
||||
|
||||
preEl.__nextra_hasCopyCode = meta
|
||||
? (defaultShowCopyCode && !/( |^)copy=false($| )/.test(meta)) || /( |^)copy($| )/.test(meta)
|
||||
: defaultShowCopyCode
|
||||
})
|
||||
}
|
||||
|
||||
export const attachMeta = () => tree => {
|
||||
visit(tree, ['div', 'pre'], node => {
|
||||
if ('data-rehype-pretty-code-fragment' in node.properties) {
|
||||
// remove <div data-rehype-pretty-code-fragment /> element that wraps <pre /> element
|
||||
// because we'll wrap with our own <div />
|
||||
Object.assign(node, node.children[0])
|
||||
}
|
||||
|
||||
node.properties.filename = node.__nextra_filename
|
||||
node.properties.hasCopyCode = node.__nextra_hasCopyCode
|
||||
})
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
{
|
||||
"name": "css-variables",
|
||||
"type": "css",
|
||||
"colors": {
|
||||
"editor.foreground": "#000001",
|
||||
"editor.background": "#000002",
|
||||
"terminal.ansiBlack": "#A00000",
|
||||
"terminal.ansiRed": "#A00001",
|
||||
"terminal.ansiGreen": "#A00002",
|
||||
"terminal.ansiYellow": "#A00003",
|
||||
"terminal.ansiBlue": "#A00004",
|
||||
"terminal.ansiMagenta": "#A00005",
|
||||
"terminal.ansiCyan": "#A00006",
|
||||
"terminal.ansiWhite": "#A00007",
|
||||
"terminal.ansiBrightBlack": "#A00008",
|
||||
"terminal.ansiBrightRed": "#A00009",
|
||||
"terminal.ansiBrightGreen": "#A00010",
|
||||
"terminal.ansiBrightYellow": "#A00011",
|
||||
"terminal.ansiBrightBlue": "#A00012",
|
||||
"terminal.ansiBrightMagenta": "#A00013",
|
||||
"terminal.ansiBrightCyan": "#A00014",
|
||||
"terminal.ansiBrightWhite": "#A00015"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"settings": {
|
||||
"foreground": "#000001"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"markup.deleted",
|
||||
"meta.diff.header.from-file",
|
||||
"punctuation.definition.deleted"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#ef6270"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"markup.inserted",
|
||||
"meta.diff.header.to-file",
|
||||
"punctuation.definition.inserted"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#4bb74a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword.operator.accessor",
|
||||
"meta.group.braces.round.function.arguments",
|
||||
"meta.template.expression",
|
||||
"markup.fenced_code meta.embedded.block"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000001"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "emphasis",
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"strong",
|
||||
"markup.heading.markdown",
|
||||
"markup.bold.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"markup.italic.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"fontStyle": "italic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "meta.link.inline.markdown",
|
||||
"settings": {
|
||||
"fontStyle": "underline",
|
||||
"foreground": "#000004"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string",
|
||||
"markup.fenced_code",
|
||||
"markup.inline"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000005"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"comment",
|
||||
"string.quoted.docstring.multi"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000006"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant.numeric",
|
||||
"constant.language",
|
||||
"constant.other.placeholder",
|
||||
"constant.character.format.placeholder",
|
||||
"variable.language.this",
|
||||
"variable.other.object",
|
||||
"variable.other.class",
|
||||
"variable.other.constant",
|
||||
"meta.property-name",
|
||||
"meta.property-value",
|
||||
"support"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000004"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword",
|
||||
"storage.modifier",
|
||||
"storage.type",
|
||||
"storage.control.clojure",
|
||||
"entity.name.function.clojure",
|
||||
"entity.name.tag.yaml",
|
||||
"support.function.node",
|
||||
"support.type.property-name.json",
|
||||
"punctuation.separator.key-value",
|
||||
"punctuation.definition.template-expression"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000007"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "variable.parameter.function",
|
||||
"settings": {
|
||||
"foreground": "#000008"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"support.function",
|
||||
"entity.name.type",
|
||||
"entity.other.inherited-class",
|
||||
"meta.function-call",
|
||||
"meta.instance.constructor",
|
||||
"entity.other.attribute-name",
|
||||
"entity.name.function",
|
||||
"constant.keyword.clojure"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000009"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.tag",
|
||||
"string.quoted",
|
||||
"string.regexp",
|
||||
"string.interpolated",
|
||||
"string.template",
|
||||
"string.unquoted.plain.out.yaml",
|
||||
"keyword.other.template"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000010"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"punctuation.definition.arguments",
|
||||
"punctuation.definition.dict",
|
||||
"punctuation.separator",
|
||||
"meta.function-call.arguments"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000011"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[Custom] Markdown links",
|
||||
"scope": [
|
||||
"markup.underline.link",
|
||||
"punctuation.definition.metadata.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000012"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[Custom] Markdown list",
|
||||
"scope": [
|
||||
"beginning.punctuation.definition.list.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000005"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[Custom] Markdown punctuation definition brackets",
|
||||
"scope": [
|
||||
"punctuation.definition.string.begin.markdown",
|
||||
"punctuation.definition.string.end.markdown",
|
||||
"string.other.link.title.markdown",
|
||||
"string.other.link.description.markdown"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "#000007"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export type BundledMdx = {
|
||||
code: string
|
||||
frontmatter: Record<string, any>
|
||||
}
|
||||
@@ -1,65 +1,16 @@
|
||||
import { ComponentProps, ReactElement, useEffect, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { IpcRendererListener } from '@electron-toolkit/preload'
|
||||
import * as components from '@it-incubator/mdx-components'
|
||||
import { ImagePreview, Typography } from '@it-incubator/ui-kit'
|
||||
import { getMDXComponent } from 'mdx-bundler/client'
|
||||
import { View } from './components/view/view'
|
||||
import { Layout } from './layout'
|
||||
|
||||
import s from './view.module.scss'
|
||||
|
||||
import { Pre } from './components/pre'
|
||||
function App() {
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [fileName, setFileName] = useState<string>('')
|
||||
const [srcPreview, setSrcPreview] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
const listener: IpcRendererListener = (_event, content) => {
|
||||
setCode(content.code)
|
||||
setFileName(content.fileName)
|
||||
}
|
||||
|
||||
window.electron.ipcRenderer.on('file-changed', listener)
|
||||
|
||||
return () => {
|
||||
window.electron.ipcRenderer.removeAllListeners('file-changed')
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!code) {
|
||||
return null
|
||||
}
|
||||
|
||||
const Component = getMDXComponent(code, { components: components })
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography.H1>{fileName}</Typography.H1>
|
||||
<article className={s.root}>
|
||||
<ImagePreview onClose={() => setSrcPreview('')} open={!!srcPreview} src={srcPreview} />
|
||||
<Component
|
||||
components={{
|
||||
code: Code,
|
||||
img: props => (
|
||||
<img
|
||||
{...props}
|
||||
onClick={() => setSrcPreview(props.src || '')}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
),
|
||||
pre: Pre,
|
||||
}}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
<Layout fileName={fileName}>
|
||||
<View setFileName={setFileName} />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
const Code = ({ children, ...props }: ComponentProps<'code'>): ReactElement => {
|
||||
return (
|
||||
<code className={s.inline} dir={'ltr'} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,206 +1,13 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.code-toolbar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.copy-to-clipboard-button {
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
background-color: #011727;
|
||||
border: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4891f7;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:root {
|
||||
--shiki-color-text: oklch(37.53% 0 0);
|
||||
--shiki-color-background: hsl(var(--primary-hue) 100% 39% / 5%);
|
||||
--shiki-token-constant: oklch(56.45% 0.163 253.27);
|
||||
--shiki-token-string: oklch(54.64% 0.144 147.32);
|
||||
--shiki-token-comment: oklch(73.8% 0 0);
|
||||
--shiki-token-keyword: oklch(56.8% 0.2 26.41);
|
||||
--shiki-token-parameter: oklch(77.03% 0.174 64.05);
|
||||
--shiki-token-function: oklch(50.15% 0.188 294.99);
|
||||
--shiki-token-string-expression: var(--shiki-token-string);
|
||||
--shiki-token-punctuation: oklch(24.78% 0 0);
|
||||
--shiki-token-link: var(--shiki-token-string);
|
||||
|
||||
/* from github-light */
|
||||
--shiki-color-ansi-black: #24292e;
|
||||
--shiki-color-ansi-black-dim: #24292e80;
|
||||
--shiki-color-ansi-red: #d73a49;
|
||||
--shiki-color-ansi-red-dim: #d73a4980;
|
||||
--shiki-color-ansi-green: #28a745;
|
||||
--shiki-color-ansi-green-dim: #28a74580;
|
||||
--shiki-color-ansi-yellow: #dbab09;
|
||||
--shiki-color-ansi-yellow-dim: #dbab0980;
|
||||
--shiki-color-ansi-blue: #0366d6;
|
||||
--shiki-color-ansi-blue-dim: #0366d680;
|
||||
--shiki-color-ansi-magenta: #5a32a3;
|
||||
--shiki-color-ansi-magenta-dim: #5a32a380;
|
||||
--shiki-color-ansi-cyan: #1b7c83;
|
||||
--shiki-color-ansi-cyan-dim: #1b7c8380;
|
||||
--shiki-color-ansi-white: #6a737d;
|
||||
--shiki-color-ansi-white-dim: #6a737d80;
|
||||
--shiki-color-ansi-bright-black: #959da5;
|
||||
--shiki-color-ansi-bright-black-dim: #959da580;
|
||||
--shiki-color-ansi-bright-red: #cb2431;
|
||||
--shiki-color-ansi-bright-red-dim: #cb243180;
|
||||
--shiki-color-ansi-bright-green: #22863a;
|
||||
--shiki-color-ansi-bright-green-dim: #22863a80;
|
||||
--shiki-color-ansi-bright-yellow: #b08800;
|
||||
--shiki-color-ansi-bright-yellow-dim: #b0880080;
|
||||
--shiki-color-ansi-bright-blue: #005cc5;
|
||||
--shiki-color-ansi-bright-blue-dim: #005cc580;
|
||||
--shiki-color-ansi-bright-magenta: #5a32a3;
|
||||
--shiki-color-ansi-bright-magenta-dim: #5a32a380;
|
||||
--shiki-color-ansi-bright-cyan: #3192aa;
|
||||
--shiki-color-ansi-bright-cyan-dim: #3192aa80;
|
||||
--shiki-color-ansi-bright-white: #d1d5da;
|
||||
--shiki-color-ansi-bright-white-dim: #d1d5da80;
|
||||
--primary-hue: 212deg;
|
||||
--header-height: 60px;
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
--primary-hue: 204deg;
|
||||
--shiki-color-background: hsl(var(--primary-hue) 100% 77% / 10%);
|
||||
--shiki-color-text: oklch(86.07% 0 0);
|
||||
--shiki-token-constant: oklch(76.85% 0.121 252.34);
|
||||
--shiki-token-string: oklch(81.11% 0.124 55.08);
|
||||
--shiki-token-comment: oklch(55.18% 0.017 251.27);
|
||||
--shiki-token-keyword: oklch(72.14% 0.162 15.49);
|
||||
|
||||
/* --shiki-token-parameter: #ff9800; is same as in light mode */
|
||||
--shiki-token-function: oklch(72.67% 0.137 299.15);
|
||||
--shiki-token-string-expression: oklch(69.28% 0.179 143.2);
|
||||
--shiki-token-punctuation: oklch(79.21% 0 0);
|
||||
--shiki-token-link: var(--shiki-token-string);
|
||||
|
||||
/* from github-dark */
|
||||
--shiki-color-ansi-black: #586069;
|
||||
--shiki-color-ansi-black-dim: #58606980;
|
||||
--shiki-color-ansi-red: #ea4a5a;
|
||||
--shiki-color-ansi-red-dim: #ea4a5a80;
|
||||
--shiki-color-ansi-green: #34d058;
|
||||
--shiki-color-ansi-green-dim: #34d05880;
|
||||
--shiki-color-ansi-yellow: #ffea7f;
|
||||
--shiki-color-ansi-yellow-dim: #ffea7f80;
|
||||
--shiki-color-ansi-blue: #2188ff;
|
||||
--shiki-color-ansi-blue-dim: #2188ff80;
|
||||
--shiki-color-ansi-magenta: #b392f0;
|
||||
--shiki-color-ansi-magenta-dim: #b392f080;
|
||||
--shiki-color-ansi-cyan: #39c5cf;
|
||||
--shiki-color-ansi-cyan-dim: #39c5cf80;
|
||||
--shiki-color-ansi-white: #d1d5da;
|
||||
--shiki-color-ansi-white-dim: #d1d5da80;
|
||||
--shiki-color-ansi-bright-black: #959da5;
|
||||
--shiki-color-ansi-bright-black-dim: #959da580;
|
||||
--shiki-color-ansi-bright-red: #f97583;
|
||||
--shiki-color-ansi-bright-red-dim: #f9758380;
|
||||
--shiki-color-ansi-bright-green: #85e89d;
|
||||
--shiki-color-ansi-bright-green-dim: #85e89d80;
|
||||
--shiki-color-ansi-bright-yellow: #ffea7f;
|
||||
--shiki-color-ansi-bright-yellow-dim: #ffea7f80;
|
||||
--shiki-color-ansi-bright-blue: #79b8ff;
|
||||
--shiki-color-ansi-bright-blue-dim: #79b8ff80;
|
||||
--shiki-color-ansi-bright-magenta: #b392f0;
|
||||
--shiki-color-ansi-bright-magenta-dim: #b392f080;
|
||||
--shiki-color-ansi-bright-cyan: #56d4dd;
|
||||
--shiki-color-ansi-bright-cyan-dim: #56d4dd80;
|
||||
--shiki-color-ansi-bright-white: #fafbfc;
|
||||
--shiki-color-ansi-bright-white-dim: #fafbfc80;
|
||||
#root,
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
counter-reset: line;
|
||||
font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1;
|
||||
box-decoration-break: slice;
|
||||
|
||||
&[data-line-numbers] > [data-line] {
|
||||
padding-inline: 1rem;
|
||||
|
||||
&::before {
|
||||
content: counter(line);
|
||||
counter-increment: line;
|
||||
|
||||
float: left;
|
||||
|
||||
height: 100%;
|
||||
padding-right: 1rem;
|
||||
|
||||
color: #6b7280;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-nextra-word-wrap] & {
|
||||
word-break: break-word;
|
||||
|
||||
@apply whitespace-pre-wrap md:whitespace-pre;
|
||||
|
||||
[data-line] {
|
||||
@apply inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.nextra-copy-icon {
|
||||
animation: fade-in 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
|
||||
.nextra-button {
|
||||
@apply backdrop-blur-md bg-opacity-[.85] dark:bg-opacity-80;
|
||||
}
|
||||
}
|
||||
|
||||
.ordered-list {
|
||||
padding-left: 20px;
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.unordered-list {
|
||||
padding-left: 20px;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: black;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
.container {
|
||||
> [class*='tree'] {
|
||||
margin-top: 0;
|
||||
> [class*='container'] {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fileCustomLabel {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid var(--mdx-color-outline-focus);
|
||||
}
|
||||
}
|
||||
79
src/renderer/src/components/file-selector/file-selector.tsx
Normal file
79
src/renderer/src/components/file-selector/file-selector.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { FileTree } from '@it-incubator/ui-kit'
|
||||
|
||||
import s from './file-selector.module.scss'
|
||||
|
||||
export enum EntryType {
|
||||
Directory = 'directory',
|
||||
File = 'file',
|
||||
}
|
||||
|
||||
export type FileOrDirectory = {
|
||||
children?: FileOrDirectory[]
|
||||
name: string
|
||||
path: string
|
||||
type: EntryType
|
||||
}
|
||||
|
||||
type Props = {
|
||||
data: FileOrDirectory[]
|
||||
selectedMdx: string
|
||||
setSelectedMdx: (s: string) => void
|
||||
}
|
||||
export const MdxFileSelector = ({ data, selectedMdx, setSelectedMdx }: Props) => {
|
||||
return (
|
||||
<div className={s.container}>
|
||||
<FileTree>
|
||||
{data?.map((item, index) => {
|
||||
return (
|
||||
<RenderItem
|
||||
isFirst={index === 0 && !selectedMdx}
|
||||
item={item}
|
||||
key={item.path}
|
||||
onFileClick={setSelectedMdx}
|
||||
selectedItemPath={selectedMdx}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</FileTree>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const shouldOpenFolder = (dirPath: string, filePath: string): boolean => {
|
||||
return filePath.startsWith(dirPath)
|
||||
}
|
||||
|
||||
type RenderItemProps = {
|
||||
isFirst?: boolean
|
||||
item: FileOrDirectory
|
||||
onFileClick: (path: string) => void
|
||||
selectedItemPath: string
|
||||
}
|
||||
|
||||
const RenderItem = ({ isFirst, item, onFileClick, selectedItemPath }: RenderItemProps) => {
|
||||
const isOpen = selectedItemPath ? shouldOpenFolder(item.path, selectedItemPath) : isFirst
|
||||
|
||||
if (item.type === EntryType.Directory) {
|
||||
return (
|
||||
<FileTree.Folder defaultOpen={isOpen} name={item.name}>
|
||||
{item.children &&
|
||||
item.children.map(childItem => (
|
||||
<RenderItem
|
||||
item={childItem}
|
||||
key={childItem.path}
|
||||
onFileClick={onFileClick}
|
||||
selectedItemPath={selectedItemPath}
|
||||
/>
|
||||
))}
|
||||
</FileTree.Folder>
|
||||
)
|
||||
}
|
||||
|
||||
const handleFileSelected = () => {
|
||||
onFileClick(item.path)
|
||||
}
|
||||
|
||||
const isSelected = selectedItemPath === item.path
|
||||
|
||||
return <FileTree.File active={isSelected} name={item.name} onClick={handleFileSelected} />
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { ComponentProps, ReactElement } from 'react'
|
||||
|
||||
export function CheckIcon(props: ComponentProps<'svg'>): ReactElement {
|
||||
return (
|
||||
<svg fill={'currentColor'} height={'1em'} viewBox={'0 0 20 20'} width={'1em'} {...props}>
|
||||
<path
|
||||
clipRule={'evenodd'}
|
||||
d={
|
||||
'M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z'
|
||||
}
|
||||
fillRule={'evenodd'}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
.root {
|
||||
pointer-events: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { ComponentProps, ReactElement, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import s from './copy-to-clipboard.module.scss'
|
||||
|
||||
import { CheckIcon } from './check'
|
||||
import { CopyIcon } from './copy'
|
||||
|
||||
export const CopyToClipboard = ({
|
||||
getValue,
|
||||
...props
|
||||
}: {
|
||||
getValue: () => string
|
||||
} & ComponentProps<'button'>): ReactElement => {
|
||||
const [isCopied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCopied) {
|
||||
return
|
||||
}
|
||||
const timerId = setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 2000)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timerId)
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const handleClick = useCallback<NonNullable<ComponentProps<'button'>['onClick']>>(async () => {
|
||||
setCopied(true)
|
||||
if (!navigator?.clipboard) {
|
||||
console.error('Access to clipboard rejected!')
|
||||
}
|
||||
try {
|
||||
await navigator.clipboard.writeText(getValue())
|
||||
} catch {
|
||||
console.error('Failed to copy!')
|
||||
}
|
||||
}, [getValue])
|
||||
|
||||
const IconToUse = isCopied ? CheckIcon : CopyIcon
|
||||
|
||||
return (
|
||||
<button className={s.button} onClick={handleClick} tabIndex={0} title={'Copy code'} {...props}>
|
||||
<IconToUse className={clsx('nextra-copy-icon', s.root)} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { ComponentProps, ReactElement } from 'react'
|
||||
|
||||
export function CopyIcon(props: ComponentProps<'svg'>): ReactElement {
|
||||
return (
|
||||
<svg
|
||||
fill={'none'}
|
||||
height={'24'}
|
||||
stroke={'currentColor'}
|
||||
viewBox={'0 0 24 24'}
|
||||
width={'24'}
|
||||
xmlns={'http://www.w3.org/2000/svg'}
|
||||
{...props}
|
||||
>
|
||||
<rect
|
||||
height={'13'}
|
||||
rx={'2'}
|
||||
strokeLinecap={'round'}
|
||||
strokeLinejoin={'round'}
|
||||
strokeWidth={'2'}
|
||||
width={'13'}
|
||||
x={'9'}
|
||||
y={'9'}
|
||||
/>
|
||||
<path
|
||||
d={
|
||||
'M5 15H4C2.89543 15 2 14.1046 2 13V4C2 2.89543 2.89543 2 4 2H13C14.1046 2 15 2.89543 15 4V5'
|
||||
}
|
||||
strokeLinecap={'round'}
|
||||
strokeLinejoin={'round'}
|
||||
strokeWidth={'2'}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './pre'
|
||||
@@ -1,80 +0,0 @@
|
||||
.codeBlock {
|
||||
position: relative;
|
||||
margin-top: 1.5rem;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filename {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
font-size: 0.75rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
background-color: hsl(var(--primary-hue) 100% 39% / 5%);
|
||||
border-radius: 0.375rem 0.375rem 0 0;
|
||||
}
|
||||
|
||||
.preCommon {
|
||||
overflow-x: auto;
|
||||
|
||||
margin-bottom: 1rem;
|
||||
|
||||
font-size: 0.9em;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.preWithFilename {
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.preWithoutFilename {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.controlDiv {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
|
||||
margin: 11px 0 0 -11px;
|
||||
}
|
||||
|
||||
.top8 {
|
||||
top: 2rem;
|
||||
}
|
||||
|
||||
.top0 {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.mdHidden {
|
||||
@media (width >= 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.iconDimensions {
|
||||
pointer-events: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { ComponentProps, ReactElement, useRef } from 'react'
|
||||
|
||||
import { Scrollbar } from '@it-incubator/ui-kit'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import styles from './pre.module.scss'
|
||||
|
||||
import { CopyToClipboard } from './copy-to-clipboard'
|
||||
export const Pre = ({
|
||||
children,
|
||||
className,
|
||||
filename,
|
||||
...props
|
||||
}: ComponentProps<'pre'> & {
|
||||
filename?: string
|
||||
hasCopyCode?: boolean
|
||||
}): ReactElement => {
|
||||
const preRef = useRef<HTMLPreElement | null>(null)
|
||||
|
||||
return (
|
||||
<Scrollbar className={styles.codeBlock} type={'hover'}>
|
||||
{filename && (
|
||||
<div className={styles.filename}>
|
||||
{filename}
|
||||
<CopyToClipboard
|
||||
getValue={() => preRef.current?.querySelector('code')?.textContent || ''}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<pre
|
||||
className={clsx(
|
||||
styles.preCommon,
|
||||
filename ? styles.preWithFilename : styles.preWithoutFilename,
|
||||
className
|
||||
)}
|
||||
ref={preRef}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
</Scrollbar>
|
||||
)
|
||||
}
|
||||
1
src/renderer/src/components/toc/index.ts
Normal file
1
src/renderer/src/components/toc/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './toc'
|
||||
23
src/renderer/src/components/toc/toc-node.module.scss
Normal file
23
src/renderer/src/components/toc/toc-node.module.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.list {
|
||||
padding-left: 0.5rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.link {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-m);
|
||||
|
||||
&[data-depth='1'] {
|
||||
font-weight: var(--font-weight-semi-bold);
|
||||
}
|
||||
|
||||
&[data-depth='2'] {
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-accent-500);
|
||||
}
|
||||
}
|
||||
89
src/renderer/src/components/toc/toc-node.tsx
Normal file
89
src/renderer/src/components/toc/toc-node.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { MouseEvent } from 'react'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import s from './toc-node.module.scss'
|
||||
|
||||
import { NodeData } from './toc-node.types'
|
||||
|
||||
interface NodeProps {
|
||||
currentHeading?: string
|
||||
data: NodeData
|
||||
depth: number
|
||||
onLinkClick: (e: MouseEvent<HTMLAnchorElement>) => void
|
||||
}
|
||||
|
||||
export function TocNode({ currentHeading, data, depth, onLinkClick }: NodeProps) {
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'list':
|
||||
return (
|
||||
<ul className={s.list}>
|
||||
{data.children.map((child, index) => (
|
||||
<TocNode
|
||||
currentHeading={currentHeading}
|
||||
data={child}
|
||||
depth={depth}
|
||||
key={index}
|
||||
onLinkClick={onLinkClick}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
case 'listItem':
|
||||
return (
|
||||
<li className={s.listItem}>
|
||||
{data.children.map((child, index) => (
|
||||
<TocNode
|
||||
currentHeading={currentHeading}
|
||||
data={child}
|
||||
depth={depth + 1}
|
||||
key={index}
|
||||
onLinkClick={onLinkClick}
|
||||
/>
|
||||
))}
|
||||
</li>
|
||||
)
|
||||
case 'paragraph':
|
||||
return (
|
||||
<>
|
||||
{data.children.map((child, index) => (
|
||||
<TocNode
|
||||
currentHeading={currentHeading}
|
||||
data={child}
|
||||
depth={depth}
|
||||
key={index}
|
||||
onLinkClick={onLinkClick}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
case 'link':
|
||||
return (
|
||||
<a
|
||||
className={clsx(s.link, data.url === `#${currentHeading}` && s.active)}
|
||||
data-depth={depth}
|
||||
href={data.url}
|
||||
onClick={onLinkClick}
|
||||
title={data.title ?? undefined}
|
||||
>
|
||||
{data.children.map((child, index) => (
|
||||
<TocNode
|
||||
currentHeading={currentHeading}
|
||||
data={child}
|
||||
depth={depth}
|
||||
key={index}
|
||||
onLinkClick={onLinkClick}
|
||||
/>
|
||||
))}
|
||||
</a>
|
||||
)
|
||||
case 'text':
|
||||
return <>{data.value}</>
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
31
src/renderer/src/components/toc/toc-node.types.ts
Normal file
31
src/renderer/src/components/toc/toc-node.types.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export type TextNode = {
|
||||
type: 'text'
|
||||
value: string
|
||||
}
|
||||
|
||||
export type LinkNode = {
|
||||
children: Array<TextNode>
|
||||
title: null | string
|
||||
type: 'link'
|
||||
url: string
|
||||
}
|
||||
|
||||
export type ParagraphNode = {
|
||||
children: Array<LinkNode>
|
||||
type: 'paragraph'
|
||||
}
|
||||
|
||||
export type ListItemNode = {
|
||||
children: Array<ListNode | ParagraphNode>
|
||||
spread: boolean
|
||||
type: 'listItem'
|
||||
}
|
||||
|
||||
export type ListNode = {
|
||||
children: Array<ListItemNode>
|
||||
ordered: boolean
|
||||
spread: boolean
|
||||
type: 'list'
|
||||
}
|
||||
|
||||
export type NodeData = LinkNode | ListItemNode | ListNode | ParagraphNode | TextNode
|
||||
8
src/renderer/src/components/toc/toc.module.scss
Normal file
8
src/renderer/src/components/toc/toc.module.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.toc {
|
||||
position: sticky;
|
||||
top: 19px;
|
||||
|
||||
> ul {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
73
src/renderer/src/components/toc/toc.tsx
Normal file
73
src/renderer/src/components/toc/toc.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { MouseEvent, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Typography } from '@it-incubator/ui-kit'
|
||||
|
||||
import s from './toc.module.scss'
|
||||
|
||||
import { TocNode } from './toc-node'
|
||||
import { NodeData } from './toc-node.types'
|
||||
|
||||
type Props = {
|
||||
tocMap: NodeData
|
||||
}
|
||||
export const TableOfContents = ({ tocMap }: Props) => {
|
||||
const [currentHeading, setCurrentHeading] = useState('')
|
||||
const headingsObserverRef = useRef<IntersectionObserver | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const setCurrent: IntersectionObserverCallback = entries => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
setCurrentHeading(entry.target.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const observerOptions: IntersectionObserverInit = {
|
||||
// Negative top margin accounts for `scroll-margin`.
|
||||
// Negative bottom margin means heading needs to be towards top of viewport to trigger intersection.
|
||||
rootMargin: '-60px 0% -66%',
|
||||
threshold: 1,
|
||||
}
|
||||
|
||||
if (!headingsObserverRef.current) {
|
||||
headingsObserverRef.current = new IntersectionObserver(setCurrent, observerOptions)
|
||||
}
|
||||
|
||||
const headingsObserver = headingsObserverRef.current
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('article :is(h1,h2,h3)').forEach(h => headingsObserver.observe(h))
|
||||
}, 100)
|
||||
|
||||
return () => {
|
||||
headingsObserver.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (headingsObserverRef.current) {
|
||||
const headingsObserver = headingsObserverRef.current
|
||||
|
||||
// Disconnect and reconnect the observer to refresh it
|
||||
headingsObserver.disconnect()
|
||||
setTimeout(() => {
|
||||
document.querySelectorAll('article :is(h1,h2,h3)').forEach(h => headingsObserver.observe(h))
|
||||
}, 100)
|
||||
}
|
||||
}, [tocMap])
|
||||
|
||||
const onLinkClick = (e: MouseEvent<HTMLAnchorElement>) => {
|
||||
setCurrentHeading(e.currentTarget.getAttribute('href')!.replace('#', ''))
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className={s.toc}>
|
||||
<Typography.Subtitle2 mb={'13px'} ml={'12px'} mt={'19px'}>
|
||||
Содержание:
|
||||
</Typography.Subtitle2>
|
||||
<TocNode currentHeading={currentHeading} data={tocMap} depth={0} onLinkClick={onLinkClick} />
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
35
src/renderer/src/components/view/view.module.scss
Normal file
35
src/renderer/src/components/view/view.module.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
.page {
|
||||
width: 100%;
|
||||
padding: 22px 0 43px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 200px minmax(65ch, 100%) 190px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: var(--color-light-mode-100);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
|
||||
:global(.dark-mode) & {
|
||||
background-color: var(--color-dark-mode-600);
|
||||
}
|
||||
|
||||
> div {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
border-right: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
border-left: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
overflow: auto;
|
||||
padding: 31px 24px;
|
||||
}
|
||||
81
src/renderer/src/components/view/view.tsx
Normal file
81
src/renderer/src/components/view/view.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { IpcRendererListener } from '@electron-toolkit/preload'
|
||||
import { MdxComponent, Prose } from '@it-incubator/mdx-components'
|
||||
import { ImagePreview } from '@it-incubator/ui-kit'
|
||||
|
||||
import s from './view.module.scss'
|
||||
|
||||
import { FileOrDirectory, MdxFileSelector } from '../file-selector/file-selector'
|
||||
import { TableOfContents } from '../toc'
|
||||
|
||||
export const View = ({ setFileName }) => {
|
||||
const [selectedFile, setSelectedFile] = useState<string>('')
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [toc, setToc] = useState<any>({})
|
||||
const [srcPreview, setSrcPreview] = useState<string>('')
|
||||
|
||||
const [directoryContents, setDirectoryContents] = useState<{
|
||||
data: FileOrDirectory[]
|
||||
dir: string
|
||||
dirName: string
|
||||
files: string[]
|
||||
} | null>(null)
|
||||
|
||||
const handleFileClick = (filePath: string) => {
|
||||
setSelectedFile(filePath)
|
||||
window.electron.ipcRenderer.send('open-file', filePath)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const contentListener: IpcRendererListener = (_event, content) => {
|
||||
setCode(content.code)
|
||||
setFileName(content.fileName)
|
||||
setSelectedFile(content.fileName)
|
||||
setToc(content.toc)
|
||||
}
|
||||
|
||||
const directoryContentsListener: IpcRendererListener = (_event, content) => {
|
||||
setDirectoryContents(content)
|
||||
}
|
||||
|
||||
window.electron.ipcRenderer.on('current-content', contentListener)
|
||||
window.electron.ipcRenderer.on('directory-contents', directoryContentsListener)
|
||||
|
||||
window.electron.ipcRenderer.send('get-current-dir')
|
||||
window.electron.ipcRenderer.send('get-current-content')
|
||||
|
||||
return () => {
|
||||
window.electron.ipcRenderer.removeAllListeners('file-changed')
|
||||
window.electron.ipcRenderer.removeAllListeners('current-content')
|
||||
window.electron.ipcRenderer.removeAllListeners('directory-contents')
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!code) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.page}>
|
||||
<ImagePreview onClose={() => setSrcPreview('')} open={!!srcPreview} src={srcPreview} />
|
||||
<div className={s.container}>
|
||||
<div>
|
||||
{directoryContents && (
|
||||
<MdxFileSelector
|
||||
data={directoryContents.data}
|
||||
selectedMdx={selectedFile}
|
||||
setSelectedMdx={handleFileClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Prose as={'article'} className={s.root}>
|
||||
<MdxComponent code={code} onImageClick={setSrcPreview} />
|
||||
</Prose>
|
||||
<div>
|
||||
<TableOfContents tocMap={toc?.map} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
src/renderer/src/layout/index.ts
Normal file
1
src/renderer/src/layout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './layout'
|
||||
36
src/renderer/src/layout/layout.module.scss
Normal file
36
src/renderer/src/layout/layout.module.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
.scrollbar {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: calc(1.5rem + var(--header-height));
|
||||
|
||||
position: initial !important;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
height: 100%;
|
||||
padding-top: var(--header-height);
|
||||
|
||||
> div[class*='scrollbar'] {
|
||||
margin-top: var(--header-height);
|
||||
}
|
||||
|
||||
@media screen and (width <= 768px) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
max-width: 1186px;
|
||||
height: 100%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
@media screen and (width <= 1300px) {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
@media screen and (width <= 768px) {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
30
src/renderer/src/layout/layout.tsx
Normal file
30
src/renderer/src/layout/layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ReactNode, useState } from 'react'
|
||||
|
||||
import { Button, Header, Scrollbar, Typography } from '@it-incubator/ui-kit'
|
||||
|
||||
import s from './layout.module.scss'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
fileName: string
|
||||
}
|
||||
export const Layout = ({ children, fileName }: Props) => {
|
||||
const [isDark, setIsDark] = useState<boolean>(false)
|
||||
|
||||
const handleThemeChanged = () => {
|
||||
setIsDark(!isDark)
|
||||
document.body.classList.toggle('dark-mode', !isDark)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<Typography.H1>{fileName}</Typography.H1>
|
||||
<Button onClick={() => handleThemeChanged()}>Toggle theme</Button>
|
||||
</Header>
|
||||
<Scrollbar className={s.scrollbar}>
|
||||
<main className={s.main}>{children}</main>
|
||||
</Scrollbar>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
.page {
|
||||
width: 100%;
|
||||
padding: 22px 0 43px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 200px minmax(65ch, 100%) 190px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: var(--color-light-mode-100);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
|
||||
:global(.dark-mode) & {
|
||||
background-color: var(--color-dark-mode-600);
|
||||
}
|
||||
|
||||
> div {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
border-right: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
border-left: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
overflow: auto;
|
||||
padding: 31px 24px;
|
||||
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& p {
|
||||
margin-top: 1.5rem;
|
||||
line-height: 1.75rem;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& h1 {
|
||||
margin-top: 0.5rem;
|
||||
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
line-height: 2.5rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
& h2 {
|
||||
margin-top: 2.5rem;
|
||||
padding-bottom: 0.25rem;
|
||||
|
||||
font-size: 1.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 2.25rem;
|
||||
letter-spacing: -0.025em;
|
||||
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
& h3 {
|
||||
margin-top: 2rem;
|
||||
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 2rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
& h4 {
|
||||
margin-top: 2rem;
|
||||
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.75rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
& h5 {
|
||||
margin-top: 2rem;
|
||||
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.75rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
& h6 {
|
||||
margin-top: 2rem;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
& ul {
|
||||
margin: 1.5rem 0 0 1.5rem;
|
||||
list-style-type: disc;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& ol {
|
||||
margin: 1.5rem 0 0 1.5rem;
|
||||
list-style-type: decimal;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& li {
|
||||
margin-inline: 0.5rem;
|
||||
}
|
||||
|
||||
& blockquote {
|
||||
margin-top: 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
|
||||
font-style: italic;
|
||||
color: #374151;
|
||||
|
||||
border-left: 4px solid #d1d5db;
|
||||
|
||||
:global(.dark-mode) & {
|
||||
color: rgb(156 163 175);
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& hr {
|
||||
margin-block: 2rem;
|
||||
}
|
||||
|
||||
& code {
|
||||
padding: 0.125rem 0.25em;
|
||||
|
||||
font-size: 0.9em;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
background-color: rgb(0 0 0 / 3%);
|
||||
border: 1px solid rgb(0 0 0 / 4%);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
& pre {
|
||||
contain: paint;
|
||||
|
||||
& code {
|
||||
display: grid;
|
||||
|
||||
min-width: 100%;
|
||||
padding: 0;
|
||||
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: currentcolor;
|
||||
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
border-radius: 0;
|
||||
|
||||
[data-line] {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:global(.dark-mode) & code {
|
||||
background-color: rgb(255 255 255 / 6%);
|
||||
border: 1px solid rgb(255 255 255 / 7%);
|
||||
}
|
||||
|
||||
:global(.dark-mode) & pre code {
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export const View = ({ code }: { code: string }) => {
|
||||
return <div>View</div>
|
||||
}
|
||||
Reference in New Issue
Block a user