an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

Compare changes

Choose any two refs to compare.

+22 -4
README.md
··· 1 1 # Red Dwarf 2 2 Red Dwarf is a Bluesky client that does not use any AppView servers, instead it gathers the data from [Constellation](https://constellation.microcosm.blue/) and each users' PDS. 3 3 4 - ![screenshot of red dwarf](/public/screenshot.png) 4 + ![screenshot of red dwarf](/public/screenshot.jpg) 5 5 6 6 huge thanks to [Microcosm](https://microcosm.blue/) for making this possible 7 7 8 8 ## running dev and build 9 9 in the `vite.config.ts` file you should change these values 10 10 ```ts 11 - const PROD_URL = "https://reddwarf.whey.party" 11 + const PROD_URL = "https://reddwarf.app" 12 12 const DEV_URL = "https://local3768forumtest.whey.party" 13 13 ``` 14 14 the PROD_URL is what will compile your oauth client metadata so it is very important to change that. same for DEV_URL if you are using a tunnel for dev work ··· 52 52 and for list feeds, you can just use something like graze or skyfeed to input a list of users and output a custom feed 53 53 54 54 ## Tanstack Router 55 - it does the job, nothing very specific was used here 55 + something specific was used here 56 + 57 + so tanstack router is used as the base, but the home route is using tanstack-router-keepalive to preserve the route for better responsiveness, and it also saves scroll position of feeds into jotai (persistent) 58 + 59 + i previously used a tanstack router loader to ensure the tanstack query cache is ready to prevent scroll jumps but it is way too slow so i replaced it with tanstack-router-keepalive 60 + 61 + ## Icons 62 + this project uses Material icons. do not the light variant. sometimes i use `Mdi` if the icon needed doesnt exist in `MaterialSymbols` 56 63 57 - im planning to use the loader system on select pages to prevent loss of scroll positon and state though its really complex so i havent done it yet but the migration to tanstack query is a huge first step towards this goal 64 + the project uses unplugin icon auto import, so you can just use the component and itll just work! 65 + 66 + the format is: 67 + ```tsx 68 + <IconMaterialSymbols{icon name here} /> 69 + // or 70 + <IconMdi{icon name here} /> 71 + ``` 72 + 73 + you can get the full list of icon names from iconify ([Material Symbols](https://icon-sets.iconify.design/material-symbols/) or [MDI](https://icon-sets.iconify.design/mdi/)) 74 + 75 + while it is nice to keep everything consistent by using material icons, if the icon you need is not provided by either material symbols nor mdi, you are allowed to just grab any icon from any pack (please do prioritize icons that fit in)
+4980 -25
package-lock.json
··· 8 8 "dependencies": { 9 9 "@atproto/api": "^0.16.6", 10 10 "@atproto/oauth-client-browser": "^0.3.33", 11 + "@radix-ui/react-dialog": "^1.1.15", 12 + "@radix-ui/react-dropdown-menu": "^2.1.16", 13 + "@radix-ui/react-hover-card": "^1.1.15", 14 + "@radix-ui/react-slider": "^1.3.6", 11 15 "@tailwindcss/vite": "^4.0.6", 12 16 "@tanstack/query-sync-storage-persister": "^5.85.6", 13 17 "@tanstack/react-devtools": "^0.2.2", ··· 16 20 "@tanstack/react-router": "^1.130.2", 17 21 "@tanstack/react-router-devtools": "^1.131.5", 18 22 "@tanstack/router-plugin": "^1.121.2", 23 + "dompurify": "^3.3.0", 24 + "i": "^0.3.7", 19 25 "idb-keyval": "^6.2.2", 20 26 "jotai": "^2.13.1", 27 + "npm": "^11.6.2", 28 + "radix-ui": "^1.4.3", 21 29 "react": "^19.0.0", 22 30 "react-dom": "^19.0.0", 23 31 "react-player": "^3.3.2", 24 - "tailwindcss": "^4.0.6" 32 + "tailwindcss": "^4.0.6", 33 + "tanstack-router-keepalive": "^1.0.0" 25 34 }, 26 35 "devDependencies": { 27 36 "@eslint-react/eslint-plugin": "^2.2.1", 37 + "@iconify-icon/react": "^3.0.1", 38 + "@iconify-json/material-symbols": "^1.2.42", 39 + "@iconify-json/mdi": "^1.2.3", 40 + "@iconify/json": "^2.2.396", 41 + "@svgr/core": "^8.1.0", 42 + "@svgr/plugin-jsx": "^8.1.0", 28 43 "@testing-library/dom": "^10.4.0", 29 44 "@testing-library/react": "^16.2.0", 30 45 "@types/node": "^24.3.0", ··· 42 57 "prettier": "^3.6.2", 43 58 "typescript": "^5.7.2", 44 59 "typescript-eslint": "^8.46.1", 60 + "unplugin-auto-import": "^20.2.0", 61 + "unplugin-icons": "^22.4.2", 45 62 "vite": "^6.3.5", 46 63 "vitest": "^3.0.5", 47 64 "web-vitals": "^4.2.4" ··· 58 75 }, 59 76 "engines": { 60 77 "node": ">=6.0.0" 78 + } 79 + }, 80 + "node_modules/@antfu/install-pkg": { 81 + "version": "1.1.0", 82 + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", 83 + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", 84 + "dev": true, 85 + "license": "MIT", 86 + "dependencies": { 87 + "package-manager-detector": "^1.3.0", 88 + "tinyexec": "^1.0.1" 89 + }, 90 + "funding": { 91 + "url": "https://github.com/sponsors/antfu" 92 + } 93 + }, 94 + "node_modules/@antfu/install-pkg/node_modules/tinyexec": { 95 + "version": "1.0.1", 96 + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", 97 + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", 98 + "dev": true, 99 + "license": "MIT" 100 + }, 101 + "node_modules/@antfu/utils": { 102 + "version": "9.3.0", 103 + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", 104 + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", 105 + "dev": true, 106 + "license": "MIT", 107 + "funding": { 108 + "url": "https://github.com/sponsors/antfu" 61 109 } 62 110 }, 63 111 "node_modules/@asamuzakjp/css-color": { ··· 1549 1597 "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1550 1598 } 1551 1599 }, 1600 + "node_modules/@floating-ui/core": { 1601 + "version": "1.7.3", 1602 + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", 1603 + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", 1604 + "dependencies": { 1605 + "@floating-ui/utils": "^0.2.10" 1606 + } 1607 + }, 1608 + "node_modules/@floating-ui/dom": { 1609 + "version": "1.7.4", 1610 + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", 1611 + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", 1612 + "dependencies": { 1613 + "@floating-ui/core": "^1.7.3", 1614 + "@floating-ui/utils": "^0.2.10" 1615 + } 1616 + }, 1617 + "node_modules/@floating-ui/react-dom": { 1618 + "version": "2.1.6", 1619 + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", 1620 + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", 1621 + "dependencies": { 1622 + "@floating-ui/dom": "^1.7.4" 1623 + }, 1624 + "peerDependencies": { 1625 + "react": ">=16.8.0", 1626 + "react-dom": ">=16.8.0" 1627 + } 1628 + }, 1629 + "node_modules/@floating-ui/utils": { 1630 + "version": "0.2.10", 1631 + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", 1632 + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" 1633 + }, 1552 1634 "node_modules/@humanfs/core": { 1553 1635 "version": "0.19.1", 1554 1636 "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", ··· 1605 1687 "url": "https://github.com/sponsors/nzakas" 1606 1688 } 1607 1689 }, 1690 + "node_modules/@iconify-icon/react": { 1691 + "version": "3.0.1", 1692 + "resolved": "https://registry.npmjs.org/@iconify-icon/react/-/react-3.0.1.tgz", 1693 + "integrity": "sha512-/4CAVpk8HDyKS78r1G0rZhML7hI6jLxb8kAmjEXsCtuVUDwdGqicGCRg0T14mqeHNImrQPR49MhbuSSS++JlUA==", 1694 + "dev": true, 1695 + "license": "MIT", 1696 + "dependencies": { 1697 + "iconify-icon": "^3.0.1" 1698 + }, 1699 + "funding": { 1700 + "url": "https://github.com/sponsors/cyberalien" 1701 + }, 1702 + "peerDependencies": { 1703 + "react": ">=16" 1704 + } 1705 + }, 1706 + "node_modules/@iconify-json/material-symbols": { 1707 + "version": "1.2.42", 1708 + "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.42.tgz", 1709 + "integrity": "sha512-FDRfnQqy8iXaq/swVPFWaHftqP9tk3qDCRhC30s3UZL2j4mvGZk5gVECRXCkZv5jnsAiTpZxGQM8HrMiwE7GtA==", 1710 + "dev": true, 1711 + "license": "Apache-2.0", 1712 + "dependencies": { 1713 + "@iconify/types": "*" 1714 + } 1715 + }, 1716 + "node_modules/@iconify-json/mdi": { 1717 + "version": "1.2.3", 1718 + "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.3.tgz", 1719 + "integrity": "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==", 1720 + "dev": true, 1721 + "license": "Apache-2.0", 1722 + "dependencies": { 1723 + "@iconify/types": "*" 1724 + } 1725 + }, 1726 + "node_modules/@iconify/json": { 1727 + "version": "2.2.396", 1728 + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.396.tgz", 1729 + "integrity": "sha512-tijg77JFuYIt32S9N8p7La8C0zp9zKZsX6UP8ip5GVB1F6Mp3pZA5Vc5eAquTY50NoDJX58U6z4Qn3d6Wyossg==", 1730 + "dev": true, 1731 + "license": "MIT", 1732 + "dependencies": { 1733 + "@iconify/types": "*", 1734 + "pathe": "^2.0.0" 1735 + } 1736 + }, 1737 + "node_modules/@iconify/types": { 1738 + "version": "2.0.0", 1739 + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", 1740 + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", 1741 + "dev": true, 1742 + "license": "MIT" 1743 + }, 1744 + "node_modules/@iconify/utils": { 1745 + "version": "3.0.2", 1746 + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", 1747 + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", 1748 + "dev": true, 1749 + "license": "MIT", 1750 + "dependencies": { 1751 + "@antfu/install-pkg": "^1.1.0", 1752 + "@antfu/utils": "^9.2.0", 1753 + "@iconify/types": "^2.0.0", 1754 + "debug": "^4.4.1", 1755 + "globals": "^15.15.0", 1756 + "kolorist": "^1.8.0", 1757 + "local-pkg": "^1.1.1", 1758 + "mlly": "^1.7.4" 1759 + } 1760 + }, 1761 + "node_modules/@iconify/utils/node_modules/globals": { 1762 + "version": "15.15.0", 1763 + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", 1764 + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", 1765 + "dev": true, 1766 + "license": "MIT", 1767 + "engines": { 1768 + "node": ">=18" 1769 + }, 1770 + "funding": { 1771 + "url": "https://github.com/sponsors/sindresorhus" 1772 + } 1773 + }, 1608 1774 "node_modules/@isaacs/fs-minipass": { 1609 1775 "version": "4.0.1", 1610 1776 "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", ··· 1768 1934 "node": ">= 8" 1769 1935 } 1770 1936 }, 1937 + "node_modules/@radix-ui/number": { 1938 + "version": "1.1.1", 1939 + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", 1940 + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==" 1941 + }, 1942 + "node_modules/@radix-ui/primitive": { 1943 + "version": "1.1.3", 1944 + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", 1945 + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==" 1946 + }, 1947 + "node_modules/@radix-ui/react-accessible-icon": { 1948 + "version": "1.1.7", 1949 + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", 1950 + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", 1951 + "dependencies": { 1952 + "@radix-ui/react-visually-hidden": "1.2.3" 1953 + }, 1954 + "peerDependencies": { 1955 + "@types/react": "*", 1956 + "@types/react-dom": "*", 1957 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 1958 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 1959 + }, 1960 + "peerDependenciesMeta": { 1961 + "@types/react": { 1962 + "optional": true 1963 + }, 1964 + "@types/react-dom": { 1965 + "optional": true 1966 + } 1967 + } 1968 + }, 1969 + "node_modules/@radix-ui/react-accordion": { 1970 + "version": "1.2.12", 1971 + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", 1972 + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", 1973 + "dependencies": { 1974 + "@radix-ui/primitive": "1.1.3", 1975 + "@radix-ui/react-collapsible": "1.1.12", 1976 + "@radix-ui/react-collection": "1.1.7", 1977 + "@radix-ui/react-compose-refs": "1.1.2", 1978 + "@radix-ui/react-context": "1.1.2", 1979 + "@radix-ui/react-direction": "1.1.1", 1980 + "@radix-ui/react-id": "1.1.1", 1981 + "@radix-ui/react-primitive": "2.1.3", 1982 + "@radix-ui/react-use-controllable-state": "1.2.2" 1983 + }, 1984 + "peerDependencies": { 1985 + "@types/react": "*", 1986 + "@types/react-dom": "*", 1987 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 1988 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 1989 + }, 1990 + "peerDependenciesMeta": { 1991 + "@types/react": { 1992 + "optional": true 1993 + }, 1994 + "@types/react-dom": { 1995 + "optional": true 1996 + } 1997 + } 1998 + }, 1999 + "node_modules/@radix-ui/react-alert-dialog": { 2000 + "version": "1.1.15", 2001 + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", 2002 + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", 2003 + "dependencies": { 2004 + "@radix-ui/primitive": "1.1.3", 2005 + "@radix-ui/react-compose-refs": "1.1.2", 2006 + "@radix-ui/react-context": "1.1.2", 2007 + "@radix-ui/react-dialog": "1.1.15", 2008 + "@radix-ui/react-primitive": "2.1.3", 2009 + "@radix-ui/react-slot": "1.2.3" 2010 + }, 2011 + "peerDependencies": { 2012 + "@types/react": "*", 2013 + "@types/react-dom": "*", 2014 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2015 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2016 + }, 2017 + "peerDependenciesMeta": { 2018 + "@types/react": { 2019 + "optional": true 2020 + }, 2021 + "@types/react-dom": { 2022 + "optional": true 2023 + } 2024 + } 2025 + }, 2026 + "node_modules/@radix-ui/react-arrow": { 2027 + "version": "1.1.7", 2028 + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", 2029 + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", 2030 + "dependencies": { 2031 + "@radix-ui/react-primitive": "2.1.3" 2032 + }, 2033 + "peerDependencies": { 2034 + "@types/react": "*", 2035 + "@types/react-dom": "*", 2036 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2037 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2038 + }, 2039 + "peerDependenciesMeta": { 2040 + "@types/react": { 2041 + "optional": true 2042 + }, 2043 + "@types/react-dom": { 2044 + "optional": true 2045 + } 2046 + } 2047 + }, 2048 + "node_modules/@radix-ui/react-aspect-ratio": { 2049 + "version": "1.1.7", 2050 + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", 2051 + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", 2052 + "dependencies": { 2053 + "@radix-ui/react-primitive": "2.1.3" 2054 + }, 2055 + "peerDependencies": { 2056 + "@types/react": "*", 2057 + "@types/react-dom": "*", 2058 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2059 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2060 + }, 2061 + "peerDependenciesMeta": { 2062 + "@types/react": { 2063 + "optional": true 2064 + }, 2065 + "@types/react-dom": { 2066 + "optional": true 2067 + } 2068 + } 2069 + }, 2070 + "node_modules/@radix-ui/react-avatar": { 2071 + "version": "1.1.10", 2072 + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", 2073 + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", 2074 + "dependencies": { 2075 + "@radix-ui/react-context": "1.1.2", 2076 + "@radix-ui/react-primitive": "2.1.3", 2077 + "@radix-ui/react-use-callback-ref": "1.1.1", 2078 + "@radix-ui/react-use-is-hydrated": "0.1.0", 2079 + "@radix-ui/react-use-layout-effect": "1.1.1" 2080 + }, 2081 + "peerDependencies": { 2082 + "@types/react": "*", 2083 + "@types/react-dom": "*", 2084 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2085 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2086 + }, 2087 + "peerDependenciesMeta": { 2088 + "@types/react": { 2089 + "optional": true 2090 + }, 2091 + "@types/react-dom": { 2092 + "optional": true 2093 + } 2094 + } 2095 + }, 2096 + "node_modules/@radix-ui/react-checkbox": { 2097 + "version": "1.3.3", 2098 + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", 2099 + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", 2100 + "dependencies": { 2101 + "@radix-ui/primitive": "1.1.3", 2102 + "@radix-ui/react-compose-refs": "1.1.2", 2103 + "@radix-ui/react-context": "1.1.2", 2104 + "@radix-ui/react-presence": "1.1.5", 2105 + "@radix-ui/react-primitive": "2.1.3", 2106 + "@radix-ui/react-use-controllable-state": "1.2.2", 2107 + "@radix-ui/react-use-previous": "1.1.1", 2108 + "@radix-ui/react-use-size": "1.1.1" 2109 + }, 2110 + "peerDependencies": { 2111 + "@types/react": "*", 2112 + "@types/react-dom": "*", 2113 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2114 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2115 + }, 2116 + "peerDependenciesMeta": { 2117 + "@types/react": { 2118 + "optional": true 2119 + }, 2120 + "@types/react-dom": { 2121 + "optional": true 2122 + } 2123 + } 2124 + }, 2125 + "node_modules/@radix-ui/react-collapsible": { 2126 + "version": "1.1.12", 2127 + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", 2128 + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", 2129 + "dependencies": { 2130 + "@radix-ui/primitive": "1.1.3", 2131 + "@radix-ui/react-compose-refs": "1.1.2", 2132 + "@radix-ui/react-context": "1.1.2", 2133 + "@radix-ui/react-id": "1.1.1", 2134 + "@radix-ui/react-presence": "1.1.5", 2135 + "@radix-ui/react-primitive": "2.1.3", 2136 + "@radix-ui/react-use-controllable-state": "1.2.2", 2137 + "@radix-ui/react-use-layout-effect": "1.1.1" 2138 + }, 2139 + "peerDependencies": { 2140 + "@types/react": "*", 2141 + "@types/react-dom": "*", 2142 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2143 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2144 + }, 2145 + "peerDependenciesMeta": { 2146 + "@types/react": { 2147 + "optional": true 2148 + }, 2149 + "@types/react-dom": { 2150 + "optional": true 2151 + } 2152 + } 2153 + }, 2154 + "node_modules/@radix-ui/react-collection": { 2155 + "version": "1.1.7", 2156 + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", 2157 + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", 2158 + "dependencies": { 2159 + "@radix-ui/react-compose-refs": "1.1.2", 2160 + "@radix-ui/react-context": "1.1.2", 2161 + "@radix-ui/react-primitive": "2.1.3", 2162 + "@radix-ui/react-slot": "1.2.3" 2163 + }, 2164 + "peerDependencies": { 2165 + "@types/react": "*", 2166 + "@types/react-dom": "*", 2167 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2168 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2169 + }, 2170 + "peerDependenciesMeta": { 2171 + "@types/react": { 2172 + "optional": true 2173 + }, 2174 + "@types/react-dom": { 2175 + "optional": true 2176 + } 2177 + } 2178 + }, 2179 + "node_modules/@radix-ui/react-compose-refs": { 2180 + "version": "1.1.2", 2181 + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", 2182 + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", 2183 + "peerDependencies": { 2184 + "@types/react": "*", 2185 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2186 + }, 2187 + "peerDependenciesMeta": { 2188 + "@types/react": { 2189 + "optional": true 2190 + } 2191 + } 2192 + }, 2193 + "node_modules/@radix-ui/react-context": { 2194 + "version": "1.1.2", 2195 + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", 2196 + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", 2197 + "peerDependencies": { 2198 + "@types/react": "*", 2199 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2200 + }, 2201 + "peerDependenciesMeta": { 2202 + "@types/react": { 2203 + "optional": true 2204 + } 2205 + } 2206 + }, 2207 + "node_modules/@radix-ui/react-context-menu": { 2208 + "version": "2.2.16", 2209 + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", 2210 + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", 2211 + "dependencies": { 2212 + "@radix-ui/primitive": "1.1.3", 2213 + "@radix-ui/react-context": "1.1.2", 2214 + "@radix-ui/react-menu": "2.1.16", 2215 + "@radix-ui/react-primitive": "2.1.3", 2216 + "@radix-ui/react-use-callback-ref": "1.1.1", 2217 + "@radix-ui/react-use-controllable-state": "1.2.2" 2218 + }, 2219 + "peerDependencies": { 2220 + "@types/react": "*", 2221 + "@types/react-dom": "*", 2222 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2223 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2224 + }, 2225 + "peerDependenciesMeta": { 2226 + "@types/react": { 2227 + "optional": true 2228 + }, 2229 + "@types/react-dom": { 2230 + "optional": true 2231 + } 2232 + } 2233 + }, 2234 + "node_modules/@radix-ui/react-dialog": { 2235 + "version": "1.1.15", 2236 + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", 2237 + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", 2238 + "dependencies": { 2239 + "@radix-ui/primitive": "1.1.3", 2240 + "@radix-ui/react-compose-refs": "1.1.2", 2241 + "@radix-ui/react-context": "1.1.2", 2242 + "@radix-ui/react-dismissable-layer": "1.1.11", 2243 + "@radix-ui/react-focus-guards": "1.1.3", 2244 + "@radix-ui/react-focus-scope": "1.1.7", 2245 + "@radix-ui/react-id": "1.1.1", 2246 + "@radix-ui/react-portal": "1.1.9", 2247 + "@radix-ui/react-presence": "1.1.5", 2248 + "@radix-ui/react-primitive": "2.1.3", 2249 + "@radix-ui/react-slot": "1.2.3", 2250 + "@radix-ui/react-use-controllable-state": "1.2.2", 2251 + "aria-hidden": "^1.2.4", 2252 + "react-remove-scroll": "^2.6.3" 2253 + }, 2254 + "peerDependencies": { 2255 + "@types/react": "*", 2256 + "@types/react-dom": "*", 2257 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2258 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2259 + }, 2260 + "peerDependenciesMeta": { 2261 + "@types/react": { 2262 + "optional": true 2263 + }, 2264 + "@types/react-dom": { 2265 + "optional": true 2266 + } 2267 + } 2268 + }, 2269 + "node_modules/@radix-ui/react-direction": { 2270 + "version": "1.1.1", 2271 + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", 2272 + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", 2273 + "peerDependencies": { 2274 + "@types/react": "*", 2275 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2276 + }, 2277 + "peerDependenciesMeta": { 2278 + "@types/react": { 2279 + "optional": true 2280 + } 2281 + } 2282 + }, 2283 + "node_modules/@radix-ui/react-dismissable-layer": { 2284 + "version": "1.1.11", 2285 + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", 2286 + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", 2287 + "dependencies": { 2288 + "@radix-ui/primitive": "1.1.3", 2289 + "@radix-ui/react-compose-refs": "1.1.2", 2290 + "@radix-ui/react-primitive": "2.1.3", 2291 + "@radix-ui/react-use-callback-ref": "1.1.1", 2292 + "@radix-ui/react-use-escape-keydown": "1.1.1" 2293 + }, 2294 + "peerDependencies": { 2295 + "@types/react": "*", 2296 + "@types/react-dom": "*", 2297 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2298 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2299 + }, 2300 + "peerDependenciesMeta": { 2301 + "@types/react": { 2302 + "optional": true 2303 + }, 2304 + "@types/react-dom": { 2305 + "optional": true 2306 + } 2307 + } 2308 + }, 2309 + "node_modules/@radix-ui/react-dropdown-menu": { 2310 + "version": "2.1.16", 2311 + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", 2312 + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", 2313 + "dependencies": { 2314 + "@radix-ui/primitive": "1.1.3", 2315 + "@radix-ui/react-compose-refs": "1.1.2", 2316 + "@radix-ui/react-context": "1.1.2", 2317 + "@radix-ui/react-id": "1.1.1", 2318 + "@radix-ui/react-menu": "2.1.16", 2319 + "@radix-ui/react-primitive": "2.1.3", 2320 + "@radix-ui/react-use-controllable-state": "1.2.2" 2321 + }, 2322 + "peerDependencies": { 2323 + "@types/react": "*", 2324 + "@types/react-dom": "*", 2325 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2326 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2327 + }, 2328 + "peerDependenciesMeta": { 2329 + "@types/react": { 2330 + "optional": true 2331 + }, 2332 + "@types/react-dom": { 2333 + "optional": true 2334 + } 2335 + } 2336 + }, 2337 + "node_modules/@radix-ui/react-focus-guards": { 2338 + "version": "1.1.3", 2339 + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", 2340 + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", 2341 + "peerDependencies": { 2342 + "@types/react": "*", 2343 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2344 + }, 2345 + "peerDependenciesMeta": { 2346 + "@types/react": { 2347 + "optional": true 2348 + } 2349 + } 2350 + }, 2351 + "node_modules/@radix-ui/react-focus-scope": { 2352 + "version": "1.1.7", 2353 + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", 2354 + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", 2355 + "dependencies": { 2356 + "@radix-ui/react-compose-refs": "1.1.2", 2357 + "@radix-ui/react-primitive": "2.1.3", 2358 + "@radix-ui/react-use-callback-ref": "1.1.1" 2359 + }, 2360 + "peerDependencies": { 2361 + "@types/react": "*", 2362 + "@types/react-dom": "*", 2363 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2364 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2365 + }, 2366 + "peerDependenciesMeta": { 2367 + "@types/react": { 2368 + "optional": true 2369 + }, 2370 + "@types/react-dom": { 2371 + "optional": true 2372 + } 2373 + } 2374 + }, 2375 + "node_modules/@radix-ui/react-form": { 2376 + "version": "0.1.8", 2377 + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", 2378 + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", 2379 + "dependencies": { 2380 + "@radix-ui/primitive": "1.1.3", 2381 + "@radix-ui/react-compose-refs": "1.1.2", 2382 + "@radix-ui/react-context": "1.1.2", 2383 + "@radix-ui/react-id": "1.1.1", 2384 + "@radix-ui/react-label": "2.1.7", 2385 + "@radix-ui/react-primitive": "2.1.3" 2386 + }, 2387 + "peerDependencies": { 2388 + "@types/react": "*", 2389 + "@types/react-dom": "*", 2390 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2391 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2392 + }, 2393 + "peerDependenciesMeta": { 2394 + "@types/react": { 2395 + "optional": true 2396 + }, 2397 + "@types/react-dom": { 2398 + "optional": true 2399 + } 2400 + } 2401 + }, 2402 + "node_modules/@radix-ui/react-hover-card": { 2403 + "version": "1.1.15", 2404 + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", 2405 + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", 2406 + "license": "MIT", 2407 + "dependencies": { 2408 + "@radix-ui/primitive": "1.1.3", 2409 + "@radix-ui/react-compose-refs": "1.1.2", 2410 + "@radix-ui/react-context": "1.1.2", 2411 + "@radix-ui/react-dismissable-layer": "1.1.11", 2412 + "@radix-ui/react-popper": "1.2.8", 2413 + "@radix-ui/react-portal": "1.1.9", 2414 + "@radix-ui/react-presence": "1.1.5", 2415 + "@radix-ui/react-primitive": "2.1.3", 2416 + "@radix-ui/react-use-controllable-state": "1.2.2" 2417 + }, 2418 + "peerDependencies": { 2419 + "@types/react": "*", 2420 + "@types/react-dom": "*", 2421 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2422 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2423 + }, 2424 + "peerDependenciesMeta": { 2425 + "@types/react": { 2426 + "optional": true 2427 + }, 2428 + "@types/react-dom": { 2429 + "optional": true 2430 + } 2431 + } 2432 + }, 2433 + "node_modules/@radix-ui/react-id": { 2434 + "version": "1.1.1", 2435 + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", 2436 + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", 2437 + "dependencies": { 2438 + "@radix-ui/react-use-layout-effect": "1.1.1" 2439 + }, 2440 + "peerDependencies": { 2441 + "@types/react": "*", 2442 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2443 + }, 2444 + "peerDependenciesMeta": { 2445 + "@types/react": { 2446 + "optional": true 2447 + } 2448 + } 2449 + }, 2450 + "node_modules/@radix-ui/react-label": { 2451 + "version": "2.1.7", 2452 + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", 2453 + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", 2454 + "dependencies": { 2455 + "@radix-ui/react-primitive": "2.1.3" 2456 + }, 2457 + "peerDependencies": { 2458 + "@types/react": "*", 2459 + "@types/react-dom": "*", 2460 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2461 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2462 + }, 2463 + "peerDependenciesMeta": { 2464 + "@types/react": { 2465 + "optional": true 2466 + }, 2467 + "@types/react-dom": { 2468 + "optional": true 2469 + } 2470 + } 2471 + }, 2472 + "node_modules/@radix-ui/react-menu": { 2473 + "version": "2.1.16", 2474 + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", 2475 + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", 2476 + "dependencies": { 2477 + "@radix-ui/primitive": "1.1.3", 2478 + "@radix-ui/react-collection": "1.1.7", 2479 + "@radix-ui/react-compose-refs": "1.1.2", 2480 + "@radix-ui/react-context": "1.1.2", 2481 + "@radix-ui/react-direction": "1.1.1", 2482 + "@radix-ui/react-dismissable-layer": "1.1.11", 2483 + "@radix-ui/react-focus-guards": "1.1.3", 2484 + "@radix-ui/react-focus-scope": "1.1.7", 2485 + "@radix-ui/react-id": "1.1.1", 2486 + "@radix-ui/react-popper": "1.2.8", 2487 + "@radix-ui/react-portal": "1.1.9", 2488 + "@radix-ui/react-presence": "1.1.5", 2489 + "@radix-ui/react-primitive": "2.1.3", 2490 + "@radix-ui/react-roving-focus": "1.1.11", 2491 + "@radix-ui/react-slot": "1.2.3", 2492 + "@radix-ui/react-use-callback-ref": "1.1.1", 2493 + "aria-hidden": "^1.2.4", 2494 + "react-remove-scroll": "^2.6.3" 2495 + }, 2496 + "peerDependencies": { 2497 + "@types/react": "*", 2498 + "@types/react-dom": "*", 2499 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2500 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2501 + }, 2502 + "peerDependenciesMeta": { 2503 + "@types/react": { 2504 + "optional": true 2505 + }, 2506 + "@types/react-dom": { 2507 + "optional": true 2508 + } 2509 + } 2510 + }, 2511 + "node_modules/@radix-ui/react-menubar": { 2512 + "version": "1.1.16", 2513 + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", 2514 + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", 2515 + "dependencies": { 2516 + "@radix-ui/primitive": "1.1.3", 2517 + "@radix-ui/react-collection": "1.1.7", 2518 + "@radix-ui/react-compose-refs": "1.1.2", 2519 + "@radix-ui/react-context": "1.1.2", 2520 + "@radix-ui/react-direction": "1.1.1", 2521 + "@radix-ui/react-id": "1.1.1", 2522 + "@radix-ui/react-menu": "2.1.16", 2523 + "@radix-ui/react-primitive": "2.1.3", 2524 + "@radix-ui/react-roving-focus": "1.1.11", 2525 + "@radix-ui/react-use-controllable-state": "1.2.2" 2526 + }, 2527 + "peerDependencies": { 2528 + "@types/react": "*", 2529 + "@types/react-dom": "*", 2530 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2531 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2532 + }, 2533 + "peerDependenciesMeta": { 2534 + "@types/react": { 2535 + "optional": true 2536 + }, 2537 + "@types/react-dom": { 2538 + "optional": true 2539 + } 2540 + } 2541 + }, 2542 + "node_modules/@radix-ui/react-navigation-menu": { 2543 + "version": "1.2.14", 2544 + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", 2545 + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", 2546 + "dependencies": { 2547 + "@radix-ui/primitive": "1.1.3", 2548 + "@radix-ui/react-collection": "1.1.7", 2549 + "@radix-ui/react-compose-refs": "1.1.2", 2550 + "@radix-ui/react-context": "1.1.2", 2551 + "@radix-ui/react-direction": "1.1.1", 2552 + "@radix-ui/react-dismissable-layer": "1.1.11", 2553 + "@radix-ui/react-id": "1.1.1", 2554 + "@radix-ui/react-presence": "1.1.5", 2555 + "@radix-ui/react-primitive": "2.1.3", 2556 + "@radix-ui/react-use-callback-ref": "1.1.1", 2557 + "@radix-ui/react-use-controllable-state": "1.2.2", 2558 + "@radix-ui/react-use-layout-effect": "1.1.1", 2559 + "@radix-ui/react-use-previous": "1.1.1", 2560 + "@radix-ui/react-visually-hidden": "1.2.3" 2561 + }, 2562 + "peerDependencies": { 2563 + "@types/react": "*", 2564 + "@types/react-dom": "*", 2565 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2566 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2567 + }, 2568 + "peerDependenciesMeta": { 2569 + "@types/react": { 2570 + "optional": true 2571 + }, 2572 + "@types/react-dom": { 2573 + "optional": true 2574 + } 2575 + } 2576 + }, 2577 + "node_modules/@radix-ui/react-one-time-password-field": { 2578 + "version": "0.1.8", 2579 + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", 2580 + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", 2581 + "dependencies": { 2582 + "@radix-ui/number": "1.1.1", 2583 + "@radix-ui/primitive": "1.1.3", 2584 + "@radix-ui/react-collection": "1.1.7", 2585 + "@radix-ui/react-compose-refs": "1.1.2", 2586 + "@radix-ui/react-context": "1.1.2", 2587 + "@radix-ui/react-direction": "1.1.1", 2588 + "@radix-ui/react-primitive": "2.1.3", 2589 + "@radix-ui/react-roving-focus": "1.1.11", 2590 + "@radix-ui/react-use-controllable-state": "1.2.2", 2591 + "@radix-ui/react-use-effect-event": "0.0.2", 2592 + "@radix-ui/react-use-is-hydrated": "0.1.0", 2593 + "@radix-ui/react-use-layout-effect": "1.1.1" 2594 + }, 2595 + "peerDependencies": { 2596 + "@types/react": "*", 2597 + "@types/react-dom": "*", 2598 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2599 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2600 + }, 2601 + "peerDependenciesMeta": { 2602 + "@types/react": { 2603 + "optional": true 2604 + }, 2605 + "@types/react-dom": { 2606 + "optional": true 2607 + } 2608 + } 2609 + }, 2610 + "node_modules/@radix-ui/react-password-toggle-field": { 2611 + "version": "0.1.3", 2612 + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", 2613 + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", 2614 + "dependencies": { 2615 + "@radix-ui/primitive": "1.1.3", 2616 + "@radix-ui/react-compose-refs": "1.1.2", 2617 + "@radix-ui/react-context": "1.1.2", 2618 + "@radix-ui/react-id": "1.1.1", 2619 + "@radix-ui/react-primitive": "2.1.3", 2620 + "@radix-ui/react-use-controllable-state": "1.2.2", 2621 + "@radix-ui/react-use-effect-event": "0.0.2", 2622 + "@radix-ui/react-use-is-hydrated": "0.1.0" 2623 + }, 2624 + "peerDependencies": { 2625 + "@types/react": "*", 2626 + "@types/react-dom": "*", 2627 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2628 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2629 + }, 2630 + "peerDependenciesMeta": { 2631 + "@types/react": { 2632 + "optional": true 2633 + }, 2634 + "@types/react-dom": { 2635 + "optional": true 2636 + } 2637 + } 2638 + }, 2639 + "node_modules/@radix-ui/react-popover": { 2640 + "version": "1.1.15", 2641 + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", 2642 + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", 2643 + "dependencies": { 2644 + "@radix-ui/primitive": "1.1.3", 2645 + "@radix-ui/react-compose-refs": "1.1.2", 2646 + "@radix-ui/react-context": "1.1.2", 2647 + "@radix-ui/react-dismissable-layer": "1.1.11", 2648 + "@radix-ui/react-focus-guards": "1.1.3", 2649 + "@radix-ui/react-focus-scope": "1.1.7", 2650 + "@radix-ui/react-id": "1.1.1", 2651 + "@radix-ui/react-popper": "1.2.8", 2652 + "@radix-ui/react-portal": "1.1.9", 2653 + "@radix-ui/react-presence": "1.1.5", 2654 + "@radix-ui/react-primitive": "2.1.3", 2655 + "@radix-ui/react-slot": "1.2.3", 2656 + "@radix-ui/react-use-controllable-state": "1.2.2", 2657 + "aria-hidden": "^1.2.4", 2658 + "react-remove-scroll": "^2.6.3" 2659 + }, 2660 + "peerDependencies": { 2661 + "@types/react": "*", 2662 + "@types/react-dom": "*", 2663 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2664 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2665 + }, 2666 + "peerDependenciesMeta": { 2667 + "@types/react": { 2668 + "optional": true 2669 + }, 2670 + "@types/react-dom": { 2671 + "optional": true 2672 + } 2673 + } 2674 + }, 2675 + "node_modules/@radix-ui/react-popper": { 2676 + "version": "1.2.8", 2677 + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", 2678 + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", 2679 + "dependencies": { 2680 + "@floating-ui/react-dom": "^2.0.0", 2681 + "@radix-ui/react-arrow": "1.1.7", 2682 + "@radix-ui/react-compose-refs": "1.1.2", 2683 + "@radix-ui/react-context": "1.1.2", 2684 + "@radix-ui/react-primitive": "2.1.3", 2685 + "@radix-ui/react-use-callback-ref": "1.1.1", 2686 + "@radix-ui/react-use-layout-effect": "1.1.1", 2687 + "@radix-ui/react-use-rect": "1.1.1", 2688 + "@radix-ui/react-use-size": "1.1.1", 2689 + "@radix-ui/rect": "1.1.1" 2690 + }, 2691 + "peerDependencies": { 2692 + "@types/react": "*", 2693 + "@types/react-dom": "*", 2694 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2695 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2696 + }, 2697 + "peerDependenciesMeta": { 2698 + "@types/react": { 2699 + "optional": true 2700 + }, 2701 + "@types/react-dom": { 2702 + "optional": true 2703 + } 2704 + } 2705 + }, 2706 + "node_modules/@radix-ui/react-portal": { 2707 + "version": "1.1.9", 2708 + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", 2709 + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", 2710 + "dependencies": { 2711 + "@radix-ui/react-primitive": "2.1.3", 2712 + "@radix-ui/react-use-layout-effect": "1.1.1" 2713 + }, 2714 + "peerDependencies": { 2715 + "@types/react": "*", 2716 + "@types/react-dom": "*", 2717 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2718 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2719 + }, 2720 + "peerDependenciesMeta": { 2721 + "@types/react": { 2722 + "optional": true 2723 + }, 2724 + "@types/react-dom": { 2725 + "optional": true 2726 + } 2727 + } 2728 + }, 2729 + "node_modules/@radix-ui/react-presence": { 2730 + "version": "1.1.5", 2731 + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", 2732 + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", 2733 + "dependencies": { 2734 + "@radix-ui/react-compose-refs": "1.1.2", 2735 + "@radix-ui/react-use-layout-effect": "1.1.1" 2736 + }, 2737 + "peerDependencies": { 2738 + "@types/react": "*", 2739 + "@types/react-dom": "*", 2740 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2741 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2742 + }, 2743 + "peerDependenciesMeta": { 2744 + "@types/react": { 2745 + "optional": true 2746 + }, 2747 + "@types/react-dom": { 2748 + "optional": true 2749 + } 2750 + } 2751 + }, 2752 + "node_modules/@radix-ui/react-primitive": { 2753 + "version": "2.1.3", 2754 + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", 2755 + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", 2756 + "dependencies": { 2757 + "@radix-ui/react-slot": "1.2.3" 2758 + }, 2759 + "peerDependencies": { 2760 + "@types/react": "*", 2761 + "@types/react-dom": "*", 2762 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2763 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2764 + }, 2765 + "peerDependenciesMeta": { 2766 + "@types/react": { 2767 + "optional": true 2768 + }, 2769 + "@types/react-dom": { 2770 + "optional": true 2771 + } 2772 + } 2773 + }, 2774 + "node_modules/@radix-ui/react-progress": { 2775 + "version": "1.1.7", 2776 + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", 2777 + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", 2778 + "dependencies": { 2779 + "@radix-ui/react-context": "1.1.2", 2780 + "@radix-ui/react-primitive": "2.1.3" 2781 + }, 2782 + "peerDependencies": { 2783 + "@types/react": "*", 2784 + "@types/react-dom": "*", 2785 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2786 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2787 + }, 2788 + "peerDependenciesMeta": { 2789 + "@types/react": { 2790 + "optional": true 2791 + }, 2792 + "@types/react-dom": { 2793 + "optional": true 2794 + } 2795 + } 2796 + }, 2797 + "node_modules/@radix-ui/react-radio-group": { 2798 + "version": "1.3.8", 2799 + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", 2800 + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", 2801 + "dependencies": { 2802 + "@radix-ui/primitive": "1.1.3", 2803 + "@radix-ui/react-compose-refs": "1.1.2", 2804 + "@radix-ui/react-context": "1.1.2", 2805 + "@radix-ui/react-direction": "1.1.1", 2806 + "@radix-ui/react-presence": "1.1.5", 2807 + "@radix-ui/react-primitive": "2.1.3", 2808 + "@radix-ui/react-roving-focus": "1.1.11", 2809 + "@radix-ui/react-use-controllable-state": "1.2.2", 2810 + "@radix-ui/react-use-previous": "1.1.1", 2811 + "@radix-ui/react-use-size": "1.1.1" 2812 + }, 2813 + "peerDependencies": { 2814 + "@types/react": "*", 2815 + "@types/react-dom": "*", 2816 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2817 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2818 + }, 2819 + "peerDependenciesMeta": { 2820 + "@types/react": { 2821 + "optional": true 2822 + }, 2823 + "@types/react-dom": { 2824 + "optional": true 2825 + } 2826 + } 2827 + }, 2828 + "node_modules/@radix-ui/react-roving-focus": { 2829 + "version": "1.1.11", 2830 + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", 2831 + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", 2832 + "dependencies": { 2833 + "@radix-ui/primitive": "1.1.3", 2834 + "@radix-ui/react-collection": "1.1.7", 2835 + "@radix-ui/react-compose-refs": "1.1.2", 2836 + "@radix-ui/react-context": "1.1.2", 2837 + "@radix-ui/react-direction": "1.1.1", 2838 + "@radix-ui/react-id": "1.1.1", 2839 + "@radix-ui/react-primitive": "2.1.3", 2840 + "@radix-ui/react-use-callback-ref": "1.1.1", 2841 + "@radix-ui/react-use-controllable-state": "1.2.2" 2842 + }, 2843 + "peerDependencies": { 2844 + "@types/react": "*", 2845 + "@types/react-dom": "*", 2846 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2847 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2848 + }, 2849 + "peerDependenciesMeta": { 2850 + "@types/react": { 2851 + "optional": true 2852 + }, 2853 + "@types/react-dom": { 2854 + "optional": true 2855 + } 2856 + } 2857 + }, 2858 + "node_modules/@radix-ui/react-scroll-area": { 2859 + "version": "1.2.10", 2860 + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", 2861 + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", 2862 + "dependencies": { 2863 + "@radix-ui/number": "1.1.1", 2864 + "@radix-ui/primitive": "1.1.3", 2865 + "@radix-ui/react-compose-refs": "1.1.2", 2866 + "@radix-ui/react-context": "1.1.2", 2867 + "@radix-ui/react-direction": "1.1.1", 2868 + "@radix-ui/react-presence": "1.1.5", 2869 + "@radix-ui/react-primitive": "2.1.3", 2870 + "@radix-ui/react-use-callback-ref": "1.1.1", 2871 + "@radix-ui/react-use-layout-effect": "1.1.1" 2872 + }, 2873 + "peerDependencies": { 2874 + "@types/react": "*", 2875 + "@types/react-dom": "*", 2876 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2877 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2878 + }, 2879 + "peerDependenciesMeta": { 2880 + "@types/react": { 2881 + "optional": true 2882 + }, 2883 + "@types/react-dom": { 2884 + "optional": true 2885 + } 2886 + } 2887 + }, 2888 + "node_modules/@radix-ui/react-select": { 2889 + "version": "2.2.6", 2890 + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", 2891 + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", 2892 + "dependencies": { 2893 + "@radix-ui/number": "1.1.1", 2894 + "@radix-ui/primitive": "1.1.3", 2895 + "@radix-ui/react-collection": "1.1.7", 2896 + "@radix-ui/react-compose-refs": "1.1.2", 2897 + "@radix-ui/react-context": "1.1.2", 2898 + "@radix-ui/react-direction": "1.1.1", 2899 + "@radix-ui/react-dismissable-layer": "1.1.11", 2900 + "@radix-ui/react-focus-guards": "1.1.3", 2901 + "@radix-ui/react-focus-scope": "1.1.7", 2902 + "@radix-ui/react-id": "1.1.1", 2903 + "@radix-ui/react-popper": "1.2.8", 2904 + "@radix-ui/react-portal": "1.1.9", 2905 + "@radix-ui/react-primitive": "2.1.3", 2906 + "@radix-ui/react-slot": "1.2.3", 2907 + "@radix-ui/react-use-callback-ref": "1.1.1", 2908 + "@radix-ui/react-use-controllable-state": "1.2.2", 2909 + "@radix-ui/react-use-layout-effect": "1.1.1", 2910 + "@radix-ui/react-use-previous": "1.1.1", 2911 + "@radix-ui/react-visually-hidden": "1.2.3", 2912 + "aria-hidden": "^1.2.4", 2913 + "react-remove-scroll": "^2.6.3" 2914 + }, 2915 + "peerDependencies": { 2916 + "@types/react": "*", 2917 + "@types/react-dom": "*", 2918 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2919 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2920 + }, 2921 + "peerDependenciesMeta": { 2922 + "@types/react": { 2923 + "optional": true 2924 + }, 2925 + "@types/react-dom": { 2926 + "optional": true 2927 + } 2928 + } 2929 + }, 2930 + "node_modules/@radix-ui/react-separator": { 2931 + "version": "1.1.7", 2932 + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", 2933 + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", 2934 + "dependencies": { 2935 + "@radix-ui/react-primitive": "2.1.3" 2936 + }, 2937 + "peerDependencies": { 2938 + "@types/react": "*", 2939 + "@types/react-dom": "*", 2940 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2941 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2942 + }, 2943 + "peerDependenciesMeta": { 2944 + "@types/react": { 2945 + "optional": true 2946 + }, 2947 + "@types/react-dom": { 2948 + "optional": true 2949 + } 2950 + } 2951 + }, 2952 + "node_modules/@radix-ui/react-slider": { 2953 + "version": "1.3.6", 2954 + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", 2955 + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", 2956 + "dependencies": { 2957 + "@radix-ui/number": "1.1.1", 2958 + "@radix-ui/primitive": "1.1.3", 2959 + "@radix-ui/react-collection": "1.1.7", 2960 + "@radix-ui/react-compose-refs": "1.1.2", 2961 + "@radix-ui/react-context": "1.1.2", 2962 + "@radix-ui/react-direction": "1.1.1", 2963 + "@radix-ui/react-primitive": "2.1.3", 2964 + "@radix-ui/react-use-controllable-state": "1.2.2", 2965 + "@radix-ui/react-use-layout-effect": "1.1.1", 2966 + "@radix-ui/react-use-previous": "1.1.1", 2967 + "@radix-ui/react-use-size": "1.1.1" 2968 + }, 2969 + "peerDependencies": { 2970 + "@types/react": "*", 2971 + "@types/react-dom": "*", 2972 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 2973 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2974 + }, 2975 + "peerDependenciesMeta": { 2976 + "@types/react": { 2977 + "optional": true 2978 + }, 2979 + "@types/react-dom": { 2980 + "optional": true 2981 + } 2982 + } 2983 + }, 2984 + "node_modules/@radix-ui/react-slot": { 2985 + "version": "1.2.3", 2986 + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", 2987 + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", 2988 + "dependencies": { 2989 + "@radix-ui/react-compose-refs": "1.1.2" 2990 + }, 2991 + "peerDependencies": { 2992 + "@types/react": "*", 2993 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 2994 + }, 2995 + "peerDependenciesMeta": { 2996 + "@types/react": { 2997 + "optional": true 2998 + } 2999 + } 3000 + }, 3001 + "node_modules/@radix-ui/react-switch": { 3002 + "version": "1.2.6", 3003 + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", 3004 + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", 3005 + "dependencies": { 3006 + "@radix-ui/primitive": "1.1.3", 3007 + "@radix-ui/react-compose-refs": "1.1.2", 3008 + "@radix-ui/react-context": "1.1.2", 3009 + "@radix-ui/react-primitive": "2.1.3", 3010 + "@radix-ui/react-use-controllable-state": "1.2.2", 3011 + "@radix-ui/react-use-previous": "1.1.1", 3012 + "@radix-ui/react-use-size": "1.1.1" 3013 + }, 3014 + "peerDependencies": { 3015 + "@types/react": "*", 3016 + "@types/react-dom": "*", 3017 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3018 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3019 + }, 3020 + "peerDependenciesMeta": { 3021 + "@types/react": { 3022 + "optional": true 3023 + }, 3024 + "@types/react-dom": { 3025 + "optional": true 3026 + } 3027 + } 3028 + }, 3029 + "node_modules/@radix-ui/react-tabs": { 3030 + "version": "1.1.13", 3031 + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", 3032 + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", 3033 + "dependencies": { 3034 + "@radix-ui/primitive": "1.1.3", 3035 + "@radix-ui/react-context": "1.1.2", 3036 + "@radix-ui/react-direction": "1.1.1", 3037 + "@radix-ui/react-id": "1.1.1", 3038 + "@radix-ui/react-presence": "1.1.5", 3039 + "@radix-ui/react-primitive": "2.1.3", 3040 + "@radix-ui/react-roving-focus": "1.1.11", 3041 + "@radix-ui/react-use-controllable-state": "1.2.2" 3042 + }, 3043 + "peerDependencies": { 3044 + "@types/react": "*", 3045 + "@types/react-dom": "*", 3046 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3047 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3048 + }, 3049 + "peerDependenciesMeta": { 3050 + "@types/react": { 3051 + "optional": true 3052 + }, 3053 + "@types/react-dom": { 3054 + "optional": true 3055 + } 3056 + } 3057 + }, 3058 + "node_modules/@radix-ui/react-toast": { 3059 + "version": "1.2.15", 3060 + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", 3061 + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", 3062 + "dependencies": { 3063 + "@radix-ui/primitive": "1.1.3", 3064 + "@radix-ui/react-collection": "1.1.7", 3065 + "@radix-ui/react-compose-refs": "1.1.2", 3066 + "@radix-ui/react-context": "1.1.2", 3067 + "@radix-ui/react-dismissable-layer": "1.1.11", 3068 + "@radix-ui/react-portal": "1.1.9", 3069 + "@radix-ui/react-presence": "1.1.5", 3070 + "@radix-ui/react-primitive": "2.1.3", 3071 + "@radix-ui/react-use-callback-ref": "1.1.1", 3072 + "@radix-ui/react-use-controllable-state": "1.2.2", 3073 + "@radix-ui/react-use-layout-effect": "1.1.1", 3074 + "@radix-ui/react-visually-hidden": "1.2.3" 3075 + }, 3076 + "peerDependencies": { 3077 + "@types/react": "*", 3078 + "@types/react-dom": "*", 3079 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3080 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3081 + }, 3082 + "peerDependenciesMeta": { 3083 + "@types/react": { 3084 + "optional": true 3085 + }, 3086 + "@types/react-dom": { 3087 + "optional": true 3088 + } 3089 + } 3090 + }, 3091 + "node_modules/@radix-ui/react-toggle": { 3092 + "version": "1.1.10", 3093 + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", 3094 + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", 3095 + "dependencies": { 3096 + "@radix-ui/primitive": "1.1.3", 3097 + "@radix-ui/react-primitive": "2.1.3", 3098 + "@radix-ui/react-use-controllable-state": "1.2.2" 3099 + }, 3100 + "peerDependencies": { 3101 + "@types/react": "*", 3102 + "@types/react-dom": "*", 3103 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3104 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3105 + }, 3106 + "peerDependenciesMeta": { 3107 + "@types/react": { 3108 + "optional": true 3109 + }, 3110 + "@types/react-dom": { 3111 + "optional": true 3112 + } 3113 + } 3114 + }, 3115 + "node_modules/@radix-ui/react-toggle-group": { 3116 + "version": "1.1.11", 3117 + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", 3118 + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", 3119 + "dependencies": { 3120 + "@radix-ui/primitive": "1.1.3", 3121 + "@radix-ui/react-context": "1.1.2", 3122 + "@radix-ui/react-direction": "1.1.1", 3123 + "@radix-ui/react-primitive": "2.1.3", 3124 + "@radix-ui/react-roving-focus": "1.1.11", 3125 + "@radix-ui/react-toggle": "1.1.10", 3126 + "@radix-ui/react-use-controllable-state": "1.2.2" 3127 + }, 3128 + "peerDependencies": { 3129 + "@types/react": "*", 3130 + "@types/react-dom": "*", 3131 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3132 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3133 + }, 3134 + "peerDependenciesMeta": { 3135 + "@types/react": { 3136 + "optional": true 3137 + }, 3138 + "@types/react-dom": { 3139 + "optional": true 3140 + } 3141 + } 3142 + }, 3143 + "node_modules/@radix-ui/react-toolbar": { 3144 + "version": "1.1.11", 3145 + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", 3146 + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", 3147 + "dependencies": { 3148 + "@radix-ui/primitive": "1.1.3", 3149 + "@radix-ui/react-context": "1.1.2", 3150 + "@radix-ui/react-direction": "1.1.1", 3151 + "@radix-ui/react-primitive": "2.1.3", 3152 + "@radix-ui/react-roving-focus": "1.1.11", 3153 + "@radix-ui/react-separator": "1.1.7", 3154 + "@radix-ui/react-toggle-group": "1.1.11" 3155 + }, 3156 + "peerDependencies": { 3157 + "@types/react": "*", 3158 + "@types/react-dom": "*", 3159 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3160 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3161 + }, 3162 + "peerDependenciesMeta": { 3163 + "@types/react": { 3164 + "optional": true 3165 + }, 3166 + "@types/react-dom": { 3167 + "optional": true 3168 + } 3169 + } 3170 + }, 3171 + "node_modules/@radix-ui/react-tooltip": { 3172 + "version": "1.2.8", 3173 + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", 3174 + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", 3175 + "dependencies": { 3176 + "@radix-ui/primitive": "1.1.3", 3177 + "@radix-ui/react-compose-refs": "1.1.2", 3178 + "@radix-ui/react-context": "1.1.2", 3179 + "@radix-ui/react-dismissable-layer": "1.1.11", 3180 + "@radix-ui/react-id": "1.1.1", 3181 + "@radix-ui/react-popper": "1.2.8", 3182 + "@radix-ui/react-portal": "1.1.9", 3183 + "@radix-ui/react-presence": "1.1.5", 3184 + "@radix-ui/react-primitive": "2.1.3", 3185 + "@radix-ui/react-slot": "1.2.3", 3186 + "@radix-ui/react-use-controllable-state": "1.2.2", 3187 + "@radix-ui/react-visually-hidden": "1.2.3" 3188 + }, 3189 + "peerDependencies": { 3190 + "@types/react": "*", 3191 + "@types/react-dom": "*", 3192 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3193 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3194 + }, 3195 + "peerDependenciesMeta": { 3196 + "@types/react": { 3197 + "optional": true 3198 + }, 3199 + "@types/react-dom": { 3200 + "optional": true 3201 + } 3202 + } 3203 + }, 3204 + "node_modules/@radix-ui/react-use-callback-ref": { 3205 + "version": "1.1.1", 3206 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", 3207 + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", 3208 + "peerDependencies": { 3209 + "@types/react": "*", 3210 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3211 + }, 3212 + "peerDependenciesMeta": { 3213 + "@types/react": { 3214 + "optional": true 3215 + } 3216 + } 3217 + }, 3218 + "node_modules/@radix-ui/react-use-controllable-state": { 3219 + "version": "1.2.2", 3220 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", 3221 + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", 3222 + "dependencies": { 3223 + "@radix-ui/react-use-effect-event": "0.0.2", 3224 + "@radix-ui/react-use-layout-effect": "1.1.1" 3225 + }, 3226 + "peerDependencies": { 3227 + "@types/react": "*", 3228 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3229 + }, 3230 + "peerDependenciesMeta": { 3231 + "@types/react": { 3232 + "optional": true 3233 + } 3234 + } 3235 + }, 3236 + "node_modules/@radix-ui/react-use-effect-event": { 3237 + "version": "0.0.2", 3238 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", 3239 + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", 3240 + "dependencies": { 3241 + "@radix-ui/react-use-layout-effect": "1.1.1" 3242 + }, 3243 + "peerDependencies": { 3244 + "@types/react": "*", 3245 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3246 + }, 3247 + "peerDependenciesMeta": { 3248 + "@types/react": { 3249 + "optional": true 3250 + } 3251 + } 3252 + }, 3253 + "node_modules/@radix-ui/react-use-escape-keydown": { 3254 + "version": "1.1.1", 3255 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", 3256 + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", 3257 + "dependencies": { 3258 + "@radix-ui/react-use-callback-ref": "1.1.1" 3259 + }, 3260 + "peerDependencies": { 3261 + "@types/react": "*", 3262 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3263 + }, 3264 + "peerDependenciesMeta": { 3265 + "@types/react": { 3266 + "optional": true 3267 + } 3268 + } 3269 + }, 3270 + "node_modules/@radix-ui/react-use-is-hydrated": { 3271 + "version": "0.1.0", 3272 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", 3273 + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", 3274 + "dependencies": { 3275 + "use-sync-external-store": "^1.5.0" 3276 + }, 3277 + "peerDependencies": { 3278 + "@types/react": "*", 3279 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3280 + }, 3281 + "peerDependenciesMeta": { 3282 + "@types/react": { 3283 + "optional": true 3284 + } 3285 + } 3286 + }, 3287 + "node_modules/@radix-ui/react-use-layout-effect": { 3288 + "version": "1.1.1", 3289 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", 3290 + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", 3291 + "peerDependencies": { 3292 + "@types/react": "*", 3293 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3294 + }, 3295 + "peerDependenciesMeta": { 3296 + "@types/react": { 3297 + "optional": true 3298 + } 3299 + } 3300 + }, 3301 + "node_modules/@radix-ui/react-use-previous": { 3302 + "version": "1.1.1", 3303 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", 3304 + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", 3305 + "peerDependencies": { 3306 + "@types/react": "*", 3307 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3308 + }, 3309 + "peerDependenciesMeta": { 3310 + "@types/react": { 3311 + "optional": true 3312 + } 3313 + } 3314 + }, 3315 + "node_modules/@radix-ui/react-use-rect": { 3316 + "version": "1.1.1", 3317 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", 3318 + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", 3319 + "dependencies": { 3320 + "@radix-ui/rect": "1.1.1" 3321 + }, 3322 + "peerDependencies": { 3323 + "@types/react": "*", 3324 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3325 + }, 3326 + "peerDependenciesMeta": { 3327 + "@types/react": { 3328 + "optional": true 3329 + } 3330 + } 3331 + }, 3332 + "node_modules/@radix-ui/react-use-size": { 3333 + "version": "1.1.1", 3334 + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", 3335 + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", 3336 + "dependencies": { 3337 + "@radix-ui/react-use-layout-effect": "1.1.1" 3338 + }, 3339 + "peerDependencies": { 3340 + "@types/react": "*", 3341 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3342 + }, 3343 + "peerDependenciesMeta": { 3344 + "@types/react": { 3345 + "optional": true 3346 + } 3347 + } 3348 + }, 3349 + "node_modules/@radix-ui/react-visually-hidden": { 3350 + "version": "1.2.3", 3351 + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", 3352 + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", 3353 + "dependencies": { 3354 + "@radix-ui/react-primitive": "2.1.3" 3355 + }, 3356 + "peerDependencies": { 3357 + "@types/react": "*", 3358 + "@types/react-dom": "*", 3359 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 3360 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 3361 + }, 3362 + "peerDependenciesMeta": { 3363 + "@types/react": { 3364 + "optional": true 3365 + }, 3366 + "@types/react-dom": { 3367 + "optional": true 3368 + } 3369 + } 3370 + }, 3371 + "node_modules/@radix-ui/rect": { 3372 + "version": "1.1.1", 3373 + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", 3374 + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" 3375 + }, 1771 3376 "node_modules/@rolldown/pluginutils": { 1772 3377 "version": "1.0.0-beta.27", 1773 3378 "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", ··· 2082 3687 "solid-js": "^1.6.12" 2083 3688 } 2084 3689 }, 3690 + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { 3691 + "version": "8.0.0", 3692 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", 3693 + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", 3694 + "dev": true, 3695 + "license": "MIT", 3696 + "engines": { 3697 + "node": ">=14" 3698 + }, 3699 + "funding": { 3700 + "type": "github", 3701 + "url": "https://github.com/sponsors/gregberge" 3702 + }, 3703 + "peerDependencies": { 3704 + "@babel/core": "^7.0.0-0" 3705 + } 3706 + }, 3707 + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { 3708 + "version": "8.0.0", 3709 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", 3710 + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", 3711 + "dev": true, 3712 + "license": "MIT", 3713 + "engines": { 3714 + "node": ">=14" 3715 + }, 3716 + "funding": { 3717 + "type": "github", 3718 + "url": "https://github.com/sponsors/gregberge" 3719 + }, 3720 + "peerDependencies": { 3721 + "@babel/core": "^7.0.0-0" 3722 + } 3723 + }, 3724 + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { 3725 + "version": "8.0.0", 3726 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", 3727 + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", 3728 + "dev": true, 3729 + "license": "MIT", 3730 + "engines": { 3731 + "node": ">=14" 3732 + }, 3733 + "funding": { 3734 + "type": "github", 3735 + "url": "https://github.com/sponsors/gregberge" 3736 + }, 3737 + "peerDependencies": { 3738 + "@babel/core": "^7.0.0-0" 3739 + } 3740 + }, 3741 + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { 3742 + "version": "8.0.0", 3743 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", 3744 + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", 3745 + "dev": true, 3746 + "license": "MIT", 3747 + "engines": { 3748 + "node": ">=14" 3749 + }, 3750 + "funding": { 3751 + "type": "github", 3752 + "url": "https://github.com/sponsors/gregberge" 3753 + }, 3754 + "peerDependencies": { 3755 + "@babel/core": "^7.0.0-0" 3756 + } 3757 + }, 3758 + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { 3759 + "version": "8.0.0", 3760 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", 3761 + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", 3762 + "dev": true, 3763 + "license": "MIT", 3764 + "engines": { 3765 + "node": ">=14" 3766 + }, 3767 + "funding": { 3768 + "type": "github", 3769 + "url": "https://github.com/sponsors/gregberge" 3770 + }, 3771 + "peerDependencies": { 3772 + "@babel/core": "^7.0.0-0" 3773 + } 3774 + }, 3775 + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { 3776 + "version": "8.0.0", 3777 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", 3778 + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", 3779 + "dev": true, 3780 + "license": "MIT", 3781 + "engines": { 3782 + "node": ">=14" 3783 + }, 3784 + "funding": { 3785 + "type": "github", 3786 + "url": "https://github.com/sponsors/gregberge" 3787 + }, 3788 + "peerDependencies": { 3789 + "@babel/core": "^7.0.0-0" 3790 + } 3791 + }, 3792 + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { 3793 + "version": "8.1.0", 3794 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", 3795 + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", 3796 + "dev": true, 3797 + "license": "MIT", 3798 + "engines": { 3799 + "node": ">=14" 3800 + }, 3801 + "funding": { 3802 + "type": "github", 3803 + "url": "https://github.com/sponsors/gregberge" 3804 + }, 3805 + "peerDependencies": { 3806 + "@babel/core": "^7.0.0-0" 3807 + } 3808 + }, 3809 + "node_modules/@svgr/babel-plugin-transform-svg-component": { 3810 + "version": "8.0.0", 3811 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", 3812 + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", 3813 + "dev": true, 3814 + "license": "MIT", 3815 + "engines": { 3816 + "node": ">=12" 3817 + }, 3818 + "funding": { 3819 + "type": "github", 3820 + "url": "https://github.com/sponsors/gregberge" 3821 + }, 3822 + "peerDependencies": { 3823 + "@babel/core": "^7.0.0-0" 3824 + } 3825 + }, 3826 + "node_modules/@svgr/babel-preset": { 3827 + "version": "8.1.0", 3828 + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", 3829 + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", 3830 + "dev": true, 3831 + "license": "MIT", 3832 + "dependencies": { 3833 + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", 3834 + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", 3835 + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", 3836 + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", 3837 + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", 3838 + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", 3839 + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", 3840 + "@svgr/babel-plugin-transform-svg-component": "8.0.0" 3841 + }, 3842 + "engines": { 3843 + "node": ">=14" 3844 + }, 3845 + "funding": { 3846 + "type": "github", 3847 + "url": "https://github.com/sponsors/gregberge" 3848 + }, 3849 + "peerDependencies": { 3850 + "@babel/core": "^7.0.0-0" 3851 + } 3852 + }, 3853 + "node_modules/@svgr/core": { 3854 + "version": "8.1.0", 3855 + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", 3856 + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", 3857 + "dev": true, 3858 + "license": "MIT", 3859 + "dependencies": { 3860 + "@babel/core": "^7.21.3", 3861 + "@svgr/babel-preset": "8.1.0", 3862 + "camelcase": "^6.2.0", 3863 + "cosmiconfig": "^8.1.3", 3864 + "snake-case": "^3.0.4" 3865 + }, 3866 + "engines": { 3867 + "node": ">=14" 3868 + }, 3869 + "funding": { 3870 + "type": "github", 3871 + "url": "https://github.com/sponsors/gregberge" 3872 + } 3873 + }, 3874 + "node_modules/@svgr/hast-util-to-babel-ast": { 3875 + "version": "8.0.0", 3876 + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", 3877 + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", 3878 + "dev": true, 3879 + "license": "MIT", 3880 + "dependencies": { 3881 + "@babel/types": "^7.21.3", 3882 + "entities": "^4.4.0" 3883 + }, 3884 + "engines": { 3885 + "node": ">=14" 3886 + }, 3887 + "funding": { 3888 + "type": "github", 3889 + "url": "https://github.com/sponsors/gregberge" 3890 + } 3891 + }, 3892 + "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": { 3893 + "version": "4.5.0", 3894 + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 3895 + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 3896 + "dev": true, 3897 + "license": "BSD-2-Clause", 3898 + "engines": { 3899 + "node": ">=0.12" 3900 + }, 3901 + "funding": { 3902 + "url": "https://github.com/fb55/entities?sponsor=1" 3903 + } 3904 + }, 3905 + "node_modules/@svgr/plugin-jsx": { 3906 + "version": "8.1.0", 3907 + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", 3908 + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", 3909 + "dev": true, 3910 + "license": "MIT", 3911 + "dependencies": { 3912 + "@babel/core": "^7.21.3", 3913 + "@svgr/babel-preset": "8.1.0", 3914 + "@svgr/hast-util-to-babel-ast": "8.0.0", 3915 + "svg-parser": "^2.0.4" 3916 + }, 3917 + "engines": { 3918 + "node": ">=14" 3919 + }, 3920 + "funding": { 3921 + "type": "github", 3922 + "url": "https://github.com/sponsors/gregberge" 3923 + }, 3924 + "peerDependencies": { 3925 + "@svgr/core": "*" 3926 + } 3927 + }, 2085 3928 "node_modules/@svta/common-media-library": { 2086 3929 "version": "0.12.4", 2087 3930 "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.12.4.tgz", ··· 2909 4752 "@types/react": "^19.0.0" 2910 4753 } 2911 4754 }, 4755 + "node_modules/@types/trusted-types": { 4756 + "version": "2.0.7", 4757 + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", 4758 + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", 4759 + "license": "MIT", 4760 + "optional": true 4761 + }, 2912 4762 "node_modules/@typescript-eslint/eslint-plugin": { 2913 4763 "version": "8.46.1", 2914 4764 "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", ··· 3433 5283 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 3434 5284 "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 3435 5285 "dev": true, 3436 - "license": "Python-2.0", 3437 - "peer": true 5286 + "license": "Python-2.0" 5287 + }, 5288 + "node_modules/aria-hidden": { 5289 + "version": "1.2.6", 5290 + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", 5291 + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", 5292 + "dependencies": { 5293 + "tslib": "^2.0.0" 5294 + }, 5295 + "engines": { 5296 + "node": ">=10" 5297 + } 3438 5298 }, 3439 5299 "node_modules/aria-query": { 3440 5300 "version": "5.3.0", ··· 3846 5706 "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 3847 5707 "dev": true, 3848 5708 "license": "MIT", 3849 - "peer": true, 3850 5709 "engines": { 3851 5710 "node": ">=6" 3852 5711 } 3853 5712 }, 5713 + "node_modules/camelcase": { 5714 + "version": "6.3.0", 5715 + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 5716 + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 5717 + "dev": true, 5718 + "license": "MIT", 5719 + "engines": { 5720 + "node": ">=10" 5721 + }, 5722 + "funding": { 5723 + "url": "https://github.com/sponsors/sindresorhus" 5724 + } 5725 + }, 3854 5726 "node_modules/caniuse-lite": { 3855 5727 "version": "1.0.30001737", 3856 5728 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", ··· 4041 5913 "dev": true, 4042 5914 "license": "MIT" 4043 5915 }, 5916 + "node_modules/confbox": { 5917 + "version": "0.2.2", 5918 + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", 5919 + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", 5920 + "dev": true, 5921 + "license": "MIT" 5922 + }, 4044 5923 "node_modules/convert-source-map": { 4045 5924 "version": "2.0.0", 4046 5925 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", ··· 4064 5943 "url": "https://opencollective.com/core-js" 4065 5944 } 4066 5945 }, 5946 + "node_modules/cosmiconfig": { 5947 + "version": "8.3.6", 5948 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", 5949 + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", 5950 + "dev": true, 5951 + "license": "MIT", 5952 + "dependencies": { 5953 + "import-fresh": "^3.3.0", 5954 + "js-yaml": "^4.1.0", 5955 + "parse-json": "^5.2.0", 5956 + "path-type": "^4.0.0" 5957 + }, 5958 + "engines": { 5959 + "node": ">=14" 5960 + }, 5961 + "funding": { 5962 + "url": "https://github.com/sponsors/d-fischer" 5963 + }, 5964 + "peerDependencies": { 5965 + "typescript": ">=4.9.5" 5966 + }, 5967 + "peerDependenciesMeta": { 5968 + "typescript": { 5969 + "optional": true 5970 + } 5971 + } 5972 + }, 4067 5973 "node_modules/cross-spawn": { 4068 5974 "version": "7.0.6", 4069 5975 "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", ··· 4203 6109 } 4204 6110 }, 4205 6111 "node_modules/debug": { 4206 - "version": "4.4.1", 4207 - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 4208 - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 6112 + "version": "4.4.3", 6113 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 6114 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 4209 6115 "license": "MIT", 4210 6116 "dependencies": { 4211 6117 "ms": "^2.1.3" ··· 4299 6205 "node": ">=8" 4300 6206 } 4301 6207 }, 6208 + "node_modules/detect-node-es": { 6209 + "version": "1.1.0", 6210 + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", 6211 + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" 6212 + }, 4302 6213 "node_modules/diff": { 4303 6214 "version": "8.0.2", 4304 6215 "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", ··· 4328 6239 "dev": true, 4329 6240 "license": "MIT" 4330 6241 }, 6242 + "node_modules/dompurify": { 6243 + "version": "3.3.0", 6244 + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", 6245 + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", 6246 + "license": "(MPL-2.0 OR Apache-2.0)", 6247 + "optionalDependencies": { 6248 + "@types/trusted-types": "^2.0.7" 6249 + } 6250 + }, 6251 + "node_modules/dot-case": { 6252 + "version": "3.0.4", 6253 + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", 6254 + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", 6255 + "dev": true, 6256 + "license": "MIT", 6257 + "dependencies": { 6258 + "no-case": "^3.0.4", 6259 + "tslib": "^2.0.3" 6260 + } 6261 + }, 4331 6262 "node_modules/dunder-proto": { 4332 6263 "version": "1.0.1", 4333 6264 "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", ··· 4373 6304 }, 4374 6305 "funding": { 4375 6306 "url": "https://github.com/fb55/entities?sponsor=1" 6307 + } 6308 + }, 6309 + "node_modules/error-ex": { 6310 + "version": "1.3.4", 6311 + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", 6312 + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", 6313 + "dev": true, 6314 + "license": "MIT", 6315 + "dependencies": { 6316 + "is-arrayish": "^0.2.1" 4376 6317 } 4377 6318 }, 4378 6319 "node_modules/es-abstract": { ··· 5036 6977 "node": ">=0.10.0" 5037 6978 } 5038 6979 }, 6980 + "node_modules/eventemitter3": { 6981 + "version": "5.0.1", 6982 + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", 6983 + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", 6984 + "license": "MIT" 6985 + }, 5039 6986 "node_modules/expect-type": { 5040 6987 "version": "1.2.2", 5041 6988 "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", ··· 5045 6992 "engines": { 5046 6993 "node": ">=12.0.0" 5047 6994 } 6995 + }, 6996 + "node_modules/exsolve": { 6997 + "version": "1.0.7", 6998 + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", 6999 + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", 7000 + "dev": true, 7001 + "license": "MIT" 5048 7002 }, 5049 7003 "node_modules/fast-deep-equal": { 5050 7004 "version": "3.1.3", ··· 5275 7229 }, 5276 7230 "funding": { 5277 7231 "url": "https://github.com/sponsors/ljharb" 7232 + } 7233 + }, 7234 + "node_modules/get-nonce": { 7235 + "version": "1.0.1", 7236 + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", 7237 + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", 7238 + "engines": { 7239 + "node": ">=6" 5278 7240 } 5279 7241 }, 5280 7242 "node_modules/get-proto": { ··· 5584 7546 "node": ">= 14" 5585 7547 } 5586 7548 }, 7549 + "node_modules/i": { 7550 + "version": "0.3.7", 7551 + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", 7552 + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", 7553 + "engines": { 7554 + "node": ">=0.4" 7555 + } 7556 + }, 7557 + "node_modules/iconify-icon": { 7558 + "version": "3.0.1", 7559 + "resolved": "https://registry.npmjs.org/iconify-icon/-/iconify-icon-3.0.1.tgz", 7560 + "integrity": "sha512-M3/kH3C+e/ufhmQuOSYSb1Ri1ImJ+ZEQYcVRMKnlSc8Nrdoy+iY9YvFnplX8t/3aCRuo5wN4RVPtCSHGnbt8dg==", 7561 + "dev": true, 7562 + "license": "MIT", 7563 + "dependencies": { 7564 + "@iconify/types": "^2.0.0" 7565 + }, 7566 + "funding": { 7567 + "url": "https://github.com/sponsors/cyberalien" 7568 + } 7569 + }, 5587 7570 "node_modules/iconv-lite": { 5588 7571 "version": "0.6.3", 5589 7572 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", ··· 5626 7609 "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 5627 7610 "dev": true, 5628 7611 "license": "MIT", 5629 - "peer": true, 5630 7612 "dependencies": { 5631 7613 "parent-module": "^1.0.0", 5632 7614 "resolve-from": "^4.0.0" ··· 5714 7696 "funding": { 5715 7697 "url": "https://github.com/sponsors/ljharb" 5716 7698 } 7699 + }, 7700 + "node_modules/is-arrayish": { 7701 + "version": "0.2.1", 7702 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 7703 + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", 7704 + "dev": true, 7705 + "license": "MIT" 5717 7706 }, 5718 7707 "node_modules/is-async-function": { 5719 7708 "version": "2.1.1", ··· 6238 8227 "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 6239 8228 "dev": true, 6240 8229 "license": "MIT", 6241 - "peer": true, 6242 8230 "dependencies": { 6243 8231 "argparse": "^2.0.1" 6244 8232 }, ··· 6306 8294 "license": "MIT", 6307 8295 "peer": true 6308 8296 }, 8297 + "node_modules/json-parse-even-better-errors": { 8298 + "version": "2.3.1", 8299 + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 8300 + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 8301 + "dev": true, 8302 + "license": "MIT" 8303 + }, 6309 8304 "node_modules/json-schema-traverse": { 6310 8305 "version": "0.4.1", 6311 8306 "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", ··· 6361 8356 "json-buffer": "3.0.1" 6362 8357 } 6363 8358 }, 8359 + "node_modules/kolorist": { 8360 + "version": "1.8.0", 8361 + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", 8362 + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", 8363 + "dev": true, 8364 + "license": "MIT" 8365 + }, 6364 8366 "node_modules/levn": { 6365 8367 "version": "0.4.1", 6366 8368 "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", ··· 6613 8615 "url": "https://opencollective.com/parcel" 6614 8616 } 6615 8617 }, 8618 + "node_modules/lines-and-columns": { 8619 + "version": "1.2.4", 8620 + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 8621 + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", 8622 + "dev": true, 8623 + "license": "MIT" 8624 + }, 8625 + "node_modules/local-pkg": { 8626 + "version": "1.1.2", 8627 + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", 8628 + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", 8629 + "dev": true, 8630 + "license": "MIT", 8631 + "dependencies": { 8632 + "mlly": "^1.7.4", 8633 + "pkg-types": "^2.3.0", 8634 + "quansync": "^0.2.11" 8635 + }, 8636 + "engines": { 8637 + "node": ">=14" 8638 + }, 8639 + "funding": { 8640 + "url": "https://github.com/sponsors/antfu" 8641 + } 8642 + }, 6616 8643 "node_modules/localforage": { 6617 8644 "version": "1.10.0", 6618 8645 "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", ··· 6639 8666 "url": "https://github.com/sponsors/sindresorhus" 6640 8667 } 6641 8668 }, 8669 + "node_modules/lodash.clonedeep": { 8670 + "version": "4.5.0", 8671 + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 8672 + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", 8673 + "license": "MIT" 8674 + }, 6642 8675 "node_modules/lodash.merge": { 6643 8676 "version": "4.6.2", 6644 8677 "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", ··· 6666 8699 "dev": true, 6667 8700 "license": "MIT" 6668 8701 }, 8702 + "node_modules/lower-case": { 8703 + "version": "2.0.2", 8704 + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 8705 + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 8706 + "dev": true, 8707 + "license": "MIT", 8708 + "dependencies": { 8709 + "tslib": "^2.0.3" 8710 + } 8711 + }, 6669 8712 "node_modules/lru-cache": { 6670 8713 "version": "5.1.1", 6671 8714 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", ··· 6686 8729 } 6687 8730 }, 6688 8731 "node_modules/magic-string": { 6689 - "version": "0.30.18", 6690 - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", 6691 - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", 8732 + "version": "0.30.19", 8733 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", 8734 + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", 6692 8735 "license": "MIT", 6693 8736 "dependencies": { 6694 8737 "@jridgewell/sourcemap-codec": "^1.5.5" ··· 6793 8836 "url": "https://github.com/sponsors/isaacs" 6794 8837 } 6795 8838 }, 8839 + "node_modules/mlly": { 8840 + "version": "1.8.0", 8841 + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", 8842 + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", 8843 + "dev": true, 8844 + "license": "MIT", 8845 + "dependencies": { 8846 + "acorn": "^8.15.0", 8847 + "pathe": "^2.0.3", 8848 + "pkg-types": "^1.3.1", 8849 + "ufo": "^1.6.1" 8850 + } 8851 + }, 8852 + "node_modules/mlly/node_modules/confbox": { 8853 + "version": "0.1.8", 8854 + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", 8855 + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", 8856 + "dev": true, 8857 + "license": "MIT" 8858 + }, 8859 + "node_modules/mlly/node_modules/pkg-types": { 8860 + "version": "1.3.1", 8861 + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", 8862 + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", 8863 + "dev": true, 8864 + "license": "MIT", 8865 + "dependencies": { 8866 + "confbox": "^0.1.8", 8867 + "mlly": "^1.7.4", 8868 + "pathe": "^2.0.1" 8869 + } 8870 + }, 6796 8871 "node_modules/ms": { 6797 8872 "version": "2.1.3", 6798 8873 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", ··· 6842 8917 "dev": true, 6843 8918 "license": "MIT" 6844 8919 }, 8920 + "node_modules/no-case": { 8921 + "version": "3.0.4", 8922 + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 8923 + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 8924 + "dev": true, 8925 + "license": "MIT", 8926 + "dependencies": { 8927 + "lower-case": "^2.0.2", 8928 + "tslib": "^2.0.3" 8929 + } 8930 + }, 6845 8931 "node_modules/node-releases": { 6846 8932 "version": "2.0.19", 6847 8933 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", ··· 6857 8943 "node": ">=0.10.0" 6858 8944 } 6859 8945 }, 8946 + "node_modules/npm": { 8947 + "version": "11.6.2", 8948 + "resolved": "https://registry.npmjs.org/npm/-/npm-11.6.2.tgz", 8949 + "integrity": "sha512-7iKzNfy8lWYs3zq4oFPa8EXZz5xt9gQNKJZau3B1ErLBb6bF7sBJ00x09485DOvRT2l5Gerbl3VlZNT57MxJVA==", 8950 + "bundleDependencies": [ 8951 + "@isaacs/string-locale-compare", 8952 + "@npmcli/arborist", 8953 + "@npmcli/config", 8954 + "@npmcli/fs", 8955 + "@npmcli/map-workspaces", 8956 + "@npmcli/package-json", 8957 + "@npmcli/promise-spawn", 8958 + "@npmcli/redact", 8959 + "@npmcli/run-script", 8960 + "@sigstore/tuf", 8961 + "abbrev", 8962 + "archy", 8963 + "cacache", 8964 + "chalk", 8965 + "ci-info", 8966 + "cli-columns", 8967 + "fastest-levenshtein", 8968 + "fs-minipass", 8969 + "glob", 8970 + "graceful-fs", 8971 + "hosted-git-info", 8972 + "ini", 8973 + "init-package-json", 8974 + "is-cidr", 8975 + "json-parse-even-better-errors", 8976 + "libnpmaccess", 8977 + "libnpmdiff", 8978 + "libnpmexec", 8979 + "libnpmfund", 8980 + "libnpmorg", 8981 + "libnpmpack", 8982 + "libnpmpublish", 8983 + "libnpmsearch", 8984 + "libnpmteam", 8985 + "libnpmversion", 8986 + "make-fetch-happen", 8987 + "minimatch", 8988 + "minipass", 8989 + "minipass-pipeline", 8990 + "ms", 8991 + "node-gyp", 8992 + "nopt", 8993 + "npm-audit-report", 8994 + "npm-install-checks", 8995 + "npm-package-arg", 8996 + "npm-pick-manifest", 8997 + "npm-profile", 8998 + "npm-registry-fetch", 8999 + "npm-user-validate", 9000 + "p-map", 9001 + "pacote", 9002 + "parse-conflict-json", 9003 + "proc-log", 9004 + "qrcode-terminal", 9005 + "read", 9006 + "semver", 9007 + "spdx-expression-parse", 9008 + "ssri", 9009 + "supports-color", 9010 + "tar", 9011 + "text-table", 9012 + "tiny-relative-date", 9013 + "treeverse", 9014 + "validate-npm-package-name", 9015 + "which" 9016 + ], 9017 + "license": "Artistic-2.0", 9018 + "workspaces": [ 9019 + "docs", 9020 + "smoke-tests", 9021 + "mock-globals", 9022 + "mock-registry", 9023 + "workspaces/*" 9024 + ], 9025 + "dependencies": { 9026 + "@isaacs/string-locale-compare": "^1.1.0", 9027 + "@npmcli/arborist": "^9.1.6", 9028 + "@npmcli/config": "^10.4.2", 9029 + "@npmcli/fs": "^4.0.0", 9030 + "@npmcli/map-workspaces": "^5.0.0", 9031 + "@npmcli/package-json": "^7.0.1", 9032 + "@npmcli/promise-spawn": "^8.0.3", 9033 + "@npmcli/redact": "^3.2.2", 9034 + "@npmcli/run-script": "^10.0.0", 9035 + "@sigstore/tuf": "^4.0.0", 9036 + "abbrev": "^3.0.1", 9037 + "archy": "~1.0.0", 9038 + "cacache": "^20.0.1", 9039 + "chalk": "^5.6.2", 9040 + "ci-info": "^4.3.1", 9041 + "cli-columns": "^4.0.0", 9042 + "fastest-levenshtein": "^1.0.16", 9043 + "fs-minipass": "^3.0.3", 9044 + "glob": "^11.0.3", 9045 + "graceful-fs": "^4.2.11", 9046 + "hosted-git-info": "^9.0.2", 9047 + "ini": "^5.0.0", 9048 + "init-package-json": "^8.2.2", 9049 + "is-cidr": "^6.0.1", 9050 + "json-parse-even-better-errors": "^4.0.0", 9051 + "libnpmaccess": "^10.0.3", 9052 + "libnpmdiff": "^8.0.9", 9053 + "libnpmexec": "^10.1.8", 9054 + "libnpmfund": "^7.0.9", 9055 + "libnpmorg": "^8.0.1", 9056 + "libnpmpack": "^9.0.9", 9057 + "libnpmpublish": "^11.1.2", 9058 + "libnpmsearch": "^9.0.1", 9059 + "libnpmteam": "^8.0.2", 9060 + "libnpmversion": "^8.0.2", 9061 + "make-fetch-happen": "^15.0.2", 9062 + "minimatch": "^10.0.3", 9063 + "minipass": "^7.1.1", 9064 + "minipass-pipeline": "^1.2.4", 9065 + "ms": "^2.1.2", 9066 + "node-gyp": "^11.4.2", 9067 + "nopt": "^8.1.0", 9068 + "npm-audit-report": "^6.0.0", 9069 + "npm-install-checks": "^7.1.2", 9070 + "npm-package-arg": "^13.0.1", 9071 + "npm-pick-manifest": "^11.0.1", 9072 + "npm-profile": "^12.0.0", 9073 + "npm-registry-fetch": "^19.0.0", 9074 + "npm-user-validate": "^3.0.0", 9075 + "p-map": "^7.0.3", 9076 + "pacote": "^21.0.3", 9077 + "parse-conflict-json": "^4.0.0", 9078 + "proc-log": "^5.0.0", 9079 + "qrcode-terminal": "^0.12.0", 9080 + "read": "^4.1.0", 9081 + "semver": "^7.7.3", 9082 + "spdx-expression-parse": "^4.0.0", 9083 + "ssri": "^12.0.0", 9084 + "supports-color": "^10.2.2", 9085 + "tar": "^7.5.1", 9086 + "text-table": "~0.2.0", 9087 + "tiny-relative-date": "^2.0.2", 9088 + "treeverse": "^3.0.0", 9089 + "validate-npm-package-name": "^6.0.2", 9090 + "which": "^5.0.0" 9091 + }, 9092 + "bin": { 9093 + "npm": "bin/npm-cli.js", 9094 + "npx": "bin/npx-cli.js" 9095 + }, 9096 + "engines": { 9097 + "node": "^20.17.0 || >=22.9.0" 9098 + } 9099 + }, 9100 + "node_modules/npm/node_modules/@isaacs/balanced-match": { 9101 + "version": "4.0.1", 9102 + "inBundle": true, 9103 + "license": "MIT", 9104 + "engines": { 9105 + "node": "20 || >=22" 9106 + } 9107 + }, 9108 + "node_modules/npm/node_modules/@isaacs/brace-expansion": { 9109 + "version": "5.0.0", 9110 + "inBundle": true, 9111 + "license": "MIT", 9112 + "dependencies": { 9113 + "@isaacs/balanced-match": "^4.0.1" 9114 + }, 9115 + "engines": { 9116 + "node": "20 || >=22" 9117 + } 9118 + }, 9119 + "node_modules/npm/node_modules/@isaacs/cliui": { 9120 + "version": "8.0.2", 9121 + "inBundle": true, 9122 + "license": "ISC", 9123 + "dependencies": { 9124 + "string-width": "^5.1.2", 9125 + "string-width-cjs": "npm:string-width@^4.2.0", 9126 + "strip-ansi": "^7.0.1", 9127 + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 9128 + "wrap-ansi": "^8.1.0", 9129 + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 9130 + }, 9131 + "engines": { 9132 + "node": ">=12" 9133 + } 9134 + }, 9135 + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { 9136 + "version": "6.2.2", 9137 + "inBundle": true, 9138 + "license": "MIT", 9139 + "engines": { 9140 + "node": ">=12" 9141 + }, 9142 + "funding": { 9143 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 9144 + } 9145 + }, 9146 + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { 9147 + "version": "9.2.2", 9148 + "inBundle": true, 9149 + "license": "MIT" 9150 + }, 9151 + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { 9152 + "version": "5.1.2", 9153 + "inBundle": true, 9154 + "license": "MIT", 9155 + "dependencies": { 9156 + "eastasianwidth": "^0.2.0", 9157 + "emoji-regex": "^9.2.2", 9158 + "strip-ansi": "^7.0.1" 9159 + }, 9160 + "engines": { 9161 + "node": ">=12" 9162 + }, 9163 + "funding": { 9164 + "url": "https://github.com/sponsors/sindresorhus" 9165 + } 9166 + }, 9167 + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { 9168 + "version": "7.1.2", 9169 + "inBundle": true, 9170 + "license": "MIT", 9171 + "dependencies": { 9172 + "ansi-regex": "^6.0.1" 9173 + }, 9174 + "engines": { 9175 + "node": ">=12" 9176 + }, 9177 + "funding": { 9178 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 9179 + } 9180 + }, 9181 + "node_modules/npm/node_modules/@isaacs/fs-minipass": { 9182 + "version": "4.0.1", 9183 + "inBundle": true, 9184 + "license": "ISC", 9185 + "dependencies": { 9186 + "minipass": "^7.0.4" 9187 + }, 9188 + "engines": { 9189 + "node": ">=18.0.0" 9190 + } 9191 + }, 9192 + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { 9193 + "version": "1.1.0", 9194 + "inBundle": true, 9195 + "license": "ISC" 9196 + }, 9197 + "node_modules/npm/node_modules/@npmcli/agent": { 9198 + "version": "4.0.0", 9199 + "inBundle": true, 9200 + "license": "ISC", 9201 + "dependencies": { 9202 + "agent-base": "^7.1.0", 9203 + "http-proxy-agent": "^7.0.0", 9204 + "https-proxy-agent": "^7.0.1", 9205 + "lru-cache": "^11.2.1", 9206 + "socks-proxy-agent": "^8.0.3" 9207 + }, 9208 + "engines": { 9209 + "node": "^20.17.0 || >=22.9.0" 9210 + } 9211 + }, 9212 + "node_modules/npm/node_modules/@npmcli/arborist": { 9213 + "version": "9.1.6", 9214 + "inBundle": true, 9215 + "license": "ISC", 9216 + "dependencies": { 9217 + "@isaacs/string-locale-compare": "^1.1.0", 9218 + "@npmcli/fs": "^4.0.0", 9219 + "@npmcli/installed-package-contents": "^3.0.0", 9220 + "@npmcli/map-workspaces": "^5.0.0", 9221 + "@npmcli/metavuln-calculator": "^9.0.2", 9222 + "@npmcli/name-from-folder": "^3.0.0", 9223 + "@npmcli/node-gyp": "^4.0.0", 9224 + "@npmcli/package-json": "^7.0.0", 9225 + "@npmcli/query": "^4.0.0", 9226 + "@npmcli/redact": "^3.0.0", 9227 + "@npmcli/run-script": "^10.0.0", 9228 + "bin-links": "^5.0.0", 9229 + "cacache": "^20.0.1", 9230 + "common-ancestor-path": "^1.0.1", 9231 + "hosted-git-info": "^9.0.0", 9232 + "json-stringify-nice": "^1.1.4", 9233 + "lru-cache": "^11.2.1", 9234 + "minimatch": "^10.0.3", 9235 + "nopt": "^8.0.0", 9236 + "npm-install-checks": "^7.1.0", 9237 + "npm-package-arg": "^13.0.0", 9238 + "npm-pick-manifest": "^11.0.1", 9239 + "npm-registry-fetch": "^19.0.0", 9240 + "pacote": "^21.0.2", 9241 + "parse-conflict-json": "^4.0.0", 9242 + "proc-log": "^5.0.0", 9243 + "proggy": "^3.0.0", 9244 + "promise-all-reject-late": "^1.0.0", 9245 + "promise-call-limit": "^3.0.1", 9246 + "semver": "^7.3.7", 9247 + "ssri": "^12.0.0", 9248 + "treeverse": "^3.0.0", 9249 + "walk-up-path": "^4.0.0" 9250 + }, 9251 + "bin": { 9252 + "arborist": "bin/index.js" 9253 + }, 9254 + "engines": { 9255 + "node": "^20.17.0 || >=22.9.0" 9256 + } 9257 + }, 9258 + "node_modules/npm/node_modules/@npmcli/config": { 9259 + "version": "10.4.2", 9260 + "inBundle": true, 9261 + "license": "ISC", 9262 + "dependencies": { 9263 + "@npmcli/map-workspaces": "^5.0.0", 9264 + "@npmcli/package-json": "^7.0.0", 9265 + "ci-info": "^4.0.0", 9266 + "ini": "^5.0.0", 9267 + "nopt": "^8.1.0", 9268 + "proc-log": "^5.0.0", 9269 + "semver": "^7.3.5", 9270 + "walk-up-path": "^4.0.0" 9271 + }, 9272 + "engines": { 9273 + "node": "^20.17.0 || >=22.9.0" 9274 + } 9275 + }, 9276 + "node_modules/npm/node_modules/@npmcli/fs": { 9277 + "version": "4.0.0", 9278 + "inBundle": true, 9279 + "license": "ISC", 9280 + "dependencies": { 9281 + "semver": "^7.3.5" 9282 + }, 9283 + "engines": { 9284 + "node": "^18.17.0 || >=20.5.0" 9285 + } 9286 + }, 9287 + "node_modules/npm/node_modules/@npmcli/git": { 9288 + "version": "7.0.0", 9289 + "inBundle": true, 9290 + "license": "ISC", 9291 + "dependencies": { 9292 + "@npmcli/promise-spawn": "^8.0.0", 9293 + "ini": "^5.0.0", 9294 + "lru-cache": "^11.2.1", 9295 + "npm-pick-manifest": "^11.0.1", 9296 + "proc-log": "^5.0.0", 9297 + "promise-retry": "^2.0.1", 9298 + "semver": "^7.3.5", 9299 + "which": "^5.0.0" 9300 + }, 9301 + "engines": { 9302 + "node": "^20.17.0 || >=22.9.0" 9303 + } 9304 + }, 9305 + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { 9306 + "version": "3.0.0", 9307 + "inBundle": true, 9308 + "license": "ISC", 9309 + "dependencies": { 9310 + "npm-bundled": "^4.0.0", 9311 + "npm-normalize-package-bin": "^4.0.0" 9312 + }, 9313 + "bin": { 9314 + "installed-package-contents": "bin/index.js" 9315 + }, 9316 + "engines": { 9317 + "node": "^18.17.0 || >=20.5.0" 9318 + } 9319 + }, 9320 + "node_modules/npm/node_modules/@npmcli/map-workspaces": { 9321 + "version": "5.0.0", 9322 + "inBundle": true, 9323 + "license": "ISC", 9324 + "dependencies": { 9325 + "@npmcli/name-from-folder": "^3.0.0", 9326 + "@npmcli/package-json": "^7.0.0", 9327 + "glob": "^11.0.3", 9328 + "minimatch": "^10.0.3" 9329 + }, 9330 + "engines": { 9331 + "node": "^20.17.0 || >=22.9.0" 9332 + } 9333 + }, 9334 + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { 9335 + "version": "9.0.2", 9336 + "inBundle": true, 9337 + "license": "ISC", 9338 + "dependencies": { 9339 + "cacache": "^20.0.0", 9340 + "json-parse-even-better-errors": "^4.0.0", 9341 + "pacote": "^21.0.0", 9342 + "proc-log": "^5.0.0", 9343 + "semver": "^7.3.5" 9344 + }, 9345 + "engines": { 9346 + "node": "^20.17.0 || >=22.9.0" 9347 + } 9348 + }, 9349 + "node_modules/npm/node_modules/@npmcli/name-from-folder": { 9350 + "version": "3.0.0", 9351 + "inBundle": true, 9352 + "license": "ISC", 9353 + "engines": { 9354 + "node": "^18.17.0 || >=20.5.0" 9355 + } 9356 + }, 9357 + "node_modules/npm/node_modules/@npmcli/node-gyp": { 9358 + "version": "4.0.0", 9359 + "inBundle": true, 9360 + "license": "ISC", 9361 + "engines": { 9362 + "node": "^18.17.0 || >=20.5.0" 9363 + } 9364 + }, 9365 + "node_modules/npm/node_modules/@npmcli/package-json": { 9366 + "version": "7.0.1", 9367 + "inBundle": true, 9368 + "license": "ISC", 9369 + "dependencies": { 9370 + "@npmcli/git": "^7.0.0", 9371 + "glob": "^11.0.3", 9372 + "hosted-git-info": "^9.0.0", 9373 + "json-parse-even-better-errors": "^4.0.0", 9374 + "proc-log": "^5.0.0", 9375 + "semver": "^7.5.3", 9376 + "validate-npm-package-license": "^3.0.4" 9377 + }, 9378 + "engines": { 9379 + "node": "^20.17.0 || >=22.9.0" 9380 + } 9381 + }, 9382 + "node_modules/npm/node_modules/@npmcli/promise-spawn": { 9383 + "version": "8.0.3", 9384 + "inBundle": true, 9385 + "license": "ISC", 9386 + "dependencies": { 9387 + "which": "^5.0.0" 9388 + }, 9389 + "engines": { 9390 + "node": "^18.17.0 || >=20.5.0" 9391 + } 9392 + }, 9393 + "node_modules/npm/node_modules/@npmcli/query": { 9394 + "version": "4.0.1", 9395 + "inBundle": true, 9396 + "license": "ISC", 9397 + "dependencies": { 9398 + "postcss-selector-parser": "^7.0.0" 9399 + }, 9400 + "engines": { 9401 + "node": "^18.17.0 || >=20.5.0" 9402 + } 9403 + }, 9404 + "node_modules/npm/node_modules/@npmcli/redact": { 9405 + "version": "3.2.2", 9406 + "inBundle": true, 9407 + "license": "ISC", 9408 + "engines": { 9409 + "node": "^18.17.0 || >=20.5.0" 9410 + } 9411 + }, 9412 + "node_modules/npm/node_modules/@npmcli/run-script": { 9413 + "version": "10.0.0", 9414 + "inBundle": true, 9415 + "license": "ISC", 9416 + "dependencies": { 9417 + "@npmcli/node-gyp": "^4.0.0", 9418 + "@npmcli/package-json": "^7.0.0", 9419 + "@npmcli/promise-spawn": "^8.0.0", 9420 + "node-gyp": "^11.0.0", 9421 + "proc-log": "^5.0.0", 9422 + "which": "^5.0.0" 9423 + }, 9424 + "engines": { 9425 + "node": "^20.17.0 || >=22.9.0" 9426 + } 9427 + }, 9428 + "node_modules/npm/node_modules/@pkgjs/parseargs": { 9429 + "version": "0.11.0", 9430 + "inBundle": true, 9431 + "license": "MIT", 9432 + "optional": true, 9433 + "engines": { 9434 + "node": ">=14" 9435 + } 9436 + }, 9437 + "node_modules/npm/node_modules/@sigstore/bundle": { 9438 + "version": "4.0.0", 9439 + "inBundle": true, 9440 + "license": "Apache-2.0", 9441 + "dependencies": { 9442 + "@sigstore/protobuf-specs": "^0.5.0" 9443 + }, 9444 + "engines": { 9445 + "node": "^20.17.0 || >=22.9.0" 9446 + } 9447 + }, 9448 + "node_modules/npm/node_modules/@sigstore/core": { 9449 + "version": "3.0.0", 9450 + "inBundle": true, 9451 + "license": "Apache-2.0", 9452 + "engines": { 9453 + "node": "^20.17.0 || >=22.9.0" 9454 + } 9455 + }, 9456 + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { 9457 + "version": "0.5.0", 9458 + "inBundle": true, 9459 + "license": "Apache-2.0", 9460 + "engines": { 9461 + "node": "^18.17.0 || >=20.5.0" 9462 + } 9463 + }, 9464 + "node_modules/npm/node_modules/@sigstore/sign": { 9465 + "version": "4.0.1", 9466 + "inBundle": true, 9467 + "license": "Apache-2.0", 9468 + "dependencies": { 9469 + "@sigstore/bundle": "^4.0.0", 9470 + "@sigstore/core": "^3.0.0", 9471 + "@sigstore/protobuf-specs": "^0.5.0", 9472 + "make-fetch-happen": "^15.0.2", 9473 + "proc-log": "^5.0.0", 9474 + "promise-retry": "^2.0.1" 9475 + }, 9476 + "engines": { 9477 + "node": "^20.17.0 || >=22.9.0" 9478 + } 9479 + }, 9480 + "node_modules/npm/node_modules/@sigstore/tuf": { 9481 + "version": "4.0.0", 9482 + "inBundle": true, 9483 + "license": "Apache-2.0", 9484 + "dependencies": { 9485 + "@sigstore/protobuf-specs": "^0.5.0", 9486 + "tuf-js": "^4.0.0" 9487 + }, 9488 + "engines": { 9489 + "node": "^20.17.0 || >=22.9.0" 9490 + } 9491 + }, 9492 + "node_modules/npm/node_modules/@sigstore/verify": { 9493 + "version": "3.0.0", 9494 + "inBundle": true, 9495 + "license": "Apache-2.0", 9496 + "dependencies": { 9497 + "@sigstore/bundle": "^4.0.0", 9498 + "@sigstore/core": "^3.0.0", 9499 + "@sigstore/protobuf-specs": "^0.5.0" 9500 + }, 9501 + "engines": { 9502 + "node": "^20.17.0 || >=22.9.0" 9503 + } 9504 + }, 9505 + "node_modules/npm/node_modules/@tufjs/canonical-json": { 9506 + "version": "2.0.0", 9507 + "inBundle": true, 9508 + "license": "MIT", 9509 + "engines": { 9510 + "node": "^16.14.0 || >=18.0.0" 9511 + } 9512 + }, 9513 + "node_modules/npm/node_modules/@tufjs/models": { 9514 + "version": "4.0.0", 9515 + "inBundle": true, 9516 + "license": "MIT", 9517 + "dependencies": { 9518 + "@tufjs/canonical-json": "2.0.0", 9519 + "minimatch": "^9.0.5" 9520 + }, 9521 + "engines": { 9522 + "node": "^20.17.0 || >=22.9.0" 9523 + } 9524 + }, 9525 + "node_modules/npm/node_modules/@tufjs/models/node_modules/minimatch": { 9526 + "version": "9.0.5", 9527 + "inBundle": true, 9528 + "license": "ISC", 9529 + "dependencies": { 9530 + "brace-expansion": "^2.0.1" 9531 + }, 9532 + "engines": { 9533 + "node": ">=16 || 14 >=14.17" 9534 + }, 9535 + "funding": { 9536 + "url": "https://github.com/sponsors/isaacs" 9537 + } 9538 + }, 9539 + "node_modules/npm/node_modules/abbrev": { 9540 + "version": "3.0.1", 9541 + "inBundle": true, 9542 + "license": "ISC", 9543 + "engines": { 9544 + "node": "^18.17.0 || >=20.5.0" 9545 + } 9546 + }, 9547 + "node_modules/npm/node_modules/agent-base": { 9548 + "version": "7.1.4", 9549 + "inBundle": true, 9550 + "license": "MIT", 9551 + "engines": { 9552 + "node": ">= 14" 9553 + } 9554 + }, 9555 + "node_modules/npm/node_modules/ansi-regex": { 9556 + "version": "5.0.1", 9557 + "inBundle": true, 9558 + "license": "MIT", 9559 + "engines": { 9560 + "node": ">=8" 9561 + } 9562 + }, 9563 + "node_modules/npm/node_modules/ansi-styles": { 9564 + "version": "6.2.3", 9565 + "inBundle": true, 9566 + "license": "MIT", 9567 + "engines": { 9568 + "node": ">=12" 9569 + }, 9570 + "funding": { 9571 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 9572 + } 9573 + }, 9574 + "node_modules/npm/node_modules/aproba": { 9575 + "version": "2.1.0", 9576 + "inBundle": true, 9577 + "license": "ISC" 9578 + }, 9579 + "node_modules/npm/node_modules/archy": { 9580 + "version": "1.0.0", 9581 + "inBundle": true, 9582 + "license": "MIT" 9583 + }, 9584 + "node_modules/npm/node_modules/balanced-match": { 9585 + "version": "1.0.2", 9586 + "inBundle": true, 9587 + "license": "MIT" 9588 + }, 9589 + "node_modules/npm/node_modules/bin-links": { 9590 + "version": "5.0.0", 9591 + "inBundle": true, 9592 + "license": "ISC", 9593 + "dependencies": { 9594 + "cmd-shim": "^7.0.0", 9595 + "npm-normalize-package-bin": "^4.0.0", 9596 + "proc-log": "^5.0.0", 9597 + "read-cmd-shim": "^5.0.0", 9598 + "write-file-atomic": "^6.0.0" 9599 + }, 9600 + "engines": { 9601 + "node": "^18.17.0 || >=20.5.0" 9602 + } 9603 + }, 9604 + "node_modules/npm/node_modules/binary-extensions": { 9605 + "version": "3.1.0", 9606 + "inBundle": true, 9607 + "license": "MIT", 9608 + "engines": { 9609 + "node": ">=18.20" 9610 + }, 9611 + "funding": { 9612 + "url": "https://github.com/sponsors/sindresorhus" 9613 + } 9614 + }, 9615 + "node_modules/npm/node_modules/brace-expansion": { 9616 + "version": "2.0.2", 9617 + "inBundle": true, 9618 + "license": "MIT", 9619 + "dependencies": { 9620 + "balanced-match": "^1.0.0" 9621 + } 9622 + }, 9623 + "node_modules/npm/node_modules/cacache": { 9624 + "version": "20.0.1", 9625 + "inBundle": true, 9626 + "license": "ISC", 9627 + "dependencies": { 9628 + "@npmcli/fs": "^4.0.0", 9629 + "fs-minipass": "^3.0.0", 9630 + "glob": "^11.0.3", 9631 + "lru-cache": "^11.1.0", 9632 + "minipass": "^7.0.3", 9633 + "minipass-collect": "^2.0.1", 9634 + "minipass-flush": "^1.0.5", 9635 + "minipass-pipeline": "^1.2.4", 9636 + "p-map": "^7.0.2", 9637 + "ssri": "^12.0.0", 9638 + "unique-filename": "^4.0.0" 9639 + }, 9640 + "engines": { 9641 + "node": "^20.17.0 || >=22.9.0" 9642 + } 9643 + }, 9644 + "node_modules/npm/node_modules/chalk": { 9645 + "version": "5.6.2", 9646 + "inBundle": true, 9647 + "license": "MIT", 9648 + "engines": { 9649 + "node": "^12.17.0 || ^14.13 || >=16.0.0" 9650 + }, 9651 + "funding": { 9652 + "url": "https://github.com/chalk/chalk?sponsor=1" 9653 + } 9654 + }, 9655 + "node_modules/npm/node_modules/chownr": { 9656 + "version": "3.0.0", 9657 + "inBundle": true, 9658 + "license": "BlueOak-1.0.0", 9659 + "engines": { 9660 + "node": ">=18" 9661 + } 9662 + }, 9663 + "node_modules/npm/node_modules/ci-info": { 9664 + "version": "4.3.1", 9665 + "funding": [ 9666 + { 9667 + "type": "github", 9668 + "url": "https://github.com/sponsors/sibiraj-s" 9669 + } 9670 + ], 9671 + "inBundle": true, 9672 + "license": "MIT", 9673 + "engines": { 9674 + "node": ">=8" 9675 + } 9676 + }, 9677 + "node_modules/npm/node_modules/cidr-regex": { 9678 + "version": "5.0.1", 9679 + "inBundle": true, 9680 + "license": "BSD-2-Clause", 9681 + "dependencies": { 9682 + "ip-regex": "5.0.0" 9683 + }, 9684 + "engines": { 9685 + "node": ">=20" 9686 + } 9687 + }, 9688 + "node_modules/npm/node_modules/cli-columns": { 9689 + "version": "4.0.0", 9690 + "inBundle": true, 9691 + "license": "MIT", 9692 + "dependencies": { 9693 + "string-width": "^4.2.3", 9694 + "strip-ansi": "^6.0.1" 9695 + }, 9696 + "engines": { 9697 + "node": ">= 10" 9698 + } 9699 + }, 9700 + "node_modules/npm/node_modules/cmd-shim": { 9701 + "version": "7.0.0", 9702 + "inBundle": true, 9703 + "license": "ISC", 9704 + "engines": { 9705 + "node": "^18.17.0 || >=20.5.0" 9706 + } 9707 + }, 9708 + "node_modules/npm/node_modules/color-convert": { 9709 + "version": "2.0.1", 9710 + "inBundle": true, 9711 + "license": "MIT", 9712 + "dependencies": { 9713 + "color-name": "~1.1.4" 9714 + }, 9715 + "engines": { 9716 + "node": ">=7.0.0" 9717 + } 9718 + }, 9719 + "node_modules/npm/node_modules/color-name": { 9720 + "version": "1.1.4", 9721 + "inBundle": true, 9722 + "license": "MIT" 9723 + }, 9724 + "node_modules/npm/node_modules/common-ancestor-path": { 9725 + "version": "1.0.1", 9726 + "inBundle": true, 9727 + "license": "ISC" 9728 + }, 9729 + "node_modules/npm/node_modules/cross-spawn": { 9730 + "version": "7.0.6", 9731 + "inBundle": true, 9732 + "license": "MIT", 9733 + "dependencies": { 9734 + "path-key": "^3.1.0", 9735 + "shebang-command": "^2.0.0", 9736 + "which": "^2.0.1" 9737 + }, 9738 + "engines": { 9739 + "node": ">= 8" 9740 + } 9741 + }, 9742 + "node_modules/npm/node_modules/cross-spawn/node_modules/isexe": { 9743 + "version": "2.0.0", 9744 + "inBundle": true, 9745 + "license": "ISC" 9746 + }, 9747 + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { 9748 + "version": "2.0.2", 9749 + "inBundle": true, 9750 + "license": "ISC", 9751 + "dependencies": { 9752 + "isexe": "^2.0.0" 9753 + }, 9754 + "bin": { 9755 + "node-which": "bin/node-which" 9756 + }, 9757 + "engines": { 9758 + "node": ">= 8" 9759 + } 9760 + }, 9761 + "node_modules/npm/node_modules/cssesc": { 9762 + "version": "3.0.0", 9763 + "inBundle": true, 9764 + "license": "MIT", 9765 + "bin": { 9766 + "cssesc": "bin/cssesc" 9767 + }, 9768 + "engines": { 9769 + "node": ">=4" 9770 + } 9771 + }, 9772 + "node_modules/npm/node_modules/debug": { 9773 + "version": "4.4.3", 9774 + "inBundle": true, 9775 + "license": "MIT", 9776 + "dependencies": { 9777 + "ms": "^2.1.3" 9778 + }, 9779 + "engines": { 9780 + "node": ">=6.0" 9781 + }, 9782 + "peerDependenciesMeta": { 9783 + "supports-color": { 9784 + "optional": true 9785 + } 9786 + } 9787 + }, 9788 + "node_modules/npm/node_modules/diff": { 9789 + "version": "8.0.2", 9790 + "inBundle": true, 9791 + "license": "BSD-3-Clause", 9792 + "engines": { 9793 + "node": ">=0.3.1" 9794 + } 9795 + }, 9796 + "node_modules/npm/node_modules/eastasianwidth": { 9797 + "version": "0.2.0", 9798 + "inBundle": true, 9799 + "license": "MIT" 9800 + }, 9801 + "node_modules/npm/node_modules/emoji-regex": { 9802 + "version": "8.0.0", 9803 + "inBundle": true, 9804 + "license": "MIT" 9805 + }, 9806 + "node_modules/npm/node_modules/encoding": { 9807 + "version": "0.1.13", 9808 + "inBundle": true, 9809 + "license": "MIT", 9810 + "optional": true, 9811 + "dependencies": { 9812 + "iconv-lite": "^0.6.2" 9813 + } 9814 + }, 9815 + "node_modules/npm/node_modules/env-paths": { 9816 + "version": "2.2.1", 9817 + "inBundle": true, 9818 + "license": "MIT", 9819 + "engines": { 9820 + "node": ">=6" 9821 + } 9822 + }, 9823 + "node_modules/npm/node_modules/err-code": { 9824 + "version": "2.0.3", 9825 + "inBundle": true, 9826 + "license": "MIT" 9827 + }, 9828 + "node_modules/npm/node_modules/exponential-backoff": { 9829 + "version": "3.1.2", 9830 + "inBundle": true, 9831 + "license": "Apache-2.0" 9832 + }, 9833 + "node_modules/npm/node_modules/fastest-levenshtein": { 9834 + "version": "1.0.16", 9835 + "inBundle": true, 9836 + "license": "MIT", 9837 + "engines": { 9838 + "node": ">= 4.9.1" 9839 + } 9840 + }, 9841 + "node_modules/npm/node_modules/foreground-child": { 9842 + "version": "3.3.1", 9843 + "inBundle": true, 9844 + "license": "ISC", 9845 + "dependencies": { 9846 + "cross-spawn": "^7.0.6", 9847 + "signal-exit": "^4.0.1" 9848 + }, 9849 + "engines": { 9850 + "node": ">=14" 9851 + }, 9852 + "funding": { 9853 + "url": "https://github.com/sponsors/isaacs" 9854 + } 9855 + }, 9856 + "node_modules/npm/node_modules/fs-minipass": { 9857 + "version": "3.0.3", 9858 + "inBundle": true, 9859 + "license": "ISC", 9860 + "dependencies": { 9861 + "minipass": "^7.0.3" 9862 + }, 9863 + "engines": { 9864 + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" 9865 + } 9866 + }, 9867 + "node_modules/npm/node_modules/glob": { 9868 + "version": "11.0.3", 9869 + "inBundle": true, 9870 + "license": "ISC", 9871 + "dependencies": { 9872 + "foreground-child": "^3.3.1", 9873 + "jackspeak": "^4.1.1", 9874 + "minimatch": "^10.0.3", 9875 + "minipass": "^7.1.2", 9876 + "package-json-from-dist": "^1.0.0", 9877 + "path-scurry": "^2.0.0" 9878 + }, 9879 + "bin": { 9880 + "glob": "dist/esm/bin.mjs" 9881 + }, 9882 + "engines": { 9883 + "node": "20 || >=22" 9884 + }, 9885 + "funding": { 9886 + "url": "https://github.com/sponsors/isaacs" 9887 + } 9888 + }, 9889 + "node_modules/npm/node_modules/graceful-fs": { 9890 + "version": "4.2.11", 9891 + "inBundle": true, 9892 + "license": "ISC" 9893 + }, 9894 + "node_modules/npm/node_modules/hosted-git-info": { 9895 + "version": "9.0.2", 9896 + "inBundle": true, 9897 + "license": "ISC", 9898 + "dependencies": { 9899 + "lru-cache": "^11.1.0" 9900 + }, 9901 + "engines": { 9902 + "node": "^20.17.0 || >=22.9.0" 9903 + } 9904 + }, 9905 + "node_modules/npm/node_modules/http-cache-semantics": { 9906 + "version": "4.2.0", 9907 + "inBundle": true, 9908 + "license": "BSD-2-Clause" 9909 + }, 9910 + "node_modules/npm/node_modules/http-proxy-agent": { 9911 + "version": "7.0.2", 9912 + "inBundle": true, 9913 + "license": "MIT", 9914 + "dependencies": { 9915 + "agent-base": "^7.1.0", 9916 + "debug": "^4.3.4" 9917 + }, 9918 + "engines": { 9919 + "node": ">= 14" 9920 + } 9921 + }, 9922 + "node_modules/npm/node_modules/https-proxy-agent": { 9923 + "version": "7.0.6", 9924 + "inBundle": true, 9925 + "license": "MIT", 9926 + "dependencies": { 9927 + "agent-base": "^7.1.2", 9928 + "debug": "4" 9929 + }, 9930 + "engines": { 9931 + "node": ">= 14" 9932 + } 9933 + }, 9934 + "node_modules/npm/node_modules/iconv-lite": { 9935 + "version": "0.6.3", 9936 + "inBundle": true, 9937 + "license": "MIT", 9938 + "optional": true, 9939 + "dependencies": { 9940 + "safer-buffer": ">= 2.1.2 < 3.0.0" 9941 + }, 9942 + "engines": { 9943 + "node": ">=0.10.0" 9944 + } 9945 + }, 9946 + "node_modules/npm/node_modules/ignore-walk": { 9947 + "version": "8.0.0", 9948 + "inBundle": true, 9949 + "license": "ISC", 9950 + "dependencies": { 9951 + "minimatch": "^10.0.3" 9952 + }, 9953 + "engines": { 9954 + "node": "^20.17.0 || >=22.9.0" 9955 + } 9956 + }, 9957 + "node_modules/npm/node_modules/imurmurhash": { 9958 + "version": "0.1.4", 9959 + "inBundle": true, 9960 + "license": "MIT", 9961 + "engines": { 9962 + "node": ">=0.8.19" 9963 + } 9964 + }, 9965 + "node_modules/npm/node_modules/ini": { 9966 + "version": "5.0.0", 9967 + "inBundle": true, 9968 + "license": "ISC", 9969 + "engines": { 9970 + "node": "^18.17.0 || >=20.5.0" 9971 + } 9972 + }, 9973 + "node_modules/npm/node_modules/init-package-json": { 9974 + "version": "8.2.2", 9975 + "inBundle": true, 9976 + "license": "ISC", 9977 + "dependencies": { 9978 + "@npmcli/package-json": "^7.0.0", 9979 + "npm-package-arg": "^13.0.0", 9980 + "promzard": "^2.0.0", 9981 + "read": "^4.0.0", 9982 + "semver": "^7.7.2", 9983 + "validate-npm-package-license": "^3.0.4", 9984 + "validate-npm-package-name": "^6.0.2" 9985 + }, 9986 + "engines": { 9987 + "node": "^20.17.0 || >=22.9.0" 9988 + } 9989 + }, 9990 + "node_modules/npm/node_modules/ip-address": { 9991 + "version": "10.0.1", 9992 + "inBundle": true, 9993 + "license": "MIT", 9994 + "engines": { 9995 + "node": ">= 12" 9996 + } 9997 + }, 9998 + "node_modules/npm/node_modules/ip-regex": { 9999 + "version": "5.0.0", 10000 + "inBundle": true, 10001 + "license": "MIT", 10002 + "engines": { 10003 + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 10004 + }, 10005 + "funding": { 10006 + "url": "https://github.com/sponsors/sindresorhus" 10007 + } 10008 + }, 10009 + "node_modules/npm/node_modules/is-cidr": { 10010 + "version": "6.0.1", 10011 + "inBundle": true, 10012 + "license": "BSD-2-Clause", 10013 + "dependencies": { 10014 + "cidr-regex": "5.0.1" 10015 + }, 10016 + "engines": { 10017 + "node": ">=20" 10018 + } 10019 + }, 10020 + "node_modules/npm/node_modules/is-fullwidth-code-point": { 10021 + "version": "3.0.0", 10022 + "inBundle": true, 10023 + "license": "MIT", 10024 + "engines": { 10025 + "node": ">=8" 10026 + } 10027 + }, 10028 + "node_modules/npm/node_modules/isexe": { 10029 + "version": "3.1.1", 10030 + "inBundle": true, 10031 + "license": "ISC", 10032 + "engines": { 10033 + "node": ">=16" 10034 + } 10035 + }, 10036 + "node_modules/npm/node_modules/jackspeak": { 10037 + "version": "4.1.1", 10038 + "inBundle": true, 10039 + "license": "BlueOak-1.0.0", 10040 + "dependencies": { 10041 + "@isaacs/cliui": "^8.0.2" 10042 + }, 10043 + "engines": { 10044 + "node": "20 || >=22" 10045 + }, 10046 + "funding": { 10047 + "url": "https://github.com/sponsors/isaacs" 10048 + } 10049 + }, 10050 + "node_modules/npm/node_modules/json-parse-even-better-errors": { 10051 + "version": "4.0.0", 10052 + "inBundle": true, 10053 + "license": "MIT", 10054 + "engines": { 10055 + "node": "^18.17.0 || >=20.5.0" 10056 + } 10057 + }, 10058 + "node_modules/npm/node_modules/json-stringify-nice": { 10059 + "version": "1.1.4", 10060 + "inBundle": true, 10061 + "license": "ISC", 10062 + "funding": { 10063 + "url": "https://github.com/sponsors/isaacs" 10064 + } 10065 + }, 10066 + "node_modules/npm/node_modules/jsonparse": { 10067 + "version": "1.3.1", 10068 + "engines": [ 10069 + "node >= 0.2.0" 10070 + ], 10071 + "inBundle": true, 10072 + "license": "MIT" 10073 + }, 10074 + "node_modules/npm/node_modules/just-diff": { 10075 + "version": "6.0.2", 10076 + "inBundle": true, 10077 + "license": "MIT" 10078 + }, 10079 + "node_modules/npm/node_modules/just-diff-apply": { 10080 + "version": "5.5.0", 10081 + "inBundle": true, 10082 + "license": "MIT" 10083 + }, 10084 + "node_modules/npm/node_modules/libnpmaccess": { 10085 + "version": "10.0.3", 10086 + "inBundle": true, 10087 + "license": "ISC", 10088 + "dependencies": { 10089 + "npm-package-arg": "^13.0.0", 10090 + "npm-registry-fetch": "^19.0.0" 10091 + }, 10092 + "engines": { 10093 + "node": "^20.17.0 || >=22.9.0" 10094 + } 10095 + }, 10096 + "node_modules/npm/node_modules/libnpmdiff": { 10097 + "version": "8.0.9", 10098 + "inBundle": true, 10099 + "license": "ISC", 10100 + "dependencies": { 10101 + "@npmcli/arborist": "^9.1.6", 10102 + "@npmcli/installed-package-contents": "^3.0.0", 10103 + "binary-extensions": "^3.0.0", 10104 + "diff": "^8.0.2", 10105 + "minimatch": "^10.0.3", 10106 + "npm-package-arg": "^13.0.0", 10107 + "pacote": "^21.0.2", 10108 + "tar": "^7.5.1" 10109 + }, 10110 + "engines": { 10111 + "node": "^20.17.0 || >=22.9.0" 10112 + } 10113 + }, 10114 + "node_modules/npm/node_modules/libnpmexec": { 10115 + "version": "10.1.8", 10116 + "inBundle": true, 10117 + "license": "ISC", 10118 + "dependencies": { 10119 + "@npmcli/arborist": "^9.1.6", 10120 + "@npmcli/package-json": "^7.0.0", 10121 + "@npmcli/run-script": "^10.0.0", 10122 + "ci-info": "^4.0.0", 10123 + "npm-package-arg": "^13.0.0", 10124 + "pacote": "^21.0.2", 10125 + "proc-log": "^5.0.0", 10126 + "promise-retry": "^2.0.1", 10127 + "read": "^4.0.0", 10128 + "semver": "^7.3.7", 10129 + "signal-exit": "^4.1.0", 10130 + "walk-up-path": "^4.0.0" 10131 + }, 10132 + "engines": { 10133 + "node": "^20.17.0 || >=22.9.0" 10134 + } 10135 + }, 10136 + "node_modules/npm/node_modules/libnpmfund": { 10137 + "version": "7.0.9", 10138 + "inBundle": true, 10139 + "license": "ISC", 10140 + "dependencies": { 10141 + "@npmcli/arborist": "^9.1.6" 10142 + }, 10143 + "engines": { 10144 + "node": "^20.17.0 || >=22.9.0" 10145 + } 10146 + }, 10147 + "node_modules/npm/node_modules/libnpmorg": { 10148 + "version": "8.0.1", 10149 + "inBundle": true, 10150 + "license": "ISC", 10151 + "dependencies": { 10152 + "aproba": "^2.0.0", 10153 + "npm-registry-fetch": "^19.0.0" 10154 + }, 10155 + "engines": { 10156 + "node": "^20.17.0 || >=22.9.0" 10157 + } 10158 + }, 10159 + "node_modules/npm/node_modules/libnpmpack": { 10160 + "version": "9.0.9", 10161 + "inBundle": true, 10162 + "license": "ISC", 10163 + "dependencies": { 10164 + "@npmcli/arborist": "^9.1.6", 10165 + "@npmcli/run-script": "^10.0.0", 10166 + "npm-package-arg": "^13.0.0", 10167 + "pacote": "^21.0.2" 10168 + }, 10169 + "engines": { 10170 + "node": "^20.17.0 || >=22.9.0" 10171 + } 10172 + }, 10173 + "node_modules/npm/node_modules/libnpmpublish": { 10174 + "version": "11.1.2", 10175 + "inBundle": true, 10176 + "license": "ISC", 10177 + "dependencies": { 10178 + "@npmcli/package-json": "^7.0.0", 10179 + "ci-info": "^4.0.0", 10180 + "npm-package-arg": "^13.0.0", 10181 + "npm-registry-fetch": "^19.0.0", 10182 + "proc-log": "^5.0.0", 10183 + "semver": "^7.3.7", 10184 + "sigstore": "^4.0.0", 10185 + "ssri": "^12.0.0" 10186 + }, 10187 + "engines": { 10188 + "node": "^20.17.0 || >=22.9.0" 10189 + } 10190 + }, 10191 + "node_modules/npm/node_modules/libnpmsearch": { 10192 + "version": "9.0.1", 10193 + "inBundle": true, 10194 + "license": "ISC", 10195 + "dependencies": { 10196 + "npm-registry-fetch": "^19.0.0" 10197 + }, 10198 + "engines": { 10199 + "node": "^20.17.0 || >=22.9.0" 10200 + } 10201 + }, 10202 + "node_modules/npm/node_modules/libnpmteam": { 10203 + "version": "8.0.2", 10204 + "inBundle": true, 10205 + "license": "ISC", 10206 + "dependencies": { 10207 + "aproba": "^2.0.0", 10208 + "npm-registry-fetch": "^19.0.0" 10209 + }, 10210 + "engines": { 10211 + "node": "^20.17.0 || >=22.9.0" 10212 + } 10213 + }, 10214 + "node_modules/npm/node_modules/libnpmversion": { 10215 + "version": "8.0.2", 10216 + "inBundle": true, 10217 + "license": "ISC", 10218 + "dependencies": { 10219 + "@npmcli/git": "^7.0.0", 10220 + "@npmcli/run-script": "^10.0.0", 10221 + "json-parse-even-better-errors": "^4.0.0", 10222 + "proc-log": "^5.0.0", 10223 + "semver": "^7.3.7" 10224 + }, 10225 + "engines": { 10226 + "node": "^20.17.0 || >=22.9.0" 10227 + } 10228 + }, 10229 + "node_modules/npm/node_modules/lru-cache": { 10230 + "version": "11.2.2", 10231 + "inBundle": true, 10232 + "license": "ISC", 10233 + "engines": { 10234 + "node": "20 || >=22" 10235 + } 10236 + }, 10237 + "node_modules/npm/node_modules/make-fetch-happen": { 10238 + "version": "15.0.2", 10239 + "inBundle": true, 10240 + "license": "ISC", 10241 + "dependencies": { 10242 + "@npmcli/agent": "^4.0.0", 10243 + "cacache": "^20.0.1", 10244 + "http-cache-semantics": "^4.1.1", 10245 + "minipass": "^7.0.2", 10246 + "minipass-fetch": "^4.0.0", 10247 + "minipass-flush": "^1.0.5", 10248 + "minipass-pipeline": "^1.2.4", 10249 + "negotiator": "^1.0.0", 10250 + "proc-log": "^5.0.0", 10251 + "promise-retry": "^2.0.1", 10252 + "ssri": "^12.0.0" 10253 + }, 10254 + "engines": { 10255 + "node": "^20.17.0 || >=22.9.0" 10256 + } 10257 + }, 10258 + "node_modules/npm/node_modules/minimatch": { 10259 + "version": "10.0.3", 10260 + "inBundle": true, 10261 + "license": "ISC", 10262 + "dependencies": { 10263 + "@isaacs/brace-expansion": "^5.0.0" 10264 + }, 10265 + "engines": { 10266 + "node": "20 || >=22" 10267 + }, 10268 + "funding": { 10269 + "url": "https://github.com/sponsors/isaacs" 10270 + } 10271 + }, 10272 + "node_modules/npm/node_modules/minipass": { 10273 + "version": "7.1.2", 10274 + "inBundle": true, 10275 + "license": "ISC", 10276 + "engines": { 10277 + "node": ">=16 || 14 >=14.17" 10278 + } 10279 + }, 10280 + "node_modules/npm/node_modules/minipass-collect": { 10281 + "version": "2.0.1", 10282 + "inBundle": true, 10283 + "license": "ISC", 10284 + "dependencies": { 10285 + "minipass": "^7.0.3" 10286 + }, 10287 + "engines": { 10288 + "node": ">=16 || 14 >=14.17" 10289 + } 10290 + }, 10291 + "node_modules/npm/node_modules/minipass-fetch": { 10292 + "version": "4.0.1", 10293 + "inBundle": true, 10294 + "license": "MIT", 10295 + "dependencies": { 10296 + "minipass": "^7.0.3", 10297 + "minipass-sized": "^1.0.3", 10298 + "minizlib": "^3.0.1" 10299 + }, 10300 + "engines": { 10301 + "node": "^18.17.0 || >=20.5.0" 10302 + }, 10303 + "optionalDependencies": { 10304 + "encoding": "^0.1.13" 10305 + } 10306 + }, 10307 + "node_modules/npm/node_modules/minipass-flush": { 10308 + "version": "1.0.5", 10309 + "inBundle": true, 10310 + "license": "ISC", 10311 + "dependencies": { 10312 + "minipass": "^3.0.0" 10313 + }, 10314 + "engines": { 10315 + "node": ">= 8" 10316 + } 10317 + }, 10318 + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { 10319 + "version": "3.3.6", 10320 + "inBundle": true, 10321 + "license": "ISC", 10322 + "dependencies": { 10323 + "yallist": "^4.0.0" 10324 + }, 10325 + "engines": { 10326 + "node": ">=8" 10327 + } 10328 + }, 10329 + "node_modules/npm/node_modules/minipass-pipeline": { 10330 + "version": "1.2.4", 10331 + "inBundle": true, 10332 + "license": "ISC", 10333 + "dependencies": { 10334 + "minipass": "^3.0.0" 10335 + }, 10336 + "engines": { 10337 + "node": ">=8" 10338 + } 10339 + }, 10340 + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { 10341 + "version": "3.3.6", 10342 + "inBundle": true, 10343 + "license": "ISC", 10344 + "dependencies": { 10345 + "yallist": "^4.0.0" 10346 + }, 10347 + "engines": { 10348 + "node": ">=8" 10349 + } 10350 + }, 10351 + "node_modules/npm/node_modules/minipass-sized": { 10352 + "version": "1.0.3", 10353 + "inBundle": true, 10354 + "license": "ISC", 10355 + "dependencies": { 10356 + "minipass": "^3.0.0" 10357 + }, 10358 + "engines": { 10359 + "node": ">=8" 10360 + } 10361 + }, 10362 + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { 10363 + "version": "3.3.6", 10364 + "inBundle": true, 10365 + "license": "ISC", 10366 + "dependencies": { 10367 + "yallist": "^4.0.0" 10368 + }, 10369 + "engines": { 10370 + "node": ">=8" 10371 + } 10372 + }, 10373 + "node_modules/npm/node_modules/minizlib": { 10374 + "version": "3.1.0", 10375 + "inBundle": true, 10376 + "license": "MIT", 10377 + "dependencies": { 10378 + "minipass": "^7.1.2" 10379 + }, 10380 + "engines": { 10381 + "node": ">= 18" 10382 + } 10383 + }, 10384 + "node_modules/npm/node_modules/ms": { 10385 + "version": "2.1.3", 10386 + "inBundle": true, 10387 + "license": "MIT" 10388 + }, 10389 + "node_modules/npm/node_modules/mute-stream": { 10390 + "version": "2.0.0", 10391 + "inBundle": true, 10392 + "license": "ISC", 10393 + "engines": { 10394 + "node": "^18.17.0 || >=20.5.0" 10395 + } 10396 + }, 10397 + "node_modules/npm/node_modules/negotiator": { 10398 + "version": "1.0.0", 10399 + "inBundle": true, 10400 + "license": "MIT", 10401 + "engines": { 10402 + "node": ">= 0.6" 10403 + } 10404 + }, 10405 + "node_modules/npm/node_modules/node-gyp": { 10406 + "version": "11.4.2", 10407 + "inBundle": true, 10408 + "license": "MIT", 10409 + "dependencies": { 10410 + "env-paths": "^2.2.0", 10411 + "exponential-backoff": "^3.1.1", 10412 + "graceful-fs": "^4.2.6", 10413 + "make-fetch-happen": "^14.0.3", 10414 + "nopt": "^8.0.0", 10415 + "proc-log": "^5.0.0", 10416 + "semver": "^7.3.5", 10417 + "tar": "^7.4.3", 10418 + "tinyglobby": "^0.2.12", 10419 + "which": "^5.0.0" 10420 + }, 10421 + "bin": { 10422 + "node-gyp": "bin/node-gyp.js" 10423 + }, 10424 + "engines": { 10425 + "node": "^18.17.0 || >=20.5.0" 10426 + } 10427 + }, 10428 + "node_modules/npm/node_modules/node-gyp/node_modules/@npmcli/agent": { 10429 + "version": "3.0.0", 10430 + "inBundle": true, 10431 + "license": "ISC", 10432 + "dependencies": { 10433 + "agent-base": "^7.1.0", 10434 + "http-proxy-agent": "^7.0.0", 10435 + "https-proxy-agent": "^7.0.1", 10436 + "lru-cache": "^10.0.1", 10437 + "socks-proxy-agent": "^8.0.3" 10438 + }, 10439 + "engines": { 10440 + "node": "^18.17.0 || >=20.5.0" 10441 + } 10442 + }, 10443 + "node_modules/npm/node_modules/node-gyp/node_modules/cacache": { 10444 + "version": "19.0.1", 10445 + "inBundle": true, 10446 + "license": "ISC", 10447 + "dependencies": { 10448 + "@npmcli/fs": "^4.0.0", 10449 + "fs-minipass": "^3.0.0", 10450 + "glob": "^10.2.2", 10451 + "lru-cache": "^10.0.1", 10452 + "minipass": "^7.0.3", 10453 + "minipass-collect": "^2.0.1", 10454 + "minipass-flush": "^1.0.5", 10455 + "minipass-pipeline": "^1.2.4", 10456 + "p-map": "^7.0.2", 10457 + "ssri": "^12.0.0", 10458 + "tar": "^7.4.3", 10459 + "unique-filename": "^4.0.0" 10460 + }, 10461 + "engines": { 10462 + "node": "^18.17.0 || >=20.5.0" 10463 + } 10464 + }, 10465 + "node_modules/npm/node_modules/node-gyp/node_modules/glob": { 10466 + "version": "10.4.5", 10467 + "inBundle": true, 10468 + "license": "ISC", 10469 + "dependencies": { 10470 + "foreground-child": "^3.1.0", 10471 + "jackspeak": "^3.1.2", 10472 + "minimatch": "^9.0.4", 10473 + "minipass": "^7.1.2", 10474 + "package-json-from-dist": "^1.0.0", 10475 + "path-scurry": "^1.11.1" 10476 + }, 10477 + "bin": { 10478 + "glob": "dist/esm/bin.mjs" 10479 + }, 10480 + "funding": { 10481 + "url": "https://github.com/sponsors/isaacs" 10482 + } 10483 + }, 10484 + "node_modules/npm/node_modules/node-gyp/node_modules/jackspeak": { 10485 + "version": "3.4.3", 10486 + "inBundle": true, 10487 + "license": "BlueOak-1.0.0", 10488 + "dependencies": { 10489 + "@isaacs/cliui": "^8.0.2" 10490 + }, 10491 + "funding": { 10492 + "url": "https://github.com/sponsors/isaacs" 10493 + }, 10494 + "optionalDependencies": { 10495 + "@pkgjs/parseargs": "^0.11.0" 10496 + } 10497 + }, 10498 + "node_modules/npm/node_modules/node-gyp/node_modules/lru-cache": { 10499 + "version": "10.4.3", 10500 + "inBundle": true, 10501 + "license": "ISC" 10502 + }, 10503 + "node_modules/npm/node_modules/node-gyp/node_modules/make-fetch-happen": { 10504 + "version": "14.0.3", 10505 + "inBundle": true, 10506 + "license": "ISC", 10507 + "dependencies": { 10508 + "@npmcli/agent": "^3.0.0", 10509 + "cacache": "^19.0.1", 10510 + "http-cache-semantics": "^4.1.1", 10511 + "minipass": "^7.0.2", 10512 + "minipass-fetch": "^4.0.0", 10513 + "minipass-flush": "^1.0.5", 10514 + "minipass-pipeline": "^1.2.4", 10515 + "negotiator": "^1.0.0", 10516 + "proc-log": "^5.0.0", 10517 + "promise-retry": "^2.0.1", 10518 + "ssri": "^12.0.0" 10519 + }, 10520 + "engines": { 10521 + "node": "^18.17.0 || >=20.5.0" 10522 + } 10523 + }, 10524 + "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { 10525 + "version": "9.0.5", 10526 + "inBundle": true, 10527 + "license": "ISC", 10528 + "dependencies": { 10529 + "brace-expansion": "^2.0.1" 10530 + }, 10531 + "engines": { 10532 + "node": ">=16 || 14 >=14.17" 10533 + }, 10534 + "funding": { 10535 + "url": "https://github.com/sponsors/isaacs" 10536 + } 10537 + }, 10538 + "node_modules/npm/node_modules/node-gyp/node_modules/path-scurry": { 10539 + "version": "1.11.1", 10540 + "inBundle": true, 10541 + "license": "BlueOak-1.0.0", 10542 + "dependencies": { 10543 + "lru-cache": "^10.2.0", 10544 + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 10545 + }, 10546 + "engines": { 10547 + "node": ">=16 || 14 >=14.18" 10548 + }, 10549 + "funding": { 10550 + "url": "https://github.com/sponsors/isaacs" 10551 + } 10552 + }, 10553 + "node_modules/npm/node_modules/nopt": { 10554 + "version": "8.1.0", 10555 + "inBundle": true, 10556 + "license": "ISC", 10557 + "dependencies": { 10558 + "abbrev": "^3.0.0" 10559 + }, 10560 + "bin": { 10561 + "nopt": "bin/nopt.js" 10562 + }, 10563 + "engines": { 10564 + "node": "^18.17.0 || >=20.5.0" 10565 + } 10566 + }, 10567 + "node_modules/npm/node_modules/npm-audit-report": { 10568 + "version": "6.0.0", 10569 + "inBundle": true, 10570 + "license": "ISC", 10571 + "engines": { 10572 + "node": "^18.17.0 || >=20.5.0" 10573 + } 10574 + }, 10575 + "node_modules/npm/node_modules/npm-bundled": { 10576 + "version": "4.0.0", 10577 + "inBundle": true, 10578 + "license": "ISC", 10579 + "dependencies": { 10580 + "npm-normalize-package-bin": "^4.0.0" 10581 + }, 10582 + "engines": { 10583 + "node": "^18.17.0 || >=20.5.0" 10584 + } 10585 + }, 10586 + "node_modules/npm/node_modules/npm-install-checks": { 10587 + "version": "7.1.2", 10588 + "inBundle": true, 10589 + "license": "BSD-2-Clause", 10590 + "dependencies": { 10591 + "semver": "^7.1.1" 10592 + }, 10593 + "engines": { 10594 + "node": "^18.17.0 || >=20.5.0" 10595 + } 10596 + }, 10597 + "node_modules/npm/node_modules/npm-normalize-package-bin": { 10598 + "version": "4.0.0", 10599 + "inBundle": true, 10600 + "license": "ISC", 10601 + "engines": { 10602 + "node": "^18.17.0 || >=20.5.0" 10603 + } 10604 + }, 10605 + "node_modules/npm/node_modules/npm-package-arg": { 10606 + "version": "13.0.1", 10607 + "inBundle": true, 10608 + "license": "ISC", 10609 + "dependencies": { 10610 + "hosted-git-info": "^9.0.0", 10611 + "proc-log": "^5.0.0", 10612 + "semver": "^7.3.5", 10613 + "validate-npm-package-name": "^6.0.0" 10614 + }, 10615 + "engines": { 10616 + "node": "^20.17.0 || >=22.9.0" 10617 + } 10618 + }, 10619 + "node_modules/npm/node_modules/npm-packlist": { 10620 + "version": "10.0.2", 10621 + "inBundle": true, 10622 + "license": "ISC", 10623 + "dependencies": { 10624 + "ignore-walk": "^8.0.0", 10625 + "proc-log": "^5.0.0" 10626 + }, 10627 + "engines": { 10628 + "node": "^20.17.0 || >=22.9.0" 10629 + } 10630 + }, 10631 + "node_modules/npm/node_modules/npm-pick-manifest": { 10632 + "version": "11.0.1", 10633 + "inBundle": true, 10634 + "license": "ISC", 10635 + "dependencies": { 10636 + "npm-install-checks": "^7.1.0", 10637 + "npm-normalize-package-bin": "^4.0.0", 10638 + "npm-package-arg": "^13.0.0", 10639 + "semver": "^7.3.5" 10640 + }, 10641 + "engines": { 10642 + "node": "^20.17.0 || >=22.9.0" 10643 + } 10644 + }, 10645 + "node_modules/npm/node_modules/npm-profile": { 10646 + "version": "12.0.0", 10647 + "inBundle": true, 10648 + "license": "ISC", 10649 + "dependencies": { 10650 + "npm-registry-fetch": "^19.0.0", 10651 + "proc-log": "^5.0.0" 10652 + }, 10653 + "engines": { 10654 + "node": "^20.17.0 || >=22.9.0" 10655 + } 10656 + }, 10657 + "node_modules/npm/node_modules/npm-registry-fetch": { 10658 + "version": "19.0.0", 10659 + "inBundle": true, 10660 + "license": "ISC", 10661 + "dependencies": { 10662 + "@npmcli/redact": "^3.0.0", 10663 + "jsonparse": "^1.3.1", 10664 + "make-fetch-happen": "^15.0.0", 10665 + "minipass": "^7.0.2", 10666 + "minipass-fetch": "^4.0.0", 10667 + "minizlib": "^3.0.1", 10668 + "npm-package-arg": "^13.0.0", 10669 + "proc-log": "^5.0.0" 10670 + }, 10671 + "engines": { 10672 + "node": "^20.17.0 || >=22.9.0" 10673 + } 10674 + }, 10675 + "node_modules/npm/node_modules/npm-user-validate": { 10676 + "version": "3.0.0", 10677 + "inBundle": true, 10678 + "license": "BSD-2-Clause", 10679 + "engines": { 10680 + "node": "^18.17.0 || >=20.5.0" 10681 + } 10682 + }, 10683 + "node_modules/npm/node_modules/p-map": { 10684 + "version": "7.0.3", 10685 + "inBundle": true, 10686 + "license": "MIT", 10687 + "engines": { 10688 + "node": ">=18" 10689 + }, 10690 + "funding": { 10691 + "url": "https://github.com/sponsors/sindresorhus" 10692 + } 10693 + }, 10694 + "node_modules/npm/node_modules/package-json-from-dist": { 10695 + "version": "1.0.1", 10696 + "inBundle": true, 10697 + "license": "BlueOak-1.0.0" 10698 + }, 10699 + "node_modules/npm/node_modules/pacote": { 10700 + "version": "21.0.3", 10701 + "inBundle": true, 10702 + "license": "ISC", 10703 + "dependencies": { 10704 + "@npmcli/git": "^7.0.0", 10705 + "@npmcli/installed-package-contents": "^3.0.0", 10706 + "@npmcli/package-json": "^7.0.0", 10707 + "@npmcli/promise-spawn": "^8.0.0", 10708 + "@npmcli/run-script": "^10.0.0", 10709 + "cacache": "^20.0.0", 10710 + "fs-minipass": "^3.0.0", 10711 + "minipass": "^7.0.2", 10712 + "npm-package-arg": "^13.0.0", 10713 + "npm-packlist": "^10.0.1", 10714 + "npm-pick-manifest": "^11.0.1", 10715 + "npm-registry-fetch": "^19.0.0", 10716 + "proc-log": "^5.0.0", 10717 + "promise-retry": "^2.0.1", 10718 + "sigstore": "^4.0.0", 10719 + "ssri": "^12.0.0", 10720 + "tar": "^7.4.3" 10721 + }, 10722 + "bin": { 10723 + "pacote": "bin/index.js" 10724 + }, 10725 + "engines": { 10726 + "node": "^20.17.0 || >=22.9.0" 10727 + } 10728 + }, 10729 + "node_modules/npm/node_modules/parse-conflict-json": { 10730 + "version": "4.0.0", 10731 + "inBundle": true, 10732 + "license": "ISC", 10733 + "dependencies": { 10734 + "json-parse-even-better-errors": "^4.0.0", 10735 + "just-diff": "^6.0.0", 10736 + "just-diff-apply": "^5.2.0" 10737 + }, 10738 + "engines": { 10739 + "node": "^18.17.0 || >=20.5.0" 10740 + } 10741 + }, 10742 + "node_modules/npm/node_modules/path-key": { 10743 + "version": "3.1.1", 10744 + "inBundle": true, 10745 + "license": "MIT", 10746 + "engines": { 10747 + "node": ">=8" 10748 + } 10749 + }, 10750 + "node_modules/npm/node_modules/path-scurry": { 10751 + "version": "2.0.0", 10752 + "inBundle": true, 10753 + "license": "BlueOak-1.0.0", 10754 + "dependencies": { 10755 + "lru-cache": "^11.0.0", 10756 + "minipass": "^7.1.2" 10757 + }, 10758 + "engines": { 10759 + "node": "20 || >=22" 10760 + }, 10761 + "funding": { 10762 + "url": "https://github.com/sponsors/isaacs" 10763 + } 10764 + }, 10765 + "node_modules/npm/node_modules/postcss-selector-parser": { 10766 + "version": "7.1.0", 10767 + "inBundle": true, 10768 + "license": "MIT", 10769 + "dependencies": { 10770 + "cssesc": "^3.0.0", 10771 + "util-deprecate": "^1.0.2" 10772 + }, 10773 + "engines": { 10774 + "node": ">=4" 10775 + } 10776 + }, 10777 + "node_modules/npm/node_modules/proc-log": { 10778 + "version": "5.0.0", 10779 + "inBundle": true, 10780 + "license": "ISC", 10781 + "engines": { 10782 + "node": "^18.17.0 || >=20.5.0" 10783 + } 10784 + }, 10785 + "node_modules/npm/node_modules/proggy": { 10786 + "version": "3.0.0", 10787 + "inBundle": true, 10788 + "license": "ISC", 10789 + "engines": { 10790 + "node": "^18.17.0 || >=20.5.0" 10791 + } 10792 + }, 10793 + "node_modules/npm/node_modules/promise-all-reject-late": { 10794 + "version": "1.0.1", 10795 + "inBundle": true, 10796 + "license": "ISC", 10797 + "funding": { 10798 + "url": "https://github.com/sponsors/isaacs" 10799 + } 10800 + }, 10801 + "node_modules/npm/node_modules/promise-call-limit": { 10802 + "version": "3.0.2", 10803 + "inBundle": true, 10804 + "license": "ISC", 10805 + "funding": { 10806 + "url": "https://github.com/sponsors/isaacs" 10807 + } 10808 + }, 10809 + "node_modules/npm/node_modules/promise-retry": { 10810 + "version": "2.0.1", 10811 + "inBundle": true, 10812 + "license": "MIT", 10813 + "dependencies": { 10814 + "err-code": "^2.0.2", 10815 + "retry": "^0.12.0" 10816 + }, 10817 + "engines": { 10818 + "node": ">=10" 10819 + } 10820 + }, 10821 + "node_modules/npm/node_modules/promzard": { 10822 + "version": "2.0.0", 10823 + "inBundle": true, 10824 + "license": "ISC", 10825 + "dependencies": { 10826 + "read": "^4.0.0" 10827 + }, 10828 + "engines": { 10829 + "node": "^18.17.0 || >=20.5.0" 10830 + } 10831 + }, 10832 + "node_modules/npm/node_modules/qrcode-terminal": { 10833 + "version": "0.12.0", 10834 + "inBundle": true, 10835 + "bin": { 10836 + "qrcode-terminal": "bin/qrcode-terminal.js" 10837 + } 10838 + }, 10839 + "node_modules/npm/node_modules/read": { 10840 + "version": "4.1.0", 10841 + "inBundle": true, 10842 + "license": "ISC", 10843 + "dependencies": { 10844 + "mute-stream": "^2.0.0" 10845 + }, 10846 + "engines": { 10847 + "node": "^18.17.0 || >=20.5.0" 10848 + } 10849 + }, 10850 + "node_modules/npm/node_modules/read-cmd-shim": { 10851 + "version": "5.0.0", 10852 + "inBundle": true, 10853 + "license": "ISC", 10854 + "engines": { 10855 + "node": "^18.17.0 || >=20.5.0" 10856 + } 10857 + }, 10858 + "node_modules/npm/node_modules/retry": { 10859 + "version": "0.12.0", 10860 + "inBundle": true, 10861 + "license": "MIT", 10862 + "engines": { 10863 + "node": ">= 4" 10864 + } 10865 + }, 10866 + "node_modules/npm/node_modules/safer-buffer": { 10867 + "version": "2.1.2", 10868 + "inBundle": true, 10869 + "license": "MIT", 10870 + "optional": true 10871 + }, 10872 + "node_modules/npm/node_modules/semver": { 10873 + "version": "7.7.3", 10874 + "inBundle": true, 10875 + "license": "ISC", 10876 + "bin": { 10877 + "semver": "bin/semver.js" 10878 + }, 10879 + "engines": { 10880 + "node": ">=10" 10881 + } 10882 + }, 10883 + "node_modules/npm/node_modules/shebang-command": { 10884 + "version": "2.0.0", 10885 + "inBundle": true, 10886 + "license": "MIT", 10887 + "dependencies": { 10888 + "shebang-regex": "^3.0.0" 10889 + }, 10890 + "engines": { 10891 + "node": ">=8" 10892 + } 10893 + }, 10894 + "node_modules/npm/node_modules/shebang-regex": { 10895 + "version": "3.0.0", 10896 + "inBundle": true, 10897 + "license": "MIT", 10898 + "engines": { 10899 + "node": ">=8" 10900 + } 10901 + }, 10902 + "node_modules/npm/node_modules/signal-exit": { 10903 + "version": "4.1.0", 10904 + "inBundle": true, 10905 + "license": "ISC", 10906 + "engines": { 10907 + "node": ">=14" 10908 + }, 10909 + "funding": { 10910 + "url": "https://github.com/sponsors/isaacs" 10911 + } 10912 + }, 10913 + "node_modules/npm/node_modules/sigstore": { 10914 + "version": "4.0.0", 10915 + "inBundle": true, 10916 + "license": "Apache-2.0", 10917 + "dependencies": { 10918 + "@sigstore/bundle": "^4.0.0", 10919 + "@sigstore/core": "^3.0.0", 10920 + "@sigstore/protobuf-specs": "^0.5.0", 10921 + "@sigstore/sign": "^4.0.0", 10922 + "@sigstore/tuf": "^4.0.0", 10923 + "@sigstore/verify": "^3.0.0" 10924 + }, 10925 + "engines": { 10926 + "node": "^20.17.0 || >=22.9.0" 10927 + } 10928 + }, 10929 + "node_modules/npm/node_modules/smart-buffer": { 10930 + "version": "4.2.0", 10931 + "inBundle": true, 10932 + "license": "MIT", 10933 + "engines": { 10934 + "node": ">= 6.0.0", 10935 + "npm": ">= 3.0.0" 10936 + } 10937 + }, 10938 + "node_modules/npm/node_modules/socks": { 10939 + "version": "2.8.7", 10940 + "inBundle": true, 10941 + "license": "MIT", 10942 + "dependencies": { 10943 + "ip-address": "^10.0.1", 10944 + "smart-buffer": "^4.2.0" 10945 + }, 10946 + "engines": { 10947 + "node": ">= 10.0.0", 10948 + "npm": ">= 3.0.0" 10949 + } 10950 + }, 10951 + "node_modules/npm/node_modules/socks-proxy-agent": { 10952 + "version": "8.0.5", 10953 + "inBundle": true, 10954 + "license": "MIT", 10955 + "dependencies": { 10956 + "agent-base": "^7.1.2", 10957 + "debug": "^4.3.4", 10958 + "socks": "^2.8.3" 10959 + }, 10960 + "engines": { 10961 + "node": ">= 14" 10962 + } 10963 + }, 10964 + "node_modules/npm/node_modules/spdx-correct": { 10965 + "version": "3.2.0", 10966 + "inBundle": true, 10967 + "license": "Apache-2.0", 10968 + "dependencies": { 10969 + "spdx-expression-parse": "^3.0.0", 10970 + "spdx-license-ids": "^3.0.0" 10971 + } 10972 + }, 10973 + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { 10974 + "version": "3.0.1", 10975 + "inBundle": true, 10976 + "license": "MIT", 10977 + "dependencies": { 10978 + "spdx-exceptions": "^2.1.0", 10979 + "spdx-license-ids": "^3.0.0" 10980 + } 10981 + }, 10982 + "node_modules/npm/node_modules/spdx-exceptions": { 10983 + "version": "2.5.0", 10984 + "inBundle": true, 10985 + "license": "CC-BY-3.0" 10986 + }, 10987 + "node_modules/npm/node_modules/spdx-expression-parse": { 10988 + "version": "4.0.0", 10989 + "inBundle": true, 10990 + "license": "MIT", 10991 + "dependencies": { 10992 + "spdx-exceptions": "^2.1.0", 10993 + "spdx-license-ids": "^3.0.0" 10994 + } 10995 + }, 10996 + "node_modules/npm/node_modules/spdx-license-ids": { 10997 + "version": "3.0.22", 10998 + "inBundle": true, 10999 + "license": "CC0-1.0" 11000 + }, 11001 + "node_modules/npm/node_modules/ssri": { 11002 + "version": "12.0.0", 11003 + "inBundle": true, 11004 + "license": "ISC", 11005 + "dependencies": { 11006 + "minipass": "^7.0.3" 11007 + }, 11008 + "engines": { 11009 + "node": "^18.17.0 || >=20.5.0" 11010 + } 11011 + }, 11012 + "node_modules/npm/node_modules/string-width": { 11013 + "version": "4.2.3", 11014 + "inBundle": true, 11015 + "license": "MIT", 11016 + "dependencies": { 11017 + "emoji-regex": "^8.0.0", 11018 + "is-fullwidth-code-point": "^3.0.0", 11019 + "strip-ansi": "^6.0.1" 11020 + }, 11021 + "engines": { 11022 + "node": ">=8" 11023 + } 11024 + }, 11025 + "node_modules/npm/node_modules/string-width-cjs": { 11026 + "name": "string-width", 11027 + "version": "4.2.3", 11028 + "inBundle": true, 11029 + "license": "MIT", 11030 + "dependencies": { 11031 + "emoji-regex": "^8.0.0", 11032 + "is-fullwidth-code-point": "^3.0.0", 11033 + "strip-ansi": "^6.0.1" 11034 + }, 11035 + "engines": { 11036 + "node": ">=8" 11037 + } 11038 + }, 11039 + "node_modules/npm/node_modules/strip-ansi": { 11040 + "version": "6.0.1", 11041 + "inBundle": true, 11042 + "license": "MIT", 11043 + "dependencies": { 11044 + "ansi-regex": "^5.0.1" 11045 + }, 11046 + "engines": { 11047 + "node": ">=8" 11048 + } 11049 + }, 11050 + "node_modules/npm/node_modules/strip-ansi-cjs": { 11051 + "name": "strip-ansi", 11052 + "version": "6.0.1", 11053 + "inBundle": true, 11054 + "license": "MIT", 11055 + "dependencies": { 11056 + "ansi-regex": "^5.0.1" 11057 + }, 11058 + "engines": { 11059 + "node": ">=8" 11060 + } 11061 + }, 11062 + "node_modules/npm/node_modules/supports-color": { 11063 + "version": "10.2.2", 11064 + "inBundle": true, 11065 + "license": "MIT", 11066 + "engines": { 11067 + "node": ">=18" 11068 + }, 11069 + "funding": { 11070 + "url": "https://github.com/chalk/supports-color?sponsor=1" 11071 + } 11072 + }, 11073 + "node_modules/npm/node_modules/tar": { 11074 + "version": "7.5.1", 11075 + "inBundle": true, 11076 + "license": "ISC", 11077 + "dependencies": { 11078 + "@isaacs/fs-minipass": "^4.0.0", 11079 + "chownr": "^3.0.0", 11080 + "minipass": "^7.1.2", 11081 + "minizlib": "^3.1.0", 11082 + "yallist": "^5.0.0" 11083 + }, 11084 + "engines": { 11085 + "node": ">=18" 11086 + } 11087 + }, 11088 + "node_modules/npm/node_modules/tar/node_modules/yallist": { 11089 + "version": "5.0.0", 11090 + "inBundle": true, 11091 + "license": "BlueOak-1.0.0", 11092 + "engines": { 11093 + "node": ">=18" 11094 + } 11095 + }, 11096 + "node_modules/npm/node_modules/text-table": { 11097 + "version": "0.2.0", 11098 + "inBundle": true, 11099 + "license": "MIT" 11100 + }, 11101 + "node_modules/npm/node_modules/tiny-relative-date": { 11102 + "version": "2.0.2", 11103 + "inBundle": true, 11104 + "license": "MIT" 11105 + }, 11106 + "node_modules/npm/node_modules/tinyglobby": { 11107 + "version": "0.2.15", 11108 + "inBundle": true, 11109 + "license": "MIT", 11110 + "dependencies": { 11111 + "fdir": "^6.5.0", 11112 + "picomatch": "^4.0.3" 11113 + }, 11114 + "engines": { 11115 + "node": ">=12.0.0" 11116 + }, 11117 + "funding": { 11118 + "url": "https://github.com/sponsors/SuperchupuDev" 11119 + } 11120 + }, 11121 + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { 11122 + "version": "6.5.0", 11123 + "inBundle": true, 11124 + "license": "MIT", 11125 + "engines": { 11126 + "node": ">=12.0.0" 11127 + }, 11128 + "peerDependencies": { 11129 + "picomatch": "^3 || ^4" 11130 + }, 11131 + "peerDependenciesMeta": { 11132 + "picomatch": { 11133 + "optional": true 11134 + } 11135 + } 11136 + }, 11137 + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { 11138 + "version": "4.0.3", 11139 + "inBundle": true, 11140 + "license": "MIT", 11141 + "engines": { 11142 + "node": ">=12" 11143 + }, 11144 + "funding": { 11145 + "url": "https://github.com/sponsors/jonschlinkert" 11146 + } 11147 + }, 11148 + "node_modules/npm/node_modules/treeverse": { 11149 + "version": "3.0.0", 11150 + "inBundle": true, 11151 + "license": "ISC", 11152 + "engines": { 11153 + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" 11154 + } 11155 + }, 11156 + "node_modules/npm/node_modules/tuf-js": { 11157 + "version": "4.0.0", 11158 + "inBundle": true, 11159 + "license": "MIT", 11160 + "dependencies": { 11161 + "@tufjs/models": "4.0.0", 11162 + "debug": "^4.4.1", 11163 + "make-fetch-happen": "^15.0.0" 11164 + }, 11165 + "engines": { 11166 + "node": "^20.17.0 || >=22.9.0" 11167 + } 11168 + }, 11169 + "node_modules/npm/node_modules/unique-filename": { 11170 + "version": "4.0.0", 11171 + "inBundle": true, 11172 + "license": "ISC", 11173 + "dependencies": { 11174 + "unique-slug": "^5.0.0" 11175 + }, 11176 + "engines": { 11177 + "node": "^18.17.0 || >=20.5.0" 11178 + } 11179 + }, 11180 + "node_modules/npm/node_modules/unique-slug": { 11181 + "version": "5.0.0", 11182 + "inBundle": true, 11183 + "license": "ISC", 11184 + "dependencies": { 11185 + "imurmurhash": "^0.1.4" 11186 + }, 11187 + "engines": { 11188 + "node": "^18.17.0 || >=20.5.0" 11189 + } 11190 + }, 11191 + "node_modules/npm/node_modules/util-deprecate": { 11192 + "version": "1.0.2", 11193 + "inBundle": true, 11194 + "license": "MIT" 11195 + }, 11196 + "node_modules/npm/node_modules/validate-npm-package-license": { 11197 + "version": "3.0.4", 11198 + "inBundle": true, 11199 + "license": "Apache-2.0", 11200 + "dependencies": { 11201 + "spdx-correct": "^3.0.0", 11202 + "spdx-expression-parse": "^3.0.0" 11203 + } 11204 + }, 11205 + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { 11206 + "version": "3.0.1", 11207 + "inBundle": true, 11208 + "license": "MIT", 11209 + "dependencies": { 11210 + "spdx-exceptions": "^2.1.0", 11211 + "spdx-license-ids": "^3.0.0" 11212 + } 11213 + }, 11214 + "node_modules/npm/node_modules/validate-npm-package-name": { 11215 + "version": "6.0.2", 11216 + "inBundle": true, 11217 + "license": "ISC", 11218 + "engines": { 11219 + "node": "^18.17.0 || >=20.5.0" 11220 + } 11221 + }, 11222 + "node_modules/npm/node_modules/walk-up-path": { 11223 + "version": "4.0.0", 11224 + "inBundle": true, 11225 + "license": "ISC", 11226 + "engines": { 11227 + "node": "20 || >=22" 11228 + } 11229 + }, 11230 + "node_modules/npm/node_modules/which": { 11231 + "version": "5.0.0", 11232 + "inBundle": true, 11233 + "license": "ISC", 11234 + "dependencies": { 11235 + "isexe": "^3.1.1" 11236 + }, 11237 + "bin": { 11238 + "node-which": "bin/which.js" 11239 + }, 11240 + "engines": { 11241 + "node": "^18.17.0 || >=20.5.0" 11242 + } 11243 + }, 11244 + "node_modules/npm/node_modules/wrap-ansi": { 11245 + "version": "8.1.0", 11246 + "inBundle": true, 11247 + "license": "MIT", 11248 + "dependencies": { 11249 + "ansi-styles": "^6.1.0", 11250 + "string-width": "^5.0.1", 11251 + "strip-ansi": "^7.0.1" 11252 + }, 11253 + "engines": { 11254 + "node": ">=12" 11255 + }, 11256 + "funding": { 11257 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 11258 + } 11259 + }, 11260 + "node_modules/npm/node_modules/wrap-ansi-cjs": { 11261 + "name": "wrap-ansi", 11262 + "version": "7.0.0", 11263 + "inBundle": true, 11264 + "license": "MIT", 11265 + "dependencies": { 11266 + "ansi-styles": "^4.0.0", 11267 + "string-width": "^4.1.0", 11268 + "strip-ansi": "^6.0.0" 11269 + }, 11270 + "engines": { 11271 + "node": ">=10" 11272 + }, 11273 + "funding": { 11274 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 11275 + } 11276 + }, 11277 + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 11278 + "version": "4.3.0", 11279 + "inBundle": true, 11280 + "license": "MIT", 11281 + "dependencies": { 11282 + "color-convert": "^2.0.1" 11283 + }, 11284 + "engines": { 11285 + "node": ">=8" 11286 + }, 11287 + "funding": { 11288 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 11289 + } 11290 + }, 11291 + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { 11292 + "version": "6.2.2", 11293 + "inBundle": true, 11294 + "license": "MIT", 11295 + "engines": { 11296 + "node": ">=12" 11297 + }, 11298 + "funding": { 11299 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 11300 + } 11301 + }, 11302 + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { 11303 + "version": "9.2.2", 11304 + "inBundle": true, 11305 + "license": "MIT" 11306 + }, 11307 + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { 11308 + "version": "5.1.2", 11309 + "inBundle": true, 11310 + "license": "MIT", 11311 + "dependencies": { 11312 + "eastasianwidth": "^0.2.0", 11313 + "emoji-regex": "^9.2.2", 11314 + "strip-ansi": "^7.0.1" 11315 + }, 11316 + "engines": { 11317 + "node": ">=12" 11318 + }, 11319 + "funding": { 11320 + "url": "https://github.com/sponsors/sindresorhus" 11321 + } 11322 + }, 11323 + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { 11324 + "version": "7.1.2", 11325 + "inBundle": true, 11326 + "license": "MIT", 11327 + "dependencies": { 11328 + "ansi-regex": "^6.0.1" 11329 + }, 11330 + "engines": { 11331 + "node": ">=12" 11332 + }, 11333 + "funding": { 11334 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 11335 + } 11336 + }, 11337 + "node_modules/npm/node_modules/write-file-atomic": { 11338 + "version": "6.0.0", 11339 + "inBundle": true, 11340 + "license": "ISC", 11341 + "dependencies": { 11342 + "imurmurhash": "^0.1.4", 11343 + "signal-exit": "^4.0.1" 11344 + }, 11345 + "engines": { 11346 + "node": "^18.17.0 || >=20.5.0" 11347 + } 11348 + }, 11349 + "node_modules/npm/node_modules/yallist": { 11350 + "version": "4.0.0", 11351 + "inBundle": true, 11352 + "license": "ISC" 11353 + }, 6860 11354 "node_modules/nwsapi": { 6861 11355 "version": "2.2.21", 6862 11356 "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", ··· 7042 11536 "url": "https://github.com/sponsors/sindresorhus" 7043 11537 } 7044 11538 }, 11539 + "node_modules/package-manager-detector": { 11540 + "version": "1.4.1", 11541 + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.4.1.tgz", 11542 + "integrity": "sha512-dSMiVLBEA4XaNJ0PRb4N5cV/SEP4BWrWZKBmfF+OUm2pQTiZ6DDkKeWaltwu3JRhLoy59ayIkJ00cx9K9CaYTg==", 11543 + "dev": true, 11544 + "license": "MIT" 11545 + }, 7045 11546 "node_modules/parent-module": { 7046 11547 "version": "1.0.1", 7047 11548 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 7048 11549 "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 7049 11550 "dev": true, 7050 11551 "license": "MIT", 7051 - "peer": true, 7052 11552 "dependencies": { 7053 11553 "callsites": "^3.0.0" 7054 11554 }, ··· 7056 11556 "node": ">=6" 7057 11557 } 7058 11558 }, 11559 + "node_modules/parse-json": { 11560 + "version": "5.2.0", 11561 + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 11562 + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 11563 + "dev": true, 11564 + "license": "MIT", 11565 + "dependencies": { 11566 + "@babel/code-frame": "^7.0.0", 11567 + "error-ex": "^1.3.1", 11568 + "json-parse-even-better-errors": "^2.3.0", 11569 + "lines-and-columns": "^1.1.6" 11570 + }, 11571 + "engines": { 11572 + "node": ">=8" 11573 + }, 11574 + "funding": { 11575 + "url": "https://github.com/sponsors/sindresorhus" 11576 + } 11577 + }, 7059 11578 "node_modules/parse5": { 7060 11579 "version": "7.3.0", 7061 11580 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", ··· 7104 11623 "dev": true, 7105 11624 "license": "MIT" 7106 11625 }, 11626 + "node_modules/path-type": { 11627 + "version": "4.0.0", 11628 + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 11629 + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 11630 + "dev": true, 11631 + "license": "MIT", 11632 + "engines": { 11633 + "node": ">=8" 11634 + } 11635 + }, 7107 11636 "node_modules/pathe": { 7108 11637 "version": "2.0.3", 7109 11638 "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", ··· 7137 11666 }, 7138 11667 "funding": { 7139 11668 "url": "https://github.com/sponsors/jonschlinkert" 11669 + } 11670 + }, 11671 + "node_modules/pkg-types": { 11672 + "version": "2.3.0", 11673 + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", 11674 + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", 11675 + "dev": true, 11676 + "license": "MIT", 11677 + "dependencies": { 11678 + "confbox": "^0.2.2", 11679 + "exsolve": "^1.0.7", 11680 + "pathe": "^2.0.3" 7140 11681 } 7141 11682 }, 7142 11683 "node_modules/player.style": { ··· 7261 11802 "node": ">=6" 7262 11803 } 7263 11804 }, 11805 + "node_modules/quansync": { 11806 + "version": "0.2.11", 11807 + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", 11808 + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", 11809 + "dev": true, 11810 + "funding": [ 11811 + { 11812 + "type": "individual", 11813 + "url": "https://github.com/sponsors/antfu" 11814 + }, 11815 + { 11816 + "type": "individual", 11817 + "url": "https://github.com/sponsors/sxzz" 11818 + } 11819 + ], 11820 + "license": "MIT" 11821 + }, 7264 11822 "node_modules/queue-microtask": { 7265 11823 "version": "1.2.3", 7266 11824 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", ··· 7282 11840 ], 7283 11841 "license": "MIT" 7284 11842 }, 11843 + "node_modules/radix-ui": { 11844 + "version": "1.4.3", 11845 + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", 11846 + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", 11847 + "dependencies": { 11848 + "@radix-ui/primitive": "1.1.3", 11849 + "@radix-ui/react-accessible-icon": "1.1.7", 11850 + "@radix-ui/react-accordion": "1.2.12", 11851 + "@radix-ui/react-alert-dialog": "1.1.15", 11852 + "@radix-ui/react-arrow": "1.1.7", 11853 + "@radix-ui/react-aspect-ratio": "1.1.7", 11854 + "@radix-ui/react-avatar": "1.1.10", 11855 + "@radix-ui/react-checkbox": "1.3.3", 11856 + "@radix-ui/react-collapsible": "1.1.12", 11857 + "@radix-ui/react-collection": "1.1.7", 11858 + "@radix-ui/react-compose-refs": "1.1.2", 11859 + "@radix-ui/react-context": "1.1.2", 11860 + "@radix-ui/react-context-menu": "2.2.16", 11861 + "@radix-ui/react-dialog": "1.1.15", 11862 + "@radix-ui/react-direction": "1.1.1", 11863 + "@radix-ui/react-dismissable-layer": "1.1.11", 11864 + "@radix-ui/react-dropdown-menu": "2.1.16", 11865 + "@radix-ui/react-focus-guards": "1.1.3", 11866 + "@radix-ui/react-focus-scope": "1.1.7", 11867 + "@radix-ui/react-form": "0.1.8", 11868 + "@radix-ui/react-hover-card": "1.1.15", 11869 + "@radix-ui/react-label": "2.1.7", 11870 + "@radix-ui/react-menu": "2.1.16", 11871 + "@radix-ui/react-menubar": "1.1.16", 11872 + "@radix-ui/react-navigation-menu": "1.2.14", 11873 + "@radix-ui/react-one-time-password-field": "0.1.8", 11874 + "@radix-ui/react-password-toggle-field": "0.1.3", 11875 + "@radix-ui/react-popover": "1.1.15", 11876 + "@radix-ui/react-popper": "1.2.8", 11877 + "@radix-ui/react-portal": "1.1.9", 11878 + "@radix-ui/react-presence": "1.1.5", 11879 + "@radix-ui/react-primitive": "2.1.3", 11880 + "@radix-ui/react-progress": "1.1.7", 11881 + "@radix-ui/react-radio-group": "1.3.8", 11882 + "@radix-ui/react-roving-focus": "1.1.11", 11883 + "@radix-ui/react-scroll-area": "1.2.10", 11884 + "@radix-ui/react-select": "2.2.6", 11885 + "@radix-ui/react-separator": "1.1.7", 11886 + "@radix-ui/react-slider": "1.3.6", 11887 + "@radix-ui/react-slot": "1.2.3", 11888 + "@radix-ui/react-switch": "1.2.6", 11889 + "@radix-ui/react-tabs": "1.1.13", 11890 + "@radix-ui/react-toast": "1.2.15", 11891 + "@radix-ui/react-toggle": "1.1.10", 11892 + "@radix-ui/react-toggle-group": "1.1.11", 11893 + "@radix-ui/react-toolbar": "1.1.11", 11894 + "@radix-ui/react-tooltip": "1.2.8", 11895 + "@radix-ui/react-use-callback-ref": "1.1.1", 11896 + "@radix-ui/react-use-controllable-state": "1.2.2", 11897 + "@radix-ui/react-use-effect-event": "0.0.2", 11898 + "@radix-ui/react-use-escape-keydown": "1.1.1", 11899 + "@radix-ui/react-use-is-hydrated": "0.1.0", 11900 + "@radix-ui/react-use-layout-effect": "1.1.1", 11901 + "@radix-ui/react-use-size": "1.1.1", 11902 + "@radix-ui/react-visually-hidden": "1.2.3" 11903 + }, 11904 + "peerDependencies": { 11905 + "@types/react": "*", 11906 + "@types/react-dom": "*", 11907 + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", 11908 + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" 11909 + }, 11910 + "peerDependenciesMeta": { 11911 + "@types/react": { 11912 + "optional": true 11913 + }, 11914 + "@types/react-dom": { 11915 + "optional": true 11916 + } 11917 + } 11918 + }, 7285 11919 "node_modules/react": { 7286 11920 "version": "19.1.1", 7287 11921 "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", ··· 7343 11977 "node": ">=0.10.0" 7344 11978 } 7345 11979 }, 11980 + "node_modules/react-remove-scroll": { 11981 + "version": "2.7.1", 11982 + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", 11983 + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", 11984 + "dependencies": { 11985 + "react-remove-scroll-bar": "^2.3.7", 11986 + "react-style-singleton": "^2.2.3", 11987 + "tslib": "^2.1.0", 11988 + "use-callback-ref": "^1.3.3", 11989 + "use-sidecar": "^1.1.3" 11990 + }, 11991 + "engines": { 11992 + "node": ">=10" 11993 + }, 11994 + "peerDependencies": { 11995 + "@types/react": "*", 11996 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" 11997 + }, 11998 + "peerDependenciesMeta": { 11999 + "@types/react": { 12000 + "optional": true 12001 + } 12002 + } 12003 + }, 12004 + "node_modules/react-remove-scroll-bar": { 12005 + "version": "2.3.8", 12006 + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", 12007 + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", 12008 + "dependencies": { 12009 + "react-style-singleton": "^2.2.2", 12010 + "tslib": "^2.0.0" 12011 + }, 12012 + "engines": { 12013 + "node": ">=10" 12014 + }, 12015 + "peerDependencies": { 12016 + "@types/react": "*", 12017 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 12018 + }, 12019 + "peerDependenciesMeta": { 12020 + "@types/react": { 12021 + "optional": true 12022 + } 12023 + } 12024 + }, 12025 + "node_modules/react-style-singleton": { 12026 + "version": "2.2.3", 12027 + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", 12028 + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", 12029 + "dependencies": { 12030 + "get-nonce": "^1.0.0", 12031 + "tslib": "^2.0.0" 12032 + }, 12033 + "engines": { 12034 + "node": ">=10" 12035 + }, 12036 + "peerDependencies": { 12037 + "@types/react": "*", 12038 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" 12039 + }, 12040 + "peerDependenciesMeta": { 12041 + "@types/react": { 12042 + "optional": true 12043 + } 12044 + } 12045 + }, 7346 12046 "node_modules/readdirp": { 7347 12047 "version": "3.6.0", 7348 12048 "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", ··· 7448 12148 "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 7449 12149 "dev": true, 7450 12150 "license": "MIT", 7451 - "peer": true, 7452 12151 "engines": { 7453 12152 "node": ">=4" 7454 12153 } ··· 7630 12329 "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", 7631 12330 "license": "MIT" 7632 12331 }, 12332 + "node_modules/scule": { 12333 + "version": "1.3.0", 12334 + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", 12335 + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", 12336 + "dev": true, 12337 + "license": "MIT" 12338 + }, 7633 12339 "node_modules/semver": { 7634 12340 "version": "6.3.1", 7635 12341 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", ··· 7817 12523 "dev": true, 7818 12524 "license": "ISC" 7819 12525 }, 12526 + "node_modules/snake-case": { 12527 + "version": "3.0.4", 12528 + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", 12529 + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", 12530 + "dev": true, 12531 + "license": "MIT", 12532 + "dependencies": { 12533 + "dot-case": "^3.0.4", 12534 + "tslib": "^2.0.3" 12535 + } 12536 + }, 7820 12537 "node_modules/solid-js": { 7821 12538 "version": "1.9.9", 7822 12539 "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", ··· 8000 12717 } 8001 12718 }, 8002 12719 "node_modules/strip-literal": { 8003 - "version": "3.0.0", 8004 - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", 8005 - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", 12720 + "version": "3.1.0", 12721 + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", 12722 + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", 8006 12723 "dev": true, 8007 12724 "license": "MIT", 8008 12725 "dependencies": { ··· 8052 12769 "url": "https://github.com/sponsors/ljharb" 8053 12770 } 8054 12771 }, 12772 + "node_modules/svg-parser": { 12773 + "version": "2.0.4", 12774 + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", 12775 + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", 12776 + "dev": true, 12777 + "license": "MIT" 12778 + }, 8055 12779 "node_modules/symbol-tree": { 8056 12780 "version": "3.2.4", 8057 12781 "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", ··· 8064 12788 "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", 8065 12789 "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", 8066 12790 "license": "MIT" 12791 + }, 12792 + "node_modules/tanstack-router-keepalive": { 12793 + "version": "1.0.0", 12794 + "resolved": "https://registry.npmjs.org/tanstack-router-keepalive/-/tanstack-router-keepalive-1.0.0.tgz", 12795 + "integrity": "sha512-SxMl9sgIZGjB4OZvGXufTz14ygmZi+eAbrhz3sjmXYZzhSQOekx5LYi9TvKcMXVu8fQR6W/itF5hdglqD6WB/w==", 12796 + "license": "MIT", 12797 + "dependencies": { 12798 + "eventemitter3": "^5.0.1", 12799 + "lodash.clonedeep": "^4.5.0" 12800 + } 8067 12801 }, 8068 12802 "node_modules/tapable": { 8069 12803 "version": "2.2.3", ··· 8137 12871 "license": "MIT" 8138 12872 }, 8139 12873 "node_modules/tinyglobby": { 8140 - "version": "0.2.14", 8141 - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 8142 - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 12874 + "version": "0.2.15", 12875 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 12876 + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 8143 12877 "license": "MIT", 8144 12878 "dependencies": { 8145 - "fdir": "^6.4.4", 8146 - "picomatch": "^4.0.2" 12879 + "fdir": "^6.5.0", 12880 + "picomatch": "^4.0.3" 8147 12881 }, 8148 12882 "engines": { 8149 12883 "node": ">=12.0.0" ··· 8521 13255 "node": "*" 8522 13256 } 8523 13257 }, 13258 + "node_modules/ufo": { 13259 + "version": "1.6.1", 13260 + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", 13261 + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", 13262 + "dev": true, 13263 + "license": "MIT" 13264 + }, 8524 13265 "node_modules/uint8arrays": { 8525 13266 "version": "3.0.0", 8526 13267 "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", ··· 8556 13297 "devOptional": true, 8557 13298 "license": "MIT" 8558 13299 }, 13300 + "node_modules/unimport": { 13301 + "version": "5.5.0", 13302 + "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.5.0.tgz", 13303 + "integrity": "sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==", 13304 + "dev": true, 13305 + "license": "MIT", 13306 + "dependencies": { 13307 + "acorn": "^8.15.0", 13308 + "escape-string-regexp": "^5.0.0", 13309 + "estree-walker": "^3.0.3", 13310 + "local-pkg": "^1.1.2", 13311 + "magic-string": "^0.30.19", 13312 + "mlly": "^1.8.0", 13313 + "pathe": "^2.0.3", 13314 + "picomatch": "^4.0.3", 13315 + "pkg-types": "^2.3.0", 13316 + "scule": "^1.3.0", 13317 + "strip-literal": "^3.1.0", 13318 + "tinyglobby": "^0.2.15", 13319 + "unplugin": "^2.3.10", 13320 + "unplugin-utils": "^0.3.0" 13321 + }, 13322 + "engines": { 13323 + "node": ">=18.12.0" 13324 + } 13325 + }, 13326 + "node_modules/unimport/node_modules/escape-string-regexp": { 13327 + "version": "5.0.0", 13328 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", 13329 + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", 13330 + "dev": true, 13331 + "license": "MIT", 13332 + "engines": { 13333 + "node": ">=12" 13334 + }, 13335 + "funding": { 13336 + "url": "https://github.com/sponsors/sindresorhus" 13337 + } 13338 + }, 13339 + "node_modules/unimport/node_modules/picomatch": { 13340 + "version": "4.0.3", 13341 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 13342 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 13343 + "dev": true, 13344 + "license": "MIT", 13345 + "engines": { 13346 + "node": ">=12" 13347 + }, 13348 + "funding": { 13349 + "url": "https://github.com/sponsors/jonschlinkert" 13350 + } 13351 + }, 8559 13352 "node_modules/unplugin": { 8560 - "version": "2.3.9", 8561 - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.9.tgz", 8562 - "integrity": "sha512-2dcbZq6aprwXTkzptq3k5qm5B8cvpjG9ynPd5fyM2wDJuuF7PeUK64Sxf0d+X1ZyDOeGydbNzMqBSIVlH8GIfA==", 13353 + "version": "2.3.10", 13354 + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", 13355 + "integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==", 8563 13356 "license": "MIT", 8564 13357 "dependencies": { 8565 13358 "@jridgewell/remapping": "^2.3.5", ··· 8571 13364 "node": ">=18.12.0" 8572 13365 } 8573 13366 }, 13367 + "node_modules/unplugin-auto-import": { 13368 + "version": "20.2.0", 13369 + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-20.2.0.tgz", 13370 + "integrity": "sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==", 13371 + "dev": true, 13372 + "license": "MIT", 13373 + "dependencies": { 13374 + "local-pkg": "^1.1.2", 13375 + "magic-string": "^0.30.19", 13376 + "picomatch": "^4.0.3", 13377 + "unimport": "^5.4.0", 13378 + "unplugin": "^2.3.10", 13379 + "unplugin-utils": "^0.3.0" 13380 + }, 13381 + "engines": { 13382 + "node": ">=14" 13383 + }, 13384 + "funding": { 13385 + "url": "https://github.com/sponsors/antfu" 13386 + }, 13387 + "peerDependencies": { 13388 + "@nuxt/kit": "^4.0.0", 13389 + "@vueuse/core": "*" 13390 + }, 13391 + "peerDependenciesMeta": { 13392 + "@nuxt/kit": { 13393 + "optional": true 13394 + }, 13395 + "@vueuse/core": { 13396 + "optional": true 13397 + } 13398 + } 13399 + }, 13400 + "node_modules/unplugin-auto-import/node_modules/picomatch": { 13401 + "version": "4.0.3", 13402 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 13403 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 13404 + "dev": true, 13405 + "license": "MIT", 13406 + "engines": { 13407 + "node": ">=12" 13408 + }, 13409 + "funding": { 13410 + "url": "https://github.com/sponsors/jonschlinkert" 13411 + } 13412 + }, 13413 + "node_modules/unplugin-icons": { 13414 + "version": "22.4.2", 13415 + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-22.4.2.tgz", 13416 + "integrity": "sha512-Yv15405unO67Chme0Slk0JRA/H2AiAZLK5t7ebt8/ZpTDlBfM4d4En2qD3MX2rzOSkIteQ0syIm3q8MSofeoBA==", 13417 + "dev": true, 13418 + "license": "MIT", 13419 + "dependencies": { 13420 + "@antfu/install-pkg": "^1.1.0", 13421 + "@iconify/utils": "^3.0.2", 13422 + "debug": "^4.4.3", 13423 + "local-pkg": "^1.1.2", 13424 + "unplugin": "^2.3.10" 13425 + }, 13426 + "funding": { 13427 + "url": "https://github.com/sponsors/antfu" 13428 + }, 13429 + "peerDependencies": { 13430 + "@svgr/core": ">=7.0.0", 13431 + "@svgx/core": "^1.0.1", 13432 + "@vue/compiler-sfc": "^3.0.2 || ^2.7.0", 13433 + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0", 13434 + "vue-template-compiler": "^2.6.12", 13435 + "vue-template-es2015-compiler": "^1.9.0" 13436 + }, 13437 + "peerDependenciesMeta": { 13438 + "@svgr/core": { 13439 + "optional": true 13440 + }, 13441 + "@svgx/core": { 13442 + "optional": true 13443 + }, 13444 + "@vue/compiler-sfc": { 13445 + "optional": true 13446 + }, 13447 + "svelte": { 13448 + "optional": true 13449 + }, 13450 + "vue-template-compiler": { 13451 + "optional": true 13452 + }, 13453 + "vue-template-es2015-compiler": { 13454 + "optional": true 13455 + } 13456 + } 13457 + }, 13458 + "node_modules/unplugin-utils": { 13459 + "version": "0.3.1", 13460 + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", 13461 + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", 13462 + "dev": true, 13463 + "license": "MIT", 13464 + "dependencies": { 13465 + "pathe": "^2.0.3", 13466 + "picomatch": "^4.0.3" 13467 + }, 13468 + "engines": { 13469 + "node": ">=20.19.0" 13470 + }, 13471 + "funding": { 13472 + "url": "https://github.com/sponsors/sxzz" 13473 + } 13474 + }, 13475 + "node_modules/unplugin-utils/node_modules/picomatch": { 13476 + "version": "4.0.3", 13477 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", 13478 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", 13479 + "dev": true, 13480 + "license": "MIT", 13481 + "engines": { 13482 + "node": ">=12" 13483 + }, 13484 + "funding": { 13485 + "url": "https://github.com/sponsors/jonschlinkert" 13486 + } 13487 + }, 8574 13488 "node_modules/unplugin/node_modules/picomatch": { 8575 13489 "version": "4.0.3", 8576 13490 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", ··· 8622 13536 "peer": true, 8623 13537 "dependencies": { 8624 13538 "punycode": "^2.1.0" 13539 + } 13540 + }, 13541 + "node_modules/use-callback-ref": { 13542 + "version": "1.3.3", 13543 + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", 13544 + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", 13545 + "dependencies": { 13546 + "tslib": "^2.0.0" 13547 + }, 13548 + "engines": { 13549 + "node": ">=10" 13550 + }, 13551 + "peerDependencies": { 13552 + "@types/react": "*", 13553 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" 13554 + }, 13555 + "peerDependenciesMeta": { 13556 + "@types/react": { 13557 + "optional": true 13558 + } 13559 + } 13560 + }, 13561 + "node_modules/use-sidecar": { 13562 + "version": "1.1.3", 13563 + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", 13564 + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", 13565 + "dependencies": { 13566 + "detect-node-es": "^1.1.0", 13567 + "tslib": "^2.0.0" 13568 + }, 13569 + "engines": { 13570 + "node": ">=10" 13571 + }, 13572 + "peerDependencies": { 13573 + "@types/react": "*", 13574 + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" 13575 + }, 13576 + "peerDependenciesMeta": { 13577 + "@types/react": { 13578 + "optional": true 13579 + } 8625 13580 } 8626 13581 }, 8627 13582 "node_modules/use-sync-external-store": {
+18 -1
package.json
··· 12 12 "dependencies": { 13 13 "@atproto/api": "^0.16.6", 14 14 "@atproto/oauth-client-browser": "^0.3.33", 15 + "@radix-ui/react-dialog": "^1.1.15", 16 + "@radix-ui/react-dropdown-menu": "^2.1.16", 17 + "@radix-ui/react-hover-card": "^1.1.15", 18 + "@radix-ui/react-slider": "^1.3.6", 15 19 "@tailwindcss/vite": "^4.0.6", 16 20 "@tanstack/query-sync-storage-persister": "^5.85.6", 17 21 "@tanstack/react-devtools": "^0.2.2", ··· 20 24 "@tanstack/react-router": "^1.130.2", 21 25 "@tanstack/react-router-devtools": "^1.131.5", 22 26 "@tanstack/router-plugin": "^1.121.2", 27 + "dompurify": "^3.3.0", 28 + "i": "^0.3.7", 23 29 "idb-keyval": "^6.2.2", 24 30 "jotai": "^2.13.1", 31 + "npm": "^11.6.2", 32 + "radix-ui": "^1.4.3", 25 33 "react": "^19.0.0", 26 34 "react-dom": "^19.0.0", 27 35 "react-player": "^3.3.2", 28 - "tailwindcss": "^4.0.6" 36 + "tailwindcss": "^4.0.6", 37 + "tanstack-router-keepalive": "^1.0.0" 29 38 }, 30 39 "devDependencies": { 31 40 "@eslint-react/eslint-plugin": "^2.2.1", 41 + "@iconify-icon/react": "^3.0.1", 42 + "@iconify-json/material-symbols": "^1.2.42", 43 + "@iconify-json/mdi": "^1.2.3", 44 + "@iconify/json": "^2.2.396", 45 + "@svgr/core": "^8.1.0", 46 + "@svgr/plugin-jsx": "^8.1.0", 32 47 "@testing-library/dom": "^10.4.0", 33 48 "@testing-library/react": "^16.2.0", 34 49 "@types/node": "^24.3.0", ··· 46 61 "prettier": "^3.6.2", 47 62 "typescript": "^5.7.2", 48 63 "typescript-eslint": "^8.46.1", 64 + "unplugin-auto-import": "^20.2.0", 65 + "unplugin-icons": "^22.4.2", 49 66 "vite": "^6.3.5", 50 67 "vitest": "^3.0.5", 51 68 "web-vitals": "^4.2.4"
public/screenshot.jpg

This is a binary file and will not be displayed.

public/screenshot.png

This is a binary file and will not be displayed.

+22
src/auto-imports.d.ts
··· 1 + /* eslint-disable */ 2 + /* prettier-ignore */ 3 + // @ts-nocheck 4 + // noinspection JSUnusedGlobalSymbols 5 + // Generated by unplugin-auto-import 6 + // biome-ignore lint: disable 7 + export {} 8 + declare global { 9 + const IconMaterialSymbolsAccountCircle: typeof import('~icons/material-symbols/account-circle.jsx').default 10 + const IconMaterialSymbolsAccountCircleOutline: typeof import('~icons/material-symbols/account-circle-outline.jsx').default 11 + const IconMaterialSymbolsArrowBack: typeof import('~icons/material-symbols/arrow-back.jsx').default 12 + const IconMaterialSymbolsHome: typeof import('~icons/material-symbols/home.jsx').default 13 + const IconMaterialSymbolsHomeOutline: typeof import('~icons/material-symbols/home-outline.jsx').default 14 + const IconMaterialSymbolsNotifications: typeof import('~icons/material-symbols/notifications.jsx').default 15 + const IconMaterialSymbolsNotificationsOutline: typeof import('~icons/material-symbols/notifications-outline.jsx').default 16 + const IconMaterialSymbolsSearch: typeof import('~icons/material-symbols/search.jsx').default 17 + const IconMaterialSymbolsSettings: typeof import('~icons/material-symbols/settings.jsx').default 18 + const IconMaterialSymbolsSettingsOutline: typeof import('~icons/material-symbols/settings-outline.jsx').default 19 + const IconMaterialSymbolsTag: typeof import('~icons/material-symbols/tag.jsx').default 20 + const IconMdiAccountCircle: typeof import('~icons/mdi/account-circle.jsx').default 21 + const IconMdiPencilOutline: typeof import('~icons/mdi/pencil-outline.jsx').default 22 + }
+292
src/components/Composer.tsx
··· 1 + import { AppBskyRichtextFacet, RichText } from "@atproto/api"; 2 + import { useAtom } from "jotai"; 3 + import { Dialog } from "radix-ui"; 4 + import { useEffect, useRef, useState } from "react"; 5 + 6 + import { useAuth } from "~/providers/UnifiedAuthProvider"; 7 + import { composerAtom } from "~/utils/atoms"; 8 + import { useQueryPost } from "~/utils/useQuery"; 9 + 10 + import { ProfileThing } from "./Login"; 11 + import { UniversalPostRendererATURILoader } from "./UniversalPostRenderer"; 12 + 13 + const MAX_POST_LENGTH = 300; 14 + 15 + export function Composer() { 16 + const [composerState, setComposerState] = useAtom(composerAtom); 17 + const { agent } = useAuth(); 18 + 19 + const [postText, setPostText] = useState(""); 20 + const [posting, setPosting] = useState(false); 21 + const [postSuccess, setPostSuccess] = useState(false); 22 + const [postError, setPostError] = useState<string | null>(null); 23 + 24 + useEffect(() => { 25 + setPostText(""); 26 + setPosting(false); 27 + setPostSuccess(false); 28 + setPostError(null); 29 + }, [composerState.kind]); 30 + 31 + const parentUri = 32 + composerState.kind === "reply" 33 + ? composerState.parent 34 + : composerState.kind === "quote" 35 + ? composerState.subject 36 + : undefined; 37 + 38 + const { data: parentPost, isLoading: isParentLoading } = 39 + useQueryPost(parentUri); 40 + 41 + async function handlePost() { 42 + if (!agent || !postText.trim() || postText.length > MAX_POST_LENGTH) return; 43 + 44 + setPosting(true); 45 + setPostError(null); 46 + 47 + try { 48 + const rt = new RichText({ text: postText }); 49 + await rt.detectFacets(agent); 50 + 51 + if (rt.facets?.length) { 52 + rt.facets = rt.facets.filter((item) => { 53 + if (item.$type !== "app.bsky.richtext.facet") return true; 54 + if (!item.features?.length) return true; 55 + 56 + item.features = item.features.filter((feature) => { 57 + if (feature.$type !== "app.bsky.richtext.facet#mention") return true; 58 + const did = feature.$type === "app.bsky.richtext.facet#mention" ? (feature as AppBskyRichtextFacet.Mention)?.did : undefined; 59 + return typeof did === "string" && did.startsWith("did:"); 60 + }); 61 + 62 + return item.features.length > 0; 63 + }); 64 + } 65 + 66 + const record: Record<string, unknown> = { 67 + $type: "app.bsky.feed.post", 68 + text: rt.text, 69 + facets: rt.facets, 70 + createdAt: new Date().toISOString(), 71 + }; 72 + 73 + if (composerState.kind === "reply" && parentPost) { 74 + record.reply = { 75 + root: parentPost.value?.reply?.root ?? { 76 + uri: parentPost.uri, 77 + cid: parentPost.cid, 78 + }, 79 + parent: { 80 + uri: parentPost.uri, 81 + cid: parentPost.cid, 82 + }, 83 + }; 84 + } 85 + 86 + if (composerState.kind === "quote" && parentPost) { 87 + record.embed = { 88 + $type: "app.bsky.embed.record", 89 + record: { 90 + uri: parentPost.uri, 91 + cid: parentPost.cid, 92 + }, 93 + }; 94 + } 95 + 96 + await agent.com.atproto.repo.createRecord({ 97 + collection: "app.bsky.feed.post", 98 + repo: agent.assertDid, 99 + record, 100 + }); 101 + 102 + setPostSuccess(true); 103 + setPostText(""); 104 + 105 + setTimeout(() => { 106 + setPostSuccess(false); 107 + setComposerState({ kind: "closed" }); 108 + }, 1500); 109 + } catch (e: any) { 110 + setPostError(e?.message || "Failed to post"); 111 + } finally { 112 + setPosting(false); 113 + } 114 + } 115 + // if (composerState.kind === "closed") { 116 + // return null; 117 + // } 118 + 119 + const getPlaceholder = () => { 120 + switch (composerState.kind) { 121 + case "reply": 122 + return "Post your reply"; 123 + case "quote": 124 + return "Add a comment..."; 125 + case "root": 126 + default: 127 + return "What's happening?!"; 128 + } 129 + }; 130 + 131 + const charsLeft = MAX_POST_LENGTH - postText.length; 132 + const isPostButtonDisabled = 133 + posting || !postText.trim() || isParentLoading || charsLeft < 0; 134 + 135 + return ( 136 + <Dialog.Root 137 + open={composerState.kind !== "closed"} 138 + onOpenChange={(open) => { 139 + if (!open) setComposerState({ kind: "closed" }); 140 + }} 141 + > 142 + <Dialog.Portal> 143 + <Dialog.Overlay className="fixed inset-0 z-50 bg-black/40 dark:bg-black/50 data-[state=open]:animate-fadeIn" /> 144 + 145 + <Dialog.Content className="fixed overflow-y-scroll inset-0 z-50 flex items-start justify-center py-10 sm:py-20"> 146 + <div className="bg-gray-50 dark:bg-gray-950 border border-gray-200 dark:border-gray-700 rounded-2xl shadow-xl w-full max-w-xl relative mx-4"> 147 + <div className="flex flex-row justify-between p-2"> 148 + <Dialog.Close asChild> 149 + <button 150 + className="h-8 w-8 flex items-center justify-center rounded-full text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800" 151 + disabled={posting} 152 + aria-label="Close" 153 + > 154 + <svg 155 + xmlns="http://www.w3.org/2000/svg" 156 + width="20" 157 + height="20" 158 + viewBox="0 0 24 24" 159 + fill="none" 160 + stroke="currentColor" 161 + strokeWidth="2.5" 162 + strokeLinecap="round" 163 + strokeLinejoin="round" 164 + > 165 + <line x1="18" y1="6" x2="6" y2="18"></line> 166 + <line x1="6" y1="6" x2="18" y2="18"></line> 167 + </svg> 168 + </button> 169 + </Dialog.Close> 170 + 171 + <div className="flex-1" /> 172 + <div className="flex items-center gap-4"> 173 + <span 174 + className={`text-sm ${charsLeft < 0 ? "text-red-500" : "text-gray-500"}`} 175 + > 176 + {charsLeft} 177 + </span> 178 + <button 179 + className="bg-gray-600 hover:bg-gray-700 text-white font-bold py-1 px-4 rounded-full disabled:opacity-50 disabled:cursor-not-allowed transition-colors" 180 + onClick={handlePost} 181 + disabled={isPostButtonDisabled} 182 + > 183 + {posting ? "Posting..." : "Post"} 184 + </button> 185 + </div> 186 + </div> 187 + 188 + {postSuccess ? ( 189 + <div className="flex flex-col items-center justify-center py-16"> 190 + <span className="text-gray-500 text-6xl mb-4">โœ“</span> 191 + <span className="text-xl font-bold text-black dark:text-white"> 192 + Posted! 193 + </span> 194 + </div> 195 + ) : ( 196 + <div className="px-4"> 197 + {composerState.kind === "reply" && ( 198 + <div className="mb-1 -mx-4"> 199 + {isParentLoading ? ( 200 + <div className="text-sm text-gray-500 animate-pulse"> 201 + Loading parent post... 202 + </div> 203 + ) : parentUri ? ( 204 + <UniversalPostRendererATURILoader 205 + atUri={parentUri} 206 + bottomReplyLine 207 + bottomBorder={false} 208 + /> 209 + ) : ( 210 + <div className="text-sm text-red-500 rounded-lg border border-red-500/50 p-3"> 211 + Could not load parent post. 212 + </div> 213 + )} 214 + </div> 215 + )} 216 + 217 + <div className="flex w-full gap-1 flex-col"> 218 + <ProfileThing agent={agent} large /> 219 + <div className="flex pl-[50px]"> 220 + <AutoGrowTextarea 221 + className="w-full text-lg bg-transparent focus:outline-none resize-none placeholder:text-gray-500 text-black dark:text-white pb-2" 222 + rows={5} 223 + placeholder={getPlaceholder()} 224 + value={postText} 225 + onChange={(e) => setPostText(e.target.value)} 226 + disabled={posting} 227 + autoFocus 228 + /> 229 + </div> 230 + </div> 231 + 232 + {composerState.kind === "quote" && ( 233 + <div className="mb-4 ml-[50px] rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden"> 234 + {isParentLoading ? ( 235 + <div className="text-sm text-gray-500 animate-pulse"> 236 + Loading parent post... 237 + </div> 238 + ) : parentUri ? ( 239 + <UniversalPostRendererATURILoader 240 + atUri={parentUri} 241 + isQuote 242 + /> 243 + ) : ( 244 + <div className="text-sm text-red-500 rounded-lg border border-red-500/50 p-3"> 245 + Could not load parent post. 246 + </div> 247 + )} 248 + </div> 249 + )} 250 + 251 + {postError && ( 252 + <div className="text-red-500 text-sm my-2 text-center"> 253 + {postError} 254 + </div> 255 + )} 256 + </div> 257 + )} 258 + </div> 259 + </Dialog.Content> 260 + </Dialog.Portal> 261 + </Dialog.Root> 262 + ); 263 + } 264 + 265 + function AutoGrowTextarea({ 266 + value, 267 + className, 268 + onChange, 269 + ...props 270 + }: React.DetailedHTMLProps< 271 + React.TextareaHTMLAttributes<HTMLTextAreaElement>, 272 + HTMLTextAreaElement 273 + >) { 274 + const ref = useRef<HTMLTextAreaElement>(null); 275 + 276 + useEffect(() => { 277 + const el = ref.current; 278 + if (!el) return; 279 + el.style.height = "auto"; 280 + el.style.height = el.scrollHeight + "px"; 281 + }, [value]); 282 + 283 + return ( 284 + <textarea 285 + ref={ref} 286 + className={className} 287 + value={value} 288 + onChange={onChange} 289 + {...props} 290 + /> 291 + ); 292 + }
+33
src/components/Header.tsx
··· 1 + import { Link, useRouter } from "@tanstack/react-router"; 2 + import { useAtom } from "jotai"; 3 + 4 + import { isAtTopAtom } from "~/utils/atoms"; 5 + 6 + export function Header({ 7 + backButtonCallback, 8 + title 9 + }: { 10 + backButtonCallback?: () => void; 11 + title?: string; 12 + }) { 13 + const router = useRouter(); 14 + const [isAtTop] = useAtom(isAtTopAtom); 15 + //const what = router.history. 16 + return ( 17 + <div className={`flex items-center gap-3 px-3 py-3 h-[52px] sticky top-0 bg-[var(--header-bg-light)] dark:bg-[var(--header-bg-dark)] z-10 border-0 sm:border-b ${!isAtTop && "shadow-sm"} sm:shadow-none sm:dark:bg-gray-950 sm:bg-white border-gray-200 dark:border-gray-700`}> 18 + {backButtonCallback ? (<Link 19 + to=".." 20 + //className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 21 + className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 22 + onClick={(e) => { 23 + e.preventDefault(); 24 + backButtonCallback(); 25 + }} 26 + aria-label="Go back" 27 + > 28 + <IconMaterialSymbolsArrowBack className="w-6 h-6" /> 29 + </Link>) : (<div className="w-[0px]" />)} 30 + <span className="text-[21px] sm:text-[19px] sm:font-semibold font-roboto">{title}</span> 31 + </div> 32 + ); 33 + }
+150
src/components/Import.tsx
··· 1 + import { AtUri } from "@atproto/api"; 2 + import { useNavigate, type UseNavigateResult } from "@tanstack/react-router"; 3 + import { useState } from "react"; 4 + 5 + /** 6 + * Basically the best equivalent to Search that i can do 7 + */ 8 + export function Import() { 9 + const [textInput, setTextInput] = useState<string | undefined>(); 10 + const navigate = useNavigate(); 11 + 12 + const handleEnter = () => { 13 + if (!textInput) return; 14 + handleImport({ 15 + text: textInput, 16 + navigate, 17 + }); 18 + }; 19 + 20 + return ( 21 + <div className="w-full relative"> 22 + <IconMaterialSymbolsSearch className="w-5 h-5 absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" /> 23 + 24 + <input 25 + type="text" 26 + placeholder="Import..." 27 + value={textInput} 28 + onChange={(e) => setTextInput(e.target.value)} 29 + onKeyDown={(e) => { 30 + if (e.key === "Enter") handleEnter(); 31 + }} 32 + className="w-full h-12 pl-12 pr-4 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-500 box-border transition" 33 + /> 34 + </div> 35 + ); 36 + } 37 + 38 + function handleImport({ 39 + text, 40 + navigate, 41 + }: { 42 + text: string; 43 + navigate: UseNavigateResult<string>; 44 + }) { 45 + const trimmed = text.trim(); 46 + // parse text 47 + /** 48 + * text might be 49 + * 1. bsky dot app url (reddwarf link segments might be uri encoded,) 50 + * 2. aturi 51 + * 3. plain handle 52 + * 4. plain did 53 + */ 54 + 55 + // 1. Check if itโ€™s a URL 56 + try { 57 + const url = new URL(text); 58 + const knownHosts = [ 59 + "bsky.app", 60 + "social.daniela.lol", 61 + "deer.social", 62 + "reddwarf.whey.party", 63 + "reddwarf.app", 64 + "main.bsky.dev", 65 + "catsky.social", 66 + "blacksky.community", 67 + "red-dwarf-social-app.whey.party", 68 + "zeppelin.social", 69 + ]; 70 + if (knownHosts.includes(url.hostname)) { 71 + // parse path to get URI or handle 72 + const path = decodeURIComponent(url.pathname.slice(1)); // remove leading / 73 + console.log("BSky URL path:", path); 74 + navigate({ 75 + to: `/${path}`, 76 + }); 77 + return; 78 + } 79 + } catch { 80 + // not a URL, continue 81 + } 82 + 83 + // 2. Check if text looks like an at-uri 84 + try { 85 + if (text.startsWith("at://")) { 86 + console.log("AT URI detected:", text); 87 + const aturi = new AtUri(text); 88 + switch (aturi.collection) { 89 + case "app.bsky.feed.post": { 90 + navigate({ 91 + to: "/profile/$did/post/$rkey", 92 + params: { 93 + did: aturi.host, 94 + rkey: aturi.rkey, 95 + }, 96 + }); 97 + return; 98 + } 99 + case "app.bsky.actor.profile": { 100 + navigate({ 101 + to: "/profile/$did", 102 + params: { 103 + did: aturi.host, 104 + }, 105 + }); 106 + return; 107 + } 108 + // todo add more handlers as more routes are added. like feeds, lists, etc etc thanks! 109 + default: { 110 + // continue 111 + } 112 + } 113 + } 114 + } catch { 115 + // continue 116 + } 117 + 118 + // 3. Plain handle (starts with @) 119 + try { 120 + if (text.startsWith("@")) { 121 + const handle = text.slice(1); 122 + console.log("Handle detected:", handle); 123 + navigate({ to: "/profile/$did", params: { did: handle } }); 124 + return; 125 + } 126 + } catch { 127 + // continue 128 + } 129 + 130 + // 4. Plain DID (starts with did:) 131 + try { 132 + if (text.startsWith("did:")) { 133 + console.log("did detected:", text); 134 + navigate({ to: "/profile/$did", params: { did: text } }); 135 + return; 136 + } 137 + } catch { 138 + // continue 139 + } 140 + 141 + // if all else fails 142 + 143 + // try { 144 + // // probably a user? 145 + // navigate({ to: "/profile/$did", params: { did: text } }); 146 + // return; 147 + // } catch { 148 + // // continue 149 + // } 150 + }
+36 -9
src/components/InfiniteCustomFeed.tsx
··· 1 + import { useQueryClient } from "@tanstack/react-query"; 1 2 import * as React from "react"; 3 + 2 4 //import { useInView } from "react-intersection-observer"; 3 5 import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 4 6 import { useAuth } from "~/providers/UnifiedAuthProvider"; 5 7 import { 6 - useQueryArbitrary, 7 - useQueryIdentity, 8 8 useInfiniteQueryFeedSkeleton, 9 + // useQueryArbitrary, 10 + // useQueryIdentity, 9 11 } from "~/utils/useQuery"; 10 12 11 13 interface InfiniteCustomFeedProps { ··· 36 38 isFetchingNextPage, 37 39 refetch, 38 40 isRefetching, 41 + queryKey, 39 42 } = useInfiniteQueryFeedSkeleton({ 40 43 feedUri: feedUri, 41 44 agent: agent ?? undefined, ··· 43 46 pdsUrl: pdsUrl, 44 47 feedServiceDid: feedServiceDid, 45 48 }); 49 + const queryClient = useQueryClient(); 50 + 46 51 47 52 const handleRefresh = () => { 53 + queryClient.removeQueries({queryKey: queryKey}); 54 + //queryClient.invalidateQueries(["infinite-feed", feedUri] as const); 48 55 refetch(); 49 56 }; 50 57 58 + const allPosts = React.useMemo(() => { 59 + const flattenedPosts = data?.pages.flatMap((page) => page?.feed) ?? []; 60 + 61 + const seenUris = new Set<string>(); 62 + 63 + return flattenedPosts.filter((item) => { 64 + if (!item?.post) return false; 65 + 66 + if (seenUris.has(item.post)) { 67 + return false; 68 + } 69 + 70 + seenUris.add(item.post); 71 + 72 + return true; 73 + }); 74 + }, [data]); 75 + 51 76 //const { ref, inView } = useInView(); 52 77 53 78 // React.useEffect(() => { ··· 66 91 ); 67 92 } 68 93 69 - const allPosts = 70 - data?.pages.flatMap((page) => { 71 - if (page) return page.feed; 72 - }) ?? []; 94 + // const allPosts = 95 + // data?.pages.flatMap((page) => { 96 + // if (page) return page.feed; 97 + // }) ?? []; 73 98 74 99 if (!allPosts || typeof allPosts !== "object" || allPosts.length === 0) { 75 100 return ( ··· 112 137 <button 113 138 onClick={handleRefresh} 114 139 disabled={isRefetching} 115 - className="sticky lg:bottom-6 bottom-24 ml-4 w-[42px] h-[42px] z-10 bg-gray-500 hover:bg-gray-600 text-gray-50 p-[9px] rounded-full shadow-lg transition-transform duration-200 ease-in-out hover:scale-110 disabled:bg-gray-400 disabled:cursor-not-allowed" 140 + className="sticky lg:bottom-4 bottom-22 ml-4 w-[42px] h-[42px] z-10 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-50 p-[9px] rounded-full shadow-lg transition-transform duration-200 ease-in-out hover:scale-110 disabled:dark:bg-gray-900 disabled:bg-gray-100 disabled:cursor-not-allowed" 116 141 aria-label="Refresh feed" 117 142 > 118 - {isRefetching ? <RefreshIcon className="h-6 w-6 animate-spin" /> : <RefreshIcon className="h-6 w-6" />} 143 + <RefreshIcon 144 + className={`h-6 w-6 text-gray-600 dark:text-gray-400 ${isRefetching && "animate-spin"}`} 145 + /> 119 146 </button> 120 147 </> 121 148 ); ··· 138 165 d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" 139 166 ></path> 140 167 </svg> 141 - ); 168 + );
+188 -58
src/components/Login.tsx
··· 1 1 // src/components/Login.tsx 2 - import React, { useEffect, useState, useRef } from "react"; 2 + import AtpAgent, { Agent } from "@atproto/api"; 3 + import { useAtom } from "jotai"; 4 + import React, { useEffect, useRef, useState } from "react"; 5 + 3 6 import { useAuth } from "~/providers/UnifiedAuthProvider"; 4 - import { Agent } from "@atproto/api"; 7 + import { imgCDNAtom } from "~/utils/atoms"; 8 + import { useQueryIdentity, useQueryProfile } from "~/utils/useQuery"; 5 9 6 10 // --- 1. The Main Component (Orchestrator with `compact` prop) --- 7 - export default function Login({ compact = false }: { compact?: boolean }) { 11 + export default function Login({ 12 + compact = false, 13 + popup = false, 14 + }: { 15 + compact?: boolean; 16 + popup?: boolean; 17 + }) { 8 18 const { status, agent, logout } = useAuth(); 9 19 10 20 // Loading state can be styled differently based on the prop ··· 14 24 className={ 15 25 compact 16 26 ? "flex items-center justify-center p-1" 17 - : "p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4 flex justify-center items-center h-[280px]" 27 + : "p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-4 mx-4 flex justify-center items-center h-[280px]" 18 28 } 19 29 > 20 30 <span ··· 33 43 // Large view 34 44 if (!compact) { 35 45 return ( 36 - <div className="p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4"> 46 + <div className="p-4 bg-gray-100 dark:bg-gray-900 rounded-xl border-gray-200 dark:border-gray-800 mt-4 mx-4"> 37 47 <div className="flex flex-col items-center justify-center text-center"> 38 48 <p className="text-lg font-semibold mb-4 text-gray-800 dark:text-gray-100"> 39 49 You are logged in! ··· 41 51 <ProfileThing agent={agent} large /> 42 52 <button 43 53 onClick={logout} 44 - className="bg-gray-600 mt-4 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors" 54 + className="bg-gray-600 mt-4 hover:bg-gray-700 text-white rounded-full px-6 py-2 font-semibold text-base transition-colors" 45 55 > 46 56 Log out 47 57 </button> ··· 67 77 if (!compact) { 68 78 // Large view renders the form directly in the card 69 79 return ( 70 - <div className="p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4"> 80 + <div className="p-4 bg-gray-100 dark:bg-gray-900 rounded-xl border-gray-200 dark:border-gray-800 mt-4 mx-4"> 71 81 <UnifiedLoginForm /> 72 82 </div> 73 83 ); 74 84 } 75 85 76 86 // Compact view renders a button that toggles the form in a dropdown 77 - return <CompactLoginButton />; 87 + return <CompactLoginButton popup={popup} />; 78 88 } 79 89 80 90 // --- 2. The Reusable, Self-Contained Login Form Component --- ··· 83 93 84 94 return ( 85 95 <div> 86 - <div className="flex border-b border-gray-200 dark:border-gray-700 mb-4"> 96 + <div className="flex bg-gray-300 rounded-full dark:bg-gray-700 mb-4"> 87 97 <TabButton 88 98 label="OAuth" 89 99 active={mode === "oauth"} ··· 103 113 // --- 3. Helper components for layouts, forms, and UI --- 104 114 105 115 // A new component to contain the logic for the compact dropdown 106 - const CompactLoginButton = () => { 116 + const CompactLoginButton = ({ popup }: { popup?: boolean }) => { 107 117 const [showForm, setShowForm] = useState(false); 108 118 const formRef = useRef<HTMLDivElement>(null); 109 119 ··· 125 135 <div className="relative" ref={formRef}> 126 136 <button 127 137 onClick={() => setShowForm(!showForm)} 128 - className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded px-3 py-1 font-medium transition-colors" 138 + className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded-full px-3 py-1 font-medium transition-colors" 129 139 > 130 140 Log in 131 141 </button> 132 142 {showForm && ( 133 - <div className="absolute top-full right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50"> 143 + <div 144 + className={`absolute ${popup ? `bottom-[calc(100%)]` : `top-full`} right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50`} 145 + > 134 146 <UnifiedLoginForm /> 135 147 </div> 136 148 )} ··· 138 150 ); 139 151 }; 140 152 141 - const TabButton = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void; }) => ( 153 + const TabButton = ({ 154 + label, 155 + active, 156 + onClick, 157 + }: { 158 + label: string; 159 + active: boolean; 160 + onClick: () => void; 161 + }) => ( 142 162 <button 143 163 onClick={onClick} 144 - className={`px-4 py-2 text-sm font-medium transition-colors ${ 164 + className={`px-4 py-2 text-sm font-medium transition-colors rounded-full flex-1 ${ 145 165 active 146 - ? "text-gray-600 dark:text-gray-200 border-b-2 border-gray-500" 147 - : "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" 166 + ? "text-gray-50 dark:text-gray-200 border-gray-500 bg-gray-400 dark:bg-gray-500" 167 + : "text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200" 148 168 }`} 149 169 > 150 170 {label} ··· 169 189 }; 170 190 return ( 171 191 <form onSubmit={handleSubmit} className="flex flex-col gap-3"> 172 - <p className="text-xs text-gray-500 dark:text-gray-400">Sign in with AT. Your password is never shared.</p> 173 - <input type="text" placeholder="handle.bsky.social" value={handle} onChange={(e) => setHandle(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" /> 174 - <button type="submit" className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors">Log in</button> 192 + <p className="text-xs text-gray-500 dark:text-gray-400"> 193 + Sign in with AT. Your password is never shared. 194 + </p> 195 + {/* <input 196 + type="text" 197 + placeholder="handle.bsky.social" 198 + value={handle} 199 + onChange={(e) => setHandle(e.target.value)} 200 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" 201 + /> */} 202 + <div className="flex flex-col gap-3"> 203 + <div className="m3input-field m3input-label m3input-border size-md flex-1"> 204 + <input 205 + type="text" 206 + placeholder=" " 207 + value={handle} 208 + onChange={(e) => setHandle(e.target.value)} 209 + /> 210 + <label>AT Handle</label> 211 + </div> 212 + <button 213 + type="submit" 214 + className="bg-gray-600 hover:bg-gray-700 text-white rounded-full px-4 py-2 font-medium text-sm transition-colors" 215 + > 216 + Log in 217 + </button> 218 + </div> 175 219 </form> 176 220 ); 177 221 }; ··· 201 245 202 246 return ( 203 247 <form onSubmit={handleSubmit} className="flex flex-col gap-3"> 204 - <p className="text-xs text-red-500 dark:text-red-400">Warning: Less secure. Use an App Password.</p> 205 - <input type="text" placeholder="handle.bsky.social" value={user} onChange={(e) => setUser(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="username" /> 206 - <input type="password" placeholder="App Password" value={password} onChange={(e) => setPassword(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="current-password" /> 207 - <input type="text" placeholder="PDS (e.g., bsky.social)" value={serviceURL} onChange={(e) => setServiceURL(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" /> 248 + <p className="text-xs text-red-500 dark:text-red-400"> 249 + Warning: Less secure. Use an App Password. 250 + </p> 251 + {/* <input 252 + type="text" 253 + placeholder="handle.bsky.social" 254 + value={user} 255 + onChange={(e) => setUser(e.target.value)} 256 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" 257 + autoComplete="username" 258 + /> 259 + <input 260 + type="password" 261 + placeholder="App Password" 262 + value={password} 263 + onChange={(e) => setPassword(e.target.value)} 264 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" 265 + autoComplete="current-password" 266 + /> 267 + <input 268 + type="text" 269 + placeholder="PDS (e.g., bsky.social)" 270 + value={serviceURL} 271 + onChange={(e) => setServiceURL(e.target.value)} 272 + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" 273 + /> */} 274 + <div className="m3input-field m3input-label m3input-border size-md flex-1"> 275 + <input 276 + type="text" 277 + placeholder=" " 278 + value={user} 279 + onChange={(e) => setUser(e.target.value)} 280 + /> 281 + <label>AT Handle</label> 282 + </div> 283 + <div className="m3input-field m3input-label m3input-border size-md flex-1"> 284 + <input 285 + type="text" 286 + placeholder=" " 287 + value={password} 288 + onChange={(e) => setPassword(e.target.value)} 289 + /> 290 + <label>App Password</label> 291 + </div> 292 + <div className="m3input-field m3input-label m3input-border size-md flex-1"> 293 + <input 294 + type="text" 295 + placeholder=" " 296 + value={serviceURL} 297 + onChange={(e) => setServiceURL(e.target.value)} 298 + /> 299 + <label>PDS</label> 300 + </div> 208 301 {error && <p className="text-xs text-red-500">{error}</p>} 209 - <button type="submit" className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors">Log in</button> 302 + <button 303 + type="submit" 304 + className="bg-gray-600 hover:bg-gray-700 text-white rounded-full px-4 py-2 font-medium text-sm transition-colors" 305 + > 306 + Log in 307 + </button> 210 308 </form> 211 309 ); 212 310 }; 213 311 214 312 // --- Profile Component (now supports a `large` prop for styling) --- 215 - export const ProfileThing = ({ agent, large = false }: { agent: Agent | null; large?: boolean }) => { 216 - const [profile, setProfile] = useState<any>(null); 313 + export const ProfileThing = ({ 314 + agent, 315 + large = false, 316 + }: { 317 + agent: Agent | null; 318 + large?: boolean; 319 + }) => { 320 + const did = ((agent as AtpAgent)?.session?.did ?? 321 + (agent as AtpAgent)?.assertDid ?? 322 + agent?.did) as string | undefined; 323 + const { data: identity } = useQueryIdentity(did); 324 + const { data: profiledata } = useQueryProfile( 325 + `at://${did}/app.bsky.actor.profile/self` 326 + ); 327 + const profile = profiledata?.value; 217 328 218 - useEffect(() => { 219 - const fetchUser = async () => { 220 - const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid; 221 - if (!did) return; 222 - try { 223 - const res = await agent!.getProfile({ actor: did }); 224 - setProfile(res.data); 225 - } catch (e) { console.error("Failed to fetch profile", e); } 226 - }; 227 - if (agent) fetchUser(); 228 - }, [agent]); 329 + const [imgcdn] = useAtom(imgCDNAtom) 330 + 331 + function getAvatarUrl(p: typeof profile) { 332 + const link = p?.avatar?.ref?.["$link"]; 333 + if (!link || !did) return null; 334 + return `https://${imgcdn}/img/avatar/plain/${did}/${link}@jpeg`; 335 + } 336 + 337 + if (!profiledata) { 338 + return ( 339 + // Skeleton loader 340 + <div 341 + className={`flex items-center gap-2.5 animate-pulse ${large ? "mb-1" : ""}`} 342 + > 343 + <div 344 + className={`rounded-full bg-gray-300 dark:bg-gray-700 ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`} 345 + /> 346 + <div className="flex flex-col gap-2"> 347 + <div 348 + className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? "h-4 w-28" : "h-3 w-20"}`} 349 + /> 350 + <div 351 + className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? "h-4 w-20" : "h-3 w-16"}`} 352 + /> 353 + </div> 354 + </div> 355 + ); 356 + } 229 357 230 - if (!profile) { 231 - return ( // Skeleton loader 232 - <div className={`flex items-center gap-2.5 animate-pulse ${large ? 'mb-1' : ''}`}> 233 - <div className={`rounded-full bg-gray-300 dark:bg-gray-700 ${large ? 'w-10 h-10' : 'w-[30px] h-[30px]'}`} /> 234 - <div className="flex flex-col gap-2"> 235 - <div className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? 'h-4 w-28' : 'h-3 w-20'}`} /> 236 - <div className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? 'h-4 w-20' : 'h-3 w-16'}`} /> 237 - </div> 238 - </div> 239 - ); 240 - } 241 - 242 - return ( 243 - <div className={`flex flex-row items-center gap-2.5 ${large ? 'mb-1' : ''}`}> 244 - <img src={profile?.avatar} alt="avatar" className={`object-cover rounded-full ${large ? 'w-10 h-10' : 'w-[30px] h-[30px]'}`} /> 245 - <div className="flex flex-col items-start text-left"> 246 - <div className={`font-medium ${large ? 'text-gray-800 dark:text-gray-100 text-md' : 'text-gray-800 dark:text-gray-100 text-sm'}`}>{profile?.displayName}</div> 247 - <div className={` ${large ? 'text-gray-500 dark:text-gray-400 text-sm' : 'text-gray-500 dark:text-gray-400 text-xs'}`}>@{profile?.handle}</div> 248 - </div> 358 + return ( 359 + <div 360 + className={`flex flex-row items-center gap-2.5 ${large ? "mb-1" : ""}`} 361 + > 362 + <img 363 + src={getAvatarUrl(profile) ?? undefined} 364 + alt="avatar" 365 + className={`object-cover rounded-full ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`} 366 + /> 367 + <div className="flex flex-col items-start text-left"> 368 + <div 369 + className={`font-medium ${large ? "text-gray-800 dark:text-gray-100 text-md" : "text-gray-800 dark:text-gray-100 text-sm"}`} 370 + > 371 + {profile?.displayName} 372 + </div> 373 + <div 374 + className={` ${large ? "text-gray-500 dark:text-gray-400 text-sm" : "text-gray-500 dark:text-gray-400 text-xs"}`} 375 + > 376 + @{identity?.handle} 249 377 </div> 250 - ); 251 - }; 378 + </div> 379 + </div> 380 + ); 381 + };
+6
src/components/Star.tsx
··· 1 + import type { SVGProps } from 'react'; 2 + import React from 'react'; 3 + 4 + export function FluentEmojiHighContrastGlowingStar(props: SVGProps<SVGSVGElement>) { 5 + return (<svg xmlns="http://www.w3.org/2000/svg" width={32} height={32} viewBox="0 0 32 32" {...props}><g fill="currentColor"><path d="m28.979 17.003l-3.108.214c-.834.06-1.178 1.079-.542 1.608l2.388 1.955c.521.428 1.314.204 1.523-.428l.709-2.127c.219-.632-.292-1.273-.97-1.222M21.75 2.691l-.72 2.9c-.2.78.66 1.41 1.34.98l2.54-1.58c.55-.34.58-1.14.05-1.52l-1.78-1.29a.912.912 0 0 0-1.43.51M6.43 4.995l2.53 1.58c.68.43 1.54-.19 1.35-.98l-.72-2.9a.92.92 0 0 0-1.43-.52l-1.78 1.29c-.53.4-.5 1.19.05 1.53M4.185 20.713l2.29-1.92c.62-.52.29-1.53-.51-1.58l-2.98-.21a.92.92 0 0 0-.94 1.2l.68 2.09c.2.62.97.84 1.46.42m13.61 7.292l-1.12-2.77c-.3-.75-1.36-.75-1.66 0l-1.12 2.77c-.24.6.2 1.26.85 1.26h2.2a.92.92 0 0 0 .85-1.26"></path><path d="m17.565 3.324l1.726 3.72c.326.694.967 1.18 1.717 1.29l4.056.624c1.835.278 2.575 2.53 1.293 3.859L23.268 16a2.28 2.28 0 0 0-.612 1.964l.71 4.374c.307 1.885-1.687 3.293-3.354 2.37l-3.405-1.894a2.25 2.25 0 0 0-2.21 0l-3.404 1.895c-1.668.922-3.661-.486-3.355-2.37l.71-4.375A2.28 2.28 0 0 0 7.736 16l-3.088-3.184c-1.293-1.34-.543-3.581 1.293-3.859l4.055-.625a2.3 2.3 0 0 0 1.717-1.29l1.727-3.719c.819-1.765 3.306-1.765 4.124 0"></path></g></svg>); 6 + }
+809 -493
src/components/UniversalPostRenderer.tsx
··· 1 1 import { useNavigate } from "@tanstack/react-router"; 2 + import DOMPurify from "dompurify"; 2 3 import { useAtom } from "jotai"; 4 + import { DropdownMenu } from "radix-ui"; 5 + import { HoverCard } from "radix-ui"; 3 6 import * as React from "react"; 4 7 import { type SVGProps } from "react"; 5 8 6 - import { likedPostsAtom } from "~/utils/atoms"; 9 + import { 10 + composerAtom, 11 + constellationURLAtom, 12 + imgCDNAtom, 13 + likedPostsAtom, 14 + } from "~/utils/atoms"; 7 15 import { useHydratedEmbed } from "~/utils/useHydrated"; 8 16 import { 9 17 useQueryConstellation, 10 18 useQueryIdentity, 11 19 useQueryPost, 12 20 useQueryProfile, 21 + yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks, 13 22 } from "~/utils/useQuery"; 14 23 15 24 function asTyped<T extends { $type: string }>(obj: T): $Typed<T> { ··· 28 37 bottomBorder?: boolean; 29 38 feedviewpost?: boolean; 30 39 repostedby?: string; 40 + style?: React.CSSProperties; 41 + ref?: React.Ref<HTMLDivElement>; 42 + dataIndexPropPass?: number; 43 + nopics?: boolean; 44 + lightboxCallback?: (d: LightboxProps) => void; 45 + maxReplies?: number; 46 + isQuote?: boolean; 31 47 } 32 48 33 49 // export async function cachedGetRecord({ ··· 132 148 bottomBorder = true, 133 149 feedviewpost = false, 134 150 repostedby, 151 + style, 152 + ref, 153 + dataIndexPropPass, 154 + nopics, 155 + lightboxCallback, 156 + maxReplies, 157 + isQuote, 135 158 }: UniversalPostRendererATURILoaderProps) { 159 + // todo remove this once tree rendering is implemented, use a prop like isTree 160 + const TEMPLINEAR = true; 136 161 // /*mass comment*/ console.log("atUri", atUri); 137 162 //const { get, set } = usePersistentStore(); 138 163 //const [record, setRecord] = React.useState<any>(null); ··· 377 402 ); 378 403 }, [links]); 379 404 405 + // const { data: repliesData } = useQueryConstellation({ 406 + // method: "/links", 407 + // target: atUri, 408 + // collection: "app.bsky.feed.post", 409 + // path: ".reply.parent.uri", 410 + // }); 411 + 412 + const [constellationurl] = useAtom(constellationURLAtom); 413 + 414 + const infinitequeryresults = useInfiniteQuery({ 415 + ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 416 + { 417 + constellation: constellationurl, 418 + method: "/links", 419 + target: atUri, 420 + collection: "app.bsky.feed.post", 421 + path: ".reply.parent.uri", 422 + } 423 + ), 424 + enabled: !!atUri && !!maxReplies && !isQuote, 425 + }); 426 + 427 + const { 428 + data: repliesData, 429 + // fetchNextPage, 430 + // hasNextPage, 431 + // isFetchingNextPage, 432 + } = infinitequeryresults; 433 + 434 + // auto-fetch all pages 435 + useEffect(() => { 436 + if (!maxReplies || isQuote || TEMPLINEAR) return; 437 + if ( 438 + infinitequeryresults.hasNextPage && 439 + !infinitequeryresults.isFetchingNextPage 440 + ) { 441 + console.log("Fetching the next page..."); 442 + infinitequeryresults.fetchNextPage(); 443 + } 444 + }, [TEMPLINEAR, infinitequeryresults, isQuote, maxReplies]); 445 + 446 + const replyAturis = repliesData 447 + ? repliesData.pages.flatMap((page) => 448 + page 449 + ? page.linking_records.map((record) => { 450 + const aturi = `at://${record.did}/${record.collection}/${record.rkey}`; 451 + return aturi; 452 + }) 453 + : [] 454 + ) 455 + : []; 456 + 457 + //const [oldestOpsReply, setOldestOpsReply] = useState<string | undefined>(undefined); 458 + 459 + const { oldestOpsReply, oldestOpsReplyElseNewestNonOpsReply } = (() => { 460 + if (isQuote || !replyAturis || replyAturis.length === 0 || !maxReplies) 461 + return { 462 + oldestOpsReply: undefined, 463 + oldestOpsReplyElseNewestNonOpsReply: undefined, 464 + }; 465 + 466 + const opdid = new AtUri( 467 + //postQuery?.value.reply?.root.uri ?? postQuery?.uri ?? atUri 468 + atUri 469 + ).host; 470 + 471 + const opReplies = replyAturis.filter( 472 + (aturi) => new AtUri(aturi).host === opdid 473 + ); 474 + 475 + if (opReplies.length > 0) { 476 + const opreply = opReplies[opReplies.length - 1]; 477 + //setOldestOpsReply(opreply); 478 + return { 479 + oldestOpsReply: opreply, 480 + oldestOpsReplyElseNewestNonOpsReply: opreply, 481 + }; 482 + } else { 483 + return { 484 + oldestOpsReply: undefined, 485 + oldestOpsReplyElseNewestNonOpsReply: replyAturis[0], 486 + }; 487 + } 488 + })(); 489 + 380 490 // const navigateToProfile = (e: React.MouseEvent) => { 381 491 // e.stopPropagation(); 382 492 // if (resolved?.did) { ··· 392 502 } 393 503 394 504 return ( 395 - <UniversalPostRendererRawRecordShim 396 - detailed={detailed} 397 - postRecord={postQuery} 398 - profileRecord={opProfile} 399 - aturi={atUri} 400 - resolved={resolved} 401 - likesCount={likes} 402 - repostsCount={reposts} 403 - repliesCount={replies} 404 - bottomReplyLine={bottomReplyLine} 405 - topReplyLine={topReplyLine} 406 - bottomBorder={bottomBorder} 407 - feedviewpost={feedviewpost} 408 - repostedby={repostedby} 409 - /> 505 + <> 506 + {/* <span>uprrs {maxReplies} {!!maxReplies&&!!oldestOpsReplyElseNewestNonOpsReply ? "true" : "false"}</span> */} 507 + <UniversalPostRendererRawRecordShim 508 + detailed={detailed} 509 + postRecord={postQuery} 510 + profileRecord={opProfile} 511 + aturi={atUri} 512 + resolved={resolved} 513 + likesCount={likes} 514 + repostsCount={reposts} 515 + repliesCount={replies} 516 + bottomReplyLine={ 517 + maxReplies && oldestOpsReplyElseNewestNonOpsReply 518 + ? true 519 + : maxReplies && !oldestOpsReplyElseNewestNonOpsReply 520 + ? false 521 + : (maxReplies === 0 && (!replies || (!!replies && replies === 0))) ? false : bottomReplyLine 522 + } 523 + topReplyLine={topReplyLine} 524 + //bottomBorder={maxReplies&&oldestOpsReplyElseNewestNonOpsReply ? false : bottomBorder} 525 + bottomBorder={ 526 + maxReplies && oldestOpsReplyElseNewestNonOpsReply 527 + ? false 528 + : maxReplies === 0 529 + ? false 530 + : bottomBorder 531 + } 532 + feedviewpost={feedviewpost} 533 + repostedby={repostedby} 534 + //style={{...style, background: oldestOpsReply === atUri ? "Red" : undefined}} 535 + style={style} 536 + ref={ref} 537 + dataIndexPropPass={dataIndexPropPass} 538 + nopics={nopics} 539 + lightboxCallback={lightboxCallback} 540 + maxReplies={maxReplies} 541 + isQuote={isQuote} 542 + /> 543 + <> 544 + {(maxReplies && maxReplies === 0 && replies && replies > 0) ? ( 545 + <> 546 + {/* <div>hello</div> */} 547 + <MoreReplies atUri={atUri} /> 548 + </> 549 + ) : (<></>)} 550 + </> 551 + {!isQuote && oldestOpsReplyElseNewestNonOpsReply && ( 552 + <> 553 + {/* <span>hello {maxReplies}</span> */} 554 + <UniversalPostRendererATURILoader 555 + //detailed={detailed} 556 + atUri={oldestOpsReplyElseNewestNonOpsReply} 557 + bottomReplyLine={(maxReplies ?? 0) > 0} 558 + topReplyLine={ 559 + (!!(maxReplies && maxReplies - 1 === 0) && 560 + !!(replies && replies > 0)) || 561 + !!((maxReplies ?? 0) > 1) 562 + } 563 + bottomBorder={bottomBorder} 564 + feedviewpost={feedviewpost} 565 + repostedby={repostedby} 566 + style={style} 567 + ref={ref} 568 + dataIndexPropPass={dataIndexPropPass} 569 + nopics={nopics} 570 + lightboxCallback={lightboxCallback} 571 + maxReplies={ 572 + maxReplies && maxReplies > 0 ? maxReplies - 1 : undefined 573 + } 574 + /> 575 + </> 576 + )} 577 + </> 410 578 ); 411 579 } 412 580 413 - function getAvatarUrl(opProfile: any, did: string) { 581 + function MoreReplies({ atUri }: { atUri: string }) { 582 + const navigate = useNavigate(); 583 + const aturio = new AtUri(atUri); 584 + return ( 585 + <div 586 + onClick={() => 587 + navigate({ 588 + to: "/profile/$did/post/$rkey", 589 + params: { did: aturio.host, rkey: aturio.rkey }, 590 + }) 591 + } 592 + className="border-b border-gray-300 dark:border-gray-800 flex flex-row px-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-900 transition-colors" 593 + > 594 + <div className="w-[42px] h-12 flex flex-col items-center justify-center"> 595 + <div 596 + style={{ 597 + width: 2, 598 + height: "100%", 599 + backgroundImage: 600 + "repeating-linear-gradient(to bottom, var(--color-gray-500) 0, var(--color-gray-500) 4px, transparent 4px, transparent 8px)", 601 + opacity: 0.5, 602 + }} 603 + className="dark:bg-[repeating-linear-gradient(to_bottom,var(--color-gray-500)_0,var(--color-gray-400)_4px,transparent_4px,transparent_8px)]" 604 + //className="border-gray-400 dark:border-gray-500" 605 + /> 606 + </div> 607 + 608 + <div className="flex items-center pl-3 text-sm text-gray-500 dark:text-gray-400 select-none"> 609 + More Replies 610 + </div> 611 + </div> 612 + ); 613 + } 614 + 615 + function getAvatarUrl(opProfile: any, did: string, cdn: string) { 414 616 const link = opProfile?.value?.avatar?.ref?.["$link"]; 415 617 if (!link) return null; 416 - return `https://cdn.bsky.app/img/avatar/plain/${did}/${link}@jpeg`; 618 + return `https://${cdn}/img/avatar/plain/${did}/${link}@jpeg`; 417 619 } 418 620 419 621 export function UniversalPostRendererRawRecordShim({ ··· 430 632 bottomBorder = true, 431 633 feedviewpost = false, 432 634 repostedby, 635 + style, 636 + ref, 637 + dataIndexPropPass, 638 + nopics, 639 + lightboxCallback, 640 + maxReplies, 641 + isQuote, 433 642 }: { 434 643 postRecord: any; 435 644 profileRecord: any; ··· 444 653 bottomBorder?: boolean; 445 654 feedviewpost?: boolean; 446 655 repostedby?: string; 656 + style?: React.CSSProperties; 657 + ref?: React.Ref<HTMLDivElement>; 658 + dataIndexPropPass?: number; 659 + nopics?: boolean; 660 + lightboxCallback?: (d: LightboxProps) => void; 661 + maxReplies?: number; 662 + isQuote?: boolean; 447 663 }) { 448 664 // /*mass comment*/ console.log(`received aturi: ${aturi} of post content: ${postRecord}`); 449 665 const navigate = useNavigate(); ··· 520 736 error: embedError, 521 737 } = useHydratedEmbed(postRecord?.value?.embed, resolved?.did); 522 738 739 + const [imgcdn] = useAtom(imgCDNAtom); 740 + 523 741 const parsedaturi = new AtUri(aturi); //parseAtUri(aturi); 524 742 743 + const fakeprofileviewbasic = React.useMemo<AppBskyActorDefs.ProfileViewBasic>( 744 + () => ({ 745 + did: resolved?.did || "", 746 + handle: resolved?.handle || "", 747 + displayName: profileRecord?.value?.displayName || "", 748 + avatar: getAvatarUrl(profileRecord, resolved?.did, imgcdn) || "", 749 + viewer: undefined, 750 + labels: profileRecord?.labels || undefined, 751 + verification: undefined, 752 + }), 753 + [imgcdn, profileRecord, resolved?.did, resolved?.handle] 754 + ); 755 + 756 + const fakeprofileviewdetailed = 757 + React.useMemo<AppBskyActorDefs.ProfileViewDetailed>( 758 + () => ({ 759 + ...fakeprofileviewbasic, 760 + $type: "app.bsky.actor.defs#profileViewDetailed", 761 + description: profileRecord?.value?.description || undefined, 762 + }), 763 + [fakeprofileviewbasic, profileRecord?.value?.description] 764 + ); 765 + 525 766 const fakepost = React.useMemo<AppBskyFeedDefs.PostView>( 526 767 () => ({ 527 768 $type: "app.bsky.feed.defs#postView", 528 769 uri: aturi, 529 770 cid: postRecord?.cid || "", 530 - author: { 531 - did: resolved?.did || "", 532 - handle: resolved?.handle || "", 533 - displayName: profileRecord?.value?.displayName || "", 534 - avatar: getAvatarUrl(profileRecord, resolved?.did) || "", 535 - viewer: undefined, 536 - labels: profileRecord?.labels || undefined, 537 - verification: undefined, 538 - }, 771 + author: fakeprofileviewbasic, 539 772 record: postRecord?.value || {}, 540 773 embed: hydratedEmbed ?? undefined, 541 774 replyCount: repliesCount ?? 0, ··· 552 785 postRecord?.cid, 553 786 postRecord?.value, 554 787 postRecord?.labels, 555 - resolved?.did, 556 - resolved?.handle, 557 - profileRecord, 788 + fakeprofileviewbasic, 558 789 hydratedEmbed, 559 790 repliesCount, 560 791 repostsCount, ··· 631 862 } 632 863 }} 633 864 post={fakepost} 865 + uprrrsauthor={fakeprofileviewdetailed} 634 866 salt={aturi} 635 867 bottomReplyLine={bottomReplyLine} 636 868 topReplyLine={topReplyLine} ··· 638 870 //extraOptionalItemInfo={{reply: postRecord?.value?.reply as AppBskyFeedDefs.ReplyRef, post: fakepost}} 639 871 feedviewpostreplyhandle={feedviewpostreplyhandle} 640 872 repostedby={feedviewpostrepostedbyhandle} 873 + style={style} 874 + ref={ref} 875 + dataIndexPropPass={dataIndexPropPass} 876 + nopics={nopics} 877 + lightboxCallback={lightboxCallback} 878 + maxReplies={maxReplies} 879 + isQuote={isQuote} 641 880 /> 642 881 </> 643 882 ); ··· 676 915 {...props} 677 916 > 678 917 <path 679 - fill="oklch(0.704 0.05 28)" 918 + fill="var(--color-gray-400)" 680 919 d="M9 22a1 1 0 0 1-1-1v-3H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6.1l-3.7 3.71c-.2.19-.45.29-.7.29zm1-6v3.08L13.08 16H20V4H4v12z" 681 920 ></path> 682 921 </svg> ··· 693 932 {...props} 694 933 > 695 934 <path 696 - fill="oklch(0.704 0.05 28)" 935 + fill="var(--color-gray-400)" 697 936 d="M17 17H7v-3l-4 4l4 4v-3h12v-6h-2M7 7h10v3l4-4l-4-4v3H5v6h2z" 698 937 ></path> 699 938 </svg> ··· 744 983 {...props} 745 984 > 746 985 <path 747 - fill="oklch(0.704 0.05 28)" 986 + fill="var(--color-gray-400)" 748 987 d="m12.1 18.55l-.1.1l-.11-.1C7.14 14.24 4 11.39 4 8.5C4 6.5 5.5 5 7.5 5c1.54 0 3.04 1 3.57 2.36h1.86C13.46 6 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5c0 2.89-3.14 5.74-7.9 10.05M16.5 3c-1.74 0-3.41.81-4.5 2.08C10.91 3.81 9.24 3 7.5 3C4.42 3 2 5.41 2 8.5c0 3.77 3.4 6.86 8.55 11.53L12 21.35l1.45-1.32C18.6 15.36 22 12.27 22 8.5C22 5.41 19.58 3 16.5 3" 749 988 ></path> 750 989 </svg> ··· 761 1000 {...props} 762 1001 > 763 1002 <path 764 - fill="oklch(0.704 0.05 28)" 1003 + fill="var(--color-gray-400)" 765 1004 d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3a3 3 0 0 0-3-3a3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3a3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66c0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08" 766 1005 ></path> 767 1006 </svg> ··· 778 1017 {...props} 779 1018 > 780 1019 <path 781 - fill="oklch(0.704 0.05 28)" 1020 + fill="var(--color-gray-400)" 782 1021 d="M16 12a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2m-6 0a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2m-6 0a2 2 0 0 1 2-2a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2" 783 1022 ></path> 784 1023 </svg> ··· 795 1034 {...props} 796 1035 > 797 1036 <path 798 - fill="oklch(0.704 0.05 28)" 1037 + fill="var(--color-gray-400)" 799 1038 d="M17.9 17.39c-.26-.8-1.01-1.39-1.9-1.39h-1v-3a1 1 0 0 0-1-1H8v-2h2a1 1 0 0 0 1-1V7h2a2 2 0 0 0 2-2v-.41a7.984 7.984 0 0 1 2.9 12.8M11 19.93c-3.95-.49-7-3.85-7-7.93c0-.62.08-1.22.21-1.79L9 15v1a2 2 0 0 0 2 2m1-16A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2" 800 1039 ></path> 801 1040 </svg> ··· 829 1068 {...props} 830 1069 > 831 1070 <path 832 - fill="oklch(0.704 0.05 28)" 1071 + fill="var(--color-gray-400)" 833 1072 d="M10 9V5l-7 7l7 7v-4.1c5 0 8.5 1.6 11 5.1c-1-5-4-10-11-11" 834 1073 ></path> 835 1074 </svg> ··· 883 1122 {...props} 884 1123 > 885 1124 <path 886 - fill="oklch(0.704 0.05 28)" 1125 + fill="var(--color-gray-400)" 887 1126 d="M17 17H7v-3l-4 4l4 4v-3h12v-6h-2M7 7h10v3l4-4l-4-4v3H5v6h2z" 888 1127 ></path> 889 1128 </svg> ··· 900 1139 {...props} 901 1140 > 902 1141 <path 903 - fill="oklch(0.704 0.05 28)" 1142 + fill="var(--color-gray-400)" 904 1143 d="M6 5.75L10.25 10H7v6h6.5l2 2H7a2 2 0 0 1-2-2v-6H1.75zm12 12.5L13.75 14H17V8h-6.5l-2-2H17a2 2 0 0 1 2 2v6h3.25z" 905 1144 ></path> 906 1145 </svg> ··· 928 1167 //import Masonry from "@mui/lab/Masonry"; 929 1168 import { 930 1169 type $Typed, 1170 + AppBskyActorDefs, 931 1171 AppBskyEmbedDefs, 932 1172 AppBskyEmbedExternal, 933 1173 AppBskyEmbedImages, ··· 951 1191 PostView, 952 1192 //ThreadViewPost, 953 1193 } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; 1194 + import { useInfiniteQuery } from "@tanstack/react-query"; 954 1195 import { useEffect, useRef, useState } from "react"; 955 1196 import ReactPlayer from "react-player"; 956 1197 957 1198 import defaultpfp from "~/../public/favicon.png"; 958 1199 import { useAuth } from "~/providers/UnifiedAuthProvider"; 1200 + import { FollowButton, Mutual } from "~/routes/profile.$did"; 1201 + import type { LightboxProps } from "~/routes/profile.$did/post.$rkey.image.$i"; 959 1202 // import type { OutputSchema } from "@atproto/api/dist/client/types/app/bsky/feed/getFeed"; 960 1203 // import type { 961 1204 // ViewRecord, ··· 1063 1306 1064 1307 function UniversalPostRenderer({ 1065 1308 post, 1309 + uprrrsauthor, 1066 1310 //setMainItem, 1067 1311 //isMainItem, 1068 1312 onPostClick, ··· 1079 1323 feedviewpostreplyhandle, 1080 1324 depth = 0, 1081 1325 repostedby, 1326 + style, 1327 + ref, 1328 + dataIndexPropPass, 1329 + nopics, 1330 + lightboxCallback, 1331 + maxReplies, 1082 1332 }: { 1083 1333 post: PostView; 1334 + uprrrsauthor?: AppBskyActorDefs.ProfileViewDetailed; 1084 1335 // optional for now because i havent ported every use to this yet 1085 1336 // setMainItem?: React.Dispatch< 1086 1337 // React.SetStateAction<AppBskyFeedDefs.FeedViewPost> ··· 1098 1349 feedviewpostreplyhandle?: string; 1099 1350 depth?: number; 1100 1351 repostedby?: string; 1352 + style?: React.CSSProperties; 1353 + ref?: React.Ref<HTMLDivElement>; 1354 + dataIndexPropPass?: number; 1355 + nopics?: boolean; 1356 + lightboxCallback?: (d: LightboxProps) => void; 1357 + maxReplies?: number; 1101 1358 }) { 1359 + const parsed = new AtUri(post.uri); 1102 1360 const navigate = useNavigate(); 1103 1361 const [likedPosts, setLikedPosts] = useAtom(likedPostsAtom); 1104 1362 const [hasRetweeted, setHasRetweeted] = useState<boolean>( ··· 1107 1365 const [hasLiked, setHasLiked] = useState<boolean>( 1108 1366 post.uri in likedPosts || post.viewer?.like ? true : false 1109 1367 ); 1368 + const [, setComposerPost] = useAtom(composerAtom); 1110 1369 const { agent } = useAuth(); 1111 1370 const [likeUri, setLikeUri] = useState<string | undefined>(post.viewer?.like); 1112 1371 const [retweetUri, setRetweetUri] = useState<string | undefined>( ··· 1167 1426 : undefined; 1168 1427 1169 1428 const emergencySalt = randomString(); 1429 + const fedi = (post.record as { bridgyOriginalText?: string }) 1430 + .bridgyOriginalText; 1170 1431 1171 1432 /* fuck you */ 1172 1433 const isMainItem = false; 1173 1434 const setMainItem = (any: any) => {}; 1435 + // eslint-disable-next-line react-hooks/refs 1436 + console.log("Received ref in UniversalPostRenderer:", ref); 1174 1437 return ( 1175 - <div 1176 - key={salt + "-" + (post.uri || emergencySalt)} 1177 - onClick={ 1178 - isMainItem 1179 - ? onPostClick 1180 - : setMainItem 1438 + <div ref={ref} style={style} data-index={dataIndexPropPass}> 1439 + <div 1440 + //ref={ref} 1441 + key={salt + "-" + (post.uri || emergencySalt)} 1442 + onClick={ 1443 + isMainItem 1181 1444 ? onPostClick 1182 - ? (e) => { 1183 - setMainItem({ post: post }); 1184 - onPostClick(e); 1185 - } 1186 - : () => { 1187 - setMainItem({ post: post }); 1188 - } 1189 - : undefined 1190 - } 1191 - style={{ 1192 - //border: "1px solid #e1e8ed", 1193 - //borderRadius: 12, 1194 - opacity: "1 !important", 1195 - background: "transparent", 1196 - paddingLeft: isQuote ? 12 : 16, 1197 - paddingRight: isQuote ? 12 : 16, 1198 - //paddingTop: 16, 1199 - paddingTop: isRepost ? 10 : isQuote ? 12 : 16, 1200 - //paddingBottom: bottomReplyLine ? 0 : 16, 1201 - paddingBottom: 0, 1202 - fontFamily: "system-ui, sans-serif", 1203 - //boxShadow: "0 2px 8px rgba(0,0,0,0.04)", 1204 - position: "relative", 1205 - // dont cursor: "pointer", 1206 - borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0, 1207 - }} 1208 - className="border-gray-300 dark:border-gray-600" 1209 - > 1210 - {isRepost && ( 1211 - <div 1212 - style={{ 1213 - marginLeft: 36, 1214 - display: "flex", 1215 - borderRadius: 12, 1216 - paddingBottom: "calc(22px - 1rem)", 1217 - fontSize: 14, 1218 - maxHeight: "1rem", 1219 - justifyContent: "flex-start", 1220 - //color: theme.textSecondary, 1221 - gap: 4, 1222 - alignItems: "center", 1223 - }} 1224 - className="text-gray-500 dark:text-gray-400" 1225 - > 1226 - <MdiRepost /> Reposted by @{isRepost}{" "} 1227 - </div> 1228 - )} 1229 - {!isQuote && ( 1230 - <div 1231 - style={{ 1232 - opacity: 1233 - topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0, 1234 - position: "absolute", 1235 - top: 0, 1236 - left: 36, // why 36 ??? 1237 - //left: 16 + (42 / 2), 1238 - width: 2, 1239 - //height: "100%", 1240 - height: isRepost ? "calc(16px + 1rem - 6px)" : 16 - 6, 1241 - // background: theme.textSecondary, 1242 - //opacity: 0.5, 1243 - // no flex here 1244 - }} 1245 - className="bg-gray-500 dark:bg-gray-400" 1246 - /> 1247 - )} 1248 - <div 1445 + : setMainItem 1446 + ? onPostClick 1447 + ? (e) => { 1448 + setMainItem({ post: post }); 1449 + onPostClick(e); 1450 + } 1451 + : () => { 1452 + setMainItem({ post: post }); 1453 + } 1454 + : undefined 1455 + } 1249 1456 style={{ 1250 - position: "absolute", 1251 - //top: isRepost ? "calc(16px + 1rem)" : 16, 1252 - //left: 16, 1253 - zIndex: 1, 1254 - top: isRepost ? "calc(16px + 1rem)" : isQuote ? 12 : 16, 1255 - left: isQuote ? 12 : 16, 1457 + //...style, 1458 + //border: "1px solid #e1e8ed", 1459 + //borderRadius: 12, 1460 + opacity: "1 !important", 1461 + background: "transparent", 1462 + paddingLeft: isQuote ? 12 : 16, 1463 + paddingRight: isQuote ? 12 : 16, 1464 + //paddingTop: 16, 1465 + paddingTop: isRepost ? 10 : isQuote ? 12 : topReplyLine ? 8 : 16, 1466 + //paddingBottom: bottomReplyLine ? 0 : 16, 1467 + paddingBottom: 0, 1468 + fontFamily: "system-ui, sans-serif", 1469 + //boxShadow: "0 2px 8px rgba(0,0,0,0.04)", 1470 + position: "relative", 1471 + // dont cursor: "pointer", 1472 + borderBottomWidth: bottomBorder ? (isQuote ? 0 : 1) : 0, 1256 1473 }} 1257 - onClick={onProfileClick} 1474 + className="border-gray-300 dark:border-gray-800" 1258 1475 > 1259 - <img 1260 - src={post.author.avatar || defaultpfp} 1261 - alt="avatar" 1262 - // transition={{ 1263 - // type: "spring", 1264 - // stiffness: 260, 1265 - // damping: 20, 1266 - // }} 1267 - style={{ 1268 - borderRadius: "50%", 1269 - marginRight: 12, 1270 - objectFit: "cover", 1271 - //background: theme.border, 1272 - //border: `1px solid ${theme.border}`, 1273 - width: isQuote ? 16 : 42, 1274 - height: isQuote ? 16 : 42, 1275 - }} 1276 - className="border border-gray-300 dark:border-gray-600 bg-gray-300 dark:bg-gray-600" 1277 - /> 1278 - </div> 1279 - <div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}> 1280 - <div 1281 - style={{ 1282 - display: "flex", 1283 - flexDirection: "column", 1284 - alignSelf: "stretch", 1285 - alignItems: "center", 1286 - overflow: "hidden", 1287 - width: expanded || isQuote ? 0 : "auto", 1288 - marginRight: expanded || isQuote ? 0 : 12, 1289 - }} 1290 - > 1291 - {/* dummy for later use */} 1292 - <div style={{ width: 42, height: 42 + 8, minHeight: 42 + 8 }} /> 1293 - {/* reply line !!!! bottomReplyLine */} 1294 - {bottomReplyLine && ( 1476 + {isRepost && ( 1477 + <div 1478 + style={{ 1479 + marginLeft: 36, 1480 + display: "flex", 1481 + borderRadius: 12, 1482 + paddingBottom: "calc(22px - 1rem)", 1483 + fontSize: 14, 1484 + maxHeight: "1rem", 1485 + justifyContent: "flex-start", 1486 + //color: theme.textSecondary, 1487 + gap: 4, 1488 + alignItems: "center", 1489 + }} 1490 + className="text-gray-500 dark:text-gray-400" 1491 + > 1492 + <MdiRepost /> Reposted by @{isRepost}{" "} 1493 + </div> 1494 + )} 1495 + {!isQuote && ( 1496 + <div 1497 + style={{ 1498 + opacity: 1499 + topReplyLine || isReply /*&& (true || expanded)*/ ? 0.5 : 0, 1500 + position: "absolute", 1501 + top: 0, 1502 + left: 36, // why 36 ??? 1503 + //left: 16 + (42 / 2), 1504 + width: 2, 1505 + //height: "100%", 1506 + height: isRepost 1507 + ? "calc(16px + 1rem - 6px)" 1508 + : topReplyLine 1509 + ? 8 - 6 1510 + : 16 - 6, 1511 + // background: theme.textSecondary, 1512 + //opacity: 0.5, 1513 + // no flex here 1514 + }} 1515 + className="bg-gray-500 dark:bg-gray-400" 1516 + /> 1517 + )} 1518 + <HoverCard.Root> 1519 + <HoverCard.Trigger asChild> 1295 1520 <div 1521 + className={`absolute`} 1296 1522 style={{ 1297 - width: 2, 1298 - height: "100%", 1299 - //background: theme.textSecondary, 1300 - opacity: 0.5, 1301 - // no flex here 1302 - //color: "Red", 1303 - //zIndex: 99 1523 + top: isRepost 1524 + ? "calc(16px + 1rem)" 1525 + : isQuote 1526 + ? 12 1527 + : topReplyLine 1528 + ? 8 1529 + : 16, 1530 + left: isQuote ? 12 : 16, 1304 1531 }} 1305 - className="bg-gray-500 dark:bg-gray-400" 1306 - /> 1307 - )} 1308 - {/* <div 1532 + onClick={onProfileClick} 1533 + > 1534 + <img 1535 + src={post.author.avatar || defaultpfp} 1536 + alt="avatar" 1537 + className={`rounded-full object-cover border border-gray-300 dark:border-gray-800 bg-gray-300 dark:bg-gray-600`} 1538 + style={{ 1539 + width: isQuote ? 16 : 42, 1540 + height: isQuote ? 16 : 42, 1541 + }} 1542 + /> 1543 + </div> 1544 + </HoverCard.Trigger> 1545 + <HoverCard.Portal> 1546 + <HoverCard.Content 1547 + className="rounded-md p-4 w-72 bg-gray-50 dark:bg-gray-900 shadow-lg border border-gray-300 dark:border-gray-800 animate-slide-fade z-50" 1548 + side={"bottom"} 1549 + sideOffset={5} 1550 + onClick={onProfileClick} 1551 + > 1552 + <div className="flex flex-col gap-2"> 1553 + <div className="flex flex-row"> 1554 + <img 1555 + src={post.author.avatar || defaultpfp} 1556 + alt="avatar" 1557 + className="rounded-full w-[58px] h-[58px] object-cover border border-gray-300 dark:border-gray-800 bg-gray-300 dark:bg-gray-600" 1558 + /> 1559 + <div className=" flex-1 flex flex-row align-middle justify-end"> 1560 + <FollowButton targetdidorhandle={post.author.did} /> 1561 + </div> 1562 + </div> 1563 + <div className="flex flex-col gap-3"> 1564 + <div> 1565 + <div className="text-gray-900 dark:text-gray-100 font-medium text-md"> 1566 + {post.author.displayName || post.author.handle}{" "} 1567 + </div> 1568 + <div className="text-gray-500 dark:text-gray-400 text-md flex flex-row gap-1"> 1569 + <Mutual targetdidorhandle={post.author.did} />@{post.author.handle}{" "} 1570 + </div> 1571 + </div> 1572 + {uprrrsauthor?.description && ( 1573 + <div className="text-gray-700 dark:text-gray-300 text-sm text-left break-words line-clamp-3"> 1574 + {uprrrsauthor.description} 1575 + </div> 1576 + )} 1577 + {/* <div className="flex gap-4"> 1578 + <div className="flex gap-1"> 1579 + <div className="font-medium text-gray-900 dark:text-gray-100"> 1580 + 0 1581 + </div> 1582 + <div className="text-gray-500 dark:text-gray-400"> 1583 + Following 1584 + </div> 1585 + </div> 1586 + <div className="flex gap-1"> 1587 + <div className="font-medium text-gray-900 dark:text-gray-100"> 1588 + 2,900 1589 + </div> 1590 + <div className="text-gray-500 dark:text-gray-400"> 1591 + Followers 1592 + </div> 1593 + </div> 1594 + </div> */} 1595 + </div> 1596 + </div> 1597 + 1598 + {/* <HoverCard.Arrow className="fill-gray-50 dark:fill-gray-900" /> */} 1599 + </HoverCard.Content> 1600 + </HoverCard.Portal> 1601 + </HoverCard.Root> 1602 + 1603 + <div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}> 1604 + <div 1605 + style={{ 1606 + display: "flex", 1607 + flexDirection: "column", 1608 + alignSelf: "stretch", 1609 + alignItems: "center", 1610 + overflow: "hidden", 1611 + width: expanded || isQuote ? 0 : "auto", 1612 + marginRight: expanded || isQuote ? 0 : 12, 1613 + }} 1614 + > 1615 + {/* dummy for later use */} 1616 + <div style={{ width: 42, height: 42 + 6, minHeight: 42 + 6 }} /> 1617 + {/* reply line !!!! bottomReplyLine */} 1618 + {bottomReplyLine && ( 1619 + <div 1620 + style={{ 1621 + width: 2, 1622 + height: "100%", 1623 + //background: theme.textSecondary, 1624 + opacity: 0.5, 1625 + // no flex here 1626 + //color: "Red", 1627 + //zIndex: 99 1628 + }} 1629 + className="bg-gray-500 dark:bg-gray-400" 1630 + /> 1631 + )} 1632 + {/* <div 1309 1633 layout 1310 1634 transition={{ duration: 0.2 }} 1311 1635 animate={{ height: expanded ? 0 : '100%' }} ··· 1315 1639 // no flex here 1316 1640 }} 1317 1641 /> */} 1318 - </div> 1319 - <div style={{ flex: 1, maxWidth: "100%" }}> 1320 - <div 1321 - style={{ 1322 - display: "flex", 1323 - flexDirection: "row", 1324 - alignItems: "center", 1325 - flexWrap: "nowrap", 1326 - maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1327 - width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1328 - marginLeft: !expanded ? (isQuote ? 26 : 0) : 54, 1329 - marginBottom: !expanded ? 4 : 6, 1330 - }} 1331 - > 1642 + </div> 1643 + <div style={{ flex: 1, maxWidth: "100%" }}> 1332 1644 <div 1333 1645 style={{ 1334 1646 display: "flex", 1335 - //overflow: "hidden", // hey why is overflow hidden unapplied 1336 - overflow: "hidden", 1337 - textOverflow: "ellipsis", 1338 - flexShrink: 1, 1339 - flexGrow: 1, 1340 - flexBasis: 0, 1341 - width: 0, 1342 - gap: expanded ? 0 : 6, 1343 - alignItems: expanded ? "flex-start" : "center", 1344 - flexDirection: expanded ? "column" : "row", 1345 - height: expanded ? 42 : "1rem", 1647 + flexDirection: "row", 1648 + alignItems: "center", 1649 + flexWrap: "nowrap", 1650 + maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1651 + width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`, 1652 + marginLeft: !expanded ? (isQuote ? 26 : 0) : 54, 1653 + marginBottom: !expanded ? 4 : 6, 1346 1654 }} 1347 1655 > 1348 - <span 1656 + <div 1349 1657 style={{ 1350 1658 display: "flex", 1351 - fontWeight: 700, 1352 - fontSize: 16, 1659 + //overflow: "hidden", // hey why is overflow hidden unapplied 1353 1660 overflow: "hidden", 1354 1661 textOverflow: "ellipsis", 1355 - whiteSpace: "nowrap", 1356 1662 flexShrink: 1, 1357 - minWidth: 0, 1358 - gap: 4, 1359 - alignItems: "center", 1360 - //color: theme.text, 1663 + flexGrow: 1, 1664 + flexBasis: 0, 1665 + width: 0, 1666 + gap: expanded ? 0 : 6, 1667 + alignItems: expanded ? "flex-start" : "center", 1668 + flexDirection: expanded ? "column" : "row", 1669 + height: expanded ? 42 : "1rem", 1361 1670 }} 1362 - className="text-gray-900 dark:text-gray-100" 1363 1671 > 1364 - {/* verified checkmark */} 1365 - {post.author.displayName || post.author.handle}{" "} 1366 - {post.author.verification?.verifiedStatus == "valid" && ( 1367 - <MdiVerified /> 1368 - )} 1369 - </span> 1672 + <span 1673 + style={{ 1674 + display: "flex", 1675 + fontWeight: 700, 1676 + fontSize: 16, 1677 + overflow: "hidden", 1678 + textOverflow: "ellipsis", 1679 + whiteSpace: "nowrap", 1680 + flexShrink: 1, 1681 + minWidth: 0, 1682 + gap: 4, 1683 + alignItems: "center", 1684 + //color: theme.text, 1685 + }} 1686 + className="text-gray-900 dark:text-gray-100" 1687 + > 1688 + {/* verified checkmark */} 1689 + {post.author.displayName || post.author.handle}{" "} 1690 + {post.author.verification?.verifiedStatus == "valid" && ( 1691 + <MdiVerified /> 1692 + )} 1693 + </span> 1370 1694 1371 - <span 1695 + <span 1696 + style={{ 1697 + //color: theme.textSecondary, 1698 + fontSize: 16, 1699 + overflowX: "hidden", 1700 + textOverflow: "ellipsis", 1701 + whiteSpace: "nowrap", 1702 + flexShrink: 1, 1703 + flexGrow: 0, 1704 + minWidth: 0, 1705 + }} 1706 + className="text-gray-500 dark:text-gray-400" 1707 + > 1708 + @{post.author.handle} 1709 + </span> 1710 + </div> 1711 + <div 1372 1712 style={{ 1373 - //color: theme.textSecondary, 1374 - fontSize: 16, 1375 - overflowX: "hidden", 1376 - textOverflow: "ellipsis", 1377 - whiteSpace: "nowrap", 1378 - flexShrink: 1, 1379 - flexGrow: 0, 1380 - minWidth: 0, 1713 + display: "flex", 1714 + alignItems: "center", 1715 + height: "1rem", 1381 1716 }} 1382 - className="text-gray-500 dark:text-gray-400" 1383 1717 > 1384 - @{post.author.handle} 1385 - </span> 1718 + <span 1719 + style={{ 1720 + //color: theme.textSecondary, 1721 + fontSize: 16, 1722 + marginLeft: 8, 1723 + whiteSpace: "nowrap", 1724 + flexShrink: 0, 1725 + maxWidth: "100%", 1726 + }} 1727 + className="text-gray-500 dark:text-gray-400" 1728 + > 1729 + ยท {/* time placeholder */} 1730 + {shortTimeAgo(post.indexedAt)} 1731 + </span> 1732 + </div> 1386 1733 </div> 1387 - <div 1388 - style={{ 1389 - display: "flex", 1390 - alignItems: "center", 1391 - height: "1rem", 1392 - }} 1393 - > 1394 - <span 1734 + {/* reply indicator */} 1735 + {!!feedviewpostreplyhandle && ( 1736 + <div 1395 1737 style={{ 1738 + display: "flex", 1739 + borderRadius: 12, 1740 + paddingBottom: 2, 1741 + fontSize: 14, 1742 + justifyContent: "flex-start", 1396 1743 //color: theme.textSecondary, 1397 - fontSize: 16, 1398 - marginLeft: 8, 1399 - whiteSpace: "nowrap", 1400 - flexShrink: 0, 1401 - maxWidth: "100%", 1744 + gap: 4, 1745 + alignItems: "center", 1746 + //marginLeft: 36, 1747 + height: 1748 + !(expanded || isQuote) && !!feedviewpostreplyhandle 1749 + ? "1rem" 1750 + : 0, 1751 + opacity: 1752 + !(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0, 1402 1753 }} 1403 1754 className="text-gray-500 dark:text-gray-400" 1404 1755 > 1405 - ยท {/* time placeholder */} 1406 - {shortTimeAgo(post.indexedAt)} 1407 - </span> 1408 - </div> 1409 - </div> 1410 - {/* reply indicator */} 1411 - {!!feedviewpostreplyhandle && ( 1756 + <MdiReply /> Reply to @{feedviewpostreplyhandle} 1757 + </div> 1758 + )} 1412 1759 <div 1413 1760 style={{ 1414 - display: "flex", 1415 - borderRadius: 12, 1416 - paddingBottom: 2, 1417 - fontSize: 14, 1418 - justifyContent: "flex-start", 1419 - //color: theme.textSecondary, 1420 - gap: 4, 1421 - alignItems: "center", 1422 - //marginLeft: 36, 1423 - height: 1424 - !(expanded || isQuote) && !!feedviewpostreplyhandle 1425 - ? "1rem" 1426 - : 0, 1427 - opacity: 1428 - !(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0, 1761 + fontSize: 16, 1762 + marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8, 1763 + whiteSpace: "pre-wrap", 1764 + textAlign: "left", 1765 + overflowWrap: "anywhere", 1766 + wordBreak: "break-word", 1767 + //color: theme.text, 1429 1768 }} 1430 - className="text-gray-500 dark:text-gray-400" 1769 + className="text-gray-900 dark:text-gray-100" 1431 1770 > 1432 - <MdiReply /> Reply to @{feedviewpostreplyhandle} 1771 + {fedi ? ( 1772 + <> 1773 + <span 1774 + className="dangerousFediContent" 1775 + dangerouslySetInnerHTML={{ 1776 + __html: DOMPurify.sanitize(fedi), 1777 + }} 1778 + /> 1779 + </> 1780 + ) : ( 1781 + <> 1782 + {renderTextWithFacets({ 1783 + text: (post.record as { text?: string }).text ?? "", 1784 + facets: (post.record.facets as Facet[]) ?? [], 1785 + navigate: navigate, 1786 + })} 1787 + </> 1788 + )} 1433 1789 </div> 1434 - )} 1435 - <div 1436 - style={{ 1437 - fontSize: 16, 1438 - marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8, 1439 - whiteSpace: "pre-wrap", 1440 - textAlign: "left", 1441 - overflowWrap: "anywhere", 1442 - wordBreak: "break-word", 1443 - //color: theme.text, 1444 - }} 1445 - className="text-gray-900 dark:text-gray-100" 1446 - > 1447 - {renderTextWithFacets({ 1448 - text: (post.record as { text?: string }).text ?? "", 1449 - facets: (post.record.facets as Facet[]) ?? [], 1450 - navigate: navigate, 1451 - })} 1452 - {} 1453 - </div> 1454 - {post.embed && depth < 1 ? ( 1455 - <PostEmbeds 1456 - embed={post.embed} 1457 - //moderation={moderation} 1458 - viewContext={PostEmbedViewContext.Feed} 1459 - salt={salt} 1460 - navigate={navigate} 1461 - /> 1462 - ) : null} 1463 - {post.embed && depth > 0 && ( 1464 - /* pretty bad hack imo. its trying to sync up with how the embed shim doesnt 1790 + {post.embed && depth < 1 ? ( 1791 + <PostEmbeds 1792 + embed={post.embed} 1793 + //moderation={moderation} 1794 + viewContext={PostEmbedViewContext.Feed} 1795 + salt={salt} 1796 + navigate={navigate} 1797 + postid={{ did: post.author.did, rkey: parsed.rkey }} 1798 + nopics={nopics} 1799 + lightboxCallback={lightboxCallback} 1800 + /> 1801 + ) : null} 1802 + {post.embed && depth > 0 && ( 1803 + /* pretty bad hack imo. its trying to sync up with how the embed shim doesnt 1465 1804 hydrate embeds this deep but the connection here is implicit 1466 1805 todo: idk make this a real part of the embed shim so its not implicit */ 1467 - <> 1468 - <div className="border-gray-300 dark:border-gray-600 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1469 - (there is an embed here thats too deep to render) 1470 - </div> 1471 - </> 1472 - )} 1473 - <div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}> 1474 - <> 1475 - {expanded && ( 1806 + <> 1807 + <div className="border-gray-300 dark:border-gray-800 p-3 rounded-xl border italic text-gray-400 text-[14px]"> 1808 + (there is an embed here thats too deep to render) 1809 + </div> 1810 + </> 1811 + )} 1812 + <div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}> 1813 + <> 1814 + {expanded && ( 1815 + <div 1816 + style={{ 1817 + overflow: "hidden", 1818 + //color: theme.textSecondary, 1819 + fontSize: 14, 1820 + display: "flex", 1821 + borderBottomStyle: "solid", 1822 + //borderBottomColor: theme.border, 1823 + //background: "#f00", 1824 + // height: "1rem", 1825 + paddingTop: 4, 1826 + paddingBottom: 8, 1827 + borderBottomWidth: 1, 1828 + marginBottom: 8, 1829 + }} // important for height animation 1830 + className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-800 was7" 1831 + > 1832 + {fullDateTimeFormat(post.indexedAt)} 1833 + </div> 1834 + )} 1835 + </> 1836 + {!isQuote && ( 1476 1837 <div 1477 1838 style={{ 1478 - overflow: "hidden", 1479 - //color: theme.textSecondary, 1480 - fontSize: 14, 1481 1839 display: "flex", 1482 - borderBottomStyle: "solid", 1483 - //borderBottomColor: theme.border, 1484 - //background: "#f00", 1485 - // height: "1rem", 1486 - paddingTop: 4, 1487 - paddingBottom: 8, 1488 - borderBottomWidth: 1, 1489 - marginBottom: 8, 1490 - }} // important for height animation 1491 - className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-700" 1492 - > 1493 - {fullDateTimeFormat(post.indexedAt)} 1494 - </div> 1495 - )} 1496 - </> 1497 - {!isQuote && ( 1498 - <div 1499 - style={{ 1500 - display: "flex", 1501 - gap: 32, 1502 - paddingTop: 8, 1503 - //color: theme.textSecondary, 1504 - fontSize: 15, 1505 - justifyContent: "space-between", 1506 - //background: "#0f0", 1507 - }} 1508 - className="text-gray-500 dark:text-gray-400" 1509 - > 1510 - <span style={btnstyle}> 1511 - <MdiCommentOutline /> 1512 - {post.replyCount} 1513 - </span> 1514 - <HitSlopButton 1515 - onClick={() => { 1516 - repostOrUnrepostPost(); 1840 + gap: 32, 1841 + paddingTop: 8, 1842 + //color: theme.textSecondary, 1843 + fontSize: 15, 1844 + justifyContent: "space-between", 1845 + //background: "#0f0", 1517 1846 }} 1518 - style={{ 1519 - ...btnstyle, 1520 - ...(hasRetweeted ? { color: "#5CEFAA" } : {}), 1521 - }} 1847 + className="text-gray-500 dark:text-gray-400" 1522 1848 > 1523 - {hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />} 1524 - {(post.repostCount || 0) + (hasRetweeted ? 1 : 0)} 1525 - </HitSlopButton> 1526 - <HitSlopButton 1527 - onClick={() => { 1528 - likeOrUnlikePost(); 1529 - }} 1530 - style={{ 1531 - ...btnstyle, 1532 - ...(hasLiked ? { color: "#EC4899" } : {}), 1533 - }} 1534 - > 1535 - {hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />} 1536 - {(post.likeCount || 0) + (hasLiked ? 1 : 0)} 1537 - </HitSlopButton> 1538 - <div style={{ display: "flex", gap: 8 }}> 1849 + <HitSlopButton 1850 + onClick={() => { 1851 + setComposerPost({ kind: "reply", parent: post.uri }); 1852 + }} 1853 + style={{ 1854 + ...btnstyle, 1855 + }} 1856 + > 1857 + <MdiCommentOutline /> 1858 + {post.replyCount} 1859 + </HitSlopButton> 1860 + <DropdownMenu.Root modal={false}> 1861 + <DropdownMenu.Trigger asChild> 1862 + <div 1863 + style={{ 1864 + ...btnstyle, 1865 + ...(hasRetweeted ? { color: "#5CEFAA" } : {}), 1866 + }} 1867 + aria-label="Repost or quote post" 1868 + > 1869 + {hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />} 1870 + {post.repostCount ?? 0} 1871 + </div> 1872 + </DropdownMenu.Trigger> 1873 + 1874 + <DropdownMenu.Portal> 1875 + <DropdownMenu.Content 1876 + align="start" 1877 + sideOffset={5} 1878 + className="bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 w-32 z-50 overflow-hidden" 1879 + > 1880 + <DropdownMenu.Item 1881 + onSelect={repostOrUnrepostPost} 1882 + className="px-3 py-2 text-sm flex items-center gap-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-700" 1883 + > 1884 + <MdiRepeat 1885 + className={hasRetweeted ? "text-green-400" : ""} 1886 + /> 1887 + <span>{hasRetweeted ? "Undo Repost" : "Repost"}</span> 1888 + </DropdownMenu.Item> 1889 + 1890 + <DropdownMenu.Item 1891 + onSelect={() => { 1892 + setComposerPost({ 1893 + kind: "quote", 1894 + subject: post.uri, 1895 + }); 1896 + }} 1897 + className="px-3 py-2 text-sm flex items-center gap-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-700" 1898 + > 1899 + {/* You might want a specific quote icon here */} 1900 + <MdiCommentOutline /> 1901 + <span>Quote</span> 1902 + </DropdownMenu.Item> 1903 + </DropdownMenu.Content> 1904 + </DropdownMenu.Portal> 1905 + </DropdownMenu.Root> 1539 1906 <HitSlopButton 1540 - onClick={async (e) => { 1541 - e.stopPropagation(); 1542 - try { 1543 - await navigator.clipboard.writeText( 1544 - "https://bsky.app" + 1545 - "/profile/" + 1546 - post.author.handle + 1547 - "/post/" + 1548 - post.uri.split("/").pop() 1549 - ); 1550 - } catch (_e) { 1551 - // idk 1552 - } 1907 + onClick={() => { 1908 + likeOrUnlikePost(); 1553 1909 }} 1554 1910 style={{ 1555 1911 ...btnstyle, 1912 + ...(hasLiked ? { color: "#EC4899" } : {}), 1556 1913 }} 1557 1914 > 1558 - <MdiShareVariant /> 1915 + {hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />} 1916 + {(post.likeCount || 0) + (hasLiked ? 1 : 0)} 1559 1917 </HitSlopButton> 1560 - <span style={btnstyle}> 1561 - <MdiMoreHoriz /> 1562 - </span> 1918 + <div style={{ display: "flex", gap: 8 }}> 1919 + <HitSlopButton 1920 + onClick={async (e) => { 1921 + e.stopPropagation(); 1922 + try { 1923 + await navigator.clipboard.writeText( 1924 + "https://bsky.app" + 1925 + "/profile/" + 1926 + post.author.handle + 1927 + "/post/" + 1928 + post.uri.split("/").pop() 1929 + ); 1930 + } catch (_e) { 1931 + // idk 1932 + } 1933 + }} 1934 + style={{ 1935 + ...btnstyle, 1936 + }} 1937 + > 1938 + <MdiShareVariant /> 1939 + </HitSlopButton> 1940 + <span style={btnstyle}> 1941 + <MdiMoreHoriz /> 1942 + </span> 1943 + </div> 1563 1944 </div> 1564 - </div> 1565 - )} 1945 + )} 1946 + </div> 1947 + <div 1948 + style={{ 1949 + //height: bottomReplyLine ? 16 : 0 1950 + height: isQuote ? 12 : 16, 1951 + }} 1952 + /> 1566 1953 </div> 1567 - <div 1568 - style={{ 1569 - //height: bottomReplyLine ? 16 : 0 1570 - height: isQuote ? 12 : 16, 1571 - }} 1572 - /> 1573 1954 </div> 1574 1955 </div> 1575 1956 </div> ··· 1661 2042 viewContext, 1662 2043 salt, 1663 2044 navigate, 2045 + postid, 2046 + nopics, 2047 + lightboxCallback, 1664 2048 }: { 1665 2049 embed?: Embed; 1666 2050 moderation?: ModerationDecision; ··· 1669 2053 viewContext?: PostEmbedViewContext; 1670 2054 salt: string; 1671 2055 navigate: (_: any) => void; 2056 + postid?: { did: string; rkey: string }; 2057 + nopics?: boolean; 2058 + lightboxCallback?: (d: LightboxProps) => void; 1672 2059 }) { 1673 - const [lightboxIndex, setLightboxIndex] = useState<number | null>(null); 2060 + //const [lightboxIndex, setLightboxIndex] = useState<number | null>(null); 2061 + function setLightboxIndex(number: number) { 2062 + navigate({ 2063 + to: "/profile/$did/post/$rkey/image/$i", 2064 + params: { 2065 + did: postid?.did, 2066 + rkey: postid?.rkey, 2067 + i: number.toString(), 2068 + }, 2069 + }); 2070 + } 1674 2071 if ( 1675 2072 AppBskyEmbedRecordWithMedia.isView(embed) && 1676 2073 AppBskyEmbedRecord.isViewRecord(embed.record.record) && ··· 1704 2101 viewContext={viewContext} 1705 2102 salt={salt} 1706 2103 navigate={navigate} 2104 + postid={postid} 2105 + nopics={nopics} 2106 + lightboxCallback={lightboxCallback} 1707 2107 /> 1708 2108 {/* padding empty div of 8px height */} 1709 2109 <div style={{ height: 12 }} /> ··· 1717 2117 //boxShadow: theme.cardShadow, 1718 2118 overflow: "hidden", 1719 2119 }} 1720 - className="shadow border border-gray-200 dark:border-gray-700" 2120 + className="shadow border border-gray-200 dark:border-gray-800 was7" 1721 2121 > 1722 2122 <UniversalPostRenderer 1723 2123 post={post} ··· 1834 2234 //boxShadow: theme.cardShadow, 1835 2235 overflow: "hidden", 1836 2236 }} 1837 - className="shadow border border-gray-200 dark:border-gray-700" 2237 + className="shadow border border-gray-200 dark:border-gray-800 was7" 1838 2238 > 1839 2239 <UniversalPostRenderer 1840 2240 post={post} ··· 1878 2278 src: img.fullsize, 1879 2279 alt: img.alt, 1880 2280 })); 2281 + console.log("rendering images"); 2282 + if (lightboxCallback) { 2283 + lightboxCallback({ images: lightboxImages }); 2284 + console.log("rendering images"); 2285 + } 2286 + 2287 + if (nopics) return; 1881 2288 1882 2289 if (images.length > 0) { 1883 2290 // const items = embed.images.map(img => ({ ··· 1907 2314 //border: `1px solid ${theme.border}`, 1908 2315 overflow: "hidden", 1909 2316 }} 1910 - className="border border-gray-200 dark:border-gray-700 bg-gray-200 dark:bg-gray-900" 2317 + className="border border-gray-200 dark:border-gray-800 was7 bg-gray-200 dark:bg-gray-900" 1911 2318 > 1912 - {lightboxIndex !== null && ( 2319 + {/* {lightboxIndex !== null && ( 1913 2320 <Lightbox 1914 2321 images={lightboxImages} 1915 2322 index={lightboxIndex} 1916 2323 onClose={() => setLightboxIndex(null)} 1917 2324 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2325 + post={postid} 1918 2326 /> 1919 - )} 2327 + )} */} 1920 2328 <img 1921 2329 src={image.fullsize} 1922 2330 alt={image.alt} ··· 1947 2355 overflow: "hidden", 1948 2356 //border: `1px solid ${theme.border}`, 1949 2357 }} 1950 - className="border border-gray-200 dark:border-gray-700" 2358 + className="border border-gray-200 dark:border-gray-800 was7" 1951 2359 > 1952 - {lightboxIndex !== null && ( 2360 + {/* {lightboxIndex !== null && ( 1953 2361 <Lightbox 1954 2362 images={lightboxImages} 1955 2363 index={lightboxIndex} 1956 2364 onClose={() => setLightboxIndex(null)} 1957 2365 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2366 + post={postid} 1958 2367 /> 1959 - )} 2368 + )} */} 1960 2369 {images.map((img, i) => ( 1961 2370 <div 1962 2371 key={i} ··· 1996 2405 //border: `1px solid ${theme.border}`, 1997 2406 // height: 240, // fixed height for cropping 1998 2407 }} 1999 - className="border border-gray-200 dark:border-gray-700" 2408 + className="border border-gray-200 dark:border-gray-800 was7" 2000 2409 > 2001 - {lightboxIndex !== null && ( 2410 + {/* {lightboxIndex !== null && ( 2002 2411 <Lightbox 2003 2412 images={lightboxImages} 2004 2413 index={lightboxIndex} 2005 2414 onClose={() => setLightboxIndex(null)} 2006 2415 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2416 + post={postid} 2007 2417 /> 2008 - )} 2418 + )} */} 2009 2419 {/* Left: 1:1 */} 2010 2420 <div 2011 2421 style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }} ··· 2080 2490 //border: `1px solid ${theme.border}`, 2081 2491 //aspectRatio: "3 / 2", // overall grid aspect 2082 2492 }} 2083 - className="border border-gray-200 dark:border-gray-700" 2493 + className="border border-gray-200 dark:border-gray-800 was7" 2084 2494 > 2085 - {lightboxIndex !== null && ( 2495 + {/* {lightboxIndex !== null && ( 2086 2496 <Lightbox 2087 2497 images={lightboxImages} 2088 2498 index={lightboxIndex} 2089 2499 onClose={() => setLightboxIndex(null)} 2090 2500 onNavigate={(newIndex) => setLightboxIndex(newIndex)} 2501 + post={postid} 2091 2502 /> 2092 - )} 2503 + )} */} 2093 2504 {images.map((img, i) => ( 2094 2505 <div 2095 2506 key={i} ··· 2180 2591 return <div />; 2181 2592 } 2182 2593 2183 - import { createPortal } from "react-dom"; 2184 - type LightboxProps = { 2185 - images: { src: string; alt?: string }[]; 2186 - index: number; 2187 - onClose: () => void; 2188 - onNavigate?: (newIndex: number) => void; 2189 - }; 2190 - export function Lightbox({ 2191 - images, 2192 - index, 2193 - onClose, 2194 - onNavigate, 2195 - }: LightboxProps) { 2196 - const image = images[index]; 2197 - 2198 - useEffect(() => { 2199 - function handleKey(e: KeyboardEvent) { 2200 - if (e.key === "Escape") onClose(); 2201 - if (e.key === "ArrowRight" && onNavigate) 2202 - onNavigate((index + 1) % images.length); 2203 - if (e.key === "ArrowLeft" && onNavigate) 2204 - onNavigate((index - 1 + images.length) % images.length); 2205 - } 2206 - window.addEventListener("keydown", handleKey); 2207 - return () => window.removeEventListener("keydown", handleKey); 2208 - }, [index, images.length, onClose, onNavigate]); 2209 - 2210 - return createPortal( 2211 - <div 2212 - className="fixed inset-0 z-50 flex items-center justify-center bg-black/80" 2213 - onClick={(e) => { 2214 - e.stopPropagation(); 2215 - onClose(); 2216 - }} 2217 - > 2218 - <img 2219 - src={image.src} 2220 - alt={image.alt} 2221 - className="max-h-[90vh] max-w-[90vw] object-contain rounded-lg shadow-lg" 2222 - onClick={(e) => e.stopPropagation()} 2223 - /> 2224 - 2225 - {images.length > 1 && ( 2226 - <> 2227 - <button 2228 - onClick={(e) => { 2229 - e.stopPropagation(); 2230 - onNavigate?.((index - 1 + images.length) % images.length); 2231 - }} 2232 - className="absolute left-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center" 2233 - > 2234 - <svg 2235 - xmlns="http://www.w3.org/2000/svg" 2236 - width={28} 2237 - height={28} 2238 - viewBox="0 0 24 24" 2239 - > 2240 - <g fill="none" fillRule="evenodd"> 2241 - <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path> 2242 - <path 2243 - fill="currentColor" 2244 - d="M8.293 12.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 1 1 1.414 1.414L10.414 12l4.95 4.95a1 1 0 0 1-1.414 1.414z" 2245 - ></path> 2246 - </g> 2247 - </svg> 2248 - </button> 2249 - <button 2250 - onClick={(e) => { 2251 - e.stopPropagation(); 2252 - onNavigate?.((index + 1) % images.length); 2253 - }} 2254 - className="absolute right-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center" 2255 - > 2256 - <svg 2257 - xmlns="http://www.w3.org/2000/svg" 2258 - width={28} 2259 - height={28} 2260 - viewBox="0 0 24 24" 2261 - > 2262 - <g fill="none" fillRule="evenodd"> 2263 - <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path> 2264 - <path 2265 - fill="currentColor" 2266 - d="M15.707 11.293a1 1 0 0 1 0 1.414l-5.657 5.657a1 1 0 1 1-1.414-1.414l4.95-4.95l-4.95-4.95a1 1 0 0 1 1.414-1.414z" 2267 - ></path> 2268 - </g> 2269 - </svg> 2270 - </button> 2271 - </> 2272 - )} 2273 - </div>, 2274 - document.body 2275 - ); 2276 - } 2277 - 2278 2594 function getDomain(url: string) { 2279 2595 try { 2280 2596 const { hostname } = new URL(url); ··· 2339 2655 return { start, end, feature: f.features[0] }; 2340 2656 }); 2341 2657 } 2342 - function renderTextWithFacets({ 2658 + export function renderTextWithFacets({ 2343 2659 text, 2344 2660 facets, 2345 2661 navigate, ··· 2502 2818 > 2503 2819 <div 2504 2820 style={containerStyle as React.CSSProperties} 2505 - className="border border-gray-200 dark:border-gray-700" 2821 + className="border border-gray-200 dark:border-gray-800 was7" 2506 2822 > 2507 2823 {thumb && ( 2508 2824 <div ··· 2516 2832 marginBottom: 8, 2517 2833 //borderBottom: `1px solid ${theme.border}`, 2518 2834 }} 2519 - className="border-b border-gray-200 dark:border-gray-700" 2835 + className="border-b border-gray-200 dark:border-gray-800 was7" 2520 2836 > 2521 2837 <img 2522 2838 src={thumb} ··· 2642 2958 borderRadius: 12, 2643 2959 //border: `1px solid ${theme.border}`, 2644 2960 }} 2645 - className="border border-gray-200 dark:border-gray-700" 2961 + className="border border-gray-200 dark:border-gray-800 was7" 2646 2962 onClick={async (e) => { 2647 2963 e.stopPropagation(); 2648 2964 setPlaying(true); ··· 2683 2999 100 / (aspect ? aspect.width / aspect.height : 16 / 9) 2684 3000 }%`, // 16:9 = 56.25%, 4:3 = 75% 2685 3001 }} 2686 - className="border border-gray-200 dark:border-gray-700" 3002 + className="border border-gray-200 dark:border-gray-800 was7" 2687 3003 > 2688 3004 <ReactPlayer 2689 3005 src={url}
+59 -14
src/main.tsx
··· 1 - import { StrictMode } from "react"; 1 + import "~/styles/app.css"; 2 + 3 + import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; 4 + import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 5 + import { persistQueryClient } from "@tanstack/react-query-persist-client"; 6 + import { createRouter, RouterProvider } from "@tanstack/react-router"; 7 + import { useSetAtom } from "jotai"; 8 + import { useEffect } from "react"; 9 + //import { StrictMode } from "react"; 2 10 import ReactDOM from "react-dom/client"; 3 - import { RouterProvider, createRouter } from "@tanstack/react-router"; 4 11 12 + import reportWebVitals from "./reportWebVitals.ts"; 5 13 // Import the generated route tree 6 14 import { routeTree } from "./routeTree.gen"; 15 + import { isAtTopAtom } from "./utils/atoms.ts"; 7 16 8 - import "~/styles/app.css"; 9 - import reportWebVitals from "./reportWebVitals.ts"; 10 - import { QueryClient, QueryClientProvider, } from "@tanstack/react-query"; 11 - import { 12 - persistQueryClient, 13 - } from "@tanstack/react-query-persist-client"; 14 - import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; 15 - 17 + //initAtomToCssVar(hueAtom, "--tw-gray-hue") 16 18 17 19 const queryClient = new QueryClient({ 18 20 defaultOptions: { ··· 28 30 persistQueryClient({ 29 31 queryClient, 30 32 persister: localStoragePersister, 31 - }) 33 + }); 32 34 33 35 // Create a new router instance 34 36 const router = createRouter({ ··· 54 56 root.render( 55 57 // double queries annoys me 56 58 // <StrictMode> 57 - <QueryClientProvider client={queryClient}> 58 - <RouterProvider router={router} /> 59 - </QueryClientProvider> 59 + <QueryClientProvider client={queryClient}> 60 + <ScrollTopWatcher /> 61 + <RouterProvider router={router} /> 62 + </QueryClientProvider> 60 63 // </StrictMode> 61 64 ); 62 65 } ··· 65 68 // to log results (for example: reportWebVitals(// /*mass comment*/ console.log)) 66 69 // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 67 70 reportWebVitals(); 71 + 72 + export default function ScrollTopWatcher() { 73 + const setIsAtTop = useSetAtom(isAtTopAtom); 74 + useEffect(() => { 75 + const meta = document.querySelector('meta[name="theme-color"]'); 76 + let lastAtTop = window.scrollY === 0; 77 + let timeoutId: number | undefined; 78 + 79 + const setVars = (atTop: boolean) => { 80 + const root = document.documentElement; 81 + root.style.setProperty("--is-top", atTop ? "1" : "0"); 82 + 83 + const bg = getComputedStyle(root).getPropertyValue("--header-bg").trim(); 84 + if (meta && bg) meta.setAttribute("content", bg); 85 + setIsAtTop(atTop); 86 + }; 87 + 88 + const check = () => { 89 + const atTop = window.scrollY === 0; 90 + if (atTop !== lastAtTop) { 91 + lastAtTop = atTop; 92 + setVars(atTop); 93 + } 94 + }; 95 + 96 + const handleScroll = () => { 97 + if (timeoutId) clearTimeout(timeoutId); 98 + timeoutId = window.setTimeout(check, 2); 99 + }; 100 + 101 + // initialize 102 + setVars(lastAtTop); 103 + window.addEventListener("scroll", handleScroll, { passive: true }); 104 + 105 + return () => { 106 + window.removeEventListener("scroll", handleScroll); 107 + if (timeoutId) clearTimeout(timeoutId); 108 + }; 109 + }, []); 110 + 111 + return null; 112 + }
+36 -5
src/routeTree.gen.ts
··· 21 21 import { Route as PathlessLayoutNestedLayoutRouteBRouteImport } from './routes/_pathlessLayout/_nested-layout/route-b' 22 22 import { Route as PathlessLayoutNestedLayoutRouteARouteImport } from './routes/_pathlessLayout/_nested-layout/route-a' 23 23 import { Route as ProfileDidPostRkeyRouteImport } from './routes/profile.$did/post.$rkey' 24 + import { Route as ProfileDidPostRkeyImageIRouteImport } from './routes/profile.$did/post.$rkey.image.$i' 24 25 25 26 const SettingsRoute = SettingsRouteImport.update({ 26 27 id: '/settings', ··· 83 84 path: '/profile/$did/post/$rkey', 84 85 getParentRoute: () => rootRouteImport, 85 86 } as any) 87 + const ProfileDidPostRkeyImageIRoute = 88 + ProfileDidPostRkeyImageIRouteImport.update({ 89 + id: '/image/$i', 90 + path: '/image/$i', 91 + getParentRoute: () => ProfileDidPostRkeyRoute, 92 + } as any) 86 93 87 94 export interface FileRoutesByFullPath { 88 95 '/': typeof IndexRoute ··· 94 101 '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 95 102 '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 96 103 '/profile/$did': typeof ProfileDidIndexRoute 97 - '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 104 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren 105 + '/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute 98 106 } 99 107 export interface FileRoutesByTo { 100 108 '/': typeof IndexRoute ··· 106 114 '/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 107 115 '/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 108 116 '/profile/$did': typeof ProfileDidIndexRoute 109 - '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 117 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren 118 + '/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute 110 119 } 111 120 export interface FileRoutesById { 112 121 __root__: typeof rootRouteImport ··· 121 130 '/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute 122 131 '/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute 123 132 '/profile/$did/': typeof ProfileDidIndexRoute 124 - '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRoute 133 + '/profile/$did/post/$rkey': typeof ProfileDidPostRkeyRouteWithChildren 134 + '/profile/$did/post/$rkey/image/$i': typeof ProfileDidPostRkeyImageIRoute 125 135 } 126 136 export interface FileRouteTypes { 127 137 fileRoutesByFullPath: FileRoutesByFullPath ··· 136 146 | '/route-b' 137 147 | '/profile/$did' 138 148 | '/profile/$did/post/$rkey' 149 + | '/profile/$did/post/$rkey/image/$i' 139 150 fileRoutesByTo: FileRoutesByTo 140 151 to: 141 152 | '/' ··· 148 159 | '/route-b' 149 160 | '/profile/$did' 150 161 | '/profile/$did/post/$rkey' 162 + | '/profile/$did/post/$rkey/image/$i' 151 163 id: 152 164 | '__root__' 153 165 | '/' ··· 162 174 | '/_pathlessLayout/_nested-layout/route-b' 163 175 | '/profile/$did/' 164 176 | '/profile/$did/post/$rkey' 177 + | '/profile/$did/post/$rkey/image/$i' 165 178 fileRoutesById: FileRoutesById 166 179 } 167 180 export interface RootRouteChildren { ··· 173 186 SettingsRoute: typeof SettingsRoute 174 187 CallbackIndexRoute: typeof CallbackIndexRoute 175 188 ProfileDidIndexRoute: typeof ProfileDidIndexRoute 176 - ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRoute 189 + ProfileDidPostRkeyRoute: typeof ProfileDidPostRkeyRouteWithChildren 177 190 } 178 191 179 192 declare module '@tanstack/react-router' { ··· 262 275 preLoaderRoute: typeof ProfileDidPostRkeyRouteImport 263 276 parentRoute: typeof rootRouteImport 264 277 } 278 + '/profile/$did/post/$rkey/image/$i': { 279 + id: '/profile/$did/post/$rkey/image/$i' 280 + path: '/image/$i' 281 + fullPath: '/profile/$did/post/$rkey/image/$i' 282 + preLoaderRoute: typeof ProfileDidPostRkeyImageIRouteImport 283 + parentRoute: typeof ProfileDidPostRkeyRoute 284 + } 265 285 } 266 286 } 267 287 ··· 295 315 PathlessLayoutRouteChildren, 296 316 ) 297 317 318 + interface ProfileDidPostRkeyRouteChildren { 319 + ProfileDidPostRkeyImageIRoute: typeof ProfileDidPostRkeyImageIRoute 320 + } 321 + 322 + const ProfileDidPostRkeyRouteChildren: ProfileDidPostRkeyRouteChildren = { 323 + ProfileDidPostRkeyImageIRoute: ProfileDidPostRkeyImageIRoute, 324 + } 325 + 326 + const ProfileDidPostRkeyRouteWithChildren = 327 + ProfileDidPostRkeyRoute._addFileChildren(ProfileDidPostRkeyRouteChildren) 328 + 298 329 const rootRouteChildren: RootRouteChildren = { 299 330 IndexRoute: IndexRoute, 300 331 PathlessLayoutRoute: PathlessLayoutRouteWithChildren, ··· 304 335 SettingsRoute: SettingsRoute, 305 336 CallbackIndexRoute: CallbackIndexRoute, 306 337 ProfileDidIndexRoute: ProfileDidIndexRoute, 307 - ProfileDidPostRkeyRoute: ProfileDidPostRkeyRoute, 338 + ProfileDidPostRkeyRoute: ProfileDidPostRkeyRouteWithChildren, 308 339 } 309 340 export const routeTree = rootRouteImport 310 341 ._addFileChildren(rootRouteChildren)
+558 -453
src/routes/__root.tsx
··· 2 2 3 3 // dont forget to run this 4 4 // npx @tanstack/router-cli generate 5 - 6 5 import type { QueryClient } from "@tanstack/react-query"; 7 6 import { 8 7 createRootRouteWithContext, 9 - Link, 10 - Outlet, 8 + // Link, 9 + // Outlet, 11 10 Scripts, 12 11 useLocation, 13 12 useNavigate, 14 13 } from "@tanstack/react-router"; 15 14 import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; 16 - import { type SVGProps,useState } from "react"; 15 + import { useAtom } from "jotai"; 17 16 import * as React from "react"; 17 + import { KeepAliveOutlet, KeepAliveProvider } from "tanstack-router-keepalive"; 18 18 19 + import { Composer } from "~/components/Composer"; 19 20 import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary"; 21 + import { Import } from "~/components/Import"; 20 22 import Login from "~/components/Login"; 21 23 import { NotFound } from "~/components/NotFound"; 24 + import { FluentEmojiHighContrastGlowingStar } from "~/components/Star"; 22 25 import { UnifiedAuthProvider, useAuth } from "~/providers/UnifiedAuthProvider"; 26 + import { composerAtom, hueAtom, useAtomCssVar } from "~/utils/atoms"; 23 27 import { seo } from "~/utils/seo"; 24 28 25 29 export const Route = createRootRouteWithContext<{ ··· 76 80 return ( 77 81 <UnifiedAuthProvider> 78 82 <RootDocument> 79 - <Outlet /> 83 + <KeepAliveProvider> 84 + <KeepAliveOutlet /> 85 + </KeepAliveProvider> 80 86 </RootDocument> 81 87 </UnifiedAuthProvider> 82 88 ); 83 89 } 84 90 85 91 function RootDocument({ children }: { children: React.ReactNode }) { 92 + useAtomCssVar(hueAtom, "--tw-gray-hue"); 86 93 const location = useLocation(); 87 94 const navigate = useNavigate(); 88 95 const { agent } = useAuth(); 89 96 const authed = !!agent?.did; 90 97 const isHome = location.pathname === "/"; 91 98 const isNotifications = location.pathname.startsWith("/notifications"); 92 - const isProfile = agent && ((location.pathname === (`/profile/${agent?.did}`)) || (location.pathname === (`/profile/${encodeURIComponent(agent?.did??"")}`))); 99 + const isProfile = 100 + agent && 101 + (location.pathname === `/profile/${agent?.did}` || 102 + location.pathname === `/profile/${encodeURIComponent(agent?.did ?? "")}`); 103 + const isSettings = location.pathname.startsWith("/settings"); 104 + const isSearch = location.pathname.startsWith("/search"); 105 + const isFeeds = location.pathname.startsWith("/feeds"); 93 106 94 - const [postOpen, setPostOpen] = useState(false); 95 - const [postText, setPostText] = useState(""); 96 - const [posting, setPosting] = useState(false); 97 - const [postSuccess, setPostSuccess] = useState(false); 98 - const [postError, setPostError] = useState<string | null>(null); 107 + const locationEnum: 108 + | "feeds" 109 + | "search" 110 + | "settings" 111 + | "notifications" 112 + | "profile" 113 + | "home" = isFeeds 114 + ? "feeds" 115 + : isSearch 116 + ? "search" 117 + : isSettings 118 + ? "settings" 119 + : isNotifications 120 + ? "notifications" 121 + : isProfile 122 + ? "profile" 123 + : "home"; 99 124 100 - async function handlePost() { 101 - if (!agent) return; 102 - setPosting(true); 103 - setPostError(null); 104 - try { 105 - await agent.com.atproto.repo.createRecord({ 106 - collection: "app.bsky.feed.post", 107 - repo: agent.assertDid, 108 - record: { 109 - $type: "app.bsky.feed.post", 110 - text: postText, 111 - createdAt: new Date().toISOString(), 112 - }, 113 - }); 114 - setPostSuccess(true); 115 - setPostText(""); 116 - setTimeout(() => { 117 - setPostSuccess(false); 118 - setPostOpen(false); 119 - }, 1500); 120 - } catch (e: any) { 121 - setPostError(e?.message || "Failed to post"); 122 - } finally { 123 - setPosting(false); 124 - } 125 - } 125 + const [, setComposerPost] = useAtom(composerAtom); 126 126 127 127 return ( 128 128 <> 129 - {postOpen && ( 130 - <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"> 131 - <div className="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-6 w-full max-w-md relative"> 132 - <button 133 - className="absolute top-2 right-2 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200" 134 - onClick={() => !posting && setPostOpen(false)} 135 - disabled={posting} 136 - aria-label="Close" 137 - > 138 - ร— 139 - </button> 140 - <h2 className="text-lg font-bold mb-2">Create Post</h2> 141 - {postSuccess ? ( 142 - <div className="flex flex-col items-center justify-center py-8"> 143 - <span className="text-green-500 text-4xl mb-2">โœ“</span> 144 - <span className="text-green-600">Posted!</span> 145 - </div> 146 - ) : ( 147 - <> 148 - <textarea 149 - className="w-full border rounded p-2 mb-2 dark:bg-gray-800 dark:border-gray-700" 150 - rows={4} 151 - placeholder="What's on your mind?" 152 - value={postText} 153 - onChange={(e) => setPostText(e.target.value)} 154 - disabled={posting} 155 - autoFocus 156 - /> 157 - {postError && ( 158 - <div className="text-red-500 text-sm mb-2">{postError}</div> 159 - )} 160 - <button 161 - className="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50" 162 - onClick={handlePost} 163 - disabled={posting || !postText.trim()} 164 - > 165 - {posting ? "Posting..." : "Post"} 166 - </button> 167 - </> 168 - )} 169 - </div> 170 - </div> 171 - )} 129 + <Composer /> 172 130 173 131 <div className="min-h-screen flex justify-center bg-gray-50 dark:bg-gray-950"> 174 - <nav className="hidden lg:flex h-screen w-[250px] flex-col gap-2 p-4 dark:border-gray-800 sticky top-0 self-start"> 132 + <nav className="hidden lg:flex h-screen w-[250px] flex-col gap-0 p-4 dark:border-gray-800 sticky top-0 self-start"> 175 133 <div className="flex items-center gap-3 mb-4"> 176 - <img src="/redstar.png" alt="Red Dwarf Logo" className="w-8 h-8" /> 134 + <FluentEmojiHighContrastGlowingStar className="h-8 w-8" style={{color: "oklch(0.6616 0.2249 calc(25.88 + (var(--safe-hue) - 28))"}} /> 177 135 <span className="font-extrabold text-2xl tracking-tight text-gray-900 dark:text-gray-100"> 178 136 Red Dwarf{" "} 179 137 {/* <span className="text-gray-500 dark:text-gray-400 text-sm"> ··· 181 139 </span> */} 182 140 </span> 183 141 </div> 184 - <Link 142 + <MaterialNavItem 143 + InactiveIcon={ 144 + <IconMaterialSymbolsHomeOutline className="w-6 h-6" /> 145 + } 146 + ActiveIcon={<IconMaterialSymbolsHome className="w-6 h-6" />} 147 + active={locationEnum === "home"} 148 + onClickCallbback={() => 149 + navigate({ 150 + to: "/", 151 + //params: { did: agent.assertDid }, 152 + }) 153 + } 154 + text="Home" 155 + /> 156 + 157 + <MaterialNavItem 158 + InactiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />} 159 + ActiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />} 160 + active={locationEnum === "search"} 161 + onClickCallbback={() => 162 + navigate({ 163 + to: "/search", 164 + //params: { did: agent.assertDid }, 165 + }) 166 + } 167 + text="Explore" 168 + /> 169 + <MaterialNavItem 170 + InactiveIcon={ 171 + <IconMaterialSymbolsNotificationsOutline className="w-6 h-6" /> 172 + } 173 + ActiveIcon={ 174 + <IconMaterialSymbolsNotifications className="w-6 h-6" /> 175 + } 176 + active={locationEnum === "notifications"} 177 + onClickCallbback={() => 178 + navigate({ 179 + to: "/notifications", 180 + //params: { did: agent.assertDid }, 181 + }) 182 + } 183 + text="Notifications" 184 + /> 185 + <MaterialNavItem 186 + InactiveIcon={<IconMaterialSymbolsTag className="w-6 h-6" />} 187 + ActiveIcon={<IconMaterialSymbolsTag className="w-6 h-6" />} 188 + active={locationEnum === "feeds"} 189 + onClickCallbback={() => 190 + navigate({ 191 + to: "/feeds", 192 + //params: { did: agent.assertDid }, 193 + }) 194 + } 195 + text="Feeds" 196 + /> 197 + <MaterialNavItem 198 + InactiveIcon={ 199 + <IconMaterialSymbolsAccountCircleOutline className="w-6 h-6" /> 200 + } 201 + ActiveIcon={ 202 + <IconMaterialSymbolsAccountCircle className="w-6 h-6" /> 203 + } 204 + active={locationEnum === "profile"} 205 + onClickCallbback={() => { 206 + if (authed && agent && agent.assertDid) { 207 + //window.location.href = `/profile/${agent.assertDid}`; 208 + navigate({ 209 + to: "/profile/$did", 210 + params: { did: agent.assertDid }, 211 + }); 212 + } 213 + }} 214 + text="Profile" 215 + /> 216 + <MaterialNavItem 217 + InactiveIcon={ 218 + <IconMaterialSymbolsSettingsOutline className="w-6 h-6" /> 219 + } 220 + ActiveIcon={<IconMaterialSymbolsSettings className="w-6 h-6" />} 221 + active={locationEnum === "settings"} 222 + onClickCallbback={() => 223 + navigate({ 224 + to: "/settings", 225 + //params: { did: agent.assertDid }, 226 + }) 227 + } 228 + text="Settings" 229 + /> 230 + <div className="flex flex-row items-center justify-center mt-3"> 231 + <MaterialPillButton 232 + InactiveIcon={<IconMdiPencilOutline className="w-6 h-6" />} 233 + ActiveIcon={<IconMdiPencilOutline className="w-6 h-6" />} 234 + //active={true} 235 + onClickCallbback={() => setComposerPost({ kind: 'root' })} 236 + text="Post" 237 + /> 238 + </div> 239 + {/* <Link 185 240 to="/" 186 241 className={ 187 242 `py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-900 text-xl flex items-center gap-3 ` + 188 243 (isHome ? "font-bold" : "") 189 244 } 190 245 > 191 - {isHome ? ( 192 - <TablerHomeFilled width={28} height={28} /> 246 + {!isHome ? ( 247 + <IconMaterialSymbolsHomeOutline width={28} height={28} /> 193 248 ) : ( 194 - <TablerHome width={28} height={28} /> 249 + <IconMaterialSymbolsHome width={28} height={28} /> 195 250 )} 196 251 <span>Home</span> 197 252 </Link> ··· 202 257 (isNotifications ? "font-bold" : "") 203 258 } 204 259 > 205 - {isNotifications ? ( 206 - <TablerBellFilled width={28} height={28} /> 260 + {!isNotifications ? ( 261 + <IconMaterialSymbolsNotificationsOutline width={28} height={28} /> 207 262 ) : ( 208 - <TablerBell width={28} height={28} /> 263 + <IconMaterialSymbolsNotifications width={28} height={28} /> 209 264 )} 210 265 <span>Notifications</span> 211 266 </Link> ··· 216 271 }`} 217 272 > 218 273 {location.pathname.startsWith("/feeds") ? ( 219 - <TablerHashtagFilled width={28} height={28} /> 274 + <IconMaterialSymbolsTag width={28} height={28} /> 220 275 ) : ( 221 - <TablerHashtag width={28} height={28} /> 276 + <IconMaterialSymbolsTag width={28} height={28} /> 222 277 )} 223 278 <span>Feeds</span> 224 279 </Link> ··· 230 285 }`} 231 286 > 232 287 {location.pathname.startsWith("/search") ? ( 233 - <TablerSearchFilled width={28} height={28} /> 288 + <IconMaterialSymbolsSearch width={28} height={28} /> 234 289 ) : ( 235 - <TablerSearch width={28} height={28} /> 290 + <IconMaterialSymbolsSearch width={28} height={28} /> 236 291 )} 237 292 <span>Search</span> 238 293 </Link> ··· 246 301 navigate({ 247 302 to: "/profile/$did", 248 303 params: { did: agent.assertDid }, 249 - }) 304 + }); 250 305 } 251 306 }} 252 307 type="button" 253 308 > 254 - <TablerUserCircle width={28} height={28} /> 309 + {!isProfile ? ( 310 + <IconMaterialSymbolsAccountCircleOutline width={28} height={28} /> 311 + ) : ( 312 + <IconMaterialSymbolsAccountCircle width={28} height={28} /> 313 + )} 255 314 <span>Profile</span> 256 315 </button> 257 316 <Link ··· 260 319 location.pathname.startsWith("/settings") ? "font-bold" : "" 261 320 }`} 262 321 > 263 - {location.pathname.startsWith("/settings") ? ( 264 - <IonSettingsSharp width={28} height={28} /> 322 + {!location.pathname.startsWith("/settings") ? ( 323 + <IconMaterialSymbolsSettingsOutline width={28} height={28} /> 265 324 ) : ( 266 - <IonSettings width={28} height={28} /> 325 + <IconMaterialSymbolsSettings width={28} height={28} /> 267 326 )} 268 327 <span>Settings</span> 269 - </Link> 270 - <button 328 + </Link> */} 329 + {/* <button 271 330 className="mt-4 w-full flex items-center justify-center gap-3 py-3 px-0 mb-3 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 text-xl font-bold rounded-full transition-colors shadow" 272 331 onClick={() => setPostOpen(true)} 273 332 type="button" 274 333 > 275 - <TablerEdit 334 + <IconMdiPencilOutline 276 335 width={24} 277 336 height={24} 278 337 className="text-gray-600 dark:text-gray-400" 279 338 /> 280 339 <span>Post</span> 281 - </button> 340 + </button> */} 282 341 <div className="flex-1"></div> 283 342 <a 284 343 href="https://tangled.sh/@whey.party/red-dwarf" ··· 309 368 </div> 310 369 </nav> 311 370 312 - <button 313 - className="lg:hidden fixed bottom-20 right-6 z-50 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-blue-600 dark:text-blue-400 rounded-full shadow-lg w-16 h-16 flex items-center justify-center border-4 border-white dark:border-gray-950 transition-all" 314 - style={{ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.12)" }} 315 - onClick={() => setPostOpen(true)} 316 - type="button" 317 - aria-label="Create Post" 318 - > 319 - <TablerEdit 320 - width={24} 321 - height={24} 322 - className="text-gray-600 dark:text-gray-400" 371 + <nav className="hidden sm:flex items-center lg:hidden h-screen flex-col gap-2 p-4 dark:border-gray-800 sticky top-0 self-start"> 372 + <div className="flex items-center gap-3 mb-4"> 373 + <FluentEmojiHighContrastGlowingStar className="h-8 w-8" style={{color: "oklch(0.6616 0.2249 calc(25.88 + (var(--safe-hue) - 28))"}} /> 374 + </div> 375 + <MaterialNavItem 376 + small 377 + InactiveIcon={ 378 + <IconMaterialSymbolsHomeOutline className="w-6 h-6" /> 379 + } 380 + ActiveIcon={<IconMaterialSymbolsHome className="w-6 h-6" />} 381 + active={locationEnum === "home"} 382 + onClickCallbback={() => 383 + navigate({ 384 + to: "/", 385 + //params: { did: agent.assertDid }, 386 + }) 387 + } 388 + text="Home" 323 389 /> 324 - </button> 325 390 326 - <main className="w-full max-w-[600px] lg:border-x border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950 pb-16 lg:pb-0"> 327 - <div className="lg:hidden flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950"> 328 - <div className="flex items-center gap-2"> 329 - <img 330 - src="/redstar.png" 331 - alt="Red Dwarf Logo" 332 - className="w-6 h-6" 333 - /> 334 - <span className="font-bold text-lg text-gray-900 dark:text-gray-100"> 335 - Red Dwarf{" "} 336 - {/* <span className="text-gray-500 dark:text-gray-400 text-sm"> 337 - lite 338 - </span> */} 339 - </span> 340 - </div> 341 - <div className="flex items-center gap-2"> 342 - <Login compact={true} /> 343 - </div> 391 + <MaterialNavItem 392 + small 393 + InactiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />} 394 + ActiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />} 395 + active={locationEnum === "search"} 396 + onClickCallbback={() => 397 + navigate({ 398 + to: "/search", 399 + //params: { did: agent.assertDid }, 400 + }) 401 + } 402 + text="Explore" 403 + /> 404 + <MaterialNavItem 405 + small 406 + InactiveIcon={ 407 + <IconMaterialSymbolsNotificationsOutline className="w-6 h-6" /> 408 + } 409 + ActiveIcon={ 410 + <IconMaterialSymbolsNotifications className="w-6 h-6" /> 411 + } 412 + active={locationEnum === "notifications"} 413 + onClickCallbback={() => 414 + navigate({ 415 + to: "/notifications", 416 + //params: { did: agent.assertDid }, 417 + }) 418 + } 419 + text="Notifications" 420 + /> 421 + <MaterialNavItem 422 + small 423 + InactiveIcon={<IconMaterialSymbolsTag className="w-6 h-6" />} 424 + ActiveIcon={<IconMaterialSymbolsTag className="w-6 h-6" />} 425 + active={locationEnum === "feeds"} 426 + onClickCallbback={() => 427 + navigate({ 428 + to: "/feeds", 429 + //params: { did: agent.assertDid }, 430 + }) 431 + } 432 + text="Feeds" 433 + /> 434 + <MaterialNavItem 435 + small 436 + InactiveIcon={ 437 + <IconMaterialSymbolsAccountCircleOutline className="w-6 h-6" /> 438 + } 439 + ActiveIcon={ 440 + <IconMaterialSymbolsAccountCircle className="w-6 h-6" /> 441 + } 442 + active={locationEnum === "profile"} 443 + onClickCallbback={() => { 444 + if (authed && agent && agent.assertDid) { 445 + //window.location.href = `/profile/${agent.assertDid}`; 446 + navigate({ 447 + to: "/profile/$did", 448 + params: { did: agent.assertDid }, 449 + }); 450 + } 451 + }} 452 + text="Profile" 453 + /> 454 + <MaterialNavItem 455 + small 456 + InactiveIcon={ 457 + <IconMaterialSymbolsSettingsOutline className="w-6 h-6" /> 458 + } 459 + ActiveIcon={<IconMaterialSymbolsSettings className="w-6 h-6" />} 460 + active={locationEnum === "settings"} 461 + onClickCallbback={() => 462 + navigate({ 463 + to: "/settings", 464 + //params: { did: agent.assertDid }, 465 + }) 466 + } 467 + text="Settings" 468 + /> 469 + <div className="flex flex-row items-center justify-center mt-3"> 470 + <MaterialPillButton 471 + small 472 + InactiveIcon={<IconMdiPencilOutline className="w-6 h-6" />} 473 + ActiveIcon={<IconMdiPencilOutline className="w-6 h-6" />} 474 + //active={true} 475 + onClickCallbback={() => setComposerPost({ kind: 'root' })} 476 + text="Post" 477 + /> 344 478 </div> 479 + </nav> 345 480 481 + {agent?.did && ( 482 + <button 483 + className="lg:hidden fixed bottom-22 right-4 z-50 bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 rounded-2xl w-14 h-14 flex items-center justify-center transition-all" 484 + style={{ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.12)" }} 485 + onClick={() => setComposerPost({ kind: 'root' })} 486 + type="button" 487 + aria-label="Create Post" 488 + > 489 + <IconMdiPencilOutline 490 + width={24} 491 + height={24} 492 + className="text-gray-600 dark:text-gray-400" 493 + /> 494 + </button> 495 + )} 496 + 497 + <main className="w-full max-w-[600px] sm:border-x border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-950 pb-16 lg:pb-0 overflow-x-clip"> 346 498 {children} 347 499 </main> 348 500 349 501 <aside className="hidden lg:flex h-screen w-[250px] sticky top-0 self-start flex-col"> 502 + <div className="px-4 pt-4"><Import /></div> 350 503 <Login /> 351 504 352 505 <div className="flex-1"></div> 353 506 <p className="text-xs text-gray-400 dark:text-gray-500 text-justify mx-4 mb-4"> 354 - Red Dwarf is a bluesky client that uses Constellation and direct PDS 355 - queries. Skylite would be a self-hosted bluesky "instance". Stay 356 - tuned for the release of Skylite. 507 + Red Dwarf is a Bluesky client that does not rely on any Bluesky API App Servers. Instead, it uses Microcosm to fetch records directly from each users' PDS (via Slingshot) and connect them using backlinks (via Constellation) 357 508 </p> 358 509 </aside> 359 510 </div> 360 511 361 - <nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-950 border-t border-gray-200 dark:border-gray-700 z-40"> 362 - <div className="flex justify-around items-center py-2"> 363 - <Link 364 - to="/" 365 - className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 366 - isHome 367 - ? "text-gray-900 dark:text-gray-100" 368 - : "text-gray-600 dark:text-gray-400" 369 - }`} 370 - > 371 - {isHome ? ( 372 - <TablerHomeFilled width={24} height={24} /> 373 - ) : ( 374 - <TablerHome width={24} height={24} /> 375 - )} 376 - <span className="text-xs mt-1">Home</span> 377 - </Link> 378 - <Link 379 - to="/search" 380 - className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 381 - location.pathname.startsWith("/search") 382 - ? "text-gray-900 dark:text-gray-100" 383 - : "text-gray-600 dark:text-gray-400" 384 - }`} 385 - > 386 - {location.pathname.startsWith("/search") ? ( 387 - <TablerSearchFilled width={24} height={24} /> 388 - ) : ( 389 - <TablerSearch width={24} height={24} /> 390 - )} 391 - <span className="text-xs mt-1">Search</span> 392 - </Link> 393 - <Link 394 - to="/notifications" 395 - className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 396 - isNotifications 397 - ? "text-gray-900 dark:text-gray-100" 398 - : "text-gray-600 dark:text-gray-400" 399 - }`} 400 - > 401 - {isNotifications ? ( 402 - <TablerBellFilled width={24} height={24} /> 403 - ) : ( 404 - <TablerBell width={24} height={24} /> 405 - )} 406 - <span className="text-xs mt-1">Notifications</span> 407 - </Link> 408 - <button 409 - className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 410 - isProfile 411 - ? "text-gray-900 dark:text-gray-100" 412 - : "text-gray-600 dark:text-gray-400" 413 - }`} 414 - onClick={() => { 415 - if (authed && agent && agent.assertDid) { 416 - //window.location.href = `/profile/${agent.assertDid}`; 512 + {agent?.did ? ( 513 + <nav className="sm:hidden fixed bottom-0 left-0 right-0 bg-gray-50 dark:bg-gray-900 border-0 shadow border-gray-200 dark:border-gray-700 z-40"> 514 + <div className="flex justify-around items-center p-2"> 515 + <MaterialNavItem 516 + small 517 + InactiveIcon={ 518 + <IconMaterialSymbolsHomeOutline className="w-6 h-6" /> 519 + } 520 + ActiveIcon={<IconMaterialSymbolsHome className="w-6 h-6" />} 521 + active={locationEnum === "home"} 522 + onClickCallbback={() => 523 + navigate({ 524 + to: "/", 525 + //params: { did: agent.assertDid }, 526 + }) 527 + } 528 + text="Home" 529 + /> 530 + {/* <Link 531 + to="/" 532 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 533 + isHome 534 + ? "text-gray-900 dark:text-gray-100" 535 + : "text-gray-600 dark:text-gray-400" 536 + }`} 537 + > 538 + {!isHome ? ( 539 + <IconMaterialSymbolsHomeOutline width={24} height={24} /> 540 + ) : ( 541 + <IconMaterialSymbolsHome width={24} height={24} /> 542 + )} 543 + <span className="text-xs mt-1">Home</span> 544 + </Link> */} 545 + <MaterialNavItem 546 + small 547 + InactiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />} 548 + ActiveIcon={<IconMaterialSymbolsSearch className="w-6 h-6" />} 549 + active={locationEnum === "search"} 550 + onClickCallbback={() => 551 + navigate({ 552 + to: "/search", 553 + //params: { did: agent.assertDid }, 554 + }) 555 + } 556 + text="Explore" 557 + /> 558 + {/* <Link 559 + to="/search" 560 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 561 + location.pathname.startsWith("/search") 562 + ? "text-gray-900 dark:text-gray-100" 563 + : "text-gray-600 dark:text-gray-400" 564 + }`} 565 + > 566 + {!location.pathname.startsWith("/search") ? ( 567 + <IconMaterialSymbolsSearch width={24} height={24} /> 568 + ) : ( 569 + <IconMaterialSymbolsSearch width={24} height={24} /> 570 + )} 571 + <span className="text-xs mt-1">Search</span> 572 + </Link> */} 573 + <MaterialNavItem 574 + small 575 + InactiveIcon={ 576 + <IconMaterialSymbolsNotificationsOutline className="w-6 h-6" /> 577 + } 578 + ActiveIcon={ 579 + <IconMaterialSymbolsNotifications className="w-6 h-6" /> 580 + } 581 + active={locationEnum === "notifications"} 582 + onClickCallbback={() => 583 + navigate({ 584 + to: "/notifications", 585 + //params: { did: agent.assertDid }, 586 + }) 587 + } 588 + text="Notifications" 589 + /> 590 + {/* <Link 591 + to="/notifications" 592 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 593 + isNotifications 594 + ? "text-gray-900 dark:text-gray-100" 595 + : "text-gray-600 dark:text-gray-400" 596 + }`} 597 + > 598 + {!isNotifications ? ( 599 + <IconMaterialSymbolsNotificationsOutline 600 + width={24} 601 + height={24} 602 + /> 603 + ) : ( 604 + <IconMaterialSymbolsNotifications width={24} height={24} /> 605 + )} 606 + <span className="text-xs mt-1">Notifications</span> 607 + </Link> */} 608 + <MaterialNavItem 609 + small 610 + InactiveIcon={ 611 + <IconMaterialSymbolsAccountCircleOutline className="w-6 h-6" /> 612 + } 613 + ActiveIcon={ 614 + <IconMaterialSymbolsAccountCircle className="w-6 h-6" /> 615 + } 616 + active={locationEnum === "profile"} 617 + onClickCallbback={() => { 618 + if (authed && agent && agent.assertDid) { 619 + //window.location.href = `/profile/${agent.assertDid}`; 620 + navigate({ 621 + to: "/profile/$did", 622 + params: { did: agent.assertDid }, 623 + }); 624 + } 625 + }} 626 + text="Profile" 627 + /> 628 + {/* <button 629 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 630 + isProfile 631 + ? "text-gray-900 dark:text-gray-100" 632 + : "text-gray-600 dark:text-gray-400" 633 + }`} 634 + onClick={() => { 635 + if (authed && agent && agent.assertDid) { 636 + //window.location.href = `/profile/${agent.assertDid}`; 637 + navigate({ 638 + to: "/profile/$did", 639 + params: { did: agent.assertDid }, 640 + }); 641 + } 642 + }} 643 + type="button" 644 + > 645 + <IconMaterialSymbolsAccountCircleOutline width={24} height={24} /> 646 + <span className="text-xs mt-1">Profile</span> 647 + </button> */} 648 + <MaterialNavItem 649 + small 650 + InactiveIcon={ 651 + <IconMaterialSymbolsSettingsOutline className="w-6 h-6" /> 652 + } 653 + ActiveIcon={<IconMaterialSymbolsSettings className="w-6 h-6" />} 654 + active={locationEnum === "settings"} 655 + onClickCallbback={() => 417 656 navigate({ 418 - to: "/profile/$did", 419 - params: { did: agent.assertDid }, 657 + to: "/settings", 658 + //params: { did: agent.assertDid }, 420 659 }) 421 660 } 422 - }} 423 - type="button" 424 - > 425 - <TablerUserCircle width={24} height={24} /> 426 - <span className="text-xs mt-1">Profile</span> 427 - </button> 428 - <Link 429 - to="/settings" 430 - className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 431 - location.pathname.startsWith("/settings") 432 - ? "text-gray-900 dark:text-gray-100" 433 - : "text-gray-600 dark:text-gray-400" 434 - }`} 435 - > 436 - {location.pathname.startsWith("/settings") ? ( 437 - <IonSettingsSharp width={24} height={24} /> 438 - ) : ( 439 - <IonSettings width={24} height={24} /> 440 - )} 441 - <span className="text-xs mt-1">Settings</span> 442 - </Link> 661 + text="Settings" 662 + /> 663 + {/* <Link 664 + to="/settings" 665 + className={`flex flex-col items-center py-2 px-3 rounded-lg transition-colors flex-1 ${ 666 + location.pathname.startsWith("/settings") 667 + ? "text-gray-900 dark:text-gray-100" 668 + : "text-gray-600 dark:text-gray-400" 669 + }`} 670 + > 671 + {!location.pathname.startsWith("/settings") ? ( 672 + <IconMaterialSymbolsSettingsOutline width={24} height={24} /> 673 + ) : ( 674 + <IconMaterialSymbolsSettings width={24} height={24} /> 675 + )} 676 + <span className="text-xs mt-1">Settings</span> 677 + </Link> */} 678 + </div> 679 + </nav> 680 + ) : ( 681 + <div className="lg:hidden flex items-center fixed bottom-0 left-0 right-0 justify-between px-4 py-3 border-0 shadow border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 z-10"> 682 + <div className="flex items-center gap-2"> 683 + <FluentEmojiHighContrastGlowingStar className="h-6 w-6" style={{color: "oklch(0.6616 0.2249 calc(25.88 + (var(--safe-hue) - 28))"}} /> 684 + <span className="font-bold text-lg text-gray-900 dark:text-gray-100"> 685 + Red Dwarf{" "} 686 + {/* <span className="text-gray-500 dark:text-gray-400 text-sm"> 687 + lite 688 + </span> */} 689 + </span> 690 + </div> 691 + <div className="flex items-center gap-2"> 692 + <Login compact={true} popup={true} /> 693 + </div> 443 694 </div> 444 - </nav> 695 + )} 445 696 446 - <TanStackRouterDevtools position="bottom-right" /> 697 + <TanStackRouterDevtools position="bottom-left" /> 447 698 <Scripts /> 448 699 </> 449 700 ); 450 701 } 451 - export function TablerHashtag(props: SVGProps<SVGSVGElement>) { 452 - return ( 453 - <svg 454 - xmlns="http://www.w3.org/2000/svg" 455 - width={24} 456 - height={24} 457 - viewBox="0 0 24 24" 458 - {...props} 459 - > 460 - <path 461 - fill="none" 462 - stroke="currentColor" 463 - strokeLinecap="round" 464 - strokeLinejoin="round" 465 - strokeWidth={2} 466 - d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16" 467 - ></path> 468 - </svg> 469 - ); 470 - } 471 702 472 - export function TablerHashtagFilled(props: SVGProps<SVGSVGElement>) { 473 - return ( 474 - <svg 475 - xmlns="http://www.w3.org/2000/svg" 476 - width={24} 477 - height={24} 478 - viewBox="0 0 24 24" 479 - {...props} 480 - > 481 - <path 482 - fill="none" 483 - stroke="currentColor" 484 - strokeLinecap="round" 485 - strokeLinejoin="round" 486 - strokeWidth={3} 487 - d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16" 488 - ></path> 489 - </svg> 490 - ); 491 - } 492 - export function TablerEdit(props: SVGProps<SVGSVGElement>) { 493 - return ( 494 - <svg 495 - xmlns="http://www.w3.org/2000/svg" 496 - width={24} 497 - height={24} 498 - viewBox="0 0 24 24" 499 - className="text-white" 500 - {...props} 501 - > 502 - <g 503 - fill="none" 504 - stroke="currentColor" 505 - strokeLinecap="round" 506 - strokeLinejoin="round" 507 - strokeWidth={2} 703 + function MaterialNavItem({ 704 + InactiveIcon, 705 + ActiveIcon, 706 + text, 707 + active, 708 + onClickCallbback, 709 + small, 710 + }: { 711 + InactiveIcon: React.ReactElement; 712 + ActiveIcon: React.ReactElement; 713 + text: string; 714 + active: boolean; 715 + onClickCallbback: () => void; 716 + small?: boolean | string; 717 + }) { 718 + if (small) 719 + return ( 720 + <button 721 + className={`flex flex-col items-center rounded-lg transition-colors ${small} gap-1 ${ 722 + active 723 + ? "text-gray-900 dark:text-gray-100" 724 + : "text-gray-600 dark:text-gray-400" 725 + }`} 726 + onClick={() => { 727 + onClickCallbback(); 728 + }} 508 729 > 509 - <path d="M16.475 5.408a2.36 2.36 0 1 1 3.34 3.34L7.5 21H3v-4.5z"></path> 510 - </g> 511 - </svg> 512 - ); 513 - } 514 - export function TablerHome(props: SVGProps<SVGSVGElement>) { 515 - return ( 516 - <svg 517 - xmlns="http://www.w3.org/2000/svg" 518 - width={24} 519 - height={24} 520 - viewBox="0 0 24 24" 521 - className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 522 - {...props} 523 - > 524 - <g 525 - stroke="currentColor" 526 - strokeLinecap="round" 527 - strokeLinejoin="round" 528 - strokeWidth={2} 529 - fill="none" 530 - > 531 - <path d="M5 12H3l9-9l9 9h-2M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7"></path> 532 - <path d="M9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6"></path> 533 - </g> 534 - </svg> 535 - ); 536 - } 537 - export function TablerHomeFilled(props: SVGProps<SVGSVGElement>) { 538 - return ( 539 - <svg 540 - xmlns="http://www.w3.org/2000/svg" 541 - width={24} 542 - height={24} 543 - viewBox="0 0 24 24" 544 - className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 545 - {...props} 546 - > 547 - <path 548 - fill="currentColor" 549 - d="m12.707 2.293l9 9c.63.63.184 1.707-.707 1.707h-1v6a3 3 0 0 1-3 3h-1v-7a3 3 0 0 0-2.824-2.995L13 12h-2a3 3 0 0 0-3 3v7H7a3 3 0 0 1-3-3v-6H3c-.89 0-1.337-1.077-.707-1.707l9-9a1 1 0 0 1 1.414 0M13 14a1 1 0 0 1 1 1v7h-4v-7a1 1 0 0 1 .883-.993L11 14z" 550 - ></path> 551 - </svg> 552 - ); 553 - } 554 - 555 - export function TablerBell(props: SVGProps<SVGSVGElement>) { 556 - return ( 557 - <svg 558 - xmlns="http://www.w3.org/2000/svg" 559 - width={24} 560 - height={24} 561 - viewBox="0 0 24 24" 562 - {...props} 563 - > 564 - <path 565 - className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 566 - stroke="currentColor" 567 - strokeLinecap="round" 568 - strokeLinejoin="round" 569 - strokeWidth={2} 570 - d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3H4a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6M9 17v1a3 3 0 0 0 6 0v-1" 571 - ></path> 572 - </svg> 573 - ); 574 - } 575 - export function TablerBellFilled(props: SVGProps<SVGSVGElement>) { 576 - return ( 577 - <svg 578 - xmlns="http://www.w3.org/2000/svg" 579 - width={24} 580 - height={24} 581 - viewBox="0 0 24 24" 582 - className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 583 - {...props} 584 - > 585 - <path 586 - fill="currentColor" 587 - stroke="currentColor" 588 - d="M14.235 19c.865 0 1.322 1.024.745 1.668A4 4 0 0 1 12 22a4 4 0 0 1-2.98-1.332c-.552-.616-.158-1.579.634-1.661l.11-.006zM12 2c1.358 0 2.506.903 2.875 2.141l.046.171l.008.043a8.01 8.01 0 0 1 4.024 6.069l.028.287L19 11v2.931l.021.136a3 3 0 0 0 1.143 1.847l.167.117l.162.099c.86.487.56 1.766-.377 1.864L20 18H4c-1.028 0-1.387-1.364-.493-1.87a3 3 0 0 0 1.472-2.063L5 13.924l.001-2.97A8 8 0 0 1 8.822 4.5l.248-.146l.01-.043a3 3 0 0 1 2.562-2.29l.182-.017z" 589 - ></path> 590 - </svg> 591 - ); 592 - } 730 + <div 731 + className={`px-4 py-1 rounded-full flex items-center justify-center ${active ? " bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 hover:dark:bg-gray-700" : "hover:bg-gray-50 hover:dark:bg-gray-900"}`} 732 + > 733 + {active ? ActiveIcon : InactiveIcon} 734 + </div> 735 + <span 736 + className={`text-[12.8px] text-roboto ${active ? "font-medium" : ""}`} 737 + > 738 + {text} 739 + </span> 740 + </button> 741 + ); 593 742 594 - export function TablerUserCircle(props: SVGProps<SVGSVGElement>) { 595 743 return ( 596 - <svg 597 - xmlns="http://www.w3.org/2000/svg" 598 - width={24} 599 - height={24} 600 - viewBox="0 0 24 24" 601 - className="text-gray-900 dark:text-gray-100 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" 602 - {...props} 603 - > 604 - <g 605 - fill="none" 606 - stroke="currentColor" 607 - strokeLinecap="round" 608 - strokeLinejoin="round" 609 - strokeWidth={2} 610 - > 611 - <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0"></path> 612 - <path d="M9 10a3 3 0 1 0 6 0a3 3 0 1 0-6 0m-2.832 8.849A4 4 0 0 1 10 16h4a4 4 0 0 1 3.834 2.855"></path> 613 - </g> 614 - </svg> 615 - ); 616 - } 617 - 618 - export function TablerSearch(props: SVGProps<SVGSVGElement>) { 619 - return ( 620 - <svg 621 - xmlns="http://www.w3.org/2000/svg" 622 - width={24} 623 - height={24} 624 - viewBox="0 0 24 24" 625 - //className="text-gray-400 dark:text-gray-500" 626 - {...props} 744 + <button 745 + className={`flex flex-row h-12 min-h-12 max-h-12 px-4 py-0.5 w-full items-center rounded-full transition-colors flex-1 gap-1 ${ 746 + active 747 + ? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-800 bg-gray-200 hover:dark:bg-gray-700" 748 + : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-900" 749 + }`} 750 + onClick={() => { 751 + onClickCallbback(); 752 + }} 627 753 > 628 - <g 629 - fill="none" 630 - stroke="currentColor" 631 - strokeLinecap="round" 632 - strokeLinejoin="round" 633 - strokeWidth={2} 754 + <div className={`mr-4 ${active ? " " : " "}`}> 755 + {active ? ActiveIcon : InactiveIcon} 756 + </div> 757 + <span 758 + className={`text-[17px] text-roboto ${active ? "font-medium" : ""}`} 634 759 > 635 - <path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0"></path> 636 - <path d="m21 21l-6-6"></path> 637 - </g> 638 - </svg> 639 - ); 640 - } 641 - export function TablerSearchFilled(props: SVGProps<SVGSVGElement>) { 642 - return ( 643 - <svg 644 - xmlns="http://www.w3.org/2000/svg" 645 - width={24} 646 - height={24} 647 - viewBox="0 0 24 24" 648 - //className="text-gray-400 dark:text-gray-500" 649 - {...props} 650 - > 651 - <g 652 - fill="none" 653 - stroke="currentColor" 654 - strokeLinecap="round" 655 - strokeLinejoin="round" 656 - strokeWidth={3} 657 - > 658 - <path d="M3 10a7 7 0 1 0 14 0a7 7 0 1 0-14 0"></path> 659 - <path d="m21 21l-6-6"></path> 660 - </g> 661 - </svg> 760 + {text} 761 + </span> 762 + </button> 662 763 ); 663 764 } 664 765 665 - export function IonSettings(props: SVGProps<SVGSVGElement>) { 666 - return ( 667 - <svg 668 - xmlns="http://www.w3.org/2000/svg" 669 - width={24} 670 - height={24} 671 - viewBox="0 0 512 512" 672 - {...props} 673 - > 674 - <path 675 - fill="none" 676 - stroke="currentColor" 677 - strokeLinecap="round" 678 - strokeLinejoin="round" 679 - strokeWidth={32} 680 - d="M262.29 192.31a64 64 0 1 0 57.4 57.4a64.13 64.13 0 0 0-57.4-57.4M416.39 256a154 154 0 0 1-1.53 20.79l45.21 35.46a10.81 10.81 0 0 1 2.45 13.75l-42.77 74a10.81 10.81 0 0 1-13.14 4.59l-44.9-18.08a16.11 16.11 0 0 0-15.17 1.75A164.5 164.5 0 0 1 325 400.8a15.94 15.94 0 0 0-8.82 12.14l-6.73 47.89a11.08 11.08 0 0 1-10.68 9.17h-85.54a11.11 11.11 0 0 1-10.69-8.87l-6.72-47.82a16.07 16.07 0 0 0-9-12.22a155 155 0 0 1-21.46-12.57a16 16 0 0 0-15.11-1.71l-44.89 18.07a10.81 10.81 0 0 1-13.14-4.58l-42.77-74a10.8 10.8 0 0 1 2.45-13.75l38.21-30a16.05 16.05 0 0 0 6-14.08c-.36-4.17-.58-8.33-.58-12.5s.21-8.27.58-12.35a16 16 0 0 0-6.07-13.94l-38.19-30A10.81 10.81 0 0 1 49.48 186l42.77-74a10.81 10.81 0 0 1 13.14-4.59l44.9 18.08a16.11 16.11 0 0 0 15.17-1.75A164.5 164.5 0 0 1 187 111.2a15.94 15.94 0 0 0 8.82-12.14l6.73-47.89A11.08 11.08 0 0 1 213.23 42h85.54a11.11 11.11 0 0 1 10.69 8.87l6.72 47.82a16.07 16.07 0 0 0 9 12.22a155 155 0 0 1 21.46 12.57a16 16 0 0 0 15.11 1.71l44.89-18.07a10.81 10.81 0 0 1 13.14 4.58l42.77 74a10.8 10.8 0 0 1-2.45 13.75l-38.21 30a16.05 16.05 0 0 0-6.05 14.08c.33 4.14.55 8.3.55 12.47" 681 - ></path> 682 - </svg> 683 - ); 684 - } 685 - export function IonSettingsSharp(props: SVGProps<SVGSVGElement>) { 766 + function MaterialPillButton({ 767 + InactiveIcon, 768 + ActiveIcon, 769 + text, 770 + //active, 771 + onClickCallbback, 772 + small, 773 + }: { 774 + InactiveIcon: React.ReactElement; 775 + ActiveIcon: React.ReactElement; 776 + text: string; 777 + //active: boolean; 778 + onClickCallbback: () => void; 779 + small?: boolean | string; 780 + }) { 781 + const active = false; 686 782 return ( 687 - <svg 688 - xmlns="http://www.w3.org/2000/svg" 689 - width={24} 690 - height={24} 691 - viewBox="0 0 512 512" 692 - {...props} 783 + <button 784 + className={`flex border border-gray-400 dark:border-gray-400 flex-row h-12 min-h-12 max-h-12 ${small ? "p-3 w-12" : "px-4 py-0.5"} items-center rounded-full transition-colors gap-1 ${ 785 + active 786 + ? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600" 787 + : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800" 788 + }`} 789 + onClick={() => { 790 + onClickCallbback(); 791 + }} 693 792 > 694 - <path 695 - fill="currentColor" 696 - d="M256 176a80 80 0 1 0 80 80a80.24 80.24 0 0 0-80-80m172.72 80a165.5 165.5 0 0 1-1.64 22.34l48.69 38.12a11.59 11.59 0 0 1 2.63 14.78l-46.06 79.52a11.64 11.64 0 0 1-14.14 4.93l-57.25-23a176.6 176.6 0 0 1-38.82 22.67l-8.56 60.78a11.93 11.93 0 0 1-11.51 9.86h-92.12a12 12 0 0 1-11.51-9.53l-8.56-60.78A169.3 169.3 0 0 1 151.05 393L93.8 416a11.64 11.64 0 0 1-14.14-4.92L33.6 331.57a11.59 11.59 0 0 1 2.63-14.78l48.69-38.12A175 175 0 0 1 83.28 256a165.5 165.5 0 0 1 1.64-22.34l-48.69-38.12a11.59 11.59 0 0 1-2.63-14.78l46.06-79.52a11.64 11.64 0 0 1 14.14-4.93l57.25 23a176.6 176.6 0 0 1 38.82-22.67l8.56-60.78A11.93 11.93 0 0 1 209.94 26h92.12a12 12 0 0 1 11.51 9.53l8.56 60.78A169.3 169.3 0 0 1 361 119l57.2-23a11.64 11.64 0 0 1 14.14 4.92l46.06 79.52a11.59 11.59 0 0 1-2.63 14.78l-48.69 38.12a175 175 0 0 1 1.64 22.66" 697 - ></path> 698 - </svg> 793 + <div className={`${!small && "mr-2"} ${active ? " " : " "}`}> 794 + {active ? ActiveIcon : InactiveIcon} 795 + </div> 796 + {!small && ( 797 + <span 798 + className={`text-[17px] text-roboto ${active ? "font-medium" : ""}`} 799 + > 800 + {text} 801 + </span> 802 + )} 803 + </button> 699 804 ); 700 805 }
+120 -114
src/routes/index.tsx
··· 3 3 import * as React from "react"; 4 4 import { useEffect, useLayoutEffect } from "react"; 5 5 6 + import { Header } from "~/components/Header"; 6 7 import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed"; 7 8 import { useAuth } from "~/providers/UnifiedAuthProvider"; 8 9 import { 9 10 agentAtom, 10 11 authedAtom, 11 12 feedScrollPositionsAtom, 13 + isAtTopAtom, 12 14 selectedFeedUriAtom, 13 15 store, 14 16 } from "~/utils/atoms"; 15 17 //import { usePersistentStore } from "~/providers/PersistentStoreProvider"; 16 18 import { 17 - constructArbitraryQuery, 18 - constructIdentityQuery, 19 - constructInfiniteFeedSkeletonQuery, 20 - constructPostQuery, 19 + //constructArbitraryQuery, 20 + //constructIdentityQuery, 21 + //constructInfiniteFeedSkeletonQuery, 22 + //constructPostQuery, 21 23 useQueryArbitrary, 22 24 useQueryIdentity, 23 25 useQueryPreferences, 24 26 } from "~/utils/useQuery"; 25 27 26 28 export const Route = createFileRoute("/")({ 27 - loader: async ({ context }) => { 28 - const { queryClient } = context; 29 - const atomauth = store.get(authedAtom); 30 - const atomagent = store.get(agentAtom); 29 + // loader: async ({ context }) => { 30 + // const { queryClient } = context; 31 + // const atomauth = store.get(authedAtom); 32 + // const atomagent = store.get(agentAtom); 31 33 32 - let identitypds: string | undefined; 33 - const initialselectedfeed = store.get(selectedFeedUriAtom); 34 - if (atomagent && atomauth && atomagent?.did) { 35 - const identityopts = constructIdentityQuery(atomagent.did); 36 - const identityresultmaybe = 37 - await queryClient.ensureQueryData(identityopts); 38 - identitypds = identityresultmaybe?.pds; 39 - } 34 + // let identitypds: string | undefined; 35 + // const initialselectedfeed = store.get(selectedFeedUriAtom); 36 + // if (atomagent && atomauth && atomagent?.did) { 37 + // const identityopts = constructIdentityQuery(atomagent.did); 38 + // const identityresultmaybe = 39 + // await queryClient.ensureQueryData(identityopts); 40 + // identitypds = identityresultmaybe?.pds; 41 + // } 40 42 41 - const arbitraryopts = constructArbitraryQuery( 42 - initialselectedfeed ?? 43 - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" 44 - ); 45 - const feedGengetrecordquery = 46 - await queryClient.ensureQueryData(arbitraryopts); 47 - const feedServiceDid = (feedGengetrecordquery?.value as any)?.did; 48 - //queryClient.ensureInfiniteQueryData() 43 + // const arbitraryopts = constructArbitraryQuery( 44 + // initialselectedfeed ?? 45 + // "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot" 46 + // ); 47 + // const feedGengetrecordquery = 48 + // await queryClient.ensureQueryData(arbitraryopts); 49 + // const feedServiceDid = (feedGengetrecordquery?.value as any)?.did; 50 + // //queryClient.ensureInfiniteQueryData() 49 51 50 - const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery({ 51 - feedUri: 52 - initialselectedfeed ?? 53 - "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 54 - agent: atomagent ?? undefined, 55 - isAuthed: atomauth ?? false, 56 - pdsUrl: identitypds, 57 - feedServiceDid: feedServiceDid, 58 - }); 52 + // const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery({ 53 + // feedUri: 54 + // initialselectedfeed ?? 55 + // "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot", 56 + // agent: atomagent ?? undefined, 57 + // isAuthed: atomauth ?? false, 58 + // pdsUrl: identitypds, 59 + // feedServiceDid: feedServiceDid, 60 + // }); 59 61 60 - const res = await queryClient.ensureInfiniteQueryData({ 61 - queryKey, 62 - queryFn, 63 - initialPageParam: undefined as never, 64 - getNextPageParam: (lastPage: any) => lastPage.cursor as null | undefined, 65 - staleTime: Infinity, 66 - //refetchOnWindowFocus: false, 67 - //enabled: true, 68 - }); 69 - await Promise.all( 70 - res.pages.map(async (page) => { 71 - await Promise.all( 72 - page.feed.map(async (feedviewpost) => { 73 - if (!feedviewpost.post) return; 74 - // /*mass comment*/ console.log("preloading: ", feedviewpost.post); 75 - const opts = constructPostQuery(feedviewpost.post); 76 - try { 77 - await queryClient.ensureQueryData(opts); 78 - } catch (e) { 79 - // /*mass comment*/ console.log(" failed:", e); 80 - } 81 - }) 82 - ); 83 - }) 84 - ); 85 - }, 62 + // const res = await queryClient.ensureInfiniteQueryData({ 63 + // queryKey, 64 + // queryFn, 65 + // initialPageParam: undefined as never, 66 + // getNextPageParam: (lastPage: any) => lastPage.cursor as null | undefined, 67 + // staleTime: Infinity, 68 + // //refetchOnWindowFocus: false, 69 + // //enabled: true, 70 + // }); 71 + // await Promise.all( 72 + // res.pages.map(async (page) => { 73 + // await Promise.all( 74 + // page.feed.map(async (feedviewpost) => { 75 + // if (!feedviewpost.post) return; 76 + // // /*mass comment*/ console.log("preloading: ", feedviewpost.post); 77 + // const opts = constructPostQuery(feedviewpost.post); 78 + // try { 79 + // await queryClient.ensureQueryData(opts); 80 + // } catch (e) { 81 + // // /*mass comment*/ console.log(" failed:", e); 82 + // } 83 + // }) 84 + // ); 85 + // }) 86 + // ); 87 + // }, 86 88 component: Home, 87 - pendingComponent: PendingHome, 89 + pendingComponent: PendingHome, // PendingHome, 90 + staticData: { keepAlive: true }, 88 91 }); 89 92 function PendingHome() { 90 93 return <div>loading... (prefetching your timeline)</div>; 91 94 } 92 - function Home() { 95 + 96 + //function Homer() { 97 + // return <div></div> 98 + //} 99 + export function Home({ hidden = false }: { hidden?: boolean }) { 93 100 const { 94 101 agent, 95 102 status, ··· 292 299 feedScrollPositionsAtom 293 300 ); 294 301 295 - const scrollRef = React.useRef<Record<string, number>>({}); 302 + const scrollPositionsRef = React.useRef(scrollPositions); 296 303 297 - useEffect(() => { 298 - const onScroll = () => { 299 - //if (!selectedFeed) return; 300 - scrollRef.current[selectedFeed ?? "null"] = window.scrollY; 301 - }; 302 - window.addEventListener("scroll", onScroll, { passive: true }); 303 - return () => window.removeEventListener("scroll", onScroll); 304 - }, [selectedFeed]); 305 - const [donerestored, setdonerestored] = React.useState(false); 304 + React.useEffect(() => { 305 + scrollPositionsRef.current = scrollPositions; 306 + }, [scrollPositions]); 306 307 307 - useEffect(() => { 308 - return () => { 309 - if (!donerestored) return; 310 - // /*mass comment*/ console.log("FEEDSCROLLSHIT saving at uhhh: ", scrollRef.current); 311 - //if (!selectedFeed) return; 312 - setScrollPositions((prev) => ({ 313 - ...prev, 314 - [selectedFeed ?? "null"]: 315 - scrollRef.current[selectedFeed ?? "null"] ?? 0, 316 - })); 317 - }; 318 - }, [selectedFeed, setScrollPositions, donerestored]); 308 + useLayoutEffect(() => { 309 + const savedPosition = scrollPositions[selectedFeed ?? "null"] ?? 0; 319 310 320 - const [restoringScrollPosition, setRestoringScrollPosition] = 321 - React.useState(false); 311 + window.scrollTo({ top: savedPosition, behavior: "instant" }); 312 + // eslint-disable-next-line react-hooks/exhaustive-deps 313 + }, [selectedFeed]); 322 314 323 315 useLayoutEffect(() => { 324 - setRestoringScrollPosition(true); 325 - const savedPosition = scrollPositions[selectedFeed ?? "null"] ?? 0; 316 + if (!selectedFeed) return; 326 317 327 - const raf = requestAnimationFrame(() => { 328 - // setRestoringScrollPosition(true); 329 - // raf = requestAnimationFrame(() => { 330 - // window.scrollTo({ top: savedPosition, behavior: "instant" }); 331 - // setRestoringScrollPosition(false); 332 - // setdonerestored(true); 333 - // }); 334 - window.scrollTo({ top: savedPosition, behavior: "instant" }); 335 - setRestoringScrollPosition(false); 336 - setdonerestored(true); 337 - }); 318 + const handleScroll = () => { 319 + scrollPositionsRef.current = { 320 + ...scrollPositionsRef.current, 321 + [selectedFeed]: window.scrollY, 322 + }; 323 + }; 324 + 325 + window.addEventListener("scroll", handleScroll, { passive: true }); 326 + return () => { 327 + window.removeEventListener("scroll", handleScroll); 338 328 339 - return () => cancelAnimationFrame(raf); 340 - }, [selectedFeed, scrollPositions]); 329 + setScrollPositions(scrollPositionsRef.current); 330 + }; 331 + }, [selectedFeed, setScrollPositions]); 341 332 342 333 const feedGengetrecordquery = useQueryArbitrary(selectedFeed ?? undefined); 343 334 const feedServiceDid = (feedGengetrecordquery?.data?.value as any)?.did; ··· 360 351 authed && agent && identity?.pds && feedServiceDid; 361 352 const isReadyForUnauthedFeed = !authed && selectedFeed; 362 353 354 + 355 + const [isAtTop] = useAtom(isAtTopAtom); 356 + 363 357 return ( 364 - <div className="relative flex flex-col divide-y divide-gray-200 dark:divide-gray-800"> 365 - <div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden scroll-thin"> 366 - {savedFeeds.length > 0 ? ( 367 - savedFeeds.map((item: any, idx: number) => { 358 + <div 359 + className={`relative flex flex-col divide-y divide-gray-200 dark:divide-gray-800 ${hidden && "hidden"}`} 360 + > 361 + {savedFeeds.length > 0 ? ( 362 + <div className={`flex items-center px-4 py-2 h-[52px] sticky top-0 bg-[var(--header-bg-light)] dark:bg-[var(--header-bg-dark)] ${!isAtTop && "shadow-sm"} sm:shadow-none sm:bg-white sm:dark:bg-gray-950 z-10 border-0 sm:border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden scroll-thin`}> 363 + {savedFeeds.map((item: any, idx: number) => { 368 364 const label = item.value.split("/").pop() || item.value; 369 365 const isActive = selectedFeed === item.value; 370 366 return ( ··· 372 368 key={item.value || idx} 373 369 className={`px-3 py-1 rounded-full whitespace-nowrap font-medium transition-colors ${ 374 370 isActive 375 - ? "bg-gray-500 text-white" 376 - : item.pinned 377 - ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200" 378 - : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200" 371 + ? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600" 372 + : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800" 373 + // ? "bg-gray-500 text-white" 374 + // : item.pinned 375 + // ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200" 376 + // : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200" 379 377 }`} 380 378 onClick={() => setSelectedFeed(item.value)} 381 379 title={item.value} 382 380 > 383 381 {label} 384 382 {item.pinned && ( 385 - <span className="ml-1 text-xs text-gray-700 dark:text-gray-200"> 383 + <span 384 + className={`ml-1 text-xs ${ 385 + isActive 386 + ? "text-gray-900 dark:text-gray-100" 387 + : "text-gray-600 dark:text-gray-400" 388 + }`} 389 + > 386 390 โ˜… 387 391 </span> 388 392 )} 389 393 </button> 390 394 ); 391 - }) 392 - ) : ( 393 - <span className="text-xl font-bold ml-2">Home</span> 394 - )} 395 - </div> 395 + })} 396 + </div> 397 + ) : ( 398 + // <span className="text-xl font-bold ml-2">Home</span> 399 + <Header title="Home" /> 400 + )} 396 401 {/* {isFeedLoading && <div className="p-4 text-gray-500">Loading...</div>} 397 402 {feedError && <div className="p-4 text-red-500">{feedError.message}</div>} 398 403 {!isFeedLoading && !feedError && feed.length === 0 && ( ··· 413 418 414 419 {isReadyForAuthedFeed || isReadyForUnauthedFeed ? ( 415 420 <InfiniteCustomFeed 421 + key={selectedFeed!} 416 422 feedUri={selectedFeed!} 417 423 pdsUrl={identity?.pds} 418 424 feedServiceDid={feedServiceDid}
+7 -3
src/routes/notifications.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 + import { useAtom } from "jotai"; 2 3 import React, { useEffect, useRef,useState } from "react"; 3 4 4 5 import { useAuth } from "~/providers/UnifiedAuthProvider"; 6 + import { constellationURLAtom } from "~/utils/atoms"; 5 7 6 8 const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 7 9 ··· 70 72 } 71 73 } 72 74 75 + const [constellationURL] = useAtom(constellationURLAtom) 76 + 73 77 useEffect(() => { 74 78 if (!did) return; 75 79 setLoading(true); 76 80 setError(null); 77 81 const urls = [ 78 - `https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet%23mention].did`, 79 - `https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[].features[app.bsky.richtext.facet%23mention].did`, 80 - `https://constellation.microcosm.blue/links?target=${encodeURIComponent(did)}&collection=app.bsky.graph.follow&path=.subject`, 82 + `https://${constellationURL}/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[app.bsky.richtext.facet].features[app.bsky.richtext.facet%23mention].did`, 83 + `https://${constellationURL}/links?target=${encodeURIComponent(did)}&collection=app.bsky.feed.post&path=.facets[].features[app.bsky.richtext.facet%23mention].did`, 84 + `https://${constellationURL}/links?target=${encodeURIComponent(did)}&collection=app.bsky.graph.follow&path=.subject`, 81 85 ]; 82 86 let ignore = false; 83 87 Promise.all(
+207 -58
src/routes/profile.$did/index.tsx
··· 1 + import { RichText } from "@atproto/api"; 1 2 import { useQueryClient } from "@tanstack/react-query"; 2 - import { createFileRoute, Link } from "@tanstack/react-router"; 3 - import React from "react"; 3 + import { createFileRoute, useNavigate } from "@tanstack/react-router"; 4 + import { useAtom } from "jotai"; 5 + import React, { type ReactNode, useEffect, useState } from "react"; 4 6 5 - import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 7 + import { Header } from "~/components/Header"; 8 + import { 9 + renderTextWithFacets, 10 + UniversalPostRendererATURILoader, 11 + } from "~/components/UniversalPostRenderer"; 6 12 import { useAuth } from "~/providers/UnifiedAuthProvider"; 7 - import { toggleFollow, useGetFollowState } from "~/utils/followState"; 13 + import { aturiListServiceAtom, imgCDNAtom } from "~/utils/atoms"; 8 14 import { 9 - useInfiniteQueryAuthorFeed, 15 + toggleFollow, 16 + useGetFollowState, 17 + useGetOneToOneState, 18 + } from "~/utils/followState"; 19 + import { 20 + useInfiniteQueryAturiList, 10 21 useQueryIdentity, 11 22 useQueryProfile, 12 23 } from "~/utils/useQuery"; ··· 18 29 function ProfileComponent() { 19 30 // booo bad this is not always the did it might be a handle, use identity.did instead 20 31 const { did } = Route.useParams(); 32 + //const navigate = useNavigate(); 21 33 const queryClient = useQueryClient(); 22 - const { agent } = useAuth(); 23 34 const { 24 35 data: identity, 25 36 isLoading: isIdentityLoading, 26 37 error: identityError, 27 38 } = useQueryIdentity(did); 28 39 29 - const followRecords = useGetFollowState({ 30 - target: identity?.did || did, 31 - user: agent?.did, 32 - }); 33 - 34 40 const resolvedDid = did.startsWith("did:") ? did : identity?.did; 35 41 const resolvedHandle = did.startsWith("did:") ? identity?.handle : did; 36 - const pdsUrl = identity?.pds; 42 + //const pdsUrl = identity?.pds; 37 43 38 44 const profileUri = resolvedDid 39 45 ? `at://${resolvedDid}/app.bsky.actor.profile/self` ··· 41 47 const { data: profileRecord } = useQueryProfile(profileUri); 42 48 const profile = profileRecord?.value; 43 49 50 + const [aturilistservice] = useAtom(aturiListServiceAtom); 51 + 44 52 const { 45 53 data: postsData, 46 54 fetchNextPage, 47 55 hasNextPage, 48 56 isFetchingNextPage, 49 57 isLoading: arePostsLoading, 50 - } = useInfiniteQueryAuthorFeed(resolvedDid, pdsUrl); 58 + } = useInfiniteQueryAturiList({ 59 + aturilistservice: aturilistservice, 60 + did: resolvedDid, 61 + collection: "app.bsky.feed.post", 62 + reverse: true 63 + }); 51 64 52 65 React.useEffect(() => { 53 66 if (postsData) { 54 67 postsData.pages.forEach((page) => { 55 - page.records.forEach((record) => { 68 + page.forEach((record) => { 56 69 if (!queryClient.getQueryData(["post", record.uri])) { 57 70 queryClient.setQueryData(["post", record.uri], record); 58 71 } ··· 62 75 }, [postsData, queryClient]); 63 76 64 77 const posts = React.useMemo( 65 - () => postsData?.pages.flatMap((page) => page.records) ?? [], 78 + () => postsData?.pages.flatMap((page) => page) ?? [], 66 79 [postsData] 67 80 ); 68 81 82 + const [imgcdn] = useAtom(imgCDNAtom); 83 + 69 84 function getAvatarUrl(p: typeof profile) { 70 85 const link = p?.avatar?.ref?.["$link"]; 71 86 if (!link || !resolvedDid) return null; 72 - return `https://cdn.bsky.app/img/avatar/plain/${resolvedDid}/${link}@jpeg`; 87 + return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`; 73 88 } 74 89 function getBannerUrl(p: typeof profile) { 75 90 const link = p?.banner?.ref?.["$link"]; 76 91 if (!link || !resolvedDid) return null; 77 - return `https://cdn.bsky.app/img/banner/plain/${resolvedDid}/${link}@jpeg`; 92 + return `https://${imgcdn}/img/banner/plain/${resolvedDid}/${link}@jpeg`; 78 93 } 79 94 80 95 const displayName = ··· 104 119 105 120 return ( 106 121 <> 107 - <div className="flex gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700"> 122 + <Header 123 + title={`Profile`} 124 + backButtonCallback={() => { 125 + if (window.history.length > 1) { 126 + window.history.back(); 127 + } else { 128 + window.location.assign("/"); 129 + } 130 + }} 131 + /> 132 + {/* <div className="flex gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700"> 108 133 <Link 109 134 to=".." 110 135 className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" ··· 121 146 โ† 122 147 </Link> 123 148 <span className="text-xl font-bold ml-2">Profile</span> 124 - </div> 149 + </div> */} 125 150 126 151 {/* Profile Header */} 127 152 <div className="w-full max-w-2xl mx-auto overflow-hidden relative bg-gray-100 dark:bg-gray-900"> ··· 151 176 also delay the backfill to be on demand because it would be pretty intense 152 177 also save it persistently 153 178 */} 154 - {identity?.did !== agent?.did ? ( 155 - <> 156 - {!(followRecords?.length && followRecords?.length > 0) ? ( 157 - <button 158 - onClick={() => 159 - toggleFollow({ 160 - agent: agent || undefined, 161 - targetDid: identity?.did, 162 - followRecords: followRecords, 163 - queryClient: queryClient, 164 - }) 165 - } 166 - className="rounded-full dark:bg-gray-600 bg-gray-300 px-3 py-2 text-[14px]" 167 - > 168 - Follow 169 - </button> 170 - ) : ( 171 - <button 172 - onClick={() => 173 - toggleFollow({ 174 - agent: agent || undefined, 175 - targetDid: identity?.did, 176 - followRecords: followRecords, 177 - queryClient: queryClient, 178 - }) 179 - } 180 - className="rounded-full dark:bg-gray-600 bg-gray-300 px-3 py-2 text-[14px]" 181 - > 182 - Unfollow 183 - </button> 184 - )} 185 - </> 186 - ) : ( 187 - <button className="rounded-full dark:bg-gray-600 bg-gray-300 px-3 py-2 text-[14px]"> 188 - Edit Profile 189 - </button> 190 - )} 179 + <FollowButton targetdidorhandle={did} /> 191 180 <button className="rounded-full dark:bg-gray-600 bg-gray-300 px-3 py-2 text-[14px]"> 192 181 ... {/* todo: icon */} 193 182 </button> ··· 196 185 {/* Info Card */} 197 186 <div className="mt-16 pb-2 px-4 text-gray-900 dark:text-gray-100"> 198 187 <div className="font-bold text-2xl">{displayName}</div> 199 - <div className="text-gray-500 dark:text-gray-400 text-base mb-3"> 188 + <div className="text-gray-500 dark:text-gray-400 text-base mb-3 flex flex-row gap-1"> 189 + <Mutual targetdidorhandle={did} /> 200 190 {handle} 201 191 </div> 202 192 {description && ( 203 193 <div className="text-base leading-relaxed text-gray-800 dark:text-gray-300 mb-5 whitespace-pre-wrap break-words text-[15px]"> 204 - {description} 194 + {/* {description} */} 195 + <RichTextRenderer key={did} description={description} /> 205 196 </div> 206 197 )} 207 198 </div> ··· 244 235 </> 245 236 ); 246 237 } 238 + 239 + export function FollowButton({ 240 + targetdidorhandle, 241 + }: { 242 + targetdidorhandle: string; 243 + }) { 244 + const { agent } = useAuth(); 245 + const { data: identity } = useQueryIdentity(targetdidorhandle); 246 + const queryClient = useQueryClient(); 247 + 248 + const followRecords = useGetFollowState({ 249 + target: identity?.did ?? targetdidorhandle, 250 + user: agent?.did, 251 + }); 252 + 253 + return ( 254 + <> 255 + {identity?.did !== agent?.did ? ( 256 + <> 257 + {!(followRecords?.length && followRecords?.length > 0) ? ( 258 + <button 259 + onClick={(e) => { 260 + e.stopPropagation(); 261 + toggleFollow({ 262 + agent: agent || undefined, 263 + targetDid: identity?.did, 264 + followRecords: followRecords, 265 + queryClient: queryClient, 266 + }); 267 + }} 268 + className="rounded-full h-10 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors px-4 py-2 text-[14px]" 269 + > 270 + Follow 271 + </button> 272 + ) : ( 273 + <button 274 + onClick={(e) => { 275 + e.stopPropagation(); 276 + toggleFollow({ 277 + agent: agent || undefined, 278 + targetDid: identity?.did, 279 + followRecords: followRecords, 280 + queryClient: queryClient, 281 + }); 282 + }} 283 + className="rounded-full h-10 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors px-4 py-2 text-[14px]" 284 + > 285 + Unfollow 286 + </button> 287 + )} 288 + </> 289 + ) : ( 290 + <button className="rounded-full h-10 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors px-4 py-2 text-[14px]"> 291 + Edit Profile 292 + </button> 293 + )} 294 + </> 295 + ); 296 + } 297 + 298 + export function Mutual({ targetdidorhandle }: { targetdidorhandle: string }) { 299 + const { agent } = useAuth(); 300 + const { data: identity } = useQueryIdentity(targetdidorhandle); 301 + 302 + const theyFollowYouRes = useGetOneToOneState( 303 + agent?.did 304 + ? { 305 + target: agent?.did, 306 + user: identity?.did ?? targetdidorhandle, 307 + collection: "app.bsky.graph.follow", 308 + path: ".subject", 309 + } 310 + : undefined 311 + ); 312 + 313 + const youFollowThemRes = useGetFollowState({ 314 + target: identity?.did ?? targetdidorhandle, 315 + user: agent?.did, 316 + }); 317 + 318 + const theyFollowYou: boolean = 319 + !!theyFollowYouRes?.length && theyFollowYouRes.length > 0; 320 + const youFollowThem: boolean = 321 + !!youFollowThemRes?.length && youFollowThemRes.length > 0; 322 + 323 + return ( 324 + <> 325 + {/* if not self */} 326 + {identity?.did !== agent?.did ? ( 327 + <> 328 + {theyFollowYou ? ( 329 + <> 330 + {youFollowThem ? ( 331 + <div className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center"> 332 + mutuals 333 + </div> 334 + ) : ( 335 + <div className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center"> 336 + follows you 337 + </div> 338 + )} 339 + </> 340 + ) : ( 341 + <></> 342 + )} 343 + </> 344 + ) : ( 345 + // lmao can someone be mutuals with themselves ?? 346 + <></> 347 + )} 348 + </> 349 + ); 350 + } 351 + 352 + export function RichTextRenderer({ description }: { description: string }) { 353 + const [richDescription, setRichDescription] = useState<string | ReactNode[]>( 354 + description 355 + ); 356 + const { agent } = useAuth(); 357 + const navigate = useNavigate(); 358 + 359 + useEffect(() => { 360 + let mounted = true; 361 + 362 + // setRichDescription(description); 363 + 364 + async function processRichText() { 365 + try { 366 + if (!agent?.did) return; 367 + const rt = new RichText({ text: description }); 368 + await rt.detectFacets(agent); 369 + 370 + if (!mounted) return; 371 + 372 + if (rt.facets) { 373 + setRichDescription( 374 + renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate }) 375 + ); 376 + } else { 377 + setRichDescription(rt.text); 378 + } 379 + } catch (error) { 380 + console.error("Failed to detect facets:", error); 381 + if (mounted) { 382 + setRichDescription(description); 383 + } 384 + } 385 + } 386 + 387 + processRichText(); 388 + 389 + return () => { 390 + mounted = false; 391 + }; 392 + }, [description, agent, navigate]); 393 + 394 + return <>{richDescription}</>; 395 + }
+165
src/routes/profile.$did/post.$rkey.image.$i.tsx
··· 1 + import { 2 + createFileRoute, 3 + useNavigate, 4 + type UseNavigateResult, 5 + } from "@tanstack/react-router"; 6 + import { useEffect, useState } from "react"; 7 + import { createPortal } from "react-dom"; 8 + 9 + import { ProfilePostComponent } from "./post.$rkey"; 10 + 11 + export const Route = createFileRoute("/profile/$did/post/$rkey/image/$i")({ 12 + component: Lightbox, 13 + }); 14 + 15 + export type LightboxProps = { 16 + images: { src: string; alt?: string }[]; 17 + }; 18 + 19 + function nextprev({ 20 + index, 21 + images, 22 + navigate, 23 + did, 24 + rkey, 25 + prev, 26 + }: { 27 + index?: number; 28 + images?: LightboxProps["images"]; 29 + navigate: UseNavigateResult<string>; 30 + did: string; 31 + rkey: string; 32 + prev?: boolean; 33 + }) { 34 + const len = images?.length ?? 0; 35 + if (len === 0) return; 36 + 37 + const nextIndex = ((index ?? 0) + (prev ? -1 : 1) + len) % len; 38 + 39 + navigate({ 40 + to: "/profile/$did/post/$rkey/image/$i", 41 + params: { 42 + did, 43 + rkey, 44 + i: nextIndex.toString(), 45 + }, 46 + replace: true, 47 + }); 48 + } 49 + 50 + export function Lightbox() { 51 + console.log("hey the $i route is loaded w!!!"); 52 + const { did, rkey, i } = Route.useParams(); 53 + const [images, setImages] = useState<LightboxProps["images"] | undefined>( 54 + undefined 55 + ); 56 + const index = Number(i); 57 + const navigate = useNavigate(); 58 + const post = true; 59 + const image = images?.[index] ?? undefined; 60 + 61 + function lightboxCallback(d: LightboxProps) { 62 + console.log("callback actually called!"); 63 + setImages(d.images); 64 + } 65 + 66 + useEffect(() => { 67 + function handleKey(e: KeyboardEvent) { 68 + if (e.key === "Escape") window.history.back(); 69 + if (e.key === "ArrowRight") 70 + nextprev({ index, images, navigate, did, rkey }); 71 + //onNavigate((index + 1) % images.length); 72 + if (e.key === "ArrowLeft") 73 + nextprev({ index, images, navigate, did, rkey, prev: true }); 74 + //onNavigate((index - 1 + images.length) % images.length); 75 + } 76 + window.addEventListener("keydown", handleKey); 77 + return () => window.removeEventListener("keydown", handleKey); 78 + }, [index, navigate, did, rkey, images]); 79 + 80 + return createPortal( 81 + <> 82 + {post && ( 83 + <div 84 + onClick={(e) => { 85 + e.stopPropagation(); 86 + e.nativeEvent.stopImmediatePropagation(); 87 + }} 88 + className="lightbox-sidebar hidden lg:flex overscroll-none disablegutter border-l dark:border-gray-800 was7 border-gray-300 fixed z-50 top-0 right-0 flex-col max-w-[350px] min-w-[350px] max-h-screen overflow-y-scroll dark:bg-gray-950 bg-white" 89 + > 90 + <ProfilePostComponent 91 + key={`/profile/${did}/post/${rkey}`} 92 + did={did} 93 + rkey={rkey} 94 + nopics 95 + lightboxCallback={lightboxCallback} 96 + /> 97 + </div> 98 + )} 99 + <div 100 + className="lightbox fixed inset-0 z-50 flex items-center justify-center bg-black/80 w-screen lg:w-[calc(100vw-350px)] lg:max-w-[calc(100vw-350px)]" 101 + onClick={(e) => { 102 + e.stopPropagation(); 103 + window.history.back(); 104 + }} 105 + > 106 + <img 107 + src={image?.src} 108 + alt={image?.alt} 109 + className="max-h-[90%] max-w-[90%] object-contain rounded-lg shadow-lg" 110 + onClick={(e) => e.stopPropagation()} 111 + /> 112 + 113 + {(images?.length ?? 0) > 1 && ( 114 + <> 115 + <button 116 + onClick={(e) => { 117 + e.stopPropagation(); 118 + nextprev({ index, images, navigate, did, rkey, prev: true }); 119 + }} 120 + className="absolute left-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center" 121 + > 122 + <svg 123 + xmlns="http://www.w3.org/2000/svg" 124 + width={28} 125 + height={28} 126 + viewBox="0 0 24 24" 127 + > 128 + <g fill="none" fillRule="evenodd"> 129 + <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path> 130 + <path 131 + fill="currentColor" 132 + d="M8.293 12.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 1 1 1.414 1.414L10.414 12l4.95 4.95a1 1 0 0 1-1.414 1.414z" 133 + ></path> 134 + </g> 135 + </svg> 136 + </button> 137 + <button 138 + onClick={(e) => { 139 + e.stopPropagation(); 140 + nextprev({ index, images, navigate, did, rkey }); 141 + }} 142 + className="absolute right-4 top-1/2 -translate-y-1/2 text-white text-4xl h-8 w-8 rounded-full bg-gray-900 flex items-center justify-center" 143 + > 144 + <svg 145 + xmlns="http://www.w3.org/2000/svg" 146 + width={28} 147 + height={28} 148 + viewBox="0 0 24 24" 149 + > 150 + <g fill="none" fillRule="evenodd"> 151 + <path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"></path> 152 + <path 153 + fill="currentColor" 154 + d="M15.707 11.293a1 1 0 0 1 0 1.414l-5.657 5.657a1 1 0 1 1-1.414-1.414l4.95-4.95l-4.95-4.95a1 1 0 0 1 1.414-1.414z" 155 + ></path> 156 + </g> 157 + </svg> 158 + </button> 159 + </> 160 + )} 161 + </div> 162 + </>, 163 + document.body 164 + ); 165 + }
+238 -68
src/routes/profile.$did/post.$rkey.tsx
··· 1 - import { useQueryClient } from "@tanstack/react-query"; 2 - import { createFileRoute, Link } from "@tanstack/react-router"; 1 + import { AtUri } from "@atproto/api"; 2 + import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; 3 + import { createFileRoute, Outlet } from "@tanstack/react-router"; 4 + import { useAtom } from "jotai"; 3 5 import React, { useLayoutEffect } from "react"; 4 6 7 + import { Header } from "~/components/Header"; 5 8 import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; 9 + import { constellationURLAtom, slingshotURLAtom } from "~/utils/atoms"; 6 10 //import { usePersistentStore } from '~/providers/PersistentStoreProvider'; 7 11 import { 8 12 constructPostQuery, 13 + type linksAllResponse, 14 + type linksRecordsResponse, 9 15 useQueryConstellation, 10 16 useQueryIdentity, 11 17 useQueryPost, 18 + yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks, 12 19 } from "~/utils/useQuery"; 20 + 21 + import type { LightboxProps } from "./post.$rkey.image.$i"; 13 22 14 23 //const HANDLE_DID_CACHE_TIMEOUT = 60 * 60 * 1000; // 1 hour 15 24 ··· 32 41 ); 33 42 } 34 43 35 - function ProfilePostComponent({ did, rkey }: { did: string; rkey: string }) { 44 + export function ProfilePostComponent({ 45 + did, 46 + rkey, 47 + nopics, 48 + lightboxCallback, 49 + }: { 50 + did: string; 51 + rkey: string; 52 + nopics?: boolean; 53 + lightboxCallback?: (d: LightboxProps) => void; 54 + }) { 36 55 //const { get, set } = usePersistentStore(); 37 56 const queryClient = useQueryClient(); 38 57 // const [resolvedDid, setResolvedDid] = React.useState<string | null>(null); ··· 179 198 () => 180 199 resolvedDid 181 200 ? `at://${decodeURIComponent(resolvedDid)}/app.bsky.feed.post/${rkey}` 182 - : "", 201 + : undefined, 183 202 [resolvedDid, rkey] 184 203 ); 185 204 186 205 const { data: mainPost } = useQueryPost(atUri); 187 206 188 - const { data: repliesData } = useQueryConstellation({ 189 - method: "/links", 207 + console.log("atUri",atUri) 208 + 209 + const opdid = React.useMemo( 210 + () => 211 + atUri 212 + ? new AtUri(atUri).host 213 + : undefined, 214 + [atUri] 215 + ); 216 + 217 + // @ts-expect-error i hate overloads 218 + const { data: links } = useQueryConstellation(atUri?{ 219 + method: "/links/all", 190 220 target: atUri, 191 - collection: "app.bsky.feed.post", 192 - path: ".reply.parent.uri", 221 + } : { 222 + method: "undefined", 223 + target: "" 224 + })as { data: linksAllResponse | undefined }; 225 + 226 + //const [likes, setLikes] = React.useState<number | null>(null); 227 + //const [reposts, setReposts] = React.useState<number | null>(null); 228 + const [replyCount, setReplyCount] = React.useState<number | null>(null); 229 + 230 + React.useEffect(() => { 231 + // /*mass comment*/ console.log(JSON.stringify(links, null, 2)); 232 + // setLikes( 233 + // links 234 + // ? links?.links?.["app.bsky.feed.like"]?.[".subject.uri"]?.records || 0 235 + // : null 236 + // ); 237 + // setReposts( 238 + // links 239 + // ? links?.links?.["app.bsky.feed.repost"]?.[".subject.uri"]?.records || 0 240 + // : null 241 + // ); 242 + setReplyCount( 243 + links 244 + ? links?.links?.["app.bsky.feed.post"]?.[".reply.parent.uri"] 245 + ?.records || 0 246 + : null 247 + ); 248 + }, [links]); 249 + 250 + const { data: opreplies } = useQueryConstellation( 251 + !!opdid && replyCount && replyCount >= 25 252 + ? { 253 + method: "/links", 254 + target: atUri, 255 + // @ts-expect-error overloading sucks so much 256 + collection: "app.bsky.feed.post", 257 + path: ".reply.parent.uri", 258 + //cursor?: string; 259 + dids: [opdid], 260 + } 261 + : { 262 + method: "undefined", 263 + target: "", 264 + } 265 + ) as { data: linksRecordsResponse | undefined }; 266 + 267 + const opReplyAturis = 268 + opreplies?.linking_records.map( 269 + (r) => `at://${r.did}/${r.collection}/${r.rkey}`, 270 + ) ?? []; 271 + 272 + 273 + // const { data: repliesData } = useQueryConstellation({ 274 + // method: "/links", 275 + // target: atUri, 276 + // collection: "app.bsky.feed.post", 277 + // path: ".reply.parent.uri", 278 + // }); 279 + // const replies = repliesData?.linking_records.slice(0, 50) ?? []; 280 + const [constellationurl] = useAtom(constellationURLAtom) 281 + 282 + const infinitequeryresults = useInfiniteQuery({ 283 + ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks( 284 + { 285 + constellation: constellationurl, 286 + method: "/links", 287 + target: atUri, 288 + collection: "app.bsky.feed.post", 289 + path: ".reply.parent.uri", 290 + } 291 + ), 292 + enabled: !!atUri, 193 293 }); 194 - const replies = repliesData?.linking_records.slice(0, 50) ?? []; 294 + 295 + const { 296 + data: infiniteRepliesData, 297 + fetchNextPage, 298 + hasNextPage, 299 + isFetchingNextPage, 300 + } = infinitequeryresults; 301 + 302 + // // auto-fetch all pages 303 + // useEffect(() => { 304 + // if ( 305 + // infinitequeryresults.hasNextPage && 306 + // !infinitequeryresults.isFetchingNextPage 307 + // ) { 308 + // console.log("Fetching the next page..."); 309 + // infinitequeryresults.fetchNextPage(); 310 + // } 311 + // }, [infinitequeryresults]); 312 + 313 + // const replyAturis = repliesData 314 + // ? repliesData.pages.flatMap((page) => 315 + // page 316 + // ? page.linking_records.map((record) => { 317 + // const aturi = `at://${record.did}/${record.collection}/${record.rkey}`; 318 + // return aturi; 319 + // }) 320 + // : [] 321 + // ) 322 + // : []; 323 + 324 + const replyAturis = React.useMemo(() => { 325 + // Get all replies from the standard infinite query 326 + const allReplies = 327 + infiniteRepliesData?.pages.flatMap( 328 + (page) => 329 + page?.linking_records.map( 330 + (r) => `at://${r.did}/${r.collection}/${r.rkey}`, 331 + ) ?? [], 332 + ) ?? []; 333 + 334 + if (replyCount && (replyCount < 25)) { 335 + // If count is low, just use the standard list and find the oldest OP reply to move to the top 336 + const opdidFromUri = atUri ? new AtUri(atUri).host : undefined; 337 + const oldestOpsIndex = allReplies.findIndex( 338 + (aturi) => new AtUri(aturi).host === opdidFromUri, 339 + ); 340 + if (oldestOpsIndex > 0) { 341 + const [oldestOpsReply] = allReplies.splice(oldestOpsIndex, 1); 342 + allReplies.unshift(oldestOpsReply); 343 + } 344 + return allReplies; 345 + } else { 346 + // If count is high, prioritize OP replies from the special query 347 + // and filter them out from the main list to avoid duplication. 348 + const opReplySet = new Set(opReplyAturis); 349 + const otherReplies = allReplies.filter((uri) => !opReplySet.has(uri)); 350 + return [...opReplyAturis, ...otherReplies]; 351 + } 352 + }, [infiniteRepliesData, opReplyAturis, replyCount, atUri]); 353 + 354 + // Find oldest OP reply 355 + const oldestOpsIndex = replyAturis.findIndex( 356 + (aturi) => new AtUri(aturi).host === opdid 357 + ); 358 + 359 + // Reorder: move oldest OP reply to the front 360 + if (oldestOpsIndex > 0) { 361 + const [oldestOpsReply] = replyAturis.splice(oldestOpsIndex, 1); 362 + replyAturis.unshift(oldestOpsReply); 363 + } 195 364 196 365 const [parents, setParents] = React.useState<any[]>([]); 197 366 const [parentsLoading, setParentsLoading] = React.useState(false); 198 367 199 368 const mainPostRef = React.useRef<HTMLDivElement>(null); 200 - const userHasScrolled = React.useRef(false); 369 + const hasPerformedInitialLayout = React.useRef(false); 201 370 202 - const scrollAnchor = React.useRef<{ top: number } | null>(null); 371 + const [layoutReady, setLayoutReady] = React.useState(false); 372 + 373 + useLayoutEffect(() => { 374 + if (parents.length > 0 && !layoutReady && mainPostRef.current) { 375 + const mainPostElement = mainPostRef.current; 203 376 377 + if (window.scrollY === 0 && !hasPerformedInitialLayout.current) { 378 + const elementTop = mainPostElement.getBoundingClientRect().top; 379 + const headerOffset = 70; 204 380 205 - React.useEffect(() => { 206 - const onScroll = () => { 381 + const targetScrollY = elementTop - headerOffset; 207 382 208 - if (window.scrollY > 50) { 209 - userHasScrolled.current = true; 383 + window.scrollBy(0, targetScrollY); 210 384 211 - window.removeEventListener("scroll", onScroll); 385 + hasPerformedInitialLayout.current = true; 212 386 } 213 - }; 214 - 215 - if (!userHasScrolled.current) { 216 - window.addEventListener("scroll", onScroll, { passive: true }); 387 + 388 + // todo idk what to do with this 389 + // eslint-disable-next-line react-hooks/set-state-in-effect 390 + setLayoutReady(true); 217 391 } 218 - return () => window.removeEventListener("scroll", onScroll); 219 - }, []); 392 + }, [parents, layoutReady]); 220 393 221 - useLayoutEffect(() => { 222 - if (parentsLoading && mainPostRef.current && !userHasScrolled.current) { 223 - scrollAnchor.current = { 224 - top: mainPostRef.current.getBoundingClientRect().top, 225 - }; 394 + 395 + const [slingshoturl] = useAtom(slingshotURLAtom) 396 + 397 + React.useEffect(() => { 398 + if (parentsLoading) { 399 + setLayoutReady(false); 226 400 } 227 - }, [parentsLoading]); 228 401 229 - useLayoutEffect(() => { 230 - if ( 231 - scrollAnchor.current && 232 - mainPostRef.current && 233 - !userHasScrolled.current 234 - ) { 235 - const newTop = mainPostRef.current.getBoundingClientRect().top; 236 - const topDiff = newTop - scrollAnchor.current.top; 237 - if (topDiff > 0) { 238 - window.scrollBy(0, topDiff); 239 - } 240 - scrollAnchor.current = null; 402 + if (!mainPost?.value?.reply?.parent?.uri && !parentsLoading) { 403 + setLayoutReady(true); 404 + hasPerformedInitialLayout.current = true; 241 405 } 242 - }, [parents]); 406 + }, [parentsLoading, mainPost]); 243 407 244 408 React.useEffect(() => { 245 409 if (!mainPost?.value?.reply?.parent?.uri) { ··· 258 422 while (currentParentUri && safetyCounter < MAX_PARENTS) { 259 423 try { 260 424 const parentPost = await queryClient.fetchQuery( 261 - constructPostQuery(currentParentUri) 425 + constructPostQuery(currentParentUri, slingshoturl) 262 426 ); 263 427 if (!parentPost) break; 264 428 parentChain.push(parentPost); ··· 290 454 291 455 return ( 292 456 <> 293 - <div className="flex items-center gap-2 px-4 py-2 h-[52px] sticky top-0 bg-white dark:bg-gray-950 z-10 border-b border-gray-200 dark:border-gray-700"> 294 - <Link 295 - to=".." 296 - className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg" 297 - onClick={(e) => { 298 - e.preventDefault(); 299 - if (window.history.length > 1) { 300 - window.history.back(); 301 - } else { 302 - window.location.assign("/"); 303 - } 304 - }} 305 - aria-label="Go back" 306 - > 307 - โ† 308 - </Link> 309 - <span className="text-xl font-bold ml-2">Post</span> 310 - </div> 457 + <Outlet /> 458 + <Header 459 + title={`Post`} 460 + backButtonCallback={() => { 461 + if (window.history.length > 1) { 462 + window.history.back(); 463 + } else { 464 + window.location.assign("/"); 465 + } 466 + }} 467 + /> 311 468 312 469 {parentsLoading && ( 313 470 <div className="text-center text-gray-500 dark:text-gray-400 flex flex-row"> ··· 322 479 )} 323 480 324 481 {/* we should use the reply lines here thats provided by UPR*/} 325 - <div style={{ maxWidth: 600, margin: "0px auto 0", padding: 0 }}> 482 + <div style={{ maxWidth: 600, padding: 0 }}> 326 483 {parents.map((parent, index) => ( 327 484 <UniversalPostRendererATURILoader 328 485 key={parent.uri} ··· 338 495 atUri={atUri} 339 496 detailed={true} 340 497 topReplyLine={parentsLoading || parents.length > 0} 498 + nopics={!!nopics} 499 + lightboxCallback={lightboxCallback} 341 500 /> 342 501 </div> 343 502 <div 344 503 style={{ 345 504 maxWidth: 600, 346 - margin: "0px auto 0", 505 + //margin: "0px auto 0", 347 506 padding: 0, 348 - minHeight: "100dvh", 507 + minHeight: "80dvh", 508 + paddingBottom: "20dvh", 349 509 }} 350 510 > 351 511 <div ··· 359 519 Replies 360 520 </div> 361 521 <div style={{ display: "flex", flexDirection: "column", gap: 0 }}> 362 - {replies.length > 0 && 363 - replies.map((reply) => { 364 - const replyAtUri = `at://${reply.did}/app.bsky.feed.post/${reply.rkey}`; 522 + {replyAturis.length > 0 && 523 + replyAturis.map((reply) => { 524 + //const replyAtUri = `at://${reply.did}/app.bsky.feed.post/${reply.rkey}`; 365 525 return ( 366 526 <UniversalPostRendererATURILoader 367 - key={replyAtUri} 368 - atUri={replyAtUri} 527 + key={reply} 528 + atUri={reply} 529 + maxReplies={4} 369 530 /> 370 531 ); 371 532 })} 533 + {hasNextPage && ( 534 + <button 535 + onClick={() => fetchNextPage()} 536 + disabled={isFetchingNextPage} 537 + 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" 538 + > 539 + {isFetchingNextPage ? "Loading..." : "Load More"} 540 + </button> 541 + )} 372 542 </div> 373 543 </div> 374 544 </>
+50 -1
src/routes/search.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 3 + import { Header } from "~/components/Header"; 4 + import { Import } from "~/components/Import"; 5 + 3 6 export const Route = createFileRoute("/search")({ 4 7 component: Search, 5 8 }); 6 9 7 10 export function Search() { 8 - return <div className="p-6">Search page (coming soon)</div>; 11 + return ( 12 + <> 13 + <Header 14 + title="Explore" 15 + backButtonCallback={() => { 16 + if (window.history.length > 1) { 17 + window.history.back(); 18 + } else { 19 + window.location.assign("/"); 20 + } 21 + }} 22 + /> 23 + <div className=" flex flex-col items-center mt-4 mx-4 gap-4"> 24 + <Import /> 25 + <div className="flex flex-col"> 26 + <p className="text-gray-600 dark:text-gray-400"> 27 + Sorry we dont have search. But instead, you can load some of these 28 + types of content into Red Dwarf: 29 + </p> 30 + <ul className="list-disc list-inside mt-2 text-gray-600 dark:text-gray-400"> 31 + <li> 32 + Bluesky URLs from supported clients (like{" "} 33 + <code className="text-sm">bsky.app</code> or{" "} 34 + <code className="text-sm">deer.social</code>). 35 + </li> 36 + <li> 37 + AT-URIs (e.g.,{" "} 38 + <code className="text-sm">at://did:example/collection/item</code> 39 + ). 40 + </li> 41 + <li> 42 + Plain handles (like{" "} 43 + <code className="text-sm">@username.bsky.social</code>). 44 + </li> 45 + <li> 46 + Direct DIDs (Decentralized Identifiers, starting with{" "} 47 + <code className="text-sm">did:</code>). 48 + </li> 49 + </ul> 50 + <p className="mt-2 text-gray-600 dark:text-gray-400"> 51 + Simply paste one of these into the import field above and press 52 + Enter to load the content. 53 + </p> 54 + </div> 55 + </div> 56 + </> 57 + ); 9 58 }
+189 -1
src/routes/settings.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 + import { useAtom } from "jotai"; 3 + import { Slider } from "radix-ui"; 4 + 5 + import { Header } from "~/components/Header"; 6 + import Login from "~/components/Login"; 7 + import { 8 + aturiListServiceAtom, 9 + constellationURLAtom, 10 + defaultaturilistservice, 11 + defaultconstellationURL, 12 + defaulthue, 13 + defaultImgCDN, 14 + defaultslingshotURL, 15 + defaultVideoCDN, 16 + hueAtom, 17 + imgCDNAtom, 18 + slingshotURLAtom, 19 + videoCDNAtom, 20 + } from "~/utils/atoms"; 2 21 3 22 export const Route = createFileRoute("/settings")({ 4 23 component: Settings, 5 24 }); 6 25 7 26 export function Settings() { 8 - return <div className="p-6">Settings page (coming soon)</div>; 27 + return ( 28 + <> 29 + <Header 30 + title="Settings" 31 + backButtonCallback={() => { 32 + if (window.history.length > 1) { 33 + window.history.back(); 34 + } else { 35 + window.location.assign("/"); 36 + } 37 + }} 38 + /> 39 + <div className="lg:hidden"> 40 + <Login /> 41 + </div> 42 + <div className="h-4" /> 43 + <TextInputSetting 44 + atom={constellationURLAtom} 45 + title={"Constellation"} 46 + description={ 47 + "Customize the Constellation instance to be used by Red Dwarf" 48 + } 49 + init={defaultconstellationURL} 50 + /> 51 + <TextInputSetting 52 + atom={slingshotURLAtom} 53 + title={"Slingshot"} 54 + description={"Customize the Slingshot instance to be used by Red Dwarf"} 55 + init={defaultslingshotURL} 56 + /> 57 + <TextInputSetting 58 + atom={aturiListServiceAtom} 59 + title={"AtUriListService"} 60 + description={"Customize the AtUriListService instance to be used by Red Dwarf"} 61 + init={defaultaturilistservice} 62 + /> 63 + <TextInputSetting 64 + atom={imgCDNAtom} 65 + title={"Image CDN"} 66 + description={ 67 + "Customize the Constellation instance to be used by Red Dwarf" 68 + } 69 + init={defaultImgCDN} 70 + /> 71 + <TextInputSetting 72 + atom={videoCDNAtom} 73 + title={"Video CDN"} 74 + description={"Customize the Slingshot instance to be used by Red Dwarf"} 75 + init={defaultVideoCDN} 76 + /> 77 + 78 + <Hue /> 79 + <p className="text-gray-500 dark:text-gray-400 py-4 px-6 text-sm"> 80 + please restart/refresh the app if changes arent applying correctly 81 + </p> 82 + </> 83 + ); 9 84 } 85 + function Hue() { 86 + const [hue, setHue] = useAtom(hueAtom); 87 + return ( 88 + <div className="flex flex-col px-4 mt-4 "> 89 + <span className="z-10">Hue</span> 90 + <div className="flex flex-row items-center gap-4"> 91 + <SliderComponent 92 + atom={hueAtom} 93 + max={360} 94 + /> 95 + <button 96 + onClick={() => setHue(defaulthue ?? 28)} 97 + className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800 98 + text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition" 99 + > 100 + Reset 101 + </button> 102 + </div> 103 + </div> 104 + ); 105 + } 106 + 107 + export function TextInputSetting({ 108 + atom, 109 + title, 110 + description, 111 + init, 112 + }: { 113 + atom: typeof constellationURLAtom; 114 + title?: string; 115 + description?: string; 116 + init?: string; 117 + }) { 118 + const [value, setValue] = useAtom(atom); 119 + return ( 120 + <div className="flex flex-col gap-2 px-4 py-2"> 121 + {/* <div> 122 + {title && ( 123 + <h3 className="text-sm font-medium text-gray-900 dark:text-gray-100"> 124 + {title} 125 + </h3> 126 + )} 127 + {description && ( 128 + <p className="text-sm text-gray-500 dark:text-gray-400"> 129 + {description} 130 + </p> 131 + )} 132 + </div> */} 133 + 134 + <div className="flex flex-row gap-2 items-center"> 135 + <div className="m3input-field m3input-label m3input-border size-md flex-1"> 136 + <input 137 + type="text" 138 + placeholder=" " 139 + value={value} 140 + onChange={(e) => setValue(e.target.value)} 141 + /> 142 + <label>{title}</label> 143 + </div> 144 + {/* <input 145 + type="text" 146 + value={value} 147 + onChange={(e) => setValue(e.target.value)} 148 + className="flex-1 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 149 + text-gray-900 dark:text-gray-100 placeholder:text-gray-500 dark:placeholder:text-gray-400 150 + focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-600" 151 + placeholder="Enter value..." 152 + /> */} 153 + <button 154 + onClick={() => setValue(init ?? "")} 155 + className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800 156 + text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition" 157 + > 158 + Reset 159 + </button> 160 + </div> 161 + </div> 162 + ); 163 + } 164 + 165 + 166 + interface SliderProps { 167 + atom: typeof hueAtom; 168 + min?: number; 169 + max?: number; 170 + step?: number; 171 + } 172 + 173 + export const SliderComponent: React.FC<SliderProps> = ({ 174 + atom, 175 + min = 0, 176 + max = 100, 177 + step = 1, 178 + }) => { 179 + 180 + const [value, setValue] = useAtom(atom) 181 + 182 + return ( 183 + <Slider.Root 184 + className="relative flex items-center w-full h-4" 185 + value={[value]} 186 + min={min} 187 + max={max} 188 + step={step} 189 + onValueChange={(v: number[]) => setValue(v[0])} 190 + > 191 + <Slider.Track className="relative flex-grow h-4 bg-gray-300 dark:bg-gray-700 rounded-full"> 192 + <Slider.Range className="absolute h-full bg-gray-500 dark:bg-gray-400 rounded-l-full rounded-r-none" /> 193 + </Slider.Track> 194 + <Slider.Thumb className="shadow-[0_0_0_8px_var(--color-white)] dark:shadow-[0_0_0_8px_var(--color-gray-950)] block w-[3px] h-12 bg-gray-500 dark:bg-gray-400 rounded-md focus:outline-none" /> 195 + </Slider.Root> 196 + ); 197 + };
+180 -13
src/styles/app.css
··· 1 + @import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&family=Spectral+SC:wght@500&display=swap'); 1 2 @import "tailwindcss"; 2 3 3 4 /* @theme { ··· 14 15 --color-gray-950: oklch(0.129 0.050 222.000); 15 16 } */ 16 17 18 + :root { 19 + --safe-hue: var(--tw-gray-hue, 28) 20 + } 21 + 17 22 @theme { 18 - --color-gray-50: oklch(0.984 0.012 28); 19 - --color-gray-100: oklch(0.968 0.017 28); 20 - --color-gray-200: oklch(0.929 0.025 28); 21 - --color-gray-300: oklch(0.869 0.035 28); 22 - --color-gray-400: oklch(0.704 0.05 28); 23 - --color-gray-500: oklch(0.554 0.06 28); 24 - --color-gray-600: oklch(0.446 0.058 28); 25 - --color-gray-700: oklch(0.372 0.058 28); 26 - --color-gray-800: oklch(0.279 0.055 28); 27 - --color-gray-900: oklch(0.208 0.055 28); 28 - --color-gray-950: oklch(0.129 0.055 28); 23 + --color-gray-50: oklch(0.984 0.012 var(--safe-hue)); 24 + --color-gray-100: oklch(0.968 0.017 var(--safe-hue)); 25 + --color-gray-200: oklch(0.929 0.025 var(--safe-hue)); 26 + --color-gray-300: oklch(0.869 0.035 var(--safe-hue)); 27 + --color-gray-400: oklch(0.704 0.05 var(--safe-hue)); 28 + --color-gray-500: oklch(0.554 0.06 var(--safe-hue)); 29 + --color-gray-600: oklch(0.446 0.058 var(--safe-hue)); 30 + --color-gray-700: oklch(0.372 0.058 var(--safe-hue)); 31 + --color-gray-800: oklch(0.279 0.055 var(--safe-hue)); 32 + --color-gray-900: oklch(0.208 0.055 var(--safe-hue)); 33 + --color-gray-950: oklch(0.129 0.055 var(--safe-hue)); 29 34 } 30 35 31 36 @layer base { ··· 48 53 } 49 54 50 55 @media (width >= 64rem /* 1024px */) { 51 - html, 52 - body { 56 + html:not(:has(.disablegutter)), 57 + body:not(:has(.disablegutter)) { 53 58 scrollbar-gutter: stable both-edges !important; 54 59 } 60 + html:has(.disablegutter), 61 + body:has(.disablegutter) { 62 + scrollbar-width: none; 63 + overflow-y: hidden; 64 + } 55 65 } 66 + 67 + .lightbox:has(+.lightbox-sidebar){ 68 + opacity: 0; 69 + } 70 + 56 71 .scroll-thin { 57 72 scrollbar-width: thin; 58 73 /*scrollbar-gutter: stable both-edges !important;*/ ··· 61 76 .scroll-none { 62 77 scrollbar-width: none; 63 78 } 79 + 80 + .dangerousFediContent { 81 + & a[href]{ 82 + text-decoration: none; 83 + color: rgb(29, 122, 242); 84 + word-break: break-all; 85 + } 86 + } 87 + 88 + .font-inter { 89 + font-family: "Inter", sans-serif; 90 + } 91 + .font-roboto { 92 + font-family: "Roboto", sans-serif; 93 + } 94 + 95 + :root { 96 + --header-bg-light: color-mix(in srgb, var(--color-white) calc(var(--is-top) * 100%), var(--color-gray-50)); 97 + --header-bg-dark: color-mix(in srgb, var(--color-gray-950) calc(var(--is-top) * 100%), var(--color-gray-900)); 98 + } 99 + 100 + :root { 101 + --header-bg: var(--header-bg-light); 102 + } 103 + @media (prefers-color-scheme: dark) { 104 + :root { 105 + --header-bg: var(--header-bg-dark); 106 + } 107 + } 108 + 109 + :root { 110 + --shadow-opacity: calc(1 - var(--is-top)); 111 + --tw-shadow-header: 0 2px 8px hsl(0 0% 0% / calc(var(--shadow-opacity) * 0.15)); 112 + } 113 + 114 + 115 + /* m3 input */ 116 + :root { 117 + --m3input-radius: 6px; 118 + --m3input-border-width: .0625rem; 119 + --m3input-font-size: 16px; 120 + --m3input-transition: 150ms cubic-bezier(.2, .8, .2, 1); 121 + /* light theme */ 122 + --m3input-bg: var(--color-gray-50); 123 + --m3input-border-color: var(--color-gray-400); 124 + --m3input-label-color: var(--color-gray-500); 125 + --m3input-text-color: var(--color-gray-900); 126 + --m3input-focus-color: var(--color-gray-600); 127 + } 128 + 129 + @media (prefers-color-scheme: dark) { 130 + :root { 131 + --m3input-bg: var(--color-gray-950); 132 + --m3input-border-color: var(--color-gray-700); 133 + --m3input-label-color: var(--color-gray-400); 134 + --m3input-text-color: var(--color-gray-50); 135 + --m3input-focus-color: var(--color-gray-400); 136 + } 137 + } 138 + 139 + /* reset page *//* 140 + html, 141 + body { 142 + background: var(--m3input-bg); 143 + margin: 0; 144 + padding: 1rem; 145 + color: var(--m3input-text-color); 146 + font-family: system-ui, sans-serif; 147 + font-size: var(--m3input-font-size); 148 + }*/ 149 + 150 + /* base wrapper */ 151 + .m3input-field.m3input-label.m3input-border { 152 + position: relative; 153 + display: inline-block; 154 + width: 100%; 155 + /*max-width: 400px;*/ 156 + } 157 + 158 + /* size variants */ 159 + .m3input-field.size-sm { 160 + --m3input-h: 40px; 161 + } 162 + 163 + .m3input-field.size-md { 164 + --m3input-h: 48px; 165 + } 166 + 167 + .m3input-field.size-lg { 168 + --m3input-h: 56px; 169 + } 170 + 171 + .m3input-field.size-xl { 172 + --m3input-h: 64px; 173 + } 174 + 175 + .m3input-field.m3input-label.m3input-border:not(.size-sm):not(.size-md):not(.size-lg):not(.size-xl) { 176 + --m3input-h: 48px; 177 + } 178 + 179 + /* outlined input */ 180 + .m3input-field.m3input-label.m3input-border input { 181 + width: 100%; 182 + height: var(--m3input-h); 183 + border: var(--m3input-border-width) solid var(--m3input-border-color); 184 + border-radius: var(--m3input-radius); 185 + background: var(--m3input-bg); 186 + color: var(--m3input-text-color); 187 + font-size: var(--m3input-font-size); 188 + padding: 0 12px; 189 + box-sizing: border-box; 190 + outline: none; 191 + transition: border-color var(--m3input-transition), box-shadow var(--m3input-transition); 192 + } 193 + 194 + /* focus ring */ 195 + .m3input-field.m3input-label.m3input-border input:focus { 196 + border-color: var(--m3input-focus-color); 197 + /*box-shadow: 0 0 0 2px color-mix(in srgb, var(--focus-color) 20%, transparent);*/ 198 + } 199 + 200 + /* label */ 201 + .m3input-field.m3input-label.m3input-border label { 202 + position: absolute; 203 + left: 12px; 204 + top: 50%; 205 + transform: translateY(-50%); 206 + background: var(--m3input-bg); 207 + padding: 0 .25em; 208 + color: var(--m3input-label-color); 209 + pointer-events: none; 210 + transition: all var(--m3input-transition); 211 + } 212 + 213 + /* float on focus or when filled */ 214 + .m3input-field.m3input-label.m3input-border input:focus+label, 215 + .m3input-field.m3input-label.m3input-border input:not(:placeholder-shown)+label { 216 + top: 0; 217 + transform: translateY(-50%) scale(.78); 218 + left: 0; 219 + color: var(--m3input-focus-color); 220 + } 221 + 222 + /* placeholder trick */ 223 + .m3input-field.m3input-label.m3input-border input::placeholder { 224 + color: transparent; 225 + } 226 + 227 + /* radix i love you but like cmon man */ 228 + body[data-scroll-locked]{ 229 + margin-left: var(--removed-body-scroll-bar-size) !important; 230 + }
+64 -6
src/utils/atoms.ts
··· 1 1 import type Agent from "@atproto/api"; 2 - import { atom, createStore } from "jotai"; 3 - import { atomWithStorage } from 'jotai/utils'; 2 + import { atom, createStore, useAtomValue } from "jotai"; 3 + import { atomWithStorage } from "jotai/utils"; 4 + import { useEffect } from "react"; 4 5 5 6 export const store = createStore(); 6 7 7 8 export const selectedFeedUriAtom = atomWithStorage<string | null>( 8 - 'selectedFeedUri', 9 + "selectedFeedUri", 9 10 null 10 11 ); 11 12 12 13 //export const feedScrollPositionsAtom = atom<Record<string, number>>({}); 13 14 14 15 export const feedScrollPositionsAtom = atomWithStorage<Record<string, number>>( 15 - 'feedscrollpositions', 16 + "feedscrollpositions", 16 17 {} 17 18 ); 18 19 19 20 export const likedPostsAtom = atomWithStorage<Record<string, string>>( 20 - 'likedPosts', 21 + "likedPosts", 21 22 {} 22 23 ); 23 24 24 - export const agentAtom = atom<Agent|null>(null); 25 + export const defaultconstellationURL = "constellation.microcosm.blue"; 26 + export const constellationURLAtom = atomWithStorage<string>( 27 + "constellationURL", 28 + defaultconstellationURL 29 + ); 30 + export const defaultslingshotURL = "slingshot.microcosm.blue"; 31 + export const slingshotURLAtom = atomWithStorage<string>( 32 + "slingshotURL", 33 + defaultslingshotURL 34 + ); 35 + export const defaultaturilistservice = "aturilistservice.reddwarf.app"; 36 + export const aturiListServiceAtom = atomWithStorage<string>( 37 + "aturilistservice", 38 + defaultaturilistservice 39 + ); 40 + export const defaultImgCDN = "cdn.bsky.app"; 41 + export const imgCDNAtom = atomWithStorage<string>("imgcdnurl", defaultImgCDN); 42 + export const defaultVideoCDN = "video.bsky.app"; 43 + export const videoCDNAtom = atomWithStorage<string>( 44 + "videocdnurl", 45 + defaultVideoCDN 46 + ); 47 + 48 + export const defaulthue = 28; 49 + export const hueAtom = atomWithStorage<number>("hue", defaulthue); 50 + 51 + export const isAtTopAtom = atom<boolean>(true); 52 + 53 + type ComposerState = 54 + | { kind: "closed" } 55 + | { kind: "root" } 56 + | { kind: "reply"; parent: string } 57 + | { kind: "quote"; subject: string }; 58 + export const composerAtom = atom<ComposerState>({ kind: "closed" }); 59 + 60 + export const agentAtom = atom<Agent | null>(null); 25 61 export const authedAtom = atom<boolean>(false); 62 + 63 + export function useAtomCssVar(atom: typeof hueAtom, cssVar: string) { 64 + const value = useAtomValue(atom); 65 + 66 + useEffect(() => { 67 + document.documentElement.style.setProperty(cssVar, value.toString()); 68 + }, [value, cssVar]); 69 + 70 + useEffect(() => { 71 + document.documentElement.style.setProperty(cssVar, value.toString()); 72 + }, []); 73 + } 74 + 75 + hueAtom.onMount = (setAtom) => { 76 + const stored = localStorage.getItem("hue"); 77 + if (stored != null) setAtom(Number(stored)); 78 + }; 79 + // export function initAtomToCssVar(atom: typeof hueAtom, cssVar: string) { 80 + // const initial = store.get(atom); 81 + // console.log("atom get ", initial); 82 + // document.documentElement.style.setProperty(cssVar, initial.toString()); 83 + // }
+37 -3
src/utils/followState.ts
··· 1 - import { AtUri, type Agent } from "@atproto/api"; 2 - import { useQueryConstellation, type linksRecordsResponse } from "./useQuery"; 1 + import { type Agent,AtUri } from "@atproto/api"; 2 + import { TID } from "@atproto/common-web"; 3 3 import type { QueryClient } from "@tanstack/react-query"; 4 - import { TID } from "@atproto/common-web"; 4 + 5 + import { type linksRecordsResponse,useQueryConstellation } from "./useQuery"; 5 6 6 7 export function useGetFollowState({ 7 8 target, ··· 127 128 }; 128 129 }); 129 130 } 131 + 132 + 133 + 134 + export function useGetOneToOneState(params?: { 135 + target: string; 136 + user: string; 137 + collection: string; 138 + path: string; 139 + }): string[] | undefined { 140 + const { data: arbitrarydata } = useQueryConstellation( 141 + params && params.user 142 + ? { 143 + method: "/links", 144 + target: params.target, 145 + // @ts-expect-error overloading sucks so much 146 + collection: params.collection, 147 + path: params.path, 148 + dids: [params.user], 149 + } 150 + : { method: "undefined", target: "whatever" } 151 + // overloading sucks so much 152 + ) as { data: linksRecordsResponse | undefined }; 153 + if (!params || !params.user) return undefined; 154 + const data = arbitrarydata?.linking_records.slice(0, 50) ?? []; 155 + 156 + if (data.length > 0) { 157 + return data.map((linksRecord) => { 158 + return `at://${linksRecord.did}/${linksRecord.collection}/${linksRecord.rkey}`; 159 + }); 160 + } 161 + 162 + return undefined; 163 + }
+53 -23
src/utils/useHydrated.ts
··· 9 9 AppBskyFeedPost, 10 10 AtUri, 11 11 } from "@atproto/api"; 12 + import { useAtom } from "jotai"; 12 13 import { useMemo } from "react"; 13 14 14 - import { useQueryIdentity,useQueryPost, useQueryProfile } from "./useQuery"; 15 + import { imgCDNAtom, videoCDNAtom } from "./atoms"; 16 + import { useQueryIdentity, useQueryPost, useQueryProfile } from "./useQuery"; 15 17 16 - type QueryResultData<T extends (...args: any) => any> = ReturnType<T> extends 17 - | { data: infer D } 18 - | undefined 19 - ? D 20 - : never; 18 + type QueryResultData<T extends (...args: any) => any> = 19 + ReturnType<T> extends { data: infer D } | undefined ? D : never; 21 20 22 21 function asTyped<T extends { $type: string }>(obj: T): $Typed<T> { 23 22 return obj as $Typed<T>; ··· 26 25 export function hydrateEmbedImages( 27 26 embed: AppBskyEmbedImages.Main, 28 27 did: string, 28 + cdn: string 29 29 ): $Typed<AppBskyEmbedImages.View> { 30 30 return asTyped({ 31 31 $type: "app.bsky.embed.images#view" as const, ··· 34 34 const link = img.image.ref?.["$link"]; 35 35 if (!link) return null; 36 36 return { 37 - thumb: `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${link}@jpeg`, 38 - fullsize: `https://cdn.bsky.app/img/feed_fullsize/plain/${did}/${link}@jpeg`, 37 + thumb: `https://${cdn}/img/feed_thumbnail/plain/${did}/${link}@jpeg`, 38 + fullsize: `https://${cdn}/img/feed_fullsize/plain/${did}/${link}@jpeg`, 39 39 alt: img.alt || "", 40 40 aspectRatio: img.aspectRatio, 41 41 }; ··· 47 47 export function hydrateEmbedExternal( 48 48 embed: AppBskyEmbedExternal.Main, 49 49 did: string, 50 + cdn: string 50 51 ): $Typed<AppBskyEmbedExternal.View> { 51 52 return asTyped({ 52 53 $type: "app.bsky.embed.external#view" as const, ··· 55 56 title: embed.external.title, 56 57 description: embed.external.description, 57 58 thumb: embed.external.thumb?.ref?.$link 58 - ? `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${embed.external.thumb.ref.$link}@jpeg` 59 + ? `https://${cdn}/img/feed_thumbnail/plain/${did}/${embed.external.thumb.ref.$link}@jpeg` 59 60 : undefined, 60 61 }, 61 62 }); ··· 64 65 export function hydrateEmbedVideo( 65 66 embed: AppBskyEmbedVideo.Main, 66 67 did: string, 68 + videocdn: string 67 69 ): $Typed<AppBskyEmbedVideo.View> { 68 70 const videoLink = embed.video.ref.$link; 69 71 return asTyped({ 70 72 $type: "app.bsky.embed.video#view" as const, 71 - playlist: `https://video.bsky.app/watch/${did}/${videoLink}/playlist.m3u8`, 72 - thumbnail: `https://video.bsky.app/watch/${did}/${videoLink}/thumbnail.jpg`, 73 + playlist: `https://${videocdn}/watch/${did}/${videoLink}/playlist.m3u8`, 74 + thumbnail: `https://${videocdn}/watch/${did}/${videoLink}/thumbnail.jpg`, 73 75 aspectRatio: embed.aspectRatio, 74 76 cid: videoLink, 75 77 }); ··· 80 82 quotedPost: QueryResultData<typeof useQueryPost>, 81 83 quotedProfile: QueryResultData<typeof useQueryProfile>, 82 84 quotedIdentity: QueryResultData<typeof useQueryIdentity>, 85 + cdn: string 83 86 ): $Typed<AppBskyEmbedRecord.View> | undefined { 84 87 if (!quotedPost || !quotedProfile || !quotedIdentity) { 85 88 return undefined; ··· 91 94 handle: quotedIdentity.handle, 92 95 displayName: quotedProfile.value.displayName ?? quotedIdentity.handle, 93 96 avatar: quotedProfile.value.avatar?.ref?.$link 94 - ? `https://cdn.bsky.app/img/avatar/plain/${quotedIdentity.did}/${quotedProfile.value.avatar.ref.$link}@jpeg` 97 + ? `https://${cdn}/img/avatar/plain/${quotedIdentity.did}/${quotedProfile.value.avatar.ref.$link}@jpeg` 95 98 : undefined, 96 99 viewer: {}, 97 100 labels: [], ··· 122 125 quotedPost: QueryResultData<typeof useQueryPost>, 123 126 quotedProfile: QueryResultData<typeof useQueryProfile>, 124 127 quotedIdentity: QueryResultData<typeof useQueryIdentity>, 128 + cdn: string 125 129 ): $Typed<AppBskyEmbedRecordWithMedia.View> | undefined { 126 130 const hydratedRecord = hydrateEmbedRecord( 127 131 embed.record, 128 132 quotedPost, 129 133 quotedProfile, 130 134 quotedIdentity, 135 + cdn 131 136 ); 132 137 133 138 if (!hydratedRecord) return undefined; ··· 148 153 149 154 export function useHydratedEmbed( 150 155 embed: AppBskyFeedPost.Record["embed"], 151 - postAuthorDid: string | undefined, 156 + postAuthorDid: string | undefined 152 157 ) { 153 158 const recordInfo = useMemo(() => { 154 159 if (AppBskyEmbedRecordWithMedia.isMain(embed)) { ··· 181 186 error: profileError, 182 187 } = useQueryProfile(profileUri); 183 188 189 + const [imgcdn] = useAtom(imgCDNAtom); 190 + const [videocdn] = useAtom(videoCDNAtom); 191 + 184 192 const queryidentityresult = useQueryIdentity(quotedAuthorDid); 185 193 186 194 const hydratedEmbed: HydratedEmbedView | undefined = (() => { 187 195 if (!embed || !postAuthorDid) return undefined; 188 196 189 - if (isRecordType && (!usequerypostresults?.data || !quotedProfile || !queryidentityresult?.data)) { 197 + if ( 198 + isRecordType && 199 + (!usequerypostresults?.data || 200 + !quotedProfile || 201 + !queryidentityresult?.data) 202 + ) { 190 203 return undefined; 191 204 } 192 205 193 206 try { 194 207 if (AppBskyEmbedImages.isMain(embed)) { 195 - return hydrateEmbedImages(embed, postAuthorDid); 208 + return hydrateEmbedImages(embed, postAuthorDid, imgcdn); 196 209 } else if (AppBskyEmbedExternal.isMain(embed)) { 197 - return hydrateEmbedExternal(embed, postAuthorDid); 210 + return hydrateEmbedExternal(embed, postAuthorDid, imgcdn); 198 211 } else if (AppBskyEmbedVideo.isMain(embed)) { 199 - return hydrateEmbedVideo(embed, postAuthorDid); 212 + return hydrateEmbedVideo(embed, postAuthorDid, videocdn); 200 213 } else if (AppBskyEmbedRecord.isMain(embed)) { 201 214 return hydrateEmbedRecord( 202 215 embed, 203 216 usequerypostresults?.data, 204 217 quotedProfile, 205 218 queryidentityresult?.data, 219 + imgcdn 206 220 ); 207 221 } else if (AppBskyEmbedRecordWithMedia.isMain(embed)) { 208 222 let hydratedMedia: ··· 212 226 | undefined; 213 227 214 228 if (AppBskyEmbedImages.isMain(embed.media)) { 215 - hydratedMedia = hydrateEmbedImages(embed.media, postAuthorDid); 229 + hydratedMedia = hydrateEmbedImages( 230 + embed.media, 231 + postAuthorDid, 232 + imgcdn 233 + ); 216 234 } else if (AppBskyEmbedExternal.isMain(embed.media)) { 217 - hydratedMedia = hydrateEmbedExternal(embed.media, postAuthorDid); 235 + hydratedMedia = hydrateEmbedExternal( 236 + embed.media, 237 + postAuthorDid, 238 + imgcdn 239 + ); 218 240 } else if (AppBskyEmbedVideo.isMain(embed.media)) { 219 - hydratedMedia = hydrateEmbedVideo(embed.media, postAuthorDid); 241 + hydratedMedia = hydrateEmbedVideo( 242 + embed.media, 243 + postAuthorDid, 244 + videocdn 245 + ); 220 246 } 221 247 222 248 if (hydratedMedia) { ··· 226 252 usequerypostresults?.data, 227 253 quotedProfile, 228 254 queryidentityresult?.data, 255 + imgcdn 229 256 ); 230 257 } 231 258 } ··· 236 263 })(); 237 264 238 265 const isLoading = isRecordType 239 - ? usequerypostresults?.isLoading || isLoadingProfile || queryidentityresult?.isLoading 266 + ? usequerypostresults?.isLoading || 267 + isLoadingProfile || 268 + queryidentityresult?.isLoading 240 269 : false; 241 270 242 - const error = usequerypostresults?.error || profileError || queryidentityresult?.error; 271 + const error = 272 + usequerypostresults?.error || profileError || queryidentityresult?.error; 243 273 244 274 return { data: hydratedEmbed, isLoading, error }; 245 - } 275 + }
+161 -17
src/utils/useQuery.ts
··· 1 1 import * as ATPAPI from "@atproto/api"; 2 2 import { 3 + infiniteQueryOptions, 3 4 type QueryFunctionContext, 4 5 queryOptions, 5 6 useInfiniteQuery, 6 7 useQuery, 7 8 type UseQueryResult} from "@tanstack/react-query"; 9 + import { useAtom } from "jotai"; 8 10 9 - export function constructIdentityQuery(didorhandle?: string) { 11 + import { constellationURLAtom, slingshotURLAtom } from "./atoms"; 12 + 13 + export function constructIdentityQuery(didorhandle?: string, slingshoturl?: string) { 10 14 return queryOptions({ 11 15 queryKey: ["identity", didorhandle], 12 16 queryFn: async () => { 13 17 if (!didorhandle) return undefined as undefined 14 18 const res = await fetch( 15 - `https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${encodeURIComponent(didorhandle)}` 19 + `https://${slingshoturl}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${encodeURIComponent(didorhandle)}` 16 20 ); 17 21 if (!res.ok) throw new Error("Failed to fetch post"); 18 22 try { ··· 54 58 Error 55 59 > 56 60 export function useQueryIdentity(didorhandle?: string) { 57 - return useQuery(constructIdentityQuery(didorhandle)); 61 + const [slingshoturl] = useAtom(slingshotURLAtom) 62 + return useQuery(constructIdentityQuery(didorhandle, slingshoturl)); 58 63 } 59 64 60 - export function constructPostQuery(uri?: string) { 65 + export function constructPostQuery(uri?: string, slingshoturl?: string) { 61 66 return queryOptions({ 62 67 queryKey: ["post", uri], 63 68 queryFn: async () => { 64 69 if (!uri) return undefined as undefined 65 70 const res = await fetch( 66 - `https://slingshot.microcosm.blue/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` 71 + `https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` 67 72 ); 68 73 let data: any; 69 74 try { ··· 117 122 Error 118 123 > 119 124 export function useQueryPost(uri?: string) { 120 - return useQuery(constructPostQuery(uri)); 125 + const [slingshoturl] = useAtom(slingshotURLAtom) 126 + return useQuery(constructPostQuery(uri, slingshoturl)); 121 127 } 122 128 123 - export function constructProfileQuery(uri?: string) { 129 + export function constructProfileQuery(uri?: string, slingshoturl?: string) { 124 130 return queryOptions({ 125 131 queryKey: ["profile", uri], 126 132 queryFn: async () => { 127 133 if (!uri) return undefined as undefined 128 134 const res = await fetch( 129 - `https://slingshot.microcosm.blue/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` 135 + `https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` 130 136 ); 131 137 let data: any; 132 138 try { ··· 180 186 Error 181 187 > 182 188 export function useQueryProfile(uri?: string) { 183 - return useQuery(constructProfileQuery(uri)); 189 + const [slingshoturl] = useAtom(slingshotURLAtom) 190 + return useQuery(constructProfileQuery(uri, slingshoturl)); 184 191 } 185 192 186 193 // export function constructConstellationQuery( ··· 216 223 // target: string 217 224 // ): QueryOptions<linksAllResponse, Error>; 218 225 export function constructConstellationQuery(query?:{ 226 + constellation: string, 219 227 method: 220 228 | "/links" 221 229 | "/links/distinct-dids" ··· 249 257 const cursor = query.cursor 250 258 const dids = query?.dids 251 259 const res = await fetch( 252 - `https://constellation.microcosm.blue${method}?target=${encodeURIComponent(target)}${collection ? `&collection=${encodeURIComponent(collection)}` : ""}${path ? `&path=${encodeURIComponent(path)}` : ""}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}${dids ? dids.map((did) => `&did=${encodeURIComponent(did)}`).join("") : ""}` 260 + `https://${query.constellation}${method}?target=${encodeURIComponent(target)}${collection ? `&collection=${encodeURIComponent(collection)}` : ""}${path ? `&path=${encodeURIComponent(path)}` : ""}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}${dids ? dids.map((did) => `&did=${encodeURIComponent(did)}`).join("") : ""}` 253 261 ); 254 262 if (!res.ok) throw new Error("Failed to fetch post"); 255 263 try { ··· 338 346 > 339 347 | undefined { 340 348 //if (!query) return; 349 + const [constellationurl] = useAtom(constellationURLAtom) 341 350 return useQuery( 342 - constructConstellationQuery(query) 351 + constructConstellationQuery(query && {constellation: constellationurl, ...query}) 343 352 ); 344 353 } 345 354 ··· 361 370 type linksCountResponse = { 362 371 total: string; 363 372 }; 364 - type linksAllResponse = { 373 + export type linksAllResponse = { 365 374 links: Record< 366 375 string, 367 376 Record< ··· 444 453 445 454 446 455 447 - export function constructArbitraryQuery(uri?: string) { 456 + export function constructArbitraryQuery(uri?: string, slingshoturl?: string) { 448 457 return queryOptions({ 449 458 queryKey: ["arbitrary", uri], 450 459 queryFn: async () => { 451 460 if (!uri) return undefined as undefined 452 461 const res = await fetch( 453 - `https://slingshot.microcosm.blue/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` 462 + `https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` 454 463 ); 455 464 let data: any; 456 465 try { ··· 503 512 Error 504 513 >; 505 514 export function useQueryArbitrary(uri?: string) { 506 - return useQuery(constructArbitraryQuery(uri)); 515 + const [slingshoturl] = useAtom(slingshotURLAtom) 516 + return useQuery(constructArbitraryQuery(uri, slingshoturl)); 507 517 } 508 518 509 519 export function constructFallbackNothingQuery(){ ··· 555 565 }); 556 566 } 557 567 568 + export const ATURI_PAGE_LIMIT = 100; 569 + 570 + export interface AturiDirectoryAturisItem { 571 + uri: string; 572 + cid: string; 573 + rkey: string; 574 + } 575 + 576 + export type AturiDirectoryAturis = AturiDirectoryAturisItem[]; 577 + 578 + export function constructAturiListQuery(aturilistservice: string, did: string, collection: string, reverse?: boolean) { 579 + return queryOptions({ 580 + // A unique key for this query, including all parameters that affect the data. 581 + queryKey: ["aturiList", did, collection, { reverse }], 582 + 583 + // The function that fetches the data. 584 + queryFn: async ({ pageParam }: QueryFunctionContext) => { 585 + const cursor = pageParam as string | undefined; 586 + 587 + // Use URLSearchParams for safe and clean URL construction. 588 + const params = new URLSearchParams({ 589 + did, 590 + collection, 591 + }); 592 + 593 + if (cursor) { 594 + params.set("cursor", cursor); 595 + } 596 + 597 + // Add the reverse parameter if it's true 598 + if (reverse) { 599 + params.set("reverse", "true"); 600 + } 601 + 602 + const url = `https://${aturilistservice}/aturis?${params.toString()}`; 603 + 604 + const res = await fetch(url); 605 + if (!res.ok) { 606 + // You can add more specific error handling here 607 + throw new Error(`Failed to fetch AT-URI list for ${did}`); 608 + } 609 + 610 + return res.json() as Promise<AturiDirectoryAturis>; 611 + }, 612 + }); 613 + } 614 + 615 + export function useInfiniteQueryAturiList({aturilistservice, did, collection, reverse}:{aturilistservice: string, did: string | undefined, collection: string | undefined, reverse?: boolean}) { 616 + // We only enable the query if both `did` and `collection` are provided. 617 + const isEnabled = !!did && !!collection; 618 + 619 + const { queryKey, queryFn } = constructAturiListQuery(aturilistservice, did!, collection!, reverse); 620 + 621 + return useInfiniteQuery({ 622 + queryKey, 623 + queryFn, 624 + initialPageParam: undefined as never, // ???? what is this shit 625 + 626 + // @ts-expect-error i wouldve used as null | undefined, anyways 627 + getNextPageParam: (lastPage: AturiDirectoryAturis) => { 628 + // If the last page returned no records, we're at the end. 629 + if (!lastPage || lastPage.length === 0) { 630 + return undefined; 631 + } 632 + 633 + // If the number of records is less than our page limit, it must be the last page. 634 + if (lastPage.length < ATURI_PAGE_LIMIT) { 635 + return undefined; 636 + } 637 + 638 + // The cursor for the next page is the `rkey` of the last item we received. 639 + const lastItem = lastPage[lastPage.length - 1]; 640 + return lastItem.rkey; 641 + }, 642 + 643 + enabled: isEnabled, 644 + }); 645 + } 646 + 647 + 558 648 type FeedSkeletonPage = ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema; 559 649 560 650 export function constructInfiniteFeedSkeletonQuery(options: { ··· 605 695 }) { 606 696 const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery(options); 607 697 608 - return useInfiniteQuery({ 698 + return {...useInfiniteQuery({ 609 699 queryKey, 610 700 queryFn, 611 701 initialPageParam: undefined as never, ··· 613 703 staleTime: Infinity, 614 704 refetchOnWindowFocus: false, 615 705 enabled: !!options.feedUri && (options.isAuthed ? !!options.agent && !!options.pdsUrl && !!options.feedServiceDid : true), 616 - }); 706 + }), queryKey: queryKey}; 707 + } 708 + 709 + 710 + export function yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(query?: { 711 + constellation: string, 712 + method: '/links' 713 + target?: string 714 + collection: string 715 + path: string 716 + }) { 717 + console.log( 718 + 'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks', 719 + query, 720 + ) 721 + 722 + return infiniteQueryOptions({ 723 + enabled: !!query?.target, 724 + queryKey: [ 725 + 'reddwarf_constellation', 726 + query?.method, 727 + query?.target, 728 + query?.collection, 729 + query?.path, 730 + ] as const, 731 + 732 + queryFn: async ({pageParam}: {pageParam?: string}) => { 733 + if (!query || !query?.target) return undefined 734 + 735 + const method = query.method 736 + const target = query.target 737 + const collection = query.collection 738 + const path = query.path 739 + const cursor = pageParam 740 + 741 + const res = await fetch( 742 + `https://${query.constellation}${method}?target=${encodeURIComponent(target)}${ 743 + collection ? `&collection=${encodeURIComponent(collection)}` : '' 744 + }${path ? `&path=${encodeURIComponent(path)}` : ''}${ 745 + cursor ? `&cursor=${encodeURIComponent(cursor)}` : '' 746 + }`, 747 + ) 748 + 749 + if (!res.ok) throw new Error('Failed to fetch') 750 + 751 + return (await res.json()) as linksRecordsResponse 752 + }, 753 + 754 + getNextPageParam: lastPage => { 755 + return (lastPage as any)?.cursor ?? undefined 756 + }, 757 + initialPageParam: undefined, 758 + staleTime: 5 * 60 * 1000, 759 + gcTime: 5 * 60 * 1000, 760 + }) 617 761 }
+1 -1
tsconfig.json
··· 5 5 "jsx": "react-jsx", 6 6 "module": "ESNext", 7 7 "lib": ["ES2022", "DOM", "DOM.Iterable"], 8 - "types": ["vite/client"], 8 + "types": ["vite/client", "unplugin-icons/types/react"], 9 9 10 10 /* Bundler mode */ 11 11 "moduleResolution": "bundler",
+22 -1
vite.config.ts
··· 3 3 import tailwindcss from "@tailwindcss/vite"; 4 4 import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; 5 5 import viteReact from "@vitejs/plugin-react"; 6 + import AutoImport from 'unplugin-auto-import/vite' 7 + import IconsResolver from 'unplugin-icons/resolver' 8 + import Icons from 'unplugin-icons/vite' 6 9 import { defineConfig } from "vite"; 7 10 8 11 import { generateMetadataPlugin } from "./oauthdev.mts"; 9 12 10 - const PROD_URL = "https://reddwarf.whey.party" 13 + const PROD_URL = "https://reddwarf.app" 11 14 const DEV_URL = "https://local3768forumtest.whey.party" 12 15 13 16 function shp(url: string): string { ··· 28 31 }, 29 32 }), 30 33 tailwindcss(), 34 + AutoImport({ 35 + include: [ 36 + /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx 37 + ], 38 + resolvers: [ 39 + IconsResolver({ 40 + prefix: 'Icon', 41 + extension: 'jsx', 42 + enabledCollections: ['mdi','material-symbols'], 43 + }), 44 + ], 45 + dts: 'src/auto-imports.d.ts', 46 + }), 47 + Icons({ 48 + //autoInstall: true, 49 + compiler: 'jsx', 50 + jsx: 'react' 51 + }), 31 52 ], 32 53 // test: { 33 54 // globals: true,