diff --git a/assets/icons/blockquoteicon.svg b/assets/icons/blockquoteicon.svg new file mode 100644 index 0000000..3eabf64 --- /dev/null +++ b/assets/icons/blockquoteicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/boldicon.svg b/assets/icons/boldicon.svg new file mode 100644 index 0000000..8d49a11 --- /dev/null +++ b/assets/icons/boldicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/bulletlisticon.svg b/assets/icons/bulletlisticon.svg new file mode 100644 index 0000000..28fff17 --- /dev/null +++ b/assets/icons/bulletlisticon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/italicicon.svg b/assets/icons/italicicon.svg new file mode 100644 index 0000000..06e09b5 --- /dev/null +++ b/assets/icons/italicicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/numberedlisticon.svg b/assets/icons/numberedlisticon.svg new file mode 100644 index 0000000..7401bbd --- /dev/null +++ b/assets/icons/numberedlisticon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/textformaticon.svg b/assets/icons/textformaticon.svg new file mode 100644 index 0000000..f59def5 --- /dev/null +++ b/assets/icons/textformaticon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/texticon.svg b/assets/icons/texticon.svg new file mode 100644 index 0000000..c35172e --- /dev/null +++ b/assets/icons/texticon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/underlineicon.svg b/assets/icons/underlineicon.svg new file mode 100644 index 0000000..7d1fe91 --- /dev/null +++ b/assets/icons/underlineicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index e9e4d54..3ccac17 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -35,6 +35,8 @@ .ant-picker-input, .ant-picker-header-view button, .ant-badge, +.ant-select-dropdown, +.ant-splitter, [class*=' ant-radio'] { font-family: 'DM Sans'; } @@ -168,23 +170,6 @@ code { } } -.markdown-display > .ant-space-item *:first-child { - margin-top: 0; -} - -.markdown-display > .ant-space-item *:last-child { - margin-bottom: 0; -} - -.markdown-display > .ant-space-item h1, -.markdown-display > .ant-space-item h2, -.markdown-display > .ant-space-item h3, -.markdown-display > .ant-space-item h4, -.markdown-display > .ant-space-item h5, -.markdown-display > .ant-space-item h6 { - margin-bottom: 0.15em; -} - .iddisplay .ant-popover-inner { padding: 0 !important; } @@ -446,3 +431,125 @@ body { justify-content: center; z-index: 1; } + +/* --- Start of src/components/Dashboard/common/MarkdownInput.css --- */ +.md-editor__content { + padding: 24px; + font-size: 14px; + line-height: 1.6; + color: var(--baseText); + white-space: pre-wrap; + outline: none; +} + +.md-editor { + color: var(--baseText); +} + +.md-editor .tiptap { + min-height: var(--md-editor-min-height); +} + +.md-editor .tiptap p.is-editor-empty:first-child::before { + content: 'Enter text here...'; + color: var(--baseBorderHover); + float: left; + height: 0; + pointer-events: none; +} + +.markdown pre, +.markdown code, +.markdown blockquote { + background: var(--baseBgSubtle); + color: var(--baseTextContrast); +} + +.markdown pre { + border-radius: 8px; + padding: 12px; + overflow-x: auto; +} + +.markdown blockquote { + border-left: 3px solid var(--color-primary); + margin: 0; + padding: 8px 12px; +} + +.markdown hr { + border: 0; + border-top: 1px solid #8484844d; + margin: 10px 0; +} + +.markdown *:first-child { + margin-top: 0; +} + +.markdown *:last-child { + margin-bottom: 0; +} + +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + margin-bottom: 10px; + line-height: 1; +} + +.markdown p { + font-size: 14px; + margin: 10px 0; + line-height: 1; +} + +.markdown h1 { + font-size: 32px; +} + +.markdown h2 { + font-size: 26px; +} + +.markdown h3 { + font-size: 20px; +} + +.markdown h4 { + font-size: 16px; +} + +.markdown { + width: 100%; +} + +.markdown ul, +.markdown ol { + padding-left: 20px; + margin: 10px 0; +} + +.markdown li { + margin: 10px 0; + line-height: 1; +} + +.markdown-code-editor { + padding: 20px; +} + +.markdown-code-editor .cm-editor { + background-color: transparent; +} + +.markdown-code-editor .cm-editor.cm-focused { + outline: none; +} + +.markdown-code-editor .cm-editor .cm-gutters { + background-color: transparent; +} diff --git a/package.json b/package.json index db71bcf..5316d57 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,13 @@ "@codemirror/theme-one-dark": "^6.1.3", "@simplewebauthn/browser": "^13.1.2", "@tanstack/react-query": "^5.90.10", + "@tiptap/extension-link": "^3.20.1", + "@tiptap/extension-placeholder": "^3.20.1", + "@tiptap/extension-underline": "^3.20.1", + "@tiptap/markdown": "^3.20.1", + "@tiptap/pm": "^3.20.1", + "@tiptap/react": "^3.20.1", + "@tiptap/starter-kit": "^3.20.1", "@tsparticles/react": "^3.0.0", "@tsparticles/slim": "^3.9.1", "@uiw/react-codemirror": "^4.25.1", @@ -136,11 +143,15 @@ "target": [ { "target": "dmg", - "arch": ["arm64"] + "arch": [ + "arm64" + ] }, { "target": "dmg", - "arch": ["x64"] + "arch": [ + "x64" + ] } ], "mergeASARs": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e308565..01bfcf1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,27 @@ importers: '@tanstack/react-query': specifier: ^5.90.10 version: 5.90.20(react@19.2.4) + '@tiptap/extension-link': + specifier: ^3.20.1 + version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-placeholder': + specifier: ^3.20.1 + version: 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-underline': + specifier: ^3.20.1 + version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/markdown': + specifier: ^3.20.1 + version: 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/pm': + specifier: ^3.20.1 + version: 3.20.1 + '@tiptap/react': + specifier: ^3.20.1 + version: 3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tiptap/starter-kit': + specifier: ^3.20.1 + version: 3.20.1 '@tsparticles/react': specifier: ^3.0.0 version: 3.0.0(@tsparticles/engine@3.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1109,6 +1130,15 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1438,6 +1468,9 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -1678,6 +1711,166 @@ packages: peerDependencies: react: ^18 || ^19 + '@tiptap/core@3.20.1': + resolution: {integrity: sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA==} + peerDependencies: + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-blockquote@3.20.1': + resolution: {integrity: sha512-WzNXk/63PQI2fav4Ta6P0GmYRyu8Gap1pV3VUqaVK829iJ6Zt1T21xayATHEHWMK27VT1GLPJkx9Ycr2jfDyQw==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-bold@3.20.1': + resolution: {integrity: sha512-fz++Qv6Rk/Hov0IYG/r7TJ1Y4zWkuGONe0UN5g0KY32NIMg3HeOHicbi4xsNWTm9uAOl3eawWDkezEMrleObMw==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-bubble-menu@3.20.1': + resolution: {integrity: sha512-XaPvO6aCoWdFnCBus0s88lnj17NR/OopV79i8Qhgz3WMR0vrsL5zsd45l0lZuu9pSvm5VW47SoxakkJiZC1suw==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-bullet-list@3.20.1': + resolution: {integrity: sha512-mbrlvOZo5OF3vLhp+3fk9KuL/6J/wsN0QxF6ZFRAHzQ9NkJdtdfARcBeBnkWXGN8inB6YxbTGY1/E4lmBkOpOw==} + peerDependencies: + '@tiptap/extension-list': ^3.20.1 + + '@tiptap/extension-code-block@3.20.1': + resolution: {integrity: sha512-vKejwBq+Nlj4Ybd3qOyDxIQKzYymdNH+8eXkKwGShk2nfLJIxq69DCyGvmuHgipIO1qcYPJ149UNpGN+YGcdmA==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-code@3.20.1': + resolution: {integrity: sha512-509DHINIA/Gg+eTG7TEkfsS8RUiPLH5xZNyLRT0A1oaoaJmECKfrV6aAm05IdfTyqDqz6LW5pbnX6DdUC4keug==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-document@3.20.1': + resolution: {integrity: sha512-9vrqdGmRV7bQCSY3NLgu7UhIwgOCDp4sKqMNsoNRX0aZ021QQMTvBQDPkiRkCf7MNsnWrNNnr52PVnULEn3vFQ==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-dropcursor@3.20.1': + resolution: {integrity: sha512-K18L9FX4znn+ViPSIbTLOGcIaXMx/gLNwAPE8wPLwswbHhQqdiY1zzdBw6drgOc1Hicvebo2dIoUlSXOZsOEcw==} + peerDependencies: + '@tiptap/extensions': ^3.20.1 + + '@tiptap/extension-floating-menu@3.20.1': + resolution: {integrity: sha512-BeDC6nfOesIMn5pFuUnkEjOxGv80sOJ8uk1mdt9/3Fkvra8cB9NIYYCVtd6PU8oQFmJ8vFqPrRkUWrG5tbqnOg==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-gapcursor@3.20.1': + resolution: {integrity: sha512-kZOtttV6Ai8VUAgEng3h4WKFbtdSNJ6ps7r0cRPY+FctWhVmgNb/JJwwyC+vSilR7nRENAhrA/Cv/RxVlvLw+g==} + peerDependencies: + '@tiptap/extensions': ^3.20.1 + + '@tiptap/extension-hard-break@3.20.1': + resolution: {integrity: sha512-9sKpmg/IIdlLXimYWUZ3PplIRcehv4Oc7V1miTqlnAthMzjMqigDkjjgte4JZV67RdnDJTQkRw8TklCAU28Emg==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-heading@3.20.1': + resolution: {integrity: sha512-unudyfQP6FxnyWinxvPqe/51DG91J6AaJm666RnAubgYMCgym+33kBftx4j4A6qf+ddWYbD00thMNKOnVLjAEQ==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-horizontal-rule@3.20.1': + resolution: {integrity: sha512-rjFKFXNntdl0jay8oIGFvvykHlpyQTLmrH3Ag2fj3i8yh6MVvqhtaDomYQbw5sxECd5hBkL+T4n2d2DRuVw/QQ==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-italic@3.20.1': + resolution: {integrity: sha512-ZYRX13Kt8tR8JOzSXirH3pRpi8x30o7LHxZY58uXBdUvr3tFzOkh03qbN523+diidSVeHP/aMd/+IrplHRkQug==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-link@3.20.1': + resolution: {integrity: sha512-oYTTIgsQMqpkSnJAuAc+UtIKMuI4lv9e1y4LfI1iYm6NkEUHhONppU59smhxHLzb3Ww7YpDffbp5IgDTAiJztA==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-list-item@3.20.1': + resolution: {integrity: sha512-tzgnyTW82lYJkUnadYbatwkI9dLz/OWRSWuFpQPRje/ItmFMWuQ9c9NDD8qLbXPdEYnvrgSAA+ipCD/1G0qA0Q==} + peerDependencies: + '@tiptap/extension-list': ^3.20.1 + + '@tiptap/extension-list-keymap@3.20.1': + resolution: {integrity: sha512-Dr0xsQKx0XPOgDg7xqoWwfv7FFwZ3WeF3eOjqh3rDXlNHMj1v+UW5cj1HLphrsAZHTrVTn2C+VWPJkMZrSbpvQ==} + peerDependencies: + '@tiptap/extension-list': ^3.20.1 + + '@tiptap/extension-list@3.20.1': + resolution: {integrity: sha512-euBRAn0mkV7R2VEE+AuOt3R0j9RHEMFXamPFmtvTo8IInxDClusrm6mJoDjS8gCGAXsQCRiAe1SCQBPgGbOOwg==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/extension-ordered-list@3.20.1': + resolution: {integrity: sha512-Y+3Ad7OwAdagqdYwCnbqf7/to5ypD4NnUNHA0TXRCs7cAHRA8AdgPoIcGFpaaSpV86oosNU3yfeJouYeroffog==} + peerDependencies: + '@tiptap/extension-list': ^3.20.1 + + '@tiptap/extension-paragraph@3.20.1': + resolution: {integrity: sha512-QFrAtXNyv7JSnomMQc1nx5AnG9mMznfbYJAbdOQYVdbLtAzTfiTuNPNbQrufy5ZGtGaHxDCoaygu2QEfzaKG+Q==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-placeholder@3.20.1': + resolution: {integrity: sha512-k+jfbCugYGuIFBdojukgEopGazIMOgHrw46FnyN2X/6ICOIjQP2rh2ObslrsUOsJYoEevxCsNF9hZl1HvWX66g==} + peerDependencies: + '@tiptap/extensions': ^3.20.1 + + '@tiptap/extension-strike@3.20.1': + resolution: {integrity: sha512-EYgyma10lpsY+rwbVQL9u+gA7hBlKLSMFH7Zgd37FSxukOjr+HE8iKPQQ+SwbGejyDsPlLT8Z5Jnuxo5Ng90Pg==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-text@3.20.1': + resolution: {integrity: sha512-7PlIbYW8UenV6NPOXHmv8IcmPGlGx6HFq66RmkJAOJRPXPkTLAiX0N8rQtzUJ6jDEHqoJpaHFEHJw0xzW1yF+A==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extension-underline@3.20.1': + resolution: {integrity: sha512-fmHvDKzwCgnZUwRreq8tYkb1YyEwgzZ6QQkAQ0CsCRtvRMqzerr3Duz0Als4i8voZTuGDEL3VR6nAJbLAb/wPg==} + peerDependencies: + '@tiptap/core': ^3.20.1 + + '@tiptap/extensions@3.20.1': + resolution: {integrity: sha512-JRc/v+OBH0qLTdvQ7HvHWTxGJH73QOf1MC0R8NhOX2QnAbg2mPFv1h+FjGa2gfLGuCXBdWQomjekWkUKbC4e5A==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/markdown@3.20.1': + resolution: {integrity: sha512-dNrtP7kmabDomgjv9G/6+JSFL6WraPaFbmKh1eHSYKdDGvIwBfJnVPTV2VS3bP1OuYJEDJN/2ydtiCHyOTrQsQ==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + + '@tiptap/pm@3.20.1': + resolution: {integrity: sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw==} + + '@tiptap/react@3.20.1': + resolution: {integrity: sha512-UH1NpVpCaZBGB3Yr5N6aTS+rsCMDl9wHfrt/w+6+Gz4KHFZ2OILA82hELxZzhNc1Lmjz8vgCArKcsYql9gbzJA==} + peerDependencies: + '@tiptap/core': ^3.20.1 + '@tiptap/pm': ^3.20.1 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@3.20.1': + resolution: {integrity: sha512-opqWxL/4OTEiqmVC0wsU4o3JhAf6LycJ2G/gRIZVAIFLljI9uHfpPMTFGxZ5w9IVVJaP5PJysfwW/635kKqkrw==} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -1941,9 +2134,18 @@ packages: '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -1959,6 +2161,11 @@ packages: '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react@19.2.10': resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} @@ -1974,6 +2181,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/verror@1.10.11': resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} @@ -3384,6 +3594,10 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -4093,6 +4307,12 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + load-json-file@2.0.0: resolution: {integrity: sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==} engines: {node: '>=4'} @@ -4176,9 +4396,18 @@ packages: resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==} engines: {node: ^18.17.0 || >=20.5.0} + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} + hasBin: true + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@17.0.4: + resolution: {integrity: sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==} + engines: {node: '>= 20'} + hasBin: true + matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -4244,6 +4473,9 @@ packages: mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -4618,6 +4850,9 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -4853,6 +5088,64 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + prosemirror-changeset@2.4.0: + resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.0: + resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.4: + resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} + + prosemirror-menu@1.3.0: + resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==} + + prosemirror-model@1.25.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.11.0: + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} + + prosemirror-view@1.41.6: + resolution: {integrity: sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4863,6 +5156,10 @@ packages: pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -5326,6 +5623,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -5904,6 +6204,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} @@ -5979,6 +6282,11 @@ packages: peerDependencies: react: '>= 16.x' + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utf8-byte-length@1.0.5: resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} @@ -7382,6 +7690,20 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + optional: true + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + optional: true + + '@floating-ui/utils@0.2.11': + optional: true + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -7745,6 +8067,8 @@ snapshots: react-dom: 19.2.4(react@19.2.4) react-is: 18.3.1 + '@remirror/core-constants@3.0.0': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/pluginutils@4.2.1': @@ -7931,6 +8255,193 @@ snapshots: '@tanstack/query-core': 5.90.20 react: 19.2.4 + '@tiptap/core@3.20.1(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/pm': 3.20.1 + + '@tiptap/extension-blockquote@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-bold@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-bubble-menu@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + optional: true + + '@tiptap/extension-bullet-list@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + + '@tiptap/extension-code@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-document@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-dropcursor@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-floating-menu@3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + optional: true + + '@tiptap/extension-gapcursor@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-hard-break@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-heading@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-horizontal-rule@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + + '@tiptap/extension-italic@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-link@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + linkifyjs: 4.3.2 + + '@tiptap/extension-list-item@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-list-keymap@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + + '@tiptap/extension-ordered-list@3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-paragraph@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-placeholder@3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + + '@tiptap/extension-strike@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-text@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extension-underline@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + + '@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + + '@tiptap/markdown@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + marked: 17.0.4 + + '@tiptap/pm@3.20.1': + dependencies: + prosemirror-changeset: 2.4.0 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.0 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.4 + prosemirror-menu: 1.3.0 + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6) + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + '@tiptap/react@3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-floating-menu': 3.20.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.20.1': + dependencies: + '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) + '@tiptap/extension-blockquote': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-bold': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-bullet-list': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-code': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-code-block': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-document': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-dropcursor': 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-gapcursor': 3.20.1(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-hard-break': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-heading': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-horizontal-rule': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-italic': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-link': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-list': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/extension-list-item': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-list-keymap': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-ordered-list': 3.20.1(@tiptap/extension-list@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)) + '@tiptap/extension-paragraph': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-strike': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-text': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extension-underline': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1)) + '@tiptap/extensions': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) + '@tiptap/pm': 3.20.1 + '@trysound/sax@0.2.0': {} '@tsparticles/basic@3.9.1': @@ -8282,10 +8793,19 @@ snapshots: dependencies: '@types/node': 22.19.7 + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@2.0.0': {} + '@types/ms@2.1.0': {} '@types/node@22.19.7': @@ -8304,6 +8824,10 @@ snapshots: xmlbuilder: 15.1.1 optional: true + '@types/react-dom@19.2.3(@types/react@19.2.10)': + dependencies: + '@types/react': 19.2.10 + '@types/react@19.2.10': dependencies: csstype: 3.2.3 @@ -8318,6 +8842,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/verror@1.10.11': optional: true @@ -10194,6 +10720,8 @@ snapshots: fast-diff@1.3.0: {} + fast-equals@5.4.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -10939,6 +11467,12 @@ snapshots: lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + load-json-file@2.0.0: dependencies: graceful-fs: 4.2.11 @@ -11038,8 +11572,19 @@ snapshots: transitivePeerDependencies: - supports-color + markdown-it@14.1.1: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + markdown-table@3.0.4: {} + marked@17.0.4: {} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -11210,6 +11755,8 @@ snapshots: mdn-data@2.12.2: {} + mdurl@2.0.0: {} + media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -11711,6 +12258,8 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -11938,6 +12487,109 @@ snapshots: property-information@7.1.0: {} + prosemirror-changeset@2.4.0: + dependencies: + prosemirror-transform: 1.11.0 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-gapcursor@1.4.0: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.6 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.4: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.1 + prosemirror-model: 1.25.4 + + prosemirror-menu@1.3.0: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.6 + + prosemirror-transform@1.11.0: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-view@1.41.6: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -11950,6 +12602,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode.js@2.3.1: {} + punycode@2.3.1: {} qs@6.14.1: @@ -12567,6 +13221,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + router@2.2.0: dependencies: debug: 4.4.3 @@ -13307,6 +13963,8 @@ snapshots: typescript@5.9.3: {} + uc.micro@2.1.0: {} + ufo@1.6.3: {} unbox-primitive@1.1.0: @@ -13398,6 +14056,10 @@ snapshots: dependencies: react: 19.2.4 + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + utf8-byte-length@1.0.5: {} util-deprecate@1.0.2: {} diff --git a/src/components/Dashboard/Management/Notes/NoteInfo.jsx b/src/components/Dashboard/Management/Notes/NoteInfo.jsx index b1b0d28..07af2df 100644 --- a/src/components/Dashboard/Management/Notes/NoteInfo.jsx +++ b/src/components/Dashboard/Management/Notes/NoteInfo.jsx @@ -1,18 +1,21 @@ import { useRef, useState } from 'react' import { useLocation } from 'react-router-dom' import { Space, Flex, Card } from 'antd' +import { LoadingOutlined } from '@ant-design/icons' import loglevel from 'loglevel' -import config from '../../../../config' -import useCollapseState from '../../hooks/useCollapseState' -import NotesPanel from '../../common/NotesPanel' -import InfoCollapse from '../../common/InfoCollapse' -import ObjectInfo from '../../common/ObjectInfo' -import ViewButton from '../../common/ViewButton' +import config from '../../../../config.js' +import useCollapseState from '../../hooks/useCollapseState.jsx' +import NotesPanel from '../../common/NotesPanel.jsx' +import InfoCollapse from '../../common/InfoCollapse.jsx' +import ObjectInfo from '../../common/ObjectInfo.jsx' +import ObjectProperty from '../../common/ObjectProperty.jsx' +import ViewButton from '../../common/ViewButton.jsx' import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' +import TextIcon from '../../../Icons/TextIcon.jsx' import NoteIcon from '../../../Icons/NoteIcon.jsx' import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' -import ObjectForm from '../../common/ObjectForm' -import EditButtons from '../../common/EditButtons' +import ObjectForm from '../../common/ObjectForm.jsx' +import EditButtons from '../../common/EditButtons.jsx' import LockIndicator from '../../common/LockIndicator.jsx' import ActionHandler from '../../common/ActionHandler.jsx' import ObjectActions from '../../common/ObjectActions.jsx' @@ -21,6 +24,7 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import ScrollBox from '../../common/ScrollBox.jsx' +import { getModelProperty } from '../../../../database/ObjectModels.js' const log = loglevel.getLogger('NoteInfo') log.setLevel(config.logLevel) @@ -32,6 +36,7 @@ const NoteInfo = () => { const noteId = new URLSearchParams(location.search).get('noteId') const [collapseState, updateCollapseState] = useCollapseState('NoteInfo', { info: true, + content: true, notes: true, auditLogs: true }) @@ -87,6 +92,7 @@ const NoteInfo = () => { disabled={objectFormState.loading} items={[ { key: 'info', label: 'Note Information' }, + { key: 'content', label: 'Note Content' }, { key: 'notes', label: 'Notes' }, { key: 'auditLogs', label: 'Audit Logs' } ]} @@ -132,32 +138,58 @@ const NoteInfo = () => { loading={objectFormState.loading} ref={actionHandlerRef} > - } - active={collapseState.info} - onToggle={(expanded) => updateCollapseState('info', expanded)} - collapseKey='info' + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} > - { - setEditFormState((prev) => ({ ...prev, ...state })) - }} - > - {({ loading, isEditing, objectData }) => ( - - )} - - + {({ loading, isEditing, objectData }) => ( + + } + active={collapseState.info} + onToggle={(expanded) => + updateCollapseState('info', expanded) + } + collapseKey='info' + > + } + isEditing={isEditing} + type='note' + objectData={objectData} + visibleProperties={{ content: false }} + /> + + } + active={collapseState.content} + onToggle={(expanded) => + updateCollapseState('content', expanded) + } + collapseKey='content' + > + + + + + + )} + ( - -) +const UlComponent = ({ children }) => UlComponent.propTypes = { children: PropTypes.node } @@ -33,13 +31,13 @@ BlockquoteComponent.propTypes = { children: PropTypes.node } const MarkdownDisplay = ({ content }) => { const components = { - h1: (props) => , - h2: (props) => <Title level={2} {...props} />, - h3: (props) => <Title level={3} {...props} />, - h4: (props) => <Title level={4} {...props} />, - h5: (props) => <Title level={5} {...props} />, - h6: (props) => <Title level={6} {...props} />, - p: (props) => <Paragraph {...props} />, + h1: (props) => <h1 {...props} />, + h2: (props) => <h2 {...props} />, + h3: (props) => <h3 {...props} />, + h4: (props) => <h4 {...props} />, + h5: (props) => <h5 {...props} />, + h6: (props) => <h6 {...props} />, + p: (props) => <p {...props} />, ul: UlComponent, ol: OlComponent, li: LiComponent, @@ -57,15 +55,11 @@ const MarkdownDisplay = ({ content }) => { } return ( - <Space - direction='vertical' - style={{ width: '100%' }} - className={'markdown-display'} - > + <div className="markdown markdown-display"> <ReactMarkdown remarkPlugins={[remarkGfm]} components={components}> {content} </ReactMarkdown> - </Space> + </div> ) } diff --git a/src/components/Dashboard/common/MarkdownInput.jsx b/src/components/Dashboard/common/MarkdownInput.jsx index 3ca12c5..27ad49e 100644 --- a/src/components/Dashboard/common/MarkdownInput.jsx +++ b/src/components/Dashboard/common/MarkdownInput.jsx @@ -1,32 +1,213 @@ -import { Card, Splitter } from 'antd' +import { EditorContent, useEditor } from '@tiptap/react' +import Link from '@tiptap/extension-link' +import Placeholder from '@tiptap/extension-placeholder' +import Underline from '@tiptap/extension-underline' +import { Markdown } from '@tiptap/markdown' +import StarterKit from '@tiptap/starter-kit' +import { Card, Splitter, theme } from 'antd' import PropTypes from 'prop-types' +import { useEffect, useRef, useState } from 'react' +import { useThemeContext } from '../context/ThemeContext' +import MarkdownToolbar from './MarkdownToolbar' import CodeBlockEditor from './CodeBlockEditor' -import MarkdownDisplay from './MarkdownDisplay' -const MarkdownInput = ({ value, onChange }) => { +const MarkdownInput = ({ + value, + onChange, + minHeight = '120px', + size = 'small', + showCard = true +}) => { + const markdownValue = typeof value === 'string' ? value : '' + const lastMarkdownRef = useRef(markdownValue) + const { isDarkMode } = useThemeContext() + const { token } = theme.useToken() + const [viewState, setViewState] = useState({ editor: true, code: false }) + const [focusedPanel, setFocusedPanel] = useState(null) + const editorPanelRef = useRef(null) + const codePanelRef = useRef(null) + + const handlePanelBlur = () => { + requestAnimationFrame(() => { + const active = document.activeElement + if (editorPanelRef.current?.contains(active)) { + setFocusedPanel('editor') + } else if (codePanelRef.current?.contains(active)) { + setFocusedPanel('code') + } else { + setFocusedPanel(null) + } + }) + } + const editor = useEditor({ + extensions: [ + StarterKit, + Link.configure({ + autolink: true, + defaultProtocol: 'https', + openOnClick: false + }), + Placeholder.configure({ + placeholder: 'Enter text here...' + }), + Underline, + Markdown + ], + content: markdownValue, + contentType: 'markdown', + editorProps: { + attributes: { + class: 'md-editor__content' + } + }, + immediatelyRender: false, + onUpdate: ({ editor }) => { + const nextMarkdown = editor.getMarkdown() + lastMarkdownRef.current = nextMarkdown + onChange?.(nextMarkdown) + } + }) + + useEffect(() => { + if (!editor) { + return + } + + if (markdownValue !== lastMarkdownRef.current) { + editor.commands.setContent(markdownValue, { + contentType: 'markdown', + emitUpdate: false + }) + lastMarkdownRef.current = markdownValue + } + }, [editor, markdownValue]) + + const editorPanel = ( + <div + ref={editorPanelRef} + className='markdown md-editor' + onFocusCapture={() => setFocusedPanel('editor')} + onBlurCapture={handlePanelBlur} + > + <EditorContent editor={editor} /> + </div> + ) + + const codePanel = ( + <div + ref={codePanelRef} + className='markdown-code-editor' + onFocusCapture={() => setFocusedPanel('code')} + onBlurCapture={handlePanelBlur} + > + <CodeBlockEditor + code={markdownValue} + language='markdown' + style={{ border: 'none' }} + onChange={(val) => { + onChange?.(val) + }} + /> + </div> + ) + + const renderContent = () => { + if (viewState.editor && viewState.code) { + return ( + <Splitter className='farmcontrol-splitter' vertical={true}> + {viewState.editor && ( + <Splitter.Panel style={{ minHeight: 80 }}> + {editorPanel} + </Splitter.Panel> + )} + {viewState.code && ( + <Splitter.Panel style={{ minHeight: 80 }}> + {codePanel} + </Splitter.Panel> + )} + </Splitter> + ) + } + if (viewState.code) { + return codePanel + } + return editorPanel + } + + const cardContent = ( + <div + className={isDarkMode ? 'dark-theme' : 'light-theme'} + style={{ + '--basePageBg': token.colorBgContainer, + '--baseBase': token.colorBgContainer, + '--baseBgSubtle': token.colorBgElevated, + '--baseBg': token.colorFillTertiary, + '--baseBgHover': token.colorFillSecondary, + '--baseBgActive': token.colorFill, + '--baseLine': token.colorBorderSecondary, + '--baseBorder': token.colorBorder, + '--baseBorderHover': token.colorTextTertiary, + '--baseSolid': token.colorTextTertiary, + '--baseSolidHover': token.colorTextSecondary, + '--baseText': token.colorText, + '--baseTextContrast': token.colorText, + '--accentBase': token.colorPrimaryBg, + '--accentBgSubtle': token.colorPrimaryBg, + '--accentBg': token.colorPrimaryBgHover, + '--accentBgHover': token.colorPrimaryHover, + '--accentBgActive': token.colorPrimaryActive, + '--accentLine': token.colorPrimaryBorder, + '--accentBorder': token.colorPrimaryBorderHover, + '--accentBorderHover': token.colorPrimaryHover, + '--accentSolid': token.colorPrimary, + '--accentSolidHover': token.colorPrimaryHover, + '--accentText': token.colorPrimary, + '--accentTextContrast': token.colorWhite, + '--md-editor-min-height': minHeight + }} + > + <MarkdownToolbar + editor={editor} + size={size} + showCard={showCard} + viewState={viewState} + onViewStateChange={setViewState} + editingToolsDisabled={ + focusedPanel === 'code' || (viewState.code && !viewState.editor) + } + /> + {renderContent()} + </div> + ) + return ( - <Splitter className={'farmcontrol-splitter'} style={{ height: '100%' }}> - <Splitter.Panel> - <Card> - <CodeBlockEditor - code={value} - onChange={onChange} - language='markdown' - /> + <> + {showCard ? ( + <Card + style={{ height: '100%' }} + styles={{ + body: { + height: '100%', + padding: 0, + overflow: 'auto' + } + }} + > + {cardContent} </Card> - </Splitter.Panel> - <Splitter.Panel> - <Card style={{ height: '100%' }}> - <MarkdownDisplay content={value} /> - </Card> - </Splitter.Panel> - </Splitter> + ) : ( + cardContent + )} + </> ) } MarkdownInput.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func + value: PropTypes.string, + onChange: PropTypes.func, + minHeight: PropTypes.string, + size: PropTypes.string, + showCard: PropTypes.bool } export default MarkdownInput diff --git a/src/components/Dashboard/common/MarkdownToolbar.jsx b/src/components/Dashboard/common/MarkdownToolbar.jsx new file mode 100644 index 0000000..dd0b93b --- /dev/null +++ b/src/components/Dashboard/common/MarkdownToolbar.jsx @@ -0,0 +1,347 @@ +import { MinusOutlined } from '@ant-design/icons' +import { useEditorState } from '@tiptap/react' +import { useState } from 'react' +import { + Button, + Card, + Checkbox, + Flex, + Input, + Popover, + Select, + Space, + Tooltip +} from 'antd' +import PropTypes from 'prop-types' +import BlockquoteIcon from '../../Icons/BlockquoteIcon' +import BoldIcon from '../../Icons/BoldIcon' +import BulletListIcon from '../../Icons/BulletListIcon' +import CheckIcon from '../../Icons/CheckIcon' +import ItalicIcon from '../../Icons/ItalicIcon' +import NumberedListIcon from '../../Icons/NumberedListIcon' +import JsonObjectIcon from '../../Icons/JsonObjectIcon' +import LinkIcon from '../../Icons/LinkIcon' +import TextFormatIcon from '../../Icons/TextFormatIcon' +import UnderlineIcon from '../../Icons/UnderlineIcon' + +const BLOCK_TYPE_OPTIONS = [ + { label: 'Paragraph', value: 'paragraph' }, + { label: 'Heading 1', value: 'h1' }, + { label: 'Heading 2', value: 'h2' }, + { label: 'Heading 3', value: 'h3' }, + { label: 'Heading 4', value: 'h4' }, + { label: 'Quote', value: 'quote' } +] + +const getBlockTypeValue = (editor) => { + if (!editor) { + return 'paragraph' + } + + if (editor.isActive('blockquote')) { + return 'quote' + } + + for (const level of [1, 2, 3, 4]) { + if (editor.isActive('heading', { level })) { + return `h${level}` + } + } + + return 'paragraph' +} + +const VIEW_ITEMS = [ + { key: 'editor', label: 'Editor' }, + { key: 'code', label: 'Code' } +] + +const MarkdownToolbar = ({ + editor, + size = 'small', + showCard = true, + viewState = { editor: true, code: false }, + onViewStateChange = () => {}, + editingToolsDisabled = false +}) => { + const [linkPopoverOpen, setLinkPopoverOpen] = useState(false) + const [linkUrl, setLinkUrl] = useState('') + + const editorState = useEditorState({ + editor, + selector: ({ editor }) => ({ + blockTypeValue: getBlockTypeValue(editor), + isBold: editor?.isActive('bold') ?? false, + isItalic: editor?.isActive('italic') ?? false, + isUnderline: editor?.isActive('underline') ?? false, + isBulletList: editor?.isActive('bulletList') ?? false, + isOrderedList: editor?.isActive('orderedList') ?? false + }) + }) + + const handleViewStateChange = (key, checked) => { + const nextState = { ...viewState, [key]: checked } + if (!nextState.editor && !nextState.code) { + nextState[key] = true + } + onViewStateChange(nextState) + } + + const handleLinkPopoverOpenChange = (open) => { + setLinkPopoverOpen(open) + if (open) { + setLinkUrl(editor?.getAttributes('link').href ?? '') + } + } + + const handleLinkSubmit = () => { + if (!editor) { + return + } + + const trimmedUrl = linkUrl.trim() + + if (!trimmedUrl) { + editor.chain().focus().extendMarkRange('link').unsetLink().run() + } else { + editor + .chain() + .focus() + .extendMarkRange('link') + .setLink({ href: trimmedUrl }) + .run() + } + + setLinkPopoverOpen(false) + } + + const handleBlockTypeChange = (nextBlockType) => { + if (!editor) { + return + } + + const chain = editor.chain().focus() + + if (editor.isActive('blockquote') && nextBlockType !== 'quote') { + chain.toggleBlockquote() + } + + if (nextBlockType === 'paragraph') { + chain.setParagraph().run() + return + } + + if (nextBlockType === 'quote') { + if (editor.isActive('heading')) { + chain.setParagraph() + } + + chain.toggleBlockquote().run() + return + } + + const headingLevel = Number(nextBlockType.replace('h', '')) + chain.setHeading({ level: headingLevel }).run() + } + + const linkPopoverContent = ( + <Space.Compact block style={{ width: 280 }}> + <Input + placeholder='Enter a URL...' + value={linkUrl} + onChange={(e) => setLinkUrl(e.target.value)} + onPressEnter={handleLinkSubmit} + style={{ flex: 1 }} + /> + <Button + type='primary' + onClick={handleLinkSubmit} + icon={<CheckIcon />} + style={{ minWidth: 32 }} + /> + </Space.Compact> + ) + + const cardContent = ( + <Space wrap size={8}> + <Popover + content={ + <Flex vertical gap='middle' style={{ margin: '4px 8px' }}> + {VIEW_ITEMS.map((item) => ( + <Checkbox + checked={viewState[item.key]} + key={item.key} + onChange={(e) => + handleViewStateChange(item.key, e.target.checked) + } + > + {item.label} + </Checkbox> + ))} + </Flex> + } + placement='bottomLeft' + trigger='hover' + arrow={false} + > + <Button size={size} style={{ minWidth: 32 }}> + View + </Button> + </Popover> + <Select + size={size} + value={editorState.blockTypeValue} + options={BLOCK_TYPE_OPTIONS} + onChange={handleBlockTypeChange} + prefix={<TextFormatIcon />} + style={{ width: 160 }} + status={'defult'} + disabled={!editor || editingToolsDisabled} + /> + <Space.Compact size='small'> + <Tooltip title='Bold' arrow={false}> + <Button + size={size} + type={editorState.isBold ? 'primary' : 'default'} + icon={<BoldIcon />} + onClick={() => editor?.chain().focus().toggleBold().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + <Tooltip title='Italic' arrow={false}> + <Button + size={size} + type={editorState.isItalic ? 'primary' : 'default'} + icon={<ItalicIcon />} + onClick={() => editor?.chain().focus().toggleItalic().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + <Tooltip title='Underline' arrow={false}> + <Button + size={size} + type={editorState.isUnderline ? 'primary' : 'default'} + icon={<UnderlineIcon />} + onClick={() => editor?.chain().focus().toggleUnderline().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + </Space.Compact> + <Space.Compact size='small'> + <Tooltip title='Bulleted list' arrow={false}> + <Button + size={size} + type={editorState.isBulletList ? 'primary' : 'default'} + icon={<BulletListIcon />} + onClick={() => editor?.chain().focus().toggleBulletList().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + <Tooltip title='Numbered list' arrow={false}> + <Button + size={size} + type={editorState.isOrderedList ? 'primary' : 'default'} + icon={<NumberedListIcon />} + onClick={() => editor?.chain().focus().toggleOrderedList().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + </Space.Compact> + <Tooltip title='Block quote' arrow={false}> + <Button + size={size} + type={editorState.blockTypeValue === 'quote' ? 'primary' : 'default'} + icon={<BlockquoteIcon />} + onClick={() => + handleBlockTypeChange( + editorState.blockTypeValue === 'quote' ? 'paragraph' : 'quote' + ) + } + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + <Popover + content={linkPopoverContent} + placement='bottom' + trigger='click' + arrow={false} + open={linkPopoverOpen} + styles={{ body: { borderRadius: '22px' } }} + onOpenChange={handleLinkPopoverOpenChange} + > + <Tooltip title='Insert link' arrow={false}> + <Button + size={size} + icon={<LinkIcon />} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + </Popover> + <Tooltip title='Insert code block' arrow={false}> + <Button + size={size} + icon={<JsonObjectIcon />} + onClick={() => editor?.chain().focus().toggleCodeBlock().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + <Tooltip title='Insert horizontal rule' arrow={false}> + <Button + size={size} + icon={<MinusOutlined />} + onClick={() => editor?.chain().focus().setHorizontalRule().run()} + disabled={!editor || editingToolsDisabled} + style={{ minWidth: 32 }} + /> + </Tooltip> + </Space> + ) + + return ( + <> + {showCard ? ( + <Card + style={{ + width: '100%', + borderRadius: '12px 12px 0 0', + borderTop: 'none', + borderLeft: 'none', + borderRight: 'none' + }} + styles={{ + body: { + padding: 12 + } + }} + > + {cardContent} + </Card> + ) : ( + cardContent + )} + </> + ) +} + +MarkdownToolbar.propTypes = { + editor: PropTypes.shape({ + chain: PropTypes.func, + getAttributes: PropTypes.func, + isActive: PropTypes.func + }), + size: PropTypes.string, + showCard: PropTypes.bool, + viewState: PropTypes.object, + onViewStateChange: PropTypes.func, + editingToolsDisabled: PropTypes.bool +} + +export default MarkdownToolbar diff --git a/src/components/Dashboard/common/ObjectProperty.jsx b/src/components/Dashboard/common/ObjectProperty.jsx index 3ed2d2b..041a197 100644 --- a/src/components/Dashboard/common/ObjectProperty.jsx +++ b/src/components/Dashboard/common/ObjectProperty.jsx @@ -91,6 +91,7 @@ const ObjectProperty = ({ maxWidth = '100%', loading = false, rollups = [], + showCard = true, ...rest }) => { if (value && typeof value == 'function' && objectData) { @@ -733,7 +734,9 @@ const ObjectProperty = ({ /> ) case 'markdown': - return <MarkdownInput {...inputProps} /> + return ( + <MarkdownInput {...inputProps} size={size} showCard={showCard} /> + ) case 'id': // id is not editable, just show view mode if (value) { @@ -868,7 +871,8 @@ ObjectProperty.propTypes = { showSince: PropTypes.bool, loading: PropTypes.bool, rollups: PropTypes.arrayOf(PropTypes.object), - canAddRemove: PropTypes.bool + canAddRemove: PropTypes.bool, + showCard: PropTypes.bool } export default ObjectProperty diff --git a/src/components/Icons/BlockquoteIcon.jsx b/src/components/Icons/BlockquoteIcon.jsx new file mode 100644 index 0000000..e7861ee --- /dev/null +++ b/src/components/Icons/BlockquoteIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/blockquoteicon.svg?react' + +const BlockquoteIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default BlockquoteIcon diff --git a/src/components/Icons/BoldIcon.jsx b/src/components/Icons/BoldIcon.jsx new file mode 100644 index 0000000..c32e587 --- /dev/null +++ b/src/components/Icons/BoldIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/boldicon.svg?react' + +const BoldIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default BoldIcon diff --git a/src/components/Icons/BulletListIcon.jsx b/src/components/Icons/BulletListIcon.jsx new file mode 100644 index 0000000..00a8ca3 --- /dev/null +++ b/src/components/Icons/BulletListIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/bulletlisticon.svg?react' + +const BulletListIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default BulletListIcon diff --git a/src/components/Icons/ItalicIcon.jsx b/src/components/Icons/ItalicIcon.jsx new file mode 100644 index 0000000..7fdf600 --- /dev/null +++ b/src/components/Icons/ItalicIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/italicicon.svg?react' + +const ItalicIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default ItalicIcon diff --git a/src/components/Icons/NumberedListIcon.jsx b/src/components/Icons/NumberedListIcon.jsx new file mode 100644 index 0000000..7355219 --- /dev/null +++ b/src/components/Icons/NumberedListIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/numberedlisticon.svg?react' + +const NumberedListIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default NumberedListIcon diff --git a/src/components/Icons/TextFormatIcon.jsx b/src/components/Icons/TextFormatIcon.jsx new file mode 100644 index 0000000..9abbe77 --- /dev/null +++ b/src/components/Icons/TextFormatIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/textformaticon.svg?react' + +const TextFormatIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default TextFormatIcon diff --git a/src/components/Icons/TextIcon.jsx b/src/components/Icons/TextIcon.jsx new file mode 100644 index 0000000..62f5318 --- /dev/null +++ b/src/components/Icons/TextIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/texticon.svg?react' + +const TextIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default TextIcon diff --git a/src/components/Icons/UnderlineIcon.jsx b/src/components/Icons/UnderlineIcon.jsx new file mode 100644 index 0000000..c1de193 --- /dev/null +++ b/src/components/Icons/UnderlineIcon.jsx @@ -0,0 +1,6 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/underlineicon.svg?react' + +const UnderlineIcon = (props) => <Icon component={CustomIconSvg} {...props} /> + +export default UnderlineIcon diff --git a/vite.config.js b/vite.config.js index 78b0113..3fedfbb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -106,6 +106,11 @@ export default defineConfig({ ) { return 'tsparticles' } + + // --- Tiptap + if (id.includes('node_modules/@tiptap')) { + return 'tiptap' + } } } }