From b5ba87443ca60f1cb55b4b63346388f18c3c14d4 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Wed, 18 Dec 2024 12:47:09 +0100
Subject: [PATCH 01/12] refactor(blog): replace editor

---
 package-lock.json                             | 3546 ++++++++++++++++-
 package.json                                  |    1 +
 .../editor/PlainTextCodeEditorDescriptor.tsx  |   64 +
 src/components/editor/YoutubeButton.tsx       |   36 +
 .../editor/YoutubeDirectiveDescriptor.tsx     |   49 +
 src/components/editor/index.tsx               |  105 +
 src/pages/blog/index.tsx                      |   33 +-
 src/pages/write/action.ts                     |   12 +-
 src/pages/write/index.tsx                     |   64 +-
 src/styles/mdxEditor.scss                     |  103 +
 10 files changed, 3930 insertions(+), 83 deletions(-)
 create mode 100644 src/components/editor/PlainTextCodeEditorDescriptor.tsx
 create mode 100644 src/components/editor/YoutubeButton.tsx
 create mode 100644 src/components/editor/YoutubeDirectiveDescriptor.tsx
 create mode 100644 src/components/editor/index.tsx
 create mode 100644 src/styles/mdxEditor.scss

diff --git a/package-lock.json b/package-lock.json
index 5368607..1171a49 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
       "version": "0.0.0-alpha-1",
       "dependencies": {
         "@getalby/lightning-tools": "5.0.3",
+        "@mdxeditor/editor": "^3.20.0",
         "@nostr-dev-kit/ndk": "2.10.0",
         "@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
         "@reduxjs/toolkit": "2.2.6",
@@ -442,6 +443,469 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@codemirror/autocomplete": {
+      "version": "6.18.3",
+      "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz",
+      "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/commands": {
+      "version": "6.7.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
+      "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.4.0",
+        "@codemirror/view": "^6.27.0",
+        "@lezer/common": "^1.1.0"
+      }
+    },
+    "node_modules/@codemirror/lang-angular": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.3.tgz",
+      "integrity": "sha512-xgeWGJQQl1LyStvndWtruUvb4SnBZDAu/gvFH/ZU+c0W25tQR8e5hq7WTwiIY2dNxnf+49mRiGI/9yxIwB6f5w==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.1.2",
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.3.3"
+      }
+    },
+    "node_modules/@codemirror/lang-cpp": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz",
+      "integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/cpp": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-css": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
+      "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.2",
+        "@lezer/css": "^1.1.7"
+      }
+    },
+    "node_modules/@codemirror/lang-go": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
+      "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.6.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/go": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-html": {
+      "version": "6.4.9",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
+      "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/lang-css": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.0.0",
+        "@codemirror/language": "^6.4.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/css": "^1.1.0",
+        "@lezer/html": "^1.3.0"
+      }
+    },
+    "node_modules/@codemirror/lang-java": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.1.tgz",
+      "integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/java": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-javascript": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+      "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.6.0",
+        "@codemirror/lint": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/javascript": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-json": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+      "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/json": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-less": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz",
+      "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-css": "^6.2.0",
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-liquid": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.2.2.tgz",
+      "integrity": "sha512-7Dm841fk37+JQW6j2rI1/uGkJyESrjzyhiIkaLjbbR0U6aFFQvMrJn35WxQreRMADMhzkyVkZM4467OR7GR8nQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.3.1"
+      }
+    },
+    "node_modules/@codemirror/lang-markdown": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.1.tgz",
+      "integrity": "sha512-y3sSPuQjBKZQbQwe3ZJKrSW6Silyl9PnrU/Mf0m2OQgIlPoSYTtOvEL7xs94SVMkb8f4x+SQFnzXPdX4Wk2lsg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.7.1",
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/language": "^6.3.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.2.1",
+        "@lezer/markdown": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-php": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.1.tgz",
+      "integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/php": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-python": {
+      "version": "6.1.6",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.6.tgz",
+      "integrity": "sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.3.2",
+        "@codemirror/language": "^6.8.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.2.1",
+        "@lezer/python": "^1.1.4"
+      }
+    },
+    "node_modules/@codemirror/lang-rust": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz",
+      "integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/rust": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-sass": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz",
+      "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-css": "^6.2.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.2",
+        "@lezer/sass": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-sql": {
+      "version": "6.8.0",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.8.0.tgz",
+      "integrity": "sha512-aGLmY4OwGqN3TdSx3h6QeA1NrvaYtF7kkoWR/+W7/JzB0gQtJ+VJxewlnE3+VImhA4WVlhmkJr109PefOOhjLg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-vue": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
+      "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.1.2",
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.3.1"
+      }
+    },
+    "node_modules/@codemirror/lang-wast": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz",
+      "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-xml": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
+      "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.4.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/xml": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/lang-yaml": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
+      "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.2.0",
+        "@lezer/lr": "^1.0.0",
+        "@lezer/yaml": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/language": {
+      "version": "6.10.6",
+      "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.6.tgz",
+      "integrity": "sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.23.0",
+        "@lezer/common": "^1.1.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0",
+        "style-mod": "^4.0.0"
+      }
+    },
+    "node_modules/@codemirror/language-data": {
+      "version": "6.5.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.1.tgz",
+      "integrity": "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-angular": "^0.1.0",
+        "@codemirror/lang-cpp": "^6.0.0",
+        "@codemirror/lang-css": "^6.0.0",
+        "@codemirror/lang-go": "^6.0.0",
+        "@codemirror/lang-html": "^6.0.0",
+        "@codemirror/lang-java": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.0.0",
+        "@codemirror/lang-json": "^6.0.0",
+        "@codemirror/lang-less": "^6.0.0",
+        "@codemirror/lang-liquid": "^6.0.0",
+        "@codemirror/lang-markdown": "^6.0.0",
+        "@codemirror/lang-php": "^6.0.0",
+        "@codemirror/lang-python": "^6.0.0",
+        "@codemirror/lang-rust": "^6.0.0",
+        "@codemirror/lang-sass": "^6.0.0",
+        "@codemirror/lang-sql": "^6.0.0",
+        "@codemirror/lang-vue": "^0.1.1",
+        "@codemirror/lang-wast": "^6.0.0",
+        "@codemirror/lang-xml": "^6.0.0",
+        "@codemirror/lang-yaml": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/legacy-modes": "^6.4.0"
+      }
+    },
+    "node_modules/@codemirror/legacy-modes": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.4.2.tgz",
+      "integrity": "sha512-HsvWu08gOIIk303eZQCal4H4t65O/qp1V4ul4zVa3MHK5FJ0gz3qz3O55FIkm+aQUcshUOjBx38t2hPiJwW5/g==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0"
+      }
+    },
+    "node_modules/@codemirror/lint": {
+      "version": "6.8.4",
+      "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz",
+      "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.35.0",
+        "crelt": "^1.0.5"
+      }
+    },
+    "node_modules/@codemirror/merge": {
+      "version": "6.7.4",
+      "resolved": "https://registry.npmjs.org/@codemirror/merge/-/merge-6.7.4.tgz",
+      "integrity": "sha512-9FpIFTgzkaxkZE93XKoFR6caAB6sCAfYCW2NT+atGEmdv/1Mt1ouxA+hKxGRYdMvdH9Ph0KMJtYnzEi+QCGAiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/highlight": "^1.0.0",
+        "style-mod": "^4.1.0"
+      }
+    },
+    "node_modules/@codemirror/search": {
+      "version": "6.5.8",
+      "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
+      "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "crelt": "^1.0.5"
+      }
+    },
+    "node_modules/@codemirror/state": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
+      "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
+      "license": "MIT",
+      "dependencies": {
+        "@marijn/find-cluster-break": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/view": {
+      "version": "6.35.3",
+      "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.3.tgz",
+      "integrity": "sha512-ScY7L8+EGdPl4QtoBiOzE4FELp7JmNUsBvgBcCakXWM2uiv/K89VAzU3BMDscf0DsACLvTKePbd5+cFDTcei6g==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.5.0",
+        "style-mod": "^4.1.0",
+        "w3c-keyname": "^2.2.4"
+      }
+    },
+    "node_modules/@codesandbox/nodebox": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz",
+      "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==",
+      "license": "SEE LICENSE IN ./LICENSE",
+      "dependencies": {
+        "outvariant": "^1.4.0",
+        "strict-event-emitter": "^0.4.3"
+      }
+    },
+    "node_modules/@codesandbox/sandpack-client": {
+      "version": "2.19.8",
+      "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz",
+      "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@codesandbox/nodebox": "0.1.8",
+        "buffer": "^6.0.3",
+        "dequal": "^2.0.2",
+        "mime-db": "^1.52.0",
+        "outvariant": "1.4.0",
+        "static-browser-server": "1.0.3"
+      }
+    },
+    "node_modules/@codesandbox/sandpack-react": {
+      "version": "2.19.10",
+      "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.19.10.tgz",
+      "integrity": "sha512-X/7NzhR7R5pp5qYS+Gc31OzJvy+EzGz++H1YN9bJlDE+VzxTBsMN9dv3adzeo5wtxUhqexVOJS7mGr//e7KP2A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.4.0",
+        "@codemirror/commands": "^6.1.3",
+        "@codemirror/lang-css": "^6.0.1",
+        "@codemirror/lang-html": "^6.4.0",
+        "@codemirror/lang-javascript": "^6.1.2",
+        "@codemirror/language": "^6.3.2",
+        "@codemirror/state": "^6.2.0",
+        "@codemirror/view": "^6.7.1",
+        "@codesandbox/sandpack-client": "^2.19.8",
+        "@lezer/highlight": "^1.1.3",
+        "@react-hook/intersection-observer": "^3.1.1",
+        "@stitches/core": "^1.2.6",
+        "anser": "^2.1.1",
+        "clean-set": "^1.1.2",
+        "dequal": "^2.0.2",
+        "escape-carriage": "^1.3.1",
+        "lz-string": "^1.4.4",
+        "react-devtools-inline": "4.4.0",
+        "react-is": "^17.0.2"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17 || ^18",
+        "react-dom": "^16.8.0 || ^17 || ^18"
+      }
+    },
+    "node_modules/@codesandbox/sandpack-react/node_modules/react-is": {
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+      "license": "MIT"
+    },
     "node_modules/@cspotcode/source-map-support": {
       "version": "0.8.1",
       "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -925,6 +1389,44 @@
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
+    "node_modules/@floating-ui/core": {
+      "version": "1.6.8",
+      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
+      "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/utils": "^0.2.8"
+      }
+    },
+    "node_modules/@floating-ui/dom": {
+      "version": "1.6.12",
+      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz",
+      "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/core": "^1.6.0",
+        "@floating-ui/utils": "^0.2.8"
+      }
+    },
+    "node_modules/@floating-ui/react-dom": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
+      "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/dom": "^1.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0",
+        "react-dom": ">=16.8.0"
+      }
+    },
+    "node_modules/@floating-ui/utils": {
+      "version": "0.2.8",
+      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
+      "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
+      "license": "MIT"
+    },
     "node_modules/@getalby/lightning-tools": {
       "version": "5.0.3",
       "resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.0.3.tgz",
@@ -1042,6 +1544,523 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@lexical/clipboard": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.17.1.tgz",
+      "integrity": "sha512-OVqnEfWX8XN5xxuMPo6BfgGKHREbz++D5V5ISOiml0Z8fV/TQkdgwqbBJcUdJHGRHWSUwdK7CWGs/VALvVvZyw==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/html": "0.17.1",
+        "@lexical/list": "0.17.1",
+        "@lexical/selection": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/code": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.17.1.tgz",
+      "integrity": "sha512-ZspfTm6g6dN3nAb4G5bPp3SqxzdkB/bjGfa0uRKMU6/eBKtrMUgZsGxt0a8JRZ1eq2TZrQhx+l1ceRoLXii/bQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1",
+        "prismjs": "^1.27.0"
+      }
+    },
+    "node_modules/@lexical/devtools-core": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.17.1.tgz",
+      "integrity": "sha512-SzL1EX9Rt5GptIo87t6nDxAc9TtYtl6DyAPNz/sCltspdd69KQgs23sTRa26/tkNFCS1jziRN7vpN3mlnmm5wA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/html": "0.17.1",
+        "@lexical/link": "0.17.1",
+        "@lexical/mark": "0.17.1",
+        "@lexical/table": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      },
+      "peerDependencies": {
+        "react": ">=17.x",
+        "react-dom": ">=17.x"
+      }
+    },
+    "node_modules/@lexical/dragon": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.17.1.tgz",
+      "integrity": "sha512-lhBRKP7RlhiVCLtF0qiNqmMhEO6cQB43sMe7d4bvuY1G2++oKY/XAJPg6QJZdXRrCGRQ6vZ26QRNhRPmCxL5Ng==",
+      "license": "MIT",
+      "dependencies": {
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/hashtag": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.17.1.tgz",
+      "integrity": "sha512-XtP0BI8vEewAe7tzq9MC49UPUvuChuNJI/jqFp+ezZlt/RUq0BClQCOPuSlrTJhluvE2rWnUnOnVMk8ILRvggQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/history": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.17.1.tgz",
+      "integrity": "sha512-OU/ohajz4FXchUhghsWC7xeBPypFe50FCm5OePwo767G7P233IztgRKIng2pTT4zhCPW7S6Mfl53JoFHKehpWA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/html": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.17.1.tgz",
+      "integrity": "sha512-yGG+K2DXl7Wn2DpNuZ0Y3uCHJgfHkJN3/MmnFb4jLnH1FoJJiuy7WJb/BRRh9H+6xBJ9v70iv+kttDJ0u1xp5w==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/selection": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/link": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.17.1.tgz",
+      "integrity": "sha512-qFJEKBesZAtR8kfJfIVXRFXVw6dwcpmGCW7duJbtBRjdLjralOxrlVKyFhW9PEXGhi4Mdq2Ux16YnnDncpORdQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/list": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.17.1.tgz",
+      "integrity": "sha512-k9ZnmQuBvW+xVUtWJZwoGtiVG2cy+hxzkLGU4jTq1sqxRIoSeGcjvhFAK8JSEj4i21SgkB1FmkWXoYK5kbwtRA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/mark": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.17.1.tgz",
+      "integrity": "sha512-V82SSRjvygmV+ZMwVpy5gwgr2ZDrJpl3TvEDO+G5I4SDSjbgvua8hO4dKryqiDVlooxQq9dsou0GrZ9Qtm6rYg==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/markdown": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.17.1.tgz",
+      "integrity": "sha512-uexR9snyT54jfQTrbr/GZAtzX+8Oyykr4p1HS0vCVL1KU5MDuP2PoyFfOv3rcfB2TASc+aYiINhU2gSXzwCHNg==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/code": "0.17.1",
+        "@lexical/link": "0.17.1",
+        "@lexical/list": "0.17.1",
+        "@lexical/rich-text": "0.17.1",
+        "@lexical/text": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/offset": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.17.1.tgz",
+      "integrity": "sha512-fX0ZSIFWwUKAjxf6l21vyXFozJGExKWyWxA+EMuOloNAGotHnAInxep0Mt8t/xcvHs7luuyQUxEPw7YrTJP7aw==",
+      "license": "MIT",
+      "dependencies": {
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/overflow": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.17.1.tgz",
+      "integrity": "sha512-oElVDq486R3rO2+Zz0EllXJGpW3tN0tfcH+joZ5h36+URKuNeKddqkJuDRvgSLOr9l8Jhtv3+/YKduPJVKMz6w==",
+      "license": "MIT",
+      "dependencies": {
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/plain-text": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.17.1.tgz",
+      "integrity": "sha512-CSvi4j1a4ame0OAvOKUCCmn2XrNsWcST4lExGTa9Ei/VIh8IZ+a97h4Uby8T3lqOp10x+oiizYWzY30pb9QaBg==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/clipboard": "0.17.1",
+        "@lexical/selection": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/react": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.17.1.tgz",
+      "integrity": "sha512-DI4k25tO0E1WyozrjaLgKMOmLjOB7+39MT4eZN9brPlU7g+w0wzdGbTZUPgPmFGIKPK+MSLybCwAJCK97j8HzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/clipboard": "0.17.1",
+        "@lexical/code": "0.17.1",
+        "@lexical/devtools-core": "0.17.1",
+        "@lexical/dragon": "0.17.1",
+        "@lexical/hashtag": "0.17.1",
+        "@lexical/history": "0.17.1",
+        "@lexical/link": "0.17.1",
+        "@lexical/list": "0.17.1",
+        "@lexical/mark": "0.17.1",
+        "@lexical/markdown": "0.17.1",
+        "@lexical/overflow": "0.17.1",
+        "@lexical/plain-text": "0.17.1",
+        "@lexical/rich-text": "0.17.1",
+        "@lexical/selection": "0.17.1",
+        "@lexical/table": "0.17.1",
+        "@lexical/text": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "@lexical/yjs": "0.17.1",
+        "lexical": "0.17.1",
+        "react-error-boundary": "^3.1.4"
+      },
+      "peerDependencies": {
+        "react": ">=17.x",
+        "react-dom": ">=17.x"
+      }
+    },
+    "node_modules/@lexical/rich-text": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.17.1.tgz",
+      "integrity": "sha512-T3kvj4P1OpedX9jvxN3WN8NP1Khol6mCW2ScFIRNRz2dsXgyN00thH1Q1J/uyu7aKyGS7rzcY0rb1Pz1qFufqQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/clipboard": "0.17.1",
+        "@lexical/selection": "0.17.1",
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/selection": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.17.1.tgz",
+      "integrity": "sha512-qBKVn+lMV2YIoyRELNr1/QssXx/4c0id9NCB/BOuYlG8du5IjviVJquEF56NEv2t0GedDv4BpUwkhXT2QbNAxA==",
+      "license": "MIT",
+      "dependencies": {
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/table": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.17.1.tgz",
+      "integrity": "sha512-2fUYPmxhyuMQX3MRvSsNaxbgvwGNJpHaKx1Ldc+PT2MvDZ6ALZkfsxbi0do54Q3i7dOon8/avRp4TuVaCnqvoA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/utils": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/text": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.17.1.tgz",
+      "integrity": "sha512-zD2pAGXaMfPpT8PeNrx3+n0+jGnQORHyn0NEBO+hnyacKfUq5z5sI6Gebsq5NwH789bRadmJM5LvX5w8fsuv6w==",
+      "license": "MIT",
+      "dependencies": {
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/utils": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.17.1.tgz",
+      "integrity": "sha512-jCQER5EsvhLNxKH3qgcpdWj/necUb82Xjp8qWQ3c0tyL07hIRm2tDRA/s9mQmvcP855HEZSmGVmR5SKtkcEAVg==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/list": "0.17.1",
+        "@lexical/selection": "0.17.1",
+        "@lexical/table": "0.17.1",
+        "lexical": "0.17.1"
+      }
+    },
+    "node_modules/@lexical/yjs": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.17.1.tgz",
+      "integrity": "sha512-9mn5PDtaH5uLMH6hQ59EAx5FkRzmJJFcVs3E6zSIbtgkG3UASR3CFEfgsLKTjl/GC5NnTGuMck+jXaupDVBhOg==",
+      "license": "MIT",
+      "dependencies": {
+        "@lexical/offset": "0.17.1",
+        "lexical": "0.17.1"
+      },
+      "peerDependencies": {
+        "yjs": ">=13.5.22"
+      }
+    },
+    "node_modules/@lezer/common": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
+      "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
+      "license": "MIT"
+    },
+    "node_modules/@lezer/cpp": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.2.tgz",
+      "integrity": "sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/css": {
+      "version": "1.1.9",
+      "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz",
+      "integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/go": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.0.tgz",
+      "integrity": "sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/highlight": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+      "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/html": {
+      "version": "1.3.10",
+      "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
+      "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/java": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz",
+      "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/javascript": {
+      "version": "1.4.21",
+      "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.21.tgz",
+      "integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.1.3",
+        "@lezer/lr": "^1.3.0"
+      }
+    },
+    "node_modules/@lezer/json": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
+      "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/lr": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+      "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/markdown": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.3.2.tgz",
+      "integrity": "sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.0.0",
+        "@lezer/highlight": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/php": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.2.tgz",
+      "integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.1.0"
+      }
+    },
+    "node_modules/@lezer/python": {
+      "version": "1.1.15",
+      "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.15.tgz",
+      "integrity": "sha512-aVQ43m2zk4FZYedCqL0KHPEUsqZOrmAvRhkhHlVPnDD1HODDyyQv5BRIuod4DadkgBEZd53vQOtXTonNbEgjrQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/rust": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz",
+      "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/sass": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.0.7.tgz",
+      "integrity": "sha512-8HLlOkuX/SMHOggI2DAsXUw38TuURe+3eQ5hiuk9QmYOUyC55B1dYEIMkav5A4IELVaW4e1T4P9WRiI5ka4mdw==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/xml": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.5.tgz",
+      "integrity": "sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/yaml": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz",
+      "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.4.0"
+      }
+    },
+    "node_modules/@marijn/find-cluster-break": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
+      "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
+      "license": "MIT"
+    },
+    "node_modules/@mdxeditor/editor": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-3.20.0.tgz",
+      "integrity": "sha512-vt/2jrse+xjT2Lnx/KT9jbCEg+93GBzDSQNSdBYcyJztz34dOYZjQmT5NDH7tdcMOSL007PvLCKXbO7YQ96R6g==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/lang-markdown": "^6.2.3",
+        "@codemirror/language-data": "^6.5.1",
+        "@codemirror/merge": "^6.4.0",
+        "@codemirror/state": "^6.4.0",
+        "@codemirror/view": "^6.23.0",
+        "@codesandbox/sandpack-react": "^2.10.0",
+        "@lexical/clipboard": "^0.17.1",
+        "@lexical/link": "^0.17.1",
+        "@lexical/list": "^0.17.1",
+        "@lexical/markdown": "^0.17.1",
+        "@lexical/plain-text": "^0.17.1",
+        "@lexical/react": "^0.17.1",
+        "@lexical/rich-text": "^0.17.1",
+        "@lexical/selection": "^0.17.1",
+        "@lexical/utils": "^0.17.1",
+        "@mdxeditor/gurx": "^1.1.4",
+        "@radix-ui/colors": "^3.0.0",
+        "@radix-ui/react-dialog": "^1.0.5",
+        "@radix-ui/react-icons": "^1.3.0",
+        "@radix-ui/react-popover": "^1.0.7",
+        "@radix-ui/react-select": "^2.0.0",
+        "@radix-ui/react-toggle-group": "^1.0.4",
+        "@radix-ui/react-toolbar": "^1.0.4",
+        "@radix-ui/react-tooltip": "^1.0.7",
+        "classnames": "^2.3.2",
+        "cm6-theme-basic-light": "^0.2.0",
+        "codemirror": "^6.0.1",
+        "downshift": "^7.6.0",
+        "js-yaml": "4.1.0",
+        "lexical": "^0.17.1",
+        "mdast-util-directive": "^3.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-frontmatter": "^2.0.1",
+        "mdast-util-gfm-strikethrough": "^2.0.0",
+        "mdast-util-gfm-table": "^2.0.0",
+        "mdast-util-gfm-task-list-item": "^2.0.0",
+        "mdast-util-mdx": "^3.0.0",
+        "mdast-util-mdx-jsx": "^3.0.0",
+        "mdast-util-to-markdown": "^2.1.0",
+        "micromark-extension-directive": "^3.0.0",
+        "micromark-extension-frontmatter": "^2.0.0",
+        "micromark-extension-gfm-strikethrough": "^2.0.0",
+        "micromark-extension-gfm-table": "^2.0.0",
+        "micromark-extension-gfm-task-list-item": "^2.0.1",
+        "micromark-extension-mdx-jsx": "^3.0.0",
+        "micromark-extension-mdx-md": "^2.0.0",
+        "micromark-extension-mdxjs": "^3.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.1",
+        "micromark-util-symbol": "^2.0.0",
+        "react-hook-form": "^7.44.2",
+        "unidiff": "^1.0.2"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "react": ">= 18 || >= 19",
+        "react-dom": ">= 18 || >= 19"
+      }
+    },
+    "node_modules/@mdxeditor/gurx": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/@mdxeditor/gurx/-/gurx-1.2.3.tgz",
+      "integrity": "sha512-5DQOlEx46oN9spggrC8husAGAhVoEFBGIYKN48es08XhRUbSU6l5bcIQYwRrQaY8clU1tExIcXzw8/fNnoxjpg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "react": ">= 18 || >= 19",
+        "react-dom": ">= 18 || >= 19"
+      }
+    },
     "node_modules/@mixmark-io/domino": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
