diff --git a/.env.example b/.env.example index e6b55e6..83ddd81 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,7 @@ VITE_REPORTING_NPUB= VITE_FALLBACK_MOD_IMAGE=https://image.nostr.build/1fa7afd3489302c2da8957993ac0fd6c4308eedd4b1b95b24ecfabe3651b2183.png # if there's no image, or if the image breaks somewhere down the line, then it should default to this image -VITE_FALLBACK_GAME_IMAGE=https://image.nostr.build/1fa7afd3489302c2da8957993ac0fd6c4308eedd4b1b95b24ecfabe3651b2183.png \ No newline at end of file +VITE_FALLBACK_GAME_IMAGE=https://image.nostr.build/1fa7afd3489302c2da8957993ac0fd6c4308eedd4b1b95b24ecfabe3651b2183.png + +# A comma separated list of npubs, this list is used to fetch just the posts from the admin +VITE_BLOG_NPUBS= diff --git a/package-lock.json b/package-lock.json index f29ff29..19b9ff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,11 @@ "@nostr-dev-kit/ndk": "2.10.0", "@nostr-dev-kit/ndk-cache-dexie": "2.5.1", "@reduxjs/toolkit": "2.2.6", - "@tiptap/core": "2.6.6", - "@tiptap/extension-link": "2.6.6", - "@tiptap/react": "2.6.6", - "@tiptap/starter-kit": "2.6.6", + "@tiptap/core": "2.9.1", + "@tiptap/extension-image": "^2.9.1", + "@tiptap/extension-link": "2.9.1", + "@tiptap/react": "2.9.1", + "@tiptap/starter-kit": "2.9.1", "@types/react-helmet": "^6.1.11", "axios": "1.7.3", "bech32": "2.0.0", @@ -26,6 +27,7 @@ "file-saver": "2.0.5", "fslightbox-react": "1.7.6", "lodash": "4.17.21", + "marked": "^14.1.3", "nostr-login": "1.5.2", "nostr-tools": "2.7.1", "papaparse": "5.4.1", @@ -1160,6 +1162,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1189,9 +1192,10 @@ } }, "node_modules/@remirror/core-constants": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", - "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" }, "node_modules/@remix-run/router": { "version": "1.17.1", @@ -1487,45 +1491,49 @@ } }, "node_modules/@tiptap/core": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.6.tgz", - "integrity": "sha512-VO5qTsjt6rwworkuo0s5AqYMfDA0ZwiTiH6FHKFSu2G/6sS7HKcc/LjPq+5Legzps4QYdBDl3W28wGsGuS1GdQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", + "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^2.6.6" + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-blockquote": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.6.6.tgz", - "integrity": "sha512-hAdsNlMfzzxld154hJqPqtWqO5i4/7HoDfuxmyqBxdMJ+e2UMaIGBGwoLRXG0V9UoRwJusjqlpyD7pIorxNlgA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.9.1.tgz", + "integrity": "sha512-Y0jZxc/pdkvcsftmEZFyG+73um8xrx6/DMfgUcNg3JAM63CISedNcr+OEI11L0oFk1KFT7/aQ9996GM6Kubdqg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-bold": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.6.6.tgz", - "integrity": "sha512-CD6gBhdQtCoqYSmx8oAV8gvKtVOGZSyyvuNYo7by9eZ56DqLYnd7kbUj0RH7o9Ymf/iJTOUJ6XcvrsWwo4lubg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.9.1.tgz", + "integrity": "sha512-e2P1zGpnnt4+TyxTC5pX/lPxPasZcuHCYXY0iwQ3bf8qRQQEjDfj3X7EI+cXqILtnhOiviEOcYmeu5op2WhQDg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.6.6.tgz", - "integrity": "sha512-IkfmlZq67aaegym5sBddBc/xXWCArxn5WJEl1oxKEayjQhybKSaqI7tk0lOx/x7fa5Ml1WlGpCFh+KKXbQTG0g==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.9.1.tgz", + "integrity": "sha512-DWUF6NG08/bZDWw0jCeotSTvpkyqZTi4meJPomG9Wzs/Ol7mEwlNCsCViD999g0+IjyXFatBk4DfUq1YDDu++Q==", + "license": "MIT", "dependencies": { "tippy.js": "^6.3.7" }, @@ -1534,76 +1542,82 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.6.6.tgz", - "integrity": "sha512-WEKxbVSYuvmX2wkHWP8HXk5nzA7stYwtdaubwWH/R17kGI3IGScJuMQ9sEN82uzJU8bfgL9yCbH2bY8Fj/Q4Ow==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.9.1.tgz", + "integrity": "sha512-0hizL/0j9PragJObjAWUVSuGhN1jKjCFnhLQVRxtx4HutcvS/lhoWMvFg6ZF8xqWgIa06n6A7MaknQkqhTdhKA==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-code": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.6.6.tgz", - "integrity": "sha512-JrEFKsZiLvfvOFhOnnrpA0TzCuJjDeysfbMeuKUZNV4+DhYOL28d39H1++rEtJAX0LcbBU60oC5/PrlU9SpvRQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.9.1.tgz", + "integrity": "sha512-WQqcVGe7i/E+yO3wz5XQteU1ETNZ00euUEl4ylVVmH2NM4Dh0KDjEhbhHlCM0iCfLUo7jhjC7dmS+hMdPUb+Tg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-code-block": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.6.6.tgz", - "integrity": "sha512-1YLp/zHMHSkE2xzht8nPR6T4sQJJ3ket798czxWuQEbetFv/l0U/mpiPpYSLObj6oTAoqYZ0kWXZj5eQSpPB8Q==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.9.1.tgz", + "integrity": "sha512-A/50wPWDqEUUUPhrwRKILP5gXMO5UlQ0F6uBRGYB9CEVOREam9yIgvONOnZVJtszHqOayjIVMXbH/JMBeq11/g==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-document": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.6.6.tgz", - "integrity": "sha512-6qlH5VWzLHHRVeeciRC6C4ZHpMsAGPNG16EF53z0GeMSaaFD/zU3B239QlmqXmLsAl8bpf8Bn93N0t2ABUvScw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.9.1.tgz", + "integrity": "sha512-1a+HCoDPnBttjqExfYLwfABq8MYdiowhy/wp8eCxVb6KGFEENO53KapstISvPzqH7eOi+qRjBB1KtVYb/ZXicg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.6.6.tgz", - "integrity": "sha512-O6CeKriA9uyHsg7Ui4z5ZjEWXQxrIL+1zDekffW0wenGC3G4LUsCzAiFS4LSrR9a3u7tnwqGApW10rdkmCGF4w==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.9.1.tgz", + "integrity": "sha512-wJZspSmJRkDBtPkzFz1g7gvZOEOayk8s93UHsgbJxcV4VWHYleZ5XhT74sZunSjefNDm3qC6v2BSgLp3vNHVKQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.6.6.tgz", - "integrity": "sha512-lPkESOfAUxgmXRiNqUU23WSyja5FUfSWjsW4hqe+BKNjsUt1OuFMEtYJtNc+MCGhhtPfFvM3Jg6g9jd6g5XsLQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.9.1.tgz", + "integrity": "sha512-MxZ7acNNsoNaKpetxfwi3Z11Bgrh0T2EJlCV77v9N1vWK38+st3H1WJanmLbPNtc2ocvhHJrz+DjDz3CWxQ9rQ==", + "license": "MIT", "dependencies": { "tippy.js": "^6.3.7" }, @@ -1612,89 +1626,109 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.6.6.tgz", - "integrity": "sha512-O2lQ2t0X0Vsbn3yLWxFFHrXY6C2N9Y6ZF/M7LWzpcDTUZeWuhoNkFE/1yOM0h6ZX1DO2A9hNIrKpi5Ny8yx+QA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.9.1.tgz", + "integrity": "sha512-jsRBmX01vr+5H02GljiHMo0n5H1vzoMLmFarxe0Yq2d2l9G/WV2VWX2XnGliqZAYWd1bI0phs7uLQIN3mxGQTw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.6.6.tgz", - "integrity": "sha512-bsUuyYBrMDEiudx1dOQSr9MzKv13m0xHWrOK+DYxuIDYJb5g+c9un5cK7Js+et/HEYYSPOoH/iTW6h+4I5YeUg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.9.1.tgz", + "integrity": "sha512-fCuaOD/b7nDjm47PZ58oanq7y4ccS2wjPh42Qm0B0yipu/1fmC8eS1SmaXmk28F89BLtuL6uOCtR1spe+lZtlQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-heading": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.6.6.tgz", - "integrity": "sha512-bgx9vptVFi5yFkIw1OI53J7+xJ71Or3SOe/Q8eSpZv53DlaKpL/TzKw8Z54t1PrI2rJ6H9vrLtkvixJvBZH1Ug==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.9.1.tgz", + "integrity": "sha512-SjZowzLixOFaCrV2cMaWi1mp8REK0zK1b3OcVx7bCZfVSmsOETJyrAIUpCKA8o60NwF7pwhBg0MN8oXlNKMeFw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-history": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.6.6.tgz", - "integrity": "sha512-tPTzAmPGqMX5Bd5H8lzRpmsaMvB9DvI5Dy2za/VQuFtxgXmDiFVgHRkRXIuluSkPTuANu84XBOQ0cBijqY8x4w==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.9.1.tgz", + "integrity": "sha512-wp9qR1NM+LpvyLZFmdNaAkDq0d4jDJ7z7Fz7icFQPu31NVxfQYO3IXNmvJDCNu8hFAbImpA5aG8MBuwzRo0H9w==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.6.6.tgz", - "integrity": "sha512-cFEfv7euDpuLSe8exY8buwxkreKBAZY9Hn3EetKhPcLQo+ut5Y24chZTxFyf9b+Y0wz3UhOhLTZSz7fTobLqBA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.9.1.tgz", + "integrity": "sha512-ydUhABeaBI1CoJp+/BBqPhXINfesp1qMNL/jiDcMsB66fsD4nOyphpAJT7FaRFZFtQVF06+nttBtFZVkITQVqg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-image": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.9.1.tgz", + "integrity": "sha512-aGqJnsuS8oagIhsx7wetm8jw4NEDsOV0OSx4FQ4VPlUqWlnzK0N+erFKKJmXTdAxL8PGzoPSlITFH63MV3eV3Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-italic": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.6.6.tgz", - "integrity": "sha512-t7ZPsXqa8nJZZ/6D0rQyZ/KsvzLaSihC6hBTjUQ77CeDGV9PhDWjIcBW4OrvwraJDBd12ETBeQ2CkULJOgH+lQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.9.1.tgz", + "integrity": "sha512-VkNA6Vz96+/+7uBlsgM7bDXXx4b62T1fDam/3UKifA72aD/fZckeWrbT7KrtdUbzuIniJSbA0lpTs5FY29+86Q==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-link": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.6.6.tgz", - "integrity": "sha512-NJSR5Yf/dI3do0+Mr6e6nkbxRQcqbL7NOPxo5Xw8VaKs2Oe8PX+c7hyqN3GZgn6uEbZdbVi1xjAniUokouwpFg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.9.1.tgz", + "integrity": "sha512-yG+e3e8cCCN9dZjX4ttEe3e2xhh58ryi3REJV4MdiEkOT9QF75Bl5pUbMIS4tQ8HkOr04QBFMHKM12kbSxg1BA==", + "license": "MIT", "dependencies": { "linkifyjs": "^4.1.0" }, @@ -1703,78 +1737,97 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-list-item": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.6.6.tgz", - "integrity": "sha512-k+oEzZu2cgVKqPqOP1HzASOKLpTEV9m7mRVPAbuaaX8mSyvIgD6f+JUx9PvgYv//D918wk98LMoRBFX53tDJ4w==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.9.1.tgz", + "integrity": "sha512-6O4NtYNR5N2Txi4AC0/4xMRJq9xd4+7ShxCZCDVL0WDVX37IhaqMO7LGQtA6MVlYyNaX4W1swfdJaqrJJ5HIUw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.6.6.tgz", - "integrity": "sha512-AJwyfLXIi7iUGnK5twJbwdVVpQyh7fU6OK75h1AwDztzsOcoPcxtffDlZvUOd4ZtwuyhkzYqVkeI0f+abTWZTw==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.9.1.tgz", + "integrity": "sha512-6J9jtv1XP8dW7/JNSH/K4yiOABc92tBJtgCsgP8Ep4+fjfjdj4HbjS1oSPWpgItucF2Fp/VF8qg55HXhjxHjTw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.6.6.tgz", - "integrity": "sha512-fD/onCr16UQWx+/xEmuFC2MccZZ7J5u4YaENh8LMnAnBXf78iwU7CAcmuc9rfAEO3qiLoYGXgLKiHlh2ZfD4wA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.9.1.tgz", + "integrity": "sha512-JOmT0xd4gd3lIhLwrsjw8lV+ZFROKZdIxLi0Ia05XSu4RLrrvWj0zdKMSB+V87xOWfSB3Epo95zAvnPox5Q16A==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-strike": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.6.6.tgz", - "integrity": "sha512-Ze8KhGk+wzSJSJRl5fbhTI6AvPu2LmcHYeO3pMEH8u4gV5WTXfmKJVStEIAzkoqvwEQVWzXvy8nDgsFQHiojPg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.9.1.tgz", + "integrity": "sha512-V5aEXdML+YojlPhastcu7w4biDPwmzy/fWq0T2qjfu5Te/THcqDmGYVBKESBm5x6nBy5OLkanw2O+KHu2quDdg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/extension-text": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.6.6.tgz", - "integrity": "sha512-e84uILnRzNzcwK1DVQNpXVmBG1Cq3BJipTOIDl1LHifOok7MBjhI/X+/NR0bd3N2t6gmDTWi63+4GuJ5EeDmsg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.9.1.tgz", + "integrity": "sha512-3wo9uCrkLVLQFgbw2eFU37QAa1jq1/7oExa+FF/DVxdtHRS9E2rnUZ8s2hat/IWzvPUHXMwo3Zg2XfhoamQpCA==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6" + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.9.1.tgz", + "integrity": "sha512-LAxc0SeeiPiAVBwksczeA7BJSZb6WtVpYhy5Esvy9K0mK5kttB4KxtnXWeQzMIJZQbza65yftGKfQlexf/Y7yg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" } }, "node_modules/@tiptap/pm": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.6.tgz", - "integrity": "sha512-56FGLPn3fwwUlIbLs+BO21bYfyqP9fKyZQbQyY0zWwA/AG2kOwoXaRn7FOVbjP6CylyWpFJnpRRmgn694QKHEg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz", + "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==", + "license": "MIT", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.5.2", + "prosemirror-commands": "^1.6.0", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", @@ -1782,14 +1835,14 @@ "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.0", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.22.2", + "prosemirror-model": "^1.22.3", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.4.0", - "prosemirror-trailing-node": "^2.0.9", - "prosemirror-transform": "^1.9.0", - "prosemirror-view": "^1.33.9" + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.0", + "prosemirror-view": "^1.34.3" }, "funding": { "type": "github", @@ -1797,13 +1850,15 @@ } }, "node_modules/@tiptap/react": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.6.6.tgz", - "integrity": "sha512-AUmdb/J1O/vCO2b8LL68ctcZr9a3931BwX4fUUZ1kCrCA5lTj2xz0rjeAtpxEdzLnR+Z7q96vB7vf7bPYOUAew==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.9.1.tgz", + "integrity": "sha512-LQJ34ZPfXtJF36SZdcn4Fiwsl2WxZ9YRJI87OLnsjJ45O+gV/PfBzz/4ap+LF8LOS0AbbGhTTjBOelPoNm+aYA==", + "license": "MIT", "dependencies": { - "@tiptap/extension-bubble-menu": "^2.6.6", - "@tiptap/extension-floating-menu": "^2.6.6", + "@tiptap/extension-bubble-menu": "^2.9.1", + "@tiptap/extension-floating-menu": "^2.9.1", "@types/use-sync-external-store": "^0.0.6", + "fast-deep-equal": "^3", "use-sync-external-store": "^1.2.2" }, "funding": { @@ -1811,8 +1866,8 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/pm": "^2.6.6", + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" } @@ -1823,30 +1878,32 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, "node_modules/@tiptap/starter-kit": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.6.6.tgz", - "integrity": "sha512-zb9xIg3WjG9AsJoyWrfqx5SL9WH7/HTdkB79jFpWtOF/Kaigo7fHFmhs2FsXtJMJlcdMTO2xeRuCYHt5ozXlhg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.9.1.tgz", + "integrity": "sha512-nsw6UF/7wDpPfHRhtGOwkj1ipIEiWZS1VGw+c14K61vM1CNj0uQ4jogbHwHZqN1dlL5Hh+FCqUHDPxG6ECbijg==", + "license": "MIT", "dependencies": { - "@tiptap/core": "^2.6.6", - "@tiptap/extension-blockquote": "^2.6.6", - "@tiptap/extension-bold": "^2.6.6", - "@tiptap/extension-bullet-list": "^2.6.6", - "@tiptap/extension-code": "^2.6.6", - "@tiptap/extension-code-block": "^2.6.6", - "@tiptap/extension-document": "^2.6.6", - "@tiptap/extension-dropcursor": "^2.6.6", - "@tiptap/extension-gapcursor": "^2.6.6", - "@tiptap/extension-hard-break": "^2.6.6", - "@tiptap/extension-heading": "^2.6.6", - "@tiptap/extension-history": "^2.6.6", - "@tiptap/extension-horizontal-rule": "^2.6.6", - "@tiptap/extension-italic": "^2.6.6", - "@tiptap/extension-list-item": "^2.6.6", - "@tiptap/extension-ordered-list": "^2.6.6", - "@tiptap/extension-paragraph": "^2.6.6", - "@tiptap/extension-strike": "^2.6.6", - "@tiptap/extension-text": "^2.6.6", - "@tiptap/pm": "^2.6.6" + "@tiptap/core": "^2.9.1", + "@tiptap/extension-blockquote": "^2.9.1", + "@tiptap/extension-bold": "^2.9.1", + "@tiptap/extension-bullet-list": "^2.9.1", + "@tiptap/extension-code": "^2.9.1", + "@tiptap/extension-code-block": "^2.9.1", + "@tiptap/extension-document": "^2.9.1", + "@tiptap/extension-dropcursor": "^2.9.1", + "@tiptap/extension-gapcursor": "^2.9.1", + "@tiptap/extension-hard-break": "^2.9.1", + "@tiptap/extension-heading": "^2.9.1", + "@tiptap/extension-history": "^2.9.1", + "@tiptap/extension-horizontal-rule": "^2.9.1", + "@tiptap/extension-italic": "^2.9.1", + "@tiptap/extension-list-item": "^2.9.1", + "@tiptap/extension-ordered-list": "^2.9.1", + "@tiptap/extension-paragraph": "^2.9.1", + "@tiptap/extension-strike": "^2.9.1", + "@tiptap/extension-text": "^2.9.1", + "@tiptap/extension-text-style": "^2.9.1", + "@tiptap/pm": "^2.9.1" }, "funding": { "type": "github", @@ -3208,8 +3265,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -3862,6 +3918,18 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/marked": { + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", + "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -4519,11 +4587,12 @@ } }, "node_modules/prosemirror-trailing-node": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.9.tgz", - "integrity": "sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", "dependencies": { - "@remirror/core-constants": "^2.0.2", + "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { @@ -4536,6 +4605,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -4552,9 +4622,10 @@ } }, "node_modules/prosemirror-view": { - "version": "1.34.1", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.1.tgz", - "integrity": "sha512-KS2xmqrAM09h3SLu1S2pNO/ZoIP38qkTJ6KFd7+BeSfmX/ek0n5yOfGuiTZjFNTC8GOsEIUa1tHxt+2FMu3yWQ==", + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.35.0.tgz", + "integrity": "sha512-Umtbh22fmUlpZpRTiOVXA0PpdRZeYEeXQsLp51VfnMhjkJrqJ0n8APinIZrRAD5Jr3UxH8FnOaUqRylSuMsqHA==", + "license": "MIT", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -5043,6 +5114,7 @@ "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", "dependencies": { "@popperjs/core": "^2.9.0" } diff --git a/package.json b/package.json index fa455fa..b356c8a 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,11 @@ "@nostr-dev-kit/ndk": "2.10.0", "@nostr-dev-kit/ndk-cache-dexie": "2.5.1", "@reduxjs/toolkit": "2.2.6", - "@tiptap/core": "2.6.6", - "@tiptap/extension-link": "2.6.6", - "@tiptap/react": "2.6.6", - "@tiptap/starter-kit": "2.6.6", + "@tiptap/core": "2.9.1", + "@tiptap/extension-image": "^2.9.1", + "@tiptap/extension-link": "2.9.1", + "@tiptap/react": "2.9.1", + "@tiptap/starter-kit": "2.9.1", "@types/react-helmet": "^6.1.11", "axios": "1.7.3", "bech32": "2.0.0", @@ -28,6 +29,7 @@ "file-saver": "2.0.5", "fslightbox-react": "1.7.6", "lodash": "4.17.21", + "marked": "^14.1.3", "nostr-login": "1.5.2", "nostr-tools": "2.7.1", "papaparse": "5.4.1", diff --git a/src/components/BlogCard.tsx b/src/components/BlogCard.tsx index e2065b7..e2d52a9 100644 --- a/src/components/BlogCard.tsx +++ b/src/components/BlogCard.tsx @@ -1,37 +1,33 @@ +import { Link } from 'react-router-dom' +import { BlogCardDetails } from 'types' +import { getBlogPageRoute } from 'routes' import '../styles/cardBlogs.css' +import placeholder from '../assets/img/DEGMods Placeholder Img.png' -type BlogCardProps = { - backgroundLink: string -} +type BlogCardProps = Partial + +export const BlogCard = ({ title, image, nsfw, naddr }: BlogCardProps) => { + if (!naddr) return null -export const BlogCard = ({ backgroundLink }: BlogCardProps) => { return ( - +
-
-

