Merge branch 'master' into release/next-vere

This commit is contained in:
pilfer-pandex 2020-08-06 12:08:43 -07:00
commit effdd30d54
89 changed files with 3247 additions and 817 deletions

View File

@ -30,7 +30,7 @@ If applicable, add screenshots to help explain your problem.
**System (please supply the following information, if relevant):**
- OS: [e.g. macOS, linux64, FreeBSD]
- Vere and Urbit OS versions
- Your ship's `%base` hash (use `.^(@uv %cz /=base=)` to check)
- Your ship's `%base` hash (use `+trouble` to check)
**Additional context**
Add any other context about the problem here.

View File

@ -27,13 +27,13 @@ If applicable, add screenshots to help explain your problem. If possible, please
**Desktop (please complete the following information):**
- OS: [e.g. MacOS 10.15.3]
- Browser [e.g. chrome, safari]
- Base hash of your urbit ship. Run ` .^(@uv %cz /=base=)` in Dojo to see this.
- Base hash of your urbit ship. Run `+trouble` in Dojo to see this.
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Base hash of your urbit ship. Run ` .^(@uv %cz /=base=)` in Dojo to see this.
- Base hash of your urbit ship. Run `+trouble` in Dojo to see this.
**Additional context**
Add any other context about the problem here.

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ecf3f8593815742e409008421f318b664124e672b1eecd131e4a1e49864a1c2a
size 6175676
oid sha256:d7088528dbfd54a913921ade093251d678c4ccebfd0ad85ef2022520266b3954
size 16451173

View File

@ -72,7 +72,7 @@
+$ glyph char
++ glyphs "!@#$%^&()-=_+[]\{}'\\:\",.<>?"
::
+$ nu-security ?(%channel %village %village-with-group)
+$ nu-security ?(%channel %village)
::
+$ command
$% [%target (set target)] :: set messaging target
@ -81,7 +81,7 @@
::
::
:: create chat
[%create nu-security path (unit glyph) (unit ?)]
[%create nu-security path (unit resource) (unit glyph) (unit ?)]
[%delete path] :: delete chat
[%invite [? path] (set ship)] :: allow
[%banish [? path] (set ship)] :: disallow
@ -293,8 +293,6 @@
::
++ target-to-path
|= target
%+ weld
?:(in-group ~ /~)
[(scot %p ship) path]
:: +path-to-target: deduces a target from a mailbox path
::
@ -464,6 +462,7 @@
security
;~ plug
path
(punt ;~(pfix ace group))
(punt ;~(pfix ace glyph))
(punt ;~(pfix ace (fuss 'y' 'n')))
==
@ -535,16 +534,15 @@
:: ;~(pfix ace ;~(plug i.opt $(opt t.opt)))
:: --
::
++ group ;~((glue net) ship sym)
++ tag |*(a=@tas (cold a (jest a))) ::TODO into stdlib
++ ship ;~(pfix sig fed:ag)
++ path ;~(pfix net ;~(plug urs:ab (easy ~))) ::NOTE short only, tmp
:: +mang: un/managed indicator prefix
::
++ mang
;~ pose
(cold %| (jest '~/'))
(cold %& (easy ~))
==
:: deprecated, as sig prefix is no longer used
::
++ mang (cold %& (easy ~))
:: +tarl: local target, as /path
::
++ tarl (stag our-self path)
@ -585,7 +583,7 @@
:: +security: security mode
::
++ security
(perk %channel %village-with-group %village ~)
(perk %channel %village ~)
::
:: +glyph: shorthand character
::
@ -741,15 +739,21 @@
:: +create: new local mailbox
::
++ create
|= [security=nu-security =path gyf=(unit char) allow-history=(unit ?)]
|= $: security=nu-security
=path
ugroup=(unit resource)
gyf=(unit char)
allow-history=(unit ?)
==
^- (quip card _state)
=/ with-group=? ?=(%village-with-group security)
=/ with-group=? ?=(^ ugroup)
=/ =target [with-group our-self path]
=/ real-path=^path (target-to-path target)
=/ group-path=^path ?~(ugroup ship+real-path (en-path:resource u.ugroup))
=/ =policy
?- security
%channel *open:policy
?(%village %village-with-group) *invite:policy
%channel *open:policy
%village *invite:policy
==
?^ (scry-for (unit mailbox:store) %chat-store [%mailbox real-path])
=- [[- ~] state]
@ -767,7 +771,7 @@
(rsh 3 1 (spat path))
''
real-path :: chat
real-path :: group
group-path :: group
policy
~
(fall allow-history %.y)

View File

@ -18,17 +18,19 @@
state-1
state-2
state-3
state-4
state-5
state-6
state-7
==
::
+$ state-3
$: %3
state-base
==
+$ state-7 [%7 state-base]
+$ state-6 [%6 state-base]
+$ state-5 [%5 state-base]
+$ state-4 [%4 state-base]
+$ state-3 [%3 state-base]
+$ state-2 [%2 state-base]
::
+$ state-2
$: %2
state-base
==
+$ state-1
$: %1
loaded-cards=*
@ -52,7 +54,7 @@
$% [%chat-update update:store]
==
--
=| state-3
=| state-7
=* state -
::
%- agent:dbug
@ -81,8 +83,20 @@
=/ old !<(versioned-state old-vase)
=| cards=(list card)
|-
?: ?=(%3 -.old)
?: ?=(%7 -.old)
[cards this(state old)]
?: ?=(%6 -.old)
=. cards
%+ weld cards
^- (list card)
[%pass /s %agent [our.bol %chat-hook] %poke %noun !>(%fix-out-of-sync)]~
$(-.old %7)
?: ?=(?(%3 %4 %5) -.old)
=. cards
%+ weld cards
^- (list card)
[%pass /pokeme %agent [our.bol %chat-hook] %poke %noun !>(%fix-dm)]~
$(-.old %6)
?: ?=(%2 -.old)
=. cards
%+ weld cards
@ -319,9 +333,9 @@
^- (quip card _this)
=^ cards state
?+ mark (on-poke:def mark vase)
%json (poke-json:cc !<(json vase))
%chat-action (poke-chat-action:cc !<(action:store vase))
%noun [~ state]
%json (poke-json:cc !<(json vase))
%chat-action (poke-chat-action:cc !<(action:store vase))
%noun (poke-noun:cc !<(?(%fix-dm %fix-out-of-sync) vase))
::
%chat-hook-action
(poke-chat-hook-action:cc !<(action:hook vase))
@ -383,6 +397,81 @@
|_ bol=bowl:gall
++ grp ~(. grpl bol)
::
++ poke-noun
|= a=?(%fix-dm %fix-out-of-sync)
^- (quip card _state)
|^
:_ state
?- a
%fix-dm (fix-dm %fix-dm)
%fix-out-of-sync (fix-out-of-sync %fix-out-of-sync)
==
::
++ fix-out-of-sync
|= b=%fix-out-of-sync
^- (list card)
%- zing
%+ turn ~(tap by synced)
|= [=path host=ship]
^- (list card)
?: =(host our.bol) ~
?> ?=([@ @ ~] path)
=/ =ship (slav %p i.path)
:~ =- [%pass / %agent [our.bol %chat-hook] %poke %chat-hook-action -]
!> ^- action:hook
[%remove path]
::
=- [%pass / %agent [our.bol %chat-hook] %poke %chat-hook-action -]
!> ^- action:hook
[%add-synced ship path %.y]
==
::
++ fix-dm
|= b=%fix-dm
^- (list card)
%- zing
%+ turn
~(tap by synced)
|= [=path host=ship]
^- (list card)
?> ?=([@ @ *] path)
=/ =ship (slav %p i.path)
?: =(ship our.bol)
:: local dm, no need to do cleanup
~
?: ?=(^ (groups-of-chat path))
:: correctly initialized, no need to do cleanup
::
~
?. =((end 3 4 i.t.path) 'dm--')
~
:- =- [%pass /fixdm %agent [our.bol %chat-view] %poke %chat-view-action -]
!> ^- action:view
[%delete path]
=/ new-dm /(scot %p our.bol)/(crip (weld "dm--" (trip (scot %p ship))))
=/ mailbox=(unit mailbox:store) (chat-scry path)
?~ mailbox
~
:~ =- [%pass /fixdm %agent [our.bol %chat-view] %poke %chat-view-action -]
!> ^- action:view
:* %create
%- crip
(zing [(trip (scot %p our.bol)) " <-> " (trip (scot %p ship)) ~])
''
new-dm
ship+new-dm
[%invite (silt ~[ship])]
(silt ~[ship])
%.y
%.n
==
::
=- [%pass /fixdm %agent [our.bol %chat-store] %poke %chat-action -]
!> ^- action:store
[%messages new-dm envelopes.u.mailbox]
==
--
::
++ poke-json
|= jon=json
^- (quip card _state)

View File

@ -269,7 +269,7 @@
==
::
++ on-leave on-leave:def
++ on-peek
++ on-peek
|= =path
^- (unit (unit cage))
|^
@ -287,10 +287,9 @@
*@uv
=/ parent (scot %p ship.u.ota)
=+ .^(=cass:clay %cs /[parent]/[desk.u.ota]/1/late/foo)
%^ end 3 3
%^ end 0 25
.^(@uv %cz /[parent]/[desk.u.ota]/(scot %ud ud.cass))
--
++ on-agent on-agent:def
++ on-fail on-fail:def
--

View File

@ -1,7 +1,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v5.knd3c.vvtvt.h0gg0.8qcau.8iii4
++ hash 0v3.cus8h.vc64c.rfb3t.22oji.b529a
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0
@ -58,7 +58,6 @@
++ on-load
|= old-state=vase
^- (quip card _this)
~& > %initting
=+ !<(old=all-states old-state)
?> ?=(%0 -.old)
?~ glob.old

View File