@@ -1158,6 +2177,12 @@
         "typescript-lru-cache": "^2.0.0"
       }
     },
+    "node_modules/@open-draft/deferred-promise": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
+      "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
+      "license": "MIT"
+    },
     "node_modules/@popperjs/core": {
       "version": "2.11.8",
       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1168,6 +2193,788 @@
         "url": "https://opencollective.com/popperjs"
       }
     },
+    "node_modules/@radix-ui/colors": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz",
+      "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==",
+      "license": "MIT"
+    },
+    "node_modules/@radix-ui/number": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
+      "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==",
+      "license": "MIT"
+    },
+    "node_modules/@radix-ui/primitive": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
+      "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
+      "license": "MIT"
+    },
+    "node_modules/@radix-ui/react-arrow": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
+      "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-primitive": "2.0.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-collection": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
+      "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-slot": "1.1.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-compose-refs": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
+      "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-context": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
+      "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-dialog": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.3.tgz",
+      "integrity": "sha512-ujGvqQNkZ0J7caQyl8XuZRj2/TIrYcOGwqz5TeD1OMcCdfBuEMP0D12ve+8J5F9XuNUth3FAKFWo/wt0E/GJrQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-dismissable-layer": "1.1.2",
+        "@radix-ui/react-focus-guards": "1.1.1",
+        "@radix-ui/react-focus-scope": "1.1.1",
+        "@radix-ui/react-id": "1.1.0",
+        "@radix-ui/react-portal": "1.1.3",
+        "@radix-ui/react-presence": "1.1.2",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-slot": "1.1.1",
+        "@radix-ui/react-use-controllable-state": "1.1.0",
+        "aria-hidden": "^1.1.1",
+        "react-remove-scroll": "2.6.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-direction": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
+      "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-dismissable-layer": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz",
+      "integrity": "sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-use-callback-ref": "1.1.0",
+        "@radix-ui/react-use-escape-keydown": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-focus-guards": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+      "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-focus-scope": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
+      "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-use-callback-ref": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-icons": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
+      "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc"
+      }
+    },
+    "node_modules/@radix-ui/react-id": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+      "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-layout-effect": "1.1.0"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-popover": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.3.tgz",
+      "integrity": "sha512-MBDKFwRe6fi0LT8m/Jl4V8J3WbS/UfXJtsgg8Ym5w5AyPG3XfHH4zhBp1P8HmZK83T8J7UzVm6/JpDE3WMl1Dw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-dismissable-layer": "1.1.2",
+        "@radix-ui/react-focus-guards": "1.1.1",
+        "@radix-ui/react-focus-scope": "1.1.1",
+        "@radix-ui/react-id": "1.1.0",
+        "@radix-ui/react-popper": "1.2.1",
+        "@radix-ui/react-portal": "1.1.3",
+        "@radix-ui/react-presence": "1.1.2",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-slot": "1.1.1",
+        "@radix-ui/react-use-controllable-state": "1.1.0",
+        "aria-hidden": "^1.1.1",
+        "react-remove-scroll": "2.6.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-popper": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
+      "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/react-dom": "^2.0.0",
+        "@radix-ui/react-arrow": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-use-callback-ref": "1.1.0",
+        "@radix-ui/react-use-layout-effect": "1.1.0",
+        "@radix-ui/react-use-rect": "1.1.0",
+        "@radix-ui/react-use-size": "1.1.0",
+        "@radix-ui/rect": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-portal": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
+      "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-use-layout-effect": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-presence": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
+      "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-use-layout-effect": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-primitive": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+      "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-slot": "1.1.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-roving-focus": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
+      "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-collection": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-direction": "1.1.0",
+        "@radix-ui/react-id": "1.1.0",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-use-callback-ref": "1.1.0",
+        "@radix-ui/react-use-controllable-state": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-select": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.3.tgz",
+      "integrity": "sha512-tlLwaewTfrKetiex8iW9wwME/qrYlzlH0qcgYmos7xS54MO00SiPHasLoAykg/yVrjf41GQptPPi4oXzrP+sgg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/number": "1.1.0",
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-collection": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-direction": "1.1.0",
+        "@radix-ui/react-dismissable-layer": "1.1.2",
+        "@radix-ui/react-focus-guards": "1.1.1",
+        "@radix-ui/react-focus-scope": "1.1.1",
+        "@radix-ui/react-id": "1.1.0",
+        "@radix-ui/react-popper": "1.2.1",
+        "@radix-ui/react-portal": "1.1.3",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-slot": "1.1.1",
+        "@radix-ui/react-use-callback-ref": "1.1.0",
+        "@radix-ui/react-use-controllable-state": "1.1.0",
+        "@radix-ui/react-use-layout-effect": "1.1.0",
+        "@radix-ui/react-use-previous": "1.1.0",
+        "@radix-ui/react-visually-hidden": "1.1.1",
+        "aria-hidden": "^1.1.1",
+        "react-remove-scroll": "2.6.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-separator": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz",
+      "integrity": "sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-primitive": "2.0.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-slot": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+      "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-compose-refs": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-toggle": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.1.tgz",
+      "integrity": "sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-use-controllable-state": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-toggle-group": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.1.tgz",
+      "integrity": "sha512-OgDLZEA30Ylyz8YSXvnGqIHtERqnUt1KUYTKdw/y8u7Ci6zGiJfXc02jahmcSNK3YcErqioj/9flWC9S1ihfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-direction": "1.1.0",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-roving-focus": "1.1.1",
+        "@radix-ui/react-toggle": "1.1.1",
+        "@radix-ui/react-use-controllable-state": "1.1.0"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-toolbar": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.1.tgz",
+      "integrity": "sha512-r7T80WOCHc2n3KRzFCbHWGVzkfVTCzDofGU4gqa5ZuIzgnVaLogGsdyifFJXWQDp0lAr5hrf+X9uqQdE0pa6Ww==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-direction": "1.1.0",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-roving-focus": "1.1.1",
+        "@radix-ui/react-separator": "1.1.1",
+        "@radix-ui/react-toggle-group": "1.1.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-tooltip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.5.tgz",
+      "integrity": "sha512-IucoQPcK5nwUuztaxBQvudvYwH58wtRcJlv1qvaMSyIbL9dEBfFN0vRf/D8xDbu6HmAJLlNGty4z8Na+vIqe9Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.1",
+        "@radix-ui/react-compose-refs": "1.1.1",
+        "@radix-ui/react-context": "1.1.1",
+        "@radix-ui/react-dismissable-layer": "1.1.2",
+        "@radix-ui/react-id": "1.1.0",
+        "@radix-ui/react-popper": "1.2.1",
+        "@radix-ui/react-portal": "1.1.3",
+        "@radix-ui/react-presence": "1.1.2",
+        "@radix-ui/react-primitive": "2.0.1",
+        "@radix-ui/react-slot": "1.1.1",
+        "@radix-ui/react-use-controllable-state": "1.1.0",
+        "@radix-ui/react-visually-hidden": "1.1.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-callback-ref": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+      "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-controllable-state": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+      "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-callback-ref": "1.1.0"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-escape-keydown": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
+      "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-callback-ref": "1.1.0"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-layout-effect": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
+      "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-previous": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
+      "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-rect": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
+      "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/rect": "1.1.0"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-use-size": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
+      "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-layout-effect": "1.1.0"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-visually-hidden": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
+      "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-primitive": "2.0.1"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/@radix-ui/rect": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
+      "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
+      "license": "MIT"
+    },
+    "node_modules/@react-hook/intersection-observer": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz",
+      "integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@react-hook/passive-layout-effect": "^1.2.0",
+        "intersection-observer": "^0.10.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8"
+      }
+    },
+    "node_modules/@react-hook/passive-layout-effect": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz",
+      "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": ">=16.8"
+      }
+    },
     "node_modules/@reduxjs/toolkit": {
       "version": "2.2.6",
       "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.6.tgz",
@@ -1548,6 +3355,12 @@
         "url": "https://paulmillr.com/funding/"
       }
     },
+    "node_modules/@stitches/core": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz",
+      "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==",
+      "license": "MIT"
+    },
     "node_modules/@tiptap/core": {
       "version": "2.9.1",
       "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz",
@@ -1992,6 +3805,15 @@
       "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
       "dev": true
     },
+    "node_modules/@types/acorn": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
+      "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "*"
+      }
+    },
     "node_modules/@types/babel__core": {
       "version": "7.20.5",
       "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2041,6 +3863,15 @@
         "@types/filesystem": "*"
       }
     },
+    "node_modules/@types/debug": {
+      "version": "4.1.12",
+      "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+      "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/ms": "*"
+      }
+    },
     "node_modules/@types/dompurify": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
@@ -2054,9 +3885,17 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
       "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
-      "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/estree-jsx": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+      "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "*"
+      }
+    },
     "node_modules/@types/file-saver": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
@@ -2085,12 +3924,36 @@
         "@types/react": "*"
       }
     },
+    "node_modules/@types/hast": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+      "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "*"
+      }
+    },
     "node_modules/@types/lodash": {
       "version": "4.17.7",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
       "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
       "dev": true
     },
+    "node_modules/@types/mdast": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+      "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "*"
+      }
+    },
+    "node_modules/@types/ms": {
+      "version": "0.7.34",
+      "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
+      "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
+      "license": "MIT"
+    },
     "node_modules/@types/node": {
       "version": "20.14.10",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
@@ -2127,7 +3990,7 @@
       "version": "18.3.0",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
       "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
-      "dev": true,
+      "devOptional": true,
       "dependencies": {
         "@types/react": "*"
       }
@@ -2163,6 +4026,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/unist": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+      "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+      "license": "MIT"
+    },
     "node_modules/@types/use-sync-external-store": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
@@ -2388,7 +4257,6 @@
       "version": "8.12.1",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
       "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
-      "dev": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -2400,7 +4268,6 @@
       "version": "5.3.2",
       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
       "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
-      "dev": true,
       "peerDependencies": {
         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
@@ -2433,6 +4300,12 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/anser": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.0.tgz",
+      "integrity": "sha512-pGGR7Nq1K/i9KGs29PvHDXA8AsfZ3OiYRMDClT3FIC085Kbns9CJ7ogq9MEiGnrjd9THOGoh7B+kWzePHzZyJQ==",
+      "license": "MIT"
+    },
     "node_modules/ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2478,6 +4351,18 @@
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
     },
+    "node_modules/aria-hidden": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+      "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/array-union": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -2671,6 +4556,16 @@
         }
       ]
     },
+    "node_modules/ccount": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+      "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2685,6 +4580,46 @@
         "node": ">=4"
       }
     },
+    "node_modules/character-entities": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+      "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/character-entities-html4": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+      "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/character-entities-legacy": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+      "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/character-reference-invalid": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+      "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/chokidar": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -2721,6 +4656,18 @@
         "node": ">= 6"
       }
     },
+    "node_modules/classnames": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+      "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+      "license": "MIT"
+    },
+    "node_modules/clean-set": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz",
+      "integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==",
+      "license": "MIT"
+    },
     "node_modules/clsx": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -2729,6 +4676,33 @@
         "node": ">=6"
       }
     },
+    "node_modules/cm6-theme-basic-light": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz",
+      "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/highlight": "^1.0.0"
+      }
+    },
+    "node_modules/codemirror": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+      "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/commands": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/lint": "^6.0.0",
+        "@codemirror/search": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0"
+      }
+    },
     "node_modules/color-convert": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -2755,6 +4729,12 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/compute-scroll-into-view": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
+      "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==",
+      "license": "MIT"
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2843,6 +4823,19 @@
         }
       }
     },
+    "node_modules/decode-named-character-reference": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+      "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+      "license": "MIT",
+      "dependencies": {
+        "character-entities": "^2.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/deep-is": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2857,6 +4850,34 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/dequal": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+      "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/detect-node-es": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+      "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+      "license": "MIT"
+    },
+    "node_modules/devlop": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+      "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+      "license": "MIT",
+      "dependencies": {
+        "dequal": "^2.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/dexie": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz",
@@ -2900,6 +4921,40 @@
       "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
       "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
     },
+    "node_modules/dotenv": {
+      "version": "16.4.7",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+      "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/downshift": {
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.2.tgz",
+      "integrity": "sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.14.8",
+        "compute-scroll-into-view": "^2.0.4",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.12.0"
+      }
+    },
+    "node_modules/downshift/node_modules/react-is": {
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+      "license": "MIT"
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.823",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.823.tgz",
@@ -3001,6 +5056,12 @@
         "node": ">=6"
       }
     },
+    "node_modules/escape-carriage": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
+      "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==",
+      "license": "MIT"
+    },
     "node_modules/escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -3297,6 +5358,30 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/estree-util-is-identifier-name": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+      "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/estree-util-visit": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz",
+      "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree-jsx": "^1.0.0",
+        "@types/unist": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/esutils": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3377,6 +5462,19 @@
         "reusify": "^1.0.4"
       }
     },
+    "node_modules/fault": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz",
+      "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==",
+      "license": "MIT",
+      "dependencies": {
+        "format": "^0.2.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/fetch-blob": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
@@ -3496,6 +5594,14 @@
         "node": ">= 6"
       }
     },
+    "node_modules/format": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
+      "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
+      "engines": {
+        "node": ">=0.4.x"
+      }
+    },
     "node_modules/formdata-polyfill": {
       "version": "4.0.10",
       "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -3546,6 +5652,15 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/get-nonce": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+      "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/glob": {
       "version": "7.2.3",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3736,6 +5851,36 @@
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
       "dev": true
     },
+    "node_modules/intersection-observer": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz",
+      "integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==",
+      "license": "W3C-20150513"
+    },
+    "node_modules/is-alphabetical": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+      "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/is-alphanumerical": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+      "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+      "license": "MIT",
+      "dependencies": {
+        "is-alphabetical": "^2.0.0",
+        "is-decimal": "^2.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/is-binary-path": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3748,6 +5893,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-decimal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+      "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -3769,6 +5924,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-hexadecimal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+      "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/is-number": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -3798,6 +5963,17 @@
       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
       "dev": true
     },
+    "node_modules/isomorphic.js": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
+      "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
+      "license": "MIT",
+      "peer": true,
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      }
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -3807,7 +5983,6 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-      "dev": true,
       "dependencies": {
         "argparse": "^2.0.1"
       },
@@ -3879,6 +6054,34 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lexical": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.17.1.tgz",
+      "integrity": "sha512-72/MhR7jqmyqD10bmJw8gztlCm4KDDT+TPtU4elqXrEvHoO5XENi34YAEUD9gIkPfqSwyLa9mwAX1nKzIr5xEA==",
+      "license": "MIT"
+    },
+    "node_modules/lib0": {
+      "version": "0.2.99",
+      "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.99.tgz",
+      "integrity": "sha512-vwztYuUf1uf/1zQxfzRfO5yzfNKhTtgOByCruuiQQxWQXnPb8Itaube5ylofcV0oM0aKal9Mv+S1s1Ky0UYP1w==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "isomorphic.js": "^0.2.4"
+      },
+      "bin": {
+        "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
+        "0gentesthtml": "bin/gentesthtml.js",
+        "0serve": "bin/0serve.js"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      }
+    },
     "node_modules/light-bolt11-decoder": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz",
@@ -3937,6 +6140,16 @@
       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
       "dev": true
     },
+    "node_modules/longest-streak": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+      "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/loose-envify": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -3957,6 +6170,15 @@
         "yallist": "^3.0.2"
       }
     },
+    "node_modules/lz-string": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+      "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+      "license": "MIT",
+      "bin": {
+        "lz-string": "bin/bin.js"
+      }
+    },
     "node_modules/make-error": {
       "version": "1.3.6",
       "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -3979,6 +6201,16 @@
         "markdown-it": "bin/markdown-it.mjs"
       }
     },
+    "node_modules/markdown-table": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
+      "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/marked": {
       "version": "14.1.3",
       "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz",
@@ -3991,6 +6223,253 @@
         "node": ">= 18"
       }
     },
+    "node_modules/mdast-util-directive": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz",
+      "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "@types/unist": "^3.0.0",
+        "devlop": "^1.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0",
+        "parse-entities": "^4.0.0",
+        "stringify-entities": "^4.0.0",
+        "unist-util-visit-parents": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-from-markdown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+      "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "@types/unist": "^3.0.0",
+        "decode-named-character-reference": "^1.0.0",
+        "devlop": "^1.0.0",
+        "mdast-util-to-string": "^4.0.0",
+        "micromark": "^4.0.0",
+        "micromark-util-decode-numeric-character-reference": "^2.0.0",
+        "micromark-util-decode-string": "^2.0.0",
+        "micromark-util-normalize-identifier": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0",
+        "unist-util-stringify-position": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-frontmatter": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz",
+      "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "devlop": "^1.0.0",
+        "escape-string-regexp": "^5.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0",
+        "micromark-extension-frontmatter": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mdast-util-gfm-strikethrough": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+      "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-gfm-table": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+      "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "devlop": "^1.0.0",
+        "markdown-table": "^3.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-gfm-task-list-item": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+      "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "devlop": "^1.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-mdx": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz",
+      "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==",
+      "license": "MIT",
+      "dependencies": {
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-mdx-expression": "^2.0.0",
+        "mdast-util-mdx-jsx": "^3.0.0",
+        "mdast-util-mdxjs-esm": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-mdx-expression": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+      "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree-jsx": "^1.0.0",
+        "@types/hast": "^3.0.0",
+        "@types/mdast": "^4.0.0",
+        "devlop": "^1.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-mdx-jsx": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz",
+      "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree-jsx": "^1.0.0",
+        "@types/hast": "^3.0.0",
+        "@types/mdast": "^4.0.0",
+        "@types/unist": "^3.0.0",
+        "ccount": "^2.0.0",
+        "devlop": "^1.1.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0",
+        "parse-entities": "^4.0.0",
+        "stringify-entities": "^4.0.0",
+        "unist-util-stringify-position": "^4.0.0",
+        "vfile-message": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-mdxjs-esm": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+      "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree-jsx": "^1.0.0",
+        "@types/hast": "^3.0.0",
+        "@types/mdast": "^4.0.0",
+        "devlop": "^1.0.0",
+        "mdast-util-from-markdown": "^2.0.0",
+        "mdast-util-to-markdown": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-phrasing": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+      "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "unist-util-is": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-to-markdown": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+      "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0",
+        "@types/unist": "^3.0.0",
+        "longest-streak": "^3.0.0",
+        "mdast-util-phrasing": "^4.0.0",
+        "mdast-util-to-string": "^4.0.0",
+        "micromark-util-classify-character": "^2.0.0",
+        "micromark-util-decode-string": "^2.0.0",
+        "unist-util-visit": "^5.0.0",
+        "zwitch": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/mdast-util-to-string": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+      "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mdast": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/mdurl": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
@@ -4010,6 +6489,691 @@
         "node": ">= 8"
       }
     },
+    "node_modules/micromark": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz",
+      "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@types/debug": "^4.0.0",
+        "debug": "^4.0.0",
+        "decode-named-character-reference": "^1.0.0",
+        "devlop": "^1.0.0",
+        "micromark-core-commonmark": "^2.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-chunked": "^2.0.0",
+        "micromark-util-combine-extensions": "^2.0.0",
+        "micromark-util-decode-numeric-character-reference": "^2.0.0",
+        "micromark-util-encode": "^2.0.0",
+        "micromark-util-normalize-identifier": "^2.0.0",
+        "micromark-util-resolve-all": "^2.0.0",
+        "micromark-util-sanitize-uri": "^2.0.0",
+        "micromark-util-subtokenize": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-core-commonmark": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz",
+      "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "decode-named-character-reference": "^1.0.0",
+        "devlop": "^1.0.0",
+        "micromark-factory-destination": "^2.0.0",
+        "micromark-factory-label": "^2.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-factory-title": "^2.0.0",
+        "micromark-factory-whitespace": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-chunked": "^2.0.0",
+        "micromark-util-classify-character": "^2.0.0",
+        "micromark-util-html-tag-name": "^2.0.0",
+        "micromark-util-normalize-identifier": "^2.0.0",
+        "micromark-util-resolve-all": "^2.0.0",
+        "micromark-util-subtokenize": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-extension-directive": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz",
+      "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==",
+      "license": "MIT",
+      "dependencies": {
+        "devlop": "^1.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-factory-whitespace": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0",
+        "parse-entities": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-frontmatter": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz",
+      "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==",
+      "license": "MIT",
+      "dependencies": {
+        "fault": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-gfm-strikethrough": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+      "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+      "license": "MIT",
+      "dependencies": {
+        "devlop": "^1.0.0",
+        "micromark-util-chunked": "^2.0.0",
+        "micromark-util-classify-character": "^2.0.0",
+        "micromark-util-resolve-all": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-gfm-table": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
+      "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
+      "license": "MIT",
+      "dependencies": {
+        "devlop": "^1.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-gfm-task-list-item": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+      "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+      "license": "MIT",
+      "dependencies": {
+        "devlop": "^1.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-mdx-expression": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz",
+      "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "devlop": "^1.0.0",
+        "micromark-factory-mdx-expression": "^2.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-events-to-acorn": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-extension-mdx-jsx": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz",
+      "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/acorn": "^4.0.0",
+        "@types/estree": "^1.0.0",
+        "devlop": "^1.0.0",
+        "estree-util-is-identifier-name": "^3.0.0",
+        "micromark-factory-mdx-expression": "^2.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-events-to-acorn": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0",
+        "vfile-message": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-mdx-md": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz",
+      "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-types": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-mdxjs": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz",
+      "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.0.0",
+        "acorn-jsx": "^5.0.0",
+        "micromark-extension-mdx-expression": "^3.0.0",
+        "micromark-extension-mdx-jsx": "^3.0.0",
+        "micromark-extension-mdx-md": "^2.0.0",
+        "micromark-extension-mdxjs-esm": "^3.0.0",
+        "micromark-util-combine-extensions": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-extension-mdxjs-esm": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz",
+      "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "devlop": "^1.0.0",
+        "micromark-core-commonmark": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-events-to-acorn": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0",
+        "unist-util-position-from-estree": "^2.0.0",
+        "vfile-message": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/micromark-factory-destination": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+      "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-factory-label": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+      "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "devlop": "^1.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-factory-mdx-expression": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz",
+      "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "devlop": "^1.0.0",
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-events-to-acorn": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0",
+        "unist-util-position-from-estree": "^2.0.0",
+        "vfile-message": "^4.0.0"
+      }
+    },
+    "node_modules/micromark-factory-space": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+      "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-factory-title": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+      "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-factory-whitespace": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+      "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-factory-space": "^2.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-character": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+      "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-chunked": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+      "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-symbol": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-classify-character": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+      "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-combine-extensions": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+      "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-chunked": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-decode-numeric-character-reference": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+      "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-symbol": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-decode-string": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+      "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "decode-named-character-reference": "^1.0.0",
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-decode-numeric-character-reference": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-encode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+      "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/micromark-util-events-to-acorn": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz",
+      "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@types/acorn": "^4.0.0",
+        "@types/estree": "^1.0.0",
+        "@types/unist": "^3.0.0",
+        "devlop": "^1.0.0",
+        "estree-util-visit": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0",
+        "vfile-message": "^4.0.0"
+      }
+    },
+    "node_modules/micromark-util-html-tag-name": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+      "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/micromark-util-normalize-identifier": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+      "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-symbol": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-resolve-all": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+      "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-sanitize-uri": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+      "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "micromark-util-character": "^2.0.0",
+        "micromark-util-encode": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-subtokenize": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz",
+      "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "devlop": "^1.0.0",
+        "micromark-util-chunked": "^2.0.0",
+        "micromark-util-symbol": "^2.0.0",
+        "micromark-util-types": "^2.0.0"
+      }
+    },
+    "node_modules/micromark-util-symbol": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+      "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/micromark-util-types": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz",
+      "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==",
+      "funding": [
+        {
+          "type": "GitHub Sponsors",
+          "url": "https://github.com/sponsors/unifiedjs"
+        },
+        {
+          "type": "OpenCollective",
+          "url": "https://opencollective.com/unified"
+        }
+      ],
+      "license": "MIT"
+    },
     "node_modules/micromatch": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -4063,9 +7227,9 @@
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     "node_modules/nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
       "dev": true,
       "funding": [
         {
@@ -4073,6 +7237,7 @@
           "url": "https://github.com/sponsors/ai"
         }
       ],
