an appview-less Bluesky client using Constellation and PDS Queries - https://reddwarf.app/

Compare changes

Choose any two refs to compare.

Changed files
+603 -607
src
components
placeholders
routes
+41 -72
package-lock.json
··· 376 376 "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", 377 377 "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", 378 378 "license": "MIT", 379 + "peer": true, 379 380 "dependencies": { 380 381 "@ampproject/remapping": "^2.2.0", 381 382 "@babel/code-frame": "^7.27.1", ··· 883 884 } 884 885 ], 885 886 "license": "MIT", 887 + "peer": true, 886 888 "engines": { 887 889 "node": ">=18" 888 890 }, ··· 906 908 } 907 909 ], 908 910 "license": "MIT", 911 + "peer": true, 909 912 "engines": { 910 913 "node": ">=18" 911 914 } ··· 1494 1497 "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", 1495 1498 "dev": true, 1496 1499 "license": "Apache-2.0", 1497 - "peer": true, 1498 1500 "dependencies": { 1499 1501 "@eslint/object-schema": "^2.1.6", 1500 1502 "debug": "^4.3.1", ··· 1510 1512 "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", 1511 1513 "dev": true, 1512 1514 "license": "Apache-2.0", 1513 - "peer": true, 1514 1515 "dependencies": { 1515 1516 "@eslint/core": "^0.16.0" 1516 1517 }, ··· 1524 1525 "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", 1525 1526 "dev": true, 1526 1527 "license": "Apache-2.0", 1527 - "peer": true, 1528 1528 "dependencies": { 1529 1529 "@types/json-schema": "^7.0.15" 1530 1530 }, ··· 1538 1538 "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", 1539 1539 "dev": true, 1540 1540 "license": "MIT", 1541 - "peer": true, 1542 1541 "dependencies": { 1543 1542 "ajv": "^6.12.4", 1544 1543 "debug": "^4.3.2", ··· 1563 1562 "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", 1564 1563 "dev": true, 1565 1564 "license": "MIT", 1566 - "peer": true, 1567 1565 "engines": { 1568 1566 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1569 1567 }, ··· 1577 1575 "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 1578 1576 "dev": true, 1579 1577 "license": "Apache-2.0", 1580 - "peer": true, 1581 1578 "engines": { 1582 1579 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1583 1580 } ··· 1588 1585 "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", 1589 1586 "dev": true, 1590 1587 "license": "Apache-2.0", 1591 - "peer": true, 1592 1588 "dependencies": { 1593 1589 "@eslint/core": "^0.16.0", 1594 1590 "levn": "^0.4.1" ··· 1637 1633 "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 1638 1634 "dev": true, 1639 1635 "license": "Apache-2.0", 1640 - "peer": true, 1641 1636 "engines": { 1642 1637 "node": ">=18.18.0" 1643 1638 } ··· 1648 1643 "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", 1649 1644 "dev": true, 1650 1645 "license": "Apache-2.0", 1651 - "peer": true, 1652 1646 "dependencies": { 1653 1647 "@humanfs/core": "^0.19.1", 1654 1648 "@humanwhocodes/retry": "^0.4.0" ··· 1663 1657 "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 1664 1658 "dev": true, 1665 1659 "license": "Apache-2.0", 1666 - "peer": true, 1667 1660 "engines": { 1668 1661 "node": ">=12.22" 1669 1662 }, ··· 1678 1671 "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", 1679 1672 "dev": true, 1680 1673 "license": "Apache-2.0", 1681 - "peer": true, 1682 1674 "engines": { 1683 1675 "node": ">=18.18" 1684 1676 }, ··· 3856 3848 "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", 3857 3849 "dev": true, 3858 3850 "license": "MIT", 3851 + "peer": true, 3859 3852 "dependencies": { 3860 3853 "@babel/core": "^7.21.3", 3861 3854 "@svgr/babel-preset": "8.1.0", ··· 4330 4323 "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.6.tgz", 4331 4324 "integrity": "sha512-VUAag4ERjh+qlmg0wNivQIVCZUrYndqYu3/wPCVZd4r0E+1IqotbeyGTc+ICroL/PqbpSaGZg02zSWYfcvxbdA==", 4332 4325 "license": "MIT", 4326 + "peer": true, 4333 4327 "dependencies": { 4334 4328 "@tanstack/query-core": "5.85.6" 4335 4329 }, ··· 4363 4357 "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.131.28.tgz", 4364 4358 "integrity": "sha512-vWExhrqHJuT9v+6/2DCQ4pVvPaYoLazMNw8WXiLNuzBXh1FuEoIGaW3jw3DEP0OJCmMiWtTi34NzQnakkQZlQg==", 4365 4359 "license": "MIT", 4360 + "peer": true, 4366 4361 "dependencies": { 4367 4362 "@tanstack/history": "1.131.2", 4368 4363 "@tanstack/react-store": "^0.7.0", ··· 4427 4422 "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.131.28.tgz", 4428 4423 "integrity": "sha512-f+vdfr3WKSS/BcqgI5s4vZg9xYb7NkvIolkaMELrbz3l+khkw1aTjx8wqCHRY4dqwIAxq+iZBZtMWXA7pztGJg==", 4429 4424 "license": "MIT", 4425 + "peer": true, 4430 4426 "dependencies": { 4431 4427 "@tanstack/history": "1.131.2", 4432 4428 "@tanstack/store": "^0.7.0", ··· 4599 4595 "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", 4600 4596 "dev": true, 4601 4597 "license": "MIT", 4598 + "peer": true, 4602 4599 "dependencies": { 4603 4600 "@babel/code-frame": "^7.10.4", 4604 4601 "@babel/runtime": "^7.12.5", ··· 4721 4718 "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 4722 4719 "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 4723 4720 "dev": true, 4724 - "license": "MIT", 4725 - "peer": true 4721 + "license": "MIT" 4726 4722 }, 4727 4723 "node_modules/@types/node": { 4728 4724 "version": "24.3.0", ··· 4730 4726 "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", 4731 4727 "devOptional": true, 4732 4728 "license": "MIT", 4729 + "peer": true, 4733 4730 "dependencies": { 4734 4731 "undici-types": "~7.10.0" 4735 4732 } ··· 4739 4736 "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", 4740 4737 "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", 4741 4738 "license": "MIT", 4739 + "peer": true, 4742 4740 "dependencies": { 4743 4741 "csstype": "^3.0.2" 4744 4742 } ··· 4748 4746 "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", 4749 4747 "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", 4750 4748 "license": "MIT", 4749 + "peer": true, 4751 4750 "peerDependencies": { 4752 4751 "@types/react": "^19.0.0" 4753 4752 } ··· 4765 4764 "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", 4766 4765 "dev": true, 4767 4766 "license": "MIT", 4767 + "peer": true, 4768 4768 "dependencies": { 4769 4769 "@eslint-community/regexpp": "^4.10.0", 4770 4770 "@typescript-eslint/scope-manager": "8.46.1", ··· 4805 4805 "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", 4806 4806 "dev": true, 4807 4807 "license": "MIT", 4808 + "peer": true, 4808 4809 "dependencies": { 4809 4810 "@typescript-eslint/scope-manager": "8.46.1", 4810 4811 "@typescript-eslint/types": "8.46.1", ··· 5187 5188 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 5188 5189 "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 5189 5190 "license": "MIT", 5191 + "peer": true, 5190 5192 "bin": { 5191 5193 "acorn": "bin/acorn" 5192 5194 }, ··· 5200 5202 "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 5201 5203 "dev": true, 5202 5204 "license": "MIT", 5203 - "peer": true, 5204 5205 "peerDependencies": { 5205 5206 "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 5206 5207 } ··· 5221 5222 "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 5222 5223 "dev": true, 5223 5224 "license": "MIT", 5224 - "peer": true, 5225 5225 "dependencies": { 5226 5226 "fast-deep-equal": "^3.1.1", 5227 5227 "fast-json-stable-stringify": "^2.0.0", ··· 5627 5627 } 5628 5628 ], 5629 5629 "license": "MIT", 5630 + "peer": true, 5630 5631 "dependencies": { 5631 5632 "caniuse-lite": "^1.0.30001737", 5632 5633 "electron-to-chromium": "^1.5.211", ··· 5784 5785 "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 5785 5786 "dev": true, 5786 5787 "license": "MIT", 5787 - "peer": true, 5788 5788 "dependencies": { 5789 5789 "ansi-styles": "^4.1.0", 5790 5790 "supports-color": "^7.1.0" ··· 5802 5802 "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 5803 5803 "dev": true, 5804 5804 "license": "MIT", 5805 - "peer": true, 5806 5805 "dependencies": { 5807 5806 "color-convert": "^2.0.1" 5808 5807 }, ··· 5883 5882 "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 5884 5883 "dev": true, 5885 5884 "license": "MIT", 5886 - "peer": true, 5887 5885 "dependencies": { 5888 5886 "color-name": "~1.1.4" 5889 5887 }, ··· 5896 5894 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 5897 5895 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 5898 5896 "dev": true, 5899 - "license": "MIT", 5900 - "peer": true 5897 + "license": "MIT" 5901 5898 }, 5902 5899 "node_modules/compare-versions": { 5903 5900 "version": "6.1.1", ··· 5976 5973 "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 5977 5974 "dev": true, 5978 5975 "license": "MIT", 5979 - "peer": true, 5980 5976 "dependencies": { 5981 5977 "path-key": "^3.1.0", 5982 5978 "shebang-command": "^2.0.0", ··· 6004 6000 "version": "3.1.3", 6005 6001 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 6006 6002 "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 6007 - "license": "MIT" 6003 + "license": "MIT", 6004 + "peer": true 6008 6005 }, 6009 6006 "node_modules/custom-media-element": { 6010 6007 "version": "1.4.5", ··· 6147 6144 "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 6148 6145 "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 6149 6146 "dev": true, 6150 - "license": "MIT", 6151 - "peer": true 6147 + "license": "MIT" 6152 6148 }, 6153 6149 "node_modules/define-data-property": { 6154 6150 "version": "1.1.4", ··· 6556 6552 "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 6557 6553 "dev": true, 6558 6554 "license": "MIT", 6559 - "peer": true, 6560 6555 "engines": { 6561 6556 "node": ">=10" 6562 6557 }, ··· 6848 6843 "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", 6849 6844 "dev": true, 6850 6845 "license": "BSD-2-Clause", 6851 - "peer": true, 6852 6846 "dependencies": { 6853 6847 "esrecurse": "^4.3.0", 6854 6848 "estraverse": "^5.2.0" ··· 6879 6873 "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 6880 6874 "dev": true, 6881 6875 "license": "ISC", 6882 - "peer": true, 6883 6876 "dependencies": { 6884 6877 "is-glob": "^4.0.3" 6885 6878 }, ··· 6893 6886 "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", 6894 6887 "dev": true, 6895 6888 "license": "BSD-2-Clause", 6896 - "peer": true, 6897 6889 "dependencies": { 6898 6890 "acorn": "^8.15.0", 6899 6891 "acorn-jsx": "^5.3.2", ··· 6925 6917 "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 6926 6918 "dev": true, 6927 6919 "license": "BSD-3-Clause", 6928 - "peer": true, 6929 6920 "dependencies": { 6930 6921 "estraverse": "^5.1.0" 6931 6922 }, ··· 6939 6930 "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 6940 6931 "dev": true, 6941 6932 "license": "BSD-2-Clause", 6942 - "peer": true, 6943 6933 "dependencies": { 6944 6934 "estraverse": "^5.2.0" 6945 6935 }, ··· 7028 7018 "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 7029 7019 "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 7030 7020 "dev": true, 7031 - "license": "MIT", 7032 - "peer": true 7021 + "license": "MIT" 7033 7022 }, 7034 7023 "node_modules/fast-levenshtein": { 7035 7024 "version": "2.0.6", 7036 7025 "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 7037 7026 "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 7038 7027 "dev": true, 7039 - "license": "MIT", 7040 - "peer": true 7028 + "license": "MIT" 7041 7029 }, 7042 7030 "node_modules/fastq": { 7043 7031 "version": "1.19.1", ··· 7055 7043 "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 7056 7044 "dev": true, 7057 7045 "license": "MIT", 7058 - "peer": true, 7059 7046 "dependencies": { 7060 7047 "flat-cache": "^4.0.0" 7061 7048 }, ··· 7081 7068 "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 7082 7069 "dev": true, 7083 7070 "license": "MIT", 7084 - "peer": true, 7085 7071 "dependencies": { 7086 7072 "locate-path": "^6.0.0", 7087 7073 "path-exists": "^4.0.0" ··· 7099 7085 "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 7100 7086 "dev": true, 7101 7087 "license": "MIT", 7102 - "peer": true, 7103 7088 "dependencies": { 7104 7089 "flatted": "^3.2.9", 7105 7090 "keyv": "^4.5.4" ··· 7113 7098 "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 7114 7099 "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 7115 7100 "dev": true, 7116 - "license": "ISC", 7117 - "peer": true 7101 + "license": "ISC" 7118 7102 }, 7119 7103 "node_modules/for-each": { 7120 7104 "version": "0.3.5", ··· 7301 7285 "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 7302 7286 "dev": true, 7303 7287 "license": "MIT", 7304 - "peer": true, 7305 7288 "engines": { 7306 7289 "node": ">=18" 7307 7290 }, ··· 7379 7362 "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 7380 7363 "dev": true, 7381 7364 "license": "MIT", 7382 - "peer": true, 7383 7365 "engines": { 7384 7366 "node": ">=8" 7385 7367 } ··· 7592 7574 "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 7593 7575 "dev": true, 7594 7576 "license": "MIT", 7595 - "peer": true, 7596 7577 "engines": { 7597 7578 "node": ">= 4" 7598 7579 } ··· 7635 7616 "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 7636 7617 "dev": true, 7637 7618 "license": "MIT", 7638 - "peer": true, 7639 7619 "engines": { 7640 7620 "node": ">=0.8.19" 7641 7621 } ··· 8141 8121 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 8142 8122 "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 8143 8123 "dev": true, 8144 - "license": "ISC", 8145 - "peer": true 8124 + "license": "ISC" 8146 8125 }, 8147 8126 "node_modules/iso-datestring-validator": { 8148 8127 "version": "2.2.2", ··· 8240 8219 "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", 8241 8220 "dev": true, 8242 8221 "license": "MIT", 8222 + "peer": true, 8243 8223 "dependencies": { 8244 8224 "cssstyle": "^4.2.1", 8245 8225 "data-urls": "^5.0.0", ··· 8291 8271 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 8292 8272 "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 8293 8273 "dev": true, 8294 - "license": "MIT", 8295 - "peer": true 8274 + "license": "MIT" 8296 8275 }, 8297 8276 "node_modules/json-parse-even-better-errors": { 8298 8277 "version": "2.3.1", ··· 8306 8285 "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 8307 8286 "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 8308 8287 "dev": true, 8309 - "license": "MIT", 8310 - "peer": true 8288 + "license": "MIT" 8311 8289 }, 8312 8290 "node_modules/json-stable-stringify-without-jsonify": { 8313 8291 "version": "1.0.1", 8314 8292 "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 8315 8293 "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 8316 8294 "dev": true, 8317 - "license": "MIT", 8318 - "peer": true 8295 + "license": "MIT" 8319 8296 }, 8320 8297 "node_modules/json5": { 8321 8298 "version": "2.2.3", ··· 8351 8328 "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 8352 8329 "dev": true, 8353 8330 "license": "MIT", 8354 - "peer": true, 8355 8331 "dependencies": { 8356 8332 "json-buffer": "3.0.1" 8357 8333 } ··· 8369 8345 "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 8370 8346 "dev": true, 8371 8347 "license": "MIT", 8372 - "peer": true, 8373 8348 "dependencies": { 8374 8349 "prelude-ls": "^1.2.1", 8375 8350 "type-check": "~0.4.0" ··· 8655 8630 "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 8656 8631 "dev": true, 8657 8632 "license": "MIT", 8658 - "peer": true, 8659 8633 "dependencies": { 8660 8634 "p-locate": "^5.0.0" 8661 8635 }, ··· 8677 8651 "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 8678 8652 "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 8679 8653 "dev": true, 8680 - "license": "MIT", 8681 - "peer": true 8654 + "license": "MIT" 8682 8655 }, 8683 8656 "node_modules/loose-envify": { 8684 8657 "version": "1.4.0", ··· 11138 11111 "version": "4.0.3", 11139 11112 "inBundle": true, 11140 11113 "license": "MIT", 11114 + "peer": true, 11141 11115 "engines": { 11142 11116 "node": ">=12" 11143 11117 }, ··· 11471 11445 "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 11472 11446 "dev": true, 11473 11447 "license": "MIT", 11474 - "peer": true, 11475 11448 "dependencies": { 11476 11449 "deep-is": "^0.1.3", 11477 11450 "fast-levenshtein": "^2.0.6", ··· 11508 11481 "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 11509 11482 "dev": true, 11510 11483 "license": "MIT", 11511 - "peer": true, 11512 11484 "dependencies": { 11513 11485 "yocto-queue": "^0.1.0" 11514 11486 }, ··· 11525 11497 "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 11526 11498 "dev": true, 11527 11499 "license": "MIT", 11528 - "peer": true, 11529 11500 "dependencies": { 11530 11501 "p-limit": "^3.0.2" 11531 11502 }, ··· 11600 11571 "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 11601 11572 "dev": true, 11602 11573 "license": "MIT", 11603 - "peer": true, 11604 11574 "engines": { 11605 11575 "node": ">=8" 11606 11576 } ··· 11611 11581 "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 11612 11582 "dev": true, 11613 11583 "license": "MIT", 11614 - "peer": true, 11615 11584 "engines": { 11616 11585 "node": ">=8" 11617 11586 } ··· 11740 11709 "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 11741 11710 "dev": true, 11742 11711 "license": "MIT", 11743 - "peer": true, 11744 11712 "engines": { 11745 11713 "node": ">= 0.8.0" 11746 11714 } ··· 11921 11889 "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", 11922 11890 "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", 11923 11891 "license": "MIT", 11892 + "peer": true, 11924 11893 "engines": { 11925 11894 "node": ">=0.10.0" 11926 11895 } ··· 11930 11899 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", 11931 11900 "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", 11932 11901 "license": "MIT", 11902 + "peer": true, 11933 11903 "dependencies": { 11934 11904 "scheduler": "^0.26.0" 11935 11905 }, ··· 12350 12320 "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", 12351 12321 "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", 12352 12322 "license": "MIT", 12323 + "peer": true, 12353 12324 "engines": { 12354 12325 "node": ">=10" 12355 12326 } ··· 12421 12392 "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 12422 12393 "dev": true, 12423 12394 "license": "MIT", 12424 - "peer": true, 12425 12395 "dependencies": { 12426 12396 "shebang-regex": "^3.0.0" 12427 12397 }, ··· 12435 12405 "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 12436 12406 "dev": true, 12437 12407 "license": "MIT", 12438 - "peer": true, 12439 12408 "engines": { 12440 12409 "node": ">=8" 12441 12410 } ··· 12539 12508 "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", 12540 12509 "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", 12541 12510 "license": "MIT", 12511 + "peer": true, 12542 12512 "dependencies": { 12543 12513 "csstype": "^3.1.0", 12544 12514 "seroval": "~1.3.0", ··· 12708 12678 "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 12709 12679 "dev": true, 12710 12680 "license": "MIT", 12711 - "peer": true, 12712 12681 "engines": { 12713 12682 "node": ">=8" 12714 12683 }, ··· 12748 12717 "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 12749 12718 "dev": true, 12750 12719 "license": "MIT", 12751 - "peer": true, 12752 12720 "dependencies": { 12753 12721 "has-flag": "^4.0.0" 12754 12722 }, ··· 12848 12816 "version": "1.3.3", 12849 12817 "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", 12850 12818 "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", 12851 - "license": "MIT" 12819 + "license": "MIT", 12820 + "peer": true 12852 12821 }, 12853 12822 "node_modules/tiny-warning": { 12854 12823 "version": "1.0.3", ··· 12908 12877 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 12909 12878 "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 12910 12879 "license": "MIT", 12880 + "peer": true, 12911 12881 "engines": { 12912 12882 "node": ">=12" 12913 12883 }, ··· 13105 13075 "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 13106 13076 "dev": true, 13107 13077 "license": "MIT", 13108 - "peer": true, 13109 13078 "dependencies": { 13110 13079 "prelude-ls": "^1.2.1" 13111 13080 }, ··· 13197 13166 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 13198 13167 "dev": true, 13199 13168 "license": "Apache-2.0", 13169 + "peer": true, 13200 13170 "bin": { 13201 13171 "tsc": "bin/tsc", 13202 13172 "tsserver": "bin/tsserver" ··· 13533 13503 "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 13534 13504 "dev": true, 13535 13505 "license": "BSD-2-Clause", 13536 - "peer": true, 13537 13506 "dependencies": { 13538 13507 "punycode": "^2.1.0" 13539 13508 } ··· 13602 13571 "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", 13603 13572 "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", 13604 13573 "license": "MIT", 13574 + "peer": true, 13605 13575 "dependencies": { 13606 13576 "esbuild": "^0.25.0", 13607 13577 "fdir": "^6.4.4", ··· 13716 13686 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 13717 13687 "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 13718 13688 "license": "MIT", 13689 + "peer": true, 13719 13690 "engines": { 13720 13691 "node": ">=12" 13721 13692 }, ··· 13897 13868 "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 13898 13869 "dev": true, 13899 13870 "license": "ISC", 13900 - "peer": true, 13901 13871 "dependencies": { 13902 13872 "isexe": "^2.0.0" 13903 13873 }, ··· 14029 13999 "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 14030 14000 "dev": true, 14031 14001 "license": "MIT", 14032 - "peer": true, 14033 14002 "engines": { 14034 14003 "node": ">=0.10.0" 14035 14004 } ··· 14084 14053 "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 14085 14054 "dev": true, 14086 14055 "license": "MIT", 14087 - "peer": true, 14088 14056 "engines": { 14089 14057 "node": ">=10" 14090 14058 }, ··· 14103 14071 "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", 14104 14072 "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", 14105 14073 "license": "MIT", 14074 + "peer": true, 14106 14075 "funding": { 14107 14076 "url": "https://github.com/sponsors/colinhacks" 14108 14077 }
+31
src/components/placeholders/TextPlaceholder.tsx
··· 1 + import type { ReactNode } from "react"; 2 + 3 + export function TextPlaceholder({ 4 + className, 5 + pulse = false, 6 + }: { 7 + className?: string; 8 + pulse?: boolean; 9 + }) { 10 + return ( 11 + <span 12 + className={`bg-gray-100 dark:bg-gray-800 rounded h-4 ${pulse ? "animate-pulse" : ""} ${className ?? ""}`} 13 + ></span> 14 + ); 15 + } 16 + 17 + export function ConditionalTextPlaceholder({ 18 + show, 19 + placeholderClassName, 20 + children, 21 + }: { 22 + show: boolean; 23 + placeholderClassName?: string; 24 + children?: ReactNode; 25 + }) { 26 + return ( 27 + <> 28 + {show ? children : <TextPlaceholder className={placeholderClassName} />} 29 + </> 30 + ); 31 + }
+531 -535
src/routes/notifications.tsx
··· 7 7 import defaultpfp from "~/../public/favicon.png"; 8 8 import { Header } from "~/components/Header"; 9 9 import { 10 - ReusableTabRoute, 11 - useReusableTabScrollRestore, 10 + ReusableTabRoute, 11 + useReusableTabScrollRestore, 12 12 } from "~/components/ReusableTabRoute"; 13 13 import { 14 - MdiCardsHeartOutline, 15 - MdiCommentOutline, 16 - MdiRepeat, 17 - UniversalPostRendererATURILoader, 14 + MdiCardsHeartOutline, 15 + MdiCommentOutline, 16 + MdiRepeat, 17 + UniversalPostRendererATURILoader, 18 18 } from "~/components/UniversalPostRenderer"; 19 19 import { useAuth } from "~/providers/UnifiedAuthProvider"; 20 20 import { 21 - constellationURLAtom, 22 - enableBitesAtom, 23 - imgCDNAtom, 24 - postInteractionsFiltersAtom, 21 + constellationURLAtom, 22 + enableBitesAtom, 23 + imgCDNAtom, 24 + postInteractionsFiltersAtom, 25 25 } from "~/utils/atoms"; 26 26 import { 27 - useInfiniteQueryAuthorFeed, 28 - useQueryConstellation, 29 - useQueryIdentity, 30 - useQueryProfile, 31 - yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks, 27 + useInfiniteQueryAuthorFeed, 28 + useQueryConstellation, 29 + useQueryIdentity, 30 + useQueryProfile, 31 + yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks, 32 32 } from "~/utils/useQuery"; 33 33 34 34 import { FollowButton, Mutual } from "./profile.$did"; 35 + import { ConditionalTextPlaceholder } from "~/components/placeholders/TextPlaceholder"; 35 36 36 37 export function NotificationsComponent() { 37 - return ( 38 - <div className=""> 39 - <Header 40 - title={`Notifications`} 41 - backButtonCallback={() => { 42 - if (window.history.length > 1) { 43 - window.history.back(); 44 - } else { 45 - window.location.assign("/"); 46 - } 47 - }} 48 - bottomBorderDisabled={true} 49 - /> 50 - <NotificationsTabs /> 51 - </div> 52 - ); 38 + return ( 39 + <div className=""> 40 + <Header 41 + title={`Notifications`} 42 + backButtonCallback={() => { 43 + if (window.history.length > 1) { 44 + window.history.back(); 45 + } else { 46 + window.location.assign("/"); 47 + } 48 + }} 49 + bottomBorderDisabled={true} 50 + /> 51 + <NotificationsTabs /> 52 + </div> 53 + ); 53 54 } 54 55 55 56 export const Route = createFileRoute("/notifications")({ 56 - component: NotificationsComponent, 57 + component: NotificationsComponent, 57 58 }); 58 59 59 60 export default function NotificationsTabs() { 60 - const [bitesEnabled] = useAtom(enableBitesAtom); 61 - return ( 62 - <ReusableTabRoute 63 - route={`Notifications`} 64 - tabs={{ 65 - Mentions: <MentionsTab />, 66 - Follows: <FollowsTab />, 67 - "Post Interactions": <PostInteractionsTab />, 68 - ...bitesEnabled ? { 69 - Bites: <BitesTab />, 70 - } : {} 71 - }} 72 - /> 73 - ); 61 + const [bitesEnabled] = useAtom(enableBitesAtom); 62 + return ( 63 + <ReusableTabRoute 64 + route={`Notifications`} 65 + tabs={{ 66 + Mentions: <MentionsTab />, 67 + Follows: <FollowsTab />, 68 + "Post Interactions": <PostInteractionsTab />, 69 + ...(bitesEnabled 70 + ? { 71 + Bites: <BitesTab />, 72 + } 73 + : {}), 74 + }} 75 + /> 76 + ); 74 77 } 75 78 76 79 function MentionsTab() { 77 - const { agent } = useAuth(); 78 - const [constellationurl] = useAtom(constellationURLAtom); 79 - const infinitequeryresults = useInfiniteQuery({ 80 - ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 81 - { 82 - constellation: constellationurl, 83 - method: "/links", 84 - target: agent?.did, 85 - collection: "app.bsky.feed.post", 86 - path: ".facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did", 87 - } 88 - ), 89 - enabled: !!agent?.did, 90 - }); 80 + const { agent } = useAuth(); 81 + const [constellationurl] = useAtom(constellationURLAtom); 82 + const infinitequeryresults = useInfiniteQuery({ 83 + ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 84 + { 85 + constellation: constellationurl, 86 + method: "/links", 87 + target: agent?.did, 88 + collection: "app.bsky.feed.post", 89 + path: ".facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet#mention].did", 90 + } 91 + ), 92 + enabled: !!agent?.did, 93 + }); 91 94 92 - const { 93 - data: infiniteMentionsData, 94 - fetchNextPage, 95 - hasNextPage, 96 - isFetchingNextPage, 97 - isLoading, 98 - isError, 99 - error, 100 - } = infinitequeryresults; 95 + const { 96 + data: infiniteMentionsData, 97 + fetchNextPage, 98 + hasNextPage, 99 + isFetchingNextPage, 100 + isLoading, 101 + isError, 102 + error, 103 + } = infinitequeryresults; 101 104 102 - const mentionsAturis = React.useMemo(() => { 103 - // Get all replies from the standard infinite query 104 - return ( 105 - infiniteMentionsData?.pages.flatMap( 106 - (page) => 107 - page?.linking_records.map( 108 - (r) => `at://${r.did}/${r.collection}/${r.rkey}` 109 - ) ?? [] 110 - ) ?? [] 111 - ); 112 - }, [infiniteMentionsData]); 105 + const mentionsAturis = React.useMemo(() => { 106 + // Get all replies from the standard infinite query 107 + return ( 108 + infiniteMentionsData?.pages.flatMap( 109 + (page) => 110 + page?.linking_records.map( 111 + (r) => `at://${r.did}/${r.collection}/${r.rkey}` 112 + ) ?? [] 113 + ) ?? [] 114 + ); 115 + }, [infiniteMentionsData]); 113 116 114 - useReusableTabScrollRestore("Notifications"); 117 + useReusableTabScrollRestore("Notifications"); 115 118 116 - if (isLoading) return <LoadingState text="Loading mentions..." />; 117 - if (isError) return <ErrorState error={error} />; 119 + if (isLoading) return <LoadingState text="Loading mentions..." />; 120 + if (isError) return <ErrorState error={error} />; 118 121 119 - if (!mentionsAturis?.length) return <EmptyState text="No mentions yet." />; 122 + if (!mentionsAturis?.length) return <EmptyState text="No mentions yet." />; 120 123 121 - return ( 122 - <> 123 - {mentionsAturis.map((m) => ( 124 - <UniversalPostRendererATURILoader key={m} atUri={m} /> 125 - ))} 124 + return ( 125 + <> 126 + {mentionsAturis.map((m) => ( 127 + <UniversalPostRendererATURILoader key={m} atUri={m} /> 128 + ))} 126 129 127 - {hasNextPage && ( 128 - <button 129 - onClick={() => fetchNextPage()} 130 - disabled={isFetchingNextPage} 131 - className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 132 - > 133 - {isFetchingNextPage ? "Loading..." : "Load More"} 134 - </button> 135 - )} 136 - </> 137 - ); 130 + {hasNextPage && ( 131 + <button 132 + onClick={() => fetchNextPage()} 133 + disabled={isFetchingNextPage} 134 + className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 135 + > 136 + {isFetchingNextPage ? "Loading..." : "Load More"} 137 + </button> 138 + )} 139 + </> 140 + ); 138 141 } 139 142 140 - export function FollowsTab({did}:{did?:string}) { 141 - const { agent } = useAuth(); 142 - const userdidunsafe = did ?? agent?.did; 143 - const { data: identity} = useQueryIdentity(userdidunsafe); 144 - const userdid = identity?.did; 145 - 146 - const [constellationurl] = useAtom(constellationURLAtom); 147 - const infinitequeryresults = useInfiniteQuery({ 148 - ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 149 - { 150 - constellation: constellationurl, 151 - method: "/links", 152 - target: userdid, 153 - collection: "app.bsky.graph.follow", 154 - path: ".subject", 155 - } 156 - ), 157 - enabled: !!userdid, 158 - }); 143 + export function FollowsTab({ did }: { did?: string }) { 144 + const { agent } = useAuth(); 145 + const userdidunsafe = did ?? agent?.did; 146 + const { data: identity } = useQueryIdentity(userdidunsafe); 147 + const userdid = identity?.did; 159 148 160 - const { 161 - data: infiniteFollowsData, 162 - fetchNextPage, 163 - hasNextPage, 164 - isFetchingNextPage, 165 - isLoading, 166 - isError, 167 - error, 168 - } = infinitequeryresults; 149 + const [constellationurl] = useAtom(constellationURLAtom); 150 + const infinitequeryresults = useInfiniteQuery({ 151 + ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 152 + { 153 + constellation: constellationurl, 154 + method: "/links", 155 + target: userdid, 156 + collection: "app.bsky.graph.follow", 157 + path: ".subject", 158 + } 159 + ), 160 + enabled: !!userdid, 161 + }); 162 + 163 + const { 164 + data: infiniteFollowsData, 165 + fetchNextPage, 166 + hasNextPage, 167 + isFetchingNextPage, 168 + isLoading, 169 + isError, 170 + error, 171 + } = infinitequeryresults; 169 172 170 - const followsAturis = React.useMemo(() => { 171 - // Get all replies from the standard infinite query 172 - return ( 173 - infiniteFollowsData?.pages.flatMap( 174 - (page) => 175 - page?.linking_records.map( 176 - (r) => `at://${r.did}/${r.collection}/${r.rkey}` 177 - ) ?? [] 178 - ) ?? [] 179 - ); 180 - }, [infiniteFollowsData]); 173 + const followsAturis = React.useMemo(() => { 174 + // Get all replies from the standard infinite query 175 + return ( 176 + infiniteFollowsData?.pages.flatMap( 177 + (page) => 178 + page?.linking_records.map( 179 + (r) => `at://${r.did}/${r.collection}/${r.rkey}` 180 + ) ?? [] 181 + ) ?? [] 182 + ); 183 + }, [infiniteFollowsData]); 181 184 182 - useReusableTabScrollRestore("Notifications"); 185 + useReusableTabScrollRestore("Notifications"); 183 186 184 - if (isLoading) return <LoadingState text="Loading follows..." />; 185 - if (isError) return <ErrorState error={error} />; 187 + if (isLoading) return <LoadingState text="Loading follows..." />; 188 + if (isError) return <ErrorState error={error} />; 186 189 187 - if (!followsAturis?.length) return <EmptyState text="No follows yet." />; 190 + if (!followsAturis?.length) return <EmptyState text="No follows yet." />; 188 191 189 - return ( 190 - <> 191 - {followsAturis.map((m) => ( 192 - <NotificationItem key={m} notification={m} /> 193 - ))} 192 + return ( 193 + <> 194 + {followsAturis.map((m) => ( 195 + <NotificationItem key={m} notification={m} /> 196 + ))} 194 197 195 - {hasNextPage && ( 196 - <button 197 - onClick={() => fetchNextPage()} 198 - disabled={isFetchingNextPage} 199 - className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 200 - > 201 - {isFetchingNextPage ? "Loading..." : "Load More"} 202 - </button> 203 - )} 204 - </> 205 - ); 198 + {hasNextPage && ( 199 + <button 200 + onClick={() => fetchNextPage()} 201 + disabled={isFetchingNextPage} 202 + className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 203 + > 204 + {isFetchingNextPage ? "Loading..." : "Load More"} 205 + </button> 206 + )} 207 + </> 208 + ); 206 209 } 207 210 211 + export function BitesTab({ did }: { did?: string }) { 212 + const { agent } = useAuth(); 213 + const userdidunsafe = did ?? agent?.did; 214 + const { data: identity } = useQueryIdentity(userdidunsafe); 215 + const userdid = identity?.did; 208 216 209 - export function BitesTab({did}:{did?:string}) { 210 - const { agent } = useAuth(); 211 - const userdidunsafe = did ?? agent?.did; 212 - const { data: identity} = useQueryIdentity(userdidunsafe); 213 - const userdid = identity?.did; 214 - 215 - const [constellationurl] = useAtom(constellationURLAtom); 216 - const infinitequeryresults = useInfiniteQuery({ 217 - ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 218 - { 219 - constellation: constellationurl, 220 - method: "/links", 221 - target: "at://"+userdid, 222 - collection: "net.wafrn.feed.bite", 223 - path: ".subject", 224 - staleMult: 0 // safe fun 225 - } 226 - ), 227 - enabled: !!userdid, 228 - }); 217 + const [constellationurl] = useAtom(constellationURLAtom); 218 + const infinitequeryresults = useInfiniteQuery({ 219 + ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 220 + { 221 + constellation: constellationurl, 222 + method: "/links", 223 + target: "at://" + userdid, 224 + collection: "net.wafrn.feed.bite", 225 + path: ".subject", 226 + staleMult: 0, // safe fun 227 + } 228 + ), 229 + enabled: !!userdid, 230 + }); 229 231 230 - const { 231 - data: infiniteFollowsData, 232 - fetchNextPage, 233 - hasNextPage, 234 - isFetchingNextPage, 235 - isLoading, 236 - isError, 237 - error, 238 - } = infinitequeryresults; 232 + const { 233 + data: infiniteFollowsData, 234 + fetchNextPage, 235 + hasNextPage, 236 + isFetchingNextPage, 237 + isLoading, 238 + isError, 239 + error, 240 + } = infinitequeryresults; 239 241 240 - const followsAturis = React.useMemo(() => { 241 - // Get all replies from the standard infinite query 242 - return ( 243 - infiniteFollowsData?.pages.flatMap( 244 - (page) => 245 - page?.linking_records.map( 246 - (r) => `at://${r.did}/${r.collection}/${r.rkey}` 247 - ) ?? [] 248 - ) ?? [] 249 - ); 250 - }, [infiniteFollowsData]); 242 + const followsAturis = React.useMemo(() => { 243 + // Get all replies from the standard infinite query 244 + return ( 245 + infiniteFollowsData?.pages.flatMap( 246 + (page) => 247 + page?.linking_records.map( 248 + (r) => `at://${r.did}/${r.collection}/${r.rkey}` 249 + ) ?? [] 250 + ) ?? [] 251 + ); 252 + }, [infiniteFollowsData]); 251 253 252 - useReusableTabScrollRestore("Notifications"); 254 + useReusableTabScrollRestore("Notifications"); 253 255 254 - if (isLoading) return <LoadingState text="Loading bites..." />; 255 - if (isError) return <ErrorState error={error} />; 256 + if (isLoading) return <LoadingState text="Loading bites..." />; 257 + if (isError) return <ErrorState error={error} />; 256 258 257 - if (!followsAturis?.length) return <EmptyState text="No bites yet." />; 259 + if (!followsAturis?.length) return <EmptyState text="No bites yet." />; 258 260 259 - return ( 260 - <> 261 - {followsAturis.map((m) => ( 262 - <NotificationItem key={m} notification={m} /> 263 - ))} 261 + return ( 262 + <> 263 + {followsAturis.map((m) => ( 264 + <NotificationItem key={m} notification={m} /> 265 + ))} 264 266 265 - {hasNextPage && ( 266 - <button 267 - onClick={() => fetchNextPage()} 268 - disabled={isFetchingNextPage} 269 - className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 270 - > 271 - {isFetchingNextPage ? "Loading..." : "Load More"} 272 - </button> 273 - )} 274 - </> 275 - ); 267 + {hasNextPage && ( 268 + <button 269 + onClick={() => fetchNextPage()} 270 + disabled={isFetchingNextPage} 271 + className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 272 + > 273 + {isFetchingNextPage ? "Loading..." : "Load More"} 274 + </button> 275 + )} 276 + </> 277 + ); 276 278 } 277 279 278 280 function PostInteractionsTab() { 279 - const { agent } = useAuth(); 280 - const { data: identity } = useQueryIdentity(agent?.did); 281 - const queryClient = useQueryClient(); 282 - const { 283 - data: postsData, 284 - fetchNextPage, 285 - hasNextPage, 286 - isFetchingNextPage, 287 - isLoading: arePostsLoading, 288 - } = useInfiniteQueryAuthorFeed(agent?.did, identity?.pds); 281 + const { agent } = useAuth(); 282 + const { data: identity } = useQueryIdentity(agent?.did); 283 + const queryClient = useQueryClient(); 284 + const { 285 + data: postsData, 286 + fetchNextPage, 287 + hasNextPage, 288 + isFetchingNextPage, 289 + isLoading: arePostsLoading, 290 + } = useInfiniteQueryAuthorFeed(agent?.did, identity?.pds); 289 291 290 - React.useEffect(() => { 291 - if (postsData) { 292 - postsData.pages.forEach((page) => { 293 - page.records.forEach((record) => { 294 - if (!queryClient.getQueryData(["post", record.uri])) { 295 - queryClient.setQueryData(["post", record.uri], record); 296 - } 297 - }); 298 - }); 299 - } 300 - }, [postsData, queryClient]); 292 + React.useEffect(() => { 293 + if (postsData) { 294 + postsData.pages.forEach((page) => { 295 + page.records.forEach((record) => { 296 + if (!queryClient.getQueryData(["post", record.uri])) { 297 + queryClient.setQueryData(["post", record.uri], record); 298 + } 299 + }); 300 + }); 301 + } 302 + }, [postsData, queryClient]); 301 303 302 - const posts = React.useMemo( 303 - () => postsData?.pages.flatMap((page) => page.records) ?? [], 304 - [postsData] 305 - ); 304 + const posts = React.useMemo( 305 + () => postsData?.pages.flatMap((page) => page.records) ?? [], 306 + [postsData] 307 + ); 306 308 307 - useReusableTabScrollRestore("Notifications"); 309 + useReusableTabScrollRestore("Notifications"); 308 310 309 - const [filters] = useAtom(postInteractionsFiltersAtom); 310 - const empty = (!filters.likes && !filters.quotes && !filters.replies && !filters.reposts); 311 + const [filters] = useAtom(postInteractionsFiltersAtom); 312 + const empty = 313 + !filters.likes && !filters.quotes && !filters.replies && !filters.reposts; 311 314 312 - return ( 313 - <> 314 - <PostInteractionsFilterChipBar /> 315 - {!empty && posts.map((m) => ( 316 - <PostInteractionsItem key={m.uri} uri={m.uri} /> 317 - ))} 315 + return ( 316 + <> 317 + <PostInteractionsFilterChipBar /> 318 + {!empty && 319 + posts.map((m) => <PostInteractionsItem key={m.uri} uri={m.uri} />)} 318 320 319 - {hasNextPage && ( 320 - <button 321 - onClick={() => fetchNextPage()} 322 - disabled={isFetchingNextPage} 323 - className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 324 - > 325 - {isFetchingNextPage ? "Loading..." : "Load More"} 326 - </button> 327 - )} 328 - </> 329 - ); 321 + {hasNextPage && ( 322 + <button 323 + onClick={() => fetchNextPage()} 324 + disabled={isFetchingNextPage} 325 + className="w-[calc(100%-2rem)] mx-4 my-4 px-4 py-2 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-semibold disabled:opacity-50" 326 + > 327 + {isFetchingNextPage ? "Loading..." : "Load More"} 328 + </button> 329 + )} 330 + </> 331 + ); 330 332 } 331 333 332 334 function PostInteractionsFilterChipBar() { 333 - const [filters, setFilters] = useAtom(postInteractionsFiltersAtom); 334 - // const empty = (!filters.likes && !filters.quotes && !filters.replies && !filters.reposts); 335 + const [filters, setFilters] = useAtom(postInteractionsFiltersAtom); 336 + // const empty = (!filters.likes && !filters.quotes && !filters.replies && !filters.reposts); 335 337 336 - // useEffect(() => { 337 - // if (empty) { 338 - // setFilters((prev) => ({ 339 - // ...prev, 340 - // likes: true, 341 - // })); 342 - // } 343 - // }, [ 344 - // empty, 345 - // setFilters, 346 - // ]); 338 + // useEffect(() => { 339 + // if (empty) { 340 + // setFilters((prev) => ({ 341 + // ...prev, 342 + // likes: true, 343 + // })); 344 + // } 345 + // }, [ 346 + // empty, 347 + // setFilters, 348 + // ]); 347 349 348 - const toggle = (key: keyof typeof filters) => { 349 - setFilters((prev) => ({ 350 - ...prev, 351 - [key]: !prev[key], 352 - })); 353 - }; 350 + const toggle = (key: keyof typeof filters) => { 351 + setFilters((prev) => ({ 352 + ...prev, 353 + [key]: !prev[key], 354 + })); 355 + }; 354 356 355 - return ( 356 - <div className="flex flex-row flex-wrap gap-2 px-4 pt-4"> 357 - <Chip 358 - state={filters.likes} 359 - text="Likes" 360 - onClick={() => toggle("likes")} 361 - /> 362 - <Chip 363 - state={filters.reposts} 364 - text="Reposts" 365 - onClick={() => toggle("reposts")} 366 - /> 367 - <Chip 368 - state={filters.replies} 369 - text="Replies" 370 - onClick={() => toggle("replies")} 371 - /> 372 - <Chip 373 - state={filters.quotes} 374 - text="Quotes" 375 - onClick={() => toggle("quotes")} 376 - /> 377 - <Chip 378 - state={filters.showAll} 379 - text="Show All Metrics" 380 - onClick={() => toggle("showAll")} 381 - /> 382 - </div> 383 - ); 357 + return ( 358 + <div className="flex flex-row flex-wrap gap-2 px-4 pt-4"> 359 + <Chip 360 + state={filters.likes} 361 + text="Likes" 362 + onClick={() => toggle("likes")} 363 + /> 364 + <Chip 365 + state={filters.reposts} 366 + text="Reposts" 367 + onClick={() => toggle("reposts")} 368 + /> 369 + <Chip 370 + state={filters.replies} 371 + text="Replies" 372 + onClick={() => toggle("replies")} 373 + /> 374 + <Chip 375 + state={filters.quotes} 376 + text="Quotes" 377 + onClick={() => toggle("quotes")} 378 + /> 379 + <Chip 380 + state={filters.showAll} 381 + text="Show All Metrics" 382 + onClick={() => toggle("showAll")} 383 + /> 384 + </div> 385 + ); 384 386 } 385 387 386 388 export function Chip({ 387 - state, 388 - text, 389 - onClick, 389 + state, 390 + text, 391 + onClick, 390 392 }: { 391 - state: boolean; 392 - text: string; 393 - onClick: React.MouseEventHandler<HTMLButtonElement>; 393 + state: boolean; 394 + text: string; 395 + onClick: React.MouseEventHandler<HTMLButtonElement>; 394 396 }) { 395 - return ( 396 - <button 397 - onClick={onClick} 398 - className={`relative inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium transition-all 397 + return ( 398 + <button 399 + onClick={onClick} 400 + className={`relative inline-flex items-center px-3 py-1.5 rounded-lg text-sm font-medium transition-all 399 401 ${ 400 - state 401 - ? "bg-primary/20 text-primary bg-gray-200 dark:bg-gray-800 border border-transparent" 402 - : "bg-surface-container-low text-on-surface-variant border border-outline" 403 - } 402 + state 403 + ? "bg-primary/20 text-primary bg-gray-200 dark:bg-gray-800 border border-transparent" 404 + : "bg-surface-container-low text-on-surface-variant border border-outline" 405 + } 404 406 hover:bg-primary/30 active:scale-[0.97] 405 407 dark:border-outline-variant 406 408 `} 407 - > 408 - {state && ( 409 - <IconMdiCheck 410 - className="mr-1.5 inline-block w-4 h-4 rounded-full bg-primary" 411 - aria-hidden 412 - /> 413 - )} 414 - {text} 415 - </button> 416 - ); 409 + > 410 + {state && ( 411 + <IconMdiCheck 412 + className="mr-1.5 inline-block w-4 h-4 rounded-full bg-primary" 413 + aria-hidden 414 + /> 415 + )} 416 + {text} 417 + </button> 418 + ); 417 419 } 418 420 419 421 function PostInteractionsItem({ uri }: { uri: string }) { 420 - const [filters] = useAtom(postInteractionsFiltersAtom); 421 - const { data: links } = useQueryConstellation({ 422 - method: "/links/all", 423 - target: uri, 424 - }); 422 + const [filters] = useAtom(postInteractionsFiltersAtom); 423 + const { data: links } = useQueryConstellation({ 424 + method: "/links/all", 425 + target: uri, 426 + }); 425 427 426 - const likes = 427 - links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0; 428 - const replies = 429 - links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"]?.records || 0; 430 - const reposts = 431 - links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0; 432 - const quotes1 = 433 - links?.links?.["app.bsky.feed.post"]?.[".embed.record.uri"]?.records || 0; 434 - const quotes2 = 435 - links?.links?.["app.bsky.feed.post"]?.[".embed.record.record.uri"] 436 - ?.records || 0; 437 - const quotes = quotes1 + quotes2; 428 + const likes = 429 + links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0; 430 + const replies = 431 + links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"]?.records || 0; 432 + const reposts = 433 + links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0; 434 + const quotes1 = 435 + links?.links?.["app.bsky.feed.post"]?.[".embed.record.uri"]?.records || 0; 436 + const quotes2 = 437 + links?.links?.["app.bsky.feed.post"]?.[".embed.record.record.uri"] 438 + ?.records || 0; 439 + const quotes = quotes1 + quotes2; 438 440 439 - const all = likes + replies + reposts + quotes; 441 + const all = likes + replies + reposts + quotes; 440 442 441 - //const failLikes = filters.likes && likes < 1; 442 - //const failReposts = filters.reposts && reposts < 1; 443 - //const failReplies = filters.replies && replies < 1; 444 - //const failQuotes = filters.quotes && quotes < 1; 443 + //const failLikes = filters.likes && likes < 1; 444 + //const failReposts = filters.reposts && reposts < 1; 445 + //const failReplies = filters.replies && replies < 1; 446 + //const failQuotes = filters.quotes && quotes < 1; 445 447 446 - const showLikes = filters.showAll || filters.likes 447 - const showReposts = filters.showAll || filters.reposts 448 - const showReplies = filters.showAll || filters.replies 449 - const showQuotes = filters.showAll || filters.quotes 448 + const showLikes = filters.showAll || filters.likes; 449 + const showReposts = filters.showAll || filters.reposts; 450 + const showReplies = filters.showAll || filters.replies; 451 + const showQuotes = filters.showAll || filters.quotes; 450 452 451 - //const showNone = !showLikes && !showReposts && !showReplies && !showQuotes; 453 + //const showNone = !showLikes && !showReposts && !showReplies && !showQuotes; 452 454 453 - //const fail = failLikes || failReposts || failReplies || failQuotes || showNone; 455 + //const fail = failLikes || failReposts || failReplies || failQuotes || showNone; 454 456 455 - const matchesLikes = filters.likes && likes > 0; 456 - const matchesReposts = filters.reposts && reposts > 0; 457 - const matchesReplies = filters.replies && replies > 0; 458 - const matchesQuotes = filters.quotes && quotes > 0; 457 + const matchesLikes = filters.likes && likes > 0; 458 + const matchesReposts = filters.reposts && reposts > 0; 459 + const matchesReplies = filters.replies && replies > 0; 460 + const matchesQuotes = filters.quotes && quotes > 0; 459 461 460 - const matchesAnything = 461 - // filters.showAll || 462 - matchesLikes || 463 - matchesReposts || 464 - matchesReplies || 465 - matchesQuotes; 462 + const matchesAnything = 463 + // filters.showAll || 464 + matchesLikes || matchesReposts || matchesReplies || matchesQuotes; 466 465 467 - if (!matchesAnything) return null; 466 + if (!matchesAnything) return null; 468 467 469 - //if (fail) return; 468 + //if (fail) return; 470 469 471 - return ( 472 - <div className="flex flex-col"> 473 - {/* <span>fail likes {failLikes ? "true" : "false"}</span> 470 + return ( 471 + <div className="flex flex-col"> 472 + {/* <span>fail likes {failLikes ? "true" : "false"}</span> 474 473 <span>fail repost {failReposts ? "true" : "false"}</span> 475 474 <span>fail reply {failReplies ? "true" : "false"}</span> 476 475 <span>fail qupte {failQuotes ? "true" : "false"}</span> */} 477 - <div className="border rounded-xl mx-4 mt-4 overflow-hidden"> 478 - <UniversalPostRendererATURILoader 479 - isQuote 480 - key={uri} 481 - atUri={uri} 482 - nopics={true} 483 - concise={true} 484 - /> 485 - <div className="flex flex-col divide-x"> 486 - {showLikes &&(<InteractionsButton 487 - type={"like"} 488 - uri={uri} 489 - count={likes} 490 - />)} 491 - {showReposts && (<InteractionsButton 492 - type={"repost"} 493 - uri={uri} 494 - count={reposts} 495 - />)} 496 - {showReplies && (<InteractionsButton 497 - type={"reply"} 498 - uri={uri} 499 - count={replies} 500 - />)} 501 - {showQuotes && (<InteractionsButton 502 - type={"quote"} 503 - uri={uri} 504 - count={quotes} 505 - />)} 506 - {!all && ( 507 - <div className="text-center text-gray-500 dark:text-gray-400 pb-3 pt-2 border-t"> 508 - No interactions yet. 509 - </div> 510 - )} 511 - </div> 512 - </div> 513 - </div> 514 - ); 476 + <div className="border rounded-xl mx-4 mt-4 overflow-hidden"> 477 + <UniversalPostRendererATURILoader 478 + isQuote 479 + key={uri} 480 + atUri={uri} 481 + nopics={true} 482 + concise={true} 483 + /> 484 + <div className="flex flex-col divide-x"> 485 + {showLikes && ( 486 + <InteractionsButton type={"like"} uri={uri} count={likes} /> 487 + )} 488 + {showReposts && ( 489 + <InteractionsButton type={"repost"} uri={uri} count={reposts} /> 490 + )} 491 + {showReplies && ( 492 + <InteractionsButton type={"reply"} uri={uri} count={replies} /> 493 + )} 494 + {showQuotes && ( 495 + <InteractionsButton type={"quote"} uri={uri} count={quotes} /> 496 + )} 497 + {!all && ( 498 + <div className="text-center text-gray-500 dark:text-gray-400 pb-3 pt-2 border-t"> 499 + No interactions yet. 500 + </div> 501 + )} 502 + </div> 503 + </div> 504 + </div> 505 + ); 515 506 } 516 507 517 508 function InteractionsButton({ 518 - type, 519 - uri, 520 - count, 509 + type, 510 + uri, 511 + count, 521 512 }: { 522 - type: "reply" | "repost" | "like" | "quote"; 523 - uri: string; 524 - count: number; 513 + type: "reply" | "repost" | "like" | "quote"; 514 + uri: string; 515 + count: number; 525 516 }) { 526 - if (!count) return <></>; 527 - const aturi = new AtUri(uri); 528 - return ( 529 - <Link 530 - to={ 531 - `/profile/$did/post/$rkey` + 532 - (type === "like" 533 - ? "/liked-by" 534 - : type === "repost" 535 - ? "/reposted-by" 536 - : type === "quote" 537 - ? "/quotes" 538 - : "") 539 - } 540 - params={{ 541 - did: aturi.host, 542 - rkey: aturi.rkey, 543 - }} 544 - className="flex-1 border-t py-2 px-4 flex flex-row items-center gap-2 transition-colors hover:bg-gray-100 hover:dark:bg-gray-800" 545 - > 546 - {type === "like" ? ( 547 - <MdiCardsHeartOutline height={22} width={22} /> 548 - ) : type === "repost" ? ( 549 - <MdiRepeat height={22} width={22} /> 550 - ) : type === "reply" ? ( 551 - <MdiCommentOutline height={22} width={22} /> 552 - ) : type === "quote" ? ( 553 - <IconMdiMessageReplyTextOutline 554 - height={22} 555 - width={22} 556 - className=" text-gray-400" 557 - /> 558 - ) : ( 559 - <></> 560 - )} 561 - {type === "like" 562 - ? "likes" 563 - : type === "reply" 564 - ? "replies" 565 - : type === "quote" 566 - ? "quotes" 567 - : type === "repost" 568 - ? "reposts" 569 - : ""} 570 - <div className="flex-1" /> {count} 571 - </Link> 572 - ); 517 + if (!count) return <></>; 518 + const aturi = new AtUri(uri); 519 + return ( 520 + <Link 521 + to={ 522 + `/profile/$did/post/$rkey` + 523 + (type === "like" 524 + ? "/liked-by" 525 + : type === "repost" 526 + ? "/reposted-by" 527 + : type === "quote" 528 + ? "/quotes" 529 + : "") 530 + } 531 + params={{ 532 + did: aturi.host, 533 + rkey: aturi.rkey, 534 + }} 535 + className="flex-1 border-t py-2 px-4 flex flex-row items-center gap-2 transition-colors hover:bg-gray-100 hover:dark:bg-gray-800" 536 + > 537 + {type === "like" ? ( 538 + <MdiCardsHeartOutline height={22} width={22} /> 539 + ) : type === "repost" ? ( 540 + <MdiRepeat height={22} width={22} /> 541 + ) : type === "reply" ? ( 542 + <MdiCommentOutline height={22} width={22} /> 543 + ) : type === "quote" ? ( 544 + <IconMdiMessageReplyTextOutline 545 + height={22} 546 + width={22} 547 + className=" text-gray-400" 548 + /> 549 + ) : ( 550 + <></> 551 + )} 552 + {type === "like" 553 + ? "likes" 554 + : type === "reply" 555 + ? "replies" 556 + : type === "quote" 557 + ? "quotes" 558 + : type === "repost" 559 + ? "reposts" 560 + : ""} 561 + <div className="flex-1" /> {count} 562 + </Link> 563 + ); 573 564 } 574 565 575 566 export function NotificationItem({ notification }: { notification: string }) { 576 - const aturi = new AtUri(notification); 577 - const bite = aturi.collection === "net.wafrn.feed.bite"; 578 - const navigate = useNavigate(); 579 - const { data: identity } = useQueryIdentity(aturi.host); 580 - const resolvedDid = identity?.did; 581 - const profileUri = resolvedDid 582 - ? `at://${resolvedDid}/app.bsky.actor.profile/self` 583 - : undefined; 584 - const { data: profileRecord } = useQueryProfile(profileUri); 585 - const profile = profileRecord?.value; 567 + const aturi = new AtUri(notification); 568 + const bite = aturi.collection === "net.wafrn.feed.bite"; 569 + const navigate = useNavigate(); 570 + const { data: identity } = useQueryIdentity(aturi.host); 571 + const resolvedDid = identity?.did; 572 + const profileUri = resolvedDid 573 + ? `at://${resolvedDid}/app.bsky.actor.profile/self` 574 + : undefined; 575 + const { data: profileRecord } = useQueryProfile(profileUri); 576 + const profile = profileRecord?.value; 586 577 587 - const [imgcdn] = useAtom(imgCDNAtom); 578 + const [imgcdn] = useAtom(imgCDNAtom); 588 579 589 - function getAvatarUrl(p: typeof profile) { 590 - const link = p?.avatar?.ref?.["$link"]; 591 - if (!link || !resolvedDid) return null; 592 - return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`; 593 - } 580 + function getAvatarUrl(p: typeof profile) { 581 + const link = p?.avatar?.ref?.["$link"]; 582 + if (!link || !resolvedDid) return null; 583 + return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`; 584 + } 594 585 595 - const avatar = getAvatarUrl(profile); 586 + const avatar = getAvatarUrl(profile); 596 587 597 - return ( 598 - <div 599 - className="flex items-center p-4 cursor-pointer gap-3 justify-around border-b flex-row" 600 - onClick={() => 601 - aturi && 602 - navigate({ 603 - to: "/profile/$did", 604 - params: { did: aturi.host }, 605 - }) 606 - } 607 - > 608 - {/* <div> 588 + return ( 589 + <div 590 + className="flex items-center p-4 cursor-pointer gap-3 justify-around border-b flex-row" 591 + onClick={() => 592 + aturi && 593 + navigate({ 594 + to: "/profile/$did", 595 + params: { did: aturi.host }, 596 + }) 597 + } 598 + > 599 + {/* <div> 609 600 {aturi.collection === "app.bsky.graph.follow" ? ( 610 601 <IconMdiAccountPlus /> 611 602 ) : aturi.collection === "app.bsky.feed.like" ? ( ··· 614 605 <></> 615 606 )} 616 607 </div> */} 617 - {profile ? ( 618 - <img 619 - src={avatar || defaultpfp} 620 - alt={identity?.handle} 621 - className="w-10 h-10 rounded-full" 622 - /> 623 - ) : ( 624 - <div className="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-700" /> 625 - )} 626 - <div className="flex flex-col min-w-0"> 627 - <div className="flex flex-row gap-2 overflow-hidden text-ellipsis whitespace-nowrap min-w-0"> 628 - <span className="font-medium text-gray-900 dark:text-gray-100 truncate"> 629 - {profile?.displayName || identity?.handle || "Someone"} 630 - </span> 631 - <span className="text-gray-700 dark:text-gray-400 truncate"> 632 - @{identity?.handle} 633 - </span> 634 - </div> 635 - <div className="flex flex-row gap-2"> 636 - {identity?.did && <Mutual targetdidorhandle={identity?.did} />} 637 - {/* <span className="text-sm text-gray-600 dark:text-gray-400"> 608 + {profile ? ( 609 + <img 610 + src={avatar || defaultpfp} 611 + alt={identity?.handle} 612 + className="w-10 h-10 rounded-full" 613 + /> 614 + ) : ( 615 + <div className="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-700" /> 616 + )} 617 + <div className="flex flex-col min-w-0 flex-grow"> 618 + <div className="flex flex-row gap-2 items-center overflow-hidden text-ellipsis whitespace-nowrap min-w-0"> 619 + <ConditionalTextPlaceholder 620 + show={profile != undefined} 621 + placeholderClassName="flex-grow" 622 + > 623 + <span className="font-medium text-gray-900 dark:text-gray-100 truncate"> 624 + {profile?.displayName || identity?.handle} 625 + </span> 626 + </ConditionalTextPlaceholder> 627 + <span className="text-gray-700 dark:text-gray-400 truncate"> 628 + @{identity?.handle} 629 + </span> 630 + </div> 631 + <div className="flex flex-row gap-2"> 632 + {identity?.did && <Mutual targetdidorhandle={identity?.did} />} 633 + {/* <span className="text-sm text-gray-600 dark:text-gray-400"> 638 634 followed you 639 635 </span> */} 640 - </div> 641 - </div> 642 - <div className="flex-1" /> 643 - {identity?.did && <FollowButton targetdidorhandle={identity?.did} />} 644 - </div> 645 - ); 636 + </div> 637 + </div> 638 + <div className="flex-1" /> 639 + {identity?.did && <FollowButton targetdidorhandle={identity?.did} />} 640 + </div> 641 + ); 646 642 } 647 643 648 644 export const EmptyState = ({ text }: { text: string }) => ( 649 - <div className="py-10 text-center text-gray-500 dark:text-gray-400"> 650 - {text} 651 - </div> 645 + <div className="py-10 text-center text-gray-500 dark:text-gray-400"> 646 + {text} 647 + </div> 652 648 ); 653 649 654 650 export const LoadingState = ({ text }: { text: string }) => ( 655 - <div className="py-10 text-center text-gray-500 dark:text-gray-400 italic"> 656 - {text} 657 - </div> 651 + <div className="py-10 text-center text-gray-500 dark:text-gray-400 italic"> 652 + {text} 653 + </div> 658 654 ); 659 655 660 656 export const ErrorState = ({ error }: { error: unknown }) => ( 661 - <div className="py-10 text-center text-red-600 dark:text-red-400"> 662 - Error: {(error as Error)?.message || "Something went wrong."} 663 - </div> 657 + <div className="py-10 text-center text-red-600 dark:text-red-400"> 658 + Error: {(error as Error)?.message || "Something went wrong."} 659 + </div> 664 660 );