mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-27 17:02:32 +03:00
Merge branch 'urbit:master' into master
This commit is contained in:
commit
ab35dd4028
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9a56f675d2a6c5dafa92a9e2d55040d994f3d3d27a1ed827bd87d1158b1e69d0
|
||||
size 3749183
|
||||
oid sha256:ae4a7a69fe81c5f2114d7b7360c05602f614fe66b96d1db4c3dc0c2a2a5d856e
|
||||
size 7536000
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:843387cce113f18b403f76b6ba97ddf1746a5436b107d087d1f33b38db6f8c1a
|
||||
size 26237959
|
||||
oid sha256:bcab0698de6efda1bbac54b0833da5e853bca058919110aa5668aa63fb40626e
|
||||
size 9392699
|
||||
|
@ -2,27 +2,28 @@
|
||||
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|
||||
|%
|
||||
+$ state
|
||||
$~ [%22 *state:drum *state:helm *state:kiln]
|
||||
$>(%22 any-state)
|
||||
$~ [%23 *state:drum *state:helm *state:kiln]
|
||||
$>(%23 any-state)
|
||||
::
|
||||
+$ any-state
|
||||
$% [ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
|
||||
[%7 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%8 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%9 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%10 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%11 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%12 drum=state-2:drum helm=state:helm kiln=state-0:kiln]
|
||||
[%13 drum=state-2:drum helm=state:helm kiln=state-1:kiln]
|
||||
[%14 drum=state-2:drum helm=state:helm kiln=state-1:kiln]
|
||||
[%15 drum=state-2:drum helm=state:helm kiln=state-2:kiln]
|
||||
[%16 drum=state-4:drum helm=state:helm kiln=state-3:kiln]
|
||||
[%17 drum=state-4:drum helm=state:helm kiln=state-4:kiln]
|
||||
[%18 drum=state-4:drum helm=state:helm kiln=state-5:kiln]
|
||||
[%19 drum=state-4:drum helm=state:helm kiln=state-6:kiln]
|
||||
[%20 drum=state-4:drum helm=state:helm kiln=state-7:kiln]
|
||||
[%21 drum=state-4:drum helm=state:helm kiln=state-8:kiln]
|
||||
[%22 drum=state-4:drum helm=state:helm kiln=state-9:kiln]
|
||||
[%7 drum=state-2:drum helm=state-1:helm kiln=state-0:kiln]
|
||||
[%8 drum=state-2:drum helm=state-1:helm kiln=state-0:kiln]
|
||||
[%9 drum=state-2:drum helm=state-1:helm kiln=state-0:kiln]
|
||||
[%10 drum=state-2:drum helm=state-1:helm kiln=state-0:kiln]
|
||||
[%11 drum=state-2:drum helm=state-1:helm kiln=state-0:kiln]
|
||||
[%12 drum=state-2:drum helm=state-1:helm kiln=state-0:kiln]
|
||||
[%13 drum=state-2:drum helm=state-1:helm kiln=state-1:kiln]
|
||||
[%14 drum=state-2:drum helm=state-1:helm kiln=state-1:kiln]
|
||||
[%15 drum=state-2:drum helm=state-1:helm kiln=state-2:kiln]
|
||||
[%16 drum=state-4:drum helm=state-1:helm kiln=state-3:kiln]
|
||||
[%17 drum=state-4:drum helm=state-1:helm kiln=state-4:kiln]
|
||||
[%18 drum=state-4:drum helm=state-1:helm kiln=state-5:kiln]
|
||||
[%19 drum=state-4:drum helm=state-1:helm kiln=state-6:kiln]
|
||||
[%20 drum=state-4:drum helm=state-1:helm kiln=state-7:kiln]
|
||||
[%21 drum=state-4:drum helm=state-1:helm kiln=state-8:kiln]
|
||||
[%22 drum=state-4:drum helm=state-1:helm kiln=state-9:kiln]
|
||||
[%23 drum=state-4:drum helm=state-2:helm kiln=state-9:kiln]
|
||||
==
|
||||
+$ any-state-tuple
|
||||
$: drum=any-state:drum
|
||||
@ -48,6 +49,7 @@
|
||||
++ on-fail on-fail:def
|
||||
++ on-init
|
||||
^- step:agent:gall
|
||||
=^ h helm.state on-init:helm-core
|
||||
=^ d drum.state on-init:drum-core
|
||||
=^ k kiln.state on-init:kiln-core
|
||||
[:(welp d k) this]
|
||||
|
@ -1,27 +1,33 @@
|
||||
/+ pill
|
||||
=* card card:agent:gall
|
||||
|%
|
||||
+$ state state-1
|
||||
+$ state state-2
|
||||
+$ any-state
|
||||
$~ *state
|
||||
$% state-1
|
||||
$% state-2
|
||||
state-1
|
||||
state-0
|
||||
==
|
||||
+$ state-1
|
||||
$: %1
|
||||
mass-timer=[way=wire nex=@da tim=@dr]
|
||||
==
|
||||
+$ state-2 [%2 =mass-timer]
|
||||
+$ state-1 [%1 =mass-timer]
|
||||
+$ state-0 [%0 hoc=(map bone session-0)]
|
||||
+$ session-0
|
||||
$: say=*
|
||||
mud=*
|
||||
mass-timer=[way=wire nex=@da tim=@dr]
|
||||
=mass-timer
|
||||
==
|
||||
::
|
||||
+$ mass-timer [way=wire nex=@da tim=@dr]
|
||||
::
|
||||
++ state-0-to-1
|
||||
|= s=state-0
|
||||
^- state
|
||||
^- state-1
|
||||
[%1 mass-timer:(~(got by hoc.s) 0)]
|
||||
::
|
||||
++ state-1-to-2
|
||||
|= s=state-1
|
||||
^- state-2
|
||||
[%2 +.s]
|
||||
--
|
||||
|= [=bowl:gall sat=state]
|
||||
=| moz=(list card)
|
||||
@ -39,27 +45,46 @@
|
||||
^+ this
|
||||
?~(caz this $(caz t.caz, this (emit i.caz)))
|
||||
::
|
||||
++ on-init
|
||||
(poke-serve [~ /who] %base /gen/who/hoon ~)
|
||||
::
|
||||
++ on-load
|
||||
|= [hood-version=@ud old=any-state]
|
||||
=< abet
|
||||
=? old ?=(%0 -.old) (state-0-to-1 old)
|
||||
?> ?=(%1 -.old)
|
||||
=? this ?=(%1 -.old)
|
||||
(emil -:(poke-serve [~ /who] %base /gen/who/hoon ~))
|
||||
=? old ?=(%1 -.old) (state-1-to-2 old)
|
||||
?> ?=(%2 -.old)
|
||||
this(sat old)
|
||||
::
|
||||
++ poke-rekey :: rotate private keys
|
||||
|= des=@t
|
||||
=/ sed=(unit seed:jael)
|
||||
=/ fud=(unit feed:jael)
|
||||
%+ biff
|
||||
(bind (slaw %uw des) cue)
|
||||
(soft seed:jael)
|
||||
(soft feed:jael)
|
||||
=< abet
|
||||
?~ sed
|
||||
?~ fud
|
||||
~& %invalid-private-key
|
||||
this
|
||||
?. =(our.bowl who.u.sed)
|
||||
~& [%wrong-private-key-ship who.u.sed]
|
||||
=/ fed (need fud)
|
||||
?@ -.fed
|
||||
?. =(our.bowl who.fed)
|
||||
~& [%wrong-private-key-ship who.fed]
|
||||
this
|
||||
(emit %pass / %arvo %j %rekey lyf.u.sed key.u.sed)
|
||||
(emit %pass / %arvo %j %rekey lyf.fed key.fed)
|
||||
?. =(our.bowl who.fed)
|
||||
~& [%wrong-private-key-ship who.fed]
|
||||
this
|
||||
=| caz=(list card)
|
||||
%- emil
|
||||
|-
|
||||
?~ kyz.fed (flop caz)
|
||||
%= $
|
||||
kyz.fed t.kyz.fed
|
||||
caz [[%pass / %arvo %j %rekey i.kyz.fed] caz]
|
||||
==
|
||||
::
|
||||
++ ames-secret
|
||||
^- @t
|
||||
|
@ -2235,6 +2235,112 @@
|
||||
=/ pub (from.j qj)
|
||||
?< =([0 0] pub)
|
||||
pub
|
||||
++ schnorr
|
||||
~% %schnorr ..schnorr ~
|
||||
=> |%
|
||||
++ tagged-hash
|
||||
|= [tag=@ [l=@ x=@]]
|
||||
=+ hat=(sha-256:sha (swp 3 tag))
|
||||
%- sha-256l:sha
|
||||
:- (add 64 l)
|
||||
(can 3 ~[[l x] [32 hat] [32 hat]])
|
||||
++ lift-x
|
||||
|= x=@I
|
||||
^- (unit point)
|
||||
=/ c curve
|
||||
?. (lth x p.domain.c)
|
||||
~
|
||||
=/ fop field-p.c
|
||||
=+ [fadd fpow]=[sum.fop exp.fop]
|
||||
=/ cp (fadd (fpow 3 x) 7)
|
||||
=/ y (fpow (rsh [0 2] +(p.domain.c)) cp)
|
||||
?. =(cp (fpow 2 y))
|
||||
~
|
||||
%- some :- x
|
||||
?: =(0 (mod y 2))
|
||||
y
|
||||
(sub p.domain.c y)
|
||||
--
|
||||
|%
|
||||
::
|
||||
++ sign :: schnorr signature
|
||||
~/ %sosi
|
||||
|= [sk=@I m=@I a=@I]
|
||||
^- @J
|
||||
?> (gte 32 (met 3 m))
|
||||
?> (gte 32 (met 3 a))
|
||||
=/ c curve
|
||||
:: implies (gte 32 (met 3 sk))
|
||||
::
|
||||
?< |(=(0 sk) (gte sk n.domain.c))
|
||||
=/ pp
|
||||
(mul-point-scalar g.domain.c sk)
|
||||
=/ d
|
||||
?: =(0 (mod y.pp 2))
|
||||
sk
|
||||
(sub n.domain.c sk)
|
||||
=/ t
|
||||
%+ mix d
|
||||
(tagged-hash 'BIP0340/aux' [32 a])
|
||||
=/ rand
|
||||
%+ tagged-hash 'BIP0340/nonce'
|
||||
:- 96
|
||||
(rep 8 ~[m x.pp t])
|
||||
=/ kp (mod rand n.domain.c)
|
||||
?< =(0 kp)
|
||||
=/ rr (mul-point-scalar g.domain.c kp)
|
||||
=/ k
|
||||
?: =(0 (mod y.rr 2))
|
||||
kp
|
||||
(sub n.domain.c kp)
|
||||
=/ e
|
||||
%- mod
|
||||
:_ n.domain.c
|
||||
%+ tagged-hash 'BIP0340/challenge'
|
||||
:- 96
|
||||
(rep 8 ~[m x.pp x.rr])
|
||||
=/ sig
|
||||
%^ cat 8
|
||||
(mod (add k (mul e d)) n.domain.c)
|
||||
x.rr
|
||||
?> (verify x.pp m sig)
|
||||
sig
|
||||
::
|
||||
++ verify :: schnorr verify
|
||||
~/ %sove
|
||||
|= [pk=@I m=@I sig=@J]
|
||||
^- ?
|
||||
?> (gte 32 (met 3 pk))
|
||||
?> (gte 32 (met 3 m))
|
||||
?> (gte 64 (met 3 sig))
|
||||
=/ c curve
|
||||
=/ pup (lift-x pk)
|
||||
?~ pup
|
||||
%.n
|
||||
=/ pp u.pup
|
||||
=/ r (cut 8 [1 1] sig)
|
||||
?: (gte r p.domain.c)
|
||||
%.n
|
||||
=/ s (end 8 sig)
|
||||
?: (gte s n.domain.c)
|
||||
%.n
|
||||
=/ e
|
||||
%- mod
|
||||
:_ n.domain.c
|
||||
%+ tagged-hash 'BIP0340/challenge'
|
||||
:- 96
|
||||
(rep 8 ~[m x.pp r])
|
||||
=/ aa
|
||||
(mul-point-scalar g.domain.c s)
|
||||
=/ bb
|
||||
(mul-point-scalar pp (sub n.domain.c e))
|
||||
?: &(=(x.aa x.bb) !=(y.aa y.bb)) :: infinite?
|
||||
%.n
|
||||
=/ rr (add-points aa bb)
|
||||
?. =(0 (mod y.rr 2))
|
||||
%.n
|
||||
=(r x.rr)
|
||||
--
|
||||
--
|
||||
--
|
||||
::
|
||||
|
@ -116,4 +116,237 @@
|
||||
3d07.03a9.9925.0581.
|
||||
f7de.cd5e.f0f4.f809
|
||||
==
|
||||
++ test-schnorr
|
||||
=> |%
|
||||
+$ case-sec
|
||||
$: sec=@
|
||||
pub=@
|
||||
aux=@
|
||||
mes=@
|
||||
sig=@
|
||||
==
|
||||
+$ case-pub
|
||||
$: pub=@
|
||||
mes=@
|
||||
sig=@
|
||||
res=?
|
||||
==
|
||||
--
|
||||
=< %+ category "bip-0340 vectors"
|
||||
(zing :(weld t1 t2 t3))
|
||||
=/ cases-sec=(list case-sec)
|
||||
:~
|
||||
:* 0x3
|
||||
0xf930.8a01.9258.c310.4934.4f85.f89d.5229.
|
||||
b531.c845.836f.99b0.8601.f113.bce0.36f9
|
||||
0
|
||||
0
|
||||
0xe907.831f.8084.8d10.69a5.371b.4024.1036.
|
||||
4bdf.1c5f.8307.b008.4c55.f1ce.2dca.8215.
|
||||
25f6.6a4a.85ea.8b71.e482.a74f.382d.2ce5.
|
||||
ebee.e8fd.b217.2f47.7df4.900d.3105.36c0
|
||||
==
|
||||
:* 0xb7e1.5162.8aed.2a6a.bf71.5880.9cf4.f3c7.
|
||||
62e7.160f.38b4.da56.a784.d904.5190.cfef
|
||||
0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
1
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x6896.bd60.eeae.296d.b48a.229f.f71d.fe07.
|
||||
1bde.413e.6d43.f917.dc8d.cf8c.78de.3341.
|
||||
8906.d11a.c976.abcc.b20b.0912.92bf.f4ea.
|
||||
897e.fcb6.39ea.871c.fa95.f6de.339e.4b0a
|
||||
==
|
||||
:* 0xc90f.daa2.2168.c234.c4c6.628b.80dc.1cd1.
|
||||
2902.4e08.8a67.cc74.020b.bea6.3b14.e5c9
|
||||
0xdd30.8afe.c577.7e13.121f.a72b.9cc1.b7cc.
|
||||
0139.7153.09b0.86c9.60e1.8fd9.6977.4eb8
|
||||
0xc87a.a538.24b4.d7ae.2eb0.35a2.b5bb.bccc.
|
||||
080e.76cd.c6d1.692c.4b0b.62d7.98e6.d906
|
||||
0x7e2d.58d8.b3bc.df1a.bade.c782.9054.f90d.
|
||||
da98.05aa.b56c.7733.3024.b9d0.a508.b75c
|
||||
0x5831.aaee.d7b4.4bb7.4e5e.ab94.ba9d.4294.
|
||||
c49b.cf2a.6072.8d8b.4c20.0f50.dd31.3c1b.
|
||||
ab74.5879.a5ad.954a.72c4.5a91.c3a5.1d3c.
|
||||
7ade.a98d.82f8.481e.0e1e.0367.4a6f.3fb7
|
||||
==
|
||||
:* 0xb43.2b26.7793.7381.aef0.5bb0.2a66.ecd0.
|
||||
1277.3062.cf3f.a254.9e44.f58e.d240.1710
|
||||
0x25d1.dff9.5105.f525.3c40.22f6.28a9.96ad.
|
||||
3a0d.95fb.f21d.468a.1b33.f8c1.60d8.f517
|
||||
0xffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff
|
||||
0xffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff
|
||||
0x7eb0.5097.57e2.46f1.9449.8856.5161.1cb9.
|
||||
65ec.c1a1.87dd.51b6.4fda.1edc.9637.d5ec.
|
||||
9758.2b9c.b13d.b393.3705.b32b.a982.af5a.
|
||||
f25f.d788.81eb.b327.71fc.5922.efc6.6ea3
|
||||
==
|
||||
==
|
||||
=/ t1
|
||||
%+ turn cases-sec
|
||||
|= case-sec
|
||||
^- tang
|
||||
%+ expect-eq
|
||||
!> sig
|
||||
!> (sign:schnorr:ecc sec mes aux)
|
||||
=/ t2
|
||||
%+ turn cases-sec
|
||||
|= case-sec
|
||||
^- tang
|
||||
%- expect
|
||||
!> (verify:schnorr:ecc pub mes sig)
|
||||
=/ cases-pub=(list case-pub)
|
||||
:~
|
||||
:* 0xd69c.3509.bb99.e412.e68b.0fe8.544e.7283.
|
||||
7dfa.3074.6d8b.e2aa.6597.5f29.d22d.c7b9
|
||||
0x4df3.c3f6.8fcc.83b2.7e9d.42c9.0431.a724.
|
||||
99f1.7875.c81a.599b.566c.9889.b969.6703
|
||||
0x3b.78ce.563f.89a0.ed94.14f5.aa28.ad0d.
|
||||
96d6.795f.9c63.76af.b154.8af6.03b3.eb45.
|
||||
c9f8.207d.ee10.60cb.71c0.4e80.f593.060b.
|
||||
07d2.8308.d7f4
|
||||
%.y
|
||||
==
|
||||
:* 0xeefd.ea4c.db67.7750.a420.fee8.07ea.cf21.
|
||||
eb98.98ae.79b9.7687.66e4.faa0.4a2d.4a34
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x6cff.5c3b.a86c.69ea.4b73.76f3.1a9b.cb4f.
|
||||
74c1.9760.89b2.d996.3da2.e554.3e17.7769.
|
||||
69e8.9b4c.5564.d003.4910.6b84.9778.5dd7.
|
||||
d1d7.13a8.ae82.b32f.a79d.5f7f.c407.d39b
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0xfff9.7bd5.755e.eea4.2045.3a14.3552.35d3.
|
||||
82f6.472f.8568.a18b.2f05.7a14.6029.7556.
|
||||
3cc2.7944.640a.c607.cd10.7ae1.0923.d9ef.
|
||||
7a73.c643.e166.be5e.beaf.a34b.1ac5.53e2
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x1fa6.2e33.1edb.c21c.3947.92d2.ab11.00a7.
|
||||
b432.b013.df3f.6ff4.f99f.cb33.e0e1.515f.
|
||||
2889.0b3e.db6e.7189.b630.448b.515c.e4f8.
|
||||
622a.954c.fe54.5735.aaea.5134.fccd.b2bd
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x6cff.5c3b.a86c.69ea.4b73.76f3.1a9b.cb4f.
|
||||
74c1.9760.89b2.d996.3da2.e554.3e17.7769.
|
||||
9617.64b3.aa9b.2ffc.b6ef.947b.6887.a226.
|
||||
e8d7.c93e.00c5.ed0c.1834.ff0d.0c2e.6da6
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x123d.da83.28af.9c23.a94c.1fee.cfd1.23ba.
|
||||
4fb7.3476.f0d5.94dc.b65c.6425.bd18.6051
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x1.7615.fbaf.5ae2.8864.013c.0997.42de.
|
||||
adb4.dba8.7f11.ac67.54f9.3780.d5a1.837c.
|
||||
f197
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x4a29.8dac.ae57.395a.15d0.795d.dbfd.1dcb.
|
||||
564d.a82b.0f26.9bc7.0a74.f822.0429.ba1d.
|
||||
69e8.9b4c.5564.d003.4910.6b84.9778.5dd7.
|
||||
d1d7.13a8.ae82.b32f.a79d.5f7f.c407.d39b
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0xffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.fffe.ffff.fc2f.
|
||||
69e8.9b4c.5564.d003.4910.6b84.9778.5dd7.
|
||||
d1d7.13a8.ae82.b32f.a79d.5f7f.c407.d39b
|
||||
%.n
|
||||
==
|
||||
:* 0xdff1.d77f.2a67.1c5f.3618.3726.db23.41be.
|
||||
58fe.ae1d.a2de.ced8.4324.0f7b.502b.a659
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x6cff.5c3b.a86c.69ea.4b73.76f3.1a9b.cb4f.
|
||||
74c1.9760.89b2.d996.3da2.e554.3e17.7769.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.fffe.
|
||||
baae.dce6.af48.a03b.bfd2.5e8c.d036.4141
|
||||
%.n
|
||||
==
|
||||
:* 0xffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.fffe.ffff.fc30
|
||||
0x243f.6a88.85a3.08d3.1319.8a2e.0370.7344.
|
||||
a409.3822.299f.31d0.082e.fa98.ec4e.6c89
|
||||
0x6cff.5c3b.a86c.69ea.4b73.76f3.1a9b.cb4f.
|
||||
74c1.9760.89b2.d996.3da2.e554.3e17.7769.
|
||||
69e8.9b4c.5564.d003.4910.6b84.9778.5dd7.
|
||||
d1d7.13a8.ae82.b32f.a79d.5f7f.c407.d39b
|
||||
%.n
|
||||
==
|
||||
==
|
||||
:_ .
|
||||
^= t3
|
||||
%+ turn cases-pub
|
||||
|= case-pub
|
||||
^- tang
|
||||
%+ expect-eq
|
||||
!> res
|
||||
!> (verify:schnorr:ecc pub mes sig)
|
||||
++ test-schnorr-bounds
|
||||
=> |% +$ case [sec=@ pub=@ aux=@ mes=@ sig=@] --
|
||||
=< %+ category "bounds"
|
||||
(zing (weld t1 t2))
|
||||
=/ too-big
|
||||
0xff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff
|
||||
=/ big-sig
|
||||
0xff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff.
|
||||
ffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff
|
||||
=/ cases-big-sec=(list case)
|
||||
:~ [too-big 0 0 0 0]
|
||||
[1 0 too-big 0 0]
|
||||
[1 0 0 too-big 0]
|
||||
==
|
||||
=/ cases-big-pub=(list case)
|
||||
:~ [0 too-big 0 0 0]
|
||||
[0 0 0 too-big 0]
|
||||
[0 0 0 0 big-sig]
|
||||
==
|
||||
=/ t1
|
||||
%+ turn cases-big-sec
|
||||
|= case
|
||||
%- expect-fail
|
||||
|. (sign:schnorr:ecc sec mes aux)
|
||||
:_ .
|
||||
^= t2
|
||||
%+ turn cases-big-pub
|
||||
|= case
|
||||
%- expect-fail
|
||||
|. (verify:schnorr:ecc pub mes sig)
|
||||
--
|
||||
|
@ -1,10 +1,10 @@
|
||||
:~ title+'System'
|
||||
info+'An app launcher for Urbit.'
|
||||
color+0xee.5432
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v4.64ana.19ug9.ik7l6.og080.68ce4.glob' 0v4.64ana.19ug9.ik7l6.og080.68ce4]
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v5.1o2c9.g1btf.nandl.703oh.40up1.glob' 0v5.1o2c9.g1btf.nandl.703oh.40up1]
|
||||
::glob-ames+~zod^0v0
|
||||
base+'grid'
|
||||
version+[1 0 2]
|
||||
version+[1 0 3]
|
||||
website+'https://tlon.io'
|
||||
license+'MIT'
|
||||
==
|
||||
|
@ -9,15 +9,14 @@ const { execSync } = require('child_process');
|
||||
const GIT_DESC = execSync('git describe --always', { encoding: 'utf8' }).trim();
|
||||
|
||||
let devServer = {
|
||||
contentBase: path.join(__dirname, '../public'),
|
||||
hot: true,
|
||||
port: 9000,
|
||||
host: '0.0.0.0',
|
||||
disableHostCheck: true,
|
||||
historyApiFallback: {
|
||||
index: '/apps/landscape/index.html',
|
||||
disableDotRule: true
|
||||
},
|
||||
publicPath: '/apps/landscape/'
|
||||
}
|
||||
};
|
||||
|
||||
const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost:9000`);
|
||||
@ -25,7 +24,6 @@ const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost
|
||||
if(urbitrc.URL) {
|
||||
devServer = {
|
||||
...devServer,
|
||||
index: 'index.html',
|
||||
// headers: {
|
||||
// 'Service-Worker-Allowed': '/'
|
||||
// },
|
||||
|
33258
pkg/interface/package-lock.json
generated
33258
pkg/interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@
|
||||
"css-loader": "^3.6.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "^2.1.5",
|
||||
"fuzzy": "^0.1.3",
|
||||
"immer": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Urbit from '@urbit/http-api';
|
||||
const api = new Urbit('', '', (window as any).desk);
|
||||
api.ship = window.ship;
|
||||
api.verbose = true;
|
||||
// api.verbose = true;
|
||||
// @ts-ignore TODO window typings
|
||||
window.api = api;
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
import useMetadataState from '../state/metadata';
|
||||
import ob from 'urbit-ob';
|
||||
import useInviteState from '../state/invite';
|
||||
import {resourceAsPath} from '../../../../npm/api/dist';
|
||||
import { deSig, resourceAsPath } from '@urbit/api';
|
||||
import { createJoinParams } from '~/views/landscape/components/Join/Join';
|
||||
|
||||
function getGroupResourceRedirect(key: string) {
|
||||
const association = useMetadataState.getState().associations.graph[`/ship/${key}`];
|
||||
const { metadata } = association;
|
||||
if(!association || !('graph' in metadata.config)) {
|
||||
const graphs = useMetadataState.getState().associations.graph;
|
||||
const association = graphs[`/ship/${key}`];
|
||||
if(!association || !('graph' in association.metadata.config)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const section = association.group === association.resource ? '/messages' : association.group;
|
||||
return `/~landscape${section}/resource/${metadata.config.graph}${association.resource}`;
|
||||
return `/~landscape${section}/resource/${association.metadata.config.graph}${association.resource}`;
|
||||
}
|
||||
|
||||
function getPostRedirect(key: string, segs: string[]) {
|
||||
@ -70,8 +71,18 @@ function getGraphRedirect(link: string) {
|
||||
function getInviteRedirect(link: string) {
|
||||
const [,,app,uid] = link.split('/');
|
||||
const invite = useInviteState.getState().invites[app][uid];
|
||||
if(!invite) { return ''; }
|
||||
return { search: `?join-kind=${app}&join-path=${encodeURIComponent(resourceAsPath(invite.resource))}` };
|
||||
if(!invite || (app !== 'groups' && app !== 'graph')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { ship, name } = invite.resource;
|
||||
const alreadyJoined = getGroupResourceRedirect(`~${deSig(ship)}/${name}`);
|
||||
|
||||
if (alreadyJoined) {
|
||||
return alreadyJoined;
|
||||
}
|
||||
|
||||
return { search: createJoinParams(app, resourceAsPath(invite.resource)) };
|
||||
}
|
||||
|
||||
function getDmRedirect(link: string) {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { isChannelAdmin } from '~/logic/lib/group';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { createJoinParams } from '~/views/landscape/components/Join/Join';
|
||||
|
||||
const makeIndexes = () => new Map([
|
||||
['ships', []],
|
||||
['commands', []],
|
||||
['subscriptions', []],
|
||||
['groups', []],
|
||||
['apps', []],
|
||||
['other', []]
|
||||
]);
|
||||
|
||||
@ -56,34 +56,11 @@ const commandIndex = function (currentGroup, groups, associations) {
|
||||
if (canAdd) {
|
||||
commands.push(result('Channel: Create', `/~landscape${workspace}/new`, 'Groups', null));
|
||||
}
|
||||
commands.push(result('Groups: Join', '?join-kind=group', 'Groups', null));
|
||||
commands.push(result('Groups: Join', createJoinParams('groups'), 'Groups', null));
|
||||
|
||||
return commands;
|
||||
};
|
||||
|
||||
const appIndex = function (apps) {
|
||||
// all apps are indexed from launch data
|
||||
// indexed into 'apps'
|
||||
const applications = [];
|
||||
Object.keys(apps)
|
||||
.filter((e) => {
|
||||
return !['weather','clock'].includes(e);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.localeCompare(b);
|
||||
})
|
||||
.map((e) => {
|
||||
const obj = result(
|
||||
apps[e].type?.basic?.title || apps[e].type.custom?.tile || e,
|
||||
apps[e]?.type.basic?.linkedUrl || apps[e]?.type.custom?.linkedUrl || '',
|
||||
apps[e]?.type?.basic?.title || apps[e].type.custom?.tile || e,
|
||||
null
|
||||
);
|
||||
applications.push(obj);
|
||||
});
|
||||
return applications;
|
||||
};
|
||||
|
||||
const otherIndex = function(config) {
|
||||
const other = [];
|
||||
const idx = {
|
||||
@ -102,7 +79,7 @@ const otherIndex = function(config) {
|
||||
return other;
|
||||
};
|
||||
|
||||
export default function index(contacts, associations, apps, currentGroup, groups, hide): Map<string, OmniboxItem[]> {
|
||||
export default function index(contacts, associations, currentGroup, groups, hide): Map<string, OmniboxItem[]> {
|
||||
const indexes = makeIndexes();
|
||||
indexes.set('ships', shipIndex(contacts));
|
||||
// all metadata from all apps is indexed
|
||||
@ -164,7 +141,6 @@ export default function index(contacts, associations, apps, currentGroup, groups
|
||||
indexes.set('commands', commandIndex(currentGroup, groups, associations));
|
||||
indexes.set('subscriptions', subscriptions);
|
||||
indexes.set('groups', landscape);
|
||||
indexes.set('apps', appIndex(apps));
|
||||
indexes.set('other', otherIndex(hide));
|
||||
|
||||
return indexes;
|
||||
|
@ -210,6 +210,9 @@ function more(json: any, state: HarkState): HarkState {
|
||||
function added(json: any, state: HarkState): HarkState {
|
||||
if('added' in json) {
|
||||
const { bin } = json.added;
|
||||
if(bin.place.desk !== window.desk) {
|
||||
return state;
|
||||
}
|
||||
const binId = harkBinToId(bin);
|
||||
state.unseen[binId] = json.added;
|
||||
}
|
||||
@ -239,6 +242,9 @@ function timebox(json: any, state: HarkState): HarkState {
|
||||
const time = makePatDa(lid.archive);
|
||||
const old = state.archive.get(time) || {};
|
||||
notifications.forEach((note: any) => {
|
||||
if(note.bin.place.desk !== window.desk) {
|
||||
return;
|
||||
}
|
||||
const binId = harkBinToId(note.bin);
|
||||
old[binId] = note;
|
||||
});
|
||||
@ -246,6 +252,9 @@ function timebox(json: any, state: HarkState): HarkState {
|
||||
} else {
|
||||
const seen = 'seen' in lid ? 'seen' : 'unseen';
|
||||
notifications.forEach((note: any) => {
|
||||
if(note.bin.place.desk !== window.desk) {
|
||||
return;
|
||||
}
|
||||
const binId = harkBinToId(note.bin);
|
||||
state[seen][binId] = note;
|
||||
});
|
||||
|
@ -40,6 +40,7 @@ export interface SettingsState {
|
||||
hideUnreads: boolean;
|
||||
hideGroups: boolean;
|
||||
hideUtilities: boolean;
|
||||
disableSpellcheck: boolean;
|
||||
};
|
||||
keyboard: ShortcutMapping;
|
||||
remoteContentPolicy: RemoteContentPolicy;
|
||||
@ -72,7 +73,8 @@ const useSettingsState = createState<SettingsState>(
|
||||
hideAvatars: false,
|
||||
hideUnreads: false,
|
||||
hideGroups: false,
|
||||
hideUtilities: false
|
||||
hideUtilities: false,
|
||||
disableSpellcheck: false
|
||||
},
|
||||
remoteContentPolicy: {
|
||||
imageShown: true,
|
||||
|
@ -27,7 +27,7 @@ import './css/indigo-static.css';
|
||||
import { Content } from './landscape/components/Content';
|
||||
import './landscape/css/custom.css';
|
||||
import { bootstrapApi } from '~/logic/api/bootstrap';
|
||||
import { uxToHex } from '@urbit/api/dist';
|
||||
import { uxToHex } from '@urbit/api';
|
||||
|
||||
function ensureValidHex(color) {
|
||||
if (!color)
|
||||
@ -43,7 +43,11 @@ const Root = withState(styled.div`
|
||||
font-family: ${p => p.theme.fonts.sans};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
padding-left: env(safe-area-inset-left, 0px);
|
||||
padding-right: env(safe-area-inset-right, 0px);
|
||||
padding-top: env(safe-area-inset-top, 0px);
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
|
||||
margin: 0;
|
||||
${p => p.display.backgroundType === 'url' ? `
|
||||
background-image: url('${p.display.background}');
|
||||
|
@ -89,7 +89,7 @@ const ChatResource = (props: ChatResourceProps): ReactElement => {
|
||||
);
|
||||
|
||||
const isAdmin = useMemo(
|
||||
() => (group ? group.tags.role.admin.has(`~${window.ship}`) : false),
|
||||
() => group ? group.tags.role.admin.has(deSig(window.ship)) : false,
|
||||
[group]
|
||||
);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { acceptDm, cite, Content, declineDm, deSig, Post, removeDmMessage } from '@urbit/api';
|
||||
import { acceptDm, cite, Content, declineDm, deSig, Post } from '@urbit/api';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import _ from 'lodash';
|
||||
import bigInt from 'big-integer';
|
||||
@ -77,8 +77,10 @@ export function DmResource(props: DmResourceProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if(dm.size === 0 && !pending) {
|
||||
getNewest(`~${window.ship}`, 'dm-inbox', 100, `/${patp2dec(ship)}`);
|
||||
}, [ship]);
|
||||
}
|
||||
}, [ship, dm]);
|
||||
|
||||
const fetchMessages = useCallback(
|
||||
async (newer: boolean) => {
|
||||
@ -125,10 +127,6 @@ export function DmResource(props: DmResourceProps) {
|
||||
[ship, addDmMessage]
|
||||
);
|
||||
|
||||
const onDelete = useCallback((msg: Post) => {
|
||||
airlock.poke(removeDmMessage(`~${window.ship}`, msg.index));
|
||||
}, []);
|
||||
|
||||
const onAccept = async () => {
|
||||
await airlock.poke(acceptDm(ship));
|
||||
};
|
||||
@ -136,6 +134,7 @@ export function DmResource(props: DmResourceProps) {
|
||||
history.push('/~landscape/messages');
|
||||
await airlock.poke(declineDm(ship));
|
||||
};
|
||||
|
||||
return (
|
||||
<Col width="100%" height="100%" overflow="hidden">
|
||||
<Row
|
||||
@ -206,7 +205,6 @@ export function DmResource(props: DmResourceProps) {
|
||||
onReply={quoteReply}
|
||||
fetchMessages={fetchMessages}
|
||||
dismissUnread={dismissUnread}
|
||||
onDelete={onDelete}
|
||||
getPermalink={() => undefined}
|
||||
isAdmin={false}
|
||||
onSubmit={onSubmit}
|
||||
|
@ -8,6 +8,7 @@ import React, { useRef, ClipboardEvent, useEffect, useImperativeHandle } from 'r
|
||||
import { Controlled as CodeEditor } from 'react-codemirror2';
|
||||
import styled from 'styled-components';
|
||||
import { MOBILE_BROWSER_REGEX } from '~/logic/lib/util';
|
||||
import useSettingsState from '~/logic/state/settings';
|
||||
import '../css/custom.css';
|
||||
import { useChatStore } from './ChatPane';
|
||||
|
||||
@ -131,6 +132,8 @@ const ChatEditor = React.forwardRef<CodeMirrorShim, ChatEditorProps>(({ inCodeMo
|
||||
useImperativeHandle(ref, () => editorRef.current);
|
||||
const editor = editorRef.current;
|
||||
|
||||
const disableSpellcheck = useSettingsState(s => s.calm.disableSpellcheck);
|
||||
|
||||
const {
|
||||
message,
|
||||
setMessage
|
||||
@ -234,6 +237,7 @@ const ChatEditor = React.forwardRef<CodeMirrorShim, ChatEditorProps>(({ inCodeMo
|
||||
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
|
||||
fontSize={1}
|
||||
lineHeight="tall"
|
||||
spellCheck={!disableSpellcheck}
|
||||
value={message}
|
||||
rows={1}
|
||||
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
|
||||
|
@ -284,6 +284,9 @@ const MessageActionItem = (props) => {
|
||||
const MessageActions = ({ onReply, onDelete, msg, isAdmin, permalink }) => {
|
||||
const isOwn = () => msg.author === window.ship;
|
||||
const { doCopy, copyDisplay } = useCopy(permalink, 'Copy Message Link');
|
||||
const showCopyMessageLink = Boolean(permalink);
|
||||
const showDelete = (isAdmin || isOwn()) && onDelete;
|
||||
const showDropdown = showCopyMessageLink || showDelete;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -304,6 +307,7 @@ const MessageActions = ({ onReply, onDelete, msg, isAdmin, permalink }) => {
|
||||
>
|
||||
<Icon icon='Chat' size={3} />
|
||||
</Box>
|
||||
{showDropdown && (
|
||||
<Dropdown
|
||||
dropWidth='250px'
|
||||
width='auto'
|
||||
@ -325,20 +329,15 @@ const MessageActions = ({ onReply, onDelete, msg, isAdmin, permalink }) => {
|
||||
<MessageActionItem onClick={() => onReply(msg)}>
|
||||
Reply
|
||||
</MessageActionItem>
|
||||
{permalink ? (
|
||||
{showCopyMessageLink && (
|
||||
<MessageActionItem onClick={doCopy}>
|
||||
{copyDisplay}
|
||||
</MessageActionItem>
|
||||
) : null }
|
||||
{(isAdmin || isOwn()) ? (
|
||||
)}
|
||||
{showDelete && (
|
||||
<MessageActionItem onClick={e => onDelete(msg)} color='red'>
|
||||
Delete Message
|
||||
</MessageActionItem>
|
||||
) : null}
|
||||
{false && (
|
||||
<MessageActionItem onClick={e => console.log(e)}>
|
||||
View Signature
|
||||
</MessageActionItem>
|
||||
)}
|
||||
</Col>
|
||||
}
|
||||
@ -347,6 +346,7 @@ const MessageActions = ({ onReply, onDelete, msg, isAdmin, permalink }) => {
|
||||
<Icon icon='Menu' size={3} />
|
||||
</Box>
|
||||
</Dropdown>
|
||||
)}
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
@ -418,7 +418,7 @@ function ChatMessage(props: ChatMessageProps) {
|
||||
}
|
||||
|
||||
const onReply = props?.onReply || emptyCallback;
|
||||
const onDelete = props?.onDelete || emptyCallback;
|
||||
const onDelete = props?.onDelete; // If missing hide delete action
|
||||
const transcluded = props?.transcluded || 0;
|
||||
const renderSigil = props.renderSigil || (Boolean(nextMsg && msg.author !== nextMsg.author) ||
|
||||
!nextMsg
|
||||
@ -513,111 +513,3 @@ function ChatMessage(props: ChatMessageProps) {
|
||||
export default React.memo(React.forwardRef((props: Omit<ChatMessageProps, 'innerRef'>, ref: any) => (
|
||||
<ChatMessage {...props} innerRef={ref} />
|
||||
)));
|
||||
|
||||
export const MessagePlaceholder = ({
|
||||
height,
|
||||
index,
|
||||
className = '',
|
||||
style = {},
|
||||
...props
|
||||
}) => (
|
||||
<Box
|
||||
width='100%'
|
||||
fontSize={2}
|
||||
pl={3}
|
||||
pt={4}
|
||||
pr={3}
|
||||
display='flex'
|
||||
lineHeight='tall'
|
||||
className={className}
|
||||
style={{ height, ...style }}
|
||||
{...props}
|
||||
>
|
||||
<Box
|
||||
pr={3}
|
||||
verticalAlign='top'
|
||||
backgroundColor='white'
|
||||
style={{ float: 'left' }}
|
||||
>
|
||||
<Text
|
||||
display='block'
|
||||
background='washedGray'
|
||||
width='24px'
|
||||
height='24px'
|
||||
borderRadius='50%'
|
||||
style={{
|
||||
visibility: index % 5 == 0 ? 'initial' : 'hidden'
|
||||
}}
|
||||
></Text>
|
||||
</Box>
|
||||
<Box
|
||||
style={{ float: 'right', flexGrow: 1 }}
|
||||
color='black'
|
||||
className='clamp-message'
|
||||
>
|
||||
<Box
|
||||
className='hide-child'
|
||||
paddingTop={4}
|
||||
style={{ visibility: index % 5 == 0 ? 'initial' : 'hidden' }}
|
||||
>
|
||||
<Text
|
||||
display='inline-block'
|
||||
verticalAlign='middle'
|
||||
fontSize={0}
|
||||
color='washedGray'
|
||||
cursor='default'
|
||||
>
|
||||
<Text maxWidth='32rem' display='block'>
|
||||
<Text
|
||||
backgroundColor='washedGray'
|
||||
borderRadius={2}
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
></Text>
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
display='inline-block'
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize={0}
|
||||
color='washedGray'
|
||||
>
|
||||
<Text
|
||||
background='washedGray'
|
||||
borderRadius={2}
|
||||
display='block'
|
||||
height='1em'
|
||||
style={{ width: `${((index % 3) + 1) * 3}em` }}
|
||||
></Text>
|
||||
</Text>
|
||||
<Text
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize={0}
|
||||
ml={2}
|
||||
color='washedGray'
|
||||
borderRadius={2}
|
||||
display={['none', 'inline-block']}
|
||||
className='child'
|
||||
>
|
||||
<Text
|
||||
backgroundColor='washedGray'
|
||||
borderRadius={2}
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
></Text>
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
display='block'
|
||||
backgroundColor='washedGray'
|
||||
borderRadius={2}
|
||||
height='1em'
|
||||
style={{ width: `${(index % 5) * 20}%` }}
|
||||
></Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -161,6 +161,13 @@ class ChatWindow extends Component<
|
||||
}
|
||||
}
|
||||
|
||||
onTopLoaded = () => {
|
||||
const { graphSize, unreadCount } = this.props;
|
||||
if(graphSize >= unreadCount) {
|
||||
this.props.dismissUnread();
|
||||
}
|
||||
};
|
||||
|
||||
onBottomLoaded = () => {
|
||||
if(this.state.unreadIndex.eq(bigInt.zero)) {
|
||||
this.calculateUnreadIndex();
|
||||
@ -274,6 +281,7 @@ class ChatWindow extends Component<
|
||||
origin='bottom'
|
||||
style={virtScrollerStyle}
|
||||
onBottomLoaded={this.onBottomLoaded}
|
||||
onTopLoaded={this.onTopLoaded}
|
||||
// @ts-ignore paging @liam-fitzgerald on virtualscroller props
|
||||
onScroll={this.onScroll}
|
||||
data={graph}
|
||||
|
@ -12,7 +12,7 @@ import ModalButton from "./components/ModalButton";
|
||||
import Tiles from "./components/tiles";
|
||||
import Tile from "./components/tiles/tile";
|
||||
import "./css/custom.css";
|
||||
import { Join, JoinRoute } from "~/views/landscape/components/Join/Join";
|
||||
import { createJoinParams, Join, JoinRoute } from "~/views/landscape/components/Join/Join";
|
||||
|
||||
const ScrollbarLessBox = styled(Box)`
|
||||
scrollbar-width: none !important;
|
||||
@ -40,7 +40,7 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
</title>
|
||||
</Helmet>
|
||||
<Route path="/join/:ship/:name">
|
||||
<JoinRoute modal />
|
||||
<JoinRoute />
|
||||
</Route>
|
||||
<ScrollbarLessBox
|
||||
height="100%"
|
||||
@ -94,7 +94,7 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
border={0}
|
||||
p={0}
|
||||
borderRadius={2}
|
||||
onClick={() => history.push({ search: "?join-kind=group" })}
|
||||
onClick={() => history.push({ search: createJoinParams('groups') })}
|
||||
>
|
||||
<Row backgroundColor="white" gapX="2" p={2} height="100%" width="100%" alignItems="center">
|
||||
<Icon icon="BootNode" />
|
||||
|
@ -22,6 +22,7 @@ import useSettingsState, {
|
||||
} from "~/logic/state/settings";
|
||||
import Tile from "../components/tiles/tile";
|
||||
import { useQuery } from "~/logic/lib/useQuery";
|
||||
import { createJoinParams } from "~/views/landscape/components/Join/Join";
|
||||
|
||||
const sortGroupsAlph = (a: Association, b: Association) =>
|
||||
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||
@ -123,8 +124,7 @@ function PendingGroup(props: PendingGroupProps) {
|
||||
const title = preview?.metadata?.title || path;
|
||||
const { toQuery } = useQuery();
|
||||
const onClick = () => {
|
||||
const { ship, name } = resourceFromPath(path);
|
||||
history.push(toQuery({ "join-kind": "groups", "join-path": path }));
|
||||
history.push(toQuery(createJoinParams('groups', path, null, false)));
|
||||
};
|
||||
|
||||
const joining = useGroupState((s) => s.pendingJoin[path]?.progress);
|
||||
|
@ -16,6 +16,7 @@ import { TranscludedNode } from './TranscludedNode';
|
||||
import styled from 'styled-components';
|
||||
import Author from '~/views/components/Author';
|
||||
import useDocketState, { useTreaty } from '~/logic/state/docket';
|
||||
import { createJoinParams } from '~/views/landscape/components/Join/Join';
|
||||
|
||||
function Placeholder(type) {
|
||||
const lines = (type) => {
|
||||
@ -118,8 +119,7 @@ function GraphPermalink(
|
||||
const permalink = (() => {
|
||||
const link = `/perma${getPermalinkForGraph(group, graph, index).slice(16)}`;
|
||||
return (!association && !loading)
|
||||
? { search: `?join-kind=group&join-path=${encodeURIComponent(group)}&redir=${encodeURIComponent(link)}` }
|
||||
: link
|
||||
? { search: createJoinParams('groups', group, link) } : link;
|
||||
})();
|
||||
|
||||
const [nodeGroupHost, nodeGroupName] = association?.group.split('/').slice(-2) ?? ['Unknown', 'Unknown'];
|
||||
|
@ -46,13 +46,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
props.history.push(rootUrl);
|
||||
};
|
||||
|
||||
if (typeof note.post === 'string' || !note.post) {
|
||||
return (
|
||||
<Box width="100%" pt="2" textAlign="center">
|
||||
<Text gray>This note has been deleted.</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const comments = getComments(note);
|
||||
const [, title, , post] = getLatestRevision(note);
|
||||
@ -148,4 +141,16 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default Note;
|
||||
export default function(props: NoteProps & RouteComponentProps) {
|
||||
const { note } = props;
|
||||
|
||||
if (typeof note.post === 'string' || !note.post) {
|
||||
return (
|
||||
<Box width="100%" pt="2" textAlign="center">
|
||||
<Text gray>This note has been deleted.</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (<Note {...props} />);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ interface FormSchema {
|
||||
audioShown: boolean;
|
||||
oembedShown: boolean;
|
||||
videoShown: boolean;
|
||||
disableSpellcheck: boolean;
|
||||
}
|
||||
|
||||
const settingsSel = (s: SettingsState): FormSchema => ({
|
||||
@ -28,10 +29,11 @@ const settingsSel = (s: SettingsState): FormSchema => ({
|
||||
hideUnreads: s.calm.hideUnreads,
|
||||
hideGroups: s.calm.hideGroups,
|
||||
hideUtilities: s.calm.hideUtilities,
|
||||
disableSpellcheck: s.calm.disableSpellcheck,
|
||||
imageShown: !s.remoteContentPolicy.imageShown,
|
||||
videoShown: !s.remoteContentPolicy.videoShown,
|
||||
oembedShown: !s.remoteContentPolicy.oembedShown,
|
||||
audioShown: !s.remoteContentPolicy.audioShown
|
||||
audioShown: !s.remoteContentPolicy.audioShown,
|
||||
});
|
||||
|
||||
export function CalmPrefs() {
|
||||
@ -108,6 +110,12 @@ export function CalmPrefs() {
|
||||
id="oembedShown"
|
||||
caption="Embedded content may contain scripts that can track you"
|
||||
/>
|
||||
<Text fontWeight="medium">Input settings</Text>
|
||||
<Toggle
|
||||
label="Disable spellcheck"
|
||||
id="disableSpellcheck"
|
||||
caption="Disable browser spellcheck"
|
||||
/>
|
||||
</Col>
|
||||
</Form>
|
||||
</FormikOnBlur>
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from 'formik';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import useSettingsState from '~/logic/state/settings';
|
||||
import { ShipImage } from './ShipImage';
|
||||
|
||||
interface FormSchema {
|
||||
@ -35,6 +36,7 @@ interface CommentInputProps {
|
||||
const SubmitTextArea = (props) => {
|
||||
const { submitForm } = useFormikContext<FormSchema>();
|
||||
const [field] = useField(props.id);
|
||||
const disableSpellcheck = useSettingsState(s => s.calm.disableSpellcheck);
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.getModifierState('Control') || e.metaKey) && e.key === 'Enter') {
|
||||
submitForm();
|
||||
@ -50,6 +52,7 @@ const SubmitTextArea = (props) => {
|
||||
fontWeight="500"
|
||||
fontSize="1"
|
||||
flexGrow={1}
|
||||
spellCheck={!disableSpellcheck}
|
||||
style={{ resize: 'vertical' }}
|
||||
{...field}
|
||||
onKeyDown={onKeyDown}
|
||||
|
@ -3,6 +3,7 @@ import React, { ReactElement, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useMetadataState, { usePreview } from '~/logic/state/metadata';
|
||||
import { PropFunc } from '~/types';
|
||||
import { createJoinParams } from '../landscape/components/Join/Join';
|
||||
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
||||
|
||||
type GroupLinkProps = {
|
||||
@ -26,7 +27,7 @@ const { preview } = usePreview(resource);
|
||||
<Row
|
||||
{...rest}
|
||||
as={Link}
|
||||
to={joined ? `/~landscape/ship/${name}` : { search: `?join-kind=groups&join-path=/ship/${name}`}}
|
||||
to={joined ? `/~landscape/ship/${name}` : { search: createJoinParams('groups', `/ship/${name}`) }}
|
||||
flexShrink={1}
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
|
@ -90,6 +90,11 @@ export interface VirtualScrollerProps<K,V> {
|
||||
* Callback to execute when finished loading from start
|
||||
*/
|
||||
onBottomLoaded?: () => void;
|
||||
/*
|
||||
* Callback to execute when finished loading from end
|
||||
*/
|
||||
onTopLoaded?: () => void;
|
||||
|
||||
/*
|
||||
* equality function for the key type
|
||||
*/
|
||||
@ -413,6 +418,9 @@ export default class VirtualScroller<K,V> extends Component<VirtualScrollerProps
|
||||
if(newer && this.props.onBottomLoaded) {
|
||||
this.props.onBottomLoaded();
|
||||
}
|
||||
if(!newer && this.props.onTopLoaded) {
|
||||
this.props.onTopLoaded();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Box, Row, Text } from '@tlon/indigo-react';
|
||||
import { omit } from 'lodash';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import fuzzy from 'fuzzy';
|
||||
import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import React, {
|
||||
@ -40,11 +41,23 @@ const SEARCHED_CATEGORIES = [
|
||||
'other',
|
||||
'groups',
|
||||
'subscriptions',
|
||||
'apps'
|
||||
];
|
||||
const settingsSel = (s: SettingsState) => s.leap;
|
||||
const CAT_LIMIT = 6;
|
||||
|
||||
/**
|
||||
* Flatten `catMap` according to ordering in `cats`
|
||||
*/
|
||||
function flattenCattegoryMap(cats: string[], catMap: Map<string, OmniboxItem[]>) {
|
||||
let res = [] as OmniboxItem[];
|
||||
cats.forEach(cat => {
|
||||
res = res.concat(_.take(catMap.get(cat), CAT_LIMIT));
|
||||
});
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
@ -57,7 +70,6 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
const contactState = useContactState(state => state.contacts);
|
||||
const notificationCount = useHarkState(state => state.notificationsCount);
|
||||
const invites = useInviteState(state => state.invites);
|
||||
const tiles = useLaunchState(state => state.tiles);
|
||||
const [leapCursor, setLeapCursor] = useState('pointer');
|
||||
|
||||
const contacts = useMemo(() => {
|
||||
@ -83,12 +95,11 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
return makeIndex(
|
||||
contacts,
|
||||
associations,
|
||||
tiles,
|
||||
selectedGroup,
|
||||
groups,
|
||||
leapConfig
|
||||
);
|
||||
}, [selectedGroup, leapConfig, contacts, associations, groups, tiles]);
|
||||
}, [selectedGroup, leapConfig, contacts, associations, groups]);
|
||||
|
||||
const onOutsideClick = useCallback(() => {
|
||||
props.show && props.toggle();
|
||||
@ -127,29 +138,28 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
);
|
||||
}, [index]);
|
||||
|
||||
const results = useMemo(() => {
|
||||
const [results, categoryOrder] = useMemo(
|
||||
(): [Map<string, OmniboxItem[]>, string[]] => {
|
||||
if (query.length <= 1) {
|
||||
return initialResults;
|
||||
return [initialResults, ['other']];
|
||||
}
|
||||
const q = query.toLowerCase();
|
||||
const resultsMap = new Map<string, OmniboxItem[]>();
|
||||
let categoryMaxes: Record<string, number> = {};
|
||||
|
||||
SEARCHED_CATEGORIES.map((category) => {
|
||||
const categoryIndex = index.get(category);
|
||||
resultsMap.set(
|
||||
category,
|
||||
categoryIndex.filter((result) => {
|
||||
return (
|
||||
result.title.toLowerCase().includes(q) ||
|
||||
result.link.toLowerCase().includes(q) ||
|
||||
result.app.toLowerCase().includes(q) ||
|
||||
(result.host !== null
|
||||
? result.host.toLowerCase().includes(q)
|
||||
: false)
|
||||
);
|
||||
})
|
||||
);
|
||||
const fuzzied = fuzzy
|
||||
.filter(q, categoryIndex, { extract: res => res.title });
|
||||
categoryMaxes[category] = fuzzied
|
||||
.map(a => a.score)
|
||||
.reduce((a,b) => Math.max(a,b), 0);
|
||||
resultsMap.set(category, fuzzied.map(a => a.original));
|
||||
});
|
||||
return resultsMap;
|
||||
let order = Object.entries(categoryMaxes)
|
||||
.sort(([,a],[,b]) => b - a)
|
||||
.map(([id]) => id);
|
||||
return [resultsMap, order];
|
||||
}, [query, index]);
|
||||
|
||||
const navigate = useCallback(
|
||||
@ -184,7 +194,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
);
|
||||
|
||||
const setPreviousSelected = useCallback(() => {
|
||||
const flattenedResults = Array.from(results.values()).map(f.take(CAT_LIMIT)).flat();
|
||||
const flattenedResults = flattenCattegoryMap(categoryOrder, results);
|
||||
const totalLength = flattenedResults.length;
|
||||
if (selected.length) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
@ -204,10 +214,10 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
const { app, link } = flattenedResults[totalLength - 1];
|
||||
setSelected([app, link]);
|
||||
}
|
||||
}, [results, selected]);
|
||||
}, [results, categoryOrder, selected]);
|
||||
|
||||
const setNextSelected = useCallback(() => {
|
||||
const flattenedResults = Array.from(results.values()).map(f.take(CAT_LIMIT)).flat();
|
||||
const flattenedResults = flattenCattegoryMap(categoryOrder, results);
|
||||
if (selected.length) {
|
||||
const currentIndex = flattenedResults.indexOf(
|
||||
// @ts-ignore unclear how to give this spread a return signature
|
||||
@ -226,7 +236,7 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
const { app, link } = flattenedResults[0];
|
||||
setSelected([app, link]);
|
||||
}
|
||||
}, [selected, results]);
|
||||
}, [results, categoryOrder, selected]);
|
||||
|
||||
const setSelection = (app, link) => {
|
||||
setLeapCursor('pointer');
|
||||
@ -258,14 +268,15 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
}
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault();
|
||||
let values = flattenCattegoryMap(categoryOrder, results);
|
||||
if (selected.length) {
|
||||
navigate(selected[0], selected[1], evt.shiftKey);
|
||||
} else if (Array.from(results.values()).flat().length === 0) {
|
||||
} else if (values.length === 0) {
|
||||
return;
|
||||
} else {
|
||||
navigate(
|
||||
Array.from(results.values()).flat()[0].app,
|
||||
Array.from(results.values()).flat()[0].link,
|
||||
values[0].app,
|
||||
values[0].link,
|
||||
evt.shiftKey
|
||||
);
|
||||
}
|
||||
@ -278,14 +289,15 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
query,
|
||||
props.show,
|
||||
results,
|
||||
categoryOrder,
|
||||
setPreviousSelected,
|
||||
setNextSelected
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const flattenedResultLinks: [string, string][] = Array.from(results.values())
|
||||
.flat()
|
||||
const flattenedResultLinks: [string, string][] =
|
||||
flattenCattegoryMap(categoryOrder, results)
|
||||
.map(result => [result.app, result.link]);
|
||||
if (!flattenedResultLinks.includes(selected as [string, string])) {
|
||||
setSelected(flattenedResultLinks[0] || []);
|
||||
@ -322,10 +334,10 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
||||
borderBottomLeftRadius={2}
|
||||
borderBottomRightRadius={2}
|
||||
>
|
||||
{SEARCHED_CATEGORIES.map(category =>
|
||||
{categoryOrder.map(category =>
|
||||
({
|
||||
category,
|
||||
categoryResults: _.take(results.get(category).sort(sortResults), CAT_LIMIT)
|
||||
categoryResults: _.take(results.get(category), CAT_LIMIT)
|
||||
})
|
||||
)
|
||||
.filter(category => category.categoryResults.length > 0)
|
||||
|
@ -114,7 +114,6 @@ export function GroupSwitcher(props: {
|
||||
width="100%"
|
||||
alignItems="stretch"
|
||||
>
|
||||
{(props.baseUrl === '/~landscape/home') ?
|
||||
<GroupSwitcherItem to="">
|
||||
<Icon
|
||||
mr={2}
|
||||
@ -124,16 +123,6 @@ export function GroupSwitcher(props: {
|
||||
/>
|
||||
<Text>All Groups</Text>
|
||||
</GroupSwitcherItem>
|
||||
:
|
||||
<GroupSwitcherItem to="/~landscape/home">
|
||||
<Icon
|
||||
mr={2}
|
||||
color="gray"
|
||||
display="block"
|
||||
icon="Home"
|
||||
/>
|
||||
<Text>My Channels</Text>
|
||||
</GroupSwitcherItem>}
|
||||
<RecentGroups
|
||||
recent={props.recentGroups}
|
||||
/>
|
||||
|
@ -7,19 +7,19 @@ import {
|
||||
ManagedTextInputField,
|
||||
ManagedCheckboxField,
|
||||
ContinuousProgressBar,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, Form } from "formik";
|
||||
import React, { useEffect } from "react";
|
||||
import { useHistory, useLocation, useParams } from "react-router-dom";
|
||||
import useGroupState from "~/logic/state/group";
|
||||
import useInviteState, { useInviteForResource } from "~/logic/state/invite";
|
||||
import useMetadataState, { usePreview } from "~/logic/state/metadata";
|
||||
import { decline, Invite } from "@urbit/api";
|
||||
import { join, JoinRequest } from "@urbit/api/groups";
|
||||
import airlock from "~/logic/api";
|
||||
import { joinError, joinResult, joinLoad, JoinProgress } from "@urbit/api";
|
||||
import { useQuery } from "~/logic/lib/useQuery";
|
||||
import { JoinKind, JoinDesc, JoinSkeleton } from "./Skeleton";
|
||||
} from '@tlon/indigo-react';
|
||||
import { Formik, Form } from 'formik';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import { useInviteForResource } from '~/logic/state/invite';
|
||||
import useMetadataState, { usePreview } from '~/logic/state/metadata';
|
||||
import { decline, Invite } from '@urbit/api';
|
||||
import { join, JoinRequest } from '@urbit/api/groups';
|
||||
import airlock from '~/logic/api';
|
||||
import { joinError, joinLoad, JoinProgress } from '@urbit/api';
|
||||
import { useQuery } from '~/logic/lib/useQuery';
|
||||
import { JoinKind, JoinDesc, JoinSkeleton } from './Skeleton';
|
||||
|
||||
interface InviteWithUid extends Invite {
|
||||
uid: string;
|
||||
@ -42,7 +42,7 @@ function JoinForm(props: {
|
||||
}) {
|
||||
const { desc, dismiss, invite } = props;
|
||||
const onSubmit = (values: FormSchema) => {
|
||||
const [, , ship, name] = desc.group.split("/");
|
||||
const [, , ship, name] = desc.group.split('/');
|
||||
airlock.poke(
|
||||
join(ship, name, desc.kind, values.autojoin, values.shareContact)
|
||||
);
|
||||
@ -52,26 +52,26 @@ function JoinForm(props: {
|
||||
airlock.poke(decline(desc.kind, invite.uid));
|
||||
dismiss();
|
||||
};
|
||||
const isGroups = desc.kind === "groups";
|
||||
const isGroups = desc.kind === 'groups';
|
||||
|
||||
return (
|
||||
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<Form>
|
||||
<Col p="4" gapY="4">
|
||||
<Col p='4' gapY='4'>
|
||||
{isGroups ? (
|
||||
<ManagedCheckboxField id="autojoin" label="Join all channels" />
|
||||
<ManagedCheckboxField id='autojoin' label='Join all channels' />
|
||||
) : null}
|
||||
<ManagedCheckboxField id="shareContact" label="Share identity" />
|
||||
<Row justifyContent="space-between" width="100%">
|
||||
<ManagedCheckboxField id='shareContact' label='Share identity' />
|
||||
<Row justifyContent='space-between' width='100%'>
|
||||
<Button onClick={dismiss}>Dismiss</Button>
|
||||
<Row gapX="2">
|
||||
<Row gapX='2'>
|
||||
{!invite ? null : (
|
||||
<Button onClick={onDecline} destructive type="button">
|
||||
<Button onClick={onDecline} destructive type='button'>
|
||||
Decline
|
||||
</Button>
|
||||
)}
|
||||
<Button primary type="submit">
|
||||
{!invite ? "Join Group" : "Accept"}
|
||||
<Button primary type='submit'>
|
||||
{!invite ? 'Join Group' : 'Accept'}
|
||||
</Button>
|
||||
</Row>
|
||||
</Row>
|
||||
@ -80,10 +80,6 @@ function JoinForm(props: {
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
const REQUEST: JoinDesc = {
|
||||
group: "/ship/~bitbet-bolbel/urbit-community",
|
||||
kind: "groups",
|
||||
};
|
||||
|
||||
export function JoinInitial(props: {
|
||||
invite?: InviteWithUid;
|
||||
@ -93,7 +89,7 @@ export function JoinInitial(props: {
|
||||
}) {
|
||||
const { desc, dismiss, modal, invite } = props;
|
||||
const title = (() => {
|
||||
const name = desc.kind === "graph" ? "Group Chat" : "Group";
|
||||
const name = desc.kind === 'graph' ? 'Group Chat' : 'Group';
|
||||
if (invite) {
|
||||
return `You've been invited to a ${name}`;
|
||||
} else {
|
||||
@ -117,11 +113,11 @@ function JoinLoading(props: {
|
||||
const { desc, request, dismiss, modal, finished } = props;
|
||||
const history = useHistory();
|
||||
useEffect(() => {
|
||||
if (desc.kind === "graph" && request.progress === "done") {
|
||||
if (desc.kind === 'graph' && request.progress === 'done') {
|
||||
history.push(finished);
|
||||
}
|
||||
}, [request]);
|
||||
const name = desc.kind === "graph" ? "Group Chat" : "Group";
|
||||
const name = desc.kind === 'graph' ? 'Group Chat' : 'Group';
|
||||
const title = `Joining ${name}, please wait`;
|
||||
const onCancel = () => {
|
||||
useGroupState.getState().abortJoin(desc.group);
|
||||
@ -129,7 +125,7 @@ function JoinLoading(props: {
|
||||
};
|
||||
return (
|
||||
<JoinSkeleton modal={modal} desc={desc} title={title}>
|
||||
<Col maxWidth="512px" p="4" gapY="4">
|
||||
<Col maxWidth='512px' p='4' gapY='4'>
|
||||
{joinLoad.indexOf(request.progress as any) !== -1 ? (
|
||||
<JoinProgressIndicator progress={request.progress} />
|
||||
) : null}
|
||||
@ -139,7 +135,7 @@ function JoinLoading(props: {
|
||||
offline, or the connection between you both may be unstable.
|
||||
</Text>
|
||||
</Box>
|
||||
<Row gapX="2">
|
||||
<Row gapX='2'>
|
||||
<Button onClick={dismiss}>Dismiss</Button>
|
||||
<Button destructive onClick={onCancel}>
|
||||
Cancel Join
|
||||
@ -160,14 +156,14 @@ function JoinError(props: {
|
||||
const group = preview?.metadata?.title ?? desc.group;
|
||||
const title = `Joining ${group} failed`;
|
||||
const explanation =
|
||||
request.progress === "no-perms"
|
||||
? "You do not have the correct permissions"
|
||||
: "An unexpected error occurred";
|
||||
request.progress === 'no-perms'
|
||||
? 'You do not have the correct permissions'
|
||||
: 'An unexpected error occurred';
|
||||
|
||||
return (
|
||||
<JoinSkeleton modal={modal} title={title} desc={desc}>
|
||||
<Col p="4" gapY="4">
|
||||
<Text fontWeight="medium">{explanation}</Text>
|
||||
<Col p='4' gapY='4'>
|
||||
<Text fontWeight='medium'>{explanation}</Text>
|
||||
<Row>
|
||||
<Button>Dismiss</Button>
|
||||
</Row>
|
||||
@ -186,26 +182,37 @@ export interface JoinProps {
|
||||
export function Join(props: JoinProps) {
|
||||
const { desc, modal, dismiss, redir } = props;
|
||||
const { group, kind } = desc;
|
||||
const [, , ship, name] = group.split("/");
|
||||
const graph = kind === "graph";
|
||||
const finishedPath = !!redir
|
||||
const [, , ship, name] = group.split('/');
|
||||
const graph = kind === 'graph';
|
||||
const associations = useMetadataState(s => s.associations);
|
||||
const joined = graph ? associations.graph[group] : associations.groups[group];
|
||||
const finishedPath = redir
|
||||
? redir
|
||||
: graph
|
||||
? `/~landscape/messages/resource/chat/${ship}/${name}`
|
||||
: `/~landscape/ship/${ship}/${name}`;
|
||||
|
||||
const history = useHistory();
|
||||
const joinRequest = useGroupState((s) => s.pendingJoin[group]);
|
||||
const joinRequest = useGroupState(s => s.pendingJoin[group]);
|
||||
const [openedRequest, setOpenedRequest] = useState<JoinRequest>();
|
||||
const invite = useInviteForResource(kind, ship, name);
|
||||
|
||||
const isDone = joinRequest && joinRequest.progress === "done";
|
||||
const isDone = openedRequest && openedRequest.progress === 'done' && joined;
|
||||
const isErrored =
|
||||
joinRequest && joinError.includes(joinRequest.progress as any);
|
||||
openedRequest && joinError.includes(openedRequest.progress as any);
|
||||
const isLoading =
|
||||
joinRequest && joinLoad.includes(joinRequest.progress as any);
|
||||
openedRequest && joinLoad.includes(openedRequest.progress as any);
|
||||
|
||||
// If we opened this modal from a join request,
|
||||
// don't let the request getting deleted move us to the wrong state
|
||||
useEffect(() => {
|
||||
if (joinRequest) {
|
||||
setOpenedRequest(joinRequest);
|
||||
}
|
||||
}, [joinRequest]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDone && desc.kind == "graph") {
|
||||
if (isDone && desc.kind == 'graph') {
|
||||
history.push(finishedPath);
|
||||
}
|
||||
}, [isDone, desc]);
|
||||
@ -222,20 +229,16 @@ export function Join(props: JoinProps) {
|
||||
modal={modal}
|
||||
dismiss={dismiss}
|
||||
desc={desc}
|
||||
request={joinRequest}
|
||||
request={openedRequest}
|
||||
finished={finishedPath}
|
||||
/>
|
||||
) : isErrored ? (
|
||||
<JoinError modal={modal} desc={desc} request={joinRequest} />
|
||||
<JoinError modal={modal} desc={desc} request={openedRequest} />
|
||||
) : (
|
||||
<JoinInitial modal={modal} dismiss={dismiss} desc={desc} invite={invite} />
|
||||
);
|
||||
}
|
||||
|
||||
interface PromptFormProps {
|
||||
kind: string;
|
||||
}
|
||||
|
||||
interface PromptFormSchema {
|
||||
link: string;
|
||||
}
|
||||
@ -245,37 +248,37 @@ export interface JoinPromptProps {
|
||||
}
|
||||
|
||||
export function JoinPrompt(props: JoinPromptProps) {
|
||||
const { kind, dismiss } = props;
|
||||
const { query, appendQuery } = useQuery();
|
||||
const { dismiss } = props;
|
||||
const { appendQuery } = useQuery();
|
||||
const history = useHistory();
|
||||
const initialValues = {
|
||||
link: "",
|
||||
link: ''
|
||||
};
|
||||
|
||||
const onSubmit = async ({ link }: PromptFormSchema) => {
|
||||
const path = `/ship/${link}`;
|
||||
history.push({
|
||||
search: appendQuery({ "join-path": path }),
|
||||
search: appendQuery({ 'join-path': path })
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<JoinSkeleton modal body={<Text>a</Text>} title="Join a Group">
|
||||
<JoinSkeleton modal body={<Text>a</Text>} title='Join a Group'>
|
||||
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<Form>
|
||||
<Col p="4" gapY="4">
|
||||
<Col p='4' gapY='4'>
|
||||
<ManagedTextInputField
|
||||
label="Invite Link"
|
||||
id="link"
|
||||
caption="Enter either a web+urbitgraph:// link or an identifier in the form ~sampel-palnet/group"
|
||||
label='Invite Link'
|
||||
id='link'
|
||||
caption='Enter either a web+urbitgraph:// link or an identifier in the form ~sampel-palnet/group'
|
||||
/>
|
||||
<Row gapX="2">
|
||||
{!!dismiss ? (
|
||||
<Button type="button" onClick={dismiss}>
|
||||
<Row gapX='2'>
|
||||
{dismiss ? (
|
||||
<Button type='button' onClick={dismiss}>
|
||||
Dismiss
|
||||
</Button>
|
||||
) : null}
|
||||
<Button type="submit" primary>
|
||||
<Button type='submit' primary>
|
||||
Join
|
||||
</Button>
|
||||
</Row>
|
||||
@ -289,26 +292,26 @@ export function JoinPrompt(props: JoinPromptProps) {
|
||||
function JoinProgressIndicator(props: { progress: JoinProgress }) {
|
||||
const { progress } = props;
|
||||
const percentage =
|
||||
progress === "done" ? 100 : (joinLoad.indexOf(progress as any) + 1) * 25;
|
||||
progress === 'done' ? 100 : (joinLoad.indexOf(progress as any) + 1) * 25;
|
||||
|
||||
const description = (() => {
|
||||
switch (progress) {
|
||||
case "start":
|
||||
return "Connecting to host";
|
||||
case "added":
|
||||
return "Retrieving members";
|
||||
case "metadata":
|
||||
return "Retrieving channels";
|
||||
case "done":
|
||||
return "Finished";
|
||||
case 'start':
|
||||
return 'Connecting to host';
|
||||
case 'added':
|
||||
return 'Retrieving members';
|
||||
case 'metadata':
|
||||
return 'Retrieving channels';
|
||||
case 'done':
|
||||
return 'Finished';
|
||||
default:
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<Col gapY="2">
|
||||
<Text color="lightGray">{description}</Text>
|
||||
<Col gapY='2'>
|
||||
<Text color='lightGray'>{description}</Text>
|
||||
<ContinuousProgressBar percentage={percentage} />
|
||||
</Col>
|
||||
);
|
||||
@ -323,8 +326,7 @@ export interface JoinDoneProps {
|
||||
|
||||
export function JoinDone(props: JoinDoneProps) {
|
||||
const { desc, modal, finished, dismiss } = props;
|
||||
const { preview, error } = usePreview(desc.group);
|
||||
const name = desc.kind === "groups" ? "Group" : "Group Chat";
|
||||
const name = desc.kind === 'groups' ? 'Group' : 'Group Chat';
|
||||
const title = `Joined ${name} successfully`;
|
||||
const history = useHistory();
|
||||
|
||||
@ -334,9 +336,9 @@ export function JoinDone(props: JoinDoneProps) {
|
||||
|
||||
return (
|
||||
<JoinSkeleton title={title} modal={modal} desc={desc}>
|
||||
<Col p="4" gapY="4">
|
||||
<JoinProgressIndicator progress="done" />
|
||||
<Row gapX="2">
|
||||
<Col p='4' gapY='4'>
|
||||
<JoinProgressIndicator progress='done' />
|
||||
<Row gapX='2'>
|
||||
<Button onClick={dismiss}>Dismiss</Button>
|
||||
<Button onClick={onView} primary>
|
||||
View Group
|
||||
@ -347,21 +349,46 @@ export function JoinDone(props: JoinDoneProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function JoinRoute(props: { graph?: boolean; modal?: boolean }) {
|
||||
const { modal = false, graph = false } = props;
|
||||
export interface JoinParams extends Record<string, string> {
|
||||
'join-kind': JoinKind;
|
||||
'join-path'?: string;
|
||||
redir?: string;
|
||||
}
|
||||
|
||||
export function createJoinParams(kind: JoinKind, path?: string, redirect?: string, inLink?: true): string;
|
||||
export function createJoinParams(kind: JoinKind, path?: string, redirect?: string, inLink?: false): JoinParams;
|
||||
export function createJoinParams(kind: JoinKind, path?: string, redirect?: string, inLink = true) {
|
||||
const params = {
|
||||
'join-kind': kind
|
||||
};
|
||||
|
||||
if (path) {
|
||||
params['join-path'] = path;
|
||||
}
|
||||
|
||||
if (redirect) {
|
||||
params['redir'] = redirect;
|
||||
}
|
||||
|
||||
return inLink ? '?' + new URLSearchParams(params).toString() : params;
|
||||
}
|
||||
|
||||
export function JoinRoute() {
|
||||
const { query } = useQuery();
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const kind = query.get("join-kind");
|
||||
const path = query.get("join-path");
|
||||
const redir = query.get("redir");
|
||||
const kind = query.get('join-kind');
|
||||
const path = query.get('join-path')?.replace('web+urbitgraph://group/', '');
|
||||
const redir = query.get('redir');
|
||||
|
||||
if (!kind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const desc: JoinDesc = path
|
||||
? {
|
||||
group: path,
|
||||
kind: graph ? "graph" : "groups",
|
||||
kind: kind === 'graph' ? 'graph' : 'groups'
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
@ -221,7 +221,6 @@ export const SidebarAssociationItem = React.memo(
|
||||
mod = association.metadata.config.graph;
|
||||
}
|
||||
const pending = useGroupState(s => association.group in s.pendingJoin);
|
||||
console.log(pending);
|
||||
const rid = association?.resource;
|
||||
const { hideNicknames } = useSettingsState((s) => s.calm);
|
||||
const contacts = useContactState((s) => s.contacts);
|
||||
|
@ -45,7 +45,7 @@ export function SidebarListHeader(props: {
|
||||
|
||||
const metadata = associations?.groups?.[groupPath]?.metadata;
|
||||
const memberMetadata =
|
||||
groupPath ? metadata.vip === 'member-metadata' : false;
|
||||
groupPath && metadata ? metadata.vip === 'member-metadata' : false;
|
||||
|
||||
const isAdmin = memberMetadata || (role === 'admin') || (props.workspace?.type === 'home') || (props.workspace?.type === 'messages');
|
||||
|
||||
|
@ -45,7 +45,9 @@ const makeTheme = (dark: boolean): ITheme => {
|
||||
foreground: fg,
|
||||
background: bg,
|
||||
brightBlack: '#7f7f7f', // NOTE slogs
|
||||
cursor: fg
|
||||
cursor: fg,
|
||||
cursorAccent: bg,
|
||||
selection: fg
|
||||
};
|
||||
};
|
||||
|
||||
@ -66,7 +68,9 @@ const termConfig: ITerminalOptions = {
|
||||
bellSound: bel,
|
||||
//
|
||||
// allows text selection by holding modifier (option, or shift)
|
||||
macOptionClickForcesSelection: true
|
||||
macOptionClickForcesSelection: true,
|
||||
// prevent insertion of simulated arrow keys on-altclick
|
||||
altClickMovesCursor: false
|
||||
};
|
||||
|
||||
const csi = (cmd: string, ...args: number[]) => {
|
||||
|
@ -203,7 +203,8 @@
|
||||
?> =(1 ~(wyt by nodes))
|
||||
=/ ship-screen (~(get ju screened) src.bowl)
|
||||
=. ship-screen (~(uni in ship-screen) (normalize-incoming nodes))
|
||||
:_ state(screened (~(put by screened) src.bowl ship-screen))
|
||||
=. screened (~(put by screened) src.bowl ship-screen)
|
||||
:_ state
|
||||
=/ =action:hook
|
||||
[%pendings ~(key by screened)]
|
||||
:- (fact:io dm-hook-action+!>(action) ~[/updates])
|
||||
|
@ -219,11 +219,12 @@
|
||||
?. allowed
|
||||
~
|
||||
`vas
|
||||
::
|
||||
%add-signatures ``vas
|
||||
%remove-signatures ``vas
|
||||
::
|
||||
%add-graph [~ ~]
|
||||
%remove-graph [~ ~]
|
||||
%add-signatures [~ ~]
|
||||
%remove-signatures [~ ~]
|
||||
%archive-graph [~ ~]
|
||||
%unarchive-graph [~ ~]
|
||||
%add-tag [~ ~]
|
||||
|
@ -326,7 +326,7 @@
|
||||
:_ this
|
||||
%+ turn ~(tap by associations)
|
||||
|= [=md-resource:metadata =association:metadata]
|
||||
%+ poke-our:pass:io %metadata-store
|
||||
%+ poke-our:pass:io:hc %metadata-store
|
||||
:- %metadata-update-2
|
||||
!> ^- update:metadata
|
||||
[%remove resource md-resource]
|
||||
|
@ -1,6 +1,6 @@
|
||||
:: metadata-push-hook [landscape]:
|
||||
::
|
||||
/- *group, *invite-store, store=metadata-store
|
||||
/- *group, *invite-store, store=metadata-store, group-store
|
||||
/+ default-agent, verb, dbug, grpl=group, push-hook,
|
||||
resource, mdl=metadata, gral=graph, agentio
|
||||
~% %group-hook-top ..part ~
|
||||
@ -29,6 +29,14 @@
|
||||
--
|
||||
::
|
||||
::
|
||||
=+
|
||||
^= hook-core
|
||||
|_ =bowl:gall
|
||||
+* io ~(. agentio bowl)
|
||||
pass pass:io
|
||||
++ watch-groups (~(watch-our pass /groups) %group-store /groups)
|
||||
--
|
||||
::
|
||||
=| state-zero
|
||||
=* state -
|
||||
%- agent:dbug
|
||||
@ -43,11 +51,20 @@
|
||||
met ~(. mdl bowl)
|
||||
gra ~(. gral bowl)
|
||||
io ~(. agentio bowl)
|
||||
hc ~(. hook-core bowl)
|
||||
pass pass:io
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(~)
|
||||
++ on-load on-load:def
|
||||
++ on-init
|
||||
:_ this
|
||||
~[watch-groups:hc]
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= =vase
|
||||
=+ !<(old=versioned-state vase)
|
||||
?: ?=([%0 ~] old) `this
|
||||
:_ this
|
||||
~[watch-groups:hc]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -82,7 +99,44 @@
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
?. ?=([%groups ~] wire)
|
||||
(on-agent:def wire sign)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%kick :_(this ~[watch-groups:hc])
|
||||
::
|
||||
%fact
|
||||
?. =(p.cage.sign %group-update-0) `this
|
||||
=+ !<(=update:group-store q.cage.sign)
|
||||
?. ?=(%remove-members -.update) `this
|
||||
|^
|
||||
=/ graphs=(set resource)
|
||||
(hosting-graphs resource.update)
|
||||
:_ this
|
||||
%+ weld
|
||||
(turn ~(tap in graphs) (cury revoke %graph-push-hook))
|
||||
?. =(entity.resource.update our.bowl) ~
|
||||
(revoke %metadata-push-hook resource.update)^~
|
||||
::
|
||||
++ revoke
|
||||
|= [=dude:gall rid=resource]
|
||||
=/ =action:push-hook [%revoke ships.update rid]
|
||||
=/ =cage push-hook-action+!>(action)
|
||||
(poke-our:pass dude cage)
|
||||
::
|
||||
++ hosting-graphs
|
||||
|= rid=resource
|
||||
^- (set resource)
|
||||
=/ graphs=associations:store
|
||||
(app-metadata-for-group:met resource.update %graph)
|
||||
%- ~(gas in *(set resource))
|
||||
%+ murn ~(tap in ~(key by graphs))
|
||||
|= [app=term graph=resource]
|
||||
?. =(our.bowl entity.graph) ~
|
||||
`graph
|
||||
--
|
||||
==
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
|
@ -1,10 +1,10 @@
|
||||
:~ title+'Groups'
|
||||
info+'A suite of applications to communicate on Urbit'
|
||||
color+0xee.5432
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v3.m2nd4.9tg9d.vs9ls.9rj6u.7lqhg.glob' 0v3.m2nd4.9tg9d.vs9ls.9rj6u.7lqhg]
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v2.2tc97.h3e0k.7b26d.a0ma8.em5ce.glob' 0v2.2tc97.h3e0k.7b26d.a0ma8.em5ce]
|
||||
|
||||
base+'landscape'
|
||||
version+[1 0 4]
|
||||
version+[1 0 6]
|
||||
website+'https://tlon.io'
|
||||
license+'MIT'
|
||||
==
|
||||
|
@ -432,7 +432,7 @@
|
||||
::
|
||||
++ tr-emis
|
||||
|= caz=(list card)
|
||||
tr-core(cards (welp (flop cards) cards))
|
||||
tr-core(cards (welp (flop caz) cards))
|
||||
::
|
||||
++ tr-ap-og
|
||||
|= ap=_^?(|.(*(quip card _pull-hook)))
|
||||
|
@ -423,11 +423,14 @@
|
||||
::
|
||||
++ revoke
|
||||
|= [ships=(set ship) rid=resource]
|
||||
=/ pax=path
|
||||
=/ ver-pax=path
|
||||
[%resource %ver (en-path:resource rid)]
|
||||
=/ unver-pax=path
|
||||
[%resource (en-path:resource rid)]
|
||||
:_ state
|
||||
%+ murn
|
||||
(incoming-subscriptions pax)
|
||||
%+ welp (incoming-subscriptions unver-pax)
|
||||
(incoming-subscriptions ver-pax)
|
||||
|= [her=ship =path]
|
||||
^- (unit card)
|
||||
?. (~(has in ships) her)
|
||||
|
@ -21,7 +21,7 @@
|
||||
|= vip=vip-metadata:met
|
||||
^- permissions:graph
|
||||
?+ index.p.i !!
|
||||
[@ ~] [%self %self %no]
|
||||
[@ ~] [%yes %self %no]
|
||||
==
|
||||
::
|
||||
++ notification-kind
|
||||
|
@ -22,9 +22,20 @@
|
||||
:- %pull-hook-action
|
||||
!> ^- action:pull-hook
|
||||
[%remove rid]
|
||||
;< ~ bind:m (raw-poke-our %contact-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (raw-poke-our %metadata-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (raw-poke-our %group-pull-hook pull-hook-act)
|
||||
;< ~ bind:m (raw-poke-our %group-store %group-update-0 !>([%remove-group rid ~]))
|
||||
;< ~ bind:m (cleanup-md:view rid)
|
||||
=/ leave=cage
|
||||
:- %group-update-0
|
||||
!> ^- update:store
|
||||
[%remove-members rid (silt our.bowl ~)]
|
||||
=/ remove=cage
|
||||
:- %group-update-0
|
||||
!> ^- update:store
|
||||
[%remove-group rid ~]
|
||||
;< ~ bind:m
|
||||
(raw-poke-our %group-push-hook leave)
|
||||
;< ~ bind:m
|
||||
(raw-poke-our %group-pull-hook pull-hook-act)
|
||||
;< ~ bind:m
|
||||
(raw-poke-our %contact-pull-hook pull-hook-act)
|
||||
;< ~ bind:m
|
||||
(raw-poke-our %group-store remove)
|
||||
(pure:m !>(~))
|
||||
|
@ -1,9 +1,9 @@
|
||||
:~ title+'Terminal'
|
||||
info+'A web interface to your Urbit\'s command line.'
|
||||
color+0x2e.4347
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v1.fgmgl.utdgt.kdu3r.4e5f9.v58rk.glob' 0v1.fgmgl.utdgt.kdu3r.4e5f9.v58rk]
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v7.1hgb7.euged.6oj3e.cdhdg.rah02.glob' 0v7.1hgb7.euged.6oj3e.cdhdg.rah02]
|
||||
base+'webterm'
|
||||
version+[1 0 0]
|
||||
version+[1 0 1]
|
||||
website+'https://tlon.io'
|
||||
license+'MIT'
|
||||
==
|
||||
|
Loading…
Reference in New Issue
Block a user