@ -0,0 +1,572 @@
/+ store=graph-store, sigs=signatures, res=resource, default-agent, dbug
~% %graph-store-top ..is ~
|%
+$ card card:agent:gall
+$ versioned-state
$% state-0
==
::
+$ state-0 [%0 network:store]
++ orm orm:store
++ orm-log orm-log:store
--
::
=| state-0
=* state -
::
%- agent:dbug
^- agent:gall
~% %graph-store-agent ..card ~
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
::
++ on-init [~ this]
++ on-save !>(state)
++ on-load
|= old=vase
^- (quip card _this)
[~ this(state !<(state-0 old))]
::
++ on-watch
~/ %graph-store-watch
|= =path
^- (quip card _this)
|^
?> (team:title our.bowl src.bowl)
=/ cards=(list card)
?+ path (on-watch:def path)
[%updates ~] ~
[%keys ~] (give [%keys ~(key by graphs)])
[%tags ~] (give [%tags ~(key by tag-queries)])
==
[cards this]
::
++ give
|= =update-0:store
^- (list card)
[%give %fact ~ [%graph-update !>([%0 now.bowl update-0])]]~
--
::
++ on-poke
~/ %graph-store-poke
|= [=mark =vase]
^- (quip card _this)
|^
?> (team:title our.bowl src.bowl)
=^ cards state
?+ mark (on-poke:def mark vase)
%graph-update (graph-update !<(update:store vase))
==
[cards this]
::
++ graph-update
|= =update:store
^- (quip card _state)
|^
?> ?=(%0 -.update)
?- -.q.update
%add-graph (add-graph +.q.update)
%remove-graph (remove-graph +.q.update)
%add-nodes (add-nodes p.update +.q.update)
%remove-nodes (remove-nodes p.update +.q.update)
%add-signatures (add-signatures p.update +.q.update)
%remove-signatures (remove-signatures p.update +.q.update)
%add-tag (add-tag +.q.update)
%remove-tag (remove-tag +.q.update)
%archive-graph (archive-graph +.q.update)
%unarchive-graph (unarchive-graph +.q.update)
%run-updates (run-updates +.q.update)
%keys ~|('cannot send %keys as poke' !!)
%tags ~|('cannot send %tags as poke' !!)
%tag-queries ~|('cannot send %tag-queries as poke' !!)
==
::
++ add-graph
|= [=resource:store =graph:store mark=(unit mark:store)]
^- (quip card _state)
?< (~(has by archive) resource)
?< (~(has by graphs) resource)
?> (validate-graph graph mark)
:_ %_ state
graphs (~(put by graphs) resource [graph mark])
update-logs (~(put by update-logs) resource (gas:orm-log ~ ~))
validators
?~ mark validators
(~(put in validators) u.mark)
==
%- zing
:~ (give [/updates /keys ~] [%add-graph resource graph mark])
?~ mark ~
?: (~(has in validators) u.mark) ~
=/ wire (weld /graph (en-path:res resource))
=/ =rave:clay [%sing %b [%da now.bowl] /[u.mark]]
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
==
::
++ remove-graph
|= =resource:store
^- (quip card _state)
?< (~(has by archive) resource)
?> (~(has by graphs) resource)
:- (give [/updates /keys ~] [%remove-graph resource])
%_ state
graphs (~(del by graphs) resource)
update-logs (~(del by update-logs) resource)
==
::
++ add-nodes
|= $: =time
=resource:store
nodes=(map index:store node:store)
==
^- (quip card _state)
|^
=/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [%0 time [%add-nodes resource nodes]])
::
:- (give [/updates]~ [%add-nodes resource nodes])
%_ state
update-logs (~(put by update-logs) resource update-log)
graphs
%+ ~(put by graphs)
resource
:_ mark
(add-node-list resource graph mark (sort-nodes nodes))
==
::
++ sort-nodes
|= nodes=(map index:store node:store)
^- (list [index:store node:store])
%+ sort ~(tap by nodes)
|= [p=[=index:store *] q=[=index:store *]]
^- ?
(lth (lent index.p) (lent index.q))
::
++ add-node-list
|= $: =resource:store
=graph:store
mark=(unit mark:store)
node-list=(list [index:store node:store])
==
^- graph:store
?~ node-list graph
=* index -.i.node-list
=* node +.i.node-list
%_ $
node-list t.node-list
graph (add-node-at-index graph index node mark)
==
::
++ add-node-at-index
=| parent-hash=(unit hash:store)
|= $: =graph:store
=index:store
=node:store
mark=(unit mark:store)
==
^- graph:store
?< ?=(~ index)
~| "validation of node failed using mark {<mark>}"
?> (validate-graph (gas:orm ~ [i.index node]~) mark)
=* atom i.index
%^ put:orm
graph
atom
:: add child
::
?~ t.index
=* p post.node
=/ =validated-portion:store
[parent-hash author.p time-sent.p contents.p]
=/ =hash:store `@ux`(sham validated-portion)
?~ hash.p node(signatures.post *signatures:store)
~| "signatures do not match the calculated hash"
?> (are-signatures-valid:sigs signatures.p hash now.bowl)
~| "hash of post does not match calculated hash"
?> =(hash u.hash.p)
node
:: recurse children
::
=/ parent=node:store
~| "index does not exist to add a node to!"
(need (get:orm graph atom))
%_ parent
children
^- internal-graph:store
:- %graph
%_ $
index t.index
parent-hash hash.post.parent
graph
?: ?=(%graph -.children.parent)
p.children.parent
(gas:orm ~ ~)
==
==
--
::
++ remove-nodes
|= [=time =resource:store indices=(set index:store)]
^- (quip card _state)
|^
=/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [%0 time [%remove-nodes resource indices]])
::
:- (give [/updates]~ [%remove-nodes resource indices])
%_ state
update-logs (~(put by update-logs) resource update-log)
graphs
%+ ~(put by graphs)
resource
[(remove-indices resource graph ~(tap in indices)) mark]
==
::
++ remove-indices
|= [=resource:store =graph:store indices=(list index:store)]
^- graph:store
?~ indices graph
%_ $
indices t.indices
graph (remove-index graph i.indices)
==
::
++ remove-index
|= [=graph:store =index:store]
^- graph:store
?~ index graph
=* atom i.index
:: last index in list
::
?~ t.index
+:`[* graph:store]`(del:orm graph atom)
=/ =node:store
~| "parent index does not exist to remove a node from!"
(need (get:orm graph atom))
~| "child index does not exist to remove a node from!"
?> ?=(%graph -.children.node)
%^ put:orm
graph
atom
node(p.children $(graph p.children.node, index t.index))
--
::
++ add-signatures
|= [=time =uid:store =signatures:store]
^- (quip card _state)
|^
=* resource resource.uid
=/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [%0 time [%add-signatures uid signatures]])
::
:- (give [/updates]~ [%add-signatures uid signatures])
%_ state
update-logs (~(put by update-logs) resource update-log)
graphs
%+ ~(put by graphs) resource
[(add-at-index graph index.uid signatures) mark]
==
::
++ add-at-index
|= [=graph:store =index:store =signatures:store]
^- graph:store
?~ index graph
=* atom i.index
=/ =node:store
~| "node does not exist to add signatures to!"
(need (get:orm graph atom))
:: last index in list
::
%^ put:orm
graph
atom
?~ t.index
~| "cannot add signatures to a node missing a hash"
?> ?=(^ hash.post.node)
~| "signatures did not match public keys!"
?> (are-signatures-valid:sigs signatures u.hash.post.node now.bowl)
node(signatures.post (~(uni in signatures) signatures.post.node))
~| "child graph does not exist to add signatures to!"
?> ?=(%graph -.children.node)
node(p.children $(graph p.children.node, index t.index))
--
::
++ remove-signatures
|= [=time =uid:store =signatures:store]
^- (quip card _state)
|^
=* resource resource.uid
=/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource)
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
%^ put:orm-log update-log
time
[%0 time [%remove-signatures uid signatures]]
::
:- (give [/updates]~ [%remove-signatures uid signatures])
%_ state
update-logs (~(put by update-logs) resource update-log)
graphs
%+ ~(put by graphs) resource
[(remove-at-index graph index.uid signatures) mark]
==
::
++ remove-at-index
|= [=graph:store =index:store =signatures:store]
^- graph:store
?~ index graph
=* atom i.index
=/ =node:store
~| "node does not exist to add signatures to!"
(need (get:orm graph atom))
:: last index in list
::
%^ put:orm
graph
atom
?~ t.index
node(signatures.post (~(dif in signatures) signatures.post.node))
~| "child graph does not exist to add signatures to!"
?> ?=(%graph -.children.node)
node(p.children $(graph p.children.node, index t.index))
--
::
++ add-tag
|= [=term =resource:store]
^- (quip card _state)
?> (~(has by graphs) resource)
:- (give [/updates /tags ~] [%add-tag term resource])
%_ state
tag-queries (~(put ju tag-queries) term resource)
==
::
++ remove-tag
|= [=term =resource:store]
^- (quip card _state)
?> (~(has by graphs) resource)
:- (give [/updates /tags ~] [%remove-tag term resource])
%_ state
tag-queries (~(del ju tag-queries) term resource)
==
::
++ archive-graph
|= =resource:store
^- (quip card _state)
?< (~(has by archive) resource)
?> (~(has by graphs) resource)
:- (give [/updates /keys /tags ~] [%archive-graph resource])
%_ state
archive (~(put by archive) resource (~(got by graphs) resource))
graphs (~(del by graphs) resource)
update-logs (~(del by update-logs) resource)
tag-queries
%- ~(run by tag-queries)
|= =resources:store
(~(del in resources) resource)
==
::
++ unarchive-graph
|= =resource:store
^- (quip card _state)
?> (~(has by archive) resource)
?< (~(has by graphs) resource)
:- (give [/updates /keys ~] [%unarchive-graph resource])
%_ state
archive (~(del by archive) resource)
graphs (~(put by graphs) resource (~(got by archive) resource))
update-logs (~(put by update-logs) resource (gas:orm-log ~ ~))
==
::
++ run-updates
|= [=resource:store =update-log:store]
^- (quip card _state)
?< (~(has by archive) resource)
?> (~(has by graphs) resource)
:_ state
%+ turn (tap:orm-log update-log)
|= [=time update=logged-update:store]
^- card
?> ?=(%0 -.update)
:* %pass
/run-updates/(scot %da time)
%agent
[our.bowl %graph-store]
%poke
:- %graph-update
!>
^- update:store
?- -.q.update
%add-nodes update(resource.q resource)
%remove-nodes update(resource.q resource)
%add-signatures update(resource.uid.q resource)
%remove-signatures update(resource.uid.q resource)
==
==
::
++ validate-graph
|= [=graph:store mark=(unit mark:store)]
^- ?
?~ mark %.y
?~ graph %.y
=/ =dais:clay
.^ =dais:clay
%cb
/(scot %p our.bowl)/[q.byk.bowl]/(scot %da now.bowl)/[u.mark]
==
%+ roll (tap:orm graph)
|= [[=atom =node:store] out=?]
?& out
=(%& -:(mule |.((vale:dais [atom post.node]))))
?- -.children.node
%empty %.y
%graph ^$(graph p.children.node)
==
==
::
++ give
|= [paths=(list path) update=update-0:store]
^- (list card)
[%give %fact paths [%graph-update !>([%0 now.bowl update])]]~
--
--
::
++ on-peek
~/ %graph-store-peek
|= =path
^- (unit (unit cage))
|^
?> (team:title our.bowl src.bowl)
?+ path (on-peek:def path)
[%x %keys ~] ``noun+!>(~(key by graphs))
[%x %tags ~] ``noun+!>(~(key by tag-queries))
[%x %tag-queries ~] ``noun+!>(tag-queries)
[%x %graph @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ result=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ result [~ ~]
``noun+!>(u.result)
::
[%x %graph-subset @ @ @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ start=(unit atom) (rush i.t.t.t.t.path dem:ag)
=/ end=(unit atom) (rush i.t.t.t.t.t.path dem:ag)
=/ graph=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ graph [~ ~]
``noun+!>(`graph:store`(subset:orm p.u.graph start end))
::
[%x %node @ @ @ *]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ =index:store
(turn t.t.t.t.path |=(=cord (slav %ud cord)))
=/ node=(unit node:store) (get-node ship term index)
?~ node [~ ~]
``noun+!>(u.node)
::
[%x %post @ @ @ *]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ =index:store
(turn t.t.t.t.path |=(=cord (slav %ud cord)))
=/ node=(unit node:store) (get-node ship term index)
?~ node [~ ~]
``noun+!>(post.u.node)
::
[%x %node-children @ @ @ *]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ =index:store
(turn t.t.t.t.path |=(=cord (slav %ud cord)))
=/ node=(unit node:store) (get-node ship term index)
?~ node [~ ~]
?- -.children.u.node
%empty [~ ~]
%graph ``noun+!>(p.children.u.node)
==
::
[%x %node-children-subset @ @ @ @ @ *]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ start=(unit atom) (rush i.t.t.t.t.path dem:ag)
=/ end=(unit atom) (rush i.t.t.t.t.t.path dem:ag)
=/ =index:store
(turn t.t.t.t.t.t.path |=(=cord (slav %ud cord)))
=/ node=(unit node:store) (get-node ship term index)
?~ node [~ ~]
?- -.children.u.node
%empty [~ ~]
%graph ``noun+!>(`graph:store`(subset:orm p.children.u.node start end))
==
::
[%x %update-log @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term])
?~ update-log [~ ~]
``noun+!>(u.update-log)
::
[%x %peek-update-log @ @ ~]
=/ =ship (slav %p i.t.t.path)
=/ =term i.t.t.t.path
=/ update-log=(unit update-log:store) (~(get by update-logs) [ship term])
?~ update-log [~ ~]
=/ result=(unit [time update:store])
(peek:orm-log:store u.update-log)
?~ result [~ ~]
``noun+!>([~ -.u.result])
==
::
++ get-node
|= [=ship =term =index:store]
^- (unit node:store)
=/ parent-graph=(unit marked-graph:store)
(~(get by graphs) [ship term])
?~ parent-graph ~
=/ node=(unit node:store) ~
=/ =graph:store p.u.parent-graph
|-
?~ index
node
?~ t.index
(get:orm graph i.index)
=. node (get:orm graph i.index)
?~ node ~
?- -.children.u.node
%empty ~
%graph $(graph p.children.u.node, index t.index)
==
--
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?+ -.sign-arvo (on-arvo:def wire sign-arvo)
%c
:_ this
?> ?=([%graph @ *] wire)
=/ =resource:store (de-path:res t.wire)
=/ gra=(unit marked-graph:store) (~(get by graphs) resource)
?~ gra ~
?~ q.u.gra ~
=/ =rave:clay [%next %b [%da now.bowl] /[u.q.u.gra]]
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
==
::
++ on-agent on-agent:def
++ on-leave on-leave:def
++ on-fail on-fail:def
--

View File

@ -2,22 +2,16 @@
/+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|%
+$ state
$: %8
drum=state:drum
helm=state:helm
kiln=state:kiln
==
+$ state-7
$: %7
$: %9
drum=state:drum
helm=state:helm
kiln=state:kiln
==
+$ any-state
$% state
state-7
[ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
[%7 drum=state:drum helm=state:helm kiln=state:kiln]
[%8 drum=state:drum helm=state:helm kiln=state:kiln]
==
+$ any-state-tuple
$: drum=any-state:drum

View File

@ -85,15 +85,16 @@
old [%2 +.old]
::
cards
%+ turn
%+ murn
~(tap in ~(key by group-indices.old))
|= =group-path
^- card
=/ rid=resource
(de-path:resource group-path)
?: =(our.bowl entity.rid)
(poke-md-hook %add-owned group-path)
(poke-md-hook %add-synced entity.rid group-path)
^- (unit card)
=/ rid=(unit resource)
(de-path-soft:resource group-path)
?~ rid ~
?: =(our.bowl entity.u.rid)
`(poke-md-hook %add-owned group-path)
`(poke-md-hook %add-synced entity.u.rid group-path)
==
=/ new-state=state-one
%* . *state-one
@ -254,6 +255,11 @@
=/ =group-path (stab (slav %t i.t.t.path))
=/ =md-resource [`@tas`i.t.t.t.path (stab (slav %t i.t.t.t.t.path))]
``noun+!>((~(get by associations) [group-path md-resource]))
::
[%x %resource @ *]
=/ app=@tas i.t.t.path
=/ app-path=^path t.t.t.path
``noun+!>((~(get by resource-indices) app app-path))
==
::
++ on-agent on-agent:def

View File

@ -54,6 +54,7 @@
[%3 state-three]
[%4 state-three]
[%5 state-three]
[%6 state-three]
==
::
+$ metadata-delta
@ -69,7 +70,7 @@
==
--
::
=| [%5 state-three]
=| [%6 state-three]
=* state -
%- agent:dbug
%+ verb |
@ -86,7 +87,6 @@
:_ this
:~ [%pass /view-bind %arvo %e %connect [~ /'publish-view'] %publish]
[%pass /read/paths %arvo %c %warp our.bol q.byk.bol `rav]
[%pass /permissions %agent [our.bol %permission-store] %watch /updates]
(invite-poke:main [%create /publish])
:* %pass /invites %agent [our.bol %invite-store] %watch
/invitatory/publish
@ -218,6 +218,26 @@
==
::
%5
%= $
-.p.old-state %6
cards
%+ weld cards
%+ roll ~(tap by books.p.old-state)
|= [[[who=@p book=@tas] nb=notebook] out=(list card)]
^- (list card)
?. =(who our.bol)
out
=/ rid (de-path:resource writers.nb)
=/ grp=(unit group) (scry-group:grup:main rid)
?~ grp out
?: hidden.u.grp
out
=/ =tag [%publish (cat 3 'writers-' book)]
:_ out
(group-proxy-poke entity.rid %add-tag rid tag members.u.grp)
==
::
%6
[cards this(state p.old-state)]
==
++ convert-notebook-3-4
@ -995,6 +1015,22 @@
[~ state]
:_ state
%- zing
:- ^- (list card)
%+ roll ~(tap by books)
|= [[[who=@p book=@tas] nb=notebook] out=(list card)]
^- (list card)
?. =(who our.bol)
out
?. =(writers.nb path)
out
=/ rid (de-path:resource writers.nb)
=/ grp=(unit group) (scry-group:grup rid)
?~ grp out
?: hidden.u.grp
out
=/ =tag [%publish (cat 3 'writers-' book)]
:_ out
(group-proxy-poke entity.rid %add-tag rid tag members.u.grp)
%+ turn ~(tap in ships)
|= who=@p
?. (allowed who %read u.book)
@ -1226,12 +1262,19 @@
^- [(list card) write=path read=path]
?> ?=(^ group-path.group)
=/ scry-path
;:(welp /(scot %p our.bol)/group-store/(scot %da now.bol) [%groups group-path.group] /noun)
=/ grp .^((unit ^group) %gx scry-path)
;: welp
/(scot %p our.bol)/group-store/(scot %da now.bol)
[%groups group-path.group]
/noun
==
=/ rid=resource (de-path:resource group-path.group)
=/ grp=(unit ^group) (scry-group:grup rid)
?: use-preexisting.group
?~ grp !!
?. (is-managed group-path.group) !!
`[group-path.group group-path.group]
=/ =tag [%publish (cat 3 'writers-' book)]
:_ [group-path.group group-path.group]
[(group-proxy-poke entity.rid %add-tag rid tag members.u.grp)]~
::
=/ =policy
*open:policy
@ -1684,10 +1727,9 @@
?> ?=(^ subscribers.u.book)
=/ cards=(list card)
~[(delete-dir pax)]
=/ rid=resource
(de-path:resource writers.u.book)
=? cards (is-managed:grup rid)
=? cards !(is-managed:grup rid)
[(group-poke %remove-group rid ~) cards]
[cards state]
:: %del-note:
@ -1791,6 +1833,10 @@
?> (team:title our.bol src.bol)
=/ join-wire=wire
/join-group/[(scot %p who.act)]/[book.act]
=/ meta=(unit (set path))
(metadata-resource-scry %publish /(scot %p who.act)/[book.act])
?^ meta
(subscribe-notebook who.act book.act)
=/ rid=resource
[who.act book.act]
=/ =cage
@ -1811,12 +1857,16 @@
(de-path:resource writers.book)
=/ =group
(need (scry-group:grup rid))
:_ state(books (~(del by books) who.act book.act))
:~ `card`[%pass wir %agent [who.act %publish] %leave ~]
`card`[%give %fact [/primary]~ %publish-primary-delta !>(del)]
(group-proxy-poke who.act %remove-members rid (sy our.bol ~))
(group-poke %remove-group rid ~)
==
=/ cards=(list card)
:~ [%pass wir %agent [who.act %publish] %leave ~]
[%give %fact [/primary]~ %publish-primary-delta !>(del)]
==
=? cards hidden.group
%+ weld cards
:~ (group-proxy-poke who.act %remove-members rid (sy our.bol ~))
(group-poke %remove-group rid ~)
==
[cards state(books (~(del by books) who.act book.act))]
:: %read
::
%read
@ -1952,6 +2002,19 @@
/noun
==
::
++ metadata-resource-scry
|= [app=@tas app-path=path]
^- (unit (set path))
?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~) ~
.^ (unit (set path))
%gx
;: weld
/(scot %p our.bol)/metadata-store/(scot %da now.bol)/resource/[app]
app-path
/noun
==
==
::
++ emit-metadata
|= del=metadata-delta
^- (list card)
@ -2044,9 +2107,11 @@
(emit-updates-and-state host.del book.del data.del del sty)
=/ rid=resource
(de-path:resource writers.data.del)
=? cards !=(our.bol entity.rid)
:_ cards
(group-pull-hook-poke [%add host.del rid])
:_ state
:* (group-pull-hook-poke [%add host.del rid])
(metadata-hook-poke [%add-synced host.del writers.data.del])
:* (metadata-hook-poke [%add-synced host.del writers.data.del])
cards
==
::

View File

@ -0,0 +1,10 @@
:: graph-store|add-graph: add new graph
::
/+ *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=resource mark=(unit mark) ~] ~]
==
:- %graph-update
^- update
[%0 now [%add-graph resource (gas:orm ~ ~) mark]]

View File

@ -0,0 +1,20 @@
:: graph-store|add-post: add post to a graph
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[[our=ship name=term] contents=(list content) ~] ~]
==
=/ =post *post
=: author.post our
index.post [now]~
time-sent.post now
contents.post contents
==
::
:- %graph-update
^- update
:+ %0 now
:+ %add-nodes [our name]
%- ~(gas by *(map index node))
~[[[now]~ [post [%empty ~]]]]

View File

@ -0,0 +1,10 @@
:: graph-store|add-signatures: add signatures to a node at a particular uid
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[[=resource =index] =signatures ~] ~]
==
:- %graph-update
^- update
[%0 now [%add-signatures [resource index] signatures]]

View File

@ -0,0 +1,10 @@
:: graph-store|add-tag: tag a particular graph
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=term =resource ~] ~]
==
:- %graph-update
^- update
[%0 now [%add-tag term resource]]

View File

@ -0,0 +1,10 @@
:: graph-store|archive-graph: archive graph
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=resource ~] ~]
==
:- %graph-update
^- update
[%0 now [%archive-graph resource]]

View File

@ -0,0 +1,10 @@
:: graph-store|remove-graph: remove graph
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=resource ~] ~]
==
:- %graph-update
^- update
[%0 now [%remove-graph resource]]

View File

@ -0,0 +1,10 @@
:: graph-store|remove-nodes: remove nodes from a graph at indices
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=resource indices=(set index) ~] ~]
==
:- %graph-update
^- update
[%0 now [%remove-nodes resource indices]]

View File

@ -0,0 +1,11 @@
:: graph-store|remove-signatures: remove signatures from a node at a
:: particular uid
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[[=resource =index] =signatures ~] ~]
==
:- %graph-update
^- update
[%0 now [%remove-signatures [resource index] signatures]]

View File

@ -0,0 +1,10 @@
:: graph-store|remove-tag: remove a tag from a particular graph
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=term =resource ~] ~]
==
:- %graph-update
^- update
[%0 now [%remove-tag term resource]]

View File

@ -0,0 +1,10 @@
:: graph-store|unarchive-graph: unarchive graph
::
/- *graph-store
:- %say
|= $: [now=@da eny=@uvJ =beak]
[[=resource ~] ~]
==
:- %graph-update
^- update
[%0 now [%unarchive-graph resource]]

View File

@ -36,7 +36,7 @@
public-key
=/ cub (pit:nu:crub:crypto 512 (shaz (jam mon life eny)))
=/ =seed:able:jael
[mon 1 sec:ex:cub ~]
[mon life sec:ex:cub ~]
%- %- slog
:~ leaf+"moon: {(scow %p mon)}"
leaf+(scow %uw (jam seed))

View File

