+22
-4
README.md
+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
-

4
+

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
+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
+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
public/screenshot.jpg
This is a binary file and will not be displayed.
public/screenshot.png
public/screenshot.png
This is a binary file and will not be displayed.
+22
src/auto-imports.d.ts
+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
+
}
+339
src/components/Composer.tsx
+339
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 { Button } from "./radix-m3-rd/Button";
12
+
import { UniversalPostRendererATURILoader } from "./UniversalPostRenderer";
13
+
14
+
const MAX_POST_LENGTH = 300;
15
+
16
+
export function Composer() {
17
+
const [composerState, setComposerState] = useAtom(composerAtom);
18
+
const [closeConfirmState, setCloseConfirmState] = useState<boolean>(false);
19
+
const { agent } = useAuth();
20
+
21
+
const [postText, setPostText] = useState("");
22
+
const [posting, setPosting] = useState(false);
23
+
const [postSuccess, setPostSuccess] = useState(false);
24
+
const [postError, setPostError] = useState<string | null>(null);
25
+
26
+
useEffect(() => {
27
+
setPostText("");
28
+
setPosting(false);
29
+
setPostSuccess(false);
30
+
setPostError(null);
31
+
}, [composerState.kind]);
32
+
33
+
const parentUri =
34
+
composerState.kind === "reply"
35
+
? composerState.parent
36
+
: composerState.kind === "quote"
37
+
? composerState.subject
38
+
: undefined;
39
+
40
+
const { data: parentPost, isLoading: isParentLoading } =
41
+
useQueryPost(parentUri);
42
+
43
+
async function handlePost() {
44
+
if (!agent || !postText.trim() || postText.length > MAX_POST_LENGTH) return;
45
+
46
+
setPosting(true);
47
+
setPostError(null);
48
+
49
+
try {
50
+
const rt = new RichText({ text: postText });
51
+
await rt.detectFacets(agent);
52
+
53
+
if (rt.facets?.length) {
54
+
rt.facets = rt.facets.filter((item) => {
55
+
if (item.$type !== "app.bsky.richtext.facet") return true;
56
+
if (!item.features?.length) return true;
57
+
58
+
item.features = item.features.filter((feature) => {
59
+
if (feature.$type !== "app.bsky.richtext.facet#mention") return true;
60
+
const did = feature.$type === "app.bsky.richtext.facet#mention" ? (feature as AppBskyRichtextFacet.Mention)?.did : undefined;
61
+
return typeof did === "string" && did.startsWith("did:");
62
+
});
63
+
64
+
return item.features.length > 0;
65
+
});
66
+
}
67
+
68
+
const record: Record<string, unknown> = {
69
+
$type: "app.bsky.feed.post",
70
+
text: rt.text,
71
+
facets: rt.facets,
72
+
createdAt: new Date().toISOString(),
73
+
};
74
+
75
+
if (composerState.kind === "reply" && parentPost) {
76
+
record.reply = {
77
+
root: parentPost.value?.reply?.root ?? {
78
+
uri: parentPost.uri,
79
+
cid: parentPost.cid,
80
+
},
81
+
parent: {
82
+
uri: parentPost.uri,
83
+
cid: parentPost.cid,
84
+
},
85
+
};
86
+
}
87
+
88
+
if (composerState.kind === "quote" && parentPost) {
89
+
record.embed = {
90
+
$type: "app.bsky.embed.record",
91
+
record: {
92
+
uri: parentPost.uri,
93
+
cid: parentPost.cid,
94
+
},
95
+
};
96
+
}
97
+
98
+
await agent.com.atproto.repo.createRecord({
99
+
collection: "app.bsky.feed.post",
100
+
repo: agent.assertDid,
101
+
record,
102
+
});
103
+
104
+
setPostSuccess(true);
105
+
setPostText("");
106
+
107
+
setTimeout(() => {
108
+
setPostSuccess(false);
109
+
setComposerState({ kind: "closed" });
110
+
}, 1500);
111
+
} catch (e: any) {
112
+
setPostError(e?.message || "Failed to post");
113
+
} finally {
114
+
setPosting(false);
115
+
}
116
+
}
117
+
118
+
const getPlaceholder = () => {
119
+
switch (composerState.kind) {
120
+
case "reply":
121
+
return "Post your reply";
122
+
case "quote":
123
+
return "Add a comment...";
124
+
case "root":
125
+
default:
126
+
return "What's happening?!";
127
+
}
128
+
};
129
+
130
+
const charsLeft = MAX_POST_LENGTH - postText.length;
131
+
const isPostButtonDisabled =
132
+
posting || !postText.trim() || isParentLoading || charsLeft < 0;
133
+
134
+
function handleAttemptClose() {
135
+
if (postText.trim() && !posting) {
136
+
setCloseConfirmState(true);
137
+
} else {
138
+
setComposerState({ kind: "closed" });
139
+
}
140
+
}
141
+
142
+
function handleConfirmClose() {
143
+
setComposerState({ kind: "closed" });
144
+
setCloseConfirmState(false);
145
+
setPostText("");
146
+
}
147
+
148
+
return (
149
+
<>
150
+
<Dialog.Root
151
+
open={composerState.kind !== "closed"}
152
+
onOpenChange={(open) => {
153
+
if (!open) handleAttemptClose();
154
+
}}
155
+
>
156
+
<Dialog.Portal>
157
+
<Dialog.Overlay className="disablegutter fixed inset-0 z-50 bg-black/40 dark:bg-black/50 data-[state=open]:animate-fadeIn" />
158
+
159
+
<Dialog.Content className="fixed overflow-y-auto gutter inset-0 z-50 flex items-start justify-center pt-10 sm:pt-20 pb-[50dvh] sm:pb-[50dvh]">
160
+
<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">
161
+
<div className="flex flex-row justify-between p-2">
162
+
<Dialog.Close asChild>
163
+
<button
164
+
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"
165
+
disabled={posting}
166
+
aria-label="Close"
167
+
onClick={handleAttemptClose}
168
+
>
169
+
<svg
170
+
xmlns="http://www.w3.org/2000/svg"
171
+
width="20"
172
+
height="20"
173
+
viewBox="0 0 24 24"
174
+
fill="none"
175
+
stroke="currentColor"
176
+
strokeWidth="2.5"
177
+
strokeLinecap="round"
178
+
strokeLinejoin="round"
179
+
>
180
+
<line x1="18" y1="6" x2="6" y2="18"></line>
181
+
<line x1="6" y1="6" x2="18" y2="18"></line>
182
+
</svg>
183
+
</button>
184
+
</Dialog.Close>
185
+
186
+
<div className="flex-1" />
187
+
<div className="flex items-center gap-4">
188
+
<span
189
+
className={`text-sm ${charsLeft < 0 ? "text-red-500" : "text-gray-500"}`}
190
+
>
191
+
{charsLeft}
192
+
</span>
193
+
<Button
194
+
onClick={handlePost}
195
+
disabled={isPostButtonDisabled}
196
+
>
197
+
{posting ? "Posting..." : "Post"}
198
+
</Button>
199
+
</div>
200
+
</div>
201
+
202
+
{postSuccess ? (
203
+
<div className="flex flex-col items-center justify-center py-16">
204
+
<span className="text-gray-500 text-6xl mb-4">โ</span>
205
+
<span className="text-xl font-bold text-black dark:text-white">
206
+
Posted!
207
+
</span>
208
+
</div>
209
+
) : (
210
+
<div className="px-4">
211
+
{composerState.kind === "reply" && (
212
+
<div className="mb-1 -mx-4">
213
+
{isParentLoading ? (
214
+
<div className="text-sm text-gray-500 animate-pulse">
215
+
Loading parent post...
216
+
</div>
217
+
) : parentUri ? (
218
+
<UniversalPostRendererATURILoader
219
+
atUri={parentUri}
220
+
bottomReplyLine
221
+
bottomBorder={false}
222
+
/>
223
+
) : (
224
+
<div className="text-sm text-red-500 rounded-lg border border-red-500/50 p-3">
225
+
Could not load parent post.
226
+
</div>
227
+
)}
228
+
</div>
229
+
)}
230
+
231
+
<div className="flex w-full gap-1 flex-col">
232
+
<ProfileThing agent={agent} large />
233
+
<div className="flex pl-[50px]">
234
+
<AutoGrowTextarea
235
+
className="w-full text-lg bg-transparent focus:outline-none resize-none placeholder:text-gray-500 text-black dark:text-white pb-2"
236
+
rows={5}
237
+
placeholder={getPlaceholder()}
238
+
value={postText}
239
+
onChange={(e) => setPostText(e.target.value)}
240
+
disabled={posting}
241
+
autoFocus
242
+
/>
243
+
</div>
244
+
</div>
245
+
246
+
{composerState.kind === "quote" && (
247
+
<div className="mb-4 ml-[50px] rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
248
+
{isParentLoading ? (
249
+
<div className="text-sm text-gray-500 animate-pulse">
250
+
Loading parent post...
251
+
</div>
252
+
) : parentUri ? (
253
+
<UniversalPostRendererATURILoader
254
+
atUri={parentUri}
255
+
isQuote
256
+
/>
257
+
) : (
258
+
<div className="text-sm text-red-500 rounded-lg border border-red-500/50 p-3">
259
+
Could not load parent post.
260
+
</div>
261
+
)}
262
+
</div>
263
+
)}
264
+
265
+
{postError && (
266
+
<div className="text-red-500 text-sm my-2 text-center">
267
+
{postError}
268
+
</div>
269
+
)}
270
+
</div>
271
+
)}
272
+
</div>
273
+
</Dialog.Content>
274
+
</Dialog.Portal>
275
+
</Dialog.Root>
276
+
277
+
{/* Close confirmation dialog */}
278
+
<Dialog.Root open={closeConfirmState} onOpenChange={setCloseConfirmState}>
279
+
<Dialog.Portal>
280
+
281
+
<Dialog.Overlay className="disablegutter fixed inset-0 z-50 bg-black/40 dark:bg-black/50 data-[state=open]:animate-fadeIn" />
282
+
283
+
<Dialog.Content className="fixed gutter inset-0 z-50 flex items-start justify-center pt-30 sm:pt-40">
284
+
<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-md relative mx-4 py-6">
285
+
<div className="text-xl mb-4 text-center">
286
+
Discard your post?
287
+
</div>
288
+
<div className="text-md mb-4 text-center">
289
+
You will lose your draft
290
+
</div>
291
+
<div className="flex justify-end gap-2 px-6">
292
+
<Button
293
+
onClick={handleConfirmClose}
294
+
>
295
+
Discard
296
+
</Button>
297
+
<Button
298
+
variant={"outlined"}
299
+
onClick={() => setCloseConfirmState(false)}
300
+
>
301
+
Cancel
302
+
</Button>
303
+
</div>
304
+
</div>
305
+
</Dialog.Content>
306
+
</Dialog.Portal>
307
+
</Dialog.Root>
308
+
</>
309
+
);
310
+
}
311
+
312
+
function AutoGrowTextarea({
313
+
value,
314
+
className,
315
+
onChange,
316
+
...props
317
+
}: React.DetailedHTMLProps<
318
+
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
319
+
HTMLTextAreaElement
320
+
>) {
321
+
const ref = useRef<HTMLTextAreaElement>(null);
322
+
323
+
useEffect(() => {
324
+
const el = ref.current;
325
+
if (!el) return;
326
+
el.style.height = "auto";
327
+
el.style.height = el.scrollHeight + "px";
328
+
}, [value]);
329
+
330
+
return (
331
+
<textarea
332
+
ref={ref}
333
+
className={className}
334
+
value={value}
335
+
onChange={onChange}
336
+
{...props}
337
+
/>
338
+
);
339
+
}
+33
src/components/Header.tsx
+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
+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
+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
+
);
+192
-60
src/components/Login.tsx
+192
-60
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";
9
+
10
+
import { Button } from "./radix-m3-rd/Button";
5
11
6
12
// --- 1. The Main Component (Orchestrator with `compact` prop) ---
7
-
export default function Login({ compact = false }: { compact?: boolean }) {
13
+
export default function Login({
14
+
compact = false,
15
+
popup = false,
16
+
}: {
17
+
compact?: boolean;
18
+
popup?: boolean;
19
+
}) {
8
20
const { status, agent, logout } = useAuth();
9
21
10
22
// Loading state can be styled differently based on the prop
···
14
26
className={
15
27
compact
16
28
? "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]"
29
+
: "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
30
}
19
31
>
20
32
<span
···
33
45
// Large view
34
46
if (!compact) {
35
47
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">
48
+
<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
49
<div className="flex flex-col items-center justify-center text-center">
38
50
<p className="text-lg font-semibold mb-4 text-gray-800 dark:text-gray-100">
39
51
You are logged in!
40
52
</p>
41
53
<ProfileThing agent={agent} large />
42
-
<button
54
+
<Button
43
55
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"
56
+
className="mt-4"
45
57
>
46
58
Log out
47
-
</button>
59
+
</Button>
48
60
</div>
49
61
</div>
50
62
);
···
67
79
if (!compact) {
68
80
// Large view renders the form directly in the card
69
81
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">
82
+
<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
83
<UnifiedLoginForm />
72
84
</div>
73
85
);
74
86
}
75
87
76
88
// Compact view renders a button that toggles the form in a dropdown
77
-
return <CompactLoginButton />;
89
+
return <CompactLoginButton popup={popup} />;
78
90
}
79
91
80
92
// --- 2. The Reusable, Self-Contained Login Form Component ---
···
83
95
84
96
return (
85
97
<div>
86
-
<div className="flex border-b border-gray-200 dark:border-gray-700 mb-4">
98
+
<div className="flex bg-gray-300 rounded-full dark:bg-gray-700 mb-4">
87
99
<TabButton
88
100
label="OAuth"
89
101
active={mode === "oauth"}
···
103
115
// --- 3. Helper components for layouts, forms, and UI ---
104
116
105
117
// A new component to contain the logic for the compact dropdown
106
-
const CompactLoginButton = () => {
118
+
const CompactLoginButton = ({ popup }: { popup?: boolean }) => {
107
119
const [showForm, setShowForm] = useState(false);
108
120
const formRef = useRef<HTMLDivElement>(null);
109
121
···
125
137
<div className="relative" ref={formRef}>
126
138
<button
127
139
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"
140
+
className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded-full px-3 py-1 font-medium transition-colors"
129
141
>
130
142
Log in
131
143
</button>
132
144
{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">
145
+
<div
146
+
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`}
147
+
>
134
148
<UnifiedLoginForm />
135
149
</div>
136
150
)}
···
138
152
);
139
153
};
140
154
141
-
const TabButton = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void; }) => (
155
+
const TabButton = ({
156
+
label,
157
+
active,
158
+
onClick,
159
+
}: {
160
+
label: string;
161
+
active: boolean;
162
+
onClick: () => void;
163
+
}) => (
142
164
<button
143
165
onClick={onClick}
144
-
className={`px-4 py-2 text-sm font-medium transition-colors ${
166
+
className={`px-4 py-2 text-sm font-medium transition-colors rounded-full flex-1 ${
145
167
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"
168
+
? "text-gray-50 dark:text-gray-200 border-gray-500 bg-gray-400 dark:bg-gray-500"
169
+
: "text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200"
148
170
}`}
149
171
>
150
172
{label}
···
169
191
};
170
192
return (
171
193
<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>
194
+
<p className="text-xs text-gray-500 dark:text-gray-400">
195
+
Sign in with AT. Your password is never shared.
196
+
</p>
197
+
{/* <input
198
+
type="text"
199
+
placeholder="handle.bsky.social"
200
+
value={handle}
201
+
onChange={(e) => setHandle(e.target.value)}
202
+
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"
203
+
/> */}
204
+
<div className="flex flex-col gap-3">
205
+
<div className="m3input-field m3input-label m3input-border size-md flex-1">
206
+
<input
207
+
type="text"
208
+
placeholder=" "
209
+
value={handle}
210
+
onChange={(e) => setHandle(e.target.value)}
211
+
/>
212
+
<label>AT Handle</label>
213
+
</div>
214
+
<button
215
+
type="submit"
216
+
className="bg-gray-600 hover:bg-gray-700 text-white rounded-full px-4 py-2 font-medium text-sm transition-colors"
217
+
>
218
+
Log in
219
+
</button>
220
+
</div>
175
221
</form>
176
222
);
177
223
};
···
201
247
202
248
return (
203
249
<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" />
250
+
<p className="text-xs text-red-500 dark:text-red-400">
251
+
Warning: Less secure. Use an App Password.
252
+
</p>
253
+
{/* <input
254
+
type="text"
255
+
placeholder="handle.bsky.social"
256
+
value={user}
257
+
onChange={(e) => setUser(e.target.value)}
258
+
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"
259
+
autoComplete="username"
260
+
/>
261
+
<input
262
+
type="password"
263
+
placeholder="App Password"
264
+
value={password}
265
+
onChange={(e) => setPassword(e.target.value)}
266
+
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"
267
+
autoComplete="current-password"
268
+
/>
269
+
<input
270
+
type="text"
271
+
placeholder="PDS (e.g., bsky.social)"
272
+
value={serviceURL}
273
+
onChange={(e) => setServiceURL(e.target.value)}
274
+
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"
275
+
/> */}
276
+
<div className="m3input-field m3input-label m3input-border size-md flex-1">
277
+
<input
278
+
type="text"
279
+
placeholder=" "
280
+
value={user}
281
+
onChange={(e) => setUser(e.target.value)}
282
+
/>
283
+
<label>AT Handle</label>
284
+
</div>
285
+
<div className="m3input-field m3input-label m3input-border size-md flex-1">
286
+
<input
287
+
type="text"
288
+
placeholder=" "
289
+
value={password}
290
+
onChange={(e) => setPassword(e.target.value)}
291
+
/>
292
+
<label>App Password</label>
293
+
</div>
294
+
<div className="m3input-field m3input-label m3input-border size-md flex-1">
295
+
<input
296
+
type="text"
297
+
placeholder=" "
298
+
value={serviceURL}
299
+
onChange={(e) => setServiceURL(e.target.value)}
300
+
/>
301
+
<label>PDS</label>
302
+
</div>
208
303
{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>
304
+
<button
305
+
type="submit"
306
+
className="bg-gray-600 hover:bg-gray-700 text-white rounded-full px-4 py-2 font-medium text-sm transition-colors"
307
+
>
308
+
Log in
309
+
</button>
210
310
</form>
211
311
);
212
312
};
213
313
214
314
// --- 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);
315
+
export const ProfileThing = ({
316
+
agent,
317
+
large = false,
318
+
}: {
319
+
agent: Agent | null;
320
+
large?: boolean;
321
+
}) => {
322
+
const did = ((agent as AtpAgent)?.session?.did ??
323
+
(agent as AtpAgent)?.assertDid ??
324
+
agent?.did) as string | undefined;
325
+
const { data: identity } = useQueryIdentity(did);
326
+
const { data: profiledata } = useQueryProfile(
327
+
`at://${did}/app.bsky.actor.profile/self`
328
+
);
329
+
const profile = profiledata?.value;
217
330
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]);
331
+
const [imgcdn] = useAtom(imgCDNAtom)
229
332
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>
333
+
function getAvatarUrl(p: typeof profile) {
334
+
const link = p?.avatar?.ref?.["$link"];
335
+
if (!link || !did) return null;
336
+
return `https://${imgcdn}/img/avatar/plain/${did}/${link}@jpeg`;
337
+
}
338
+
339
+
if (!profiledata) {
340
+
return (
341
+
// Skeleton loader
342
+
<div
343
+
className={`flex items-center gap-2.5 animate-pulse ${large ? "mb-1" : ""}`}
344
+
>
345
+
<div
346
+
className={`rounded-full bg-gray-300 dark:bg-gray-700 ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`}
347
+
/>
348
+
<div className="flex flex-col gap-2">
349
+
<div
350
+
className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? "h-4 w-28" : "h-3 w-20"}`}
351
+
/>
352
+
<div
353
+
className={`bg-gray-300 dark:bg-gray-700 rounded ${large ? "h-4 w-20" : "h-3 w-16"}`}
354
+
/>
355
+
</div>
356
+
</div>
357
+
);
358
+
}
359
+
360
+
return (
361
+
<div
362
+
className={`flex flex-row items-center gap-2.5 ${large ? "mb-1" : ""}`}
363
+
>
364
+
<img
365
+
src={getAvatarUrl(profile) ?? undefined}
366
+
alt="avatar"
367
+
className={`object-cover rounded-full ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`}
368
+
/>
369
+
<div className="flex flex-col items-start text-left">
370
+
<div
371
+
className={`font-medium ${large ? "text-gray-800 dark:text-gray-100 text-md" : "text-gray-800 dark:text-gray-100 text-sm"}`}
372
+
>
373
+
{profile?.displayName}
374
+
</div>
375
+
<div
376
+
className={` ${large ? "text-gray-500 dark:text-gray-400 text-sm" : "text-gray-500 dark:text-gray-400 text-xs"}`}
377
+
>
378
+
@{identity?.handle}
249
379
</div>
250
-
);
251
-
};
380
+
</div>
381
+
</div>
382
+
);
383
+
};
+6
src/components/Star.tsx
+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
+
}
+811
-493
src/components/UniversalPostRenderer.tsx
+811
-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
+
<div className=" flex flex-col justify-start">
1561
+
<FollowButton targetdidorhandle={post.author.did} />
1562
+
</div>
1563
+
</div>
1564
+
</div>
1565
+
<div className="flex flex-col gap-3">
1566
+
<div>
1567
+
<div className="text-gray-900 dark:text-gray-100 font-medium text-md">
1568
+
{post.author.displayName || post.author.handle}{" "}
1569
+
</div>
1570
+
<div className="text-gray-500 dark:text-gray-400 text-md flex flex-row gap-1">
1571
+
<Mutual targetdidorhandle={post.author.did} />@{post.author.handle}{" "}
1572
+
</div>
1573
+
</div>
1574
+
{uprrrsauthor?.description && (
1575
+
<div className="text-gray-700 dark:text-gray-300 text-sm text-left break-words line-clamp-3">
1576
+
{uprrrsauthor.description}
1577
+
</div>
1578
+
)}
1579
+
{/* <div className="flex gap-4">
1580
+
<div className="flex gap-1">
1581
+
<div className="font-medium text-gray-900 dark:text-gray-100">
1582
+
0
1583
+
</div>
1584
+
<div className="text-gray-500 dark:text-gray-400">
1585
+
Following
1586
+
</div>
1587
+
</div>
1588
+
<div className="flex gap-1">
1589
+
<div className="font-medium text-gray-900 dark:text-gray-100">
1590
+
2,900
1591
+
</div>
1592
+
<div className="text-gray-500 dark:text-gray-400">
1593
+
Followers
1594
+
</div>
1595
+
</div>
1596
+
</div> */}
1597
+
</div>
1598
+
</div>
1599
+
1600
+
{/* <HoverCard.Arrow className="fill-gray-50 dark:fill-gray-900" /> */}
1601
+
</HoverCard.Content>
1602
+
</HoverCard.Portal>
1603
+
</HoverCard.Root>
1604
+
1605
+
<div style={{ display: "flex", alignItems: "flex-start", zIndex: 2 }}>
1606
+
<div
1607
+
style={{
1608
+
display: "flex",
1609
+
flexDirection: "column",
1610
+
alignSelf: "stretch",
1611
+
alignItems: "center",
1612
+
overflow: "hidden",
1613
+
width: expanded || isQuote ? 0 : "auto",
1614
+
marginRight: expanded || isQuote ? 0 : 12,
1615
+
}}
1616
+
>
1617
+
{/* dummy for later use */}
1618
+
<div style={{ width: 42, height: 42 + 6, minHeight: 42 + 6 }} />
1619
+
{/* reply line !!!! bottomReplyLine */}
1620
+
{bottomReplyLine && (
1621
+
<div
1622
+
style={{
1623
+
width: 2,
1624
+
height: "100%",
1625
+
//background: theme.textSecondary,
1626
+
opacity: 0.5,
1627
+
// no flex here
1628
+
//color: "Red",
1629
+
//zIndex: 99
1630
+
}}
1631
+
className="bg-gray-500 dark:bg-gray-400"
1632
+
/>
1633
+
)}
1634
+
{/* <div
1309
1635
layout
1310
1636
transition={{ duration: 0.2 }}
1311
1637
animate={{ height: expanded ? 0 : '100%' }}
···
1315
1641
// no flex here
1316
1642
}}
1317
1643
/> */}
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
-
>
1644
+
</div>
1645
+
<div style={{ flex: 1, maxWidth: "100%" }}>
1332
1646
<div
1333
1647
style={{
1334
1648
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",
1649
+
flexDirection: "row",
1650
+
alignItems: "center",
1651
+
flexWrap: "nowrap",
1652
+
maxWidth: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`,
1653
+
width: `calc(100% - ${!expanded ? (isQuote ? 26 : 0) : 54}px)`,
1654
+
marginLeft: !expanded ? (isQuote ? 26 : 0) : 54,
1655
+
marginBottom: !expanded ? 4 : 6,
1346
1656
}}
1347
1657
>
1348
-
<span
1658
+
<div
1349
1659
style={{
1350
1660
display: "flex",
1351
-
fontWeight: 700,
1352
-
fontSize: 16,
1661
+
//overflow: "hidden", // hey why is overflow hidden unapplied
1353
1662
overflow: "hidden",
1354
1663
textOverflow: "ellipsis",
1355
-
whiteSpace: "nowrap",
1356
1664
flexShrink: 1,
1357
-
minWidth: 0,
1358
-
gap: 4,
1359
-
alignItems: "center",
1360
-
//color: theme.text,
1665
+
flexGrow: 1,
1666
+
flexBasis: 0,
1667
+
width: 0,
1668
+
gap: expanded ? 0 : 6,
1669
+
alignItems: expanded ? "flex-start" : "center",
1670
+
flexDirection: expanded ? "column" : "row",
1671
+
height: expanded ? 42 : "1rem",
1361
1672
}}
1362
-
className="text-gray-900 dark:text-gray-100"
1363
1673
>
1364
-
{/* verified checkmark */}
1365
-
{post.author.displayName || post.author.handle}{" "}
1366
-
{post.author.verification?.verifiedStatus == "valid" && (
1367
-
<MdiVerified />
1368
-
)}
1369
-
</span>
1674
+
<span
1675
+
style={{
1676
+
display: "flex",
1677
+
fontWeight: 700,
1678
+
fontSize: 16,
1679
+
overflow: "hidden",
1680
+
textOverflow: "ellipsis",
1681
+
whiteSpace: "nowrap",
1682
+
flexShrink: 1,
1683
+
minWidth: 0,
1684
+
gap: 4,
1685
+
alignItems: "center",
1686
+
//color: theme.text,
1687
+
}}
1688
+
className="text-gray-900 dark:text-gray-100"
1689
+
>
1690
+
{/* verified checkmark */}
1691
+
{post.author.displayName || post.author.handle}{" "}
1692
+
{post.author.verification?.verifiedStatus == "valid" && (
1693
+
<MdiVerified />
1694
+
)}
1695
+
</span>
1370
1696
1371
-
<span
1697
+
<span
1698
+
style={{
1699
+
//color: theme.textSecondary,
1700
+
fontSize: 16,
1701
+
overflowX: "hidden",
1702
+
textOverflow: "ellipsis",
1703
+
whiteSpace: "nowrap",
1704
+
flexShrink: 1,
1705
+
flexGrow: 0,
1706
+
minWidth: 0,
1707
+
}}
1708
+
className="text-gray-500 dark:text-gray-400"
1709
+
>
1710
+
@{post.author.handle}
1711
+
</span>
1712
+
</div>
1713
+
<div
1372
1714
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,
1715
+
display: "flex",
1716
+
alignItems: "center",
1717
+
height: "1rem",
1381
1718
}}
1382
-
className="text-gray-500 dark:text-gray-400"
1383
1719
>
1384
-
@{post.author.handle}
1385
-
</span>
1720
+
<span
1721
+
style={{
1722
+
//color: theme.textSecondary,
1723
+
fontSize: 16,
1724
+
marginLeft: 8,
1725
+
whiteSpace: "nowrap",
1726
+
flexShrink: 0,
1727
+
maxWidth: "100%",
1728
+
}}
1729
+
className="text-gray-500 dark:text-gray-400"
1730
+
>
1731
+
ยท {/* time placeholder */}
1732
+
{shortTimeAgo(post.indexedAt)}
1733
+
</span>
1734
+
</div>
1386
1735
</div>
1387
-
<div
1388
-
style={{
1389
-
display: "flex",
1390
-
alignItems: "center",
1391
-
height: "1rem",
1392
-
}}
1393
-
>
1394
-
<span
1736
+
{/* reply indicator */}
1737
+
{!!feedviewpostreplyhandle && (
1738
+
<div
1395
1739
style={{
1740
+
display: "flex",
1741
+
borderRadius: 12,
1742
+
paddingBottom: 2,
1743
+
fontSize: 14,
1744
+
justifyContent: "flex-start",
1396
1745
//color: theme.textSecondary,
1397
-
fontSize: 16,
1398
-
marginLeft: 8,
1399
-
whiteSpace: "nowrap",
1400
-
flexShrink: 0,
1401
-
maxWidth: "100%",
1746
+
gap: 4,
1747
+
alignItems: "center",
1748
+
//marginLeft: 36,
1749
+
height:
1750
+
!(expanded || isQuote) && !!feedviewpostreplyhandle
1751
+
? "1rem"
1752
+
: 0,
1753
+
opacity:
1754
+
!(expanded || isQuote) && !!feedviewpostreplyhandle ? 1 : 0,
1402
1755
}}
1403
1756
className="text-gray-500 dark:text-gray-400"
1404
1757
>
1405
-
ยท {/* time placeholder */}
1406
-
{shortTimeAgo(post.indexedAt)}
1407
-
</span>
1408
-
</div>
1409
-
</div>
1410
-
{/* reply indicator */}
1411
-
{!!feedviewpostreplyhandle && (
1758
+
<MdiReply /> Reply to @{feedviewpostreplyhandle}
1759
+
</div>
1760
+
)}
1412
1761
<div
1413
1762
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,
1763
+
fontSize: 16,
1764
+
marginBottom: !post.embed /*|| depth > 0*/ ? 0 : 8,
1765
+
whiteSpace: "pre-wrap",
1766
+
textAlign: "left",
1767
+
overflowWrap: "anywhere",
1768
+
wordBreak: "break-word",
1769
+
//color: theme.text,
1429
1770
}}
1430
-
className="text-gray-500 dark:text-gray-400"
1771
+
className="text-gray-900 dark:text-gray-100"
1431
1772
>
1432
-
<MdiReply /> Reply to @{feedviewpostreplyhandle}
1773
+
{fedi ? (
1774
+
<>
1775
+
<span
1776
+
className="dangerousFediContent"
1777
+
dangerouslySetInnerHTML={{
1778
+
__html: DOMPurify.sanitize(fedi),
1779
+
}}
1780
+
/>
1781
+
</>
1782
+
) : (
1783
+
<>
1784
+
{renderTextWithFacets({
1785
+
text: (post.record as { text?: string }).text ?? "",
1786
+
facets: (post.record.facets as Facet[]) ?? [],
1787
+
navigate: navigate,
1788
+
})}
1789
+
</>
1790
+
)}
1433
1791
</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
1792
+
{post.embed && depth < 1 ? (
1793
+
<PostEmbeds
1794
+
embed={post.embed}
1795
+
//moderation={moderation}
1796
+
viewContext={PostEmbedViewContext.Feed}
1797
+
salt={salt}
1798
+
navigate={navigate}
1799
+
postid={{ did: post.author.did, rkey: parsed.rkey }}
1800
+
nopics={nopics}
1801
+
lightboxCallback={lightboxCallback}
1802
+
/>
1803
+
) : null}
1804
+
{post.embed && depth > 0 && (
1805
+
/* pretty bad hack imo. its trying to sync up with how the embed shim doesnt
1465
1806
hydrate embeds this deep but the connection here is implicit
1466
1807
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 && (
1808
+
<>
1809
+
<div className="border-gray-300 dark:border-gray-800 p-3 rounded-xl border italic text-gray-400 text-[14px]">
1810
+
(there is an embed here thats too deep to render)
1811
+
</div>
1812
+
</>
1813
+
)}
1814
+
<div style={{ paddingTop: post.embed && depth < 1 ? 4 : 0 }}>
1815
+
<>
1816
+
{expanded && (
1817
+
<div
1818
+
style={{
1819
+
overflow: "hidden",
1820
+
//color: theme.textSecondary,
1821
+
fontSize: 14,
1822
+
display: "flex",
1823
+
borderBottomStyle: "solid",
1824
+
//borderBottomColor: theme.border,
1825
+
//background: "#f00",
1826
+
// height: "1rem",
1827
+
paddingTop: 4,
1828
+
paddingBottom: 8,
1829
+
borderBottomWidth: 1,
1830
+
marginBottom: 8,
1831
+
}} // important for height animation
1832
+
className="text-gray-500 dark:text-gray-400 border-gray-200 dark:border-gray-800 was7"
1833
+
>
1834
+
{fullDateTimeFormat(post.indexedAt)}
1835
+
</div>
1836
+
)}
1837
+
</>
1838
+
{!isQuote && (
1476
1839
<div
1477
1840
style={{
1478
-
overflow: "hidden",
1479
-
//color: theme.textSecondary,
1480
-
fontSize: 14,
1481
1841
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();
1842
+
gap: 32,
1843
+
paddingTop: 8,
1844
+
//color: theme.textSecondary,
1845
+
fontSize: 15,
1846
+
justifyContent: "space-between",
1847
+
//background: "#0f0",
1517
1848
}}
1518
-
style={{
1519
-
...btnstyle,
1520
-
...(hasRetweeted ? { color: "#5CEFAA" } : {}),
1521
-
}}
1849
+
className="text-gray-500 dark:text-gray-400"
1522
1850
>
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 }}>
1851
+
<HitSlopButton
1852
+
onClick={() => {
1853
+
setComposerPost({ kind: "reply", parent: post.uri });
1854
+
}}
1855
+
style={{
1856
+
...btnstyle,
1857
+
}}
1858
+
>
1859
+
<MdiCommentOutline />
1860
+
{post.replyCount}
1861
+
</HitSlopButton>
1862
+
<DropdownMenu.Root modal={false}>
1863
+
<DropdownMenu.Trigger asChild>
1864
+
<div
1865
+
style={{
1866
+
...btnstyle,
1867
+
...(hasRetweeted ? { color: "#5CEFAA" } : {}),
1868
+
}}
1869
+
aria-label="Repost or quote post"
1870
+
>
1871
+
{hasRetweeted ? <MdiRepeatGreen /> : <MdiRepeat />}
1872
+
{post.repostCount ?? 0}
1873
+
</div>
1874
+
</DropdownMenu.Trigger>
1875
+
1876
+
<DropdownMenu.Portal>
1877
+
<DropdownMenu.Content
1878
+
align="start"
1879
+
sideOffset={5}
1880
+
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"
1881
+
>
1882
+
<DropdownMenu.Item
1883
+
onSelect={repostOrUnrepostPost}
1884
+
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"
1885
+
>
1886
+
<MdiRepeat
1887
+
className={hasRetweeted ? "text-green-400" : ""}
1888
+
/>
1889
+
<span>{hasRetweeted ? "Undo Repost" : "Repost"}</span>
1890
+
</DropdownMenu.Item>
1891
+
1892
+
<DropdownMenu.Item
1893
+
onSelect={() => {
1894
+
setComposerPost({
1895
+
kind: "quote",
1896
+
subject: post.uri,
1897
+
});
1898
+
}}
1899
+
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"
1900
+
>
1901
+
{/* You might want a specific quote icon here */}
1902
+
<MdiCommentOutline />
1903
+
<span>Quote</span>
1904
+
</DropdownMenu.Item>
1905
+
</DropdownMenu.Content>
1906
+
</DropdownMenu.Portal>
1907
+
</DropdownMenu.Root>
1539
1908
<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
-
}
1909
+
onClick={() => {
1910
+
likeOrUnlikePost();
1553
1911
}}
1554
1912
style={{
1555
1913
...btnstyle,
1914
+
...(hasLiked ? { color: "#EC4899" } : {}),
1556
1915
}}
1557
1916
>
1558
-
<MdiShareVariant />
1917
+
{hasLiked ? <MdiCardsHeart /> : <MdiCardsHeartOutline />}
1918
+
{(post.likeCount || 0) + (hasLiked ? 1 : 0)}
1559
1919
</HitSlopButton>
1560
-
<span style={btnstyle}>
1561
-
<MdiMoreHoriz />
1562
-
</span>
1920
+
<div style={{ display: "flex", gap: 8 }}>
1921
+
<HitSlopButton
1922
+
onClick={async (e) => {
1923
+
e.stopPropagation();
1924
+
try {
1925
+
await navigator.clipboard.writeText(
1926
+
"https://bsky.app" +
1927
+
"/profile/" +
1928
+
post.author.handle +
1929
+
"/post/" +
1930
+
post.uri.split("/").pop()
1931
+
);
1932
+
} catch (_e) {
1933
+
// idk
1934
+
}
1935
+
}}
1936
+
style={{
1937
+
...btnstyle,
1938
+
}}
1939
+
>
1940
+
<MdiShareVariant />
1941
+
</HitSlopButton>
1942
+
<span style={btnstyle}>
1943
+
<MdiMoreHoriz />
1944
+
</span>
1945
+
</div>
1563
1946
</div>
1564
-
</div>
1565
-
)}
1947
+
)}
1948
+
</div>
1949
+
<div
1950
+
style={{
1951
+
//height: bottomReplyLine ? 16 : 0
1952
+
height: isQuote ? 12 : 16,
1953
+
}}
1954
+
/>
1566
1955
</div>
1567
-
<div
1568
-
style={{
1569
-
//height: bottomReplyLine ? 16 : 0
1570
-
height: isQuote ? 12 : 16,
1571
-
}}
1572
-
/>
1573
1956
</div>
1574
1957
</div>
1575
1958
</div>
···
1661
2044
viewContext,
1662
2045
salt,
1663
2046
navigate,
2047
+
postid,
2048
+
nopics,
2049
+
lightboxCallback,
1664
2050
}: {
1665
2051
embed?: Embed;
1666
2052
moderation?: ModerationDecision;
···
1669
2055
viewContext?: PostEmbedViewContext;
1670
2056
salt: string;
1671
2057
navigate: (_: any) => void;
2058
+
postid?: { did: string; rkey: string };
2059
+
nopics?: boolean;
2060
+
lightboxCallback?: (d: LightboxProps) => void;
1672
2061
}) {
1673
-
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
2062
+
//const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
2063
+
function setLightboxIndex(number: number) {
2064
+
navigate({
2065
+
to: "/profile/$did/post/$rkey/image/$i",
2066
+
params: {
2067
+
did: postid?.did,
2068
+
rkey: postid?.rkey,
2069
+
i: number.toString(),
2070
+
},
2071
+
});
2072
+
}
1674
2073
if (
1675
2074
AppBskyEmbedRecordWithMedia.isView(embed) &&
1676
2075
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
···
1704
2103
viewContext={viewContext}
1705
2104
salt={salt}
1706
2105
navigate={navigate}
2106
+
postid={postid}
2107
+
nopics={nopics}
2108
+
lightboxCallback={lightboxCallback}
1707
2109
/>
1708
2110
{/* padding empty div of 8px height */}
1709
2111
<div style={{ height: 12 }} />
···
1717
2119
//boxShadow: theme.cardShadow,
1718
2120
overflow: "hidden",
1719
2121
}}
1720
-
className="shadow border border-gray-200 dark:border-gray-700"
2122
+
className="shadow border border-gray-200 dark:border-gray-800 was7"
1721
2123
>
1722
2124
<UniversalPostRenderer
1723
2125
post={post}
···
1834
2236
//boxShadow: theme.cardShadow,
1835
2237
overflow: "hidden",
1836
2238
}}
1837
-
className="shadow border border-gray-200 dark:border-gray-700"
2239
+
className="shadow border border-gray-200 dark:border-gray-800 was7"
1838
2240
>
1839
2241
<UniversalPostRenderer
1840
2242
post={post}
···
1878
2280
src: img.fullsize,
1879
2281
alt: img.alt,
1880
2282
}));
2283
+
console.log("rendering images");
2284
+
if (lightboxCallback) {
2285
+
lightboxCallback({ images: lightboxImages });
2286
+
console.log("rendering images");
2287
+
}
2288
+
2289
+
if (nopics) return;
1881
2290
1882
2291
if (images.length > 0) {
1883
2292
// const items = embed.images.map(img => ({
···
1907
2316
//border: `1px solid ${theme.border}`,
1908
2317
overflow: "hidden",
1909
2318
}}
1910
-
className="border border-gray-200 dark:border-gray-700 bg-gray-200 dark:bg-gray-900"
2319
+
className="border border-gray-200 dark:border-gray-800 was7 bg-gray-200 dark:bg-gray-900"
1911
2320
>
1912
-
{lightboxIndex !== null && (
2321
+
{/* {lightboxIndex !== null && (
1913
2322
<Lightbox
1914
2323
images={lightboxImages}
1915
2324
index={lightboxIndex}
1916
2325
onClose={() => setLightboxIndex(null)}
1917
2326
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2327
+
post={postid}
1918
2328
/>
1919
-
)}
2329
+
)} */}
1920
2330
<img
1921
2331
src={image.fullsize}
1922
2332
alt={image.alt}
···
1947
2357
overflow: "hidden",
1948
2358
//border: `1px solid ${theme.border}`,
1949
2359
}}
1950
-
className="border border-gray-200 dark:border-gray-700"
2360
+
className="border border-gray-200 dark:border-gray-800 was7"
1951
2361
>
1952
-
{lightboxIndex !== null && (
2362
+
{/* {lightboxIndex !== null && (
1953
2363
<Lightbox
1954
2364
images={lightboxImages}
1955
2365
index={lightboxIndex}
1956
2366
onClose={() => setLightboxIndex(null)}
1957
2367
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2368
+
post={postid}
1958
2369
/>
1959
-
)}
2370
+
)} */}
1960
2371
{images.map((img, i) => (
1961
2372
<div
1962
2373
key={i}
···
1996
2407
//border: `1px solid ${theme.border}`,
1997
2408
// height: 240, // fixed height for cropping
1998
2409
}}
1999
-
className="border border-gray-200 dark:border-gray-700"
2410
+
className="border border-gray-200 dark:border-gray-800 was7"
2000
2411
>
2001
-
{lightboxIndex !== null && (
2412
+
{/* {lightboxIndex !== null && (
2002
2413
<Lightbox
2003
2414
images={lightboxImages}
2004
2415
index={lightboxIndex}
2005
2416
onClose={() => setLightboxIndex(null)}
2006
2417
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2418
+
post={postid}
2007
2419
/>
2008
-
)}
2420
+
)} */}
2009
2421
{/* Left: 1:1 */}
2010
2422
<div
2011
2423
style={{ flex: 1, aspectRatio: "1 / 1", position: "relative" }}
···
2080
2492
//border: `1px solid ${theme.border}`,
2081
2493
//aspectRatio: "3 / 2", // overall grid aspect
2082
2494
}}
2083
-
className="border border-gray-200 dark:border-gray-700"
2495
+
className="border border-gray-200 dark:border-gray-800 was7"
2084
2496
>
2085
-
{lightboxIndex !== null && (
2497
+
{/* {lightboxIndex !== null && (
2086
2498
<Lightbox
2087
2499
images={lightboxImages}
2088
2500
index={lightboxIndex}
2089
2501
onClose={() => setLightboxIndex(null)}
2090
2502
onNavigate={(newIndex) => setLightboxIndex(newIndex)}
2503
+
post={postid}
2091
2504
/>
2092
-
)}
2505
+
)} */}
2093
2506
{images.map((img, i) => (
2094
2507
<div
2095
2508
key={i}
···
2180
2593
return <div />;
2181
2594
}
2182
2595
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
2596
function getDomain(url: string) {
2279
2597
try {
2280
2598
const { hostname } = new URL(url);
···
2339
2657
return { start, end, feature: f.features[0] };
2340
2658
});
2341
2659
}
2342
-
function renderTextWithFacets({
2660
+
export function renderTextWithFacets({
2343
2661
text,
2344
2662
facets,
2345
2663
navigate,
···
2502
2820
>
2503
2821
<div
2504
2822
style={containerStyle as React.CSSProperties}
2505
-
className="border border-gray-200 dark:border-gray-700"
2823
+
className="border border-gray-200 dark:border-gray-800 was7"
2506
2824
>
2507
2825
{thumb && (
2508
2826
<div
···
2516
2834
marginBottom: 8,
2517
2835
//borderBottom: `1px solid ${theme.border}`,
2518
2836
}}
2519
-
className="border-b border-gray-200 dark:border-gray-700"
2837
+
className="border-b border-gray-200 dark:border-gray-800 was7"
2520
2838
>
2521
2839
<img
2522
2840
src={thumb}
···
2642
2960
borderRadius: 12,
2643
2961
//border: `1px solid ${theme.border}`,
2644
2962
}}
2645
-
className="border border-gray-200 dark:border-gray-700"
2963
+
className="border border-gray-200 dark:border-gray-800 was7"
2646
2964
onClick={async (e) => {
2647
2965
e.stopPropagation();
2648
2966
setPlaying(true);
···
2683
3001
100 / (aspect ? aspect.width / aspect.height : 16 / 9)
2684
3002
}%`, // 16:9 = 56.25%, 4:3 = 75%
2685
3003
}}
2686
-
className="border border-gray-200 dark:border-gray-700"
3004
+
className="border border-gray-200 dark:border-gray-800 was7"
2687
3005
>
2688
3006
<ReactPlayer
2689
3007
src={url}
+59
src/components/radix-m3-rd/Button.tsx
+59
src/components/radix-m3-rd/Button.tsx
···
1
+
import { Slot } from "@radix-ui/react-slot";
2
+
import clsx from "clsx";
3
+
import * as React from "react";
4
+
5
+
export type ButtonVariant = "filled" | "outlined" | "text" | "secondary";
6
+
export type ButtonSize = "sm" | "md" | "lg";
7
+
8
+
const variantClasses: Record<ButtonVariant, string> = {
9
+
filled:
10
+
"bg-gray-300 text-gray-900 hover:bg-gray-400 dark:bg-gray-600 dark:text-white dark:hover:bg-gray-500",
11
+
secondary:
12
+
"bg-gray-300 text-gray-900 hover:bg-gray-400 dark:bg-gray-600 dark:text-white dark:hover:bg-gray-500",
13
+
outlined:
14
+
"border border-gray-800 text-gray-800 hover:bg-gray-100 dark:border-gray-200 dark:text-gray-200 dark:hover:bg-gray-800/10",
15
+
text: "bg-transparent text-gray-800 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-800/10",
16
+
};
17
+
18
+
const sizeClasses: Record<ButtonSize, string> = {
19
+
sm: "px-3 py-1.5 text-sm",
20
+
md: "px-4 py-2 text-base",
21
+
lg: "px-6 py-3 text-lg",
22
+
};
23
+
24
+
export function Button({
25
+
variant = "filled",
26
+
size = "md",
27
+
asChild = false,
28
+
ref,
29
+
className,
30
+
children,
31
+
...props
32
+
}: {
33
+
variant?: ButtonVariant;
34
+
size?: ButtonSize;
35
+
asChild?: boolean;
36
+
className?: string;
37
+
children?: React.ReactNode;
38
+
ref?: React.Ref<HTMLButtonElement>;
39
+
} & React.ComponentPropsWithoutRef<"button">) {
40
+
const Comp = asChild ? Slot : "button";
41
+
42
+
return (
43
+
<Comp
44
+
ref={ref}
45
+
className={clsx(
46
+
//focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-gray-500 dark:focus:ring-gray-300
47
+
"inline-flex items-center justify-center rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
48
+
variantClasses[variant],
49
+
sizeClasses[size],
50
+
className
51
+
)}
52
+
{...props}
53
+
>
54
+
{children}
55
+
</Comp>
56
+
);
57
+
}
58
+
59
+
Button.displayName = "Button";
+59
-14
src/main.tsx
+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
+
}
+26
-23
src/providers/UnifiedAuthProvider.tsx
+26
-23
src/providers/UnifiedAuthProvider.tsx
···
1
-
// src/providers/UnifiedAuthProvider.tsx
2
-
// Import both Agent and the (soon to be deprecated) AtpAgent
3
1
import { Agent, AtpAgent, type AtpSessionData } from "@atproto/api";
4
2
import {
5
3
type OAuthSession,
···
7
5
TokenRefreshError,
8
6
TokenRevokedError,
9
7
} from "@atproto/oauth-client-browser";
8
+
import { useAtom } from "jotai";
10
9
import React, {
11
10
createContext,
12
11
use,
···
15
14
useState,
16
15
} from "react";
17
16
18
-
import { oauthClient } from "../utils/oauthClient"; // Adjust path if needed
17
+
import { quickAuthAtom } from "~/utils/atoms";
18
+
19
+
import { oauthClient } from "../utils/oauthClient";
19
20
20
-
// Define the unified status and authentication method
21
21
type AuthStatus = "loading" | "signedIn" | "signedOut";
22
22
type AuthMethod = "password" | "oauth" | null;
23
23
24
24
interface AuthContextValue {
25
-
agent: Agent | null; // The agent is typed as the base class `Agent`
25
+
agent: Agent | null;
26
26
status: AuthStatus;
27
27
authMethod: AuthMethod;
28
28
loginWithPassword: (
···
41
41
}: {
42
42
children: React.ReactNode;
43
43
}) => {
44
-
// The state is typed as the base class `Agent`, which accepts both `Agent` and `AtpAgent` instances.
45
44
const [agent, setAgent] = useState<Agent | null>(null);
46
45
const [status, setStatus] = useState<AuthStatus>("loading");
47
46
const [authMethod, setAuthMethod] = useState<AuthMethod>(null);
48
47
const [oauthSession, setOauthSession] = useState<OAuthSession | null>(null);
48
+
const [quickAuth, setQuickAuth] = useAtom(quickAuthAtom);
49
49
50
-
// Unified Initialization Logic
51
50
const initialize = useCallback(async () => {
52
-
// --- 1. Try OAuth initialization first ---
53
51
try {
54
52
const oauthResult = await oauthClient.init();
55
53
if (oauthResult) {
56
54
// /*mass comment*/ console.log("OAuth session restored.");
57
-
const apiAgent = new Agent(oauthResult.session); // Standard Agent
55
+
const apiAgent = new Agent(oauthResult.session);
58
56
setAgent(apiAgent);
59
57
setOauthSession(oauthResult.session);
60
58
setAuthMethod("oauth");
61
59
setStatus("signedIn");
62
-
return; // Success
60
+
setQuickAuth(apiAgent?.did || null);
61
+
return;
63
62
}
64
63
} catch (e) {
65
64
console.error("OAuth init failed, checking password session.", e);
65
+
if (!quickAuth) {
66
+
// quickAuth restoration. if last used method is oauth we immediately call for oauth redo
67
+
// (and set a persistent atom somewhere to not retry again if it failed)
68
+
}
66
69
}
67
70
68
-
// --- 2. If no OAuth, try password-based session using AtpAgent ---
69
71
try {
70
72
const service = localStorage.getItem("service");
71
73
const sessionString = localStorage.getItem("sess");
72
74
73
75
if (service && sessionString) {
74
76
// /*mass comment*/ console.log("Resuming password-based session using AtpAgent...");
75
-
// Use the original, working AtpAgent logic
76
77
const apiAgent = new AtpAgent({ service });
77
78
const session: AtpSessionData = JSON.parse(sessionString);
78
79
await apiAgent.resumeSession(session);
79
80
80
81
// /*mass comment*/ console.log("Password-based session resumed successfully.");
81
-
setAgent(apiAgent); // This works because AtpAgent is a subclass of Agent
82
+
setAgent(apiAgent);
82
83
setAuthMethod("password");
83
84
setStatus("signedIn");
84
-
return; // Success
85
+
setQuickAuth(apiAgent?.did || null);
86
+
return;
85
87
}
86
88
} catch (e) {
87
89
console.error("Failed to resume password-based session.", e);
···
89
91
localStorage.removeItem("service");
90
92
}
91
93
92
-
// --- 3. If neither worked, user is signed out ---
93
94
// /*mass comment*/ console.log("No active session found.");
94
95
setStatus("signedOut");
95
96
setAgent(null);
96
97
setAuthMethod(null);
97
-
}, []);
98
+
// do we want to null it here?
99
+
setQuickAuth(null);
100
+
}, [quickAuth, setQuickAuth]);
98
101
99
102
useEffect(() => {
100
103
const handleOAuthSessionDeleted = (
···
105
108
setOauthSession(null);
106
109
setAuthMethod(null);
107
110
setStatus("signedOut");
111
+
setQuickAuth(null);
108
112
};
109
113
110
114
oauthClient.addEventListener("deleted", handleOAuthSessionDeleted as EventListener);
···
113
117
return () => {
114
118
oauthClient.removeEventListener("deleted", handleOAuthSessionDeleted as EventListener);
115
119
};
116
-
}, [initialize]);
120
+
}, [initialize, setQuickAuth]);
117
121
118
-
// --- Login Methods ---
119
122
const loginWithPassword = async (
120
123
user: string,
121
124
password: string,
···
125
128
setStatus("loading");
126
129
try {
127
130
let sessionData: AtpSessionData | undefined;
128
-
// Use the AtpAgent for its simple login and session persistence
129
131
const apiAgent = new AtpAgent({
130
132
service,
131
133
persistSession: (_evt, sess) => {
···
137
139
if (sessionData) {
138
140
localStorage.setItem("service", service);
139
141
localStorage.setItem("sess", JSON.stringify(sessionData));
140
-
setAgent(apiAgent); // Store the AtpAgent instance in our state
142
+
setAgent(apiAgent);
141
143
setAuthMethod("password");
142
144
setStatus("signedIn");
145
+
setQuickAuth(apiAgent?.did || null);
143
146
// /*mass comment*/ console.log("Successfully logged in with password.");
144
147
} else {
145
148
throw new Error("Session data not persisted after login.");
···
147
150
} catch (e) {
148
151
console.error("Password login failed:", e);
149
152
setStatus("signedOut");
153
+
setQuickAuth(null);
150
154
throw e;
151
155
}
152
156
};
···
161
165
}
162
166
}, [status]);
163
167
164
-
// --- Unified Logout ---
165
168
const logout = useCallback(async () => {
166
169
if (status !== "signedIn" || !agent) return;
167
170
setStatus("loading");
···
173
176
} else if (authMethod === "password") {
174
177
localStorage.removeItem("service");
175
178
localStorage.removeItem("sess");
176
-
// AtpAgent has its own logout methods
177
179
await (agent as AtpAgent).com.atproto.server.deleteSession();
178
180
// /*mass comment*/ console.log("Password-based session deleted.");
179
181
}
···
184
186
setAuthMethod(null);
185
187
setOauthSession(null);
186
188
setStatus("signedOut");
189
+
setQuickAuth(null);
187
190
}
188
-
}, [status, authMethod, agent, oauthSession]);
191
+
}, [status, agent, authMethod, oauthSession, setQuickAuth]);
189
192
190
193
return (
191
194
<AuthContext
+36
-5
src/routeTree.gen.ts
+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
+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
}
+155
-150
src/routes/index.tsx
+155
-150
src/routes/index.tsx
···
1
1
import { createFileRoute } from "@tanstack/react-router";
2
2
import { useAtom } from "jotai";
3
3
import * as React from "react";
4
-
import { useEffect, useLayoutEffect } from "react";
4
+
import { useLayoutEffect, useState } 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
-
agentAtom,
10
-
authedAtom,
11
10
feedScrollPositionsAtom,
11
+
isAtTopAtom,
12
+
quickAuthAtom,
12
13
selectedFeedUriAtom,
13
-
store,
14
14
} from "~/utils/atoms";
15
15
//import { usePersistentStore } from "~/providers/PersistentStoreProvider";
16
16
import {
17
-
constructArbitraryQuery,
18
-
constructIdentityQuery,
19
-
constructInfiniteFeedSkeletonQuery,
20
-
constructPostQuery,
17
+
//constructArbitraryQuery,
18
+
//constructIdentityQuery,
19
+
//constructInfiniteFeedSkeletonQuery,
20
+
//constructPostQuery,
21
21
useQueryArbitrary,
22
22
useQueryIdentity,
23
23
useQueryPreferences,
24
24
} from "~/utils/useQuery";
25
25
26
26
export const Route = createFileRoute("/")({
27
-
loader: async ({ context }) => {
28
-
const { queryClient } = context;
29
-
const atomauth = store.get(authedAtom);
30
-
const atomagent = store.get(agentAtom);
27
+
// loader: async ({ context }) => {
28
+
// const { queryClient } = context;
29
+
// const atomauth = store.get(authedAtom);
30
+
// const atomagent = store.get(agentAtom);
31
31
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
-
}
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
+
// }
40
40
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()
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()
49
49
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
-
});
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
+
// });
59
59
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
-
},
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
+
// },
86
86
component: Home,
87
-
pendingComponent: PendingHome,
87
+
pendingComponent: PendingHome, // PendingHome,
88
+
staticData: { keepAlive: true },
88
89
});
89
90
function PendingHome() {
90
91
return <div>loading... (prefetching your timeline)</div>;
91
92
}
92
-
function Home() {
93
+
94
+
//function Homer() {
95
+
// return <div></div>
96
+
//}
97
+
export function Home({ hidden = false }: { hidden?: boolean }) {
93
98
const {
94
99
agent,
95
100
status,
···
100
105
} = useAuth();
101
106
const authed = !!agent?.did;
102
107
103
-
useEffect(() => {
104
-
if (agent?.did) {
105
-
store.set(authedAtom, true);
106
-
} else {
107
-
store.set(authedAtom, false);
108
-
}
109
-
}, [status, agent, authed]);
110
-
useEffect(() => {
111
-
if (agent) {
112
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
113
-
// @ts-ignore is it just me or is the type really weird here it should be Agent not AtpAgent
114
-
store.set(agentAtom, agent);
115
-
} else {
116
-
store.set(agentAtom, null);
117
-
}
118
-
}, [status, agent, authed]);
108
+
// i dont remember why this is even here
109
+
// useEffect(() => {
110
+
// if (agent?.did) {
111
+
// store.set(authedAtom, true);
112
+
// } else {
113
+
// store.set(authedAtom, false);
114
+
// }
115
+
// }, [status, agent, authed]);
116
+
// useEffect(() => {
117
+
// if (agent) {
118
+
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
119
+
// // @ts-ignore is it just me or is the type really weird here it should be Agent not AtpAgent
120
+
// store.set(agentAtom, agent);
121
+
// } else {
122
+
// store.set(agentAtom, null);
123
+
// }
124
+
// }, [status, agent, authed]);
119
125
120
126
//const { get, set } = usePersistentStore();
121
127
// const [feed, setFeed] = React.useState<any[]>([]);
···
155
161
156
162
// const savedFeeds = savedFeedsPref?.items || [];
157
163
158
-
const identityresultmaybe = useQueryIdentity(agent?.did);
164
+
const [quickAuth, setQuickAuth] = useAtom(quickAuthAtom);
165
+
const isAuthRestoring = quickAuth ? status === "loading" : false;
166
+
167
+
const identityresultmaybe = useQueryIdentity(!isAuthRestoring ? agent?.did : undefined);
159
168
const identity = identityresultmaybe?.data;
160
169
161
170
const prefsresultmaybe = useQueryPreferences({
162
-
agent: agent ?? undefined,
163
-
pdsUrl: identity?.pds,
171
+
agent: !isAuthRestoring ? (agent ?? undefined) : undefined,
172
+
pdsUrl: !isAuthRestoring ? (identity?.pds) : undefined,
164
173
});
165
174
const prefs = prefsresultmaybe?.data;
166
175
···
171
180
return savedFeedsPref?.items || [];
172
181
}, [prefs]);
173
182
174
-
const [persistentSelectedFeed, setPersistentSelectedFeed] =
175
-
useAtom(selectedFeedUriAtom); // React.useState<string | null>(null);
176
-
const [unauthedSelectedFeed, setUnauthedSelectedFeed] = React.useState(
177
-
persistentSelectedFeed
178
-
); // React.useState<string | null>(null);
183
+
const [persistentSelectedFeed, setPersistentSelectedFeed] = useAtom(selectedFeedUriAtom);
184
+
const [unauthedSelectedFeed, setUnauthedSelectedFeed] = useState(persistentSelectedFeed);
179
185
const selectedFeed = agent?.did
180
186
? persistentSelectedFeed
181
187
: unauthedSelectedFeed;
···
292
298
feedScrollPositionsAtom
293
299
);
294
300
295
-
const scrollRef = React.useRef<Record<string, number>>({});
301
+
const scrollPositionsRef = React.useRef(scrollPositions);
296
302
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);
303
+
React.useEffect(() => {
304
+
scrollPositionsRef.current = scrollPositions;
305
+
}, [scrollPositions]);
306
306
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]);
307
+
useLayoutEffect(() => {
308
+
if (isAuthRestoring) return;
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, isAuthRestoring]);
322
314
323
315
useLayoutEffect(() => {
324
-
setRestoringScrollPosition(true);
325
-
const savedPosition = scrollPositions[selectedFeed ?? "null"] ?? 0;
316
+
if (!selectedFeed || isAuthRestoring) 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
+
};
338
324
339
-
return () => cancelAnimationFrame(raf);
340
-
}, [selectedFeed, scrollPositions]);
325
+
window.addEventListener("scroll", handleScroll, { passive: true });
326
+
return () => {
327
+
window.removeEventListener("scroll", handleScroll);
341
328
342
-
const feedGengetrecordquery = useQueryArbitrary(selectedFeed ?? undefined);
343
-
const feedServiceDid = (feedGengetrecordquery?.data?.value as any)?.did;
329
+
setScrollPositions(scrollPositionsRef.current);
330
+
};
331
+
}, [isAuthRestoring, selectedFeed, setScrollPositions]);
332
+
333
+
const feedGengetrecordquery = useQueryArbitrary(!isAuthRestoring ? selectedFeed ?? undefined : undefined);
334
+
const feedServiceDid = !isAuthRestoring ? (feedGengetrecordquery?.data?.value as any)?.did as string | undefined : undefined;
344
335
345
336
// const {
346
337
// data: feedData,
···
356
347
357
348
// const feed = feedData?.feed || [];
358
349
359
-
const isReadyForAuthedFeed =
360
-
authed && agent && identity?.pds && feedServiceDid;
361
-
const isReadyForUnauthedFeed = !authed && selectedFeed;
350
+
const isReadyForAuthedFeed = !isAuthRestoring && authed && agent && identity?.pds && feedServiceDid;
351
+
const isReadyForUnauthedFeed = !isAuthRestoring && !authed && selectedFeed;
352
+
353
+
354
+
const [isAtTop] = useAtom(isAtTopAtom);
362
355
363
356
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) => {
357
+
<div
358
+
className={`relative flex flex-col divide-y divide-gray-200 dark:divide-gray-800 ${hidden && "hidden"}`}
359
+
>
360
+
{!isAuthRestoring && savedFeeds.length > 0 ? (
361
+
<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`}>
362
+
{savedFeeds.map((item: any, idx: number) => {
368
363
const label = item.value.split("/").pop() || item.value;
369
364
const isActive = selectedFeed === item.value;
370
365
return (
···
372
367
key={item.value || idx}
373
368
className={`px-3 py-1 rounded-full whitespace-nowrap font-medium transition-colors ${
374
369
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"
370
+
? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600"
371
+
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800"
372
+
// ? "bg-gray-500 text-white"
373
+
// : item.pinned
374
+
// ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200"
375
+
// : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200"
379
376
}`}
380
377
onClick={() => setSelectedFeed(item.value)}
381
378
title={item.value}
382
379
>
383
380
{label}
384
381
{item.pinned && (
385
-
<span className="ml-1 text-xs text-gray-700 dark:text-gray-200">
382
+
<span
383
+
className={`ml-1 text-xs ${
384
+
isActive
385
+
? "text-gray-900 dark:text-gray-100"
386
+
: "text-gray-600 dark:text-gray-400"
387
+
}`}
388
+
>
386
389
โ
387
390
</span>
388
391
)}
389
392
</button>
390
393
);
391
-
})
392
-
) : (
393
-
<span className="text-xl font-bold ml-2">Home</span>
394
-
)}
395
-
</div>
394
+
})}
395
+
</div>
396
+
) : (
397
+
// <span className="text-xl font-bold ml-2">Home</span>
398
+
<Header title="Home" />
399
+
)}
396
400
{/* {isFeedLoading && <div className="p-4 text-gray-500">Loading...</div>}
397
401
{feedError && <div className="p-4 text-red-500">{feedError.message}</div>}
398
402
{!isFeedLoading && !feedError && feed.length === 0 && (
···
405
409
/>
406
410
))} */}
407
411
408
-
{authed && (!identity?.pds || !feedServiceDid) && (
412
+
{isAuthRestoring || authed && (!identity?.pds || !feedServiceDid) && (
409
413
<div className="p-4 text-center text-gray-500">
410
414
Preparing your feed...
411
415
</div>
412
416
)}
413
417
414
-
{isReadyForAuthedFeed || isReadyForUnauthedFeed ? (
418
+
{!isAuthRestoring && (isReadyForAuthedFeed || isReadyForUnauthedFeed) ? (
415
419
<InfiniteCustomFeed
420
+
key={selectedFeed!}
416
421
feedUri={selectedFeed!}
417
422
pdsUrl={identity?.pds}
418
423
feedServiceDid={feedServiceDid}
419
424
/>
420
425
) : (
421
426
<div className="p-4 text-center text-gray-500">
422
-
Select a feed to get started.
427
+
Loading.......
423
428
</div>
424
429
)}
425
430
{/* {false && restoringScrollPosition && (
+7
-3
src/routes/notifications.tsx
+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(
+194
-55
src/routes/profile.$did/index.tsx
+194
-55
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 { Button } from "~/components/radix-m3-rd/Button";
9
+
import {
10
+
renderTextWithFacets,
11
+
UniversalPostRendererATURILoader,
12
+
} from "~/components/UniversalPostRenderer";
6
13
import { useAuth } from "~/providers/UnifiedAuthProvider";
7
-
import { toggleFollow, useGetFollowState } from "~/utils/followState";
14
+
import { imgCDNAtom } from "~/utils/atoms";
15
+
import {
16
+
toggleFollow,
17
+
useGetFollowState,
18
+
useGetOneToOneState,
19
+
} from "~/utils/followState";
8
20
import {
9
21
useInfiniteQueryAuthorFeed,
10
22
useQueryIdentity,
···
18
30
function ProfileComponent() {
19
31
// booo bad this is not always the did it might be a handle, use identity.did instead
20
32
const { did } = Route.useParams();
33
+
const navigate = useNavigate();
21
34
const queryClient = useQueryClient();
22
-
const { agent } = useAuth();
23
35
const {
24
36
data: identity,
25
37
isLoading: isIdentityLoading,
26
38
error: identityError,
27
39
} = useQueryIdentity(did);
28
-
29
-
const followRecords = useGetFollowState({
30
-
target: identity?.did || did,
31
-
user: agent?.did,
32
-
});
33
40
34
41
const resolvedDid = did.startsWith("did:") ? did : identity?.did;
35
42
const resolvedHandle = did.startsWith("did:") ? identity?.handle : did;
···
66
73
[postsData]
67
74
);
68
75
76
+
const [imgcdn] = useAtom(imgCDNAtom);
77
+
69
78
function getAvatarUrl(p: typeof profile) {
70
79
const link = p?.avatar?.ref?.["$link"];
71
80
if (!link || !resolvedDid) return null;
72
-
return `https://cdn.bsky.app/img/avatar/plain/${resolvedDid}/${link}@jpeg`;
81
+
return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`;
73
82
}
74
83
function getBannerUrl(p: typeof profile) {
75
84
const link = p?.banner?.ref?.["$link"];
76
85
if (!link || !resolvedDid) return null;
77
-
return `https://cdn.bsky.app/img/banner/plain/${resolvedDid}/${link}@jpeg`;
86
+
return `https://${imgcdn}/img/banner/plain/${resolvedDid}/${link}@jpeg`;
78
87
}
79
88
80
89
const displayName =
···
104
113
105
114
return (
106
115
<>
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">
116
+
<Header
117
+
title={`Profile`}
118
+
backButtonCallback={() => {
119
+
if (window.history.length > 1) {
120
+
window.history.back();
121
+
} else {
122
+
window.location.assign("/");
123
+
}
124
+
}}
125
+
/>
126
+
{/* <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
127
<Link
109
128
to=".."
110
129
className="px-3 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-900 font-bold text-lg"
···
121
140
โ
122
141
</Link>
123
142
<span className="text-xl font-bold ml-2">Profile</span>
124
-
</div>
143
+
</div> */}
125
144
126
145
{/* Profile Header */}
127
146
<div className="w-full max-w-2xl mx-auto overflow-hidden relative bg-gray-100 dark:bg-gray-900">
···
151
170
also delay the backfill to be on demand because it would be pretty intense
152
171
also save it persistently
153
172
*/}
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
-
)}
191
-
<button className="rounded-full dark:bg-gray-600 bg-gray-300 px-3 py-2 text-[14px]">
173
+
<FollowButton targetdidorhandle={did} />
174
+
<Button className="rounded-full" variant={"secondary"}>
192
175
... {/* todo: icon */}
193
-
</button>
176
+
</Button>
194
177
</div>
195
178
196
179
{/* Info Card */}
197
180
<div className="mt-16 pb-2 px-4 text-gray-900 dark:text-gray-100">
198
181
<div className="font-bold text-2xl">{displayName}</div>
199
-
<div className="text-gray-500 dark:text-gray-400 text-base mb-3">
182
+
<div className="text-gray-500 dark:text-gray-400 text-base mb-3 flex flex-row gap-1">
183
+
<Mutual targetdidorhandle={did} />
200
184
{handle}
201
185
</div>
202
186
{description && (
203
187
<div className="text-base leading-relaxed text-gray-800 dark:text-gray-300 mb-5 whitespace-pre-wrap break-words text-[15px]">
204
-
{description}
188
+
{/* {description} */}
189
+
<RichTextRenderer key={did} description={description} />
205
190
</div>
206
191
)}
207
192
</div>
···
244
229
</>
245
230
);
246
231
}
232
+
233
+
export function FollowButton({
234
+
targetdidorhandle,
235
+
}: {
236
+
targetdidorhandle: string;
237
+
}) {
238
+
const { agent } = useAuth();
239
+
const { data: identity } = useQueryIdentity(targetdidorhandle);
240
+
const queryClient = useQueryClient();
241
+
242
+
const followRecords = useGetFollowState({
243
+
target: identity?.did ?? targetdidorhandle,
244
+
user: agent?.did,
245
+
});
246
+
247
+
return (
248
+
<>
249
+
{identity?.did !== agent?.did ? (
250
+
<>
251
+
{!(followRecords?.length && followRecords?.length > 0) ? (
252
+
<Button
253
+
onClick={(e) => {
254
+
e.stopPropagation();
255
+
toggleFollow({
256
+
agent: agent || undefined,
257
+
targetDid: identity?.did,
258
+
followRecords: followRecords,
259
+
queryClient: queryClient,
260
+
});
261
+
}}
262
+
>
263
+
Follow
264
+
</Button>
265
+
) : (
266
+
<Button
267
+
onClick={(e) => {
268
+
e.stopPropagation();
269
+
toggleFollow({
270
+
agent: agent || undefined,
271
+
targetDid: identity?.did,
272
+
followRecords: followRecords,
273
+
queryClient: queryClient,
274
+
});
275
+
}}
276
+
>
277
+
Unfollow
278
+
</Button>
279
+
)}
280
+
</>
281
+
) : (
282
+
<Button variant={"secondary"}>Edit Profile</Button>
283
+
)}
284
+
</>
285
+
);
286
+
}
287
+
288
+
export function Mutual({ targetdidorhandle }: { targetdidorhandle: string }) {
289
+
const { agent } = useAuth();
290
+
const { data: identity } = useQueryIdentity(targetdidorhandle);
291
+
292
+
const theyFollowYouRes = useGetOneToOneState(
293
+
agent?.did
294
+
? {
295
+
target: agent?.did,
296
+
user: identity?.did ?? targetdidorhandle,
297
+
collection: "app.bsky.graph.follow",
298
+
path: ".subject",
299
+
}
300
+
: undefined
301
+
);
302
+
303
+
const youFollowThemRes = useGetFollowState({
304
+
target: identity?.did ?? targetdidorhandle,
305
+
user: agent?.did,
306
+
});
307
+
308
+
const theyFollowYou: boolean =
309
+
!!theyFollowYouRes?.length && theyFollowYouRes.length > 0;
310
+
const youFollowThem: boolean =
311
+
!!youFollowThemRes?.length && youFollowThemRes.length > 0;
312
+
313
+
return (
314
+
<>
315
+
{/* if not self */}
316
+
{identity?.did !== agent?.did ? (
317
+
<>
318
+
{theyFollowYou ? (
319
+
<>
320
+
{youFollowThem ? (
321
+
<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">
322
+
mutuals
323
+
</div>
324
+
) : (
325
+
<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">
326
+
follows you
327
+
</div>
328
+
)}
329
+
</>
330
+
) : (
331
+
<></>
332
+
)}
333
+
</>
334
+
) : (
335
+
// lmao can someone be mutuals with themselves ??
336
+
<></>
337
+
)}
338
+
</>
339
+
);
340
+
}
341
+
342
+
export function RichTextRenderer({ description }: { description: string }) {
343
+
const [richDescription, setRichDescription] = useState<string | ReactNode[]>(
344
+
description
345
+
);
346
+
const { agent } = useAuth();
347
+
const navigate = useNavigate();
348
+
349
+
useEffect(() => {
350
+
let mounted = true;
351
+
352
+
// setRichDescription(description);
353
+
354
+
async function processRichText() {
355
+
try {
356
+
if (!agent?.did) return;
357
+
const rt = new RichText({ text: description });
358
+
await rt.detectFacets(agent);
359
+
360
+
if (!mounted) return;
361
+
362
+
if (rt.facets) {
363
+
setRichDescription(
364
+
renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate })
365
+
);
366
+
} else {
367
+
setRichDescription(rt.text);
368
+
}
369
+
} catch (error) {
370
+
console.error("Failed to detect facets:", error);
371
+
if (mounted) {
372
+
setRichDescription(description);
373
+
}
374
+
}
375
+
}
376
+
377
+
processRichText();
378
+
379
+
return () => {
380
+
mounted = false;
381
+
};
382
+
}, [description, agent, navigate]);
383
+
384
+
return <>{richDescription}</>;
385
+
}
+165
src/routes/profile.$did/post.$rkey.image.$i.tsx
+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 disablescroll 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
+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
+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
}
+181
-1
src/routes/settings.tsx
+181
-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
+
constellationURLAtom,
9
+
defaultconstellationURL,
10
+
defaulthue,
11
+
defaultImgCDN,
12
+
defaultslingshotURL,
13
+
defaultVideoCDN,
14
+
hueAtom,
15
+
imgCDNAtom,
16
+
slingshotURLAtom,
17
+
videoCDNAtom,
18
+
} from "~/utils/atoms";
2
19
3
20
export const Route = createFileRoute("/settings")({
4
21
component: Settings,
5
22
});
6
23
7
24
export function Settings() {
8
-
return <div className="p-6">Settings page (coming soon)</div>;
25
+
return (
26
+
<>
27
+
<Header
28
+
title="Settings"
29
+
backButtonCallback={() => {
30
+
if (window.history.length > 1) {
31
+
window.history.back();
32
+
} else {
33
+
window.location.assign("/");
34
+
}
35
+
}}
36
+
/>
37
+
<div className="lg:hidden">
38
+
<Login />
39
+
</div>
40
+
<div className="h-4" />
41
+
<TextInputSetting
42
+
atom={constellationURLAtom}
43
+
title={"Constellation"}
44
+
description={
45
+
"Customize the Constellation instance to be used by Red Dwarf"
46
+
}
47
+
init={defaultconstellationURL}
48
+
/>
49
+
<TextInputSetting
50
+
atom={slingshotURLAtom}
51
+
title={"Slingshot"}
52
+
description={"Customize the Slingshot instance to be used by Red Dwarf"}
53
+
init={defaultslingshotURL}
54
+
/>
55
+
<TextInputSetting
56
+
atom={imgCDNAtom}
57
+
title={"Image CDN"}
58
+
description={
59
+
"Customize the Constellation instance to be used by Red Dwarf"
60
+
}
61
+
init={defaultImgCDN}
62
+
/>
63
+
<TextInputSetting
64
+
atom={videoCDNAtom}
65
+
title={"Video CDN"}
66
+
description={"Customize the Slingshot instance to be used by Red Dwarf"}
67
+
init={defaultVideoCDN}
68
+
/>
69
+
70
+
<Hue />
71
+
<p className="text-gray-500 dark:text-gray-400 py-4 px-6 text-sm">
72
+
please restart/refresh the app if changes arent applying correctly
73
+
</p>
74
+
</>
75
+
);
76
+
}
77
+
function Hue() {
78
+
const [hue, setHue] = useAtom(hueAtom);
79
+
return (
80
+
<div className="flex flex-col px-4 mt-4 ">
81
+
<span className="z-10">Hue</span>
82
+
<div className="flex flex-row items-center gap-4">
83
+
<SliderComponent
84
+
atom={hueAtom}
85
+
max={360}
86
+
/>
87
+
<button
88
+
onClick={() => setHue(defaulthue ?? 28)}
89
+
className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800
90
+
text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition"
91
+
>
92
+
Reset
93
+
</button>
94
+
</div>
95
+
</div>
96
+
);
9
97
}
98
+
99
+
export function TextInputSetting({
100
+
atom,
101
+
title,
102
+
description,
103
+
init,
104
+
}: {
105
+
atom: typeof constellationURLAtom;
106
+
title?: string;
107
+
description?: string;
108
+
init?: string;
109
+
}) {
110
+
const [value, setValue] = useAtom(atom);
111
+
return (
112
+
<div className="flex flex-col gap-2 px-4 py-2">
113
+
{/* <div>
114
+
{title && (
115
+
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100">
116
+
{title}
117
+
</h3>
118
+
)}
119
+
{description && (
120
+
<p className="text-sm text-gray-500 dark:text-gray-400">
121
+
{description}
122
+
</p>
123
+
)}
124
+
</div> */}
125
+
126
+
<div className="flex flex-row gap-2 items-center">
127
+
<div className="m3input-field m3input-label m3input-border size-md flex-1">
128
+
<input
129
+
type="text"
130
+
placeholder=" "
131
+
value={value}
132
+
onChange={(e) => setValue(e.target.value)}
133
+
/>
134
+
<label>{title}</label>
135
+
</div>
136
+
{/* <input
137
+
type="text"
138
+
value={value}
139
+
onChange={(e) => setValue(e.target.value)}
140
+
className="flex-1 px-3 py-2 rounded-lg bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700
141
+
text-gray-900 dark:text-gray-100 placeholder:text-gray-500 dark:placeholder:text-gray-400
142
+
focus:outline-none focus:ring-2 focus:ring-gray-400 dark:focus:ring-gray-600"
143
+
placeholder="Enter value..."
144
+
/> */}
145
+
<button
146
+
onClick={() => setValue(init ?? "")}
147
+
className="px-6 py-2 h-12 rounded-full bg-gray-100 dark:bg-gray-800
148
+
text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition"
149
+
>
150
+
Reset
151
+
</button>
152
+
</div>
153
+
</div>
154
+
);
155
+
}
156
+
157
+
158
+
interface SliderProps {
159
+
atom: typeof hueAtom;
160
+
min?: number;
161
+
max?: number;
162
+
step?: number;
163
+
}
164
+
165
+
export const SliderComponent: React.FC<SliderProps> = ({
166
+
atom,
167
+
min = 0,
168
+
max = 100,
169
+
step = 1,
170
+
}) => {
171
+
172
+
const [value, setValue] = useAtom(atom)
173
+
174
+
return (
175
+
<Slider.Root
176
+
className="relative flex items-center w-full h-4"
177
+
value={[value]}
178
+
min={min}
179
+
max={max}
180
+
step={step}
181
+
onValueChange={(v: number[]) => setValue(v[0])}
182
+
>
183
+
<Slider.Track className="relative flex-grow h-4 bg-gray-300 dark:bg-gray-700 rounded-full">
184
+
<Slider.Range className="absolute h-full bg-gray-500 dark:bg-gray-400 rounded-l-full rounded-r-none" />
185
+
</Slider.Track>
186
+
<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" />
187
+
</Slider.Root>
188
+
);
189
+
};
+184
-13
src/styles/app.css
+184
-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 {
···
47
52
}
48
53
}
49
54
55
+
.gutter{
56
+
scrollbar-gutter: stable both-edges;
57
+
}
58
+
50
59
@media (width >= 64rem /* 1024px */) {
51
-
html,
52
-
body {
60
+
html:not(:has(.disablegutter)),
61
+
body:not(:has(.disablegutter)) {
53
62
scrollbar-gutter: stable both-edges !important;
54
63
}
64
+
html:has(.disablescroll),
65
+
body:has(.disablescroll) {
66
+
scrollbar-width: none;
67
+
overflow-y: hidden;
68
+
}
69
+
}
70
+
71
+
.lightbox:has(+.lightbox-sidebar){
72
+
opacity: 0;
55
73
}
74
+
56
75
.scroll-thin {
57
76
scrollbar-width: thin;
58
77
/*scrollbar-gutter: stable both-edges !important;*/
···
61
80
.scroll-none {
62
81
scrollbar-width: none;
63
82
}
83
+
84
+
.dangerousFediContent {
85
+
& a[href]{
86
+
text-decoration: none;
87
+
color: rgb(29, 122, 242);
88
+
word-break: break-all;
89
+
}
90
+
}
91
+
92
+
.font-inter {
93
+
font-family: "Inter", sans-serif;
94
+
}
95
+
.font-roboto {
96
+
font-family: "Roboto", sans-serif;
97
+
}
98
+
99
+
:root {
100
+
--header-bg-light: color-mix(in srgb, var(--color-white) calc(var(--is-top) * 100%), var(--color-gray-50));
101
+
--header-bg-dark: color-mix(in srgb, var(--color-gray-950) calc(var(--is-top) * 100%), var(--color-gray-900));
102
+
}
103
+
104
+
:root {
105
+
--header-bg: var(--header-bg-light);
106
+
}
107
+
@media (prefers-color-scheme: dark) {
108
+
:root {
109
+
--header-bg: var(--header-bg-dark);
110
+
}
111
+
}
112
+
113
+
:root {
114
+
--shadow-opacity: calc(1 - var(--is-top));
115
+
--tw-shadow-header: 0 2px 8px hsl(0 0% 0% / calc(var(--shadow-opacity) * 0.15));
116
+
}
117
+
118
+
119
+
/* m3 input */
120
+
:root {
121
+
--m3input-radius: 6px;
122
+
--m3input-border-width: .0625rem;
123
+
--m3input-font-size: 16px;
124
+
--m3input-transition: 150ms cubic-bezier(.2, .8, .2, 1);
125
+
/* light theme */
126
+
--m3input-bg: var(--color-gray-50);
127
+
--m3input-border-color: var(--color-gray-400);
128
+
--m3input-label-color: var(--color-gray-500);
129
+
--m3input-text-color: var(--color-gray-900);
130
+
--m3input-focus-color: var(--color-gray-600);
131
+
}
132
+
133
+
@media (prefers-color-scheme: dark) {
134
+
:root {
135
+
--m3input-bg: var(--color-gray-950);
136
+
--m3input-border-color: var(--color-gray-700);
137
+
--m3input-label-color: var(--color-gray-400);
138
+
--m3input-text-color: var(--color-gray-50);
139
+
--m3input-focus-color: var(--color-gray-400);
140
+
}
141
+
}
142
+
143
+
/* reset page *//*
144
+
html,
145
+
body {
146
+
background: var(--m3input-bg);
147
+
margin: 0;
148
+
padding: 1rem;
149
+
color: var(--m3input-text-color);
150
+
font-family: system-ui, sans-serif;
151
+
font-size: var(--m3input-font-size);
152
+
}*/
153
+
154
+
/* base wrapper */
155
+
.m3input-field.m3input-label.m3input-border {
156
+
position: relative;
157
+
display: inline-block;
158
+
width: 100%;
159
+
/*max-width: 400px;*/
160
+
}
161
+
162
+
/* size variants */
163
+
.m3input-field.size-sm {
164
+
--m3input-h: 40px;
165
+
}
166
+
167
+
.m3input-field.size-md {
168
+
--m3input-h: 48px;
169
+
}
170
+
171
+
.m3input-field.size-lg {
172
+
--m3input-h: 56px;
173
+
}
174
+
175
+
.m3input-field.size-xl {
176
+
--m3input-h: 64px;
177
+
}
178
+
179
+
.m3input-field.m3input-label.m3input-border:not(.size-sm):not(.size-md):not(.size-lg):not(.size-xl) {
180
+
--m3input-h: 48px;
181
+
}
182
+
183
+
/* outlined input */
184
+
.m3input-field.m3input-label.m3input-border input {
185
+
width: 100%;
186
+
height: var(--m3input-h);
187
+
border: var(--m3input-border-width) solid var(--m3input-border-color);
188
+
border-radius: var(--m3input-radius);
189
+
background: var(--m3input-bg);
190
+
color: var(--m3input-text-color);
191
+
font-size: var(--m3input-font-size);
192
+
padding: 0 12px;
193
+
box-sizing: border-box;
194
+
outline: none;
195
+
transition: border-color var(--m3input-transition), box-shadow var(--m3input-transition);
196
+
}
197
+
198
+
/* focus ring */
199
+
.m3input-field.m3input-label.m3input-border input:focus {
200
+
border-color: var(--m3input-focus-color);
201
+
/*box-shadow: 0 0 0 2px color-mix(in srgb, var(--focus-color) 20%, transparent);*/
202
+
}
203
+
204
+
/* label */
205
+
.m3input-field.m3input-label.m3input-border label {
206
+
position: absolute;
207
+
left: 12px;
208
+
top: 50%;
209
+
transform: translateY(-50%);
210
+
background: var(--m3input-bg);
211
+
padding: 0 .25em;
212
+
color: var(--m3input-label-color);
213
+
pointer-events: none;
214
+
transition: all var(--m3input-transition);
215
+
}
216
+
217
+
/* float on focus or when filled */
218
+
.m3input-field.m3input-label.m3input-border input:focus+label,
219
+
.m3input-field.m3input-label.m3input-border input:not(:placeholder-shown)+label {
220
+
top: 0;
221
+
transform: translateY(-50%) scale(.78);
222
+
left: 0;
223
+
color: var(--m3input-focus-color);
224
+
}
225
+
226
+
/* placeholder trick */
227
+
.m3input-field.m3input-label.m3input-border input::placeholder {
228
+
color: transparent;
229
+
}
230
+
231
+
/* radix i love you but like cmon man */
232
+
body[data-scroll-locked]{
233
+
margin-left: var(--removed-body-scroll-bar-size) !important;
234
+
}
+65
-8
src/utils/atoms.ts
+65
-8
src/utils/atoms.ts
···
1
-
import type Agent from "@atproto/api";
2
-
import { atom, createStore } from "jotai";
3
-
import { atomWithStorage } from 'jotai/utils';
1
+
import { atom, createStore, useAtomValue } from "jotai";
2
+
import { atomWithStorage } from "jotai/utils";
3
+
import { useEffect } from "react";
4
4
5
5
export const store = createStore();
6
6
7
+
export const quickAuthAtom = atomWithStorage<string | null>(
8
+
"quickAuth",
9
+
null
10
+
);
11
+
7
12
export const selectedFeedUriAtom = atomWithStorage<string | null>(
8
-
'selectedFeedUri',
13
+
"selectedFeedUri",
9
14
null
10
15
);
11
16
12
17
//export const feedScrollPositionsAtom = atom<Record<string, number>>({});
13
18
14
19
export const feedScrollPositionsAtom = atomWithStorage<Record<string, number>>(
15
-
'feedscrollpositions',
20
+
"feedscrollpositions",
16
21
{}
17
22
);
18
23
19
24
export const likedPostsAtom = atomWithStorage<Record<string, string>>(
20
-
'likedPosts',
25
+
"likedPosts",
21
26
{}
22
27
);
23
28
24
-
export const agentAtom = atom<Agent|null>(null);
25
-
export const authedAtom = atom<boolean>(false);
29
+
export const defaultconstellationURL = "constellation.microcosm.blue";
30
+
export const constellationURLAtom = atomWithStorage<string>(
31
+
"constellationURL",
32
+
defaultconstellationURL
33
+
);
34
+
export const defaultslingshotURL = "slingshot.microcosm.blue";
35
+
export const slingshotURLAtom = atomWithStorage<string>(
36
+
"slingshotURL",
37
+
defaultslingshotURL
38
+
);
39
+
export const defaultImgCDN = "cdn.bsky.app";
40
+
export const imgCDNAtom = atomWithStorage<string>("imgcdnurl", defaultImgCDN);
41
+
export const defaultVideoCDN = "video.bsky.app";
42
+
export const videoCDNAtom = atomWithStorage<string>(
43
+
"videocdnurl",
44
+
defaultVideoCDN
45
+
);
46
+
47
+
export const defaulthue = 28;
48
+
export const hueAtom = atomWithStorage<number>("hue", defaulthue);
49
+
50
+
export const isAtTopAtom = atom<boolean>(true);
51
+
52
+
type ComposerState =
53
+
| { kind: "closed" }
54
+
| { kind: "root" }
55
+
| { kind: "reply"; parent: string }
56
+
| { kind: "quote"; subject: string };
57
+
export const composerAtom = atom<ComposerState>({ kind: "closed" });
58
+
59
+
//export const agentAtom = atom<Agent | null>(null);
60
+
//export const authedAtom = atom<boolean>(false);
61
+
62
+
export function useAtomCssVar(atom: typeof hueAtom, cssVar: string) {
63
+
const value = useAtomValue(atom);
64
+
65
+
useEffect(() => {
66
+
document.documentElement.style.setProperty(cssVar, value.toString());
67
+
}, [value, cssVar]);
68
+
69
+
useEffect(() => {
70
+
document.documentElement.style.setProperty(cssVar, value.toString());
71
+
}, []);
72
+
}
73
+
74
+
hueAtom.onMount = (setAtom) => {
75
+
const stored = localStorage.getItem("hue");
76
+
if (stored != null) setAtom(Number(stored));
77
+
};
78
+
// export function initAtomToCssVar(atom: typeof hueAtom, cssVar: string) {
79
+
// const initial = store.get(atom);
80
+
// console.log("atom get ", initial);
81
+
// document.documentElement.style.setProperty(cssVar, initial.toString());
82
+
// }
+37
-3
src/utils/followState.ts
+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
+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
+
}
+81
-17
src/utils/useQuery.ts
+81
-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(){
···
605
615
}) {
606
616
const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery(options);
607
617
608
-
return useInfiniteQuery({
618
+
return {...useInfiniteQuery({
609
619
queryKey,
610
620
queryFn,
611
621
initialPageParam: undefined as never,
···
613
623
staleTime: Infinity,
614
624
refetchOnWindowFocus: false,
615
625
enabled: !!options.feedUri && (options.isAuthed ? !!options.agent && !!options.pdsUrl && !!options.feedServiceDid : true),
616
-
});
626
+
}), queryKey: queryKey};
627
+
}
628
+
629
+
630
+
export function yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(query?: {
631
+
constellation: string,
632
+
method: '/links'
633
+
target?: string
634
+
collection: string
635
+
path: string
636
+
}) {
637
+
console.log(
638
+
'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks',
639
+
query,
640
+
)
641
+
642
+
return infiniteQueryOptions({
643
+
enabled: !!query?.target,
644
+
queryKey: [
645
+
'reddwarf_constellation',
646
+
query?.method,
647
+
query?.target,
648
+
query?.collection,
649
+
query?.path,
650
+
] as const,
651
+
652
+
queryFn: async ({pageParam}: {pageParam?: string}) => {
653
+
if (!query || !query?.target) return undefined
654
+
655
+
const method = query.method
656
+
const target = query.target
657
+
const collection = query.collection
658
+
const path = query.path
659
+
const cursor = pageParam
660
+
661
+
const res = await fetch(
662
+
`https://${query.constellation}${method}?target=${encodeURIComponent(target)}${
663
+
collection ? `&collection=${encodeURIComponent(collection)}` : ''
664
+
}${path ? `&path=${encodeURIComponent(path)}` : ''}${
665
+
cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''
666
+
}`,
667
+
)
668
+
669
+
if (!res.ok) throw new Error('Failed to fetch')
670
+
671
+
return (await res.json()) as linksRecordsResponse
672
+
},
673
+
674
+
getNextPageParam: lastPage => {
675
+
return (lastPage as any)?.cursor ?? undefined
676
+
},
677
+
initialPageParam: undefined,
678
+
staleTime: 5 * 60 * 1000,
679
+
gcTime: 5 * 60 * 1000,
680
+
})
617
681
}
+1
-1
tsconfig.json
+1
-1
tsconfig.json
+22
-1
vite.config.ts
+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,