on master help sign in
ui: refactor to work with groups2 and latest react query
commit000ad6e06abd3f234fdca3576b267d6b72788966 authorHunter Miller <hunter@hmiller.dev> dateSat 29 Apr 2023, 20:12 (3 years ago) parent372eb82
25 files changed+696−638
desk/desk.docket-0+1−1 ui/package-lock.json+82−361 ui/package.json+3−2 ui/src/app.tsx+1−2 ui/src/components/Listing.tsx+74 ui/src/components/Listings.tsx+4−39 ui/src/components/PostOptions.tsx+3−3 ui/src/keys.ts+14 ui/src/logic/utils.ts+11 ui/src/manage-listings/AllListings.tsx+4−3 ui/src/manage-listings/Apps.tsx+3−2 ui/src/manage-listings/Groups.tsx+4−3 ui/src/manage-listings/MyListings.tsx+4−4 ui/src/manage-listings/Notebooks.tsx+13−11 ui/src/manage-listings/Posts.tsx+6−5 ui/src/pages/Search.tsx+5−4 ui/src/pages/Tag.tsx+2−3 ui/src/state/apps.ts+13−12 ui/src/state/groups.ts+54−20 ui/src/state/pals.ts+6−5 ui/src/state/posts.ts+56−151 ui/src/state/search.ts+3−3 ui/src/state/tags.ts+3−2 ui/src/types/index.ts+246 ui/src/utils.ts+81−2
desk/desk.docket-0+1−1
@@ -4,7 +4,7 @@
44 image+'https://nyc3.digitaloceanspaces.com/hmillerdev/nocsyx-lassul/2022.7.16..04.35.22-sphinx-web-shifted.svg'
55 glob-http+['https://nyc3.digitaloceanspaces.com/hmillerdev/sphinx/glob-0v7.folii.k685s.mjn1b.46up6.but26.glob' 0v7.folii.k685s.mjn1b.46up6.but26]
66 base+'sphinx'
7− version+[0 5 0]
7+ version+[0 6 0]
88 website+'https://github.com'
99 license+'MIT'
1010==
ui/package-lock.json+82−361
@@ -12,16 +12,17 @@
1212 "@radix-ui/react-dropdown-menu": "^0.1.6",
1313 "@radix-ui/react-select": "^0.1.1",
1414 "@radix-ui/react-toggle-group": "^0.1.5",
15− "@urbit/api": "^2.0.0",
15+ "@tanstack/react-query": "^4.29.5",
16+ "@urbit/api": "2.2",
1617 "@urbit/http-api": "^2.0.0",
1718 "big-integer": "^1.6.51",
1819 "classnames": "^2.3.1",
20+ "color2k": "^2.0.2",
1921 "lodash.debounce": "^4.0.8",
2022 "query-string": "^7.1.1",
2123 "react": "^17.0.2",
2224 "react-dom": "^17.0.2",
2325 "react-hook-form": "^7.33.1",
24− "react-query": "^3.39.1",
2526 "react-router-dom": "^6.3.0",
2627 "react-select": "^5.4.0",
2728 "remove-markdown-and-html": "^0.1.0",
@@ -1068,6 +1069,49 @@
10681069 "node": ">= 8.0.0"
10691070 }
10701071 },
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+ },
10711115 "node_modules/@types/lodash": {
10721116 "version": "4.14.182",
10731117 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@@ -1126,9 +1170,9 @@
11261170 "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
11271171 },
11281172 "node_modules/@urbit/api": {
1129− "version": "2.1.1",
1130− "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz",
1131− "integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==",
1173+ "version": "2.2.0",
1174+ "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
1175+ "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
11321176 "dependencies": {
11331177 "@babel/runtime": "^7.16.0",
11341178 "big-integer": "^1.6.48",
@@ -1291,11 +1335,6 @@
12911335 "resolve": "^1.12.0"
12921336 }
12931337 },
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− },
12991338 "node_modules/big-integer": {
13001339 "version": "1.6.51",
13011340 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -1318,15 +1357,6 @@
13181357 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
13191358 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
13201359 },
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− },
13301360 "node_modules/braces": {
13311361 "version": "3.0.2",
13321362 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -1339,21 +1369,6 @@
13391369 "node": ">=8"
13401370 }
13411371 },
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− },
13571372 "node_modules/browser-or-node": {
13581373 "version": "1.3.0",
13591374 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
@@ -1488,10 +1503,10 @@
14881503 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
14891504 "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
14901505 },
1491− "node_modules/concat-map": {
1492− "version": "0.0.1",
1493− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1494− "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
1506+ "node_modules/color2k": {
1507+ "version": "2.0.2",
1508+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
1509+ "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
14951510 },
14961511 "node_modules/connect-history-api-fallback": {
14971512 "version": "1.6.0",
@@ -1582,11 +1597,6 @@
15821597 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
15831598 "dev": true
15841599 },
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− },
15901600 "node_modules/detect-node-es": {
15911601 "version": "1.1.0",
15921602 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -2095,11 +2105,6 @@
20952105 "url": "https://www.patreon.com/infusion"
20962106 }
20972107 },
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− },
21032108 "node_modules/fsevents": {
21042109 "version": "2.3.2",
21052110 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -2135,25 +2140,6 @@
21352140 "node": ">=6"
21362141 }
21372142 },
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− },
21572143 "node_modules/glob-parent": {
21582144 "version": "6.0.2",
21592145 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2233,20 +2219,6 @@
22332219 "url": "https://github.com/sponsors/sindresorhus"
22342220 }
22352221 },
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− },
22502222 "node_modules/invariant": {
22512223 "version": "2.2.4",
22522224 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -2313,11 +2285,6 @@
23132285 "node": ">=0.12.0"
23142286 }
23152287 },
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− },
23212288 "node_modules/js-tokens": {
23222289 "version": "4.0.0",
23232290 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2390,15 +2357,6 @@
23902357 "loose-envify": "cli.js"
23912358 }
23922359 },
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− },
24022360 "node_modules/memoize-one": {
24032361 "version": "5.2.1",
24042362 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -2426,22 +2384,6 @@
24262384 "node": ">=8.6"
24272385 }
24282386 },
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− },
24452387 "node_modules/minimist": {
24462388 "version": "1.2.6",
24472389 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@@ -2453,14 +2395,6 @@
24532395 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
24542396 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
24552397 },
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− },
24642398 "node_modules/nanoid": {
24652399 "version": "3.3.4",
24662400 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@@ -2513,19 +2447,6 @@
25132447 "node": ">= 6"
25142448 }
25152449 },
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− },
25292450 "node_modules/parent-module": {
25302451 "version": "1.0.1",
25312452 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -2554,14 +2475,6 @@
25542475 "url": "https://github.com/sponsors/sindresorhus"
25552476 }
25562477 },
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− },
25652478 "node_modules/path-parse": {
25662479 "version": "1.0.7",
25672480 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -2832,31 +2745,6 @@
28322745 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
28332746 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
28342747 },
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− },
28602748 "node_modules/react-refresh": {
28612749 "version": "0.10.0",
28622750 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@@ -3031,11 +2919,6 @@
30312919 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
30322920 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
30332921 },
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− },
30392922 "node_modules/remove-markdown-and-html": {
30402923 "version": "0.1.0",
30412924 "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
@@ -3075,20 +2958,6 @@
30752958 "node": ">=0.10.0"
30762959 }
30772960 },
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− },
30922961 "node_modules/rollup": {
30932962 "version": "2.75.7",
30942963 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
@@ -3302,15 +3171,6 @@
33023171 "node": ">=4.2.0"
33033172 }
33043173 },
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− },
33143174 "node_modules/update-browserslist-db": {
33153175 "version": "1.0.4",
33163176 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
@@ -3475,11 +3335,6 @@
34753335 "vite": "^2.0.2"
34763336 }
34773337 },
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− },
34833338 "node_modules/xtend": {
34843339 "version": "4.0.2",
34853340 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -4314,6 +4169,28 @@
43144169 "picomatch": "^2.2.2"
43154170 }
43164171 },
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+ },
43174194 "@types/lodash": {
43184195 "version": "4.14.182",
43194196 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@@ -4372,9 +4249,9 @@
43724249 "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
43734250 },
43744251 "@urbit/api": {
4375− "version": "2.1.1",
4376− "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz",
4377− "integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==",
4252+ "version": "2.2.0",
4253+ "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
4254+ "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
43784255 "requires": {
43794256 "@babel/runtime": "^7.16.0",
43804257 "big-integer": "^1.6.48",
@@ -4496,11 +4373,6 @@
44964373 "resolve": "^1.12.0"
44974374 }
44984375 },
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− },
45044376 "big-integer": {
45054377 "version": "1.6.51",
45064378 "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
@@ -4517,15 +4389,6 @@
45174389 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
45184390 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
45194391 },
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− },
45294392 "braces": {
45304393 "version": "3.0.2",
45314394 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -4535,21 +4398,6 @@
45354398 "fill-range": "^7.0.1"
45364399 }
45374400 },
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− },
45534401 "browser-or-node": {
45544402 "version": "1.3.0",
45554403 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
@@ -4637,10 +4485,10 @@
46374485 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
46384486 "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
46394487 },
4640− "concat-map": {
4641− "version": "0.0.1",
4642− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
4643− "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
4488+ "color2k": {
4489+ "version": "2.0.2",
4490+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
4491+ "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
46444492 },
46454493 "connect-history-api-fallback": {
46464494 "version": "1.6.0",
@@ -4703,11 +4551,6 @@
47034551 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
47044552 "dev": true
47054553 },
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− },
47114554 "detect-node-es": {
47124555 "version": "1.1.0",
47134556 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -5000,11 +4843,6 @@
50004843 "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
50014844 "dev": true
50024845 },
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− },
50084846 "fsevents": {
50094847 "version": "2.3.2",
50104848 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -5027,19 +4865,6 @@
50274865 "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
50284866 "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
50294867 },
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− },
50434868 "glob-parent": {
50444869 "version": "6.0.2",
50454870 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -5097,20 +4922,6 @@
50974922 "resolve-from": "^4.0.0"
50984923 }
50994924 },
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− },
51144925 "invariant": {
51154926 "version": "2.2.4",
51164927 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -5162,11 +4973,6 @@
51624973 "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
51634974 "dev": true
51644975 },
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− },
51704976 "js-tokens": {
51714977 "version": "4.0.0",
51724978 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5221,15 +5027,6 @@
52215027 "js-tokens": "^3.0.0 || ^4.0.0"
52225028 }
52235029 },
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− },
52335030 "memoize-one": {
52345031 "version": "5.2.1",
52355032 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -5251,19 +5048,6 @@
52515048 "picomatch": "^2.3.1"
52525049 }
52535050 },
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− },
52675051 "minimist": {
52685052 "version": "1.2.6",
52695053 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@@ -5275,14 +5059,6 @@
52755059 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
52765060 "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
52775061 },
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− },
52865062 "nanoid": {
52875063 "version": "3.3.4",
52885064 "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@@ -5317,19 +5093,6 @@
53175093 "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
53185094 "dev": true
53195095 },
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− },
53335096 "parent-module": {
53345097 "version": "1.0.1",
53355098 "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5349,11 +5112,6 @@
53495112 "lines-and-columns": "^1.1.6"
53505113 }
53515114 },
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− },
53575115 "path-parse": {
53585116 "version": "1.0.7",
53595117 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -5510,16 +5268,6 @@
55105268 "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
55115269 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
55125270 },
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− },
55235271 "react-refresh": {
55245272 "version": "0.10.0",
55255273 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@@ -5643,11 +5391,6 @@
56435391 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
56445392 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
56455393 },
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− },
56515394 "remove-markdown-and-html": {
56525395 "version": "0.1.0",
56535396 "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
@@ -5674,14 +5417,6 @@
56745417 "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
56755418 "dev": true
56765419 },
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− },
56855420 "rollup": {
56865421 "version": "2.75.7",
56875422 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
@@ -5828,15 +5563,6 @@
58285563 "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
58295564 "dev": true
58305565 },
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− },
58405566 "update-browserslist-db": {
58415567 "version": "1.0.4",
58425568 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
@@ -5928,11 +5654,6 @@
59285654 "connect-history-api-fallback": "^1.6.0"
59295655 }
59305656 },
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− },
59365657 "xtend": {
59375658 "version": "4.0.2",
59385659 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
ui/package.json+3−2
@@ -14,16 +14,17 @@
1414 "@radix-ui/react-dropdown-menu": "^0.1.6",
1515 "@radix-ui/react-select": "^0.1.1",
1616 "@radix-ui/react-toggle-group": "^0.1.5",
17− "@urbit/api": "^2.0.0",
17+ "@tanstack/react-query": "^4.29.5",
18+ "@urbit/api": "2.2",
1819 "@urbit/http-api": "^2.0.0",
1920 "big-integer": "^1.6.51",
2021 "classnames": "^2.3.1",
22+ "color2k": "^2.0.2",
2123 "lodash.debounce": "^4.0.8",
2224 "query-string": "^7.1.1",
2325 "react": "^17.0.2",
2426 "react-dom": "^17.0.2",
2527 "react-hook-form": "^7.33.1",
26− "react-query": "^3.39.1",
2728 "react-router-dom": "^6.3.0",
2829 "react-select": "^5.4.0",
2930 "remove-markdown-and-html": "^0.1.0",
ui/src/app.tsx+1−2
@@ -1,5 +1,5 @@
11import React from 'react';
2−import { QueryClient, QueryClientProvider } from 'react-query';
2+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
33import { BrowserRouter, Route, Routes } from 'react-router-dom';
44import { CenterColumn } from './components/CenterColumn';
55import { Layout } from './components/Layout';
@@ -9,7 +9,6 @@ import { Groups } from './manage-listings/Groups';
99import { MyListings } from './manage-listings/MyListings';
1010import { Notebooks } from './manage-listings/Notebooks';
1111import { Post } from './manage-listings/Post';
12−import { Posts } from './manage-listings/Posts';
1312import { Search } from './pages/Search';
1413import { Tag } from './pages/Tag';
1514
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';
31import cn from 'classnames';
42import React from 'react';
53import { usePals } from '../state/pals';
6−import { Listing } from '../types/sphinx';
7−import { ListingImage } from './ListingImage';
4+import { Listing as ListingType } from '../types/sphinx';
5+import { Listing } from './Listing';
86
97interface ListingsProps {
10− listings: Listing[];
8+ listings: ListingType[];
119 remove: (hash: string) => void;
1210 className?: string;
1311}
@@ -35,40 +33,7 @@ export const Listings = ({ listings, remove, className }: ListingsProps) => {
3533 <ul className={cn('space-y-6', className)}>
3634 {listings && listings.map(l => (
3735 <li key={l.hash}>
38− <article className='group-1 flex w-full'>
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>
36+ <Listing {...l} outgoing={outgoing} remove={remove} removePal={removePal} />
7237 </li>
7338 ))}
7439 </ul>
ui/src/components/PostOptions.tsx+3−3
@@ -1,9 +1,9 @@
11import { CheckIcon } from '@heroicons/react/solid';
2−import { uxToHex } from '@urbit/api';
32import cn from 'classnames';
43import React from 'react';
54import { useFormContext } from 'react-hook-form';
65import { PostOption, PostOptionsForm } from '../types/sphinx';
6+import { normalizeColor } from '../utils';
77
88interface PostOptionsProps {
99 options: PostOption[];
@@ -28,12 +28,12 @@ export const PostOptions = ({ options, showTags = false, className }: PostOption
2828 src={o.post.image}
2929 className="show-when-checked w-16 h-16 col-span-1 object-cover rounded-lg opacity-60"
3030 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 }}
3232 />
3333 ) : (
3434 <div
3535 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 }}
3737 />
3838 )}
3939 <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 @@
11import React from 'react';
2−import { useMutation, useQueryClient } from 'react-query';
2+import { useMutation, useQueryClient } from '@tanstack/react-query';
33import { useParams } from 'react-router-dom';
44import api from '../api';
55import { Listings } from '../components/Listings';
66import { Paginator } from '../components/Paginator';
77import { useSearch } from '../state/search';
88import { Remove, Search } from '../types/sphinx';
9+import { PUBLISHED_KEY } from '../keys';
910
1011interface RouteParams extends Record<string, string | undefined> {
1112 limit?: string;
@@ -24,7 +25,7 @@ export const AllListings = () => {
2425 pageInt,
2526 linkBuild
2627 } = useSearch({
27− key: (start, size) => `all-${start}-${size}`,
28+ key: (start, size) => ['all', start, size],
2829 fetcher: (start, size) => api.scry<Search>({
2930 app: 'sphinx',
3031 path: `/lookup/all/${start}/${size}`
@@ -42,7 +43,7 @@ export const AllListings = () => {
4243 })
4344 }, {
4445 onSuccess: () => {
45− queryClient.invalidateQueries('published');
46+ queryClient.invalidateQueries(PUBLISHED_KEY);
4647 }
4748 });
4849
ui/src/manage-listings/Apps.tsx+3−2
@@ -1,12 +1,13 @@
11import React, { useCallback, useEffect, useState } from 'react';
22import { FormProvider, useForm } from 'react-hook-form';
3−import { useMutation, useQueryClient } from 'react-query';
3+import { useMutation, useQueryClient } from '@tanstack/react-query';
44import { Link } from 'react-router-dom';
55import api from '../api';
66import { PostOptions } from '../components/PostOptions';
77import { Spinner } from '../components/Spinner';
88import { useApps } from '../state/apps';
99import { Declare, Listing, PostOption, PostOptionsForm } from '../types/sphinx';
10+import { APP_LISTINGS_KEY } from '../keys';
1011
1112function getAppKeys(apps: PostOption[]): string[] {
1213 return apps.map(({ key }) => key);
@@ -42,7 +43,7 @@ export const Apps = () => {
4243 });
4344 }, {
4445 onSuccess: () => {
45− queryClient.invalidateQueries('app-listings');
46+ queryClient.invalidateQueries(APP_LISTINGS_KEY);
4647 }
4748 })
4849
ui/src/manage-listings/Groups.tsx+4−3
@@ -1,12 +1,13 @@
11import React, { useCallback, useEffect, useState } from 'react';
22import { FormProvider, useForm } from 'react-hook-form';
3−import { useMutation, useQueryClient } from 'react-query';
3+import { useMutation, useQueryClient } from '@tanstack/react-query';
44import { Link } from 'react-router-dom';
55import api from '../api';
66import { PostOptions } from '../components/PostOptions';
77import { Spinner } from '../components/Spinner';
88import { 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';
1011
1112export const Groups = () => {
1213 const { groups, loading } = useGroups();
@@ -49,7 +50,7 @@ export const Groups = () => {
4950 });
5051 }, {
5152 onSuccess: () => {
52− queryClient.invalidateQueries('group-listings')
53+ queryClient.invalidateQueries(GROUP_LISTINGS_KEY)
5354 }
5455 })
5556
ui/src/manage-listings/MyListings.tsx+4−4
@@ -1,13 +1,13 @@
1−import { size } from 'lodash';
21import React from 'react';
3−import { useMutation, useQuery, useQueryClient } from 'react-query';
2+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
43import api from '../api';
54import { Listings } from '../components/Listings';
65import { Directory, Remove } from '../types/sphinx';
6+import { PUBLISHED_KEY } from '../keys';
77
88export const MyListings = () => {
99 const queryClient = useQueryClient();
10− const { data } = useQuery('published', () => api.scry<Directory>({
10+ const { data } = useQuery(PUBLISHED_KEY, () => api.scry<Directory>({
1111 app: 'sphinx',
1212 path: '/published'
1313 }));
@@ -19,7 +19,7 @@ export const MyListings = () => {
1919 })
2020 }, {
2121 onSuccess: () => {
22− queryClient.invalidateQueries('published');
22+ queryClient.invalidateQueries(PUBLISHED_KEY);
2323 }
2424 });
2525
ui/src/manage-listings/Notebooks.tsx+13−11
@@ -1,18 +1,18 @@
1−import { uxToHex } from '@urbit/api';
21import cn from 'classnames';
32import React from 'react';
4−import { Link, NavLink, useParams } from 'react-router-dom';
3+import { NavLink, useParams } from 'react-router-dom';
54import { Spinner } from '../components/Spinner';
6−import useMedia, { useIsMobile } from '../logic/useMedia';
5+import useMedia from '../logic/useMedia';
76import { useNotebooks } from '../state/posts';
87import { Posts } from './Posts';
8+import { getColor, getImage } from '../utils';
99
1010export const Notebooks = () => {
1111 const { ship, name } = useParams<{ ship: string, name: string }>();
1212 const limited = useMedia('(max-width: 899px)');
1313 const { groups, notebooks, loading } = useNotebooks();
1414 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));
1616
1717 return (
1818 <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 = () => {
2828 <span>Loading notebooks...</span>
2929 </div>}
3030 {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) || '');
3234
3335 return (
3436 <li key={id}>
3537 <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 ? (
3739 <img
38− src={group.metadata.picture}
40+ src={image}
3941 className="flex-none w-10 h-10 object-cover rounded-md"
4042 loading={index > 15 ? 'lazy' : 'eager'}
41− style={{ backgroundColor: group.metadata.color ? `#${uxToHex(group.metadata.color)}` : undefined }}
43+ style={{ backgroundColor: color }}
4244 />
4345 ) : group ? (
4446 <div
4547 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 }}
4749 />
4850 ) : null}
4951 <div className='flex flex-col min-w-0'>
50− <strong className='w-full truncate'>{n.metadata.title}</strong>
51− {group && <span className='text-sm'>{group.metadata.title}</span>}
52+ <strong className='w-full truncate'>{n.meta.title}</strong>
53+ {group && <span className='text-sm'>{group.meta.title}</span>}
5254 </div>
5355 </NavLink>
5456 </li>
ui/src/manage-listings/Posts.tsx+6−5
@@ -1,8 +1,8 @@
11import { ChevronLeftIcon } from '@heroicons/react/solid';
22import React, { useCallback, useEffect, useState } from 'react';
33import { FormProvider, useForm } from 'react-hook-form';
4−import { useMutation, useQueryClient } from 'react-query';
5−import { Link, useParams } from 'react-router-dom';
4+import { useMutation, useQueryClient } from '@tanstack/react-query';
5+import { Link } from 'react-router-dom';
66import { MultiValue } from 'react-select';
77import api from '../api';
88import { Filter } from '../components/Filter';
@@ -12,6 +12,7 @@ import { TagField, Option } from '../components/TagField';
1212import useMedia from '../logic/useMedia';
1313import { usePosts } from '../state/posts';
1414import { Declare, PostOptionsForm, PostType } from '../types/sphinx';
15+import { POST_LISTINGS_KEY } from '../keys';
1516
1617interface PostsProps {
1718 ship: string;
@@ -62,7 +63,7 @@ export const Posts = ({ ship, name }: PostsProps) => {
6263 });
6364 }, {
6465 onSuccess: () => {
65− queryClient.invalidateQueries('post-listings')
66+ queryClient.invalidateQueries(POST_LISTINGS_KEY)
6667 }
6768 })
6869
@@ -77,10 +78,10 @@ export const Posts = ({ ship, name }: PostsProps) => {
7778 {limited && (
7879 <Link to="/manage-listings/posts" className='flex items-center'>
7980 <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>
8182 </Link>
8283 )}
83− {!limited && <h1 className='text-2xl font-semibold'>{notebook?.metadata?.title}</h1>}
84+ {!limited && <h1 className='text-2xl font-semibold'>{notebook?.meta.title}</h1>}
8485 {posts.length > 0 && (
8586 <button className='p-1.5 ml-auto text-sm underline font-semibold' onClick={toggleAll}>
8687 {options.length !== posts.length && 'select all'}
ui/src/pages/Search.tsx+5−4
@@ -1,7 +1,7 @@
11import cn from 'classnames';
22import React, { useCallback, useRef, useState } from 'react';
33import debounce from 'lodash.debounce';
4−import { useMutation, useQueryClient } from 'react-query';
4+import { useMutation, useQueryClient } from '@tanstack/react-query';
55import { useNavigate, useParams } from 'react-router-dom';
66import { SearchInput } from '../components/SearchInput';
77import { Listings } from '../components/Listings';
@@ -15,6 +15,7 @@ import { TagCloud } from '../components/TagCloud';
1515import { useTags } from '../state/tags';
1616import { useSearch } from '../state/search';
1717import { encodeLookup } from '../utils';
18+import { lookupKey } from '../keys';
1819
1920interface RouteParams extends Record<string, string | undefined> {
2021 lookup?: string;
@@ -42,7 +43,7 @@ export const Search = () => {
4243 isLoading,
4344 linkBuild
4445 } = useSearch({
45− key: (start, size) => `lookup-${selected}-${size}-${start}-${lookup}`,
46+ key: lookupKey(selected, lookup),
4647 fetcher: (start, size) => api.scry<SearchType>({
4748 app: 'sphinx',
4849 path: `/lookup/${selected}/${start}/${size}/${encodeLookup(lookup)}`
@@ -62,7 +63,7 @@ export const Search = () => {
6263 })
6364 }, {
6465 onSuccess: () => {
65− queryClient.invalidateQueries(`lookup-${selected}-${size}-${start}-${lookup}`)
66+ queryClient.invalidateQueries(lookupKey(selected, lookup)(start, size))
6667 }
6768 });
6869
@@ -82,7 +83,7 @@ export const Search = () => {
8283 return (
8384 <div className={cn('w-full space-y-6', !lookup && 'm-auto')}>
8485 <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} />
8687 <Filter selected={selected} onSelect={setSelected} className="min-w-0 sm:w-20" />
8788 </header>
8889 {!palsInstalled && (
ui/src/pages/Tag.tsx+2−3
@@ -2,7 +2,7 @@ import { PlusIcon, PlusSmIcon } from '@heroicons/react/solid';
22import cn from 'classnames';
33import qs from 'query-string';
44import React from 'react';
5−import { useQueryClient, useMutation } from 'react-query';
5+import { useQueryClient, useMutation } from '@tanstack/react-query';
66import { Link, useParams } from 'react-router-dom';
77import api from '../api';
88import { Listings } from '../components/Listings';
@@ -12,6 +12,7 @@ import { useSearch } from '../state/search';
1212import { useTags } from '../state/tags';
1313import { Remove, Search } from '../types/sphinx';
1414import { encodeLookup } from '../utils';
15+import { tagKey } from '../keys';
1516
1617interface RouteParams extends Record<string, string | undefined> {
1718 tag?: string;
@@ -19,8 +20,6 @@ interface RouteParams extends Record<string, string | undefined> {
1920 page?: string;
2021}
2122
22−const tagKey = (tag?: string) => (start: number, size: number) => `tag-${tag || ''}-${size}-${start}`
23−
2423export const Tag = () => {
2524 const tags = useTags();
2625 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";
2−import { useQuery } from "react-query"
1+import { Charge, ChargeUpdateInitial, getPikes, Pike, Pikes, scryCharges } from "@urbit/api";
2+import { useQuery } from "@tanstack/react-query"
33import api from "../api";
44import { Post, Search } from "../types/sphinx";
5+import { APP_LISTINGS_KEY, CHARGES_KEY, VATS_KEY } from "../keys";
56
6−function getAppPost(app: string, vat: Vat, charge: Charge): Post {
7− const ship = vat.arak.rail?.publisher || vat.arak.rail?.ship || `~${window.ship}`;
7+function getAppPost(app: string, pike: Pike, charge: Charge): Post {
8+ const ship = pike.sync?.ship || `~${window.ship}`;
89
910 return {
1011 type: 'app',
1112 title: charge.title,
12− link: `web+urbitgraph://${ship}/${app}`,
13+ link: `/1/desk/${ship}/${app}`, //`/apps/grid/search/${ship}/apps/${ship}/${app}`,
1314 description: charge.info || charge.title,
1415 image: charge.image || '',
1516 color: charge.color,
@@ -18,18 +19,18 @@ function getAppPost(app: string, vat: Vat, charge: Charge): Post {
1819}
1920
2021export const useApps = () => {
21− const { data: vats, isLoading: vatsLoading } = useQuery('vats', () => api.scry<Vats>(getVats));
22− const { data: charges, isLoading: chargesLoading } = useQuery('charges', () => api.scry<ChargeUpdateInitial>(scryCharges));
23− const { data, isLoading } = useQuery('app-listings', () => api.scry<Search>({
22+ const { data: pikes, isLoading: pikesLoading } = useQuery(VATS_KEY, () => api.scry<Pikes>(getPikes));
23+ const { data: charges, isLoading: chargesLoading } = useQuery(CHARGES_KEY, () => api.scry<ChargeUpdateInitial>(scryCharges));
24+ const { data, isLoading } = useQuery(APP_LISTINGS_KEY, () => api.scry<Search>({
2425 app: 'sphinx',
2526 path: '/lookup/app/0/999'
2627 }));
2728
2829 return {
29− loading: vatsLoading || chargesLoading || isLoading,
30− apps: !vats || !charges || !data ? [] : Object.entries(charges.initial)
31− .filter(([k]) => k in vats)
32− .map(([k,v]) => ({ key: k, post: getAppPost(k, vats[k], v) }))
30+ loading: pikesLoading || chargesLoading || isLoading,
31+ apps: !pikes || !charges || !data ? [] : Object.entries(charges.initial)
32+ .filter(([k]) => k in pikes)
33+ .map(([k,v]) => ({ key: k, post: getAppPost(k, pikes[k], v) }))
3334 .filter(({ post }) => !data.listings.some(l => l.post.link === post.link || l.post.title === post.title))
3435 }
3536}
ui/src/state/groups.ts+54−20
@@ -1,26 +1,34 @@
1−import { Associations, GroupUpdateInitial, MetadataUpdateInitial } from "@urbit/api";
2−import { useQuery } from "react-query"
1+import { useQuery } from "@tanstack/react-query"
32import 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";
58
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+ );
717
8−export const usePublicGroups = (metadata?: { 'metadata-update': MetadataUpdateInitial }) => {
9− const { data, isLoading } = useQuery('metadata', () => api.subscribeOnce<MetaDataUpdate>('metadata-store', '/all'), {
10− enabled: !metadata
11− });
12− const { data: groupData, isLoading: groupDataLoading } = useQuery('groups', () => api.subscribeOnce<{ groupUpdate: GroupUpdateInitial }>('group-store', '/groups'));
18+ const groups = useMemo(() => {
19+ if (!data) return [];
20+ return Object.entries(data).filter(([,v]) => !v.secret);
21+ }, [data]);
1322
1423 return {
15− loading: isLoading || groupDataLoading,
16− groups: !data || !groupData ? [] : Object.entries(data['metadata-update'].associations)
17− .filter(([k,v]) => v['app-name'] === 'groups' && 'open' in (groupData.groupUpdate.initial[v.resource]?.policy || {}))
24+ ...query,
25+ groups
1826 }
1927}
2028
2129export const useGroups = () => {
22− const { groups, loading } = usePublicGroups();
23− const { data: d, isLoading } = useQuery('group-listings', () => api.scry<Search>({
30+ const { groups, isLoading: loading } = usePublicGroups();
31+ const { data: d, isLoading } = useQuery(GROUP_LISTINGS_KEY, () => api.scry<Search>({
2432 app: 'sphinx',
2533 path: '/lookup/group/0/999'
2634 }));
@@ -29,13 +37,39 @@ export const useGroups = () => {
2937 loading: loading || isLoading,
3038 groups: groups.map(([k,v]) => ({ key: k, post: {
3139 type: 'group' as PostType,
32− title: v.metadata.title,
33− link: v.group.replace('/ship/', 'web+urbitgraph://group/'),
34− image: v.metadata.picture,
35− color: v.metadata.color,
36− description: v.metadata.description,
40+ title: v.meta.title,
41+ link: `/1/group/${k}`, //`/apps/groups/groups/${k}`,
42+ image: getImage(v.meta.image) || getImage(v.meta.cover) || '',
43+ color: getColor(v.meta.image) || getColor(v.meta.cover) || '',
44+ description: v.meta.description,
3745 tags: ['group']
3846 }}))
3947 .filter(({ post }) => !d || d.listings.length === 0 || !d.listings.some(l => l.post.link === post.link || l.post.title === post.title))
4048 }
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 @@
11import { ChargeUpdateInitial, scryCharges } from '@urbit/api';
2−import { useMutation, useQuery, useQueryClient } from 'react-query';
2+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
33import api from '../api';
4+import { CHARGES_KEY, PALS_KEY } from '../keys';
45
56interface Pal {
67 lists: string[];
78 ack: true | null;
89}
910
10−interface Pals {
11+export interface Pals {
1112 [k: string]: Pal;
1213}
1314
@@ -26,10 +27,10 @@ interface RemovePal {
2627
2728export const usePals = () => {
2829 const queryClient = useQueryClient();
29− const { data: charges } = useQuery('charges', () => {
30+ const { data: charges } = useQuery(CHARGES_KEY, () => {
3031 return api.scry<ChargeUpdateInitial>(scryCharges);
3132 })
32− const { data } = useQuery('pals', () => {
33+ const { data } = useQuery(PALS_KEY, () => {
3334 return api.scry<PalsData>({
3435 app: 'pals',
3536 path: '/json'
@@ -48,7 +49,7 @@ export const usePals = () => {
4849 })
4950 }, {
5051 onSuccess: () => {
51− queryClient.invalidateQueries('pals');
52+ queryClient.invalidateQueries(PALS_KEY);
5253 }
5354 })
5455
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';
41import _ from "lodash";
5−import { useQuery } from "react-query"
2+import { useQuery } from "@tanstack/react-query"
63import api from "../api"
74import { 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';
910
10−interface Resource {
11− ship: string;
12− name: string;
13−}
11+export const useNotebooks = () => {
12+ const { groups, isLoading: groupsLoading } = usePublicGroups();
13+ const { data, isLoading } = useQuery(notesKey(), () => api.scry<Shelf>({
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;
1422
15−interface GraphKeyUpdate {
16− 'graph-update': {
17− keys: Resource[]
18− }
19−}
23+ Object.entries(data).forEach(([k,v]) => {
24+ const group = groups.find(([g]) => g === v.perms.group);
25+ if (!group) return;
2026
21−export const useNotebooks = () => {
22− const { data: keys, isLoading: keysLoading } = useQuery('graph-keys', async () => {
23− const json = await api.scry<GraphKeyUpdate>(getKeys())
24− const update = _.get(json, 'graph-update', false);
25− const data: Resource[] = _.get(update, 'keys', false) || [];
27+ debugger;
28+ const channel = group[1].channels[`diary/${k}`];
29+ if (!channel) return;
2630
27− return new Set(data.map((res) => {
28− const resource = res.ship + '/' + res.name;
29− return resource;
30− }));
31− });
32− const { data, isLoading: metadataLoading } = useQuery('metadata', () => api.subscribeOnce<MetaDataUpdate>('metadata-store', '/all'));
33− const { groups, loading } = usePublicGroups(data || {} as MetaDataUpdate);
31+ if (channel.readers.length === 0) {
32+ nbs[k] = { ...v, ...channel };
33+ }
34+ });
3435
35− const notebooks: Record<string, Association<MetadataConfig>> = _.fromPairs(!data || loading ? [] : Object.entries(data['metadata-update'].associations)
36− .filter(([,v]) => v['app-name'] === 'graph'
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]));
36+ return nbs;
37+ }, [data, groups]);
4238
4339 return {
44− loading: metadataLoading || loading,
45− notebooks,
46− groups
40+ notebooks,
41+ groups,
42+ loading: isLoading || groupsLoading
4743 }
4844}
4945
5046export const usePosts = (ship: string, name: string, type: PostType, tags: string[]) => {
47+ const flag = `${ship}/${name}`;
5148 const { notebooks, loading } = useNotebooks();
52− const association = notebooks[`${ship}/${name}`] || {};
53− const { data, isLoading } = useQuery(`graph-${association?.resource}`, () => api.scry<Graph>(getGraph(ship, name)), {
54− enabled: !!association?.resource
55− });
56− const { data: d, isLoading: postsLoading } = useQuery('post-listings', () => api.scry<Search>({
49+ const { data, isLoading } = useQuery(notesKey(flag), () => api.scry<DiaryOutlines>({
50+ app: 'diary',
51+ path: `/diary/${flag}/notes/newest/9.999.999/outline`
52+ }));
53+ const { data: d, isLoading: postsLoading } = useQuery(POST_LISTINGS_KEY, () => api.scry<Search>({
5754 app: 'sphinx',
58− path: '/lookup/tag/0/999/~~post'
55+ path: '/lookup/tag/0/9.999/~~post'
5956 }));
60− const graph = addGraph(data);
61− const items = data && graph ? Array.from(graph)
62− .filter(([, node]) => node && typeof node?.post !== 'string')
63− .map(([date, node]) => {
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−
57+
58+ const items = data ? Object.entries(data)
59+ .map(([date, outline]) => {
7460 return { key: date.toString(), post: {
7561 type,
76− title,
77− link: permalink,
78− image: snippet.image,
62+ title: outline.title,
63+ link: getPermalink(flag, date),
64+ image: outline.image,
7965 color: '',
80− description: snippet.content,
66+ description: getSnippet(outline.content),
8167 tags
8268 }}
8369 }) : [];
@@ -85,108 +71,27 @@ export const usePosts = (ship: string, name: string, type: PostType, tags: strin
8571 const posts = items.filter(p => !d || d.listings.length === 0 || !d.listings.find(l => l.post.link === p.post.link));
8672
8773 return {
88− notebook: association,
74+ notebook: notebooks[flag],
8975 loading: loading || isLoading || postsLoading,
9076 posts
9177 }
9278}
9379
94−function getImage(snippet: string) {
95− const pattern = /!\[.*\]\((.*)\)/
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− };
80+function getPermalink(flag: string, time: string): string {
81+ return `/1/chan/diary/${flag}/note/${udToDec(time)}`;
12182}
12283
123−export function getSnippet(body: Content[]): { image: string, content: string } {
124− const IMAGE_REGEX = new RegExp(
125− /(\.jpg|\.img|\.png|\.gif|\.tiff|\.jpeg|\.webp|\.webm|\.svg)$/i
126− );
84+function getSnippet(content: NoteContent): string {
85+ const inlines = content.filter(c => 'inlines' in c) as VerseInline[];
86+ if (inlines.length === 0) return '';
12787
128− let sum = 0;
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;
140− let start = content.substring(0, end);
88+ const text = inlines[0].inlines.map(inlineToString).join(' ');
89+ const end = text.length > 256 ? 253 : text.length;
90+ let start = text.substring(0, end);
14191 const length = new Blob([start]).size;
14292 if (length > end) {
14393 start = start.substring(0, end - (length - end))
14494 }
14595
146− return {
147− content: start === content ? 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}`;
96+ return start === text ? start : `${start}...`;
16597}
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 @@
11import { useCallback } from "react";
2−import { useQuery } from "react-query";
2+import { QueryKey, useQuery } from "@tanstack/react-query";
33import { Search } from "../types/sphinx";
44
55interface UseSearchParams {
6− key: (start: number, size: number) => string;
6+ key: (start: number, size: number) => QueryKey;
77 fetcher: (start: number, size: number) => Promise<Search>;
88 enabled: boolean;
99 linkPrefix: string;
@@ -16,7 +16,7 @@ export const useSearch = ({ key, fetcher, enabled, limit, page, linkPrefix }: Us
1616 const pageInt = parseInt(page || '1', 10) - 1;
1717 const start = pageInt * size;
1818
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), {
2020 enabled,
2121 keepPreviousData: true
2222 });
ui/src/state/tags.ts+3−2
@@ -1,10 +1,11 @@
11import { useMemo } from "react";
2−import { useQuery } from "react-query";
2+import { useQuery } from "@tanstack/react-query";
33import api from "../api";
44import { Tags } from "../types/sphinx";
5+import { TAGS_KEY } from "../keys";
56
67export const useTags = () => {
7− const { data } = useQuery('tags', () => api.scry<Tags>({
8+ const { data } = useQuery(TAGS_KEY, () => api.scry<Tags>({
89 app: 'sphinx',
910 path: '/tags'
1011 }));
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 { stringToTa } from "@urbit/api";
1+import { useQuery } from "@tanstack/react-query";
2+import { stringToTa, uxToHex } from "@urbit/api";
3+import { parseToRgba } from "color2k";
4+import api from "./api";
5+import { ChannelPreview } from "./types";
26
37export function encodeLookup(value: string | undefined) {
48 if (!value) {
@@ -6,4 +10,79 @@ export function encodeLookup(value: string | undefined) {
610 }
711
812 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+}