+      "license": "MIT",
       "bin": {
         "nanoid": "bin/nanoid.cjs"
       },
@@ -4336,6 +7501,12 @@
       "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
       "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
     },
+    "node_modules/outvariant": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz",
+      "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==",
+      "license": "MIT"
+    },
     "node_modules/p-limit": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -4383,6 +7554,31 @@
         "node": ">=6"
       }
     },
+    "node_modules/parse-entities": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+      "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^2.0.0",
+        "character-entities-legacy": "^3.0.0",
+        "character-reference-invalid": "^2.0.0",
+        "decode-named-character-reference": "^1.0.0",
+        "is-alphanumerical": "^2.0.0",
+        "is-decimal": "^2.0.0",
+        "is-hexadecimal": "^2.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
+    "node_modules/parse-entities/node_modules/@types/unist": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+      "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+      "license": "MIT"
+    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4495,6 +7691,15 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/prismjs": {
+      "version": "1.29.0",
+      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+      "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/prop-types": {
       "version": "15.8.1",
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4768,6 +7973,15 @@
         "react-dom": ">= 15"
       }
     },
+    "node_modules/react-devtools-inline": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz",
+      "integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es6-symbol": "^3"
+      }
+    },
     "node_modules/react-dom": {
       "version": "18.3.1",
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
@@ -4780,6 +7994,22 @@
         "react": "^18.3.1"
       }
     },
+    "node_modules/react-error-boundary": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz",
+      "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.12.5"
+      },
+      "engines": {
+        "node": ">=10",
+        "npm": ">=6"
+      },
+      "peerDependencies": {
+        "react": ">=16.13.1"
+      }
+    },
     "node_modules/react-fast-compare": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
@@ -4801,6 +8031,22 @@
         "react": ">=16.3.0"
       }
     },
+    "node_modules/react-hook-form": {
+      "version": "7.54.1",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.1.tgz",
+      "integrity": "sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/react-hook-form"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17 || ^18 || ^19"
+      }
+    },
     "node_modules/react-is": {
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -4837,6 +8083,53 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react-remove-scroll": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz",
+      "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==",
+      "license": "MIT",
+      "dependencies": {
+        "react-remove-scroll-bar": "^2.3.6",
+        "react-style-singleton": "^2.2.1",
+        "tslib": "^2.1.0",
+        "use-callback-ref": "^1.3.0",
+        "use-sidecar": "^1.1.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-remove-scroll-bar": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+      "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+      "license": "MIT",
+      "dependencies": {
+        "react-style-singleton": "^2.2.2",
+        "tslib": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/react-router": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.1.tgz",
@@ -4876,6 +8169,28 @@
         "react": "^16.3.0 || ^17.0.0 || ^18.0.0"
       }
     },
+    "node_modules/react-style-singleton": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+      "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-nonce": "^1.0.0",
+        "tslib": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/react-toastify": {
       "version": "10.0.5",
       "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
@@ -5118,6 +8433,38 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/static-browser-server": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz",
+      "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@open-draft/deferred-promise": "^2.1.0",
+        "dotenv": "^16.0.3",
+        "mime-db": "^1.52.0",
+        "outvariant": "^1.3.0"
+      }
+    },
+    "node_modules/strict-event-emitter": {
+      "version": "0.4.6",
+      "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz",
+      "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==",
+      "license": "MIT"
+    },
+    "node_modules/stringify-entities": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+      "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+      "license": "MIT",
+      "dependencies": {
+        "character-entities-html4": "^2.0.0",
+        "character-entities-legacy": "^3.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/strip-ansi": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -5142,6 +8489,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/style-mod": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+      "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
+      "license": "MIT"
+    },
     "node_modules/supports-color": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -5299,6 +8652,12 @@
       "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.2.2.tgz",
       "integrity": "sha512-GgPFuNx+08UaYBYmJQmuI86ykYa2PUUtfXAYb4MLRHGunSCp8k9N+dbsR4PK1yk4/zV9q4e4PrNg8ymXqGYaYA=="
     },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "license": "0BSD"
+    },
     "node_modules/tstl": {
       "version": "2.5.16",
       "resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.16.tgz",
@@ -5379,6 +8738,92 @@
       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
       "dev": true
     },
+    "node_modules/unidiff": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unidiff/-/unidiff-1.0.4.tgz",
+      "integrity": "sha512-ynU0vsAXw0ir8roa+xPCUHmnJ5goc5BTM2Kuc3IJd8UwgaeRs7VSD5+eeaQL+xp1JtB92hu/Zy/Lgy7RZcr1pQ==",
+      "license": "MIT",
+      "dependencies": {
+        "diff": "^5.1.0"
+      }
+    },
+    "node_modules/unidiff/node_modules/diff": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+      "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/unist-util-is": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+      "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/unist-util-position-from-estree": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz",
+      "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/unist-util-stringify-position": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+      "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/unist-util-visit": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+      "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0",
+        "unist-util-is": "^6.0.0",
+        "unist-util-visit-parents": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/unist-util-visit-parents": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+      "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0",
+        "unist-util-is": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/update-browserslist-db": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
@@ -5418,6 +8863,49 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-callback-ref": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
+      "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/use-sidecar": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+      "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "detect-node-es": "^1.1.0",
+        "tslib": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/use-sync-external-store": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
@@ -5464,6 +8952,20 @@
       "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
       "dev": true
     },
+    "node_modules/vfile-message": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+      "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0",
+        "unist-util-stringify-position": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/vite": {
       "version": "5.4.11",
       "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
@@ -5646,6 +9148,24 @@
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
       "dev": true
     },
+    "node_modules/yjs": {
+      "version": "13.6.20",
+      "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.20.tgz",
+      "integrity": "sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==",
+      "license": "MIT",
+      "peer": true,
+      "dependencies": {
+        "lib0": "^0.2.98"
+      },
+      "engines": {
+        "node": ">=16.0.0",
+        "npm": ">=8.0.0"
+      },
+      "funding": {
+        "type": "GitHub Sponsors ❤",
+        "url": "https://github.com/sponsors/dmonad"
+      }
+    },
     "node_modules/yn": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
