From 6bfccede35eb13d9a476c6a41bf3edd5a77053ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Demirel?= Date: Sat, 4 Apr 2026 16:45:24 +0300 Subject: [PATCH] feat: article comments --- .github/workflows/discussions.yml | 31 ++ ...3ae993ef635ba2b83929b9f3750000019d584eb91a | 1 - ...fd118dde19c7ea5856716a903b0000019d58ab7543 | 1 + ...b76f487f9ea62b27bfd0b4e8c0000019d58abd69d} | 0 ...31ec1b033fba0574bb30dd3f5e0000019d5849f694 | 1 - ...f94b433711e7b7cb035b8b8cca0000019d58abd69d | 1 + ...43724c857ce74a35b86238b75e6e807ff7f.sqlite | Bin 16384 -> 16384 bytes astro.config.mjs | 2 + package.json | 3 + pnpm-lock.yaml | 407 ++++++++++++++++++ scripts/create-discussion.mjs | 153 +++++++ src/components/discussion/CommentItem.tsx | 196 +++++++++ src/components/discussion/Discussion.tsx | 128 ++++++ src/components/discussion/types.ts | 24 ++ src/config.mjs | 2 + src/pages/api/auth/login.ts | 2 +- src/pages/api/discussions/[slug].ts | 121 ++++++ src/pages/api/discussions/comment.ts | 108 +++++ src/pages/api/discussions/react.ts | 76 ++++ src/pages/articles/[slug].astro | 3 + 20 files changed, 1257 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/discussions.yml delete mode 100644 .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/88a48a5ca411d99cd63c9bb308aa3868125abf3ae993ef635ba2b83929b9f3750000019d584eb91a create mode 100644 .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/b3d6638997825f65360e0cbd969cd990495ad0fd118dde19c7ea5856716a903b0000019d58ab7543 rename .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/{1945ae1d9220b9b9595df3b70c7f12606f5505a287016a57770571eee21dc7690000019d584eb91f => d18e89a6949134ecfb1db7ca34893fec965ff42b76f487f9ea62b27bfd0b4e8c0000019d58abd69d} (100%) delete mode 100644 .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/d30c0dd40129710b5dbf376654872cdd10c68631ec1b033fba0574bb30dd3f5e0000019d5849f694 create mode 100644 .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/da8c8c19db7816a6a84a300785827f493670aff94b433711e7b7cb035b8b8cca0000019d58abd69d create mode 100644 scripts/create-discussion.mjs create mode 100644 src/components/discussion/CommentItem.tsx create mode 100644 src/components/discussion/Discussion.tsx create mode 100644 src/components/discussion/types.ts create mode 100644 src/pages/api/discussions/[slug].ts create mode 100644 src/pages/api/discussions/comment.ts create mode 100644 src/pages/api/discussions/react.ts diff --git a/.github/workflows/discussions.yml b/.github/workflows/discussions.yml new file mode 100644 index 0000000..9f2f745 --- /dev/null +++ b/.github/workflows/discussions.yml @@ -0,0 +1,31 @@ +name: Sync Article Discussions + +on: + push: + branches: + - main + paths: + - 'src/content/articles/**' + workflow_dispatch: + +jobs: + sync_discussions: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm ci || npm install --legacy-peer-deps + + - name: Run script + env: + GITHUB_REPO_OWNER: ${{ github.repository_owner }} + GITHUB_REPO_NAME: ${{ github.event.repository.name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/create-discussion.mjs diff --git a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/88a48a5ca411d99cd63c9bb308aa3868125abf3ae993ef635ba2b83929b9f3750000019d584eb91a b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/88a48a5ca411d99cd63c9bb308aa3868125abf3ae993ef635ba2b83929b9f3750000019d584eb91a deleted file mode 100644 index 56a6051..0000000 --- a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/88a48a5ca411d99cd63c9bb308aa3868125abf3ae993ef635ba2b83929b9f3750000019d584eb91a +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/b3d6638997825f65360e0cbd969cd990495ad0fd118dde19c7ea5856716a903b0000019d58ab7543 b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/b3d6638997825f65360e0cbd969cd990495ad0fd118dde19c7ea5856716a903b0000019d58ab7543 new file mode 100644 index 0000000..301160a --- /dev/null +++ b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/b3d6638997825f65360e0cbd969cd990495ad0fd118dde19c7ea5856716a903b0000019d58ab7543 @@ -0,0 +1 @@ +8 \ No newline at end of file diff --git a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/1945ae1d9220b9b9595df3b70c7f12606f5505a287016a57770571eee21dc7690000019d584eb91f b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/d18e89a6949134ecfb1db7ca34893fec965ff42b76f487f9ea62b27bfd0b4e8c0000019d58abd69d similarity index 100% rename from .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/1945ae1d9220b9b9595df3b70c7f12606f5505a287016a57770571eee21dc7690000019d584eb91f rename to .wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/d18e89a6949134ecfb1db7ca34893fec965ff42b76f487f9ea62b27bfd0b4e8c0000019d58abd69d diff --git a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/d30c0dd40129710b5dbf376654872cdd10c68631ec1b033fba0574bb30dd3f5e0000019d5849f694 b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/d30c0dd40129710b5dbf376654872cdd10c68631ec1b033fba0574bb30dd3f5e0000019d5849f694 deleted file mode 100644 index c793025..0000000 --- a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/d30c0dd40129710b5dbf376654872cdd10c68631ec1b033fba0574bb30dd3f5e0000019d5849f694 +++ /dev/null @@ -1 +0,0 @@ -7 \ No newline at end of file diff --git a/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/da8c8c19db7816a6a84a300785827f493670aff94b433711e7b7cb035b8b8cca0000019d58abd69d b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/da8c8c19db7816a6a84a300785827f493670aff94b433711e7b7cb035b8b8cca0000019d58abd69d new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/.wrangler/state/v3/kv/0c407860ba17425581796410dbce9e19/blobs/da8c8c19db7816a6a84a300785827f493670aff94b433711e7b7cb035b8b8cca0000019d58abd69d @@ -0,0 +1 @@ +5 \ No newline at end of file diff --git a/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/d1866dd99f5441005a55b0a86f46e43724c857ce74a35b86238b75e6e807ff7f.sqlite b/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/d1866dd99f5441005a55b0a86f46e43724c857ce74a35b86238b75e6e807ff7f.sqlite index 44557415355c6f9aa6e8ea0c9514e320590c1740..f1b2f98a06b13712db195b54cc0c1bc2cdf8ede1 100644 GIT binary patch delta 425 zcmZ9HJx&8L5QTS@CEX9u|}Sw0E61ud~bPXwqBef5ZQ`DVq1im<>;iuZ;b$FZSIsKG?X$_Ip|0{iutg Gi{US<>2yv2 delta 403 zcmZXPJxW7C6ou!J5JD0hY+@0KD+rl8Gk0b#mhMBCUkoG>vH_os8+ciWXrY@>u(!2L zV`F3CdtJa=oaP?Bb531XbzMEa%&T$pI={Gc&THo}dJT|>VlpaH2ue8bl`{?``cRb5 zL@F1fFnAf9B1wUYvM9O(O6YVB1pa0?fcphw>(w^<-C}c_@_Mn&X|+pAM3IsfAjyIV zm|E_YH3qe_GNuF~8fO&b2tg=Sf){9Y*Ci!YFds!xF{C$>!DScKd@Rqdfi(KuPLNWB zwi^hIcEteT0=+hZbupMFV^FG#^3o9-#?#<2j1+L zv)*bqZr+x&^U1Ua)&G%0#@MVYbTZ<=22.12.0'} + '@astrojs/react@5.0.2': + resolution: {integrity: sha512-BDpPrapV3Wgp9sD7aTMvP+ORH0jFEue9OmkBu98KcBbTlsQCnvisDW3m7PQrMptXwEDlX5HGfP/CHmkEVY2tZA==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + '@astrojs/sitemap@3.7.1': resolution: {integrity: sha512-IzQqdTeskaMX+QDZCzMuJIp8A8C1vgzMBp/NmHNnadepHYNHcxQdGLQZYfkbd2EbRXUfOS+UDIKx8sKg0oWVdw==} @@ -189,6 +207,44 @@ packages: '@astrojs/yaml2ts@0.2.3': resolution: {integrity: sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -197,11 +253,39 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -1115,6 +1199,9 @@ packages: resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} engines: {node: '>= 10'} + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -1311,6 +1398,18 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -1446,6 +1545,14 @@ packages: '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -1572,6 +1679,12 @@ packages: peerDependencies: vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0 + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@volar/kit@2.4.28': resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} peerDependencies: @@ -1702,6 +1815,11 @@ packages: resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} engines: {node: '>= 0.4'} + baseline-browser-mapping@2.10.14: + resolution: {integrity: sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==} + engines: {node: '>=6.0.0'} + hasBin: true + blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} @@ -1716,6 +1834,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1729,6 +1852,9 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + caniuse-lite@1.0.30001785: + resolution: {integrity: sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1832,6 +1958,9 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} @@ -1891,6 +2020,9 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} @@ -2087,6 +2219,9 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + electron-to-chromium@1.5.331: + resolution: {integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==} + emmet@2.4.11: resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} @@ -2344,6 +2479,10 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2539,6 +2678,9 @@ packages: jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -2547,6 +2689,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2559,6 +2706,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonc-parser@2.3.1: resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} @@ -2692,6 +2844,9 @@ packages: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-regexp@0.10.0: resolution: {integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==} @@ -2945,6 +3100,9 @@ packages: node-mock-http@1.0.4: resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3124,6 +3282,19 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3267,10 +3438,17 @@ packages: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -3633,6 +3811,12 @@ packages: uploadthing: optional: true + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3861,6 +4045,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -4079,6 +4266,31 @@ snapshots: dependencies: prismjs: 1.30.0 + '@astrojs/react@5.0.2(@types/node@25.5.0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(jiti@2.6.1)(lightningcss@1.32.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(terser@5.46.0)(yaml@2.8.2)': + dependencies: + '@astrojs/internal-helpers': 0.8.0 + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': 5.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(yaml@2.8.2)) + devalue: 5.6.4 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + ultrahtml: 1.6.0 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(yaml@2.8.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@astrojs/sitemap@3.7.1': dependencies: sitemap: 9.0.1 @@ -4103,14 +4315,113 @@ snapshots: dependencies: yaml: 2.8.2 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -4766,6 +5077,8 @@ snapshots: '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 '@resvg/resvg-js-win32-x64-msvc': 2.6.2 + '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 @@ -4905,6 +5218,27 @@ snapshots: tslib: 2.8.1 optional: true + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + '@types/d3-array@3.2.2': {} '@types/d3-axis@3.0.6': @@ -5064,6 +5398,14 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + '@types/sax@1.2.7': dependencies: '@types/node': 25.5.0 @@ -5256,6 +5598,18 @@ snapshots: unplugin-utils: 0.3.1 vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(yaml@2.8.2) + '@vitejs/plugin-react@5.2.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + '@volar/kit@2.4.28(typescript@5.9.3)': dependencies: '@volar/language-service': 2.4.28 @@ -5497,6 +5851,8 @@ snapshots: base64-js@0.0.8: {} + baseline-browser-mapping@2.10.14: {} + blake3-wasm@2.1.5: {} boolbase@1.0.0: {} @@ -5509,6 +5865,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.14 + caniuse-lite: 1.0.30001785 + electron-to-chromium: 1.5.331 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-crc32@0.2.13: {} buffer-from@1.1.2: @@ -5518,6 +5882,8 @@ snapshots: camelize@1.0.1: {} + caniuse-lite@1.0.30001785: {} + ccount@2.0.1: {} character-entities-html4@2.1.0: {} @@ -5611,6 +5977,8 @@ snapshots: consola@3.4.2: {} + convert-source-map@2.0.0: {} + cookie-es@1.2.2: {} cookie@1.1.1: {} @@ -5670,6 +6038,8 @@ snapshots: dependencies: css-tree: 2.2.1 + csstype@3.2.3: {} + d3-array@3.2.4: dependencies: internmap: 2.0.3 @@ -5878,6 +6248,8 @@ snapshots: duplexer@0.1.2: {} + electron-to-chromium@1.5.331: {} + emmet@2.4.11: dependencies: '@emmetio/abbreviation': 2.3.3 @@ -6210,6 +6582,8 @@ snapshots: fsevents@2.3.3: optional: true + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-east-asian-width@1.5.0: {} @@ -6474,6 +6848,8 @@ snapshots: jose@6.2.2: {} + js-tokens@4.0.0: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -6483,6 +6859,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -6491,6 +6869,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + jsonc-parser@2.3.1: {} jsonc-parser@3.3.1: {} @@ -6609,6 +6989,10 @@ snapshots: lru-cache@11.2.7: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + magic-regexp@0.10.0: dependencies: estree-walker: 3.0.3 @@ -7154,6 +7538,8 @@ snapshots: node-mock-http@1.0.4: {} + node-releases@2.0.37: {} + normalize-path@3.0.0: {} nth-check@2.1.1: @@ -7361,6 +7747,15 @@ snapshots: radix3@1.1.2: {} + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-refresh@0.18.0: {} + + react@19.2.4: {} + readdirp@4.1.2: {} readdirp@5.0.0: {} @@ -7611,11 +8006,15 @@ snapshots: sax@1.6.0: {} + scheduler@0.27.0: {} + section-matter@1.0.0: dependencies: extend-shallow: 2.0.1 kind-of: 6.0.3 + semver@6.3.1: {} + semver@7.7.4: {} sharp@0.34.5: @@ -7993,6 +8392,12 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.3 + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -8193,6 +8598,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@5.0.0: {} yaml-language-server@1.20.0: diff --git a/scripts/create-discussion.mjs b/scripts/create-discussion.mjs new file mode 100644 index 0000000..d341391 --- /dev/null +++ b/scripts/create-discussion.mjs @@ -0,0 +1,153 @@ +import * as fs from 'fs' +import * as path from 'path' +import { fileURLToPath } from 'url' +import matter from 'gray-matter' +import { SITE } from '../src/config.mjs' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const GITHUB_GRAPHQL_API = 'https://api.github.com/graphql' + +const OWNER = process.env.GITHUB_REPO_OWNER +const REPO_NAME = process.env.GITHUB_REPO_NAME +const TOKEN = process.env.GITHUB_TOKEN + +if (!OWNER || !REPO_NAME || !TOKEN) { + console.error('Missing required environment variables: GITHUB_REPO_OWNER, GITHUB_REPO_NAME, GITHUB_TOKEN') + process.exit(1) +} + +async function githubGraphQL(query, variables = {}) { + const response = await fetch(GITHUB_GRAPHQL_API, { + method: 'POST', + headers: { + Authorization: `Bearer ${TOKEN}`, + 'Content-Type': 'application/json', + 'User-Agent': '4byte-dev' + }, + body: JSON.stringify({ query, variables }) + }) + const data = await response.json() + if (data.errors) { + console.error('GraphQL errors:', JSON.stringify(data.errors, null, 2)) + throw new Error('GraphQL query failed') + } + return data.data +} + +async function getRepositoryAndCategory() { + const query = ` + query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + id + discussionCategories(first: 10) { + nodes { + id + name + slug + } + } + } + } + ` + const data = await githubGraphQL(query, { owner: OWNER, name: REPO_NAME }) + if (!data.repository) throw new Error('Repository not found') + + const categoryName = SITE.discussionCategory || 'General' + const category = data.repository.discussionCategories.nodes.find( + c => c.name === categoryName || c.name.toLowerCase() === categoryName.toLowerCase() || c.slug === categoryName.toLowerCase().replace(/\s+/g, '-') + ) || data.repository.discussionCategories.nodes[0] + + return { + repositoryId: data.repository.id, + categoryId: category.id + } +} + +async function getExistingDiscussions() { + const query = ` + query($searchQuery: String!) { + search(query: $searchQuery, type: DISCUSSION, first: 100) { + nodes { + ... on Discussion { + title + } + } + } + } + ` + const searchQuery = `repo:${OWNER}/${REPO_NAME} type:discussion` + const data = await githubGraphQL(query, { searchQuery }) + return data.search.nodes.map(node => node.title) +} + +async function createDiscussion(repositoryId, categoryId, title, body) { + const query = ` + mutation($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) { + createDiscussion(input: { + repositoryId: $repositoryId, + categoryId: $categoryId, + title: $title, + body: $body + }) { + discussion { + id + url + } + } + } + ` + await githubGraphQL(query, { repositoryId, categoryId, title, body }) +} + +async function main() { + try { + console.log('Fetching repository and discussion categories...') + const { repositoryId, categoryId } = await getRepositoryAndCategory() + + console.log('Fetching existing discussions...') + const existingTitles = await getExistingDiscussions() + + const articlesDir = path.join(__dirname, '../src/content/articles') + + function getFiles(dir) { + const dirents = fs.readdirSync(dir, { withFileTypes: true }); + const files = dirents.map((dirent) => { + const res = path.resolve(dir, dirent.name); + return dirent.isDirectory() ? getFiles(res) : res; + }); + return Array.prototype.concat(...files); + } + + const files = getFiles(articlesDir) + + for (const filePath of files) { + if (!filePath.endsWith('.mdx') && !filePath.endsWith('.md')) continue + + const content = fs.readFileSync(filePath, 'utf-8') + const { data } = matter(content) + + if (data.status !== 'Published') continue + + const slug = data.slug || path.basename(filePath).replace(/\.mdx?$/, '') + + if (existingTitles.includes(slug)) { + console.log(`Discussion already exists for: ${slug}`) + continue + } + + console.log(`Creating discussion for: ${slug}...`) + const body = `This is the discussion thread for the article: **${data.title}**. \n\nRead it here: https://4byte.dev/articles/${slug}` + await createDiscussion(repositoryId, categoryId, slug, body) + console.log(`Created!`) + } + + console.log('Done syncing discussions.') + } catch (error) { + console.error('Error:', error) + process.exit(1) + } +} + +main() diff --git a/src/components/discussion/CommentItem.tsx b/src/components/discussion/CommentItem.tsx new file mode 100644 index 0000000..ad07c7d --- /dev/null +++ b/src/components/discussion/CommentItem.tsx @@ -0,0 +1,196 @@ +import { useState } from 'react' +import type { Comment } from './types' + +export interface CommentItemProps { + comment: Comment + onReply?: (commentId: string) => void + isReply?: boolean + loggedIn?: boolean +} + +const REACTION_EMOJIS: Record = { + THUMBS_UP: 'πŸ‘', + THUMBS_DOWN: 'πŸ‘Ž', + LAUGH: 'πŸ˜„', + HOORAY: 'πŸŽ‰', + CONFUSED: 'πŸ˜•', + HEART: '❀️', + ROCKET: 'πŸš€', + EYES: 'πŸ‘€', +} + +export default function CommentItem({ comment: initialComment, onReply, isReply = false, loggedIn = false }: CommentItemProps) { + const [comment, setComment] = useState(initialComment) + const [replyBoxOpen, setReplyBoxOpen] = useState(false) + const [replyText, setReplyText] = useState('') + const [replying, setReplying] = useState(false) + + const hasTokens = loggedIn + + const date = new Date(comment.createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }) + + const submitReply = async () => { + if (!replyText.trim() || !hasTokens) return + setReplying(true) + try { + const res = await fetch('/api/discussions/comment', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ subjectId: comment.id, body: replyText, isReply: true }) + }) + const data = await res.json() + if (data.comment) { + setComment({ + ...comment, + replies: { + nodes: [...(comment.replies?.nodes || []), data.comment] + } + }) + setReplyText('') + setReplyBoxOpen(false) + } + } catch (error) { + console.error(error) + } finally { + setReplying(false) + } + } + + const toggleReaction = async (content: string, viewerHasReacted: boolean) => { + if (!hasTokens) return + const action = viewerHasReacted ? 'remove' : 'add' + + // Optimistic update + const newReactions = comment.reactionGroups?.map(r => { + if (r.content === content) { + return { + ...r, + viewerHasReacted: !viewerHasReacted, + users: { totalCount: r.users.totalCount + (viewerHasReacted ? -1 : 1) } + } + } + return r + }) || [] + + const didExist = newReactions.find(r => r.content === content) + if (!didExist && action === 'add') { + newReactions.push({ content, viewerHasReacted: true, users: { totalCount: 1 } }) + } + + setComment({ ...comment, reactionGroups: newReactions }) + + try { + await fetch('/api/discussions/react', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ subjectId: comment.id, content, action }) + }) + } catch (error) { + // Revert on error + setComment(initialComment) + } + } + + return ( +
+ + {comment.author.login} + + +
+
+
+
+ + {comment.author.login} + + + on {date} + +
+
+
+
+ +
+ {['THUMBS_UP', 'HEART', 'ROCKET'].map(content => { + const reaction = comment.reactionGroups?.find(r => r.content === content) + const count = reaction?.users.totalCount || 0 + const userReacted = reaction?.viewerHasReacted || false + + if (count === 0 && !hasTokens) return null + + return ( + + ) + })} + + {hasTokens && !isReply && ( + + )} +
+ + {replyBoxOpen && ( +
+
+