diff --git a/.gitea/workflows/demo.yml b/.gitea/workflows/demo.yml new file mode 100644 index 0000000..0588af6 --- /dev/null +++ b/.gitea/workflows/demo.yml @@ -0,0 +1,10 @@ +name: Gitea Actions Demo +run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 +on: [push] + +jobs: + Explore-Gitea-Actions: + runs-on: linux + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." + - run: echo "🎉 Test job completed! 🚀" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..97c5bae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:18-alpine + +RUN npm install -g pnpm + +WORKDIR /app + +COPY package.json pnpm-lock.yaml ./ + +RUN pnpm install --frozen-lockfile + +COPY . . + +RUN pnpm build + +CMD ["pnpm", "start"] diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..54622e3 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +PROJECT_PATH=$1 + +echo "Deploying project..." +cd "$PROJECT_PATH" || { echo "Directory not found: $PROJECT_PATH"; exit 1; } +git pull origin main +docker compose up -d --no-deps --build nextapp +echo "Deployment finished!" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..79c97b6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3.8" +services: + nextapp: + build: . + deploy: + update_config: + parallelism: 1 + delay: 5s + order: start-first + container_name: logipedia + restart: always + labels: + - "traefik.enable=true" + - "traefik.http.routers.logipedia.rule=Host(`logipedia.shortman.me`)" + - "traefik.http.routers.logipedia.entrypoints=websecure" + - "traefik.http.services.logipedia.loadbalancer.server.port=3000" + - "traefik.http.routers.logipedia.tls.certresolver=myresolver" + expose: + - "3000" + # ports: + # - "3000:3000" + networks: + - webproxy + db: + image: postgres:latest + container_name: logipedia-db + restart: always + shm_size: 128mb + env_file: + - .env + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + +volumes: + pgdata: + driver: local + +networks: + webproxy: + external: true diff --git a/next.config.js b/next.config.js index 69fb737..5f48ad3 100644 --- a/next.config.js +++ b/next.config.js @@ -6,6 +6,9 @@ import "./src/env.js"; /** @type {import("next").NextConfig} */ const config = { + eslint: { + ignoreDuringBuilds: true, + }, experimental: { serverActions: { bodySizeLimit: "2mb", diff --git a/package.json b/package.json index 7e16507..c35ba99 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "wiki-antifa", + "name": "logipedia", "version": "0.1.0", "private": true, "type": "module", @@ -46,6 +46,7 @@ "@trpc/client": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446", + "argon2": "^0.41.1", "cheerio": "^1.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -67,7 +68,9 @@ "superjson": "^2.2.1", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", + "tiptap-extension-resize-image": "^1.2.1", "use-debounce": "^10.0.4", + "winston": "^3.17.0", "zod": "^3.24.2" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e667b4f..20853c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: '@trpc/server': specifier: ^11.0.0-rc.446 version: 11.0.0-rc.824(typescript@5.8.2) + argon2: + specifier: ^0.41.1 + version: 0.41.1 cheerio: specifier: ^1.0.0 version: 1.0.0 @@ -149,9 +152,15 @@ 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-resize-image: + specifier: ^1.2.1 + version: 1.2.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-image@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))(@tiptap/pm@2.11.5) use-debounce: specifier: ^10.0.4 version: 10.0.4(react@18.3.1) + winston: + specifier: ^3.17.0 + version: 3.17.0 zod: specifier: ^3.24.2 version: 3.24.2 @@ -255,10 +264,17 @@ packages: '@cfcs/core@0.0.6': resolution: {integrity: sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@daybrush/utils@1.13.0': resolution: {integrity: sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==} @@ -969,6 +985,10 @@ packages: '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@phc/format@1.0.0': + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1930,6 +1950,9 @@ packages: '@types/react@18.3.18': resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2035,6 +2058,10 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argon2@0.41.1: + resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} + engines: {node: '>=16.17.0'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2085,6 +2112,9 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -2202,20 +2232,32 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -2470,6 +2512,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encoding-sniffer@0.2.0: resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} @@ -2688,6 +2733,9 @@ packages: picomatch: optional: true + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2707,6 +2755,9 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -2976,6 +3027,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -3076,6 +3131,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -3107,6 +3165,10 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -3305,6 +3367,14 @@ packages: sass: optional: true + node-addon-api@8.3.1: + resolution: {integrity: sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==} + engines: {node: ^18 || ^20 || >= 21} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3359,6 +3429,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3737,6 +3810,10 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3793,6 +3870,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -3801,6 +3881,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -3892,6 +3976,9 @@ packages: stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -3927,6 +4014,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -4004,6 +4094,9 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -4024,6 +4117,13 @@ packages: tiptap-extension-global-drag-handle@0.1.18: resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==} + tiptap-extension-resize-image@1.2.1: + resolution: {integrity: sha512-SLMAujDa+0LN/6Iv2HtU4Uk0BL6LMh4b/r85frpdnjFDW2i6pIOfTVG8jzJQ8T1EgYHNn2YG1U2HoVAGuwLc3Q==} + peerDependencies: + '@tiptap/core': ^2.0.0 + '@tiptap/extension-image': ^2.0.0 + '@tiptap/pm': ^2.0.0 + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4031,6 +4131,10 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -4240,6 +4344,14 @@ packages: engines: {node: '>= 8'} hasBin: true + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.17.0: + resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4327,10 +4439,18 @@ snapshots: dependencies: '@egjs/component': 3.0.5 + '@colors/colors@1.6.0': {} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + '@daybrush/utils@1.13.0': {} '@drizzle-team/brocli@0.10.2': {} @@ -4785,6 +4905,8 @@ snapshots: dependencies: '@noble/hashes': 1.7.1 + '@phc/format@1.0.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -5737,6 +5859,8 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/triple-beam@1.3.5': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -5860,6 +5984,12 @@ snapshots: arg@5.0.2: {} + argon2@0.41.1: + dependencies: + '@phc/format': 1.0.0 + node-addon-api: 8.3.1 + node-gyp-build: 4.8.4 + argparse@2.0.1: {} aria-hidden@1.2.4: @@ -5936,6 +6066,8 @@ snapshots: async-function@1.0.0: {} + async@3.2.6: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -6074,17 +6206,27 @@ snapshots: - '@types/react' - '@types/react-dom' + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} color-string@1.9.1: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - optional: true + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 color@4.2.3: dependencies: @@ -6092,6 +6234,11 @@ snapshots: color-string: 1.9.1 optional: true + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + comma-separated-tokens@2.0.3: {} commander@4.1.1: {} @@ -6257,6 +6404,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encoding-sniffer@0.2.0: dependencies: iconv-lite: 0.6.3 @@ -6691,6 +6840,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fecha@4.2.3: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -6712,6 +6863,8 @@ snapshots: flatted@3.3.3: {} + fn.name@1.1.0: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -6919,8 +7072,7 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-arrayish@0.3.2: - optional: true + is-arrayish@0.3.2: {} is-async-function@2.1.1: dependencies: @@ -7013,6 +7165,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@2.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -7111,6 +7265,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kuler@2.0.0: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -7138,6 +7294,15 @@ snapshots: lodash.merge@4.6.2: {} + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -7460,6 +7625,10 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-addon-api@8.3.1: {} + + node-gyp-build@4.8.4: {} + normalize-path@3.0.0: {} novel@1.0.2(@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -7557,6 +7726,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -7944,6 +8117,12 @@ snapshots: dependencies: pify: 2.3.0 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -8023,6 +8202,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -8034,6 +8215,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} scheduler@0.23.2: @@ -8147,7 +8330,6 @@ snapshots: simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - optional: true sonner@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -8167,6 +8349,8 @@ snapshots: stable-hash@0.0.4: {} + stack-trace@0.0.10: {} + streamsearch@1.1.0: {} string-width@4.2.3: @@ -8231,6 +8415,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -8322,6 +8510,8 @@ snapshots: tapable@2.2.1: {} + text-hex@1.0.0: {} + text-table@0.2.0: {} thenify-all@1.6.0: @@ -8343,12 +8533,20 @@ snapshots: tiptap-extension-global-drag-handle@0.1.18: {} + tiptap-extension-resize-image@1.2.1(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/extension-image@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)))(@tiptap/pm@2.11.5): + dependencies: + '@tiptap/core': 2.11.5(@tiptap/pm@2.11.5) + '@tiptap/extension-image': 2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5)) + '@tiptap/pm': 2.11.5 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 trim-lines@3.0.1: {} + triple-beam@1.4.1: {} + trough@2.2.0: {} ts-api-utils@2.0.1(typescript@5.8.2): @@ -8600,6 +8798,26 @@ snapshots: dependencies: isexe: 2.0.0 + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.17.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: diff --git a/public/placeholder.svg b/public/placeholder.svg new file mode 100644 index 0000000..e763910 --- /dev/null +++ b/public/placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/uploads/upload-1741879341639-pexels-rdne-8052216.jpg.png b/public/uploads/upload-1741879341639-pexels-rdne-8052216.jpg.png deleted file mode 100644 index f7aaa9e..0000000 Binary files a/public/uploads/upload-1741879341639-pexels-rdne-8052216.jpg.png and /dev/null differ diff --git a/public/uploads/upload-1742077219929-person-3.jpg.png b/public/uploads/upload-1742077219929-person-3.jpg.png new file mode 100644 index 0000000..4129a0f Binary files /dev/null and b/public/uploads/upload-1742077219929-person-3.jpg.png differ diff --git a/public/uploads/upload-1741879517853-pexels-italo-melo-881954-2379005.jpg.png b/public/uploads/upload-1742077526129-pexels-italo-melo-881954-2379005.jpg.png similarity index 100% rename from public/uploads/upload-1741879517853-pexels-italo-melo-881954-2379005.jpg.png rename to public/uploads/upload-1742077526129-pexels-italo-melo-881954-2379005.jpg.png diff --git a/seed-data/index.ts b/seed-data/index.ts index e5ece4c..3c1d578 100644 --- a/seed-data/index.ts +++ b/seed-data/index.ts @@ -1,8 +1,9 @@ import "dotenv/config"; +import { env } from "@/env"; import { db } from "../src/server/db"; import { articles, categories, users } from "../src/server/db/schema"; -async function seed() { +async function developmentSeed() { const usersData = Array.from({ length: 100 }).map((_, i) => ({ name: `User ${i + 1}`, email: `user${i + 1}@example.com`, @@ -37,9 +38,29 @@ async function seed() { console.log("Seeded " + a.length + " articles"); } +async function productionSeed() { + const user = await db.query.users.findFirst(); + if (user) { + console.log("Skipped seeding, user already exists"); + return; + } + const initialUser = { + name: "Admin", + email: "payblot@gmail.com", + role: 7, + }; + await db.insert(users).values(initialUser); + console.log("Seeded user"); +} + async function init() { try { - await seed(); + if (env.NODE_ENV === "development") await developmentSeed(); + else if (env.NODE_ENV === "production") await productionSeed(); + else + console.log( + "Skipped seeding, NODE_ENV is not set to development or production", + ); } catch (error) { console.error(error); } finally { diff --git a/src/app/(PAGES)/artikel/[slug]/page.tsx b/src/app/(PAGES)/artikel/[slug]/page.tsx index ec240cb..1761a6f 100644 --- a/src/app/(PAGES)/artikel/[slug]/page.tsx +++ b/src/app/(PAGES)/artikel/[slug]/page.tsx @@ -1,5 +1,6 @@ -import RenderArticle from "@/components/article/render-article"; import BreadNavigator from "@/components/bread-navigator"; +import RenderContent from "@/components/editor/render-content"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { appRoutes } from "@/config"; import { hasPermission, Role } from "@/lib/validation/permissions"; @@ -21,8 +22,9 @@ async function Page({ params }: { params: Promise<{ slug: string }> }) { return (
-
+
}) { />
{isEditor && ( -
+
+ + {article.published ? "Veröffentlicht" : "Draft"} + +

{article.title}

- - {article?.content?.length ? ( - - ) : null} + {article.content && }
); } diff --git a/src/app/(PAGES)/me/_components/user-form.tsx b/src/app/(PAGES)/me/_components/user-form.tsx index 9d7327c..635d9ae 100644 --- a/src/app/(PAGES)/me/_components/user-form.tsx +++ b/src/app/(PAGES)/me/_components/user-form.tsx @@ -14,21 +14,21 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { z } from "zod"; -import { userProfileSchema } from "@/lib/validation/zod/user"; +import { userSchema } from "@/lib/validation/zod/user"; import { User } from "next-auth"; import { updateUserProfile } from "@/server/actions/user"; import { toast } from "sonner"; function UserForm({ server_user, cb }: { server_user: User; cb?: () => void }) { - const form = useForm>({ - resolver: zodResolver(userProfileSchema), + const form = useForm>({ + resolver: zodResolver(userSchema), defaultValues: { name: server_user?.name ?? "", }, }); // 2. Define a submit handler. - async function onSubmit(values: z.infer) { + async function onSubmit(values: z.infer) { // Do something with the form values. // ✅ This will be type-safe and validated. const { success } = await updateUserProfile(values); diff --git a/src/app/(PAGES)/page.tsx b/src/app/(PAGES)/page.tsx index a1c4476..2ccccb0 100644 --- a/src/app/(PAGES)/page.tsx +++ b/src/app/(PAGES)/page.tsx @@ -10,8 +10,8 @@ import { Button } from "@/components/ui/button"; import Link from "next/link"; export default async function Home() { - const categories = await api.category.getAll({ limit: 6 }); - const articles = await api.article.getAll({ limit: 6 }); + const categories = await api.category.getMany({ limit: 6 }); + const articles = await api.article.getMany({ limit: 6 }); return ( <> diff --git a/src/app/api/url-preview/route.ts b/src/app/api/url-preview/route.ts index bf070c3..203fc5b 100644 --- a/src/app/api/url-preview/route.ts +++ b/src/app/api/url-preview/route.ts @@ -1,8 +1,7 @@ import * as cheerio from "cheerio"; -import { NextApiRequest } from "next"; import { NextResponse } from "next/server"; -export async function GET(req: NextApiRequest) { +export async function GET(req: Request) { if (req.method !== "GET") { return NextResponse.json({ error: "Method not allowed" }); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0ec361c..3de588c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,10 +6,11 @@ import { type Metadata } from "next"; import { TRPCReactProvider } from "@/trpc/react"; import { Toaster } from "@/components/ui/sonner"; import { ThemeProvider } from "@/components/theme-provider"; +import { appConfig } from "@/config"; export const metadata: Metadata = { - title: "Create T3 App", - description: "Generated by create-t3-app", + title: appConfig.name, + description: appConfig.description, icons: [{ rel: "icon", url: "/favicon.ico" }], }; diff --git a/src/components/article/article-card.tsx b/src/components/article/article-card.tsx index 785194c..5be70af 100644 --- a/src/components/article/article-card.tsx +++ b/src/components/article/article-card.tsx @@ -13,14 +13,17 @@ import { } from "@/components/ui/card"; import Avatar from "../avatar"; import { Icons } from "../icons"; +import { Badge } from "../ui/badge"; function ArticleCard({ title, slug, author, + published, createdAt, -}: Pick) { +}: Pick) { const authorName = author?.name ?? `${appConfig.name} Team`; + return ( @@ -37,11 +40,21 @@ function ArticleCard({ )} {authorName}
-

- {createdAt.toLocaleDateString("de-DE", { - dateStyle: "long", - })} -

+
+ {typeof published === "boolean" && ( + + {published ? "Veröffentlicht" : "Draft"} + + )} +

+ {createdAt.toLocaleDateString("de-DE", { + dateStyle: "long", + })} +

+
diff --git a/src/components/article/article-filter-bar.tsx b/src/components/article/article-filter-bar.tsx index cc3bea4..0b255fe 100644 --- a/src/components/article/article-filter-bar.tsx +++ b/src/components/article/article-filter-bar.tsx @@ -85,6 +85,7 @@ function ArticleFilterBar({ { onFilterChange({ category: category?.length ? category : undefined, diff --git a/src/components/article/article-form.tsx b/src/components/article/article-form.tsx index 9628b9c..ced26fc 100644 --- a/src/components/article/article-form.tsx +++ b/src/components/article/article-form.tsx @@ -25,7 +25,9 @@ import CategorySelect from "@/components/category/category-select"; import { CheckCircle, XCircle } from "lucide-react"; import PublishArticleAlertDialog from "./publish-article-alert-dialog"; import { Label } from "@/components/ui/label"; -import Editor from "../editor"; +import dynamic from "next/dynamic"; +import { appRoutes } from "@/config"; +const Editor = dynamic(() => import("../editor"), { ssr: false }); export default ({ server_article }: { server_article: Article }) => { const [loading, setLoading] = React.useState(false); @@ -77,7 +79,7 @@ export default ({ server_article }: { server_article: Article }) => { { field.onChange(e); @@ -116,7 +118,7 @@ export default ({ server_article }: { server_article: Article }) => { // loading && "border-t-blue-600", )} > -
+
{
- ? Hilfe + Ansehen
{ { field.onChange(categoryId); diff --git a/src/components/article/grid/infinite-article-grid.tsx b/src/components/article/grid/infinite-article-grid.tsx index 30a9ad2..cfd1276 100644 --- a/src/components/article/grid/infinite-article-grid.tsx +++ b/src/components/article/grid/infinite-article-grid.tsx @@ -7,6 +7,7 @@ import ArticleCard from "../article-card"; import { useInfiniteItemsObserver } from "@/lib/hooks/infinite-items-observer-hook"; import { Skeleton } from "@/components/ui/skeleton"; import ArticleFilterBar, { ArticleFilter } from "../article-filter-bar"; +import { Article } from "@/server/db/schema"; function InfiniteArticlesGrid() { const [filter, setFilter] = React.useState( @@ -19,6 +20,9 @@ function InfiniteArticlesGrid() { }, { getNextPageParam: (lastPage) => lastPage.nextCursor, + // staleTime: 60 * 4 * 1000, // 4 minutes stale time + refetchOnMount: false, // Prevents unnecessary refetching + refetchOnWindowFocus: false, // Avoids refetch when switching tabs }, ); // Calculate all visible items across all loaded pages @@ -43,7 +47,7 @@ function InfiniteArticlesGrid() { {data?.pages?.length ? allItems.map((article, idx) => (
  • - +
  • )) : null} diff --git a/src/components/article/render-article.tsx b/src/components/article/render-article.tsx deleted file mode 100644 index ddd76c3..0000000 --- a/src/components/article/render-article.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -function RenderArticle({ content }: { content: string }) { - return "render article: in work"; //; -} - -export default RenderArticle; diff --git a/src/components/category/category-select.tsx b/src/components/category/category-select.tsx index 826b700..ffd03d7 100644 --- a/src/components/category/category-select.tsx +++ b/src/components/category/category-select.tsx @@ -5,7 +5,7 @@ import { Combobox, ComboboxProps } from "../combobox"; import { Icons } from "../icons"; function CategorySelect(props: Partial) { - const { data: categories } = api.category.getAll.useQuery(); + const { data: categories } = api.category.getMany.useQuery(); return ( ) { empty: "Keine Kategorien gefunden", }} data={ - categories?.map(({ name, id }) => ({ label: name!, value: id! })) ?? [] + categories?.map(({ name, slug }) => ({ label: name, value: slug })) ?? + [] } /> ); diff --git a/src/components/category/grid/category-grid.tsx b/src/components/category/grid/category-grid.tsx index c1ddb7e..f6a7bcb 100644 --- a/src/components/category/grid/category-grid.tsx +++ b/src/components/category/grid/category-grid.tsx @@ -9,8 +9,8 @@ export const CATEGORY_GRID_CLASS = function CategoryGrid({ categories }: { categories: Category[] }) { return ( - {categories.map((category) => ( -
  • + {categories.map((category, idx) => ( +
  • ))} diff --git a/src/components/category/grid/infinite-category-grid.tsx b/src/components/category/grid/infinite-category-grid.tsx index ca76780..2e91e27 100644 --- a/src/components/category/grid/infinite-category-grid.tsx +++ b/src/components/category/grid/infinite-category-grid.tsx @@ -18,6 +18,9 @@ export default function InfiniteCategoryGrid() { }, { getNextPageParam: (lastPage) => lastPage.nextCursor, + staleTime: 60 * 4 * 1000, // 4 minutes stale time + refetchOnMount: false, // Prevents unnecessary refetching + refetchOnWindowFocus: false, // Avoids refetch when switching tabs }, ); // Calculate all visible items across all loaded pages diff --git a/src/components/color-picker/color-picker-popover.tsx b/src/components/color-picker/color-picker-popover.tsx deleted file mode 100644 index 6ff6e22..0000000 --- a/src/components/color-picker/color-picker-popover.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client"; - -import ColorPicker, { type ColorPickerProps } from "."; -import { Button } from "../ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; - -export default function ColorPickerPopover(props: ColorPickerProps) { - return ( - - - - - - - - - ); -} diff --git a/src/components/color-picker/color-picker.css b/src/components/color-picker/color-picker.css deleted file mode 100644 index e68e5e3..0000000 --- a/src/components/color-picker/color-picker.css +++ /dev/null @@ -1,16 +0,0 @@ -.color-picker .react-colorful { - width: 100%; - height: 240px; -} -.color-picker .react-colorful__saturation { - border-radius: 4px 4px 0 0; -} -.color-picker .react-colorful__hue { - height: 40px; - border-radius: 0 0 4px 4px; -} -.color-picker .react-colorful__hue-pointer { - width: 12px; - height: inherit; - border-radius: 0; -} diff --git a/src/components/color-picker/index.tsx b/src/components/color-picker/index.tsx deleted file mode 100644 index 339430a..0000000 --- a/src/components/color-picker/index.tsx +++ /dev/null @@ -1,103 +0,0 @@ -"use client"; -import "./color-picker.css"; - -import React from "react"; -import { useState, useEffect } from "react"; -import { HexColorPicker } from "react-colorful"; -import { Input } from "../ui/input"; -import { debounce } from "@/lib/utils"; - -const STORAGE_KEY = "savedColors"; -const MAX_COLORS = 12; -const SELECT_DEBOUNCE = 500; - -export type ColorPickerProps = { - onInput?: (color: string) => void; - initialColor?: string; -}; - -function ColorPicker({ onInput, initialColor }: ColorPickerProps) { - const [mounted, setMounted] = useState(false); - const [customColors, setCustomColors] = useState([]); - const [color, setColor] = useState(initialColor ?? "#ff0000"); - - // Load colors from localStorage on mount - useEffect(() => { - if (initialColor?.length) setColor(initialColor); - const storageColors = localStorage.getItem(STORAGE_KEY); - const storedColors = storageColors ? JSON.parse(storageColors) : []; - if (storedColors.length) { - setCustomColors(storedColors); - if (!initialColor?.length) setColor(storedColors[0]); - setMounted(true); - } - }, []); - - const persistColor = (newColor: string) => { - if (newColor.length < 2) return; - if (customColors[0] === newColor) return; // Prevent duplicate consecutive colors - - const updatedColors = [ - newColor, - ...customColors.filter((c) => c !== newColor), - ].slice(0, MAX_COLORS); - console.log(updatedColors); - - setCustomColors(updatedColors); - localStorage.setItem(STORAGE_KEY, JSON.stringify(updatedColors)); - }; - - const selectColor = (newColor: string) => { - persistColor(newColor); - onInput?.(newColor); - }; - - const selectColorDebounced = debounce((newColor: string) => { - selectColor(newColor); - }, SELECT_DEBOUNCE); - - const handleColorChange = (newColor: string, skipDebounce = false) => { - setColor(newColor); - if (skipDebounce) - selectColor(newColor); // Delayed save - else selectColorDebounced(newColor); - }; - - return ( -
    - {/* React Colorful Picker */} - - - {/* Default Input (Native Color Picker) */} -
    -
    - handleColorChange(e.currentTarget.value)} - /> -
    - - {/* Display Recent Colors */} -
    - {customColors.map((col) => ( -
    -
    - ); -} - -export default ColorPicker; diff --git a/src/components/combobox.tsx b/src/components/combobox.tsx index d995fbd..c149d6f 100644 --- a/src/components/combobox.tsx +++ b/src/components/combobox.tsx @@ -50,6 +50,7 @@ export function Combobox({ const [open, setOpen] = React.useState(false); const [value, setValue] = React.useState(initialValue ?? ""); const selectedItem = data.find((item) => item.value === initialValue)!; + return ( diff --git a/src/components/data-table/index.tsx b/src/components/data-table/index.tsx index 92760f7..3dde896 100644 --- a/src/components/data-table/index.tsx +++ b/src/components/data-table/index.tsx @@ -36,7 +36,6 @@ export function DataTable({ columns, data, }: DataTableProps) { - "use no memo"; const [pagination, setPagination] = React.useState({ pageSize: 25, pageIndex: 0, diff --git a/src/components/editor/extentions/index.tsx b/src/components/editor/extentions/index.tsx index 77f04cf..fbbc69c 100644 --- a/src/components/editor/extentions/index.tsx +++ b/src/components/editor/extentions/index.tsx @@ -18,15 +18,15 @@ import { Youtube, } from "novel"; import LinkPreview from "./link-preview"; -import { Heading } from "@tiptap/extension-heading"; import { cx } from "class-variance-authority"; import { slashCommand } from "./slash-commands"; +import ImageResize from "tiptap-extension-resize-image"; const placeholder = Placeholder; const tiptapLink = TiptapLink.configure({ HTMLAttributes: { class: cx( - "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", + "underline text-foreground underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", ), }, }); @@ -46,6 +46,12 @@ const tiptapImage = TiptapImage.extend({ }, }); +const resizeImage = ImageResize.configure({ + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}); + const updatedImage = UpdatedImage.configure({ HTMLAttributes: { class: cx("rounded-lg border border-muted"), @@ -108,6 +114,7 @@ export const defaultExtensions = [ tiptapLink, tiptapImage, updatedImage, + resizeImage, taskList, taskItem, horizontalRule, diff --git a/src/components/editor/extentions/link-preview/link-preview-component.tsx b/src/components/editor/extentions/link-preview/link-preview-component.tsx index 5a639fe..445d8ea 100644 --- a/src/components/editor/extentions/link-preview/link-preview-component.tsx +++ b/src/components/editor/extentions/link-preview/link-preview-component.tsx @@ -28,7 +28,7 @@ const InputLink = ({ onSubmit }: { onSubmit: (link: string) => void }) => { inputRef.current?.focus(); }, []); return ( -
    + void }) => { placeholder="Enter a link" value={link} onChange={(e) => setLink(e.target.value)} - className="flex-1 focus-visible:ring-transparent border-0" + className="flex-1 border-0 focus-visible:ring-transparent" /> + +//
    +// Du hast bereits ein Konto?{" "} +// +// Anmelden +// +//
    +//
    +// +// + +//
    +// Image +//
    +// +// +// +//
    +// ); +// } + +// Login page + +// export function LoginForm({ +// className, +// ...props +// }: React.ComponentProps<"div">) { +// const form = useForm>({ +// resolver: zodResolver(loginSchema), +// defaultValues: { +// email: "", +// password: "", +// }, +// }); + +// // 2. Define a submit handler. +// async function onSubmit(values: z.infer) { +// const success = await login(values); +// // if (!success) toast.error("Login fehlgeschlagen"); +// // form.reset(); +// } +// return ( +// +// ); +// } + +// export function AuthProviderList() { +// return ( +//
    +// +// +// +//
    +// ); +// } + +// export function LeagalFooter() { +// return ( +//
    +// By clicking continue, you agree to our Terms of Service{" "} +// and Privacy Policy. +//
    +// ); +// } diff --git a/src/server/db/index.ts b/src/server/db/index.ts index df399a4..c4c4460 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -15,6 +15,8 @@ const globalForDb = globalThis as unknown as { const conn = globalForDb.conn ?? postgres(env.DATABASE_URL); if (env.NODE_ENV !== "production") globalForDb.conn = conn; -export const db = drizzle(conn, { schema }); +export const db = drizzle(conn, { + schema, +}); export type DBType = typeof db; diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 1a369ab..5c5bbc3 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -1,7 +1,6 @@ import { createId } from "@paralleldrive/cuid2"; -import { relations, SQL, sql } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { - AnyPgColumn, boolean, index, integer, @@ -16,11 +15,7 @@ import { User } from "next-auth"; import { type AdapterAccount } from "next-auth/adapters"; import { JSONContent } from "novel"; -export function lower(value: AnyPgColumn): SQL { - return sql`lower(${value})`; -} - -export const createTable = pgTableCreator((name) => `wiki-antifa_${name}`); +export const createTable = pgTableCreator((name) => `logipedia_${name}`); export const articles = createTable( "article", @@ -43,7 +38,9 @@ export const articles = createTable( ), }, (example) => ({ - articleTitleIndex: index("article_title_idx").on(example.title), + titleIndex: index("article_title_idx").on(example.title), + slugIndex: index("article_slug_idx").on(example.slug), + createdAtIndex: index("article_created_at_idx").on(example.createdAt), }), ); export type Article = typeof articles.$inferSelect & { @@ -81,7 +78,9 @@ export const categories = createTable( ), }, (example) => ({ - categoryNameIndex: index("category_name_idx").on(example.name), + nameIndex: index("category_name_idx").on(example.name), + slugameIndex: index("category_slug_idx").on(example.slug), + createdAtIndex: index("category_created_at_idx").on(example.createdAt), }), ); export type Category = typeof categories.$inferSelect & { @@ -105,6 +104,7 @@ export const users = createTable("user", { withTimezone: true, }).default(sql`CURRENT_TIMESTAMP`), image: varchar("image", { length: 255 }), + password: varchar("password", { length: 255 }), }); export const usersRelations = relations(users, ({ many }) => ({ diff --git a/src/styles/globals.css b/src/styles/globals.css index 29eab60..32095e9 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -50,7 +50,7 @@ --primary-foreground: 240 5.9% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; + --muted: 240 3.7% 5.9%; --muted-foreground: 240 5% 64.9%; --accent: 240 3.7% 15.9%; --accent-foreground: 0 0% 98%; @@ -96,7 +96,7 @@ transform: scaleX(1); /* Slight overshoot for bounce effect */ } 100% { - transform: scaleX(0.2); + transform: scaleX(0); } } diff --git a/tailwind.config.ts b/tailwind.config.ts index e644c75..0941f1a 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -69,5 +69,25 @@ export default { }, }, }, - plugins: [require("tailwindcss-animate")], + plugins: [ + require("tailwindcss-animate"), + function ({ addBase, theme }: any) { + function extractColorVars(colorObj: any, colorGroup = "") { + return Object.keys(colorObj).reduce((vars: any, colorKey: any) => { + const value = colorObj[colorKey]; + + const newVars: any = + typeof value === "string" + ? { [`--color${colorGroup}-${colorKey}`]: value } + : extractColorVars(value, `-${colorKey}`); + + return { ...vars, ...newVars }; + }, {}); + } + + addBase({ + ":root": extractColorVars(theme("colors")), + }); + }, + ], } satisfies Config;