@@ -5666,6 +9186,16 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/zwitch": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+      "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index a99abbb..828f6be 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
   },
   "dependencies": {
     "@getalby/lightning-tools": "5.0.3",
+    "@mdxeditor/editor": "^3.20.0",
     "@nostr-dev-kit/ndk": "2.10.0",
     "@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
     "@reduxjs/toolkit": "2.2.6",
diff --git a/src/components/editor/PlainTextCodeEditorDescriptor.tsx b/src/components/editor/PlainTextCodeEditorDescriptor.tsx
new file mode 100644
index 0000000..c6ed160
--- /dev/null
+++ b/src/components/editor/PlainTextCodeEditorDescriptor.tsx
@@ -0,0 +1,64 @@
+import {
+  CodeBlockEditorDescriptor,
+  useCodeBlockEditorContext
+} from '@mdxeditor/editor'
+import { useCallback, useEffect, useRef } from 'react'
+
+export const PlainTextCodeEditorDescriptor: CodeBlockEditorDescriptor = {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  match: (_language, _meta) => true,
+  priority: 0,
+  Editor: ({ code, focusEmitter }) => {
+    const { parentEditor, lexicalNode, setCode } = useCodeBlockEditorContext()
+    const defaultValue = useRef(code)
+    const codeRef = useRef<HTMLElement>(null)
+
+    const handleInput = useCallback(
+      (e: React.FormEvent<HTMLElement>) => {
+        setCode(e.currentTarget.innerHTML)
+      },
+      [setCode]
+    )
+
+    useEffect(() => {
+      const handleFocus = () => {
+        if (codeRef.current) {
+          codeRef.current.focus()
+        }
+      }
+      focusEmitter.subscribe(handleFocus)
+    }, [focusEmitter])
+
+    useEffect(() => {
+      const currentRef = codeRef.current
+      const handleKeyDown = (event: KeyboardEvent) => {
+        if (event.key === 'Backspace' || event.key === 'Delete') {
+          if (codeRef.current?.textContent === '') {
+            parentEditor.update(() => {
+              lexicalNode.remove(false)
+            })
+          }
+        }
+      }
+      if (currentRef) {
+        currentRef.addEventListener('keydown', handleKeyDown)
+      }
+      return () => {
+        if (currentRef) {
+          currentRef.removeEventListener('keydown', handleKeyDown)
+        }
+      }
+    }, [lexicalNode, parentEditor])
+
+    return (
+      <pre>
+        <code
+          ref={codeRef}
+          contentEditable={true}
+          onInput={handleInput}
+          dangerouslySetInnerHTML={{ __html: defaultValue.current }}
+        />
+      </pre>
+    )
+  }
+}
diff --git a/src/components/editor/YoutubeButton.tsx b/src/components/editor/YoutubeButton.tsx
new file mode 100644
index 0000000..bada79a
--- /dev/null
+++ b/src/components/editor/YoutubeButton.tsx
@@ -0,0 +1,36 @@
+import { LeafDirective } from 'mdast-util-directive'
+import { usePublisher, insertDirective$, DialogButton } from '@mdxeditor/editor'
+
+function getId(url: string) {
+  const regExp =
+    /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/
+  const match = url.match(regExp)
+  return match && match[7].length == 11 ? match[7] : false
+}
+
+export const YouTubeButton = () => {
+  const insertDirective = usePublisher(insertDirective$)
+
+  return (
+    <DialogButton
+      tooltipTitle='Insert Youtube video'
+      submitButtonTitle='Insert video'
+      dialogInputPlaceholder='Paste the youtube video URL'
+      buttonContent='YT'
+      onSubmit={(url) => {
+        const videoId = getId(url)
+        if (videoId) {
+          insertDirective({
+            name: 'youtube',
+            type: 'leafDirective',
+
+            attributes: { id: videoId },
+            children: []
+          } as LeafDirective)
+        } else {
+          alert('Invalid YouTube URL')
+        }
+      }}
+    />
+  )
+}
diff --git a/src/components/editor/YoutubeDirectiveDescriptor.tsx b/src/components/editor/YoutubeDirectiveDescriptor.tsx
new file mode 100644
index 0000000..7d60856
--- /dev/null
+++ b/src/components/editor/YoutubeDirectiveDescriptor.tsx
@@ -0,0 +1,49 @@
+import { LeafDirective } from 'mdast-util-directive'
+import { DirectiveDescriptor } from '@mdxeditor/editor'
+
+interface YoutubeDirectiveNode extends LeafDirective {
+  name: 'youtube'
+  attributes: { id: string }
+}
+
+export const YoutubeDirectiveDescriptor: DirectiveDescriptor<YoutubeDirectiveNode> =
+  {
+    name: 'youtube',
+    type: 'leafDirective',
+    testNode(node) {
+      return node.name === 'youtube'
+    },
+    attributes: ['id'],
+    hasChildren: false,
+    Editor: ({ mdastNode, lexicalNode, parentEditor }) => {
+      return (
+        <div
+          style={{
+            display: 'flex',
+            flexDirection: 'column',
+            alignItems: 'flex-start'
+          }}
+        >
+          <button
+            type='button'
+            onClick={() => {
+              parentEditor.update(() => {
+                lexicalNode.selectNext()
+                lexicalNode.remove()
+              })
+            }}
+          >
+            delete
+          </button>
+          <iframe
+            width='560'
+            height='315'
+            src={`https://www.youtube.com/embed/${mdastNode.attributes.id}`}
+            title='YouTube video player'
+            frameBorder='0'
+            allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
+          ></iframe>
+        </div>
+      )
+    }
+  }
diff --git a/src/components/editor/index.tsx b/src/components/editor/index.tsx
new file mode 100644
index 0000000..d7a96de
--- /dev/null
+++ b/src/components/editor/index.tsx
@@ -0,0 +1,105 @@
+import {
+  BlockTypeSelect,
+  BoldItalicUnderlineToggles,
+  codeBlockPlugin,
+  CodeToggle,
+  CreateLink,
+  directivesPlugin,
+  headingsPlugin,
+  imagePlugin,
+  InsertCodeBlock,
+  InsertImage,
+  InsertTable,
+  InsertThematicBreak,
+  linkDialogPlugin,
+  linkPlugin,
+  listsPlugin,
+  ListsToggle,
+  markdownShortcutPlugin,
+  MDXEditor,
+  MDXEditorProps,
+  quotePlugin,
+  Separator,
+  StrikeThroughSupSubToggles,
+  tablePlugin,
+  thematicBreakPlugin,
+  toolbarPlugin,
+  UndoRedo
+} from '@mdxeditor/editor'
+import { PlainTextCodeEditorDescriptor } from './PlainTextCodeEditorDescriptor'
+import { YoutubeDirectiveDescriptor } from './YoutubeDirectiveDescriptor'
+import { YouTubeButton } from './YoutubeButton'
+import '@mdxeditor/editor/style.css'
+import '../../styles/mdxEditor.scss'
+
+interface EditorProps extends MDXEditorProps {}
+
+export const Editor = ({
+  readOnly,
+  markdown,
+  onChange,
+  ...rest
+}: EditorProps) => {
+  const plugins = [
+    directivesPlugin({ directiveDescriptors: [YoutubeDirectiveDescriptor] }),
+    codeBlockPlugin({
+      defaultCodeBlockLanguage: '',
+      codeBlockEditorDescriptors: [PlainTextCodeEditorDescriptor]
+    }),
+    headingsPlugin(),
+    quotePlugin(),
+    imagePlugin(),
+    tablePlugin(),
+    linkPlugin(),
+    linkDialogPlugin(),
+    listsPlugin(),
+    thematicBreakPlugin(),
+    markdownShortcutPlugin()
+  ]
+
+  if (!readOnly) {
+    plugins.push(
+      toolbarPlugin({
+        toolbarContents: () => (
+          <>
+            <UndoRedo />
+            <Separator />
+            <BoldItalicUnderlineToggles />
+            <CodeToggle />
+            <Separator />
+            <StrikeThroughSupSubToggles />
+            <Separator />
+            <ListsToggle />
+            <Separator />
+            <BlockTypeSelect />
+            <Separator />
+
+            <CreateLink />
+            <InsertImage />
+            <YouTubeButton />
+
+            <Separator />
+
+            <InsertTable />
+            <InsertThematicBreak />
+
+            <Separator />
+            <InsertCodeBlock />
+          </>
+        )
+      })
+    )
+  }
+
+  return (
+    <MDXEditor
+      contentEditableClassName='editor'
+      className='dark-theme dark-editor'
+      readOnly={readOnly}
+      markdown={markdown}
+      plugins={plugins}
+      onChange={onChange}
+      {...rest}
+    />
+  )
+}
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 0c26a57..84be825 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -5,12 +5,6 @@ import {
   useNavigation,
   useSubmit
 } from 'react-router-dom'
-import StarterKit from '@tiptap/starter-kit'
-import Link from '@tiptap/extension-link'
-import Image from '@tiptap/extension-image'
-import { EditorContent, useEditor } from '@tiptap/react'
-import DOMPurify from 'dompurify'
-import { marked } from 'marked'
 import { LoadingSpinner } from 'components/LoadingSpinner'
 import { ProfileSection } from 'components/ProfileSection'
 import { Comments } from 'components/comment'
@@ -23,6 +17,7 @@ import { copyTextToClipboard } from 'utils'
 import { toast } from 'react-toastify'
 import { useAppSelector, useBodyScrollDisable } from 'hooks'
 import { ReportPopup } from 'components/ReportPopup'
+import { Editor } from 'components/editor'
 
 const BLOG_REPORT_REASONS = [
   { label: 'Actually CP', key: 'actuallyCP' },
@@ -42,25 +37,6 @@ export const BlogPage = () => {
     userState.user.npub === import.meta.env.VITE_REPORTING_NPUB
   const navigation = useNavigation()
   const [commentCount, setCommentCount] = useState(0)
-  const html = marked.parse(blog?.content || '', { async: false })
-  const sanitized = DOMPurify.sanitize(html)
-  const editor = useEditor(
-    {
-      content: sanitized,
-      extensions: [
-        StarterKit,
-        Link,
-        Image.configure({
-          inline: true,
-          HTMLAttributes: {
-            class: 'IBMSMSMBSSPostImg'
-          }
-        })
-      ],
-      editable: false
-    },
-    [sanitized]
-  )
 
   const [showReportPopUp, setShowReportPopUp] = useState<number>()
   useBodyScrollDisable(!!showReportPopUp)
@@ -266,7 +242,12 @@ export const BlogPage = () => {
                           </h1>
                         </div>
                         <div className='IBMSMSMBSSPostBody'>
-                          <EditorContent editor={editor} />
+                          <Editor
+                            key={blog.id}
+                            markdown={blog?.content || ''}
+                            readOnly={true}
+                            spellCheck={false}
+                          />
                         </div>
                         <div className='IBMSMSMBSSTags'>
                           {blog.nsfw && (
diff --git a/src/pages/write/action.ts b/src/pages/write/action.ts
index 61bb7ad..e0f33d3 100644
--- a/src/pages/write/action.ts
+++ b/src/pages/write/action.ts
@@ -10,7 +10,6 @@ import {
   now,
   parseFormData
 } from 'utils'
-import TurndownService from 'turndown'
 import { kinds, UnsignedEvent, Event, nip19 } from 'nostr-tools'
 import { toast } from 'react-toastify'
 import { NDKEvent } from '@nostr-dev-kit/ndk'
@@ -57,9 +56,8 @@ export const writeRouteAction =
     // Return earily if there are any errors
     if (Object.keys(formErrors).length) return formErrors
 
-    // Get the markdown from the html
-    const turndownService = new TurndownService()
-    const content = turndownService.turndown(formSubmit.content!)
+    // Get the markdown from formData
+    const content = decodeURIComponent(formSubmit.content!)
 
     // Check if we are editing or this is a new blog
     const { naddr } = params
@@ -154,11 +152,7 @@ const validateFormData = async (
     errors.title = 'Title field can not be empty'
   }
 
-  if (
-    !formData.content ||
-    formData.content.trim() === '' ||
-    formData.content.trim() === '<p></p>'
-  ) {
+  if (!formData.content || formData.content.trim() === '') {
     errors.content = 'Content field can not be empty'
   }
 
diff --git a/src/pages/write/index.tsx b/src/pages/write/index.tsx
index 26544e0..1e9088d 100644
--- a/src/pages/write/index.tsx
+++ b/src/pages/write/index.tsx
@@ -8,8 +8,7 @@ import {
 import {
   CheckboxFieldUncontrolled,
   InputError,
-  InputFieldUncontrolled,
-  MenuBar
+  InputFieldUncontrolled
 } from '../../components/Inputs'
 import { ProfileSection } from '../../components/ProfileSection'
 import { useAppSelector } from '../../hooks'
@@ -18,14 +17,10 @@ import '../../styles/innerPage.css'
 import '../../styles/styles.css'
 import '../../styles/write.css'
 import { LoadingSpinner } from 'components/LoadingSpinner'
-import { marked } from 'marked'
-import DOMPurify from 'dompurify'
-import { EditorContent, useEditor } from '@tiptap/react'
-import StarterKit from '@tiptap/starter-kit'
-import Link from '@tiptap/extension-link'
-import Image from '@tiptap/extension-image'
 import { AlertPopup } from 'components/AlertPopup'
 
+import { Editor } from 'components/editor'
+
 export const WritePage = () => {
   const userState = useAppSelector((state) => state.user)
   const data = useLoaderData() as BlogPageLoaderResult
@@ -34,26 +29,7 @@ export const WritePage = () => {
 
   const blog = data?.blog
   const title = data?.blog ? 'Edit blog post' : 'Submit a blog post'
-  const html = marked.parse(blog?.content || '', { async: false })
-  const sanitized = DOMPurify.sanitize(html)
-  const [content, setContent] = useState<string>(sanitized)
-  const editor = useEditor({
-    content: content,
-    extensions: [
-      StarterKit,
-      Link,
-      Image.configure({
-        inline: true,
-        HTMLAttributes: {
-          class: 'IBMSMSMBSSPostImg'
-        }
-      })
-    ],
-    onUpdate: ({ editor }) => {
-      setContent(editor.getHTML())
-    }
-  })
-
+  const [content, setContent] = useState(blog?.content || '')
   const formRef = useRef<HTMLFormElement>(null)
   const [showConfirmPopup, setShowConfirmPopup] = useState<boolean>(false)
   const handleReset = () => {
@@ -94,19 +70,27 @@ export const WritePage = () => {
                   defaultValue={blog?.title}
                   error={formErrors?.title}
                 />
-                {editor && (
-                  <div className='inputLabelWrapperMain'>
-                    <label className='form-label labelMain'>Content</label>
-                    <div className='inputMain'>
-                      <MenuBar editor={editor} />
-                      <EditorContent editor={editor} />
-                    </div>
-                    {typeof formErrors?.content !== 'undefined' && (
-                      <InputError message={formErrors?.content} />
-                    )}
-                    <input name='content' hidden value={content} readOnly />
+                <div className='inputLabelWrapperMain'>
+                  <label className='form-label labelMain'>Content</label>
+                  <div className='inputMain'>
+                    <Editor
+                      markdown={content}
+                      onChange={(md) => {
+                        setContent(md)
+                      }}
+                    />
                   </div>
-                )}
+                  {typeof formErrors?.content !== 'undefined' && (
+                    <InputError message={formErrors?.content} />
+                  )}
+                  {/* encode to keep the markdown formatting */}
+                  <input
+                    name='content'
+                    hidden
+                    value={encodeURIComponent(content)}
+                    readOnly
+                  />
+                </div>
                 <InputFieldUncontrolled
                   label='Featured Image URL'
                   name='image'
diff --git a/src/styles/mdxEditor.scss b/src/styles/mdxEditor.scss
new file mode 100644
index 0000000..9e9384b
--- /dev/null
+++ b/src/styles/mdxEditor.scss
@@ -0,0 +1,103 @@
+.editor {
+  padding: 0;
+  --basePageBg: var(--slate-3);
+
+  > {
+    p {
+      margin: 5px 0 10px 0;
+    }
+    ul,
+    ol {
+      padding: 0 1rem;
+      margin: 1.25rem 1rem 1.25rem 0.4rem;
+
+      li p {
+        margin-top: 0.25em;
+        margin-bottom: 0.25em;
+      }
+    }
+
+    h1,
+    h2,
+    h3,
+    h4,
+    h5,
+    h6 {
+      margin: 15px 0 15px 0;
+      border-bottom: solid 1px rgb(255 255 255 / 10%);
+      padding: 0px 0 10px 0;
+      line-height: 1.5;
+      text-wrap: pretty;
+    }
+
+    h1 {
+      font-size: 1.4rem;
+    }
+
+    h2 {
+      font-size: 1.2rem;
+    }
+
+    h3 {
+      font-size: 1.1rem;
+    }
+
+    h4,
+    h5,
+    h6 {
+      font-size: 1rem;
+    }
+
+    blockquote {
+      border-radius: 0 10px 10px 0;
+      border-left: solid 6px rgba(255, 255, 255, 0.1);
+      padding: 25px;
+      background: #232323;
+      color: rgba(255, 255, 255, 0.75);
+      margin: 10px 0;
+    }
+  }
+
+  code {
+    background-color: var(--purple-light); // todo: fix the color
+    border-radius: 0.4rem;
+    color: var(--black);
+    font-size: 0.85rem;
+    padding: 0.25em 0.3em;
+    outline: none;
+
+    &:empty:before {
+      content: ' ';
+    }
+  }
+
+  pre {
+    background: var(--black); // todo: fix the color
+    color: var(--white);
+    font-family: 'JetBrainsMono', monospace;
+    margin: 1.5rem 0;
+    padding: 0.75rem 1rem;
+    background: #00000030;
+    border-radius: 5px;
+    border: solid 2px rebeccapurple;
+
+    code {
+      background: none;
+      color: inherit;
+      font-size: 0.8rem;
+      padding: 0;
+    }
+  }
+
+  img {
+    width: 100%;
+    margin: 15px 0;
+    background: #232323;
+    border-radius: 10px;
+  }
+}
+
+.mdxeditor,
+.mdxeditor-popup-container {
+  --basePageBg: var(--slate-3);
+}
-- 
2.34.1


From 52f1735d4048716911adbe57bc72726410d251d7 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Wed, 18 Dec 2024 12:48:10 +0100
Subject: [PATCH 02/12] fix(ndk): disable debug

debug mode should be only enabled locally or on proper env
---
 src/contexts/NDKContext.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/contexts/NDKContext.tsx b/src/contexts/NDKContext.tsx
index 7ff19af..853f5c3 100644
--- a/src/contexts/NDKContext.tsx
+++ b/src/contexts/NDKContext.tsx
@@ -111,7 +111,7 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
   }
 
   const ndk = useMemo(() => {
-    localStorage.setItem('debug', '*')
+    localStorage.removeItem('debug')
     const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'degmod-db' })
     dexieAdapter.locking = true
     const ndk = new NDK({
-- 
2.34.1


From 70c15dceb012e10f0b5a53c4129691c2333eeaac Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Wed, 18 Dec 2024 12:48:47 +0100
Subject: [PATCH 03/12] fix(category): show indeterminate on linked category
 parents

---
 src/components/Filters/CategoryFilterPopup.tsx | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/components/Filters/CategoryFilterPopup.tsx b/src/components/Filters/CategoryFilterPopup.tsx
index 820caa0..efb833e 100644
--- a/src/components/Filters/CategoryFilterPopup.tsx
+++ b/src/components/Filters/CategoryFilterPopup.tsx
@@ -405,10 +405,22 @@ const CategoryCheckbox: React.FC<CategoryCheckboxProps> = ({
     const anyChildCombinationSelected = childPaths.some((childPath) =>
       selectedCombinations.includes(childPath)
     )
-    setIsIndeterminate(
-      anyChildCombinationSelected && !selectedCombinations.includes(pathString)
+    const anyChildCombinationLinked = childPaths.some(
+      (childPath) =>
+        linkedHierarchy !== null && linkedHierarchy.includes(childPath)
     )
-  }, [category, name, path, selectedCombinations, selectedSingles])
+    setIsIndeterminate(
+      (anyChildCombinationSelected || anyChildCombinationLinked) &&
+        !selectedCombinations.includes(pathString)
+    )
+  }, [
+    category,
+    linkedHierarchy,
+    name,
+    path,
+    selectedCombinations,
+    selectedSingles
+  ])
 
   const handleSingleChange = () => {
     setIsSingleChecked(!isSingleChecked)
-- 
2.34.1


From 1b1aa4289a57ec44d5be858e45e895e02d209df2 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Mon, 23 Dec 2024 20:21:06 +0100
Subject: [PATCH 04/12] refactor: add viewer, swap editor, and refactor
 submitMod page

---
 src/components/Inputs.tsx                     |   2 +-
 src/components/Markdown/Editor.tsx            | 131 ++++++
 .../PlainTextCodeEditorDescriptor.tsx         |   3 +-
 src/components/Markdown/Viewer.tsx            |  13 +
 .../{editor => Markdown}/YoutubeButton.tsx    |   0
 .../YoutubeDirectiveDescriptor.tsx            |   0
 src/components/ModForm.tsx                    | 396 +++++-------------
 src/components/editor/index.tsx               | 105 -----
 src/layout/header.tsx                         |   5 +-
 src/pages/blog/index.tsx                      |   6 +-
 src/pages/mod/index.tsx                       |  37 +-
 src/pages/mod/loader.ts                       |   8 +-
 src/pages/submitMod.tsx                       |  88 ----
 src/pages/submitMod/action.ts                 | 238 +++++++++++
 src/pages/submitMod/index.tsx                 |  39 ++
 src/pages/write/index.tsx                     |  10 +-
 src/routes/index.tsx                          |  13 +-
 src/styles/mdxEditor.scss                     |  10 +-
 src/types/mod.ts                              |  16 +-
 src/utils/mod.ts                              |   1 +
 20 files changed, 596 insertions(+), 525 deletions(-)
 create mode 100644 src/components/Markdown/Editor.tsx
 rename src/components/{editor => Markdown}/PlainTextCodeEditorDescriptor.tsx (95%)
 create mode 100644 src/components/Markdown/Viewer.tsx
 rename src/components/{editor => Markdown}/YoutubeButton.tsx (100%)
 rename src/components/{editor => Markdown}/YoutubeDirectiveDescriptor.tsx (100%)
 delete mode 100644 src/components/editor/index.tsx
 delete mode 100644 src/pages/submitMod.tsx
 create mode 100644 src/pages/submitMod/action.ts
 create mode 100644 src/pages/submitMod/index.tsx

diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx
index 95b4f48..aaf8162 100644
--- a/src/components/Inputs.tsx
+++ b/src/components/Inputs.tsx
@@ -330,7 +330,7 @@ const MenuBarButton = ({
 )
 
 interface InputFieldUncontrolledProps extends React.ComponentProps<'input'> {
-  label: string
+  label: string | React.ReactElement
   description?: string
   error?: string
 }
diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx
new file mode 100644
index 0000000..727e008
--- /dev/null
+++ b/src/components/Markdown/Editor.tsx
@@ -0,0 +1,131 @@
+import {
+  BlockTypeSelect,
+  BoldItalicUnderlineToggles,
+  codeBlockPlugin,
+  CodeToggle,
+  CreateLink,
+  directivesPlugin,
+  headingsPlugin,
+  imagePlugin,
+  InsertCodeBlock,
+  InsertImage,
+  InsertTable,
+  InsertThematicBreak,
+  linkDialogPlugin,
+  linkPlugin,
+  listsPlugin,
+  ListsToggle,
+  markdownShortcutPlugin,
+  MDXEditor,
+  MDXEditorMethods,
+  MDXEditorProps,
+  quotePlugin,
+  Separator,
+  StrikeThroughSupSubToggles,
+  tablePlugin,
+  thematicBreakPlugin,
+  toolbarPlugin,
+  UndoRedo
+} from '@mdxeditor/editor'
+import { PlainTextCodeEditorDescriptor } from './PlainTextCodeEditorDescriptor'
+import { YoutubeDirectiveDescriptor } from './YoutubeDirectiveDescriptor'
+import { YouTubeButton } from './YoutubeButton'
+import '@mdxeditor/editor/style.css'
+import '../../styles/mdxEditor.scss'
+import React, {
+  forwardRef,
+  useCallback,
+  useImperativeHandle,
+  useMemo,
+  useRef
+} from 'react'
+
+export interface EditorRef {
+  setMarkdown: (md: string) => void
+}
+interface EditorProps extends MDXEditorProps {}
+/**
+ * The editor component is small wrapper (`forwardRef`) around {@link MDXEditor MDXEditor} that sets up the toolbars and plugins, and requires `markdown` and `onChange`.
+ * To reset editor markdown it's required to pass the {@link EditorRef EditorRef}.
+ *
+ * Extends {@link MDXEditorProps MDXEditorProps}
+ *
+ * **Important**: the markdown is not a state, but an _initialState_ and is not "controlled".
+ * All updates are handled with onChange and will not be reflected on markdown prop.
+ * This component should never re-render if used correctly.
+ * @see https://mdxeditor.dev/editor/docs/getting-started#basic-usage
+ */
+export const Editor = React.memo(
+  forwardRef<EditorRef, EditorProps>(({ markdown, onChange, ...rest }, ref) => {
+    const editorRef = useRef<MDXEditorMethods>(null)
+    const setMarkdown = useCallback((md: string) => {
+      editorRef.current?.setMarkdown(md)
+    }, [])
+    useImperativeHandle(ref, () => ({ setMarkdown }))
+    const plugins = useMemo(
+      () => [
+        toolbarPlugin({
+          toolbarContents: () => (
+            <>
+              <UndoRedo />
+              <Separator />
+              <BoldItalicUnderlineToggles />
+              <CodeToggle />
+              <Separator />
+              <StrikeThroughSupSubToggles />
+              <Separator />
+              <ListsToggle />
+              <Separator />
+              <BlockTypeSelect />
+              <Separator />
+
+              <CreateLink />
+              <InsertImage />
+              <YouTubeButton />
+
+              <Separator />
+
+              <InsertTable />
+              <InsertThematicBreak />
+
+              <Separator />
+              <InsertCodeBlock />
+            </>
+          )
+        }),
+        headingsPlugin(),
+        quotePlugin(),
+        imagePlugin(),
+        tablePlugin(),
+        linkPlugin(),
+        linkDialogPlugin(),
+        listsPlugin(),
+        thematicBreakPlugin(),
+        directivesPlugin({
+          directiveDescriptors: [YoutubeDirectiveDescriptor]
+        }),
+        markdownShortcutPlugin(),
+        // HACK: due to a bug with shortcut interaction shortcut for code block is disabled
+        // Editor freezes if you type in ```word and put a space in between ``` word
+        codeBlockPlugin({
+          defaultCodeBlockLanguage: '',
+          codeBlockEditorDescriptors: [PlainTextCodeEditorDescriptor]
+        })
+      ],
+      []
+    )
+
+    return (
+      <MDXEditor
+        ref={editorRef}
+        contentEditableClassName='editor'
+        className='dark-theme dark-editor'
+        markdown={markdown}
+        plugins={plugins}
+        onChange={onChange}
+        {...rest}
+      />
+    )
+  }),
+  () => true
+)
diff --git a/src/components/editor/PlainTextCodeEditorDescriptor.tsx b/src/components/Markdown/PlainTextCodeEditorDescriptor.tsx
similarity index 95%
rename from src/components/editor/PlainTextCodeEditorDescriptor.tsx
rename to src/components/Markdown/PlainTextCodeEditorDescriptor.tsx
index c6ed160..1dd48cb 100644
--- a/src/components/editor/PlainTextCodeEditorDescriptor.tsx
+++ b/src/components/Markdown/PlainTextCodeEditorDescriptor.tsx
@@ -35,7 +35,8 @@ export const PlainTextCodeEditorDescriptor: CodeBlockEditorDescriptor = {
         if (event.key === 'Backspace' || event.key === 'Delete') {
           if (codeRef.current?.textContent === '') {
             parentEditor.update(() => {
-              lexicalNode.remove(false)
+              lexicalNode.selectNext()
+              lexicalNode.remove()
             })
           }
         }
diff --git a/src/components/Markdown/Viewer.tsx b/src/components/Markdown/Viewer.tsx
new file mode 100644
index 0000000..2a22e75
--- /dev/null
+++ b/src/components/Markdown/Viewer.tsx
@@ -0,0 +1,13 @@
+import DOMPurify from 'dompurify'
+import { marked } from 'marked'
+
+interface ViewerProps {
+  markdown: string
+}
+
+export const Viewer = ({ markdown }: ViewerProps) => {
+  const html = DOMPurify.sanitize(marked.parse(markdown, { async: false }))
+  return (
+    <div className='viewer' dangerouslySetInnerHTML={{ __html: html }}></div>
+  )
+}
diff --git a/src/components/editor/YoutubeButton.tsx b/src/components/Markdown/YoutubeButton.tsx
similarity index 100%
rename from src/components/editor/YoutubeButton.tsx
rename to src/components/Markdown/YoutubeButton.tsx
diff --git a/src/components/editor/YoutubeDirectiveDescriptor.tsx b/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
similarity index 100%
rename from src/components/editor/YoutubeDirectiveDescriptor.tsx
rename to src/components/Markdown/YoutubeDirectiveDescriptor.tsx
diff --git a/src/components/ModForm.tsx b/src/components/ModForm.tsx
index 1db8b96..19d0326 100644
--- a/src/components/ModForm.tsx
+++ b/src/components/ModForm.tsx
@@ -1,5 +1,4 @@
 import _ from 'lodash'
-import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
 import React, {
   Fragment,
   useCallback,
@@ -8,80 +7,51 @@ import React, {
   useRef,
   useState
 } from 'react'
-import { useLocation, useNavigate } from 'react-router-dom'
-import { toast } from 'react-toastify'
-import { FixedSizeList } from 'react-window'
-import { v4 as uuidv4 } from 'uuid'
-import { T_TAG_VALUE } from '../constants'
-import { useAppSelector, useGames, useNDKContext } from '../hooks'
-import { appRoutes, getModPageRoute } from '../routes'
-import '../styles/styles.css'
-import { DownloadUrl, ModDetails, ModFormState } from '../types'
 import {
-  initializeFormState,
-  isReachable,
-  isValidImageUrl,
-  isValidUrl,
-  log,
-  LogType,
-  now
-} from '../utils'
+  useActionData,
+  useLoaderData,
+  useNavigation,
+  useSubmit
+} from 'react-router-dom'
+import { FixedSizeList } from 'react-window'
+import { useGames } from '../hooks'
+import '../styles/styles.css'
+import {
+  DownloadUrl,
+  FormErrors,
+  ModFormState,
+  ModPageLoaderResult
+} from '../types'
+import { initializeFormState } from '../utils'
 import { CheckboxField, InputError, InputField } from './Inputs'
-import { LoadingSpinner } from './LoadingSpinner'
-import { NDKEvent } from '@nostr-dev-kit/ndk'
 import { OriginalAuthor } from './OriginalAuthor'
 import { CategoryAutocomplete } from './CategoryAutocomplete'
 import { AlertPopup } from './AlertPopup'
-
-interface FormErrors {
-  game?: string
-  title?: string
-  body?: string
-  featuredImageUrl?: string
-  summary?: string
-  nsfw?: string
-  screenshotsUrls?: string[]
-  tags?: string
-  downloadUrls?: string[]
-  author?: string
-  originalAuthor?: string
-}
+import { Editor, EditorRef } from './Markdown/Editor'
+import TurndownService from 'turndown'
+import DOMPurify from 'dompurify'
 
 interface GameOption {
   value: string
   label: string
 }
 
-type ModFormProps = {
-  existingModData?: ModDetails
-}
-
-export const ModForm = ({ existingModData }: ModFormProps) => {
-  const location = useLocation()
-  const navigate = useNavigate()
-  const { ndk, publish } = useNDKContext()
+export const ModForm = () => {
+  const data = useLoaderData() as ModPageLoaderResult
+  const mod = data?.mod
+  const formErrors = useActionData() as FormErrors
+  const navigation = useNavigation()
+  const submit = useSubmit()
   const games = useGames()
-  const userState = useAppSelector((state) => state.user)
-
-  const [isPublishing, setIsPublishing] = useState(false)
   const [gameOptions, setGameOptions] = useState<GameOption[]>([])
   const [formState, setFormState] = useState<ModFormState>(
-    initializeFormState()
+    initializeFormState(mod)
   )
-  const [formErrors, setFormErrors] = useState<FormErrors>({})
-
-  useEffect(() => {
-    if (location.pathname === appRoutes.submitMod) {
-      // Only trigger when the pathname changes to submit-mod
-      setFormState(initializeFormState())
-    }
-  }, [location.pathname])
-
-  useEffect(() => {
-    if (existingModData) {
-      setFormState(initializeFormState(existingModData))
-    }
-  }, [existingModData])
+  const editorRef = useRef<EditorRef>(null)
+  const sanitized = DOMPurify.sanitize(formState.body)
+  const turndown = new TurndownService()
+  turndown.keep(['sup', 'sub'])
+  const markdown = turndown.turndown(sanitized)
 
   useEffect(() => {
     const options = games.map((game) => ({
@@ -188,221 +158,39 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
     if (!confirm) return
 
     // Editing
-    if (existingModData) {
+    if (mod) {
+      const initial = initializeFormState(mod)
+
+      // Reset editor
+      editorRef.current?.setMarkdown(initial.body)
+
       // Reset fields to the original existing data
-      setFormState(initializeFormState(existingModData))
+      setFormState(initial)
       return
     }
 
     // New - set form state to the initial (clear form state)
     setFormState(initializeFormState())
   }
-
-  const handlePublish = async () => {
-    setIsPublishing(true)
-
-    let hexPubkey: string
-
-    if (userState.auth && userState.user?.pubkey) {
-      hexPubkey = userState.user.pubkey as string
-    } else {
-      hexPubkey = (await window.nostr?.getPublicKey()) as string
-    }
-
-    if (!hexPubkey) {
-      toast.error('Could not get pubkey')
-      return
-    }
-
-    if (!(await validateState())) {
-      setIsPublishing(false)
-      return
-    }
-
-    const uuid = formState.dTag || uuidv4()
-    const currentTimeStamp = now()
-
-    const aTag =
-      formState.aTag || `${kinds.ClassifiedListing}:${hexPubkey}:${uuid}`
-
-    const tags = [
-      ['d', uuid],
-      ['a', aTag],
-      ['r', formState.rTag],
-      ['t', T_TAG_VALUE],
-      [
-        'published_at',
-        existingModData
-          ? existingModData.published_at.toString()
-          : currentTimeStamp.toString()
-      ],
-      ['game', formState.game],
-      ['title', formState.title],
-      ['featuredImageUrl', formState.featuredImageUrl],
-      ['summary', formState.summary],
-      ['nsfw', formState.nsfw.toString()],
-      ['repost', formState.repost.toString()],
-      ['screenshotsUrls', ...formState.screenshotsUrls],
-      ['tags', ...formState.tags.split(',')],
-      [
-        'downloadUrls',
-        ...formState.downloadUrls.map((downloadUrl) =>
-          JSON.stringify(downloadUrl)
-        )
-      ]
-    ]
-    if (formState.repost && formState.originalAuthor) {
-      tags.push(['originalAuthor', formState.originalAuthor])
-    }
-
-    // Prepend com.degmods to avoid leaking categories to 3rd party client's search
-    // Add hierarchical namespaces labels
-    if (formState.LTags.length > 0) {
-      for (let i = 0; i < formState.LTags.length; i++) {
-        tags.push(['L', `com.degmods:${formState.LTags[i]}`])
-      }
-    }
-
-    // Add category labels
-    if (formState.lTags.length > 0) {
-      for (let i = 0; i < formState.lTags.length; i++) {
-        tags.push(['l', `com.degmods:${formState.lTags[i]}`])
-      }
-    }
-
-    const unsignedEvent: UnsignedEvent = {
-      kind: kinds.ClassifiedListing,
-      created_at: currentTimeStamp,
-      pubkey: hexPubkey,
-      content: formState.body,
-      tags
-    }
-
-    const signedEvent = await window.nostr
-      ?.signEvent(unsignedEvent)
-      .then((event) => event as Event)
-      .catch((err) => {
-        toast.error('Failed to sign the event!')
-        log(true, LogType.Error, 'Failed to sign the event!', err)
-        return null
-      })
-
-    if (!signedEvent) {
-      setIsPublishing(false)
-      return
-    }
-
-    const ndkEvent = new NDKEvent(ndk, signedEvent)
-    const publishedOnRelays = await publish(ndkEvent)
-
-    // Handle cases where publishing failed or succeeded
-    if (publishedOnRelays.length === 0) {
-      toast.error('Failed to publish event on any relay')
-    } else {
-      toast.success(
-        `Event published successfully to the following relays\n\n${publishedOnRelays.join(
-          '\n'
-        )}`
-      )
-
-      const naddr = nip19.naddrEncode({
-        identifier: aTag,
-        pubkey: signedEvent.pubkey,
-        kind: signedEvent.kind,
-        relays: publishedOnRelays
-      })
-
-      navigate(getModPageRoute(naddr))
-    }
-
-    setIsPublishing(false)
-  }
-
-  const validateState = async (): Promise<boolean> => {
-    const errors: FormErrors = {}
-
-    if (formState.game === '') {
-      errors.game = 'Game field can not be empty'
-    }
-
-    if (formState.title === '') {
-      errors.title = 'Title field can not be empty'
-    }
-
-    if (formState.body === '') {
-      errors.body = 'Body field can not be empty'
-    }
-
-    if (formState.featuredImageUrl === '') {
-      errors.featuredImageUrl = 'FeaturedImageUrl field can not be empty'
-    } else if (
-      !isValidImageUrl(formState.featuredImageUrl) ||
-      !(await isReachable(formState.featuredImageUrl))
-    ) {
-      errors.featuredImageUrl =
-        'FeaturedImageUrl must be a valid and reachable image URL'
-    }
-
-    if (formState.summary === '') {
-      errors.summary = 'Summary field can not be empty'
-    }
-
-    if (formState.screenshotsUrls.length === 0) {
-      errors.screenshotsUrls = ['Required at least one screenshot url']
-    } else {
-      for (let i = 0; i < formState.screenshotsUrls.length; i++) {
-        const url = formState.screenshotsUrls[i]
-        if (
-          !isValidUrl(url) ||
-          !isValidImageUrl(url) ||
-          !(await isReachable(url))
-        ) {
-          if (!errors.screenshotsUrls)
-            errors.screenshotsUrls = Array(formState.screenshotsUrls.length)
-
-          errors.screenshotsUrls![i] =
-            'All screenshot URLs must be valid and reachable image URLs'
-        }
-      }
-    }
-
-    if (
-      formState.repost &&
-      (!formState.originalAuthor || formState.originalAuthor === '')
-    ) {
-      errors.originalAuthor = 'Original author field can not be empty'
-    }
-
-    if (formState.tags === '') {
-      errors.tags = 'Tags field can not be empty'
-    }
-
-    if (formState.downloadUrls.length === 0) {
-      errors.downloadUrls = ['Required at least one download url']
-    } else {
-      for (let i = 0; i < formState.downloadUrls.length; i++) {
-        const downloadUrl = formState.downloadUrls[i]
-        if (!isValidUrl(downloadUrl.url)) {
-          if (!errors.downloadUrls)
-            errors.downloadUrls = Array(formState.downloadUrls.length)
-
-          errors.downloadUrls![i] = 'Download url must be valid and reachable'
-        }
-      }
-    }
-
-    setFormErrors(errors)
-
-    return Object.keys(errors).length === 0
+  const handlePublish = () => {
+    submit(JSON.stringify(formState), {
+      method: mod ? 'put' : 'post',
+      encType: 'application/json'
+    })
   }
 
   return (
-    <>
-      {isPublishing && <LoadingSpinner desc='Publishing mod to relays' />}
+    <form
+      className='IBMSMSMBS_Write'
+      onSubmit={(e) => {
+        e.preventDefault()
+        handlePublish()
+      }}
+    >
       <GameDropdown
         options={gameOptions}
-        selected={formState.game}
-        error={formErrors.game}
+        selected={formState?.game}
+        error={formErrors?.game}
         onChange={handleInputChange}
       />
 
@@ -411,19 +199,32 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
         placeholder='Return the banana mod'
         name='title'
         value={formState.title}
-        error={formErrors.title}
+        error={formErrors?.title}
         onChange={handleInputChange}
       />
 
-      <InputField
-        label='Body'
-        type='richtext'
-        placeholder="Here's what this mod is all about"
-        name='body'
-        value={formState.body}
-        error={formErrors.body}
-        onChange={handleInputChange}
-      />
+      <div className='inputLabelWrapperMain'>
+        <label className='form-label labelMain'>Body</label>
+        <div className='inputMain'>
+          <Editor
+            ref={editorRef}
+            markdown={markdown}
+            placeholder="Here's what this mod is all about"
+            onChange={(md) => {
+              handleInputChange('body', md)
+            }}
+          />
+        </div>
+        {typeof formErrors?.body !== 'undefined' && (
+          <InputError message={formErrors?.body} />
+        )}
+        <input
+          name='body'
+          hidden
+          value={encodeURIComponent(formState?.body)}
+          readOnly
+        />
+      </div>
 
       <InputField
         label='Featured Image URL'
@@ -433,7 +234,7 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
         placeholder='Image URL'
         name='featuredImageUrl'
         value={formState.featuredImageUrl}
-        error={formErrors.featuredImageUrl}
+        error={formErrors?.featuredImageUrl}
         onChange={handleInputChange}
       />
       <InputField
@@ -442,7 +243,7 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
         placeholder='This is a quick description of my mod'
         name='summary'
         value={formState.summary}
-        error={formErrors.summary}
+        error={formErrors?.summary}
         onChange={handleInputChange}
       />
       <CheckboxField
@@ -472,7 +273,7 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
             placeholder="Original author's name, npub or nprofile"
             name='originalAuthor'
             value={formState.originalAuthor || ''}
-            error={formErrors.originalAuthor || ''}
+            error={formErrors?.originalAuthor}
             onChange={handleInputChange}
           />
         </>
@@ -508,16 +309,16 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
               onUrlChange={handleScreenshotUrlChange}
               onRemove={removeScreenshotUrl}
             />
-            {formErrors.screenshotsUrls &&
-              formErrors.screenshotsUrls[index] && (
-                <InputError message={formErrors.screenshotsUrls[index]} />
+            {formErrors?.screenshotsUrls &&
+              formErrors?.screenshotsUrls[index] && (
+                <InputError message={formErrors?.screenshotsUrls[index]} />
               )}
           </Fragment>
         ))}
         {formState.screenshotsUrls.length === 0 &&
-          formErrors.screenshotsUrls &&
-          formErrors.screenshotsUrls[0] && (
-            <InputError message={formErrors.screenshotsUrls[0]} />
+          formErrors?.screenshotsUrls &&
+          formErrors?.screenshotsUrls[0] && (
+            <InputError message={formErrors?.screenshotsUrls[0]} />
           )}
       </div>
       <InputField
@@ -526,7 +327,7 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
         placeholder='Tags'
         name='tags'
         value={formState.tags}
-        error={formErrors.tags}
+        error={formErrors?.tags}
         onChange={handleInputChange}
       />
       <CategoryAutocomplete
@@ -573,15 +374,15 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
               onUrlChange={handleDownloadUrlChange}
               onRemove={removeDownloadUrl}
             />
-            {formErrors.downloadUrls && formErrors.downloadUrls[index] && (
-              <InputError message={formErrors.downloadUrls[index]} />
+            {formErrors?.downloadUrls && formErrors?.downloadUrls[index] && (
+              <InputError message={formErrors?.downloadUrls[index]} />
             )}
           </Fragment>
         ))}
         {formState.downloadUrls.length === 0 &&
-          formErrors.downloadUrls &&
-          formErrors.downloadUrls[0] && (
-            <InputError message={formErrors.downloadUrls[0]} />
+          formErrors?.downloadUrls &&
+          formErrors?.downloadUrls[0] && (
+            <InputError message={formErrors?.downloadUrls[0]} />
           )}
       </div>
       <div className='IBMSMSMBS_WriteAction'>
@@ -589,17 +390,20 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
           className='btn btnMain'
           type='button'
           onClick={handleReset}
-          disabled={isPublishing}
+          disabled={
+            navigation.state === 'loading' || navigation.state === 'submitting'
+          }
         >
-          {existingModData ? 'Reset' : 'Clear fields'}
+          {mod ? 'Reset' : 'Clear fields'}
         </button>
         <button
           className='btn btnMain'
-          type='button'
-          onClick={handlePublish}
-          disabled={isPublishing}
+          type='submit'
+          disabled={
+            navigation.state === 'loading' || navigation.state === 'submitting'
+          }
         >
-          Publish
+          {navigation.state === 'submitting' ? 'Publishing...' : 'Publish'}
         </button>
       </div>
       {showConfirmPopup && (
@@ -608,13 +412,13 @@ export const ModForm = ({ existingModData }: ModFormProps) => {
           handleClose={() => setShowConfirmPopup(false)}
           header={'Are you sure?'}
           label={
-            existingModData
+            mod
               ? `Are you sure you want to clear all changes?`
               : `Are you sure you want to clear all field data?`
           }
         />
       )}
-    </>
+    </form>
   )
 }
 type DownloadUrlFieldsProps = {
diff --git a/src/components/editor/index.tsx b/src/components/editor/index.tsx
deleted file mode 100644
index d7a96de..0000000
--- a/src/components/editor/index.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import {
-  BlockTypeSelect,
-  BoldItalicUnderlineToggles,
-  codeBlockPlugin,
-  CodeToggle,
-  CreateLink,
-  directivesPlugin,
-  headingsPlugin,
-  imagePlugin,
-  InsertCodeBlock,
-  InsertImage,
-  InsertTable,
-  InsertThematicBreak,
-  linkDialogPlugin,
-  linkPlugin,
-  listsPlugin,
-  ListsToggle,
-  markdownShortcutPlugin,
-  MDXEditor,
-  MDXEditorProps,
-  quotePlugin,
-  Separator,
-  StrikeThroughSupSubToggles,
-  tablePlugin,
-  thematicBreakPlugin,
-  toolbarPlugin,
-  UndoRedo
-} from '@mdxeditor/editor'
-import { PlainTextCodeEditorDescriptor } from './PlainTextCodeEditorDescriptor'
-import { YoutubeDirectiveDescriptor } from './YoutubeDirectiveDescriptor'
-import { YouTubeButton } from './YoutubeButton'
-import '@mdxeditor/editor/style.css'
-import '../../styles/mdxEditor.scss'
-
-interface EditorProps extends MDXEditorProps {}
-
-export const Editor = ({
-  readOnly,
-  markdown,
-  onChange,
-  ...rest
-}: EditorProps) => {
-  const plugins = [
-    directivesPlugin({ directiveDescriptors: [YoutubeDirectiveDescriptor] }),
-    codeBlockPlugin({
-      defaultCodeBlockLanguage: '',
-      codeBlockEditorDescriptors: [PlainTextCodeEditorDescriptor]
-    }),
-    headingsPlugin(),
-    quotePlugin(),
-    imagePlugin(),
-    tablePlugin(),
-    linkPlugin(),
-    linkDialogPlugin(),
-    listsPlugin(),
-    thematicBreakPlugin(),
-    markdownShortcutPlugin()
-  ]
-
-  if (!readOnly) {
-    plugins.push(
-      toolbarPlugin({
-        toolbarContents: () => (
-          <>
-            <UndoRedo />
-            <Separator />
-            <BoldItalicUnderlineToggles />
-            <CodeToggle />
-            <Separator />
-            <StrikeThroughSupSubToggles />
-            <Separator />
-            <ListsToggle />
-            <Separator />
-            <BlockTypeSelect />
-            <Separator />
-
-            <CreateLink />
-            <InsertImage />
-            <YouTubeButton />
-
-            <Separator />
-
-            <InsertTable />
-            <InsertThematicBreak />
-
-            <Separator />
-            <InsertCodeBlock />
-          </>
-        )
-      })
-    )
-  }
-
-  return (
-    <MDXEditor
-      contentEditableClassName='editor'
-      className='dark-theme dark-editor'
-      readOnly={readOnly}
-      markdown={markdown}
-      plugins={plugins}
-      onChange={onChange}
-      {...rest}
-    />
-  )
-}
diff --git a/src/layout/header.tsx b/src/layout/header.tsx
index f131dc6..1953ab6 100644
--- a/src/layout/header.tsx
+++ b/src/layout/header.tsx
@@ -91,10 +91,7 @@ export const Header = () => {
         <div className={mainStyles.ContainerMain}>
           <div className={navStyles.NavMainTopInside}>
             <div className={navStyles.NMTI_Sec}>
-              <Link
-                to={appRoutes.index}
-                className={navStyles.NMTI_Sec_HomeLink}
-              >
+              <Link to={appRoutes.home} className={navStyles.NMTI_Sec_HomeLink}>
                 <div className={navStyles.NMTI_Sec_HomeLink_Logo}>
                   <img
                     className={navStyles.NMTI_Sec_HomeLink_LogoImg}
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 84be825..836ad7b 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -17,7 +17,7 @@ import { copyTextToClipboard } from 'utils'
 import { toast } from 'react-toastify'
 import { useAppSelector, useBodyScrollDisable } from 'hooks'
 import { ReportPopup } from 'components/ReportPopup'
-import { Editor } from 'components/editor'
+import { Viewer } from 'components/Markdown/Viewer'
 
 const BLOG_REPORT_REASONS = [
   { label: 'Actually CP', key: 'actuallyCP' },
@@ -242,11 +242,9 @@ export const BlogPage = () => {
                           </h1>
                         </div>
                         <div className='IBMSMSMBSSPostBody'>
-                          <Editor
+                          <Viewer
                             key={blog.id}
                             markdown={blog?.content || ''}
-                            readOnly={true}
-                            spellCheck={false}
                           />
                         </div>
                         <div className='IBMSMSMBSSTags'>
diff --git a/src/pages/mod/index.tsx b/src/pages/mod/index.tsx
index 2edec2d..a911877 100644
--- a/src/pages/mod/index.tsx
+++ b/src/pages/mod/index.tsx
@@ -1,9 +1,6 @@
-import Link from '@tiptap/extension-link'
-import { EditorContent, useEditor } from '@tiptap/react'
-import StarterKit from '@tiptap/starter-kit'
 import FsLightbox from 'fslightbox-react'
 import { nip19 } from 'nostr-tools'
-import { useRef, useState } from 'react'
+import { useEffect, useMemo, useRef, useState } from 'react'
 import {
   Link as ReactRouterLink,
   useLoaderData,
@@ -40,6 +37,9 @@ import { ReportPopup } from 'components/ReportPopup'
 import { Spinner } from 'components/Spinner'
 import { RouterLoadingSpinner } from 'components/LoadingSpinner'
 import { OriginalAuthor } from 'components/OriginalAuthor'
+import DOMPurify from 'dompurify'
+import TurndownService from 'turndown'
+import { Viewer } from 'components/Markdown/Viewer'
 
 const MOD_REPORT_REASONS = [
   { label: 'Actually CP', key: 'actuallyCP' },
@@ -448,9 +448,17 @@ const Body = ({
   repost,
   originalAuthor
 }: BodyProps) => {
+  const COLLAPSED_MAX_SIZE = 250
   const postBodyRef = useRef<HTMLDivElement>(null)
   const viewFullPostBtnRef = useRef<HTMLDivElement>(null)
 
+  const markdown = useMemo(() => {
+    const sanitized = DOMPurify.sanitize(body)
+    const turndown = new TurndownService()
+    turndown.keep(['sup', 'sub'])
+    return turndown.turndown(sanitized)
+  }, [body])
+
   const [lightBoxController, setLightBoxController] = useState({
     toggler: false,
     slide: 1
@@ -463,6 +471,14 @@ const Body = ({
     }))
   }
 
+  useEffect(() => {
+    if (postBodyRef.current) {
+      if (postBodyRef.current.scrollHeight <= COLLAPSED_MAX_SIZE) {
+        viewFullPost()
+      }
+    }
+  }, [])
+
   const viewFullPost = () => {
     if (postBodyRef.current && viewFullPostBtnRef.current) {
       postBodyRef.current.style.maxHeight = 'unset'
@@ -471,12 +487,6 @@ const Body = ({
     }
   }
 
-  const editor = useEditor({
-    content: body,
-    extensions: [StarterKit, Link],
-    editable: false
-  })
-
   return (
     <>
       <div className='IBMSMSMBSSPost'>
@@ -493,9 +503,12 @@ const Body = ({
           <div
             ref={postBodyRef}
             className='IBMSMSMBSSPostBody'
-            style={{ maxHeight: '250px', padding: '10px 18px' }}
+            style={{
+              maxHeight: `${COLLAPSED_MAX_SIZE}px`,
+              padding: '10px 18px'
+            }}
           >
-            <EditorContent editor={editor} />
+            <Viewer markdown={markdown} />
             <div ref={viewFullPostBtnRef} className='IBMSMSMBSSPostBodyHide'>
               <div className='IBMSMSMBSSPostBodyHideText'>
                 <p onClick={viewFullPost}>Read Full</p>
diff --git a/src/pages/mod/loader.ts b/src/pages/mod/loader.ts
index 22476f1..032d697 100644
--- a/src/pages/mod/loader.ts
+++ b/src/pages/mod/loader.ts
@@ -28,7 +28,7 @@ export const modRouteLoader =
     const { naddr } = params
     if (!naddr) {
       log(true, LogType.Error, 'Required naddr.')
-      return redirect(appRoutes.blogs)
+      return redirect(appRoutes.mods)
     }
 
     // Decode from naddr
@@ -42,7 +42,7 @@ export const modRouteLoader =
       pubkey = decoded.data.pubkey
     } catch (error) {
       log(true, LogType.Error, `Failed to decode naddr: ${naddr}`, error)
-      throw new Error('Failed to fetch the blog. The address might be wrong')
+      throw new Error('Failed to fetch the mod. The address might be wrong')
     }
 
     const userState = store.getState().user
@@ -80,7 +80,7 @@ export const modRouteLoader =
         latestFilter['#L'] = ['content-warning']
       }
 
-      // Parallel fetch blog event, latest events, mute, and nsfw lists
+      // Parallel fetch mod event, latest events, mute, nsfw, repost lists
       const settled = await Promise.allSettled([
         ndkContext.fetchEvent(modFilter),
         ndkContext.fetchEvents(latestFilter),
@@ -106,7 +106,7 @@ export const modRouteLoader =
         log(
           true,
           LogType.Error,
-          'Unable to fetch the blog event.',
+          'Unable to fetch the mod event.',
           fetchEventResult.reason
         )
       }
diff --git a/src/pages/submitMod.tsx b/src/pages/submitMod.tsx
deleted file mode 100644
index ba79874..0000000
--- a/src/pages/submitMod.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { NDKFilter } from '@nostr-dev-kit/ndk'
-import { nip19 } from 'nostr-tools'
-import { useState } from 'react'
-import { useLocation, useParams } from 'react-router-dom'
-import { toast } from 'react-toastify'
-import { LoadingSpinner } from '../components/LoadingSpinner'
-import { ModForm } from '../components/ModForm'
-import { ProfileSection } from '../components/ProfileSection'
-import { useAppSelector, useDidMount, useNDKContext } from '../hooks'
-import '../styles/innerPage.css'
-import '../styles/styles.css'
-import '../styles/write.css'
-import { ModDetails } from '../types'
-import { extractModData, log, LogType } from '../utils'
-
-export const SubmitModPage = () => {
-  const location = useLocation()
-  const { naddr } = useParams()
-  const { fetchEvent } = useNDKContext()
-  const [modData, setModData] = useState<ModDetails>()
-  const [isFetching, setIsFetching] = useState(false)
-
-  const userState = useAppSelector((state) => state.user)
-
-  const title = location.pathname.startsWith('/edit-mod')
-    ? 'Edit Mod'
-    : 'Submit a mod'
-
-  useDidMount(async () => {
-    if (naddr) {
-      const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
-      const { identifier, kind, pubkey } = decoded.data
-
-      const filter: NDKFilter = {
-        '#a': [identifier],
-        authors: [pubkey],
-        kinds: [kind]
-      }
-
-      setIsFetching(true)
-
-      fetchEvent(filter)
-        .then((event) => {
-          if (event) {
-            const extracted = extractModData(event)
-            setModData(extracted)
-          }
-        })
-        .catch((err) => {
-          log(
-            true,
-            LogType.Error,
-            'An error occurred in fetching mod details from relays',
-            err
-          )
-          toast.error('An error occurred in fetching mod details from relays')
-        })
-        .finally(() => {
-          setIsFetching(false)
-        })
-    }
-  })
-  return (
-    <div className='InnerBodyMain'>
-      <div className='ContainerMain'>
-        <div className='IBMSecMainGroup IBMSecMainGroupAlt'>
-          <div className='IBMSMSplitMain'>
-            <div className='IBMSMSplitMainBigSide'>
-              <div className='IBMSMTitleMain'>
-                <h2 className='IBMSMTitleMainHeading'>{title}</h2>
-              </div>
-              <div className='IBMSMSMBS_Write'>
-                {isFetching ? (
-                  <LoadingSpinner desc='Fetching mod details from relays' />
-                ) : (
-                  <ModForm existingModData={modData} />
-                )}
-              </div>
-            </div>
-            {userState.auth && userState.user?.pubkey && (
-              <ProfileSection pubkey={userState.user.pubkey as string} />
-            )}
-          </div>
-        </div>
-      </div>
-    </div>
-  )
-}
diff --git a/src/pages/submitMod/action.ts b/src/pages/submitMod/action.ts
new file mode 100644
index 0000000..a731d0c
--- /dev/null
+++ b/src/pages/submitMod/action.ts
@@ -0,0 +1,238 @@
+import { NDKEvent } from '@nostr-dev-kit/ndk'
+import { NDKContextType } from 'contexts/NDKContext'
+import { kinds, nip19, Event, UnsignedEvent } from 'nostr-tools'
+import { ActionFunctionArgs, redirect } from 'react-router-dom'
+import { toast } from 'react-toastify'
+import { getModPageRoute } from 'routes'
+import { store } from 'store'
+import { FormErrors, ModFormState } from 'types'
+import {
+  isReachable,
+  isValidImageUrl,
+  isValidUrl,
+  log,
+  LogType,
+  now
+} from 'utils'
+import { v4 as uuidv4 } from 'uuid'
+import { T_TAG_VALUE } from '../../constants'
+
+export const submitModRouteAction =
+  (ndkContext: NDKContextType) =>
+  async ({ params, request }: ActionFunctionArgs) => {
+    const userState = store.getState().user
+    let hexPubkey: string
+    if (userState.auth && userState.user?.pubkey) {
+      hexPubkey = userState.user.pubkey as string
+    } else {
+      try {
+        hexPubkey = (await window.nostr?.getPublicKey()) as string
+      } catch (error) {
+        if (error instanceof Error) {
+          log(true, LogType.Error, 'Failed to get public key.', error)
+        }
+
+        toast.error('Failed to get public key.')
+        return null
+      }
+    }
+
+    if (!hexPubkey) {
+      toast.error('Could not get pubkey')
+      return null
+    }
+
+    // Get the form data from submit request
+    try {
+      const formState = (await request.json()) as ModFormState
+
+      // Check for errors
+      const formErrors = await validateState(formState)
+
+      // Return earily if there are any errors
+      if (Object.keys(formErrors).length) return formErrors
+
+      // Check if we are editing or this is a new mob
+      const { naddr } = params
+      const isEditing = naddr && request.method === 'PUT'
+
+      const uuid = formState.dTag || uuidv4()
+      const currentTimeStamp = now()
+      const aTag =
+        formState.aTag || `${kinds.ClassifiedListing}:${hexPubkey}:${uuid}`
+
+      const tags = [
+        ['d', uuid],
+        ['a', aTag],
+        ['r', formState.rTag],
+        ['t', T_TAG_VALUE],
+        [
+          'published_at',
+          isEditing
+            ? formState.published_at.toString()
+            : currentTimeStamp.toString()
+        ],
+        ['game', formState.game],
+        ['title', formState.title],
+        ['featuredImageUrl', formState.featuredImageUrl],
+        ['summary', formState.summary],
+        ['nsfw', formState.nsfw.toString()],
+        ['repost', formState.repost.toString()],
+        ['screenshotsUrls', ...formState.screenshotsUrls],
+        ['tags', ...formState.tags.split(',')],
+        [
+          'downloadUrls',
+          ...formState.downloadUrls.map((downloadUrl) =>
+            JSON.stringify(downloadUrl)
+          )
+        ]
+      ]
+      if (formState.repost && formState.originalAuthor) {
+        tags.push(['originalAuthor', formState.originalAuthor])
+      }
+
+      // Prepend com.degmods to avoid leaking categories to 3rd party client's search
+      // Add hierarchical namespaces labels
+      if (formState.LTags.length > 0) {
+        for (let i = 0; i < formState.LTags.length; i++) {
+          tags.push(['L', `com.degmods:${formState.LTags[i]}`])
+        }
+      }
+
+      // Add category labels
+      if (formState.lTags.length > 0) {
+        for (let i = 0; i < formState.lTags.length; i++) {
+          tags.push(['l', `com.degmods:${formState.lTags[i]}`])
+        }
+      }
+
+      const unsignedEvent: UnsignedEvent = {
+        kind: kinds.ClassifiedListing,
+        created_at: currentTimeStamp,
+        pubkey: hexPubkey,
+        content: formState.body,
+        tags
+      }
+
+      const signedEvent = await window.nostr
+        ?.signEvent(unsignedEvent)
+        .then((event) => event as Event)
+        .catch((err) => {
+          toast.error('Failed to sign the event!')
+          log(true, LogType.Error, 'Failed to sign the event!', err)
+          return null
+        })
+
+      if (!signedEvent) {
+        toast.error('Failed to sign the event!')
+        return null
+      }
+
+      const ndkEvent = new NDKEvent(ndkContext.ndk, signedEvent)
+      const publishedOnRelays = await ndkContext.publish(ndkEvent)
+
+      // Handle cases where publishing failed or succeeded
+      if (publishedOnRelays.length === 0) {
+        toast.error('Failed to publish event on any relay')
+      } else {
+        toast.success(
+          `Event published successfully to the following relays\n\n${publishedOnRelays.join(
+            '\n'
+          )}`
+        )
+
+        const naddr = nip19.naddrEncode({
+          identifier: aTag,
+          pubkey: signedEvent.pubkey,
+          kind: signedEvent.kind,
+          relays: publishedOnRelays
+        })
+
+        return redirect(getModPageRoute(naddr))
+      }
+    } catch (error) {
+      log(true, LogType.Error, 'Failed to sign the event!', error)
+      toast.error('Failed to sign the event!')
+      return null
+    }
+
+    return null
+  }
+
+const validateState = async (
+  formState: Partial<ModFormState>
+): Promise<FormErrors> => {
+  const errors: FormErrors = {}
+
+  if (!formState.game || formState.game === '') {
+    errors.game = 'Game field can not be empty'
+  }
+
+  if (!formState.title || formState.title === '') {
+    errors.title = 'Title field can not be empty'
+  }
+
+  if (!formState.body || formState.body === '') {
+    errors.body = 'Body field can not be empty'
+  }
+
+  if (!formState.featuredImageUrl || formState.featuredImageUrl === '') {
+    errors.featuredImageUrl = 'FeaturedImageUrl field can not be empty'
+  } else if (
+    !isValidImageUrl(formState.featuredImageUrl) ||
+    !(await isReachable(formState.featuredImageUrl))
+  ) {
+    errors.featuredImageUrl =
+      'FeaturedImageUrl must be a valid and reachable image URL'
+  }
+
+  if (!formState.summary || formState.summary === '') {
+    errors.summary = 'Summary field can not be empty'
+  }
+
+  if (!formState.screenshotsUrls || formState.screenshotsUrls.length === 0) {
+    errors.screenshotsUrls = ['Required at least one screenshot url']
+  } else {
+    for (let i = 0; i < formState.screenshotsUrls.length; i++) {
+      const url = formState.screenshotsUrls[i]
+      if (
+        !isValidUrl(url) ||
+        !isValidImageUrl(url) ||
+        !(await isReachable(url))
+      ) {
+        if (!errors.screenshotsUrls)
+          errors.screenshotsUrls = Array(formState.screenshotsUrls.length)
+
+        errors.screenshotsUrls![i] =
+          'All screenshot URLs must be valid and reachable image URLs'
+      }
+    }
+  }
+
+  if (
+    formState.repost &&
+    (!formState.originalAuthor || formState.originalAuthor === '')
+  ) {
+    errors.originalAuthor = 'Original author field can not be empty'
+  }
+
+  if (!formState.tags || formState.tags === '') {
+    errors.tags = 'Tags field can not be empty'
+  }
+
+  if (!formState.downloadUrls || formState.downloadUrls.length === 0) {
+    errors.downloadUrls = ['Required at least one download url']
+  } else {
+    for (let i = 0; i < formState.downloadUrls.length; i++) {
+      const downloadUrl = formState.downloadUrls[i]
+      if (!isValidUrl(downloadUrl.url)) {
+        if (!errors.downloadUrls)
+          errors.downloadUrls = Array(formState.downloadUrls.length)
+
+        errors.downloadUrls![i] = 'Download url must be valid and reachable'
+      }
+    }
+  }
+
+  return errors
+}
diff --git a/src/pages/submitMod/index.tsx b/src/pages/submitMod/index.tsx
new file mode 100644
index 0000000..cfd1b66
--- /dev/null
+++ b/src/pages/submitMod/index.tsx
@@ -0,0 +1,39 @@
+import { LoadingSpinner } from 'components/LoadingSpinner'
+import { ModForm } from 'components/ModForm'
+import { ProfileSection } from 'components/ProfileSection'
+import { useAppSelector } from 'hooks'
+import { useLoaderData, useNavigation } from 'react-router-dom'
+import { ModPageLoaderResult } from 'types'
+
+export const SubmitModPage = () => {
+  const data = useLoaderData() as ModPageLoaderResult
+  const mod = data?.mod
+  const navigation = useNavigation()
+  const userState = useAppSelector((state) => state.user)
+  const title = mod ? 'Edit Mod' : 'Submit a mod'
+  return (
+    <div className='InnerBodyMain'>
+      <div className='ContainerMain'>
+        <div className='IBMSecMainGroup IBMSecMainGroupAlt'>
+          <div className='IBMSMSplitMain'>
+            <div className='IBMSMSplitMainBigSide'>
+              <div className='IBMSMTitleMain'>
+                <h2 className='IBMSMTitleMainHeading'>{title}</h2>
+              </div>
+              {navigation.state === 'loading' && (
+                <LoadingSpinner desc='Fetching mod details from relays' />
+              )}
+              {navigation.state === 'submitting' && (
+                <LoadingSpinner desc='Publishing mod to relays' />
+              )}
+              <ModForm />
+            </div>
+            {userState.auth && userState.user?.pubkey && (
+              <ProfileSection pubkey={userState.user.pubkey as string} />
+            )}
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
diff --git a/src/pages/write/index.tsx b/src/pages/write/index.tsx
index 1e9088d..2bd4bb0 100644
--- a/src/pages/write/index.tsx
+++ b/src/pages/write/index.tsx
@@ -18,8 +18,7 @@ import '../../styles/styles.css'
 import '../../styles/write.css'
 import { LoadingSpinner } from 'components/LoadingSpinner'
 import { AlertPopup } from 'components/AlertPopup'
-
-import { Editor } from 'components/editor'
+import { Editor, EditorRef } from 'components/Markdown/Editor'
 
 export const WritePage = () => {
   const userState = useAppSelector((state) => state.user)
@@ -31,6 +30,7 @@ export const WritePage = () => {
   const title = data?.blog ? 'Edit blog post' : 'Submit a blog post'
   const [content, setContent] = useState(blog?.content || '')
   const formRef = useRef<HTMLFormElement>(null)
+  const editorRef = useRef<EditorRef>(null)
   const [showConfirmPopup, setShowConfirmPopup] = useState<boolean>(false)
   const handleReset = () => {
     setShowConfirmPopup(true)
@@ -41,6 +41,11 @@ export const WritePage = () => {
     // Cancel if not confirmed
     if (!confirm) return
 
+    // Reset editor
+    if (blog?.content) {
+      editorRef.current?.setMarkdown(blog?.content)
+    }
+
     formRef.current?.reset()
   }
 
@@ -74,6 +79,7 @@ export const WritePage = () => {
                   <label className='form-label labelMain'>Content</label>
                   <div className='inputMain'>
                     <Editor
+                      ref={editorRef}
                       markdown={content}
                       onChange={(md) => {
                         setContent(md)
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 54bdc0a..bee8638 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -16,6 +16,7 @@ import { profileRouteLoader } from 'pages/profile/loader'
 import { SettingsPage } from '../pages/settings'
 import { GamePage } from '../pages/game'
 import { NotFoundPage } from '../pages/404'
+import { submitModRouteAction } from 'pages/submitMod/action'
 import { FeedLayout } from '../layout/feed'
 import { FeedPage } from '../pages/feed'
 import { NotificationsPage } from '../pages/notifications'
@@ -29,7 +30,6 @@ import { blogRouteAction } from '../pages/blog/action'
 import { reportRouteAction } from '../actions/report'
 
 export const appRoutes = {
-  index: '/',
   home: '/',
   games: '/games',
   game: '/game/:name',
@@ -75,7 +75,7 @@ export const routerWithNdkContext = (context: NDKContextType) =>
       element: <Layout />,
       children: [
         {
-          path: appRoutes.index,
+          path: appRoutes.home,
           element: <HomePage />
         },
         {
@@ -131,11 +131,16 @@ export const routerWithNdkContext = (context: NDKContextType) =>
         },
         {
           path: appRoutes.submitMod,
-          element: <SubmitModPage key='submit' />
+          action: submitModRouteAction(context),
+          element: <SubmitModPage key='submit' />,
+          errorElement: <NotFoundPage title={'Something went wrong.'} />
         },
         {
           path: appRoutes.editMod,
-          element: <SubmitModPage key='edit' />
+          loader: modRouteLoader(context),
+          action: submitModRouteAction(context),
+          element: <SubmitModPage key='edit' />,
+          errorElement: <NotFoundPage title={'Something went wrong.'} />
         },
         {
           path: appRoutes.write,
diff --git a/src/styles/mdxEditor.scss b/src/styles/mdxEditor.scss
index 9e9384b..4cc4a2a 100644
--- a/src/styles/mdxEditor.scss
+++ b/src/styles/mdxEditor.scss
@@ -1,6 +1,6 @@
-.editor {
+.editor,
+.viewer {
   padding: 0;
-  --basePageBg: var(--slate-3);
 
   > {
     p {
@@ -96,7 +96,11 @@
     border-radius: 10px;
   }
 }
-
+.editor {
+  --basePageBg: var(--slate-3);
+  padding-top: 10px;
+  min-height: 75px;
+}
 .mdxeditor,
 .mdxeditor-popup-container {
   --basePageBg: var(--slate-3);
diff --git a/src/types/mod.ts b/src/types/mod.ts
index c520ac3..799ef64 100644
--- a/src/types/mod.ts
+++ b/src/types/mod.ts
@@ -36,6 +36,7 @@ export interface ModFormState {
   /** Category labels for category search */
   lTags: string[]
   downloadUrls: DownloadUrl[]
+  published_at: number
 }
 
 export interface DownloadUrl {
@@ -49,7 +50,6 @@ export interface DownloadUrl {
 
 export interface ModDetails extends Omit<ModFormState, 'tags'> {
   id: string
-  published_at: number
   edited_at: number
   author: string
   tags: string[]
@@ -67,3 +67,17 @@ export interface ModPageLoaderResult {
   isBlocked: boolean
   isRepost: boolean
 }
+
+export interface FormErrors {
+  game?: string
+  title?: string
+  body?: string
+  featuredImageUrl?: string
+  summary?: string
+  nsfw?: string
+  screenshotsUrls?: string[]
+  tags?: string
+  downloadUrls?: string[]
+  author?: string
+  originalAuthor?: string
+}
diff --git a/src/utils/mod.ts b/src/utils/mod.ts
index 07f34a0..71ceb39 100644
--- a/src/utils/mod.ts
+++ b/src/utils/mod.ts
@@ -119,6 +119,7 @@ export const initializeFormState = (
 ): ModFormState => ({
   dTag: existingModData?.dTag || '',
   aTag: existingModData?.aTag || '',
+  published_at: existingModData?.published_at || 0,
   rTag: existingModData?.rTag || window.location.host,
   game: existingModData?.game || '',
   title: existingModData?.title || '',
-- 
2.34.1


From 3080b376aa5ab3f0dbf9054b01212cda3893aa5d Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Mon, 23 Dec 2024 20:24:11 +0100
Subject: [PATCH 05/12] refactor: remove tiptap

---
 package-lock.json         | 711 +-------------------------------------
 package.json              |   5 -
 src/components/Inputs.tsx | 222 +-----------
 src/styles/tiptap.scss    | 104 ------
 4 files changed, 4 insertions(+), 1038 deletions(-)
 delete mode 100644 src/styles/tiptap.scss

diff --git a/package-lock.json b/package-lock.json
index 1171a49..659a347 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,11 +13,6 @@
         "@nostr-dev-kit/ndk": "2.10.0",
         "@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
         "@reduxjs/toolkit": "2.2.6",
-        "@tiptap/core": "2.9.1",
-        "@tiptap/extension-image": "^2.9.1",
-        "@tiptap/extension-link": "2.9.1",
-        "@tiptap/react": "2.9.1",
-        "@tiptap/starter-kit": "2.9.1",
         "@types/react-helmet": "^6.1.11",
         "axios": "^1.7.9",
         "bech32": "2.0.0",
@@ -2183,16 +2178,6 @@
       "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
       "license": "MIT"
     },
-    "node_modules/@popperjs/core": {
-      "version": "2.11.8",
-      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
-      "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
-      "license": "MIT",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/popperjs"
-      }
-    },
     "node_modules/@radix-ui/colors": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz",
@@ -2998,12 +2983,6 @@
         }
       }
     },
-    "node_modules/@remirror/core-constants": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
-      "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
-      "license": "MIT"
-    },
     "node_modules/@remix-run/router": {
       "version": "1.17.1",
       "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.1.tgz",
@@ -3361,426 +3340,6 @@
       "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==",
       "license": "MIT"
     },
-    "node_modules/@tiptap/core": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz",
-      "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-blockquote": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.9.1.tgz",
-      "integrity": "sha512-Y0jZxc/pdkvcsftmEZFyG+73um8xrx6/DMfgUcNg3JAM63CISedNcr+OEI11L0oFk1KFT7/aQ9996GM6Kubdqg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-bold": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.9.1.tgz",
-      "integrity": "sha512-e2P1zGpnnt4+TyxTC5pX/lPxPasZcuHCYXY0iwQ3bf8qRQQEjDfj3X7EI+cXqILtnhOiviEOcYmeu5op2WhQDg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-bubble-menu": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.9.1.tgz",
-      "integrity": "sha512-DWUF6NG08/bZDWw0jCeotSTvpkyqZTi4meJPomG9Wzs/Ol7mEwlNCsCViD999g0+IjyXFatBk4DfUq1YDDu++Q==",
-      "license": "MIT",
-      "dependencies": {
-        "tippy.js": "^6.3.7"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-bullet-list": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.9.1.tgz",
-      "integrity": "sha512-0hizL/0j9PragJObjAWUVSuGhN1jKjCFnhLQVRxtx4HutcvS/lhoWMvFg6ZF8xqWgIa06n6A7MaknQkqhTdhKA==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-code": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.9.1.tgz",
-      "integrity": "sha512-WQqcVGe7i/E+yO3wz5XQteU1ETNZ00euUEl4ylVVmH2NM4Dh0KDjEhbhHlCM0iCfLUo7jhjC7dmS+hMdPUb+Tg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-code-block": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.9.1.tgz",
-      "integrity": "sha512-A/50wPWDqEUUUPhrwRKILP5gXMO5UlQ0F6uBRGYB9CEVOREam9yIgvONOnZVJtszHqOayjIVMXbH/JMBeq11/g==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-document": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.9.1.tgz",
-      "integrity": "sha512-1a+HCoDPnBttjqExfYLwfABq8MYdiowhy/wp8eCxVb6KGFEENO53KapstISvPzqH7eOi+qRjBB1KtVYb/ZXicg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-dropcursor": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.9.1.tgz",
-      "integrity": "sha512-wJZspSmJRkDBtPkzFz1g7gvZOEOayk8s93UHsgbJxcV4VWHYleZ5XhT74sZunSjefNDm3qC6v2BSgLp3vNHVKQ==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-floating-menu": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.9.1.tgz",
-      "integrity": "sha512-MxZ7acNNsoNaKpetxfwi3Z11Bgrh0T2EJlCV77v9N1vWK38+st3H1WJanmLbPNtc2ocvhHJrz+DjDz3CWxQ9rQ==",
-      "license": "MIT",
-      "dependencies": {
-        "tippy.js": "^6.3.7"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-gapcursor": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.9.1.tgz",
-      "integrity": "sha512-jsRBmX01vr+5H02GljiHMo0n5H1vzoMLmFarxe0Yq2d2l9G/WV2VWX2XnGliqZAYWd1bI0phs7uLQIN3mxGQTw==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-hard-break": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.9.1.tgz",
-      "integrity": "sha512-fCuaOD/b7nDjm47PZ58oanq7y4ccS2wjPh42Qm0B0yipu/1fmC8eS1SmaXmk28F89BLtuL6uOCtR1spe+lZtlQ==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-heading": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.9.1.tgz",
-      "integrity": "sha512-SjZowzLixOFaCrV2cMaWi1mp8REK0zK1b3OcVx7bCZfVSmsOETJyrAIUpCKA8o60NwF7pwhBg0MN8oXlNKMeFw==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-history": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.9.1.tgz",
-      "integrity": "sha512-wp9qR1NM+LpvyLZFmdNaAkDq0d4jDJ7z7Fz7icFQPu31NVxfQYO3IXNmvJDCNu8hFAbImpA5aG8MBuwzRo0H9w==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-horizontal-rule": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.9.1.tgz",
-      "integrity": "sha512-ydUhABeaBI1CoJp+/BBqPhXINfesp1qMNL/jiDcMsB66fsD4nOyphpAJT7FaRFZFtQVF06+nttBtFZVkITQVqg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-image": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.9.1.tgz",
-      "integrity": "sha512-aGqJnsuS8oagIhsx7wetm8jw4NEDsOV0OSx4FQ4VPlUqWlnzK0N+erFKKJmXTdAxL8PGzoPSlITFH63MV3eV3Q==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-italic": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.9.1.tgz",
-      "integrity": "sha512-VkNA6Vz96+/+7uBlsgM7bDXXx4b62T1fDam/3UKifA72aD/fZckeWrbT7KrtdUbzuIniJSbA0lpTs5FY29+86Q==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-link": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.9.1.tgz",
-      "integrity": "sha512-yG+e3e8cCCN9dZjX4ttEe3e2xhh58ryi3REJV4MdiEkOT9QF75Bl5pUbMIS4tQ8HkOr04QBFMHKM12kbSxg1BA==",
-      "license": "MIT",
-      "dependencies": {
-        "linkifyjs": "^4.1.0"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-list-item": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.9.1.tgz",
-      "integrity": "sha512-6O4NtYNR5N2Txi4AC0/4xMRJq9xd4+7ShxCZCDVL0WDVX37IhaqMO7LGQtA6MVlYyNaX4W1swfdJaqrJJ5HIUw==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-ordered-list": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.9.1.tgz",
-      "integrity": "sha512-6J9jtv1XP8dW7/JNSH/K4yiOABc92tBJtgCsgP8Ep4+fjfjdj4HbjS1oSPWpgItucF2Fp/VF8qg55HXhjxHjTw==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-paragraph": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.9.1.tgz",
-      "integrity": "sha512-JOmT0xd4gd3lIhLwrsjw8lV+ZFROKZdIxLi0Ia05XSu4RLrrvWj0zdKMSB+V87xOWfSB3Epo95zAvnPox5Q16A==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-strike": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.9.1.tgz",
-      "integrity": "sha512-V5aEXdML+YojlPhastcu7w4biDPwmzy/fWq0T2qjfu5Te/THcqDmGYVBKESBm5x6nBy5OLkanw2O+KHu2quDdg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-text": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.9.1.tgz",
-      "integrity": "sha512-3wo9uCrkLVLQFgbw2eFU37QAa1jq1/7oExa+FF/DVxdtHRS9E2rnUZ8s2hat/IWzvPUHXMwo3Zg2XfhoamQpCA==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/extension-text-style": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.9.1.tgz",
-      "integrity": "sha512-LAxc0SeeiPiAVBwksczeA7BJSZb6WtVpYhy5Esvy9K0mK5kttB4KxtnXWeQzMIJZQbza65yftGKfQlexf/Y7yg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0"
-      }
-    },
-    "node_modules/@tiptap/pm": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz",
-      "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-changeset": "^2.2.1",
-        "prosemirror-collab": "^1.3.1",
-        "prosemirror-commands": "^1.6.0",
-        "prosemirror-dropcursor": "^1.8.1",
-        "prosemirror-gapcursor": "^1.3.2",
-        "prosemirror-history": "^1.4.1",
-        "prosemirror-inputrules": "^1.4.0",
-        "prosemirror-keymap": "^1.2.2",
-        "prosemirror-markdown": "^1.13.0",
-        "prosemirror-menu": "^1.2.4",
-        "prosemirror-model": "^1.22.3",
-        "prosemirror-schema-basic": "^1.2.3",
-        "prosemirror-schema-list": "^1.4.1",
-        "prosemirror-state": "^1.4.3",
-        "prosemirror-tables": "^1.4.0",
-        "prosemirror-trailing-node": "^3.0.0",
-        "prosemirror-transform": "^1.10.0",
-        "prosemirror-view": "^1.34.3"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      }
-    },
-    "node_modules/@tiptap/react": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.9.1.tgz",
-      "integrity": "sha512-LQJ34ZPfXtJF36SZdcn4Fiwsl2WxZ9YRJI87OLnsjJ45O+gV/PfBzz/4ap+LF8LOS0AbbGhTTjBOelPoNm+aYA==",
-      "license": "MIT",
-      "dependencies": {
-        "@tiptap/extension-bubble-menu": "^2.9.1",
-        "@tiptap/extension-floating-menu": "^2.9.1",
-        "@types/use-sync-external-store": "^0.0.6",
-        "fast-deep-equal": "^3",
-        "use-sync-external-store": "^1.2.2"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      },
-      "peerDependencies": {
-        "@tiptap/core": "^2.7.0",
-        "@tiptap/pm": "^2.7.0",
-        "react": "^17.0.0 || ^18.0.0",
-        "react-dom": "^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/@tiptap/react/node_modules/@types/use-sync-external-store": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
-      "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
-    },
-    "node_modules/@tiptap/starter-kit": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.9.1.tgz",
-      "integrity": "sha512-nsw6UF/7wDpPfHRhtGOwkj1ipIEiWZS1VGw+c14K61vM1CNj0uQ4jogbHwHZqN1dlL5Hh+FCqUHDPxG6ECbijg==",
-      "license": "MIT",
-      "dependencies": {
-        "@tiptap/core": "^2.9.1",
-        "@tiptap/extension-blockquote": "^2.9.1",
-        "@tiptap/extension-bold": "^2.9.1",
-        "@tiptap/extension-bullet-list": "^2.9.1",
-        "@tiptap/extension-code": "^2.9.1",
-        "@tiptap/extension-code-block": "^2.9.1",
-        "@tiptap/extension-document": "^2.9.1",
-        "@tiptap/extension-dropcursor": "^2.9.1",
-        "@tiptap/extension-gapcursor": "^2.9.1",
-        "@tiptap/extension-hard-break": "^2.9.1",
-        "@tiptap/extension-heading": "^2.9.1",
-        "@tiptap/extension-history": "^2.9.1",
-        "@tiptap/extension-horizontal-rule": "^2.9.1",
-        "@tiptap/extension-italic": "^2.9.1",
-        "@tiptap/extension-list-item": "^2.9.1",
-        "@tiptap/extension-ordered-list": "^2.9.1",
-        "@tiptap/extension-paragraph": "^2.9.1",
-        "@tiptap/extension-strike": "^2.9.1",
-        "@tiptap/extension-text": "^2.9.1",
-        "@tiptap/extension-text-style": "^2.9.1",
-        "@tiptap/pm": "^2.9.1"
-      },
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/ueberdosis"
-      }
-    },
     "node_modules/@tsconfig/node10": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -4961,17 +4520,6 @@
       "integrity": "sha512-4h+oPeAiGQOHFyUJOqpoEcPj/xxlicxBzOErVeYVMMmAiXUXsGpsFd0QXBMaUUbnD8hhSfLf9uw+MlsoIA7j5w==",
       "dev": true
     },
-    "node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
     "node_modules/es5-ext": {
       "version": "0.10.64",
       "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
@@ -5411,7 +4959,8 @@
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
     },
     "node_modules/fast-glob": {
       "version": "3.3.2",
@@ -6101,19 +5650,6 @@
         }
       ]
     },
-    "node_modules/linkify-it": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
-      "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
-      "dependencies": {
-        "uc.micro": "^2.0.0"
-      }
-    },
-    "node_modules/linkifyjs": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz",
-      "integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg=="
-    },
     "node_modules/locate-path": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -6185,22 +5721,6 @@
       "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
       "dev": true
     },
-    "node_modules/markdown-it": {
-      "version": "14.1.0",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
-      "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
-      "dependencies": {
-        "argparse": "^2.0.1",
-        "entities": "^4.4.0",
-        "linkify-it": "^5.0.0",
-        "mdurl": "^2.0.0",
-        "punycode.js": "^2.3.1",
-        "uc.micro": "^2.1.0"
-      },
-      "bin": {
-        "markdown-it": "bin/markdown-it.mjs"
-      }
-    },
     "node_modules/markdown-table": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
@@ -6470,11 +5990,6 @@
         "url": "https://opencollective.com/unified"
       }
     },
-    "node_modules/mdurl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
-      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
-    },
     "node_modules/memoize-one": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -7496,11 +7011,6 @@
         "node": ">= 0.8.0"
       }
     },
-    "node_modules/orderedmap": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
-      "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
-    },
     "node_modules/outvariant": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz",
@@ -7710,196 +7220,6 @@
         "react-is": "^16.13.1"
       }
     },
-    "node_modules/prosemirror-changeset": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz",
-      "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==",
-      "dependencies": {
-        "prosemirror-transform": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-collab": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
-      "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-commands": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz",
-      "integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==",
-      "dependencies": {
-        "prosemirror-model": "^1.0.0",
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-dropcursor": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz",
-      "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.1.0",
-        "prosemirror-view": "^1.1.0"
-      }
-    },
-    "node_modules/prosemirror-gapcursor": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz",
-      "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==",
-      "dependencies": {
-        "prosemirror-keymap": "^1.0.0",
-        "prosemirror-model": "^1.0.0",
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-view": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-history": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz",
-      "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==",
-      "dependencies": {
-        "prosemirror-state": "^1.2.2",
-        "prosemirror-transform": "^1.0.0",
-        "prosemirror-view": "^1.31.0",
-        "rope-sequence": "^1.3.0"
-      }
-    },
-    "node_modules/prosemirror-inputrules": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
-      "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-keymap": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz",
-      "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0",
-        "w3c-keyname": "^2.2.0"
-      }
-    },
-    "node_modules/prosemirror-markdown": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.0.tgz",
-      "integrity": "sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==",
-      "dependencies": {
-        "markdown-it": "^14.0.0",
-        "prosemirror-model": "^1.20.0"
-      }
-    },
-    "node_modules/prosemirror-menu": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz",
-      "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==",
-      "dependencies": {
-        "crelt": "^1.0.0",
-        "prosemirror-commands": "^1.0.0",
-        "prosemirror-history": "^1.0.0",
-        "prosemirror-state": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-model": {
-      "version": "1.22.3",
-      "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz",
-      "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==",
-      "dependencies": {
-        "orderedmap": "^2.0.0"
-      }
-    },
-    "node_modules/prosemirror-schema-basic": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz",
-      "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==",
-      "dependencies": {
-        "prosemirror-model": "^1.19.0"
-      }
-    },
-    "node_modules/prosemirror-schema-list": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz",
-      "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==",
-      "dependencies": {
-        "prosemirror-model": "^1.0.0",
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.7.3"
-      }
-    },
-    "node_modules/prosemirror-state": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
-      "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
-      "dependencies": {
-        "prosemirror-model": "^1.0.0",
-        "prosemirror-transform": "^1.0.0",
-        "prosemirror-view": "^1.27.0"
-      }
-    },
-    "node_modules/prosemirror-tables": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.5.0.tgz",
-      "integrity": "sha512-VMx4zlYWm7aBlZ5xtfJHpqa3Xgu3b7srV54fXYnXgsAcIGRqKSrhiK3f89omzzgaAgAtDOV4ImXnLKhVfheVNQ==",
-      "dependencies": {
-        "prosemirror-keymap": "^1.1.2",
-        "prosemirror-model": "^1.8.1",
-        "prosemirror-state": "^1.3.1",
-        "prosemirror-transform": "^1.2.1",
-        "prosemirror-view": "^1.13.3"
-      }
-    },
-    "node_modules/prosemirror-trailing-node": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
-      "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@remirror/core-constants": "3.0.0",
-        "escape-string-regexp": "^4.0.0"
-      },
-      "peerDependencies": {
-        "prosemirror-model": "^1.22.1",
-        "prosemirror-state": "^1.4.2",
-        "prosemirror-view": "^1.33.8"
-      }
-    },
-    "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/prosemirror-transform": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.0.tgz",
-      "integrity": "sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==",
-      "dependencies": {
-        "prosemirror-model": "^1.21.0"
-      }
-    },
-    "node_modules/prosemirror-view": {
-      "version": "1.35.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.35.0.tgz",
-      "integrity": "sha512-Umtbh22fmUlpZpRTiOVXA0PpdRZeYEeXQsLp51VfnMhjkJrqJ0n8APinIZrRAD5Jr3UxH8FnOaUqRylSuMsqHA==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-model": "^1.20.0",
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.1.0"
-      }
-    },
     "node_modules/proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -7914,14 +7234,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/punycode.js": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
-      "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/qrcode.react": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-3.1.0.tgz",
@@ -8328,11 +7640,6 @@
         "fsevents": "~2.3.2"
       }
     },
-    "node_modules/rope-sequence": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
-      "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="
-    },
     "node_modules/run-parallel": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -8531,15 +7838,6 @@
       "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
       "dev": true
     },
-    "node_modules/tippy.js": {
-      "version": "6.3.7",
-      "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
-      "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@popperjs/core": "^2.9.0"
-      }
-    },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -8727,11 +8025,6 @@
       "resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz",
       "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA=="
     },
-    "node_modules/uc.micro": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
-      "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
-    },
     "node_modules/undici-types": {
       "version": "5.26.5",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
diff --git a/package.json b/package.json
index 828f6be..542e4a1 100644
--- a/package.json
+++ b/package.json
@@ -15,11 +15,6 @@
     "@nostr-dev-kit/ndk": "2.10.0",
     "@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
     "@reduxjs/toolkit": "2.2.6",
-    "@tiptap/core": "2.9.1",
-    "@tiptap/extension-image": "^2.9.1",
-    "@tiptap/extension-link": "2.9.1",
-    "@tiptap/react": "2.9.1",
-    "@tiptap/starter-kit": "2.9.1",
     "@types/react-helmet": "^6.1.11",
     "axios": "^1.7.9",
     "bech32": "2.0.0",
diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx
index aaf8162..530fe53 100644
--- a/src/components/Inputs.tsx
+++ b/src/components/Inputs.tsx
@@ -1,15 +1,10 @@
-import Link from '@tiptap/extension-link'
-import Image from '@tiptap/extension-image'
-import { Editor, EditorContent, useEditor } from '@tiptap/react'
-import StarterKit from '@tiptap/starter-kit'
-import React, { useEffect } from 'react'
+import React from 'react'
 import '../styles/styles.css'
-import '../styles/tiptap.scss'
 
 interface InputFieldProps {
   label: string | React.ReactElement
   description?: string
-  type?: 'text' | 'textarea' | 'richtext'
+  type?: 'text' | 'textarea'
   placeholder: string
   name: string
   inputMode?: 'url'
@@ -48,11 +43,6 @@ export const InputField = React.memo(
             value={value}
             onChange={handleChange}
           ></textarea>
-        ) : type === 'richtext' ? (
-          <RichTextEditor
-            content={value}
-            updateContent={(content) => onChange(name, content)}
-          />
         ) : (
           <input
             type={type}
@@ -121,214 +111,6 @@ export const CheckboxField = React.memo(
   )
 )
 
-type RichTextEditorProps = {
-  content: string
-  updateContent: (updatedContent: string) => void
-}
-
-const RichTextEditor = ({ content, updateContent }: RichTextEditorProps) => {
-  const editor = useEditor({
-    extensions: [
-      StarterKit,
-      Link,
-      Image.configure({
-        HTMLAttributes: {
-          class: 'IBMSMSMBSSPostImg'
-        }
-      })
-    ],
-    onUpdate: ({ editor }) => {
-      // Update the state when the editor content changes
-      updateContent(editor.getHTML())
-    },
-    content
-  })
-
-  // Update editor content when the `content` prop changes
-  useEffect(() => {
-    if (editor && editor.getHTML() !== content) {
-      editor.commands.setContent(content, false)
-    }
-  }, [content, editor])
-
-  return (
-    <div className='inputMain'>
-      {editor && (
-        <>
-          <MenuBar editor={editor} />
-          <EditorContent editor={editor} />
-        </>
-      )}
-    </div>
-  )
-}
-
-type MenuBarProps = {
-  editor: Editor
-}
-
-export const MenuBar = ({ editor }: MenuBarProps) => {
-  const setLink = () => {
-    // Prompt the user to enter a URL
-    let url = prompt('URL')
-
-    // Check if the user provided a URL
-    if (url) {
-      // If the URL doesn't start with 'http://' or 'https://',
-      // prepend 'https://' to the URL
-      if (!/^(http|https):\/\//i.test(url)) {
-        url = `https://${url}`
-      }
-
-      return editor.chain().focus().setLink({ href: url }).run()
-    }
-
-    // If no URL was provided (e.g., the user cancels the prompt),
-    // return false, indicating that the link was not set.
-    return false
-  }
-
-  const unsetLink = () => editor.chain().focus().unsetLink().run()
-
-  const setImage = () => {
-    let url = prompt('URL')
-    if (url) {
-      if (!/^(http|https):\/\//i.test(url)) {
-        url = `https://${url}`
-      }
-      return editor.chain().focus().setImage({ src: url }).run()
-    }
-    return false
-  }
-
-  const buttons: MenuBarButtonProps[] = [
-    {
-      label: 'Bold',
-      disabled: !editor.can().chain().focus().toggleBold().run(),
-      isActive: editor.isActive('bold'),
-      onClick: () => editor.chain().focus().toggleBold().run()
-    },
-    {
-      label: 'Italic',
-      disabled: !editor.can().chain().focus().toggleItalic().run(),
-      isActive: editor.isActive('italic'),
-      onClick: () => editor.chain().focus().toggleItalic().run()
-    },
-    {
-      label: 'Strike',
-      disabled: !editor.can().chain().focus().toggleStrike().run(),
-      isActive: editor.isActive('strike'),
-      onClick: () => editor.chain().focus().toggleStrike().run()
-    },
-    {
-      label: 'Clear marks',
-      onClick: () => editor.chain().focus().unsetAllMarks().run()
-    },
-    {
-      label: 'Clear nodes',
-      onClick: () => editor.chain().focus().clearNodes().run()
-    },
-    {
-      label: 'Paragraph',
-      isActive: editor.isActive('paragraph'),
-      onClick: () => editor.chain().focus().setParagraph().run()
-    },
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    ...[1, 2, 3, 4, 5, 6].map((level: any) => ({
-      label: `H${level}`,
-      isActive: editor.isActive('heading', { level }),
-      onClick: () => editor.chain().focus().toggleHeading({ level }).run()
-    })),
-    {
-      label: 'Bullet list',
-      isActive: editor.isActive('bulletList'),
-      onClick: () => editor.chain().focus().toggleBulletList().run()
-    },
-    {
-      label: 'Ordered list',
-      isActive: editor.isActive('orderedList'),
-      onClick: () => editor.chain().focus().toggleOrderedList().run()
-    },
-    {
-      label: 'Code block',
-      isActive: editor.isActive('codeBlock'),
-      onClick: () => editor.chain().focus().toggleCodeBlock().run()
-    },
-    {
-      label: 'Blockquote',
-      isActive: editor.isActive('blockquote'),
-      onClick: () => editor.chain().focus().toggleBlockquote().run()
-    },
-    {
-      label: 'Link',
-      isActive: editor.isActive('link'),
-      onClick: editor.isActive('link') ? unsetLink : setLink
-    },
-    {
-      label: 'Image',
-      isActive: editor.isActive('image'),
-      onClick: setImage
-    },
-    {
-      label: 'Horizontal rule',
-      onClick: () => editor.chain().focus().setHorizontalRule().run()
-    },
-    {
-      label: 'Hard break',
-      onClick: () => editor.chain().focus().setHardBreak().run()
-    },
-    {
-      label: 'Undo',
-      disabled: !editor.can().chain().focus().undo().run(),
-      onClick: () => editor.chain().focus().undo().run()
-    },
-    {
-      label: 'Redo',
-      disabled: !editor.can().chain().focus().redo().run(),
-      onClick: () => editor.chain().focus().redo().run()
-    }
-  ]
-
-  return (
-    <div className='control-group'>
-      <div className='button-group'>
-        {buttons.map(({ label, disabled, isActive, onClick }) => (
-          <MenuBarButton
-            key={label}
-            label={label}
-            disabled={disabled}
-            isActive={isActive}
-            onClick={onClick}
-          />
-        ))}
-      </div>
-    </div>
-  )
-}
-
-interface MenuBarButtonProps {
-  label: string
-  isActive?: boolean
-  disabled?: boolean
-  onClick: () => boolean
-}
-
-const MenuBarButton = ({
-  label,
-  isActive = false,
-  disabled = false,
-  onClick
-}: MenuBarButtonProps) => (
-  <button
-    onClick={onClick}
-    disabled={disabled}
-    className={`btn btnMain btnMainTipTap ${isActive ? 'is-active' : ''}`}
-    type='button'
-  >
-    {label}
-  </button>
-)
-
 interface InputFieldUncontrolledProps extends React.ComponentProps<'input'> {
   label: string | React.ReactElement
   description?: string
diff --git a/src/styles/tiptap.scss b/src/styles/tiptap.scss
deleted file mode 100644
index 91657d8..0000000
--- a/src/styles/tiptap.scss
+++ /dev/null
@@ -1,104 +0,0 @@
-/* Basic editor styles */
-.tiptap {
-  /* List styles */
-  p {
-    margin: 5px 0px;
-  }
-  ul,
-  ol {
-    padding: 0 1rem;
-    margin: 1.25rem 1rem 1.25rem 0.4rem;
-
-    li p {
-      margin-top: 0.25em;
-      margin-bottom: 0.25em;
-    }
-  }
-
-  /* Heading styles */
-  h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6 {
-    line-height: 1.1;
-    margin: 10px 0px;
-    text-wrap: pretty;
-  }
-
-  h1,
-  h2 {
-
-  }
-
-  h1 {
-    font-size: 1.4rem;
-  }
-
-  h2 {
-    font-size: 1.2rem;
-  }
-
-  h3 {
-    font-size: 1.1rem;
-  }
-
-  h4,
-  h5,
-  h6 {
-    font-size: 1rem;
-  }
-
-  code {
-    background-color: var(--purple-light); // todo: fix the color
-    border-radius: 0.4rem;
-    color: var(--black);
-    font-size: 0.85rem;
-    padding: 0.25em 0.3em;
-  }
-
-  pre {
-    background: var(--black); // todo: fix the color
-    color: var(--white);
-    font-family: 'JetBrainsMono', monospace;
-    margin: 1.5rem 0;
-    padding: 0.75rem 1rem;
-    background: #00000030;
-    border-radius: 5px;
-    border: solid 2px rebeccapurple;
-
-    code {
-      background: none;
-      color: inherit;
-      font-size: 0.8rem;
-      padding: 0;
-    }
-  }
-
-  blockquote {
-    border-radius: 0 10px 10px 0;
-    border-left: solid 6px rgba(255, 255, 255, 0.1);
-    padding: 25px;
-    background: #232323;
-    color: rgba(255, 255, 255, 0.75);
-    margin: 10px 0;
-  }
-}
-
-/* Toolbar Styling */
-.control-group {
-    padding: 5px 0px 15px 0px;
-    border-radius: 0px;
-    border-bottom: solid 1px rgb(255 255 255 / 10%);
-}
-
-.ProseMirror {
-  min-height: 75px;
-  }
-
-.btnMain.btnMainTipTap {
-    padding: 5px 10px;
-    height: 35px;
-    font-size: 14px;
-}
\ No newline at end of file
-- 
2.34.1


From e384bae9458bab365637ec000774f4b2a413dbc4 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 14:00:04 +0100
Subject: [PATCH 06/12] refactor(editor): override image and link dialog

---
 src/components/Markdown/Dialog.module.scss |  10 +
 src/components/Markdown/Editor.tsx         |  10 +-
 src/components/Markdown/ImageDialog.tsx    | 166 +++++++++++
 src/components/Markdown/LinkDialog.tsx     | 306 +++++++++++++++++++++
 src/styles/mdxEditor.scss                  |   3 +
 5 files changed, 493 insertions(+), 2 deletions(-)
 create mode 100644 src/components/Markdown/Dialog.module.scss
 create mode 100644 src/components/Markdown/ImageDialog.tsx
 create mode 100644 src/components/Markdown/LinkDialog.tsx

diff --git a/src/components/Markdown/Dialog.module.scss b/src/components/Markdown/Dialog.module.scss
new file mode 100644
index 0000000..7212245
--- /dev/null
+++ b/src/components/Markdown/Dialog.module.scss
@@ -0,0 +1,10 @@
+.formAction {
+  display: flex;
+  width: 100%;
+  justify-content: flex-end;
+  gap: var(--spacing-2);
+}
+
+.wrapper {
+  border-radius: 0;
+}
diff --git a/src/components/Markdown/Editor.tsx b/src/components/Markdown/Editor.tsx
index 727e008..45bea77 100644
--- a/src/components/Markdown/Editor.tsx
+++ b/src/components/Markdown/Editor.tsx
@@ -39,6 +39,8 @@ import React, {
   useMemo,
   useRef
 } from 'react'
+import { ImageDialog } from './ImageDialog'
+import { LinkDialog } from './LinkDialog'
 
 export interface EditorRef {
   setMarkdown: (md: string) => void
@@ -95,10 +97,14 @@ export const Editor = React.memo(
         }),
         headingsPlugin(),
         quotePlugin(),
-        imagePlugin(),
+        imagePlugin({
+          ImageDialog: ImageDialog
+        }),
         tablePlugin(),
         linkPlugin(),
-        linkDialogPlugin(),
+        linkDialogPlugin({
+          LinkDialog: LinkDialog
+        }),
         listsPlugin(),
         thematicBreakPlugin(),
         directivesPlugin({
diff --git a/src/components/Markdown/ImageDialog.tsx b/src/components/Markdown/ImageDialog.tsx
new file mode 100644
index 0000000..0482283
--- /dev/null
+++ b/src/components/Markdown/ImageDialog.tsx
@@ -0,0 +1,166 @@
+import React, { useCallback, useEffect, useState } from 'react'
+import { useForm } from 'react-hook-form'
+import { useCellValues, usePublisher } from '@mdxeditor/gurx'
+import {
+  closeImageDialog$,
+  editorRootElementRef$,
+  imageDialogState$,
+  imageUploadHandler$,
+  saveImage$
+} from '@mdxeditor/editor'
+import styles from './Dialog.module.scss'
+import { createPortal } from 'react-dom'
+
+interface ImageFormFields {
+  src: string
+  title: string
+  altText: string
+  file: FileList
+}
+
+export const ImageDialog: React.FC = () => {
+  const [state, editorRootElementRef, imageUploadHandler] = useCellValues(
+    imageDialogState$,
+    editorRootElementRef$,
+    imageUploadHandler$
+  )
+  const saveImage = usePublisher(saveImage$)
+  const closeImageDialog = usePublisher(closeImageDialog$)
+  const { register, handleSubmit, setValue, reset } = useForm<ImageFormFields>({
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
+    values: state.type === 'editing' ? (state.initialValues as any) : {}
+  })
+  const [open, setOpen] = useState(state.type !== 'inactive')
+
+  useEffect(() => {
+    setOpen(state.type !== 'inactive')
+  }, [state.type])
+
+  useEffect(() => {
+    if (!open) {
+      closeImageDialog()
+      reset({ src: '', title: '', altText: '' })
+    }
+  }, [closeImageDialog, open, reset])
+
+  const handleClose = useCallback(() => {
+    setOpen(false)
+  }, [])
+
+  if (!open) return null
+  if (!editorRootElementRef?.current) return null
+
+  return createPortal(
+    <div className='popUpMain'>
+      <div className='ContainerMain'>
+        <div className='popUpMainCardWrapper'>
+          <div className='popUpMainCard popUpMainCardQR'>
+            <div className='popUpMainCardTop'>
+              <div className='popUpMainCardTopInfo'>
+                <h3>Add an image</h3>
+              </div>
+              <div className='popUpMainCardTopClose' onClick={handleClose}>
+                <svg
+                  xmlns='http://www.w3.org/2000/svg'
+                  viewBox='-96 0 512 512'
+                  width='1em'
+                  height='1em'
+                  fill='currentColor'
+                  style={{ zIndex: 1 }}
+                >
+                  <path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z'></path>
+                </svg>
+              </div>
+            </div>
+            <div className='pUMCB_Zaps'>
+              <form
+                className='pUMCB_ZapsInside'
+                onSubmit={(e) => {
+                  void handleSubmit(saveImage)(e)
+                  reset({ src: '', title: '', altText: '' })
+                  e.preventDefault()
+                  e.stopPropagation()
+                }}
+              >
+                {imageUploadHandler === null ? (
+                  <input type='hidden' accept='image/*' {...register('file')} />
+                ) : (
+                  <div className='inputLabelWrapperMain'>
+                    <label className='form-label labelMain' htmlFor='file'>
+                      Upload an image from your device:
+                    </label>
+                    <input type='file' accept='image/*' {...register('file')} />
+                  </div>
+                )}
+
+                <div className='inputLabelWrapperMain'>
+                  <label className='form-label labelMain' htmlFor='src'>
+                    {imageUploadHandler !== null
+                      ? 'Or add an image from an URL:'
+                      : 'Add an image from an URL:'}
+                  </label>
+                  <input
+                    defaultValue={
+                      state.type === 'editing'
+                        ? state.initialValues.src ?? ''
+                        : ''
+                    }
+                    className='inputMain'
+                    size={40}
+                    autoFocus
+                    {...register('src')}
+                    onChange={(e) => setValue('src', e.currentTarget.value)}
+                    placeholder={'Paste an image src'}
+                  />
+                </div>
+
+                <div className='inputLabelWrapperMain'>
+                  <label className='form-label labelMain' htmlFor='alt'>
+                    Alt:
+                  </label>
+                  <input
+                    type='text'
+                    {...register('altText')}
+                    className='inputMain'
+                  />
+                </div>
+
+                <div className='inputLabelWrapperMain'>
+                  <label className='form-label labelMain' htmlFor='title'>
+                    Title:
+                  </label>
+                  <input
+                    type='text'
+                    {...register('title')}
+                    className='inputMain'
+                  />
+                </div>
+
+                <div className={styles.formAction}>
+                  <button
+                    type='submit'
+                    title={'Save'}
+                    aria-label={'Save'}
+                    className='btn btnMain btnMainPopup'
+                  >
+                    Save
+                  </button>
+                  <button
+                    type='reset'
+                    title={'Cancel'}
+                    aria-label={'Cancel'}
+                    className='btn btnMain btnMainPopup'
+                    onClick={handleClose}
+                  >
+                    Cancel
+                  </button>
+                </div>
+              </form>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>,
+    editorRootElementRef?.current
+  )
+}
diff --git a/src/components/Markdown/LinkDialog.tsx b/src/components/Markdown/LinkDialog.tsx
new file mode 100644
index 0000000..7b2ef11
--- /dev/null
+++ b/src/components/Markdown/LinkDialog.tsx
@@ -0,0 +1,306 @@
+/* eslint-disable @typescript-eslint/no-unsafe-call */
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+/* eslint-disable @typescript-eslint/no-unsafe-assignment */
+import * as Popover from '@radix-ui/react-popover'
+import * as Tooltip from '@radix-ui/react-tooltip'
+import React from 'react'
+
+import {
+  activeEditor$,
+  editorRootElementRef$,
+  iconComponentFor$,
+  cancelLinkEdit$,
+  linkDialogState$,
+  onWindowChange$,
+  removeLink$,
+  switchFromPreviewToLinkEdit$,
+  updateLink$,
+  ClickLinkCallback
+} from '@mdxeditor/editor'
+import { useForm } from 'react-hook-form'
+import { Cell, useCellValues, usePublisher } from '@mdxeditor/gurx'
+import styles from './Dialog.module.scss'
+
+interface LinkEditFormProps {
+  url: string
+  title: string
+  onSubmit: (link: { url: string; title: string }) => void
+  onCancel: () => void
+}
+
+interface LinkFormFields {
+  url: string
+  title: string
+}
+
+export function LinkEditForm({
+  url,
+  title,
+  onSubmit,
+  onCancel
+}: LinkEditFormProps) {
+  const { register, handleSubmit, setValue } = useForm<LinkFormFields>({
+    values: {
+      url,
+      title
+    }
+  })
+
+  return (
+    <div className='pUMCB_Zaps'>
+      <form
+        className='pUMCB_ZapsInside'
+        onSubmit={(e) => {
+          void handleSubmit(onSubmit)(e)
+          e.stopPropagation()
+          e.preventDefault()
+        }}
+        onReset={(e) => {
+          e.stopPropagation()
+          onCancel()
+        }}
+      >
+        <div className='inputLabelWrapperMain'>
+          <label className='form-label labelMain' htmlFor='file'>
+            URL:
+          </label>
+          <input
+            defaultValue={url}
+            className='inputMain'
+            size={40}
+            autoFocus
+            {...register('url')}
+            onChange={(e) => setValue('url', e.currentTarget.value)}
+            placeholder={'Paste an URL'}
+          />
+        </div>
+
+        <div className='inputLabelWrapperMain'>
+          <label className='form-label labelMain' htmlFor='link-title'>
+            Title:
+          </label>
+          <input
+            id='link-title'
+            className='inputMain'
+            size={40}
+            {...register('title')}
+          />
+        </div>
+
+        <div className={styles.formAction}>
+          <button
+            type='submit'
+            title={'Set URL'}
+            aria-label={'Set URL'}
+            className='btn btnMain btnMainPopup'
+          >
+            Save
+          </button>
+          <button
+            type='reset'
+            title={'Cancel change'}
+            aria-label={'Cancel change'}
+            className='btn btnMain btnMainPopup'
+          >
+            Cancel
+          </button>
+        </div>
+      </form>
+    </div>
+  )
+}
+
+export const onClickLinkCallback$ = Cell<ClickLinkCallback | null>(null)
+
+/** @internal */
+export const LinkDialog = () => {
+  const [
+    editorRootElementRef,
+    activeEditor,
+    iconComponentFor,
+    linkDialogState,
+    onClickLinkCallback
+  ] = useCellValues(
+    editorRootElementRef$,
+    activeEditor$,
+    iconComponentFor$,
+    linkDialogState$,
+    onClickLinkCallback$
+  )
+  const publishWindowChange = usePublisher(onWindowChange$)
+  const updateLink = usePublisher(updateLink$)
+  const cancelLinkEdit = usePublisher(cancelLinkEdit$)
+  const switchFromPreviewToLinkEdit = usePublisher(switchFromPreviewToLinkEdit$)
+  const removeLink = usePublisher(removeLink$)
+
+  React.useEffect(() => {
+    const update = () => {
+      activeEditor?.getEditorState().read(() => {
+        publishWindowChange(true)
+      })
+    }
+
+    window.addEventListener('resize', update)
+    window.addEventListener('scroll', update)
+
+    return () => {
+      window.removeEventListener('resize', update)
+      window.removeEventListener('scroll', update)
+    }
+  }, [activeEditor, publishWindowChange])
+
+  const [copyUrlTooltipOpen, setCopyUrlTooltipOpen] = React.useState(false)
+
+  const theRect = linkDialogState.rectangle
+
+  const urlIsExternal =
+    linkDialogState.type === 'preview' && linkDialogState.url.startsWith('http')
+
+  return (
+    <Popover.Root open={linkDialogState.type !== 'inactive'}>
+      <Popover.Anchor
+        data-visible={linkDialogState.type === 'edit'}
+        style={{
+          position: 'fixed',
+          top: `${theRect?.top ?? 0}px`,
+          left: `${theRect?.left ?? 0}px`,
+          width: `${theRect?.width ?? 0}px`,
+          height: `${theRect?.height ?? 0}px`
+        }}
+      />
+
+      <Popover.Portal container={editorRootElementRef?.current}>
+        <Popover.Content
+          sideOffset={5}
+          onOpenAutoFocus={(e) => {
+            e.preventDefault()
+          }}
+          key={linkDialogState.linkNodeKey}
+          className={[
+            'popUpMainCard',
+            ...(linkDialogState.type === 'edit' ? [styles.wrapper] : [])
+          ].join(' ')}
+        >
+          {linkDialogState.type === 'edit' && (
+            <LinkEditForm
+              url={linkDialogState.url}
+              title={linkDialogState.title}
+              onSubmit={updateLink}
+              onCancel={cancelLinkEdit.bind(null)}
+            />
+          )}
+
+          {linkDialogState.type === 'preview' && (
+            <>
+              <div className='IBMSMSMSSS_Author_Top_AddressWrapper'>
+                <div className='IBMSMSMSSS_Author_Top_AddressWrapped'>
+                  <p className='IBMSMSMSSS_Author_Top_Address'>
+                    <a
+                      className={styles.linkDialogPreviewAnchor}
+                      href={linkDialogState.url}
+                      {...(urlIsExternal
+                        ? { target: '_blank', rel: 'noreferrer' }
+                        : {})}
+                      onClick={(e) => {
+                        if (
+                          onClickLinkCallback !== null &&
+                          typeof onClickLinkCallback === 'function'
+                        ) {
+                          e.preventDefault()
+                          onClickLinkCallback(linkDialogState.url)
+                        }
+                      }}
+                      title={
+                        urlIsExternal
+                          ? `Open ${linkDialogState.url} in new window`
+                          : linkDialogState.url
+                      }
+                    >
+                      <span>{linkDialogState.url}</span>
+                      {urlIsExternal && iconComponentFor('open_in_new')}
+                    </a>
+                  </p>
+                </div>
+                <div className='IBMSMSMSSS_Author_Top_IconWrapper'>
+                  <div
+                    className='IBMSMSMSSS_Author_Top_IconWrapped'
+                    onClick={() => {
+                      switchFromPreviewToLinkEdit()
+                    }}
+                  >
+                    <svg
+                      xmlns='http://www.w3.org/2000/svg'
+                      viewBox='0 0 512 512'
+                      width='1em'
+                      height='1em'
+                      fill='currentColor'
+                      className='IBMSMSMSSS_Author_Top_Icon'
+                    >
+                      <path d='M362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32L492.7 58.75C517.7 83.74 517.7 124.3 492.7 149.3L444.3 197.7L314.3 67.72L362.7 19.32zM421.7 220.3L188.5 453.4C178.1 463.8 165.2 471.5 151.1 475.6L30.77 511C22.35 513.5 13.24 511.2 7.03 504.1C.8198 498.8-1.502 489.7 .976 481.2L36.37 360.9C40.53 346.8 48.16 333.9 58.57 323.5L291.7 90.34L421.7 220.3z'></path>
+                    </svg>
+                  </div>
+
+                  <Tooltip.Provider>
+                    <Tooltip.Root open={copyUrlTooltipOpen}>
+                      <Tooltip.Trigger asChild>
+                        <div
+                          className='IBMSMSMSSS_Author_Top_IconWrapped'
+                          onClick={() => {
+                            void window.navigator.clipboard
+                              .writeText(linkDialogState.url)
+                              .then(() => {
+                                setCopyUrlTooltipOpen(true)
+                                setTimeout(() => {
+                                  setCopyUrlTooltipOpen(false)
+                                }, 1000)
+                              })
+                          }}
+                        >
+                          <svg
+                            xmlns='http://www.w3.org/2000/svg'
+                            viewBox='0 0 512 512'
+                            width='1em'
+                            height='1em'
+                            fill='currentColor'
+                            className='IBMSMSMSSS_Author_Top_Icon'
+                          >
+                            <path d='M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z'></path>
+                          </svg>
+                        </div>
+                      </Tooltip.Trigger>
+                      <Tooltip.Portal container={editorRootElementRef?.current}>
+                        <Tooltip.Content sideOffset={5}>
+                          {'Copied!'}
+                          <Tooltip.Arrow />
+                        </Tooltip.Content>
+                      </Tooltip.Portal>
+                    </Tooltip.Root>
+                  </Tooltip.Provider>
+
+                  <div
+                    className='IBMSMSMSSS_Author_Top_IconWrapped'
+                    onClick={() => {
+                      removeLink()
+                    }}
+                  >
+                    <svg
+                      xmlns='http://www.w3.org/2000/svg'
+                      viewBox='0 0 640 512'
+                      width='1em'
+                      height='1em'
+                      fill='currentColor'
+                      className='IBMSMSMSSS_Author_Top_Icon'
+                    >
+                      <path d='M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c8.4-19.3 10.6-41.4 4.8-63.3c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zM373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5L373 389.9z' />
+                    </svg>
+                  </div>
+                </div>
+              </div>
+            </>
+          )}
+          <Popover.Arrow />
+        </Popover.Content>
+      </Popover.Portal>
+    </Popover.Root>
+  )
+}
diff --git a/src/styles/mdxEditor.scss b/src/styles/mdxEditor.scss
index 4cc4a2a..2bfc808 100644
--- a/src/styles/mdxEditor.scss
+++ b/src/styles/mdxEditor.scss
@@ -101,6 +101,9 @@
   padding-top: 10px;
   min-height: 75px;
 }
+.mdxeditor {
+  --baseBg: rgba(255, 255, 255, 0.05);
+}
 .mdxeditor,
 .mdxeditor-popup-container {
   --basePageBg: var(--slate-3);
-- 
2.34.1


From e40ec6c5aa92b787e0a052cf0b4066e529c8e7c9 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 14:15:36 +0100
Subject: [PATCH 07/12] refactor(editor): update yt delete button

---
 .../Markdown/YoutubeDirectiveDescriptor.tsx          | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/components/Markdown/YoutubeDirectiveDescriptor.tsx b/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
index 7d60856..13a525f 100644
--- a/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
+++ b/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
@@ -26,6 +26,8 @@ export const YoutubeDirectiveDescriptor: DirectiveDescriptor<YoutubeDirectiveNod
         >
           <button
             type='button'
+            title='delete'
+            className='btnMain'
             onClick={() => {
               parentEditor.update(() => {
                 lexicalNode.selectNext()
@@ -33,7 +35,15 @@ export const YoutubeDirectiveDescriptor: DirectiveDescriptor<YoutubeDirectiveNod
               })
             }}
           >
-            delete
+            <svg
+              xmlns='http://www.w3.org/2000/svg'
+              viewBox='0 0 448 512'
+              width='1em'
+              height='1em'
+              fill='currentColor'
+            >
+              <path d='M135.2 17.7L128 32 32 32C14.3 32 0 46.3 0 64S14.3 96 32 96l384 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0-7.2-14.3C307.4 6.8 296.3 0 284.2 0L163.8 0c-12.1 0-23.2 6.8-28.6 17.7zM416 128L32 128 53.2 467c1.6 25.3 22.6 45 47.9 45l245.8 0c25.3 0 46.3-19.7 47.9-45L416 128z' />
+            </svg>
           </button>
           <iframe
             width='560'
-- 
2.34.1


From 026dae65bb40115f53dcca2721b96687cdd8c6f8 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 16:07:00 +0100
Subject: [PATCH 08/12] refactor(viewer): add yt directive renderer with
 marked-directive pkg

---
 package-lock.json                             | 37 +++++++++++++++++++
 package.json                                  |  1 +
 src/components/Markdown/Viewer.tsx            |  7 +++-
 src/components/Markdown/YoutubeDirective.tsx  | 31 ++++++++++++++++
 .../Markdown/YoutubeDirectiveDescriptor.tsx   |  2 +-
 src/styles/mdxEditor.scss                     |  6 +--
 6 files changed, 78 insertions(+), 6 deletions(-)
 create mode 100644 src/components/Markdown/YoutubeDirective.tsx

diff --git a/package-lock.json b/package-lock.json
index 659a347..ecdf918 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,6 +24,7 @@
         "fslightbox-react": "1.7.6",
         "lodash": "4.17.21",
         "marked": "^14.1.3",
+        "marked-directive": "^1.0.7",
         "nostr-login": "1.5.2",
         "nostr-tools": "2.7.1",
         "papaparse": "5.4.1",
@@ -3936,6 +3937,15 @@
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
+    "node_modules/attributes-parser": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/attributes-parser/-/attributes-parser-2.2.3.tgz",
+      "integrity": "sha512-zjOUWt95la8AdUO+kP1GBOonWrV5jy9NjJP+z9tva/DSA6FIzGKcN/gk3tdqQf/pOeB8dkyd3FCPrjhELMmrkg==",
+      "license": "MIT",
+      "dependencies": {
+        "json-loose": "^1.2.4"
+      }
+    },
     "node_modules/axios": {
       "version": "1.7.9",
       "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
@@ -5557,6 +5567,15 @@
       "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
       "dev": true
     },
+    "node_modules/json-loose": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/json-loose/-/json-loose-1.2.4.tgz",
+      "integrity": "sha512-lwMWNC5pvVI33rhYWmAsmtICWE2IH7euDY/iIPeMFE5AuzAifYgqQrjqSMzwbrFV6MWPs41XD+CajElHI4cZMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "moo": "^0.5.2"
+      }
+    },
     "node_modules/json-schema-traverse": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -5743,6 +5762,18 @@
         "node": ">= 18"
       }
     },
+    "node_modules/marked-directive": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/marked-directive/-/marked-directive-1.0.7.tgz",
+      "integrity": "sha512-2OilBJg5kSM0b7ijKlmIne8k1eA1YrNAWasw84fmfihgWuOQJFPmI5GzZd3DgM8PSquAKnYx87Qt5j/2Sw7JNA==",
+      "license": "MIT",
+      "dependencies": {
+        "attributes-parser": "^2.2.3"
+      },
+      "peerDependencies": {
+        "marked": ">=7.0.0"
+      }
+    },
     "node_modules/mdast-util-directive": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz",
@@ -6736,6 +6767,12 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/moo": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
+      "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
diff --git a/package.json b/package.json
index 542e4a1..0bbfdcf 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
     "fslightbox-react": "1.7.6",
     "lodash": "4.17.21",
     "marked": "^14.1.3",
+    "marked-directive": "^1.0.7",
     "nostr-login": "1.5.2",
     "nostr-tools": "2.7.1",
     "papaparse": "5.4.1",
diff --git a/src/components/Markdown/Viewer.tsx b/src/components/Markdown/Viewer.tsx
index 2a22e75..f2684ca 100644
--- a/src/components/Markdown/Viewer.tsx
+++ b/src/components/Markdown/Viewer.tsx
@@ -1,12 +1,17 @@
 import DOMPurify from 'dompurify'
 import { marked } from 'marked'
+import { createDirectives, presetDirectiveConfigs } from 'marked-directive'
+import { youtubeDirective } from './YoutubeDirective'
 
 interface ViewerProps {
   markdown: string
 }
 
 export const Viewer = ({ markdown }: ViewerProps) => {
-  const html = DOMPurify.sanitize(marked.parse(markdown, { async: false }))
+  const html = marked
+    .use(createDirectives([...presetDirectiveConfigs, youtubeDirective]))
+    .parse(DOMPurify.sanitize(markdown), { async: false })
+
   return (
     <div className='viewer' dangerouslySetInnerHTML={{ __html: html }}></div>
   )
diff --git a/src/components/Markdown/YoutubeDirective.tsx b/src/components/Markdown/YoutubeDirective.tsx
new file mode 100644
index 0000000..44e4446
--- /dev/null
+++ b/src/components/Markdown/YoutubeDirective.tsx
@@ -0,0 +1,31 @@
+import { type DirectiveConfig } from 'marked-directive'
+
+// defines `:youtube` directive
+export const youtubeDirective: DirectiveConfig = {
+  level: 'block',
+  marker: '::',
+  renderer(token) {
+    //https://www.youtube.com/embed/<VIDEO_ID>
+    //{#<VIDEO_ID>}
+    let vid: string = ''
+    if (token.attrs && token.meta.name === 'youtube') {
+      for (const attr in token.attrs) {
+        if (
+          Object.prototype.hasOwnProperty.call(token.attrs, attr) &&
+          attr.startsWith('#')
+        ) {
+          vid = attr.replace('#', '')
+          console.log(vid)
+        }
+      }
+    }
+
+    if (vid) {
+      return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${vid}" title="${
+        token.text || ''
+      }" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`
+    }
+
+    return false
+  }
+}
diff --git a/src/components/Markdown/YoutubeDirectiveDescriptor.tsx b/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
index 13a525f..d9c21c7 100644
--- a/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
+++ b/src/components/Markdown/YoutubeDirectiveDescriptor.tsx
@@ -52,7 +52,7 @@ export const YoutubeDirectiveDescriptor: DirectiveDescriptor<YoutubeDirectiveNod
             title='YouTube video player'
             frameBorder='0'
             allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
-          ></iframe>
+          />
         </div>
       )
     }
diff --git a/src/styles/mdxEditor.scss b/src/styles/mdxEditor.scss
index 2bfc808..0a55bc4 100644
--- a/src/styles/mdxEditor.scss
+++ b/src/styles/mdxEditor.scss
@@ -59,7 +59,7 @@
   }
 
   code {
-    background-color: var(--purple-light); // todo: fix the color
+    background-color: mediumpurple;
     border-radius: 0.4rem;
     color: var(--black);
     font-size: 0.85rem;
@@ -72,7 +72,7 @@
   }
 
   pre {
-    background: var(--black); // todo: fix the color
+    background: var(--black);
     color: var(--white);
     font-family: 'JetBrainsMono', monospace;
     margin: 1.5rem 0;
@@ -90,8 +90,6 @@
   }
 
   img {
-    width: 100%;
-    margin: 15px 0;
     background: #232323;
     border-radius: 10px;
   }
-- 
2.34.1


From 0760a3e81e64ed1980d45cbf7bed1e436d3dfc80 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 16:33:43 +0100
Subject: [PATCH 09/12] refactor(viewer): table styling

---
 src/styles/mdxEditor.scss | 42 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

diff --git a/src/styles/mdxEditor.scss b/src/styles/mdxEditor.scss
index 0a55bc4..e60850d 100644
--- a/src/styles/mdxEditor.scss
+++ b/src/styles/mdxEditor.scss
@@ -66,8 +66,8 @@
     padding: 0.25em 0.3em;
     outline: none;
 
-    &:empty:before {
-      content: ' ';
+    &:empty::before {
+      content: '\00A0';
     }
   }
 
@@ -99,6 +99,44 @@
   padding-top: 10px;
   min-height: 75px;
 }
+.viewer table {
+  table-layout: fixed;
+  width: 100%;
+  border-spacing: 0;
+  border-collapse: collapse;
+
+  & > tbody > tr > td,
+  & > thead > tr > th {
+    border: 1px solid #e0e1e6;
+    padding: 0.25rem 0.5rem;
+    white-space: normal;
+
+    & > div {
+      outline: none;
+
+      & > p {
+        margin: 0;
+      }
+    }
+
+    & > tbody > tr > td,
+    & > thead > tr > th {
+      [align='left'] {
+        text-align: left;
+      }
+      [align='center'] {
+        text-align: center;
+      }
+      [align='right'] {
+        text-align: right;
+      }
+    }
+
+    &:empty::before {
+      content: '\00A0';
+    }
+  }
+}
 .mdxeditor {
   --baseBg: rgba(255, 255, 255, 0.05);
 }
-- 
2.34.1


From a6ed390fadaf2f3950adb564fb9dd322325a2451 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 16:36:29 +0100
Subject: [PATCH 10/12] fix(viewer): yt directive

---
 src/components/Markdown/YoutubeDirective.tsx | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/src/components/Markdown/YoutubeDirective.tsx b/src/components/Markdown/YoutubeDirective.tsx
index 44e4446..bedfbe0 100644
--- a/src/components/Markdown/YoutubeDirective.tsx
+++ b/src/components/Markdown/YoutubeDirective.tsx
@@ -6,7 +6,7 @@ export const youtubeDirective: DirectiveConfig = {
   marker: '::',
   renderer(token) {
     //https://www.youtube.com/embed/<VIDEO_ID>
-    //{#<VIDEO_ID>}
+    //::youtube{#<VIDEO_ID>}
     let vid: string = ''
     if (token.attrs && token.meta.name === 'youtube') {
       for (const attr in token.attrs) {
@@ -15,15 +15,12 @@ export const youtubeDirective: DirectiveConfig = {
           attr.startsWith('#')
         ) {
           vid = attr.replace('#', '')
-          console.log(vid)
         }
       }
     }
 
     if (vid) {
-      return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${vid}" title="${
-        token.text || ''
-      }" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`
+      return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${vid}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`
     }
 
     return false
-- 
2.34.1


From 137cd95c4e4476c10fc08e48c3b0b31da3a1dc54 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 17:03:18 +0100
Subject: [PATCH 11/12] fix(viewer): allow iframe, only from custom yt
 directive

---
 src/components/Markdown/Viewer.tsx           | 25 +++++++++++++++++---
 src/components/Markdown/YoutubeDirective.tsx |  2 +-
 2 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/src/components/Markdown/Viewer.tsx b/src/components/Markdown/Viewer.tsx
index f2684ca..6f4a1cb 100644
--- a/src/components/Markdown/Viewer.tsx
+++ b/src/components/Markdown/Viewer.tsx
@@ -2,15 +2,34 @@ import DOMPurify from 'dompurify'
 import { marked } from 'marked'
 import { createDirectives, presetDirectiveConfigs } from 'marked-directive'
 import { youtubeDirective } from './YoutubeDirective'
+import { useMemo } from 'react'
 
 interface ViewerProps {
   markdown: string
 }
 
 export const Viewer = ({ markdown }: ViewerProps) => {
-  const html = marked
-    .use(createDirectives([...presetDirectiveConfigs, youtubeDirective]))
-    .parse(DOMPurify.sanitize(markdown), { async: false })
+  const html = useMemo(() => {
+    DOMPurify.addHook('beforeSanitizeAttributes', function (node) {
+      if (node.nodeName && node.nodeName === 'IFRAME') {
+        const src = node.attributes.getNamedItem('src')
+        if (!(src && src.value.startsWith('https://www.youtube.com/embed/'))) {
+          node.remove()
+        }
+      }
+    })
+
+    return DOMPurify.sanitize(
+      marked
+        .use(createDirectives([...presetDirectiveConfigs, youtubeDirective]))
+        .parse(`${markdown}`, {
+          async: false
+        }),
+      {
+        ADD_TAGS: ['iframe']
+      }
+    )
+  }, [markdown])
 
   return (
     <div className='viewer' dangerouslySetInnerHTML={{ __html: html }}></div>
diff --git a/src/components/Markdown/YoutubeDirective.tsx b/src/components/Markdown/YoutubeDirective.tsx
index bedfbe0..4908d0d 100644
--- a/src/components/Markdown/YoutubeDirective.tsx
+++ b/src/components/Markdown/YoutubeDirective.tsx
@@ -20,7 +20,7 @@ export const youtubeDirective: DirectiveConfig = {
     }
 
     if (vid) {
-      return `<iframe width="560" height="315" src="https://www.youtube.com/embed/${vid}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`
+      return `<iframe title="Video embed" width="560" height="315" src="https://www.youtube.com/embed/${vid}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`
     }
 
     return false
-- 
2.34.1


From cbd53852a5349082a1fe1376c33fc5cf8f4cafd4 Mon Sep 17 00:00:00 2001
From: enes <enes@nostrdev.com>
Date: Tue, 24 Dec 2024 17:09:01 +0100
Subject: [PATCH 12/12] fix(viewer): image bug

---
 src/styles/mdxEditor.scss | 68 +++++++++++++++++++++------------------
 1 file changed, 37 insertions(+), 31 deletions(-)

diff --git a/src/styles/mdxEditor.scss b/src/styles/mdxEditor.scss
index e60850d..7ba06fc 100644
--- a/src/styles/mdxEditor.scss
+++ b/src/styles/mdxEditor.scss
@@ -99,41 +99,47 @@
   padding-top: 10px;
   min-height: 75px;
 }
-.viewer table {
-  table-layout: fixed;
-  width: 100%;
-  border-spacing: 0;
-  border-collapse: collapse;
-
-  & > tbody > tr > td,
-  & > thead > tr > th {
-    border: 1px solid #e0e1e6;
-    padding: 0.25rem 0.5rem;
-    white-space: normal;
-
-    & > div {
-      outline: none;
-
-      & > p {
-        margin: 0;
-      }
-    }
+.viewer {
+  img {
+    max-width: 100%;
+    height: auto;
+  }
+  table {
+    table-layout: fixed;
+    width: 100%;
+    border-spacing: 0;
+    border-collapse: collapse;
 
     & > tbody > tr > td,
     & > thead > tr > th {
-      [align='left'] {
-        text-align: left;
-      }
-      [align='center'] {
-        text-align: center;
-      }
-      [align='right'] {
-        text-align: right;
-      }
-    }
+      border: 1px solid #e0e1e6;
+      padding: 0.25rem 0.5rem;
+      white-space: normal;
 
-    &:empty::before {
-      content: '\00A0';
+      & > div {
+        outline: none;
+
+        & > p {
+          margin: 0;
+        }
+      }
+
+      & > tbody > tr > td,
+      & > thead > tr > th {
+        [align='left'] {
+          text-align: left;
+        }
+        [align='center'] {
+          text-align: center;
+        }
+        [align='right'] {
+          text-align: right;
+        }
+      }
+
+      &:empty::before {
+        content: '\00A0';
+      }
     }
   }
 }
-- 
2.34.1