on master help sign in
ui: refactor to work with groups2 and latest react query
000ad6eHunter Miller
desk/desk.docket-0+1−1
@@ -4,7 +4,7 @@
4 image+'https://nyc3.digitaloceanspaces.com/hmillerdev/nocsyx-lassul/2022.7.16..04.35.22-sphinx-web-shifted.svg'
4 image+'https://nyc3.digitaloceanspaces.com/hmillerdev/nocsyx-lassul/2022.7.16..04.35.22-sphinx-web-shifted.svg'
5 glob-http+['https://nyc3.digitaloceanspaces.com/hmillerdev/sphinx/glob-0v7.folii.k685s.mjn1b.46up6.but26.glob' 0v7.folii.k685s.mjn1b.46up6.but26]
5 glob-http+['https://nyc3.digitaloceanspaces.com/hmillerdev/sphinx/glob-0v7.folii.k685s.mjn1b.46up6.but26.glob' 0v7.folii.k685s.mjn1b.46up6.but26]
6 base+'sphinx'
6 base+'sphinx'
7− version+[0 5 0]
7+ version+[0 6 0]
8 website+'https://github.com'
8 website+'https://github.com'
9 license+'MIT'
9 license+'MIT'
10==
10==
ui/package-lock.json+82−361
@@ -12,16 +12,17 @@
12 "@radix-ui/react-dropdown-menu": "^0.1.6",
12 "@radix-ui/react-dropdown-menu": "^0.1.6",
13 "@radix-ui/react-select": "^0.1.1",
13 "@radix-ui/react-select": "^0.1.1",
14 "@radix-ui/react-toggle-group": "^0.1.5",
14 "@radix-ui/react-toggle-group": "^0.1.5",
15+ "@tanstack/react-query": "^4.29.5",
15− "@urbit/api": "^2.0.0",
16+ "@urbit/api": "2.2",
16 "@urbit/http-api": "^2.0.0",
17 "@urbit/http-api": "^2.0.0",
17 "big-integer": "^1.6.51",
18 "big-integer": "^1.6.51",
18 "classnames": "^2.3.1",
19 "classnames": "^2.3.1",
20+ "color2k": "^2.0.2",
19 "lodash.debounce": "^4.0.8",
21 "lodash.debounce": "^4.0.8",
20 "query-string": "^7.1.1",
22 "query-string": "^7.1.1",
21 "react": "^17.0.2",
23 "react": "^17.0.2",
22 "react-dom": "^17.0.2",
24 "react-dom": "^17.0.2",
23 "react-hook-form": "^7.33.1",
25 "react-hook-form": "^7.33.1",
24− "react-query": "^3.39.1",
25 "react-router-dom": "^6.3.0",
26 "react-router-dom": "^6.3.0",
26 "react-select": "^5.4.0",
27 "react-select": "^5.4.0",
27 "remove-markdown-and-html": "^0.1.0",
28 "remove-markdown-and-html": "^0.1.0",
@@ -1068,6 +1069,49 @@
1068 "node": ">= 8.0.0"
1069 "node": ">= 8.0.0"
1069 }
1070 }
1070 },
1071 },
1072+ "node_modules/@tanstack/query-core": {
1073+ "version": "4.29.5",
1074+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz",
1075+ "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ==",
1076+ "funding": {
1077+ "type": "github",
1078+ "url": "https://github.com/sponsors/tannerlinsley"
1079+ }
1080+ },
1081+ "node_modules/@tanstack/react-query": {
1082+ "version": "4.29.5",
1083+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz",
1084+ "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==",
1085+ "dependencies": {
1086+ "@tanstack/query-core": "4.29.5",
1087+ "use-sync-external-store": "^1.2.0"
1088+ },
1089+ "funding": {
1090+ "type": "github",
1091+ "url": "https://github.com/sponsors/tannerlinsley"
1092+ },
1093+ "peerDependencies": {
1094+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
1095+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
1096+ "react-native": "*"
1097+ },
1098+ "peerDependenciesMeta": {
1099+ "react-dom": {
1100+ "optional": true
1101+ },
1102+ "react-native": {
1103+ "optional": true
1104+ }
1105+ }
1106+ },
1107+ "node_modules/@tanstack/react-query/node_modules/use-sync-external-store": {
1108+ "version": "1.2.0",
1109+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
1110+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
1111+ "peerDependencies": {
1112+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
1113+ }
1114+ },
1071 "node_modules/@types/lodash": {
1115 "node_modules/@types/lodash": {
1072 "version": "4.14.182",
1116 "version": "4.14.182",
1073 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
1117 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@@ -1126,9 +1170,9 @@
1126 "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
1170 "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
1127 },
1171 },
1128 "node_modules/@urbit/api": {
1172 "node_modules/@urbit/api": {
1129− "version": "2.1.1",
1173+ "version": "2.2.0",
1130− "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz",
1174+ "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
1131− "integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==",
1175+ "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
1132 "dependencies": {
1176 "dependencies": {
1133 "@babel/runtime": "^7.16.0",
1177 "@babel/runtime": "^7.16.0",
1134 "big-integer": "^1.6.48",
1178 "big-integer": "^1.6.48",
@@ -1291,11 +1335,6 @@
1291 "resolve": "^1.12.0"
1335 "resolve": "^1.12.0"
1292 }
1336 }
1293 },
1337 },
1294− "node_modules/balanced-match": {
1295− "version": "1.0.2",
1296− "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
1297− "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
1298− },
1299 "node_modules/big-integer": {
1338 "node_modules/big-integer": {
1300 "version": "1.6.51",
1339 "version": "1.6.51",
1301 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
1340 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -1318,15 +1357,6 @@
1318 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
1357 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
1319 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
1358 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
1320 },
1359 },
1321− "node_modules/brace-expansion": {
1322− "version": "1.1.11",
1323− "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
1324− "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
1325− "dependencies": {
1326− "balanced-match": "^1.0.0",
1327− "concat-map": "0.0.1"
1328− }
1329− },
1330 "node_modules/braces": {
1360 "node_modules/braces": {
1331 "version": "3.0.2",
1361 "version": "3.0.2",
1332 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
1362 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -1339,21 +1369,6 @@
1339 "node": ">=8"
1369 "node": ">=8"
1340 }
1370 }
1341 },
1371 },
1342− "node_modules/broadcast-channel": {
1343− "version": "3.7.0",
1344− "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
1345− "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
1346− "dependencies": {
1347− "@babel/runtime": "^7.7.2",
1348− "detect-node": "^2.1.0",
1349− "js-sha3": "0.8.0",
1350− "microseconds": "0.2.0",
1351− "nano-time": "1.0.0",
1352− "oblivious-set": "1.0.0",
1353− "rimraf": "3.0.2",
1354− "unload": "2.2.0"
1355− }
1356− },
1357 "node_modules/browser-or-node": {
1372 "node_modules/browser-or-node": {
1358 "version": "1.3.0",
1373 "version": "1.3.0",
1359 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
1374 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
@@ -1488,10 +1503,10 @@
1488 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
1503 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
1489 "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
1504 "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
1490 },
1505 },
1491− "node_modules/concat-map": {
1506+ "node_modules/color2k": {
1492− "version": "0.0.1",
1507+ "version": "2.0.2",
1493− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1508+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
1494− "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
1509+ "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
1495 },
1510 },
1496 "node_modules/connect-history-api-fallback": {
1511 "node_modules/connect-history-api-fallback": {
1497 "version": "1.6.0",
1512 "version": "1.6.0",
@@ -1582,11 +1597,6 @@
1582 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
1597 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
1583 "dev": true
1598 "dev": true
1584 },
1599 },
1585− "node_modules/detect-node": {
1586− "version": "2.1.0",
1587− "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
1588− "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
1589− },
1590 "node_modules/detect-node-es": {
1600 "node_modules/detect-node-es": {
1591 "version": "1.1.0",
1601 "version": "1.1.0",
1592 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
1602 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -2095,11 +2105,6 @@
2095 "url": "https://www.patreon.com/infusion"
2105 "url": "https://www.patreon.com/infusion"
2096 }
2106 }
2097 },
2107 },
2098− "node_modules/fs.realpath": {
2099− "version": "1.0.0",
2100− "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
2101− "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
2102− },
2103 "node_modules/fsevents": {
2108 "node_modules/fsevents": {
2104 "version": "2.3.2",
2109 "version": "2.3.2",
2105 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
2110 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -2135,25 +2140,6 @@
2135 "node": ">=6"
2140 "node": ">=6"
2136 }
2141 }
2137 },
2142 },
2138− "node_modules/glob": {
2139− "version": "7.2.3",
2140− "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
2141− "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
2142− "dependencies": {
2143− "fs.realpath": "^1.0.0",
2144− "inflight": "^1.0.4",
2145− "inherits": "2",
2146− "minimatch": "^3.1.1",
2147− "once": "^1.3.0",
2148− "path-is-absolute": "^1.0.0"
2149− },
2150− "engines": {
2151− "node": "*"
2152− },
2153− "funding": {
2154− "url": "https://github.com/sponsors/isaacs"
2155− }
2156− },
2157 "node_modules/glob-parent": {
2143 "node_modules/glob-parent": {
2158 "version": "6.0.2",
2144 "version": "6.0.2",
2159 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
2145 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2233,20 +2219,6 @@
2233 "url": "https://github.com/sponsors/sindresorhus"
2219 "url": "https://github.com/sponsors/sindresorhus"
2234 }
2220 }
2235 },
2221 },
2236− "node_modules/inflight": {
2237− "version": "1.0.6",
2238− "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
2239− "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
2240− "dependencies": {
2241− "once": "^1.3.0",
2242− "wrappy": "1"
2243− }
2244− },
2245− "node_modules/inherits": {
2246− "version": "2.0.4",
2247− "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
2248− "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
2249− },
2250 "node_modules/invariant": {
2222 "node_modules/invariant": {
2251 "version": "2.2.4",
2223 "version": "2.2.4",
2252 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
2224 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -2313,11 +2285,6 @@
2313 "node": ">=0.12.0"
2285 "node": ">=0.12.0"
2314 }
2286 }
2315 },
2287 },
2316− "node_modules/js-sha3": {
2317− "version": "0.8.0",
2318− "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
2319− "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
2320− },
2321 "node_modules/js-tokens": {
2288 "node_modules/js-tokens": {
2322 "version": "4.0.0",
2289 "version": "4.0.0",
2323 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
2290 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2390,15 +2357,6 @@
2390 "loose-envify": "cli.js"
2357 "loose-envify": "cli.js"
2391 }
2358 }
2392 },
2359 },
2393− "node_modules/match-sorter": {
2394− "version": "6.3.1",
2395− "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
2396− "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
2397− "dependencies": {
2398− "@babel/runtime": "^7.12.5",
2399− "remove-accents": "0.4.2"
2400− }
2401− },
2402 "node_modules/memoize-one": {
2360 "node_modules/memoize-one": {
2403 "version": "5.2.1",
2361 "version": "5.2.1",
2404 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
2362 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -2426,22 +2384,6 @@
2426 "node": ">=8.6"
2384 "node": ">=8.6"
2427 }
2385 }
2428 },
2386 },
2429− "node_modules/microseconds": {
2430− "version": "0.2.0",
2431− "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
2432− "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
2433− },
2434− "node_modules/minimatch": {
2435− "version": "3.1.2",
2436− "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
2437− "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
2438− "dependencies": {
2439− "brace-expansion": "^1.1.7"
2440− },
2441− "engines": {
2442− "node": "*"
2443− }
2444− },
2445 "node_modules/minimist": {
2387 "node_modules/minimist": {
2446 "version": "1.2.6",
2388 "version": "1.2.6",
2447 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
2389 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@@ -2453,14 +2395,6 @@
2453 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
2395 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
2454 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
2396 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
2455 },
2397 },
2456− "node_modules/nano-time": {
2457− "version": "1.0.0",
2458− "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
2459− "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
2460− "dependencies": {
2461− "big-integer": "^1.6.16"
2462− }
2463− },
2464 "node_modules/nanoid": {
2398 "node_modules/nanoid": {
2465 "version": "3.3.4",
2399 "version": "3.3.4",
2466 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
2400 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@@ -2513,19 +2447,6 @@
2513 "node": ">= 6"
2447 "node": ">= 6"
2514 }
2448 }
2515 },
2449 },
2516− "node_modules/oblivious-set": {
2517− "version": "1.0.0",
2518− "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
2519− "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
2520− },
2521− "node_modules/once": {
2522− "version": "1.4.0",
2523− "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
2524− "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
2525− "dependencies": {
2526− "wrappy": "1"
2527− }
2528− },
2529 "node_modules/parent-module": {
2450 "node_modules/parent-module": {
2530 "version": "1.0.1",
2451 "version": "1.0.1",
2531 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
2452 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -2554,14 +2475,6 @@
2554 "url": "https://github.com/sponsors/sindresorhus"
2475 "url": "https://github.com/sponsors/sindresorhus"
2555 }
2476 }
2556 },
2477 },
2557− "node_modules/path-is-absolute": {
2558− "version": "1.0.1",
2559− "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
2560− "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
2561− "engines": {
2562− "node": ">=0.10.0"
2563− }
2564− },
2565 "node_modules/path-parse": {
2478 "node_modules/path-parse": {
2566 "version": "1.0.7",
2479 "version": "1.0.7",
2567 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
2480 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -2832,31 +2745,6 @@
2832 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
2745 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
2833 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
2746 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
2834 },
2747 },
2835− "node_modules/react-query": {
2836− "version": "3.39.1",
2837− "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.1.tgz",
2838− "integrity": "sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A==",
2839− "dependencies": {
2840− "@babel/runtime": "^7.5.5",
2841− "broadcast-channel": "^3.4.1",
2842− "match-sorter": "^6.0.2"
2843− },
2844− "funding": {
2845− "type": "github",
2846− "url": "https://github.com/sponsors/tannerlinsley"
2847− },
2848− "peerDependencies": {
2849− "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
2850− },
2851− "peerDependenciesMeta": {
2852− "react-dom": {
2853− "optional": true
2854− },
2855− "react-native": {
2856− "optional": true
2857− }
2858− }
2859− },
2860 "node_modules/react-refresh": {
2748 "node_modules/react-refresh": {
2861 "version": "0.10.0",
2749 "version": "0.10.0",
2862 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
2750 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@@ -3031,11 +2919,6 @@
3031 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
2919 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
3032 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
2920 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
3033 },
2921 },
3034− "node_modules/remove-accents": {
3035− "version": "0.4.2",
3036− "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
3037− "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
3038− },
3039 "node_modules/remove-markdown-and-html": {
2922 "node_modules/remove-markdown-and-html": {
3040 "version": "0.1.0",
2923 "version": "0.1.0",
3041 "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
2924 "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
@@ -3075,20 +2958,6 @@
3075 "node": ">=0.10.0"
2958 "node": ">=0.10.0"
3076 }
2959 }
3077 },
2960 },
3078− "node_modules/rimraf": {
3079− "version": "3.0.2",
3080− "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
3081− "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
3082− "dependencies": {
3083− "glob": "^7.1.3"
3084− },
3085− "bin": {
3086− "rimraf": "bin.js"
3087− },
3088− "funding": {
3089− "url": "https://github.com/sponsors/isaacs"
3090− }
3091− },
3092 "node_modules/rollup": {
2961 "node_modules/rollup": {
3093 "version": "2.75.7",
2962 "version": "2.75.7",
3094 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
2963 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
@@ -3302,15 +3171,6 @@
3302 "node": ">=4.2.0"
3171 "node": ">=4.2.0"
3303 }
3172 }
3304 },
3173 },
3305− "node_modules/unload": {
3306− "version": "2.2.0",
3307− "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
3308− "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
3309− "dependencies": {
3310− "@babel/runtime": "^7.6.2",
3311− "detect-node": "^2.0.4"
3312− }
3313− },
3314 "node_modules/update-browserslist-db": {
3174 "node_modules/update-browserslist-db": {
3315 "version": "1.0.4",
3175 "version": "1.0.4",
3316 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
3176 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
@@ -3475,11 +3335,6 @@
3475 "vite": "^2.0.2"
3335 "vite": "^2.0.2"
3476 }
3336 }
3477 },
3337 },
3478− "node_modules/wrappy": {
3479− "version": "1.0.2",
3480− "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
3481− "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
3482− },
3483 "node_modules/xtend": {
3338 "node_modules/xtend": {
3484 "version": "4.0.2",
3339 "version": "4.0.2",
3485 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
3340 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -4314,6 +4169,28 @@
4314 "picomatch": "^2.2.2"
4169 "picomatch": "^2.2.2"
4315 }
4170 }
4316 },
4171 },
4172+ "@tanstack/query-core": {
4173+ "version": "4.29.5",
4174+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz",
4175+ "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ=="
4176+ },
4177+ "@tanstack/react-query": {
4178+ "version": "4.29.5",
4179+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz",
4180+ "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==",
4181+ "requires": {
4182+ "@tanstack/query-core": "4.29.5",
4183+ "use-sync-external-store": "^1.2.0"
4184+ },
4185+ "dependencies": {
4186+ "use-sync-external-store": {
4187+ "version": "1.2.0",
4188+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
4189+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
4190+ "requires": {}
4191+ }
4192+ }
4193+ },
4317 "@types/lodash": {
4194 "@types/lodash": {
4318 "version": "4.14.182",
4195 "version": "4.14.182",
4319 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
4196 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@@ -4372,9 +4249,9 @@
4372 "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
4249 "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
4373 },
4250 },
4374 "@urbit/api": {
4251 "@urbit/api": {
4375− "version": "2.1.1",
4252+ "version": "2.2.0",
4376− "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz",
4253+ "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
4377− "integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==",
4254+ "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
4378 "requires": {
4255 "requires": {
4379 "@babel/runtime": "^7.16.0",
4256 "@babel/runtime": "^7.16.0",
4380 "big-integer": "^1.6.48",
4257 "big-integer": "^1.6.48",
@@ -4496,11 +4373,6 @@
4496 "resolve": "^1.12.0"
4373 "resolve": "^1.12.0"
4497 }
4374 }
4498 },
4375 },
4499− "balanced-match": {
4500− "version": "1.0.2",
4501− "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
4502− "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
4503− },
4504 "big-integer": {
4376 "big-integer": {
4505 "version": "1.6.51",
4377 "version": "1.6.51",
4506 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
4378 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -4517,15 +4389,6 @@
4517 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
4389 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
4518 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
4390 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
4519 },
4391 },
4520− "brace-expansion": {
4521− "version": "1.1.11",
4522− "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
4523− "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
4524− "requires": {
4525− "balanced-match": "^1.0.0",
4526− "concat-map": "0.0.1"
4527− }
4528− },
4529 "braces": {
4392 "braces": {
4530 "version": "3.0.2",
4393 "version": "3.0.2",
4531 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
4394 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -4535,21 +4398,6 @@
4535 "fill-range": "^7.0.1"
4398 "fill-range": "^7.0.1"
4536 }
4399 }
4537 },
4400 },
4538− "broadcast-channel": {
4539− "version": "3.7.0",
4540− "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
4541− "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
4542− "requires": {
4543− "@babel/runtime": "^7.7.2",
4544− "detect-node": "^2.1.0",
4545− "js-sha3": "0.8.0",
4546− "microseconds": "0.2.0",
4547− "nano-time": "1.0.0",
4548− "oblivious-set": "1.0.0",
4549− "rimraf": "3.0.2",
4550− "unload": "2.2.0"
4551− }
4552− },
4553 "browser-or-node": {
4401 "browser-or-node": {
4554 "version": "1.3.0",
4402 "version": "1.3.0",
4555 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
4403 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
@@ -4637,10 +4485,10 @@
4637 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
4485 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
4638 "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
4486 "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
4639 },
4487 },
4640− "concat-map": {
4488+ "color2k": {
4641− "version": "0.0.1",
4489+ "version": "2.0.2",
4642− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
4490+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
4643− "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
4491+ "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
4644 },
4492 },
4645 "connect-history-api-fallback": {
4493 "connect-history-api-fallback": {
4646 "version": "1.6.0",
4494 "version": "1.6.0",
@@ -4703,11 +4551,6 @@
4703 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
4551 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
4704 "dev": true
4552 "dev": true
4705 },
4553 },
4706− "detect-node": {
4707− "version": "2.1.0",
4708− "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
4709− "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
4710− },
4711 "detect-node-es": {
4554 "detect-node-es": {
4712 "version": "1.1.0",
4555 "version": "1.1.0",
4713 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
4556 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -5000,11 +4843,6 @@
5000 "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
4843 "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
5001 "dev": true
4844 "dev": true
5002 },
4845 },
5003− "fs.realpath": {
5004− "version": "1.0.0",
5005− "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
5006− "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
5007− },
5008 "fsevents": {
4846 "fsevents": {
5009 "version": "2.3.2",
4847 "version": "2.3.2",
5010 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
4848 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -5027,19 +4865,6 @@
5027 "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
4865 "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
5028 "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
4866 "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
5029 },
4867 },
5030− "glob": {
5031− "version": "7.2.3",
5032− "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
5033− "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
5034− "requires": {
5035− "fs.realpath": "^1.0.0",
5036− "inflight": "^1.0.4",
5037− "inherits": "2",
5038− "minimatch": "^3.1.1",
5039− "once": "^1.3.0",
5040− "path-is-absolute": "^1.0.0"
5041− }
5042− },
5043 "glob-parent": {
4868 "glob-parent": {
5044 "version": "6.0.2",
4869 "version": "6.0.2",
5045 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
4870 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -5097,20 +4922,6 @@
5097 "resolve-from": "^4.0.0"
4922 "resolve-from": "^4.0.0"
5098 }
4923 }
5099 },
4924 },
5100− "inflight": {
5101− "version": "1.0.6",
5102− "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
5103− "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
5104− "requires": {
5105− "once": "^1.3.0",
5106− "wrappy": "1"
5107− }
5108− },
5109− "inherits": {
5110− "version": "2.0.4",
5111− "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
5112− "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
5113− },
5114 "invariant": {
4925 "invariant": {
5115 "version": "2.2.4",
4926 "version": "2.2.4",
5116 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
4927 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -5162,11 +4973,6 @@
5162 "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
4973 "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
5163 "dev": true
4974 "dev": true
5164 },
4975 },
5165− "js-sha3": {
5166− "version": "0.8.0",
5167− "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
5168− "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
5169− },
5170 "js-tokens": {
4976 "js-tokens": {
5171 "version": "4.0.0",
4977 "version": "4.0.0",
5172 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
4978 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5221,15 +5027,6 @@
5221 "js-tokens": "^3.0.0 || ^4.0.0"
5027 "js-tokens": "^3.0.0 || ^4.0.0"
5222 }
5028 }
5223 },
5029 },
5224− "match-sorter": {
5225− "version": "6.3.1",
5226− "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
5227− "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
5228− "requires": {
5229− "@babel/runtime": "^7.12.5",
5230− "remove-accents": "0.4.2"
5231− }
5232− },
5233 "memoize-one": {
5030 "memoize-one": {
5234 "version": "5.2.1",
5031 "version": "5.2.1",
5235 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
5032 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -5251,19 +5048,6 @@
5251 "picomatch": "^2.3.1"
5048 "picomatch": "^2.3.1"
5252 }
5049 }
5253 },
5050 },
5254− "microseconds": {
5255− "version": "0.2.0",
5256− "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
5257− "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
5258− },
5259− "minimatch": {
5260− "version": "3.1.2",
5261− "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
5262− "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
5263− "requires": {
5264− "brace-expansion": "^1.1.7"
5265− }
5266− },
5267 "minimist": {
5051 "minimist": {
5268 "version": "1.2.6",
5052 "version": "1.2.6",
5269 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
5053 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@@ -5275,14 +5059,6 @@
5275 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
5059 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
5276 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
5060 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
5277 },
5061 },
5278− "nano-time": {
5279− "version": "1.0.0",
5280− "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
5281− "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
5282− "requires": {
5283− "big-integer": "^1.6.16"
5284− }
5285− },
5286 "nanoid": {
5062 "nanoid": {
5287 "version": "3.3.4",
5063 "version": "3.3.4",
5288 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
5064 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@@ -5317,19 +5093,6 @@
5317 "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
5093 "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
5318 "dev": true
5094 "dev": true
5319 },
5095 },
5320− "oblivious-set": {
5321− "version": "1.0.0",
5322− "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
5323− "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
5324− },
5325− "once": {
5326− "version": "1.4.0",
5327− "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
5328− "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
5329− "requires": {
5330− "wrappy": "1"
5331− }
5332− },
5333 "parent-module": {
5096 "parent-module": {
5334 "version": "1.0.1",
5097 "version": "1.0.1",
5335 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
5098 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5349,11 +5112,6 @@
5349 "lines-and-columns": "^1.1.6"
5112 "lines-and-columns": "^1.1.6"
5350 }
5113 }
5351 },
5114 },
5352− "path-is-absolute": {
5353− "version": "1.0.1",
5354− "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
5355− "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
5356− },
5357 "path-parse": {
5115 "path-parse": {
5358 "version": "1.0.7",
5116 "version": "1.0.7",
5359 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
5117 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -5510,16 +5268,6 @@
5510 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
5268 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
5511 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
5269 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
5512 },
5270 },
5513− "react-query": {
5514− "version": "3.39.1",
5515− "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.1.tgz",
5516− "integrity": "sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A==",
5517− "requires": {
5518− "@babel/runtime": "^7.5.5",
5519− "broadcast-channel": "^3.4.1",
5520− "match-sorter": "^6.0.2"
5521− }
5522− },
5523 "react-refresh": {
5271 "react-refresh": {
5524 "version": "0.10.0",
5272 "version": "0.10.0",
5525 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
5273 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@@ -5643,11 +5391,6 @@
5643 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
5391 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
5644 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
5392 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
5645 },
5393 },
5646− "remove-accents": {
5647− "version": "0.4.2",
5648− "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
5649− "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
5650− },
5651 "remove-markdown-and-html": {
5394 "remove-markdown-and-html": {
5652 "version": "0.1.0",
5395 "version": "0.1.0",
5653 "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
5396 "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
@@ -5674,14 +5417,6 @@
5674 "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
5417 "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
5675 "dev": true
5418 "dev": true
5676 },
5419 },
5677− "rimraf": {
5678− "version": "3.0.2",
5679− "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
5680− "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
5681− "requires": {
5682− "glob": "^7.1.3"
5683− }
5684− },
5685 "rollup": {
5420 "rollup": {
5686 "version": "2.75.7",
5421 "version": "2.75.7",
5687 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
5422 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
@@ -5828,15 +5563,6 @@
5828 "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
5563 "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
5829 "dev": true
5564 "dev": true
5830 },
5565 },
5831− "unload": {
5832− "version": "2.2.0",
5833− "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
5834− "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
5835− "requires": {
5836− "@babel/runtime": "^7.6.2",
5837− "detect-node": "^2.0.4"
5838− }
5839− },
5840 "update-browserslist-db": {
5566 "update-browserslist-db": {
5841 "version": "1.0.4",
5567 "version": "1.0.4",
5842 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
5568 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
@@ -5928,11 +5654,6 @@
5928 "connect-history-api-fallback": "^1.6.0"
5654 "connect-history-api-fallback": "^1.6.0"
5929 }
5655 }
5930 },
5656 },
5931− "wrappy": {
5932− "version": "1.0.2",
5933− "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
5934− "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
5935− },
5936 "xtend": {
5657 "xtend": {
5937 "version": "4.0.2",
5658 "version": "4.0.2",
5938 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
5659 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
ui/package.json+3−2
@@ -14,16 +14,17 @@
14 "@radix-ui/react-dropdown-menu": "^0.1.6",
14 "@radix-ui/react-dropdown-menu": "^0.1.6",
15 "@radix-ui/react-select": "^0.1.1",
15 "@radix-ui/react-select": "^0.1.1",
16 "@radix-ui/react-toggle-group": "^0.1.5",
16 "@radix-ui/react-toggle-group": "^0.1.5",
17+ "@tanstack/react-query": "^4.29.5",
17− "@urbit/api": "^2.0.0",
18+ "@urbit/api": "2.2",
18 "@urbit/http-api": "^2.0.0",
19 "@urbit/http-api": "^2.0.0",
19 "big-integer": "^1.6.51",
20 "big-integer": "^1.6.51",
20 "classnames": "^2.3.1",
21 "classnames": "^2.3.1",
22+ "color2k": "^2.0.2",
21 "lodash.debounce": "^4.0.8",
23 "lodash.debounce": "^4.0.8",
22 "query-string": "^7.1.1",
24 "query-string": "^7.1.1",
23 "react": "^17.0.2",
25 "react": "^17.0.2",
24 "react-dom": "^17.0.2",
26 "react-dom": "^17.0.2",
25 "react-hook-form": "^7.33.1",
27 "react-hook-form": "^7.33.1",
26− "react-query": "^3.39.1",
27 "react-router-dom": "^6.3.0",
28 "react-router-dom": "^6.3.0",
28 "react-select": "^5.4.0",
29 "react-select": "^5.4.0",
29 "remove-markdown-and-html": "^0.1.0",
30 "remove-markdown-and-html": "^0.1.0",
ui/src/app.tsx+1−2
@@ -1,5 +1,5 @@
1import React from 'react';
1import React from 'react';
2−import { QueryClient, QueryClientProvider } from 'react-query';
2+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3import { BrowserRouter, Route, Routes } from 'react-router-dom';
3import { BrowserRouter, Route, Routes } from 'react-router-dom';
4import { CenterColumn } from './components/CenterColumn';
4import { CenterColumn } from './components/CenterColumn';
5import { Layout } from './components/Layout';
5import { Layout } from './components/Layout';
@@ -9,7 +9,6 @@ import { Groups } from './manage-listings/Groups';
9import { MyListings } from './manage-listings/MyListings';
9import { MyListings } from './manage-listings/MyListings';
10import { Notebooks } from './manage-listings/Notebooks';
10import { Notebooks } from './manage-listings/Notebooks';
11import { Post } from './manage-listings/Post';
11import { Post } from './manage-listings/Post';
12−import { Posts } from './manage-listings/Posts';
13import { Search } from './pages/Search';
12import { Search } from './pages/Search';
14import { Tag } from './pages/Tag';
13import { Tag } from './pages/Tag';
15
14
ui/src/components/Listing.tsxnew file+74
@@ -0,0 +1,74 @@
1+import { UsersIcon, UserRemoveIcon } from '@heroicons/react/solid';
2+import { deSig } from '@urbit/api';
3+import React from 'react';
4+import { ListingImage } from './ListingImage';
5+import { Listing as ListingType } from '../types/sphinx';
6+import { normalizeLink, refToURL } from '../utils';
7+import { Pals } from '../state/pals';
8+import { ChannelPreview } from '../types';
9+import { useChannelPreview } from '../state/groups';
10+
11+interface ListingProps extends ListingType {
12+ remove: (hash: string) => void;
13+ removePal: (ship: string) => void;
14+ outgoing: Pals;
15+}
16+
17+function getURL(link: string | { nest: string, where: string }, preview?: ChannelPreview) {
18+ if (typeof link === 'string') {
19+ return link;
20+ }
21+
22+ const prefix = `/apps/groups`;
23+ const suffix = `/${link.nest}/${link.where}`
24+
25+ if (!preview) {
26+ return `${prefix}/1/chan${suffix}`;
27+ }
28+
29+ return `${prefix}/groups/${preview.group.flag}/channels${suffix}`;
30+}
31+
32+export const Listing = ({ hash, time, source, post, remove, removePal, outgoing }: ListingProps) => {
33+ const cite = normalizeLink(post.link);
34+ const link = refToURL(cite);
35+ const preview = useChannelPreview(typeof link === 'object' ? link.nest : '');
36+ const hardURL = getURL(link, preview);
37+
38+ return (
39+ <article className='group-1 flex w-full'>
40+ <ListingImage {...post} className="hidden sm:inline-block w-24 h-24 rounded-xl" />
41+ <div className='flex-1 min-w-0 sm:ml-4'>
42+ <div className='flex items-center'>
43+ <ListingImage {...post} className="flex-none sm:hidden w-14 h-14 mr-2 mb-2 rounded-xl" />
44+ <a className='flex-1 min-w-0 mb-2 hover:text-rosy transition-colors' href={hardURL}>
45+ <h2 className='block font-semibold text-lg leading-none mb-1'>{post.title}</h2>
46+ <div className='font-mono text-xs space-x-3 truncate'>
47+ <strong>%{post.type}</strong>
48+ <span>{cite}</span>
49+ </div>
50+ </a>
51+ </div>
52+ <p className='mb-2 text-sm'>{post.description}</p>
53+ <div className='flex flex-col items-start sm:flex-row sm:items-center w-full font-mono text-xs text-zinc-500 sm:opacity-0 group-1-hover:opacity-100 transition-opacity'>
54+ <div className='flex items-center'>
55+ <span className='inline-flex items-center'>
56+ <span className='font-semibold font-sans mr-1'>by:</span>
57+ <span>{source}</span>
58+ {outgoing[deSig(source) || ''] && (
59+ <button className='group-2 relative inline-flex items-center justify-center px-2 hover:text-rosy' aria-label='Remove pal' title="Remove Pal" onClick={() => removePal(deSig(source) || '')}>
60+ <UsersIcon className='h-4 w-4 opacity-0 sm:opacity-100 group-2-hover:opacity-0' />
61+ <UserRemoveIcon className='absolute h-4 w-4 sm:opacity-0 group-2-hover:opacity-100' />
62+ </button>
63+ )}
64+ </span>
65+ <time dateTime={(new Date(time)).toISOString()} className="ml-3">
66+ {(new Date(time)).toLocaleString()}
67+ </time>
68+ </div>
69+ <button className='sm:ml-auto font-semibold font-sans hover:text-rosy bg-transparent border-0' onClick={() => remove(hash)}>remove</button>
70+ </div>
71+ </div>
72+ </article>
73+ )
74+}
ui/src/components/Listings.tsx+4−39
@@ -1,13 +1,11 @@
1−import { UserRemoveIcon, UsersIcon } from '@heroicons/react/solid';
2−import { deSig, uxToHex } from '@urbit/api';
3import cn from 'classnames';
1import cn from 'classnames';
4import React from 'react';
2import React from 'react';
5import { usePals } from '../state/pals';
3import { usePals } from '../state/pals';
6−import { Listing } from '../types/sphinx';
4+import { Listing as ListingType } from '../types/sphinx';
7−import { ListingImage } from './ListingImage';
5+import { Listing } from './Listing';
8
6
9interface ListingsProps {
7interface ListingsProps {
10− listings: Listing[];
8+ listings: ListingType[];
11 remove: (hash: string) => void;
9 remove: (hash: string) => void;
12 className?: string;
10 className?: string;
13}
11}
@@ -35,40 +33,7 @@ export const Listings = ({ listings, remove, className }: ListingsProps) => {
35 <ul className={cn('space-y-6', className)}>
33 <ul className={cn('space-y-6', className)}>
36 {listings && listings.map(l => (
34 {listings && listings.map(l => (
37 <li key={l.hash}>
35 <li key={l.hash}>
38− <article className='group-1 flex w-full'>
36+ <Listing {...l} outgoing={outgoing} remove={remove} removePal={removePal} />
39− <ListingImage {...l.post} className="hidden sm:inline-block w-24 h-24 rounded-xl" />
40− <div className='flex-1 min-w-0 sm:ml-4'>
41− <div className='flex items-center'>
42− <ListingImage {...l.post} className="flex-none sm:hidden w-14 h-14 mr-2 mb-2 rounded-xl" />
43− <a className='flex-1 min-w-0 mb-2 hover:text-rosy transition-colors' href={l.post.link}>
44− <h2 className='block font-semibold text-lg leading-none mb-1'>{l.post.title}</h2>
45− <div className='font-mono text-xs space-x-3 truncate'>
46− <strong>%{l.post.type}</strong>
47− <span>{l.post.link.replace('web+urbitgraph://', '')}</span>
48− </div>
49− </a>
50− </div>
51− <p className='mb-2 text-sm'>{l.post.description}</p>
52− <div className='flex flex-col items-start sm:flex-row sm:items-center w-full font-mono text-xs text-zinc-500 sm:opacity-0 group-1-hover:opacity-100 transition-opacity'>
53− <div className='flex items-center'>
54− <span className='inline-flex items-center'>
55− <span className='font-semibold font-sans mr-1'>by:</span>
56− <span>{l.source}</span>
57− {outgoing[deSig(l.source) || ''] && (
58− <button className='group-2 relative inline-flex items-center justify-center px-2 hover:text-rosy' aria-label='Remove pal' title="Remove Pal" onClick={() => removePal(deSig(l.source) || '')}>
59− <UsersIcon className='h-4 w-4 opacity-0 sm:opacity-100 group-2-hover:opacity-0' />
60− <UserRemoveIcon className='absolute h-4 w-4 sm:opacity-0 group-2-hover:opacity-100' />
61− </button>
62− )}
63− </span>
64− <time dateTime={(new Date(l.time)).toISOString()} className="ml-3">
65− {(new Date(l.time)).toLocaleString()}
66− </time>
67− </div>
68− <button className='sm:ml-auto font-semibold font-sans hover:text-rosy bg-transparent border-0' onClick={() => remove(l.hash)}>remove</button>
69− </div>
70− </div>
71− </article>
72 </li>
37 </li>
73 ))}
38 ))}
74 </ul>
39 </ul>
ui/src/components/PostOptions.tsx+3−3
@@ -1,9 +1,9 @@
1import { CheckIcon } from '@heroicons/react/solid';
1import { CheckIcon } from '@heroicons/react/solid';
2−import { uxToHex } from '@urbit/api';
3import cn from 'classnames';
2import cn from 'classnames';
4import React from 'react';
3import React from 'react';
5import { useFormContext } from 'react-hook-form';
4import { useFormContext } from 'react-hook-form';
6import { PostOption, PostOptionsForm } from '../types/sphinx';
5import { PostOption, PostOptionsForm } from '../types/sphinx';
6+import { normalizeColor } from '../utils';
7
7
8interface PostOptionsProps {
8interface PostOptionsProps {
9 options: PostOption[];
9 options: PostOption[];
@@ -28,12 +28,12 @@ export const PostOptions = ({ options, showTags = false, className }: PostOption
28 src={o.post.image}
28 src={o.post.image}
29 className="show-when-checked w-16 h-16 col-span-1 object-cover rounded-lg opacity-60"
29 className="show-when-checked w-16 h-16 col-span-1 object-cover rounded-lg opacity-60"
30 loading={index > 15 ? 'lazy' : 'eager'}
30 loading={index > 15 ? 'lazy' : 'eager'}
31− style={{ backgroundColor: o.post.color ? `#${uxToHex(o.post.color)}` : undefined }}
31+ style={{ backgroundColor: o.post.color ? normalizeColor(o.post.color) : undefined }}
32 />
32 />
33 ) : (
33 ) : (
34 <div
34 <div
35 className='w-16 h-16 col-span-1 bg-rosy/20 rounded-lg'
35 className='w-16 h-16 col-span-1 bg-rosy/20 rounded-lg'
36− style={{ backgroundColor: o.post.color ? `#${uxToHex(o.post.color)}` : undefined }}
36+ style={{ backgroundColor: o.post.color ? normalizeColor(o.post.color) : undefined }}
37 />
37 />
38 )}
38 )}
39 <div className='ml-4 min-w-0 sm:space-y-2'>
39 <div className='ml-4 min-w-0 sm:space-y-2'>
ui/src/keys.tsnew file+14
@@ -0,0 +1,14 @@
1+import { QueryKey } from "@tanstack/react-query";
2+
3+export const GROUPS_KEY: QueryKey = ['groups'];
4+export const VATS_KEY: QueryKey = ['vats'];
5+export const PALS_KEY: QueryKey = ['pals'];
6+export const TAGS_KEY: QueryKey = ['tags'];
7+export const CHARGES_KEY: QueryKey = ['charges'];
8+export const APP_LISTINGS_KEY: QueryKey = ['app-listings'];
9+export const GROUP_LISTINGS_KEY: QueryKey = ['group-listings'];
10+export const POST_LISTINGS_KEY: QueryKey = ['post-listings'];
11+export const PUBLISHED_KEY: QueryKey = ['published'];
12+export const notesKey = (diary?: string): QueryKey => diary ? ['notes', diary] : ['notes'];
13+export const tagKey = (tag?: string) => (start: number, size: number): QueryKey => ['tag', tag || '', size, start];
14+export const lookupKey = (selected: string, lookup: string | undefined) => (start: number, size: number): QueryKey => ['lookup', selected, size.toString(), start.toString(), lookup || ''];
ui/src/logic/utils.tsnew file+11
@@ -0,0 +1,11 @@
1+export function preSig(ship: string): string {
2+ if (!ship) {
3+ return '';
4+ }
5+
6+ if (ship.trim().startsWith('~')) {
7+ return ship.trim();
8+ }
9+
10+ return '~'.concat(ship.trim());
11+}
ui/src/manage-listings/AllListings.tsx+4−3
@@ -1,11 +1,12 @@
1import React from 'react';
1import React from 'react';
2−import { useMutation, useQueryClient } from 'react-query';
2+import { useMutation, useQueryClient } from '@tanstack/react-query';
3import { useParams } from 'react-router-dom';
3import { useParams } from 'react-router-dom';
4import api from '../api';
4import api from '../api';
5import { Listings } from '../components/Listings';
5import { Listings } from '../components/Listings';
6import { Paginator } from '../components/Paginator';
6import { Paginator } from '../components/Paginator';
7import { useSearch } from '../state/search';
7import { useSearch } from '../state/search';
8import { Remove, Search } from '../types/sphinx';
8import { Remove, Search } from '../types/sphinx';
9+import { PUBLISHED_KEY } from '../keys';
9
10
10interface RouteParams extends Record<string, string | undefined> {
11interface RouteParams extends Record<string, string | undefined> {
11 limit?: string;
12 limit?: string;
@@ -24,7 +25,7 @@ export const AllListings = () => {
24 pageInt,
25 pageInt,
25 linkBuild
26 linkBuild
26 } = useSearch({
27 } = useSearch({
27− key: (start, size) => `all-${start}-${size}`,
28+ key: (start, size) => ['all', start, size],
28 fetcher: (start, size) => api.scry<Search>({
29 fetcher: (start, size) => api.scry<Search>({
29 app: 'sphinx',
30 app: 'sphinx',
30 path: `/lookup/all/${start}/${size}`
31 path: `/lookup/all/${start}/${size}`
@@ -42,7 +43,7 @@ export const AllListings = () => {
42 })
43 })
43 }, {
44 }, {
44 onSuccess: () => {
45 onSuccess: () => {
45− queryClient.invalidateQueries('published');
46+ queryClient.invalidateQueries(PUBLISHED_KEY);
46 }
47 }
47 });
48 });
48
49
ui/src/manage-listings/Apps.tsx+3−2
@@ -1,12 +1,13 @@
1import React, { useCallback, useEffect, useState } from 'react';
1import React, { useCallback, useEffect, useState } from 'react';
2import { FormProvider, useForm } from 'react-hook-form';
2import { FormProvider, useForm } from 'react-hook-form';
3−import { useMutation, useQueryClient } from 'react-query';
3+import { useMutation, useQueryClient } from '@tanstack/react-query';
4import { Link } from 'react-router-dom';
4import { Link } from 'react-router-dom';
5import api from '../api';
5import api from '../api';
6import { PostOptions } from '../components/PostOptions';
6import { PostOptions } from '../components/PostOptions';
7import { Spinner } from '../components/Spinner';
7import { Spinner } from '../components/Spinner';
8import { useApps } from '../state/apps';
8import { useApps } from '../state/apps';
9import { Declare, Listing, PostOption, PostOptionsForm } from '../types/sphinx';
9import { Declare, Listing, PostOption, PostOptionsForm } from '../types/sphinx';
10+import { APP_LISTINGS_KEY } from '../keys';
10
11
11function getAppKeys(apps: PostOption[]): string[] {
12function getAppKeys(apps: PostOption[]): string[] {
12 return apps.map(({ key }) => key);
13 return apps.map(({ key }) => key);
@@ -42,7 +43,7 @@ export const Apps = () => {
42 });
43 });
43 }, {
44 }, {
44 onSuccess: () => {
45 onSuccess: () => {
45− queryClient.invalidateQueries('app-listings');
46+ queryClient.invalidateQueries(APP_LISTINGS_KEY);
46 }
47 }
47 })
48 })
48
49
ui/src/manage-listings/Groups.tsx+4−3
@@ -1,12 +1,13 @@
1import React, { useCallback, useEffect, useState } from 'react';
1import React, { useCallback, useEffect, useState } from 'react';
2import { FormProvider, useForm } from 'react-hook-form';
2import { FormProvider, useForm } from 'react-hook-form';
3−import { useMutation, useQueryClient } from 'react-query';
3+import { useMutation, useQueryClient } from '@tanstack/react-query';
4import { Link } from 'react-router-dom';
4import { Link } from 'react-router-dom';
5import api from '../api';
5import api from '../api';
6import { PostOptions } from '../components/PostOptions';
6import { PostOptions } from '../components/PostOptions';
7import { Spinner } from '../components/Spinner';
7import { Spinner } from '../components/Spinner';
8import { useGroups } from '../state/groups';
8import { useGroups } from '../state/groups';
9−import { Declare, PostOption, PostOptionsForm } from '../types/sphinx';
9+import { Declare, PostOptionsForm } from '../types/sphinx';
10+import { GROUP_LISTINGS_KEY } from '../keys';
10
11
11export const Groups = () => {
12export const Groups = () => {
12 const { groups, loading } = useGroups();
13 const { groups, loading } = useGroups();
@@ -49,7 +50,7 @@ export const Groups = () => {
49 });
50 });
50 }, {
51 }, {
51 onSuccess: () => {
52 onSuccess: () => {
52− queryClient.invalidateQueries('group-listings')
53+ queryClient.invalidateQueries(GROUP_LISTINGS_KEY)
53 }
54 }
54 })
55 })
55
56
ui/src/manage-listings/MyListings.tsx+4−4
@@ -1,13 +1,13 @@
1−import { size } from 'lodash';
2import React from 'react';
1import React from 'react';
3−import { useMutation, useQuery, useQueryClient } from 'react-query';
2+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4import api from '../api';
3import api from '../api';
5import { Listings } from '../components/Listings';
4import { Listings } from '../components/Listings';
6import { Directory, Remove } from '../types/sphinx';
5import { Directory, Remove } from '../types/sphinx';
6+import { PUBLISHED_KEY } from '../keys';
7
7
8export const MyListings = () => {
8export const MyListings = () => {
9 const queryClient = useQueryClient();
9 const queryClient = useQueryClient();
10− const { data } = useQuery('published', () => api.scry<Directory>({
10+ const { data } = useQuery(PUBLISHED_KEY, () => api.scry<Directory>({
11 app: 'sphinx',
11 app: 'sphinx',
12 path: '/published'
12 path: '/published'
13 }));
13 }));
@@ -19,7 +19,7 @@ export const MyListings = () => {
19 })
19 })
20 }, {
20 }, {
21 onSuccess: () => {
21 onSuccess: () => {
22− queryClient.invalidateQueries('published');
22+ queryClient.invalidateQueries(PUBLISHED_KEY);
23 }
23 }
24 });
24 });
25
25
ui/src/manage-listings/Notebooks.tsx+13−11
@@ -1,18 +1,18 @@
1−import { uxToHex } from '@urbit/api';
2import cn from 'classnames';
1import cn from 'classnames';
3import React from 'react';
2import React from 'react';
4−import { Link, NavLink, useParams } from 'react-router-dom';
3+import { NavLink, useParams } from 'react-router-dom';
5import { Spinner } from '../components/Spinner';
4import { Spinner } from '../components/Spinner';
6−import useMedia, { useIsMobile } from '../logic/useMedia';
5+import useMedia from '../logic/useMedia';
7import { useNotebooks } from '../state/posts';
6import { useNotebooks } from '../state/posts';
8import { Posts } from './Posts';
7import { Posts } from './Posts';
8+import { getColor, getImage } from '../utils';
9
9
10export const Notebooks = () => {
10export const Notebooks = () => {
11 const { ship, name } = useParams<{ ship: string, name: string }>();
11 const { ship, name } = useParams<{ ship: string, name: string }>();
12 const limited = useMedia('(max-width: 899px)');
12 const limited = useMedia('(max-width: 899px)');
13 const { groups, notebooks, loading } = useNotebooks();
13 const { groups, notebooks, loading } = useNotebooks();
14 const sortedNotebooks = Object.entries(notebooks)
14 const sortedNotebooks = Object.entries(notebooks)
15− .sort(([,a], [,b]) => a.metadata.title.localeCompare(b.metadata.title));
15+ .sort(([,a], [,b]) => a.meta.title.localeCompare(b.meta.title));
16
16
17 return (
17 return (
18 <div className="flex-none flex flex-col max-w-5xl w-full p-4 sm:py-12 sm:px-8 space-y-6 sm:pl-32 xl:pl-8">
18 <div className="flex-none flex flex-col max-w-5xl w-full p-4 sm:py-12 sm:px-8 space-y-6 sm:pl-32 xl:pl-8">
@@ -28,27 +28,29 @@ export const Notebooks = () => {
28 <span>Loading notebooks...</span>
28 <span>Loading notebooks...</span>
29 </div>}
29 </div>}
30 {sortedNotebooks.map(([id, n], index) => {
30 {sortedNotebooks.map(([id, n], index) => {
31− const [,group] = groups.find(([,v]) => v.group === n.group) || [];
31+ const [,group] = groups.find(([k]) => k === n.perms.group) || [];
32+ const image = group && (getImage(group.meta.image) || getImage(group.meta.cover) || '');
33+ const color = group && (getColor(group.meta.image) || getColor(group.meta.cover) || '');
32
34
33 return (
35 return (
34 <li key={id}>
36 <li key={id}>
35 <NavLink to={`/manage-listings/posts/${id}`} className={({ isActive }) => cn('flex items-center p-2 space-x-2 leading-tight rounded-md hover:bg-fawn', isActive && 'bg-fawn')}>
37 <NavLink to={`/manage-listings/posts/${id}`} className={({ isActive }) => cn('flex items-center p-2 space-x-2 leading-tight rounded-md hover:bg-fawn', isActive && 'bg-fawn')}>
36− {group && group.metadata.picture ? (
38+ {group && image ? (
37 <img
39 <img
38− src={group.metadata.picture}
40+ src={image}
39 className="flex-none w-10 h-10 object-cover rounded-md"
41 className="flex-none w-10 h-10 object-cover rounded-md"
40 loading={index > 15 ? 'lazy' : 'eager'}
42 loading={index > 15 ? 'lazy' : 'eager'}
41− style={{ backgroundColor: group.metadata.color ? `#${uxToHex(group.metadata.color)}` : undefined }}
43+ style={{ backgroundColor: color }}
42 />
44 />
43 ) : group ? (
45 ) : group ? (
44 <div
46 <div
45 className='flex-none w-10 h-10 bg-rosy/20 rounded-md'
47 className='flex-none w-10 h-10 bg-rosy/20 rounded-md'
46− style={{ backgroundColor: group.metadata.color ? `#${uxToHex(group.metadata.color)}` : undefined }}
48+ style={{ backgroundColor: color }}
47 />
49 />
48 ) : null}
50 ) : null}
49 <div className='flex flex-col min-w-0'>
51 <div className='flex flex-col min-w-0'>
50− <strong className='w-full truncate'>{n.metadata.title}</strong>
52+ <strong className='w-full truncate'>{n.meta.title}</strong>
51− {group && <span className='text-sm'>{group.metadata.title}</span>}
53+ {group && <span className='text-sm'>{group.meta.title}</span>}
52 </div>
54 </div>
53 </NavLink>
55 </NavLink>
54 </li>
56 </li>
ui/src/manage-listings/Posts.tsx+6−5
@@ -1,8 +1,8 @@
1import { ChevronLeftIcon } from '@heroicons/react/solid';
1import { ChevronLeftIcon } from '@heroicons/react/solid';
2import React, { useCallback, useEffect, useState } from 'react';
2import React, { useCallback, useEffect, useState } from 'react';
3import { FormProvider, useForm } from 'react-hook-form';
3import { FormProvider, useForm } from 'react-hook-form';
4−import { useMutation, useQueryClient } from 'react-query';
4+import { useMutation, useQueryClient } from '@tanstack/react-query';
5−import { Link, useParams } from 'react-router-dom';
5+import { Link } from 'react-router-dom';
6import { MultiValue } from 'react-select';
6import { MultiValue } from 'react-select';
7import api from '../api';
7import api from '../api';
8import { Filter } from '../components/Filter';
8import { Filter } from '../components/Filter';
@@ -12,6 +12,7 @@ import { TagField, Option } from '../components/TagField';
12import useMedia from '../logic/useMedia';
12import useMedia from '../logic/useMedia';
13import { usePosts } from '../state/posts';
13import { usePosts } from '../state/posts';
14import { Declare, PostOptionsForm, PostType } from '../types/sphinx';
14import { Declare, PostOptionsForm, PostType } from '../types/sphinx';
15+import { POST_LISTINGS_KEY } from '../keys';
15
16
16interface PostsProps {
17interface PostsProps {
17 ship: string;
18 ship: string;
@@ -62,7 +63,7 @@ export const Posts = ({ ship, name }: PostsProps) => {
62 });
63 });
63 }, {
64 }, {
64 onSuccess: () => {
65 onSuccess: () => {
65− queryClient.invalidateQueries('post-listings')
66+ queryClient.invalidateQueries(POST_LISTINGS_KEY)
66 }
67 }
67 })
68 })
68
69
@@ -77,10 +78,10 @@ export const Posts = ({ ship, name }: PostsProps) => {
77 {limited && (
78 {limited && (
78 <Link to="/manage-listings/posts" className='flex items-center'>
79 <Link to="/manage-listings/posts" className='flex items-center'>
79 <ChevronLeftIcon className='h-6 w-6 text-mauve/60' />
80 <ChevronLeftIcon className='h-6 w-6 text-mauve/60' />
80− <h1 className='text-2xl font-semibold'>{notebook?.metadata?.title}</h1>
81+ <h1 className='text-2xl font-semibold'>{notebook?.meta.title}</h1>
81 </Link>
82 </Link>
82 )}
83 )}
83− {!limited && <h1 className='text-2xl font-semibold'>{notebook?.metadata?.title}</h1>}
84+ {!limited && <h1 className='text-2xl font-semibold'>{notebook?.meta.title}</h1>}
84 {posts.length > 0 && (
85 {posts.length > 0 && (
85 <button className='p-1.5 ml-auto text-sm underline font-semibold' onClick={toggleAll}>
86 <button className='p-1.5 ml-auto text-sm underline font-semibold' onClick={toggleAll}>
86 {options.length !== posts.length && 'select all'}
87 {options.length !== posts.length && 'select all'}
ui/src/pages/Search.tsx+5−4
@@ -1,7 +1,7 @@
1import cn from 'classnames';
1import cn from 'classnames';
2import React, { useCallback, useRef, useState } from 'react';
2import React, { useCallback, useRef, useState } from 'react';
3import debounce from 'lodash.debounce';
3import debounce from 'lodash.debounce';
4−import { useMutation, useQueryClient } from 'react-query';
4+import { useMutation, useQueryClient } from '@tanstack/react-query';
5import { useNavigate, useParams } from 'react-router-dom';
5import { useNavigate, useParams } from 'react-router-dom';
6import { SearchInput } from '../components/SearchInput';
6import { SearchInput } from '../components/SearchInput';
7import { Listings } from '../components/Listings';
7import { Listings } from '../components/Listings';
@@ -15,6 +15,7 @@ import { TagCloud } from '../components/TagCloud';
15import { useTags } from '../state/tags';
15import { useTags } from '../state/tags';
16import { useSearch } from '../state/search';
16import { useSearch } from '../state/search';
17import { encodeLookup } from '../utils';
17import { encodeLookup } from '../utils';
18+import { lookupKey } from '../keys';
18
19
19interface RouteParams extends Record<string, string | undefined> {
20interface RouteParams extends Record<string, string | undefined> {
20 lookup?: string;
21 lookup?: string;
@@ -42,7 +43,7 @@ export const Search = () => {
42 isLoading,
43 isLoading,
43 linkBuild
44 linkBuild
44 } = useSearch({
45 } = useSearch({
45− key: (start, size) => `lookup-${selected}-${size}-${start}-${lookup}`,
46+ key: lookupKey(selected, lookup),
46 fetcher: (start, size) => api.scry<SearchType>({
47 fetcher: (start, size) => api.scry<SearchType>({
47 app: 'sphinx',
48 app: 'sphinx',
48 path: `/lookup/${selected}/${start}/${size}/${encodeLookup(lookup)}`
49 path: `/lookup/${selected}/${start}/${size}/${encodeLookup(lookup)}`
@@ -62,7 +63,7 @@ export const Search = () => {
62 })
63 })
63 }, {
64 }, {
64 onSuccess: () => {
65 onSuccess: () => {
65− queryClient.invalidateQueries(`lookup-${selected}-${size}-${start}-${lookup}`)
66+ queryClient.invalidateQueries(lookupKey(selected, lookup)(start, size))
66 }
67 }
67 });
68 });
68
69
@@ -82,7 +83,7 @@ export const Search = () => {
82 return (
83 return (
83 <div className={cn('w-full space-y-6', !lookup && 'm-auto')}>
84 <div className={cn('w-full space-y-6', !lookup && 'm-auto')}>
84 <header className='flex items-center space-x-2'>
85 <header className='flex items-center space-x-2'>
85− <SearchInput className='flex-1' lookup={rawSearch} loading={isLoading} onChange={onChange} />
86+ <SearchInput className='flex-1' lookup={rawSearch} loading={!!lookup && isLoading} onChange={onChange} />
86 <Filter selected={selected} onSelect={setSelected} className="min-w-0 sm:w-20" />
87 <Filter selected={selected} onSelect={setSelected} className="min-w-0 sm:w-20" />
87 </header>
88 </header>
88 {!palsInstalled && (
89 {!palsInstalled && (
ui/src/pages/Tag.tsx+2−3
@@ -2,7 +2,7 @@ import { PlusIcon, PlusSmIcon } from '@heroicons/react/solid';
2import cn from 'classnames';
2import cn from 'classnames';
3import qs from 'query-string';
3import qs from 'query-string';
4import React from 'react';
4import React from 'react';
5−import { useQueryClient, useMutation } from 'react-query';
5+import { useQueryClient, useMutation } from '@tanstack/react-query';
6import { Link, useParams } from 'react-router-dom';
6import { Link, useParams } from 'react-router-dom';
7import api from '../api';
7import api from '../api';
8import { Listings } from '../components/Listings';
8import { Listings } from '../components/Listings';
@@ -12,6 +12,7 @@ import { useSearch } from '../state/search';
12import { useTags } from '../state/tags';
12import { useTags } from '../state/tags';
13import { Remove, Search } from '../types/sphinx';
13import { Remove, Search } from '../types/sphinx';
14import { encodeLookup } from '../utils';
14import { encodeLookup } from '../utils';
15+import { tagKey } from '../keys';
15
16
16interface RouteParams extends Record<string, string | undefined> {
17interface RouteParams extends Record<string, string | undefined> {
17 tag?: string;
18 tag?: string;
@@ -19,8 +20,6 @@ interface RouteParams extends Record<string, string | undefined> {
19 page?: string;
20 page?: string;
20}
21}
21
22
22−const tagKey = (tag?: string) => (start: number, size: number) => `tag-${tag || ''}-${size}-${start}`
23−
24export const Tag = () => {
23export const Tag = () => {
25 const tags = useTags();
24 const tags = useTags();
26 const { tag, limit, page } = useParams<RouteParams>();
25 const { tag, limit, page } = useParams<RouteParams>();
ui/src/state/apps.ts+13−12
@@ -1,15 +1,16 @@
1−import { Charge, ChargeUpdateInitial, getVats, scryCharges, Vat, Vats } from "@urbit/api";
1+import { Charge, ChargeUpdateInitial, getPikes, Pike, Pikes, scryCharges } from "@urbit/api";
2−import { useQuery } from "react-query"
2+import { useQuery } from "@tanstack/react-query"
3import api from "../api";
3import api from "../api";
4import { Post, Search } from "../types/sphinx";
4import { Post, Search } from "../types/sphinx";
5+import { APP_LISTINGS_KEY, CHARGES_KEY, VATS_KEY } from "../keys";
5
6
6−function getAppPost(app: string, vat: Vat, charge: Charge): Post {
7+function getAppPost(app: string, pike: Pike, charge: Charge): Post {
7− const ship = vat.arak.rail?.publisher || vat.arak.rail?.ship || `~${window.ship}`;
8+ const ship = pike.sync?.ship || `~${window.ship}`;
8
9
9 return {
10 return {
10 type: 'app',
11 type: 'app',
11 title: charge.title,
12 title: charge.title,
12− link: `web+urbitgraph://${ship}/${app}`,
13+ link: `/1/desk/${ship}/${app}`, //`/apps/grid/search/${ship}/apps/${ship}/${app}`,
13 description: charge.info || charge.title,
14 description: charge.info || charge.title,
14 image: charge.image || '',
15 image: charge.image || '',
15 color: charge.color,
16 color: charge.color,
@@ -18,18 +19,18 @@ function getAppPost(app: string, vat: Vat, charge: Charge): Post {
18}
19}
19
20
20export const useApps = () => {
21export const useApps = () => {
21− const { data: vats, isLoading: vatsLoading } = useQuery('vats', () => api.scry<Vats>(getVats));
22+ const { data: pikes, isLoading: pikesLoading } = useQuery(VATS_KEY, () => api.scry<Pikes>(getPikes));
22− const { data: charges, isLoading: chargesLoading } = useQuery('charges', () => api.scry<ChargeUpdateInitial>(scryCharges));
23+ const { data: charges, isLoading: chargesLoading } = useQuery(CHARGES_KEY, () => api.scry<ChargeUpdateInitial>(scryCharges));
23− const { data, isLoading } = useQuery('app-listings', () => api.scry<Search>({
24+ const { data, isLoading } = useQuery(APP_LISTINGS_KEY, () => api.scry<Search>({
24 app: 'sphinx',
25 app: 'sphinx',
25 path: '/lookup/app/0/999'
26 path: '/lookup/app/0/999'
26 }));
27 }));
27
28
28 return {
29 return {
29− loading: vatsLoading || chargesLoading || isLoading,
30+ loading: pikesLoading || chargesLoading || isLoading,
30− apps: !vats || !charges || !data ? [] : Object.entries(charges.initial)
31+ apps: !pikes || !charges || !data ? [] : Object.entries(charges.initial)
31− .filter(([k]) => k in vats)
32+ .filter(([k]) => k in pikes)
32− .map(([k,v]) => ({ key: k, post: getAppPost(k, vats[k], v) }))
33+ .map(([k,v]) => ({ key: k, post: getAppPost(k, pikes[k], v) }))
33 .filter(({ post }) => !data.listings.some(l => l.post.link === post.link || l.post.title === post.title))
34 .filter(({ post }) => !data.listings.some(l => l.post.link === post.link || l.post.title === post.title))
34 }
35 }
35}
36}
ui/src/state/groups.ts+54−20
@@ -1,26 +1,34 @@
1−import { Associations, GroupUpdateInitial, MetadataUpdateInitial } from "@urbit/api";
1+import { useQuery } from "@tanstack/react-query"
2−import { useQuery } from "react-query"
3import api from "../api"
2import api from "../api"
4−import { PostOption, PostType, Search } from "../types/sphinx";
3+import { PostType, Search } from "../types/sphinx";
4+import { ChannelPreview, Groups } from "../types";
5+import { useMemo } from "react";
6+import { GROUPS_KEY, GROUP_LISTINGS_KEY } from "../keys";
7+import { getColor, getImage, isColor } from "../utils";
5
8
6−export type MetaDataUpdate = { 'metadata-update': MetadataUpdateInitial };
9+export const usePublicGroups = () => {
10+ const { data, ...query } = useQuery(GROUPS_KEY,
11+ () => api.scry<Groups>({
12+ app: 'groups',
13+ path: '/groups/light'
14+ }),
15+ { initialData: {} }
16+ );
7
17
8−export const usePublicGroups = (metadata?: { 'metadata-update': MetadataUpdateInitial }) => {
18+ const groups = useMemo(() => {
9− const { data, isLoading } = useQuery('metadata', () => api.subscribeOnce<MetaDataUpdate>('metadata-store', '/all'), {
19+ if (!data) return [];
10− enabled: !metadata
20+ return Object.entries(data).filter(([,v]) => !v.secret);
11− });
21+ }, [data]);
12− const { data: groupData, isLoading: groupDataLoading } = useQuery('groups', () => api.subscribeOnce<{ groupUpdate: GroupUpdateInitial }>('group-store', '/groups'));
13
22
14 return {
23 return {
15− loading: isLoading || groupDataLoading,
24+ ...query,
16− groups: !data || !groupData ? [] : Object.entries(data['metadata-update'].associations)
25+ groups
17− .filter(([k,v]) => v['app-name'] === 'groups' && 'open' in (groupData.groupUpdate.initial[v.resource]?.policy || {}))
18 }
26 }
19}
27}
20
28
21export const useGroups = () => {
29export const useGroups = () => {
22− const { groups, loading } = usePublicGroups();
30+ const { groups, isLoading: loading } = usePublicGroups();
23− const { data: d, isLoading } = useQuery('group-listings', () => api.scry<Search>({
31+ const { data: d, isLoading } = useQuery(GROUP_LISTINGS_KEY, () => api.scry<Search>({
24 app: 'sphinx',
32 app: 'sphinx',
25 path: '/lookup/group/0/999'
33 path: '/lookup/group/0/999'
26 }));
34 }));
@@ -29,13 +37,39 @@ export const useGroups = () => {
29 loading: loading || isLoading,
37 loading: loading || isLoading,
30 groups: groups.map(([k,v]) => ({ key: k, post: {
38 groups: groups.map(([k,v]) => ({ key: k, post: {
31 type: 'group' as PostType,
39 type: 'group' as PostType,
32− title: v.metadata.title,
40+ title: v.meta.title,
33− link: v.group.replace('/ship/', 'web+urbitgraph://group/'),
41+ link: `/1/group/${k}`, //`/apps/groups/groups/${k}`,
34− image: v.metadata.picture,
42+ image: getImage(v.meta.image) || getImage(v.meta.cover) || '',
35− color: v.metadata.color,
43+ color: getColor(v.meta.image) || getColor(v.meta.cover) || '',
36− description: v.metadata.description,
44+ description: v.meta.description,
37 tags: ['group']
45 tags: ['group']
38 }}))
46 }}))
39 .filter(({ post }) => !d || d.listings.length === 0 || !d.listings.some(l => l.post.link === post.link || l.post.title === post.title))
47 .filter(({ post }) => !d || d.listings.length === 0 || !d.listings.some(l => l.post.link === post.link || l.post.title === post.title))
40 }
48 }
41−}
49+}
50+
51+export function useChannelPreview(nest: string): ChannelPreview | undefined {
52+ const { groups } = usePublicGroups();
53+ const group = groups.find(([,g]) => {
54+ return !!g.channels[nest];
55+ });
56+
57+ const { data } = useQuery(['channel-preview', nest],
58+ () => api.subscribeOnce<ChannelPreview>('groups', `/chan/${nest}`, 5000),
59+ {
60+ enabled: groups && !!nest && !group,
61+ refetchOnMount: false,
62+ refetchOnWindowFocus: false,
63+ },
64+ );
65+
66+ return group ? {
67+ nest,
68+ meta: group[1].channels[nest].meta,
69+ group: {
70+ flag: group[0],
71+ meta: group[1].meta,
72+ secret: group[1].secret
73+ }
74+ } : data;
75+}
ui/src/state/pals.ts+6−5
@@ -1,13 +1,14 @@
1import { ChargeUpdateInitial, scryCharges } from '@urbit/api';
1import { ChargeUpdateInitial, scryCharges } from '@urbit/api';
2−import { useMutation, useQuery, useQueryClient } from 'react-query';
2+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
3import api from '../api';
3import api from '../api';
4+import { CHARGES_KEY, PALS_KEY } from '../keys';
4
5
5interface Pal {
6interface Pal {
6 lists: string[];
7 lists: string[];
7 ack: true | null;
8 ack: true | null;
8}
9}
9
10
10−interface Pals {
11+export interface Pals {
11 [k: string]: Pal;
12 [k: string]: Pal;
12}
13}
13
14
@@ -26,10 +27,10 @@ interface RemovePal {
26
27
27export const usePals = () => {
28export const usePals = () => {
28 const queryClient = useQueryClient();
29 const queryClient = useQueryClient();
29− const { data: charges } = useQuery('charges', () => {
30+ const { data: charges } = useQuery(CHARGES_KEY, () => {
30 return api.scry<ChargeUpdateInitial>(scryCharges);
31 return api.scry<ChargeUpdateInitial>(scryCharges);
31 })
32 })
32− const { data } = useQuery('pals', () => {
33+ const { data } = useQuery(PALS_KEY, () => {
33 return api.scry<PalsData>({
34 return api.scry<PalsData>({
34 app: 'pals',
35 app: 'pals',
35 path: '/json'
36 path: '/json'
@@ -48,7 +49,7 @@ export const usePals = () => {
48 })
49 })
49 }, {
50 }, {
50 onSuccess: () => {
51 onSuccess: () => {
51− queryClient.invalidateQueries('pals');
52+ queryClient.invalidateQueries(PALS_KEY);
52 }
53 }
53 })
54 })
54
55
ui/src/state/posts.ts+56−151
@@ -1,83 +1,69 @@
1−import { Association, BigIntOrderedMap, buntPost, Content, getGraph, getKeys, Graph, GraphNode, MetadataConfig, Post, resourceFromPath, TextContent, UrlContent } from "@urbit/api";
2−import bigInt, { BigInteger } from "big-integer";
3−import removeMd from 'remove-markdown-and-html';
4import _ from "lodash";
1import _ from "lodash";
5−import { useQuery } from "react-query"
2+import { useQuery } from "@tanstack/react-query"
6import api from "../api"
3import api from "../api"
7import { PostType, Search } from "../types/sphinx";
4import { PostType, Search } from "../types/sphinx";
8−import { MetaDataUpdate, usePublicGroups } from "./groups";
5+import { usePublicGroups } from "./groups";
6+import { POST_LISTINGS_KEY, notesKey } from "../keys";
7+import { Diary, DiaryOutlines, GroupChannel, NoteContent, Shelf, VerseInline, inlineToString } from "../types";
8+import { useMemo } from "react";
9+import { udToDec } from '@urbit/api';
9
10
10−interface Resource {
11+export const useNotebooks = () => {
11− ship: string;
12+ const { groups, isLoading: groupsLoading } = usePublicGroups();
12− name: string;
13+ const { data, isLoading } = useQuery(notesKey(), () => api.scry<Shelf>({
13−}
14+ app: 'diary',
15+ path: '/shelf'
16+ }));
17+
18+ const notebooks = useMemo(() => {
19+ const nbs: Record<string, GroupChannel & Diary> = {};
20+ debugger;
21+ if (!data) return nbs;
14
22
15−interface GraphKeyUpdate {
23+ Object.entries(data).forEach(([k,v]) => {
16− 'graph-update': {
24+ const group = groups.find(([g]) => g === v.perms.group);
17− keys: Resource[]
25+ if (!group) return;
18− }
19−}
20
26
21−export const useNotebooks = () => {
27+ debugger;
22− const { data: keys, isLoading: keysLoading } = useQuery('graph-keys', async () => {
28+ const channel = group[1].channels[`diary/${k}`];
23− const json = await api.scry<GraphKeyUpdate>(getKeys())
29+ if (!channel) return;
24− const update = _.get(json, 'graph-update', false);
25− const data: Resource[] = _.get(update, 'keys', false) || [];
26
30
27− return new Set(data.map((res) => {
31+ if (channel.readers.length === 0) {
28− const resource = res.ship + '/' + res.name;
32+ nbs[k] = { ...v, ...channel };
29− return resource;
33+ }
30− }));
34+ });
31− });
32− const { data, isLoading: metadataLoading } = useQuery('metadata', () => api.subscribeOnce<MetaDataUpdate>('metadata-store', '/all'));
33− const { groups, loading } = usePublicGroups(data || {} as MetaDataUpdate);
34
35
35− const notebooks: Record<string, Association<MetadataConfig>> = _.fromPairs(!data || loading ? [] : Object.entries(data['metadata-update'].associations)
36+ return nbs;
36− .filter(([,v]) => v['app-name'] === 'graph'
37+ }, [data, groups]);
37− && 'graph' in v.metadata.config
38− && v.metadata.config.graph === 'publish'
39− && keys?.has(v.resource.replace('/ship/~', ''))
40− && groups.find(([, gv]) => gv.group === v.group)
41− ).map(([,v]) => [v.resource.replace('/ship/', ''), v]));
42
38
43 return {
39 return {
44− loading: metadataLoading || loading,
40+ notebooks,
45− notebooks,
41+ groups,
46− groups
42+ loading: isLoading || groupsLoading
47 }
43 }
48}
44}
49
45
50export const usePosts = (ship: string, name: string, type: PostType, tags: string[]) => {
46export const usePosts = (ship: string, name: string, type: PostType, tags: string[]) => {
47+ const flag = `${ship}/${name}`;
51 const { notebooks, loading } = useNotebooks();
48 const { notebooks, loading } = useNotebooks();
52− const association = notebooks[`${ship}/${name}`] || {};
53− const { data, isLoading } = useQuery(`graph-${association?.resource}`, () => api.scry<Graph>(getGraph(ship, name)), {
49+ const { data, isLoading } = useQuery(notesKey(flag), () => api.scry<DiaryOutlines>({
54− enabled: !!association?.resource
50+ app: 'diary',
51+ path: `/diary/${flag}/notes/newest/9.999.999/outline`
55− });
52+ }));
56− const { data: d, isLoading: postsLoading } = useQuery('post-listings', () => api.scry<Search>({
53+ const { data: d, isLoading: postsLoading } = useQuery(POST_LISTINGS_KEY, () => api.scry<Search>({
57 app: 'sphinx',
54 app: 'sphinx',
58− path: '/lookup/tag/0/999/~~post'
55+ path: '/lookup/tag/0/9.999/~~post'
59 }));
56 }));
60− const graph = addGraph(data);
57+
61− const items = data && graph ? Array.from(graph)
58+ const items = data ? Object.entries(data)
62− .filter(([, node]) => node && typeof node?.post !== 'string')
63− .map(([date, node]) => {
59+ .map(([date, outline]) => {
64− const index = node.post.index.split('/');
65− const noteId = bigInt(index[1]);
66− const { title, body } = getLatestRevision(node);
67− const snippet = getSnippet(body);
68− const permalink = getPermalinkForGraph(
69− association.group,
70− association.resource,
71− `/${noteId.toString()}`
72− );
73−
74 return { key: date.toString(), post: {
60 return { key: date.toString(), post: {
75 type,
61 type,
76− title,
62+ title: outline.title,
77− link: permalink,
63+ link: getPermalink(flag, date),
78− image: snippet.image,
64+ image: outline.image,
79 color: '',
65 color: '',
80− description: snippet.content,
66+ description: getSnippet(outline.content),
81 tags
67 tags
82 }}
68 }}
83 }) : [];
69 }) : [];
@@ -85,108 +71,27 @@ export const usePosts = (ship: string, name: string, type: PostType, tags: strin
85 const posts = items.filter(p => !d || d.listings.length === 0 || !d.listings.find(l => l.post.link === p.post.link));
71 const posts = items.filter(p => !d || d.listings.length === 0 || !d.listings.find(l => l.post.link === p.post.link));
86
72
87 return {
73 return {
88− notebook: association,
74+ notebook: notebooks[flag],
89 loading: loading || isLoading || postsLoading,
75 loading: loading || isLoading || postsLoading,
90 posts
76 posts
91 }
77 }
92}
78}
93
79
94−function getImage(snippet: string) {
80+function getPermalink(flag: string, time: string): string {
95− const pattern = /!\[.*\]\((.*)\)/
81+ return `/1/chan/diary/${flag}/note/${udToDec(time)}`;
96− const match = snippet.match(pattern);
97−
98− if (!match) {
99− return '';
100− }
101−
102− return match[1];
103−}
104−
105−function getLatestRevision(node: GraphNode): { id: number, title: string, body: Content[], post: Post } {
106− const empty = { id: 1, title: '', body: [], post: buntPost() };
107− const revs = node.children?.get(bigInt(1));
108− if (!revs || revs.children === null) {
109− return empty;
110− }
111−
112− const [revNum, rev] = [...revs.children][0];
113− const title = rev.post.contents[0] as TextContent;
114− const body = rev.post.contents.slice(1);
115− return {
116− id: revNum.toJSNumber(),
117− title: title.text,
118− body,
119− post: rev.post
120− };
121}
82}
122
83
123−export function getSnippet(body: Content[]): { image: string, content: string } {
84+function getSnippet(content: NoteContent): string {
124− const IMAGE_REGEX = new RegExp(
85+ const inlines = content.filter(c => 'inlines' in c) as VerseInline[];
125− /(\.jpg|\.img|\.png|\.gif|\.tiff|\.jpeg|\.webp|\.webm|\.svg)$/i
86+ if (inlines.length === 0) return '';
126− );
127
87
128− let sum = 0;
88+ const text = inlines[0].inlines.map(inlineToString).join(' ');
129− const firstContent = body.reduce((text, c) => {
130− if ('text' in c && sum < 255) {
131− sum += removeMd(c.text).length;
132− return text + c.text + ' ';
133− }
134−
135− return text;
136− }, '');
137− const image = body.filter((c: Content): c is UrlContent => 'url' in c && IMAGE_REGEX.test(c.url)).map(c => c.url)[0] || '';
138− const content = removeMd(firstContent.replace(/!\[.*\]\((.*)\)\s*/g, ''));
139− const end = content.length > 256 ? 253 : content.length;
89+ const end = text.length > 256 ? 253 : text.length;
140− let start = content.substring(0, end);
90+ let start = text.substring(0, end);
141 const length = new Blob([start]).size;
91 const length = new Blob([start]).size;
142 if (length > end) {
92 if (length > end) {
143 start = start.substring(0, end - (length - end))
93 start = start.substring(0, end - (length - end))
144 }
94 }
145
95
146− return {
147− content: start === content ? start : `${start}...`,
96+ return start === text ? start : `${start}...`;
148− image: getImage(firstContent) || image
149− }
150−}
151−
152−export function getPermalinkForGraph(
153− group: string,
154− graph: string,
155− index = ''
156−) {
157− const groupLink = getPermalinkForAssociatedGroup(group);
158− const { ship, name } = resourceFromPath(graph);
159− return `${groupLink}/graph/${ship}/${name}${index}`;
160−}
161−
162−function getPermalinkForAssociatedGroup(group: string) {
163− const { ship, name } = resourceFromPath(group);
164− return `web+urbitgraph://group/${ship}/${name}`;
165}
97}
166−
167−const addGraph = (json: any): Graph => {
168− const update = _.get(json, 'graph-update', false);
169− const data = _.get(update, 'add-graph', false);
170− let graph = new BigIntOrderedMap<GraphNode>();
171− const items: [BigInteger, GraphNode][] = data.graph ? Object.entries(data.graph as any).map(([idx, value]) => [bigInt(idx), processNode(value as GraphNode)]) : [];
172−
173− if (data) {
174− graph = graph.gas(items);
175− }
176−
177− return graph;
178−};
179−
180−const processNode = (node: GraphNode): GraphNode => {
181− const empty = new BigIntOrderedMap<GraphNode>();
182− const items = _.map<GraphNode, [BigInteger, GraphNode]>((node.children || {}) as any, (item: GraphNode, idx: number) => {
183− return [bigInt(idx), processNode(item)]
184− })
185−
186− const newNode = {
187− ...node,
188− children: !node.children ? empty : empty.gas(items)
189− };
190−
191− return newNode;
192−};
ui/src/state/search.ts+3−3
@@ -1,9 +1,9 @@
1import { useCallback } from "react";
1import { useCallback } from "react";
2−import { useQuery } from "react-query";
2+import { QueryKey, useQuery } from "@tanstack/react-query";
3import { Search } from "../types/sphinx";
3import { Search } from "../types/sphinx";
4
4
5interface UseSearchParams {
5interface UseSearchParams {
6− key: (start: number, size: number) => string;
6+ key: (start: number, size: number) => QueryKey;
7 fetcher: (start: number, size: number) => Promise<Search>;
7 fetcher: (start: number, size: number) => Promise<Search>;
8 enabled: boolean;
8 enabled: boolean;
9 linkPrefix: string;
9 linkPrefix: string;
@@ -16,7 +16,7 @@ export const useSearch = ({ key, fetcher, enabled, limit, page, linkPrefix }: Us
16 const pageInt = parseInt(page || '1', 10) - 1;
16 const pageInt = parseInt(page || '1', 10) - 1;
17 const start = pageInt * size;
17 const start = pageInt * size;
18
18
19− const { data, ...query } = useQuery<unknown, unknown, Search>(key(start, size), () => fetcher(start, size), {
19+ const { data, ...query } = useQuery<Search>(key(start, size), () => fetcher(start, size), {
20 enabled,
20 enabled,
21 keepPreviousData: true
21 keepPreviousData: true
22 });
22 });
ui/src/state/tags.ts+3−2
@@ -1,10 +1,11 @@
1import { useMemo } from "react";
1import { useMemo } from "react";
2−import { useQuery } from "react-query";
2+import { useQuery } from "@tanstack/react-query";
3import api from "../api";
3import api from "../api";
4import { Tags } from "../types/sphinx";
4import { Tags } from "../types/sphinx";
5+import { TAGS_KEY } from "../keys";
5
6
6export const useTags = () => {
7export const useTags = () => {
7− const { data } = useQuery('tags', () => api.scry<Tags>({
8+ const { data } = useQuery(TAGS_KEY, () => api.scry<Tags>({
8 app: 'sphinx',
9 app: 'sphinx',
9 path: '/tags'
10 path: '/tags'
10 }));
11 }));
ui/src/types/index.tsnew file+246
@@ -0,0 +1,246 @@
1+export interface GroupMeta {
2+ title: string;
3+ description: string;
4+ image: string;
5+ cover: string;
6+}
7+
8+export interface Vessel {
9+ sects: string[];
10+ joined: number;
11+}
12+
13+export interface Fleet {
14+ [ship: string]: Vessel;
15+}
16+
17+export interface GroupChannel {
18+ meta: GroupMeta;
19+ readers: string[];
20+}
21+
22+export interface Channels {
23+ [nest: string]: GroupChannel;
24+}
25+
26+export interface Group {
27+ fleet: Fleet;
28+ channels: Channels;
29+ meta: GroupMeta;
30+ secret: boolean;
31+}
32+
33+export interface Groups {
34+ [flag: string]: Group;
35+}
36+
37+export interface GroupPreview {
38+ flag: string;
39+ meta: GroupMeta;
40+ time?: number;
41+ secret: boolean;
42+}
43+
44+export interface ChannelPreview {
45+ nest: string;
46+ meta: GroupMeta;
47+ group: GroupPreview;
48+}
49+
50+export interface Ship {
51+ ship: string;
52+}
53+
54+export interface Italics {
55+ italics: Inline[];
56+}
57+
58+export interface Bold {
59+ bold: Inline[];
60+}
61+
62+export interface Strikethrough {
63+ strike: Inline[];
64+}
65+
66+export interface Break {
67+ break: null;
68+}
69+
70+export interface InlineCode {
71+ 'inline-code': string;
72+}
73+
74+export interface BlockCode {
75+ code: string;
76+}
77+
78+export interface Blockquote {
79+ blockquote: Inline[];
80+}
81+
82+/**
83+ A reference to the accompanying blocks, indexed at 0
84+*/
85+export interface BlockReference {
86+ block: {
87+ index: number;
88+ text: string;
89+ };
90+}
91+
92+export interface Tag {
93+ tag: string;
94+}
95+
96+export interface Link {
97+ link: {
98+ href: string;
99+ content: string;
100+ };
101+}
102+
103+export type Inline =
104+ | string
105+ | Bold
106+ | Italics
107+ | Strikethrough
108+ | Ship
109+ | Break
110+ | InlineCode
111+ | BlockCode
112+ | Blockquote
113+ | BlockReference
114+ | Tag
115+ | Link;
116+
117+export type InlineKey =
118+ | 'italics'
119+ | 'bold'
120+ | 'strike'
121+ | 'blockquote'
122+ | 'inline-code'
123+ | 'block'
124+ | 'code'
125+ | 'tag'
126+ | 'link'
127+ | 'break';
128+
129+export function isBold(item: unknown): item is Bold {
130+ return typeof item === 'object' && item !== null && 'bold' in item;
131+}
132+
133+export function isItalics(item: unknown): item is Italics {
134+ return typeof item === 'object' && item !== null && 'italics' in item;
135+}
136+
137+export function isLink(item: unknown): item is Link {
138+ return typeof item === 'object' && item !== null && 'link' in item;
139+}
140+
141+export function isStrikethrough(item: unknown): item is Strikethrough {
142+ return typeof item === 'object' && item !== null && 'strike' in item;
143+}
144+
145+export function isBlockquote(item: unknown): item is Blockquote {
146+ return typeof item === 'object' && item !== null && 'blockquote' in item;
147+}
148+
149+export function isInlineCode(item: unknown): item is InlineCode {
150+ return typeof item === 'object' && item !== null && 'inline-code' in item;
151+}
152+
153+export function isBlockCode(item: unknown): item is BlockCode {
154+ return typeof item === 'object' && item !== null && 'code' in item;
155+}
156+
157+export function isBreak(item: unknown): item is Break {
158+ return typeof item === 'object' && item !== null && 'break' in item;
159+}
160+
161+export function isShip(item: unknown): item is Ship {
162+ return typeof item === 'object' && item !== null && 'ship' in item;
163+}
164+
165+export function inlineToString(inline: Inline): any {
166+ if (typeof inline === 'string') {
167+ return inline;
168+ }
169+
170+ if (isBold(inline)) {
171+ return inline.bold.map((i: Inline) => inlineToString(i)).join(' ');
172+ }
173+
174+ if (isItalics(inline)) {
175+ return inline.italics.map((i: Inline) => inlineToString(i));
176+ }
177+
178+ if (isStrikethrough(inline)) {
179+ return inline.strike.map((i: Inline) => inlineToString(i));
180+ }
181+
182+ if (isLink(inline)) {
183+ return inline.link.content;
184+ }
185+
186+ if (isBlockquote(inline)) {
187+ return Array.isArray(inline.blockquote)
188+ ? inline.blockquote.map((i) => inlineToString(i)).join(' ')
189+ : inline.blockquote;
190+ }
191+
192+ if (isInlineCode(inline)) {
193+ return typeof inline['inline-code'] === 'object'
194+ ? inlineToString(inline['inline-code'])
195+ : inline['inline-code'];
196+ }
197+
198+ if (isShip(inline)) {
199+ return inline.ship;
200+ }
201+
202+ return '';
203+}
204+
205+export interface VerseInline {
206+ inlines: Inline[];
207+}
208+
209+export interface VerseBlock {
210+ block: any;
211+}
212+
213+export type Verse = VerseInline | VerseBlock;
214+
215+export type NoteContent = Verse[];
216+
217+export interface NoteEssay {
218+ title: string;
219+ image: string;
220+ content: NoteContent;
221+ author: string;
222+ sent: number;
223+}
224+
225+export interface DiaryOutline extends NoteEssay {
226+ type: 'outline';
227+ quipCount: number;
228+ quippers: string[];
229+}
230+
231+export interface DiaryOutlines {
232+ [time: string]: DiaryOutline;
233+}
234+
235+export interface DiaryPerm {
236+ writers: string[];
237+ group: string;
238+}
239+
240+export interface Diary {
241+ perms: DiaryPerm;
242+}
243+
244+export interface Shelf {
245+ [key: string]: Diary;
246+}
ui/src/utils.ts+81−2
@@ -1,4 +1,8 @@
1+import { useQuery } from "@tanstack/react-query";
1−import { stringToTa } from "@urbit/api";
2+import { stringToTa, uxToHex } from "@urbit/api";
3+import { parseToRgba } from "color2k";
4+import api from "./api";
5+import { ChannelPreview } from "./types";
2
6
3export function encodeLookup(value: string | undefined) {
7export function encodeLookup(value: string | undefined) {
4 if (!value) {
8 if (!value) {
@@ -6,4 +10,79 @@ export function encodeLookup(value: string | undefined) {
6 }
10 }
7
11
8 return stringToTa(value).replace('~.', '~~');
12 return stringToTa(value).replace('~.', '~~');
9−}
13+}
14+
15+export function isColor(color: string): boolean {
16+ try {
17+ parseToRgba(color);
18+ return true;
19+ } catch (error) {
20+ return false;
21+ }
22+}
23+
24+export function getColor(image: string): string | undefined {
25+ return isColor(image) ? image : undefined;
26+}
27+
28+export function getImage(image: string): string | undefined {
29+ return isColor(image) ? undefined : image;
30+}
31+
32+export function normalizeColor(color: string): string {
33+ if (color.startsWith('#')) {
34+ return color;
35+ }
36+
37+ return `#${uxToHex(color)}`
38+}
39+
40+export function normalizeLink(link: string): string {
41+ // ref or URL format
42+ if (!link.startsWith('web+urbitgraph://') || link.startsWith('http')) {
43+ return link;
44+ }
45+
46+ // old format
47+ const normLink = link.replace('web+urbitgraph://', '');
48+ const parts = normLink.split('/');
49+ const first = parts[0];
50+
51+ if (first !== 'group') {
52+ return `/1/desk/${normLink}`;
53+ }
54+
55+ if (parts.length === 3) {
56+ return `/1/group/${parts[1]}/${parts[2]}`;
57+ }
58+
59+ return `/1/chan/diary/${parts[4]}/${parts[5]}/note/${parts[6]}`
60+}
61+
62+export function refToURL(cite: string): string | { nest: string, where: string } {
63+ const parts = cite.slice(1).split('/');
64+ if (parts[0] !== '1') {
65+ return '';
66+ }
67+
68+ const type = parts[1];
69+ if (type === 'desk') {
70+ const ship = parts[2];
71+ const desk = parts[3];
72+ return `/apps/grid/search/${ship}/apps/${ship}/${desk}`;
73+ }
74+
75+ if (type === 'group') {
76+ const ship = parts[2];
77+ const name = parts[3];
78+ return `/apps/groups/groups/${ship}/${name}`;
79+ }
80+
81+ const chanType = parts[2];
82+ const ship = parts[3];
83+ const name = parts[4];
84+ return {
85+ nest: `${chanType}/${ship}/${name}`,
86+ where: parts.slice(5).join('/'),
87+ }
88+}