fin prototype

This commit is contained in:
2026-04-17 03:49:48 +02:00
parent 586cd69b68
commit eb9a37a90e
15 changed files with 897 additions and 85 deletions

554
deno.lock generated
View File

@@ -4,8 +4,10 @@
"npm:eslint-config-next@16.2.3": "16.2.3_eslint@9.39.4_typescript@6.0.2",
"npm:eslint@9": "9.39.4",
"npm:next@16.2.3": "16.2.3_react@19.2.4_react-dom@19.2.4__react@19.2.4_sass@1.99.0",
"npm:prettier@^3.8.3": "3.8.3",
"npm:react-dom@19.2.4": "19.2.4_react@19.2.4",
"npm:react-drag-drop-files@^3.1.0": "3.1.0_react@19.2.4_react-dom@19.2.4__react@19.2.4",
"npm:react-markdown@^10.1.0": "10.1.0_@types+react@19.2.14_react@19.2.4",
"npm:react@19.2.4": "19.2.4",
"npm:sass@^1.99.0": "1.99.0"
},
@@ -595,15 +597,54 @@
"tslib"
]
},
"@types/debug@4.1.13": {
"integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
"dependencies": [
"@types/ms"
]
},
"@types/estree-jsx@1.0.5": {
"integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
"dependencies": [
"@types/estree"
]
},
"@types/estree@1.0.8": {
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
"@types/hast@3.0.4": {
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dependencies": [
"@types/unist@3.0.3"
]
},
"@types/json-schema@7.0.15": {
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"@types/json5@0.0.29": {
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"@types/mdast@4.0.4": {
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
"dependencies": [
"@types/unist@3.0.3"
]
},
"@types/ms@2.1.0": {
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
},
"@types/react@19.2.14": {
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dependencies": [
"csstype"
]
},
"@types/unist@2.0.11": {
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
},
"@types/unist@3.0.3": {
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
"@typescript-eslint/eslint-plugin@8.58.2_@typescript-eslint+parser@8.58.2__eslint@9.39.4__typescript@6.0.2_eslint@9.39.4_typescript@6.0.2": {
"integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==",
"dependencies": [
@@ -702,6 +743,9 @@
"eslint-visitor-keys@5.0.1"
]
},
"@ungap/structured-clone@1.3.0": {
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="
},
"@unrs/resolver-binding-android-arm-eabi@1.11.1": {
"integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
"os": ["android"],
@@ -931,6 +975,9 @@
"axobject-query@4.1.0": {
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
},
"bail@2.0.2": {
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
},
"balanced-match@1.0.2": {
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
@@ -1000,6 +1047,9 @@
"caniuse-lite@1.0.30001788": {
"integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="
},
"ccount@2.0.1": {
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
},
"chalk@4.1.2": {
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": [
@@ -1007,6 +1057,18 @@
"supports-color"
]
},
"character-entities-html4@2.1.0": {
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="
},
"character-entities-legacy@3.0.0": {
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="
},
"character-entities@2.0.2": {
"integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="
},
"character-reference-invalid@2.0.1": {
"integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="
},
"chokidar@4.0.3": {
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dependencies": [
@@ -1025,6 +1087,9 @@
"color-name@1.1.4": {
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"comma-separated-tokens@2.0.3": {
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="
},
"concat-map@0.0.1": {
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
@@ -1081,6 +1146,12 @@
"ms"
]
},
"decode-named-character-reference@1.3.0": {
"integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
"dependencies": [
"character-entities"
]
},
"deep-is@0.1.4": {
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
},
@@ -1100,9 +1171,18 @@
"object-keys"
]
},
"dequal@2.0.3": {
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
},
"detect-libc@2.1.2": {
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="
},
"devlop@1.1.0": {
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
"dependencies": [
"dequal"
]
},
"doctrine@2.1.0": {
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
"dependencies": [
@@ -1454,9 +1534,15 @@
"estraverse@5.3.0": {
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
},
"estree-util-is-identifier-name@3.0.0": {
"integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="
},
"esutils@2.0.3": {
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"extend@3.0.2": {
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fast-deep-equal@3.1.3": {
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
@@ -1646,6 +1732,32 @@
"function-bind"
]
},
"hast-util-to-jsx-runtime@2.3.6": {
"integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
"dependencies": [
"@types/estree",
"@types/hast",
"@types/unist@3.0.3",
"comma-separated-tokens",
"devlop",
"estree-util-is-identifier-name",
"hast-util-whitespace",
"mdast-util-mdx-expression",
"mdast-util-mdx-jsx",
"mdast-util-mdxjs-esm",
"property-information",
"space-separated-tokens",
"style-to-js",
"unist-util-position",
"vfile-message"
]
},
"hast-util-whitespace@3.0.0": {
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
"dependencies": [
"@types/hast"
]
},
"hermes-estree@0.25.1": {
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="
},
@@ -1655,6 +1767,9 @@
"hermes-estree"
]
},
"html-url-attributes@3.0.1": {
"integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="
},
"ignore@5.3.2": {
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="
},
@@ -1674,6 +1789,9 @@
"imurmurhash@0.1.4": {
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
},
"inline-style-parser@0.2.7": {
"integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="
},
"internal-slot@1.1.0": {
"integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
"dependencies": [
@@ -1682,6 +1800,16 @@
"side-channel"
]
},
"is-alphabetical@2.0.1": {
"integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="
},
"is-alphanumerical@2.0.1": {
"integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
"dependencies": [
"is-alphabetical",
"is-decimal"
]
},
"is-array-buffer@3.0.5": {
"integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
"dependencies": [
@@ -1743,6 +1871,9 @@
"has-tostringtag"
]
},
"is-decimal@2.0.1": {
"integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="
},
"is-extglob@2.1.1": {
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
},
@@ -1768,6 +1899,9 @@
"is-extglob"
]
},
"is-hexadecimal@2.0.1": {
"integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="
},
"is-map@2.0.3": {
"integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="
},
@@ -1784,6 +1918,9 @@
"is-number@7.0.0": {
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-plain-obj@4.1.0": {
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="
},
"is-regex@1.2.1": {
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"dependencies": [
@@ -1930,6 +2067,9 @@
"lodash.merge@4.6.2": {
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"longest-streak@3.1.0": {
"integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="
},
"loose-envify@1.4.0": {
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": [
@@ -1946,9 +2086,275 @@
"math-intrinsics@1.1.0": {
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mdast-util-from-markdown@2.0.3": {
"integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
"dependencies": [
"@types/mdast",
"@types/unist@3.0.3",
"decode-named-character-reference",
"devlop",
"mdast-util-to-string",
"micromark",
"micromark-util-decode-numeric-character-reference",
"micromark-util-decode-string",
"micromark-util-normalize-identifier",
"micromark-util-symbol",
"micromark-util-types",
"unist-util-stringify-position"
]
},
"mdast-util-mdx-expression@2.0.1": {
"integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
"dependencies": [
"@types/estree-jsx",
"@types/hast",
"@types/mdast",
"devlop",
"mdast-util-from-markdown",
"mdast-util-to-markdown"
]
},
"mdast-util-mdx-jsx@3.2.0": {
"integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
"dependencies": [
"@types/estree-jsx",
"@types/hast",
"@types/mdast",
"@types/unist@3.0.3",
"ccount",
"devlop",
"mdast-util-from-markdown",
"mdast-util-to-markdown",
"parse-entities",
"stringify-entities",
"unist-util-stringify-position",
"vfile-message"
]
},
"mdast-util-mdxjs-esm@2.0.1": {
"integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
"dependencies": [
"@types/estree-jsx",
"@types/hast",
"@types/mdast",
"devlop",
"mdast-util-from-markdown",
"mdast-util-to-markdown"
]
},
"mdast-util-phrasing@4.1.0": {
"integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
"dependencies": [
"@types/mdast",
"unist-util-is"
]
},
"mdast-util-to-hast@13.2.1": {
"integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
"dependencies": [
"@types/hast",
"@types/mdast",
"@ungap/structured-clone",
"devlop",
"micromark-util-sanitize-uri",
"trim-lines",
"unist-util-position",
"unist-util-visit",
"vfile"
]
},
"mdast-util-to-markdown@2.1.2": {
"integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
"dependencies": [
"@types/mdast",
"@types/unist@3.0.3",
"longest-streak",
"mdast-util-phrasing",
"mdast-util-to-string",
"micromark-util-classify-character",
"micromark-util-decode-string",
"unist-util-visit",
"zwitch"
]
},
"mdast-util-to-string@4.0.0": {
"integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
"dependencies": [
"@types/mdast"
]
},
"merge2@1.4.1": {
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
"micromark-core-commonmark@2.0.3": {
"integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
"dependencies": [
"decode-named-character-reference",
"devlop",
"micromark-factory-destination",
"micromark-factory-label",
"micromark-factory-space",
"micromark-factory-title",
"micromark-factory-whitespace",
"micromark-util-character",
"micromark-util-chunked",
"micromark-util-classify-character",
"micromark-util-html-tag-name",
"micromark-util-normalize-identifier",
"micromark-util-resolve-all",
"micromark-util-subtokenize",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-factory-destination@2.0.1": {
"integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
"dependencies": [
"micromark-util-character",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-factory-label@2.0.1": {
"integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
"dependencies": [
"devlop",
"micromark-util-character",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-factory-space@2.0.1": {
"integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
"dependencies": [
"micromark-util-character",
"micromark-util-types"
]
},
"micromark-factory-title@2.0.1": {
"integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
"dependencies": [
"micromark-factory-space",
"micromark-util-character",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-factory-whitespace@2.0.1": {
"integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
"dependencies": [
"micromark-factory-space",
"micromark-util-character",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-util-character@2.1.1": {
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
"dependencies": [
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-util-chunked@2.0.1": {
"integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
"dependencies": [
"micromark-util-symbol"
]
},
"micromark-util-classify-character@2.0.1": {
"integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
"dependencies": [
"micromark-util-character",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-util-combine-extensions@2.0.1": {
"integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
"dependencies": [
"micromark-util-chunked",
"micromark-util-types"
]
},
"micromark-util-decode-numeric-character-reference@2.0.2": {
"integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
"dependencies": [
"micromark-util-symbol"
]
},
"micromark-util-decode-string@2.0.1": {
"integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
"dependencies": [
"decode-named-character-reference",
"micromark-util-character",
"micromark-util-decode-numeric-character-reference",
"micromark-util-symbol"
]
},
"micromark-util-encode@2.0.1": {
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="
},
"micromark-util-html-tag-name@2.0.1": {
"integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="
},
"micromark-util-normalize-identifier@2.0.1": {
"integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
"dependencies": [
"micromark-util-symbol"
]
},
"micromark-util-resolve-all@2.0.1": {
"integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
"dependencies": [
"micromark-util-types"
]
},
"micromark-util-sanitize-uri@2.0.1": {
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
"dependencies": [
"micromark-util-character",
"micromark-util-encode",
"micromark-util-symbol"
]
},
"micromark-util-subtokenize@2.1.0": {
"integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
"dependencies": [
"devlop",
"micromark-util-chunked",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromark-util-symbol@2.0.1": {
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="
},
"micromark-util-types@2.0.2": {
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="
},
"micromark@4.0.2": {
"integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
"dependencies": [
"@types/debug",
"debug@4.4.3",
"decode-named-character-reference",
"devlop",
"micromark-core-commonmark",
"micromark-factory-space",
"micromark-util-character",
"micromark-util-chunked",
"micromark-util-combine-extensions",
"micromark-util-decode-numeric-character-reference",
"micromark-util-encode",
"micromark-util-normalize-identifier",
"micromark-util-resolve-all",
"micromark-util-sanitize-uri",
"micromark-util-subtokenize",
"micromark-util-symbol",
"micromark-util-types"
]
},
"micromatch@4.0.8": {
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [
@@ -2121,6 +2527,18 @@
"callsites"
]
},
"parse-entities@4.0.2": {
"integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
"dependencies": [
"@types/unist@2.0.11",
"character-entities-legacy",
"character-reference-invalid",
"decode-named-character-reference",
"is-alphanumerical",
"is-decimal",
"is-hexadecimal"
]
},
"path-exists@4.0.0": {
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
@@ -2153,6 +2571,10 @@
"prelude-ls@1.2.1": {
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
"prettier@3.8.3": {
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"bin": true
},
"prop-types@15.8.1": {
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dependencies": [
@@ -2161,6 +2583,9 @@
"react-is"
]
},
"property-information@7.1.0": {
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="
},
"punycode@2.3.1": {
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
@@ -2186,6 +2611,24 @@
"react-is@16.13.1": {
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-markdown@10.1.0_@types+react@19.2.14_react@19.2.4": {
"integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
"dependencies": [
"@types/hast",
"@types/mdast",
"@types/react",
"devlop",
"hast-util-to-jsx-runtime",
"html-url-attributes",
"mdast-util-to-hast",
"react",
"remark-parse",
"remark-rehype",
"unified",
"unist-util-visit",
"vfile"
]
},
"react@19.2.4": {
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="
},
@@ -2216,6 +2659,25 @@
"set-function-name"
]
},
"remark-parse@11.0.0": {
"integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
"dependencies": [
"@types/mdast",
"mdast-util-from-markdown",
"micromark-util-types",
"unified"
]
},
"remark-rehype@11.1.2": {
"integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
"dependencies": [
"@types/hast",
"@types/mdast",
"mdast-util-to-hast",
"unified",
"vfile"
]
},
"resolve-from@4.0.0": {
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
},
@@ -2402,6 +2864,9 @@
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"space-separated-tokens@2.0.2": {
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="
},
"stable-hash@0.0.5": {
"integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="
},
@@ -2474,12 +2939,31 @@
"es-object-atoms"
]
},
"stringify-entities@4.0.4": {
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
"dependencies": [
"character-entities-html4",
"character-entities-legacy"
]
},
"strip-bom@3.0.0": {
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
},
"strip-json-comments@3.1.1": {
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
},
"style-to-js@1.1.21": {
"integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
"dependencies": [
"style-to-object"
]
},
"style-to-object@1.0.14": {
"integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
"dependencies": [
"inline-style-parser"
]
},
"styled-components@6.4.0_react@19.2.4_react-dom@19.2.4__react@19.2.4": {
"integrity": "sha512-BL1EDFpt+q10eAeZB0q9ps6pSlPejaBQWBkiuM16pyoVTG4NhZrPrZK0cqNbrozxSsYwUsJ9SQYN6NyeKJYX9A==",
"dependencies": [
@@ -2525,6 +3009,12 @@
"is-number"
]
},
"trim-lines@3.0.1": {
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="
},
"trough@2.2.0": {
"integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="
},
"ts-api-utils@2.5.0_typescript@6.0.2": {
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dependencies": [
@@ -2614,6 +3104,51 @@
"which-boxed-primitive"
]
},
"unified@11.0.5": {
"integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
"dependencies": [
"@types/unist@3.0.3",
"bail",
"devlop",
"extend",
"is-plain-obj",
"trough",
"vfile"
]
},
"unist-util-is@6.0.1": {
"integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
"dependencies": [
"@types/unist@3.0.3"
]
},
"unist-util-position@5.0.0": {
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
"dependencies": [
"@types/unist@3.0.3"
]
},
"unist-util-stringify-position@4.0.0": {
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
"dependencies": [
"@types/unist@3.0.3"
]
},
"unist-util-visit-parents@6.0.2": {
"integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
"dependencies": [
"@types/unist@3.0.3",
"unist-util-is"
]
},
"unist-util-visit@5.1.0": {
"integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
"dependencies": [
"@types/unist@3.0.3",
"unist-util-is",
"unist-util-visit-parents"
]
},
"unrs-resolver@1.11.1": {
"integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
"dependencies": [
@@ -2657,6 +3192,20 @@
"punycode"
]
},
"vfile-message@4.0.3": {
"integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
"dependencies": [
"@types/unist@3.0.3",
"unist-util-stringify-position"
]
},
"vfile@6.0.3": {
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
"dependencies": [
"@types/unist@3.0.3",
"vfile-message"
]
},
"which-boxed-primitive@1.1.1": {
"integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
"dependencies": [
@@ -2730,6 +3279,9 @@
},
"zod@4.3.6": {
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="
},
"zwitch@2.0.4": {
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="
}
},
"workspace": {
@@ -2738,8 +3290,10 @@
"npm:eslint-config-next@16.2.3",
"npm:eslint@9",
"npm:next@16.2.3",
"npm:prettier@^3.8.3",
"npm:react-dom@19.2.4",
"npm:react-drag-drop-files@^3.1.0",
"npm:react-markdown@^10.1.0",
"npm:react@19.2.4",
"npm:sass@^1.99.0"
]

View File

@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
/* config options here */
allowedDevOrigins: ['192.168.11.170'],
};
export default nextConfig;

View File

@@ -10,9 +10,11 @@
},
"dependencies": {
"next": "16.2.3",
"prettier": "^3.8.3",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-drag-drop-files": "^3.1.0",
"react-markdown": "^10.1.0",
"sass": "^1.99.0"
},
"devDependencies": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,14 @@
## Entry - 8:23:28 PM
**Rating:** 3/5
this is a test
## Entry - 10:05:53 PM
**Rating:** 5/5
hello
---

View File

@@ -1,12 +1,37 @@
import { useEffect } from "react"
import { useEffect, useState } from "react"
import Markdown from "react-markdown"
export default function Preview({daySelected}){
const [preview,setPreview] = useState(undefined)
function fetchPreview(){
fetch(`/api/singleday?date=${encodeURIComponent(daySelected)}`).then((res)=>res.json()).then(data=>{setPreview(data)})
}
useEffect(()=>{
fetch(`/api/days`)
fetchPreview()
},[daySelected])
useEffect(()=>{
fetchPreview()
},[])
if (!preview || preview.files?.length === 0) {
return <p>nothing for this date</p>;
}
return(
<div className="preview">
preview
<div className="Preview" key={preview?.files?.length}>
{preview?.files?.map(((el,i)=>(
<div key={i} className="imagePreview">
<img src={`/uploads/${daySelected}/${el}`}/>
</div>
)))}
<div>
<Markdown>{preview?.note || ""}</Markdown>
</div>
</div>
)
}

View File

@@ -1,31 +1,53 @@
import { useEffect } from "react"
import { useEffect, useState } from "react";
export default function Tracker({calendar,setDaySelected}){
useEffect(()=>{
fetch(`/api/days`)
},[])
const monthLine = Object.keys(calendar).map((month, monthIndex) =>
<div key={month} className="month">
<p>
{month.charAt(0).toUpperCase() + month.slice(1)}
</p>
<div className="monthBlock">
{[...Array(calendar[month])].map(
(dayValue, dayIndex) => (
<span onClick={()=>{setDaySelected(`${monthIndex+1}/${dayIndex+1}`)}} id={dayIndex} key={dayIndex} title={`${dayIndex+1} ${month}`} className="day"/>
)
)}
</div>
</div>
)
export default function Tracker({ calendar, setDaySelected }) {
const [existingDays, setExistingDays] = useState([]);
useEffect(() => {
fetch(`/api/days`)
.then(res => res.json())
.then(data => setExistingDays(data))
.then(console.log(existingDays))
}, []);
return(
<div className="Tracker">
{monthLine}
</div>
)
};
function getColor(rating) {
const colors = [
"#d6e6854c", // 0
"#d6e685", // 1
"#8cc665", // 2
"#44a340", // 3
"#1e6823", // 4
"#0f3d1a" // 5
];
return colors[rating] || "#eee";
}
const monthLine = Object.keys(calendar).map((month, monthIndex) => (
<div key={month} className="month">
<p>{month.charAt(0).toUpperCase() + month.slice(1)}</p>
<div className="monthBlock">
{[...Array(calendar[month])].map((_, dayIndex) => {
const dayString = `${monthIndex + 1}/${dayIndex + 1}`;
const dayData = existingDays.find(d => d.date === dayString);
const rating = dayData?.appreciation ?? 0;
return (
<span
key={dayIndex}
onClick={() => setDaySelected(dayString)}
title={`${dayIndex + 1} ${month} (${rating}/5)`}
className="day"
style={{ backgroundColor: getColor(rating) }}
/>
);
})}
</div>
</div>
));
return <div className="Tracker">{monthLine}</div>;
}

View File

@@ -24,14 +24,18 @@ function DragDrop({setPreview ,file, setFile}) {
}
export default function Upload({daySelected={daySelected}}){
export default function Upload({setFetchKey, daySelected}){
const [file, setFile] = useState(null);
const [preview, setPreview] = useState(null);
const [note,setNote] = useState(undefined)
const [rating,setRating] = useState(1)
function uploadFile(){
const formData = new FormData();
formData.append("file", file);
formData.append("date", daySelected)
formData.append("note", note)
formData.append("rating", rating)
fetch("/api/days", {
method: "POST",
@@ -39,6 +43,9 @@ export default function Upload({daySelected={daySelected}}){
}).then(() => {
setFile(null);
setPreview(null);
setNote(undefined);
setRating(1);
setFetchKey(Math.random()*10)
});
}
@@ -51,12 +58,19 @@ export default function Upload({daySelected={daySelected}}){
<DragDrop setPreview={setPreview} file={file} setFile={setFile}/>
<select type="select">
<option value="gesture">Gesture</option>
<option value="blender">Blender</option>
<label htmlFor="rating">Choose rating:</label>
<select name="rating" type="select" value={rating} onChange={(e)=>{setRating(e.target.value)}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="3">4</option>
<option value="5">5</option>
</select>
<textarea value={note} onChange={(e)=>{setNote(e.target.value)}}>
</textarea>
<p onClick={()=>{
uploadFile()
}}>Upload</p>

View File

@@ -1,5 +0,0 @@
export async function GET(req){
console.log(req)
return Response.json({"message":"hello"})
}

View File

@@ -1,42 +1,105 @@
import {readdir, access, mkdir, writeFile } from "fs/promises";
import {readdir,appendFile, readFile, mkdir, writeFile } from "fs/promises";
import path from "path";
export async function GET(req) {
const dir = "./public/uploads";
const entries = await readdir(dir, { withFileTypes: true });
console.log(entries)
export async function GET() {
const baseDir = "./public/uploads";
const results = [];
try {
const months = await readdir(baseDir, { withFileTypes: true });
return Response.json({"hello":"wesh"})
for (const month of months) {
if (!month.isDirectory()) continue;
const monthPath = path.join(baseDir, month.name);
const days = await readdir(monthPath, { withFileTypes: true });
for (const day of days) {
if (!day.isDirectory()) continue;
const date = `${month.name}/${day.name}`;
const mdPath = path.join(monthPath, day.name, "note.md");
let appreciation = null;
try {
const content = await readFile(mdPath, "utf-8");
// 🎣 extract all ratings from markdown
const matches = [
...content.matchAll(/\*\*Rating:\*\*\s*(\d+)\/5/g),
];
const ratings = matches.map(m => Number(m[1]));
if (ratings.length > 0) {
// 📊 average + round to 1 decimal
appreciation =
Math.round(
(ratings.reduce((a, b) => a + b, 0) / ratings.length) * 10
) / 10;
}
} catch {
// no note.md → keep appreciation = null
}
results.push({ date, appreciation });
}
}
// 📅 sort by month/day
results.sort((a, b) => {
const [m1, d1] = a.date.split("/").map(Number);
const [m2, d2] = b.date.split("/").map(Number);
return m1 - m2 || d1 - d2;
});
return Response.json(results);
} catch (err) {
return Response.json(
{ error: "Failed to read uploads directory" },
{ status: 500 }
);
}
}
export async function POST(req) {
const formData = await req.formData();
const date = formData.get('date');
const file = formData.get('file');
console.log(file)
if (!file) {
return new Response("No file uploaded", { status: 400 });
}
const file = formData.get("file");
const date = formData.get("date");
const note = formData.get("note");
const rating = formData.get("rating");
const dir = `./public/uploads/${date}`;
await mkdir(dir, { recursive: true });
// 📁 Save file
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const filePath = path.join(dir, file.name);
await writeFile(filePath, buffer);
// if folder not created make one
try{
await access(`./public/uploads/${date}/`)
}catch{
await mkdir(`./public/uploads/${date}/`, {recursive:true})
}
// 🧼 sanitize
const safeNote = note?.toString().trim() || "No note";
const safeRating = Math.max(0, Math.min(5, Number(rating) || 0));
//get the number of files before uploading
const files = await readdir(`./public/uploads/${date}/`);
const nbFiles = (files.length);
const fileName = `${nbFiles}.${path.extname(file.name).slice(1).toLowerCase()}`
console.log(fileName);
// Save file locally
const pathName = `./public/uploads/${date}/${fileName}`;
// 📝 Append to markdown
const mdEntry = `
## Entry - ${new Date().toLocaleTimeString()}
await writeFile(pathName, buffer);
return new Response("File uploaded successfully");
**Rating:** ${safeRating}/5
${safeNote}
---
`;
const mdPath = path.join(dir, "note.md");
await appendFile(mdPath, mdEntry);
return new Response("Saved");
}

View File

@@ -0,0 +1,75 @@
import {readFile, writeFile, appendFile, mkdir } from "fs/promises";
import path from "path";
export async function GET() {
const filePath = "./public/links/links.md";
try {
const content = await readFile(filePath, "utf-8");
const entries = content.split("---").map(block => {
const titleMatch = block.match(/\[(.*?)\]\((.*?)\)/);
const ratingMatch = block.match(/Rating:\s*(\d+)/);
const noteMatch = block.match(/Note:\s*(.*)/);
if (!titleMatch) return null;
return {
title: titleMatch[1],
url: titleMatch[2],
rating: ratingMatch ? Number(ratingMatch[1]) : null,
note: noteMatch ? noteMatch[1] : "",
};
}).filter(Boolean);
return Response.json(entries);
} catch {
return Response.json([]);
}
}
export async function POST(req) {
const { title, url, note, rating } = await req.json();
const dir = "./public/links";
const filePath = path.join(dir, "links.md");
await mkdir(dir, { recursive: true });
const safeRating = Math.max(0, Math.min(5, Number(rating) || 0));
const entry = `
## [${title}](${url})
Rating: ${safeRating}
Note: ${note || ""}
---
`;
await appendFile(filePath, entry);
return Response.json({ success: true });
}
export async function DELETE(req) {
const { url } = await req.json();
const filePath = "./public/links/links.md";
try {
const content = await readFile(filePath, "utf-8");
const blocks = content.split("---");
const filtered = blocks.filter(block => !block.includes(`(${url})`));
await writeFile(filePath, filtered.join("---"));
return Response.json({ success: true });
} catch {
return Response.json({ error: "Failed to delete" }, { status: 500 });
}
}

View File

@@ -0,0 +1,36 @@
import { readdir, readFile } from "fs/promises";
import path from "path";
export async function GET(req) {
const { searchParams } = new URL(req.url);
const date = searchParams.get("date");
const dir = `./public/uploads/${date}`;
try {
// 📂 Get files
const entries = await readdir(dir, { withFileTypes: true });
const files = entries
.filter(e => e.isFile() && e.name !== "note.md")
.map(e => e.name);
// 📖 Get markdown content
let note = "";
try {
const mdPath = path.join(dir, "note.md");
note = await readFile(mdPath, "utf-8");
} catch {
note = "";
}
return Response.json({
date,
files,
note,
});
} catch (err) {
return Response.json({ error: "Not found" }, { status: 404 });
}
}

View File

@@ -5,6 +5,7 @@ import "./style.sass"
import Preview from "./Component/preview";
import Upload from "./Component/upload";
import Tracker from "./Component/tracker";
export default function Home() {
const calendar = {
@@ -25,22 +26,23 @@ export default function Home() {
const date = new Date();
const [daySelected, setDaySelected] = useState(`${date.getMonth()}/${date.getDate()}`)
const [fetchKey,setFetchKey] = useState(Math.random*10)
return (
<div className="Home">
<div className="Home" key={fetchKey}>
<aside>
<p>Link</p>
</aside>
<main>
<h2>{daySelected}</h2>
<Preview daySelected={daySelected}/>
<Upload daySelected={daySelected}/>
<Tracker calendar={calendar} setDaySelected={setDaySelected}/>
</main>
<aside>
<Upload daySelected={daySelected} setFetchKey={setFetchKey}/>
<Tracker calendar={calendar} setDaySelected={setDaySelected}/>
</aside>
</div>
);
}

View File

@@ -5,8 +5,7 @@
justify-content: space-between
height: 100vh
aside
background: rebeccapurple
width: 20%
width: 30%
height: 100%
main
width: 79%
@@ -16,28 +15,39 @@ main
.month
display: flex
flex-direction: row
flex-wrap: wrap
width: 100%
align-items: center
gap: 5px
p
width: 8%
width: 20%
.monthBlock
display: flex
gap: 5px
width: 90%
gap: 1px
.day
display: block
width: 20px
width: 10px
height: 10px
background: green
border-radius: 5px
cursor: pointer
.Upload
display: flex
gap: 5%
align-items: center
flex-wrap: wrap
.preview
height: 400px
height: 100px
img
height: 100%
select
height: 40px
height: 40px
.Preview
display: flex
flex-wrap: wrap
gap : 5%
.imagePreview
height: 500px
img
max-height: 100%
object-fit: contain