@ -8,7 +8,11 @@
::
:- %say
|= $: [now=@da eny=@uvJ bec=beak]
[arg=?(~ [her=@p sud=@tas ~]) ~]
[arg=?(~ [%disable ~] [her=@p sud=@tas ~]) ~]
==
?~ arg
:- %kiln-ota-info ~
:- %kiln-ota
?~(arg ~ `[her sud]:arg)
?: ?=([%disable ~] arg)
~
`[her sud]:arg

View File

@ -5,5 +5,5 @@
[%tang >timers< ~]
.^ (list [date=@da =duct])
%bx
(en-beam:format [p.bec %$ r.bec] /debug/timers)
(en-beam:format [p.bec %$ r.bec] /timers/debug)
==

View File

@ -0,0 +1,411 @@
/- sur=graph-store, pos=post
/+ res=resource
=< [sur .]
=< [pos .]
=, sur
=, pos
|%
:: NOTE: move these functions to zuse
++ nu :: parse number as hex
|= jon/json
?> ?=({$s *} jon)
(rash p.jon hex)
::
++ re :: recursive reparsers
|* {gar/* sef/_|.(fist:dejs-soft:format)}
|= jon/json
^- (unit _gar)
=- ~! gar ~! (need -) -
((sef) jon)
::
++ dank :: tank
^- $-(json (unit tank))
=, ^? dejs-soft:format
%+ re *tank |. ~+
%- of :~
leaf+sa
palm+(ot style+(ot mid+sa cap+sa open+sa close+sa ~) lines+(ar dank) ~)
rose+(ot style+(ot mid+sa open+sa close+sa ~) lines+(ar dank) ~)
==
::
++ orm ((ordered-map atom node) gth)
++ orm-log ((ordered-map time logged-update) gth)
::
++ enjs
=, enjs:format
|%
++ update
|= upd=^update
^- json
?> ?=(%0 -.upd)
|^ (frond %graph-update (pairs ~[(encode q.upd)]))
::
++ encode
|= upd=update-0
^- [cord json]
?- -.upd
%add-graph
:- %add-graph
%- pairs
:~ [%resource (enjs:res resource.upd)]
[%graph (graph graph.upd)]
[%mark ?~(mark.upd ~ s+u.mark.upd)]
==
::
%remove-graph
[%remove-graph (enjs:res resource.upd)]
::
%add-nodes
:- %add-nodes
%- pairs
:~ [%resource (enjs:res resource.upd)]
[%nodes (nodes nodes.upd)]
==
::
%remove-nodes
:- %remove-nodes
%- pairs
:~ [%resource (enjs:res resource.upd)]
[%indices (indices indices.upd)]
==
::
%add-signatures
:- %add-signatures
%- pairs
:~ [%uid (uid uid.upd)]
[%signatures (signatures signatures.upd)]
==
::
%remove-signatures
:- %remove-signatures
%- pairs
:~ [%uid (uid uid.upd)]
[%signatures (signatures signatures.upd)]
==
::
%add-tag
:- %add-tag
%- pairs
:~ [%term s+term.upd]
[%resource (enjs:res resource.upd)]
==
::
%remove-tag
:- %remove-tag
%- pairs
:~ [%term s+term.upd]
[%resource (enjs:res resource.upd)]
==
::
%archive-graph
[%archive-graph (enjs:res resource.upd)]
::
%unarchive-graph
[%unarchive-graph (enjs:res resource.upd)]
::
%keys
[%keys [%a (turn ~(tap in resources.upd) enjs:res)]]
::
%tags
[%tags [%a (turn ~(tap in tags.upd) |=(=term s+term))]]
::
%run-updates
[%run-updates ~]
::
%tag-queries
:- %tag-queries
%- pairs
%+ turn ~(tap by tag-queries.upd)
|= [=term =resources]
^- [cord json]
[term [%a (turn ~(tap in resources) enjs:res)]]
==
::
++ graph
|= g=^graph
^- json
:- %a
%+ turn (tap:orm g)
|= [a=atom n=^node]
^- json
:- %a
:~ (index [a]~)
(node n)
==
::
++ index
|= i=^index
^- json
=/ j=^tape ""
|-
?~ i [%s (crip j)]
=/ k=json (numb i.i)
?> ?=(%n -.k)
%_ $
i t.i
j (weld j (weld "/" (trip +.k)))
==
::
++ node
|= n=^node
^- json
%- pairs
:~ [%post (post post.n)]
:- %children
?- -.children.n
%empty ~
%graph (graph +.children.n)
==
==
::
++ post
|= p=^post
^- json
%- pairs
:~ [%author (ship author.p)]
[%index (index index.p)]
[%time-sent (time time-sent.p)]
[%contents [%a (turn contents.p content)]]
[%hash ?~(hash.p ~ s+(scot %ux u.hash.p))]
[%signatures (signatures signatures.p)]
==
::
++ content
|= c=^content
^- json
?- -.c
%text (frond %text s+text.c)
%url (frond %url s+url.c)
%reference (frond %reference (uid uid.c))
%code
%+ frond %code
%- pairs
:- [%expression s+expression.c]
:_ ~
:- %output
:: virtualize output rendering, +tank:enjs:format might crash
::
=/ result=(each (list json) tang)
(mule |.((turn output.c tank)))
?- -.result
%& a+p.result
%| a+[a+[%s '[[output rendering error]]']~]~
==
==
::
++ nodes
|= m=(map ^index ^node)
^- json
:- %a
%+ turn ~(tap by m)
|= [n=^index o=^node]
^- json
:- %a
:~ (index n)
(node o)
==
::
++ indices
|= i=(set ^index)
^- json
[%a (turn ~(tap in i) index)]
::
++ uid
|= u=^uid
^- json
%- pairs
:~ [%resource (enjs:res resource.u)]
[%index (index index.u)]
==
::
++ signatures
|= s=^signatures
^- json
[%a (turn ~(tap in s) signature)]
::
++ signature
|= s=^signature
^- json
%- pairs
:~ [%signature s+(scot %ux p.s)]
[%ship (ship q.s)]
[%life (numb r.s)]
==
--
--
::
++ dejs
=, dejs:format
|%
++ update
|= jon=json
^- ^update
:- %0
:- *time
^- update-0
=< (decode jon)
|%
++ decode
%- of
:~ [%add-graph add-graph]
[%remove-graph remove-graph]
[%add-nodes add-nodes]
[%remove-nodes remove-nodes]
[%add-signatures add-signatures]
[%remove-signatures remove-signatures]
[%add-tag add-tag]
[%remove-tag remove-tag]
[%archive-graph archive-graph]
[%unarchive-graph unarchive-graph]
[%keys keys]
[%tags tags]
[%tag-queries tag-queries]
[%run-updates run-updates]
==
::
++ add-graph
%- ot
:~ [%resource dejs:res]
[%graph graph]
[%mark (mu so)]
==
::
++ graph
|= a=json
^- ^graph
=/ or-mp ((ordered-map atom ^node) gth)
%+ gas:or-mp ~
%+ turn ~(tap by ((om node) a))
|* [b=cord c=*]
^- [atom ^node]
=> .(+< [b c]=+<)
[(rash b dem) c]
::
++ remove-graph (ot [%resource dejs:res]~)
++ archive-graph (ot [%resource dejs:res]~)
++ unarchive-graph (ot [%resource dejs:res]~)
::
++ add-nodes
%- ot
:~ [%resource dejs:res]
[%nodes nodes]
==
::
++ nodes (op ;~(pfix net (more net dem)) node)
::
++ node
%- ot
:~ [%post post]
:: TODO: support adding nodes with children by supporting the
:: graph key
[%children (of [%empty ul]~)]
==
::
++ post
%- ot
:~ [%author (su ;~(pfix sig fed:ag))]
[%index index]
[%time-sent di]
[%contents (ar content)]
[%hash (mu nu)]
[%signatures (as signature)]
==
::
++ content
%- of
:~ [%text so]
[%url so]
[%reference uid]
[%code eval]
==
::
++ eval
|= a=^json
^- [cord (list tank)]
=, ^? dejs-soft:format
=+ exp=((ot expression+so ~) a)
%- need
?~ exp [~ '' ~]
:+ ~ u.exp
:: NOTE: when sending, if output is an empty list,
:: graph-store will evaluate
(fall ((ot output+(ar dank) ~) a) ~)
::
++ remove-nodes
%- ot
:~ [%resource dejs:res]
[%indices (as index)]
==
::
++ add-signatures
%- ot
:~ [%uid uid]
[%signatures (as signature)]
==
::
++ remove-signatures
%- ot
:~ [%uid uid]
[%signatures (as signature)]
==
::
++ signature
%- ot
:~ [%hash nu]
[%ship (su ;~(pfix sig fed:ag))]
[%life ni]
==
::
++ uid
%- ot
:~ [%resource dejs:res]
[%index index]
==
::
++ index (su ;~(pfix net (more net dem)))
::
++ add-tag
%- ot
:~ [%term so]
[%resource dejs:res]
==
::
++ remove-tag
%- ot
:~ [%term so]
[%resource dejs:res]
==
::
++ keys
|= =json
*resources
::
++ tags
|= =json
*(set term)
::
++ tag-queries
|= =json
*^tag-queries
::
++ run-updates
|= a=json
^- [resource update-log]
[*resource *update-log]
--
--
::
++ create
|_ [our=ship now=time]
++ post
|= [=index contents=(list content)]
^- ^post
:* our
index
now
contents
~
*signatures
==
--
--

24
pkg/arvo/lib/graph.hoon Normal file
View File

@ -0,0 +1,24 @@
/- *resource
/+ store=graph-store
|_ =bowl:gall
++ scry-for
|* [=mold =path]
.^ mold
%gx
(scot %p our.bowl)
%graph-store
(scot %da now.bowl)
(snoc `^path`path %noun)
==
::
++ get-graph
|= res=resource
^- marked-graph:store
%+ scry-for marked-graph:store
/graph/(scot %p entity.res)/[name.res]
::
++ peek-log
|= res=resource
^- (unit time)
(scry-for (unit time) /peek-update-log/(scot %p entity.res)/[name.res])
--

View File

@ -104,6 +104,7 @@
%s3-store
%file-server
%glob
%graph-store
==
::
++ deft-fish :: default connects
@ -206,7 +207,7 @@
==
::
++ on-load
|= [hood-version=?(%1 %2 %3 %4 %5 %6 %7 %8) old=any-state]
|= [hood-version=?(%1 %2 %3 %4 %5 %6 %7 %8 %9) old=any-state]
=< se-abet =< se-view
=. sat old
=. dev (~(gut by bin) ost *source)
@ -233,6 +234,8 @@
=? ..on-load (lte hood-version %8)
=> (se-born | %home %group-push-hook)
(se-born | %home %group-pull-hook)
=? ..on-load (lte hood-version %9)
(se-born | %home %graph-store)
..on-load
::
++ reap-phat :: ack connect

View File

@ -208,7 +208,7 @@
::
++ get-germ
|= =desk
=+ .^(=cass:clay %cw /(scot %p our)/home/(scot %da now))
=+ .^(=cass:clay %cw /(scot %p our)/[desk]/(scot %da now))
?- ud.cass
%0 %init
%1 %that
@ -341,13 +341,22 @@
abet:(spam (render "already syncing" [sud her syd]:hos) ~)
abet:abet:start-sync:(auto hos)
::
++ ota-info
?~ ota
"OTAs disabled"
"OTAs enabled from {<desk.u.ota>} on {<ship.u.ota>}"
::
++ poke-ota-info
|= *
=< abet %- spam
:~ [%leaf ota-info]
[%leaf "use |ota %disable or |ota ~sponsor %kids to reset it"]
==
::
++ poke-syncs :: print sync config
|= ~
=< abet %- spam
:- :- %leaf
?~ ota
"OTAs disabled"
"OTAs from {<desk.u.ota>} on {<ship.u.ota>}"
:- [%leaf ota-info]
?: =(0 ~(wyt by syn))
[%leaf "no other syncs configured"]~
%+ turn ~(tap in ~(key by syn))
@ -416,6 +425,7 @@
%kiln-merge =;(f (f !<(_+<.f vase)) poke-merge)
%kiln-mount =;(f (f !<(_+<.f vase)) poke-mount)
%kiln-ota =;(f (f !<(_+<.f vase)) poke:update)
%kiln-ota-info =;(f (f !<(_+<.f vase)) poke-ota-info)
%kiln-permission =;(f (f !<(_+<.f vase)) poke-permission)
%kiln-rm =;(f (f !<(_+<.f vase)) poke-rm)
%kiln-schedule =;(f (f !<(_+<.f vase)) poke-schedule)

View File

@ -59,7 +59,6 @@
|~ [term tang]
*[(list card) _^|(..on-init)]
:: +resource-for-update: get affected resource from an update
++ resource-for-update
|~ vase
*(unit resource)

View File

@ -0,0 +1,43 @@
/- post
^?
=< [post .]
=, post
|%
++ sign
|= [our=ship now=time =hash]
^- signature
=/ =life .^(life %j /=life/(scot %da now)/(scot %p our))
=/ =ring .^(ring %j /=vein/(scot %da now)/(scot %ud life))
:+ `@ux`(sign:as:(nol:nu:crub:crypto ring) hash)
our
life
::
++ is-signature-valid
|= [=signature =hash now=time]
^- ?
=/ deed=(unit [a=life b=pass c=(unit @ux)])
.^ (unit [life pass (unit @ux)])
%j
/=deed/(scot %da now)/(scot %p q.signature)/(scot %ud p.signature)
==
:: we do not have a public key from ship
::
?~ deed %.y
:: we do not have a public key from ship at this life
::
?. =(a.u.deed r.signature) %.y
:: verify signature from ship at life
::
=(`hash (tear:as:crub:crypto b.u.deed p.signature))
::
++ are-signatures-valid
|= [=signatures =hash now=time]
^- ?
=/ signature-list ~(tap in signatures)
|-
?~ signature-list
%.y
?: (is-signature-valid i.signature-list hash now)
$(signature-list t.signature-list)
%.n
--

View File

@ -0,0 +1,13 @@
/+ *graph-store
|_ upd=update
++ grow
|%
++ json (update:enjs upd)
--
::
++ grab
|%
++ noun update
++ json update:dejs
--
--

View File

@ -0,0 +1,17 @@
/- *post
|_ i=indexed-post
++ grow
|%
++ noun i
--
++ grab
|%
++ noun
|= p=*
=/ ip ;;(indexed-post p)
?> ?=([@ ~] index.p.ip)
ip
--
::
++ grad %noun
--

View File

@ -0,0 +1,61 @@
/- *post
|%
+$ graph ((mop atom node) gth)
+$ marked-graph [p=graph q=(unit mark)]
::
+$ node [=post children=internal-graph]
+$ graphs (map resource marked-graph)
::
+$ tag-queries (jug term resource)
::
+$ update-log ((mop time logged-update) gth)
+$ update-logs (map resource update-log)
::
+$ internal-graph
$~ [%empty ~]
$% [%graph p=graph]
[%empty ~]
==
::
+$ network
$: =graphs
=tag-queries
=update-logs
archive=graphs
validators=(set mark)
==
::
+$ update
$% [%0 p=time q=update-0]
==
::
+$ logged-update
$% [%0 p=time q=logged-update-0]
==
::
+$ logged-update-0
$% [%add-nodes =resource nodes=(map index node)]
[%remove-nodes =resource indices=(set index)]
[%add-signatures =uid =signatures]
[%remove-signatures =uid =signatures]
==
::
+$ update-0
$% logged-update-0
[%add-graph =resource =graph mark=(unit mark)]
[%remove-graph =resource]
::
[%add-tag =term =resource]
[%remove-tag =term =resource]
::
[%archive-graph =resource]
[%unarchive-graph =resource]
[%run-updates =resource =update-log]
::
:: NOTE: cannot be sent as pokes
::
[%keys =resources]
[%tags tags=(set term)]
[%tag-queries =tag-queries]
==
--

37
pkg/arvo/sur/post.hoon Normal file
View File

@ -0,0 +1,37 @@
/- *resource
|%
+$ index (list atom)
+$ uid [=resource =index]
::
:: +sham (half sha-256) hash of +validated-portion
+$ hash @ux
::
+$ signature [p=@ux q=ship r=life]
+$ signatures (set signature)
+$ post
$: author=ship
=index
time-sent=time
contents=(list content)
hash=(unit hash)
=signatures
==
::
+$ indexed-post [a=atom p=post]
::
+$ validated-portion
$: parent-hash=(unit hash)
author=ship
time-sent=time
contents=(list content)
==
::
+$ content
$% [%text text=cord]
[%url url=cord]
[%code expression=cord output=(list tank)]
[%reference =uid]
:: TODO: maybe use a cask?
::[%cage =cage]
==
--

View File

@ -8,5 +8,4 @@
+$ update
$% [%tracking tracking=(map resource ship)]
==
::
--

View File

@ -7762,11 +7762,13 @@
++ teal
|= mod/spec
^- spec
?: ?=(%& -.tik) mod
[%over [%& 3]~ mod]
::
++ tele
|= syn/skin
^- skin
?: ?=(%& -.tik) syn
[%over [%& 3]~ syn]
::
++ gray

View File

@ -1121,17 +1121,32 @@
?> =(rcvr-life.shut-packet our-life.channel)
:: non-galaxy: update route with heard lane or forwarded lane
::
=? route.peer-state
?: =(%czar (clan:title her.channel))
%.n
=/ is-old-direct=? ?=([~ %& *] route.peer-state)
=/ is-new-direct=? ?=(~ origin.packet)
:: old direct takes precedence over new indirect
::
|(is-new-direct !is-old-direct)
=? route.peer-state !=(%czar (clan:title her.channel))
:: if new packet is direct, use that. otherwise, if the new new
:: and old lanes are indirect, use the new one. if the new lane
:: is indirect but the old lane is direct, then if the lanes are
:: identical, don't mark it indirect; if they're not identical,
:: use the new lane and mark it indirect.
::
?~ origin.packet
:: if you mark lane as indirect because you got an indirect
:: packet even though you already had a direct identical lane,
:: then delayed forwarded packets will come later and reset to
:: indirect, so you're unlikely to get a stable direct route
:: (unless the forwarder goes offline for a while).
::
:: conversely, if you don't accept indirect routes with different
:: lanes, then if your lane is stale and they're trying to talk
:: to you, your acks will go to the stale lane, and you'll never
:: time it out unless you reach out to them. this manifests as
:: needing to |hi or dotpost to get a response when the other
:: ship has changed lanes.
::
?: ?=(~ origin.packet)
`[direct=%.y lane]
?: ?=([~ %& *] route.peer-state)
?: =(lane.u.route.peer-state u.origin.packet)
route.peer-state
`[direct=%.n u.origin.packet]
`[direct=%.n u.origin.packet]
:: perform peer-specific handling of packet
::

View File

@ -109,6 +109,10 @@
mut/(list (trel path lobe cage)) :: mutations
== ::
::
:: Over-the-wire backfill request
::
+$ fill [=desk =lobe]
::
:: Ford cache
::
+$ ford-cache
@ -214,18 +218,29 @@
:: requests, and a possible nako if we've received data from the other ship and
:: are in the process of validating it.
::
++ rind :: request manager
$: nix/@ud :: request index
bom/(map @ud {p/duct q/rave}) :: outstanding
fod/(map duct @ud) :: current requests
haw/(map mood (unit cage)) :: simple cache
== ::
+$ rind :: request manager
$: nix=@ud :: request index
bom=(map @ud update-state) :: outstanding
fod=(map duct @ud) :: current requests
haw=(map mood (unit cage)) :: simple cache
== ::
::
:: Active downloads
::
+$ update-state
$: =duct
=rave
have=(map lobe blob)
need=(list lobe)
nako=(qeu (unit nako))
busy=_|
==
::
:: Result of a subscription
::
++ sub-result
$% [%blab =mood data=(each cage lobe)]
[%bleb ins=@ud range=(unit (pair aeon aeon))]
[%bleb ver=@ud ins=@ud range=(unit (pair aeon aeon))]
[%balk cage=(unit (each cage lobe)) =mood]
[%blas moods=(set mood)]
[%blub ~]
@ -246,7 +261,7 @@
:: Generally used when we store a request in our state somewhere.
::
++ cach (unit (unit (each cage lobe))) :: cached result
+$ wove [for=(unit ship) =rove] :: stored source + req
+$ wove [for=(unit [=ship ver=@ud]) =rove] :: stored source + req
++ rove :: stored request
$% [%sing =mood] :: single request
[%next =mood aeon=(unit aeon) =cach] :: next version of one
@ -1134,13 +1149,13 @@
:: Give next step in a subscription.
::
++ bleb
|= {hen/duct ins/@ud hip/(unit (pair aeon aeon))}
|= [hen=duct ver=@ud ins=@ud hip=(unit (pair aeon aeon))]
^+ +>
%^ blab hen [%w [%ud ins] ~]
:- %&
?~ hip
[%null [%atom %n ~] ~]
[%nako !>((make-nako:ze u.hip))]
[%nako !>((make-nako:ze ver u.hip))]
::
:: Tell subscriber that subscription is done.
::
@ -1183,7 +1198,7 @@
=/ =desk p.riff
=/ =wire /warp-index/(scot %p ship)/(scot %tas desk)/(scot %ud index)
=/ =path [%question desk (scot %ud index) ~]
(emit duct %pass wire %a %plea ship %c path riff)
(emit duct %pass wire %a %plea ship %c path [[%1 ~] riff])
::
:: Create a request that cannot be filled immediately.
::
@ -1210,7 +1225,7 @@
(send-over-ames hen her inx syd `rave)
%= +>+.$
nix.u.ref +(nix.u.ref)
bom.u.ref (~(put by bom.u.ref) inx [hen rave])
bom.u.ref (~(put by bom.u.ref) inx [hen rave ~ ~ ~ |])
fod.u.ref (~(put by fod.u.ref) hen inx)
==
::
@ -2003,6 +2018,7 @@
:: bob's.
::
?: ?=(%init germ)
?> ?=(~ bob-yaki)
&+`[conflicts=~ new=|+ali-yaki lat=~]
::
=/ bob-yaki (need bob-yaki)
@ -2589,7 +2605,7 @@
:: and then waiting if the subscription range extends into the future.
::
++ start-request
|= [for=(unit ship) rav=rave]
|= [for=(unit [ship @ud]) rav=rave]
^+ ..start-request
=^ [new-sub=(unit rove) sub-results=(list sub-result)] fod.dom
(try-fill-sub for (rave-to-rove rav))
@ -2612,9 +2628,9 @@
?> ?=(^ ref)
=+ ruv=(~(get by bom.u.ref) inx)
?~ ruv +>.$
=/ rav=rave q.u.ruv
=/ rav=rave rave.u.ruv
?: ?=(%many -.rav)
(take-foreign-update inx rut)
abet:(apex:(foreign-update inx) rut)
?~ rut
:: nothing here, so cache that
::
@ -2689,36 +2705,138 @@
!>(;;(@uvI q.page))
--
::
:: A full foreign update. Validate and apply to our local cache of
:: their state.
:: Respond to backfill request
::
++ take-foreign-update
|= [inx=@ud rut=(unit rand)]
^+ ..take-foreign-update
:: Maybe should verify the requester is allowed to access this blob?
::
++ give-backfill
|= =lobe
^+ ..give-backfill
(emit hen %give %boon (~(got by lat.ran) lobe))
::
:: Ingest foreign update, requesting missing blobs if necessary
::
++ foreign-update
|= inx=@ud
?> ?=(^ ref)
=/ ruv (~(get by bom.u.ref) inx)
?~ ruv
~& [%clay-foreign-update-lost her syd inx]
..take-foreign-update
=. hen p.u.ruv
=/ =rave q.u.ruv
?> ?=(%many -.rave)
|^
?~ rut
done
=. lim ?.(?=(%da -.to.moat.rave) lim p.to.moat.rave)
?> ?=(%nako p.r.u.rut)
=/ nako ;;(nako q.r.u.rut)
=. ..take-foreign-update
=< ?>(?=(^ ref) .)
(apply-foreign-update nako)
done
=/ [sat=update-state lost=?]
=/ ruv (~(get by bom.u.ref) inx)
?~ ruv
~& [%clay-foreign-update-lost her syd inx]
[*update-state &]
[u.ruv |]
=/ done=? |
=. hen duct.sat
|%
++ abet
^+ ..foreign-update
?: lost
..foreign-update
?: done
=: bom.u.ref (~(del by bom.u.ref) inx)
fod.u.ref (~(del by fod.u.ref) hen)
==
=<(?>(?=(^ ref) .) wake)
=. bom.u.ref (~(put by bom.u.ref) inx sat)
..foreign-update
::
++ done
=: bom.u.ref (~(del by bom.u.ref) inx)
bom.u.ref (~(del by bom.u.ref) hen)
==
=<(?>(?=(^ ref) .) wake)
++ apex
|= rut=(unit rand)
^+ ..abet
?: lost ..abet
?~ rut
=. nako.sat (~(put to nako.sat) ~)
work
?> ?=(%nako p.r.u.rut)
=/ nako ;;(nako q.r.u.rut)
=/ missing (missing-blobs nako)
=. need.sat `(list lobe)`(welp need.sat ~(tap in missing))
=. nako.sat (~(put to nako.sat) ~ nako)
work
::
++ missing-blobs
|= =nako
^- (set lobe)
=/ yakis ~(tap in lar.nako)
|- ^- (set lobe)
=* yaki-loop $
?~ yakis
~
=/ lobes=(list [=path =lobe]) ~(tap by q.i.yakis)
|- ^- (set lobe)
=* blob-loop $
?~ lobes
yaki-loop(yakis t.yakis)
?: (~(has by lat.ran) lobe.i.lobes)
blob-loop(lobes t.lobes)
(~(put in blob-loop(lobes t.lobes)) lobe.i.lobes)
::
:: Receive backfill response
::
++ take-backfill
|= =blob
^+ ..abet
?: lost ..abet
=? need.sat
?& ?=(%delta -.blob)
!(~(has by lat.ran) q.q.blob)
!(~(has by have.sat) q.q.blob)
==
[q.q.blob need.sat]
:: We can't put a blob in lat.ran if its parent isn't already
:: there. Unions are in reverse order so we don't overwrite
:: existing blobs.
::
=. ..abet
?: &(?=(%delta -.blob) !(~(has by lat.ran) q.q.blob))
..abet(have.sat (~(uni by (malt [p.blob `^blob`blob] ~)) have.sat))
..abet(lat.ran (~(uni by (malt [p.blob blob] ~)) lat.ran))
work(busy.sat |)
::
:: Fetch next blob
::
++ work
^+ ..abet
?: busy.sat
..abet
|- ^+ ..abet
?: =(~ need.sat)
:: NB: if you change to release nakos as we get enough blobs
:: for them instead of all at the end, you *must* store the
:: `lim` that should be applied after the nako is complete and
:: not use the one in the rave, since that will apply to the
:: end of subscription.
::
=. lat.ran (~(uni by have.sat) lat.ran)
|- ^+ ..abet
?: =(~ nako.sat)
..abet
=^ next=(unit nako) nako.sat ~(get to nako.sat)
?~ next
..abet(done &)
=. ..abet (apply-foreign-update u.next)
=. ..foreign-update =<(?>(?=(^ ref) .) wake)
$
?> ?=(^ need.sat)
:: This is what removes an item from `need`. This happens every
:: time we take a backfill response, but it could happen more than
:: once if we somehow got this data in the meantime (maybe from
:: another desk updating concurrently, or a previous update on this
:: same desk).
::
?: ?| (~(has by lat.ran) i.need.sat)
(~(has by have.sat) i.need.sat)
==
$(need.sat t.need.sat)
:: Otherwise, fetch the next blob
::
=/ =fill [syd i.need.sat]
=/ =wire /back-index/(scot %p her)/[syd]/(scot %ud inx)
=/ =path [%backfill syd (scot %ud inx) ~]
=. ..foreign-update
=< ?>(?=(^ ref) .)
(emit hen %pass wire %a %plea her %c path fill)
..abet(busy.sat &)
::
:: When we get a %w foreign update, store this in our state.
::
@ -2728,7 +2846,7 @@
::
++ apply-foreign-update
|= =nako
^+ ..take-foreign-update
^+ ..abet
:: hit: updated commit-hashes by @ud case
:: nut: new commit-hash/commit pairs
:: hut: updated commits by hash
@ -2765,12 +2883,19 @@
$(aeon +(aeon))
:: produce updated state
::
=/ =rave rave:(~(got by bom.u.ref) inx)
?> ?=(%many -.rave)
=: let.dom (max let.nako let.dom)
hit.dom hit
hut.ran hut
lat.ran lat
:: Is this correct? Seeems like it should only go to `to` if
:: we've gotten all the way to the end. Leaving this
:: behavior unchanged for now, but I believe it's wrong.
::
lim ?.(?=(%da -.to.moat.rave) lim p.to.moat.rave)
==
..take-foreign-update
..abet
--
::
:: fire function if request is in future
@ -2862,8 +2987,9 @@
:: Try to fill a subscription
::
++ try-fill-sub
|= [for=(unit ship) rov=rove]
|= [far=(unit [=ship ver=@ud]) rov=rove]
^- [[new-sub=(unit rove) (list sub-result)] ford-cache]
=/ for=(unit ship) ?~(far ~ `ship.u.far)
?- -.rov
%sing
=/ cache-value=(unit (unit cage))
@ -3075,6 +3201,7 @@
::
[`rov ~]
=/ to-aeon (case-to-aeon to.moat.rov)
=/ ver ?~(far %1 ver.u.far)
?~ to-aeon
:: we're in the middle of the range, so produce what we can,
:: but don't end the subscription
@ -3092,7 +3219,7 @@
~
:: else changes, so produce them
::
[%bleb let.dom ?:(track.rov ~ `[u.from-aeon let.dom])]~
[%bleb ver let.dom ?:(track.rov ~ `[u.from-aeon let.dom])]~
:: we're past the end of the range, so end subscription
::
:- ~
@ -3103,7 +3230,7 @@
=/ bleb=(list sub-result)
?: =(lobes.rov new-lobes)
~
[%bleb +(u.from-aeon) ?:(track.rov ~ `[u.from-aeon u.to-aeon])]~
[%bleb ver +(u.from-aeon) ?:(track.rov ~ `[u.from-aeon u.to-aeon])]~
:: end subscription
::
=/ blub=(list sub-result)
@ -3111,17 +3238,6 @@
(weld bleb blub)
==
::
++ drop-me
^+ .
~| %clay-drop-me-not-implemented
!!
:: ?~ mer
:: .
:: %- emit(mer ~) ^- move :*
:: hen.u.mer %give %mere %| %user-interrupt
:: >sor.u.mer< >our< >cas.u.mer< >gem.u.mer< ~
:: ==
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
:: This core has no additional state, and the distinction exists purely for
@ -3209,7 +3325,7 @@
:: Creates a nako of all the changes between a and b.
::
++ make-nako
|= {a/aeon b/aeon}
|= [ver=@ud a=aeon b=aeon]
^- nako
:+ ?> (lte b let.dom)
|-
@ -3219,7 +3335,7 @@
b
?: =(0 b)
[~ ~]
(data-twixt-takos (~(get by hit.dom) a) (aeon-to-tako b))
(data-twixt-takos =(0 ver) (~(get by hit.dom) a) (aeon-to-tako b))
::
:: Traverse parentage and find all ancestor hashes
::
@ -3245,16 +3361,21 @@
:: ones we found before `a`. Then convert the takos to yakis and also get
:: all the data in all the yakis.
::
:: What happens if you run an %init merge on a desk that already
:: had a commit?
::
++ data-twixt-takos
|= {a/(unit tako) b/tako}
^- {(set yaki) (set plop)}
|= [plops=? a=(unit tako) b=tako]
^- [(set yaki) (set plop)]
=+ old=?~(a ~ (reachable-takos u.a))
=/ yal/(set tako)
=/ yal=(set tako)
%- silt
%+ skip
~(tap in (reachable-takos b))
|=(tak/tako (~(has in old) tak))
|=(tak=tako (~(has in old) tak))
:- (silt (turn ~(tap in yal) tako-to-yaki))
?. plops
~
(silt (turn ~(tap in (new-lobes (new-lobes ~ old) yal)) lobe-to-blob))
::
:: Get all the lobes that are referenced in `a` except those that are
@ -3728,7 +3849,7 @@
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
=| :: instrument state
$: ver=%3 :: vane version
$: ver=%4 :: vane version
ruf=raft :: revision tree
== ::
|= [our=ship now=@da eny=@uvJ ski=sley] :: current invocation
@ -3939,7 +4060,14 @@
=^ for req
?: ?=(%warp -.req)
[~ req]
:- ?:(=(our who.req) ~ `who.req)
:: ?: =(our who.req)
:: [~ [%warp wer.req rif.req]]
=^ ver rif.req
?@ -.rif.req
[%0 rif.req]
[-<.rif.req +.rif.req]
?> ?=(@ -.rif.req)
:- ?:(=(our who.req) ~ `[who.req ver])
[%warp wer.req rif.req]
::
?> ?=(%warp -.req)
@ -3957,8 +4085,14 @@
=* pax path.plea.req
=* res payload.plea.req
::
?> ?=({%question *} pax)
=+ ryf=;;(riff res)
?: ?=([%backfill *] pax)
=+ ;;(=fill res)
=^ mos ruf
=/ den ((de our now ski hen ruf) our desk.fill)
abet:(give-backfill:den +.fill)
[[[hen %give %done ~] mos] ..^$]
?> ?=([%question *] pax)
=+ ryf=;;(riff-any res)
:_ ..^$
:~ [hen %give %done ~]
=/ =wire
@ -3971,11 +4105,58 @@
!:
|^
|= old=any-state
~! [old=old new=*state-3]
~! [old=old new=*state-4]
=? old ?=(%2 -.old) (load-2-to-3 old)
?> ?=(%3 -.old)
=? old ?=(%3 -.old) (load-3-to-4 old)
?> ?=(%4 -.old)
..^^$(ruf +.old)
::
++ load-3-to-4
|= =state-3
^- state-4
|^
=- state-3(- %4, hoy hoy.-, rom (room-3-to-4 rom.state-3))
^- hoy=(map ship rung)
%- ~(run by hoy.state-3)
|= =rung-3
^- rung
%- ~(run by rus.rung-3)
|= =rede-3
^- rede
=- rede-3(ref ref.-, qyx (cult-3-to-4 qyx.rede-3))
^- ref=(unit rind)
?~ ref.rede-3
~
=- `u.ref.rede-3(bom bom.-)
^- bom=(map @ud update-state)
%- ~(run by bom.u.ref.rede-3)
|= [=duct =rave]
^- update-state
[duct rave ~ ~ ~ |]
::
++ room-3-to-4
|= =room-3
^- room
=- room-3(dos dos.-)
^- dos=(map desk dojo)
%- ~(run by dos.room-3)
|= =dojo-3
^- dojo
dojo-3(qyx (cult-3-to-4 qyx.dojo-3))
::
++ cult-3-to-4
|= =cult-3
^- cult
%- malt
%+ turn ~(tap by cult-3)
|= [=wove-3 ducts=(set duct)]
^- [wove (set duct)]
:_ ducts :_ rove.wove-3
?~ for.wove-3
~
`[u.for.wove-3 %0]
--
::
++ load-2-to-3
|= =state-2
^- state-3
@ -4005,11 +4186,11 @@
:- %ford-fusion
[leaf+"queued merge canceled due to upgrade to ford fusion" ~]
`[duct %slip %b %drip !>([%mere %| err])]
^- rom=room
^- rom=room-3
:- hun.rom.state-2
%- ~(urn by dos.rom.state-2)
|= [=desk =dojo-2]
^- dojo
^- dojo-3
=- dojo-2(dom -)
^- dome
=/ fer=(unit reef-cache)
@ -4019,23 +4200,22 @@
(~(got by hut.ran.state-2) (~(got by hit.dom.dojo-2) let.dom.dojo-2))
`(build-reef desk q.yaki)
[ank let hit lab mim fod=*ford-cache fer=fer]:[dom.dojo-2 .]
^- hoy=(map ship rung)
^- hoy=(map ship rung-3)
%- ~(run by hoy.state-2)
|= =rung-2
^- rung
^- rung-3
%- ~(run by rus.rung-2)
|= =rede-2
^- rede
^- rede-3
=- rede-2(ref ref.-, dom dom.-)
:- ^- dom=dome
[ank let hit lab mim fod=*ford-cache fer=~]:[dom.rede-2 .]
^- ref=(unit rind)
^- ref=(unit rind-3)
?~ ref.rede-2
~
:: TODO: somehow call +wake later to notify subscribers
:- ~
^- rind
=/ rin=rind [nix bom fod haw]:u.ref.rede-2
^- rind-3
=/ rin=rind-3 [nix bom fod haw]:u.ref.rede-2
=. rin
=/ pur=(list [inx=@ud =rand *]) ~(tap by pur.u.ref.rede-2)
|- ^+ rin
@ -4138,8 +4318,46 @@
--
--
::
+$ any-state $%(state-3 state-2)
+$ state-3 [%3 raft]
+$ any-state $%(state-4 state-3 state-2)
+$ state-4 [%4 raft]
+$ state-3
$: %3
rom=room-3
hoy=(map ship rung-3)
ran=rang
mon=(map term beam)
hez=(unit duct)
cez=(map @ta crew)
pud=(unit [=desk =yoki])
pun=(list move)
==
+$ rung-3 rus=(map desk rede-3)
+$ rede-3
$: lim/@da
ref/(unit rind-3)
qyx/cult-3
dom/dome
per/regs
pew/regs
==
+$ rind-3
$: nix/@ud
bom/(map @ud {p/duct q/rave})
fod/(map duct @ud)
haw/(map mood (unit cage))
==
+$ room-3
$: hun/duct
dos/(map desk dojo-3)
==
++ dojo-3
$: qyx/cult-3
dom/dome
per/regs
pew/regs
==
+$ cult-3 (jug wove-3 duct)
+$ wove-3 [for=(unit ship) =rove]
+$ state-2
$: %2
rom=room-2 :: domestic
@ -4156,7 +4374,7 @@
dos/(map desk dojo-2) :: native desk
== ::
+$ dojo-2
$: qyx/cult :: subscribers
$: qyx/cult-3 :: subscribers
dom/dome-2 :: desk state
per/regs :: read perms per path
pew/regs :: write perms per path
@ -4172,7 +4390,7 @@
+$ rede-2
$: lim/@da :: complete to
ref/(unit rind-2) :: outgoing requests
qyx/cult :: subscribers
qyx/cult-3 :: subscribers
dom/dome-2 :: revision state
per/regs :: read perms per path
pew/regs :: write perms per path
@ -4303,6 +4521,35 @@
[mos ..^$]
==
::
?: ?=([%back-index @ @ @ ~] tea)
?+ +<.q.hin ~| %clay-backfill-index-strange !!
%done
?~ error.q.hin
[~ ..^$]
:: TODO better error handling
::
~& %clay-take-backfill-index-error^our^tea^tag.u.error.q.hin
%- (slog tang.u.error.q.hin)
[~ ..^$]
::
%lost
~| %clay-take-backfill-lost^our
:: TODO better error handling
!!
::
%boon
=+ ;; =blob payload.q.hin
::
=/ her=ship (slav %p i.t.tea)
=/ =desk (slav %tas i.t.t.tea)
=/ index=@ud (slav %ud i.t.t.t.tea)
::
=^ mos ruf
=/ den ((de our now ski hen ruf) her desk)
abet:abet:(take-backfill:(foreign-update:den index) blob)
[mos ..^$]
==
::
?: ?=([%sinks ~] tea)
?> ?=(%public-keys +<.q.hin)
?. ?=(%breach -.public-keys-result.q.hin)
@ -4396,7 +4643,9 @@
:+ desk %|
:~ ankh+&+ank.dom.dojo
mime+&+mim.dom.dojo
ford+&+fod.dom.dojo
ford-vases+&+vases.fod.dom.dojo
ford-marks+&+marks.fod.dom.dojo
ford-casts+&+casts.fod.dom.dojo
==
:~ domestic+|+domestic
foreign+&+hoy.ruf

View File

@ -861,7 +861,7 @@
$>(%trim vane-task) :: trim state
$>(%vega vane-task) :: report upgrade
{$warp wer/ship rif/riff} :: internal file req
{$werp who/ship wer/ship rif/riff} :: external file req
{$werp who/ship wer/ship rif/riff-any} :: external file req
$>(%plea vane-task) :: ames request
== ::
-- ::able
@ -967,7 +967,10 @@
who/(pair (set ship) (map @ta crew)) ::
== ::
++ regs (map path rule) :: rules for paths
++ riff {p/desk q/(unit rave)} :: request+desist
+$ riff [p=desk q=(unit rave)] :: request+desist
+$ riff-any
$^ [[%1 ~] riff]
riff
++ rite :: new permissions
$% {$r red/(unit rule)} :: for read
{$w wit/(unit rule)} :: for write

View File

@ -11,14 +11,14 @@
;< ~ bind:m (spawn az ~marbud)
;< ~ bind:m (real-ship az ~bud)
;< ~ bind:m (real-ship az ~marbud)
;< file=@t bind:m (touch-file ~bud %base %foo)
;< file=@t bind:m (touch-file ~bud %kids %foo)
;< ~ bind:m (check-file-touched ~marbud %home file)
;< ~ bind:m (breach-and-hear az ~bud ~marbud)
;< ~ bind:m (real-ship az ~bud)
;< ~ bind:m (breach-and-hear az ~marbud ~bud)
;< ~ bind:m (real-ship az ~marbud)
;< file=@t bind:m (touch-file ~bud %base %bar)
;< file=@t bind:m (touch-file ~bud %base %baz)
;< file=@t bind:m (touch-file ~bud %kids %bar)
;< file=@t bind:m (touch-file ~bud %kids %baz)
;< ~ bind:m (check-file-touched ~marbud %home file)
;< ~ bind:m end-azimuth
(pure:m *vase)

View File

@ -13,13 +13,13 @@
;< ~ bind:m (spawn az ~marbud)
;< ~ bind:m (real-ship az ~bud)
;< ~ bind:m (real-ship az ~marbud)
;< file=@t bind:m (touch-file ~bud %base %foo)
;< file=@t bind:m (touch-file ~bud %kids %foo)
;< ~ bind:m (check-file-touched ~marbud %home file)
;< ~ bind:m (breach az ~bud)
;< ~ bind:m (real-ship az ~bud)
;< ~ bind:m (dojo ~bud "|merge %base ~marbud %kids, =gem %this")
;< file=@t bind:m (touch-file ~bud %base %bar)
;< file=@t bind:m (touch-file ~bud %base %baz)
;< ~ bind:m (dojo ~bud "|merge %home ~marbud %kids, =gem %this")
;< file=@t bind:m (touch-file ~bud %kids %bar)
;< file=@t bind:m (touch-file ~bud %kids %baz)
;< ~ bind:m (check-file-touched ~marbud %home file)
;< ~ bind:m end-azimuth
(pure:m *vase)

View File

@ -13,6 +13,10 @@
;< ~ bind:m (real-ship az ~marbud)
;< file=@t bind:m (touch-file ~bud %kids %foo)
;< ~ bind:m (check-file-touched ~marbud %home file)
:: Merge so that when we unify history with the %this merge later, we
:: don't get a spurious conflict in %home
::
;< ~ bind:m (dojo ~marbud "|merge %kids our %home")
;< ~ bind:m (breach-and-hear az ~bud ~marbud)
;< ~ bind:m (real-ship az ~bud)
;< ~ bind:m (dojo ~bud "|merge %kids ~marbud %kids, =gem %this")

View File

@ -7,7 +7,8 @@
;< ~ bind:m start-simple
;< ~ bind:m (raw-ship ~bud ~)
;< ~ bind:m (raw-ship ~marbud ~)
;< file=@t bind:m (touch-file ~bud %base %foo)
;< file=@t bind:m (touch-file ~bud %home %foo)
;< ~ bind:m (dojo ~bud "|merge %kids our %home")
;< ~ bind:m (check-file-touched ~marbud %home file)
;< ~ bind:m end-simple
(pure:m *vase)

View File

@ -29,15 +29,15 @@
%^ cat 3 (get-val /mar/js/hoon)
' ~& > new-val=new-val .'
=/ js-contents
%^ cat 3 (get-val /app/publish/js/index/js)
%^ cat 3 (get-val /app/landscape/js/channel/js)
'extra'
=/ files
:~ [/sys/zuse/hoon zuse-contents]
[/mar/js/hoon mar-contents]
[/app/publish/js/index/js js-contents]
[/app/landscape/js/channel/js js-contents]
==
;< ~ bind:m (send-events (insert-files:util her desk files))
(pure:m /app/publish/js/index/js js-contents)
(pure:m /app/landscape/js/channel/js js-contents)
::
++ aqua-path
|= =path

View File

@ -1393,6 +1393,42 @@
"tslib": "^1.11.1"
}
},
"@reach/disclosure": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/disclosure/-/disclosure-0.10.5.tgz",
"integrity": "sha512-DCae28vcL7wXJNt8hySI2uaowEJ6KPDJ9U14xQMkMs0/lH7Tz8PoAO3llf7csEXk/4kzjnDpkyobDiEV3pz05g==",
"requires": {
"@reach/auto-id": "0.10.5",
"@reach/utils": "0.10.5",
"tslib": "^2.0.0"
},
"dependencies": {
"@reach/auto-id": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.10.5.tgz",
"integrity": "sha512-we4/bwjFxJ3F+2eaddQ1HltbKvJ7AB8clkN719El7Zugpn/vOjfPMOVUiBqTmPGLUvkYrq4tpuFwLvk2HyOVHg==",
"requires": {
"@reach/utils": "0.10.5",
"tslib": "^2.0.0"
}
},
"@reach/utils": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.10.5.tgz",
"integrity": "sha512-5E/xxQnUbmpI/LrufBAOXjunl96DnqX6B4zC2MO2KH/dRzLug5gM5VuOwV26egsp0jvsSPxojwciOhS43px3qw==",
"requires": {
"@types/warning": "^3.0.0",
"tslib": "^2.0.0",
"warning": "^4.0.3"
}
},
"tslib": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
}
}
},
"@reach/menu-button": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.10.1.tgz",
@ -1443,6 +1479,53 @@
"tslib": "^1.11.1"
}
},
"@reach/tabs": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/tabs/-/tabs-0.10.5.tgz",
"integrity": "sha512-oQJxQ9FwFsXo2HxEzJxFU/wP31bPVh4VU54NlhHW9f49uofyYkIKBbAhdF0Zb3TnaFp4cGKPHX39pXBYGPDkAQ==",
"requires": {
"@reach/auto-id": "0.10.5",
"@reach/descendants": "0.10.5",
"@reach/utils": "0.10.5",
"prop-types": "^15.7.2",
"tslib": "^2.0.0"
},
"dependencies": {
"@reach/auto-id": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.10.5.tgz",
"integrity": "sha512-we4/bwjFxJ3F+2eaddQ1HltbKvJ7AB8clkN719El7Zugpn/vOjfPMOVUiBqTmPGLUvkYrq4tpuFwLvk2HyOVHg==",
"requires": {
"@reach/utils": "0.10.5",
"tslib": "^2.0.0"
}
},
"@reach/descendants": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/descendants/-/descendants-0.10.5.tgz",
"integrity": "sha512-8HhN4DwS/HsPQ+Ym/Ft/XJ1spXBYdE8hqpnbYR9UcU7Nx3oDbTIdhjA6JXXt23t5avYIx2jRa8YHCtVKSHuiwA==",
"requires": {
"@reach/utils": "0.10.5",
"tslib": "^2.0.0"
}
},
"@reach/utils": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.10.5.tgz",
"integrity": "sha512-5E/xxQnUbmpI/LrufBAOXjunl96DnqX6B4zC2MO2KH/dRzLug5gM5VuOwV26egsp0jvsSPxojwciOhS43px3qw==",
"requires": {
"@types/warning": "^3.0.0",
"tslib": "^2.0.0",
"warning": "^4.0.3"
}
},
"tslib": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
}
}
},
"@reach/utils": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.10.1.tgz",
@ -1555,15 +1638,15 @@
"@styled-system/css": "^5.1.5"
}
},
"@tlon/indigo-light": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tlon/indigo-light/-/indigo-light-1.0.3.tgz",
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
},
"@tlon/indigo-react": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.1.12.tgz",
"integrity": "sha512-XBJjHwaslEwZA2r09qnoh84BeVLnd/jwZRkhq71KNABnRD+QRtg/dYNvtswueML4Km89Vx9QBtCIEIeujzrblw==",
"requires": {
"@reach/menu-button": "^0.10.0",
"@styled-system/css": "^5.1.5",
"@types/styled-system__css": "^5.0.5"
}
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.1.15.tgz",
"integrity": "sha512-Ao+1hAJjN5y1gDyT7GIUgXORPXTIpZKVVtrS++ZGYBemYMSq3oJFMIZertsSZbDHuh/TsVPenJrMUZBpV60law=="
},
"@types/anymatch": {
"version": "1.3.1",
@ -1667,14 +1750,6 @@
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/styled-system__css": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@types/styled-system__css/-/styled-system__css-5.0.8.tgz",
"integrity": "sha512-skv+daDje8vWQ8wnqVV0GCzgWVKx4gI9lJpAxWE77s52Ne6k/SCPP8HGE4BFbWDvK+qi5O3p89BGWVOQ1VHjMg==",
"requires": {
"csstype": "^2.6.6"
}
},
"@types/tapable": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz",
@ -3328,7 +3403,8 @@
"csstype": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==",
"dev": true
},
"cyclist": {
"version": "1.0.1",
@ -5949,6 +6025,15 @@
"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
},
"markdown-to-jsx": {
"version": "6.11.4",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz",
"integrity": "sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw==",
"requires": {
"prop-types": "^15.6.2",
"unquote": "^1.1.0"
}
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -5985,6 +6070,11 @@
"p-is-promise": "^2.0.0"
}
},
"memoize-one": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz",
"integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA=="
},
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -7491,6 +7581,15 @@
"tiny-warning": "^1.0.0"
}
},
"react-window": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.5.tgz",
"integrity": "sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==",
"requires": {
"@babel/runtime": "^7.0.0",
"memoize-one": ">=3.1.1 <6"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -9257,6 +9356,11 @@
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"dev": true
},
"unquote": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
"integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ="
},
"unset-value": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
@ -9565,7 +9669,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -9586,12 +9691,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -9606,17 +9713,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -9733,7 +9843,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -9745,6 +9856,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -9759,6 +9871,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -9766,12 +9879,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -9790,6 +9905,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -9851,7 +9967,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
@ -9879,7 +9996,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -9891,6 +10009,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -9968,7 +10087,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -10004,6 +10124,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -10023,6 +10144,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -10066,12 +10188,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -10552,7 +10676,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -10573,12 +10698,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -10593,17 +10720,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -10720,7 +10850,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -10732,6 +10863,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -10746,6 +10878,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -10753,12 +10886,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -10777,6 +10912,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -10838,7 +10974,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
@ -10866,7 +11003,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -10878,6 +11016,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -10955,7 +11094,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -10991,6 +11131,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -11010,6 +11151,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -11053,12 +11195,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},

View File

@ -5,13 +5,17 @@
"main": "index.js",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.1",
"@tlon/indigo-react": "^1.1.10",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.3",
"@tlon/indigo-react": "^1.1.15",
"classnames": "^2.2.6",
"codemirror": "^5.51.0",
"css-loader": "^3.5.3",
"formik": "^2.1.4",
"lodash": "^4.17.15",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1",
"mousetrap": "^1.6.5",
"prop-types": "^15.7.2",
@ -20,6 +24,7 @@
"react-dom": "^16.8.6",
"react-markdown": "^4.3.1",
"react-router-dom": "^5.0.0",
"react-window": "^1.8.5",
"remark-disable-tokenizers": "^1.0.24",
"style-loader": "^1.2.1",
"styled-components": "^5.1.0",

View File

@ -6,7 +6,8 @@ import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import './css/indigo-static.css';
import './css/fonts.css';
import { light, dark, inverted, paperDark } from '@tlon/indigo-react';
import light from './themes/light';
import dark from './themes/old-dark';
import LaunchApp from './apps/launch/app';
import ChatApp from './apps/chat/app';
@ -16,7 +17,7 @@ import LinksApp from './apps/links/app';
import PublishApp from './apps/publish/app';
import StatusBar from './components/StatusBar';
import NotFound from './components/404';
import ErrorComponent from './components/Error';
import GlobalStore from './store/store';
import GlobalSubscription from './subscription/global';
@ -85,7 +86,7 @@ class App extends React.Component {
const associations = this.state.associations ? this.state.associations : { contacts: {} };
const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : [];
const { state } = this;
const theme = state.dark ? paperDark : light;
const theme = state.dark ? dark : light;
return (
<ThemeProvider theme={theme}>
@ -161,7 +162,11 @@ class App extends React.Component {
/>
)}
/>
<Route component={NotFound} />
<Route
render={(props) => (
<ErrorComponent {...props} code={404} description="Not Found" />
)}
/>
</Switch>
</Content>
</Router>

View File

@ -94,6 +94,7 @@ interface ChatScreenState {
scrollLocked: boolean;
read: number;
active: boolean;
messages: Map<string, string>;
lastScrollHeight: number | null;
}
@ -118,6 +119,7 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
scrollLocked: false,
read: props.read,
active: true,
messages: new Map(),
// only for FF
lastScrollHeight: null,
};
@ -594,8 +596,12 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
envelopes={props.envelopes}
contacts={props.contacts}
onEnter={() => this.setState({ scrollLocked: false })}
onUnmount={(msg: string) => this.setState({
messages: this.state.messages.set(props.station, msg)
})}
s3={props.s3}
placeholder="Message..."
message={this.state.messages.get(props.station) || ""}
/>
</div>
);

View File

@ -40,7 +40,7 @@ export class ChatInput extends Component {
super(props);
this.state = {
message: '',
message: props.message,
patpSearch: null
};
@ -57,24 +57,6 @@ export class ChatInput extends Component {
this.editor = null;
// perf testing:
/* let closure = () => {
let x = 0;
for (var i = 0; i < 30; i++) {
x++;
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
{
text: `${x}`
}
);
}
setTimeout(closure, 1000);
};
this.closure = closure.bind(this);*/
moment.updateLocale('en', {
relativeTime : {
past: function(input) {
@ -99,6 +81,10 @@ export class ChatInput extends Component {
});
}
componentWillUnmount() {
this.props.onUnmount(this.state.message);
}
nextAutocompleteSuggestion(backward = false) {
const { patpSuggestions } = this.state;
let idx = patpSuggestions.findIndex(s => s === this.state.selectedSuggestion);
@ -150,6 +136,9 @@ export class ChatInput extends Component {
if(patpSearch !== null) {
this.patpAutocomplete(value, false);
}
this.setState({
message: value
});
}
getLetterType(letter) {
@ -209,29 +198,60 @@ export class ChatInput extends Component {
return;
}
let message = [];
editorMessage.split(' ').map((each) => {
if (this.isUrl(each)) {
if (message.length > 0) {
message = message.join(' ');
message = this.getLetterType(message);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
message
);
message = [];
}
const URL = this.getLetterType(each);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
URL
);
let isInCodeBlock = false;
let endOfCodeBlock = false;
editorMessage.split(/\r?\n/).forEach((line) => {
// A line of backticks enters and exits a codeblock
if (line.startsWith('```')) {
// But we need to check if we've ended a codeblock
endOfCodeBlock = isInCodeBlock;
isInCodeBlock = (!isInCodeBlock);
} else {
return message.push(each);
endOfCodeBlock = false;
}
if (isInCodeBlock) {
message.push(`\n${line}`);
} else if (endOfCodeBlock) {
message.push(`\n${line}\n`);
} else {
line.split(/\s/).forEach((str) => {
if (
(str.startsWith('`') && str !== '`')
|| (str === '`' && !isInCodeBlock)
) {
isInCodeBlock = true;
} else if (
(str.endsWith('`') && str !== '`')
|| (str === '`' && isInCodeBlock)
) {
isInCodeBlock = false;
}
if (this.isUrl(str) && !isInCodeBlock) {
if (message.length > 0) {
message = message.join(' ');
message = this.getLetterType(message);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
message
);
message = [];
}
const URL = this.getLetterType(str);
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
URL
);
} else {
message.push(str);
}
});
}
});
if (message.length > 0) {
@ -246,8 +266,24 @@ export class ChatInput extends Component {
message = [];
}
// perf:
// setTimeout(this.closure, 2000);
// perf testing:
/*let closure = () => {
let x = 0;
for (var i = 0; i < 30; i++) {
x++;
props.api.chat.message(
props.station,
`~${window.ship}`,
Date.now(),
{
text: `${x}`
}
);
}
setTimeout(closure, 1000);
};
this.closure = closure.bind(this);
setTimeout(this.closure, 2000);*/
this.editor.setValue('');
}
@ -364,6 +400,7 @@ export class ChatInput extends Component {
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 72px)' }}
>
<CodeEditor
value={this.props.message}
options={options}
editorDidMount={(editor) => {
this.editor = editor;

View File

@ -0,0 +1,28 @@
import React, { Component } from 'react';
export default class CodeContent extends Component {
render() {
const { props } = this;
const content = props.content;
const outputElement =
(Boolean(content.code.output) &&
content.code.output.length && content.code.output.length > 0) ?
(
<pre className={`f7 clamp-attachment pa1 mt0 mb0 b--gray4 b--gray1-d bl br bb`}>
{content.code.output[0].join('\n')}
</pre>
) : null;
return (
<div className="mv2">
<pre className={`f7 clamp-attachment pa1 mt0 mb0 bg-light-gray b--gray4 b--gray1-d ba`}>
{content.code.expression}
</pre>
{outputElement}
</div>
);
}
}

View File

@ -0,0 +1,65 @@
import React, { Component } from 'react';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import urbitOb from 'urbit-ob';
import { Link } from 'react-router-dom';
const DISABLED_BLOCK_TOKENS = [
'indentedCode',
'blockquote',
'atxHeading',
'thematicBreak',
'list',
'setextHeading',
'html',
'definition',
'table'
];
const DISABLED_INLINE_TOKENS = [
'autoLink',
'url',
'email',
'link',
'reference'
];
const MessageMarkdown = React.memo(props => (
<ReactMarkdown
{...props}
plugins={[[
RemarkDisableTokenizers,
{ block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }
]]} />
));
export default class TextContent extends Component {
render() {
const { props } = this;
const content = props.content;
const group = content.text.match(
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z])+([/-])?)+/
);
if ((group !== null) // matched possible chatroom
&& (group[2].length > 2) // possible ship?
&& (urbitOb.isValidPatp(group[2]) // valid patp?
&& (group[0] === content.text))) { // entire message is room name?
return (
<Link
className="bb b--black b--white-d f7 mono lh-copy v-top"
to={'/~groups/join/' + group.input}>
{content.text}
</Link>
);
} else {
return (
<section className="chat-md-message">
<MessageMarkdown source={content.text} />
</section>
);
}
}
}

View File

@ -0,0 +1,101 @@
import React, { Component } from 'react';
const IMAGE_REGEX =
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM|svg|SVG)$/;
const YOUTUBE_REGEX =
new RegExp(
String(/(?:https?:\/\/(?:[a-z]+.)?)/.source) // protocol
+ /(?:youtu\.?be(?:\.com)?\/)(?:embed\/)?/.source // short and long-links
+ /(?:(?:(?:(?:watch\?)?(?:time_continue=(?:[0-9]+))?.+v=)?([a-zA-Z0-9_-]+))(?:\?t\=(?:[0-9a-zA-Z]+))?)/.source // id
);
export default class UrlContent extends Component {
constructor() {
super();
this.state = {
unfold: false,
copied: false
};
this.unfoldEmbed = this.unfoldEmbed.bind(this);
}
unfoldEmbed(id) {
let unfoldState = this.state.unfold;
unfoldState = !unfoldState;
this.setState({ unfold: unfoldState });
const iframe = this.refs.iframe;
iframe.setAttribute('src', iframe.getAttribute('data-src'));
}
render() {
const { props } = this;
const content = props.content;
const imgMatch = IMAGE_REGEX.exec(props.content.url);
const ytMatch = YOUTUBE_REGEX.exec(props.content.url);
let contents = content.url;
if (imgMatch) {
contents = (
<img
className="o-80-d"
src={content.url}
style={{
width: '50%',
maxWidth: '250px'
}}
></img>
);
return (
<a className={`f7 lh-copy v-top word-break-all`}
href={content.url}
target="_blank"
rel="noopener noreferrer"
>
{contents}
</a>
);
} else if (ytMatch) {
contents = (
<div className={'embed-container mb2 w-100 w-75-l w-50-xl ' +
((this.state.unfold === true)
? 'db' : 'dn')}
>
<iframe
ref="iframe"
width="560"
height="315"
data-src={`https://www.youtube.com/embed/${ytMatch[1]}`}
frameBorder="0" allow="picture-in-picture, fullscreen"
>
</iframe>
</div>
);
return (
<div>
<a href={content.url}
className={`f7 lh-copy v-top bb b--white-d word-break-all`}
href={content.url}
target="_blank"
rel="noopener noreferrer">{content.url}</a>
<a className="ml2 f7 pointer lh-copy v-top"
onClick={e => this.unfoldEmbed()}>
[embed]
</a>
{contents}
</div>
);
} else {
return (
<a className={`f7 lh-copy v-top bb b--white-d b--black word-break-all`}
href={content.url}
target="_blank"
rel="noopener noreferrer"
>
{contents}
</a>
);
}
}
}

View File

@ -0,0 +1,33 @@
import React, { Component } from 'react';
import TextContent from './content/text';
import CodeContent from './content/code';
import UrlContent from './content/url';
export default class MessageContent extends Component {
render() {
const { props } = this;
const content = props.letter;
if ('code' in content) {
return <CodeContent content={content} />;
} else if ('url' in content) {
return <UrlContent content={content} />;
} else if ('me' in content) {
return (
<p className='f7 i lh-copy v-top'>
{content.me}
</p>
);
}
else if ('text' in content) {
return <TextContent content={content} />;
} else {
return null;
}
}
}

View File

@ -1,275 +1,127 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { OverlaySigil } from './overlay-sigil';
import MessageContent from './message-content';
import { uxToHex, cite, writeText } from '../../../../lib/util';
import moment from 'moment';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import urbitOb from 'urbit-ob';
const DISABLED_BLOCK_TOKENS = [
'indentedCode',
'blockquote',
'atxHeading',
'thematicBreak',
'list',
'setextHeading',
'html',
'definition',
'table'
];
const DISABLED_INLINE_TOKENS = [
'autoLink',
'url',
'email',
'link',
'reference'
];
const MessageMarkdown = React.memo(
props => (<ReactMarkdown
{...props}
plugins={[[RemarkDisableTokenizers, { block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }]]}
/>));
export class Message extends Component {
constructor() {
super();
this.state = {
unfold: false,
copied: false
};
this.unFoldEmbed = this.unFoldEmbed.bind(this);
}
unFoldEmbed(id) {
let unfoldState = this.state.unfold;
unfoldState = !unfoldState;
this.setState({ unfold: unfoldState });
const iframe = this.refs.iframe;
iframe.setAttribute('src', iframe.getAttribute('data-src'));
}
renderContent() {
const { props } = this;
const letter = props.msg.letter;
if ('code' in letter) {
const outputElement =
(Boolean(letter.code.output) &&
letter.code.output.length && letter.code.output.length > 0) ?
(
<pre className="f7 clamp-attachment pa1 mt0 mb0 b--gray4 b--gray1-d bl br bb">
{letter.code.output[0].join('\n')}
</pre>
) : null;
return (
<div className="mv2">
<pre className="f7 clamp-attachment pa1 mt0 mb0 bg-light-gray b--gray4 b--gray1-d ba">
{letter.code.expression}
</pre>
{outputElement}
</div>
);
} else if ('url' in letter) {
const imgMatch =
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|svg|SVG)$/
.exec(letter.url);
const youTubeRegex = new RegExp(String(/(?:https?:\/\/(?:[a-z]+.)?)/.source) // protocol
+ /(?:youtu\.?be(?:\.com)?\/)(?:embed\/)?/.source // short and long-links
+ /(?:(?:(?:(?:watch\?)?(?:time_continue=(?:[0-9]+))?.+v=)?([a-zA-Z0-9_-]+))(?:\?t\=(?:[0-9a-zA-Z]+))?)/.source // id
);
const ytMatch =
youTubeRegex.exec(letter.url);
let contents = letter.url;
if (imgMatch) {
contents = (
<img
className="o-80-d"
src={letter.url}
style={{
height: 'min(250px, 20vh)',
maxWidth: 'calc(100% - 36px - 1.5rem)',
objectFit: 'contain'
}}
></img>
);
return (
<a className="f7 lh-copy v-top word-break-all"
href={letter.url}
target="_blank"
rel="noopener noreferrer"
>
{contents}
</a>
);
} else if (ytMatch) {
contents = (
<div className={'embed-container mb2 w-100 w-75-l w-50-xl ' +
((this.state.unfold === true)
? 'db' : 'dn')}
>
<iframe
ref="iframe"
width="560"
height="315"
data-src={`https://www.youtube.com/embed/${ytMatch[1]}`}
frameBorder="0" allow="picture-in-picture, fullscreen"
>
</iframe>
</div>
);
return (
<div>
<a href={letter.url}
className="f7 lh-copy v-top bb b--white-d word-break-all"
target="_blank"
rel="noopener noreferrer"
>
{letter.url}
</a>
<a className="ml2 f7 pointer lh-copy v-top"
onClick={e => this.unFoldEmbed()}
>
[embed]
</a>
{contents}
</div>
);
} else {
return (
<a className="f7 lh-copy v-top bb b--white-d b--black word-break-all"
href={letter.url}
target="_blank"
rel="noopener noreferrer"
>
{contents}
</a>
);
}
} else if ('me' in letter) {
return (
<p className='f7 i lh-copy v-top'>
{letter.me}
</p>
);
} else {
const group = letter.text.match(
/([~][/])?(~[a-z]{3,6})(-[a-z]{6})?([/])(([a-z])+([/-])?)+/
);
if ((group !== null) // matched possible chatroom
&& (group[2].length > 2) // possible ship?
&& (urbitOb.isValidPatp(group[2]) // valid patp?
&& (group[0] === letter.text))) { // entire message is room name?
return (
<Link
className="bb b--black b--white-d f7 mono lh-copy v-top"
to={'/~groups/join/' + group.input}
>
{letter.text}
</Link>
);
} else {
return (
<section className="chat-md-message">
<MessageMarkdown
source={letter.text}
/>
</section>
);
}
}
}
render() {
const { props, state } = this;
const pending = props.msg.pending ? ' o-40' : '';
const datestamp = '~' + moment.unix(props.msg.when / 1000).format('YYYY.M.D');
const containerClass =
props.renderSigil ?
`w-100 f7 pl3 pt4 pr3 cf flex lh-copy ` + pending :
'w-100 pr3 cf hide-child flex' + pending;
const timestamp =
moment.unix(props.msg.when / 1000).format(
props.renderSigil ? 'hh:mm a' : 'hh:mm'
);
return (
<div
ref={this.containerRef}
className={containerClass}
style={{
minHeight: 'min-content'
}}
>
{
props.renderSigil ? (
this.renderWithSigil(timestamp)
) : (
this.renderWithoutSigil(timestamp)
)
}
</div>
);
}
renderWithSigil(timestamp) {
const { props, state } = this;
const paddingTop = props.paddingTop ? { 'paddingTop': '6px' } : '';
const datestamp =
'~' + moment.unix(props.msg.when / 1000).format('YYYY.M.D');
if (props.renderSigil) {
const timestamp = moment.unix(props.msg.when / 1000).format('hh:mm a');
const contact = props.msg.author in props.contacts
? props.contacts[props.msg.author] : false;
let name = `~${props.msg.author}`;
let color = '#000000';
let sigilClass = 'mix-blend-diff';
if (contact) {
name = (contact.nickname.length > 0)
? contact.nickname : `~${props.msg.author}`;
color = `#${uxToHex(contact.color)}`;
sigilClass = '';
}
if (`~${props.msg.author}` === name) {
name = cite(props.msg.author);
}
return (
<div
ref={this.containerRef}
className={
'w-100 f7 pl3 pt4 pr3 cf flex lh-copy ' + ' ' + pending
}
style={{
minHeight: 'min-content'
}}
>
<OverlaySigil
ship={props.msg.author}
contact={contact}
color={color}
sigilClass={sigilClass}
association={props.association}
group={props.group}
className="fl pr3 v-top bg-white bg-gray0-d"
/>
<div
className="fr clamp-message white-d"
style={{ flexGrow: 1, marginTop: -8 }}
>
<div className="hide-child" style={paddingTop}>
<p className="v-mid f9 gray2 dib mr3 c-default">
<span
className={'pointer ' + (contact.nickname || state.copied ? null : 'mono')}
onClick={() => {
writeText(props.msg.author);
this.setState({ copied: true });
setTimeout(() => {
this.setState({ copied: false });
}, 800);
}}
title={`~${props.msg.author}`}
>
{state.copied && 'Copied' || name}
</span>
</p>
<p className="v-mid mono f9 gray2 dib">{timestamp}</p>
<p className="v-mid mono f9 ml2 gray2 dib child dn-s">{datestamp}</p>
</div>
{this.renderContent()}
</div>
</div>
);
} else {
const timestamp = moment.unix(props.msg.when / 1000).format('hh:mm');
return (
<div
className={'w-100 pr3 cf hide-child flex' + pending}
style={{
minHeight: 'min-content'
}}
>
<p className="child pt2 pl2 pr1 mono f9 gray2 dib">{timestamp}</p>
<div className="fr f7 clamp-message white-d pr3 lh-copy" style={{ flexGrow: 1 }}>
{this.renderContent()}
</div>
</div>
);
const contact = props.msg.author in props.contacts
? props.contacts[props.msg.author] : false;
let name = `~${props.msg.author}`;
let color = '#000000';
let sigilClass = 'mix-blend-diff';
if (contact) {
name = (contact.nickname.length > 0)
? contact.nickname : `~${props.msg.author}`;
color = `#${uxToHex(contact.color)}`;
sigilClass = '';
}
if (`~${props.msg.author}` === name) {
name = cite(props.msg.author);
}
return (
<div className="flex w-100">
<OverlaySigil
ship={props.msg.author}
contact={contact}
color={color}
sigilClass={sigilClass}
association={props.association}
group={props.group}
className="fl pr3 v-top bg-white bg-gray0-d"
/>
<div className="fr clamp-message white-d"
style={{ flexGrow: 1, marginTop: -8 }}>
<div className="hide-child" style={paddingTop}>
<p className={`v-mid f9 gray2 dib mr3 c-default`}>
<span
className={
'mw5 dib truncate pointer ' +
(contact.nickname || state.copied ? '' : 'mono')
}
onClick={() => {
writeText(props.msg.author);
this.setState({ copied: true });
setTimeout(() => {
this.setState({ copied: false });
}, 800);
}}
title={`~${props.msg.author}`}
>
{state.copied && 'Copied' || name}
</span>
</p>
<p className={`v-mid mono f9 gray2 dib`}>{timestamp}</p>
<p className={`v-mid mono f9 ml2 gray2 dib child dn-s`}>
{datestamp}
</p>
</div>
<MessageContent letter={props.msg.letter} />
</div>
</div>
);
}
renderWithoutSigil(timestamp) {
const { props } = this;
return (
<div className="flex w-100">
<p className="child pt2 pl2 pr1 mono f9 gray2 dib">{timestamp}</p>
<div className="fr f7 clamp-message white-d pr3 lh-copy"
style={{ flexGrow: 1 }}>
<MessageContent letter={props.msg.letter} />
</div>
</div>
);
}
}

View File

@ -46,7 +46,7 @@ export class ProfileOverlay extends Component {
if (!(top || bottom)) {
bottom = `-${Math.round(OVERLAY_HEIGHT / 2)}px`;
}
const containerStyle = { top, bottom, left: '100%' };
const containerStyle = { top, bottom, left: '100%', maxWidth: '160px' };
const isOwn = window.ship === ship;
@ -79,7 +79,7 @@ export class ProfileOverlay extends Component {
</div>
<div className="pv3 pl3 pr2">
{contact && contact.nickname && (
<div className="b white-d">{contact.nickname}</div>
<div className="b white-d truncate">{contact.nickname}</div>
)}
<div className="mono gray2">{cite(`~${ship}`)}</div>
{!isOwn && (

View File

@ -6,6 +6,7 @@ import { Spinner } from '../../../components/Spinner';
import { ChatTabBar } from './lib/chat-tabbar';
import { InviteSearch } from '../../../components/InviteSearch';
import SidebarSwitcher from '../../../components/SidebarSwitch';
import Toggle from '../../../components/toggle';
export class SettingsScreen extends Component {
constructor(props) {
@ -195,18 +196,12 @@ export class SettingsScreen extends Component {
} else {
let inclusiveToggle = <div />;
if (state.targetGroup) {
// TODO toggle component into /lib
const inclusiveClasses = state.inclusive
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
inclusiveToggle = (
<div className="mt4">
<input
type="checkbox"
style={{ WebkitAppearance: 'none', width: 28 }}
className={inclusiveClasses}
onChange={this.changeInclusive}
/>
<Toggle
boolean={state.inclusive}
change={this.changeInclusive}
/>
<span className="dib f9 white-d inter ml3">
Add all members to group
</span>

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import Welcome from './lib/welcome';
import { alphabetiseAssociations } from '../../../lib/util';
import { SidebarInvite } from './lib/sidebar-invite';
import SidebarInvite from '../../../components/SidebarInvite';
import { GroupItem } from './lib/group-item';
export class Sidebar extends Component {
@ -51,10 +51,10 @@ export class Sidebar extends Component {
.map((uid) => {
return (
<SidebarInvite
uid={uid}
key={uid}
invite={props.invites[uid]}
api={props.api}
onAccept={() => props.api.invite.accept('/chat', uid)}
onDecline={() => props.api.invite.decline('/chat', uid)}
/>
);
});

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component {
render() {
@ -61,7 +62,9 @@ export class Skeleton extends Component {
width: 'calc(100% - 300px)'
}}
>
{this.props.children}
<ErrorBoundary>
{this.props.children}
</ErrorBoundary>
</div>
</div>
</div>

View File

@ -6,7 +6,6 @@ import urbitOb from 'urbit-ob';
export class JoinScreen extends Component {
constructor(props) {
super(props);
this.state = {
group: '',
error: false,
@ -21,12 +20,11 @@ export class JoinScreen extends Component {
this.componentDidUpdate();
}
componentDidUpdate(prevProps) {
componentDidUpdate() {
const { props, state } = this;
// autojoin by URL, waits for group information
if ((props.ship && props.name) &&
(prevProps && (prevProps.groups !== props.groups))) {
console.log('autojoining');
(props.contacts && (Object.keys(props.contacts).length > 0) && !state.group)) {
const incomingGroup = `${props.ship}/${props.name}`;
// push to group if already exists
if (`/ship/${incomingGroup}` in props.groups) {
@ -48,10 +46,8 @@ export class JoinScreen extends Component {
}
}
onClickJoin() {
const { props, state } = this;
console.log('i am joining');
const { group } = state;
const [ship, name] = group.split('/');
@ -101,14 +97,12 @@ export class JoinScreen extends Component {
<p className="f8 lh-copy mt3 db">Enter a <span className="mono">~ship/group-name</span></p>
<p className="f9 gray2 mb4">Group names use lowercase, hyphens, and slashes.</p>
<textarea
ref={ (e) => {
this.textarea = e;
} }
className={'f7 mono ba bg-gray0-d white-d pa3 mb2 db ' +
'focus-b--black focus-b--white-d b--gray3 b--gray2-d nowrap '}
'focus-b--black focus-b--white-d b--gray3 b--gray2-d nowrap overflow-y-hidden'}
placeholder="~zod/group-name"
spellCheck="false"
rows={1}
cols={32}
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
@ -116,7 +110,7 @@ export class JoinScreen extends Component {
}
}}
style={{
resize: 'none'
resize: 'none',
}}
onChange={this.groupChange}
value={this.state.group}

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { FixedSizeList as List } from 'react-window';
import { ContactItem } from './contact-item';
import { ShareSheet } from './share-sheet';
import { Sigil } from '../../../../lib/sigil';
@ -23,6 +25,7 @@ interface ContactSidebarProps {
}
interface ContactSidebarState {
awaiting: boolean;
memberboxHeight: number;
}
@ -31,9 +34,20 @@ export class ContactSidebar extends Component<ContactSidebarProps, ContactSideba
constructor(props) {
super(props);
this.state = {
awaiting: false
awaiting: false,
memberboxHeight: 0
};
this.memberbox = this.memberbox.bind(this);
}
memberbox(element) {
if (element) {
this.setState({
memberboxHeight: element.getBoundingClientRect().height
})
}
}
render() {
const { props } = this;
@ -145,17 +159,17 @@ export class ContactSidebar extends Component<ContactSidebarProps, ContactSideba
const detailHref = `/~groups/detail${props.path}`;
return (
<div className={'bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 ' +
<div ref={this.memberbox} className={'bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 ' +
'flex-basis-100-s flex-basis-30-ns mw5-m mw5-l mw5-xl relative ' +
'overflow-hidden flex-shrink-0 ' + responsiveClasses}
>
<div className="pt3 pb5 pl3 f8 db dn-m dn-l dn-xl">
<Link to="/~groups/">{'⟵ All Groups'}</Link>
</div>
<div className="overflow-auto h-100">
<div className="overflow-auto h-100 flex flex-column">
<Link
to={'/~groups/add' + props.path}
className={((props.path.includes(window.ship))
className={((role === "admin" || role === "moderator")
? 'dib'
: 'dn')}
>
@ -166,8 +180,20 @@ export class ContactSidebar extends Component<ContactSidebarProps, ContactSideba
>Channels</Link>
{shareSheet}
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">Members</h2>
{contactItems}
{groupItems}
<List
height={this.state.memberboxHeight}
className="flex-auto"
itemCount={contactItems.length + groupItems.length}
itemSize={44}
width="100%"
>
{({ index, style }) => (<div style={style}>{
index <= (contactItems.length - 1) // If the index is within the length of contact items,
? contactItems[index] // show a contact item
: groupItems[index - contactItems.length] // Otherwise show a group item
}</div>)}
</List>
</div>
<Spinner awaiting={this.state.awaiting} text="Removing from group..." classes="pa2 ba absolute right-1 bottom-1 b--gray1-d" />
</div>

View File

@ -1,8 +1,10 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Spinner } from '../../../../components/Spinner';
import { Toggle } from '../../../../components/toggle';
import { GroupView } from '../../../../components/Group';
import { deSig, uxToHex, writeText } from '../../../../lib/util';
import { roleForShip, resourceFromPath } from '../../../../lib/group';
export class GroupDetail extends Component {
constructor(props) {
@ -15,6 +17,7 @@ export class GroupDetail extends Component {
};
this.changeTitle = this.changeTitle.bind(this);
this.changeDescription = this.changeDescription.bind(this);
this.changePolicy = this.changePolicy.bind(this);
}
componentDidMount() {
@ -47,6 +50,17 @@ export class GroupDetail extends Component {
this.setState({ description: event.target.value });
}
changePolicy() {
this.setState({ awaiting: true }, () => {
this.props.api.groups.changePolicy(resourceFromPath(this.props.path),
Boolean(this.props.group?.policy?.open)
? { replace: { invite: { pending: [] } } }
: { replace: { open: { banned: [], banRanks: [] } } }
).then(() => this.setState({ awaiting: false }));
}
);
}
renderDetail() {
const { props } = this;
@ -175,7 +189,8 @@ export class GroupDetail extends Component {
const { group, association } = props;
const groupOwner = (deSig(props.match.params.ship) === window.ship);
const ourRole = roleForShip(group, window.ship);
const groupOwner = (ourRole === 'admin');
const deleteButtonClasses = (groupOwner) ? 'b--red2 red2 pointer bg-gray0-d' : 'b--gray3 gray3 bg-gray0-d c-default';
@ -280,7 +295,17 @@ export class GroupDetail extends Component {
}}
/>
</div>
<p className="f9 mt3 lh-copy">Delete Group</p>
<div className="relative w-100 mt6" style={{ maxWidth: '29rem' }}>
<Toggle
boolean={(Boolean(group?.policy?.invite))}
change={this.changePolicy}
/>
<span className="dib f9 white-d inter ml3">Private Group</span>
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
If private, members must be invited
</p>
</div>
<p className="f9 mt6 lh-copy">Delete Group</p>
<p className="f9 gray2 mb2">
Permanently delete this group. All current members will no longer see this group.
</p>

View File

@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { GroupItem } from './group-item';
import { Sigil } from '../../../../lib/sigil';
import { SidebarInvite } from './sidebar-invite';
import SidebarInvite from '../../../../components/SidebarInvite';
import { Welcome } from './welcome';
import { cite } from '../../../../lib/util';
@ -48,10 +48,18 @@ export class GroupSidebar extends Component {
return (
<SidebarInvite
key={uid}
api={props.api}
invite={invite}
uid={uid}
history={props.history}
onAccept={() => {
const [,,ship, name] = invite.path.split('/');
const resource = { ship, name };
api.contacts.join(resource).then(() => {
api.invite.accept('/contacts', uid);
});
props.history.push(`/~groups${invite.path}`);
}}
onDecline={() => {
api.invite.decline('/contacts', uid);
}}
/>
);
});

View File

@ -1,34 +0,0 @@
import React, { Component } from 'react';
export class SidebarInvite extends Component {
onAccept() {
const { props } = this;
const [,,ship, name] = props.invite.path.split('/');
const resource = { ship, name };
props.api.contacts.join(resource).then(() => {
props.api.invite.accept('/contacts', props.uid);
});
props.history.push(`/~groups${props.invite.path}`);
}
onDecline() {
this.props.api.invite.decline('/contacts', this.props.uid);
}
render() {
const { props } = this;
return (
<div className='pa3'>
<div className='w-100 v-mid'>
<p className="dib f8 mono white-d">
You have been invited to join {props.invite.path}
</p>
</div>
<a className="dib pointer pa2 f9 bg-green2 white mt4" onClick={this.onAccept.bind(this)}>Accept Invite</a>
<a className="dib pointer ml4 pa2 f9 bg-black white mt4" onClick={this.onDecline.bind(this)}>Decline</a>
</div>
);
}
}

View File

@ -3,6 +3,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { InviteSearch, Invites } from '../../../components/InviteSearch';
import { Spinner } from '../../../components/Spinner';
import { Toggle } from '../../../components/toggle';
import { RouteComponentProps } from 'react-router-dom';
import { Groups, GroupPolicy, Resource } from '../../../types/group-update';
import { Contacts, Rolodex } from '../../../types/contact-update';
@ -129,9 +130,6 @@ export class NewScreen extends Component<NewScreenProps, NewScreenState> {
</span>
);
}
const privacySwitchClasses = this.state.privacy
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
return (
<div className='h-100 w-100 mw6 pa3 pt4 overflow-x-hidden bg-gray0-d white-d flex flex-column'>
@ -174,11 +172,9 @@ export class NewScreen extends Component<NewScreenProps, NewScreenState> {
onChange={this.descriptionChange}
/>
<div className='mv7'>
<input
type='checkbox'
style={{ WebkitAppearance: 'none', width: 28 }}
onChange={this.groupPrivacyChange}
className={privacySwitchClasses}
<Toggle
boolean={this.state.privacy}
change={this.groupPrivacyChange}
/>
<span className='dib f9 white-d inter ml3'>Private Group</span>
<p className='f9 gray2 pt1' style={{ paddingLeft: 40 }}>

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { GroupSidebar } from './lib/group-sidebar';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component {
render() {
@ -25,7 +26,9 @@ export class Skeleton extends Component {
className={'h-100 w-100 relative ' + rightPanelClasses}
style={{ flexGrow: 1 }}
>
{props.children}
<ErrorBoundary>
{props.children}
</ErrorBoundary>
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { GroupItem } from './group-item';
import { SidebarInvite } from './sidebar-invite';
import SidebarInvite from '../../../../components/SidebarInvite';
import { Welcome } from './welcome';
import { alphabetiseAssociations } from '../../../../lib/util';
@ -17,9 +17,9 @@ export class ChannelsSidebar extends Component {
return (
<SidebarInvite
key={uid}
uid={uid}
invite={props.invites[uid]}
api={props.api}
onAccept={() => props.api.invite.accept('/link', uid)}
onDecline={() => props.api.invite.decline('/link', uid)}
/>
);
});

View File

@ -1,37 +0,0 @@
import React, { Component } from 'react';
export class SidebarInvite extends Component {
onAccept() {
this.props.api.invite.accept('/link', this.props.uid);
}
onDecline() {
this.props.api.invite.decline('/link', this.props.uid);
}
render() {
const { props } = this;
return (
<div className='w-100 bg-transparent pa4 bb b--gray4 b--gray1-d'>
<div className='w-100 v-mid'>
<p className="dib f8 mono gray4-d">
{props.invite.text}
</p>
</div>
<a
className="dib pointer pa2 f9 bg-green2 white mt4"
onClick={this.onAccept.bind(this)}
>
Accept Invite
</a>
<a
className="dib pointer ml4 pa2 f9 bg-black bg-gray0-d white mt4"
onClick={this.onDecline.bind(this)}
>
Decline
</a>
</div>
);
}
}

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { ChannelsSidebar } from './lib/channel-sidebar';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component {
render() {
@ -40,7 +41,9 @@ export class Skeleton extends Component {
flexGrow: 1
}}
>
{this.props.children}
<ErrorBoundary>
{props.children}
</ErrorBoundary>
</div>
</div>
</div>

View File

@ -65,7 +65,7 @@ export default class PublishApp extends React.Component {
this.unreadTotal = unreadTotal;
}
const { api, groups, sidebarShown } = props;
const { api, groups, sidebarShown, invites } = props;
return (
<Switch>
@ -77,7 +77,7 @@ export default class PublishApp extends React.Component {
active={'sidebar'}
rightPanelHide={true}
sidebarShown={true}
invites={props.invites}
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
@ -108,7 +108,7 @@ export default class PublishApp extends React.Component {
active={'rightPanel'}
rightPanelHide={false}
sidebarShown={sidebarShown}
invites={props.invites}
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
@ -139,7 +139,7 @@ export default class PublishApp extends React.Component {
active={'rightPanel'}
rightPanelHide={false}
sidebarShown={sidebarShown}
invites={props.invites}
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
@ -185,7 +185,7 @@ export default class PublishApp extends React.Component {
active={'rightPanel'}
rightPanelHide={false}
sidebarShown={sidebarShown}
invites={props.invites}
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}
@ -211,7 +211,7 @@ export default class PublishApp extends React.Component {
active={'rightPanel'}
rightPanelHide={false}
sidebarShown={sidebarShown}
invites={props.invites}
invites={invites}
notebooks={notebooks}
associations={associations}
contacts={contacts}
@ -263,7 +263,7 @@ export default class PublishApp extends React.Component {
active={'rightPanel'}
rightPanelHide={false}
sidebarShown={sidebarShown}
invites={props.invites}
invites={invites}
notebooks={notebooks}
selectedGroups={selectedGroups}
associations={associations}
@ -290,7 +290,7 @@ export default class PublishApp extends React.Component {
active={'rightPanel'}
rightPanelHide={false}
sidebarShown={sidebarShown}
invites={props.invites}
invites={invites}
notebooks={notebooks}
associations={associations}
selectedGroups={selectedGroups}

View File

@ -25,6 +25,29 @@ export class NewPost extends Component {
postSubmit() {
const { state } = this;
// perf testing:
/*let closure = () => {
let x = 0;
for (var i = 0; i < 5; i++) {
x++;
let rand = Math.floor(Math.random() * 1000);
const newNote = {
'new-note': {
who: this.props.ship.slice(1),
book: this.props.book,
note: stringToSymbol(this.state.title + '-' + Date.now() + '-' + rand),
title: 'asdf-' + rand + '-' + Date.now(),
body: 'asdf-' + Date.now()
}
};
this.props.api.publishAction(newNote);
}
setTimeout(closure, 3000);
};
setTimeout(closure, 2000);*/
if (state.submit && !state.disabled) {
const newNote = {
'new-note': {

View File

@ -165,8 +165,7 @@ export class NewScreen extends Component {
</p>
<Link className="green2 absolute right-0 bottom-0 f9" to="/~groups/new">Create Group</Link>
<p className="f9 gray2 db mv1 pb4">
Selected ships will be invited to read your notebook. Selected
groups will be invited to read and write notes.
Selected ships or group will be invited to read your notebook. Additional writers can be added from the &apos;subscribers&apos; panel.
</p>
</div>
<InviteSearch

View File

@ -5,6 +5,7 @@ import { NotebookPosts } from './notebook-posts';
import { Subscribers } from './subscribers';
import { Settings } from './settings';
import { cite } from '../../../../lib/util';
import { roleForShip } from '../../../../lib/group';
export class Notebook extends Component {
constructor(props) {
@ -176,11 +177,15 @@ export class Notebook extends Component {
Unsubscribe
</button>;
const subsComponent = (this.props.ship.slice(1) !== window.ship)
? null
: <Link to={subs} className={tabStyles.subscribers}>
const group = props.groups[notebook?.['writers-group-path']];
const role = group ? roleForShip(group, window.ship) : undefined;
const subsComponent = (this.props.ship.slice(1) === window.ship) || (role === 'admin')
? (<Link to={subs} className={tabStyles.subscribers}>
Subscribers
</Link>;
</Link>)
: null
const settingsComponent = (this.props.ship.slice(1) !== window.ship)
? null

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { writeText } from '../../../../lib/util';
import { Spinner } from '../../../../components/Spinner';
import { InviteSearch } from '../../../../components/InviteSearch';
import Toggle from '../../../../components/toggle';
export class Settings extends Component {
constructor(props) {
@ -133,22 +133,16 @@ export class Settings extends Component {
// don't give the option to make inclusive if we don't own the target
// group
const targetOwned = (state.targetGroup)
? state.targetGroup.slice(0, window.ship.length+3) === `/~${window.ship}/`
? Boolean(state.targetGroup.includes(`/~${window.ship}/`))
: false;
let inclusiveToggle = <div />;
if (targetOwned) {
// TODO toggle component into /lib
const inclusiveClasses = state.inclusive
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
inclusiveToggle = (
<div className="mt4">
<input
type="checkbox"
style={{ WebkitAppearance: 'none', width: 28 }}
className={inclusiveClasses}
onChange={this.changeInclusive}
/>
<Toggle
boolean={state.inclusive}
change={this.changeInclusive}
/>
<span className="dib f9 white-d inter ml3">
Add all members to group
</span>
@ -201,10 +195,6 @@ export class Settings extends Component {
}
render() {
const commentsSwitchClasses = (this.state.comments)
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
if (this.props.host.slice(1) === window.ship) {
return (
<div className="flex-column">
@ -274,11 +264,9 @@ export class Settings extends Component {
/>
</div>
<div className="mv6">
<input
type="checkbox"
style={{ WebkitAppearance: 'none', width: 28 }}
className={commentsSwitchClasses}
onChange={this.changeComments}
<Toggle
boolean={this.state.comments}
change={this.changeComments}
/>
<span className="dib f9 white-d inter ml3">Comments</span>
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
@ -293,7 +281,7 @@ export class Settings extends Component {
</div>
);
} else {
return copyShortcode;
return '';
}
}
}

View File

@ -1,38 +0,0 @@
import React, { Component } from 'react';
export class SidebarInvite extends Component {
onAccept() {
this.props.api.invite.accept('/publish', this.props.uid);
}
onDecline() {
this.props.api.invite.decline('/publish', this.props.uid);
}
render() {
const { props } = this;
return (
<div className='pa3 bb b--gray4 b--gray1-d'>
<div className='w-100 v-mid'>
<p className="dib f9 mono gray4-d">
{props.invite.text}
</p>
</div>
<a
className="dib pointer pa2 f9 bg-green2 white mt4"
onClick={this.onAccept.bind(this)}
>
Accept Invite
</a>
<a
className="dib pointer ml4 pa2 f9 bg-black bg-gray0-d white mt4"
onClick={this.onDecline.bind(this)}
>
Decline
</a>
</div>
);
}
}

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { SidebarInvite } from './sidebar-invite';
import SidebarInvite from '../../../../components/SidebarInvite';
import { Welcome } from './welcome';
import { GroupItem } from './group-item';
import { alphabetiseAssociations } from '../../../../lib/util';
@ -19,13 +19,13 @@ export class Sidebar extends Component {
const sidebarInvites = !(props.invites && props.invites['/publish'])
? null
: Object.keys(props.invites['/publish'])
.map((uid, i) => {
.map((uid) => {
return (
<SidebarInvite
uid={uid}
key={uid}
invite={props.invites['/publish'][uid]}
api={this.props.api}
key={i}
onAccept={() => props.api.invite.accept('/publish', uid)}
onDecline={() => props.api.invite.decline('/publish', uid)}
/>
);
});

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { GroupView } from '../../../../components/Group';
import { resourceFromPath } from '../../../../lib/group';
export class Subscribers extends Component {
constructor(props) {
@ -7,6 +8,7 @@ export class Subscribers extends Component {
this.redirect = this.redirect.bind(this);
this.addUser = this.addUser.bind(this);
this.removeUser = this.removeUser.bind(this);
this.addAll = this.addAll.bind(this);
}
addUser(who, path) {
@ -21,6 +23,18 @@ export class Subscribers extends Component {
window.location.href = url;
}
addAll() {
const path = this.props.notebook['writers-group-path'];
const group = path ? this.props.groups[path] : null;
const resource = resourceFromPath(path);
this.props.api.groups.addTag(
resource,
{ app: 'publish', tag: `writers-${this.props.book}` },
[...group.members].map(m => `~${m}`)
);
}
render() {
const path = this.props.notebook['writers-group-path'];
const group = path ? this.props.groups[path] : null;
@ -45,17 +59,25 @@ export class Subscribers extends Component {
];
return (
<GroupView
permissions
resourcePath={path}
group={group}
tags={tags}
appTags={appTags}
contacts={props.contacts}
groups={props.groups}
associations={props.associations}
api={this.props.api}
/>
<div>
<button
onClick={this.addAll}
className={'dib f9 black gray4-d bg-gray0-d ba pa2 mb4 b--black b--gray1-d pointer'}
>
Add all members as writers
</button>
<GroupView
permissions
resourcePath={path}
group={group}
tags={tags}
appTags={appTags}
contacts={this.props.contacts}
groups={this.props.groups}
associations={this.props.associations}
api={this.props.api}
/>
</div>
);
}
}

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { Sidebar } from './lib/sidebar';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component {
render() {
@ -36,7 +37,9 @@ export class Skeleton extends Component {
flexGrow: 1
}}
>
{props.children}
<ErrorBoundary>
{props.children}
</ErrorBoundary>
</div>
</div>
</div>

View File

@ -1,15 +0,0 @@
import React from 'react';
const NotFound = () => <div
className="fixed inter tc"
style={{
top: '50vh',
left: '50%',
transform: 'translate(-50%, -25vh)',
fontFeatureSettings: '\'zero\' 1' }}>
<h1 className="fw2 f2">404</h1>
<p className="tc">Not found.<br /><br />
If this is unexpected, email <code>support@tlon.io</code> or <a className="bb" href="https://github.com/urbit/urbit/issues/new/choose">submit an issue</a>.</p>
</div>;
export default NotFound;

View File

@ -0,0 +1,40 @@
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
type ErrorProps = RouteComponentProps & {
code?: number | string,
description?: string,
error?: Error
};
class ErrorComponent extends Component<ErrorProps> {
render () {
const { code, error, history, description } = this.props;
return (
<div className="pa4 inter tc flex flex-column items-center justify-center w-100 h-100">
<h1 className="mb4 fw2 f2" style={{
fontFeatureSettings: '\'zero\' 1',
}}>
{code ? code : 'Error'}
</h1>
{description ? <p className="tc mb4">{description}</p> : null}
{error ? (
<div className="mb4">
<p className="mb4"><code>&ldquo;{error.message}&rdquo;</code></p>
<details>
<summary>Stack trace</summary>
<pre className="tl">{error.stack}</pre>
</details>
</div>
) : null}
<p className="tc mb4">If this is unexpected, email <code>support@tlon.io</code> or <a className="bb" href="https://github.com/urbit/urbit/issues/new/choose">submit an issue</a>.</p>
{history.length > 1
? <button className="bg-light-green green2 pa2 pointer" onClick={() => history.go(-1) }>Go back</button>
: <button className="bg-light-green green2 pa2 pointer" onClick={() => history.push('/') }>Go home</button>
}
</div>
);
}
}
export default withRouter(ErrorComponent);

View File

@ -0,0 +1,35 @@
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import ErrorComponent from './Error';
class ErrorBoundary extends Component<
RouteComponentProps,
{ error?: Error }
> {
constructor(props) {
super(props);
this.state = { error: undefined };
const { history } = this.props;
history.listen((location, action) => {
if (this.state.error) {
this.setState({
error: undefined,
});
}
});
}
componentDidCatch(error) {
this.setState({ error });
return false;
}
render() {
if (this.state.error) {
return (<ErrorComponent error={this.state.error} />)
}
return this.props.children;
}
}
export default withRouter(ErrorBoundary);

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react';
import _, { capitalize } from 'lodash';
import { FixedSizeList as List } from 'react-window';
import { Dropdown } from '../apps/publish/components/lib/dropdown';
import { cite, deSig } from '../lib/util';
import { roleForShip, resourceFromPath } from '../lib/group';
@ -143,7 +145,7 @@ export class GroupView extends Component<
isAdmin(): boolean {
const us = `~${window.ship}`;
const role = roleForShip(this.props.group, us);
const role = roleForShip(this.props.group, window.ship);
const resource = resourceFromPath(this.props.resourcePath);
return resource.ship == us || role === 'admin';
}
@ -156,10 +158,15 @@ export class GroupView extends Component<
return options;
}
const role = roleForShip(group, ship);
const myRole = roleForShip(group, window.ship);
if (role === 'admin' || resource.ship === ship) {
return [];
}
if ('open' in group.policy) {
if (
'open' in group.policy // If blacklist, not whitelist
&& (this.isAdmin()) // And we can ban people (TODO: add || role === 'moderator')
&& ship !== window.ship // We can't ban ourselves
) {
options.unshift({ text: 'Ban', onSelect: () => this.banUser(ship) });
}
if (this.isAdmin() && !role) {
@ -199,52 +206,45 @@ export class GroupView extends Component<
});
}
renderMembers() {
memberElements() {
const { group, permissions } = this.props;
const { members } = group;
const isAdmin = this.isAdmin();
return (
<div className='flex flex-column'>
<div className='f9 gray2 mt6 mb3'>Members</div>
{Array.from(members).map((ship) => {
const role = roleForShip(group, deSig(ship));
const onRoleRemove =
role && isAdmin
? () => {
this.removeTag(ship, { tag: role });
}
: undefined;
const [present, missing] = this.getAppTags(ship);
const options = this.optionsForShip(ship, missing);
return Array.from(members).map((ship) => {
const role = roleForShip(group, deSig(ship));
const onRoleRemove =
role && isAdmin
? () => {
this.removeTag(ship, { tag: role });
}
: undefined;
const [present, missing] = this.getAppTags(ship);
const options = this.optionsForShip(ship, missing);
return (
<div key={ship} className='flex flex-column pv3'>
<GroupMember ship={ship} options={options}>
{((permissions && role) || present.length > 0) && (
<div className='flex mt1'>
{role && (
<Tag
onRemove={onRoleRemove}
description={capitalize(role)}
/>
)}
{present.map((tag, idx) => (
<Tag
key={idx}
onRemove={this.doIfAdmin(() =>
this.removeTag(ship, tag)
)}
description={tag.desc}
/>
))}
</div>
)}
</GroupMember>
return (
<GroupMember ship={ship} options={options}>
{((permissions && role) || present.length > 0) && (
<div className='flex mt1'>
{role && (
<Tag
onRemove={onRoleRemove}
description={capitalize(role)}
/>
)}
{present.map((tag, idx) => (
<Tag
key={idx}
onRemove={this.doIfAdmin(() =>
this.removeTag(ship, tag)
)}
description={tag.desc}
/>
))}
</div>
);
})}
</div>
);
)}
</GroupMember>
);
})
}
setInvites(invites: Invites) {
@ -321,6 +321,7 @@ export class GroupView extends Component<
render() {
const { group, resourcePath, className } = this.props;
const resource = resourceFromPath(resourcePath);
const memberElements = this.memberElements();
return (
<div className={className}>
@ -332,7 +333,17 @@ export class GroupView extends Component<
</div>
{'invite' in group.policy && this.renderInvites(group.policy)}
{'open' in group.policy && this.renderBanned(group.policy)}
{this.renderMembers()}
<div className='flex flex-column'>
<div className='f9 gray2 mt6 mb3'>Members</div>
<List
height={500}
itemCount={memberElements.length}
itemSize={44}
width="100%"
>
{({ index, style }) => <div key={index} style={style} className='flex flex-column pv3'>{memberElements[index]}</div>}
</List>
</div>
<Spinner
awaiting={this.state.awaiting}

View File

@ -1,33 +1,26 @@
import React, { Component } from 'react';
import { Invite } from '../types/invite-update';
export class SidebarInvite extends Component {
onAccept() {
this.props.api.invite.accept('/chat', this.props.uid);
}
onDecline() {
this.props.api.invite.decline('/chat', this.props.uid);
}
export class SidebarInvite extends Component<{invite: Invite, onAccept: Function, onDecline: Function}, {}> {
render() {
const { props } = this;
return (
<div className='w-100 bg-transparent pa4 bb b--gray4 b--gray1-d'>
<div className='w-100 bg-white bg-gray0-d pa4 bb b--gray4 b--gray1-d z-5' style={{position: 'sticky', top: 0}}>
<div className='w-100 v-mid'>
<p className="dib f8 mono gray4-d">
{props.invite.path}
{props.invite.text ? props.invite.text : props.invite.path}
</p>
</div>
<a
className="dib pointer pa2 f9 bg-green2 white mt4"
onClick={this.onAccept.bind(this)}
onClick={this.props.onAccept.bind(this)}
>
Accept Invite
</a>
<a
className="dib pointer ml4 pa2 f9 bg-black bg-gray0-d white mt4"
onClick={this.onDecline.bind(this)}
onClick={this.props.onDecline.bind(this)}
>
Decline
</a>
@ -36,3 +29,4 @@ export class SidebarInvite extends Component {
}
}
export default SidebarInvite;

View File

@ -0,0 +1,20 @@
import React, { Component } from 'react';
export class Toggle extends Component {
render() {
const switchClasses = (this.props.boolean)
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
return (
<input
type="checkbox"
style={{ WebkitAppearance: 'none', width: 28 }}
className={switchClasses}
onChange={this.props.change}
/>
);
}
}
export default Toggle;

View File

@ -6,6 +6,7 @@ import {
Group,
Tags,
GroupPolicy,
GroupPolicyDiff,
OpenPolicyDiff,
OpenPolicy,
InvitePolicyDiff,
@ -24,7 +25,6 @@ function decodeGroup(group: Enc<Group>): Group {
tags: decodeTags(group.tags),
policy: decodePolicy(group.policy),
};
console.log(res);
return res;
}
@ -179,6 +179,8 @@ export default class GroupReducer<S extends GroupState> {
this.openChangePolicy(diff.open, policy);
} else if ('invite' in policy && 'invite' in diff) {
this.inviteChangePolicy(diff.invite, policy);
} else if ('replace' in diff) {
state.groups[resourcePath].policy = diff.replace;
} else {
console.log('bad policy diff');
}

View File

@ -50,7 +50,6 @@ export default class InviteReducer<S extends InviteState> {
accepted(json: InviteUpdate, state: S) {
const data = _.get(json, 'accepted', false);
if (data) {
console.log(data);
delete state.invites[data.path][data.uid];
}
}

View File

@ -11,12 +11,10 @@ export default class MetadataReducer<S extends MetadataState> {
reduce(json: Cage, state: S) {
let data = json['metadata-update']
if (data) {
console.log('data: ', data);
this.associations(data, state);
this.add(data, state);
this.update(data, state);
this.remove(data, state);
console.log('state: ', state);
}
}

View File

@ -48,4 +48,3 @@ export default class PublishStore extends BaseStore {
this.responseReducer.reduce(data, this.state);
}
}

View File

@ -0,0 +1,169 @@
import baseStyled, { ThemedStyledInterface } from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const theme = {
colors: {
white: base.white,
black: base.black,
gray: scales.black60,
lightGray: scales.black30,
washedGray: scales.black10,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red10,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
16, // 4
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["768px", "1024px", "1440px", "2200px"],
};
export type Theme = typeof theme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
export default theme;

View File

@ -0,0 +1,184 @@
import baseStyled, { ThemedStyledInterface } from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white05: "rgba(255,255,255,0.05)",
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red05: "rgba(255,65,54,0.05)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow05: "rgba(255,199,0,0.05)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green05: "rgba(0,159,101,0.05)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue05: "rgba(0,142,255,0.05)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const util = {
cyan: "#00FFFF",
magenta: "#FF00FF",
yellow: "#FFFF00",
black: "#000000",
gray0: "#333333"
};
const theme = {
colors: {
white: util.gray0,
black: base.white,
gray: scales.white60,
lightGray: scales.white30,
washedGray: scales.white05,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
util: util,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["768px", "1024px", "1440px", "2200px"],
};
export type Theme = typeof theme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
export default theme;