- This is a blog title, the best blog title in the world! -

+
+

{title}

+ {nsfw && ( +
+

NSFW

+
+ )}
-
{' '} -
+
+ ) } diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx index b21e544..036e538 100644 --- a/src/components/Inputs.tsx +++ b/src/components/Inputs.tsx @@ -1,4 +1,5 @@ import Link from '@tiptap/extension-link' +import Image from '@tiptap/extension-image' import { Editor, EditorContent, useEditor } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' import React, { useEffect } from 'react' @@ -127,7 +128,15 @@ type RichTextEditorProps = { const RichTextEditor = ({ content, updateContent }: RichTextEditorProps) => { const editor = useEditor({ - extensions: [StarterKit, Link], + extensions: [ + StarterKit, + Link, + Image.configure({ + HTMLAttributes: { + class: 'IBMSMSMBSSPostImg' + } + }) + ], onUpdate: ({ editor }) => { // Update the state when the editor content changes updateContent(editor.getHTML()) @@ -181,6 +190,17 @@ const MenuBar = ({ editor }: MenuBarProps) => { const unsetLink = () => editor.chain().focus().unsetLink().run() + const setImage = () => { + let url = prompt('URL') + if (url) { + if (!/^(http|https):\/\//i.test(url)) { + url = `https://${url}` + } + return editor.chain().focus().setImage({ src: url }).run() + } + return false + } + const buttons: MenuBarButtonProps[] = [ { label: 'Bold', @@ -211,7 +231,7 @@ const MenuBar = ({ editor }: MenuBarProps) => { { label: 'Paragraph', isActive: editor.isActive('paragraph'), - onClick: () => editor.chain().focus().toggleStrike().run() + onClick: () => editor.chain().focus().setParagraph().run() }, // eslint-disable-next-line @typescript-eslint/no-explicit-any ...[1, 2, 3, 4, 5, 6].map((level: any) => ({ @@ -244,6 +264,11 @@ const MenuBar = ({ editor }: MenuBarProps) => { isActive: editor.isActive('link'), onClick: editor.isActive('link') ? unsetLink : setLink }, + { + label: 'Image', + isActive: editor.isActive('image'), + onClick: setImage + }, { label: 'Horizontal rule', onClick: () => editor.chain().focus().setHorizontalRule().run() diff --git a/src/components/Internal/Interactions.tsx b/src/components/Internal/Interactions.tsx new file mode 100644 index 0000000..534c910 --- /dev/null +++ b/src/components/Internal/Interactions.tsx @@ -0,0 +1,42 @@ +import { Addressable } from 'types' +import { abbreviateNumber } from 'utils' +import { Zap } from './Zap' +import { Reactions } from './Reactions' + +type InteractionsProps = { + addressable: Addressable + commentCount: number +} + +export const Interactions = ({ + addressable, + commentCount +}: InteractionsProps) => { + return ( +
+
+ +
+
+ + + +
+

