diff --git a/package.json b/package.json index 94f136d..6c042bf 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,15 @@ "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-table": "^8.21.3", + "@tiptap/extension-image": "^3.2.0", + "@tiptap/extension-link": "^3.2.0", + "@tiptap/extension-placeholder": "^3.2.0", + "@tiptap/extension-typography": "^3.2.0", + "@tiptap/extension-underline": "^3.2.0", + "@tiptap/pm": "^3.2.0", + "@tiptap/react": "^3.2.0", + "@tiptap/starter-kit": "^3.2.0", "@vitest/coverage-v8": "^2.1.3", "axios": "^1.7.7", "class-variance-authority": "^0.7.1", @@ -55,6 +64,7 @@ "sonner": "^2.0.6", "tailwind-merge": "^3.3.0", "tailwindcss-animate": "^1.0.7", + "turndown": "^7.2.1", "zod": "^3.24.4" }, "devDependencies": { @@ -68,6 +78,7 @@ "@types/node": "^20", "@types/react": "19.1.5", "@types/react-dom": "19.1.5", + "@types/turndown": "^5.0.5", "@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/parser": "^8.0.1", "@vitejs/plugin-react": "^4.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aae1e32..3dba909 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,33 @@ importers: "@radix-ui/react-tooltip": specifier: ^1.2.7 version: 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tanstack/react-table": + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tiptap/extension-image": + specifier: ^3.2.0 + version: 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-link": + specifier: ^3.2.0 + version: 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/extension-placeholder": + specifier: ^3.2.0 + version: 3.2.0(@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-typography": + specifier: ^3.2.0 + version: 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-underline": + specifier: ^3.2.0 + version: 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/pm": + specifier: ^3.2.0 + version: 3.2.0 + "@tiptap/react": + specifier: ^3.2.0 + version: 3.2.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + "@tiptap/starter-kit": + specifier: ^3.2.0 + version: 3.2.0 "@vitest/coverage-v8": specifier: ^2.1.3 version: 2.1.9(vitest@2.1.9(@types/node@20.19.9)(jsdom@25.0.1)(lightningcss@1.30.1)) @@ -116,6 +143,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@4.1.11) + turndown: + specifier: ^7.2.1 + version: 7.2.1 zod: specifier: ^3.24.4 version: 3.25.76 @@ -150,6 +180,9 @@ importers: "@types/react-dom": specifier: 19.1.5 version: 19.1.5(@types/react@19.1.5) + "@types/turndown": + specifier: ^5.0.5 + version: 5.0.5 "@typescript-eslint/eslint-plugin": specifier: ^8.0.1 version: 8.39.0(@typescript-eslint/parser@8.39.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) @@ -821,6 +854,10 @@ packages: "@types/react": 19.1.5 react: ">=16" + "@mixmark-io/domino@2.2.0": + resolution: + { integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw== } + "@napi-rs/wasm-runtime@0.2.12": resolution: { integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== } @@ -1342,6 +1379,10 @@ packages: resolution: { integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw== } + "@remirror/core-constants@3.0.0": + resolution: + { integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== } + "@rolldown/pluginutils@1.0.0-beta.27": resolution: { integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA== } @@ -1627,6 +1668,19 @@ packages: peerDependencies: tailwindcss: ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + "@tanstack/react-table@8.21.3": + resolution: + { integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== } + engines: { node: ">=12" } + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + + "@tanstack/table-core@8.21.3": + resolution: + { integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== } + engines: { node: ">=12" } + "@testing-library/dom@10.4.1": resolution: { integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg== } @@ -1660,6 +1714,199 @@ packages: peerDependencies: "@testing-library/dom": ">=7.21.4" + "@tiptap/core@3.2.0": + resolution: + { integrity: sha512-1Dk1AIwzJejcyi4FOEcKoBSLMkHMfxeDiQ0daG51eS1IZ1ZSF+Xlhg9OD5sAjbUTGQjK1HfU6kGwS9wJcR/9ZQ== } + peerDependencies: + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-blockquote@3.2.0": + resolution: + { integrity: sha512-ivOvkzkpj/+1i6cZwUr1M+T/knci2XMl8NW/Q6g70Kx2TinEoFjOGnDi34j1mVznyzhxVlY9jm3Xvv4s2+Ni/w== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-bold@3.2.0": + resolution: + { integrity: sha512-YuwK/muBzc0r7VFV0CcnF5Otg90zfUW2D5x+KpSF0e9bUBltSvitHgRaKWFwbPAgXYwkh6+ftxEazbXeqyTv8Q== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-bubble-menu@3.2.0": + resolution: + { integrity: sha512-ZTE/h7Bv/dWrnEdUcvff79aEsPSYnx7s311z5xFKmNaIg8OaSEypCNKav7+2ttSBt03HcSCm7cRUY0b8yY2Y+w== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-bullet-list@3.2.0": + resolution: + { integrity: sha512-xfQp2tpotSQ6pU627j20Bu+Ahgvjqe3uMVmczqjJ3V9Ze9xbiKFKJ2foC6mjQ1gfEGXkUE4AL5mVRBCWcfK8oQ== } + peerDependencies: + "@tiptap/extension-list": ^3.2.0 + + "@tiptap/extension-code-block@3.2.0": + resolution: + { integrity: sha512-rGkwir451oAegeTMZw0h0JwfLfbexrgXDWw5eUpQ32/5M3n07HU2+EamTSKt7VW76AxdlehEfsd7yo+FkuVlWA== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-code@3.2.0": + resolution: + { integrity: sha512-8eUeWzT0YNxvT9yh6p9mRziUmZjiRqHeQhHbcRYDM7gdFKb0mgsWPxiaonns0sdj7WW1vP6g0M80PC+HYCIO+g== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-document@3.2.0": + resolution: + { integrity: sha512-2SQoew2HtOwuORpk8l7NjKDzsCBotMsij9vRSTp4nwoa4ZFGwmlVMsNIaKq/E/GuS7oiejmUUROsiD3w1stPKw== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-dropcursor@3.2.0": + resolution: + { integrity: sha512-fyTfePUfyfD/s+3BauAkhJh+3HvQ3hI/mblbNKHAi9u+QqlB+a0W04I027XWag8F76DTp2clGkZunlxHYHas4g== } + peerDependencies: + "@tiptap/extensions": ^3.2.0 + + "@tiptap/extension-floating-menu@3.2.0": + resolution: + { integrity: sha512-CZJztT6eC8QKNwx+89PeJGhxSsGeU04/fPS18/y2DP4bEumrfTEq7jbtholJsLqErpghOdmm0XS+byYI6Dk9pg== } + peerDependencies: + "@floating-ui/dom": ^1.0.0 + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-gapcursor@3.2.0": + resolution: + { integrity: sha512-LUElisCLru8kAOMtihJ6nRbJ+Fml2S8GOhiry3GBRRdMpGT+Hzy2pAd9TsuNsyIdqKH+yM1lLRyquW7maaNc5g== } + peerDependencies: + "@tiptap/extensions": ^3.2.0 + + "@tiptap/extension-hard-break@3.2.0": + resolution: + { integrity: sha512-97CFsZK5vMHoWQzAP3thKD86dFDXqagXMs7vIKVK+SErwC3eR5KXmpvS5v5hi+iNs0I8+c+es6km+ZNMaUudKA== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-heading@3.2.0": + resolution: + { integrity: sha512-bgvRBeqrVZ9rnzOrlLVsr+2S6UR3m9SAPqkdPUr0eFeC2MnaK6FVapiOx1HoQRNyGY7Cw7QBZQVsWhYmEfJpbQ== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-horizontal-rule@3.2.0": + resolution: + { integrity: sha512-ttMzFO/WoRVpqfhDAZyIg1nBxgkDOXy1thE6xpfSQAEi0m7zbVEkp4OkATgt0meM69YNxJaEyzQjGztybMBgBw== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-image@3.2.0": + resolution: + { integrity: sha512-1jFWdrMXkWDfCeM4857wUd0pe6eCeDRmEJgtuZkfdvUALNYdyADdX6cWUjC7JBG8zGYFmp2XUryLQGHjNTB+iw== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-italic@3.2.0": + resolution: + { integrity: sha512-5OTX5Z3h7zrKcjj0snE8y+2a8dZ5G6GdZKVI1tedeDBfyUFqMSkvTZ/ygYenJ/zRkMvC9gz337ZU872IWx+ung== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-link@3.2.0": + resolution: + { integrity: sha512-ye9n73aFv9q/Adv6clFgV00hif7/rhsYf8gXpwmq5So/4teyOC7yp8Fg77CG2r8vZNhiJviZq6U6jlHVIBeTnw== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-list-item@3.2.0": + resolution: + { integrity: sha512-v4cvZEWO9jQGCYUCJughMOpu5c0+9mjieTemwdF+Dd2q16WLonFx7+4YJkymB38YDaUi6dO1HFRtMKuGQRxlsg== } + peerDependencies: + "@tiptap/extension-list": ^3.2.0 + + "@tiptap/extension-list-keymap@3.2.0": + resolution: + { integrity: sha512-R9r6CJpuwCFrfRQ+mU+m9vyujMjnu6C10J+8engQyvVtkc4KrgnHcABoyCkDJcZe36KRgz7mL0T6wqCezPD9lQ== } + peerDependencies: + "@tiptap/extension-list": ^3.2.0 + + "@tiptap/extension-list@3.2.0": + resolution: + { integrity: sha512-/3ZL9MpgDnEOv8tPwFwW3ct/TBbxZg3r3rN9noLHOR+YgiZKwbpMt9pDSJGu8y++rzT3vIHVmRRp0xFOXBvptQ== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/extension-ordered-list@3.2.0": + resolution: + { integrity: sha512-ditrS5HVX3ugx8R/4h2cHl8KOSnF1/T3caVvLpPccgbbTJNC8WldHJK/VvX/OusDO9DmbCMQiC8vIAagHMInhg== } + peerDependencies: + "@tiptap/extension-list": ^3.2.0 + + "@tiptap/extension-paragraph@3.2.0": + resolution: + { integrity: sha512-RQ6mu06nVP9xEVULE64oWH9MT92mQhHVwJpnkYQuc7XEe3KgPYVbUmaLWMzJbjBOnAhTBC4k37Wwk0JKiTUBNQ== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-placeholder@3.2.0": + resolution: + { integrity: sha512-+yswlhZDJrCad2VXYe2NOZ9miMHFuIT7jGnKRCjIWiGpnRgB0FBSI7fjVBl1HmCDaSdVd+p0fOMHLTGYO5Fkdw== } + peerDependencies: + "@tiptap/extensions": ^3.2.0 + + "@tiptap/extension-strike@3.2.0": + resolution: + { integrity: sha512-gIfaIJmvQNBxjvtJfNQ8LhvWuGEFVFDMJxLa80speY4ngsDYZ3K8Ea4+0FTctKJVIXEZ5cDoHZR96ELkQMPzWA== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-text@3.2.0": + resolution: + { integrity: sha512-VDQPjVr6zd3MGh8+xPIIj+fuIhnws9tpAmP7jvBep+0tL7P2RYpN2NSj0zqeStHPcGfv9iMuXEuuMkEsi5gIZg== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-typography@3.2.0": + resolution: + { integrity: sha512-6q8hcj+K/EU4WGmHuxwLcGeEnQ1zXh5QQLH3pgT1r/iDtZTdWjLBabZxJkTP7xBOfAH/xoQj+sRbuWeKBtf+xQ== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extension-underline@3.2.0": + resolution: + { integrity: sha512-0ObBGqgeOAhQpNdNRJcu23GVQodtVp5dBYNC9vSW+mcJPRQy3Sc+kv59pqyrH0qV4SwtzzeKQSHuNaQpMPOU5A== } + peerDependencies: + "@tiptap/core": ^3.2.0 + + "@tiptap/extensions@3.2.0": + resolution: + { integrity: sha512-voEvFhStH1fxw6PF5Hb7kj7PH+BMYM3d0S6d/V0bNdGB7mMsILruXkXIsxvP8Dkskv07Xmfs+iU6pdVy0hWD7Q== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.0 + + "@tiptap/pm@3.2.0": + resolution: + { integrity: sha512-2UTlVts1Q7snQaUlxUtXfwEndNgcBcEIf74f1aEzcLzJRwBRRexCS6M0n+F50IifoB0OVBZENMBv1X/YXIPGPA== } + + "@tiptap/react@3.2.0": + resolution: + { integrity: sha512-rsro1+1yP3vLJ+YAQpWhJv/WftBr3hCGTfK6168jeX5INSjulUgALpA57o4rzp3LoNDwKreWu9iSp1JS4rsjFA== } + peerDependencies: + "@tiptap/core": ^3.2.0 + "@tiptap/pm": ^3.2.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.2.0": + resolution: + { integrity: sha512-hcA12kqxVz1ypNqXhTUStQHWmDFmd2nm8NPmQmepK5T/T2FVXhVhhGSN8KsCR8sYFr3TwOk9utszxQLkT4AwOA== } + "@tsconfig/node10@1.0.11": resolution: { integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== } @@ -1724,10 +1971,22 @@ packages: resolution: { integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== } + "@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/mdx@2.0.13": resolution: { integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== } @@ -1750,6 +2009,10 @@ packages: resolution: { integrity: sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g== } + "@types/turndown@5.0.5": + resolution: + { integrity: sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w== } + "@types/unist@2.0.11": resolution: { integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== } @@ -1758,6 +2021,10 @@ packages: resolution: { integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== } + "@types/use-sync-external-store@0.0.6": + resolution: + { integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== } + "@typescript-eslint/eslint-plugin@8.39.0": resolution: { integrity: sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw== } @@ -2344,6 +2611,10 @@ packages: resolution: { integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== } + crelt@1.0.6: + resolution: + { integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== } + cross-spawn@7.0.6: resolution: { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } @@ -2536,6 +2807,11 @@ packages: { integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ== } engines: { node: ">=10.13.0" } + entities@4.5.0: + resolution: + { integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== } + engines: { node: ">=0.12" } + entities@6.0.1: resolution: { integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== } @@ -3547,6 +3823,14 @@ packages: { integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== } engines: { node: ">=14" } + linkify-it@5.0.0: + resolution: + { integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== } + + linkifyjs@4.3.2: + resolution: + { integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA== } + lint-staged@15.5.2: resolution: { integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w== } @@ -3638,6 +3922,11 @@ packages: { integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== } engines: { node: ">=16" } + markdown-it@14.1.0: + resolution: + { integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== } + hasBin: true + markdown-table@3.0.4: resolution: { integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== } @@ -3711,6 +4000,10 @@ packages: resolution: { integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== } + mdurl@2.0.0: + resolution: + { integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== } + merge-stream@2.0.0: resolution: { integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== } @@ -4094,6 +4387,10 @@ packages: { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } engines: { node: ">= 0.8.0" } + 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== } @@ -4294,10 +4591,91 @@ packages: resolution: { integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== } + prosemirror-changeset@2.3.1: + resolution: + { integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ== } + + 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.3.2: + resolution: + { integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ== } + + prosemirror-history@1.4.1: + resolution: + { integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ== } + + prosemirror-inputrules@1.5.0: + resolution: + { integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA== } + + prosemirror-keymap@1.2.3: + resolution: + { integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw== } + + prosemirror-markdown@1.13.2: + resolution: + { integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g== } + + prosemirror-menu@1.2.5: + resolution: + { integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ== } + + prosemirror-model@1.25.3: + resolution: + { integrity: sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA== } + + 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.3: + resolution: + { integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q== } + + prosemirror-tables@1.7.1: + resolution: + { integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q== } + + 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.10.4: + resolution: + { integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw== } + + prosemirror-view@1.40.1: + resolution: + { integrity: sha512-pbwUjt3G7TlsQQHDiYSupWBhJswpLVB09xXm1YiJPdkjkh9Pe7Y51XdLh5VWIZmROLY8UpUpG03lkdhm9lzIBA== } + proxy-from-env@1.1.0: resolution: { integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== } + 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== } @@ -4497,6 +4875,10 @@ packages: engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true + rope-sequence@1.3.4: + resolution: + { integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ== } + rrweb-cssom@0.7.1: resolution: { integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== } @@ -4910,6 +5292,10 @@ packages: resolution: { integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== } + turndown@7.2.1: + resolution: + { integrity: sha512-7YiPJw6rLClQL3oUKN3KgMaXeJJ2lAyZItclgKDurqnH61so4k4IH/qwmMva0zpuJc/FhRExBBnk7EbeFANlgQ== } + tw-animate-css@1.3.6: resolution: { integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA== } @@ -4950,6 +5336,10 @@ packages: engines: { node: ">=14.17" } hasBin: true + uc.micro@2.1.0: + resolution: + { integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== } + unbox-primitive@1.1.0: resolution: { integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== } @@ -5136,6 +5526,10 @@ packages: jsdom: optional: true + w3c-keyname@2.2.8: + resolution: + { integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== } + w3c-xmlserializer@5.0.0: resolution: { integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== } @@ -5762,6 +6156,8 @@ snapshots: "@types/react": 19.1.5 react: 19.1.0 + "@mixmark-io/domino@2.2.0": {} + "@napi-rs/wasm-runtime@0.2.12": dependencies: "@emnapi/core": 1.4.5 @@ -6196,6 +6592,8 @@ snapshots: "@radix-ui/rect@1.1.1": {} + "@remirror/core-constants@3.0.0": {} + "@rolldown/pluginutils@1.0.0-beta.27": {} "@rollup/rollup-android-arm-eabi@4.46.2": @@ -6385,6 +6783,14 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.1.11 + "@tanstack/react-table@8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@tanstack/table-core": 8.21.3 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + "@tanstack/table-core@8.21.3": {} + "@testing-library/dom@10.4.1": dependencies: "@babel/code-frame": 7.27.1 @@ -6420,6 +6826,193 @@ snapshots: dependencies: "@testing-library/dom": 10.4.1 + "@tiptap/core@3.2.0(@tiptap/pm@3.2.0)": + dependencies: + "@tiptap/pm": 3.2.0 + + "@tiptap/extension-blockquote@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-bold@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-bubble-menu@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@floating-ui/dom": 1.7.3 + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + optional: true + + "@tiptap/extension-bullet-list@3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extension-list": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-code-block@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + + "@tiptap/extension-code@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-document@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-dropcursor@3.2.0(@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extensions": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-floating-menu@3.2.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@floating-ui/dom": 1.7.3 + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + optional: true + + "@tiptap/extension-gapcursor@3.2.0(@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extensions": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-hard-break@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-heading@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-horizontal-rule@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + + "@tiptap/extension-image@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-italic@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-link@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + linkifyjs: 4.3.2 + + "@tiptap/extension-list-item@3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extension-list": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-list-keymap@3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extension-list": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + + "@tiptap/extension-ordered-list@3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extension-list": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-paragraph@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-placeholder@3.2.0(@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/extensions": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + + "@tiptap/extension-strike@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-text@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-typography@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extension-underline@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + + "@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + + "@tiptap/pm@3.2.0": + dependencies: + prosemirror-changeset: 2.3.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.1 + prosemirror-inputrules: 1.5.0 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.2 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.3 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.7.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1) + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + "@tiptap/react@3.2.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + "@types/use-sync-external-store": 0.0.6 + fast-deep-equal: 3.1.3 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + optionalDependencies: + "@tiptap/extension-bubble-menu": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/extension-floating-menu": 3.2.0(@floating-ui/dom@1.7.3)(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + transitivePeerDependencies: + - "@floating-ui/dom" + + "@tiptap/starter-kit@3.2.0": + dependencies: + "@tiptap/core": 3.2.0(@tiptap/pm@3.2.0) + "@tiptap/extension-blockquote": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-bold": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-bullet-list": 3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-code": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-code-block": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/extension-document": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-dropcursor": 3.2.0(@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-gapcursor": 3.2.0(@tiptap/extensions@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-hard-break": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-heading": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-horizontal-rule": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/extension-italic": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-link": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/extension-list": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/extension-list-item": 3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-list-keymap": 3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-ordered-list": 3.2.0(@tiptap/extension-list@3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0)) + "@tiptap/extension-paragraph": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-strike": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-text": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extension-underline": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0)) + "@tiptap/extensions": 3.2.0(@tiptap/core@3.2.0(@tiptap/pm@3.2.0))(@tiptap/pm@3.2.0) + "@tiptap/pm": 3.2.0 + "@tsconfig/node10@1.0.11": {} "@tsconfig/node12@1.0.11": {} @@ -6474,10 +7067,19 @@ snapshots: "@types/json5@0.0.29": {} + "@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/mdx@2.0.13": {} "@types/ms@2.1.0": {} @@ -6494,10 +7096,14 @@ snapshots: dependencies: csstype: 3.1.3 + "@types/turndown@5.0.5": {} + "@types/unist@2.0.11": {} "@types/unist@3.0.3": {} + "@types/use-sync-external-store@0.0.6": {} + "@typescript-eslint/eslint-plugin@8.39.0(@typescript-eslint/parser@8.39.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)": dependencies: "@eslint-community/regexpp": 4.12.1 @@ -6999,6 +7605,8 @@ snapshots: create-require@1.1.1: {} + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7134,6 +7742,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 + entities@4.5.0: {} + entities@6.0.1: {} environment@1.1.0: {} @@ -8200,6 +8810,12 @@ snapshots: lilconfig@3.1.3: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + lint-staged@15.5.2: dependencies: chalk: 5.5.0 @@ -8282,6 +8898,15 @@ snapshots: markdown-extensions@2.0.0: {} + markdown-it@14.1.0: + 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: {} math-intrinsics@1.1.0: {} @@ -8449,6 +9074,8 @@ snapshots: dependencies: "@types/mdast": 4.0.4 + mdurl@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -8909,6 +9536,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -9013,8 +9642,113 @@ snapshots: property-information@7.1.0: {} + prosemirror-changeset@2.3.1: + dependencies: + prosemirror-transform: 1.10.4 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.3 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + prosemirror-gapcursor@1.3.2: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.1 + + prosemirror-history@1.4.1: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.0: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.2: + dependencies: + "@types/markdown-it": 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.25.3 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.4.1 + prosemirror-state: 1.4.3 + + prosemirror-model@1.25.3: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.3 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-state@1.4.3: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + prosemirror-tables@1.7.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.40.1 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.1): + dependencies: + "@remirror/core-constants": 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-view: 1.40.1 + + prosemirror-transform@1.10.4: + dependencies: + prosemirror-model: 1.25.3 + + prosemirror-view@1.40.1: + dependencies: + prosemirror-model: 1.25.3 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + proxy-from-env@1.1.0: {} + punycode.js@2.3.1: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -9247,6 +9981,8 @@ snapshots: "@rollup/rollup-win32-x64-msvc": 4.46.2 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + rrweb-cssom@0.7.1: {} rrweb-cssom@0.8.0: {} @@ -9637,6 +10373,10 @@ snapshots: tslib@2.8.1: {} + turndown@7.2.1: + dependencies: + "@mixmark-io/domino": 2.2.0 + tw-animate-css@1.3.6: {} type-check@0.4.0: @@ -9680,6 +10420,8 @@ snapshots: typescript@5.9.2: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -9889,6 +10631,8 @@ snapshots: - supports-color - terser + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 diff --git a/src/app/[locale]/(public)/(user)/my-events/page.tsx b/src/app/[locale]/(public)/(user)/my-events/page.tsx index a973725..c82c65e 100644 --- a/src/app/[locale]/(public)/(user)/my-events/page.tsx +++ b/src/app/[locale]/(public)/(user)/my-events/page.tsx @@ -1,4 +1,4 @@ -import MyEventPage from "@/features/user/my-events"; +import { UserEventPage } from "@/features/events/pages"; interface MyEventsPageProps { searchParams: Promise<{ @@ -10,5 +10,5 @@ interface MyEventsPageProps { export default async function MyEventsPage({ searchParams }: MyEventsPageProps) { const { page, limit } = await searchParams; - return ; + return ; } diff --git a/src/app/[locale]/(public)/(user)/profile/page.tsx b/src/app/[locale]/(public)/(user)/profile/page.tsx index 5b2aa6e..a6f7456 100644 --- a/src/app/[locale]/(public)/(user)/profile/page.tsx +++ b/src/app/[locale]/(public)/(user)/profile/page.tsx @@ -1,5 +1,5 @@ -import ProfilePage from "@/features/user/profile"; +import { UserProfilePage } from "@/features/profile/pages"; export default async function Profile() { - return ; + return ; } diff --git a/src/app/[locale]/(public)/events/[id]/page.tsx b/src/app/[locale]/(public)/events/[id]/page.tsx index c85bc9c..2b9ca19 100644 --- a/src/app/[locale]/(public)/events/[id]/page.tsx +++ b/src/app/[locale]/(public)/events/[id]/page.tsx @@ -1,8 +1,8 @@ -import { EventDetailPage } from "@/features/events"; +import { PublicEventDetailPage } from "@/features/events/pages"; const EventsDetail = async (props: { params: Promise<{ id: string }> }) => { const params = await props.params; - return ; + return ; }; export default EventsDetail; diff --git a/src/app/[locale]/(public)/events/page.tsx b/src/app/[locale]/(public)/events/page.tsx index 1381b65..77314c0 100644 --- a/src/app/[locale]/(public)/events/page.tsx +++ b/src/app/[locale]/(public)/events/page.tsx @@ -1,7 +1,7 @@ -import { EventListPage } from "@/features/events"; +import { PublicEventListPage } from "@/features/events/pages"; const EventList = () => { - return ; + return ; }; export default EventList; diff --git a/src/app/[locale]/admin/events/[eventId]/edit/page.tsx b/src/app/[locale]/admin/events/[eventId]/edit/page.tsx deleted file mode 100644 index 437e901..0000000 --- a/src/app/[locale]/admin/events/[eventId]/edit/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -interface EditEventPageProps { - params: Promise<{ eventId: string }>; -} - -const EditEventPage = async ({ params }: EditEventPageProps) => { - const p = await params; - return ( -
-

Edit Event {p.eventId}

-
- ); -}; - -export default EditEventPage; diff --git a/src/app/[locale]/admin/events/[eventId]/page.tsx b/src/app/[locale]/admin/events/[eventId]/page.tsx new file mode 100644 index 0000000..a566e37 --- /dev/null +++ b/src/app/[locale]/admin/events/[eventId]/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +const EventDetail = () => { + return
; +}; + +export default EventDetail; diff --git a/src/app/[locale]/admin/events/create/page.tsx b/src/app/[locale]/admin/events/create/page.tsx new file mode 100644 index 0000000..a69f2ab --- /dev/null +++ b/src/app/[locale]/admin/events/create/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { AdminEventsCreatePage } from "@/features/events/pages"; + +const CreateEventPage = () => { + return ; +}; + +export default CreateEventPage; diff --git a/src/app/[locale]/admin/events/page.tsx b/src/app/[locale]/admin/events/page.tsx index 5b24d2c..f4afd81 100644 --- a/src/app/[locale]/admin/events/page.tsx +++ b/src/app/[locale]/admin/events/page.tsx @@ -1,23 +1,9 @@ "use client"; -import { useEvents } from "@/features/events/hooks/useEvent"; -import Link from "next/link"; +import { AdminEventsListPage } from "@/features/events/pages"; const EventListPage = () => { - const { events, isLoading } = useEvents(); - return ( -
-

Event List

- {isLoading &&

Fetching events...

} -
    - {events.map((ev) => ( -
  • - {ev.title} Edit -
  • - ))} -
-
- ); + return ; }; export default EventListPage; diff --git a/src/components/common/TableData/TableData.tsx b/src/components/common/TableData/TableData.tsx new file mode 100644 index 0000000..3b2c571 --- /dev/null +++ b/src/components/common/TableData/TableData.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { useReactTable, getCoreRowModel, flexRender, ColumnDef } from "@tanstack/react-table"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/Table"; +import { TablePagination } from "./TablePagination"; +import { TableToolbar } from "./TableToolbar"; + +interface TableDataProps { + data: T[]; + columns: ColumnDef[]; + searchable?: boolean; + searchPlaceholder?: string; + itemsPerPage?: number; + className?: string; + rightAction?: React.ReactNode; +} + +function TableData({ + data, + columns, + searchable = false, + searchPlaceholder = "Search...", + itemsPerPage = 5, + className, + rightAction, +}: TableDataProps) { + const [globalFilter, setGlobalFilter] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + + const filteredData = useMemo(() => { + if (!searchable || !globalFilter) return data; + + return data.filter((item: T) => + Object.values(item as Record).some((value) => + value?.toString().toLowerCase().includes(globalFilter.toLowerCase()) + ) + ); + }, [data, globalFilter, searchable]); + + const totalPages = Math.ceil(filteredData.length / itemsPerPage); + + const paginatedData = useMemo(() => { + const startIndex = (currentPage - 1) * itemsPerPage; + return filteredData.slice(startIndex, startIndex + itemsPerPage); + }, [filteredData, currentPage, itemsPerPage]); + + const table = useReactTable({ + data: paginatedData, + columns, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + }); + + return ( +
+ { + setGlobalFilter(value); + setCurrentPage(1); + }} + rightAction={rightAction} + /> + +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {table.getRowModel().rows.length === 0 ? ( + + + No data found. + + + ) : ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + )} + +
+
+ + {totalPages > 1 && ( +
+
+ {currentPage} of {totalPages} pages +
+ + +
+ )} +
+ ); +} + +export default TableData; diff --git a/src/components/common/TableData/TablePagination.tsx b/src/components/common/TableData/TablePagination.tsx new file mode 100644 index 0000000..0be7a13 --- /dev/null +++ b/src/components/common/TableData/TablePagination.tsx @@ -0,0 +1,97 @@ +"use client"; +import React from "react"; +import { + Pagination as UIPagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, + PaginationEllipsis, +} from "@/components/ui/Pagination"; +import { buttonVariants } from "@/components/ui/Button"; +import { cn } from "@/lib/utils"; +import { usePagination } from "@/components/hooks/UsePagination"; + +interface TablePaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + className?: string; +} + +export const TablePagination: React.FC = ({ + currentPage, + totalPages, + onPageChange, + className, +}) => { + const { hasNextPage, hasPrevPage, nextPage, prevPage, pages } = usePagination({ + currentPage, + totalPages, + }); + + if (totalPages <= 1) { + return null; + } + + return ( +
+ + + + hasPrevPage && onPageChange(prevPage!)} + className={cn( + "bg-secondary text-secondary-foreground cursor-pointer", + !hasPrevPage && "cursor-not-allowed opacity-50" + )} + size="icon" + /> + + + {pages.map((page, index) => { + if (page === "ellipsis") { + return ( + + + + ); + } + + const isActive = page === currentPage; + + return ( + + onPageChange(page as number)} + isActive={isActive} + className={cn("cursor-pointer", { + [buttonVariants({ + variant: "default", + className: "bg-hmc-base-blue hover:bg-hmc-base-blue text-white !shadow-none hover:text-white", + })]: isActive, + "bg-secondary text-secondary-foreground": !isActive, + })} + > + {page} + + + ); + })} + + + hasNextPage && onPageChange(nextPage!)} + className={cn( + "bg-secondary text-secondary-foreground cursor-pointer", + !hasNextPage && "cursor-not-allowed opacity-50" + )} + size="icon" + /> + + + +
+ ); +}; diff --git a/src/components/common/TableData/TableToolbar.tsx b/src/components/common/TableData/TableToolbar.tsx new file mode 100644 index 0000000..ff669ca --- /dev/null +++ b/src/components/common/TableData/TableToolbar.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { Input } from "@/components/ui/Input"; +import { Search } from "lucide-react"; + +interface TableToolbarProps { + searchable?: boolean; + searchPlaceholder?: string; + searchValue?: string; + onSearchChange?: (value: string) => void; + rightAction?: React.ReactNode; +} + +export const TableToolbar = ({ + searchable = false, + searchPlaceholder = "Search...", + searchValue = "", + onSearchChange, + rightAction, +}: TableToolbarProps) => { + return ( +
+ {/* Search Section */} +
+ {searchable && ( +
+ + onSearchChange?.(e.target.value)} + className="pl-10" + /> +
+ )} +
+ + {/* Right Action Section */} + {rightAction &&
{rightAction}
} +
+ ); +}; diff --git a/src/components/common/TableData/index.ts b/src/components/common/TableData/index.ts new file mode 100644 index 0000000..2a6263a --- /dev/null +++ b/src/components/common/TableData/index.ts @@ -0,0 +1,3 @@ +export { default } from "./TableData"; +export { TableToolbar } from "./TableToolbar"; +export { TablePagination } from "./TablePagination"; diff --git a/src/components/common/TextEditor/TextEditor.tsx b/src/components/common/TextEditor/TextEditor.tsx new file mode 100644 index 0000000..ad54823 --- /dev/null +++ b/src/components/common/TextEditor/TextEditor.tsx @@ -0,0 +1,190 @@ +"use client"; + +import { useEditor, EditorContent } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import Image from "@tiptap/extension-image"; +import Underline from "@tiptap/extension-underline"; +import Typography from "@tiptap/extension-typography"; +import Link from "@tiptap/extension-link"; +import Placeholder from "@tiptap/extension-placeholder"; +import { useState, useCallback, useMemo } from "react"; +import TurndownService from "turndown"; + +import { Button } from "@/components/ui/Button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card"; +import { Toolbar } from "@/components/common/TextEditor"; + +interface TextEditorProps { + markdownOutput?: boolean; +} + +const TextEditor = ({ markdownOutput = false }: TextEditorProps) => { + const [markdownContent, setMarkdownContent] = useState(""); + + const turndownService = useMemo(() => { + const service = new TurndownService({ + headingStyle: "atx", + codeBlockStyle: "fenced", + }); + + // Custom rule for underline tags + service.addRule("underline", { + filter: "u", + replacement: (content) => `${content}`, + }); + + return service; + }, []); + + const editor = useEditor({ + extensions: [ + StarterKit, + Underline, + Typography, + Placeholder.configure({ + placeholder: "Start typing your content here... Use the toolbar above to format your text.", + }), + Link.configure({ + openOnClick: false, + HTMLAttributes: { + class: "text-primary underline cursor-pointer", + }, + }), + Image.configure({ + inline: false, + allowBase64: true, + }), + ], + content: "", + immediatelyRender: false, + onCreate: ({ editor }) => { + const html = editor.getHTML(); + setMarkdownContent(turndownService.turndown(html)); + }, + onUpdate: ({ editor }) => { + const html = editor.getHTML(); + setMarkdownContent(turndownService.turndown(html)); + }, + editorProps: { + attributes: { + class: + "prose dark:prose-invert max-w-none mx-auto focus:outline-none min-h-[300px] p-3 prose-blockquote:border-primary prose-blockquote:bg-muted/50 prose-blockquote:pl-4 prose-blockquote:py-1 prose-blockquote:before:content-none prose-blockquote:not-italic prose-code:bg-muted prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none prose-pre:bg-muted prose-pre:border prose-pre:text-foreground", + }, + }, + }); + + const addImage = useCallback(() => { + const url = window.prompt("Enter image URL:"); + + if (url && editor) { + editor.chain().focus().setImage({ src: url }).run(); + } + }, [editor]); + + const addImageFromFile = useCallback(() => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + + if (file && editor) { + const reader = new FileReader(); + reader.onload = (e) => { + const src = e.target?.result as string; + editor.chain().focus().setImage({ src }).run(); + }; + reader.readAsDataURL(file); + } + }; + + input.click(); + }, [editor]); + + const downloadMarkdown = useCallback(() => { + const blob = new Blob([markdownContent], { type: "text/markdown" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "document.md"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, [markdownContent]); + + const addLink = useCallback(() => { + if (!editor) return; + + const previousUrl = editor.getAttributes("link").href; + const url = window.prompt("Enter URL", previousUrl || ""); + + if (url === null) { + return; + } + + if (url === "") { + editor.chain().focus().extendMarkRange("link").unsetLink().run(); + return; + } + + editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run(); + }, [editor]); + + if (!editor) { + return ( +
+
+
+ ); + } + + return ( +
+ + + + + + +
+ +
+
+
+ + {markdownOutput && ( + + + Markdown Output + + +
+
+                {markdownContent || "No content yet..."}
+              
+ +
+
+
+ )} +
+ ); +}; + +export default TextEditor; diff --git a/src/components/common/TextEditor/Toolbar.tsx b/src/components/common/TextEditor/Toolbar.tsx new file mode 100644 index 0000000..b1ded14 --- /dev/null +++ b/src/components/common/TextEditor/Toolbar.tsx @@ -0,0 +1,199 @@ +import { Editor } from "@tiptap/react"; +import { + Bold, + Italic, + Underline as UnderlineIcon, + Heading1, + Heading2, + Heading3, + List, + ListOrdered, + Image as ImageIcon, + Upload, + Quote, + Code, + Code2, + Undo, + Redo, + Download, + ExternalLink, +} from "lucide-react"; +import { Separator } from "@/components/ui/Separator"; +import { ToolbarButton } from "@/components/common/TextEditor"; + +interface ToolbarProps { + editor: Editor; + onAddImage: () => void; + onAddImageFromFile: () => void; + onAddLink: () => void; + onDownloadMarkdown: () => void; + isDownloadDisabled: boolean; +} + +const Toolbar = ({ + editor, + onAddImage, + onAddImageFromFile, + onAddLink, + onDownloadMarkdown, + isDownloadDisabled, +}: ToolbarProps) => { + return ( +
+ {/* Text Formatting */} +
+ editor.chain().focus().toggleBold().run()} + isActive={editor.isActive("bold")} + title="Bold (Ctrl+B)" + > + + + + editor.chain().focus().toggleItalic().run()} + isActive={editor.isActive("italic")} + title="Italic (Ctrl+I)" + > + + + + editor.chain().focus().toggleUnderline().run()} + isActive={editor.isActive("underline")} + title="Underline (Ctrl+U)" + > + + +
+ + + + {/* Headings */} +
+ editor.chain().focus().toggleHeading({ level: 1 }).run()} + isActive={editor.isActive("heading", { level: 1 })} + title="Heading 1" + > + + + + editor.chain().focus().toggleHeading({ level: 2 }).run()} + isActive={editor.isActive("heading", { level: 2 })} + title="Heading 2" + > + + + + editor.chain().focus().toggleHeading({ level: 3 }).run()} + isActive={editor.isActive("heading", { level: 3 })} + title="Heading 3" + > + + +
+ + + + {/* Lists */} +
+ editor.chain().focus().toggleBulletList().run()} + isActive={editor.isActive("bulletList")} + title="Bullet List" + > + + + + editor.chain().focus().toggleOrderedList().run()} + isActive={editor.isActive("orderedList")} + title="Numbered List" + > + + +
+ + + + {/* Images & Links */} +
+ + + + + + + + + + + +
+ + + + {/* Other Actions */} +
+ editor.chain().focus().toggleBlockquote().run()} + isActive={editor.isActive("blockquote")} + title="Quote" + > + + + + editor.chain().focus().toggleCode().run()} + isActive={editor.isActive("code")} + title="Inline Code" + > + + + + editor.chain().focus().toggleCodeBlock().run()} + isActive={editor.isActive("codeBlock")} + title="Code Block" + > + + +
+ + + + {/* Undo/Redo */} +
+ editor.chain().focus().undo().run()} + disabled={!editor.can().chain().focus().undo().run()} + title="Undo (Ctrl+Z)" + > + + + + editor.chain().focus().redo().run()} + disabled={!editor.can().chain().focus().redo().run()} + title="Redo (Ctrl+Y)" + > + + +
+ + + + {/* Export */} +
+ + + +
+
+ ); +}; + +export default Toolbar; diff --git a/src/components/common/TextEditor/ToolbarButton.tsx b/src/components/common/TextEditor/ToolbarButton.tsx new file mode 100644 index 0000000..1250766 --- /dev/null +++ b/src/components/common/TextEditor/ToolbarButton.tsx @@ -0,0 +1,25 @@ +import { Button } from "@/components/ui/Button"; + +interface ToolbarButtonProps { + onClick: () => void; + isActive?: boolean; + disabled?: boolean; + children: React.ReactNode; + title: string; + variant?: "outline" | "default" | "secondary" | "ghost"; +} + +const ToolbarButton = ({ onClick, isActive, disabled, children, title, variant = "outline" }: ToolbarButtonProps) => ( + +); + +export default ToolbarButton; diff --git a/src/components/common/TextEditor/index.ts b/src/components/common/TextEditor/index.ts new file mode 100644 index 0000000..55b191f --- /dev/null +++ b/src/components/common/TextEditor/index.ts @@ -0,0 +1,3 @@ +export { default } from "./TextEditor"; +export { default as Toolbar } from "./Toolbar"; +export { default as ToolbarButton } from "./ToolbarButton"; diff --git a/src/components/layout/Navbar/UserMenu.tsx b/src/components/layout/Navbar/UserMenu.tsx index abf6168..70e2462 100644 --- a/src/components/layout/Navbar/UserMenu.tsx +++ b/src/components/layout/Navbar/UserMenu.tsx @@ -2,10 +2,10 @@ import { Button } from "@/components/ui/Button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/DropdownMenu"; import { Link } from "@/lib/navigation"; import { ChevronLeft, LogOut, User } from "lucide-react"; -import { USER_LINKS } from "./constant"; import { useTranslations } from "next-intl"; import { useAuthUser } from "@/components/hooks/UseAuthUser"; import { useAuthService } from "@/features/auth/hooks/useAuth"; +import { getUserLinks } from "./constant"; const DesktopUserMenu = () => { const t = useTranslations("Layout"); @@ -41,16 +41,12 @@ const DesktopUserMenu = () => { - {user?.role === "admin" && ( - - Dashboard - - )} - {USER_LINKS.map(({ id, href }) => ( - - {t(`navbar.user.${id}`)} - - ))} + {user && + getUserLinks(user.role).map(({ id, href }) => ( + + {t(`navbar.user.${id}`)} + + ))} @@ -84,13 +80,14 @@ const MobileUserMenu = () => {

{user?.email}

- {USER_LINKS.map(({ id, href }) => ( - - - - ))} + {user && + getUserLinks(user.role).map(({ id, href }) => ( + + + + ))}