on master help sign in
94 files changed+5572−1137
since splitsnapshots
unifiedsplit
.gitignore+2 README.md+106−26 RELEASING.md+166 desk/app/seek.hoon−306 desk/app/sphinx.hoon+431 desk/desk.bill+1−1 desk/desk.docket-0+11−9 desk/gen/get.hoon+9 desk/gen/metaphone.hoon+6 desk/gen/publish-all.hoon+29 desk/gen/publish.hoon+6−6 desk/gen/ranks.hoon+37 desk/gen/state.hoon+8 desk/gen/validate.hoon+6 desk/lib/gossip.hoon+283−58 desk/lib/metaphone.hoon+192 desk/lib/pals.hoon+8−2 desk/lib/seek-json.hoon−39 desk/lib/seek.hoon−26 desk/lib/sift.hoon+46−10 desk/lib/sphinx-json.hoon+72 desk/lib/sphinx.hoon+255 desk/lib/validate-url.hoon+20 desk/mar/declarations.hoon+14 desk/mar/declare.hoon+14 desk/mar/directory-listing.hoon+2−2 desk/mar/directory.hoon+3−2 desk/mar/pals/command.hoon+22 desk/mar/remove.hoon+14 desk/mar/search.hoon+3−3 desk/mar/state.hoon+12 desk/mar/tags.hoon+14 desk/sur/seek.hoon → desk/sur/sphinx.hoon+68−5 desk/sys.kelvin+1−1 desk/ted/publish-all.hoon+40 flake.lock+63 flake.nix+98 ui/.nvmrc+1 ui/index.html+2−2 ui/package-lock.json+1041−501 ui/package.json+14−4 ui/postcss.config.js+2 ui/src/assets/android-chrome-512x512.png → ui/public/android-chrome-512x512.png ui/src/assets/manifest.json → ui/public/manifest.json+3−3 ui/src/api.ts+6 ui/src/app.tsx+34−5 ui/src/components/CenterColumn.tsx+6 ui/src/components/ErrorMessage.tsx+26 ui/src/components/Filter.tsx+55 ui/src/components/Layout.tsx+75 ui/src/components/Listing.tsx+74 ui/src/components/ListingImage.tsx+26 ui/src/components/Listings.tsx+41 ui/src/components/Logo.tsx+13 ui/src/components/Meta.tsx+41 ui/src/components/PostOptions.tsx+56 ui/src/components/SearchInput.tsx+6−3 ui/src/components/SearchList.tsx−37 ui/src/components/Spinner.tsx+11 ui/src/components/TagCloud.tsx+18 ui/src/components/TagField.tsx+157 ui/src/env.d.ts+8 ui/src/index.css−7 ui/src/keys.ts+14 ui/src/logic/useMedia.ts+25 ui/src/logic/utils.ts+11 ui/src/main.tsx+1−1 ui/src/manage-listings/AllListings.tsx+65 ui/src/manage-listings/Apps.tsx+92 ui/src/manage-listings/Groups.tsx+99 ui/src/manage-listings/MyListings.tsx+34 ui/src/manage-listings/Notebooks.tsx+70 ui/src/manage-listings/Post.tsx+142 ui/src/manage-listings/Posts.tsx+122 ui/src/pages/Search.tsx+75−52 ui/src/pages/Tag.tsx+82 ui/src/routes.ts+16 ui/src/state/apps.ts+39 ui/src/state/groups.ts+76 ui/src/state/pals.ts+62 ui/src/state/posts.ts+108 ui/src/state/search.ts+49 ui/src/state/tags.ts+21 desk/mar/seek-action.hoon → ui/src/styles/base.css ui/src/styles/components.css+16 ui/src/styles/index.css+8 ui/src/styles/utilities.css+3 ui/src/types/index.ts+304 ui/src/types/seek.ts−23 ui/src/types/sphinx.ts+52 ui/src/utils.ts+196 ui/tailwind.config.js+4−1 ui/tsconfig.json+1−1 ui/vite.config.ts+7−1
.gitignorenew file+2
@@ -0,0 +1,2 @@
1+/result
2+/result-*
README.md+106−26
@@ -1,45 +1,125 @@
1−# seek
1+# sphinx
22
3−## Desk
3+A search engine powered by you and your pals for finding answers to your
4+riddles.
45
5−The desk currently has the minimum amount of files necessary to distribute an application and should be distributable immediately. Any further Hoon development should happen here.
6+sphinx keeps a directory of listings: apps, groups, notebook posts and links.
7+The listings you publish go to your mutual pals and on to their mutual pals,
8+theirs come to you, and you search everything you've received.
69
7−TODO: Add further documentation on beginning Hoon development
10+## Install
811
9−## UI
12+sphinx runs on the kernels listed in `desk/sys.kelvin`, currently
13+`[%zuse 408]`. Check your ship first: in the dojo, `+vats %base` must show
14+`/sys/kelvin: [%zuse 408]`. If it shows an older kernel, let your ship finish
15+its update first. If the update is blocked, Landscape lists the apps that
16+block it, and `|bump` suspends them.
1017
11−seek is built primarily using [React], [Typescript], and [Tailwind CSS]. [Vite] ensures that all code and assets are loaded appropriately, bundles the application for distribution and provides a functional dev environment.
18+Then, in your ship's dojo:
1219
13−### Getting Started
20+```
21+|install ~donmec-sallud %sphinx
22+```
23+
24+For a new install, you can also search for ~donmec-sallud in Landscape and
25+install sphinx from there.
26+
27+sphinx shares listings through %pals. Install it with
28+`|install ~paldev %pals` and add your friends as pals. Without %pals you only
29+see your own listings.
30+
31+### Moving from ~nocsyx-lassul
32+
33+sphinx used to be distributed from ~nocsyx-lassul. Its last release there
34+only supports `[%zuse 412]`, so it can't run on a current ship. On a
35+`[%zuse 412]` ship it blocks the kernel update until you `|bump`. After the
36+kernel check above, switch it to ~donmec-sallud in the dojo:
37+
38+```
39+|install ~donmec-sallud %sphinx
40+```
41+
42+Your listings are kept. Use the dojo for this: Landscape's Get App can't
43+switch an existing install. It asks ~nocsyx-lassul again, and docket refuses
44+because sphinx is already installed.
45+
46+If you ran `|install` before your ship reached `[%zuse 408]`, sphinx stays
47+suspended after the update, and another `|install` only prints
48+`already syncing`. Run `|uninstall %sphinx`, then
49+`|install ~donmec-sallud %sphinx` again. Your listings are kept.
50+
51+## Development
52+
53+The repo has two parts:
54+
55+- `desk/`: the Hoon agent, its marks and libraries, and the docket file.
56+- `ui/`: the web interface, built with [React], [TypeScript],
57+ [Tailwind CSS] and [Vite].
1458
15−To get started using seek first you need to run `npm install` inside the `ui` directory.
59+### Build
1660
17−To develop you'll need a running ship to point to. To do so you first need to add a `.env.local` file to the `ui` directory. This file will not be committed. Adding `VITE_SHIP_URL={URL}` where **{URL}** is the URL of the ship you would like to point to, will allow you to run `npm run dev`. This will proxy all requests to the ship except for those powering the interface, allowing you to see live data.
61+With Nix:
1862
19−Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
63+```sh
64+nix build .#desk -o result-desk # the desk; Nix makes its files read-only
65+nix build .#glob -o result-glob # the UI, built for /apps/sphinx/
66+```
67+
68+To put the desk on a test ship, copy it writable and with `--delete`, as in
69+RELEASING.md step 2. vere aborts at `|unmount` if the mount is read-only.
70+
71+```sh
72+chmod -R u+w result-desk-rw 2>/dev/null; rm -rf result-desk-rw &&
73+cp -RL result-desk result-desk-rw && chmod -R u+w result-desk-rw &&
74+rsync -rpc --delete result-desk-rw/ <pier>/sphinx/
75+```
76+
77+The desk output adds what `desk/` needs from urbit's `base-dev` and
78+landscape's `desk-dev`. Both are pinned in `flake.nix` for the kelvin in
79+`desk/sys.kelvin`. When you port sphinx to a new kernel, update those two
80+inputs together with `desk/sys.kelvin`.
81+
82+Without Nix, run `npm install` and `npm run build` in `ui/` to build the UI
83+into `ui/dist/`. Use the Node version in `ui/.nvmrc`.
84+
85+To publish a release from ~donmec-sallud, follow [RELEASING.md](RELEASING.md).
2086
21−### Deploying
87+### Working on the UI
2288
23−To deploy, run `npm run build` in the `ui` directory which will bundle all the code and assets into the `dist/` folder. This can then be made into a glob by doing the following:
89+You need a running ship with sphinx installed. In `ui/`:
90+
91+1. Run `npm install`.
92+2. Create `ui/.env.local` with `VITE_SHIP_URL=<URL>`, where `<URL>` is your
93+ ship's web address, for example `http://localhost:8080`. Git ignores this
94+ file.
95+3. Run `npm run dev` and open the address it prints.
96+
97+The dev server sends every request except those for the interface itself to
98+the ship, so you see live data. Vite reloads the page as you change the code.
99+
100+### The tile image
101+
102+The tile is inlined in `desk/desk.docket-0` as a `data:` URI, so Landscape
103+doesn't load it from another server.
104+
105+To get the SVG out:
106+
107+```sh
108+sed -n "s|^ image+'data:image/svg+xml;base64,\(.*\)'\$|\1|p" desk/desk.docket-0 | base64 -d > tile.svg
109+```
24110
25−1. Create or launch an urbit using the -F flag
26−2. On that urbit, if you don't already have a desk to run from, run `|merge %work our %base` to create a new desk and mount it with `|mount %work`.
27−3. Now the `%work` desk is accessible through the host OS's filesystem as a directory of that urbit's pier ie `~/zod/work`.
28−4. From the `ui` directory you can run `rsync -avL --delete dist/ ~/zod/work/seek` where `~/zod` is your fake urbit's pier.
29−5. Once completed you can then run `|commit %work` on your urbit and you should see your files logged back out from the dojo.
30−6. Now run `=dir /=garden` to switch to the garden desk directory
31−7. You can now run `-make-glob %work /seek` which will take the folder where you just added files and create a glob which can be thought of as a sort of bundle. It will be output to `~/zod/.urb/put`.
32−8. If you navigate to `~/zod/.urb/put` you should see a file that looks like this `glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob`. The characters between `glob-` and `.glob` are a hash of the glob's contents.
33−9. Now that we have the glob it can be uploaded to any publicly available HTTP endpoint that can serve files. This allows the glob to distributed over HTTP.
34−10. Once you've uploaded the glob, you should then update the corresponding entry in the docket file at `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
111+To put a new one in, print a new `image+` line and replace the old line in
112+`desk/desk.docket-0` with it:
35113
36−```hoon
37− glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
114+```sh
115+printf " image+'data:image/svg+xml;base64,%s'\n" "$(base64 < tile.svg | tr -d '\n')"
38116```
39117
40−11. This can now be safely committed and deployed.
118+Keep the rest of the file as it is. Docket rewrites the file when a glob is
119+uploaded, and the hash copy-back in RELEASING.md should only change the hash.
120+A new tile doesn't need a new glob.
41121
42−[react]: https://reactjs.org/
122+[react]: https://react.dev/
43123[typescript]: https://www.typescriptlang.org/
44124[tailwind css]: https://tailwindcss.com/
45125[vite]: https://vitejs.dev/
RELEASING.mdnew file+166
@@ -0,0 +1,166 @@
1+# Releasing sphinx
2+
3+sphinx is published from ~donmec-sallud. Ships install it with
4+`|install ~donmec-sallud %sphinx` and then follow it: the desk syncs from
5+~donmec-sallud's `%sphinx` desk, and docket fetches the UI (the glob) over ames
6+from ~donmec-sallud.
7+
8+A release has three parts:
9+
10+1. **Desk**: copy `nix build .#desk` into ~donmec-sallud's mounted `%sphinx`
11+ desk and commit it.
12+2. **Glob**: if the UI changed, upload `nix build .#glob` at `/docket/upload`.
13+ Docket hashes it and writes the hash into the desk's `desk.docket-0`.
14+3. **Hash copy-back**: copy that `desk.docket-0` back into the repo and commit
15+ it.
16+
17+Never skip the copy-back. On every commit of the desk, docket compares the hash
18+in `desk.docket-0` with the glob it serves, and when they differ it drops the
19+glob and waits for a new upload. So a stale hash in the repo takes the UI down
20+at the next release, on ~donmec-sallud and on every ship that installed
21+sphinx, until the glob is uploaded again.
22+
23+## Before you start
24+
25+- [ ] ~donmec-sallud runs a kernel listed in `desk/sys.kelvin`. In its dojo,
26+ `+vats %base` prints it on the `/sys/kelvin:` line. If it isn't listed,
27+ port the desk to that kernel first.
28+- [ ] Optional: bump `version` in `desk/desk.docket-0`. Landscape and the
29+ sphinx footer show it.
30+- [ ] Everything is committed and pushed. The release is whatever the repo
31+ builds.
32+
33+The commands below use these shell variables:
34+
35+```sh
36+server=user@host # ssh login of the user that runs ~donmec-sallud's vere and owns the pier
37+pier=/path/to/donmec-sallud # the pier directory on that machine
38+```
39+
40+## 1. Build
41+
42+From the repo root:
43+
44+```sh
45+nix build .#desk -o result-desk
46+nix build .#glob -o result-glob
47+```
48+
49+## 2. Desk
50+
51+First release only, in ~donmec-sallud's dojo:
52+
53+```
54+|new-desk %sphinx
55+|mount %sphinx
56+```
57+
58+Leave the desk mounted. The copy-back in step 4 reads the mounted file.
59+
60+Every release, copy the desk into the mount from a writable copy of the
61+build:
62+
63+```sh
64+chmod -R u+w result-desk-rw 2>/dev/null; rm -rf result-desk-rw &&
65+cp -RL result-desk result-desk-rw && chmod -R u+w result-desk-rw &&
66+rsync -rpc --delete result-desk-rw/ "$server:$pier/sphinx/"
67+ssh "$server" find "$pier/sphinx" '!' -perm -u+w # must print nothing
68+```
69+
70+- The `&&`s stop the copy when an earlier step fails, so a leftover
71+ `result-desk-rw` from an interrupted run is never sent.
72+- Send a writable copy. Nix store files and directories are read-only, and
73+ `-p` copies their modes into the mount. Don't rely on `rsync --chmod=u+w`:
74+ macOS's `rsync` (openrsync) accepts it and ignores it. In a read-only
75+ mount, vere aborts when it has to delete a file, for example at
76+ `|unmount %sphinx` in step 3, and clay's writes to read-only files fail.
77+- `-c` compares contents. Don't use `rsync -a`: Nix store files all have
78+ mtime 1, so with `-a` rsync can skip an edited file of the same size.
79+
80+Then in the dojo:
81+
82+```
83+|commit %sphinx
84+```
85+
86+The dojo lists the changed files, for example
87+`: /~donmec-sallud/sphinx/4/desk/docket-0`. A commit that changes nothing
88+prints nothing. A mark or build error rejects the whole commit, so fix the
89+desk and repeat this step.
90+
91+First release only:
92+
93+```
94+|install our %sphinx
95+|public %sphinx
96+:treaty|publish %sphinx
97+```
98+
99+- `|install our %sphinx` starts the app on ~donmec-sallud. Docket then prints
100+ `docket: awaiting manual glob for %sphinx desk` until step 3.
101+- `|public %sphinx` lets other ships read the desk. Without it, clay doesn't
102+ answer their requests, so `|install ~donmec-sallud %sphinx` waits without
103+ an error. A waiting install continues by itself once the desk is public.
104+- `:treaty|publish %sphinx` lists sphinx under ~donmec-sallud in Landscape's
105+ app search. It also makes the desk public.
106+
107+## 3. Glob
108+
109+Upload on the first release and whenever `ui/` changed since the last one. If
110+you're unsure, upload anyway: the same files give the same hash.
111+
112+1. Log in to ~donmec-sallud's web interface in Chrome or Firefox. Safari can't
113+ upload folders. `+code` in the dojo prints the password.
114+2. Open `/docket/upload` on that host, pick the desk `sphinx`, choose the
115+ `result-glob` folder and press **glob!**. If the folder picker won't follow
116+ the `result-glob` symlink, press ⇧⌘G and paste the path that
117+ `readlink result-glob` prints.
118+3. The page answers `successfully globbed`.
119+4. Refresh the mount in the dojo:
120+
121+ ```
122+ |unmount %sphinx
123+ |mount %sphinx
124+ ```
125+
126+ Docket wrote the new hash into clay. vere rewrites a mounted file only
127+ while it still holds exactly what clay last wrote there. If the
128+ `desk.docket-0` you committed differs in any byte from what docket prints
129+ (another layout, or a final newline an editor added), the mounted file
130+ keeps the old hash, and any later `|commit %sphinx`, even one with no
131+ edits, puts the old hash back and takes the UI down. The remount rewrites
132+ the mount from clay. It's harmless when the file is already current, so
133+ always do it.
134+
135+## 4. Hash copy-back
136+
137+After every upload and remount:
138+
139+```sh
140+scp "$server:$pier/sphinx/desk.docket-0" desk/desk.docket-0
141+```
142+
143+Only the hash on the `glob-ames` line should change. Commit
144+`desk/desk.docket-0` and push it.
145+
146+## 5. Check
147+
148+- [ ] `/apps/sphinx/` on ~donmec-sallud loads.
149+- [ ] The dojo has not printed `docket: awaiting manual glob for %sphinx desk`
150+ since the last commit or upload.
151+- [ ] `+vats %sphinx` shows `app status: running`.
152+- [ ] First release only: another ship can `|install ~donmec-sallud %sphinx`.
153+ Its log shows `kiln: merge into %sphinx succeeded` and
154+ `docket: fetching %ames glob for %sphinx desk`.
155+- [ ] A ship that installed sphinx shows the release at `/apps/sphinx/`.
156+ After a glob upload, it answers 404 until it has downloaded the new
157+ glob from ~donmec-sallud.
158+
159+## If the UI is down
160+
161+`docket: awaiting manual glob for %sphinx desk` after a commit means the hash
162+in the desk's `desk.docket-0` doesn't match the glob docket serves, usually
163+because a copy-back or a remount was skipped. Build the glob from the released
164+commit, upload it and remount (step 3), then do the copy-back (step 4). Other
165+ships pick up the corrected `desk.docket-0` and fetch the glob again on their
166+own.
desk/app/seek.hoondeleted−306
@@ -1,306 +0,0 @@
1−/- s=seek
2−/+ gossip, default-agent, verb, dbug
3−/+ *seek
4−/+ sift
5−/$ grab-listing %noun %directory-listing
6−/$ grab-directory %noun %directory
7−^- agent:gall
8−=>
9− |%
10− +$ card card:agent:gall
11− +$ state-0
12− $: %0
13− =lookup:s
14− =trigrams:s
15− =trail:s
16− =directory:s
17− published=directory:s
18− ==
19− --
20−=| state-0
21−=* state -
22−=<
23− %+ verb &
24− %- %+ agent:gossip
25− [2 %mutuals %mutuals]
26− %- malt
27− ^- (list [mark $-(* vase)])
28− :~ [%directory-listing |=(n=* !>((grab-listing n)))]
29− [%directory |=(n=* !>((grab-directory n)))]
30− ==
31− %- agent:dbug
32− |_ =bowl:gall
33− +* this .
34− def ~(. (default-agent this %.n) bowl)
35− cor ~(. +> [bowl ~])
36− ++ on-init
37− ^- (quip card _this)
38− =^ cards state
39− abet:init:cor
40− [cards this]
41− ::
42− ++ on-save !>(state)
43− ++ on-load
44− |= =vase
45− ^- (quip card _this)
46− =/ old=(unit state-0)
47− (mole |.(!<(state-0 vase)))
48− ?^ old `this(state u.old)
49− ~& >>> "Incompatible load, nuking"
50− =^ cards this on-init
51− :_ this
52− =- (welp - cards)
53− %+ turn ~(tap in ~(key by wex.bowl))
54− |= [=wire =ship =term]
55− ^- card
56− [%pass wire %agent [ship term] %leave ~]
57− ::
58− ++ on-poke
59− |= [=mark =vase]
60− ^- (quip card _this)
61− =^ cards state
62− abet:(poke:cor mark vase)
63− [cards this]
64− ++ on-watch
65− |= =path
66− ^- (quip card _this)
67− =^ cards state
68− abet:(watch:cor path)
69− [cards this]
70− ::
71− ++ on-peek peek:cor
72− ::
73− ++ on-leave on-leave:def
74− ++ on-fail on-fail:def
75− ::
76− ++ on-agent
77− |= [=wire =sign:agent:gall]
78− ^- (quip card _this)
79− =^ cards state
80− abet:(agent:cor wire sign)
81− [cards this]
82− ++ on-arvo
83− |= [=wire sign=sign-arvo]
84− ^- (quip card _this)
85− =^ cards state
86− abet:(arvo:cor wire sign)
87− [cards this]
88− --
89−|_ [=bowl:gall cards=(list card)]
90−++ abet [(flop cards) state]
91−++ cor .
92−++ emit |=(=card cor(cards [card cards]))
93−++ give |=(=gift:agent:gall (emit %give gift))
94−++ init
95− ^+ cor
96− cor
97−++ poke
98− |= [=mark =vase]
99− |^ ^+ cor
100− ?+ mark ~|(bad-poke/mark !!)
101− %directory-listing
102− =+ !<(=listing:s vase)
103− (publish listing)
104− ==
105− ++ publish
106− |= =listing:s
107− ^+ cor
108− ::
109− ?> =(src.bowl source.listing)
110− ?> from-self
111− ?> =(hash.listing (digest post.listing))
112− ?: (~(has by directory) hash.listing)
113− ~& 'Listing already exists.'
114− cor
115− =. published (~(put by directory) hash.listing listing)
116− di-abet:(di-publish:(di-abed:di-core hash.listing) listing)
117− --
118−::
119−++ watch
120− |= =path
121− ^+ cor
122− ?+ path ~|(bad-watch-path/path !!)
123− [%~.~ %gossip %source ~]
124− (give %fact ~ directory+!>(published))
125− ==
126−++ agent
127− |= [=wire =sign:agent:gall]
128− ^+ cor
129− ?+ wire ~|(bad-agent-wire/wire !!)
130− [%~.~ %gossip %gossip ~]
131− ?+ -.sign ~|([%unexpected-gossip-sign -.sign] !!)
132− %fact
133− =* mark p.cage.sign
134− =* vase q.cage.sign
135− ?+ mark
136− ~& [dap.bowl %unexpected-mark-fact mark wire=wire]
137− cor
138− %directory-listing
139− =+ !<(=listing:s vase)
140− ?> =(hash.listing (digest post.listing))
141− ?: (~(has by directory) hash.listing)
142− ~& 'Listing already exists.'
143− cor
144− di-abet:(di-publish:(di-abed:di-core hash.listing) listing)
145− ::
146− %directory
147− =+ !<(d=directory:s vase)
148− cor(directory (~(uni by directory) d))
149− ==
150− ==
151− ==
152−++ arvo
153− |= [=wire sign=sign-arvo]
154− ^+ cor
155− ~& arvo/wire
156− cor
157−++ peek
158− |= =path
159− ^- (unit (unit cage))
160− ?+ path [~ ~]
161− [%x %lookup @ @ @ ~]
162− =- ``search+!>(-)
163− ^- search:s
164− =/ start (slav %ud i.t.t.t.path)
165− =/ limit (slav %ud i.t.t.t.t.path)
166− :: expects encoded @t values)
167− =/ term `@t`(slav %t i.t.t.path)
168− =/ term-counts (get-term-counts term)
169− ~& term-counts
170− =/ terms (get-terms term-counts)
171− ~& terms
172− =/ all=(list listing:s)
173− %- zing
174− %+ turn
175− terms
176− |= [=key:s v=@ud]
177− (get-listings (get-hashes key))
178− =/ listings (swag [start limit] all)
179− :* listings
180− start
181− limit
182− (lent listings)
183− (lent all)
184− ==
185− ==
186−::
187−++ get-terms
188− |= tally=(map @t @ud)
189− ^- (list [@t @ud])
190− =/ sorted
191− %+ sort
192− ~(tap by tally)
193− |= [[k=@t v=@ud] [l=@t w=@ud]]
194− (lth v w)
195− =/ median-index (div (lent sorted) 2)
196− =/ q1 (median (scag +(median-index) sorted))
197− =/ q3 (median (slag median-index sorted))
198− =/ iqr (sub q3 q1)
199− %+ skim
200− sorted
201− |= [k=@t v=@ud]
202− (gth:rs (sun:rs v) (mul:rs (sun:rs q3) .1.5))
203−++ median
204− |= terms=(list [k=@t v=@ud])
205− ^- @ud
206− =/ length (lent terms)
207− =/ middle (div length 2)
208− ?. =((mod length 2) 0) (tail (snag middle terms))
209− %+ div
210− %+ add
211− (tail (snag middle terms))
212− (tail (snag +(middle) terms))
213− 2
214−++ get-term-counts
215− |= term=@t
216− ^- (map @t @ud)
217− =/ parts (split:sift term)
218− =/ ngrams=(list @t)
219− %- zing
220− %+ turn
221− parts
222− |= part=@t
223− (iching part)
224− %+ roll
225− ngrams
226− |= [ngram=@t tally=(map @t @ud)]
227− ^- (map @t @ud)
228− =/ words=(set @t) (~(gut by trigrams) ngram *(set @t))
229− %-
230− %- ~(uno by tally)
231− %- malt
232− %+ turn
233− ~(tap in words)
234− |= word=@t
235− [word 1]
236− |= [k=@t v=@ud w=@ud]
237− (add v w)
238−++ get-hashes
239− |= =key:s
240− ^- (list hash:s)
241− %+ turn
242− %+ sort (~(gut by lookup) key *(list entry:s))
243− |= [a=entry:s b=entry:s]
244− (gte rank.a rank.b)
245− |=(=entry:s hash.entry)
246−++ get-listings
247− |= l=(list hash:s)
248− ^- (list listing:s)
249− %+ turn l
250− |=(=hash:s (~(got by directory) hash))
251−++ from-self =(our src):bowl
252−++ di-core
253− |_ [=listing:s =hash:s]
254− ++ di-core .
255− ++ di-abet
256− cor(directory (~(put by directory) hash listing))
257− ++ di-abed
258− |= h=hash:s
259− di-core(hash h, listing (~(gut by directory) h *listing:s))
260− ++ di-publish
261− |= l=listing:s
262− =. listing l
263− =. cor (emit (invent:gossip %directory-listing !>(listing)))
264− =/ entries=lookup:s
265− %- malt
266− %- zing
267− :~ %+ turn
268− (sift:sift title.post.l)
269− |= word=@t
270− [word ~[[hash rank=0]]]
271− ~[[type.post.l ~[[hash rank=1]]]]
272− %+ turn
273− tags.post.l
274− |= tag=@t
275− [tag ~[[hash rank=2]]]
276− %+ turn
277− (sift:sift description.post.l)
278− |= word=@t
279− [word ~[[hash rank=3]]]
280− ==
281− =/ keys ~(tap in ~(key by entries))
282− ~& keys
283− =. trail (~(put by trail) hash keys)
284− =. lookup
285− %- ~(uni by entries)
286− %- ~(rut by lookup)
287− |= [=key:s value=(list entry:s)]
288− ?. (~(has by entries) key) value
289− (weld value (~(got by entries) key))
290− =. trigrams
291− %+ roll
292− %+ snoc
293− %+ turn keys
294− |= =key:s
295− %- malt
296− %+ turn (iching key)
297− |= =trigram:s
298− [trigram (silt ~[key])]
299− trigrams
300− |= [next=trigrams:s t=trigrams:s]
301− %- (~(uno by t) next)
302− |= [k=key:s v=(set key:s) w=(set key:s)]
303− (~(uni in v) w)
304− di-core
305− --
306−--
desk/app/sphinx.hoonnew file+431
@@ -0,0 +1,431 @@
1+/- s=sphinx
2+/+ gossip, default-agent, verb, dbug
3+/+ *sphinx
4+/+ sift
5+/+ m=metaphone
6+/$ grab-listing %noun %directory-listing
7+/$ grab-directory %noun %directory
8+=, de-purl:html
9+^- agent:gall
10+=>
11+ |%
12+ +$ card card:agent:gall
13+ +$ edition
14+ $% state-0:s
15+ state-1:s
16+ state:s
17+ ==
18+ :: +guard-verb: refuse /verb watches from other ships
19+ ::
20+ :: lib/verb (here and inside lib/gossip) answers /verb/events and
21+ :: /verb/events-plus for anyone, and those carry every poke, card
22+ :: and wire of the agent, the mutuals in gossip's wires included
23+ ::
24+ ++ guard-verb
25+ |= inner=agent:gall
26+ ^- agent:gall
27+ |_ =bowl:gall
28+ +* this .
29+ ag ~(. inner bowl)
30+ ++ on-init
31+ ^- (quip card _this)
32+ =^ cards inner on-init:ag
33+ [cards this]
34+ ::
35+ ++ on-save on-save:ag
36+ ++ on-load
37+ |= old=vase
38+ ^- (quip card _this)
39+ =^ cards inner (on-load:ag old)
40+ :: and end the /verb watches other ships made before this guard
41+ ::
42+ :_ this
43+ %+ weld cards
44+ %+ murn ~(val by sup.bowl)
45+ |= [=ship =path]
46+ ^- (unit card)
47+ ?. &(?=([%verb *] path) !=(our.bowl ship)) ~
48+ `[%give %kick ~[path] `ship]
49+ ::
50+ ++ on-poke
51+ |= [=mark =vase]
52+ ^- (quip card _this)
53+ =^ cards inner (on-poke:ag mark vase)
54+ [cards this]
55+ ::
56+ ++ on-watch
57+ |= =path
58+ ^- (quip card _this)
59+ ?< &(?=([%verb *] path) !=(our.bowl src.bowl))
60+ =^ cards inner (on-watch:ag path)
61+ [cards this]
62+ ::
63+ ++ on-leave
64+ |= =path
65+ ^- (quip card _this)
66+ =^ cards inner (on-leave:ag path)
67+ [cards this]
68+ ::
69+ ++ on-peek on-peek:ag
70+ ++ on-agent
71+ |= [=wire =sign:agent:gall]
72+ ^- (quip card _this)
73+ =^ cards inner (on-agent:ag wire sign)
74+ [cards this]
75+ ::
76+ ++ on-arvo
77+ |= [=wire =sign-arvo]
78+ ^- (quip card _this)
79+ =^ cards inner (on-arvo:ag wire sign-arvo)
80+ [cards this]
81+ ::
82+ ++ on-fail
83+ |= [=term =tang]
84+ ^- (quip card _this)
85+ =^ cards inner (on-fail:ag term tang)
86+ [cards this]
87+ --
88+ --
89+=| state:s
90+=* state -
91+=<
92+ %- guard-verb
93+ %+ verb |
94+ %- %+ agent:gossip
95+ [2 %mutuals %mutuals |]
96+ %- malt
97+ ^- (list [mark $-(* vase)])
98+ :~ [%directory-listing |=(n=* !>((grab-listing n)))]
99+ [%directory |=(n=* !>((grab-directory n)))]
100+ ==
101+ %- agent:dbug
102+ |_ =bowl:gall
103+ +* this .
104+ def ~(. (default-agent this %.n) bowl)
105+ cor ~(. +> [bowl ~])
106+ ++ on-init
107+ ^- (quip card _this)
108+ =^ cards state
109+ abet:init:cor
110+ [cards this]
111+ ::
112+ ++ on-save !>(state)
113+ ++ on-load
114+ |= =vase
115+ =/ old !<(edition vase)
116+ |^
117+ ?- -.old
118+ %2 `this(state (get-state-2 directory.old))
119+ %1 `this(state (get-state-2 directory.old))
120+ %0 `this(state (get-state-2 (directory-0-to-1 directory.old)))
121+ ==
122+ ++ directory-0-to-1
123+ |= =directory:directory-state-zero:s
124+ ^- directory:s
125+ %- ~(run by directory)
126+ |= =listing:directory-state-zero:s
127+ ^- listing:s
128+ =/ post post.listing(+63 [+63.post.listing ''])
129+ listing(+2 post)
130+ ::
131+ ++ get-state-2
132+ |= =directory:s
133+ =/ purged (purge-invalid directory)
134+ :* %2
135+ (reindex purged)
136+ purged
137+ (purge-foreign purged)
138+ ==
139+ ++ reindex
140+ |= =directory:s
141+ %+ roll
142+ ~(tap by directory)
143+ |= [[=hash:s l=listing:s] i=index:s]
144+ (~(catalog delver i) hash l)
145+ ++ purge-invalid
146+ |= =directory:s
147+ ^- directory:s
148+ %- malt
149+ %+ skim
150+ ~(tap by directory)
151+ |= [=hash:s =listing:s]
152+ :: the old %directory handler kept peers' entries under any key
153+ ::
154+ ?& =(hash hash.listing)
155+ =(hash (digest post.listing))
156+ (listing-valid listing)
157+ ==
158+ ++ purge-foreign
159+ |= =directory:s
160+ ^- directory:s
161+ %- malt
162+ %+ skim
163+ ~(tap by directory)
164+ |= [=hash:s =listing:s]
165+ =(our.bowl source.listing)
166+ --
167+ ::
168+ ++ on-poke
169+ |= [=mark =vase]
170+ ^- (quip card _this)
171+ =^ cards state
172+ abet:(poke:cor mark vase)
173+ [cards this]
174+ ++ on-watch
175+ |= =path
176+ ^- (quip card _this)
177+ =^ cards state
178+ abet:(watch:cor path)
179+ [cards this]
180+ ::
181+ ++ on-peek peek:cor
182+ ::
183+ ++ on-leave on-leave:def
184+ ++ on-fail on-fail:def
185+ ::
186+ ++ on-agent
187+ |= [=wire =sign:agent:gall]
188+ ^- (quip card _this)
189+ =^ cards state
190+ abet:(agent:cor wire sign)
191+ [cards this]
192+ ++ on-arvo
193+ |= [=wire sign=sign-arvo]
194+ ^- (quip card _this)
195+ =^ cards state
196+ abet:(arvo:cor wire sign)
197+ [cards this]
198+ --
199+|_ [=bowl:gall cards=(list card)]
200+++ abet [(flop cards) state]
201+++ cor .
202+++ emit |=(=card cor(cards [card cards]))
203+++ give |=(=gift:agent:gall (emit %give gift))
204+++ init
205+ ^+ cor
206+ cor
207+++ poke
208+ |= [=mark =vase]
209+ |^ ^+ cor
210+ ?+ mark ~|(bad-poke/mark !!)
211+ %declare
212+ =+ !<(=declare:s vase)
213+ (publish declare cor)
214+ ::
215+ %declarations
216+ =+ !<(decs=(list declare:s) vase)
217+ =. cor (roll decs publish)
218+ cor
219+ ::
220+ %remove
221+ ?> from-self
222+ =+ !<(=hash:s vase)
223+ di-abet:di-remove:(di-abed:di-core hash)
224+ ==
225+ ++ publish
226+ |= [=declare:s core=_cor]
227+ ^+ cor
228+ :: .core accumulates a %declarations batch: a skipped listing must
229+ :: return it, not the pre-batch .cor, or the batch so far is lost
230+ ::
231+ ?> from-self
232+ =/ listing
233+ :* post=q.declare
234+ hash=(digest q.declare)
235+ reach=p.declare
236+ source=our.bowl
237+ time=now.bowl
238+ ==
239+ ?: (~(has by directory.core) hash.listing)
240+ ~& 'Listing already exists.'
241+ core
242+ ?. (listing-valid listing) core
243+ di-abet:(di-publish:(di-abed:di-core:core hash.listing) listing)
244+ --
245+::
246+++ watch
247+ |= =path
248+ ^+ cor
249+ ?+ path ~|(bad-watch-path/path !!)
250+ [%~.~ %gossip %source ~]
251+ (give %fact ~ directory+!>(directory))
252+ ==
253+++ agent
254+ |= [=wire =sign:agent:gall]
255+ ^+ cor
256+ ?+ wire ~|(bad-agent-wire/wire !!)
257+ [%~.~ %gossip %gossip ~]
258+ ?+ -.sign ~|([%unexpected-gossip-sign -.sign] !!)
259+ %fact
260+ =* mark p.cage.sign
261+ =* vase q.cage.sign
262+ ?+ mark
263+ ~& [dap.bowl %unexpected-mark-fact mark wire=wire]
264+ cor
265+ %directory-listing
266+ =+ !<(=listing:s vase)
267+ ?> =(hash.listing (digest post.listing))
268+ ?: (~(has by directory) hash.listing)
269+ ~& 'Listing already exists.'
270+ cor
271+ ?. (listing-valid listing) cor
272+ di-abet:(di-receive:(di-abed:di-core hash.listing) listing)
273+ ::
274+ %directory
275+ =+ !<(d=directory:s vase)
276+ :: take only listings we don't have yet that pass the checks a
277+ :: %directory-listing gets: a peer's map must not replace our
278+ :: listings, and each listing may be indexed only once
279+ ::
280+ =/ new=(list [=hash:s =listing:s])
281+ %+ skim ~(tap by d)
282+ |= [=hash:s l=listing:s]
283+ ?& =(hash hash.l)
284+ =(hash (digest post.l))
285+ !(~(has by directory) hash)
286+ (listing-valid l)
287+ ==
288+ =. directory (~(gas by directory) new)
289+ =. index
290+ %+ roll new
291+ |= [[=hash:s l=listing:s] i=_index]
292+ (~(catalog delver i) hash l)
293+ cor
294+ ==
295+ ==
296+ ==
297+++ arvo
298+ |= [=wire sign=sign-arvo]
299+ ^+ cor
300+ ~& arvo/wire
301+ cor
302+++ peek
303+ |= =path
304+ ^- (unit (unit cage))
305+ ?+ path [~ ~]
306+ [%x %published ~]
307+ ``directory+!>(published)
308+ ::
309+ [%x %state ~]
310+ ``noun+!>(state)
311+ [%x %tags ~]
312+ =- ``tags+!>(-)
313+ %+ roll
314+ ~(tap by directory)
315+ |= [[=hash:s =listing:s] tags=(map @t @ud)]
316+ %-
317+ %- ~(uno by tags)
318+ %- malt
319+ %+ turn
320+ tags.post.listing
321+ |=(tag=@t [tag 1])
322+ |= [key=@t a=@ud b=@ud]
323+ (add a b)
324+ ::
325+ [%x %lookup %tag @ @ @ ~]
326+ =- ``search+!>(-)
327+ ^- search:s
328+ =/ start (slav %ud i.t.t.t.path)
329+ =/ limit (slav %ud i.t.t.t.t.path)
330+ =/ tag (slav %t i.t.t.t.t.t.path)
331+ %^ page start limit
332+ %+ sort
333+ %+ skim
334+ ~(val by directory)
335+ |= =listing:s
336+ !=(~ (find ~[tag] tags.post.listing))
337+ |= [a=listing:s b=listing:s]
338+ (lth time.b time.a)
339+ ::
340+ [%x %lookup @ @ @ $@(~ [@ ~])]
341+ =- ``search+!>(-)
342+ ^- search:s
343+ =/ filter
344+ %+ rash i.t.t.path
345+ (perk [%app %group %content %other %all ~])
346+ =/ start (slav %ud i.t.t.t.path)
347+ =/ limit (slav %ud i.t.t.t.t.path)
348+ =/ term
349+ ?^ t.t.t.t.t.path
350+ `@t`(slav %t i.t.t.t.t.t.path)
351+ ~
352+ :: expects encoded @t values)
353+ %^ page start limit
354+ ?~ term
355+ %+ skim
356+ ~(val by directory)
357+ |= =listing:s
358+ |(=(filter %all) =(filter type.post.listing))
359+ =/ entries (~(get-entries delver index) term)
360+ :: ~& %+ turn
361+ :: entries
362+ :: |= [=hash:s =rank:s]
363+ :: [(~(got by directory) hash) rank]
364+ %+ skim
365+ %- get-listings
366+ %- get-hashes
367+ %- sort-entries
368+ entries
369+ |= =listing:s
370+ |(=(filter %all) =(filter type.post.listing))
371+ ==
372+++ page
373+ |= [start=@ud limit=@ud all=(list listing:s)]
374+ =/ listings (swag [start limit] all)
375+ :* listings
376+ start
377+ limit
378+ (lent listings)
379+ (lent all)
380+ ==
381+++ get-listings
382+ |= l=(list hash)
383+ %+ murn l
384+ |=(=hash:s (~(get by directory) hash))
385+++ from-self =(our src):bowl
386+++ di-core
387+ |_ [=listing:s =hash:s gone=_|]
388+ ++ di-core .
389+ ++ di-abet
390+ =. directory
391+ ?: gone (~(del by directory) hash)
392+ (~(put by directory) hash listing)
393+ cor
394+ ++ di-abed
395+ |= h=hash:s
396+ di-core(hash h, listing (~(gut by directory) h *listing:s))
397+ ++ di-remove
398+ =. gone &
399+ =/ keys (~(gut by trail.index) hash ~)
400+ =. trail.index (~(del by trail.index) hash)
401+ =. lookup.index
402+ ^- lookup:s
403+ %- ~(uni by lookup.index)
404+ %- malt
405+ %+ turn
406+ keys
407+ |= =key:s
408+ :- key
409+ ^- (list entry:s)
410+ %+ skip
411+ (~(got by lookup.index) key)
412+ |=([h=hash:s =rank:s] =(h hash))
413+ =? published (~(has by published) hash)
414+ (~(del by published) hash)
415+ di-core
416+ ++ di-publish
417+ |= l=listing:s
418+ =. listing l
419+ =. index (~(catalog delver index) hash l)
420+ =. published
421+ ?. =(source.listing our:bowl) published
422+ (~(put by published) hash.listing listing)
423+ =. cor (emit (invent:gossip %directory-listing !>(listing)))
424+ di-core
425+ ++ di-receive
426+ |= l=listing:s
427+ =. listing l
428+ =. index (~(catalog delver index) hash l)
429+ di-core
430+ --
431+--
desk/desk.bill+1−1
@@ -1,2 +1,2 @@
1−:~ %seek
1+:~ %sphinx
22==
desk/desk.docket-0+11−9
@@ -1,9 +1,11 @@
1−:~ title+'seek'
2− info+'An Urbit app that does great things'
3− color+0x2e.4347
4− glob-http+['https://bootstrap.urbit.org/glob-0v5.hurm4.ejod5.ngg9h.iub9i.n1j7o.glob' 0v5.hurm4.ejod5.ngg9h.iub9i.n1j7o]
5− base+'seek'
6− version+[0 0 1]
7− website+'https://github.com'
8− license+'MIT'
9−==
1+:~
2+ title+'sphinx'
3+ info+'A search engine powered by you and your pals for finding answers to your riddles'
4+ color+0xb2.5068
5+ version+[0 7 0]
6+ website+'https://github.com/iko-soy/sphinx'
7+ license+'MIT'
8+ image+'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0zMCAxNSA2MDAgNjAwIiBmaWxsPSJub25lIj48c3R5bGU+LnN0MXtmaWxsOiNlN2FiNzl9PC9zdHlsZT48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMjk0LjY5IDU2Mi45NmMtMy4zNCAwLTYuNjgtLjA5LTEwLjAyLjAyLTcuNTkuMjUtMTUuMS0uMi0yMi41NS0xLjg1LTkuNTQtMi4xMi0xNy44NS02LjU3LTI0LjYyLTEzLjUzLTQuMTUtNC4yNy03Ljk3LTguOTQtMTEuNC0xMy44MS0xMC40Mi0xNC44Mi0yMC40OS0yOS44OS0zMC44Ni00NC43NS0xMS44OC0xNy4wMi0yNC4wNC0zMy44NS0zNS44OC01MC45LTguNDMtMTIuMTQtMTYuNTYtMjQuNDgtMjQuNzItMzYuOC0yLjAxLTMuMDQtNC41Ny01LjE2LTcuNzYtNi44OC0yMS4zMy0xMS41MS0zOC44NC0yNy4xOC01MS40Ni00OC4wMy0zLjY0LTYuMDItNy4yNy0xMi4wNi0xMC41LTE4LjMtNC41Ni04LjgxLTYuOTgtMTguMzktOS4wNC0yOC4wNC00LjY3LTIxLjg2LTIuNDktNDMuMTIgNi42OC02My40NSA1Ljc4LTEyLjgyIDExLjkzLTI1LjUgMTguNjYtMzcuODQgMTQuNjctMjYuOTIgMzEuMTktNTIuNjcgNTAuMzctNzYuNjUgOC40OC0xMC42IDE3LjUzLTIwLjY1IDI4LjA5LTI5LjI2IDIwLjItMTYuNDcgNDIuOTctMjcuOTQgNjcuOTktMzUuMDUgOC40Ny0yLjQxIDE3LjA1LTQuMTggMjUuODEtNS4yNiA2Ljk3LS44NiAxMy44Mi0yLjYxIDIwLjc4LTMuNiA4LjE4LTEuMTUgMTYuNC0yLjA0IDI0LjYzLTIuNzggOS40NS0uODUgMTguOTMtMS41OCAyOC40MS0yLjA0IDE0Ljk5LS43MyAyOS44NS43MiA0NC42NCAzLjI1IDIzLjYgNC4wMyA0Ni44NSA5LjU2IDY5Ljk4IDE1LjY4IDMuMDUuODEgNi4wMSAxLjk4IDkuMDUgMi44NSAzLjE5LjkxIDQuNDYgMy4wNCA0LjU3IDYuMjMuMjggOC43OS43NCAxNy41Ny45OSAyNi4zNi4xNSA1LjQ1LjA4IDEwLjkxLjAzIDE2LjM2LS4wNCAzLjU3LjY1IDYuOTcgMS43OCAxMC4zNCAzLjQ3IDEwLjM0IDYuOTMgMjAuNjggMTAuMyAzMS4wNCAzLjEgOS41MyA2LjA1IDE5LjExIDkuMTcgMjguNjMgMS4yIDMuNjcgMi40OCA3LjM1IDQuMTQgMTAuODIgOC4zMiAxNy40NyAxNi43MiAzNC45IDI1LjE5IDUyLjI5IDUuNzggMTEuODYgMTEuNzIgMjMuNjMgMTcuNiAzNS40NS42MyAxLjI2IDEuMTkgMi41NSAxLjg5IDMuNzcgNC43IDguMjggMy44NCAxNi41OS0uMDkgMjQuNzctMi41OSA1LjM5LTUuNTIgMTAuNjEtOC4zOSAxNS44Ni01LjQgOS44OC0xMC43NiAxOS43OC0xNi4zMiAyOS41Ny0zLjMgNS44MS00LjY2IDEyLjAxLTUuMTMgMTguNjEtMS4zIDE4LjQxLTMuMDcgMzYuNzgtNS45MSA1NS4wNC0xLjc5IDExLjQ2LTIuOTEgMjMuMDItNC40NyAzNC41Mi0xLjU0IDExLjMyLTMuMiAyMi42My00LjkxIDMzLjkyLTIgMTMuMjEtNC4xNyAyNi40LTYuMTEgMzkuNjItLjM4IDIuNi0xLjEzIDQuNzQtMy4zIDYuMjgtLjQzLjMtLjguNjgtMS4yMSAxLjAyLTcuOTcgNi43Ny03Ljk3IDYuODEtMTguNSA3LjAxLTYuNjguMTItMTMuMzcuMTItMjAuMDUuMTQtMS4yMyAwLTIuNDYtLjEtMy42OS0uMTktMi4zMS0uMTYtMy44Ni0xLjE3LTQuNzEtMy41LTIuMjctNi4yOC00LjczLTEyLjQ4LTcuMDgtMTguNzMtMS4xMS0yLjk2LTIuMi01LjkzLTMuMjEtOC45Mi0uNTMtMS41OS0xLjY3LTIuMzUtMy4xNy0yLjI3LTMuNS4xOS03LjAzLjM4LTEwLjQ2IDEuMDEtMS4xLjItMi4yNCAxLjY0LTIuODMgMi43OS0xLjMxIDIuNTMtMy4yNiAzLjM4LTUuOTkgMy40Mi0xNS42NS4yNC0zMS4zLjU2LTQ2Ljk1LjkxLTIuMzMuMDUtNC4xMy0uNTctNS4yNC0yLjcyLS42My0xLjIxLTEuNjYtMS42Ny0zLjA1LTEuNjMtNS40NS4xNC0xMC45MS4xNy0xNi4zNi4zMy0yLjU5LjA4LTMuMjYuOTktMi4yMyAzLjMzIDMuMzQgNy41NiA2LjgzIDE1LjA1IDEwLjI4IDIyLjU2Ljk1IDIuMDggMi4wNiA0LjA5IDIuOSA2LjIxIDEuMTUgMi45My41MSA0LjQxLTIuNjIgNC45Mi01LjIuODUtMTAuNDUgMS41NC0xNS43IDEuODEtNS43OS4zMS0xMS42LjA3LTE3LjQxLjA3LS4wMy4wMS0uMDMtLjAxLS4wMy0uMDN6bTc3Ljk0LTQwMS40N2MtNC45NS4xNC0xMC45My4xOC0xNi45LjUtNy45LjQyLTE1Ljc5IDEuMDgtMjMuNTEgMy0xNS40NSAzLjg1LTMwLjA4IDkuNzktNDQuMDIgMTcuNDUtMi40NSAxLjM1LTQuODIgMi45Ni02LjkgNC44Mi0xLjc5IDEuNi0zLjU5IDMuNTItNC41NyA1LjY2LTMuMDYgNi43MS02LjA4IDEzLjQ4LTguNDkgMjAuNDQtMy41MiAxMC4xOC00LjIgMjAuNzMtMS41NyAzMS4yMSA0LjUxIDE4LjAxIDEyLjUxIDM0LjMzIDI0Ljg5IDQ4LjMyIDguMzMgOS40MiAxOC4wMyAxNy4yOSAyOC41NCAyNC4xMSAzLjUxIDIuMjggNy4zNiA0LjE3IDExLjI4IDUuNjggNS40MSAyLjA4IDExLjAzIDMuNiAxNi41MyA1LjQ0IDIuMjQuNzUgNC4zNS43NSA2LjY4LjE4IDExLjQ0LTIuODIgMjIuOS01LjU0IDM0LjM5LTguMTQgNy4yLTEuNjMgMTQuNS0yLjgzIDIxLjY4LTQuNTMgNS42NS0xLjM0IDkuNi41MyAxMi40NCA1LjM3LjYyIDEuMDYgMS4yOCAyLjExIDIgMy4xMSAxLjEzIDEuNTcgMi4yNCAxLjYgMy4yMS0uMTUgNC44Ny04Ljc2IDkuODEtMTcuNDkgMTMuMDktMjcuMDMgNC45My0xNC4zMSA0LjgtMjkuMDYgMy42Ny00My44NS0uNzgtMTAuMTYtMS45Ny0yMC4yOS0yLjk0LTMwLjQ0LS4zMi0zLjM2LTEuMDMtNC4wOC00LjM0LTMuNDQtNS43OSAxLjExLTExLjU0Ljg3LTE3LjMyLS4wMS0yLjYxLS40LTUuMjEtLjc5LTcuODMtMS4xMi0xLjYyLS4yLTIuNDcuNzktMS43NiAyLjIgMS4yNiAyLjUgMi42NCA0Ljk3IDQuMTUgNy4zMyAzLjEzIDQuODkgNi4zNCA5LjczIDkuNjEgMTQuNTIgMi4zOSAzLjUxIDIuODkgNy4yMSAxLjA3IDExLTIuNDEgNS4wMi00LjI2IDEwLjUyLTkuNzIgMTMuMzYtMS40NC43NS0xLjk4IDIuMTItMS42IDMuNTcuMzkgMS40OCAxLjEgMi45IDEuODUgNC4yNSAxLjExIDEuOTggMi40OSAzLjgxIDMuNTUgNS44MS45MyAxLjc2IDEuMTggMy44LjMxIDUuNjItMS4xNyAyLjQ3LTEuNDYgNC45Mi0xLjI2IDcuNjQuMjUgMy40Ni0xLjUyIDUuNzktNC45MSA2LjUtMi43Mi41Ny01LjU4Ljc1LTguMzcuNjUtMTAuMDItLjM1LTIwLjAzLS42NC0zMC4wNC4zMi0xLjc0LjE3LTMuNTIuMTktNS4yNy4xLTEuOTktLjExLTMuMTctMS4wMi0zLjAyLTIuMjMuMjMtMS44OCAxLjc5LTEuOTQgMy4xOS0yLjAzIDUuMjYtLjM1IDEwLjUzLS44NSAxNS44LS44OSA2LjUtLjA1IDEyLjk5LjM2IDE5LjQ5LjQzIDIuMjUuMDIgNC41NC0uMjMgNi43Ni0uNjMgMS4yOC0uMjMgMi4xMy0xLjMgMi4wNC0yLjcxLS4xLTEuNTMtLjg0LTIuNzQtMi40NS0zLjAxLTEuMi0uMjEtMi40NS0uMjEtMy42OC0uMjQtNC40LS4wOS04LjgtLjAzLTEzLjE5LS4yNS02LjQ0LS4zMS0xMi41OS44NS0xOC41OCAzLjEzLTIuNDYuOTMtNC45NiAxLjg4LTcuNTMgMi4zNi0xLjgzLjM0LTMuOTEuMjctNS42Ny0uMjgtMy4wOC0uOTYtMy45MS00LjQxLTEuNzMtNi44IDEuMDMtMS4xMyAyLjQ2LTEuOTggMy44NS0yLjY5IDQuMjMtMi4xNSA4LjU2LTQuMTIgMTIuOC02LjI1IDcuOTgtNC4wMSAxNi40Mi02LjE5IDI1LjM0LTYuNTMgMS40LS4wNSAyLjgyLS4xNCA0LjE4LS40Ni41Mi0uMTIgMS4zMS0xLjA0IDEuMjItMS40LS4xOS0uNzEtLjc4LTEuNTItMS40My0xLjg0LS43NC0uMzYtMS43Mi0uMzItMi41OS0uMjktNC4zOS4xOS04Ljc3LjUtMTMuMTcuNjMtMy4xNS4wOS02LjMxLjEzLTkuNDUtLjExLTIuMDItLjE2LTMuMDMtMS4yOS0yLjgxLTIuNDguMzItMS43NCAxLjczLTIuMDMgMy4xNy0yLjIzLjM1LS4wNS43LS4wNCAxLjA1LS4wNiA1LjgtLjE3IDExLjYxLS4yMyAxNy40LS41NiAzLjY3LS4yMSA3LjM0LS43MiAxMC45Ny0xLjMyIDIuMDgtLjM1IDQuMTItMS4xMyA1LjI0LTMuMTcuMjEtLjM5LjItLjk4LjEtMS40My0uMDQtLjE4LS42LS4zNi0uOTEtLjM1LTEwLjcxLjUyLTIxLjQyIDEuMDgtMzIuMTMgMS42LTIuNTguMTMtNC44MiAxLjA5LTYuNjcgMi44MS0xLjc5IDEuNjctMy40MSAzLjUzLTUuMDggNS4zNC0uNzEuNzctMS4yMyAxLjc1LTIuMDMgMi4zOS0uNDMuMzUtMS4zOS40My0xLjkxLjE5cy0xLS45OS0xLjExLTEuNTljLS4xMS0uNjMuMDYtMS40Ni40MS0yIDQuMTMtNi40MSA4Ljk0LTExLjc0IDE3LjQxLTExLjk2IDguMDktLjIxIDE2LjE3LS42NSAyNC4yNC0xLjA4IDIuODEtLjE1IDUuNjEtLjUxIDguMzktLjkgMy41Ni0uNSA0Ljc4LTIuNzUgMy4yMy02LS42OC0xLjQyLTEuNjQtMi43LTIuNTEtNC4wMi0zLjU5LTUuNDMtNy4zMS0xMC43Ny0xMC43Ni0xNi4yOS0yLjkxLTQuNjUtNS40LTkuNjMtNS40LTE1LjI0IDAtNi44NS41MS0xMy42OS44MS0yMC41NC4wMi0uMzUuMTYtLjY4LjIyLTEuMDMgMS4wNC02LjM0IDQuNzMtMTAuNDMgMTAuNzItMTIuNTUgOC40NC0yLjk4IDE2LjgzLTMuNTMgMjUuMTEuNTcuNjMuMzEgMS4yNy42MiAxLjkyLjg4IDIuMTYuODUgMi43OC41OCAzLjMzLTEuNzQgMS41NS02LjUxLjktNi4xNi00LjM1LTcuOC05Ljc4LTMuMDYtMTkuODItNC42NC0yOS45OS01LjcxLTEyLjI1LTEuMjYtMjQuNDktMi4zMy0zNy44Ni0xLjA5em0tMTU3LjA4IDYuMjljLTEuMzQtMTAuMS0yLjUtMjAuMjMtNC4wNi0zMC4zLTIuNDctMTUuOTctNS4yMS0zMS45MS03Ljg0LTQ3Ljg1LS4wOS0uNTItLjE0LTEuMDUtLjI3LTEuNTUtLjI3LTEuMDEtMS4yMi0xLjIzLTItLjQ1LS40OS41LS45NCAxLjA0LTEuMzggMS41OS0xNy4wMyAyMS4yOC0zMy45MSA0Mi42Ny00OS45OSA2NC42OC04LjcyIDExLjkzLTE3LjAyIDI0LjE0LTI0LjUyIDM2Ljg4LTYuODkgMTEuNy0xMi42OCAyMy45MS0xNi43NiAzNi44OS0yLjcxIDguNjMtNC4wMSAxNy40MS0zLjExIDI2LjUgMS4wNSAxMC42NiAxLjc2IDIxLjM2IDIuNjggMzIuMDQuNzEgOC4yMyAxLjM5IDE2LjQ2IDIuMzIgMjQuNjYuNzYgNi43MyAxLjI0IDYuOTYgNy45MyA2LjQ2IDE0LjAyLTEuMDUgMjguMDQtMi4wMyA0Mi4wNS0zLjEzIDUuMjUtLjQxIDEwLjQ4LTEuMTYgMTUuNzMtMS42MyA5LjI3LS44MyAxOC40Mi0yLjg1IDI3Ljc5LTIuNjMgMS4yMi4wMyAyLjQ2LS4xNyAzLjY3LS4zOSAyLjE3LS40IDIuNTQtMS4xMyAxLjgyLTMuMzUtLjE2LS41LS4zOC0uOTgtLjU5LTEuNDctMy45Ny05LjYtNy4xOC0xOS40NC05Ljg5LTI5LjQ3LS41My0xLjk1LTEuNzctMy43OC0yLjk1LTUuNDctMi43Mi0zLjg4LTUuODktNy40Ny04LjM5LTExLjQ5LTguODktMTQuMy0xMy43NS0yOS41NC0xMC41NS00Ni42NiAxLjkyLTEwLjI5IDYuMTgtMTkuMzUgMTIuOTYtMjcuMjggNC40LTUuMTUgOS44Ny04LjM3IDE2LjU3LTkuNDYgMS4yMS0uMiAyLjQxLS40OCAzLjYzLS42NyA1LjQ3LS44NSA1LjQ3LS44NCA1LjE1LTYuNDV6bTQ1LjAxIDY0LjFjLS40MS03LjQxIDEuMDYtMTQuNjQgMy42MS0yMS42MiAxLjkzLTUuMjcgNC40Mi0xMC4zNCA2LjU1LTE1LjU1IDMuMzEtOC4wOCA5LjMtMTMuNTggMTYuOTItMTcuMzcgNy40LTMuNjggMTQuODMtNy4zMyAyMi40OC0xMC40NCAxMC4xMi00LjExIDIwLjU1LTcuNTIgMzEuNDctOC41NiAxMS4zNy0xLjA5IDIyLjgtMS42IDM0LjIxLTIuMSAxMy4zOC0uNTkgMjYuNzIuMTkgNDAgMS45OCA3LjY3IDEuMDMgMTUuMzMgMi4wOSAyMi43NSA0LjQzIDIuMTguNjkgNC4zOSAxLjI5IDYuNjEgMS43OSAxLjU5LjM2IDIuMy0uMTggMi41NC0xLjgxLjE4LTEuMjEuMjEtMi40NS4yNS0zLjY4LjI4LTcuNTYuNS0xNS4xMi44My0yMi42OC4xMy0yLjk4LjU5LTUuOTYuNjctOC45NC4wOS0zLjE1LS43LTQuMDItMy44OS00LjUtMTEuNjUtMS43My0yMy4zMS0zLjQ1LTM0Ljk4LTUuMDctMi40Mi0uMzQtNC45MS0uMjktNy4zNy0uMjYtMS42Mi4wMi0yLjU5IDEuMDItMi43OCAyLjYzLS4xMiAxLjA0LS4xMSAyLjEtLjE1IDMuMTYtLjIzIDUuOC0uNCAxMS42LS42OSAxNy4zOS0uMjEgNC4wMS0xLjQ3IDUuMTctNS42NyA1LjE1LTIuNjMtLjAxLTUuMjYtLjQyLTcuOS0uNDUtNC4yMS0uMDUtOC40OS0uMzgtMTIuNjEuMjYtMy43NS41OC02LjEtLjYzLTcuOTktMy41My0xLjI1LTEuOTEtMi41NS0zLjgtMy42OC01Ljc5LTMuMDMtNS4zNi02LjE1LTEwLjY4LTguODktMTYuMTgtMS4zOS0yLjgtMy4xMi0zLjktNi4xOS0zLjU5LTguNC44NS0xNi44NSAxLjItMjUuMiAyLjM1LTI1LjExIDMuNDMtNDkuODYgOC44MS03NC40OSAxNC43LTguNzEgMi4wOC0xNy4yOCA0Ljc5LTI1Ljg3IDcuMzctMy40MiAxLjAzLTMuNzMgMS41My0zLjM2IDQuODkuODYgNy44NiAxLjkxIDE1LjcxIDIuODEgMjMuNTYuMzUgMy4wMiAxLjU0IDUuMDIgNC42NCA2LjI1IDYuODkgMi43MyAxMS40OSA4LjAyIDE0LjI3IDE0Ljc4IDEuNDcgMy41NiAyLjYzIDcuMjggMy41OSAxMS4wMiAyLjY4IDEwLjM4IDQuOTggMjAuODYgNy44IDMxLjIgMy44OCAxNC4yNSA3LjkgMjguNDYgMTIuMjIgNDIuNTggNC44MiAxNS43NCAxNC4xMiAyOC42NyAyNi41OSAzOS4yMyA3LjI0IDYuMTMgMTQuOTggMTEuNjcgMjIuNDcgMTcuNTEgNy41NCA1Ljg4IDE1LjcyIDEwLjcgMjQuNTkgMTQuMjIgNC41NCAxLjggNy42MyA0Ljg1IDkuOCA5LjA3IDIuNDkgNC44NSA1LjIxIDkuNTkgNy40OSAxNC41NCAxMS4zOSAyNC43NyAyMi42OCA0OS41OSAzNCA3NC4zOS41OCAxLjI4IDEuMTIgMi41OSAxLjc0IDMuODQgMS4wMSAyLjAyIDEuODEgMi4xIDMuNTIuNDMgNC41My00LjQzIDQuODEtNS43MSAyLjEtMTEuNTQtNi42LTE0LjE5LTEzLjIyLTI4LjM4LTE5LjktNDIuNTQtMy4zLTYuOTktNy4xNS0xMy43NS0xMC0yMC45Mi01LjAyLTEyLjYtMTAuNjYtMjQuODktMTYuNjMtMzcuMDYtMS4yLTIuNDUtMi44NC0zLjgyLTUuMzYtNC41NS0zLjM4LS45OC02LjY4LTIuMjEtMTAuMDQtMy4yMy02LjgtMi4wNy0xMy4wOC01LjE4LTE4LjgxLTkuMzUtNC4yNi0zLjExLTguNDctNi4yOS0xMi41OS05LjU5LTE5Ljc2LTE1Ljg0LTMyLjI5LTM2LjQtMzkuMzEtNjAuNTMtMS42MS01LjYxLTIuNDktMTEuMzMtMi4xNy0xNy4yOXptNzYuMjQgMTIwLjA0Yy0xLjgyLS45Mi0yLjg2LTEuNDQtMy44OS0xLjk3LTQuMzgtMi4yNC04Ljk5LTQuMTMtMTMuMDktNi44LTEyLjA4LTcuODktMjMuNzYtMTYuMzYtMzQuNTUtMjUuOTYtMTIuMzItMTAuOTUtMjEuNTYtMjQuMDUtMjYuNzEtMzkuODQtMS4wNC0zLjE3LTIuMTgtNi4zMi0zLjEyLTkuNTItNi40MS0yMS43Ni0xMS44Mi00My43OC0xNi45Ny02NS44Ni0xLjIxLTUuMi0zLjA1LTEwLjEtNi4xNi0xNC40NS0zLjA4LTQuMy03LjA2LTcuNzItMTIuMzQtOC4zNi0xMC4yMi0xLjI1LTE5LjczLjUtMjYuNjMgOS4xNC0xMS44MSAxNC44Mi0xNS4zMSAzMS40Ni05LjY5IDQ5LjU4IDUuMzggMTcuMzQgMTYuMjggMzAuOTggMzAuMjMgNDIuMyA0LjE3IDMuMzggOSA1LjUxIDE0LjM0IDYuMjQgMS41Mi4yMSAzLjE4LjE4IDQuNjctLjE4IDguMjItMS45OSAxMi40Mi0xMC4zMyA5LjA3LTE4LjEyLTEuMTgtMi43My0yLjg2LTUuMjQtNC4yMy03LjktLjU1LTEuMDctMS4wMi0yLjIzLTEuMjItMy40MS0uMDktLjUyLjQzLTEuNDEuOTMtMS43LjQ5LS4yOSAxLjQyLS4yNiAxLjk0LjAyLjc0LjQgMS40NiAxLjA1IDEuODcgMS43OCAyLjA3IDMuNjcgNC4yMyA3LjMxIDUuOTggMTEuMTMgMS4zNSAyLjkzIDEuMzEgNi4xMS40NSA5LjMtMS4yIDQuNDEtMy4zNSA4LjE3LTcuMTMgMTAuNzUtMi41MiAxLjcyLTIuOTIgMy43My0xLjc1IDYuMzQuMzYuOC41OSAxLjY1LjkgMi40OCA1LjggMTUuNTkgMTUuMjQgMjguODYgMjYuNDQgNDAuOTQgNC45NiA1LjM0IDExLjAxIDkuMjcgMTcuNjcgMTIuMjMgMy4zNyAxLjUgNi43OSAyLjkxIDEwLjA3IDQuNiA2LjMxIDMuMjQgMTMuMDYgNC4wNyAxOS45NyAzLjgxIDIuNTktLjEgNS4zMi0uNjEgNy42Ny0xLjY0IDQuNjctMi4wNCA5LjM4LTMuODEgMTUuMjgtNC45M3ptMTE4LjQzLTIxOS45NmMtLjMuMDctLjYuMTQtLjkxLjIxLS4xOSAxLjItLjUgMi40LS41NSAzLjYxLS4yNCA1LjI3LS4zOCAxMC41NC0uNiAxNS44MS0uMzUgOC4yNS0uMzYgMTYuNTMtMi40NSAyNC42LTIuMjIgOC41My0zLjQzIDE3LjIyLTMuNjkgMjYuMDEtLjQ3IDE2LjU0LjU3IDMyLjk5IDIuNzMgNDkuNDIgMS42MSAxMi4yMyAxLjU1IDI0LjU1LS45OSAzNi43My0uMTguODYtLjQ0IDEuNzEtLjUzIDIuNTgtLjIgMi4wNi40MSAzLjAzIDIuNDEgMy4zNiAyLjYuNDMgNS4yMi43OSA3Ljg0Ljk0IDguNzcuNTEgMTcuNTUuODQgMjYuMzIgMS4zOSA2LjY2LjQyIDEzLjMxIDEuMDUgMTkuOTYgMS42MiA1LjI1LjQ1IDEwLjUgMS4wMiAxNS43NSAxLjM5IDQuNTUuMzIgNS4zOS0uNzMgMy45My01LjE1LS44OC0yLjY1LTIuMTYtNS4xOS0zLjQyLTcuNjktNS4xMy0xMC4yMS0xMC40NC0yMC4zMy0xNS40NS0zMC42LTkuNDktMTkuNDMtMTguODUtMzguOTEtMjguMTctNTguNDItMS41OC0zLjMyLTIuODQtNi44MS0zLjk5LTEwLjMxLTUuNDUtMTYuNy0xMC44LTMzLjQ0LTE2LjIxLTUwLjE2LS41Ny0xLjgxLTEuMzEtMy41Ny0xLjk4LTUuMzR6bS0xNDUuMzkgMjY0LjRjLjIzLjgxLjQyIDIuMDMuOTIgMy4xMS45NiAyLjA3IDIuMSA0LjA2IDMuMTcgNi4wOCA1LjkxIDExLjIgMTIuMDEgMjIuMyAxNy42NyAzMy42MyA1Ljk3IDExLjk2IDExLjUgMjQuMTQgMTcuMjMgMzYuMjEgMS41MSAzLjE4IDMuMDIgNi4zNiA0LjY1IDkuNDcgMS4xMSAyLjExIDEuODcgMi4xMSAzLjU1LjM3IDQuOTktNS4xOSA5Ljk2LTEwLjQyIDE1LjAyLTE1LjU0IDMuMDktMy4xMyA2LjQzLTYgOS41LTkuMTUgNi4zNi02LjUzIDYuNC00LjMxIDIuNC0xMy4zNi03LjM5LTE2LjczLTE1LjA3LTMzLjM0LTIyLjY1LTQ5Ljk5LTQuNzQtMTAuNC05LjQ5LTIwLjgtMTQuMzEtMzEuMTctMS4xOC0yLjU1LTIuNjMtNC45OC0zLjk1LTcuNDYtLjc1LTEuNDEtMS44OS0xLjg2LTMuNS0xLjczLTQuNDMuMzQtOC42NyAxLjI5LTEyLjc1IDMuMDgtNC4yNiAxLjg3LTcuMzQgNC43My04LjcyIDkuMjgtMi41IDguMjQtNS4wNSAxNi40Ny03LjU3IDI0LjcxLS4yLjY2LS4zNiAxLjM1LS42NiAyLjQ2em0tMzEuNjIgMjYuNjdjMy4zNy4wMyA0LjEzLS42MSA0LjYyLTIuOTkuMTEtLjUyLjExLTEuMDUuMjEtMS41NyAyLjczLTE0Ljg3IDUuNDUtMjkuNzMgOC4xOS00NC42LjYxLTMuMjggMS4zNS02LjU0IDEuOTEtOS44NC41NC0zLjIyLjE5LTMuOS0yLjc1LTUuNDktMS43LS45Mi0zLjQ2LTEuNzQtNS4yNS0yLjQ2LTExLjktNC43Ni0yMS41LTEyLjQ1LTI5LjM4LTIyLjUxLTkuMDYtMTEuNTctMTYuODQtMjMuODUtMjEuNC0zNy45My0uODYtMi42Ny0yLjIzLTQuMDEtNS4xOS00LjIxLTcuMjQtLjUtMTMuNTMtMy41My0xOS4xOC04LS45Ni0uNzYtMS45My0xLjUyLTIuOTctMi4xNy0uMTQtLjA5LS45OS40OC0uOTYuNjMuMjkgMS4zNy42NiAyLjczIDEuMDggNC4wNiAzLjI5IDEwLjQyIDcuNzkgMjAuMzUgMTIuMTkgMzAuMzMgNS42NyAxMi44NyAxMS4yOCAyNS43NyAxNi43NyAzOC43MSA2LjA0IDE0LjI0IDExLjk0IDI4LjU1IDE3Ljg0IDQyLjg1IDIuMSA1LjA4IDUuMDMgOS42NyA4LjExIDE0LjE2IDIuNzMgMy45NyA2LjM4IDcuMDUgMTAuNzQgOS4xMyAyLjAzLjk4IDQuMjcgMS41MSA1LjQyIDEuOXptMTIyLjIxIDY0LjQ4YzUuNjIgMCAxMS4yNS0uMTMgMTYuODYuMDUgMy4wNy4xIDUuMy0uOTYgNy40MS0zLjE0IDUuNjEtNS44MSAxMS40LTExLjQ1IDE3LjEyLTE3LjE2IDYuMS02LjA4IDEyLjI4LTEyLjA3IDE4LjI2LTE4LjI3IDIuNDItMi41MSA0LjU0LTUuMzUgNi40OC04LjI1IDEuNC0yLjA5LjUtMy44NC0yLjAzLTQuMDYtMi43OC0uMjUtNS42Mi0uMjEtOC40MS4wMy01LjYuNDgtMTEuMiAxLTE2Ljc1IDEuODItMTAuOTQgMS42Mi0yMS44NSAzLjQ4LTMyLjc3IDUuMi0yLjM2LjM3LTQuMyAxLjMtNS44NiAzLjE3LTguOSAxMC42OS0xOSAyMC4yMi0yOSAyOS44NS0yLjc4IDIuNjgtNS41MSA1LjQzLTguMTEgOC4yOC0uOTggMS4wNy0yLjg5IDIuMjMtMS44MiAzLjg2LjQ1LjY4IDIuNTguNDUgMy45Mi4zMyAxMS41NC0xLjAzIDIzLjA4LTIuMSAzNC43LTEuNzF6bS0xMzEuMzEtNjIuMDRjLjMyIDEuMTguNCAxLjg2LjY4IDIuNDUgNS4yOCAxMS4zIDEwLjMyIDIyLjczIDE1Ljk0IDMzLjg2IDguOTYgMTcuNzMgMTguNDEgMzUuMiAyNi4yMSA1My40OS4yOC42NS42MSAxLjI2LjkzIDEuODkuNjkgMS4zOCAxLjgyIDIuMjEgMy4zNCAyLjIxIDUuNjIgMCAxMS4yNC0uMDUgMTYuODYtLjE4IDEuMjctLjAzIDEuNzYtMS4wMiAxLjI4LTIuMjktLjQzLTEuMTUtMS0yLjI1LTEuNTMtMy4zNS0zLjU4LTcuNDQtNy4xNC0xNC45LTEwLjc3LTIyLjMyLTcuNzQtMTUuNzktMTUuMTItMzEuNzMtMjEuNjItNDguMDctLjg0LTIuMTItMS42Mi00LjMyLTIuODEtNi4yNC0zLjQ5LTUuNjctOC05LjgtMTUuMzUtOS4wMy0zLjIxLjM0LTYuMy0uMTgtOS4zMy0xLjI1LTEuMTEtLjM5LTIuMjQtLjY5LTMuODMtMS4xN3ptLTE0MC4wNS04OC44NGM0Ljk3LS4zNyA4LjY1LS43OCAxMi4zMy0uOSAxOC44LS42MSAzNy41Ny0xLjcxIDU2LjMtMy40OSA2LjMtLjYgMTIuNTctMS40MiAxOC44NS0yLjE5IDIuNDYtLjMgMy0xLjIzIDIuMDItMy41OC0xLjIzLTIuOTEtMi42NS01Ljc1LTMuOTEtOC42NS0uODctMi4wMS0yLjMtMy00LjUtMi45NC01LjI3LjE1LTEwLjYxLS4yNC0xNS43OS40OC0yMy4xOCAzLjE5LTQ2LjQ5IDQuNzktNjkuODMgNi4wNS0xLjc1LjA5LTMuNTEuMjUtNS4yNS41MS0zLjM1LjUxLTMuNzYgMS4yOS0yLjEgNC4wMyAxLjE4IDEuOTUgMi42OSAzLjcyIDMuNzYgNS43MyAyLjA0IDMuODggNC44NyA1LjkzIDguMTIgNC45NXpNMzg0LjYzIDE0MC4zYzAgLjA4LjAxLjE2LjAxLjI0IDIuMS4xOCA0LjIuNDYgNi4zMS41IDIuOTkuMDUgMy43Ny0uNjYgMy45MS0zLjY4LjE4LTMuNjkuMy03LjM5LjItMTEuMDctLjE5LTcuMDItLjA3LTE0LjAyLTEuMDEtMjEuMDQtLjg2LTYuNDUtMy45NC0xMS4xOS04Ljg0LTE0Ljk2LTIuMjEtMS43LTQuNzItMy4wMi03LjE4LTQuMzctMy4yLTEuNzUtMy40Ni0yLjE0LTIuODItNS44My4xMi0uNjkuNDMtMS4zNy40My0yLjA2LS4wMi0zLjUuMzktNy4xLS4yOS0xMC40OC0xLjMzLTYuNTItNS45My0xMC42OC0xMS43Ny0xMy4yOC0zLjM0LTEuNDktNy4wMi0yLjIyLTEwLjU1LTMuMjctNy4yLTIuMTUtMTMuOTItLjUxLTIwLjM2IDIuNzktNi4wOSAzLjExLTcuNjQgNS45LTYuOTMgMTIuNy40NiA0LjQ0IDEuNjYgOC44IDUuMTQgMTEuODEgNC42OCA0LjA0IDkuOCA3LjM0IDE2LjIyIDguMjMgNC43OS42NiA1Ljk3IDIuMzYgNS4zOSA3LjIzLS43NCA2LjI3LjE5IDEyLjMxIDIuMjYgMTguMTUgMy4zNiA5LjUxIDguNzggMTcuOTggMTQuMDcgMjYuNDkgMS4zOSAyLjI0IDMuMjcgMi43OCA1Ljg0IDIuNDcgMy4yOS0uNDIgNi42NC0uNDEgOS45Ny0uNTd6bS0yMTguNSAyMzYuNTF2LS4xYzEwLjM1LS40MyAyMC43LS44NiAzMS4wNC0xLjI5LjUzLS4wMiAxLjA1LS4xMSAxLjU3LS4xNCAxMS4zOS0uNjQgMjIuNzgtMS4yNyAzNC4xNi0xLjkuODgtLjA1IDEuNzYuMDEgMi42My0uMDYgMS42Ny0uMTMgMi4xNy0uODMgMS41NS0yLjQ2LTEuMjMtMy4yOS0yLjU2LTYuNTQtMy44OS05Ljc5LTEuNzktNC40LTIuODUtNS4wNS03LjUyLTQuNzItOC4wNS41Ni0xNi4xIDEuMjgtMjQuMTYgMS42Mi0xNC4yMS42LTI4LjQzLjkyLTQyLjY0IDEuNDQtNC43My4xOC05LjQ2LjU4LTE0LjE4Ljk3LTEuOTIuMTYtMi40IDEuMjMtMS4zIDIuODggMi41MiAzLjggNS4xNyA3LjUzIDcuNzEgMTEuMzIgMS4wOSAxLjYzIDIuNTggMi4yNSA0LjQ3IDIuMjQgMy41NC0uMDMgNy4wNS0uMDEgMTAuNTYtLjAxem01Ni40IDY0LjZjMCAuMDQgMCAuMDkuMDEuMTMgMTMuMzItLjQ5IDI2LjY1LS45OCAzOS45Ny0xLjQ4IDEuNC0uMDUgMi44Mi0uMDggNC4yLS4yOCAxLjc5LS4yNiAyLjQ4LTEuMzMgMS43Ni0yLjk4LTEuNzYtNC4wMi0zLjY2LTcuOTctNS41My0xMS45My0uNzUtMS42LTIuMDktMi4yOC0zLjgyLTIuMzItMS4wNS0uMDItMi4xLS4xMS0zLjE2LS4xLTExLjkzLjA5LTIzLjg3LjA4LTM1LjguMzQtOC4wNi4xNy0xNi4xMi43Mi0yNC4xOCAxLjEtNC4wMy4xOS04LjA2LjM0LTEyLjA5LjYyLTEuOTUuMTQtMi42MSAxLjItMS43MSAyLjgzIDIuMzkgNC4yOSA0LjkgOC41MiA3LjM5IDEyLjc0Ljc2IDEuMyAxLjk0IDEuOTggMy40OSAxLjk2IDEuNC0uMDEgMi44MS4wNSA0LjIxLjAxIDguNDMtLjIgMTYuODUtLjQzIDI1LjI2LS42NHptLTQuMjktMTA2LjU3Yy0yLjM3LjItMy45Ni4yNS01LjUyLjQ3LTIyLjg0IDMuMTktNDUuODMgNC40Mi02OC44NiA1LjA5LTQuMzkuMTMtOC43Ny41LTEzLjE1Ljg3LTEuNjQuMTQtMi4yMyAxLjI2LTEuMzMgMi41NiAyLjYxIDMuNzUgNS4zMyA3LjQyIDguMDQgMTEuMS45NiAxLjMgMi4zIDEuMjQgMy44IDEuMTYgMTQuNzQtLjg2IDI5LjQ4LTEuNjggNDQuMjItMi40NCAxMC41My0uNTQgMjEuMDctLjk1IDMxLjYxLTEuNDcgMi44MS0uMTQgNS42MS0uNDUgOC40MS0uNzMgMy4xMy0uMzEgMy41Ni0uOTkgMi4zNS00LjEzLTEuMDctMi43OS0yLjQ1LTUuNDYtMy40Ny04LjI3LTEuMjEtMy4zNi0zLjM3LTQuODEtNi4xLTQuMjF6bS0xMi40NCA2Ni43MWgtMzEuODdjLTEuNCAwLTIuODEtLjA0LTQuMjEuMDYtMS4yNy4wOS0xLjgyIDEuMDktMS4xNyAyLjI1IDEuNTUgMi43NSAzLjIzIDUuNDMgNC44MiA4LjE2IDQuODEgOC4yNCA0LjgxIDguMjMgMTQuMzkgNy42OCA5LjgxLS41NyAxOS42Mi0xLjM1IDI5LjQ0LTEuNjMgMTIuMjctLjM2IDI0LjU1LTEuNDcgMzYuODQtLjQ2Ljg3LjA3IDEuNzYuMDQgMi42My0uMDEgMS43Ny0uMTEgMi4xOS0uODcgMS4zLTIuMzgtMi4xMy0zLjYzLTQuMzctNy4yLTYuMzgtMTAuODktMS4wMy0xLjktMi4zOS0yLjkzLTQuNTMtMy4wMy00LjU1LS4yMS05LjEtLjY4LTEzLjY0LS42Mi04Ljk0LjEyLTE3Ljg4LjU0LTI3LjYyLjg3em0tLjQ3LTQuNzd2LS40N2MxMi40Ny0uMTcgMjQuOTQtLjMzIDM3LjQxLS41LjE4IDAgLjM1IDAgLjUzLS4wMSAzLjI0LS4yIDMuODYtLjk5IDIuNzEtMy44OS0xLjI5LTMuMjYtMi44OS02LjQxLTQuMTUtOS42OS0xLjExLTIuOS0zLjE1LTQuMDctNi4xLTQtNS42Mi4xMy0xMS4yNS4xMS0xNi44Ni40LTE1LjYxLjgxLTMxLjIxIDEuNzgtNDYuODIgMi42LTQuNTYuMjQtOS4xNC4xNi0xMy43LjMyLTEuNjIuMDYtMi4yNSAxLjEtMS41IDIuNSAxLjU4IDIuOTQgMy40NiA1LjcyIDQuOTIgOC43MSAxLjUzIDMuMSAzLjc5IDQuMTIgNy4xOSA0LjA3IDEyLjEyLS4xNCAyNC4yNS0uMDQgMzYuMzctLjA0em02MC43NCAxMDguMzJjMCAuMDguMDEuMTYuMDEuMjMgNS45Ny0uMjUgMTEuOTMtLjU0IDE3LjktLjcyIDQuNzQtLjE0IDkuNDktLjEyIDE0LjIzLS4yMyAyLjY3LS4wNiAzLjA5LS43MiAxLjktMy4yNS0xLjc4LTMuODItMy44My03LjUyLTUuNTItMTEuMzgtMS4zMi0yLjk5LTMuMzktNC4zLTYuNi0zLjk5LTEuMDUuMS0yLjExLjAxLTMuMTYuMDYtNy45LjMzLTE1Ljc5Ljg3LTIzLjY5Ljk3LTEyLjk5LjE1LTI1Ljk4IDEuNDEtMzguOTguMzctLjM1LS4wMy0uNy0uMDItMS4wNSAwLTIuMjkuMTEtMi44NCAxLjAyLTEuNTcgMy4wMyAyLjUzIDQuMDEgNS4zNyA3LjgzIDcuNzYgMTEuOTMgMi4wNiAzLjUzIDQuODIgNC41MSA4Ljc2IDQuMjYgOS45OS0uNjEgMjAtLjg3IDMwLjAxLTEuMjh6bS00OC45Mi00MS43NWM3LjkyLS4zNSAxNS4yOS0uNyAyMi42NS0xIDExLjIyLS40NiAyMi40NS0uODkgMzMuNjctMS4zNSAxLjA0LS4wNCAyLjEtLjE1IDMuMTEtLjQxIDEuNC0uMzYgMS45OC0xLjQzIDEuMzctMi44NC0xLjYtMy43LTMuMzItNy4zNi01LjA0LTExLS42Ni0xLjQtMS44OS0yLjAzLTMuNDItMi4wMy0xLjIzIDAtMi40Ni4wMS0zLjY4LjA3LTUuMjYuMjgtMTAuNTEuNzItMTUuNzcuODUtMTcuMDIuNDItMzQuMDQuNzItNTEuMDYgMS4wNy0uNy4wMS0xLjQxIDAtMi4xLjA2LTIuMjUuMjEtMi42OCAxLjA0LTEuNDcgMy4xNCAyLjAyIDMuNDkgNC4yMiA2Ljg5IDYuMTcgMTAuNDEgMS4yNyAyLjI5IDMuMDggMy4xMiA1LjYxIDMuMDUgMy40OS0uMSA3LS4wMiA5Ljk2LS4wMnptMjIuMjcgMTkuMDV2LjE4YzE0Ljc0LS41MSAyOS40OS0xLjAyIDQ0LjIzLTEuNTMuODgtLjAzIDEuNzYtLjAzIDIuNjItLjE4IDIuMTItLjM2IDIuNzctMS40IDEuOTYtMy4zLS43Ni0xLjc3LTEuNjgtMy40OC0yLjU5LTUuMTgtMy44NS03LjI3LTMuMDgtNi44NS0xMS4yOC02LjUzLTE4LjQzLjctMzYuODUgMS40LTU1LjI4IDIuMDctMy4zMy4xMi02LjY4LjA2LTEwLjAxLjE4LTEuNjMuMDYtMi4yMSAxLjA1LTEuNCAyLjQ5IDEuNjMgMi45MSAzLjQ0IDUuNzIgNS4wOSA4LjYyIDEuMjUgMi4yIDMuMDQgMy4yMyA1LjU4IDMuMjEgNy4wMy0uMDYgMTQuMDYtLjAzIDIxLjA4LS4wM3ptMjUuNjMgMjcuOTZjLTMuNjkgMC0xMC41Ni0uMDctMTcuNDEuMDMtMy42OS4wNi03LjM4LjM2LTExLjA2LjY2LTEuNjkuMTQtMi4xNyAxLjEzLTEuMTYgMi41NCAyLjk4IDQuMTQgNi4wNiA4LjIgOS4xMyAxMi4yOCAxLjMyIDEuNzUgMy4yMiAxLjk3IDUuMjIgMS44NSA0LjkxLS4zIDkuODItLjcgMTQuNzQtLjg5IDEzLjE4LS41MiAyNi4zNi0uOTUgMzkuNTQtMS40MyAxLjIzLS4wNCAyLjQ3LS4wNiAzLjY3LS4yOSAxLjc2LS4zMyAyLjMtMS4yOSAxLjY0LTMuMS0uNzctMi4xNS0xLjk4LTQuMTYtMi41Ni02LjM1LTEuMzQtNS4wNy01LTYuMzYtOS41OS02LjM4LTEuMDYtLjAxLTIuMTEtLjAyLTMuMTcuMDItOC41OS4zLTE3LjIuNjItMjguOTkgMS4wNnptMjQwLjY2LTE5My41OGMuMDEtLjA1LjAxLS4xLjAyLS4xNCAzLjE3IDAgNi4zMy4wNCA5LjUtLjAzIDEuMDMtLjAyIDIuMzYuMDEgMy4wMi0uNTkgMi42Ny0yLjQyIDMuOTktNS43IDQuOTUtOS4wOS4xMi0uNDItLjU1LTEuMjktMS4wNy0xLjYtLjcyLS40NC0xLjYyLS42OC0yLjQ4LS43OS0zLjE0LS40LTYuMjgtLjgyLTkuNDQtMS4wNS0xOS42My0xLjQ2LTM5LjI3LTIuODgtNTguOS00LjMtLjg4LS4wNi0xLjc2LS4wNy0yLjY0LS4wOS0xLjU2LS4wMy0yLjc2LjYyLTMuNDIgMi4wMi0xLjUgMy4xOC0yLjk5IDYuMzctNC4zOCA5LjYtLjYzIDEuNDYtLjAzIDIuMzQgMS42NCAyLjQ0IDMuNTEuMjIgNy4wMy4yOCAxMC41NC40OCAxNy41NiAxLjAzIDM1LjExIDIuMDkgNTIuNjYgMy4xNHptLTkuNiAxNy43M2MwLS4wNC4wMS0uMDguMDEtLjEyIDEuNCAwIDIuODEuMDEgNC4yMSAwIDkuOTEtLjA2IDcuODEgMS4xNCAxMi44LTcuNjYgMi42LTQuNTggMi4xNi01LjE3LTMuMTctNS4zNS0xMi4xLS40Mi0yNC4xOS0uNzktMzYuMjgtMS4zOC02LjY1LS4zMi0xMy4yOC0xLjE0LTE5LjkyLTEuNTctNC41NS0uMy05LjExLS4zNC0xMy42Ny0uNDUtMS40My0uMDMtMi45MS4xNi0zLjcxIDEuNTMtMS44NiAzLjE3LTMuNjcgNi4zOC01LjM4IDkuNjMtLjQ3Ljg5LjAyIDEuNzcgMS4wNSAxLjk5IDEuMDIuMjIgMi4wOC4yNyAzLjEzLjMzIDcuNy40MiAxNS40MS44NCAyMy4xMiAxLjIzIDEyLjU5LjYxIDI1LjIgMS4yMSAzNy44MSAxLjgyem0tNjAuMDggMS43OGMwLS4wNiAwLS4xMi4wMS0uMTctLjcgMC0xLjQtLjAxLTIuMTEgMC0yLjcuMDUtMy4zNC43NS0yLjQ2IDMuMDggMS4zIDMuNDUgMi43OSA2LjgyIDQuMiAxMC4yMi44NCAyLjAyIDIuMzYgMi45MSA0LjU2IDIuODQgMi40Ni0uMDggNC45MS0uMDQgNy4zNy0uMDggMTQuNzQtLjI4IDI5LjQ4LS42IDQ0LjIxLS44NiA4Ljg2LS4xNiA3LjI2IDEuMzYgMTEuODgtNy4zMiAyLjE4LTQuMSAxLjgxLTQuNTktMy00Ljc2LTExLjA1LS4zOC0yMi4xLS42NC0zMy4xNC0xLjE0LTEwLjUxLS40Ny0yMS4wMS0xLjE5LTMxLjUyLTEuODF6TTIyOS42MyA3OS43MmMuMSAxLjI2LjEyIDEuNzkuMTkgMi4zMSAxLjM5IDEwLjQyIDIuNzIgMjAuODUgNC4yMSAzMS4yNS43IDQuODUgMS42MiA5LjY4IDIuNjIgMTQuNDguNTMgMi41NCAxLjQgMy4wOCAzLjY1IDIuNTUgNC4wOS0uOTcgOC4xNy0yLjAyIDEyLjIxLTMuMTcgMi4yOC0uNjUgMi41OC0xLjM3IDIuMTEtMy45Ni0uMTYtLjg2LS4zOS0xLjcxLS41Ni0yLjU3LTEuNzItOC43Ni0zLjUyLTE3LjUxLTUuMTEtMjYuMy0uOS01LTEuNDMtMTAuMDYtMi4xMy0xNS4wOS0uNjUtNC42OC0xLjc4LTUuNjUtNi4xNC00LjcyLTIuOTEuNjItNS43NCAxLjU3LTguNiAyLjQtMS43NS41Mi0yLjU4IDEuNzEtMi40NSAyLjgyem0tNi43OS0uOTVjLTQuNjggMS4xOS05LjEgMi4yNi0xMy40OSAzLjQ2LTEuMzUuMzctMS44NCAxLjU1LTEuNzEgMi45Mi4wOC44Ny4yNyAxLjczLjQxIDIuNiAxLjYyIDkuNTEgMy4yMyAxOS4wMiA0Ljg2IDI4LjUzIDEuMDEgNS44OCAxLjk5IDExLjc2IDMuMDYgMTcuNjMuNDkgMi42NyAxLjQyIDMuMjMgNC4wNSAyLjQ5IDMuMDQtLjg2IDYuMDYtMS43OCA5LjA0LTIuODIgMi43LS45NCAzLjA2LTEuNjMgMi43NC00LjI3LS4wNi0uNTItLjIyLTEuMDMtLjMxLTEuNTUtMi40OS0xNC4xNy00LjUyLTI4LjQxLTUuODYtNDIuNzQtLjEzLTEuMzktLjMyLTIuODMtLjgxLTQuMTItLjI5LS43OS0xLjIyLTEuMzUtMS45OC0yLjEzem0yMjYuNTggNDE4LjI2YzguNzkgMCAxNS40OC4wMSAyMi4xNy0uMDEgMS4wNSAwIDIuMi4xNiAzLjEzLS4yLjg3LS4zMyAyLjAyLTEuMTUgMi4xOC0xLjkzLjkxLTQuNDcgMS42My04Ljk4IDIuMjUtMTMuNTEuMjItMS42MS0uNzgtMi42NC0yLjQ1LTIuODMtLjctLjA4LTEuNC0uMTUtMi4xLS4xNC0xMS43OC4yOC0yMy41Ni41OC0zNS4zNC44Ni0xLjgyLjA0LTMuNDcuNDktNC43OSAxLjgzLTIuNTkgMi42My01LjMyIDUuMTUtNy43MyA3Ljk0LTEuMTggMS4zNy0yLjk0IDIuODUtMi4yIDUuMDUuODggMi42MiAzLjM2IDIuMjQgNS4zOCAyLjMzIDcuMTkuMzIgMTQuNC40NyAxOS41LjYxek0zMDAuMTcgMzYyLjNjLTEuNDYtLjA2LTEuNy4xLTIuMzkgMi4zNy0uMi42Ny0uMzYgMS4zNi0uNDkgMi4wNS0yLjQzIDEyLjk0LTQuODggMjUuODgtNy4yNSAzOC44My0uODIgNC40OS0xLjQxIDkuMDItMi4xNSAxMy41Mi0uMzggMi4zMi4xIDQuMDMgMi40NiA1LjE2IDIuMDQuOTggMy44NiAyLjQxIDUuOCAzLjYyIDEuNTYuOTcgMi4yOS43MiAyLjU4LTEuMDIuNTgtMy40Ni45NS02Ljk2IDEuNi0xMC40MSAyLjk3LTE1LjcyIDYuOTctMzEuMTggMTIuMTctNDYuMzEuNDYtMS4zMy45NC0yLjY1IDEuMjgtNC4wMS40LTEuNi0uMTEtMi40NC0xLjY1LTIuNjItMy42NS0uNDItNy4zMi0uNzMtMTEuOTYtMS4xOHpNMjYxLjE5IDY5Ljg3Yy0xLjU1LjQyLTQuNDYgMS4wNi03LjI2IDIuMDMtMi45OSAxLjAzLTMuMzcgMS42Ni0yLjg5IDQuNjUgMS44MyAxMS4yNiAzLjY4IDIyLjUxIDUuNjggMzMuNzQuNzQgNC4xNCAxLjgzIDguMjIgMi45MyAxMi4yOS41OCAyLjEzIDEuMjggMi41MSAzLjQ5IDIuMTMgMy4xMS0uNTMgNi4yMi0xLjE0IDkuMjgtMS44OSAyLjIyLS41NCAyLjU3LTEuMiAyLjIyLTMuMzktLjE5LTEuMjEtLjU1LTIuMzktLjgxLTMuNTktLjkxLTQuMjktMi04LjU1LTIuNjctMTIuODgtMS41LTkuNzEtMi43Ny0xOS40Ni00LjE1LTI5LjE5LS40NS0zLjIxLTEuMjYtMy45MS01LjgyLTMuOXptMTguNDgtMy4xOGMtLjQxLjA1LTIuMDEuMDgtMy41NC40My0zLjY3Ljg1LTQuNTEgMi4wNS00LjQxIDUuOTUuMDMgMS4wNS4wNiAyLjEyLjIyIDMuMTUgMS40NSA5LjU1IDIuODEgMTkuMTIgNC40NCAyOC42My43NyA0LjQ5IDIgOC45MSAzLjE3IDEzLjMyLjU5IDIuMjEgMS4zMiAyLjY2IDMuNDEgMi4zMyAyLjk1LS40NyA1Ljg5LTEuMDMgOC43OC0xLjc0IDIuMjMtLjU1IDIuOC0xLjY0IDIuNDItMy45MS0uMTItLjY5LS4zNy0xLjM2LS41NC0yLjA0LTIuNS0xMC4wNy00LjU1LTIwLjIyLTUuODUtMzAuNTEtLjUzLTQuMTgtMS4xMi04LjM2LTEuNzQtMTIuNTItLjQ4LTMuMTctLjc0LTMuMzMtNC4xNC0zLjIyLS4zNS4wMS0uNy4wNC0yLjIyLjEzem02LjQzIDQ2NC4xMWMwIC4yMi4wMS40NC4wMS42Ni01Ljk4IDAtMTEuOTYtLjExLTE3Ljk0LjAzLTUuMDkuMTItMTAuMTguNTEtMTUuMjcuODUtLjYxLjA0LTEuMTkuNTItMS43OC43OS4yOS42MS40NSAxLjMzLjg5IDEuOCAyLjAyIDIuMjEgNC4xOSA0LjI4IDYuMTUgNi41MiAyLjg5IDMuMzEgNi4zNiA0LjUyIDEwLjc4IDQuMTMgMTQuNzEtMS4yOCAyOS40NC0yLjI4IDQ0LjE2LTMuNDcgNC4wMy0uMzIgNC40LTEuMTIgMi44My01LjA4LS4zMi0uODItLjc4LTEuNTktMS4wMS0yLjQzLTEuMDQtMy43Mi0zLjU2LTQuNzItNy4yLTQuNTMtNy4yLjQtMTQuNDEuNTEtMjEuNjIuNzN6bTE4Mi4zLTE1OS40M2MwLS4wNi4wMS0uMTMuMDEtLjE5IDYuNDguMjkgMTIuOTcuNTkgMTkuNDUuODggMy4yNi4xNCAzLjU2LS4wOCA0LjMzLTMuMTcuNjctMi43MSAxLjA5LTUuNDIgMi41Ny03Ljk1IDIuNTUtNC4zNCAyLjA1LTUuMDUtMi45NC00Ljk3LTE1LjQ0LjI1LTMwLjg3LjU2LTQ2LjMxLjg1LS44OC4wMi0xLjc2LjA2LTIuNjMuMTctMi42Mi4zMy0zLjE1IDEuMDUtMi4zIDMuNC43OCAyLjE0IDEuNzkgNC4xOSAyLjYgNi4zMy45MyAyLjQ3IDIuNjggMy40MyA1LjI2IDMuNTUgNi42Ni4yOSAxMy4zMS43MiAxOS45NiAxLjF6TTI5Ni43OCA2NC42N2MtNS4yMS0uMi02LjMyLjkyLTUuOTIgNS45Ny4wMy4zNS4wNS43LjEgMS4wNSAxLjg0IDEzLjQgNC4wMyAyNi43MyA3LjM4IDM5Ljg1LjMgMS4xOS41MiAyLjQ3IDEuMTUgMy40OC40OS43OCAxLjYxIDEuNzggMi4zNSAxLjcgMy42NC0uMzkgNy4yNy0xLjAzIDEwLjg3LTEuNzYgMS40Mi0uMjkgMS44Mi0xLjU1IDEuNTctMi45MS0uMTYtLjg2LS40MS0xLjctLjY1LTIuNTUtMy43OS0xMy41Mi03LjU3LTI3LjA1LTExLjM4LTQwLjU2LTEuMTEtMy44Ny0xLjUtNC4xMS01LjQ3LTQuMjd6TTQ1MS40IDUwMS45MXYtLjIzYy0xLjA1IDAtMi4xMS4wMy0zLjE2IDAtNi4xNC0uMTgtMTIuMjctLjQtMTguNDEtLjU1LTItLjA1LTIuNjMuNjktMi4wNiAyLjU3Ljk2IDMuMTkgMi4xNyA2LjMgMy4xIDkuNS44IDIuNzUgMi40IDMuOTYgNS4zMyAzLjk5IDExLjU4LjExIDIzLjE2LjM2IDM0LjczLjU3IDEuNjIuMDMgMi43My0uNjEgMy0yLjI4LjU4LTMuNjQgMS4xNy03LjI3IDEuNjUtMTAuOTIuMTktMS40NC0uNjUtMi4zNi0yLjA4LTIuNTItMS4yMi0uMTQtMi40NS0uMTItMy42OC0uMTMtNi4xNC0uMDEtMTIuMjggMC0xOC40MiAwek0zMTQuOTcgNjMuOTdjLTEuNTMgMC0yLjQxLjAxLTMuMjkgMC00Ljg4LS4wNy01LjU3Ljc5LTQuNDQgNS41My4yNCAxLjAyLjQ5IDIuMDUuNzggMy4wNiAzLjIzIDExLjEyIDYuNDYgMjIuMjMgOS42OSAzMy4zNC41NCAxLjg1IDEuMDggMy43IDEuNjQgNS41NS40NyAxLjU2IDEuNTcgMi4zMyAzLjE1IDIuMjIgMS45Mi0uMTMgMy44My0uNDEgNS43NC0uNjEgMy4xOS0uMzMgMy43Ni0uNzEgMi45OC00LjUzLS40Mi0yLjA2LTEuMDUtNC4wNy0xLjU2LTYuMTItMi43Mi0xMC44OS01LjU0LTIxLjc1LTguMS0zMi42Ny0xLjM4LTUuOTYtMS4xOC02LjAxLTYuNTktNS43N3ptMTU4LjYgMzI3LjMzYzQuMzkgMCA4Ljc4LjA4IDEzLjE3LS4wMyAzLjAyLS4wOCAzLjcxLS43OSA0LjA3LTMuNjcuMjktMi4yNi41Mi00LjU0LjY5LTYuODEuMTktMi42My0uNTItMy40OS0zLjE5LTMuNjktNC43My0uMzQtOS40Ni0uNTgtMTQuMTktLjg1LTUuNDMtLjMxLTEwLjg3LS42Mi0xNi4zLS44OS0yLjYzLS4xMy01LjI2LS4yNi03LjktLjI1LTEuNzEgMC0yLjQ3Ljg4LTEuOTEgMi4yOCAxLjUgMy43NSAzLjA0IDcuNDggNC44NCAxMS4wOS40Ni45MiAxLjgxIDEuNjUgMi45IDEuOTUgMS44NC41MSAzLjguNzggNS43MS44MyA0LjAyLjEzIDguMDcuMDUgMTIuMTEuMDR6bS0yMC4yOSAxMzAuODV2LS4yNGMtNS4yNyAwLTEwLjU0LS4wNy0xNS44MS4wMy0yLjQ5LjA0LTMuMTQuOTgtMi40IDMuMzggMS4wMyAzLjM2IDIuMjggNi42NSAzLjM0IDkuOTkuNjggMi4xNSAyLjEyIDMuMDkgNC4zIDMuMTcgNC4zOS4xNiA4Ljc4LjUyIDEzLjE2LjUxIDMuODYtLjAyIDcuNzEtLjQ1IDExLjU3LS42NyAyLjA4LS4xMiAzLjItMS4xOCAzLjUtMy4yMi40Mi0yLjc4Ljk1LTUuNTQgMS4zMS04LjMyLjQxLTMuMTMtLjExLTMuODMtMy4xOS0zLjk5LTUuMjUtLjI5LTEwLjUxLS40NC0xNS43OC0uNjR6bS04My40OCAxLjEydi0uOTJjNS45NiAwIDExLjkyLjA0IDE3Ljg4LS4wMyAxLjM3LS4wMSAzLjI3LjEyIDQuMDItLjY3IDIuNjEtMi43NyA0Ljg1LTUuOSA3LjEzLTguOTcuMTMtLjE3LS41MS0xLjI2LS45OC0xLjQ0LS43OC0uMy0xLjcxLS4yNy0yLjU5LS4zLTcuMDEtLjIxLTE0LjAyLS4zOS0yMS4wMi0uNTgtMy41MS0uMS03LjAxLS4xNi0xMC41Mi0uMjktMi0uMDgtMy44Ni4yMS01LjM0IDEuNy0yLjk3IDIuOTgtNS45NCA1Ljk2LTguODQgOS0uMzkuNDItLjY5IDEuNDEtLjQ2IDEuODEuMjUuNDMgMS4xNy42NiAxLjc5LjY3IDYuMzEuMDQgMTIuNjIuMDIgMTguOTMuMDJ6bTY0Ljc0IDE3LjI4Yy0uNTQtMS45Mi0uODctMy4yOS0xLjMxLTQuNjMtNC4xMS0xMi41My04LjIzLTI1LjA2LTEyLjM3LTM3LjU4LS4zMy0xLS43NC0xLjk4LTEuMTktMi45My0uNTYtMS4xOC0xLjQzLTIuMDItMi44Mi0yLjA2LTEuNC0uMDQtMi44Mi0uMTMtNC4yMSAwLTEuOTYuMTktMi42NiAxLjI5LTIuMTggMy4zNC4zMiAxLjM3Ljc5IDIuNyAxLjI1IDQuMDMgNC43IDEzLjgyIDkuNDkgMjcuNjEgMTQuMDggNDEuNDcgMi4wOSA2LjMxIDIuOTggNC45OSA2LjcgMS4zOC45My0uOTEgMS41MS0yLjIgMi4wNS0zLjAyem0yNi43Ny02Ni44OWM0LjIyIDAgOS40OS4wNiAxNC43NS0uMDIgMy43Ny0uMDYgNC41Ni0uOCA1LjA2LTQuNTQuMzItMi40My41LTQuODkuNjUtNy4zNC4xNS0yLjUzLS40LTMuMzMtMi44Ni0zLjMzLTYuMTQgMC0xMi4yOC4xNy0xOC40Mi4zNS0yLjM3LjA3LTQuMzYgMS4wNi02LjA1IDIuNzktMy40NCAzLjUxLTcgNi45LTEwLjQ0IDEwLjQxLS41Mi41My0uNjUgMS40NC0uOTcgMi4xOC42Ni4xNCAxLjMzLjQzIDEuOTkuNCA1LjA4LS4yNSAxMC4xNi0uNTYgMTYuMjktLjl6bS00MS4zNS0zNzcuOGguMTVjMC0zLjUxLS4wNC03LjAzLjAxLTEwLjU0LjEyLTguMzQuODctNy42OC03Ljk4LTkuODctNS4wMi0xLjI0LTUuNjUtLjc1LTUuNDYgNC4zLjM0IDguOTUuODUgMTcuODkgMS4zIDI2LjgzLjMxIDYuMTMuOSA2LjcxIDcuMTMgNy4xMS4zNS4wMi43LjA1IDEuMDUuMDUgMy4wMS4wMyAzLjc2LS42NSAzLjc5LTMuNjUuMDMtNC43Ni4wMS05LjUuMDEtMTQuMjN6bS0zOS4yMSA0MDkuNTh2LjRjNC4zOCAwIDguNzYuMDMgMTMuMTQtLjAyIDEuMzktLjAxIDIuODEtLjEyIDQuMTctLjM5IDEuMTktLjI0IDEuODEtMS4xNyAxLjM2LTIuMzctLjg3LTIuMjktMS44Ny00LjUzLTIuODQtNi43OC0uOTQtMi4xNy0yLjU2LTMuMS00Ljk3LTMuMTEtOS4yOC0uMDEtMTguNTQtLjU4LTI3LjgxLjgyLTQuODMuNzMtNS40MS42NC0zLjkzIDUuODUuMTQuNTEuMzYuOTkuNDYgMS41MS41NyAyLjgzIDIuMjUgNC4xNSA1LjE4IDQuMTEgNS4wOS0uMDcgMTAuMTctLjAyIDE1LjI0LS4wMnptNDQuMTgtNDA5LjEyaC0uMjVjMCAzLjM0LS4wMSA2LjY4IDAgMTAuMDIuMDEgMS41OC0uMDMgMy4xNy4wOSA0Ljc0LjIyIDMgLjcxIDMuNjUgMy42MSA0LjMzIDEuNy40IDMuNDcuNTcgNS4yMS42OSAyLjYyLjE4IDMuMDktLjI2IDMuMjMtMi44Ni4wOS0xLjc1IDAtMy41Mi4wNy01LjI3LjI5LTcuMDIuNTctMTQuMDUuOTItMjEuMDYuMjMtNC42OS0uMTQtNS4yNS00LjktNi40MS0xLjUzLS4zNy0zLjA3LS43OC00LjYzLTEuMDEtMi42NC0uNC0zLjI4LjA1LTMuMzMgMi42MS0uMDkgNC43My0uMDIgOS40Ny0uMDIgMTQuMjJ6bTUwLjMzIDMxMy4zM3YuNjRjMy4zNCAwIDYuNjguMDkgMTAuMDItLjAzIDIuNDUtLjA5IDMuMS0uNjkgMy41Mi0zLjA2LjQzLTIuNDIuNzctNC44Ny44Ny03LjMyLjExLTIuNzEtLjY0LTMuNTItMy4zNC0zLjYtOS40OC0uMjktMTguOTctLjQ4LTI4LjQ2LS42OC0xLjE2LS4wMi0xLjU4Ljg0LTEuMTggMS43NyAxLjMxIDMuMDcgMi43OCA2LjA3IDQuMTIgOS4xMi45NyAyLjIxIDIuNjQgMy4xNyA0Ljk4IDMuMTYgMy4xNS0uMDEgNi4zMSAwIDkuNDcgMHptLTcyLjQ0LTMwMi4zN2MtLjMtOS4yNy0uNTgtMTguNTQtLjktMjcuODEtLjA0LTEuMjItLjI2LTIuNDQtLjQxLTMuNjUtLjIzLTEuOTQtMS4yMy0zLjItMy4xOS0zLjY4LTIuODktLjcxLTUuNzUtMS41Mi04LjY1LTIuMjEtMS43Ni0uNDItMi4yOS4wMy0yLjExIDEuODMuNDggNC44OC45NSA5Ljc2IDEuNjYgMTQuNjEuMTkgMS4yOC45OCAyLjY2IDEuODkgMy42MiA0Ljc1IDUuMDUgNy4xMyAxMS4xMyA4LjI4IDE3Ljg0LjI3IDEuNTgtLjM3IDMuNzkgMi4wNyA0LjI2IDEuMzEuMjQgMS43Ni0xLjEyIDEuMzYtNC44MXptMjQuMzYgODEuODN2LjI4Yy0xLjkyIDAtMy44NS0uMDktNS43Ny4wMi0zLjM4LjItNi40MSAxLjQ0LTkuMzQgMy4xNy0zLjcyIDIuMi00LjY3IDUuNDEtNC4xNSA5LjM3LjE4IDEuMzYgMSAxLjc0IDIuMjQgMS4zMy41LS4xNi45Ny0uNCAxLjQ2LS41OSA1LjgtMi4yNyAxMS43OS0zLjE4IDE4LjAxLTIuNSAzLjEzLjM0IDYuMjcuNjkgOS40MS44Ljg4LjAzIDIuMTUtLjUgMi42My0xLjIgMS4zMy0xLjk0LjY1LTUuNjctMS4xMS03LjIxLTIuMDMtMS43OC00LjQyLTIuODUtNy4xLTMuMTItMi4wOC0uMjEtNC4xOC0uMjQtNi4yOC0uMzV6bTQ4LjMgMjI2LjAxdi0uMjVjLTIuNjEtLjE3LTUuMjItLjQtNy44NC0uNDctMS42LS4wNS0yLjEzLjk0LTEuNDYgMi40My4wNy4xNi4xMy4zMy4yMy40NyAzLjQ0IDQuNDYgNi4yOCA5LjE1IDYuOCAxNC45Ny4xNCAxLjYgMS41IDIuMzUgMy4wNyAyLjIgMi4yNi0uMjEgNC41LS41NSA2Ljc1LS44NyAxLjM1LS4xOSAyLjI5LS45OCAyLjUxLTIuMzEuNzItNC4zIDEuNS04LjYgMS45OC0xMi45Mi4yNS0yLjIzLS42OC0zLjA4LTMuMTQtMy4yMS0yLjk2LS4xNi01LjkzLS4wNC04LjktLjA0ek0zMzAuMjQgODUuM2MuMzkgMS42Ny43NCAzLjM1IDEuMTggNSAxLjMxIDQuOSAyLjY1IDkuNzkgMy45OCAxNC42OSAxLjgyIDYuNzEuNzUgNy4yNiA5LjY3IDYuNzguNTItLjAzIDEuMDUtLjA5IDEuNTctLjE0IDEuNTItLjE1IDIuMDktMS4wMiAxLjk2LTIuNS0uNDMtNC44Ny0uODYtOS43NC0xLjIzLTE0LjYxLS4xMy0xLjc4LS44My0zLjAxLTIuNjYtMy4zOS01LjAzLTEuMDUtOS41NS0zLjI1LTE0LjQ3LTUuODN6bTk4LjYxIDEzMC4yNmMzLjE5LS41NyA2Ljg1LS45NyAxMC4zNS0xLjkzIDIuNjEtLjcyIDMuNDQtMi45NCAyLjcyLTUuNDItLjYtMi4wNi0yLjMzLTIuNjMtNC4wOS0yLjctNS41OS0uMjQtMTEuMTktLjQ5LTE2Ljc4LS4zNi0zLjcyLjA5LTcuMTUgMS42Mi0xMC4zNyAzLjQ3LS42LjM0LTEuMTkuNzUtMS42NiAxLjI2LS43Mi43OS0uNjIgMS43My4yOSAyLjIxIDEuMDYuNTYgMi4yNCAxLjAyIDMuNDIgMS4yMSA1LjE5Ljc4IDEwLjM5IDEuNDYgMTYuMTIgMi4yNnptLTI5LjUzIDY5LjMzYzMuNDUgMCA3LjEyLjAyIDEwLjgtLjAxIDEuMDQtLjAxIDIuMTItLjA0IDMuMTItLjMgMS42Ny0uNDQgMi4zOC0yLjI0IDEuNTctMy43OC0xLjI0LTIuMzgtMy4yOS0zLjYxLTUuOTItMy43NC0yLjgtLjEzLTUuNi0uMTYtOC40LS4xLTYuMDQuMTMtMTEuNzcgMS42LTE3LjE5IDQuMjQtMy45MyAxLjkyLTcuODIgMy45My0xMS42OSA1Ljk2LTEuNjYuODctMy4yMiAxLjkxLTQuODMgMi44NyAyLjEzIDEuMzEgMy44Mi0uNCA1LjctLjgzLjM0LS4wOC42Ny0uMjEuOTktLjM0IDguMi0zLjU0IDE2LjY4LTUuMjQgMjUuODUtMy45N3ptNTAuNDUtMTgzLjc5aC4yMlY4Ny40NmMwLTMuNDItMi4zMi00LjI4LTUuMzEtNC4zMy0uNTQtLjAxLTEuMzIuNTgtMS41OSAxLjEtLjM4Ljc0LS41MSAxLjY4LS41NCAyLjU0LS4yOCA4LjkxLS42IDE3LjgzLS43MiAyNi43NC0uMDUgMy4zNCAxLjY0IDQuNTkgNS4wMSA0LjM4IDEuNDItLjA5IDIuMzUtLjc4IDIuNjEtMi4xMi4yNC0xLjE5LjMtMi40My4zMS0zLjY1LjAzLTMuNjcuMDEtNy4zNC4wMS0xMS4wMnpNMjczLjgyIDU1MC4xN2MtLjA0LjI5LS4wNy41OC0uMTEuODcuOTguMyAxLjkzLjcxIDIuOTMuODggMi40Mi40MSA0Ljg1Ljg4IDcuMjkgMS4wMyA2LjMxLjM5IDEyLjY0LjcyIDE4Ljk2Ljg3IDUuMjcuMTMgMTAuNTUuMDggMTUuODItLjA4IDIuNS0uMDggMy43My0xLjA2IDMuNjQtMi4yNS0uMTUtMS44LTEuNzctMy45MS0zLjQyLTQuMjQtMS4wMi0uMjEtMi4xLS4yNC0zLjE1LS4yMi0zLjE2LjA2LTYuMzMuMDktOS40OC4zMi05LjI5LjY2LTE4LjU3IDEuNC0yNy44NSAyLjE1LTEuNTUuMTItMy4wOS40NC00LjYzLjY3em0xOTguNjUtOTYuNTh2LS4wN2MyLjQ2IDAgNC45MS4wNSA3LjM3LS4wMiAxLjU4LS4wNCAyLjczLS45NCAzLjAxLTIuNDcuNTgtMy4yOCAxLjA4LTYuNTcgMS40MS05Ljg4LjE3LTEuNjktLjc4LTIuNTgtMi41OS0yLjUzLTEuOTIuMDUtMy44My40NC01Ljc1LjU2LTIuMTMuMTQtMy40NCAxLjE5LTQuNTUgMy4wMi0xLjcyIDIuODQtMy43OSA1LjQ2LTUuNzIgOC4xNy0uNDEuNTctMS4wNyAxLjA1LTEuMjMgMS42Ny0uMTEuNDMuNDIgMS40Ny43IDEuNDggMi40NC4xMiA0LjkuMDcgNy4zNS4wN3ptLTI5LjEzLTI2Ny43MmMtLjczLS44MS0xLjM3LTEuODYtMi4zLTIuNDgtOC43My01LjgtMTcuODQtNS43NS0yNy4wMy0xLjMxLTEuNjIuNzgtMi43OSAyLjU0LTQuMDcgMy45My0uMjguMy0uMTYuOTctLjIyIDEuNDcuNDkuMDQgMS4wNS4yNyAxLjQ1LjExIDkuOTktNC4yNyAyMC4wNi00LjUyIDMwLjI0LS43MS4zMS4xMi43My4xNiAxLjAyLjA0LjI2LS4xMi40My0uNDkuOTEtMS4wNXptMTAuNDYgMzU3Ljg5Yy00LjItLjI2LTcuMzctLjQtMTAuNTItLjY4LTMuNDQtLjMtNS41OCAxLjctNy40NiA0LjEyLS4xOS4yNS4yMSAxLjMzLjYgMS41Ny41NC4zMyAxLjM1LjMyIDIuMDUuMzEgNi42OC0uMTUgMTMuMzYtLjM1IDIwLjA0LS40OSAyLjUtLjA1IDQuMjgtMS40NCA1LjkyLTMuMDcuMjgtLjI4LjMzLS45LjMtMS4zNS0uMDEtLjE2LS41OS0uMzktLjkxLS4zOS0zLjY4LS4wMy03LjM3LS4wMi0xMC4wMi0uMDJ6bS00Ny4wNy0yNy44OXYtLjA0Yy4zNSAwIC43LjAyIDEuMDUgMCAyLjkzLS4xOCAzLjUtLjkgMi42Ny0zLjU3LTEuNjYtNS4zNS0zLjM3LTEwLjY5LTUuMS0xNi4wMi0uMjYtLjgxLS42LTEuNzctMS4yMi0yLjIzLS41OC0uNDMtMS42My0uNDYtMi4zOC0uMjgtLjMxLjA4LS42NSAxLjI0LS41IDEuNzguMzYgMS4zNCAxLjAyIDIuNiAxLjUyIDMuOTEuODEgMi4xMiAxLjU5IDQuMjYgMi4zNyA2LjQgMS4wMiAyLjc2IDEuMzYgNS4zNS0uOSA3Ljg1LTEuMDkgMS4yMS0uMzggMi4xMiAxLjQ1IDIuMi4zNC4wMS42OSAwIDEuMDQgMHpNMzgzLjY5IDgxLjQxYy0uMjgtMy4yNC0uMzYtNS41My0uNzItNy43Ny0uMjQtMS41LS44OS0yLjkzLTEuMzYtNC4zOS0yLjE4IDEuMTUtLjcyIDMuMDgtLjk0IDQuNjMtLjMyIDIuMjUtLjQ1IDQuNTItLjU5IDYuNzktLjA4IDEuMzYgMS4xNiAyLjYyIDIuMjUgMi4xNy42MS0uMjUuOTMtMS4yMiAxLjM1LTEuODkuMDctLjEyLjAxLS4zNC4wMS40NnoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMzE3LjM4IDIwNy41OGMuODcuNCAxLjQ2LjgyIDIuMS45NiA2LjM3IDEuMyAxMi43MiAyLjY2IDE5LjEyIDMuNzkgOC43MiAxLjU0IDE3LjE1LjIyIDI1LjMyLTMuMDQgMS4zMS0uNTIgMi41Ny0xLjE2IDMuODgtMS42NiAxLjk0LS43NCAzLjI0LS41MSAzLjY1LjY4LjYxIDEuNzQtLjY4IDIuNTQtMS44NiAzLjMyLS41OC4zOC0xLjIyLjctMS44Ni45OS04LjgxIDQuMDQtMTguMDQgNS45NC0yNy43MSA0Ljc5LTUuNC0uNjQtMTAuNzUtMS43My0xNi4wOC0yLjgtNC4xMy0uODMtOC4yMS0xLjkzLTEyLjI5LTIuOTktMS4xNy0uMy0yLjM1LS43NS0zLjM3LTEuMzktMS4yNi0uNzktMS40Ni0yLjY1LS4zNi0zLjY3IDEtLjk0IDIuMjItMS43IDMuNDYtMi4zMSA5LjUzLTQuNzMgMTkuNTUtNy44NSAzMC4xNS05LjAzIDcuMTYtLjggMTQuMy0yIDIxLjUxLS41NCAxLjM4LjI4IDIuNDctLjA5IDIuOTYtMS40OS41OC0xLjY5IDAtMy40LTEuMzMtNC4xNC0uMzEtLjE3LS42My0uMzItLjk2LS40Mi0xMS41LTMuMzktMjIuOTMtNC45OC0zNC42LS4zMS05LjY0IDMuODYtMTkuMjkgNy42NS0yOC4yMiAxMy4wMS0yLjA4IDEuMjUtNC4xMyAyLjc3LTUuNzEgNC41OS0zLjA1IDMuNTEtMy4xMyA3LjAzLjA4IDEwLjQxIDMuMzcgMy41NSA3LjE1IDYuNzYgMTAuOTkgOS44MiAzLjI5IDIuNjIgNi44NyA0LjkgMTAuNCA3LjIgNC42NiAzLjAzIDcuNjQgNy4xOSA4Ljg4IDEyLjU5Ljc1IDMuMjUgMS41NSA2LjUgMi4xIDkuNzkgMS4zIDcuNjYgMy40NiAxNS4wNiA2LjA3IDIyLjM1IDEuODQgNS4xMiAzLjQ2IDEwLjMzIDUuMDUgMTUuNTQuNCAxLjMxLjUxIDIuNzguMzYgNC4xNC0uMDcuNjQtLjk4IDEuNDEtMS42OCAxLjY5LS40NS4xNy0xLjQ0LS4yOS0xLjc4LS43Ni0uNi0uODItLjk1LTEuODUtMS4yOC0yLjgzLTIuMTgtNi41LTQuNDctMTIuOTYtNi40My0xOS41Mi0xLjg1LTYuMjItMy4zNi0xMi41NS00Ljk2LTE4Ljg0LS40Ny0xLjg3LS44NC0zLjc4LTEuMDktNS43LS45NS03LjI3LTQuNDItMTIuNjItMTAuODctMTYuNDctNC45NS0yLjk1LTkuMzYtNi44Mi0xMy45MS0xMC40MS0xLjc5LTEuNDEtMy4zOC0zLjA4LTQuOTMtNC43NS02LjQ5LTctNC4yNi0xMy44MiAxLjExLTE5LjAzIDEuNzQtMS42OSAzLjkxLTMgNi4wMS00LjI1IDkuNzEtNS43NiAxOS45OS0xMC4zMyAzMC42Ny0xMy45NCA4Ljg2LTMgMTcuODctMy42IDI2Ljk4LS45NyAyLjE5LjYzIDQuNDUgMS4wMyA2LjY3IDEuNTYgNS4xMiAxLjIzIDEwLjMzIDIuMTUgMTUuMDYgNC42NyAxLjY0Ljg3IDIuNTIuMDkgMi42MS0xLjg0LjExLTIuMzctLjk2LTQuMDctMi44NS01LjI5LTYuMTItMy45Ni0xMi42Mi03LjAxLTIwLTcuNTktOC44Ni0uNjktMTcuNTQuMDYtMjUuOTMgMy4zMS02LjcyIDIuNi0xMy41NiA0Ljg5LTIwLjI1IDcuNTctMy40MSAxLjM3LTYuNiAzLjI4LTkuODggNC45Ny0xLjQuNzMtMi43NCAxLjYtNC4xNyAyLjI2LTEuODMuODQtMy4xOS41OS0zLjY4LS41OS0uNjktMS42NS4yMy0yLjc5IDEuNTEtMy41MSA0LjYtMi41NyA5LjA4LTUuNDQgMTMuOTItNy40NSA3Ljk1LTMuMyAxNi4xLTYuMTIgMjQuMjMtOC45NiA4LjgxLTMuMDggMTcuOTYtMy40MiAyNy4wOS0yLjE4IDcuNDMgMS4wMSAxNC4xMiA0LjI4IDIwLjIxIDguNTkgNC44NSAzLjQzIDYuMDcgMTAuNDQgMyAxNS41NS0xLjAzIDEuNzItMi41NiAyLjMzLTQuNTEgMS40OS0xLjkzLS44My0zLjctMi4wNC01LjYxLTIuOS0yLjI1LTEuMDEtMy4xNi0uMzgtMy41IDIuMTMtLjk4IDcuMzktLjk3IDcuMzUtOC40OCA1LjkyLTIuMDYtLjM5LTQuMTktLjgxLTYuMjYtLjcyLTExLjYyLjUzLTIzLjE0IDEuNzEtMzQuMDkgNi4wMi0xLjU2LjU4LTMuMzMuOS00LjY2IDIuNTl6TTIzMS43MyAyMDUuMTRjLS42OSA3LjE1LTMuMjUgMTQuOTItMTAuMTggMTkuOTQtNC43MyAzLjQzLTUuNTQgNi45NC0zLjk1IDEyLjA3IDEuMzUgNC4zNCAyLjE2IDguODYgMy4xNyAxMy4zLjM0IDEuNSAxLjAzIDMuMTctLjc4IDQuMjItMS4yNi43My0yLjUyLS4zOS0zLjUxLTMtLjMxLS44Mi0uNjEtMS42Ni0uNzktMi41MS0xLjI2LTYuMTktMi40NS0xMi4zOS0zLjc2LTE4LjU3LS41LTIuMzYuMDgtNC4xIDIuMTctNS4zOSAxLjE5LS43NCAyLjI1LTEuNjggMy4zOC0yLjUxIDUuMDgtMy43NCA3Ljc4LTguOTggOS4xNS0xNC45NSAyLjY0LTExLjUzLTguMDEtMTkuMDItMTcuNTktMTUuNDItNC4zNyAxLjY0LTguMTUgNC4xLTExLjIgNy42Mi0zLjAxIDMuNDgtNC4yNSA3LjU5LTQuMjYgMTIuMTUgMCAxLjQxLS4yMiAyLjg5LTEuOTUgMy4zMi0xLjI2LjMxLTIuMzctMS4xMi0yLjQxLTMuNDEtLjA5LTQuODcgMS4xOC05LjQyIDQuMDQtMTMuMzggNC4zOS02LjA5IDEwLjMzLTEwLjAxIDE3LjY4LTExLjUyIDEwLjYyLTIuMjIgMjAuNzggNi4xIDIwLjc5IDE4LjA0ek0yMTcuMzkgMjcwLjEzYy4wMS0zLjM4IDIuOTUtNi4wNCA2LjY4LTYuMDQgMy43MSAwIDYuNiAyLjczIDYuNTIgNi4xNi0uMDggMy40NC0yLjk2IDYuMzctNi4zOCA2LjUtMy41NC4xNC02LjgzLTMuMDUtNi44Mi02LjYyem04LjYyLjM4Yy0uNy0uNzYtMS4xNS0xLjYxLTEuNTMtMS41OS0uNy4wNS0xLjM3LjYzLTIuMDUuOTguNC42NC43MiAxLjcxIDEuMjIgMS44MS41OC4xMiAxLjMzLS42MyAyLjM2LTEuMnpNMzkyLjA4IDEyMS4yOWMtLjM3IDMuNjQtLjY0IDYuOTctMS4wNSAxMC4yOC0uMzQgMi43Mi0xLjg4IDQuNDctNC42MyA1LjA1LS42OS4xNC0xLjM2LjM2LTIuMDUuNTEtNi43NCAxLjQ4LTguMjMuODktMTEuMDUtNS40OC0zLjY5LTguMzUtNy4yLTE2Ljc5LTEwLjYxLTI1LjI2LTEuMDQtMi41OS0xLjYxLTUuMzgtMi4yMi04LjEyLS4zLTEuMzUtLjM2LTIuOC0uMjQtNC4xOS41Mi02LjAzIDQuOTktOS4xIDEwLjc0LTcuMjkgNS4xOCAxLjYzIDkuMzcgNC43NSAxMi41OCA5LjE0IDMuOTEgNS4zNSA2LjI2IDExLjM3IDcuNTIgMTcuODIuNDkgMi41OS43IDUuMjIgMS4wMSA3LjU0em0tNC43NiAxLjQyYy0uMjEtOC41Mi0yLjI5LTE1LjctNi4zMS0yMi4yOS0yLjE0LTMuNTEtNS4wMy02LjMyLTguOC04LjA5LS45NS0uNDQtMS45NC0uODctMi45Ni0xLjA3LTIuNDYtLjQ5LTQuMTMuNzUtNC4zNiAzLjI4LS4xMSAxLjItLjA2IDIuNDkuMjUgMy42NS44MSAzLjA1IDEuNjcgNi4xIDIuNzYgOS4wNiAxLjYzIDQuNDUgMy40MiA4Ljg1IDUuMjQgMTMuMjIgMS40MSAzLjQgMi45NyA2Ljc1IDQuNDggMTAuMTEuOTcgMi4xNyAyLjggMi40MiA0Ljg2IDIuMTIgMi41NS0uMzggMy41Mi0xLjEgMy45Ni0zLjQ3LjQ1LTIuNC42Ni00Ljg2Ljg4LTYuNTJ6TTM1Mi4wMiA4NC4zNGMtMS44Mi0uMzktNC45MS0uOTktNy45Ny0xLjczLTEuMDEtLjI0LTEuOTctLjc2LTIuODgtMS4yNy05LjMyLTUuMjgtOS43MS0xNi45NS0uNzctMjIuODYgMi41NS0xLjY5IDUuMzktMi41OSA4LjQzLTIuNjUgNi4xOS0uMTEgMTEuNzggMS41OCAxNi41IDUuNzQgNS4xNCA0LjUzIDUuOTIgMTAuMyAyLjExIDE2LjAyLTMuMzYgNS4wNS04LjM5IDYuNi0xNS40MiA2Ljc1em0tMS42NS0yNC4xNWMtMS43Mi4yOC0zLjUyLjMyLTUuMTQuOS0zLjczIDEuMzMtNi4yMSAzLjk3LTYuNzUgOC0uNDMgMy4yNCAyIDcuMDUgNS44NyA4LjUyIDIuOSAxLjExIDYuMDggMS45NSA5LjE2IDIuMDQgNC4xMy4xMiA3Ljk5LTEuMiAxMC4zMi01LjEzIDIuMDEtMy4zOSAxLjczLTYuMzItMS4xNC05LjAyLTMuNDMtMy4yMi03LjM5LTUuMzMtMTIuMzItNS4zMXoiLz48L3N2Zz4='
9+ base+'sphinx'
10+ glob-ames+[~donmec-sallud 0v0]
11+==
desk/gen/get.hoonnew file+9
@@ -0,0 +1,9 @@
1+/- s=sphinx
2+:- %say
3+|= $: [now=@da eny=@uv =beak]
4+ [[=hash:s ~] ~]
5+ ==
6+=/ our (scot %p p.beak)
7+=/ wen (scot %da now)
8+=/ state=state:s .^(state:s /gx/[our]/sphinx/[wen]/state/noun)
9+noun+(~(got by directory.state) hash)
desk/gen/metaphone.hoonnew file+6
@@ -0,0 +1,6 @@
1+/+ *metaphone
2+:- %say
3+|= $: [now=@da eny=@uv =beak]
4+ [[word=@t ~] ~]
5+ ==
6+noun+(utter word)
desk/gen/publish-all.hoonnew file+29
@@ -0,0 +1,29 @@
1+/- s=sphinx
2+/+ *sphinx
3+:- %say
4+|= $: [now=@da eny=@uv =beak]
5+ [~ ~]
6+ ==
7+:- %declarations
8+^- (list declare:s)
9+:~ :- %friends
10+ ['networked subject' %group 'web+urbitgraph://group/~matwet/networked-subject' 'https://subject.network | networked subject' ~['hosting' 'networking' 'urbit' 'ops'] 'https://urbits3.ams3.digitaloceanspaces.com/sitful-hatred/2022.7.07..17.15.52-ns.png' '']
11+ :- %friends
12+ ['Hooniverse' %group 'web+urbitgraph://group/~hiddev-dannut/new-hooniverse' 'Community based Hoon learning for all levels. For discussion of Hoon specific to Uqbar, join ~hiddev-dannut/uhoon' ~['hoon' 'dev' 'urbit' 'programming' 'education'] 'https://i.imgur.com/ghThlz7.png' '']
13+ :- %friends
14+ ['Hollow Mars Theory' %group 'web+urbitgraph://group/~rabsef-bicrym/hollow-mars-theory' 'Definitely NOT a Conspiracy Chat' ~['conspiracies' 'ufos' 'steel beams'] '' '']
15+ :- %friends
16+ ['celestial systems' %group 'web+urbitgraph://group/~nocsyx-lassul/celestial-systems' 'A place for pilots who are building hosting providers' ~['hosting' 'ops' 'urbit'] '' '']
17+ :- %friends
18+ ['Structure' %group 'web+urbitgraph://group/~fabled-faster/structure' 'Urbit Structural Design and Engineering Group. Always Thinking About Mechanics.' ~['design' 'ux' 'hci' 'urbit'] 'https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2022.1.27..17.59.43-image.png' '']
19+ :- %friends
20+ ['The Marketplace' %group 'web+urbitgraph://group/~tirrel/the-marketplace' 'Welcome to The Marketplace, featuring The Pit, Urbit\'s first open-outcry market!' ~['urbit' 'market' 'sell' 'buy'] 'https://snipboard.io/U0IYyi.jpg' '']
21+ :- %friends
22+ ['The Forge' %group 'web+urbitgraph://group/~middev/the-forge' 'pale fire computing' ~['dev' 'programming' 'hoon' 'urbit'] 'https://nyc3.digitaloceanspaces.com/archiv/littel-wolfur/2021.5.06..21.01.58-the%20forge.png' '']
23+ :- %friends
24+ ['UFORIA' %group 'web+urbitgraph://group/~tiplec-lacnyx/ufora' 'UFO Research, Investigations, and Analysis' ~['UFO' 'conspiracies' 'research'] 'https://tiplec-lacnyx.nyc3.digitaloceanspaces.com/tiplec-lacnyx/2021.12.27..06.09.47-change1.jpg' '']
25+ :- %friends
26+ ['Urbit Community' %group 'web+urbitgraph://group/~bitbet-bolbel/urbit-community' 'World hub, help desk, meet and greet, etc.' ~['general' 'urbit' 'community' 'help'] 'https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.4.02..21.52.41-UC.png' '']
27+ :- %friends
28+ ['Urbytes' %group 'web+urbitgraph://group/~nartes-fasrum/urbytes' 'We feed and water Mars.' ~['cooking' 'food' 'beverages' 'peanut butter eggs'] '' '']
29+==
desk/gen/publish.hoon+6−6
@@ -1,9 +1,9 @@
1−/- s=seek
2−/+ *seek
1+/- s=sphinx
2+/+ *sphinx
33:- %say
44|= $: [now=@da eny=@uv =beak]
5− [[=post:s source=@p ~] ~]
5+ [[=post:s ~] ~]
66 ==
7−:- %directory-listing
8−^- listing:s
9−[post (digest post) %friends source now]
7+:- %declare
8+^- declare:s
9+[%friends post]
desk/gen/ranks.hoonnew file+37
@@ -0,0 +1,37 @@
1+/- s=sphinx
2+/+ *sphinx, *sift
3+:- %say
4+|= $: [now=@da eny=@uv =beak]
5+ [[word=@t algo=?(%all %exact %phonetic %ngram) ~] ~]
6+ ==
7+=/ our (scot %p p.beak)
8+=/ wen (scot %da now)
9+=/ state=state:s .^(state:s /gx/[our]/sphinx/[wen]/state/noun)
10+=/ title (norm:sift word) :: full query
11+=/ parts (sift:sift word) :: split query
12+=/ index ~(. delver index.state)
13+=/ lookup
14+ ?- algo
15+ %all get-entries.index
16+ %exact get-exact.index
17+ %ngram get-trigrams.index
18+ %phonetic get-phonetics.index
19+ ==
20+=/ title-entries=(list entry:s)
21+ %+ turn
22+ (lookup title)
23+ (uprank 10)
24+=/ entries=(list entry:s)
25+ %- sum-ranks.index
26+ %- zing
27+ %+ turn
28+ parts
29+ |= word=@t
30+ (lookup word)
31+:- %noun
32+%+ turn
33+ ?: =(algo %all) (lookup word)
34+ (welp entries title-entries)
35+|= [=hash:s =rank:s]
36+=/ listing (~(got by directory.state) hash)
37+[rank title.post.listing hash]
desk/gen/state.hoonnew file+8
@@ -0,0 +1,8 @@
1+/- s=sphinx
2+:- %say
3+|= $: [now=@da eny=@uv =beak]
4+ [~ ~]
5+ ==
6+=/ our (scot %p p.beak)
7+=/ wen (scot %da now)
8+noun+.^(state:s /gx/[our]/sphinx/[wen]/state/noun)
desk/gen/validate.hoonnew file+6
@@ -0,0 +1,6 @@
1+/+ *validate-url
2+:- %say
3+|= $: [now=@da eny=@uv =beak]
4+ [[url=@t ~] ~]
5+ ==
6+noun+(validate url)
desk/lib/gossip.hoon+283−58
@@ -1,14 +1,10 @@
1−:: gossip: data sharing with pals
1+:: gossip: data sharing with pals (and pals of pals, etc)
2+::
3+:: v1.1.1: sneaky whisper
24::
35:: automates using /app/pals for peer & content discovery,
46:: letting the underlying agent focus on handling the data.
57::
6−:: at the time of writing, this library has not seen much real-world
7−:: use yet. it may be subject to behavioral or interface changes.
8−:: it tries its best to not generate obscene amounts of network load,
9−:: but has not yet proven itself in the real world.
10−:: for the time being, please tread carefully.
11−::
128:: usage
139::
1410:: to use this library, call its +agent arm with an initial
@@ -44,6 +40,10 @@
4440:: setting hops to 1 distributes the data to direct pals only.
4541:: setting hops to 0 prevents emission of any gossip data at all,
4642:: even during initial subscription setup.
43+:: setting pass to true causes 50% of locally-originating data to be
44+:: proxied through a random "tell" peer. note that the proxy will have
45+:: a 50% chance of proxying in turn. for low amounts of hops, data could
46+:: escape the local social graph entirely.
4747::
4848:: (we introduce the /~/etc path prefix convention to indicate paths
4949:: that are for library-specific use only.
@@ -75,6 +75,12 @@
7575:: we may want to use pokes exclusively, instead of watches/facts,
7676:: making it easier to exclude the src.bowl, include metadata, etc.
7777::
78+:: we currently only emit every rumor as a fact once at most. but we might
79+:: receive it again later, with a higher hop count. if we want to try for
80+:: maximum reach within the given hops, we should re-send if we receive a
81+:: known rumor with higher hop counter. but this may not be worth the added
82+:: complexity...
83+::
7884:: what if this was a userspace-infrastructure app instead of a wrapper?
7985:: how would ensuring installation of this app-dependency work?
8086:: it gains us... a higher chance of peers having this installed.
@@ -93,6 +99,7 @@
9399:: - if pals ever watch-nacks (it shouldn't), we try rewatching after ~m1.
94100::
95101:: - for facts produced on /~/gossip/source, we
102+:: - 50% chance to redirect into the "pass flow" (see below)
96103:: - wrap them as %gossip-rumor to send them out on /~/gossip/gossip
97104:: - for new pals matching the hear mode, we watch their /~/gossip/gossip
98105:: - for gone pals, we leave that watch
@@ -102,6 +109,16 @@
102109:: - re-emit them as facts on /~/gossip/gossip if there are hops left
103110:: - for nacks on those watches, we retry after ~m30
104111::
112+:: - other gossip instances may poke us with rumors directly
113+:: - the inner agent learns the rumor normally, as if it came from a fact
114+:: - based on randomness (50/50), we do one of two things:
115+:: - publish the rumor as a fact to our subscribers, like the above
116+:: - poke a randomly selected peer (in the "tell" set) with the rumor
117+:: - when poking, store the rumor until we receive a poke-ack
118+:: - upon receiving a positive poke-ack, delete the local datum
119+:: - upon receiving a negative poke-ack, do the 50/50 again
120+:: - if we do not receive a poke-ack within some time, do the 50/50 again
121+::
105122/- pals
106123/+ lp=pals, dbug, verb
107124::
@@ -112,6 +129,8 @@
112129 ==
113130+$ meta-0 hops=_0
114131::
132++$ hash @uv
133+::
115134+$ whos
116135 $? %anybody :: any ship discoverable through pals
117136 %targets :: any ship we've added as a pal
@@ -123,7 +142,11 @@
123142 :: (1 hop is pals only, 0 stops exposing data at all)
124143 hear=whos :: who to subscribe to
125144 tell=whos :: who to allow subscriptions from
145+ pass=? :: whether to (50/50) emit data through proxy
126146 ==
147+::
148+++ pass-timeout ~s30
149+::
127150++ invent
128151 |= =cage
129152 ^- card:agent:gall
@@ -146,23 +169,27 @@
146169 ^- $-(agent:gall agent:gall)
147170 |^ agent
148171 ::
149− +$ state-0
150− $: %0
151− manner=config :: latest config
152− memory=(set @uv) :: datums seen
153− future=(list rumor) :: rumors of unknown kinds
172+ +$ state-3
173+ $: %3
174+ manner=config :: latest config
175+ memory=(set hash) :: datums seen (by inner agent)
176+ shared=(set hash) :: datums shared (as fact)
177+ passed=(map hash [rumor @da]) :: pending relays & timeouts
178+ misses=(set ship) :: ships that won't relay
179+ future=(list rumor) :: rumors of unknown kinds
154180 ==
155181 ::
156182 +$ card card:agent:gall
157183 ::
158184 ++ helper
159− |_ [=bowl:gall state-0]
185+ |_ [=bowl:gall state-3]
160186 +* state +<+
161187 pals ~(. lp bowl)
162188 ++ en-cage
163189 |= =(cask *)
164190 ^- cage
165191 ?^ to=(~(get by grab) p.cask)
192+ ::TODO +soft or otherwise virtualize? don't want to risk crashes, right?
166193 [p.cask (u.to q.cask)]
167194 ~& [gossip+dap.bowl %no-mark p.cask]
168195 [%gossip-unknown !>(cask)]
@@ -177,6 +204,10 @@
177204 ~| [%en-rumor initial-hops=hops.manner]
178205 [%0 `meta-0`(dec hops.manner)]
179206 ::
207+ ++ en-hash
208+ |= rumor
209+ (sham data)
210+ ::
180211 ++ play-card :: en-rumor relevant facts, handle config changes
181212 |= =card
182213 ^- (quip ^card _state)
@@ -211,11 +242,68 @@
211242 ?> =(/~/gossip/source path)
212243 :: if hops is configured at 0, we don't broadcast at all.
213244 ::
214− =. memory (~(put in memory) (sham (de-cage cage.p.card)))
215− ?: =(0 hops.manner) [caz state]
216− =- [[- caz] state]
217245 =/ =rumor (en-rumor cage.p.card)
218− card(paths.p [/~/gossip/gossip]~, cage.p [%gossip-rumor !>(rumor)])
246+ =. memory (~(put in memory) (en-hash rumor))
247+ ?: =(0 hops.manner)
248+ [caz state]
249+ =^ cas state (emit-rumor rumor)
250+ [(weld cas caz) state]
251+ ::
252+ ++ emit-rumor :: gossip a rumor as-is
253+ |= =rumor
254+ ^- (quip card _state)
255+ =/ =hash (en-hash rumor)
256+ =* fact
257+ :- [%give %fact [/~/gossip/gossip]~ %gossip-rumor !>(rumor)]~
258+ %_ state
259+ passed (~(del by passed) hash)
260+ shared (~(put in shared) hash)
261+ ==
262+ :: if we don't want to proxy, always send as fact
263+ ::
264+ ?. pass.manner
265+ fact
266+ :: if we want to proxy, do so 50% of the time
267+ ::
268+ ?. =(0 (~(rad og eny.bowl) 2))
269+ fact
270+ :: if we're proxying, but there's no reasonable targets, send as fact
271+ ::
272+ =/ aides=(set ship)
273+ :: reasonable targets do not include ourselves, whoever
274+ :: caused us to want to (re)send this rumor, or ships that failed
275+ :: to proxy for us before.
276+ :: that last one is important because we need to avoid the mistake of
277+ :: occasionally sending them pokes that just linger in our outbound
278+ :: ames flows and keep retrying. (ideally we'd do those all pokes for
279+ :: a target on the same flow, but we really like storing information
280+ :: in the wire and don't want to do per-relay queues in state. we also
281+ :: don't particularly care about ordering guarantees for gossip.)
282+ :: we don't just get the set of ships with active incoming or outgoing
283+ :: subscriptions, because that constrains the set unnecessarily. for
284+ :: some configs, ships in .tell (that aren't in .hear) might be
285+ :: running the agent without actively listening to us.
286+ ::
287+ =- (~(dif in (~(del in (~(del in -) our.bowl)) src.bowl)) misses)
288+ ?- tell.manner
289+ %anybody (~(uni in (targets:pals ~.)) leeches:pals)
290+ %targets (targets:pals ~.)
291+ %mutuals (mutuals:pals ~.)
292+ ==
293+ =/ count=@ud ~(wyt in aides)
294+ ?: =(0 count)
295+ fact
296+ :: poke a randomly chosen proxy with the rumor
297+ ::
298+ =/ proxy=ship (snag (~(rad og +(eny.bowl)) count) ~(tap in aides))
299+ =/ =time (add now.bowl pass-timeout)
300+ =. passed (~(put by passed) hash [rumor time])
301+ :_ state
302+ =/ =wire /~/gossip/passed/(scot %p proxy)/(scot %uv hash)
303+ =/ =cage gossip-rumor+!>(rumor)
304+ :~ [%pass wire %agent [proxy dap.bowl] %poke cage]
305+ [%pass wire %arvo %b %wait time]
306+ ==
219307 ::
220308 ++ play-cards
221309 |= cards=(list card)
@@ -242,15 +330,18 @@
242330 [%gossip-rumor !>((en-rumor cage.p.i.cards))]
243331 $(out (snoc out i.cards), cards t.cards)
244332 ::
245− ++ resend-rumor
333+ ++ jump-rumor :: relay a rumor if we haven't yet
246334 |= =rumor
247− ^- (unit card)
335+ ^- (quip card _state)
336+ =/ =hash (en-hash rumor)
337+ ?: (~(has in shared) hash) [~ state]
248338 ?> =(%0 kind.rumor) ::NOTE should have been checked for already
249− ?~ meta=((soft ,hops=@ud) meta.rumor) ~
339+ ?~ meta=((soft ,hops=@ud) meta.rumor) [~ state]
250340 =* hops hops.u.meta
251− ?: =(0 hops) ~
341+ ?: =(0 hops) [~ state]
252342 =. meta.rumor (dec hops)
253− `[%give %fact [/~/gossip/gossip]~ %gossip-rumor !>(rumor)]
343+ :- [%give %fact [/~/gossip/gossip]~ %gossip-rumor !>(rumor)]~
344+ state(shared (~(put in shared) (en-hash rumor)))
254345 ::
255346 ++ may-watch
256347 |= who=ship
@@ -364,7 +455,7 @@
364455 ::
365456 ++ agent
366457 |= inner=agent:gall
367− =| state-0
458+ =| state-3
368459 =* state -
369460 %+ verb |
370461 %- agent:dbug
@@ -382,22 +473,70 @@
382473 =^ cards state (play-cards:up cards)
383474 [(weld watch-pals:up cards) this]
384475 ::
385− ++ on-save !>([[%gossip state] on-save:og])
476+ ++ on-save (slop !>([%gossip state]) on-save:og)
386477 ++ on-load
387478 |= ole=vase
388479 ^- (quip card _this)
389480 ?. ?=([[%gossip *] *] q.ole)
390481 =. manner init
391− =^ cards inner (on-load:og ole)
482+ =^ cards inner (on-load:og ole)
392483 =^ cards state (play-cards:up cards)
393484 [(weld watch-pals:up cards) this]
394485 ::
395− =+ !<([[%gossip old=state-0] ile=vase] ole)
396− =. state old
397− =^ cards inner (on-load:og ile)
398− =^ cards state (play-cards:up cards)
399− ::TODO for later versions, add :future retry logic as needed
400− [cards this]
486+ |^ =+ !<([%gossip old=state-any] (slot 2 ole))
487+ =/ ile=vase (slot 3 ole)
488+ =? old ?=(%0 -.old) (state-0-to-1 old)
489+ =? old ?=(%1 -.old) (state-1-to-2 old)
490+ :: before version 3, we stored the inner +on-save vase inside
491+ :: our own +on-save vase. undoing this double-vasing coincided
492+ :: with the h135 type-of-type upgrade.
493+ ::
494+ =? ile ?=(%2 -.old) (next-vase:h136 !<(vase:h136 ile))
495+ =? old ?=(%2 -.old) (state-2-to-3 old)
496+ ?> ?=(%3 -.old)
497+ =. state old
498+ =^ cards inner (on-load:og ile)
499+ =^ cards state (play-cards:up cards)
500+ ::TODO for later versions, add :future retry logic as needed
501+ [cards this]
502+ ::
503+ +$ state-any $%(state-0 state-1 state-2 state-3)
504+ ::
505+ +$ state-2
506+ $: %2
507+ manner=config
508+ memory=(set hash)
509+ shared=(set hash)
510+ passed=(map hash [rumor @da])
511+ misses=(set ship)
512+ future=(list rumor)
513+ ==
514+ ++ state-2-to-3
515+ |=(s=state-2 s(- %3))
516+ ::
517+ +$ state-1
518+ $: %1
519+ manner=config
520+ memory=(set hash)
521+ shared=(set hash)
522+ passed=(map hash [rumor @da])
523+ future=(list rumor)
524+ ==
525+ ++ state-1-to-2
526+ |= state-1
527+ [%2 manner memory shared passed ~ future]
528+ ::
529+ +$ state-0 [%0 manner=config-0 memory=(set hash) future=(list rumor)]
530+ +$ config-0 [hops=_1 hear=whos tell=whos]
531+ ++ state-0-to-1
532+ |= state-0
533+ ^- state-1
534+ [%1 (config-0-to-1 manner) memory memory ~ future]
535+ ++ config-0-to-1
536+ |= config-0
537+ ^- config
538+ [hops hear tell pass.init]
539+ --
401540 ::
402541 ++ on-watch
403542 |= =path
@@ -414,13 +553,43 @@
414553 =^ cards state (play-first-cards:up cards)
415554 [cards this]
416555 ::
556+ ++ on-poke
557+ |= [=mark =vase]
558+ ^- (quip card _this)
559+ ::TODO gossip config pokes
560+ ?. =(%gossip-rumor mark)
561+ =^ cards inner (on-poke:og +<)
562+ =^ cards state (play-cards:up cards)
563+ [cards this]
564+ ?. (want-target:up src.bowl)
565+ ~|(%gossip-rejected !!)
566+ :: we're getting pokes from them, so we should try relaying through
567+ :: them again if we stopped doing so
568+ ::NOTE this only ever cleans out .misses for ships producing content
569+ ::
570+ =. misses (~(del in misses) src.bowl)
571+ ::TODO dedupe with +on-agent %fact
572+ =+ !<(=rumor vase)
573+ =/ =hash (en-hash:up rumor)
574+ ?: (~(has in memory) hash)
575+ [~ this]
576+ ?. =(%0 kind.rumor)
577+ ~& [gossip+dap.bowl %delaying-unknown-rumor-kind kind.rumor]
578+ [~ this(future [rumor future])]
579+ =. memory (~(put in memory) hash)
580+ =/ mage=cage (en-cage:up data.rumor)
581+ =^ cards inner (on-agent:og /~/gossip/gossip %fact mage)
582+ =^ caz1 state (play-cards:up cards)
583+ =^ caz2 state (emit-rumor:up rumor)
584+ [(weld caz1 caz2) this]
585+ ::
417586 ++ on-agent
418587 |= [=wire =sign:agent:gall]
419588 ^- (quip card _this)
420589 ?. ?=([%~.~ %gossip *] wire)
421590 =^ cards inner (on-agent:og wire sign)
422591 =^ cards state (play-cards:up cards)
423− [cards this]
592+ [cards this]
424593 ::
425594 ?+ t.t.wire ~|([%gossip %unexpected-wire wire] !!)
426595 [%gossip @ ~]
@@ -433,18 +602,26 @@
433602 ?. =(%gossip-rumor mark)
434603 ~& [gossip+dap.bowl %ignoring-unexpected-fact mark=mark]
435604 [~ this]
605+ :: we're getting facts from them, so we should try relaying through
606+ :: them again if we stopped doing so
607+ ::NOTE this only ever cleans out .misses for ships in .hear
608+ ::
609+ =. misses (~(del in misses) src.bowl)
610+ ::TODO de-dupe with +on-poke
436611 =+ !<(=rumor vase)
437− =/ hash (sham data.rumor)
612+ =/ =hash (en-hash:up rumor)
438613 ?: (~(has in memory) hash)
439− [~ this]
614+ =^ cards state (jump-rumor:up rumor)
615+ [cards this]
440616 ?. =(%0 kind.rumor)
441617 ~& [gossip+dap.bowl %delaying-unknown-rumor-kind kind.rumor]
442618 [~ this(future [rumor future])]
619+ =. memory (~(put in memory) hash)
443620 =/ mage=cage (en-cage:up data.rumor)
444621 =^ cards inner (on-agent:og /~/gossip/gossip sign(cage mage))
445− =^ cards state (play-cards:up cards)
446− :_ this(memory (~(put in memory) hash))
447− (weld (drop (resend-rumor:up rumor)) cards)
622+ =^ caz1 state (play-cards:up cards)
623+ =^ caz2 state (jump-rumor:up rumor)
624+ [(weld caz1 caz2) this]
448625 ::
449626 %watch-ack
450627 :_ this
@@ -471,6 +648,32 @@
471648 ~& [gossip+dap.bowl %unexpected-poke-ack wire]
472649 [~ this]
473650 ==
651+ ::
652+ [%passed ?([@ ~] [@ @ ~])]
653+ ?. ?=(%poke-ack -.sign)
654+ ~& [gossip+dap.bowl %unexpected-sign wire -.sign]
655+ [~ this]
656+ ~| t.t.wire
657+ :: regardless of anything else: if they acked the poke, they are
658+ :: accepting of relay requests, if they nacked they're unaccepting
659+ ::
660+ =. misses
661+ ?: ?=(~ p.sign)
662+ (~(del in misses) src.bowl)
663+ (~(put in misses) src.bowl)
664+ =/ =hash
665+ ?: ?=([@ ~] t.t.t.wire)
666+ (slav %uv i.t.t.t.wire)
667+ (slav %uv i.t.t.t.t.wire)
668+ ?~ rum=(~(get by passed) hash)
669+ [~ this]
670+ ::NOTE emitting rest is cute, but doesn't actually work reliably,
671+ :: due to userspace duct shenanigans. %wake logic will have to
672+ :: be defensive...
673+ =/ rest=card [%pass wire %arvo %b %rest +.u.rum]
674+ ?~ p.sign [[rest]~ this(passed (~(del by passed) hash))]
675+ =^ caz state (emit-rumor:up -.u.rum)
676+ [[rest caz] this]
474677 ::
475678 [%pals @ ~]
476679 ?- -.sign
@@ -542,6 +745,14 @@
542745 ++ on-peek
543746 |= =path
544747 ^- (unit (unit cage))
748+ ?: =(/x/whey path)
749+ :+ ~ ~
750+ :- %mass
751+ !> ^- (list mass)
752+ :- %gossip^&+state
753+ =/ dat (on-peek:og path)
754+ ?: ?=(?(~ [~ ~]) dat) ~
755+ (fall ((soft (list mass)) q.q.u.u.dat) ~)
545756 ?: =(/x/dbug/state path)
546757 ``noun+(slop on-save:og !>(gossip=state))
547758 ?. ?=([@ %~.~ %gossip *] path)
@@ -560,14 +771,6 @@
560771 =^ cards state (play-cards:up cards)
561772 [cards this]
562773 ::
563− ++ on-poke
564− |= cage
565− ^- (quip card _this)
566− ::TODO gossip config pokes?
567− =^ cards inner (on-poke:og +<)
568− =^ cards state (play-cards:up cards)
569− [cards this]
570− ::
571774 ++ on-arvo
572775 |= [=wire sign=sign-arvo:agent:gall]
573776 ^- (quip card _this)
@@ -575,19 +778,41 @@
575778 =^ cards inner (on-arvo:og wire sign)
576779 =^ cards state (play-cards:up cards)
577780 [cards this]
578− ?> ?=([%retry *] t.t.wire)
579− ?> ?=(%wake +<.sign)
580− ?+ t.t.t.wire ~|(wire !!)
581− [%pals *]
582− ::NOTE this might result in subscription wire re-use,
583− :: but if we hit this path we should be loud anyway.
584− [watch-pals:up this]
781+ ?+ t.t.wire ~|(wire !!)
782+ [%passed ?([@ ~] [@ @ ~])]
783+ =/ =hash
784+ ?: ?=([@ ~] t.t.t.wire)
785+ (slav %uv i.t.t.t.wire)
786+ (slav %uv i.t.t.t.t.wire)
787+ ?~ rum=(~(get by passed) hash) [~ this]
788+ :: since timer cancellation isn't fully reliable, this timer fire
789+ :: might be from a relay that nacked our poke. if the timer fired
790+ :: earlier than expected, assume that to be the case and no-op.
791+ ::
792+ ?: (gth +.u.rum now.bowl) [~ this]
793+ :: they failed to respond to our relay request in time, mark them as
794+ :: unaccepting (if we know who they are from a the new-style wire)
795+ :: and try sending the rumor again
796+ ::
797+ =? misses ?=([@ @ ~] t.t.t.wire)
798+ (~(put in misses) (slav %p i.t.t.t.wire))
799+ =^ cards state (emit-rumor:up -.u.rum)
800+ [cards this]
585801 ::
586− [%watch @ ~]
587− :_ this
588− =/ target=ship (slav %p i.t.t.t.t.wire)
589− ?. (want-target:up target) ~
590− (watch-target:up target)
802+ [%retry *]
803+ ?> ?=(%wake +<.sign)
804+ ?+ t.t.t.wire ~|(wire !!)
805+ [%pals *]
806+ ::NOTE this might result in subscription wire re-use,
807+ :: but if we hit this path we should be loud anyway.
808+ [watch-pals:up this]
809+ ::
810+ [%watch @ ~]
811+ :_ this
812+ =/ target=ship (slav %p i.t.t.t.t.wire)
813+ ?. (want-target:up target) ~
814+ (watch-target:up target)
815+ ==
591816 ==
592817 ::
593818 ++ on-fail
@@ -598,4 +823,4 @@
598823 [cards this]
599824 --
600825 --
601−--
826+--
desk/lib/metaphone.hoonnew file+192
@@ -0,0 +1,192 @@
1+|%
2+++ utter
3+ |= corpus=@t
4+ ^- @t
5+ =/ norm (cuss (trip corpus))
6+ ?: =(norm '') ''
7+ ~(phone core [norm *tape 0])
8+++ core
9+ |_ [text=tape chars=tape index=@ud]
10+ ++ phone
11+ ?: =(current 'A')
12+ ?: =(next 'E') (jump2 'E')
13+ (jump 'A')
14+ ?:
15+ ?& =(next 'N')
16+ ?| =(current 'G')
17+ =(current 'K')
18+ =(current 'P')
19+ ==
20+ ==
21+ (jump2 'N')
22+ ?: =(current 'W')
23+ ?: =(next 'R') (jump2 'R')
24+ ?: =(next 'H') (jump2 current)
25+ ?: (vowel next) (jump2 'W')
26+ recite
27+ ?: =(current 'X') (jump 'S')
28+ ?:
29+ ?| =(current 'E')
30+ =(current 'I')
31+ =(current 'O')
32+ =(current 'U')
33+ ==
34+ (jump current)
35+ recite
36+ ++ recite
37+ ?: (gte index (lent text))
38+ (crip chars)
39+ :: B -> B unless in MB
40+ ?: &(=(current 'B') !=(prev 'M'))
41+ (jump 'B')
42+ :: 'sh' if -CIA- or -CH, but not SCH, except SCHW (SCHW is handled in S)
43+ :: S if -CI-, -CE- or -CY- dropped if -SCI-, SCE-, -SCY- (handed in S)
44+ :: else K
45+ ?: =(current 'C')
46+ ?: (soft next)
47+ :: C[IEY]
48+ ?: &(=(next 'I') =((at 2 &) 'A'))
49+ :: CIA
50+ (jump sh)
51+ ?: !=(prev 'S')
52+ (jump 'S')
53+ advance
54+ ?: =(next 'H')
55+ (jump2 sh)
56+ (jump 'K')
57+ :: J if in -DGE-, -DGI- or -DGY-, else T
58+ ?: =(current 'D')
59+ ?: &(=(next 'G') (soft (at 2 &)))
60+ (jump2 'J')
61+ (jump 'T')
62+ :: F if in -GH and not B--GH, D--GH, -H--GH, -H---GH
63+ :: else dropped if -GNED, -GN,
64+ :: else dropped if -DGE-, -DGI- or -DGY- (handled in D)
65+ :: else J if in -GE-, -GI, -GY and not GG
66+ :: else K
67+ ?: =(current 'G')
68+ ?: =(next 'H')
69+ ?: |(!(no-gh-to-f (at 3 |)) =((at 4 |) 'H'))
70+ (jump2 'F')
71+ advance
72+ ?: =(next 'N')
73+ ?: !&(=((at 2 &) 'E') =((at 3 &) 'D'))
74+ (jump 'K')
75+ advance
76+ ?: &((soft next) !=(prev 'G'))
77+ (jump 'J')
78+ (jump 'K')
79+ :: H if before a vowel and not after C,G,P,S,T
80+ ?: &(=(current 'H') (vowel next) !(dipthong-h prev))
81+ (jump 'H')
82+ :: Dropped if after C, else K
83+ ?: &(=(current 'K') !=(prev 'C'))
84+ (jump 'K')
85+ :: F if before H, else P
86+ ?: =(current 'P')
87+ ?: =(next 'H')
88+ (jump 'F')
89+ (jump 'P')
90+ :: K
91+ ?: =(current 'Q')
92+ (jump 'K')
93+ :: 'sh' in -SH-, -SIO- or -SIA- or -SCHW-, else S
94+ ?: =(current 'S')
95+ ?: &(=(next 'I') |(=((at 2 &) 'O') =((at 2 &) 'A')))
96+ (jump sh)
97+ ?: =(next 'H')
98+ (jump2 sh)
99+ (jump 'S')
100+ :: sh' in -TIA- or -TIO-, else 'th' before H, else T
101+ ?: =(current 'T')
102+ ?: &(=(next 'I') |(=((at 2 &) 'O') =((at 2 &) 'A')))
103+ (jump sh)
104+ ?: =(next 'H')
105+ (jump2 th)
106+ ?: !&(=(next 'C') =((at 2 &) 'H'))
107+ (jump 'T')
108+ advance
109+ :: F
110+ ?: =(current 'V')
111+ (jump 'F')
112+ ?: &(=(current 'W') (vowel next))
113+ (jump 'W')
114+ :: KS
115+ ?: =(current 'X')
116+ (jump 'KS')
117+ :: Y if followed by a vowel
118+ ?: &(=(current 'Y') (vowel next))
119+ (jump 'Y')
120+ :: S
121+ ?: =(current 'Z')
122+ (jump 'S')
123+ ?:
124+ ?| =(current 'F')
125+ =(current 'J')
126+ =(current 'L')
127+ =(current 'M')
128+ =(current 'N')
129+ =(current 'R')
130+ ==
131+ (jump current)
132+ advance
133+ ++ advance
134+ =. index +(index)
135+ recite
136+ ++ jump
137+ |= char=@t
138+ =. chars (snoc chars char)
139+ =. index +(index)
140+ recite
141+ ++ jump2
142+ |= char=@t
143+ =. chars (snoc chars char)
144+ =. index (add index 2)
145+ recite
146+ ++ current (snag index text)
147+ ++ next
148+ ?: (gte index (dec (lent text))) ''
149+ (snag +(index) text)
150+ ++ prev
151+ ?: (lte index 0) ''
152+ (snag (dec index) text)
153+ ++ at
154+ |= [offset=@ud forward=?]
155+ ?: forward
156+ ?: (gth (add index offset) (dec (lent text))) ''
157+ (snag (add index offset) text)
158+ ?: (lth index offset) ''
159+ ?: (lth (sub index offset) 0) ''
160+ (snag (sub index offset) text)
161+ --
162+++ soft
163+ |= char=@t
164+ ?| =(char 'E')
165+ =(char 'I')
166+ =(char 'Y')
167+ ==
168+++ vowel
169+ |= char=@t
170+ ?| =(char 'A')
171+ =(char 'E')
172+ =(char 'I')
173+ =(char 'O')
174+ =(char 'U')
175+ ==
176+++ dipthong-h
177+ |= char=@t
178+ ?| =(char 'C')
179+ =(char 'G')
180+ =(char 'P')
181+ =(char 'S')
182+ =(char 'T')
183+ ==
184+++ no-gh-to-f
185+ |= char=@t
186+ ?| =(char 'B')
187+ =(char 'D')
188+ =(char 'H')
189+ ==
190+++ sh 'X'
191+++ th '0'
192+--
desk/lib/pals.hoon+8−2
@@ -1,6 +1,9 @@
11:: pals: manual peer discovery
22::
3+/- *pals
4+::
35|_ bowl:gall
6+++ records (s ^records /)
47++ leeches (s (set ship) /leeches)
58++ targets |= list=@ta (s (set ship) %targets ?~(list / /[list]))
69++ mutuals |= list=@ta (s (set ship) %mutuals ?~(list / /[list]))
@@ -8,8 +11,11 @@
811++ target |= [list=@ta =ship] (s _| /mutuals/[list]/(scot %p ship))
912++ mutual |= [list=@ta =ship] (s _| /mutuals/[list]/(scot %p ship))
1013::
11−++ base ~+ /(scot %p our)/pals/(scot %da now)
12−++ running ~+ .^(? %gu base)
14+++ labels ?. running `(set @ta)`~
15+ ~(key by dir:.^(arch %gy (snoc base %targets)))
16+::
17+++ base ~+ `path`/(scot %p our)/pals/(scot %da now)
18+++ running ~+ .^(? %gu (snoc base %$))
1319::
1420++ s
1521 |* [=mold =path] ~+
desk/lib/seek-json.hoondeleted−39
@@ -1,39 +0,0 @@
1−/- s=seek
2−|%
3−++ enjs
4− =, enjs:format
5− |%
6− ++ post
7− |= p=post:s
8− %- pairs
9− :~ title/s/title.p
10− type/s/type.p
11− link/s/link.p
12− description/s/description.p
13− image/s/image.p
14− tags/a/(turn tags.p (lead %s))
15− ==
16− ++ listing
17− |= l=listing:s
18− %- pairs
19− :~ post/(post post.l)
20− hash/s/(scot %uv hash.l)
21− source/s/(scot %p source.l)
22− time/(time time.l)
23− ==
24− ++ search
25− |= =search:s
26− %- pairs
27− :~ listings/a/(turn listings.search listing)
28− start/n/(scot %ud start.search)
29− limit/n/(scot %ud limit.search)
30− size/n/(scot %ud size.search)
31− total/n/(scot %ud total.search)
32− ==
33− --
34−++ dejs
35− =, dejs:format
36− |%
37−
38− --
39−--
desk/lib/seek.hoondeleted−26
@@ -1,26 +0,0 @@
1−/- *seek
2−|%
3−++ iching
4− |= word=@t
5− ^- (list @t)
6− =/ w (trip word)
7− =. w
8− ?. (lth (lent w) 3) w
9− (weld w (reap (sub 3 (lent w)) ' '))
10− %+ turn
11− (gulf 0 (sub (lent w) 3))
12− |= index=@ud
13− (crip (swag [index 3] w))
14−++ digest
15− |= =post
16− ^- @uv
17− %- shax
18− %- crip
19− :~ title.post
20− `@t`type.post
21− link.post
22− image.post
23− description.post
24− (crip tags.post)
25− ==
26−--
desk/lib/sift.hoon+46−10
@@ -2,28 +2,64 @@
22++ sift
33 |= corpus=@t
44 ^- (list @t)
5+ ?: =(corpus '') ~
6+ =/ parts (split corpus)
7+ ?~ parts ~
58 %+ skip
6− (split corpus)
9+ (need parts)
710 |= word=@t
811 (~(has in words) word)
12+++ norm
13+ |= corpus=@t
14+ ^- @t
15+ ?: =(corpus '') ''
16+ =/ cleaned (trimall (weave (expunge (weave (trimall `corpus)))))
17+ ?~ cleaned ''
18+ (crip (cass (need cleaned)))
19+++ weave
20+ |= corpus=(unit tape)
21+ ^- (unit @t)
22+ ?~ corpus ~
23+ (some (crip (need corpus)))
924++ split
1025 |= corpus=@t
11− ^- (list @t)
12− =/ lines (crip (trimall corpus))
13− %+ rash lines
14− (more ace (cook crip (plus ;~(pose aln hep))))
26+ ^- (unit (list @t))
27+ ?: =(corpus '') ~
28+ =/ test (norm corpus)
29+ %+ rush test
30+ (more (plus ws) (cook crip (plus ;~(pose aln hep))))
31+++ allowed ;~(pose aln hep ace)
32+++ banned ;~(less allowed next)
33+++ ws (mask " \0a\0d\09")
34+++ expunge
35+ |= corpus=(unit @t)
36+ ^- (unit tape)
37+ ?~ corpus ~
38+ =/ text (need corpus)
39+ ?: =(text '') ~
40+ =/ parsed
41+ %+ rush text
42+ %- plus
43+ ;~ pose
44+ ;~(pfix (plus banned) (star allowed))
45+ ;~(sfix (star allowed) (plus banned))
46+ (plus allowed)
47+ ==
48+ ?~ parsed ~
49+ `(zing (need parsed))
1550++ trimall
16− |= value=@t
17− |^ ^- tape
18− %+ rash value
51+ |= corpus=(unit @t)
52+ ^- (unit tape)
53+ ?~ corpus ~
54+ =/ text (need corpus)
55+ ?: =(text '') ~
56+ %+ rush text
1957 %+ ifix [(star ws) (star ws)]
2058 %- star
2159 ;~ less
2260 ;~(plug (plus ws) ;~(less next (easy ~)))
2361 ;~(pose (cold ' ' (plus ws)) next)
2462 ==
25− ++ ws (mask " \0a\0d\09")
26− --
2763++ words
2864 %- silt
2965 :~ 'a'
desk/lib/sphinx-json.hoonnew file+72
@@ -0,0 +1,72 @@
1+/- s=sphinx
2+|%
3+++ enjs
4+ =, enjs:format
5+ |%
6+ ++ post
7+ |= p=post:s
8+ %- pairs
9+ :~ title/s/title.p
10+ type/s/type.p
11+ link/s/link.p
12+ description/s/description.p
13+ tags/a/(turn tags.p (lead %s))
14+ image/s/image.p
15+ color/s/color.p
16+ ==
17+ ++ listing
18+ |= l=listing:s
19+ %- pairs
20+ :~ post/(post post.l)
21+ hash/s/(scot %uv hash.l)
22+ source/s/(scot %p source.l)
23+ time/(time time.l)
24+ ==
25+ ++ search
26+ |= =search:s
27+ %- pairs
28+ :~ listings/a/(turn listings.search listing)
29+ :: numb, not (scot %ud): 1.234 would be read as a fraction
30+ ::
31+ start/(numb start.search)
32+ limit/(numb limit.search)
33+ size/(numb size.search)
34+ total/(numb total.search)
35+ ==
36+ ++ directory
37+ |= d=directory:s
38+ %- pairs
39+ %+ turn ~(tap by d)
40+ |= [=hash:s l=listing:s]
41+ [(scot %uv hash) (listing l)]
42+ ++ tags
43+ |= tags=(map @t @ud)
44+ %- pairs
45+ %+ turn ~(tap by tags)
46+ |= [tag=@t count=@ud]
47+ [tag (numb count)]
48+ --
49+++ dejs
50+ =, dejs:format
51+ |%
52+ ++ declarations (ar declare)
53+ ++ declare
54+ ^- $-(json declare:s)
55+ %- ot
56+ :~ reach/(su (perk [%friends %public %private ~]))
57+ post/post
58+ ==
59+ ++ post
60+ ^- $-(json post:s)
61+ %- ot
62+ :~ title/so
63+ type/type
64+ link/so
65+ description/so
66+ tags/(ar so)
67+ image/so
68+ color/so
69+ ==
70+ ++ type (su (perk [%app %group %content %other ~]))
71+ --
72+--
desk/lib/sphinx.hoonnew file+255
@@ -0,0 +1,255 @@
1+/- *sphinx
2+/+ sift
3+/+ *validate-url
4+/+ m=metaphone
5+|%
6+++ iching
7+ |= word=@t
8+ ^- (list @t)
9+ =/ w (trip word)
10+ =. w
11+ ?. (lth (lent w) 3) w
12+ (weld w (reap (sub 3 (lent w)) ' '))
13+ %+ turn
14+ (gulf 0 (sub (lent w) 3))
15+ |= index=@ud
16+ %- crip
17+ ^- tape
18+ ^- (list @)
19+ %+ sort
20+ `(list @ud)``(list @)`(swag [index 3] w)
21+ |= [p=@ud q=@ud]
22+ (gth q p)
23+++ digest
24+ |= =post
25+ ^- @uv
26+ %- shax
27+ %- crip
28+ :~ title.post
29+ `@t`type.post
30+ link.post
31+ image.post
32+ description.post
33+ (crip tags.post)
34+ ==
35+++ uprank
36+ |= offset=@ud
37+ |= =entry
38+ ^- ^entry
39+ [hash.entry (add rank.entry offset)]
40+++ sort-entries
41+ |= entries=(list entry)
42+ %+ sort entries
43+ |= [a=entry b=entry]
44+ (gth rank.a rank.b)
45+++ get-hashes
46+ |= entries=(list entry)
47+ %+ turn
48+ entries
49+ |=(=entry hash.entry)
50+:: +lengths-valid: bounds on what a peer can send, checked before
51+:: anything is indexed
52+::
53+:: source and time aren't in the digest, so a peer picks them too:
54+:: +time:enjs crashes before 1970, and rendering a huge atom is
55+:: quadratic
56+::
57+++ lengths-valid
58+ |= l=listing
59+ ?& (lte (met 3 title.post.l) 77)
60+ (lte (met 3 description.post.l) 256)
61+ (lte (lent tags.post.l) 8)
62+ (levy tags.post.l |=(tag=@t (lte (met 3 tag) 1.024)))
63+ (lte (met 3 link.post.l) 1.024)
64+ (lte (met 3 image.post.l) 1.024)
65+ (lte (met 3 color.post.l) 1.024)
66+ (lte (met 3 source.l) 16)
67+ (gte time.l ~1970.1.1)
68+ (lth time.l ~275000.1.1)
69+ ==
70+++ urls-valid
71+ |= l=listing
72+ =/ image
73+ ?: =(image.post.l '') 'X'
74+ (validate image.post.l)
75+ =/ link (validate link.post.l)
76+ &(!=(image ~) !=(link ~))
77+++ listing-valid
78+ |= l=listing
79+ :: lengths first: don't parse urls of oversized listings
80+ ::
81+ &((lengths-valid l) (urls-valid l))
82+++ delver
83+ |_ [=lookup =trigrams =phonetics =trail]
84+ ++ get
85+ ^- index
86+ [lookup trigrams phonetics trail]
87+ ++ catalog
88+ |= [=hash l=listing]
89+ ?. (listing-valid l) get
90+ =/ entries=^lookup
91+ %- malt
92+ ^- (list [@t (list entry)])
93+ %- zing
94+ :~ ~[[type.post.l ~[[hash rank=4]]]]
95+ %+ turn
96+ (sift:sift description.post.l)
97+ |= word=@t
98+ [word ~[[hash rank=8]]]
99+ :: +iching is quadratic in the length of a key, so long
100+ :: tags aren't indexed
101+ ::
102+ %+ turn
103+ (skim tags.post.l |=(tag=@t (lte (met 3 tag) 64)))
104+ |= tag=@t
105+ [(norm:sift tag) ~[[hash rank=12]]]
106+ %+ turn
107+ (sift:sift title.post.l)
108+ |= word=@t
109+ [word ~[[hash rank=16]]]
110+ ~[[(crip (cass (trip title.post.l))) ~[[hash rank=20]]]]
111+ ==
112+ =/ keys ~(tap in ~(key by entries))
113+ =. trail (~(put by trail) hash keys)
114+ :: only touch the new keys: rebuilding the whole map for every
115+ :: listing made cataloging a directory quadratic
116+ ::
117+ =. lookup
118+ %+ roll ~(tap by entries)
119+ |= [[=key value=(list entry)] lok=_lookup]
120+ (~(put by lok) key (weld value (~(gut by lok) key ~)))
121+ =. trigrams
122+ %+ roll
123+ %+ snoc
124+ %+ turn keys
125+ |= =key
126+ %- malt
127+ %+ turn (iching key)
128+ |= =trigram
129+ [trigram (silt ~[key])]
130+ trigrams
131+ |= [next=^trigrams t=^trigrams]
132+ %- (~(uno by t) next)
133+ |= [k=key v=(set key) w=(set key)]
134+ (~(uni in v) w)
135+ =. phonetics
136+ %-
137+ %- ~(uno by phonetics)
138+ %- malt
139+ %+ turn keys
140+ |= =key
141+ [(utter:m key) (silt ~[key])]
142+ |= [k=key v=(set key) w=(set key)]
143+ (~(uni in v) w)
144+ get
145+ ++ get-entries
146+ |= query=@t
147+ ^- (list entry)
148+ ?~ (find " " (trip query))
149+ =/ t (get-title-entries query)
150+ :: ~& t
151+ ~(tap by (malt t))
152+ %~ tap by
153+ %-
154+ %~ uni by :: union and prefer title entries (better rank)
155+ (malt (get-part-entries query))
156+ (malt (get-title-entries query))
157+ ++ get-part-entries
158+ |= query=@t
159+ ^- (list entry)
160+ =/ parts (sift:sift query) :: split query
161+ %- sum-ranks
162+ %- zing
163+ %+ turn
164+ parts
165+ |= word=@t
166+ ;: weld
167+ (get-phonetics word)
168+ (turn (get-trigrams word) (uprank 2))
169+ (turn (get-exact word) (uprank 6))
170+ ==
171+ ++ get-title-entries
172+ |= query=@t
173+ ^- (list entry)
174+ =/ title (norm:sift query) :: full query
175+ =/ exact (turn (get-exact title) (uprank 8))
176+ =/ trigrams (turn (get-trigrams title) (uprank 4))
177+ =/ phonetics (get-phonetics title)
178+ ;: weld
179+ phonetics
180+ trigrams
181+ exact
182+ ==
183+ ++ sum-ranks
184+ |= entries=(list entry)
185+ ^- (list entry)
186+ %+ turn
187+ %~ tap by
188+ %+ roll
189+ entries
190+ |= [=entry gourd=(jar hash rank)]
191+ (~(add ja gourd) entry)
192+ |= [=hash es=(list rank)]
193+ ^- entry
194+ [hash (roll es add)]
195+ ++ get-exact
196+ |= term=@t
197+ (~(gut by lookup) term *(list entry))
198+ ++ get-trigrams
199+ |= term=@t
200+ ^- (list entry)
201+ %- zing
202+ %+ turn
203+ (get-keys term)
204+ |= item=@t
205+ (~(gut by lookup) item *(list entry))
206+ ++ get-phonetics
207+ |= term=key
208+ ^- (list entry)
209+ =/ keys
210+ %+ skim
211+ %~ tap in
212+ (~(gut by phonetics) (utter:m term) *(set key))
213+ |= word=key
214+ !=(term word)
215+ %+ roll
216+ keys
217+ |= [word=key entries=(list entry)]
218+ %+ weld
219+ entries
220+ (~(gut by lookup) word *(list entry))
221+ ++ get-keys
222+ |= term=@t
223+ ^- (list @t)
224+ =/ tally (get-term-counts term)
225+ =/ tallys ~(tap by tally)
226+ =/ count (lent (iching term))
227+ %+ turn
228+ %+ sort
229+ %+ skim
230+ tallys
231+ |= [k=@t v=@ud]
232+ (gth:rs (div:rs (sun:rs v) (sun:rs count)) .0.6)
233+ |= [[k=@t v=@ud] [l=@t w=@ud]]
234+ (lth v w)
235+ |= [k=@t v=@ud]
236+ k
237+ ++ get-term-counts
238+ |= term=@t
239+ ^- (map @t @ud)
240+ %+ roll
241+ (iching term)
242+ |= [ngram=@t tally=(map @t @ud)]
243+ ^- (map @t @ud)
244+ =/ words=(set @t) (~(gut by trigrams) ngram *(set @t))
245+ %-
246+ %- ~(uno by tally)
247+ %- malt
248+ %+ turn
249+ ~(tap in words)
250+ |= word=@t
251+ [word 1]
252+ |= [k=@t v=@ud w=@ud]
253+ (add v w)
254+ --
255+--
desk/lib/validate-url.hoonnew file+20
@@ -0,0 +1,20 @@
1+|%
2+++ validate
3+ |= url=@t
4+ ^- (unit @t)
5+ %+ rush url
6+ %+ cold url
7+ ;~ plug
8+ ;~ pose
9+ ;~ plug
10+ ;~(plug alf (star ;~(pose aln lus dot hep)) col fas fas)
11+ ;~(pose thor:de-purl:html ;~(pfix sig fed:ag))
12+ ==
13+ (easy ~)
14+ ==
15+ ;~ plug
16+ ;~(pose apat:de-purl:html (easy *pork:eyre))
17+ yque:de-purl:html
18+ ==
19+ ==
20+--
desk/mar/declarations.hoonnew file+14
@@ -0,0 +1,14 @@
1+/- s=sphinx
2+/+ j=sphinx-json
3+|_ d=(list declare:s)
4+++ grad %noun
5+++ grow
6+ |%
7+ ++ noun d
8+ --
9+++ grab
10+ |%
11+ ++ noun (list declare:s)
12+ ++ json declarations:dejs:j
13+ --
14+--
desk/mar/declare.hoonnew file+14
@@ -0,0 +1,14 @@
1+/- s=sphinx
2+/+ j=sphinx-json
3+|_ =declare:s
4+++ grad %noun
5+++ grow
6+ |%
7+ ++ noun declare
8+ --
9+++ grab
10+ |%
11+ ++ noun declare:s
12+ ++ json declare:dejs:j
13+ --
14+--
desk/mar/directory-listing.hoon+2−2
@@ -1,5 +1,5 @@
1−/- s=seek
2−/+ j=seek-json
1+/- s=sphinx
2+/+ j=sphinx-json
33|_ =listing:s
44++ grad %noun
55++ grow
desk/mar/directory.hoon+3−2
@@ -1,10 +1,11 @@
1−/- s=seek
2−/+ j=seek-json
1+/- s=sphinx
2+/+ j=sphinx-json
33|_ =directory:s
44++ grad %noun
55++ grow
66 |%
77 ++ noun directory
8+ ++ json (directory:enjs:j directory)
89 --
910++ grab
1011 |%
desk/mar/pals/command.hoonnew file+22
@@ -0,0 +1,22 @@
1+:: pals-command: friend management
2+::
3+/- *pals
4+::
5+|_ cmd=command
6+++ grad %noun
7+++ grow
8+ |%
9+ ++ noun cmd
10+ --
11+++ grab
12+ |%
13+ ++ noun command
14+ ++ json
15+ ^- $-(^json command)
16+ =, dejs:format
17+ |^ (of meet+arg part+arg ~)
18+ ++ arg
19+ (ot 'ship'^(su fed:ag) 'in'^(as so) ~)
20+ --
21+ --
22+--
desk/mar/remove.hoonnew file+14
@@ -0,0 +1,14 @@
1+/- s=sphinx
2+/+ j=sphinx-json
3+|_ =hash:s
4+++ grad %noun
5+++ grow
6+ |%
7+ ++ noun hash
8+ --
9+++ grab
10+ |%
11+ ++ noun hash:s
12+ ++ json (se:dejs:format %uv)
13+ --
14+--
desk/mar/search.hoon+3−3
@@ -1,5 +1,5 @@
1−/- s=seek
2−/+ j=seek-json
1+/- s=sphinx
2+/+ j=sphinx-json
33|_ =search:s
44++ grad %noun
55++ grow
@@ -9,6 +9,6 @@
99 --
1010++ grab
1111 |%
12− ++ noun search
12+ ++ noun search:s
1313 --
1414--
desk/mar/state.hoonnew file+12
@@ -0,0 +1,12 @@
1+/- s=sphinx
2+|_ =state:s
3+++ grad %noun
4+++ grow
5+ |%
6+ ++ noun state
7+ --
8+++ grab
9+ |%
10+ ++ noun state:s
11+ --
12+--
desk/mar/tags.hoonnew file+14
@@ -0,0 +1,14 @@
1+/- s=sphinx
2+/+ j=sphinx-json
3+|_ tags=(map @t @ud)
4+++ grad %noun
5+++ grow
6+ |%
7+ ++ noun tags
8+ ++ json (tags:enjs:j tags)
9+ --
10+++ grab
11+ |%
12+ ++ noun (map @t @ud)
13+ --
14+--
desk/sur/seek.hoon → desk/sur/sphinx.hoonrenamed+68−5
@@ -2,6 +2,7 @@
22+$ key @t :: keyword that points to listings
33+$ hash @uv :: @uv version of shax result
44+$ rank @ud :: @ud representing priority of listing
5++$ phonetic @t :: @t consisting of phonetic characters from metaphone
56+$ trigram @t :: @t consisting of three characters taken from a word
67::
78:: an entry points to a listing hash and provides
@@ -26,16 +27,19 @@
2627 description=@t
2728 tags=(list @t)
2829 image=@t
30+ color=@t
2931 ==
3032::
33++$ reach ?(%private %friends %public)
34+::
35++$ declare (pair reach post)
36+::
3137:: a listing includes the content of the post as well as information
3238:: that would affect the hash so are left out, namely time, source and
3339:: hash. each post has a source and time so that they can be updated
3440:: via the source requesting to replace the listing and hash.
3541::
3642::
37−+$ reach ?(%private %friends %public)
38−::
3943+$ listing
4044 $: =post
4145 =hash
@@ -50,9 +54,14 @@
5054::
5155+$ lookup (map key (list entry))
5256::
53−:: trigrams act as a way to account for spelling errors. we keep a map
54−:: of trigram to set of lookup keys. we take the query's trigrams and
55−:: count how many times a particular key comes up.
57+:: phonetics act as a way to account for spelling errors. we keep a map
58+:: of phonetic to set of lookup keys. we take the query's phonetics and
59+:: use that to add other words to the results.
60++$ phonetics (map phonetic (set key))
61+::
62+:: trigrams act similarly to phonetics. we keep a map of trigram to set
63+:: of lookup keys. we take the query's trigrams and count how many
64+:: times a particular key comes up.
5665+$ trigrams (map trigram (set key))
5766::
5867:: trail is a way for us to know all the keys required to update
@@ -73,4 +82,58 @@
7382 total=@ud
7483 ==
7584::
85++$ index
86+ $: =lookup
87+ =trigrams
88+ =phonetics
89+ =trail
90+ ==
91+::
92++$ state
93+ $: %2
94+ =index
95+ =directory
96+ published=directory
97+ ==
98+::
99++$ state-1
100+ $: %1
101+ =lookup
102+ =trigrams
103+ =phonetics
104+ =trail
105+ =directory
106+ published=directory
107+ ==
108+::
109++$ state-0
110+ $: %0
111+ =lookup
112+ =trigrams
113+ =phonetics
114+ =trail
115+ =directory:directory-state-zero
116+ published=directory:directory-state-zero
117+ ==
118+++ directory-state-zero
119+ |%
120+ +$ post
121+ $: title=@t
122+ type=post-type
123+ link=@t
124+ description=@t
125+ tags=(list @t)
126+ image=@t
127+ ==
128+ ::
129+ +$ listing
130+ $: =post
131+ =hash
132+ =reach
133+ source=@p
134+ time=@da
135+ ==
136+ ::
137+ +$ directory (map hash listing)
138+ --
76139--
desk/sys.kelvin+1−1
@@ -1 +1 @@
1−[%zuse 418]
1+[%zuse 408]
desk/ted/publish-all.hoonnew file+40
@@ -0,0 +1,40 @@
1+/- spider
2+/- s=sphinx
3+/+ *strandio
4+=, strand=strand:spider
5+=/ posts
6+ ^- (list declare:s)
7+ :~ :- %friends
8+ ['networked subject' %group 'web+urbitgraph://group/~matwet/networked-subject' 'https://subject.network | networked subject' ~['hosting' 'networking' 'urbit' 'ops'] 'https://urbits3.ams3.digitaloceanspaces.com/sitful-hatred/2022.7.07..17.15.52-ns.png' '']
9+ :- %friends
10+ ['Hooniverse' %group 'web+urbitgraph://group/~hiddev-dannut/new-hooniverse' 'Community based Hoon learning for all levels. For discussion of Hoon specific to Uqbar, join ~hiddev-dannut/uhoon' ~['hoon' 'dev' 'urbit' 'programming' 'education'] 'https://i.imgur.com/ghThlz7.png' '']
11+ :- %friends
12+ ['Hollow Mars Theory' %group 'web+urbitgraph://group/~rabsef-bicrym/hollow-mars-theory' 'Definitely NOT a Conspiracy Chat' ~['conspiracies' 'ufos' 'steel beams'] '' '']
13+ :- %friends
14+ ['celestial systems' %group 'web+urbitgraph://group/~nocsyx-lassul/celestial-systems' 'A place for pilots who are building hosting providers' ~['hosting' 'ops' 'urbit'] '' '']
15+ :- %friends
16+ ['Structure' %group 'web+urbitgraph://group/~fabled-faster/structure' 'Urbit Structural Design and Engineering Group. Always Thinking About Mechanics.' ~['design' 'ux' 'hci' 'urbit'] 'https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2022.1.27..17.59.43-image.png' '']
17+ :- %friends
18+ ['The Marketplace' %group 'web+urbitgraph://group/~tirrel/the-marketplace' 'Welcome to The Marketplace, featuring The Pit, Urbit\'s first open-outcry market!' ~['urbit' 'market' 'sell' 'buy'] 'https://snipboard.io/U0IYyi.jpg' '']
19+ :- %friends
20+ ['The Forge' %group 'web+urbitgraph://group/~middev/the-forge' 'pale fire computing' ~['dev' 'programming' 'hoon' 'urbit'] 'https://nyc3.digitaloceanspaces.com/archiv/littel-wolfur/2021.5.06..21.01.58-the%20forge.png' '']
21+ :- %friends
22+ ['UFORIA' %group 'web+urbitgraph://group/~tiplec-lacnyx/ufora' 'UFO Research, Investigations, and Analysis' ~['UFO' 'conspiracies' 'research'] 'https://tiplec-lacnyx.nyc3.digitaloceanspaces.com/tiplec-lacnyx/2021.12.27..06.09.47-change1.jpg' '']
23+ :- %friends
24+ ['Urbit Community' %group 'web+urbitgraph://group/~bitbet-bolbel/urbit-community' 'World hub, help desk, meet and greet, etc.' ~['general' 'urbit' 'community' 'help'] 'https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.4.02..21.52.41-UC.png' '']
25+ :- %friends
26+ ['Urbytes' %group 'web+urbitgraph://group/~nartes-fasrum/urbytes' 'We feed and water Mars.' ~['cooking' 'food' 'beverages' 'peanut butter eggs'] '' '']
27+ ==
28+^- thread:spider
29+|= arg=vase
30+=/ m (strand ,vase)
31+^- form:m
32+;< our=@p bind:m get-our
33+;< ~ bind:m
34+ %- send-raw-cards
35+ ^- (list card:agent:gall)
36+ %+ turn
37+ posts
38+ |= =declare:s
39+ [%pass /poke %agent [our %sphinx] %poke [%declare !>(declare)]]
40+(pure:m !>(~))
flake.locknew file+63
@@ -0,0 +1,63 @@
1+{
2+ "nodes": {
3+ "landscape": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1781809440,
7+ "narHash": "sha256-woNZKzK8xlBxnMMrQOzInA7+gGxs7BgjGbmTi7OGi6g=",
8+ "owner": "tloncorp",
9+ "repo": "landscape",
10+ "rev": "3ea2679f2003ed3ca1a6b322cd7857a774eac305",
11+ "type": "github"
12+ },
13+ "original": {
14+ "owner": "tloncorp",
15+ "repo": "landscape",
16+ "rev": "3ea2679f2003ed3ca1a6b322cd7857a774eac305",
17+ "type": "github"
18+ }
19+ },
20+ "nixpkgs": {
21+ "locked": {
22+ "lastModified": 1792210252,
23+ "narHash": "sha256-Mh0PUaIv4nFY+8wAGonaEh8iBzwsmdNlwjVHmFg1Biw=",
24+ "owner": "NixOS",
25+ "repo": "nixpkgs",
26+ "rev": "09063bd1f2b36fd9fd793c36dfade7bd40a93dfc",
27+ "type": "github"
28+ },
29+ "original": {
30+ "owner": "NixOS",
31+ "ref": "nixos-unstable",
32+ "repo": "nixpkgs",
33+ "type": "github"
34+ }
35+ },
36+ "root": {
37+ "inputs": {
38+ "landscape": "landscape",
39+ "nixpkgs": "nixpkgs",
40+ "urbit": "urbit"
41+ }
42+ },
43+ "urbit": {
44+ "flake": false,
45+ "locked": {
46+ "lastModified": 1785851358,
47+ "narHash": "sha256-eHoGwqirk7zMpOn/y8dKqozz5IiQ7nqek/ypxIOaRiU=",
48+ "owner": "urbit",
49+ "repo": "urbit",
50+ "rev": "5a187fededc4582a34fcd6055c67bb63e0917b94",
51+ "type": "github"
52+ },
53+ "original": {
54+ "owner": "urbit",
55+ "ref": "408k-2",
56+ "repo": "urbit",
57+ "type": "github"
58+ }
59+ }
60+ },
61+ "root": "root",
62+ "version": 7
63+}
flake.nixnew file+98
@@ -0,0 +1,98 @@
1+{
2+ description = "sphinx desk";
3+
4+ inputs = {
5+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
7+ # Dependency desks. Keep them on releases for the kelvin in
8+ # desk/sys.kelvin ([%zuse 408]) and bump them together with it.
9+ urbit = {
10+ # release tag for [%zuse 408]
11+ url = "github:urbit/urbit/408k-2";
12+ flake = false;
13+ };
14+ landscape = {
15+ # No 408 tag: the staging head Tlon promoted on 2026-06-18, whose
16+ # desk/sys.kelvin lists [%zuse 410] [%zuse 409] [%zuse 408].
17+ url = "github:tloncorp/landscape/3ea2679f2003ed3ca1a6b322cd7857a774eac305";
18+ flake = false;
19+ };
20+ };
21+
22+ outputs =
23+ {
24+ self,
25+ nixpkgs,
26+ urbit,
27+ landscape,
28+ }:
29+ let
30+ forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
31+
32+ # Only what ./desk needs from the dependency desks: its imports,
33+ # transitively, and the marks for its file types plus json and mime.
34+ # 408 clay builds every mark on the desk at each commit and OTA, and one
35+ # that fails to build rejects the whole event, so extra marks are a
36+ # liability.
37+ basePicks = [
38+ "lib/dbug.hoon"
39+ "lib/default-agent.hoon"
40+ "lib/skeleton.hoon"
41+ "lib/strand.hoon"
42+ "lib/strandio.hoon"
43+ "lib/verb.hoon"
44+ "sur/spider.hoon"
45+ "sur/verb.hoon"
46+ "mar/bill.hoon"
47+ "mar/hoon.hoon"
48+ "mar/json.hoon"
49+ "mar/kelvin.hoon"
50+ "mar/mime.hoon"
51+ "mar/noun.hoon"
52+ "mar/txt.hoon"
53+ ];
54+ # desk.docket-0 needs mar/docket-0, which imports lib/docket and
55+ # sur/docket.
56+ docketPicks = [
57+ "lib/docket.hoon"
58+ "mar/docket-0.hoon"
59+ "sur/docket.hoon"
60+ ];
61+ in
62+ {
63+ packages = forAllSystems (
64+ system:
65+ let
66+ pkgs = nixpkgs.legacyPackages.${system};
67+ in
68+ {
69+ # The picks, then ./desk on top. -L copies symlink targets, so the
70+ # desk is plain files.
71+ desk = pkgs.runCommandLocal "sphinx-desk" { } ''
72+ mkdir $out
73+ for f in ${toString basePicks}; do
74+ install -Dm644 ${urbit}/pkg/base-dev/$f $out/$f
75+ done
76+ for f in ${toString docketPicks}; do
77+ install -Dm644 ${landscape}/desk-dev/$f $out/$f
78+ done
79+ cp -RL ${./desk}/. $out
80+ '';
81+ # The UI files, served under /apps/sphinx/. Upload this directory at
82+ # /docket/upload on the ship in desk/desk.docket-0's glob-ames.
83+ glob = pkgs.buildNpmPackage {
84+ name = "sphinx-glob";
85+ src = ./ui;
86+ nodejs = pkgs.nodejs_22;
87+ npmDepsHash = "sha256-uOUDkt0spCWdlOfto5Y3o5R9PP6oeYmGMcwUjEdzC/M=";
88+ installPhase = ''
89+ runHook preInstall
90+ cp -R dist $out
91+ runHook postInstall
92+ '';
93+ };
94+ default = self.packages.${system}.desk;
95+ }
96+ );
97+ };
98+}
ui/.nvmrcnew file+1
@@ -0,0 +1 @@
1+22
ui/index.html+2−2
@@ -3,12 +3,12 @@
33 <head>
44 <meta charset="UTF-8" />
55 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6− <title>seek</title>
6+ <title>sphinx</title>
77 <meta name="theme-color" content="#ffffff" />
88 <link rel="icon" href="/src/assets/favicon.svg" sizes="any" type="image/svg+xml" />
99 <link rel="mask-icon" href="/src/assets/safari-pinned-tab.svg" color="#000000" />
1010 <link rel="apple-touch-icon" sizes="180x180" href="/src/assets/apple-touch-icon.png" />
11− <link rel="manifest" href="/src/assets/manifest.json" />
11+ <link rel="manifest" href="/manifest.json" />
1212 </head>
1313 <body class="font-sans text-mauve bg-linen antialiased">
1414 <div id="app"></div>
ui/package-lock.json+1041−501
@@ -1,25 +1,33 @@
11{
2− "name": "seek",
2+ "name": "sphinx",
33 "version": "0.0.0",
44 "lockfileVersion": 2,
55 "requires": true,
66 "packages": {
77 "": {
8− "name": "seek",
8+ "name": "sphinx",
99 "version": "0.0.0",
1010 "dependencies": {
1111 "@heroicons/react": "^1.0.6",
1212 "@radix-ui/react-dropdown-menu": "^0.1.6",
13+ "@radix-ui/react-select": "^0.1.1",
1314 "@radix-ui/react-toggle-group": "^0.1.5",
14− "@urbit/api": "^2.0.0",
15+ "@tanstack/react-query": "^4.29.5",
16+ "@urbit/api": "2.2",
1517 "@urbit/http-api": "^2.0.0",
1618 "big-integer": "^1.6.51",
1719 "classnames": "^2.3.1",
20+ "color2k": "^2.0.2",
1821 "lodash.debounce": "^4.0.8",
22+ "query-string": "^7.1.1",
1923 "react": "^17.0.2",
2024 "react-dom": "^17.0.2",
21− "react-query": "^3.39.1",
22− "react-router-dom": "^6.3.0"
25+ "react-hook-form": "^7.33.1",
26+ "react-router-dom": "^6.3.0",
27+ "react-select": "^5.4.0",
28+ "remove-markdown-and-html": "^0.1.0",
29+ "urbit-ob": "^5.0.1",
30+ "zustand": "^4.0.0-rc.1"
2331 },
2432 "devDependencies": {
2533 "@types/lodash.debounce": "^4.0.7",
@@ -29,7 +37,9 @@
2937 "@vitejs/plugin-react-refresh": "^1.3.1",
3038 "autoprefixer": "^10.4.7",
3139 "postcss": "^8.4.14",
40+ "postcss-import": "^14.1.0",
3241 "tailwindcss": "^3.1.4",
42+ "tailwindcss-labeled-groups": "^0.0.2",
3343 "typescript": "^4.3.2",
3444 "vite": "^2.9.13"
3545 }
@@ -38,7 +48,6 @@
3848 "version": "2.2.0",
3949 "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
4050 "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
41− "dev": true,
4251 "dependencies": {
4352 "@jridgewell/gen-mapping": "^0.1.0",
4453 "@jridgewell/trace-mapping": "^0.3.9"
@@ -51,7 +60,6 @@
5160 "version": "7.18.6",
5261 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
5362 "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
54− "dev": true,
5563 "dependencies": {
5664 "@babel/highlight": "^7.18.6"
5765 },
@@ -63,7 +71,6 @@
6371 "version": "7.18.6",
6472 "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz",
6573 "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==",
66− "dev": true,
6774 "engines": {
6875 "node": ">=6.9.0"
6976 }
@@ -72,7 +79,6 @@
7279 "version": "7.18.6",
7380 "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz",
7481 "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==",
75− "dev": true,
7682 "dependencies": {
7783 "@ampproject/remapping": "^2.1.0",
7884 "@babel/code-frame": "^7.18.6",
@@ -102,7 +108,6 @@
102108 "version": "7.18.7",
103109 "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz",
104110 "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==",
105− "dev": true,
106111 "dependencies": {
107112 "@babel/types": "^7.18.7",
108113 "@jridgewell/gen-mapping": "^0.3.2",
@@ -116,7 +121,6 @@
116121 "version": "0.3.2",
117122 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
118123 "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
119− "dev": true,
120124 "dependencies": {
121125 "@jridgewell/set-array": "^1.0.1",
122126 "@jridgewell/sourcemap-codec": "^1.4.10",
@@ -130,7 +134,6 @@
130134 "version": "7.18.6",
131135 "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz",
132136 "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==",
133− "dev": true,
134137 "dependencies": {
135138 "@babel/compat-data": "^7.18.6",
136139 "@babel/helper-validator-option": "^7.18.6",
@@ -148,7 +151,6 @@
148151 "version": "7.18.6",
149152 "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz",
150153 "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==",
151− "dev": true,
152154 "engines": {
153155 "node": ">=6.9.0"
154156 }
@@ -157,7 +159,6 @@
157159 "version": "7.18.6",
158160 "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz",
159161 "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==",
160− "dev": true,
161162 "dependencies": {
162163 "@babel/template": "^7.18.6",
163164 "@babel/types": "^7.18.6"
@@ -170,7 +171,6 @@
170171 "version": "7.18.6",
171172 "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
172173 "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
173− "dev": true,
174174 "dependencies": {
175175 "@babel/types": "^7.18.6"
176176 },
@@ -182,7 +182,6 @@
182182 "version": "7.18.6",
183183 "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
184184 "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
185− "dev": true,
186185 "dependencies": {
187186 "@babel/types": "^7.18.6"
188187 },
@@ -194,7 +193,6 @@
194193 "version": "7.18.6",
195194 "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz",
196195 "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==",
197− "dev": true,
198196 "dependencies": {
199197 "@babel/helper-environment-visitor": "^7.18.6",
200198 "@babel/helper-module-imports": "^7.18.6",
@@ -213,7 +211,6 @@
213211 "version": "7.18.6",
214212 "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz",
215213 "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==",
216− "dev": true,
217214 "engines": {
218215 "node": ">=6.9.0"
219216 }
@@ -222,7 +219,6 @@
222219 "version": "7.18.6",
223220 "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz",
224221 "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==",
225− "dev": true,
226222 "dependencies": {
227223 "@babel/types": "^7.18.6"
228224 },
@@ -234,7 +230,6 @@
234230 "version": "7.18.6",
235231 "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
236232 "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
237− "dev": true,
238233 "dependencies": {
239234 "@babel/types": "^7.18.6"
240235 },
@@ -246,7 +241,6 @@
246241 "version": "7.18.6",
247242 "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
248243 "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
249− "dev": true,
250244 "engines": {
251245 "node": ">=6.9.0"
252246 }
@@ -255,7 +249,6 @@
255249 "version": "7.18.6",
256250 "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
257251 "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
258− "dev": true,
259252 "engines": {
260253 "node": ">=6.9.0"
261254 }
@@ -264,7 +257,6 @@
264257 "version": "7.18.6",
265258 "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz",
266259 "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==",
267− "dev": true,
268260 "dependencies": {
269261 "@babel/template": "^7.18.6",
270262 "@babel/traverse": "^7.18.6",
@@ -278,7 +270,6 @@
278270 "version": "7.18.6",
279271 "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
280272 "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
281− "dev": true,
282273 "dependencies": {
283274 "@babel/helper-validator-identifier": "^7.18.6",
284275 "chalk": "^2.0.0",
@@ -292,7 +283,6 @@
292283 "version": "7.18.6",
293284 "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz",
294285 "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==",
295− "dev": true,
296286 "bin": {
297287 "parser": "bin/babel-parser.js"
298288 },
@@ -300,6 +290,20 @@
300290 "node": ">=6.0.0"
301291 }
302292 },
293+ "node_modules/@babel/plugin-syntax-jsx": {
294+ "version": "7.18.6",
295+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
296+ "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
297+ "dependencies": {
298+ "@babel/helper-plugin-utils": "^7.18.6"
299+ },
300+ "engines": {
301+ "node": ">=6.9.0"
302+ },
303+ "peerDependencies": {
304+ "@babel/core": "^7.0.0-0"
305+ }
306+ },
303307 "node_modules/@babel/plugin-transform-react-jsx-self": {
304308 "version": "7.18.6",
305309 "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz",
@@ -345,7 +349,6 @@
345349 "version": "7.18.6",
346350 "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz",
347351 "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==",
348− "dev": true,
349352 "dependencies": {
350353 "@babel/code-frame": "^7.18.6",
351354 "@babel/parser": "^7.18.6",
@@ -359,7 +362,6 @@
359362 "version": "7.18.6",
360363 "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz",
361364 "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==",
362− "dev": true,
363365 "dependencies": {
364366 "@babel/code-frame": "^7.18.6",
365367 "@babel/generator": "^7.18.6",
@@ -380,7 +382,6 @@
380382 "version": "7.18.7",
381383 "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz",
382384 "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==",
383− "dev": true,
384385 "dependencies": {
385386 "@babel/helper-validator-identifier": "^7.18.6",
386387 "to-fast-properties": "^2.0.0"
@@ -389,6 +390,119 @@
389390 "node": ">=6.9.0"
390391 }
391392 },
393+ "node_modules/@emotion/babel-plugin": {
394+ "version": "11.9.2",
395+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz",
396+ "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==",
397+ "dependencies": {
398+ "@babel/helper-module-imports": "^7.12.13",
399+ "@babel/plugin-syntax-jsx": "^7.12.13",
400+ "@babel/runtime": "^7.13.10",
401+ "@emotion/hash": "^0.8.0",
402+ "@emotion/memoize": "^0.7.5",
403+ "@emotion/serialize": "^1.0.2",
404+ "babel-plugin-macros": "^2.6.1",
405+ "convert-source-map": "^1.5.0",
406+ "escape-string-regexp": "^4.0.0",
407+ "find-root": "^1.1.0",
408+ "source-map": "^0.5.7",
409+ "stylis": "4.0.13"
410+ },
411+ "peerDependencies": {
412+ "@babel/core": "^7.0.0"
413+ }
414+ },
415+ "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
416+ "version": "4.0.0",
417+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
418+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
419+ "engines": {
420+ "node": ">=10"
421+ },
422+ "funding": {
423+ "url": "https://github.com/sponsors/sindresorhus"
424+ }
425+ },
426+ "node_modules/@emotion/cache": {
427+ "version": "11.9.3",
428+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.9.3.tgz",
429+ "integrity": "sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==",
430+ "dependencies": {
431+ "@emotion/memoize": "^0.7.4",
432+ "@emotion/sheet": "^1.1.1",
433+ "@emotion/utils": "^1.0.0",
434+ "@emotion/weak-memoize": "^0.2.5",
435+ "stylis": "4.0.13"
436+ }
437+ },
438+ "node_modules/@emotion/hash": {
439+ "version": "0.8.0",
440+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
441+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
442+ },
443+ "node_modules/@emotion/memoize": {
444+ "version": "0.7.5",
445+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
446+ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
447+ },
448+ "node_modules/@emotion/react": {
449+ "version": "11.9.3",
450+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.3.tgz",
451+ "integrity": "sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==",
452+ "dependencies": {
453+ "@babel/runtime": "^7.13.10",
454+ "@emotion/babel-plugin": "^11.7.1",
455+ "@emotion/cache": "^11.9.3",
456+ "@emotion/serialize": "^1.0.4",
457+ "@emotion/utils": "^1.1.0",
458+ "@emotion/weak-memoize": "^0.2.5",
459+ "hoist-non-react-statics": "^3.3.1"
460+ },
461+ "peerDependencies": {
462+ "@babel/core": "^7.0.0",
463+ "react": ">=16.8.0"
464+ },
465+ "peerDependenciesMeta": {
466+ "@babel/core": {
467+ "optional": true
468+ },
469+ "@types/react": {
470+ "optional": true
471+ }
472+ }
473+ },
474+ "node_modules/@emotion/serialize": {
475+ "version": "1.0.4",
476+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.4.tgz",
477+ "integrity": "sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==",
478+ "dependencies": {
479+ "@emotion/hash": "^0.8.0",
480+ "@emotion/memoize": "^0.7.4",
481+ "@emotion/unitless": "^0.7.5",
482+ "@emotion/utils": "^1.0.0",
483+ "csstype": "^3.0.2"
484+ }
485+ },
486+ "node_modules/@emotion/sheet": {
487+ "version": "1.1.1",
488+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.1.tgz",
489+ "integrity": "sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA=="
490+ },
491+ "node_modules/@emotion/unitless": {
492+ "version": "0.7.5",
493+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
494+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
495+ },
496+ "node_modules/@emotion/utils": {
497+ "version": "1.1.0",
498+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz",
499+ "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
500+ },
501+ "node_modules/@emotion/weak-memoize": {
502+ "version": "0.2.5",
503+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
504+ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
505+ },
392506 "node_modules/@heroicons/react": {
393507 "version": "1.0.6",
394508 "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz",
@@ -401,7 +515,6 @@
401515 "version": "0.1.1",
402516 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
403517 "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
404− "dev": true,
405518 "dependencies": {
406519 "@jridgewell/set-array": "^1.0.0",
407520 "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -414,7 +527,6 @@
414527 "version": "3.0.8",
415528 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz",
416529 "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==",
417− "dev": true,
418530 "engines": {
419531 "node": ">=6.0.0"
420532 }
@@ -423,7 +535,6 @@
423535 "version": "1.1.2",
424536 "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
425537 "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
426− "dev": true,
427538 "engines": {
428539 "node": ">=6.0.0"
429540 }
@@ -431,14 +542,12 @@
431542 "node_modules/@jridgewell/sourcemap-codec": {
432543 "version": "1.4.14",
433544 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
434− "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
435− "dev": true
545+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
436546 },
437547 "node_modules/@jridgewell/trace-mapping": {
438548 "version": "0.3.14",
439549 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
440550 "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
441− "dev": true,
442551 "dependencies": {
443552 "@jridgewell/resolve-uri": "^3.0.3",
444553 "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -484,6 +593,14 @@
484593 "node": ">= 8"
485594 }
486595 },
596+ "node_modules/@radix-ui/number": {
597+ "version": "0.1.0",
598+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-0.1.0.tgz",
599+ "integrity": "sha512-rpf6QiOWLHAkM4FEMYu9i+5Jr8cKT893+R4mPpcdsy4LD7omr9JfdOqj/h/xPA5+EcVrpMMlU6rrRYpUB5UI8g==",
600+ "dependencies": {
601+ "@babel/runtime": "^7.13.10"
602+ }
603+ },
487604 "node_modules/@radix-ui/popper": {
488605 "version": "0.1.0",
489606 "resolved": "https://registry.npmjs.org/@radix-ui/popper/-/popper-0.1.0.tgz",
@@ -623,6 +740,21 @@
623740 "react": "^16.8 || ^17.0"
624741 }
625742 },
743+ "node_modules/@radix-ui/react-label": {
744+ "version": "0.1.5",
745+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-0.1.5.tgz",
746+ "integrity": "sha512-Au9+n4/DhvjR0IHhvZ1LPdx/OW+3CGDie30ZyCkbSHIuLp4/CV4oPPGBwJ1vY99Jog3zyQhsGww9MXj8O9Aj/A==",
747+ "dependencies": {
748+ "@babel/runtime": "^7.13.10",
749+ "@radix-ui/react-compose-refs": "0.1.0",
750+ "@radix-ui/react-context": "0.1.1",
751+ "@radix-ui/react-id": "0.1.5",
752+ "@radix-ui/react-primitive": "0.1.4"
753+ },
754+ "peerDependencies": {
755+ "react": "^16.8 || ^17.0"
756+ }
757+ },
626758 "node_modules/@radix-ui/react-menu": {
627759 "version": "0.1.6",
628760 "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-0.1.6.tgz",
@@ -729,6 +861,36 @@
729861 "react": "^16.8 || ^17.0"
730862 }
731863 },
864+ "node_modules/@radix-ui/react-select": {
865+ "version": "0.1.1",
866+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-0.1.1.tgz",
867+ "integrity": "sha512-xY3jLz2c6ZBHflldGsA79bE/swUfRMpiRPQf+JDihOufsd3z5uW22PFe0MS5bGZiIpK6aAZAvwqEd2Bu7hqp8w==",
868+ "dependencies": {
869+ "@babel/runtime": "^7.13.10",
870+ "@radix-ui/number": "0.1.0",
871+ "@radix-ui/primitive": "0.1.0",
872+ "@radix-ui/react-collection": "0.1.4",
873+ "@radix-ui/react-compose-refs": "0.1.0",
874+ "@radix-ui/react-context": "0.1.1",
875+ "@radix-ui/react-dismissable-layer": "0.1.5",
876+ "@radix-ui/react-focus-scope": "0.1.4",
877+ "@radix-ui/react-id": "0.1.5",
878+ "@radix-ui/react-label": "0.1.5",
879+ "@radix-ui/react-portal": "0.1.4",
880+ "@radix-ui/react-primitive": "0.1.4",
881+ "@radix-ui/react-use-callback-ref": "0.1.0",
882+ "@radix-ui/react-use-controllable-state": "0.1.0",
883+ "@radix-ui/react-use-layout-effect": "0.1.0",
884+ "@radix-ui/react-use-previous": "0.1.1",
885+ "@radix-ui/react-visually-hidden": "0.1.4",
886+ "aria-hidden": "^1.1.1",
887+ "react-remove-scroll": "^2.4.0"
888+ },
889+ "peerDependencies": {
890+ "react": "^16.8 || ^17.0",
891+ "react-dom": "^16.8 || ^17.0"
892+ }
893+ },
732894 "node_modules/@radix-ui/react-slot": {
733895 "version": "0.1.2",
734896 "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-0.1.2.tgz",
@@ -841,6 +1003,17 @@
8411003 "react": "^16.8 || ^17.0"
8421004 }
8431005 },
1006+ "node_modules/@radix-ui/react-use-previous": {
1007+ "version": "0.1.1",
1008+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz",
1009+ "integrity": "sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg==",
1010+ "dependencies": {
1011+ "@babel/runtime": "^7.13.10"
1012+ },
1013+ "peerDependencies": {
1014+ "react": "^16.8 || ^17.0"
1015+ }
1016+ },
8441017 "node_modules/@radix-ui/react-use-rect": {
8451018 "version": "0.1.1",
8461019 "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz",
@@ -864,6 +1037,18 @@
8641037 "react": "^16.8 || ^17.0"
8651038 }
8661039 },
1040+ "node_modules/@radix-ui/react-visually-hidden": {
1041+ "version": "0.1.4",
1042+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz",
1043+ "integrity": "sha512-K/q6AEEzqeeEq/T0NPChvBqnwlp8Tl4NnQdrI/y8IOY7BRR+Ug0PEsVk6g48HJ7cA1//COugdxXXVVK/m0X1mA==",
1044+ "dependencies": {
1045+ "@babel/runtime": "^7.13.10",
1046+ "@radix-ui/react-primitive": "0.1.4"
1047+ },
1048+ "peerDependencies": {
1049+ "react": "^16.8 || ^17.0"
1050+ }
1051+ },
8671052 "node_modules/@radix-ui/rect": {
8681053 "version": "0.1.1",
8691054 "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-0.1.1.tgz",
@@ -885,6 +1070,49 @@
8851070 "node": ">= 8.0.0"
8861071 }
8871072 },
1073+ "node_modules/@tanstack/query-core": {
1074+ "version": "4.29.5",
1075+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz",
1076+ "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ==",
1077+ "funding": {
1078+ "type": "github",
1079+ "url": "https://github.com/sponsors/tannerlinsley"
1080+ }
1081+ },
1082+ "node_modules/@tanstack/react-query": {
1083+ "version": "4.29.5",
1084+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz",
1085+ "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==",
1086+ "dependencies": {
1087+ "@tanstack/query-core": "4.29.5",
1088+ "use-sync-external-store": "^1.2.0"
1089+ },
1090+ "funding": {
1091+ "type": "github",
1092+ "url": "https://github.com/sponsors/tannerlinsley"
1093+ },
1094+ "peerDependencies": {
1095+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
1096+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
1097+ "react-native": "*"
1098+ },
1099+ "peerDependenciesMeta": {
1100+ "react-dom": {
1101+ "optional": true
1102+ },
1103+ "react-native": {
1104+ "optional": true
1105+ }
1106+ }
1107+ },
1108+ "node_modules/@tanstack/react-query/node_modules/use-sync-external-store": {
1109+ "version": "1.2.0",
1110+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
1111+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
1112+ "peerDependencies": {
1113+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
1114+ }
1115+ },
8881116 "node_modules/@types/lodash": {
8891117 "version": "4.14.182",
8901118 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@@ -900,17 +1128,20 @@
9001128 "@types/lodash": "*"
9011129 }
9021130 },
1131+ "node_modules/@types/parse-json": {
1132+ "version": "4.0.0",
1133+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
1134+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
1135+ },
9031136 "node_modules/@types/prop-types": {
9041137 "version": "15.7.5",
9051138 "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
906− "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
907− "devOptional": true
1139+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
9081140 },
9091141 "node_modules/@types/react": {
9101142 "version": "17.0.47",
9111143 "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.47.tgz",
9121144 "integrity": "sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA==",
913− "devOptional": true,
9141145 "dependencies": {
9151146 "@types/prop-types": "*",
9161147 "@types/scheduler": "*",
@@ -926,16 +1157,23 @@
9261157 "@types/react": "^17"
9271158 }
9281159 },
1160+ "node_modules/@types/react-transition-group": {
1161+ "version": "4.4.5",
1162+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
1163+ "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
1164+ "dependencies": {
1165+ "@types/react": "*"
1166+ }
1167+ },
9291168 "node_modules/@types/scheduler": {
9301169 "version": "0.16.2",
9311170 "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
932− "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
933− "devOptional": true
1171+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
9341172 },
9351173 "node_modules/@urbit/api": {
936− "version": "2.1.1",
937− "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz",
938− "integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==",
1174+ "version": "2.2.0",
1175+ "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
1176+ "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
9391177 "dependencies": {
9401178 "@babel/runtime": "^7.16.0",
9411179 "big-integer": "^1.6.48",
@@ -1018,7 +1256,6 @@
10181256 "version": "3.2.1",
10191257 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
10201258 "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
1021− "dev": true,
10221259 "dependencies": {
10231260 "color-convert": "^1.9.0"
10241261 },
@@ -1089,10 +1326,15 @@
10891326 "postcss": "^8.1.0"
10901327 }
10911328 },
1092− "node_modules/balanced-match": {
1093− "version": "1.0.2",
1094− "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
1095− "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
1329+ "node_modules/babel-plugin-macros": {
1330+ "version": "2.8.0",
1331+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
1332+ "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
1333+ "dependencies": {
1334+ "@babel/runtime": "^7.7.2",
1335+ "cosmiconfig": "^6.0.0",
1336+ "resolve": "^1.12.0"
1337+ }
10961338 },
10971339 "node_modules/big-integer": {
10981340 "version": "1.6.51",
@@ -1116,15 +1358,6 @@
11161358 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
11171359 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
11181360 },
1119− "node_modules/brace-expansion": {
1120− "version": "1.1.11",
1121− "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
1122− "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
1123− "dependencies": {
1124− "balanced-match": "^1.0.0",
1125− "concat-map": "0.0.1"
1126− }
1127− },
11281361 "node_modules/braces": {
11291362 "version": "3.0.2",
11301363 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -1137,21 +1370,6 @@
11371370 "node": ">=8"
11381371 }
11391372 },
1140− "node_modules/broadcast-channel": {
1141− "version": "3.7.0",
1142− "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
1143− "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
1144− "dependencies": {
1145− "@babel/runtime": "^7.7.2",
1146− "detect-node": "^2.1.0",
1147− "js-sha3": "0.8.0",
1148− "microseconds": "0.2.0",
1149− "nano-time": "1.0.0",
1150− "oblivious-set": "1.0.0",
1151− "rimraf": "3.0.2",
1152− "unload": "2.2.0"
1153− }
1154− },
11551373 "node_modules/browser-or-node": {
11561374 "version": "1.3.0",
11571375 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
@@ -1161,7 +1379,6 @@
11611379 "version": "4.21.1",
11621380 "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz",
11631381 "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==",
1164− "dev": true,
11651382 "funding": [
11661383 {
11671384 "type": "opencollective",
@@ -1185,6 +1402,14 @@
11851402 "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
11861403 }
11871404 },
1405+ "node_modules/callsites": {
1406+ "version": "3.1.0",
1407+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
1408+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
1409+ "engines": {
1410+ "node": ">=6"
1411+ }
1412+ },
11881413 "node_modules/camelcase-css": {
11891414 "version": "2.0.1",
11901415 "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -1198,7 +1423,6 @@
11981423 "version": "1.0.30001361",
11991424 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz",
12001425 "integrity": "sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==",
1201− "dev": true,
12021426 "funding": [
12031427 {
12041428 "type": "opencollective",
@@ -1214,7 +1438,6 @@
12141438 "version": "2.4.2",
12151439 "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
12161440 "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
1217− "dev": true,
12181441 "dependencies": {
12191442 "ansi-styles": "^3.2.1",
12201443 "escape-string-regexp": "^1.0.5",
@@ -1272,7 +1495,6 @@
12721495 "version": "1.9.3",
12731496 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
12741497 "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
1275− "dev": true,
12761498 "dependencies": {
12771499 "color-name": "1.1.3"
12781500 }
@@ -1280,13 +1502,12 @@
12801502 "node_modules/color-name": {
12811503 "version": "1.1.3",
12821504 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
1283− "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
1284− "dev": true
1505+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
12851506 },
1286− "node_modules/concat-map": {
1287− "version": "0.0.1",
1288− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1289− "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
1507+ "node_modules/color2k": {
1508+ "version": "2.0.2",
1509+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
1510+ "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
12901511 },
12911512 "node_modules/connect-history-api-fallback": {
12921513 "version": "1.6.0",
@@ -1301,7 +1522,6 @@
13011522 "version": "1.8.0",
13021523 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
13031524 "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
1304− "dev": true,
13051525 "dependencies": {
13061526 "safe-buffer": "~5.1.1"
13071527 }
@@ -1316,6 +1536,21 @@
13161536 "url": "https://opencollective.com/core-js"
13171537 }
13181538 },
1539+ "node_modules/cosmiconfig": {
1540+ "version": "6.0.0",
1541+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
1542+ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
1543+ "dependencies": {
1544+ "@types/parse-json": "^4.0.0",
1545+ "import-fresh": "^3.1.0",
1546+ "parse-json": "^5.0.0",
1547+ "path-type": "^4.0.0",
1548+ "yaml": "^1.7.2"
1549+ },
1550+ "engines": {
1551+ "node": ">=8"
1552+ }
1553+ },
13191554 "node_modules/cssesc": {
13201555 "version": "3.0.0",
13211556 "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1337,7 +1572,6 @@
13371572 "version": "4.3.4",
13381573 "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
13391574 "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
1340− "dev": true,
13411575 "dependencies": {
13421576 "ms": "2.1.2"
13431577 },
@@ -1350,17 +1584,20 @@
13501584 }
13511585 }
13521586 },
1587+ "node_modules/decode-uri-component": {
1588+ "version": "0.2.0",
1589+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
1590+ "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
1591+ "engines": {
1592+ "node": ">=0.10"
1593+ }
1594+ },
13531595 "node_modules/defined": {
13541596 "version": "1.0.0",
13551597 "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
13561598 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
13571599 "dev": true
13581600 },
1359− "node_modules/detect-node": {
1360− "version": "2.1.0",
1361− "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
1362− "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
1363− },
13641601 "node_modules/detect-node-es": {
13651602 "version": "1.1.0",
13661603 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -1395,11 +1632,27 @@
13951632 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
13961633 "dev": true
13971634 },
1635+ "node_modules/dom-helpers": {
1636+ "version": "5.2.1",
1637+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
1638+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
1639+ "dependencies": {
1640+ "@babel/runtime": "^7.8.7",
1641+ "csstype": "^3.0.2"
1642+ }
1643+ },
13981644 "node_modules/electron-to-chromium": {
13991645 "version": "1.4.176",
14001646 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.176.tgz",
1401− "integrity": "sha512-92JdgyRlcNDwuy75MjuFSb3clt6DGJ2IXSpg0MCjKd3JV9eSmuUAIyWiGAp/EtT0z2D4rqbYqThQLV90maH3Zw==",
1402− "dev": true
1647+ "integrity": "sha512-92JdgyRlcNDwuy75MjuFSb3clt6DGJ2IXSpg0MCjKd3JV9eSmuUAIyWiGAp/EtT0z2D4rqbYqThQLV90maH3Zw=="
1648+ },
1649+ "node_modules/error-ex": {
1650+ "version": "1.3.2",
1651+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
1652+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
1653+ "dependencies": {
1654+ "is-arrayish": "^0.2.1"
1655+ }
14031656 },
14041657 "node_modules/esbuild": {
14051658 "version": "0.14.48",
@@ -1760,7 +2013,6 @@
17602013 "version": "3.1.1",
17612014 "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
17622015 "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
1763− "dev": true,
17642016 "engines": {
17652017 "node": ">=6"
17662018 }
@@ -1769,7 +2021,6 @@
17692021 "version": "1.0.5",
17702022 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
17712023 "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
1772− "dev": true,
17732024 "engines": {
17742025 "node": ">=0.8.0"
17752026 }
@@ -1829,6 +2080,19 @@
18292080 "node": ">=8"
18302081 }
18312082 },
2083+ "node_modules/filter-obj": {
2084+ "version": "1.1.0",
2085+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
2086+ "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
2087+ "engines": {
2088+ "node": ">=0.10.0"
2089+ }
2090+ },
2091+ "node_modules/find-root": {
2092+ "version": "1.1.0",
2093+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
2094+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
2095+ },
18322096 "node_modules/fraction.js": {
18332097 "version": "4.2.0",
18342098 "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
@@ -1842,11 +2106,6 @@
18422106 "url": "https://www.patreon.com/infusion"
18432107 }
18442108 },
1845− "node_modules/fs.realpath": {
1846− "version": "1.0.0",
1847− "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1848− "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
1849− },
18502109 "node_modules/fsevents": {
18512110 "version": "2.3.2",
18522111 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -1864,14 +2123,12 @@
18642123 "node_modules/function-bind": {
18652124 "version": "1.1.1",
18662125 "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
1867− "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
1868− "dev": true
2126+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
18692127 },
18702128 "node_modules/gensync": {
18712129 "version": "1.0.0-beta.2",
18722130 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
18732131 "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1874− "dev": true,
18752132 "engines": {
18762133 "node": ">=6.9.0"
18772134 }
@@ -1884,25 +2141,6 @@
18842141 "node": ">=6"
18852142 }
18862143 },
1887− "node_modules/glob": {
1888− "version": "7.2.3",
1889− "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
1890− "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
1891− "dependencies": {
1892− "fs.realpath": "^1.0.0",
1893− "inflight": "^1.0.4",
1894− "inherits": "2",
1895− "minimatch": "^3.1.1",
1896− "once": "^1.3.0",
1897− "path-is-absolute": "^1.0.0"
1898− },
1899− "engines": {
1900− "node": "*"
1901− },
1902− "funding": {
1903− "url": "https://github.com/sponsors/isaacs"
1904− }
1905− },
19062144 "node_modules/glob-parent": {
19072145 "version": "6.0.2",
19082146 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -1919,7 +2157,6 @@
19192157 "version": "11.12.0",
19202158 "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
19212159 "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
1922− "dev": true,
19232160 "engines": {
19242161 "node": ">=4"
19252162 }
@@ -1928,7 +2165,6 @@
19282165 "version": "1.0.3",
19292166 "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
19302167 "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
1931− "dev": true,
19322168 "dependencies": {
19332169 "function-bind": "^1.1.1"
19342170 },
@@ -1940,7 +2176,6 @@
19402176 "version": "3.0.0",
19412177 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
19422178 "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
1943− "dev": true,
19442179 "engines": {
19452180 "node": ">=4"
19462181 }
@@ -1953,6 +2188,14 @@
19532188 "@babel/runtime": "^7.7.6"
19542189 }
19552190 },
2191+ "node_modules/hoist-non-react-statics": {
2192+ "version": "3.3.2",
2193+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
2194+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
2195+ "dependencies": {
2196+ "react-is": "^16.7.0"
2197+ }
2198+ },
19562199 "node_modules/immer": {
19572200 "version": "9.0.15",
19582201 "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
@@ -1962,20 +2205,21 @@
19622205 "url": "https://opencollective.com/immer"
19632206 }
19642207 },
1965− "node_modules/inflight": {
1966− "version": "1.0.6",
1967− "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1968− "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
2208+ "node_modules/import-fresh": {
2209+ "version": "3.3.0",
2210+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
2211+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
19692212 "dependencies": {
1970− "once": "^1.3.0",
1971− "wrappy": "1"
2213+ "parent-module": "^1.0.0",
2214+ "resolve-from": "^4.0.0"
2215+ },
2216+ "engines": {
2217+ "node": ">=6"
2218+ },
2219+ "funding": {
2220+ "url": "https://github.com/sponsors/sindresorhus"
19722221 }
19732222 },
1974− "node_modules/inherits": {
1975− "version": "2.0.4",
1976− "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1977− "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
1978− },
19792223 "node_modules/invariant": {
19802224 "version": "2.2.4",
19812225 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -1984,6 +2228,11 @@
19842228 "loose-envify": "^1.0.0"
19852229 }
19862230 },
2231+ "node_modules/is-arrayish": {
2232+ "version": "0.2.1",
2233+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
2234+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
2235+ },
19872236 "node_modules/is-binary-path": {
19882237 "version": "2.1.0",
19892238 "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -2000,7 +2249,6 @@
20002249 "version": "2.9.0",
20012250 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
20022251 "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
2003− "dev": true,
20042252 "dependencies": {
20052253 "has": "^1.0.3"
20062254 },
@@ -2038,11 +2286,6 @@
20382286 "node": ">=0.12.0"
20392287 }
20402288 },
2041− "node_modules/js-sha3": {
2042− "version": "0.8.0",
2043− "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
2044− "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
2045− },
20462289 "node_modules/js-tokens": {
20472290 "version": "4.0.0",
20482291 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -2052,7 +2295,6 @@
20522295 "version": "2.5.2",
20532296 "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
20542297 "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
2055− "dev": true,
20562298 "bin": {
20572299 "jsesc": "bin/jsesc"
20582300 },
@@ -2060,11 +2302,15 @@
20602302 "node": ">=4"
20612303 }
20622304 },
2305+ "node_modules/json-parse-even-better-errors": {
2306+ "version": "2.3.1",
2307+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
2308+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
2309+ },
20632310 "node_modules/json5": {
20642311 "version": "2.2.1",
20652312 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
20662313 "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
2067− "dev": true,
20682314 "bin": {
20692315 "json5": "lib/cli.js"
20702316 },
@@ -2081,6 +2327,11 @@
20812327 "node": ">=10"
20822328 }
20832329 },
2330+ "node_modules/lines-and-columns": {
2331+ "version": "1.2.4",
2332+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
2333+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
2334+ },
20842335 "node_modules/lodash.chunk": {
20852336 "version": "4.2.0",
20862337 "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
@@ -2107,14 +2358,10 @@
21072358 "loose-envify": "cli.js"
21082359 }
21092360 },
2110− "node_modules/match-sorter": {
2111− "version": "6.3.1",
2112− "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
2113− "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
2114− "dependencies": {
2115− "@babel/runtime": "^7.12.5",
2116− "remove-accents": "0.4.2"
2117− }
2361+ "node_modules/memoize-one": {
2362+ "version": "5.2.1",
2363+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
2364+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
21182365 },
21192366 "node_modules/merge2": {
21202367 "version": "1.4.1",
@@ -2138,22 +2385,6 @@
21382385 "node": ">=8.6"
21392386 }
21402387 },
2141− "node_modules/microseconds": {
2142− "version": "0.2.0",
2143− "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
2144− "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
2145− },
2146− "node_modules/minimatch": {
2147− "version": "3.1.2",
2148− "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
2149− "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
2150− "dependencies": {
2151− "brace-expansion": "^1.1.7"
2152− },
2153− "engines": {
2154− "node": "*"
2155− }
2156− },
21572388 "node_modules/minimist": {
21582389 "version": "1.2.6",
21592390 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@@ -2163,16 +2394,7 @@
21632394 "node_modules/ms": {
21642395 "version": "2.1.2",
21652396 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
2166− "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
2167− "dev": true
2168− },
2169− "node_modules/nano-time": {
2170− "version": "1.0.0",
2171− "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
2172− "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
2173− "dependencies": {
2174− "big-integer": "^1.6.16"
2175− }
2397+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
21762398 },
21772399 "node_modules/nanoid": {
21782400 "version": "3.3.4",
@@ -2189,8 +2411,7 @@
21892411 "node_modules/node-releases": {
21902412 "version": "2.0.5",
21912413 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
2192− "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==",
2193− "dev": true
2414+ "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q=="
21942415 },
21952416 "node_modules/normalize-path": {
21962417 "version": "3.0.0",
@@ -2227,38 +2448,51 @@
22272448 "node": ">= 6"
22282449 }
22292450 },
2230− "node_modules/oblivious-set": {
2231− "version": "1.0.0",
2232− "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
2233− "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
2234− },
2235− "node_modules/once": {
2236− "version": "1.4.0",
2237− "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
2238− "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
2451+ "node_modules/parent-module": {
2452+ "version": "1.0.1",
2453+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
2454+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
22392455 "dependencies": {
2240− "wrappy": "1"
2456+ "callsites": "^3.0.0"
2457+ },
2458+ "engines": {
2459+ "node": ">=6"
22412460 }
22422461 },
2243− "node_modules/path-is-absolute": {
2244− "version": "1.0.1",
2245− "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
2246− "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
2462+ "node_modules/parse-json": {
2463+ "version": "5.2.0",
2464+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
2465+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
2466+ "dependencies": {
2467+ "@babel/code-frame": "^7.0.0",
2468+ "error-ex": "^1.3.1",
2469+ "json-parse-even-better-errors": "^2.3.0",
2470+ "lines-and-columns": "^1.1.6"
2471+ },
22472472 "engines": {
2248− "node": ">=0.10.0"
2473+ "node": ">=8"
2474+ },
2475+ "funding": {
2476+ "url": "https://github.com/sponsors/sindresorhus"
22492477 }
22502478 },
22512479 "node_modules/path-parse": {
22522480 "version": "1.0.7",
22532481 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
2254− "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
2255− "dev": true
2482+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
2483+ },
2484+ "node_modules/path-type": {
2485+ "version": "4.0.0",
2486+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
2487+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
2488+ "engines": {
2489+ "node": ">=8"
2490+ }
22562491 },
22572492 "node_modules/picocolors": {
22582493 "version": "1.0.0",
22592494 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
2260− "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
2261− "dev": true
2495+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
22622496 },
22632497 "node_modules/picomatch": {
22642498 "version": "2.3.1",
@@ -2408,6 +2642,33 @@
24082642 "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
24092643 "dev": true
24102644 },
2645+ "node_modules/prop-types": {
2646+ "version": "15.8.1",
2647+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
2648+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
2649+ "dependencies": {
2650+ "loose-envify": "^1.4.0",
2651+ "object-assign": "^4.1.1",
2652+ "react-is": "^16.13.1"
2653+ }
2654+ },
2655+ "node_modules/query-string": {
2656+ "version": "7.1.1",
2657+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
2658+ "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
2659+ "dependencies": {
2660+ "decode-uri-component": "^0.2.0",
2661+ "filter-obj": "^1.1.0",
2662+ "split-on-first": "^1.0.0",
2663+ "strict-uri-encode": "^2.0.0"
2664+ },
2665+ "engines": {
2666+ "node": ">=6"
2667+ },
2668+ "funding": {
2669+ "url": "https://github.com/sponsors/sindresorhus"
2670+ }
2671+ },
24112672 "node_modules/queue-microtask": {
24122673 "version": "1.2.3",
24132674 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2465,31 +2726,26 @@
24652726 "react": "17.0.2"
24662727 }
24672728 },
2468− "node_modules/react-query": {
2469− "version": "3.39.1",
2470− "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.1.tgz",
2471− "integrity": "sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A==",
2472− "dependencies": {
2473− "@babel/runtime": "^7.5.5",
2474− "broadcast-channel": "^3.4.1",
2475− "match-sorter": "^6.0.2"
2729+ "node_modules/react-hook-form": {
2730+ "version": "7.33.1",
2731+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.33.1.tgz",
2732+ "integrity": "sha512-ydTfTxEJdvgjCZBj5DDXRc58oTEfnFupEwwTAQ9FSKzykEJkX+3CiAkGtAMiZG7IPWHuzgT6AOBfogiKhUvKgg==",
2733+ "engines": {
2734+ "node": ">=12.22.0"
24762735 },
24772736 "funding": {
2478− "type": "github",
2479− "url": "https://github.com/sponsors/tannerlinsley"
2737+ "type": "opencollective",
2738+ "url": "https://opencollective.com/react-hook-form"
24802739 },
24812740 "peerDependencies": {
2482− "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
2483− },
2484− "peerDependenciesMeta": {
2485− "react-dom": {
2486− "optional": true
2487− },
2488− "react-native": {
2489− "optional": true
2490− }
2741+ "react": "^16.8.0 || ^17 || ^18"
24912742 }
24922743 },
2744+ "node_modules/react-is": {
2745+ "version": "16.13.1",
2746+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
2747+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
2748+ },
24932749 "node_modules/react-refresh": {
24942750 "version": "0.10.0",
24952751 "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz",
@@ -2578,6 +2834,24 @@
25782834 "react-dom": ">=16.8"
25792835 }
25802836 },
2837+ "node_modules/react-select": {
2838+ "version": "5.4.0",
2839+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.4.0.tgz",
2840+ "integrity": "sha512-CjE9RFLUvChd5SdlfG4vqxZd55AZJRrLrHzkQyTYeHlpOztqcgnyftYAolJ0SGsBev6zAs6qFrjm6KU3eo2hzg==",
2841+ "dependencies": {
2842+ "@babel/runtime": "^7.12.0",
2843+ "@emotion/cache": "^11.4.0",
2844+ "@emotion/react": "^11.8.1",
2845+ "@types/react-transition-group": "^4.4.0",
2846+ "memoize-one": "^5.0.0",
2847+ "prop-types": "^15.6.0",
2848+ "react-transition-group": "^4.3.0"
2849+ },
2850+ "peerDependencies": {
2851+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
2852+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
2853+ }
2854+ },
25812855 "node_modules/react-style-singleton": {
25822856 "version": "2.2.1",
25832857 "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@@ -2605,6 +2879,21 @@
26052879 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
26062880 "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
26072881 },
2882+ "node_modules/react-transition-group": {
2883+ "version": "4.4.2",
2884+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
2885+ "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
2886+ "dependencies": {
2887+ "@babel/runtime": "^7.5.5",
2888+ "dom-helpers": "^5.0.1",
2889+ "loose-envify": "^1.4.0",
2890+ "prop-types": "^15.6.2"
2891+ },
2892+ "peerDependencies": {
2893+ "react": ">=16.6.0",
2894+ "react-dom": ">=16.6.0"
2895+ }
2896+ },
26082897 "node_modules/read-cache": {
26092898 "version": "1.0.0",
26102899 "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -2631,16 +2920,15 @@
26312920 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
26322921 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
26332922 },
2634− "node_modules/remove-accents": {
2635− "version": "0.4.2",
2636− "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
2637− "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
2923+ "node_modules/remove-markdown-and-html": {
2924+ "version": "0.1.0",
2925+ "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
2926+ "integrity": "sha512-Sy03RJ/R/FBbK6wutBKKIyEVMQS+TzSpGc8pBxwrh0LiqyVIaD/LkitUABIJJ59iryhvrPTmS8dzd3DoLdopHg=="
26382927 },
26392928 "node_modules/resolve": {
26402929 "version": "1.22.1",
26412930 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
26422931 "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
2643− "dev": true,
26442932 "dependencies": {
26452933 "is-core-module": "^2.9.0",
26462934 "path-parse": "^1.0.7",
@@ -2653,6 +2941,14 @@
26532941 "url": "https://github.com/sponsors/ljharb"
26542942 }
26552943 },
2944+ "node_modules/resolve-from": {
2945+ "version": "4.0.0",
2946+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
2947+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
2948+ "engines": {
2949+ "node": ">=4"
2950+ }
2951+ },
26562952 "node_modules/reusify": {
26572953 "version": "1.0.4",
26582954 "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -2663,20 +2959,6 @@
26632959 "node": ">=0.10.0"
26642960 }
26652961 },
2666− "node_modules/rimraf": {
2667− "version": "3.0.2",
2668− "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
2669− "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
2670− "dependencies": {
2671− "glob": "^7.1.3"
2672− },
2673− "bin": {
2674− "rimraf": "bin.js"
2675− },
2676− "funding": {
2677− "url": "https://github.com/sponsors/isaacs"
2678− }
2679− },
26802962 "node_modules/rollup": {
26812963 "version": "2.75.7",
26822964 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
@@ -2718,8 +3000,7 @@
27183000 "node_modules/safe-buffer": {
27193001 "version": "5.1.2",
27203002 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
2721− "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
2722− "dev": true
3003+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
27233004 },
27243005 "node_modules/scheduler": {
27253006 "version": "0.20.2",
@@ -2734,11 +3015,18 @@
27343015 "version": "6.3.0",
27353016 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
27363017 "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
2737− "dev": true,
27383018 "bin": {
27393019 "semver": "bin/semver.js"
27403020 }
27413021 },
3022+ "node_modules/source-map": {
3023+ "version": "0.5.7",
3024+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
3025+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
3026+ "engines": {
3027+ "node": ">=0.10.0"
3028+ }
3029+ },
27423030 "node_modules/source-map-js": {
27433031 "version": "1.0.2",
27443032 "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -2748,11 +3036,31 @@
27483036 "node": ">=0.10.0"
27493037 }
27503038 },
3039+ "node_modules/split-on-first": {
3040+ "version": "1.1.0",
3041+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
3042+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
3043+ "engines": {
3044+ "node": ">=6"
3045+ }
3046+ },
3047+ "node_modules/strict-uri-encode": {
3048+ "version": "2.0.0",
3049+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
3050+ "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
3051+ "engines": {
3052+ "node": ">=4"
3053+ }
3054+ },
3055+ "node_modules/stylis": {
3056+ "version": "4.0.13",
3057+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
3058+ "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
3059+ },
27513060 "node_modules/supports-color": {
27523061 "version": "5.5.0",
27533062 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
27543063 "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
2755− "dev": true,
27563064 "dependencies": {
27573065 "has-flag": "^3.0.0"
27583066 },
@@ -2764,7 +3072,6 @@
27643072 "version": "1.0.0",
27653073 "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
27663074 "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
2767− "dev": true,
27683075 "engines": {
27693076 "node": ">= 0.4"
27703077 },
@@ -2812,6 +3119,15 @@
28123119 "postcss": "^8.0.9"
28133120 }
28143121 },
3122+ "node_modules/tailwindcss-labeled-groups": {
3123+ "version": "0.0.2",
3124+ "resolved": "https://registry.npmjs.org/tailwindcss-labeled-groups/-/tailwindcss-labeled-groups-0.0.2.tgz",
3125+ "integrity": "sha512-leOVv9h2K9WDHoXPXVBOmTJgWECVkpVFc/hjrwuqGUEtNxvRvuOtCjenu9ppY+pJtky5INJUqDCNa8pS3AbYmw==",
3126+ "dev": true,
3127+ "peerDependencies": {
3128+ "tailwindcss": "^3.0.23"
3129+ }
3130+ },
28153131 "node_modules/tailwindcss/node_modules/color-name": {
28163132 "version": "1.1.4",
28173133 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
@@ -2822,7 +3138,6 @@
28223138 "version": "2.0.0",
28233139 "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
28243140 "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
2825− "dev": true,
28263141 "engines": {
28273142 "node": ">=4"
28283143 }
@@ -2857,20 +3172,10 @@
28573172 "node": ">=4.2.0"
28583173 }
28593174 },
2860− "node_modules/unload": {
2861− "version": "2.2.0",
2862− "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
2863− "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
2864− "dependencies": {
2865− "@babel/runtime": "^7.6.2",
2866− "detect-node": "^2.0.4"
2867− }
2868− },
28693175 "node_modules/update-browserslist-db": {
28703176 "version": "1.0.4",
28713177 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
28723178 "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==",
2873− "dev": true,
28743179 "funding": [
28753180 {
28763181 "type": "opencollective",
@@ -2953,6 +3258,14 @@
29533258 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
29543259 "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
29553260 },
3261+ "node_modules/use-sync-external-store": {
3262+ "version": "1.1.0",
3263+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz",
3264+ "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==",
3265+ "peerDependencies": {
3266+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
3267+ }
3268+ },
29563269 "node_modules/util-deprecate": {
29573270 "version": "1.0.2",
29583271 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -3023,11 +3336,6 @@
30233336 "vite": "^2.0.2"
30243337 }
30253338 },
3026− "node_modules/wrappy": {
3027− "version": "1.0.2",
3028− "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
3029− "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
3030− },
30313339 "node_modules/xtend": {
30323340 "version": "4.0.2",
30333341 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -3041,10 +3349,32 @@
30413349 "version": "1.10.2",
30423350 "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
30433351 "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
3044− "dev": true,
30453352 "engines": {
30463353 "node": ">= 6"
30473354 }
3355+ },
3356+ "node_modules/zustand": {
3357+ "version": "4.0.0-rc.1",
3358+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.0.0-rc.1.tgz",
3359+ "integrity": "sha512-qgcs7zLqBdHu0PuT3GW4WCIY5SgXdsv30GQMu9Qpp1BA2aS+sNS8l4x0hWuyEhjXkN+701aGWawhKDv6oWJAcw==",
3360+ "dependencies": {
3361+ "use-sync-external-store": "1.1.0"
3362+ },
3363+ "engines": {
3364+ "node": ">=12.7.0"
3365+ },
3366+ "peerDependencies": {
3367+ "immer": ">=9.0",
3368+ "react": ">=16.8"
3369+ },
3370+ "peerDependenciesMeta": {
3371+ "immer": {
3372+ "optional": true
3373+ },
3374+ "react": {
3375+ "optional": true
3376+ }
3377+ }
30483378 }
30493379 },
30503380 "dependencies": {
@@ -3052,7 +3382,6 @@
30523382 "version": "2.2.0",
30533383 "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
30543384 "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
3055− "dev": true,
30563385 "requires": {
30573386 "@jridgewell/gen-mapping": "^0.1.0",
30583387 "@jridgewell/trace-mapping": "^0.3.9"
@@ -3062,7 +3391,6 @@
30623391 "version": "7.18.6",
30633392 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
30643393 "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
3065− "dev": true,
30663394 "requires": {
30673395 "@babel/highlight": "^7.18.6"
30683396 }
@@ -3070,14 +3398,12 @@
30703398 "@babel/compat-data": {
30713399 "version": "7.18.6",
30723400 "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz",
3073− "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==",
3074− "dev": true
3401+ "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ=="
30753402 },
30763403 "@babel/core": {
30773404 "version": "7.18.6",
30783405 "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz",
30793406 "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==",
3080− "dev": true,
30813407 "requires": {
30823408 "@ampproject/remapping": "^2.1.0",
30833409 "@babel/code-frame": "^7.18.6",
@@ -3100,7 +3426,6 @@
31003426 "version": "7.18.7",
31013427 "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz",
31023428 "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==",
3103− "dev": true,
31043429 "requires": {
31053430 "@babel/types": "^7.18.7",
31063431 "@jridgewell/gen-mapping": "^0.3.2",
@@ -3111,7 +3436,6 @@
31113436 "version": "0.3.2",
31123437 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
31133438 "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
3114− "dev": true,
31153439 "requires": {
31163440 "@jridgewell/set-array": "^1.0.1",
31173441 "@jridgewell/sourcemap-codec": "^1.4.10",
@@ -3124,7 +3448,6 @@
31243448 "version": "7.18.6",
31253449 "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz",
31263450 "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==",
3127− "dev": true,
31283451 "requires": {
31293452 "@babel/compat-data": "^7.18.6",
31303453 "@babel/helper-validator-option": "^7.18.6",
@@ -3135,14 +3458,12 @@
31353458 "@babel/helper-environment-visitor": {
31363459 "version": "7.18.6",
31373460 "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz",
3138− "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==",
3139− "dev": true
3461+ "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q=="
31403462 },
31413463 "@babel/helper-function-name": {
31423464 "version": "7.18.6",
31433465 "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz",
31443466 "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==",
3145− "dev": true,
31463467 "requires": {
31473468 "@babel/template": "^7.18.6",
31483469 "@babel/types": "^7.18.6"
@@ -3152,7 +3473,6 @@
31523473 "version": "7.18.6",
31533474 "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
31543475 "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
3155− "dev": true,
31563476 "requires": {
31573477 "@babel/types": "^7.18.6"
31583478 }
@@ -3161,7 +3481,6 @@
31613481 "version": "7.18.6",
31623482 "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
31633483 "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
3164− "dev": true,
31653484 "requires": {
31663485 "@babel/types": "^7.18.6"
31673486 }
@@ -3170,7 +3489,6 @@
31703489 "version": "7.18.6",
31713490 "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz",
31723491 "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==",
3173− "dev": true,
31743492 "requires": {
31753493 "@babel/helper-environment-visitor": "^7.18.6",
31763494 "@babel/helper-module-imports": "^7.18.6",
@@ -3185,14 +3503,12 @@
31853503 "@babel/helper-plugin-utils": {
31863504 "version": "7.18.6",
31873505 "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz",
3188− "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==",
3189− "dev": true
3506+ "integrity": "sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg=="
31903507 },
31913508 "@babel/helper-simple-access": {
31923509 "version": "7.18.6",
31933510 "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz",
31943511 "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==",
3195− "dev": true,
31963512 "requires": {
31973513 "@babel/types": "^7.18.6"
31983514 }
@@ -3201,7 +3517,6 @@
32013517 "version": "7.18.6",
32023518 "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
32033519 "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
3204− "dev": true,
32053520 "requires": {
32063521 "@babel/types": "^7.18.6"
32073522 }
@@ -3209,20 +3524,17 @@
32093524 "@babel/helper-validator-identifier": {
32103525 "version": "7.18.6",
32113526 "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz",
3212− "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==",
3213− "dev": true
3527+ "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g=="
32143528 },
32153529 "@babel/helper-validator-option": {
32163530 "version": "7.18.6",
32173531 "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
3218− "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
3219− "dev": true
3532+ "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw=="
32203533 },
32213534 "@babel/helpers": {
32223535 "version": "7.18.6",
32233536 "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz",
32243537 "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==",
3225− "dev": true,
32263538 "requires": {
32273539 "@babel/template": "^7.18.6",
32283540 "@babel/traverse": "^7.18.6",
@@ -3233,7 +3545,6 @@
32333545 "version": "7.18.6",
32343546 "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
32353547 "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
3236− "dev": true,
32373548 "requires": {
32383549 "@babel/helper-validator-identifier": "^7.18.6",
32393550 "chalk": "^2.0.0",
@@ -3243,8 +3554,15 @@
32433554 "@babel/parser": {
32443555 "version": "7.18.6",
32453556 "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz",
3246− "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==",
3247− "dev": true
3557+ "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw=="
3558+ },
3559+ "@babel/plugin-syntax-jsx": {
3560+ "version": "7.18.6",
3561+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz",
3562+ "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==",
3563+ "requires": {
3564+ "@babel/helper-plugin-utils": "^7.18.6"
3565+ }
32483566 },
32493567 "@babel/plugin-transform-react-jsx-self": {
32503568 "version": "7.18.6",
@@ -3276,7 +3594,6 @@
32763594 "version": "7.18.6",
32773595 "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz",
32783596 "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==",
3279− "dev": true,
32803597 "requires": {
32813598 "@babel/code-frame": "^7.18.6",
32823599 "@babel/parser": "^7.18.6",
@@ -3287,7 +3604,6 @@
32873604 "version": "7.18.6",
32883605 "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz",
32893606 "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==",
3290− "dev": true,
32913607 "requires": {
32923608 "@babel/code-frame": "^7.18.6",
32933609 "@babel/generator": "^7.18.6",
@@ -3305,12 +3621,105 @@
33053621 "version": "7.18.7",
33063622 "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.7.tgz",
33073623 "integrity": "sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==",
3308− "dev": true,
33093624 "requires": {
33103625 "@babel/helper-validator-identifier": "^7.18.6",
33113626 "to-fast-properties": "^2.0.0"
33123627 }
33133628 },
3629+ "@emotion/babel-plugin": {
3630+ "version": "11.9.2",
3631+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz",
3632+ "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==",
3633+ "requires": {
3634+ "@babel/helper-module-imports": "^7.12.13",
3635+ "@babel/plugin-syntax-jsx": "^7.12.13",
3636+ "@babel/runtime": "^7.13.10",
3637+ "@emotion/hash": "^0.8.0",
3638+ "@emotion/memoize": "^0.7.5",
3639+ "@emotion/serialize": "^1.0.2",
3640+ "babel-plugin-macros": "^2.6.1",
3641+ "convert-source-map": "^1.5.0",
3642+ "escape-string-regexp": "^4.0.0",
3643+ "find-root": "^1.1.0",
3644+ "source-map": "^0.5.7",
3645+ "stylis": "4.0.13"
3646+ },
3647+ "dependencies": {
3648+ "escape-string-regexp": {
3649+ "version": "4.0.0",
3650+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
3651+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
3652+ }
3653+ }
3654+ },
3655+ "@emotion/cache": {
3656+ "version": "11.9.3",
3657+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.9.3.tgz",
3658+ "integrity": "sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==",
3659+ "requires": {
3660+ "@emotion/memoize": "^0.7.4",
3661+ "@emotion/sheet": "^1.1.1",
3662+ "@emotion/utils": "^1.0.0",
3663+ "@emotion/weak-memoize": "^0.2.5",
3664+ "stylis": "4.0.13"
3665+ }
3666+ },
3667+ "@emotion/hash": {
3668+ "version": "0.8.0",
3669+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
3670+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
3671+ },
3672+ "@emotion/memoize": {
3673+ "version": "0.7.5",
3674+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
3675+ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
3676+ },
3677+ "@emotion/react": {
3678+ "version": "11.9.3",
3679+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.3.tgz",
3680+ "integrity": "sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==",
3681+ "requires": {
3682+ "@babel/runtime": "^7.13.10",
3683+ "@emotion/babel-plugin": "^11.7.1",
3684+ "@emotion/cache": "^11.9.3",
3685+ "@emotion/serialize": "^1.0.4",
3686+ "@emotion/utils": "^1.1.0",
3687+ "@emotion/weak-memoize": "^0.2.5",
3688+ "hoist-non-react-statics": "^3.3.1"
3689+ }
3690+ },
3691+ "@emotion/serialize": {
3692+ "version": "1.0.4",
3693+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.4.tgz",
3694+ "integrity": "sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==",
3695+ "requires": {
3696+ "@emotion/hash": "^0.8.0",
3697+ "@emotion/memoize": "^0.7.4",
3698+ "@emotion/unitless": "^0.7.5",
3699+ "@emotion/utils": "^1.0.0",
3700+ "csstype": "^3.0.2"
3701+ }
3702+ },
3703+ "@emotion/sheet": {
3704+ "version": "1.1.1",
3705+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.1.tgz",
3706+ "integrity": "sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA=="
3707+ },
3708+ "@emotion/unitless": {
3709+ "version": "0.7.5",
3710+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
3711+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
3712+ },
3713+ "@emotion/utils": {
3714+ "version": "1.1.0",
3715+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz",
3716+ "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
3717+ },
3718+ "@emotion/weak-memoize": {
3719+ "version": "0.2.5",
3720+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
3721+ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
3722+ },
33143723 "@heroicons/react": {
33153724 "version": "1.0.6",
33163725 "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz",
@@ -3321,7 +3730,6 @@
33213730 "version": "0.1.1",
33223731 "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
33233732 "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
3324− "dev": true,
33253733 "requires": {
33263734 "@jridgewell/set-array": "^1.0.0",
33273735 "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -3330,26 +3738,22 @@
33303738 "@jridgewell/resolve-uri": {
33313739 "version": "3.0.8",
33323740 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz",
3333− "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==",
3334− "dev": true
3741+ "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w=="
33353742 },
33363743 "@jridgewell/set-array": {
33373744 "version": "1.1.2",
33383745 "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
3339− "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
3340− "dev": true
3746+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
33413747 },
33423748 "@jridgewell/sourcemap-codec": {
33433749 "version": "1.4.14",
33443750 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
3345− "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
3346− "dev": true
3751+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
33473752 },
33483753 "@jridgewell/trace-mapping": {
33493754 "version": "0.3.14",
33503755 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
33513756 "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
3352− "dev": true,
33533757 "requires": {
33543758 "@jridgewell/resolve-uri": "^3.0.3",
33553759 "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -3386,6 +3790,14 @@
33863790 "fastq": "^1.6.0"
33873791 }
33883792 },
3793+ "@radix-ui/number": {
3794+ "version": "0.1.0",
3795+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-0.1.0.tgz",
3796+ "integrity": "sha512-rpf6QiOWLHAkM4FEMYu9i+5Jr8cKT893+R4mPpcdsy4LD7omr9JfdOqj/h/xPA5+EcVrpMMlU6rrRYpUB5UI8g==",
3797+ "requires": {
3798+ "@babel/runtime": "^7.13.10"
3799+ }
3800+ },
33893801 "@radix-ui/popper": {
33903802 "version": "0.1.0",
33913803 "resolved": "https://registry.npmjs.org/@radix-ui/popper/-/popper-0.1.0.tgz",
@@ -3497,6 +3909,18 @@
34973909 "@radix-ui/react-use-layout-effect": "0.1.0"
34983910 }
34993911 },
3912+ "@radix-ui/react-label": {
3913+ "version": "0.1.5",
3914+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-0.1.5.tgz",
3915+ "integrity": "sha512-Au9+n4/DhvjR0IHhvZ1LPdx/OW+3CGDie30ZyCkbSHIuLp4/CV4oPPGBwJ1vY99Jog3zyQhsGww9MXj8O9Aj/A==",
3916+ "requires": {
3917+ "@babel/runtime": "^7.13.10",
3918+ "@radix-ui/react-compose-refs": "0.1.0",
3919+ "@radix-ui/react-context": "0.1.1",
3920+ "@radix-ui/react-id": "0.1.5",
3921+ "@radix-ui/react-primitive": "0.1.4"
3922+ }
3923+ },
35003924 "@radix-ui/react-menu": {
35013925 "version": "0.1.6",
35023926 "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-0.1.6.tgz",
@@ -3583,6 +4007,32 @@
35834007 "@radix-ui/react-use-controllable-state": "0.1.0"
35844008 }
35854009 },
4010+ "@radix-ui/react-select": {
4011+ "version": "0.1.1",
4012+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-0.1.1.tgz",
4013+ "integrity": "sha512-xY3jLz2c6ZBHflldGsA79bE/swUfRMpiRPQf+JDihOufsd3z5uW22PFe0MS5bGZiIpK6aAZAvwqEd2Bu7hqp8w==",
4014+ "requires": {
4015+ "@babel/runtime": "^7.13.10",
4016+ "@radix-ui/number": "0.1.0",
4017+ "@radix-ui/primitive": "0.1.0",
4018+ "@radix-ui/react-collection": "0.1.4",
4019+ "@radix-ui/react-compose-refs": "0.1.0",
4020+ "@radix-ui/react-context": "0.1.1",
4021+ "@radix-ui/react-dismissable-layer": "0.1.5",
4022+ "@radix-ui/react-focus-scope": "0.1.4",
4023+ "@radix-ui/react-id": "0.1.5",
4024+ "@radix-ui/react-label": "0.1.5",
4025+ "@radix-ui/react-portal": "0.1.4",
4026+ "@radix-ui/react-primitive": "0.1.4",
4027+ "@radix-ui/react-use-callback-ref": "0.1.0",
4028+ "@radix-ui/react-use-controllable-state": "0.1.0",
4029+ "@radix-ui/react-use-layout-effect": "0.1.0",
4030+ "@radix-ui/react-use-previous": "0.1.1",
4031+ "@radix-ui/react-visually-hidden": "0.1.4",
4032+ "aria-hidden": "^1.1.1",
4033+ "react-remove-scroll": "^2.4.0"
4034+ }
4035+ },
35864036 "@radix-ui/react-slot": {
35874037 "version": "0.1.2",
35884038 "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-0.1.2.tgz",
@@ -3668,6 +4118,14 @@
36684118 "@babel/runtime": "^7.13.10"
36694119 }
36704120 },
4121+ "@radix-ui/react-use-previous": {
4122+ "version": "0.1.1",
4123+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz",
4124+ "integrity": "sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg==",
4125+ "requires": {
4126+ "@babel/runtime": "^7.13.10"
4127+ }
4128+ },
36714129 "@radix-ui/react-use-rect": {
36724130 "version": "0.1.1",
36734131 "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz",
@@ -3685,6 +4143,15 @@
36854143 "@babel/runtime": "^7.13.10"
36864144 }
36874145 },
4146+ "@radix-ui/react-visually-hidden": {
4147+ "version": "0.1.4",
4148+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz",
4149+ "integrity": "sha512-K/q6AEEzqeeEq/T0NPChvBqnwlp8Tl4NnQdrI/y8IOY7BRR+Ug0PEsVk6g48HJ7cA1//COugdxXXVVK/m0X1mA==",
4150+ "requires": {
4151+ "@babel/runtime": "^7.13.10",
4152+ "@radix-ui/react-primitive": "0.1.4"
4153+ }
4154+ },
36884155 "@radix-ui/rect": {
36894156 "version": "0.1.1",
36904157 "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-0.1.1.tgz",
@@ -3703,6 +4170,28 @@
37034170 "picomatch": "^2.2.2"
37044171 }
37054172 },
4173+ "@tanstack/query-core": {
4174+ "version": "4.29.5",
4175+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz",
4176+ "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ=="
4177+ },
4178+ "@tanstack/react-query": {
4179+ "version": "4.29.5",
4180+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz",
4181+ "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==",
4182+ "requires": {
4183+ "@tanstack/query-core": "4.29.5",
4184+ "use-sync-external-store": "^1.2.0"
4185+ },
4186+ "dependencies": {
4187+ "use-sync-external-store": {
4188+ "version": "1.2.0",
4189+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
4190+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
4191+ "requires": {}
4192+ }
4193+ }
4194+ },
37064195 "@types/lodash": {
37074196 "version": "4.14.182",
37084197 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@@ -3718,17 +4207,20 @@
37184207 "@types/lodash": "*"
37194208 }
37204209 },
4210+ "@types/parse-json": {
4211+ "version": "4.0.0",
4212+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
4213+ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
4214+ },
37214215 "@types/prop-types": {
37224216 "version": "15.7.5",
37234217 "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
3724− "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
3725− "devOptional": true
4218+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
37264219 },
37274220 "@types/react": {
37284221 "version": "17.0.47",
37294222 "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.47.tgz",
37304223 "integrity": "sha512-mk0BL8zBinf2ozNr3qPnlu1oyVTYq+4V7WA76RgxUAtf0Em/Wbid38KN6n4abEkvO4xMTBWmnP1FtQzgkEiJoA==",
3731− "devOptional": true,
37324224 "requires": {
37334225 "@types/prop-types": "*",
37344226 "@types/scheduler": "*",
@@ -3744,16 +4236,23 @@
37444236 "@types/react": "^17"
37454237 }
37464238 },
4239+ "@types/react-transition-group": {
4240+ "version": "4.4.5",
4241+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz",
4242+ "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==",
4243+ "requires": {
4244+ "@types/react": "*"
4245+ }
4246+ },
37474247 "@types/scheduler": {
37484248 "version": "0.16.2",
37494249 "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
3750− "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
3751− "devOptional": true
4250+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
37524251 },
37534252 "@urbit/api": {
3754− "version": "2.1.1",
3755− "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.1.1.tgz",
3756− "integrity": "sha512-QRlqhtJ73q+pgMdSwuOO62HlxA7/2c5ylCcOUT01LXkJ2LTVCl5u+QnejdDvUmqjOuN2PyZk7df30xJVg6rC2A==",
4253+ "version": "2.2.0",
4254+ "resolved": "https://registry.npmjs.org/@urbit/api/-/api-2.2.0.tgz",
4255+ "integrity": "sha512-W8kP9OT6yOK62n+4yCPO3i9QqU5xriLvZQ9WYW4SAV7ktbSrGuf2kmYbnoqfA/NybIs9Q/MbFkPewrz4XJ96Ag==",
37574256 "requires": {
37584257 "@babel/runtime": "^7.16.0",
37594258 "big-integer": "^1.6.48",
@@ -3823,7 +4322,6 @@
38234322 "version": "3.2.1",
38244323 "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
38254324 "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
3826− "dev": true,
38274325 "requires": {
38284326 "color-convert": "^1.9.0"
38294327 }
@@ -3866,10 +4364,15 @@
38664364 "postcss-value-parser": "^4.2.0"
38674365 }
38684366 },
3869− "balanced-match": {
3870− "version": "1.0.2",
3871− "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
3872− "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
4367+ "babel-plugin-macros": {
4368+ "version": "2.8.0",
4369+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
4370+ "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
4371+ "requires": {
4372+ "@babel/runtime": "^7.7.2",
4373+ "cosmiconfig": "^6.0.0",
4374+ "resolve": "^1.12.0"
4375+ }
38734376 },
38744377 "big-integer": {
38754378 "version": "1.6.51",
@@ -3887,15 +4390,6 @@
38874390 "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
38884391 "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
38894392 },
3890− "brace-expansion": {
3891− "version": "1.1.11",
3892− "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
3893− "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
3894− "requires": {
3895− "balanced-match": "^1.0.0",
3896− "concat-map": "0.0.1"
3897− }
3898− },
38994393 "braces": {
39004394 "version": "3.0.2",
39014395 "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -3905,21 +4399,6 @@
39054399 "fill-range": "^7.0.1"
39064400 }
39074401 },
3908− "broadcast-channel": {
3909− "version": "3.7.0",
3910− "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
3911− "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
3912− "requires": {
3913− "@babel/runtime": "^7.7.2",
3914− "detect-node": "^2.1.0",
3915− "js-sha3": "0.8.0",
3916− "microseconds": "0.2.0",
3917− "nano-time": "1.0.0",
3918− "oblivious-set": "1.0.0",
3919− "rimraf": "3.0.2",
3920− "unload": "2.2.0"
3921− }
3922− },
39234402 "browser-or-node": {
39244403 "version": "1.3.0",
39254404 "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz",
@@ -3929,7 +4408,6 @@
39294408 "version": "4.21.1",
39304409 "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz",
39314410 "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==",
3932− "dev": true,
39334411 "requires": {
39344412 "caniuse-lite": "^1.0.30001359",
39354413 "electron-to-chromium": "^1.4.172",
@@ -3937,6 +4415,11 @@
39374415 "update-browserslist-db": "^1.0.4"
39384416 }
39394417 },
4418+ "callsites": {
4419+ "version": "3.1.0",
4420+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
4421+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
4422+ },
39404423 "camelcase-css": {
39414424 "version": "2.0.1",
39424425 "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -3946,14 +4429,12 @@
39464429 "caniuse-lite": {
39474430 "version": "1.0.30001361",
39484431 "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001361.tgz",
3949− "integrity": "sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==",
3950− "dev": true
4432+ "integrity": "sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ=="
39514433 },
39524434 "chalk": {
39534435 "version": "2.4.2",
39544436 "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
39554437 "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
3956− "dev": true,
39574438 "requires": {
39584439 "ansi-styles": "^3.2.1",
39594440 "escape-string-regexp": "^1.0.5",
@@ -3996,7 +4477,6 @@
39964477 "version": "1.9.3",
39974478 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
39984479 "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
3999− "dev": true,
40004480 "requires": {
40014481 "color-name": "1.1.3"
40024482 }
@@ -4004,13 +4484,12 @@
40044484 "color-name": {
40054485 "version": "1.1.3",
40064486 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
4007− "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
4008− "dev": true
4487+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
40094488 },
4010− "concat-map": {
4011− "version": "0.0.1",
4012− "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
4013− "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
4489+ "color2k": {
4490+ "version": "2.0.2",
4491+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz",
4492+ "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w=="
40144493 },
40154494 "connect-history-api-fallback": {
40164495 "version": "1.6.0",
@@ -4022,7 +4501,6 @@
40224501 "version": "1.8.0",
40234502 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
40244503 "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
4025− "dev": true,
40264504 "requires": {
40274505 "safe-buffer": "~5.1.1"
40284506 }
@@ -4032,6 +4510,18 @@
40324510 "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.23.3.tgz",
40334511 "integrity": "sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q=="
40344512 },
4513+ "cosmiconfig": {
4514+ "version": "6.0.0",
4515+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
4516+ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
4517+ "requires": {
4518+ "@types/parse-json": "^4.0.0",
4519+ "import-fresh": "^3.1.0",
4520+ "parse-json": "^5.0.0",
4521+ "path-type": "^4.0.0",
4522+ "yaml": "^1.7.2"
4523+ }
4524+ },
40354525 "cssesc": {
40364526 "version": "3.0.0",
40374527 "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -4047,22 +4537,21 @@
40474537 "version": "4.3.4",
40484538 "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
40494539 "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
4050− "dev": true,
40514540 "requires": {
40524541 "ms": "2.1.2"
40534542 }
40544543 },
4544+ "decode-uri-component": {
4545+ "version": "0.2.0",
4546+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
4547+ "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
4548+ },
40554549 "defined": {
40564550 "version": "1.0.0",
40574551 "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
40584552 "integrity": "sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==",
40594553 "dev": true
40604554 },
4061− "detect-node": {
4062− "version": "2.1.0",
4063− "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
4064− "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
4065− },
40664555 "detect-node-es": {
40674556 "version": "1.1.0",
40684557 "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -4091,11 +4580,27 @@
40914580 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
40924581 "dev": true
40934582 },
4583+ "dom-helpers": {
4584+ "version": "5.2.1",
4585+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
4586+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
4587+ "requires": {
4588+ "@babel/runtime": "^7.8.7",
4589+ "csstype": "^3.0.2"
4590+ }
4591+ },
40944592 "electron-to-chromium": {
40954593 "version": "1.4.176",
40964594 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.176.tgz",
4097− "integrity": "sha512-92JdgyRlcNDwuy75MjuFSb3clt6DGJ2IXSpg0MCjKd3JV9eSmuUAIyWiGAp/EtT0z2D4rqbYqThQLV90maH3Zw==",
4098− "dev": true
4595+ "integrity": "sha512-92JdgyRlcNDwuy75MjuFSb3clt6DGJ2IXSpg0MCjKd3JV9eSmuUAIyWiGAp/EtT0z2D4rqbYqThQLV90maH3Zw=="
4596+ },
4597+ "error-ex": {
4598+ "version": "1.3.2",
4599+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
4600+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
4601+ "requires": {
4602+ "is-arrayish": "^0.2.1"
4603+ }
40994604 },
41004605 "esbuild": {
41014606 "version": "0.14.48",
@@ -4268,14 +4773,12 @@
42684773 "escalade": {
42694774 "version": "3.1.1",
42704775 "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
4271− "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
4272− "dev": true
4776+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
42734777 },
42744778 "escape-string-regexp": {
42754779 "version": "1.0.5",
42764780 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
4277− "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
4278− "dev": true
4781+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
42794782 },
42804783 "estree-walker": {
42814784 "version": "2.0.2",
@@ -4325,17 +4828,22 @@
43254828 "to-regex-range": "^5.0.1"
43264829 }
43274830 },
4831+ "filter-obj": {
4832+ "version": "1.1.0",
4833+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
4834+ "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
4835+ },
4836+ "find-root": {
4837+ "version": "1.1.0",
4838+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
4839+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
4840+ },
43284841 "fraction.js": {
43294842 "version": "4.2.0",
43304843 "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
43314844 "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
43324845 "dev": true
43334846 },
4334− "fs.realpath": {
4335− "version": "1.0.0",
4336− "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
4337− "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
4338− },
43394847 "fsevents": {
43404848 "version": "2.3.2",
43414849 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -4346,33 +4854,18 @@
43464854 "function-bind": {
43474855 "version": "1.1.1",
43484856 "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
4349− "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
4350− "dev": true
4857+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
43514858 },
43524859 "gensync": {
43534860 "version": "1.0.0-beta.2",
43544861 "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
4355− "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
4356− "dev": true
4862+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
43574863 },
43584864 "get-nonce": {
43594865 "version": "1.0.1",
43604866 "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
43614867 "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
43624868 },
4363− "glob": {
4364− "version": "7.2.3",
4365− "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
4366− "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
4367− "requires": {
4368− "fs.realpath": "^1.0.0",
4369− "inflight": "^1.0.4",
4370− "inherits": "2",
4371− "minimatch": "^3.1.1",
4372− "once": "^1.3.0",
4373− "path-is-absolute": "^1.0.0"
4374− }
4375− },
43764869 "glob-parent": {
43774870 "version": "6.0.2",
43784871 "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -4385,14 +4878,12 @@
43854878 "globals": {
43864879 "version": "11.12.0",
43874880 "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
4388− "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
4389− "dev": true
4881+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
43904882 },
43914883 "has": {
43924884 "version": "1.0.3",
43934885 "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
43944886 "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
4395− "dev": true,
43964887 "requires": {
43974888 "function-bind": "^1.1.1"
43984889 }
@@ -4400,8 +4891,7 @@
44004891 "has-flag": {
44014892 "version": "3.0.0",
44024893 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
4403− "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
4404− "dev": true
4894+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
44054895 },
44064896 "history": {
44074897 "version": "5.3.0",
@@ -4411,25 +4901,28 @@
44114901 "@babel/runtime": "^7.7.6"
44124902 }
44134903 },
4904+ "hoist-non-react-statics": {
4905+ "version": "3.3.2",
4906+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
4907+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
4908+ "requires": {
4909+ "react-is": "^16.7.0"
4910+ }
4911+ },
44144912 "immer": {
44154913 "version": "9.0.15",
44164914 "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
44174915 "integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ=="
44184916 },
4419− "inflight": {
4420− "version": "1.0.6",
4421− "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
4422− "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
4917+ "import-fresh": {
4918+ "version": "3.3.0",
4919+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
4920+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
44234921 "requires": {
4424− "once": "^1.3.0",
4425− "wrappy": "1"
4922+ "parent-module": "^1.0.0",
4923+ "resolve-from": "^4.0.0"
44264924 }
44274925 },
4428− "inherits": {
4429− "version": "2.0.4",
4430− "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
4431− "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
4432− },
44334926 "invariant": {
44344927 "version": "2.2.4",
44354928 "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -4438,6 +4931,11 @@
44384931 "loose-envify": "^1.0.0"
44394932 }
44404933 },
4934+ "is-arrayish": {
4935+ "version": "0.2.1",
4936+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
4937+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
4938+ },
44414939 "is-binary-path": {
44424940 "version": "2.1.0",
44434941 "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4451,7 +4949,6 @@
44514949 "version": "2.9.0",
44524950 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
44534951 "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
4454− "dev": true,
44554952 "requires": {
44564953 "has": "^1.0.3"
44574954 }
@@ -4477,11 +4974,6 @@
44774974 "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
44784975 "dev": true
44794976 },
4480− "js-sha3": {
4481− "version": "0.8.0",
4482− "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
4483− "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
4484− },
44854977 "js-tokens": {
44864978 "version": "4.0.0",
44874979 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4490,14 +4982,17 @@
44904982 "jsesc": {
44914983 "version": "2.5.2",
44924984 "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
4493− "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
4494− "dev": true
4985+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
4986+ },
4987+ "json-parse-even-better-errors": {
4988+ "version": "2.3.1",
4989+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
4990+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
44954991 },
44964992 "json5": {
44974993 "version": "2.2.1",
44984994 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
4499− "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
4500− "dev": true
4995+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
45014996 },
45024997 "lilconfig": {
45034998 "version": "2.0.5",
@@ -4505,6 +5000,11 @@
45055000 "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==",
45065001 "dev": true
45075002 },
5003+ "lines-and-columns": {
5004+ "version": "1.2.4",
5005+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
5006+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
5007+ },
45085008 "lodash.chunk": {
45095009 "version": "4.2.0",
45105010 "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
@@ -4528,14 +5028,10 @@
45285028 "js-tokens": "^3.0.0 || ^4.0.0"
45295029 }
45305030 },
4531− "match-sorter": {
4532− "version": "6.3.1",
4533− "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
4534− "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
4535− "requires": {
4536− "@babel/runtime": "^7.12.5",
4537− "remove-accents": "0.4.2"
4538− }
5031+ "memoize-one": {
5032+ "version": "5.2.1",
5033+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
5034+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
45395035 },
45405036 "merge2": {
45415037 "version": "1.4.1",
@@ -4553,19 +5049,6 @@
45535049 "picomatch": "^2.3.1"
45545050 }
45555051 },
4556− "microseconds": {
4557− "version": "0.2.0",
4558− "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
4559− "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
4560− },
4561− "minimatch": {
4562− "version": "3.1.2",
4563− "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
4564− "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
4565− "requires": {
4566− "brace-expansion": "^1.1.7"
4567− }
4568− },
45695052 "minimist": {
45705053 "version": "1.2.6",
45715054 "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
@@ -4575,16 +5058,7 @@
45755058 "ms": {
45765059 "version": "2.1.2",
45775060 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
4578− "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
4579− "dev": true
4580− },
4581− "nano-time": {
4582− "version": "1.0.0",
4583− "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
4584− "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
4585− "requires": {
4586− "big-integer": "^1.6.16"
4587− }
5061+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
45885062 },
45895063 "nanoid": {
45905064 "version": "3.3.4",
@@ -4595,8 +5069,7 @@
45955069 "node-releases": {
45965070 "version": "2.0.5",
45975071 "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz",
4598− "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==",
4599− "dev": true
5072+ "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q=="
46005073 },
46015074 "normalize-path": {
46025075 "version": "3.0.0",
@@ -4621,35 +5094,39 @@
46215094 "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
46225095 "dev": true
46235096 },
4624− "oblivious-set": {
4625− "version": "1.0.0",
4626− "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
4627− "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
4628− },
4629− "once": {
4630− "version": "1.4.0",
4631− "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
4632− "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
5097+ "parent-module": {
5098+ "version": "1.0.1",
5099+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
5100+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
46335101 "requires": {
4634− "wrappy": "1"
5102+ "callsites": "^3.0.0"
46355103 }
46365104 },
4637− "path-is-absolute": {
4638− "version": "1.0.1",
4639− "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
4640− "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
5105+ "parse-json": {
5106+ "version": "5.2.0",
5107+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
5108+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
5109+ "requires": {
5110+ "@babel/code-frame": "^7.0.0",
5111+ "error-ex": "^1.3.1",
5112+ "json-parse-even-better-errors": "^2.3.0",
5113+ "lines-and-columns": "^1.1.6"
5114+ }
46415115 },
46425116 "path-parse": {
46435117 "version": "1.0.7",
46445118 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
4645− "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
4646− "dev": true
5119+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
5120+ },
5121+ "path-type": {
5122+ "version": "4.0.0",
5123+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
5124+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
46475125 },
46485126 "picocolors": {
46495127 "version": "1.0.0",
46505128 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
4651− "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
4652− "dev": true
5129+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
46535130 },
46545131 "picomatch": {
46555132 "version": "2.3.1",
@@ -4729,6 +5206,27 @@
47295206 "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
47305207 "dev": true
47315208 },
5209+ "prop-types": {
5210+ "version": "15.8.1",
5211+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
5212+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
5213+ "requires": {
5214+ "loose-envify": "^1.4.0",
5215+ "object-assign": "^4.1.1",
5216+ "react-is": "^16.13.1"
5217+ }
5218+ },
5219+ "query-string": {
5220+ "version": "7.1.1",
5221+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
5222+ "integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
5223+ "requires": {
5224+ "decode-uri-component": "^0.2.0",
5225+ "filter-obj": "^1.1.0",
5226+ "split-on-first": "^1.0.0",
5227+ "strict-uri-encode": "^2.0.0"
5228+ }
5229+ },
47325230 "queue-microtask": {
47335231 "version": "1.2.3",
47345232 "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4760,15 +5258,16 @@
47605258 "scheduler": "^0.20.2"
47615259 }
47625260 },
4763− "react-query": {
4764− "version": "3.39.1",
4765− "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.1.tgz",
4766− "integrity": "sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A==",
4767− "requires": {
4768− "@babel/runtime": "^7.5.5",
4769− "broadcast-channel": "^3.4.1",
4770− "match-sorter": "^6.0.2"
4771− }
5261+ "react-hook-form": {
5262+ "version": "7.33.1",
5263+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.33.1.tgz",
5264+ "integrity": "sha512-ydTfTxEJdvgjCZBj5DDXRc58oTEfnFupEwwTAQ9FSKzykEJkX+3CiAkGtAMiZG7IPWHuzgT6AOBfogiKhUvKgg==",
5265+ "requires": {}
5266+ },
5267+ "react-is": {
5268+ "version": "16.13.1",
5269+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
5270+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
47725271 },
47735272 "react-refresh": {
47745273 "version": "0.10.0",
@@ -4828,6 +5327,20 @@
48285327 "react-router": "6.3.0"
48295328 }
48305329 },
5330+ "react-select": {
5331+ "version": "5.4.0",
5332+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.4.0.tgz",
5333+ "integrity": "sha512-CjE9RFLUvChd5SdlfG4vqxZd55AZJRrLrHzkQyTYeHlpOztqcgnyftYAolJ0SGsBev6zAs6qFrjm6KU3eo2hzg==",
5334+ "requires": {
5335+ "@babel/runtime": "^7.12.0",
5336+ "@emotion/cache": "^11.4.0",
5337+ "@emotion/react": "^11.8.1",
5338+ "@types/react-transition-group": "^4.4.0",
5339+ "memoize-one": "^5.0.0",
5340+ "prop-types": "^15.6.0",
5341+ "react-transition-group": "^4.3.0"
5342+ }
5343+ },
48315344 "react-style-singleton": {
48325345 "version": "2.2.1",
48335346 "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@@ -4845,6 +5358,17 @@
48455358 }
48465359 }
48475360 },
5361+ "react-transition-group": {
5362+ "version": "4.4.2",
5363+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
5364+ "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
5365+ "requires": {
5366+ "@babel/runtime": "^7.5.5",
5367+ "dom-helpers": "^5.0.1",
5368+ "loose-envify": "^1.4.0",
5369+ "prop-types": "^15.6.2"
5370+ }
5371+ },
48485372 "read-cache": {
48495373 "version": "1.0.0",
48505374 "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -4868,36 +5392,32 @@
48685392 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
48695393 "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
48705394 },
4871− "remove-accents": {
4872− "version": "0.4.2",
4873− "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
4874− "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
5395+ "remove-markdown-and-html": {
5396+ "version": "0.1.0",
5397+ "resolved": "https://registry.npmjs.org/remove-markdown-and-html/-/remove-markdown-and-html-0.1.0.tgz",
5398+ "integrity": "sha512-Sy03RJ/R/FBbK6wutBKKIyEVMQS+TzSpGc8pBxwrh0LiqyVIaD/LkitUABIJJ59iryhvrPTmS8dzd3DoLdopHg=="
48755399 },
48765400 "resolve": {
48775401 "version": "1.22.1",
48785402 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
48795403 "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
4880− "dev": true,
48815404 "requires": {
48825405 "is-core-module": "^2.9.0",
48835406 "path-parse": "^1.0.7",
48845407 "supports-preserve-symlinks-flag": "^1.0.0"
48855408 }
48865409 },
5410+ "resolve-from": {
5411+ "version": "4.0.0",
5412+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
5413+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
5414+ },
48875415 "reusify": {
48885416 "version": "1.0.4",
48895417 "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
48905418 "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
48915419 "dev": true
48925420 },
4893− "rimraf": {
4894− "version": "3.0.2",
4895− "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
4896− "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
4897− "requires": {
4898− "glob": "^7.1.3"
4899− }
4900− },
49015421 "rollup": {
49025422 "version": "2.75.7",
49035423 "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz",
@@ -4919,8 +5439,7 @@
49195439 "safe-buffer": {
49205440 "version": "5.1.2",
49215441 "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
4922− "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
4923− "dev": true
5442+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
49245443 },
49255444 "scheduler": {
49265445 "version": "0.20.2",
@@ -4934,8 +5453,12 @@
49345453 "semver": {
49355454 "version": "6.3.0",
49365455 "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
4937− "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
4938− "dev": true
5456+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
5457+ },
5458+ "source-map": {
5459+ "version": "0.5.7",
5460+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
5461+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
49395462 },
49405463 "source-map-js": {
49415464 "version": "1.0.2",
@@ -4943,11 +5466,25 @@
49435466 "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
49445467 "dev": true
49455468 },
5469+ "split-on-first": {
5470+ "version": "1.1.0",
5471+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
5472+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
5473+ },
5474+ "strict-uri-encode": {
5475+ "version": "2.0.0",
5476+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
5477+ "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
5478+ },
5479+ "stylis": {
5480+ "version": "4.0.13",
5481+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
5482+ "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
5483+ },
49465484 "supports-color": {
49475485 "version": "5.5.0",
49485486 "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
49495487 "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
4950− "dev": true,
49515488 "requires": {
49525489 "has-flag": "^3.0.0"
49535490 }
@@ -4955,8 +5492,7 @@
49555492 "supports-preserve-symlinks-flag": {
49565493 "version": "1.0.0",
49575494 "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
4958− "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
4959− "dev": true
5495+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
49605496 },
49615497 "tailwindcss": {
49625498 "version": "3.1.4",
@@ -4996,11 +5532,17 @@
49965532 }
49975533 }
49985534 },
5535+ "tailwindcss-labeled-groups": {
5536+ "version": "0.0.2",
5537+ "resolved": "https://registry.npmjs.org/tailwindcss-labeled-groups/-/tailwindcss-labeled-groups-0.0.2.tgz",
5538+ "integrity": "sha512-leOVv9h2K9WDHoXPXVBOmTJgWECVkpVFc/hjrwuqGUEtNxvRvuOtCjenu9ppY+pJtky5INJUqDCNa8pS3AbYmw==",
5539+ "dev": true,
5540+ "requires": {}
5541+ },
49995542 "to-fast-properties": {
50005543 "version": "2.0.0",
50015544 "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
5002− "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
5003− "dev": true
5545+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="
50045546 },
50055547 "to-regex-range": {
50065548 "version": "5.0.1",
@@ -5022,20 +5564,10 @@
50225564 "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
50235565 "dev": true
50245566 },
5025− "unload": {
5026− "version": "2.2.0",
5027− "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
5028− "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
5029− "requires": {
5030− "@babel/runtime": "^7.6.2",
5031− "detect-node": "^2.0.4"
5032− }
5033− },
50345567 "update-browserslist-db": {
50355568 "version": "1.0.4",
50365569 "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz",
50375570 "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==",
5038− "dev": true,
50395571 "requires": {
50405572 "escalade": "^3.1.1",
50415573 "picocolors": "^1.0.0"
@@ -5082,6 +5614,12 @@
50825614 }
50835615 }
50845616 },
5617+ "use-sync-external-store": {
5618+ "version": "1.1.0",
5619+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz",
5620+ "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==",
5621+ "requires": {}
5622+ },
50855623 "util-deprecate": {
50865624 "version": "1.0.2",
50875625 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -5117,11 +5655,6 @@
51175655 "connect-history-api-fallback": "^1.6.0"
51185656 }
51195657 },
5120− "wrappy": {
5121− "version": "1.0.2",
5122− "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
5123− "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
5124− },
51255658 "xtend": {
51265659 "version": "4.0.2",
51275660 "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -5131,8 +5664,15 @@
51315664 "yaml": {
51325665 "version": "1.10.2",
51335666 "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
5134− "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
5135− "dev": true
5667+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
5668+ },
5669+ "zustand": {
5670+ "version": "4.0.0-rc.1",
5671+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.0.0-rc.1.tgz",
5672+ "integrity": "sha512-qgcs7zLqBdHu0PuT3GW4WCIY5SgXdsv30GQMu9Qpp1BA2aS+sNS8l4x0hWuyEhjXkN+701aGWawhKDv6oWJAcw==",
5673+ "requires": {
5674+ "use-sync-external-store": "1.1.0"
5675+ }
51365676 }
51375677 }
51385678}
ui/package.json+14−4
@@ -1,5 +1,5 @@
11{
2− "name": "seek",
2+ "name": "sphinx",
33 "version": "0.0.0",
44 "private": true,
55 "scripts": {
@@ -12,16 +12,24 @@
1212 "dependencies": {
1313 "@heroicons/react": "^1.0.6",
1414 "@radix-ui/react-dropdown-menu": "^0.1.6",
15+ "@radix-ui/react-select": "^0.1.1",
1516 "@radix-ui/react-toggle-group": "^0.1.5",
16− "@urbit/api": "^2.0.0",
17+ "@tanstack/react-query": "^4.29.5",
18+ "@urbit/api": "2.2",
1719 "@urbit/http-api": "^2.0.0",
1820 "big-integer": "^1.6.51",
1921 "classnames": "^2.3.1",
22+ "color2k": "^2.0.2",
2023 "lodash.debounce": "^4.0.8",
24+ "query-string": "^7.1.1",
2125 "react": "^17.0.2",
2226 "react-dom": "^17.0.2",
23− "react-query": "^3.39.1",
24− "react-router-dom": "^6.3.0"
27+ "react-hook-form": "^7.33.1",
28+ "react-router-dom": "^6.3.0",
29+ "react-select": "^5.4.0",
30+ "remove-markdown-and-html": "^0.1.0",
31+ "urbit-ob": "^5.0.1",
32+ "zustand": "^4.0.0-rc.1"
2533 },
2634 "devDependencies": {
2735 "@types/lodash.debounce": "^4.0.7",
@@ -31,7 +39,9 @@
3139 "@vitejs/plugin-react-refresh": "^1.3.1",
3240 "autoprefixer": "^10.4.7",
3341 "postcss": "^8.4.14",
42+ "postcss-import": "^14.1.0",
3443 "tailwindcss": "^3.1.4",
44+ "tailwindcss-labeled-groups": "^0.0.2",
3545 "typescript": "^4.3.2",
3646 "vite": "^2.9.13"
3747 }
ui/postcss.config.js+2
@@ -1,5 +1,7 @@
11module.exports = {
22 plugins: [
3+ require('postcss-import'),
4+ require('tailwindcss/nesting'),
35 require('tailwindcss'),
46 require('autoprefixer'),
57 ]
ui/src/assets/android-chrome-512x512.png → ui/public/android-chrome-512x512.pngrenamed

Binary file not shown.

ui/src/assets/manifest.json → ui/public/manifest.jsonrenamed+3−3
@@ -1,9 +1,9 @@
11{
2− "name": "seek",
3− "short_name": "seek",
2+ "name": "sphinx",
3+ "short_name": "sphinx",
44 "icons": [
55 {
6− "src": "/apps/grid/android-chrome-512x512.png",
6+ "src": "/apps/sphinx/android-chrome-512x512.png",
77 "sizes": "512x512",
88 "type": "image/png"
99 }
ui/src/api.ts+6
@@ -3,4 +3,10 @@ import Urbit from "@urbit/http-api";
33const api = new Urbit('', '', window.desk);
44api.ship = window.ship;
55
6+// @urbit/http-api 2.x adds `window.addEventListener('beforeunload', this.delete)`
7+// unbound, so its beacon is sent to `${location dir}/undefined` (404) and the
8+// channel is left open until eyre's 12h timeout. Replace it with a bound one.
9+window.removeEventListener('beforeunload', api.delete);
10+window.addEventListener('beforeunload', () => api.delete());
11+
612export default api;
ui/src/app.tsx+34−5
@@ -1,16 +1,45 @@
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';
4+import { CenterColumn } from './components/CenterColumn';
5+import { Layout } from './components/Layout';
6+import { AllListings } from './manage-listings/AllListings';
7+import { Apps } from './manage-listings/Apps';
8+import { Groups } from './manage-listings/Groups';
9+import { MyListings } from './manage-listings/MyListings';
10+import { Notebooks } from './manage-listings/Notebooks';
11+import { Post } from './manage-listings/Post';
412import { Search } from './pages/Search';
13+import { Tag } from './pages/Tag';
514
615const queryClient = new QueryClient();
716
817function Main() {
918 return (
1019 <Routes>
11− <Route path="/" element={<Search />} />
12− <Route path="/:lookup" element={<Search />} />
13− <Route path="/:lookup/:limit/:page" element={<Search />} />
20+ <Route element={<Layout />}>
21+ <Route element={<CenterColumn />}>
22+ <Route path="/" element={<Search />} />
23+ <Route path="/search" element={<Search />} />
24+ <Route path="/search/:lookup" element={<Search />} />
25+ <Route path="/search/:lookup/:limit/:page" element={<Search />} />
26+ <Route path="/tags" element={<Tag />} />
27+ <Route path="/tags/:tag" element={<Tag />} />
28+ <Route path="/tags/:tag/:limit/:page" element={<Tag />} />
29+ </Route>
30+ <Route path="/manage-listings">
31+ <Route element={<CenterColumn />}>
32+ <Route index element={<MyListings />} />
33+ <Route path="all" element={<AllListings />} />
34+ <Route path="all/:limit/:page" element={<AllListings />} />
35+ <Route path="post" element={<Post />} />
36+ <Route path="apps" element={<Apps />} />
37+ <Route path="groups" element={<Groups />} />
38+ </Route>
39+ <Route path="posts" element={<Notebooks />} />
40+ <Route path="posts/:ship/:name" element={<Notebooks />} />
41+ </Route>
42+ </Route>
1443 </Routes>
1544 );
1645}
@@ -18,7 +47,7 @@ function Main() {
1847export function App() {
1948 return (
2049 <QueryClientProvider client={queryClient}>
21− <BrowserRouter basename='/apps/seek'>
50+ <BrowserRouter basename='/apps/sphinx'>
2251 <Main />
2352 </BrowserRouter>
2453 </QueryClientProvider>
ui/src/components/CenterColumn.tsxnew file+6
@@ -0,0 +1,6 @@
1+import React from 'react';
2+import { Outlet } from 'react-router-dom';
3+
4+export const CenterColumn = () => (
5+ <div className="flex-1 flex flex-col max-w-2xl h-full w-full p-4 sm:py-12 sm:px-8 space-y-6">{<Outlet />}</div>
6+)
ui/src/components/ErrorMessage.tsxnew file+26
@@ -0,0 +1,26 @@
1+import cn from 'classnames';
2+import React from 'react';
3+import { FieldValues, LiteralUnion, RegisterOptions, useFormContext } from 'react-hook-form';
4+
5+type Validation = LiteralUnion<keyof RegisterOptions, string>;
6+
7+interface ErrorMessageProps<T extends FieldValues> {
8+ field: keyof T;
9+ messages: Partial<Record<Validation, string>>;
10+ className?: string;
11+}
12+
13+export function ErrorMessage<T extends FieldValues>({ field, messages, className }: ErrorMessageProps<T>) {
14+ const { formState } = useFormContext<T>();
15+ const error = formState.errors[field];
16+
17+ if (!error) {
18+ return null;
19+ }
20+
21+ return (
22+ <p className={cn('text-xs text-red-500', className)}>
23+ {messages[error.type as Validation]}
24+ </p>
25+ )
26+}
ui/src/components/Filter.tsxnew file+55
@@ -0,0 +1,55 @@
1+import React from 'react';
2+import * as Select from '@radix-ui/react-select';
3+import { PostFilter, PostType } from '../types/sphinx';
4+
5+interface FilterItemProps {
6+ title: PostFilter;
7+}
8+
9+const FilterItem = ({ title }: FilterItemProps) => {
10+ return (
11+ <Select.Item value={title} className="text-mauve cursor-default select-none relative py-2 pl-3 pr-9 focus:outline-none focus:ring-1 focus:ring-rosy font-semibold">
12+ <Select.ItemText>%{title}</Select.ItemText>
13+ <Select.ItemIndicator className="text-rosy absolute inset-y-0 right-0 flex items-center pr-4">
14+ <svg className="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
15+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
16+ </svg>
17+ </Select.ItemIndicator>
18+ </Select.Item>
19+ )
20+}
21+
22+const filters: PostFilter[] = ['all', 'app', 'group', 'content', 'other'];
23+
24+interface FilterProps<T extends string> {
25+ selected: T;
26+ onSelect: (value: T) => void;
27+ showAll?: boolean;
28+ className?: string;
29+}
30+
31+export function Filter<T extends string>({ selected, onSelect, className, showAll = true }: FilterProps<T>) {
32+ const options = showAll ? filters : filters.filter(f => f === 'all');
33+ return (
34+ <div className={className}>
35+ <Select.Root value={selected} onValueChange={onSelect}>
36+ <span className="sr-only">Filter Type</span>
37+ <Select.Trigger className="bg-linen flex justify-between w-full font-semibold border border-fawn/30 rounded-lg shadow-sm pl-3 pr-2 py-1.5 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-rosy focus:border-rosy sm:text-sm">
38+ <Select.Value>%{selected}</Select.Value>
39+ <Select.Icon asChild>
40+ <svg className="h-5 w-5 text-rosy" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
41+ <path fillRule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />
42+ </svg>
43+ </Select.Icon>
44+ </Select.Trigger>
45+ <Select.Content className="z-10 w-full bg-linen shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-rosy ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
46+ <Select.Viewport>
47+ {options.map(f => (
48+ <FilterItem key={f} title={f} />
49+ ))}
50+ </Select.Viewport>
51+ </Select.Content>
52+ </Select.Root>
53+ </div>
54+ )
55+}
ui/src/components/Layout.tsxnew file+75
@@ -0,0 +1,75 @@
1+import { MenuIcon } from '@heroicons/react/solid';
2+import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
3+import cn from 'classnames';
4+import React from 'react';
5+import { NavLink, NavLinkProps, Outlet } from 'react-router-dom';
6+import useMedia from '../logic/useMedia';
7+import { BrowsingRoutes, PostingRoutes } from '../routes';
8+import { Meta } from './Meta';
9+
10+const DesktopNavLink = ({ to, children, ...props }: NavLinkProps) => (
11+ <li>
12+ <NavLink to={to} className={({ isActive }) => cn('hover:text-rosy transition-colors', isActive && 'underline')} {...props}>
13+ { children }
14+ </NavLink>
15+ </li>
16+)
17+
18+interface WrappedNavLinkProps extends NavLinkProps {
19+ navClassName: (props: { isActive: boolean }) => string;
20+}
21+
22+const WrappedNavLink = React.forwardRef<HTMLAnchorElement, WrappedNavLinkProps>(({ className, navClassName, ...props }, forwardedRef) => {
23+ return (
24+ <NavLink
25+ {...props}
26+ className={(props) => `${className} ${navClassName(props)}`}
27+ ref={forwardedRef}
28+ />
29+ );
30+});
31+
32+const MobileNavLink = ({ to, children }: NavLinkProps) => (
33+ <DropdownMenu.Item asChild>
34+ <WrappedNavLink to={to} navClassName={({ isActive }) => cn('block hover:text-rosy transition-colors', isActive && 'underline')}>
35+ { children }
36+ </WrappedNavLink>
37+ </DropdownMenu.Item>
38+)
39+
40+export const Layout = () => {
41+ const isMobile = useMedia('(max-width: 639px)');
42+
43+ return (
44+ <main className={cn("flex flex-col items-center h-full min-h-screen")}>
45+ <Outlet />
46+ <aside className='self-start mx-4 mb-6 mt-auto sm:m-0 sm:fixed left-4 bottom-4'>
47+ {isMobile ? (
48+ <DropdownMenu.Root>
49+ <DropdownMenu.Trigger className='button fixed bottom-6 right-4 p-2 rounded-full' aria-label='Open Menu'>
50+ <MenuIcon className='w-6 h-6' />
51+ </DropdownMenu.Trigger>
52+ <DropdownMenu.Content sideOffset={16} className='min-w-[200px] bg-linen shadow-lg rounded-xl'>
53+ <div className='p-4 space-y-3 text-right bg-fawn/30 rounded-xl'>
54+ {Object.entries(BrowsingRoutes).map(([k,v]) => <MobileNavLink key={v} to={v}>{k}</MobileNavLink>)}
55+ <DropdownMenu.Separator className='border-t-2 border-mauve'/>
56+ {Object.entries(PostingRoutes).map(([k,v]) => <MobileNavLink key={v} to={v}>{k}</MobileNavLink>)}
57+ </div>
58+ </DropdownMenu.Content>
59+ </DropdownMenu.Root>
60+ ) : (
61+ <nav className='w-24 mb-10'>
62+ <ul className='font-semibold text-sm space-y-2'>
63+ {Object.entries(BrowsingRoutes).map(([k,v]) => <DesktopNavLink key={v} to={v} end={k === 'tags'}>{k}</DesktopNavLink>)}
64+ </ul>
65+ <hr className='block my-4 border-mauve/60'/>
66+ <ul className='font-semibold text-sm space-y-2'>
67+ {Object.entries(PostingRoutes).map(([k,v]) => <DesktopNavLink key={v} to={v}>{k}</DesktopNavLink>)}
68+ </ul>
69+ </nav>
70+ )}
71+ <Meta className='text-sm'/>
72+ </aside>
73+ </main>
74+ )
75+}
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 { channelURL, 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+ // Tlon routes channels under their group, which only the preview knows.
23+ // A post deep link (post/:authorId/:postId) would also need the author,
24+ // which listing refs don't carry, so link to the post's channel.
25+ if (!preview) {
26+ return '/apps/groups/';
27+ }
28+
29+ return channelURL(preview.group.flag, link.nest);
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/ListingImage.tsxnew file+26
@@ -0,0 +1,26 @@
1+import cn from 'classnames';
2+import React from 'react';
3+import { normalizeColor } from '../utils';
4+
5+interface ListingImageProps {
6+ image: string;
7+ color: string;
8+ className?: string;
9+ imageClasses?: string;
10+ colorClasses?: string;
11+}
12+
13+export const ListingImage = ({ image, color, className, imageClasses, colorClasses }: ListingImageProps) => {
14+ return image ? (
15+ <img
16+ src={image}
17+ className={cn('object-cover', className, imageClasses)}
18+ style={{ backgroundColor: color ? normalizeColor(color) : undefined }}
19+ />
20+ ) : (
21+ <div
22+ className={cn('bg-rosy/20', className, colorClasses)}
23+ style={{ backgroundColor: color ? normalizeColor(color) : undefined }}
24+ />
25+ )
26+}
ui/src/components/Listings.tsxnew file+41
@@ -0,0 +1,41 @@
1+import cn from 'classnames';
2+import React from 'react';
3+import { usePals } from '../state/pals';
4+import { Listing as ListingType } from '../types/sphinx';
5+import { Listing } from './Listing';
6+
7+interface ListingsProps {
8+ listings: ListingType[];
9+ remove: (hash: string) => void;
10+ className?: string;
11+}
12+
13+export const Listings = ({ listings, remove, className }: ListingsProps) => {
14+ const { installed, outgoing, remove: removePal } = usePals();
15+ if (!listings) {
16+ return null;
17+ }
18+
19+ if (listings.length === 0) {
20+ return (
21+ <div>
22+ <h2 className='text-xl font-semibold'>No Results</h2>
23+ {installed ? (
24+ <p>Try adding more <a href="/pals" className='underline'>pals</a> to see what others have shared</p>
25+ ) : (
26+ <p>Without pals installed you'll only see your own listings</p>
27+ )}
28+ </div>
29+ )
30+ }
31+
32+ return (
33+ <ul className={cn('space-y-6', className)}>
34+ {listings && listings.map(l => (
35+ <li key={l.hash}>
36+ <Listing {...l} outgoing={outgoing} remove={remove} removePal={removePal} />
37+ </li>
38+ ))}
39+ </ul>
40+ )
41+}
ui/src/components/Logo.tsxnew file+13
@@ -0,0 +1,13 @@
1+import React from 'react';
2+
3+interface LogoProps {
4+ className?: string;
5+}
6+
7+export const Logo = ({ className }: LogoProps) => {
8+ return (
9+ <svg className={className} fill="none" xmlns="http://www.w3.org/2000/svg" viewBox='0 0 179 58'>
10+ <path className='fill-current' d="M10.125 44.5c-1.66667 0-3.33333-.1875-5-.5625-1.66667-.375-3.0625-.875-4.1875-1.5l-.3125-7.125h3.6875l1.3125 4.5c1.5.9583 3.14583 1.4375 4.9375 1.4375 1.3333 0 2.375-.2917 3.125-.875.75-.5833 1.125-1.3542 1.125-2.3125 0-1-.375-1.8542-1.125-2.5625-.75-.7083-2.2708-1.5-4.5625-2.375-3.04167-1.1667-5.16667-2.3958-6.375-3.6875-1.20833-1.2917-1.8125-2.9167-1.8125-4.875 0-1.625.5-3.0625 1.5-4.3125s2.375-2.2292 4.125-2.9375c1.79167-.7083 3.8542-1.0625 6.1875-1.0625 1.2917 0 2.5833.125 3.875.375 1.3333.2083 2.4375.5208 3.3125.9375l.6875 7H17l-1.4375-4.375c-.4167-.25-.9167-.4375-1.5-.5625-.5833-.1667-1.1667-.25-1.75-.25-1.3333 0-2.41667.3125-3.25.9375-.79167.5833-1.1875 1.3542-1.1875 2.3125 0 1.0417.45833 1.9167 1.375 2.625.9583.7083 2.6458 1.5833 5.0625 2.625C17.0625 29.0417 19 30.25 20.125 31.5c1.1667 1.25 1.75 2.7917 1.75 4.625 0 2.5-1.1042 4.5208-3.3125 6.0625C16.3958 43.7292 13.5833 44.5 10.125 44.5ZM24.8125 57.75l-.1875-2.6875 3.875-1c0-.2083.0208-.625.0625-1.25s.0625-1.4375.0625-2.4375c.0417-1 .0833-2.1667.125-3.5s.0625-2.8125.0625-4.4375l.125-13.375c0-1.25-.0417-2.4167-.125-3.5s-.2292-2.1042-.4375-3.0625l-4-.875v-2.5625l10.1875-2.6875 1.5 1.0625-.4375 3.625.1875.0625c1.0417-1.5 2.2917-2.6875 3.75-3.5625S42.5833 16.25 44.25 16.25c2.0833 0 4 .5417 5.75 1.625 1.75 1.0417 3.1458 2.5417 4.1875 4.5 1.0833 1.9583 1.625 4.2917 1.625 7 0 2.75-.625 5.2708-1.875 7.5625-1.25 2.25-3 4.0625-5.25 5.4375s-4.875 2.0625-7.875 2.0625c-.7917 0-1.625-.0833-2.5-.25-.875-.125-1.6875-.3333-2.4375-.625V47.75c0 1.4583.0208 2.7917.0625 4 .0833 1.2083.1458 1.9792.1875 2.3125L41.25 55l-.1875 2.75h-16.25Zm16.125-17.3125c1.4583 0 2.7083-.3333 3.75-1 1.0833-.7083 1.9375-1.75 2.5625-3.125.625-1.4167.9375-3.1667.9375-5.25.0417-2.0833-.2708-3.8125-.9375-5.1875-.625-1.4167-1.4583-2.4792-2.5-3.1875-1.0417-.7083-2.2083-1.0625-3.5-1.0625-1.0417 0-2.0208.25-2.9375.75-.9167.4583-1.7292 1.1458-2.4375 2.0625v14.5c.6667.4583 1.4375.8333 2.3125 1.125.9167.25 1.8333.375 2.75.375ZM59.5625 44l-.1875-2.6875 3.875-1c.0833-.375.1458-1.1042.1875-2.1875.0417-1.0833.0833-2.3958.125-3.9375.0417-1.5833.0625-3.2292.0625-4.9375v-7.8125c0-1.4167-.0208-2.875-.0625-4.375 0-1.5-.0208-2.9375-.0625-4.3125 0-1.4167-.0208-2.6667-.0625-3.75-.0417-1.125-.0833-1.97917-.125-2.5625l-4.625-.625-.125-2.4375L69.375.5l1.5625 1c-.0417 1.29167-.1042 2.5625-.1875 3.8125-.0417 1.20833-.0833 2.54167-.125 4 0 1.4583-.0208 3.125-.0625 5-.0417 1.8333-.0833 4.0208-.125 6.5625l.1875.0625c1.3333-1.5833 2.75-2.75 4.25-3.5 1.5-.7917 3.1042-1.1875 4.8125-1.1875s3.25.4375 4.625 1.3125c1.4167.8333 2.5417 2.0833 3.375 3.75.8333 1.6667 1.25 3.7083 1.25 6.125v6.5c0 1.4167.0208 2.75.0625 4 .0833 1.2083.1667 2 .25 2.375l3.875 1L92.9375 44h-14.625l-.1875-2.6875 3.5625-1c.0833-.3333.1458-.8125.1875-1.4375.0417-.625.0625-1.3542.0625-2.1875C81.9792 35.8542 82 35 82 34.125v-5.75c0-1.625-.2708-2.9375-.8125-3.9375-.5-1.0417-1.1667-1.7917-2-2.25-.8333-.5-1.75-.75-2.75-.75-1.125 0-2.2083.2708-3.25.8125-1 .5-1.875 1.25-2.625 2.25v9.25c0 1.6667.0208 3.1042.0625 4.3125.0833 1.1667.1875 1.9167.3125 2.25l3.8125 1L74.5625 44h-15ZM95.1875 44 95 41.25l4.1875-.9375c.0417-.1667.0833-.5208.125-1.0625s.0833-1.25.125-2.125c.0417-.9167.0625-1.9792.0625-3.1875l.0625-4.75c0-1.2917-.0417-2.4792-.125-3.5625-.0833-1.125-.2292-2.1667-.4375-3.125l-4.1875-.875v-2.5625L105.5 16.375l1.438 1.0625c-.126 1.2917-.23 2.6042-.313 3.9375-.042 1.2917-.063 2.7917-.063 4.5v7.875c0 1.5417.021 2.9375.063 4.1875.083 1.25.187 2.0417.313 2.375l4.187.9375-.187 2.75H95.1875Zm7.9375-33.4375c-1.458 0-2.625-.4375-3.5-1.3125-.875-.91667-1.3125-2-1.3125-3.25 0-1.20833.4375-2.27083 1.3125-3.1875.875-.91667 2.042-1.375 3.5-1.375 1.458 0 2.604.45833 3.437 1.375.834.91667 1.25 1.97917 1.25 3.1875 0 1.25-.416 2.33333-1.25 3.25-.833.875-1.979 1.3125-3.437 1.3125ZM113.312 44l-.187-2.6875 3.875-1c.042-.1667.083-.5.125-1 .042-.5417.083-1.25.125-2.125.042-.9167.062-2 .062-3.25l.063-4.75c0-1.3333-.042-2.5417-.125-3.625-.083-1.0833-.229-2.1042-.438-3.0625l-4-.875v-2.5625L123 16.375l1.5 1.0625-.375 3.5.187.0625c1.334-1.5833 2.75-2.7708 4.25-3.5625 1.542-.7917 3.167-1.1875 4.876-1.1875 1.708 0 3.25.4375 4.624 1.3125 1.417.8333 2.542 2.0833 3.376 3.75.833 1.6667 1.25 3.7083 1.25 6.125v6.5c0 1.4167.02 2.75.062 4 .042 1.2083.125 2 .25 2.375l3.875 1L146.688 44H132l-.188-2.6875 3.626-1c.083-.3333.145-.8125.187-1.4375.042-.625.063-1.3542.063-2.1875.041-.8333.062-1.6875.062-2.5625v-5.75c0-1.625-.271-2.9375-.812-3.9375-.5-1.0417-1.167-1.7917-2-2.25-.834-.5-1.75-.75-2.75-.75-1.167 0-2.25.2708-3.25.8125-1 .5-1.876 1.25-2.626 2.25v9.25c0 1.5833.021 3 .063 4.25.083 1.2083.187 1.9792.313 2.3125l3.874 1L128.375 44h-15.063ZM178.562 44h-13.874l-.188-2.6875 2.938-1-5.626-7.375-1.937-2.4375-7.813-10.0625-2.937-1 .187-2.6875h13.626l.187 2.6875-2.625 1 4.5 5.875 1.938 2.5625 8.874 11.4375 2.938 1L178.562 44Zm-8.937-23.5625-2.625-1 .188-2.6875H177l.188 2.6875-2.938 1-7.312 8.4375-5.126 4.0625-5.312 7.375 2.938 1L159.25 44h-10.188l-.187-2.6875 2.937-1 8.063-9.8125L165 26.3125l4.625-5.875Z" />
11+ </svg>
12+ )
13+}
ui/src/components/Meta.tsxnew file+41
@@ -0,0 +1,41 @@
1+import { ChargeUpdateInitial, scryCharges } from '@urbit/api';
2+import React, { useEffect, useState } from 'react';
3+import api from '../api';
4+import { Logo } from './Logo';
5+
6+const SOURCE = 'github.com/iko-soy/sphinx';
7+
8+interface MetaProps {
9+ className?: string;
10+}
11+
12+export const Meta = ({ className }: MetaProps) => {
13+ const [version, setVersion] = useState('');
14+
15+ useEffect(() => {
16+ if (!version) {
17+ api.scry<ChargeUpdateInitial>(scryCharges).then(charges => {
18+ const version = charges.initial.sphinx?.version;
19+
20+ if (version) {
21+ setVersion(version);
22+ }
23+ })
24+ }
25+ }, []);
26+
27+ return (
28+ <footer className={className}>
29+ <div className='flex mb-2 space-x-3 items-end'>
30+ <Logo className="w-24 text-rosy" />
31+ {version && <span className='mb-[3px]'>v{version}</span>}
32+ </div>
33+ <a
34+ className="inline-block font-mono default-ring underline rounded-md hover:text-rosy"
35+ href={`https://${SOURCE}`}
36+ >
37+ {SOURCE}
38+ </a>
39+ </footer>
40+ )
41+}
ui/src/components/PostOptions.tsxnew file+56
@@ -0,0 +1,56 @@
1+import { CheckIcon } from '@heroicons/react/solid';
2+import cn from 'classnames';
3+import React from 'react';
4+import { useFormContext } from 'react-hook-form';
5+import { PostOption, PostOptionsForm } from '../types/sphinx';
6+import { normalizeColor } from '../utils';
7+
8+interface PostOptionsProps {
9+ options: PostOption[];
10+ showTags?: boolean;
11+ className?: string;
12+}
13+
14+export const PostOptions = ({ options, showTags = false, className }: PostOptionsProps) => {
15+ const { register } = useFormContext<PostOptionsForm>();
16+
17+ return (
18+ <ul className={cn('space-y-3', className)}>
19+ {options && options.map((o, index) => (
20+ <li key={o.key}>
21+ <input {...register('options')} id={o.key} value={o.key} type="checkbox" className='sr-only' />
22+ <label htmlFor={o.key} className='checked-bg-fawn group-1 relative grid grid-rows-[auto,auto] sm:grid-rows-1 grid-cols-[64px,1fr] sm:grid-cols-[64px,1fr,24px] w-full p-2 hover:bg-fawn cursor-pointer rounded-xl'>
23+ <div className='absolute z-10 top-7 left-7 flex items-center justify-center h-6 w-6 border-2 border-solid border-mauve rounded bg-linen/80'>
24+ <CheckIcon className='show-when-checked h-4 w-4 opacity-0' />
25+ </div>
26+ {o.post.image ? (
27+ <img
28+ src={o.post.image}
29+ className="show-when-checked w-16 h-16 col-span-1 object-cover rounded-lg opacity-60"
30+ loading={index > 15 ? 'lazy' : 'eager'}
31+ style={{ backgroundColor: o.post.color ? normalizeColor(o.post.color) : undefined }}
32+ />
33+ ) : (
34+ <div
35+ className='w-16 h-16 col-span-1 bg-rosy/20 rounded-lg'
36+ style={{ backgroundColor: o.post.color ? normalizeColor(o.post.color) : undefined }}
37+ />
38+ )}
39+ <div className='ml-4 min-w-0 sm:space-y-2'>
40+ <span className='block font-semibold text-lg leading-none mb-2 sm:mb-0'>{o.post.title}</span>
41+ <div className='flex flex-col sm:flex-row sm:items-center w-full text-xs space-y-2 sm:space-y-0 sm:space-x-3 truncate'>
42+ <strong className='font-mono'>%{o.post.type}</strong>
43+ {showTags && <p><strong>tags: </strong><em>{o.post.tags.join()}</em></p>}
44+ </div>
45+ <p className='hidden sm:block font-mono text-xs truncate'>{o.post.link.replace('web+urbitgraph://', '')}</p>
46+ </div>
47+ <div className='col-span-2 row-start-2 mt-2'>
48+ <p className='font-mono text-xs truncate sm:hidden'>{o.post.link.replace('web+urbitgraph://', '')}</p>
49+ <p className='text-sm'>{o.post.description}</p>
50+ </div>
51+ </label>
52+ </li>
53+ ))}
54+ </ul>
55+ )
56+}
ui/src/components/SearchInput.tsx+6−3
@@ -1,29 +1,32 @@
11import { SearchIcon } from '@heroicons/react/solid';
22import cn from 'classnames';
33import React, { ChangeEvent, useCallback } from 'react';
4+import { Spinner } from './Spinner';
45
56interface SearchInputProps {
67 lookup: string;
8+ loading: boolean;
79 className?: string;
810 onChange: (value: string) => void;
911}
1012
11−export const SearchInput = ({ lookup, className, onChange }: SearchInputProps) => {
13+export const SearchInput = ({ lookup, loading, className, onChange }: SearchInputProps) => {
1214 const onInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
1315 const { value } = event.target;
1416 onChange(value);
1517 }, [onChange]);
1618
1719 return (
18− <div className='relative flex items-center'>
20+ <div className={cn('relative flex items-center', className)}>
1921 <SearchIcon className='flip absolute left-2 h-5 w-5' />
2022 <input
2123 type='text'
2224 value={lookup}
2325 onChange={onInput}
2426 placeholder="Search"
25− className={cn('w-full py-1 pl-9 pr-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg', className)}
27+ className={cn('w-full py-1 pl-9 pr-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30')}
2628 />
29+ {loading && <Spinner className='absolute right-2 h-5 w-5' />}
2730 </div>
2831 )
2932}
ui/src/components/SearchList.tsxdeleted−37
@@ -1,37 +0,0 @@
1−import cn from 'classnames';
2−import React from 'react';
3−import { Listing } from '../types/seek';
4−
5−interface SearchListProps {
6− listings: Listing[];
7− className?: string;
8−}
9−
10−export const SearchList = ({ listings, className }: SearchListProps) => {
11− return (
12− <ul className={cn('px-2 space-y-6', className)}>
13− {listings && listings.map(l => (
14− <li key={l.hash}>
15− <article className='group relative flex w-full'>
16− {l.post.image ? (
17− <img src={l.post.image} className="absolute -left-28 w-24 h-24 object-cover" />
18− ) : <div className='absolute -left-28 w-24 h-24' />}
19− <div className='flex-1'>
20− <a className='block w-full mb-2 hover:text-rosy transition-colors' href={l.post.link}>
21− <h2 className='block font-semibold text-lg leading-none mb-1'>{l.post.title}</h2>
22− <span className='block font-mono text-xs'>{l.post.link.replace('web+urbitgraph://', '')}</span>
23− </a>
24− <p className='mb-2 text-sm'>{l.post.description}</p>
25− <div className='flex font-mono text-xs text-zinc-500 space-x-3 opacity-0 group-hover:opacity-100 transition-opacity'>
26− <span><span className='font-semibold font-sans'>by:</span> {l.source}</span>
27− <time dateTime={(new Date(l.time)).toISOString()}>
28− {(new Date(l.time)).toLocaleString()}
29− </time>
30− </div>
31− </div>
32− </article>
33− </li>
34− ))}
35− </ul>
36− )
37−}
ui/src/components/Spinner.tsxnew file+11
@@ -0,0 +1,11 @@
1+import cn from 'classnames';
2+import React from 'react';
3+
4+export const Spinner = ({ className }: { className: string }) => {
5+ return (
6+ <svg className={cn('spinner', className)} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
7+ <circle cx="12" cy="12" r="10" className="stroke-current" strokeOpacity="0.4" strokeWidth="4"></circle>
8+ <path className="fill-current" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
9+ </svg>
10+ )
11+}
ui/src/components/TagCloud.tsxnew file+18
@@ -0,0 +1,18 @@
1+import cn from 'classnames';
2+import React from 'react';
3+import { NavLink } from 'react-router-dom';
4+
5+interface TagCloudProps {
6+ tags: [string, number][];
7+}
8+
9+export const TagCloud = ({ tags }: TagCloudProps) => (
10+ <section className='flex flex-wrap gap-2'>
11+ {tags.map(([t, count]) => (
12+ <NavLink key={t} to={`/tags/${t}`} className={({ isActive }) => cn("py-0.5 px-1.5 space-x-2 rounded-md font-semibold", isActive ? 'bg-lavender text-linen' : 'bg-fawn')}>
13+ <span>{t}</span>
14+ <span className='opacity-60'>{count}</span>
15+ </NavLink>
16+ ))}
17+ </section>
18+)
ui/src/components/TagField.tsxnew file+157
@@ -0,0 +1,157 @@
1+import React, { Component, KeyboardEventHandler, useCallback, useState } from 'react';
2+
3+import CreatableSelect from 'react-select/creatable';
4+import { ActionMeta, components, ControlProps, MultiValue, MultiValueGenericProps, MultiValueRemoveProps, OnChangeValue } from 'react-select';
5+import { XIcon } from '@heroicons/react/solid';
6+import { MAX_TAG_BYTES, MAX_TAGS, byteLength } from '../utils';
7+
8+export interface Option {
9+ readonly label: string;
10+ readonly value: string;
11+}
12+
13+const createOption = (label: string) => ({
14+ label,
15+ value: label,
16+});
17+
18+function Control({ children, ...props }: ControlProps<Option, true>) {
19+ return (
20+ <components.Control
21+ {...props}
22+ className="flex items-center w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30 cursor-text text-mauve"
23+ >
24+ {children}
25+ </components.Control>
26+ );
27+}
28+
29+function TagContainer({
30+ children,
31+ ...props
32+}: MultiValueGenericProps<Option, true>) {
33+ return (
34+ <components.MultiValueContainer {...props}>
35+ <div className="flex">{children}</div>
36+ </components.MultiValueContainer>
37+ );
38+}
39+
40+function TagLabel({ data }: { data: Option }) {
41+ const { value, label } = data;
42+ return (
43+ <div className="flex h-6 items-center rounded-l bg-lavender text-linen">
44+ <span className="p-1 font-semibold">{label || value}</span>
45+ </div>
46+ );
47+}
48+
49+function TagRemove(props: MultiValueRemoveProps<Option, true>) {
50+ return (
51+ <components.MultiValueRemove {...props}>
52+ <div className="flex h-full items-center rounded-r bg-lavender pr-1">
53+ <XIcon className="h-4 w-4 text-linen" />
54+ </div>
55+ </components.MultiValueRemove>
56+ );
57+}
58+
59+interface TagFieldProps {
60+ tags: MultiValue<Option>;
61+ onTags: (tags: MultiValue<Option>) => void;
62+ className?: string;
63+}
64+
65+export const TagField = ({ tags, onTags, className }: TagFieldProps) => {
66+ const [input, setInput] = useState('');
67+
68+ const handleChange = useCallback((
69+ value: OnChangeValue<Option, true>,
70+ actionMeta: ActionMeta<Option>
71+ ) => {
72+ onTags(value);
73+ }, []);
74+
75+ const handleInputChange = useCallback((value) => {
76+ setInput(value);
77+ }, []);
78+
79+ const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = useCallback((event) => {
80+ if (!input) return;
81+
82+ switch (event.key) {
83+ case 'Enter':
84+ case 'Tab':
85+ // %sphinx drops listings with more than 8 tags and doesn't index
86+ // longer tags, so keep the input; Tab still moves on
87+ if (tags.length >= MAX_TAGS || byteLength(input) > MAX_TAG_BYTES) {
88+ if (event.key === 'Enter') event.preventDefault();
89+ return;
90+ }
91+
92+ event.preventDefault();
93+ setInput('');
94+ if (!tags.some(t => t.value === input)) {
95+ onTags([...tags, createOption(input)])
96+ }
97+ }
98+ }, [input, tags]);
99+
100+ return (
101+ <div className={className}>
102+ <CreatableSelect<Option, true>
103+ components={{
104+ ...components,
105+ Control,
106+ MultiValueContainer: TagContainer,
107+ MultiValueLabel: TagLabel,
108+ MultiValueRemove: TagRemove,
109+ DropdownIndicator: null
110+ }}
111+ styles={{
112+ control: (base) => ({}),
113+ input: (base) => ({
114+ ...base,
115+ padding: 0,
116+ margin: 0,
117+ }),
118+ clearIndicator: (base) => ({
119+ ...base,
120+ cursor: 'pointer',
121+ padding: 0
122+ }),
123+ multiValue: (base) => ({
124+ ...base,
125+ backgroundColor: '',
126+ margin: '0 2px',
127+ }),
128+ multiValueRemove: (base) => ({
129+ ...base,
130+ paddingRight: '',
131+ paddingLeft: '',
132+ '&:hover': {
133+ color: 'inherit',
134+ backgroundColor: 'inherit',
135+ },
136+ }),
137+ valueContainer: (base) => ({
138+ ...base,
139+ padding: 0
140+ })
141+ }}
142+ isOptionDisabled={() => tags.length >= MAX_TAGS}
143+ inputValue={input}
144+ isClearable
145+ isMulti
146+ menuIsOpen={false}
147+ onChange={handleChange}
148+ onInputChange={handleInputChange}
149+ onKeyDown={handleKeyDown}
150+ placeholder="Type something and press enter..."
151+ value={tags}
152+ />
153+ {byteLength(input) > MAX_TAG_BYTES && <div className='text-mauve/50 text-xs mt-1'>{MAX_TAG_BYTES} bytes per tag maximum</div>}
154+ {tags.length >= MAX_TAGS && <div className='text-mauve/50 text-xs mt-1'>{MAX_TAGS} tags maximum</div>}
155+ </div>
156+ );
157+}
ui/src/env.d.ts+8
@@ -5,3 +5,11 @@ interface ImportMetaEnv extends Readonly<Record<string, string | boolean | undef
55interface ImportMeta {
66 readonly env: ImportMetaEnv;
77}
8+
9+declare module 'remove-markdown-and-html' {
10+ export default function removeMd(input: string): string;
11+}
12+
13+declare module 'urbit-ob' {
14+ export function isValidPatp(patp: string): boolean;
15+}
ui/src/index.cssdeleted−7
@@ -1,7 +0,0 @@
1−@tailwind base;
2−@tailwind components;
3−@tailwind utilities;
4−
5−.flip {
6− transform: rotateY(180deg);
7−}
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/useMedia.tsnew file+25
@@ -0,0 +1,25 @@
1+import { useCallback, useEffect, useState } from 'react';
2+
3+export default function useMedia(mediaQuery: string) {
4+ const [match, setMatch] = useState(false);
5+
6+ const update = useCallback((e: MediaQueryListEvent) => {
7+ setMatch(e.matches);
8+ }, []);
9+
10+ useEffect(() => {
11+ const query = window.matchMedia(mediaQuery);
12+
13+ query.addEventListener('change', update);
14+ update({ matches: query.matches } as MediaQueryListEvent);
15+ return () => {
16+ query.removeEventListener('change', update);
17+ };
18+ }, [update, mediaQuery]);
19+
20+ return match;
21+}
22+
23+export function useIsMobile() {
24+ return useMedia('(max-width: 639px)');
25+}
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/main.tsx+1−1
@@ -1,7 +1,7 @@
11import React from 'react';
22import ReactDOM from 'react-dom';
33import { App } from './app';
4−import './index.css';
4+import './styles/index.css';
55
66ReactDOM.render(
77 <React.StrictMode>
ui/src/manage-listings/AllListings.tsxnew file+65
@@ -0,0 +1,65 @@
1+import React from 'react';
2+import { useMutation, useQueryClient } from '@tanstack/react-query';
3+import { useParams } from 'react-router-dom';
4+import api from '../api';
5+import { Listings } from '../components/Listings';
6+import { Paginator } from '../components/Paginator';
7+import { useSearch } from '../state/search';
8+import { Remove, Search } from '../types/sphinx';
9+import { PUBLISHED_KEY } from '../keys';
10+import { formatUd } from '../utils';
11+
12+interface RouteParams extends Record<string, string | undefined> {
13+ limit?: string;
14+ page?: string;
15+}
16+
17+export const AllListings = () => {
18+ const queryClient = useQueryClient();
19+ const {
20+ limit,
21+ page
22+ } = useParams<RouteParams>();
23+ const {
24+ results,
25+ pages,
26+ pageInt,
27+ linkBuild
28+ } = useSearch({
29+ key: (start, size) => ['all', start, size],
30+ fetcher: (start, size) => api.scry<Search>({
31+ app: 'sphinx',
32+ path: `/lookup/all/${formatUd(start)}/${formatUd(size)}`
33+ }),
34+ enabled: true,
35+ limit,
36+ page,
37+ linkPrefix: '/manage-listings/all'
38+ })
39+ const { mutate } = useMutation((hash: string) => {
40+ return api.poke<Remove>({
41+ app: 'sphinx',
42+ mark: 'remove',
43+ json: hash
44+ })
45+ }, {
46+ onSuccess: () => {
47+ queryClient.invalidateQueries(PUBLISHED_KEY);
48+ }
49+ });
50+
51+ return (
52+ <>
53+ <header className='flex items-center'>
54+ <h1 className='text-2xl font-semibold'>All Listings</h1>
55+ </header>
56+ {results && <div className='flex justify-end border-t border-zinc-300'>
57+ <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
58+ </div>}
59+ <Listings listings={results.listings} remove={mutate} />
60+ {results && pages > 1 && <div className='flex justify-end border-t border-zinc-300'>
61+ <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
62+ </div>}
63+ </>
64+ )
65+}
ui/src/manage-listings/Apps.tsxnew file+92
@@ -0,0 +1,92 @@
1+import React, { useCallback, useEffect, useState } from 'react';
2+import { FormProvider, useForm } from 'react-hook-form';
3+import { useMutation, useQueryClient } from '@tanstack/react-query';
4+import { Link } from 'react-router-dom';
5+import api from '../api';
6+import { PostOptions } from '../components/PostOptions';
7+import { Spinner } from '../components/Spinner';
8+import { useApps } from '../state/apps';
9+import { Declare, Listing, PostOption, PostOptionsForm } from '../types/sphinx';
10+import { APP_LISTINGS_KEY } from '../keys';
11+
12+function getAppKeys(apps: PostOption[]): string[] {
13+ return apps.map(({ key }) => key);
14+}
15+
16+export const Apps = () => {
17+ const { apps, loading } = useApps();
18+ const form = useForm<PostOptionsForm>({
19+ defaultValues: {
20+ options: []
21+ }
22+ });
23+ const { handleSubmit, reset, watch, setValue } = form;
24+ const options = watch('options');
25+
26+ const toggleAll = useCallback(() => {
27+ if (options.length !== apps.length) {
28+ setValue('options', getAppKeys(apps));
29+ } else {
30+ setValue('options', []);
31+ }
32+ }, [apps, options, setValue]);
33+
34+ const queryClient = useQueryClient();
35+ const { mutate } = useMutation(async (values: PostOptionsForm) => {
36+ return api.poke<Declare[]>({
37+ app: 'sphinx',
38+ mark: 'declarations',
39+ json: apps.filter(a => values.options.includes(a.key)).map(a => ({
40+ post: a.post,
41+ reach: 'friends'
42+ }))
43+ });
44+ }, {
45+ onSuccess: () => {
46+ queryClient.invalidateQueries(APP_LISTINGS_KEY);
47+ }
48+ })
49+
50+ const onSubmit = useCallback((values: PostOptionsForm) => {
51+ mutate(values)
52+ reset()
53+ }, [reset, mutate]);
54+
55+ return (
56+ <div className='w-full space-y-6 m-auto'>
57+ <header className='flex items-center'>
58+ <h1 className='text-2xl font-semibold'>Add Apps</h1>
59+ {apps.length > 0 && (
60+ <button className='p-2 ml-auto text-sm underline font-semibold' onClick={toggleAll}>
61+ {options.length !== apps.length && 'select all'}
62+ {options.length === apps.length && 'deselect all'}
63+ </button>
64+ )}
65+ </header>
66+ <FormProvider {...form}>
67+ <form onSubmit={handleSubmit(onSubmit)}>
68+ {apps.length === 0 && loading ? (
69+ <div className='flex items-center space-x-2 text-lavender'>
70+ <Spinner className='w-6 h-6' />
71+ <span>Checking apps...</span>
72+ </div>
73+ ) : apps.length === 0 && !loading ? (
74+ <h2 className='text-lavender'>All apps already indexed</h2>
75+ ) : (
76+ <PostOptions options={apps} />
77+ )}
78+ <div className='flex justify-between border-t border-zinc-300 py-3 mt-6'>
79+ <Link to="/search" className='flex items-center rounded-lg text-base font-semibold text-rosy bg-rosy/30 border-2 border-transparent hover:border-rosy leading-none py-2 px-3 transition-colors'>
80+ Back to Search
81+ </Link>
82+ {apps.length > 0 && (
83+ <button type="submit" className='flex items-center rounded-lg text-base font-semibold text-linen bg-rosy disabled:bg-zinc-200 disabled:text-zinc-400 disabled:border-transparent border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors' disabled={options.length === 0}>
84+ Publish
85+ </button>
86+ )}
87+ </div>
88+ </form>
89+ </FormProvider>
90+ </div>
91+ );
92+}
ui/src/manage-listings/Groups.tsxnew file+99
@@ -0,0 +1,99 @@
1+import React, { useCallback, useEffect, useState } from 'react';
2+import { FormProvider, useForm } from 'react-hook-form';
3+import { useMutation, useQueryClient } from '@tanstack/react-query';
4+import { Link } from 'react-router-dom';
5+import api from '../api';
6+import { PostOptions } from '../components/PostOptions';
7+import { Spinner } from '../components/Spinner';
8+import { useGroups } from '../state/groups';
9+import { Declare, PostOptionsForm } from '../types/sphinx';
10+import { GROUP_LISTINGS_KEY } from '../keys';
11+
12+export const Groups = () => {
13+ const { groups, loading } = useGroups();
14+ const [firstLoad, setFirstLoad] = useState(true);
15+ const form = useForm<PostOptionsForm>({
16+ defaultValues: {
17+ options: []
18+ }
19+ });
20+ const { handleSubmit, reset, watch, setValue } = form;
21+ const options = watch('options');
22+
23+ useEffect(() => {
24+ if (groups.length > 0 && firstLoad) {
25+ reset({
26+ options: []
27+ })
28+
29+ setFirstLoad(false);
30+ }
31+ }, [groups, firstLoad]);
32+
33+ const toggleAll = useCallback(() => {
34+ if (options.length !== groups.length) {
35+ setValue('options', groups.map(g => g.key));
36+ } else {
37+ setValue('options', []);
38+ }
39+ }, [groups, options, setValue]);
40+
41+ const queryClient = useQueryClient();
42+ const { mutate } = useMutation(async (values: PostOptionsForm) => {
43+ return api.poke<Declare[]>({
44+ app: 'sphinx',
45+ mark: 'declarations',
46+ json: groups.filter(a => values.options.includes(a.key)).map(a => ({
47+ post: a.post,
48+ reach: 'friends'
49+ }))
50+ });
51+ }, {
52+ onSuccess: () => {
53+ queryClient.invalidateQueries(GROUP_LISTINGS_KEY)
54+ }
55+ })
56+
57+ const onSubmit = useCallback((values: PostOptionsForm) => {
58+ mutate(values);
59+ reset();
60+ }, [reset, mutate]);
61+
62+ return (
63+ <div className='w-full space-y-6 m-auto'>
64+ <header className='flex items-center'>
65+ <h1 className='text-2xl font-semibold'>Add Groups</h1>
66+ {groups.length > 0 && (
67+ <button className='p-2 ml-auto text-sm underline font-semibold' onClick={toggleAll}>
68+ {options.length !== groups.length && 'select all'}
69+ {options.length === groups.length && 'deselect all'}
70+ </button>
71+ )}
72+ </header>
73+ <FormProvider {...form}>
74+ <form onSubmit={handleSubmit(onSubmit)}>
75+ {groups.length === 0 && loading ? (
76+ <div className='flex items-center space-x-2 text-lavender'>
77+ <Spinner className='w-6 h-6' />
78+ <span>Checking groups...</span>
79+ </div>
80+ ) : groups.length === 0 && !loading ? (
81+ <h2 className='text-lavender'>All groups already indexed</h2>
82+ ) : (
83+ <PostOptions options={groups} />
84+ )}
85+ <div className='flex justify-between border-t border-zinc-300 py-3 mt-6'>
86+ <Link to="/search" className='flex items-center rounded-lg text-base font-semibold text-rosy bg-rosy/30 border-2 border-transparent hover:border-rosy leading-none py-2 px-3 transition-colors'>
87+ Back to Search
88+ </Link>
89+ {groups.length > 0 && (
90+ <button type="submit" className='flex items-center rounded-lg text-base font-semibold text-linen bg-rosy disabled:bg-zinc-200 disabled:text-zinc-400 disabled:border-transparent border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors' disabled={options.length === 0}>
91+ Publish
92+ </button>
93+ )}
94+ </div>
95+ </form>
96+ </FormProvider>
97+ </div>
98+ );
99+}
ui/src/manage-listings/MyListings.tsxnew file+34
@@ -0,0 +1,34 @@
1+import React from 'react';
2+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
3+import api from '../api';
4+import { Listings } from '../components/Listings';
5+import { Directory, Remove } from '../types/sphinx';
6+import { PUBLISHED_KEY } from '../keys';
7+
8+export const MyListings = () => {
9+ const queryClient = useQueryClient();
10+ const { data } = useQuery(PUBLISHED_KEY, () => api.scry<Directory>({
11+ app: 'sphinx',
12+ path: '/published'
13+ }));
14+ const { mutate } = useMutation((hash: string) => {
15+ return api.poke<Remove>({
16+ app: 'sphinx',
17+ mark: 'remove',
18+ json: hash
19+ })
20+ }, {
21+ onSuccess: () => {
22+ queryClient.invalidateQueries(PUBLISHED_KEY);
23+ }
24+ });
25+
26+ return (
27+ <div className='w-full space-y-6 m-auto'>
28+ <header className='flex items-center'>
29+ <h1 className='text-2xl font-semibold'>My Listings</h1>
30+ </header>
31+ <Listings listings={Object.values(data || {})} remove={mutate} className="mt-6" />
32+ </div>
33+ )
34+}
ui/src/manage-listings/Notebooks.tsxnew file+70
@@ -0,0 +1,70 @@
1+import cn from 'classnames';
2+import React from 'react';
3+import { NavLink, useParams } from 'react-router-dom';
4+import { Spinner } from '../components/Spinner';
5+import useMedia from '../logic/useMedia';
6+import { useNotebooks } from '../state/posts';
7+import { Posts } from './Posts';
8+import { getColor, getImage } from '../utils';
9+
10+export const Notebooks = () => {
11+ const { ship, name } = useParams<{ ship: string, name: string }>();
12+ const limited = useMedia('(max-width: 899px)');
13+ const { groups, notebooks, loading } = useNotebooks();
14+ const sortedNotebooks = Object.entries(notebooks)
15+ .sort(([,a], [,b]) => a.meta.title.localeCompare(b.meta.title));
16+
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">
19+ <div className='flex items-start'>
20+ {((limited && !ship) || !limited) &&
21+ <aside className='sticky top-4 sm:top-12 flex-none flex flex-col sm:w-72 sm:h-[80vh] pt-14 sm:pt-0 mr-6 self-start'>
22+ <header className='flex-none fixed sm:static top-0 w-full p-4 pl-0 sm:p-0 bg-linen'>
23+ <h1 className='text-2xl font-semibold ml-2 sm:mb-6'>Add Posts</h1>
24+ </header>
25+ <ul className='flex-1 sm:overflow-y-auto'>
26+ {loading && <div className='flex items-center space-x-2 text-lavender mt-6'>
27+ <Spinner className='w-6 h-6' />
28+ <span>Loading notebooks...</span>
29+ </div>}
30+ {sortedNotebooks.map(([id, n], index) => {
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) || '');
34+
35+ return (
36+ <li key={id}>
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')}>
38+ {group && image ? (
39+ <img
40+ src={image}
41+ className="flex-none w-10 h-10 object-cover rounded-md"
42+ loading={index > 15 ? 'lazy' : 'eager'}
43+ style={{ backgroundColor: color }}
44+ />
45+ ) : group ? (
46+ <div
47+ className='flex-none w-10 h-10 bg-rosy/20 rounded-md'
48+ style={{ backgroundColor: color }}
49+ />
50+ ) : null}
51+ <div className='flex flex-col min-w-0'>
52+ <strong className='w-full truncate'>{n.meta.title}</strong>
53+ {group && <span className='text-sm'>{group.meta.title}</span>}
54+ </div>
55+ </NavLink>
56+ </li>
57+ )}
58+ )}
59+ </ul>
60+ </aside>
61+ }
62+ {((limited && ship) || !limited) &&
63+ <section className='flex-1 min-w-0 space-y-6'>
64+ {!loading && notebooks && ship && name && <Posts ship={ship} name={name} />}
65+ </section>
66+ }
67+ </div>
68+ </div>
69+ )
70+}
ui/src/manage-listings/Post.tsxnew file+142
@@ -0,0 +1,142 @@
1+import debounce from 'lodash.debounce';
2+import React, { useCallback, useEffect, useRef, useState } from 'react';
3+import { FormProvider, useForm } from 'react-hook-form';
4+import { Link, useNavigate, useSearchParams } from 'react-router-dom';
5+import { MultiValue } from 'react-select';
6+import api from '../api';
7+import { ErrorMessage } from '../components/ErrorMessage';
8+import { Filter } from '../components/Filter';
9+import { Option, TagField } from '../components/TagField';
10+import { Declare, Post as PostForm, PostType } from '../types/sphinx';
11+import {
12+ MAX_DESCRIPTION_BYTES, MAX_TAG_BYTES, MAX_TAGS, MAX_TITLE_BYTES, MAX_URL_BYTES,
13+ byteLength, isListingImage, isListingLink
14+} from '../utils';
15+
16+function errorMessages(length: number, url?: string) {
17+ return {
18+ required: 'This field is required.',
19+ bytes: `The maximum length is ${length} bytes (accented letters and emoji count as 2-4).`,
20+ url
21+ }
22+}
23+
24+// lib/sphinx +lengths-valid counts UTF-8 bytes and drops longer listings silently
25+const maxBytes = (max: number) => ({ bytes: (value: string) => byteLength(value || '') <= max });
26+
27+// what lib/validate-url rejects, which %sphinx also drops silently
28+const URL_RULES = 'no spaces, #fragments, non-ASCII letters or characters like | [ ] { }';
29+
30+export const Post = () => {
31+ const [params] = useSearchParams();
32+ // ?tags= can bring in tags that TagField wouldn't take
33+ const defaultTags = params.getAll('tags')
34+ .filter((t, i, all) => t && byteLength(t) <= MAX_TAG_BYTES && all.indexOf(t) === i)
35+ .slice(0, MAX_TAGS)
36+ .map(t => ({ label: t, value: t}));
37+ const [tags, setTags] = useState<MultiValue<Option>>(defaultTags);
38+ const [image, setImage] = useState<string>('');
39+ const form = useForm<PostForm>({
40+ defaultValues: {
41+ title: params.get('title') || '',
42+ type: params.get('type') as PostType || 'other',
43+ link: params.get('link') || '',
44+ image: params.get('image') || '',
45+ description: params.get('description') || '',
46+ color: params.get('color') || ''
47+ }
48+ });
49+
50+ const { register, watch, reset, setValue, handleSubmit } = form;
51+
52+ const updateImg = useRef(debounce(setImage));
53+ const img = watch('image');
54+ const type = watch('type');
55+
56+ const onSubmit = useCallback((values: Omit<PostForm, 'tags'>) => {
57+ api.poke<Declare>({
58+ app: 'sphinx',
59+ mark: 'declare',
60+ json: {
61+ reach: 'friends',
62+ post: {
63+ ...values,
64+ tags: tags.map(t => t.value)
65+ }
66+ }
67+ })
68+ reset();
69+ setTags([]);
70+ }, [tags]);
71+
72+ useEffect(() => {
73+ register('type')
74+ }, []);
75+
76+ useEffect(() => {
77+ if (img) {
78+ updateImg.current(img)
79+ }
80+ }, [img]);
81+
82+ return (
83+ <div className='w-full space-y-6 m-auto'>
84+ <header>
85+ <h1 className='text-2xl font-semibold'>Add a Listing</h1>
86+ </header>
87+ <FormProvider {...form}>
88+ {/* only our checks: the browser's type="url" check refuses relative links like /1/group/~zod/x */}
89+ <form noValidate onSubmit={handleSubmit(onSubmit)}>
90+ <div className='flex w-full space-x-6'>
91+ <div className='flex-1 space-y-3'>
92+ <div>
93+ <label htmlFor='title' className='text-sm font-semibold'>Title</label>
94+ <div className='flex items-center space-x-2'>
95+ <input {...register('title', { required: true, validate: maxBytes(MAX_TITLE_BYTES) })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='sphinx'/>
96+ <Filter selected={type} onSelect={(value) => setValue('type', value)} />
97+ </div>
98+ <ErrorMessage className='mt-1' field="title" messages={errorMessages(MAX_TITLE_BYTES)}/>
99+ </div>
100+ <div className='flex items-center space-x-6'>
101+ <div className='flex-1 space-y-3'>
102+ <div>
103+ <label htmlFor='image' className='text-sm font-semibold'>Image</label>
104+ <input type="url" {...register('image', {
105+ validate: { ...maxBytes(MAX_URL_BYTES), url: isListingImage }
106+ })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='https://nyc3.digitaloceanspaces.com/...' />
107+ <ErrorMessage className='mt-1' field="image" messages={errorMessages(MAX_URL_BYTES, `Use a web address (https://host/path) with ${URL_RULES}.`)}/>
108+ </div>
109+ <div>
110+ <label htmlFor='link' className='text-sm font-semibold'>Link</label>
111+ <input type="url" {...register('link', { required: true, validate: { ...maxBytes(MAX_URL_BYTES), url: isListingLink } })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='web+urbitgraph://~donmec-sallud/sphinx' />
112+ <ErrorMessage className='mt-1' field="link" messages={errorMessages(MAX_URL_BYTES, `Use a link with ${URL_RULES}.`)} />
113+ </div>
114+ </div>
115+ <img className='flex-none object-cover w-28 h-28 mt-4 border-2 border-dashed border-mauve/60 rounded-lg' src={image || undefined} />
116+ </div>
117+ <div>
118+ <label htmlFor='description' className='text-sm font-semibold'>Description</label>
119+ <textarea {...register('description', { required: true, validate: maxBytes(MAX_DESCRIPTION_BYTES) })} rows={2} className='align-middle w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='An app for answering your riddles' />
120+ <ErrorMessage className='mt-1' field="description" messages={errorMessages(MAX_DESCRIPTION_BYTES)} />
121+ </div>
122+ <div>
123+ <label className='text-sm font-semibold'>Tags</label>
124+ <TagField tags={tags} onTags={setTags} />
125+ </div>
126+ <div className='pt-3'>
127+ <div className='flex justify-between border-t border-zinc-300 py-3'>
128+ <Link to="/search" className='flex items-center rounded-lg text-base font-semibold text-rosy bg-rosy/30 border-2 border-transparent hover:border-rosy leading-none py-2 px-3 transition-colors'>
129+ Back to Search
130+ </Link>
131+ <button type="submit" className='flex items-center rounded-lg text-base font-semibold text-linen bg-rosy border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors'>
132+ Publish
133+ </button>
134+ </div>
135+ </div>
136+ </div>
137+ </div>
138+ </form>
139+ </FormProvider>
140+ </div>
141+ )
142+}
ui/src/manage-listings/Posts.tsxnew file+122
@@ -0,0 +1,122 @@
1+import { ChevronLeftIcon } from '@heroicons/react/solid';
2+import React, { useCallback, useEffect, useState } from 'react';
3+import { FormProvider, useForm } from 'react-hook-form';
4+import { useMutation, useQueryClient } from '@tanstack/react-query';
5+import { Link } from 'react-router-dom';
6+import { MultiValue } from 'react-select';
7+import api from '../api';
8+import { Filter } from '../components/Filter';
9+import { PostOptions } from '../components/PostOptions';
10+import { Spinner } from '../components/Spinner';
11+import { TagField, Option } from '../components/TagField';
12+import useMedia from '../logic/useMedia';
13+import { usePosts } from '../state/posts';
14+import { Declare, PostOptionsForm, PostType } from '../types/sphinx';
15+import { POST_LISTINGS_KEY } from '../keys';
16+
17+interface PostsProps {
18+ ship: string;
19+ name: string;
20+}
21+
22+export const Posts = ({ ship, name }: PostsProps) => {
23+ const limited = useMedia('(max-width: 899px)');
24+ const [postType, setPostType] = useState<PostType>('content')
25+ const [tags, setTags] = useState<MultiValue<Option>>([{ label: 'post', value: 'post' }])
26+ const { loading, posts, notebook } = usePosts(ship, name, postType, tags.map(t => t.value));
27+ const [firstLoad, setFirstLoad] = useState(true);
28+ const form = useForm<PostOptionsForm>({
29+ defaultValues: {
30+ options: []
31+ }
32+ });
33+ const { handleSubmit, reset, watch, setValue } = form;
34+ const options = watch('options');
35+
36+ useEffect(() => {
37+ if (posts.length > 0 && firstLoad) {
38+ reset({
39+ options: []
40+ })
41+
42+ setFirstLoad(false);
43+ }
44+ }, [posts, firstLoad]);
45+
46+ const toggleAll = useCallback(() => {
47+ if (options.length !== posts.length) {
48+ setValue('options', posts.map(p => p.key));
49+ } else {
50+ setValue('options', []);
51+ }
52+ }, [posts, options, setValue]);
53+
54+ const queryClient = useQueryClient();
55+ const { mutate } = useMutation(async (values: PostOptionsForm) => {
56+ return api.poke<Declare[]>({
57+ app: 'sphinx',
58+ mark: 'declarations',
59+ json: posts.filter(a => values.options.includes(a.key)).map(a => ({
60+ post: a.post,
61+ reach: 'friends'
62+ }))
63+ });
64+ }, {
65+ onSuccess: () => {
66+ queryClient.invalidateQueries(POST_LISTINGS_KEY)
67+ }
68+ })
69+
70+ const onSubmit = useCallback((values: PostOptionsForm) => {
71+ mutate(values);
72+ reset();
73+ }, [reset, mutate]);
74+
75+ return (
76+ <>
77+ <header className='flex items-center'>
78+ {limited && (
79+ <Link to="/manage-listings/posts" className='flex items-center'>
80+ <ChevronLeftIcon className='h-6 w-6 text-mauve/60' />
81+ <h1 className='text-2xl font-semibold'>{notebook?.meta.title}</h1>
82+ </Link>
83+ )}
84+ {!limited && <h1 className='text-2xl font-semibold'>{notebook?.meta.title}</h1>}
85+ {posts.length > 0 && (
86+ <button className='p-1.5 ml-auto text-sm underline font-semibold' onClick={toggleAll}>
87+ {options.length !== posts.length && 'select all'}
88+ {options.length === posts.length && 'deselect all'}
89+ </button>
90+ )}
91+ </header>
92+ <div className='flex flex-col items-end sm:flex-row sm:items-center space-y-2 sm:space-y-0 sm:space-x-2'>
93+ <TagField tags={tags} onTags={setTags} className="flex-1 w-full"/>
94+ <Filter className='flex-none' selected={postType} onSelect={(value) => setPostType(value)} />
95+ </div>
96+ <FormProvider {...form}>
97+ <form onSubmit={handleSubmit(onSubmit)}>
98+ {posts.length === 0 && loading ? (
99+ <div className='flex items-center space-x-2 text-lavender'>
100+ <Spinner className='w-6 h-6' />
101+ <span>Checking posts...</span>
102+ </div>
103+ ) : posts.length === 0 && !loading ? (
104+ <h2 className='text-lavender'>All posts already indexed</h2>
105+ ) : (
106+ <PostOptions options={posts} showTags />
107+ )}
108+ <div className='flex justify-between border-t border-zinc-300 py-3 mt-6'>
109+ <Link to="/search" className='flex items-center rounded-lg text-base font-semibold text-rosy bg-rosy/30 border-2 border-transparent hover:border-rosy leading-none py-2 px-3 transition-colors'>
110+ Back to Search
111+ </Link>
112+ {posts.length > 0 && (
113+ <button type="submit" className='flex items-center rounded-lg text-base font-semibold text-linen bg-rosy disabled:bg-zinc-200 disabled:text-zinc-400 disabled:border-transparent border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors' disabled={options.length === 0}>
114+ Publish
115+ </button>
116+ )}
117+ </div>
118+ </form>
119+ </FormProvider>
120+ </>
121+ );
122+}
ui/src/pages/Search.tsx+75−52
@@ -1,14 +1,21 @@
11import cn from 'classnames';
22import React, { useCallback, useRef, useState } from 'react';
33import debounce from 'lodash.debounce';
4+import { useMutation, useQueryClient } from '@tanstack/react-query';
5+import { useNavigate, useParams } from 'react-router-dom';
46import { SearchInput } from '../components/SearchInput';
5−import { SearchList } from '../components/SearchList';
6−import { useQuery } from 'react-query';
7−import { Search as SearchType } from '../types/seek';
7+import { Listings } from '../components/Listings';
8+import { PostFilter, Remove, Search as SearchType } from '../types/sphinx';
89import api from '../api';
9−import { useNavigate, useParams } from 'react-router-dom';
1010import { Paginator } from '../components/Paginator';
11−import { stringToTa } from '@urbit/api';
11+import { Filter } from '../components/Filter';
12+import { PlusSmIcon } from '@heroicons/react/solid';
13+import { usePals } from '../state/pals';
14+import { TagCloud } from '../components/TagCloud';
15+import { useTags } from '../state/tags';
16+import { useSearch } from '../state/search';
17+import { encodeLookup, formatUd } from '../utils';
18+import { lookupKey } from '../keys';
1219
1320interface RouteParams extends Record<string, string | undefined> {
1421 lookup?: string;
@@ -16,14 +23,6 @@ interface RouteParams extends Record<string, string | undefined> {
1623 page?: string;
1724}
1825
19−function encodeLookup(value: string | undefined) {
20− if (!value) {
21− return '';
22− }
23−
24− return stringToTa(value).replace('~.', '~~');
25−}
26−
2726export const Search = () => {
2827 const navigate = useNavigate();
2928 const {
@@ -31,60 +30,84 @@ export const Search = () => {
3130 limit,
3231 page
3332 } = useParams<RouteParams>();
33+ const [selected, setSelected] = useState<PostFilter>('all')
3434 const [rawSearch, setRawSearch] = useState(lookup || '');
35− const size = parseInt(limit || '10', 10);
36− const pageInt = parseInt(page || '1', 10) - 1;
37− const start = pageInt * size;
38− const { data } = useQuery<unknown, unknown, SearchType>(`lookup-${lookup}-${size}-${start}`, () => api.scry<SearchType>({
39− app: 'seek',
40− path: `/lookup/${encodeLookup(lookup)}/${start}/${size}`
41− }), {
35+ const { installed: palsInstalled } = usePals();
36+
37+ const {
38+ size,
39+ start,
40+ pageInt,
41+ pages,
42+ results,
43+ isLoading,
44+ linkBuild
45+ } = useSearch({
46+ key: lookupKey(selected, lookup),
47+ fetcher: (start, size) => api.scry<SearchType>({
48+ app: 'sphinx',
49+ path: `/lookup/${selected}/${formatUd(start)}/${formatUd(size)}/${encodeLookup(lookup)}`
50+ }),
4251 enabled: !!lookup,
43− keepPreviousData: true
52+ limit,
53+ page,
54+ linkPrefix: `/search/${lookup}`
55+ });
56+ const tags = useTags();
57+ const queryClient = useQueryClient();
58+ const { mutate } = useMutation((hash: string) => {
59+ return api.poke<Remove>({
60+ app: 'sphinx',
61+ mark: 'remove',
62+ json: hash
63+ })
64+ }, {
65+ onSuccess: () => {
66+ queryClient.invalidateQueries(lookupKey(selected, lookup)(start, size))
67+ }
4468 });
45−
46− const total = data?.total || 0;
47− const pages =
48− total % size === 0
49− ? total / size
50− : Math.floor(total / size) + 1;
5169
5270 const update = useRef(debounce((value: string) => {
5371 if (!value) {
54− navigate('/');
5572 return;
5673 }
5774
58− navigate(`/${value}/${size}/${page || '1'}`)
59− }, 200))
75+ navigate(`/search/${value}/${size}/${page || '1'}`)
76+ }, 400))
6077
6178 const onChange = useCallback((value: string) => {
6279 setRawSearch(value);
6380 update.current(value);
6481 }, []);
6582
66− const linkBuild = useCallback((page) => {
67− if (!page) {
68− return null;
69− }
70−
71− return `/${lookup}/${size}/${page || 1}`
72− }, [lookup, size]);
73−
7483 return (
75− <main className={cn("flex flex-col items-center min-h-screen", !data && 'justify-center')}>
76− <div className="max-w-xl w-full p-4 sm:py-12 px-8 space-y-6">
77− <header>
78− <SearchInput lookup={rawSearch} onChange={onChange} />
79− </header>
80− {data && <div className='flex justify-end border-t border-zinc-300'>
81− <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
82− </div>}
83− <SearchList listings={data?.listings || []} />
84− {data && pages > 1 && <div className='flex justify-end border-t border-zinc-300'>
85− <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
86− </div>}
87− </div>
88− </main>
84+ <div className={cn('w-full space-y-6', !lookup && 'm-auto')}>
85+ <header className='flex items-center space-x-2'>
86+ <SearchInput className='flex-1' lookup={rawSearch} loading={!!lookup && isLoading} onChange={onChange} />
87+ <Filter selected={selected} onSelect={setSelected} className="min-w-0 sm:w-20" />
88+ </header>
89+ {!palsInstalled && (
90+ <div>
91+ <a href="/apps/landscape/search/~paldev/apps/~paldev/pals" className='inline-flex items-center py-1 px-2 mr-2 font-semibold bg-green-800 text-linen rounded-md'>
92+ <PlusSmIcon className='h-4 w-4' />
93+ install pals
94+ </a>
95+ to see listings from others
96+ </div>
97+ )}
98+ {lookup && results && <div className='flex justify-end border-t border-zinc-300'>
99+ <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
100+ </div>}
101+ {lookup && <Listings listings={results.listings} remove={mutate} />}
102+ {!lookup && (
103+ <div className='space-y-2'>
104+ <h2 className='font-semibold'>top tags</h2>
105+ <TagCloud tags={tags.slice(0,12)} />
106+ </div>
107+ )}
108+ {results && pages > 1 && <div className='flex justify-end border-t border-zinc-300'>
109+ <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
110+ </div>}
111+ </div>
89112 )
90113}
ui/src/pages/Tag.tsxnew file+82
@@ -0,0 +1,82 @@
1+import { PlusIcon, PlusSmIcon } from '@heroicons/react/solid';
2+import cn from 'classnames';
3+import qs from 'query-string';
4+import React from 'react';
5+import { useQueryClient, useMutation } from '@tanstack/react-query';
6+import { Link, useParams } from 'react-router-dom';
7+import api from '../api';
8+import { Listings } from '../components/Listings';
9+import { Paginator } from '../components/Paginator';
10+import { TagCloud } from '../components/TagCloud';
11+import { useSearch } from '../state/search';
12+import { useTags } from '../state/tags';
13+import { Remove, Search } from '../types/sphinx';
14+import { encodeLookup, formatUd } from '../utils';
15+import { tagKey } from '../keys';
16+
17+interface RouteParams extends Record<string, string | undefined> {
18+ tag?: string;
19+ limit?: string;
20+ page?: string;
21+}
22+
23+export const Tag = () => {
24+ const tags = useTags();
25+ const { tag, limit, page } = useParams<RouteParams>();
26+ const {
27+ size,
28+ start,
29+ pageInt,
30+ pages,
31+ results,
32+ linkBuild
33+ } = useSearch({
34+ key: tagKey(tag),
35+ fetcher: (start, size) => api.scry<Search>({
36+ app: 'sphinx',
37+ path: `/lookup/tag/${formatUd(start)}/${formatUd(size)}/${encodeLookup(tag)}`
38+ }),
39+ enabled: !!tag,
40+ limit,
41+ page,
42+ linkPrefix: `/tags/${tag}`
43+ });
44+ const queryClient = useQueryClient();
45+ const { mutate } = useMutation((hash: string) => {
46+ return api.poke<Remove>({
47+ app: 'sphinx',
48+ mark: 'remove',
49+ json: hash
50+ })
51+ }, {
52+ onSuccess: () => {
53+ queryClient.invalidateQueries(tagKey(tag)(start, size))
54+ }
55+ });
56+
57+ return (
58+ <div className={cn('w-full space-y-6', !tag && 'm-auto')}>
59+ {!tag && <h1 className='text-2xl font-semibold'>Tags</h1>}
60+ <div className={cn('h-[136px] overflow-y-auto', tag && 'mb-12')}>
61+ <TagCloud tags={tags} />
62+ </div>
63+ {tag && <header className='flex items-center justify-between'>
64+ <h1 className='text-2xl font-semibold leading-none'>{tag}</h1>
65+ <Link
66+ to={{ pathname: '/manage-listings/post', search: qs.stringify({ tags: [tag] }) }}
67+ className="inline-flex items-center px-2 pr-3 py-1 font-semibold text-linen bg-lavender hover:bg-rosy transition-colors rounded-lg"
68+ >
69+ <PlusSmIcon className='w-5 h-5' />
70+ <span>Add</span>
71+ </Link>
72+ </header>}
73+ {tag && results && <div className='flex justify-end border-t border-zinc-300'>
74+ <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
75+ </div>}
76+ {tag && <Listings listings={results.listings} remove={mutate} />}
77+ {results && pages > 1 && <div className='flex justify-end border-t border-zinc-300'>
78+ <Paginator pages={pages} currentPage={pageInt} linkBuilder={linkBuild} />
79+ </div>}
80+ </div>
81+ )
82+}
ui/src/routes.tsnew file+16
@@ -0,0 +1,16 @@
1+export const BrowsingRoutes = {
2+ search: "/search",
3+ tags: "/tags",
4+ apps: "/tags/app",
5+ groups: "/tags/group",
6+ jobs: "/tags/job",
7+ 'my listings': "/manage-listings",
8+ 'all listings': "/manage-listings/all"
9+}
10+
11+export const PostingRoutes = {
12+ 'post listing': "/manage-listings/post",
13+ 'add apps': "/manage-listings/apps",
14+ 'add groups': "/manage-listings/groups",
15+ 'add posts': "/manage-listings/posts"
16+}
ui/src/state/apps.tsnew file+39
@@ -0,0 +1,39 @@
1+import { Charge, ChargeUpdateInitial, getPikes, Pike, Pikes, scryCharges } from "@urbit/api";
2+import { useQuery } from "@tanstack/react-query"
3+import api from "../api";
4+import { Post, Search } from "../types/sphinx";
5+import { APP_LISTINGS_KEY, CHARGES_KEY, VATS_KEY } from "../keys";
6+import { MAX_DESCRIPTION_BYTES, MAX_TITLE_BYTES, listingImage, truncateBytes } from "../utils";
7+
8+function getAppPost(app: string, pike: Pike, charge: Charge): Post {
9+ const ship = pike.sync?.ship || `~${window.ship}`;
10+
11+ return {
12+ type: 'app',
13+ title: truncateBytes(charge.title, MAX_TITLE_BYTES),
14+ // the desk on the publisher, which may differ from the local name
15+ // (|install ~ship %desk, =local %other)
16+ link: `/1/desk/${ship}/${pike.sync?.desk || app}`,
17+ description: truncateBytes(charge.info || charge.title, MAX_DESCRIPTION_BYTES),
18+ image: listingImage(charge.image),
19+ color: charge.color,
20+ tags: ['app']
21+ }
22+}
23+
24+export const useApps = () => {
25+ const { data: pikes, isLoading: pikesLoading } = useQuery(VATS_KEY, () => api.scry<Pikes>(getPikes));
26+ const { data: charges, isLoading: chargesLoading } = useQuery(CHARGES_KEY, () => api.scry<ChargeUpdateInitial>(scryCharges));
27+ const { data, isLoading } = useQuery(APP_LISTINGS_KEY, () => api.scry<Search>({
28+ app: 'sphinx',
29+ path: '/lookup/app/0/999'
30+ }));
31+
32+ return {
33+ loading: pikesLoading || chargesLoading || isLoading,
34+ apps: !pikes || !charges || !data ? [] : Object.entries(charges.initial)
35+ .filter(([k]) => k in pikes)
36+ .map(([k,v]) => ({ key: k, post: getAppPost(k, pikes[k], v) }))
37+ .filter(({ post }) => !data.listings.some(l => l.post.link === post.link || l.post.title === post.title))
38+ }
39+}
ui/src/state/groups.tsnew file+76
@@ -0,0 +1,76 @@
1+import { useQuery } from "@tanstack/react-query"
2+import api from "../api"
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 { MAX_DESCRIPTION_BYTES, MAX_TITLE_BYTES, getColor, getImage, isColor, listingImage, truncateBytes } from "../utils";
8+
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+ );
17+
18+ const groups = useMemo(() => {
19+ if (!data) return [];
20+ return Object.entries(data).filter(([,v]) => !v.secret);
21+ }, [data]);
22+
23+ return {
24+ ...query,
25+ groups
26+ }
27+}
28+
29+export const useGroups = () => {
30+ const { groups, isLoading: loading } = usePublicGroups();
31+ const { data: d, isLoading } = useQuery(GROUP_LISTINGS_KEY, () => api.scry<Search>({
32+ app: 'sphinx',
33+ path: '/lookup/group/0/999'
34+ }));
35+
36+ return {
37+ loading: loading || isLoading,
38+ groups: groups.map(([k,v]) => ({ key: k, post: {
39+ type: 'group' as PostType,
40+ title: truncateBytes(v.meta.title, MAX_TITLE_BYTES),
41+ link: `/1/group/${k}`, //`/apps/groups/groups/${k}`,
42+ image: listingImage(getImage(v.meta.image) || getImage(v.meta.cover)),
43+ color: getColor(v.meta.image) || getColor(v.meta.cover) || '',
44+ description: truncateBytes(v.meta.description, MAX_DESCRIPTION_BYTES),
45+ tags: ['group']
46+ }}))
47+ .filter(({ post }) => !d || d.listings.length === 0 || !d.listings.some(l => l.post.link === post.link || l.post.title === post.title))
48+ }
49+}
50+
51+export function useChannelPreview(nest: string): ChannelPreview | undefined {
52+ const { groups, isFetched } = usePublicGroups();
53+ const group = groups.find(([,g]) => {
54+ return !!g.channels[nest];
55+ });
56+
57+ // %groups asks the channel's host and answers once (mark channel-preview-1)
58+ const { data } = useQuery(['channel-preview', nest],
59+ () => api.subscribeOnce<ChannelPreview>('groups', `/v1/channels/${nest}/preview`, 5000),
60+ {
61+ // only for channels outside our groups, so wait until those are known
62+ enabled: isFetched && !!nest && !group,
63+ refetchOnMount: false,
64+ refetchOnWindowFocus: false,
65+ },
66+ );
67+
68+ return group ? {
69+ nest,
70+ meta: group[1].channels[nest].meta,
71+ group: {
72+ flag: group[0],
73+ meta: group[1].meta,
74+ }
75+ } : data;
76+}
ui/src/state/pals.tsnew file+62
@@ -0,0 +1,62 @@
1+import { ChargeUpdateInitial, scryCharges } from '@urbit/api';
2+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
3+import api from '../api';
4+import { CHARGES_KEY, PALS_KEY } from '../keys';
5+
6+interface Pal {
7+ lists: string[];
8+ ack: true | null;
9+}
10+
11+export interface Pals {
12+ [k: string]: Pal;
13+}
14+
15+interface PalsData {
16+ incoming: Pals;
17+ outgoing: Pals;
18+ [k: string]: Pal | Pals;
19+}
20+
21+interface RemovePal {
22+ part: {
23+ ship: string;
24+ in: string[];
25+ }
26+}
27+
28+export const usePals = () => {
29+ const queryClient = useQueryClient();
30+ const { data: charges } = useQuery(CHARGES_KEY, () => {
31+ return api.scry<ChargeUpdateInitial>(scryCharges);
32+ })
33+ const { data } = useQuery(PALS_KEY, () => {
34+ return api.scry<PalsData>({
35+ app: 'pals',
36+ path: '/json'
37+ });
38+ });
39+ const { mutate: remove } = useMutation((pal: string) => {
40+ return api.poke<RemovePal>({
41+ app: 'pals',
42+ mark: 'pals-command',
43+ json: {
44+ part: {
45+ ship: pal,
46+ in: []
47+ }
48+ }
49+ })
50+ }, {
51+ onSuccess: () => {
52+ queryClient.invalidateQueries(PALS_KEY);
53+ }
54+ })
55+
56+ return {
57+ installed: !charges ? true : 'pals' in charges.initial,
58+ incoming: data ? Object.keys(data.incoming) : [],
59+ outgoing: data ? data.outgoing : {},
60+ remove
61+ }
62+}
ui/src/state/posts.tsnew file+108
@@ -0,0 +1,108 @@
1+import { useQuery } from "@tanstack/react-query"
2+import api from "../api"
3+import { PostType, Search } from "../types/sphinx";
4+import { usePublicGroups } from "./groups";
5+import { POST_LISTINGS_KEY, notesKey } from "../keys";
6+import { ChannelsResponse, Diary, GroupChannel, NoteContent, PagedChannelPosts, VerseInline, inlineToString } from "../types";
7+import { useMemo } from "react";
8+import { udToDec } from '@urbit/api';
9+import { MAX_DESCRIPTION_BYTES, MAX_TITLE_BYTES, listingImage, normalizeLink, truncateBytes } from "../utils";
10+
11+// notebooks are %channels channels of kind %diary, keyed by nest
12+// "diary/~ship/name" (the old %diary agent is gone from the %groups desk)
13+const DIARY_PREFIX = 'diary/';
14+
15+export const useNotebooks = () => {
16+ const { groups, isLoading: groupsLoading } = usePublicGroups();
17+ const { data, isLoading } = useQuery(notesKey(), () => api.scry<ChannelsResponse>({
18+ app: 'channels',
19+ path: '/v1/channels'
20+ }));
21+
22+ const notebooks = useMemo(() => {
23+ const nbs: Record<string, GroupChannel & Diary> = {};
24+ if (!data) return nbs;
25+
26+ Object.entries(data).forEach(([nest, v]) => {
27+ if (!nest.startsWith(DIARY_PREFIX)) return;
28+ const flag = nest.slice(DIARY_PREFIX.length);
29+ const group = groups.find(([g]) => g === v.perms.group);
30+ if (!group) return;
31+
32+ const channel = group[1].channels[nest];
33+ if (!channel) return;
34+
35+ if (channel.readers.length === 0) {
36+ nbs[flag] = { ...v, ...channel };
37+ }
38+ });
39+
40+ return nbs;
41+ }, [data, groups]);
42+
43+ return {
44+ notebooks,
45+ groups,
46+ loading: isLoading || groupsLoading
47+ }
48+}
49+
50+export const usePosts = (ship: string, name: string, type: PostType, tags: string[]) => {
51+ const flag = `${ship}/${name}`;
52+ const { notebooks, loading } = useNotebooks();
53+ const { data, isLoading } = useQuery(notesKey(flag), () => api.scry<PagedChannelPosts>({
54+ app: 'channels',
55+ path: `/v1/${DIARY_PREFIX}${flag}/posts/newest/9.999.999/outline`
56+ }));
57+ // every listing we have, ours or a mutual's, whatever its tags or type
58+ // (%sphinx refuses to publish a listing it already has)
59+ const { data: d, isLoading: postsLoading } = useQuery(POST_LISTINGS_KEY, () => api.scry<Search>({
60+ app: 'sphinx',
61+ path: '/lookup/all/0/9.999.999'
62+ }));
63+
64+ const items = data ? Object.entries(data.posts)
65+ .flatMap(([id, post]) => {
66+ if (!post) return []; // deleted post
67+ const diary = post.essay['kind-data']?.diary;
68+ return [{ key: id, post: {
69+ type,
70+ title: truncateBytes(diary?.title || '', MAX_TITLE_BYTES),
71+ link: getPermalink(flag, id),
72+ image: listingImage(diary?.image),
73+ color: '',
74+ description: getSnippet(post.essay.content),
75+ tags
76+ }}];
77+ }) : [];
78+
79+ // listings store refs; older ones may still be web+urbitgraph links
80+ const listed = useMemo(
81+ () => new Set((d?.listings || []).map(l => normalizeLink(l.post.link))),
82+ [d]
83+ );
84+ const posts = items.filter(p => !listed.has(p.post.link));
85+
86+ return {
87+ notebook: notebooks[flag],
88+ loading: loading || isLoading || postsLoading,
89+ posts
90+ }
91+}
92+
93+function getPermalink(flag: string, id: string): string {
94+ return `/1/chan/${DIARY_PREFIX}${flag}/note/${udToDec(id)}`;
95+}
96+
97+// the first paragraph with any text, as plain text
98+function getSnippet(content: NoteContent): string {
99+ const inlines = content.filter(c => 'inline' in c) as VerseInline[];
100+ for (const verse of inlines) {
101+ const text = verse.inline.map(inlineToString).join('').replace(/\s+/g, ' ').trim();
102+ if (text) {
103+ return truncateBytes(text, MAX_DESCRIPTION_BYTES);
104+ }
105+ }
106+
107+ return '';
108+}
ui/src/state/search.tsnew file+49
@@ -0,0 +1,49 @@
1+import { useCallback } from "react";
2+import { QueryKey, useQuery } from "@tanstack/react-query";
3+import { Search } from "../types/sphinx";
4+
5+interface UseSearchParams {
6+ key: (start: number, size: number) => QueryKey;
7+ fetcher: (start: number, size: number) => Promise<Search>;
8+ enabled: boolean;
9+ linkPrefix: string;
10+ limit?: string;
11+ page?: string;
12+}
13+
14+export const useSearch = ({ key, fetcher, enabled, limit, page, linkPrefix }: UseSearchParams) => {
15+ const size = parseInt(limit || '10', 10);
16+ const pageInt = parseInt(page || '1', 10) - 1;
17+ const start = pageInt * size;
18+
19+ const { data, ...query } = useQuery<Search>(key(start, size), () => fetcher(start, size), {
20+ enabled,
21+ keepPreviousData: true
22+ });
23+
24+ const total = data?.total || 0;
25+ const pages =
26+ total % size === 0
27+ ? total / size
28+ : Math.floor(total / size) + 1;
29+
30+ const linkBuild = useCallback((page) => {
31+ if (!page) {
32+ return null;
33+ }
34+
35+ return `${linkPrefix}/${size}/${page || 1}`
36+ }, [linkPrefix, size]);
37+
38+ return {
39+ results: data || {
40+ listings: []
41+ },
42+ ...query,
43+ size,
44+ pageInt,
45+ start,
46+ pages,
47+ linkBuild
48+ }
49+}
ui/src/state/tags.tsnew file+21
@@ -0,0 +1,21 @@
1+import { useMemo } from "react";
2+import { useQuery } from "@tanstack/react-query";
3+import api from "../api";
4+import { Tags } from "../types/sphinx";
5+import { TAGS_KEY } from "../keys";
6+
7+export const useTags = () => {
8+ const { data } = useQuery(TAGS_KEY, () => api.scry<Tags>({
9+ app: 'sphinx',
10+ path: '/tags'
11+ }));
12+
13+ return useMemo(() => data ? Object.entries(data).sort(([tagA, a], [tagB, b]) => {
14+ if (a === b) {
15+ return tagA.localeCompare(tagB);
16+ }
17+
18+ return b - a
19+ })
20+ : [], [data]);
21+}
desk/mar/seek-action.hoon → ui/src/styles/base.cssrenamed

The content is the same.

ui/src/styles/components.cssnew file+16
@@ -0,0 +1,16 @@
1+.button {
2+ @apply flex items-center rounded-lg text-base font-semibold text-linen bg-rosy disabled:bg-zinc-200 disabled:text-zinc-400 disabled:border-transparent border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors;
3+}
4+
5+input:checked + label .show-when-checked {
6+ @apply opacity-100;
7+}
8+
9+input:checked + .checked-bg-fawn,
10+input:checked + label .checked-bg-fawn {
11+ @apply bg-fawn;
12+}
13+
14+.spinner {
15+ @apply inline-flex items-center w-6 h-6 animate-spin;
16+}
ui/src/styles/index.cssnew file+8
@@ -0,0 +1,8 @@
1+@import 'tailwindcss/base';
2+@import './base';
3+
4+@import 'tailwindcss/components';
5+@import './components';
6+
7+@import 'tailwindcss/utilities';
8+@import './utilities';
ui/src/styles/utilities.cssnew file+3
@@ -0,0 +1,3 @@
1+.flip {
2+ transform: rotateY(180deg);
3+}
ui/src/types/index.tsnew file+304
@@ -0,0 +1,304 @@
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+// %groups /v1/channels/<kind>/<ship>/<name>/preview (mark channel-preview-1).
38+// Previews built from our own groups only fill in flag and meta.
39+export interface GroupPreview {
40+ flag: string;
41+ meta: GroupMeta;
42+ time?: number;
43+ 'member-count'?: number;
44+ privacy?: 'public' | 'private' | 'secret';
45+}
46+
47+export interface ChannelPreview {
48+ nest: string;
49+ meta: GroupMeta;
50+ group: GroupPreview;
51+}
52+
53+export interface Ship {
54+ ship: string;
55+}
56+
57+export interface Italics {
58+ italics: Inline[];
59+}
60+
61+export interface Bold {
62+ bold: Inline[];
63+}
64+
65+export interface Strikethrough {
66+ strike: Inline[];
67+}
68+
69+export interface Break {
70+ break: null;
71+}
72+
73+export interface InlineCode {
74+ 'inline-code': string;
75+}
76+
77+export interface BlockCode {
78+ code: string;
79+}
80+
81+export interface Blockquote {
82+ blockquote: Inline[];
83+}
84+
85+/**
86+ A reference to the accompanying blocks, indexed at 0
87+*/
88+export interface BlockReference {
89+ block: {
90+ index: number;
91+ text: string;
92+ };
93+}
94+
95+export interface Tag {
96+ tag: string;
97+}
98+
99+export interface Link {
100+ link: {
101+ href: string;
102+ content: string;
103+ };
104+}
105+
106+export interface Task {
107+ task: {
108+ checked: boolean;
109+ content: Inline[];
110+ };
111+}
112+
113+// a role mention
114+export interface Sect {
115+ sect: string | null;
116+}
117+
118+export type Inline =
119+ | string
120+ | Bold
121+ | Italics
122+ | Strikethrough
123+ | Ship
124+ | Break
125+ | InlineCode
126+ | BlockCode
127+ | Blockquote
128+ | BlockReference
129+ | Tag
130+ | Link
131+ | Task
132+ | Sect;
133+
134+export type InlineKey =
135+ | 'italics'
136+ | 'bold'
137+ | 'strike'
138+ | 'blockquote'
139+ | 'inline-code'
140+ | 'block'
141+ | 'code'
142+ | 'tag'
143+ | 'link'
144+ | 'break';
145+
146+export function isBold(item: unknown): item is Bold {
147+ return typeof item === 'object' && item !== null && 'bold' in item;
148+}
149+
150+export function isItalics(item: unknown): item is Italics {
151+ return typeof item === 'object' && item !== null && 'italics' in item;
152+}
153+
154+export function isLink(item: unknown): item is Link {
155+ return typeof item === 'object' && item !== null && 'link' in item;
156+}
157+
158+export function isStrikethrough(item: unknown): item is Strikethrough {
159+ return typeof item === 'object' && item !== null && 'strike' in item;
160+}
161+
162+export function isBlockquote(item: unknown): item is Blockquote {
163+ return typeof item === 'object' && item !== null && 'blockquote' in item;
164+}
165+
166+export function isInlineCode(item: unknown): item is InlineCode {
167+ return typeof item === 'object' && item !== null && 'inline-code' in item;
168+}
169+
170+export function isBlockCode(item: unknown): item is BlockCode {
171+ return typeof item === 'object' && item !== null && 'code' in item;
172+}
173+
174+export function isBreak(item: unknown): item is Break {
175+ return typeof item === 'object' && item !== null && 'break' in item;
176+}
177+
178+export function isShip(item: unknown): item is Ship {
179+ return typeof item === 'object' && item !== null && 'ship' in item;
180+}
181+
182+export function isTag(item: unknown): item is Tag {
183+ return typeof item === 'object' && item !== null && 'tag' in item;
184+}
185+
186+export function isTask(item: unknown): item is Task {
187+ return typeof item === 'object' && item !== null && 'task' in item;
188+}
189+
190+// plain text of an inline; text runs carry their own whitespace
191+export function inlineToString(inline: Inline): string {
192+ if (typeof inline === 'string') {
193+ return inline;
194+ }
195+
196+ if (isBold(inline)) {
197+ return inline.bold.map(inlineToString).join('');
198+ }
199+
200+ if (isItalics(inline)) {
201+ return inline.italics.map(inlineToString).join('');
202+ }
203+
204+ if (isStrikethrough(inline)) {
205+ return inline.strike.map(inlineToString).join('');
206+ }
207+
208+ if (isLink(inline)) {
209+ return inline.link.content || inline.link.href;
210+ }
211+
212+ if (isBlockquote(inline)) {
213+ return inline.blockquote.map(inlineToString).join('');
214+ }
215+
216+ if (isInlineCode(inline)) {
217+ return inline['inline-code'];
218+ }
219+
220+ if (isBlockCode(inline)) {
221+ return inline.code;
222+ }
223+
224+ if (isTag(inline)) {
225+ return inline.tag;
226+ }
227+
228+ if (isTask(inline)) {
229+ return inline.task.content.map(inlineToString).join('');
230+ }
231+
232+ if (isShip(inline)) {
233+ return inline.ship;
234+ }
235+
236+ if (isBreak(inline)) {
237+ return ' ';
238+ }
239+
240+ return '';
241+}
242+
243+// story verses (lib/story-json): {inline: [...]} or {block: {...}}
244+export interface VerseInline {
245+ inline: Inline[];
246+}
247+
248+export interface VerseBlock {
249+ block: any;
250+}
251+
252+export type Verse = VerseInline | VerseBlock;
253+
254+export type NoteContent = Verse[];
255+
256+export interface DiaryPerm {
257+ writers: string[];
258+ group: string;
259+}
260+
261+export interface Diary {
262+ perms: DiaryPerm;
263+}
264+
265+// %channels /v1/channels (mark %channels): keyed by nest "<kind>/~ship/name";
266+// posts are always empty here
267+export interface ChannelSummary extends Diary {
268+ posts: Record<string, unknown>;
269+ order: string[];
270+ view: string;
271+ sort: string;
272+}
273+
274+export interface ChannelsResponse {
275+ [nest: string]: ChannelSummary;
276+}
277+
278+// %channels /v1/<kind>/~ship/name/posts/newest/<count>/outline
279+// (mark %channel-posts)
280+export interface ChannelEssay {
281+ content: NoteContent;
282+ author: string;
283+ sent: number;
284+ 'kind-data': {
285+ diary?: { title: string; image: string };
286+ heap?: string | null;
287+ chat?: { notice: null } | null;
288+ };
289+}
290+
291+export interface ChannelPost {
292+ seal: { id: string };
293+ revision: string;
294+ essay: ChannelEssay;
295+ type: 'post';
296+}
297+
298+export interface PagedChannelPosts {
299+ // keyed by @ud post id ("170.141..."); null for a deleted post
300+ posts: { [id: string]: ChannelPost | null };
301+ newer: string | null;
302+ older: string | null;
303+ total: number;
304+}
ui/src/types/seek.tsdeleted−23
@@ -1,23 +0,0 @@
1−export interface Post {
2− title: string;
3− type: 'app' | 'group' | 'content' | 'other';
4− description: string;
5− image: string;
6− tags: string[];
7− link: string;
8−}
9−
10−export interface Listing {
11− post: Post;
12− hash: string;
13− source: string;
14− time: number;
15−}
16−
17−export type Search = {
18− listings: Listing[];
19− start: number;
20− limit: number;
21− size: number;
22− total: number;
23−}
ui/src/types/sphinx.tsnew file+52
@@ -0,0 +1,52 @@
1+export type PostType = 'app' | 'group' | 'content' | 'other';
2+
3+export type PostFilter = 'all' | PostType;
4+
5+export interface Declare {
6+ reach: 'friends' | 'public' | 'private';
7+ post: Post
8+}
9+
10+export type Remove = string;
11+
12+export interface Post {
13+ title: string;
14+ type: PostType;
15+ description: string;
16+ image: string;
17+ color: string;
18+ tags: string[];
19+ link: string;
20+}
21+
22+export interface Listing {
23+ post: Post;
24+ hash: string;
25+ source: string;
26+ time: number;
27+}
28+
29+export type Search = {
30+ listings: Listing[];
31+ start: number;
32+ limit: number;
33+ size: number;
34+ total: number;
35+}
36+
37+export interface Directory {
38+ [hash: string]: Listing;
39+}
40+
41+export interface PostOption {
42+ post: Post;
43+ key: string;
44+}
45+
46+export interface PostOptionsForm {
47+ options: string[];
48+}
49+
50+export interface Tags {
51+ [key: string]: number;
52+}
ui/src/utils.tsnew file+196
@@ -0,0 +1,196 @@
1+import { stringToTa, uxToHex } from "@urbit/api";
2+import { parseToRgba } from "color2k";
3+import { isValidPatp } from "urbit-ob";
4+
5+export function encodeLookup(value: string | undefined) {
6+ if (!value) {
7+ return '';
8+ }
9+
10+ return stringToTa(value).replace('~.', '~~');
11+}
12+
13+export function isColor(color: string): boolean {
14+ try {
15+ parseToRgba(color);
16+ return true;
17+ } catch (error) {
18+ return false;
19+ }
20+}
21+
22+export function getColor(image: string): string | undefined {
23+ return isColor(image) ? image : undefined;
24+}
25+
26+export function getImage(image: string): string | undefined {
27+ return isColor(image) ? undefined : image;
28+}
29+
30+export function normalizeColor(color: string): string {
31+ if (color.startsWith('#')) {
32+ return color;
33+ }
34+
35+ return `#${uxToHex(color)}`
36+}
37+
38+export function normalizeLink(link: string): string {
39+ // ref or URL format
40+ if (!link.startsWith('web+urbitgraph://') || link.startsWith('http')) {
41+ return link;
42+ }
43+
44+ // old format
45+ const normLink = link.replace('web+urbitgraph://', '');
46+ const parts = normLink.split('/');
47+ const first = parts[0];
48+
49+ if (first !== 'group') {
50+ return `/1/desk/${normLink}`;
51+ }
52+
53+ if (parts.length === 3) {
54+ return `/1/group/${parts[1]}/${parts[2]}`;
55+ }
56+
57+ return `/1/chan/diary/${parts[4]}/${parts[5]}/note/${parts[6]}`
58+}
59+
60+// Tlon web routes (tlon-apps packages/app/navigation/linking.ts):
61+// /apps/groups/group/:groupId[/channel/:channelId], ids URI-encoded.
62+export function groupURL(flag: string): string {
63+ return `/apps/groups/group/${encodeURIComponent(flag)}`;
64+}
65+
66+export function channelURL(flag: string, nest: string): string {
67+ return `${groupURL(flag)}/channel/${encodeURIComponent(nest)}`;
68+}
69+
70+export function refToURL(cite: string): string | { nest: string, where: string } {
71+ // plain web links (e.g. manual listings) are used as they are
72+ if (/^https?:\/\//i.test(cite)) {
73+ return cite;
74+ }
75+
76+ const parts = cite.slice(1).split('/');
77+ if (parts[0] !== '1') {
78+ return '';
79+ }
80+
81+ const type = parts[1];
82+ if (type === 'desk') {
83+ const ship = parts[2];
84+ const desk = parts[3];
85+ return `/apps/landscape/search/${ship}/apps/${ship}/${desk}`;
86+ }
87+
88+ if (type === 'group') {
89+ const ship = parts[2];
90+ const name = parts[3];
91+ return groupURL(`${ship}/${name}`);
92+ }
93+
94+ const chanType = parts[2];
95+ const ship = parts[3];
96+ const name = parts[4];
97+ return {
98+ nest: `${chanType}/${ship}/${name}`,
99+ where: parts.slice(5).join('/'),
100+ }
101+}
102+
103+// a number as Hoon prints @ud: `slav %ud` reads 1.000 but not 1000
104+// (@urbit/api's decToUd turns 0 into '')
105+export function formatUd(n: number): string {
106+ return String(n).replace(/\B(?=(\d{3})+$)/g, '.');
107+}
108+
109+const encoder = new TextEncoder();
110+
111+// lib/sphinx +lengths-valid limits, in UTF-8 bytes
112+export const MAX_TITLE_BYTES = 77;
113+export const MAX_DESCRIPTION_BYTES = 256;
114+export const MAX_URL_BYTES = 1024;
115+export const MAX_TAGS = 8;
116+// the longest tag +catalog indexes
117+export const MAX_TAG_BYTES = 64;
118+
119+export function byteLength(text: string): number {
120+ return encoder.encode(text).length;
121+}
122+
123+// lib/validate-url (de-purl:html in zuse) as regular expressions: an optional
124+// scheme://host[:port] (host a DNS name or IPv4, or a ~ship without a port),
125+// then an optional /path and ?key=value&key=value query. No #fragment, no
126+// spaces or non-ASCII, none of | [ ] { } ^ ` \ and no '=' inside a value.
127+// %sphinx silently drops a listing whose image or link fails this.
128+const QUERY_CHAR = "(?:[A-Za-z0-9_.!~*'()+/?:,-]|%[0-9A-Fa-f]{2})";
129+const PATH_CHAR = "(?:[A-Za-z0-9_.!~*'()$&+,;=:@/-]|%[0-9A-Fa-f]{2})";
130+const PAIR = `${QUERY_CHAR}+(?:=${QUERY_CHAR}*)?`;
131+const PATH_QUERY = `(?:/${PATH_CHAR}*)?(?:[?](?:${PAIR}(?:[&;]${PAIR})*)?)?`;
132+const SCHEME = '[A-Za-z][A-Za-z0-9+.-]*://';
133+const LABEL = '[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?';
134+const LAST_LABEL = '[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?';
135+const OCTET = '(?:0|[1-9][0-9]{0,2})';
136+const HOST = `(?:(?:${LABEL}[.])*${LAST_LABEL}|${OCTET}(?:[.]${OCTET}){3})(?::(?:0|[1-9][0-9]*))?`;
137+// the shape of a @p; isListingLink checks the syllables with isValidPatp
138+const SHIP = '~[a-z]{3}(?:[a-z]{3})?(?:--?[a-z]{6})*';
139+const IMAGE_RE = new RegExp(`^${SCHEME}${HOST}${PATH_QUERY}$`);
140+const LINK_RE = new RegExp(`^(?:${SCHEME}(?:${HOST}|(${SHIP})))?${PATH_QUERY}$`);
141+
142+export function isListingImage(image: string): boolean {
143+ return !image || (byteLength(image) <= MAX_URL_BYTES && IMAGE_RE.test(image));
144+}
145+
146+export function isListingLink(link: string): boolean {
147+ if (byteLength(link) > MAX_URL_BYTES) {
148+ return false;
149+ }
150+
151+ const match = LINK_RE.exec(link);
152+ return !!match && (!match[1] || isValidPatp(match[1]));
153+}
154+
155+// A listing image %sphinx accepts, or '' (so the listing goes out without
156+// one): http(s) URLs are percent-encoded by URL and lose their #fragment;
157+// anything else, data: URIs included (docket images may be one), is dropped.
158+export function listingImage(image: string | null | undefined): string {
159+ if (!image) {
160+ return '';
161+ }
162+
163+ try {
164+ const url = new URL(image);
165+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
166+ return '';
167+ }
168+
169+ url.hash = '';
170+ return isListingImage(url.href) ? url.href : '';
171+ } catch (e) {
172+ return '';
173+ }
174+}
175+
176+// %sphinx measures listing fields in UTF-8 bytes (lib/sphinx +lengths-valid)
177+// and silently drops listings that are too long, so cut text to at most `max`
178+// bytes on a code point boundary, marking the cut with an ellipsis.
179+export function truncateBytes(text: string, max: number): string {
180+ if (encoder.encode(text).length <= max) {
181+ return text;
182+ }
183+
184+ const ellipsis = '...';
185+ let out = '';
186+ let size = 0;
187+ for (const char of text) {
188+ size += encoder.encode(char).length;
189+ if (size > max - ellipsis.length) {
190+ break;
191+ }
192+ out += char;
193+ }
194+
195+ return `${out.trimEnd()}${ellipsis}`;
196+}
ui/tailwind.config.js+4−1
@@ -1,3 +1,4 @@
1+/** @type {import('tailwindcss').Config} */
12module.exports = {
23 content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
34 theme: {
@@ -12,5 +13,7 @@ module.exports = {
1213 }
1314 },
1415 screens: {},
15− plugins: []
16+ plugins: [
17+ require('tailwindcss-labeled-groups')(['1', '2', '3'])
18+ ]
1619};
ui/tsconfig.json+1−1
@@ -2,7 +2,7 @@
22 "compilerOptions": {
33 "target": "ESNext",
44 "lib": ["DOM", "DOM.Iterable", "ESNext"],
5− "allowJs": false,
5+ "allowJs": true,
66 "skipLibCheck": false,
77 "esModuleInterop": false,
88 "allowSyntheticDefaultImports": true,
ui/vite.config.ts+7−1
@@ -9,6 +9,12 @@ export default ({ mode }) => {
99 console.log(SHIP_URL);
1010
1111 return defineConfig({
12− plugins: [urbitPlugin({ base: 'seek', target: SHIP_URL, secure: false }), reactRefresh()]
12+ plugins: [urbitPlugin({ base: 'sphinx', target: SHIP_URL, changeOrigin: true, secure: false }), reactRefresh()],
13+ // The entry chunk has no import or export, so esbuild 0.14 minifies it as
14+ // a sloppy-mode script and hoists the block-level functions in
15+ // @radix-ui/react-focus-scope and react-use-body-pointer-events in random
16+ // order: rebuilds would give a different bundle and glob hash. Browsers
17+ // run the bundle as a module, which is strict anyway.
18+ build: { rollupOptions: { output: { banner: '"use strict";' } } }
1319 });
1420};