+ {abbreviateNumber(commentCount)} +

+
+
+ + +
+
+ ) +} diff --git a/src/components/Internal/PublishDetails.tsx b/src/components/Internal/PublishDetails.tsx new file mode 100644 index 0000000..88cef78 --- /dev/null +++ b/src/components/Internal/PublishDetails.tsx @@ -0,0 +1,86 @@ +import { formatDate } from 'date-fns' + +type PublishDetailsProps = { + published_at: number + edited_at: number + site: string +} + +export const PublishDetails = ({ + published_at, + edited_at, + site +}: PublishDetailsProps) => { + return ( +
+
+
+ + + +

+ {formatDate( + (published_at !== -1 ? published_at : edited_at) * 1000, + 'dd/MM/yyyy hh:mm:ss aa' + )} +

+
+
+ + + +

+ {formatDate(edited_at * 1000, 'dd/MM/yyyy hh:mm:ss aa')} +

+
+ + + + +

{site}

+
+
+
+ ) +} diff --git a/src/pages/mod/internal/reactions/index.tsx b/src/components/Internal/Reactions.tsx similarity index 92% rename from src/pages/mod/internal/reactions/index.tsx rename to src/components/Internal/Reactions.tsx index d50a787..4e69f13 100644 --- a/src/pages/mod/internal/reactions/index.tsx +++ b/src/components/Internal/Reactions.tsx @@ -1,11 +1,11 @@ import { useReactions } from 'hooks' -import { ModDetails } from 'types' +import { Addressable } from 'types' type ReactionsProps = { - modDetails: ModDetails + addressable: Addressable } -export const Reactions = ({ modDetails }: ReactionsProps) => { +export const Reactions = ({ addressable }: ReactionsProps) => { const { isDataLoaded, likesCount, @@ -14,9 +14,9 @@ export const Reactions = ({ modDetails }: ReactionsProps) => { hasReactedPositively, hasReactedNegatively } = useReactions({ - pubkey: modDetails.author, - eTag: modDetails.id, - aTag: modDetails.aTag + pubkey: addressable.author, + eTag: addressable.id, + aTag: addressable.aTag }) if (!isDataLoaded) return null diff --git a/src/pages/mod/internal/zap/index.tsx b/src/components/Internal/Zap.tsx similarity index 88% rename from src/pages/mod/internal/zap/index.tsx rename to src/components/Internal/Zap.tsx index 996c8d2..0c5cb7a 100644 --- a/src/pages/mod/internal/zap/index.tsx +++ b/src/components/Internal/Zap.tsx @@ -7,14 +7,14 @@ import { } from 'hooks' import { useState } from 'react' import { toast } from 'react-toastify' -import { ModDetails } from 'types' +import { Addressable } from 'types' import { abbreviateNumber } from 'utils' type ZapProps = { - modDetails: ModDetails + addressable: Addressable } -export const Zap = ({ modDetails }: ZapProps) => { +export const Zap = ({ addressable }: ZapProps) => { const [isOpen, setIsOpen] = useState(false) const [totalZappedAmount, setTotalZappedAmount] = useState(0) const [hasZapped, setHasZapped] = useState(false) @@ -26,9 +26,9 @@ export const Zap = ({ modDetails }: ZapProps) => { useDidMount(() => { getTotalZapAmount( - modDetails.author, - modDetails.id, - modDetails.aTag, + addressable.author, + addressable.id, + addressable.aTag, userState.user?.pubkey as string ) .then((res) => { @@ -70,9 +70,9 @@ export const Zap = ({ modDetails }: ZapProps) => { {isOpen && ( setIsOpen(false)} diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index 6abd397..c54b300 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -12,7 +12,7 @@ import { useComments } from 'hooks/useComments' export const ModCard = React.memo((props: ModDetails) => { const [totalZappedAmount, setTotalZappedAmount] = useState(0) const [commentCount, setCommentCount] = useState(0) - const { commentEvents } = useComments(props) + const { commentEvents } = useComments(props.author, props.aTag) const { likesCount, disLikesCount } = useReactions({ pubkey: props.author, eTag: props.id, diff --git a/src/pages/mod/internal/comment/index.tsx b/src/components/comment/index.tsx similarity index 97% rename from src/pages/mod/internal/comment/index.tsx rename to src/components/comment/index.tsx index f238524..8d7fd93 100644 --- a/src/pages/mod/internal/comment/index.tsx +++ b/src/components/comment/index.tsx @@ -21,9 +21,9 @@ import { Link } from 'react-router-dom' import { toast } from 'react-toastify' import { getProfilePageRoute } from 'routes' import { + Addressable, CommentEvent, CommentEventStatus, - ModDetails, UserProfile } from 'types/index.ts' import { abbreviateNumber, hexToNpub, log, LogType, now } from 'utils' @@ -44,13 +44,16 @@ type FilterOptions = { } type Props = { - modDetails: ModDetails + addressable: Addressable setCommentCount: Dispatch> } -export const Comments = ({ modDetails, setCommentCount }: Props) => { +export const Comments = ({ addressable, setCommentCount }: Props) => { const { ndk, publish } = useNDKContext() - const { commentEvents, setCommentEvents } = useComments(modDetails) + const { commentEvents, setCommentEvents } = useComments( + addressable.author, + addressable.aTag + ) const [filterOptions, setFilterOptions] = useState({ sort: SortByEnum.Latest, author: AuthorFilterEnum.All_Comments @@ -84,9 +87,9 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => { kind: kinds.ShortTextNote, created_at: now(), tags: [ - ['e', modDetails.id], - ['a', modDetails.aTag], - ['p', modDetails.author] + ['e', addressable.id], + ['a', addressable.aTag], + ['p', addressable.author] ] } @@ -176,7 +179,7 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => { let filteredComments = commentEvents if (filterOptions.author === AuthorFilterEnum.Creator_Comments) { filteredComments = filteredComments.filter( - (comment) => comment.pubkey === modDetails.author + (comment) => comment.pubkey === addressable.author ) } @@ -187,7 +190,7 @@ export const Comments = ({ modDetails, setCommentCount }: Props) => { } return filteredComments - }, [commentEvents, filterOptions, modDetails.author]) + }, [commentEvents, filterOptions, addressable.author]) return (
diff --git a/src/hooks/useComments.ts b/src/hooks/useComments.ts index fb57eb8..8b107bc 100644 --- a/src/hooks/useComments.ts +++ b/src/hooks/useComments.ts @@ -7,22 +7,30 @@ import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk' import { useEffect, useState } from 'react' -import { CommentEvent, ModDetails, UserRelaysType } from 'types' +import { CommentEvent, UserRelaysType } from 'types' import { log, LogType, timeout } from 'utils' import { useNDKContext } from './useNDKContext' -export const useComments = (mod: ModDetails) => { +export const useComments = ( + author: string | undefined, + aTag: string | undefined +) => { const { ndk } = useNDKContext() const [commentEvents, setCommentEvents] = useState([]) useEffect(() => { + if (!(author && aTag)) { + // Author and aTag are required + return + } + let subscription: NDKSubscription // Define the subscription variable here for cleanup const setupSubscription = async () => { // Find the mod author's relays. const authorReadRelays = await Promise.race([ - getRelayListForUser(mod.author, ndk), + getRelayListForUser(author, ndk), timeout(10 * 1000) // add a 10 sec timeout ]) .then((ndkRelayList) => { @@ -33,7 +41,7 @@ export const useComments = (mod: ModDetails) => { log( true, LogType.Error, - `An error occurred in fetching user's (${mod.author}) ${UserRelaysType.Read}`, + `An error occurred in fetching user's (${author}) ${UserRelaysType.Read}`, err ) return [] as string[] @@ -41,7 +49,7 @@ export const useComments = (mod: ModDetails) => { const filter: NDKFilter = { kinds: [NDKKind.Text], - '#a': [mod.aTag] + '#a': [aTag] } const relayUrls = new Set() @@ -92,7 +100,7 @@ export const useComments = (mod: ModDetails) => { subscription.stop() } } - }, [mod.aTag, mod.author, ndk]) + }, [aTag, author, ndk]) return { commentEvents, diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index 971c453..46becf8 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -1,21 +1,50 @@ +import { useState } from 'react' +import { useLoaderData } from 'react-router-dom' +import StarterKit from '@tiptap/starter-kit' +import Link from '@tiptap/extension-link' +import Image from '@tiptap/extension-image' +import { EditorContent, useEditor } from '@tiptap/react' +import DOMPurify from 'dompurify' +import { marked } from 'marked' import { LoadingSpinner } from 'components/LoadingSpinner' import { ProfileSection } from 'components/ProfileSection' -import { useLoaderData } from 'react-router-dom' -import { BlogDetails } from 'types' +import { Comments } from 'components/comment' +import { Addressable, BlogDetails } from 'types' +import placeholder from '../../assets/img/DEGMods Placeholder Img.png' +import { PublishDetails } from 'components/Internal/PublishDetails' +import { Interactions } from 'components/Internal/Interactions' export const BlogPage = () => { const data = useLoaderData() as Partial - - if (!data) return + const [commentCount, setCommentCount] = useState(0) + const html = marked.parse(data?.content || '', { async: false }) + const sanitized = DOMPurify.sanitize(html) + const editor = useEditor({ + content: sanitized, + extensions: [ + StarterKit, + Link, + Image.configure({ + inline: true, + HTMLAttributes: { + class: 'IBMSMSMBSSPostImg' + } + }) + ], + editable: false + }) return (
-
-
- {/*
+ {!data ? ( + + ) : ( +
+
+ {/*

Post for: Mod Name

*/} -
-
-
-
-

Heading

-
-
-
-
-

NSFW

+
+
+
+
+

+ {data.title} +

+
+
+ +
+
+ {data.nsfw && ( +
+

NSFW

+
+ )} + {data.tTags && + data.tTags.map((t) => ( + + {t} + + ))}
- Tag 1 - Tag 2 - Tag 3
-
- {/*
-
- -
-
- -
-

420

-
-
-
-
- -
-

69k

-
-
-
-
-
-
- -
-

4.2k

-
-
-
-
-
-
- -
-

69

-
-
-
-
-
-
-
-
-
- - -

01/11/2024

-
-
- - -

24/11/2024

-
- - -

degmods.com

-
-
-
-
*/} - {/*
+ + + {/* */} - {/*
-
-

Comments

-
- -
-
-
- -
-

Yo this article was insane to read!

-
-
-
-
- - - -

52

-
-
-
-
-
- - - -

4

-
-
-
-
-
- - - -

6

-
-
-
-
-
- - - -

500K

-
-
-
-
-
- - - -

12

-

Replies

-
-
-

Reply

-
-
-
-
-
-
-
+
+
-
*/} +
-
- {!!data.author && } + )} + {!!data?.author && }
diff --git a/src/pages/blog/loader.ts b/src/pages/blog/loader.ts index c241b7e..4cd0338 100644 --- a/src/pages/blog/loader.ts +++ b/src/pages/blog/loader.ts @@ -1,16 +1,10 @@ -import { filterForEventsTaggingId, NDKEvent } from '@nostr-dev-kit/ndk' +import { filterForEventsTaggingId } from '@nostr-dev-kit/ndk' import { NDKContextType } from 'contexts/NDKContext' import { LoaderFunctionArgs, redirect } from 'react-router-dom' import { toast } from 'react-toastify' import { appRoutes } from 'routes' -import { BlogDetails } from 'types' -import { - getFirstTagValue, - getFirstTagValueAsInt, - getTagValue, - log, - LogType -} from 'utils' +import { log, LogType } from 'utils' +import { extractBlogDetails } from 'utils/blog' export const blogRouteLoader = (ndkContext: NDKContextType) => @@ -23,21 +17,20 @@ export const blogRouteLoader = try { const filter = filterForEventsTaggingId(naddr) + if (!filter) { log(true, LogType.Error, 'Unable to create filter from blog naddr.') return redirect(appRoutes.blogs) } - const event = await ndkContext.fetchEvent(filter) - console.log(event) - if (event) { - const blogDetails = extractBlogDetails(event) - - console.log(blogDetails) - return blogDetails + if (!event) { + log(true, LogType.Error, 'Unable to fetch the blog event.') + return null } - return null + const blogDetails = extractBlogDetails(event) + + return blogDetails } catch (error) { log( true, @@ -45,26 +38,7 @@ export const blogRouteLoader = 'An error occurred in fetching blog details from relays', error ) - toast.error('An error occurred in fetching mod details from relays') + toast.error('An error occurred in fetching blog details from relays') return redirect(appRoutes.blogs) } } - -function extractBlogDetails(event: NDKEvent): Partial { - return { - title: getFirstTagValue(event, 'title'), - content: event.content, - summary: getFirstTagValue(event, 'summary'), - image: getFirstTagValue(event, 'image'), - nsfw: getFirstTagValue(event, 'nsfw') === 'true', - - id: event.id, - author: event.pubkey, - published_at: getFirstTagValueAsInt(event, 'published_at'), - edited_at: event.created_at, - rTag: getFirstTagValue(event, 'r') || 'N/A', - dTag: getFirstTagValue(event, 'd'), - aTag: getFirstTagValue(event, 'a'), - tTags: getTagValue(event, 't') || [] - } -} diff --git a/src/pages/blogs.tsx b/src/pages/blogs.tsx deleted file mode 100644 index 822aa61..0000000 --- a/src/pages/blogs.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { BlogCard } from '../components/BlogCard' -import '../styles/filters.css' -import '../styles/pagination.css' -import '../styles/search.css' -import '../styles/styles.css' -import placeholder from '../assets/img/DEGMods Placeholder Img.png' - -export const BlogsPage = () => { - return ( -
-
-
-
-
-
-

Blogs (WIP)

-
-
-
-
- - -
-
-
-
-
- - - -
-
- - - - - - - - -
-
- - -
-
-
- ) -} diff --git a/src/pages/blogs/index.tsx b/src/pages/blogs/index.tsx new file mode 100644 index 0000000..1d8e96d --- /dev/null +++ b/src/pages/blogs/index.tsx @@ -0,0 +1,205 @@ +import { useMemo, useRef, useState } from 'react' +import { useLoaderData, useSearchParams } from 'react-router-dom' +import { useLocalStorage } from 'hooks' +import { BlogCardDetails, NSFWFilter, SortBy } from 'types' +import { SearchInput } from '../../components/SearchInput' +import { BlogCard } from '../../components/BlogCard' +import '../../styles/filters.css' +import '../../styles/pagination.css' +import '../../styles/search.css' +import '../../styles/styles.css' + +export const BlogsPage = () => { + const blogs = useLoaderData() as Partial[] | undefined + const [filterOptions, setFilterOptions] = useLocalStorage('filter-blog', { + sort: SortBy.Latest, + nsfw: NSFWFilter.Hide_NSFW + }) + + // Search + const searchTermRef = useRef(null) + const [searchParams, setSearchParams] = useSearchParams() + const [searchTerm, setSearchTerm] = useState(searchParams.get('q') || '') + const handleSearch = () => { + const value = searchTermRef.current?.value || '' // Access the input value from the ref + setSearchTerm(value) + + if (value) { + searchParams.set('q', value) + } else { + searchParams.delete('q') + } + + setSearchParams(searchParams, { + replace: true + }) + } + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleSearch() + } + } + + // Filter + const filteredBlogs = useMemo(() => { + const filterNsfwFn = (blog: Partial) => { + switch (filterOptions.nsfw) { + case NSFWFilter.Hide_NSFW: + return !blog.nsfw + case NSFWFilter.Only_NSFW: + return blog.nsfw + default: + return blog + } + } + + let filtered = blogs?.filter(filterNsfwFn) || [] + const lowerCaseSearchTerm = searchTerm.toLowerCase() + + if (searchTerm !== '') { + const filterSearchTermFn = (blog: Partial) => + (blog.title || '').toLowerCase().includes(lowerCaseSearchTerm) || + (blog.summary || '').toLowerCase().includes(lowerCaseSearchTerm) || + (blog.content || '').toLowerCase().includes(lowerCaseSearchTerm) || + (blog.tTags || []).findIndex((tag) => + tag.toLowerCase().includes(lowerCaseSearchTerm) + ) > -1 + filtered = filtered.filter(filterSearchTermFn) + } + + if (filterOptions.sort === SortBy.Latest) { + filtered.sort((a, b) => + a.published_at && b.published_at ? b.published_at - a.published_at : 0 + ) + } else if (filterOptions.sort === SortBy.Oldest) { + filtered.sort((a, b) => + a.published_at && b.published_at ? a.published_at - b.published_at : 0 + ) + } + + return filtered + }, [blogs, searchTerm, filterOptions.sort, filterOptions.nsfw]) + + return ( +
+
+
+
+
+
+

Blogs

+
+ +
+
+ +
+
+
+
+ +
+ {Object.values(SortBy).map((item, index) => ( +
+ setFilterOptions((prev) => ({ + ...prev, + sort: item + })) + } + > + {item} +
+ ))} +
+
+
+
+
+ +
+ {Object.values(NSFWFilter).map((item, index) => ( +
+ setFilterOptions((prev) => ({ + ...prev, + nsfw: item + })) + } + > + {item} +
+ ))} +
+
+
+
+ +
+
+ {filteredBlogs && + filteredBlogs.map((b) => )} +
+
+ + +
+
+
+
+ ) +} diff --git a/src/pages/blogs/loader.ts b/src/pages/blogs/loader.ts new file mode 100644 index 0000000..74066f5 --- /dev/null +++ b/src/pages/blogs/loader.ts @@ -0,0 +1,37 @@ +import { NDKFilter } from '@nostr-dev-kit/ndk' +import { NDKContextType } from 'contexts/NDKContext' +import { kinds } from 'nostr-tools' +import { toast } from 'react-toastify' +import { log, LogType, npubToHex } from 'utils' +import { extractBlogCardDetails } from 'utils/blog' + +export const blogsRouteLoader = (ndkContext: NDKContextType) => async () => { + try { + const blogNpubs = import.meta.env.VITE_BLOG_NPUBS.split(',') + const blogHexkeys = blogNpubs + .map(npubToHex) + .filter((hexkey) => hexkey !== null) + + const filter: NDKFilter = { + authors: blogHexkeys, + kinds: [kinds.LongFormArticle] + } + const events = await ndkContext.fetchEvents(filter) + + if (!events) { + log(true, LogType.Error, 'Unable to fetch the blog events.') + return null + } + + return events.map(extractBlogCardDetails).filter((e) => e.naddr) + } catch (error) { + log( + true, + LogType.Error, + 'An error occurred in fetching blog details from relays', + error + ) + toast.error('An error occurred in fetching blog details from relays') + return null + } +} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 5c15e25..903c207 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -119,10 +119,10 @@ export const HomePage = () => {

Blog Posts (WIP)

- - - - + + + +
diff --git a/src/pages/mod/index.tsx b/src/pages/mod/index.tsx index 5aa9bf9..580ba24 100644 --- a/src/pages/mod/index.tsx +++ b/src/pages/mod/index.tsx @@ -2,7 +2,6 @@ import { NDKFilter, NDKKind } from '@nostr-dev-kit/ndk' import Link from '@tiptap/extension-link' import { EditorContent, useEditor } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' -import { formatDate } from 'date-fns' import FsLightbox from 'fslightbox-react' import { nip19, UnsignedEvent } from 'nostr-tools' import { useEffect, useRef, useState } from 'react' @@ -31,7 +30,6 @@ import '../../styles/tags.css' import '../../styles/write.css' import { DownloadUrl, ModDetails, UserRelaysType } from '../../types' import { - abbreviateNumber, copyTextToClipboard, downloadFile, extractModData, @@ -43,11 +41,11 @@ import { sendDMUsingRandomKey, signAndPublish } from '../../utils' -import { Comments } from './internal/comment' -import { Reactions } from './internal/reactions' -import { Zap } from './internal/zap' +import { Comments } from '../../components/comment' import { CheckboxField } from 'components/Inputs' import placeholder from '../../assets/img/DEGMods Placeholder Img.png' +import { PublishDetails } from 'components/Internal/PublishDetails' +import { Interactions } from 'components/Internal/Interactions' export const ModPage = () => { const { naddr } = useParams() @@ -143,7 +141,7 @@ export const ModPage = () => { nsfw={modData.nsfw} /> { Creator's Blog Posts (WIP)
- - - + + +
@@ -972,126 +970,6 @@ const Body = ({ ) } -type InteractionsProps = { - modDetails: ModDetails - commentCount: number -} - -const Interactions = ({ modDetails, commentCount }: InteractionsProps) => { - return ( - - ) -} - -type PublishDetailsProps = { - published_at: number - edited_at: number - site: string -} - -const PublishDetails = ({ - published_at, - edited_at, - site -}: PublishDetailsProps) => { - return ( -
-
-
- - - -

- {formatDate( - (published_at !== -1 ? published_at : edited_at) * 1000, - 'dd/MM/yyyy hh:mm:ss aa' - )} -

-
-
- - - -

- {formatDate(edited_at * 1000, 'dd/MM/yyyy hh:mm:ss aa')} -

-
- - - - -

{site}

-
-
-
- ) -} - const Download = ({ url, hash, diff --git a/src/pages/write/action.tsx b/src/pages/write/action.tsx index 3dfb1a5..ae16b2b 100644 --- a/src/pages/write/action.tsx +++ b/src/pages/write/action.tsx @@ -1,7 +1,7 @@ import { NDKContextType } from 'contexts/NDKContext' import { ActionFunctionArgs, redirect } from 'react-router-dom' import { getBlogPageRoute } from 'routes' -import { BlogFormErrors, BlogFormSubmit } from 'types' +import { BlogFormErrors, BlogEventSubmitForm } from 'types' import { isReachable, isValidImageUrl, @@ -19,7 +19,7 @@ import { store } from 'store' export const writeRouteAction = (ndkContext: NDKContextType) => - async ({ params, request }: ActionFunctionArgs) => { + async ({ request }: ActionFunctionArgs) => { // Get the current state const userState = store.getState().user let hexPubkey: string @@ -47,7 +47,7 @@ export const writeRouteAction = const formData = await request.formData() // Parse the the data - const formSubmit = parseFormData(formData) + const formSubmit = parseFormData(formData) // Check for errors const formErrors = await validateFormData(formSubmit) @@ -110,7 +110,7 @@ export const writeRouteAction = )}` ) const naddr = nip19.naddrEncode({ - identifier: aTag, + identifier: uuid, pubkey: signedEvent.pubkey, kind: signedEvent.kind, relays: publishedOnRelays @@ -125,7 +125,7 @@ export const writeRouteAction = } const validateFormData = async ( - formData: Partial + formData: Partial ): Promise => { const errors: BlogFormErrors = {} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index e875d86..6c7e88a 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -20,6 +20,7 @@ import { writeRouteAction } from '../pages/write/action' import { BlogsPage } from 'pages/blogs' import { BlogPage } from 'pages/blog' import { blogRouteLoader } from 'pages/blog/loader' +import { blogsRouteLoader } from 'pages/blogs/loader' export const appRoutes = { index: '/', @@ -29,8 +30,8 @@ export const appRoutes = { mods: '/mods', mod: '/mod/:naddr', about: '/about', - blogs: '/blogs', - blog: '/blogs/:naddr', + blogs: '/blog', + blog: '/blog/:naddr', submitMod: '/submit-mod', editMod: '/edit-mod/:naddr', write: '/write', @@ -90,7 +91,8 @@ export const routerWithNdkContext = (context: NDKContextType) => }, { path: appRoutes.blogs, - element: + element: , + loader: blogsRouteLoader(context) }, { path: appRoutes.blog, diff --git a/src/types/blog.ts b/src/types/blog.ts index bedb751..051c153 100644 --- a/src/types/blog.ts +++ b/src/types/blog.ts @@ -1,11 +1,13 @@ -export interface BlogDetails { +export interface BlogForm { title: string content: string - summary: string image: string - nsfw: boolean + summary: string tags: string + nsfw: boolean +} +export interface BlogDetails extends BlogForm { id: string author: string published_at: number @@ -16,20 +18,12 @@ export interface BlogDetails { tTags: string[] } -export interface BlogFormSubmit - extends Omit< - BlogDetails, - | 'nsfw' - | 'id' - | 'author' - | 'published_at' - | 'edited_at' - | 'rTag' - | 'dTag' - | 'aTag' - | 'tTag' - > { +export interface BlogEventSubmitForm extends Omit { nsfw: string } -export interface BlogFormErrors extends Partial {} +export interface BlogFormErrors extends Partial {} + +export interface BlogCardDetails extends BlogDetails { + naddr: string +} diff --git a/src/types/nostr.ts b/src/types/nostr.ts index 00e5ade..bdf46a4 100644 --- a/src/types/nostr.ts +++ b/src/types/nostr.ts @@ -7,3 +7,9 @@ export interface SignedEvent { id: string sig: string } + +export interface Addressable { + author: string + id: string + aTag: string +} diff --git a/src/utils/blog.ts b/src/utils/blog.ts new file mode 100644 index 0000000..0de30db --- /dev/null +++ b/src/utils/blog.ts @@ -0,0 +1,38 @@ +import { NDKEvent } from '@nostr-dev-kit/ndk' +import { BlogCardDetails, BlogDetails } from 'types' +import { getFirstTagValue, getFirstTagValueAsInt, getTagValue } from './nostr' +import { kinds, nip19 } from 'nostr-tools' + +export const extractBlogDetails = (event: NDKEvent): Partial => ({ + title: getFirstTagValue(event, 'title'), + content: event.content, + summary: getFirstTagValue(event, 'summary'), + image: getFirstTagValue(event, 'image'), + nsfw: getFirstTagValue(event, 'nsfw') === 'true', + + id: event.id, + author: event.pubkey, + published_at: getFirstTagValueAsInt(event, 'published_at'), + edited_at: event.created_at, + rTag: getFirstTagValue(event, 'r') || 'N/A', + dTag: getFirstTagValue(event, 'd'), + aTag: getFirstTagValue(event, 'a'), + tTags: getTagValue(event, 't') || [] +}) + +export const extractBlogCardDetails = ( + event: NDKEvent +): Partial => { + const blogDetails = extractBlogDetails(event) + + return { + ...blogDetails, + naddr: blogDetails.dTag + ? nip19.naddrEncode({ + identifier: blogDetails.dTag, + kind: kinds.LongFormArticle, + pubkey: event.pubkey + }) + : undefined + } +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 1f3c47c..6363e3e 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -6,6 +6,7 @@ interface ImportMetaEnv { readonly VITE_REPORTING_NPUB: string readonly VITE_FALLBACK_MOD_IMAGE: string readonly VITE_FALLBACK_GAME_IMAGE: string + readonly VITE_BLOG_NPUBS: string // more env variables... }