.
This commit is contained in:
parent
bccd089c7f
commit
7135e34699
@ -21,11 +21,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/drizzle-adapter": "^1.7.2",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||
"@radix-ui/react-avatar": "^1.1.3",
|
||||
"@radix-ui/react-checkbox": "^1.1.4",
|
||||
"@radix-ui/react-collapsible": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
@ -37,6 +42,7 @@
|
||||
"@t3-oss/env-nextjs": "^0.10.1",
|
||||
"@tanstack/react-query": "^5.50.0",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@tiptap/core": "^2.11.5",
|
||||
"@tiptap/extension-color": "^2.11.5",
|
||||
"@tiptap/extension-image": "^2.11.5",
|
||||
"@tiptap/extension-list-item": "^2.11.5",
|
||||
@ -58,8 +64,10 @@
|
||||
"next-auth": "5.0.0-beta.25",
|
||||
"next-themes": "^0.4.4",
|
||||
"postgres": "^3.4.4",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"react": "^18.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-textarea-autosize": "^8.5.7",
|
||||
@ -68,6 +76,7 @@
|
||||
"superjson": "^2.2.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tiptap-extension-global-drag-handle": "^0.1.18",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
202
pnpm-lock.yaml
generated
202
pnpm-lock.yaml
generated
@ -11,6 +11,18 @@ importers:
|
||||
'@auth/drizzle-adapter':
|
||||
specifier: ^1.7.2
|
||||
version: 1.8.0
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/modifiers':
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/sortable':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(react@18.3.1)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^4.1.3
|
||||
version: 4.1.3(react-hook-form@7.54.2(react@18.3.1))
|
||||
@ -26,6 +38,9 @@ importers:
|
||||
'@radix-ui/react-checkbox':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-collapsible':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@ -59,6 +74,9 @@ importers:
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.21.2
|
||||
version: 8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tiptap/core':
|
||||
specifier: ^2.11.5
|
||||
version: 2.11.5(@tiptap/pm@2.11.5)
|
||||
'@tiptap/extension-color':
|
||||
specifier: ^2.11.5
|
||||
version: 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-text-style@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))
|
||||
@ -122,12 +140,18 @@ importers:
|
||||
postgres:
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.5
|
||||
prosemirror-state:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
react-colorful:
|
||||
specifier: ^5.6.1
|
||||
version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-dnd:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1(@types/node@20.17.23)(@types/react@18.3.18)(react@18.3.1)
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
@ -152,6 +176,9 @@ importers:
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.23)(typescript@5.8.2)))
|
||||
tiptap-extension-global-drag-handle:
|
||||
specifier: ^0.1.18
|
||||
version: 0.1.18
|
||||
zod:
|
||||
specifier: ^3.24.2
|
||||
version: 3.24.2
|
||||
@ -268,6 +295,34 @@ packages:
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/core@6.3.1':
|
||||
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/modifiers@9.0.0':
|
||||
resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.3.0
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/sortable@10.0.0':
|
||||
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.3.0
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@dnd-kit/utilities@3.2.2':
|
||||
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
@ -1031,6 +1086,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.3':
|
||||
resolution: {integrity: sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collection@1.1.2':
|
||||
resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==}
|
||||
peerDependencies:
|
||||
@ -1505,6 +1573,15 @@ packages:
|
||||
'@radix-ui/rect@1.1.0':
|
||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||
|
||||
'@react-dnd/asap@5.0.2':
|
||||
resolution: {integrity: sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==}
|
||||
|
||||
'@react-dnd/invariant@4.0.2':
|
||||
resolution: {integrity: sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==}
|
||||
|
||||
'@react-dnd/shallowequal@4.0.2':
|
||||
resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==}
|
||||
|
||||
'@remirror/core-constants@3.0.0':
|
||||
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
|
||||
|
||||
@ -2115,6 +2192,9 @@ packages:
|
||||
dlv@1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
|
||||
dnd-core@16.0.1:
|
||||
resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
|
||||
|
||||
doctrine@2.1.0:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2572,6 +2652,9 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@ -3220,6 +3303,21 @@ packages:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
react-dnd@16.0.1:
|
||||
resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==}
|
||||
peerDependencies:
|
||||
'@types/hoist-non-react-statics': '>= 3.3.1'
|
||||
'@types/node': '>= 12'
|
||||
'@types/react': '>= 16'
|
||||
react: '>= 16.14'
|
||||
peerDependenciesMeta:
|
||||
'@types/hoist-non-react-statics':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
peerDependencies:
|
||||
@ -3291,6 +3389,9 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
redux@4.2.1:
|
||||
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
|
||||
|
||||
reflect.getprototypeof@1.0.10:
|
||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3542,6 +3643,9 @@ packages:
|
||||
tippy.js@6.3.7:
|
||||
resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
|
||||
|
||||
tiptap-extension-global-drag-handle@0.1.18:
|
||||
resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
@ -3782,6 +3886,38 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/accessibility': 3.1.1(react@18.3.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
|
||||
react: 18.3.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
|
||||
react: 18.3.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/utilities@3.2.2(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@emnapi/runtime@1.3.1':
|
||||
@ -4288,6 +4424,22 @@ snapshots:
|
||||
'@types/react': 18.3.18
|
||||
'@types/react-dom': 18.3.5(@types/react@18.3.18)
|
||||
|
||||
'@radix-ui/react-collapsible@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.1
|
||||
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.18
|
||||
'@types/react-dom': 18.3.5(@types/react@18.3.18)
|
||||
|
||||
'@radix-ui/react-collection@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
@ -4740,6 +4892,12 @@ snapshots:
|
||||
|
||||
'@radix-ui/rect@1.1.0': {}
|
||||
|
||||
'@react-dnd/asap@5.0.2': {}
|
||||
|
||||
'@react-dnd/invariant@4.0.2': {}
|
||||
|
||||
'@react-dnd/shallowequal@4.0.2': {}
|
||||
|
||||
'@remirror/core-constants@3.0.0': {}
|
||||
|
||||
'@rtsao/scc@1.1.0': {}
|
||||
@ -5391,6 +5549,12 @@ snapshots:
|
||||
|
||||
dlv@1.1.3: {}
|
||||
|
||||
dnd-core@16.0.1:
|
||||
dependencies:
|
||||
'@react-dnd/asap': 5.0.2
|
||||
'@react-dnd/invariant': 4.0.2
|
||||
redux: 4.2.1
|
||||
|
||||
doctrine@2.1.0:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
@ -5629,8 +5793,8 @@ snapshots:
|
||||
'@typescript-eslint/parser': 8.26.0(eslint@8.57.1)(typescript@5.8.2)
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.37.4(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
||||
@ -5649,7 +5813,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.0
|
||||
@ -5660,18 +5824,18 @@ snapshots:
|
||||
stable-hash: 0.0.4
|
||||
tinyglobby: 0.2.12
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.26.0(eslint@8.57.1)(typescript@5.8.2)
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-import-resolver-typescript: 3.8.3(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -5679,7 +5843,7 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 8.57.1
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
@ -5690,7 +5854,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.3)(eslint@8.57.1)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@ -5998,6 +6162,10 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hoist-non-react-statics@3.3.2:
|
||||
dependencies:
|
||||
react-is: 16.13.1
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
@ -6608,6 +6776,18 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-dnd@16.0.1(@types/node@20.17.23)(@types/react@18.3.18)(react@18.3.1):
|
||||
dependencies:
|
||||
'@react-dnd/invariant': 4.0.2
|
||||
'@react-dnd/shallowequal': 4.0.2
|
||||
dnd-core: 16.0.1
|
||||
fast-deep-equal: 3.1.3
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/node': 20.17.23
|
||||
'@types/react': 18.3.18
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@ -6679,6 +6859,10 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
redux@4.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.9
|
||||
|
||||
reflect.getprototypeof@1.0.10:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@ -7019,6 +7203,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.8
|
||||
|
||||
tiptap-extension-global-drag-handle@0.1.18: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { appRoutes } from "@/config";
|
||||
import { Category } from "@/server/db/schema";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
@ -8,7 +9,7 @@ function CategoryCard({
|
||||
createdAt,
|
||||
}: Pick<Category, "name" | "slug" | "createdAt">) {
|
||||
return (
|
||||
<Link href={`/kategorie/${slug}`}>
|
||||
<Link href={appRoutes.category(slug)}>
|
||||
<div className="rounded-md border p-4">{name}</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@ -3,7 +3,7 @@ import { auth } from "@/server/auth";
|
||||
import { api } from "@/trpc/server";
|
||||
import { notFound } from "next/navigation";
|
||||
import React from "react";
|
||||
import Editor from "@/components/article/editor/article-form";
|
||||
import ArticleEditor from "@/components/article/article-form";
|
||||
|
||||
async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
||||
const { slug } = await params;
|
||||
@ -13,7 +13,7 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
||||
: false;
|
||||
const article = await api.article.get({ slug: slug });
|
||||
if (!article || !isEditor) return notFound();
|
||||
return <Editor server_article={article} />;
|
||||
return <ArticleEditor server_article={article} />;
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@ -1,4 +1,7 @@
|
||||
import RenderArticle from "@/components/article/render-article";
|
||||
import BreadNavigator from "@/components/bread-navigator";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { appRoutes } from "@/config";
|
||||
import { hasPermission, Role } from "@/lib/validation/permissions";
|
||||
import { auth } from "@/server/auth";
|
||||
import { api } from "@/trpc/server";
|
||||
@ -16,13 +19,28 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
||||
? hasPermission(session.user.role, Role.EDITOR)
|
||||
: false;
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h1 className="text-4xl font-bold">{article.title}</h1>
|
||||
<div className="w-full">
|
||||
<BreadNavigator
|
||||
links={[
|
||||
...(article?.category
|
||||
? [
|
||||
{
|
||||
label: article.category.name!,
|
||||
href: appRoutes.category(article.category.slug),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ label: "Artikel", href: appRoutes.allArticles },
|
||||
{ label: article.title!, href: appRoutes.article(article.slug) },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{isEditor && (
|
||||
<div className="space-x-2">
|
||||
<div className="flex w-full justify-end space-x-2">
|
||||
<Button asChild variant={"outline"}>
|
||||
<Link href={`/artikel/${article.slug}/edit`}>
|
||||
<Link href={appRoutes.editArticle(article.slug)}>
|
||||
<Edit className="size-4" />
|
||||
<span>Bearbeiten</span>
|
||||
</Link>
|
||||
@ -33,6 +51,11 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold">{article.title}</h1>
|
||||
|
||||
{article?.content?.length ? (
|
||||
<RenderArticle content={article.content} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,8 @@ import { api } from "@/trpc/server";
|
||||
import { Edit } from "lucide-react";
|
||||
import { notFound } from "next/navigation";
|
||||
import React from "react";
|
||||
import ArticleCard from "../../_components/article/article-card";
|
||||
import ArticleCard from "../../../../components/article/article-card";
|
||||
import { appRoutes } from "@/config";
|
||||
|
||||
async function Page({ params }: { params: Promise<{ name: string }> }) {
|
||||
const { name } = await params;
|
||||
@ -22,10 +23,10 @@ async function Page({ params }: { params: Promise<{ name: string }> }) {
|
||||
<div className="flex items-center justify-between">
|
||||
<BreadNavigator
|
||||
links={[
|
||||
{ label: "Kategorie", href: "/kategorie" },
|
||||
{ label: "Kategorie", href: appRoutes.allCategories },
|
||||
{
|
||||
label: name,
|
||||
href: `/kategorie/${name}`,
|
||||
href: appRoutes.category(name),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -2,7 +2,7 @@ import { auth } from "@/server/auth";
|
||||
import { api } from "@/trpc/server";
|
||||
import Link from "next/link";
|
||||
import GlobalStats from "./_components/global-stats";
|
||||
import ArticleCard from "./_components/article/article-card";
|
||||
import ArticleCard from "../../components/article/article-card";
|
||||
import CategoryCard from "./_components/category/category-card";
|
||||
|
||||
export default async function Home() {
|
||||
|
||||
@ -14,7 +14,7 @@ import Link from "next/link";
|
||||
import SidebarLink from "@/components/layout/sidebar-link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LogOutIcon } from "lucide-react";
|
||||
import { appConfig } from "@/app.config";
|
||||
import { appConfig } from "@/config/app.config";
|
||||
|
||||
const data = {
|
||||
navMain: [
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { appRoutes } from "@/config";
|
||||
import { Article } from "@/server/db/schema";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
@ -8,7 +9,7 @@ function ArticleCard({
|
||||
createdAt,
|
||||
}: Pick<Article, "title" | "slug" | "createdAt">) {
|
||||
return (
|
||||
<Link href={`/artikel/${slug}`}>
|
||||
<Link href={appRoutes.article(slug)}>
|
||||
<div className="rounded-md border p-4">{title}</div>
|
||||
</Link>
|
||||
);
|
||||
@ -19,41 +19,32 @@ import {
|
||||
import { articleSchema } from "@/lib/validation/zod/article";
|
||||
import { cn, debounce } from "@/lib/utils";
|
||||
import { updateArticle } from "@/server/actions/article";
|
||||
import Editor from "./editor";
|
||||
import Editor from "../text-editor";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Link from "next/link";
|
||||
import CategorySelect from "@/components/category/category-select";
|
||||
import {
|
||||
Check,
|
||||
CheckCircle,
|
||||
MessageCircleWarning,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import PublishArticleAlertDialog from "../publish-article-alert-dialog";
|
||||
import { CheckCircle, XCircle } from "lucide-react";
|
||||
import PublishArticleAlertDialog from "./publish-article-alert-dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export default ({ server_article }: { server_article: Article }) => {
|
||||
const [selectedCategory, setSelectedCategory] =
|
||||
React.useState<Category | null>(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const form = useForm<z.infer<typeof articleSchema>>({
|
||||
resolver: zodResolver(articleSchema),
|
||||
defaultValues: {
|
||||
title: server_article?.title ?? "",
|
||||
slug: server_article?.slug,
|
||||
published: server_article?.published ?? false,
|
||||
categoryId: server_article?.categoryId ?? "",
|
||||
authorId: server_article?.authorId ?? "",
|
||||
content:
|
||||
server_article?.content ??
|
||||
`<h2>
|
||||
Hey bearbeite mich!
|
||||
</h2>`,
|
||||
content: server_article?.content,
|
||||
},
|
||||
});
|
||||
|
||||
// 2. Define a submit handler.
|
||||
async function onSubmit(values: z.infer<typeof articleSchema>) {
|
||||
console.log("Content before save", values.content);
|
||||
|
||||
setLoading(true);
|
||||
await updateArticle(values, server_article.id);
|
||||
setLoading(false);
|
||||
@ -100,11 +91,17 @@ export default ({ server_article }: { server_article: Article }) => {
|
||||
<FormItem className="w-full">
|
||||
<FormControl>
|
||||
<Editor
|
||||
// content={field.value}
|
||||
editorProviderProps={{
|
||||
editorProps: { attributes: { class: "min-h-64" } },
|
||||
content: field.value,
|
||||
editorProps: { attributes: { class: "min-h-64" } },
|
||||
onUpdate: (value) => {
|
||||
field.onChange(value.editor.getHTML());
|
||||
const newContent = value.editor.getHTML();
|
||||
console.log(
|
||||
"Content :: form",
|
||||
JSON.stringify(newContent),
|
||||
);
|
||||
field.onChange(newContent);
|
||||
debouncedSubmit();
|
||||
},
|
||||
}}
|
||||
@ -23,24 +23,20 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { articleSchema } from "@/lib/validation/zod/article";
|
||||
import { createArticleSchema } from "@/lib/validation/zod/article";
|
||||
import { createArticle } from "@/server/actions/article";
|
||||
|
||||
const formSchema = articleSchema.pick({
|
||||
title: true,
|
||||
});
|
||||
|
||||
function CreateArticleDialog() {
|
||||
const [open, setOpen] = React.useState<boolean>(false);
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
const form = useForm<z.infer<typeof createArticleSchema>>({
|
||||
resolver: zodResolver(createArticleSchema),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
},
|
||||
});
|
||||
|
||||
// 2. Define a submit handler.
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
async function onSubmit(values: z.infer<typeof createArticleSchema>) {
|
||||
setOpen(false);
|
||||
await createArticle(values);
|
||||
}
|
||||
8
src/components/article/render-article.tsx
Normal file
8
src/components/article/render-article.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import Editor from "../text-editor";
|
||||
|
||||
function RenderArticle({ content }: { content: string }) {
|
||||
return <Editor readOnly editorProviderProps={{ content: content }} />;
|
||||
}
|
||||
|
||||
export default RenderArticle;
|
||||
@ -13,6 +13,8 @@ function BreadNavigator({
|
||||
}: {
|
||||
links: { label: string; href: string }[];
|
||||
}) {
|
||||
const labelClass =
|
||||
"w-full max-w-52 overflow-hidden text-ellipsis whitespace-nowrap capitalize block";
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
@ -20,8 +22,8 @@ function BreadNavigator({
|
||||
if (idx < links.length - 1)
|
||||
return (
|
||||
<React.Fragment key={href}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink className="capitalize" href={href}>
|
||||
<BreadcrumbItem className="">
|
||||
<BreadcrumbLink className={labelClass} href={href}>
|
||||
{label}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
@ -29,7 +31,7 @@ function BreadNavigator({
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<BreadcrumbItem className="capitalize" key={href}>
|
||||
<BreadcrumbItem className={labelClass} key={href}>
|
||||
<BreadcrumbPage>{label}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
|
||||
@ -5,23 +5,31 @@ import {
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import Link from "next/link";
|
||||
import { Separator } from "../ui/separator";
|
||||
import SidebarLink from "./sidebar-link";
|
||||
import { Button } from "../ui/button";
|
||||
import { HomeIcon } from "lucide-react";
|
||||
import { Minus, Plus } from "lucide-react";
|
||||
import { api } from "@/trpc/server";
|
||||
import { appConfig } from "@/app.config";
|
||||
import { appConfig } from "@/config/app.config";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "../ui/collapsible";
|
||||
import { appRoutes } from "@/config";
|
||||
import SidebarLink from "./sidebar-link";
|
||||
|
||||
export async function AppSidebar({
|
||||
...props
|
||||
}: React.ComponentProps<typeof Sidebar>) {
|
||||
const articles = await api.article.getAllPreviews();
|
||||
const sidebarContent = await api.app.getSidebarContent();
|
||||
return (
|
||||
<Sidebar {...props}>
|
||||
<SidebarHeader className="flex h-14 items-center justify-center border-b">
|
||||
@ -31,39 +39,39 @@ export async function AppSidebar({
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<Button asChild>
|
||||
<Link href={"/"}>
|
||||
<HomeIcon className="size-4" />
|
||||
<span>Start</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</SidebarGroup>
|
||||
{/* We create a SidebarGroup for each parent. */}
|
||||
{/* {data.navMain.map((item) => (
|
||||
<SidebarGroup key={item.title}>
|
||||
<SidebarGroupLabel>{item.title}</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{item.items.map((item) => (
|
||||
<SidebarLink key={item.title} {...item} />
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
))} */}
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Artikel</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{articles?.map((article) => (
|
||||
<SidebarLink
|
||||
key={article.slug}
|
||||
title={article.title!}
|
||||
url={`/artikel/${article.slug}`}
|
||||
/>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{sidebarContent?.map((category, index) => (
|
||||
<Collapsible
|
||||
key={category.id}
|
||||
defaultOpen={index === 1}
|
||||
className="group/collapsible"
|
||||
>
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton>
|
||||
{category.name}{" "}
|
||||
<Plus className="ml-auto group-data-[state=open]/collapsible:hidden" />
|
||||
<Minus className="ml-auto group-data-[state=closed]/collapsible:hidden" />
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
{category?.articles?.length ? (
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{category.articles.map((article) => (
|
||||
<SidebarMenuSubItem key={article.slug}>
|
||||
<SidebarLink
|
||||
title={article.title!}
|
||||
url={appRoutes.article(article.slug)}
|
||||
/>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
|
||||
@ -10,8 +10,9 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import CreateArticleDialog from "@/app/(PAGES)/_components/article/create-article-dialog";
|
||||
import CreateArticleDialog from "@/components/article/create-article-dialog";
|
||||
import CreateCategoryDialog from "@/app/(PAGES)/_components/category/create-category-dialog";
|
||||
import { appRoutes } from "@/config";
|
||||
|
||||
async function Navbar() {
|
||||
const session = await auth();
|
||||
@ -44,7 +45,7 @@ async function Navbar() {
|
||||
)}
|
||||
{isAdmin && (
|
||||
<Button asChild>
|
||||
<Link href={"/admin"}>Admin Dashboard</Link>
|
||||
<Link href={appRoutes.admin.base}>Admin Dashboard</Link>
|
||||
</Button>
|
||||
)}
|
||||
{session ? (
|
||||
|
||||
@ -2,17 +2,15 @@
|
||||
|
||||
import React from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
|
||||
import { SidebarMenuSubButton } from "@/components/ui/sidebar";
|
||||
import Link from "next/link";
|
||||
|
||||
function SidebarLink({ url, title }: { url: string; title: string }) {
|
||||
const isActive = usePathname() === url;
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild isActive={isActive}>
|
||||
<Link href={url}>{title}</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuSubButton asChild isActive={isActive}>
|
||||
<Link href={url}>{title}</Link>
|
||||
</SidebarMenuSubButton>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
0
src/components/text-editor/editor.tsx
Normal file
0
src/components/text-editor/editor.tsx
Normal file
92
src/components/text-editor/extentions/block-drag.tsx
Normal file
92
src/components/text-editor/extentions/block-drag.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import React from "react";
|
||||
import { NodeViewWrapper, NodeViewContent } from "@tiptap/react";
|
||||
import { useDrag, useDrop } from "react-dnd";
|
||||
|
||||
const DRAG_TYPE = "TIPTAP_BLOCK";
|
||||
|
||||
export const DraggableBlockView = (props) => {
|
||||
const { node, getPos, editor } = props;
|
||||
const [showDragHandle, setShowDragHandle] = useState(false);
|
||||
|
||||
// Get node position and ID
|
||||
const nodePos = getPos();
|
||||
const nodeId = `${node.type.name}-${nodePos}`;
|
||||
|
||||
// Set up drag
|
||||
const [{ isDragging }, drag, dragPreview] = useDrag({
|
||||
type: DRAG_TYPE,
|
||||
item: { id: nodeId, pos: nodePos, type: node.type.name },
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
begin: () => {
|
||||
// Select the block on drag start
|
||||
editor.commands.setNodeSelection(nodePos);
|
||||
return { id: nodeId, pos: nodePos, type: node.type.name };
|
||||
},
|
||||
});
|
||||
|
||||
// Set up drop
|
||||
const [{ isOver, canDrop }, drop] = useDrop({
|
||||
accept: DRAG_TYPE,
|
||||
drop: (item) => {
|
||||
if (item.pos !== nodePos) {
|
||||
// Move the dragged node to this position
|
||||
const tr = editor.state.tr;
|
||||
const sourcePos = item.pos;
|
||||
const targetPos = nodePos;
|
||||
|
||||
// Logic to move nodes in the document
|
||||
// This is the complex part that requires careful handling of ProseMirror positions
|
||||
const sourceNode = tr.doc.nodeAt(sourcePos);
|
||||
if (sourceNode) {
|
||||
tr.delete(sourcePos, sourcePos + sourceNode.nodeSize);
|
||||
const newTargetPos =
|
||||
targetPos > sourcePos ? targetPos - sourceNode.nodeSize : targetPos;
|
||||
tr.insert(newTargetPos, sourceNode);
|
||||
editor.view.dispatch(tr);
|
||||
}
|
||||
}
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Reference for the drag preview (the whole block)
|
||||
const dragRef = useRef(null);
|
||||
|
||||
return (
|
||||
<NodeViewWrapper
|
||||
ref={drop}
|
||||
data-drag-handle={nodeId}
|
||||
style={{
|
||||
position: "relative",
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
backgroundColor: isOver ? "#f0f9ff" : "transparent",
|
||||
}}
|
||||
onMouseEnter={() => setShowDragHandle(true)}
|
||||
onMouseLeave={() => setShowDragHandle(false)}
|
||||
>
|
||||
{showDragHandle && (
|
||||
<div
|
||||
ref={drag}
|
||||
className="drag-handle"
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: "-24px",
|
||||
top: "8px",
|
||||
cursor: "grab",
|
||||
color: "#aaa",
|
||||
}}
|
||||
>
|
||||
⋮⋮
|
||||
</div>
|
||||
)}
|
||||
<div ref={dragPreview}>
|
||||
<NodeViewContent />
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
};
|
||||
@ -3,6 +3,23 @@ import ListItem from "@tiptap/extension-list-item";
|
||||
import TextStyle from "@tiptap/extension-text-style";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import GlobalDragHandle from "tiptap-extension-global-drag-handle";
|
||||
import { Extension } from "@tiptap/core";
|
||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||
import { DraggableBlockView } from "./block-drag";
|
||||
|
||||
const DraggableBlocks = Extension.create({
|
||||
name: "draggableBlocks",
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
return [];
|
||||
},
|
||||
|
||||
// Apply this nodeview to all block nodes
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(DraggableBlockView);
|
||||
},
|
||||
});
|
||||
|
||||
export const extensions = [
|
||||
Color.configure({ types: [TextStyle.name, ListItem.name] }),
|
||||
@ -11,7 +28,9 @@ export const extensions = [
|
||||
StarterKit.configure({
|
||||
code: false,
|
||||
codeBlock: false,
|
||||
horizontalRule: {},
|
||||
heading: {
|
||||
levels: [1, 2, 3, 4, 5, 6],
|
||||
},
|
||||
bulletList: {
|
||||
keepMarks: true,
|
||||
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||
@ -21,5 +40,16 @@ export const extensions = [
|
||||
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
|
||||
},
|
||||
}),
|
||||
GlobalDragHandle,
|
||||
Image,
|
||||
DraggableBlocks.configure({
|
||||
types: [
|
||||
"paragraph",
|
||||
"heading",
|
||||
"blockquote",
|
||||
"codeBlock",
|
||||
"bulletList",
|
||||
"orderedList",
|
||||
],
|
||||
}),
|
||||
];
|
||||
@ -5,6 +5,7 @@ import React from "react";
|
||||
import { EditorProvider, EditorProviderProps } from "@tiptap/react";
|
||||
import { MenuBar } from "./menu-bar";
|
||||
import { extensions } from "./extentions";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Editor({
|
||||
editorProviderProps,
|
||||
@ -14,15 +15,22 @@ function Editor({
|
||||
readOnly?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-md bg-gradient-to-b from-muted to-background p-2">
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-md p-2",
|
||||
!readOnly && "bg-gradient-to-b from-muted to-background",
|
||||
)}
|
||||
>
|
||||
<EditorProvider
|
||||
immediatelyRender={false}
|
||||
extensions={extensions}
|
||||
slotBefore={!readOnly && <MenuBar />}
|
||||
{...editorProviderProps}
|
||||
editable={!readOnly}
|
||||
/>
|
||||
|
||||
{JSON.stringify(editorProviderProps.content)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Editor;
|
||||
34
src/components/text-editor/plugins/draggable-node.tsx
Normal file
34
src/components/text-editor/plugins/draggable-node.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { NodeViewWrapper, NodeViewContent } from "@tiptap/react";
|
||||
import { useDraggable } from "@dnd-kit/core";
|
||||
|
||||
const DraggableBlock = ({ node }: any) => {
|
||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||
id: node.attrs.id,
|
||||
});
|
||||
|
||||
return (
|
||||
<NodeViewWrapper
|
||||
ref={setNodeRef}
|
||||
style={{
|
||||
transform: transform
|
||||
? `translate(${transform.x}px, ${transform.y}px)`
|
||||
: "none",
|
||||
}}
|
||||
className="relative cursor-grab border bg-white p-2 shadow-md"
|
||||
>
|
||||
{/* Drag Handle */}
|
||||
<div
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
className="absolute left-0 top-0 cursor-grab bg-gray-300 p-1"
|
||||
>
|
||||
⠿
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<NodeViewContent as="div" />
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DraggableBlock;
|
||||
@ -112,3 +112,24 @@ ol {
|
||||
.menu-bar .is-active {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
|
||||
.block-drag-handle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #aaa;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.block-drag-handle:hover {
|
||||
color: #333;
|
||||
background: #f1f1f1;
|
||||
}
|
||||
11
src/components/ui/collapsible.tsx
Normal file
11
src/components/ui/collapsible.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||
43
src/config/app.routes.ts
Normal file
43
src/config/app.routes.ts
Normal file
@ -0,0 +1,43 @@
|
||||
export type RouteWithParam = (param: string) => string;
|
||||
export type Route = string | RouteWithParam;
|
||||
|
||||
export type AppRoutes = {
|
||||
// Home and admin
|
||||
home: string;
|
||||
admin: {
|
||||
base: string;
|
||||
};
|
||||
|
||||
// Article routes
|
||||
allArticles: string;
|
||||
article: RouteWithParam;
|
||||
editArticle: RouteWithParam;
|
||||
|
||||
// Category routes
|
||||
allCategories: string;
|
||||
category: RouteWithParam;
|
||||
editCategory: RouteWithParam;
|
||||
|
||||
// Auth routes
|
||||
signin: string;
|
||||
signout: string;
|
||||
};
|
||||
|
||||
export const appRoutes: AppRoutes = {
|
||||
home: "/",
|
||||
admin: { base: "/admin" },
|
||||
|
||||
// article
|
||||
allArticles: "/artikel",
|
||||
article: (slug) => `/artikel/${slug}`,
|
||||
editArticle: (slug) => `/artikel/${slug}/bearbeiten`,
|
||||
|
||||
// category
|
||||
allCategories: "/kategorie",
|
||||
category: (slug) => `/kategorie/${slug}`,
|
||||
editCategory: (slug) => `/kategorie/${slug}/bearbeiten`,
|
||||
|
||||
// auth
|
||||
signin: "/api/auth/signin",
|
||||
signout: "/api/auth/signout",
|
||||
};
|
||||
2
src/config/index.ts
Normal file
2
src/config/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { appConfig } from "./app.config";
|
||||
export { appRoutes } from "./app.routes";
|
||||
@ -6,7 +6,13 @@ export function cn(...inputs: ClassValue[]) {
|
||||
}
|
||||
|
||||
export function generateSlug(title: string) {
|
||||
return title.toLowerCase().replace(/\s+/g, "-");
|
||||
return title
|
||||
.toLowerCase() // Convert to lowercase
|
||||
.trim() // Remove leading/trailing whitespace
|
||||
.replace(/[^\w\s-]/g, "") // Remove special characters
|
||||
.replace(/\s+/g, "-") // Replace spaces with hyphens
|
||||
.replace(/-+/g, "-") // Remove consecutive hyphens
|
||||
.replace(/^-+|-+$/g, ""); // Remove leading/trailing hyphens
|
||||
}
|
||||
|
||||
export function debounce<T extends (...args: any[]) => void>(
|
||||
@ -20,18 +26,3 @@ export function debounce<T extends (...args: any[]) => void>(
|
||||
timeoutId = setTimeout(() => func(...args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
export function RGBAToHexA(rgba: string, forceRemoveAlpha = false) {
|
||||
return (
|
||||
"#" +
|
||||
rgba
|
||||
.replace(/^rgba?\(|\s+|\)$/g, "") // Get's rgba / rgb string values
|
||||
.split(",") // splits them at ","
|
||||
.filter((string, index) => !forceRemoveAlpha || index !== 3)
|
||||
.map((string) => parseFloat(string)) // Converts them to numbers
|
||||
.map((number, index) => (index === 3 ? Math.round(number * 255) : number)) // Converts alpha to 255 number
|
||||
.map((number) => number.toString(16)) // Converts numbers to hex
|
||||
.map((string) => (string.length === 1 ? "0" + string : string)) // Adds 0 when length of one number is 1
|
||||
.join("")
|
||||
); // Puts the array to togehter to a string
|
||||
}
|
||||
|
||||
@ -2,8 +2,13 @@ import { z } from "zod";
|
||||
|
||||
export const articleSchema = z.object({
|
||||
title: z.string().min(1),
|
||||
content: z.string().optional(),
|
||||
slug: z.string().min(1),
|
||||
content: z.any().optional(),
|
||||
authorId: z.string().optional(),
|
||||
categoryId: z.string().optional(),
|
||||
published: z.boolean(),
|
||||
});
|
||||
|
||||
export const createArticleSchema = articleSchema.pick({
|
||||
title: true,
|
||||
});
|
||||
|
||||
@ -1,22 +1,29 @@
|
||||
"use server";
|
||||
|
||||
import { articleSchema } from "@/lib/validation/zod/article";
|
||||
import { appRoutes } from "@/config";
|
||||
import {
|
||||
articleSchema,
|
||||
createArticleSchema,
|
||||
} from "@/lib/validation/zod/article";
|
||||
import { api } from "@/trpc/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function createArticle(article: z.infer<typeof articleSchema>) {
|
||||
export async function createArticle(
|
||||
article: z.infer<typeof createArticleSchema>,
|
||||
) {
|
||||
const result = await api.article.create({
|
||||
article,
|
||||
});
|
||||
if (!result[0]?.slug?.length) return false;
|
||||
return redirect(`/artikel/${result[0].slug}/edit`);
|
||||
return redirect(appRoutes.editArticle(result[0].slug));
|
||||
}
|
||||
export async function updateArticle(
|
||||
article: z.infer<typeof articleSchema>,
|
||||
articleId: string,
|
||||
) {
|
||||
console.log("Content :: action", JSON.stringify(article.content));
|
||||
|
||||
const result = await api.article.update({
|
||||
article,
|
||||
articleId,
|
||||
|
||||
@ -3,7 +3,7 @@ import { categoryRouter } from "@/server/api/routers/category";
|
||||
import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
|
||||
import { usersRouter } from "./routers/users";
|
||||
import { authorRouter } from "./routers/author";
|
||||
|
||||
import { appRouter as globalRouter } from "./routers/app";
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
*
|
||||
@ -14,6 +14,7 @@ export const appRouter = createTRPCRouter({
|
||||
category: categoryRouter,
|
||||
users: usersRouter,
|
||||
author: authorRouter,
|
||||
app: globalRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
16
src/server/api/routers/app.ts
Normal file
16
src/server/api/routers/app.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
getSidebarContent: publicProcedure.query(async ({ ctx }) => {
|
||||
return await ctx.db.query.categories.findMany({
|
||||
with: {
|
||||
articles: {
|
||||
columns: {
|
||||
title: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
@ -6,7 +6,10 @@ import {
|
||||
publicProcedure,
|
||||
} from "@/server/api/trpc";
|
||||
import { articles } from "@/server/db/schema";
|
||||
import { articleSchema } from "@/lib/validation/zod/article";
|
||||
import {
|
||||
articleSchema,
|
||||
createArticleSchema,
|
||||
} from "@/lib/validation/zod/article";
|
||||
import { count, eq } from "drizzle-orm";
|
||||
import { hasPermission, Role } from "@/lib/validation/permissions";
|
||||
import { generateSlug } from "@/lib/utils";
|
||||
@ -18,6 +21,7 @@ export const articleRouter = createTRPCRouter({
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await ctx.db.query.articles.findFirst({
|
||||
where: eq(articles.slug, input.slug),
|
||||
with: { category: true },
|
||||
});
|
||||
}),
|
||||
|
||||
@ -60,7 +64,7 @@ export const articleRouter = createTRPCRouter({
|
||||
}),
|
||||
// mutations
|
||||
create: protectedProcedure
|
||||
.input(z.object({ article: articleSchema }))
|
||||
.input(z.object({ article: createArticleSchema }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const isEditor = hasPermission(ctx.session.user.role, Role.EDITOR);
|
||||
if (!isEditor) {
|
||||
@ -81,6 +85,11 @@ export const articleRouter = createTRPCRouter({
|
||||
if (!isEditor) {
|
||||
throw new Error("You are not allowed to update articles");
|
||||
}
|
||||
console.log(
|
||||
"Content before save",
|
||||
JSON.stringify(input.article.content),
|
||||
);
|
||||
|
||||
return await ctx.db
|
||||
.update(articles)
|
||||
.set(input.article)
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
pgTableCreator,
|
||||
primaryKey,
|
||||
text,
|
||||
@ -11,6 +12,8 @@ import {
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { type AdapterAccount } from "next-auth/adapters";
|
||||
import { type JSONContent } from "@tiptap/react";
|
||||
|
||||
/**
|
||||
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
||||
* database instance for multiple projects.
|
||||
@ -27,9 +30,9 @@ export const articles = createTable(
|
||||
.$defaultFn(() => createId())
|
||||
.notNull(),
|
||||
title: varchar("title", { length: 256 }),
|
||||
slug: varchar("slug", { length: 256 }).unique(),
|
||||
slug: varchar("slug", { length: 256 }).unique().notNull(),
|
||||
authorId: varchar("author_id", { length: 255 }),
|
||||
content: text("content"),
|
||||
content: text("content"),
|
||||
categoryId: varchar("category_id", { length: 255 }),
|
||||
published: boolean("published").default(false),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
@ -44,9 +47,8 @@ export const articles = createTable(
|
||||
}),
|
||||
);
|
||||
export type Article = typeof articles.$inferSelect;
|
||||
|
||||
export const articleRelations = relations(articles, ({ one }) => ({
|
||||
categories: one(categories, {
|
||||
category: one(categories, {
|
||||
fields: [articles.categoryId],
|
||||
references: [categories.id],
|
||||
}),
|
||||
@ -60,7 +62,7 @@ export const categories = createTable(
|
||||
.$defaultFn(() => createId())
|
||||
.notNull(),
|
||||
name: varchar("name", { length: 256 }),
|
||||
slug: varchar("slug", { length: 256 }).unique(),
|
||||
slug: varchar("slug", { length: 256 }).unique().notNull(),
|
||||
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.default(sql`CURRENT_TIMESTAMP`)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user