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):** **System (please supply the following information, if relevant):**
- OS: [e.g. macOS, linux64, FreeBSD] - OS: [e.g. macOS, linux64, FreeBSD]
- Vere and Urbit OS versions - 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** **Additional context**
Add any other context about the problem here. 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):** **Desktop (please complete the following information):**
- OS: [e.g. MacOS 10.15.3] - OS: [e.g. MacOS 10.15.3]
- Browser [e.g. chrome, safari] - 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):** **Smartphone (please complete the following information):**
- Device: [e.g. iPhone6] - Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1] - OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari] - 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** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

View File

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

View File

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

View File

@ -18,17 +18,19 @@
state-1 state-1
state-2 state-2
state-3 state-3
state-4
state-5
state-6
state-7
== ==
:: ::
+$ state-3 +$ state-7 [%7 state-base]
$: %3 +$ state-6 [%6 state-base]
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 +$ state-1
$: %1 $: %1
loaded-cards=* loaded-cards=*
@ -52,7 +54,7 @@
$% [%chat-update update:store] $% [%chat-update update:store]
== ==
-- --
=| state-3 =| state-7
=* state - =* state -
:: ::
%- agent:dbug %- agent:dbug
@ -81,8 +83,20 @@
=/ old !<(versioned-state old-vase) =/ old !<(versioned-state old-vase)
=| cards=(list card) =| cards=(list card)
|- |-
?: ?=(%3 -.old) ?: ?=(%7 -.old)
[cards this(state 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) ?: ?=(%2 -.old)
=. cards =. cards
%+ weld cards %+ weld cards
@ -321,7 +335,7 @@
?+ mark (on-poke:def mark vase) ?+ mark (on-poke:def mark vase)
%json (poke-json:cc !<(json vase)) %json (poke-json:cc !<(json vase))
%chat-action (poke-chat-action:cc !<(action:store vase)) %chat-action (poke-chat-action:cc !<(action:store vase))
%noun [~ state] %noun (poke-noun:cc !<(?(%fix-dm %fix-out-of-sync) vase))
:: ::
%chat-hook-action %chat-hook-action
(poke-chat-hook-action:cc !<(action:hook vase)) (poke-chat-hook-action:cc !<(action:hook vase))
@ -383,6 +397,81 @@
|_ bol=bowl:gall |_ bol=bowl:gall
++ grp ~(. grpl bol) ++ 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 ++ poke-json
|= jon=json |= jon=json
^- (quip card _state) ^- (quip card _state)

View File

@ -287,10 +287,9 @@
*@uv *@uv
=/ parent (scot %p ship.u.ota) =/ parent (scot %p ship.u.ota)
=+ .^(=cass:clay %cs /[parent]/[desk.u.ota]/1/late/foo) =+ .^(=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)) .^(@uv %cz /[parent]/[desk.u.ota]/(scot %ud ud.cass))
-- --
++ on-agent on-agent:def ++ on-agent on-agent:def
++ on-fail on-fail:def ++ on-fail on-fail:def
-- --

View File

@ -1,7 +1,7 @@
/- glob /- glob
/+ default-agent, verb, dbug /+ 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))] +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states +$ all-states
$% state-0 $% state-0
@ -58,7 +58,6 @@
++ on-load ++ on-load
|= old-state=vase |= old-state=vase
^- (quip card _this) ^- (quip card _this)
~& > %initting
=+ !<(old=all-states old-state) =+ !<(old=all-states old-state)
?> ?=(%0 -.old) ?> ?=(%0 -.old)
?~ glob.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 /+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln
|% |%
+$ state +$ state
$: %8 $: %9
drum=state:drum
helm=state:helm
kiln=state:kiln
==
+$ state-7
$: %7
drum=state:drum drum=state:drum
helm=state:helm helm=state:helm
kiln=state:kiln kiln=state:kiln
== ==
+$ any-state +$ any-state
$% state $% state
state-7
[ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)] [ver=?(%1 %2 %3 %4 %5 %6) lac=(map @tas fin-any-state)]
[%7 drum=state:drum helm=state:helm kiln=state:kiln] [%7 drum=state:drum helm=state:helm kiln=state:kiln]
[%8 drum=state:drum helm=state:helm kiln=state:kiln]
== ==
+$ any-state-tuple +$ any-state-tuple
$: drum=any-state:drum $: drum=any-state:drum

View File

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

View File

@ -54,6 +54,7 @@
[%3 state-three] [%3 state-three]
[%4 state-three] [%4 state-three]
[%5 state-three] [%5 state-three]
[%6 state-three]
== ==
:: ::
+$ metadata-delta +$ metadata-delta
@ -69,7 +70,7 @@
== ==
-- --
:: ::
=| [%5 state-three] =| [%6 state-three]
=* state - =* state -
%- agent:dbug %- agent:dbug
%+ verb | %+ verb |
@ -86,7 +87,6 @@
:_ this :_ this
:~ [%pass /view-bind %arvo %e %connect [~ /'publish-view'] %publish] :~ [%pass /view-bind %arvo %e %connect [~ /'publish-view'] %publish]
[%pass /read/paths %arvo %c %warp our.bol q.byk.bol `rav] [%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]) (invite-poke:main [%create /publish])
:* %pass /invites %agent [our.bol %invite-store] %watch :* %pass /invites %agent [our.bol %invite-store] %watch
/invitatory/publish /invitatory/publish
@ -218,6 +218,26 @@
== ==
:: ::
%5 %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)] [cards this(state p.old-state)]
== ==
++ convert-notebook-3-4 ++ convert-notebook-3-4
@ -995,6 +1015,22 @@
[~ state] [~ state]
:_ state :_ state
%- zing %- 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) %+ turn ~(tap in ships)
|= who=@p |= who=@p
?. (allowed who %read u.book) ?. (allowed who %read u.book)
@ -1226,12 +1262,19 @@
^- [(list card) write=path read=path] ^- [(list card) write=path read=path]
?> ?=(^ group-path.group) ?> ?=(^ group-path.group)
=/ scry-path =/ scry-path
;:(welp /(scot %p our.bol)/group-store/(scot %da now.bol) [%groups group-path.group] /noun) ;: welp
=/ grp .^((unit ^group) %gx scry-path) /(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 ?: use-preexisting.group
?~ grp !! ?~ grp !!
?. (is-managed group-path.group) !! ?. (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 =/ =policy
*open:policy *open:policy
@ -1684,10 +1727,9 @@
?> ?=(^ subscribers.u.book) ?> ?=(^ subscribers.u.book)
=/ cards=(list card) =/ cards=(list card)
~[(delete-dir pax)] ~[(delete-dir pax)]
=/ rid=resource =/ rid=resource
(de-path:resource writers.u.book) (de-path:resource writers.u.book)
=? cards (is-managed:grup rid) =? cards !(is-managed:grup rid)
[(group-poke %remove-group rid ~) cards] [(group-poke %remove-group rid ~) cards]
[cards state] [cards state]
:: %del-note: :: %del-note:
@ -1791,6 +1833,10 @@
?> (team:title our.bol src.bol) ?> (team:title our.bol src.bol)
=/ join-wire=wire =/ join-wire=wire
/join-group/[(scot %p who.act)]/[book.act] /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 =/ rid=resource
[who.act book.act] [who.act book.act]
=/ =cage =/ =cage
@ -1811,12 +1857,16 @@
(de-path:resource writers.book) (de-path:resource writers.book)
=/ =group =/ =group
(need (scry-group:grup rid)) (need (scry-group:grup rid))
:_ state(books (~(del by books) who.act book.act)) =/ cards=(list card)
:~ `card`[%pass wir %agent [who.act %publish] %leave ~] :~ [%pass wir %agent [who.act %publish] %leave ~]
`card`[%give %fact [/primary]~ %publish-primary-delta !>(del)] [%give %fact [/primary]~ %publish-primary-delta !>(del)]
(group-proxy-poke who.act %remove-members rid (sy our.bol ~)) ==
=? cards hidden.group
%+ weld cards
:~ (group-proxy-poke who.act %remove-members rid (sy our.bol ~))
(group-poke %remove-group rid ~) (group-poke %remove-group rid ~)
== ==
[cards state(books (~(del by books) who.act book.act))]
:: %read :: %read
:: ::
%read %read
@ -1952,6 +2002,19 @@
/noun /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 ++ emit-metadata
|= del=metadata-delta |= del=metadata-delta
^- (list card) ^- (list card)
@ -2044,9 +2107,11 @@
(emit-updates-and-state host.del book.del data.del del sty) (emit-updates-and-state host.del book.del data.del del sty)
=/ rid=resource =/ rid=resource
(de-path:resource writers.data.del) (de-path:resource writers.data.del)
=? cards !=(our.bol entity.rid)
:_ cards
(group-pull-hook-poke [%add host.del rid])
:_ state :_ 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 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 public-key
=/ cub (pit:nu:crub:crypto 512 (shaz (jam mon life eny))) =/ cub (pit:nu:crub:crypto 512 (shaz (jam mon life eny)))
=/ =seed:able:jael =/ =seed:able:jael
[mon 1 sec:ex:cub ~] [mon life sec:ex:cub ~]
%- %- slog %- %- slog
:~ leaf+"moon: {(scow %p mon)}" :~ leaf+"moon: {(scow %p mon)}"
leaf+(scow %uw (jam seed)) leaf+(scow %uw (jam seed))

View File

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

View File

@ -5,5 +5,5 @@
[%tang >timers< ~] [%tang >timers< ~]
.^ (list [date=@da =duct]) .^ (list [date=@da =duct])
%bx %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 %s3-store
%file-server %file-server
%glob %glob
%graph-store
== ==
:: ::
++ deft-fish :: default connects ++ deft-fish :: default connects
@ -206,7 +207,7 @@
== ==
:: ::
++ on-load ++ 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 =< se-abet =< se-view
=. sat old =. sat old
=. dev (~(gut by bin) ost *source) =. dev (~(gut by bin) ost *source)
@ -233,6 +234,8 @@
=? ..on-load (lte hood-version %8) =? ..on-load (lte hood-version %8)
=> (se-born | %home %group-push-hook) => (se-born | %home %group-push-hook)
(se-born | %home %group-pull-hook) (se-born | %home %group-pull-hook)
=? ..on-load (lte hood-version %9)
(se-born | %home %graph-store)
..on-load ..on-load
:: ::
++ reap-phat :: ack connect ++ reap-phat :: ack connect

View File

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

View File

@ -59,7 +59,6 @@
|~ [term tang] |~ [term tang]
*[(list card) _^|(..on-init)] *[(list card) _^|(..on-init)]
:: +resource-for-update: get affected resource from an update :: +resource-for-update: get affected resource from an update
++ resource-for-update ++ resource-for-update
|~ vase |~ vase
*(unit resource) *(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 +$ update
$% [%tracking tracking=(map resource ship)] $% [%tracking tracking=(map resource ship)]
== ==
::
-- --

View File

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

View File

@ -1121,17 +1121,32 @@
?> =(rcvr-life.shut-packet our-life.channel) ?> =(rcvr-life.shut-packet our-life.channel)
:: non-galaxy: update route with heard lane or forwarded lane :: non-galaxy: update route with heard lane or forwarded lane
:: ::
=? route.peer-state =? route.peer-state !=(%czar (clan:title her.channel))
?: =(%czar (clan:title her.channel)) :: if new packet is direct, use that. otherwise, if the new new
%.n :: and old lanes are indirect, use the new one. if the new lane
=/ is-old-direct=? ?=([~ %& *] route.peer-state) :: is indirect but the old lane is direct, then if the lanes are
=/ is-new-direct=? ?=(~ origin.packet) :: identical, don't mark it indirect; if they're not identical,
:: old direct takes precedence over new indirect :: use the new lane and mark it indirect.
:: ::
|(is-new-direct !is-old-direct) :: 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).
:: ::
?~ origin.packet :: 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] `[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] `[direct=%.n u.origin.packet]
:: perform peer-specific handling of packet :: perform peer-specific handling of packet
:: ::

View File

@ -109,6 +109,10 @@
mut/(list (trel path lobe cage)) :: mutations mut/(list (trel path lobe cage)) :: mutations
== :: == ::
:: ::
:: Over-the-wire backfill request
::
+$ fill [=desk =lobe]
::
:: Ford cache :: Ford cache
:: ::
+$ ford-cache +$ ford-cache
@ -214,18 +218,29 @@
:: requests, and a possible nako if we've received data from the other ship and :: requests, and a possible nako if we've received data from the other ship and
:: are in the process of validating it. :: are in the process of validating it.
:: ::
++ rind :: request manager +$ rind :: request manager
$: nix/@ud :: request index $: nix=@ud :: request index
bom/(map @ud {p/duct q/rave}) :: outstanding bom=(map @ud update-state) :: outstanding
fod/(map duct @ud) :: current requests fod=(map duct @ud) :: current requests
haw/(map mood (unit cage)) :: simple cache 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 :: Result of a subscription
:: ::
++ sub-result ++ sub-result
$% [%blab =mood data=(each cage lobe)] $% [%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] [%balk cage=(unit (each cage lobe)) =mood]
[%blas moods=(set mood)] [%blas moods=(set mood)]
[%blub ~] [%blub ~]
@ -246,7 +261,7 @@
:: Generally used when we store a request in our state somewhere. :: Generally used when we store a request in our state somewhere.
:: ::
++ cach (unit (unit (each cage lobe))) :: cached result ++ 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 ++ rove :: stored request
$% [%sing =mood] :: single request $% [%sing =mood] :: single request
[%next =mood aeon=(unit aeon) =cach] :: next version of one [%next =mood aeon=(unit aeon) =cach] :: next version of one
@ -1134,13 +1149,13 @@
:: Give next step in a subscription. :: Give next step in a subscription.
:: ::
++ bleb ++ 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] ~] %^ blab hen [%w [%ud ins] ~]
:- %& :- %&
?~ hip ?~ hip
[%null [%atom %n ~] ~] [%null [%atom %n ~] ~]
[%nako !>((make-nako:ze u.hip))] [%nako !>((make-nako:ze ver u.hip))]
:: ::
:: Tell subscriber that subscription is done. :: Tell subscriber that subscription is done.
:: ::
@ -1183,7 +1198,7 @@
=/ =desk p.riff =/ =desk p.riff
=/ =wire /warp-index/(scot %p ship)/(scot %tas desk)/(scot %ud index) =/ =wire /warp-index/(scot %p ship)/(scot %tas desk)/(scot %ud index)
=/ =path [%question 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. :: Create a request that cannot be filled immediately.
:: ::
@ -1210,7 +1225,7 @@
(send-over-ames hen her inx syd `rave) (send-over-ames hen her inx syd `rave)
%= +>+.$ %= +>+.$
nix.u.ref +(nix.u.ref) 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) fod.u.ref (~(put by fod.u.ref) hen inx)
== ==
:: ::
@ -2003,6 +2018,7 @@
:: bob's. :: bob's.
:: ::
?: ?=(%init germ) ?: ?=(%init germ)
?> ?=(~ bob-yaki)
&+`[conflicts=~ new=|+ali-yaki lat=~] &+`[conflicts=~ new=|+ali-yaki lat=~]
:: ::
=/ bob-yaki (need bob-yaki) =/ bob-yaki (need bob-yaki)
@ -2589,7 +2605,7 @@
:: and then waiting if the subscription range extends into the future. :: and then waiting if the subscription range extends into the future.
:: ::
++ start-request ++ start-request
|= [for=(unit ship) rav=rave] |= [for=(unit [ship @ud]) rav=rave]
^+ ..start-request ^+ ..start-request
=^ [new-sub=(unit rove) sub-results=(list sub-result)] fod.dom =^ [new-sub=(unit rove) sub-results=(list sub-result)] fod.dom
(try-fill-sub for (rave-to-rove rav)) (try-fill-sub for (rave-to-rove rav))
@ -2612,9 +2628,9 @@
?> ?=(^ ref) ?> ?=(^ ref)
=+ ruv=(~(get by bom.u.ref) inx) =+ ruv=(~(get by bom.u.ref) inx)
?~ ruv +>.$ ?~ ruv +>.$
=/ rav=rave q.u.ruv =/ rav=rave rave.u.ruv
?: ?=(%many -.rav) ?: ?=(%many -.rav)
(take-foreign-update inx rut) abet:(apex:(foreign-update inx) rut)
?~ rut ?~ rut
:: nothing here, so cache that :: nothing here, so cache that
:: ::
@ -2689,36 +2705,138 @@
!>(;;(@uvI q.page)) !>(;;(@uvI q.page))
-- --
:: ::
:: A full foreign update. Validate and apply to our local cache of :: Respond to backfill request
:: their state.
:: ::
++ take-foreign-update :: Maybe should verify the requester is allowed to access this blob?
|= [inx=@ud rut=(unit rand)] ::
^+ ..take-foreign-update ++ 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) ?> ?=(^ ref)
=/ [sat=update-state lost=?]
=/ ruv (~(get by bom.u.ref) inx) =/ ruv (~(get by bom.u.ref) inx)
?~ ruv ?~ ruv
~& [%clay-foreign-update-lost her syd inx] ~& [%clay-foreign-update-lost her syd inx]
..take-foreign-update [*update-state &]
=. hen p.u.ruv [u.ruv |]
=/ =rave q.u.ruv =/ done=? |
?> ?=(%many -.rave) =. hen duct.sat
|^ |%
?~ rut ++ abet
done ^+ ..foreign-update
=. lim ?.(?=(%da -.to.moat.rave) lim p.to.moat.rave) ?: lost
?> ?=(%nako p.r.u.rut) ..foreign-update
=/ nako ;;(nako q.r.u.rut) ?: done
=. ..take-foreign-update
=< ?>(?=(^ ref) .)
(apply-foreign-update nako)
done
::
++ done
=: bom.u.ref (~(del by bom.u.ref) inx) =: bom.u.ref (~(del by bom.u.ref) inx)
bom.u.ref (~(del by bom.u.ref) hen) fod.u.ref (~(del by fod.u.ref) hen)
== ==
=<(?>(?=(^ ref) .) wake) =<(?>(?=(^ ref) .) wake)
=. bom.u.ref (~(put by bom.u.ref) inx sat)
..foreign-update
::
++ 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. :: When we get a %w foreign update, store this in our state.
:: ::
@ -2728,7 +2846,7 @@
:: ::
++ apply-foreign-update ++ apply-foreign-update
|= =nako |= =nako
^+ ..take-foreign-update ^+ ..abet
:: hit: updated commit-hashes by @ud case :: hit: updated commit-hashes by @ud case
:: nut: new commit-hash/commit pairs :: nut: new commit-hash/commit pairs
:: hut: updated commits by hash :: hut: updated commits by hash
@ -2765,12 +2883,19 @@
$(aeon +(aeon)) $(aeon +(aeon))
:: produce updated state :: produce updated state
:: ::
=/ =rave rave:(~(got by bom.u.ref) inx)
?> ?=(%many -.rave)
=: let.dom (max let.nako let.dom) =: let.dom (max let.nako let.dom)
hit.dom hit hit.dom hit
hut.ran hut hut.ran hut
lat.ran lat 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 :: fire function if request is in future
@ -2862,8 +2987,9 @@
:: Try to fill a subscription :: Try to fill a subscription
:: ::
++ try-fill-sub ++ try-fill-sub
|= [for=(unit ship) rov=rove] |= [far=(unit [=ship ver=@ud]) rov=rove]
^- [[new-sub=(unit rove) (list sub-result)] ford-cache] ^- [[new-sub=(unit rove) (list sub-result)] ford-cache]
=/ for=(unit ship) ?~(far ~ `ship.u.far)
?- -.rov ?- -.rov
%sing %sing
=/ cache-value=(unit (unit cage)) =/ cache-value=(unit (unit cage))
@ -3075,6 +3201,7 @@
:: ::
[`rov ~] [`rov ~]
=/ to-aeon (case-to-aeon to.moat.rov) =/ to-aeon (case-to-aeon to.moat.rov)
=/ ver ?~(far %1 ver.u.far)
?~ to-aeon ?~ to-aeon
:: we're in the middle of the range, so produce what we can, :: we're in the middle of the range, so produce what we can,
:: but don't end the subscription :: but don't end the subscription
@ -3092,7 +3219,7 @@
~ ~
:: else changes, so produce them :: 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 :: we're past the end of the range, so end subscription
:: ::
:- ~ :- ~
@ -3103,7 +3230,7 @@
=/ bleb=(list sub-result) =/ bleb=(list sub-result)
?: =(lobes.rov new-lobes) ?: =(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 :: end subscription
:: ::
=/ blub=(list sub-result) =/ blub=(list sub-result)
@ -3111,17 +3238,6 @@
(weld bleb blub) (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 :: 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. :: Creates a nako of all the changes between a and b.
:: ::
++ make-nako ++ make-nako
|= {a/aeon b/aeon} |= [ver=@ud a=aeon b=aeon]
^- nako ^- nako
:+ ?> (lte b let.dom) :+ ?> (lte b let.dom)
|- |-
@ -3219,7 +3335,7 @@
b b
?: =(0 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 :: 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 :: ones we found before `a`. Then convert the takos to yakis and also get
:: all the data in all the yakis. :: 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 ++ data-twixt-takos
|= {a/(unit tako) b/tako} |= [plops=? a=(unit tako) b=tako]
^- {(set yaki) (set plop)} ^- [(set yaki) (set plop)]
=+ old=?~(a ~ (reachable-takos u.a)) =+ old=?~(a ~ (reachable-takos u.a))
=/ yal/(set tako) =/ yal=(set tako)
%- silt %- silt
%+ skip %+ skip
~(tap in (reachable-takos b)) ~(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)) :- (silt (turn ~(tap in yal) tako-to-yaki))
?. plops
~
(silt (turn ~(tap in (new-lobes (new-lobes ~ old) yal)) lobe-to-blob)) (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 :: Get all the lobes that are referenced in `a` except those that are
@ -3728,7 +3849,7 @@
:: ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
=| :: instrument state =| :: instrument state
$: ver=%3 :: vane version $: ver=%4 :: vane version
ruf=raft :: revision tree ruf=raft :: revision tree
== :: == ::
|= [our=ship now=@da eny=@uvJ ski=sley] :: current invocation |= [our=ship now=@da eny=@uvJ ski=sley] :: current invocation
@ -3939,7 +4060,14 @@
=^ for req =^ for req
?: ?=(%warp -.req) ?: ?=(%warp -.req)
[~ 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 wer.req rif.req]
:: ::
?> ?=(%warp -.req) ?> ?=(%warp -.req)
@ -3957,8 +4085,14 @@
=* pax path.plea.req =* pax path.plea.req
=* res payload.plea.req =* res payload.plea.req
:: ::
?> ?=({%question *} pax) ?: ?=([%backfill *] pax)
=+ ryf=;;(riff res) =+ ;;(=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 ~] :~ [hen %give %done ~]
=/ =wire =/ =wire
@ -3971,11 +4105,58 @@
!: !:
|^ |^
|= old=any-state |= old=any-state
~! [old=old new=*state-3] ~! [old=old new=*state-4]
=? old ?=(%2 -.old) (load-2-to-3 old) =? old ?=(%2 -.old) (load-2-to-3 old)
?> ?=(%3 -.old) =? old ?=(%3 -.old) (load-3-to-4 old)
?> ?=(%4 -.old)
..^^$(ruf +.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 ++ load-2-to-3
|= =state-2 |= =state-2
^- state-3 ^- state-3
@ -4005,11 +4186,11 @@
:- %ford-fusion :- %ford-fusion
[leaf+"queued merge canceled due to upgrade to ford fusion" ~] [leaf+"queued merge canceled due to upgrade to ford fusion" ~]
`[duct %slip %b %drip !>([%mere %| err])] `[duct %slip %b %drip !>([%mere %| err])]
^- rom=room ^- rom=room-3
:- hun.rom.state-2 :- hun.rom.state-2
%- ~(urn by dos.rom.state-2) %- ~(urn by dos.rom.state-2)
|= [=desk =dojo-2] |= [=desk =dojo-2]
^- dojo ^- dojo-3
=- dojo-2(dom -) =- dojo-2(dom -)
^- dome ^- dome
=/ fer=(unit reef-cache) =/ fer=(unit reef-cache)
@ -4019,23 +4200,22 @@
(~(got by hut.ran.state-2) (~(got by hit.dom.dojo-2) let.dom.dojo-2)) (~(got by hut.ran.state-2) (~(got by hit.dom.dojo-2) let.dom.dojo-2))
`(build-reef desk q.yaki) `(build-reef desk q.yaki)
[ank let hit lab mim fod=*ford-cache fer=fer]:[dom.dojo-2 .] [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) %- ~(run by hoy.state-2)
|= =rung-2 |= =rung-2
^- rung ^- rung-3
%- ~(run by rus.rung-2) %- ~(run by rus.rung-2)
|= =rede-2 |= =rede-2
^- rede ^- rede-3
=- rede-2(ref ref.-, dom dom.-) =- rede-2(ref ref.-, dom dom.-)
:- ^- dom=dome :- ^- dom=dome
[ank let hit lab mim fod=*ford-cache fer=~]:[dom.rede-2 .] [ank let hit lab mim fod=*ford-cache fer=~]:[dom.rede-2 .]
^- ref=(unit rind) ^- ref=(unit rind-3)
?~ ref.rede-2 ?~ ref.rede-2
~ ~
:: TODO: somehow call +wake later to notify subscribers
:- ~ :- ~
^- rind ^- rind-3
=/ rin=rind [nix bom fod haw]:u.ref.rede-2 =/ rin=rind-3 [nix bom fod haw]:u.ref.rede-2
=. rin =. rin
=/ pur=(list [inx=@ud =rand *]) ~(tap by pur.u.ref.rede-2) =/ pur=(list [inx=@ud =rand *]) ~(tap by pur.u.ref.rede-2)
|- ^+ rin |- ^+ rin
@ -4138,8 +4318,46 @@
-- --
-- --
:: ::
+$ any-state $%(state-3 state-2) +$ any-state $%(state-4 state-3 state-2)
+$ state-3 [%3 raft] +$ 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 +$ state-2
$: %2 $: %2
rom=room-2 :: domestic rom=room-2 :: domestic
@ -4156,7 +4374,7 @@
dos/(map desk dojo-2) :: native desk dos/(map desk dojo-2) :: native desk
== :: == ::
+$ dojo-2 +$ dojo-2
$: qyx/cult :: subscribers $: qyx/cult-3 :: subscribers
dom/dome-2 :: desk state dom/dome-2 :: desk state
per/regs :: read perms per path per/regs :: read perms per path
pew/regs :: write perms per path pew/regs :: write perms per path
@ -4172,7 +4390,7 @@
+$ rede-2 +$ rede-2
$: lim/@da :: complete to $: lim/@da :: complete to
ref/(unit rind-2) :: outgoing requests ref/(unit rind-2) :: outgoing requests
qyx/cult :: subscribers qyx/cult-3 :: subscribers
dom/dome-2 :: revision state dom/dome-2 :: revision state
per/regs :: read perms per path per/regs :: read perms per path
pew/regs :: write perms per path pew/regs :: write perms per path
@ -4303,6 +4521,35 @@
[mos ..^$] [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) ?: ?=([%sinks ~] tea)
?> ?=(%public-keys +<.q.hin) ?> ?=(%public-keys +<.q.hin)
?. ?=(%breach -.public-keys-result.q.hin) ?. ?=(%breach -.public-keys-result.q.hin)
@ -4396,7 +4643,9 @@
:+ desk %| :+ desk %|
:~ ankh+&+ank.dom.dojo :~ ankh+&+ank.dom.dojo
mime+&+mim.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 :~ domestic+|+domestic
foreign+&+hoy.ruf foreign+&+hoy.ruf

View File

@ -861,7 +861,7 @@
$>(%trim vane-task) :: trim state $>(%trim vane-task) :: trim state
$>(%vega vane-task) :: report upgrade $>(%vega vane-task) :: report upgrade
{$warp wer/ship rif/riff} :: internal file req {$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 $>(%plea vane-task) :: ames request
== :: == ::
-- ::able -- ::able
@ -967,7 +967,10 @@
who/(pair (set ship) (map @ta crew)) :: who/(pair (set ship) (map @ta crew)) ::
== :: == ::
++ regs (map path rule) :: rules for paths ++ 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 ++ rite :: new permissions
$% {$r red/(unit rule)} :: for read $% {$r red/(unit rule)} :: for read
{$w wit/(unit rule)} :: for write {$w wit/(unit rule)} :: for write

View File

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

View File

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

View File

@ -13,6 +13,10 @@
;< ~ bind:m (real-ship az ~marbud) ;< ~ bind:m (real-ship az ~marbud)
;< file=@t bind:m (touch-file ~bud %kids %foo) ;< file=@t bind:m (touch-file ~bud %kids %foo)
;< ~ bind:m (check-file-touched ~marbud %home file) ;< ~ 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 (breach-and-hear az ~bud ~marbud)
;< ~ bind:m (real-ship az ~bud) ;< ~ bind:m (real-ship az ~bud)
;< ~ bind:m (dojo ~bud "|merge %kids ~marbud %kids, =gem %this") ;< ~ bind:m (dojo ~bud "|merge %kids ~marbud %kids, =gem %this")

View File

@ -7,7 +7,8 @@
;< ~ bind:m start-simple ;< ~ bind:m start-simple
;< ~ bind:m (raw-ship ~bud ~) ;< ~ bind:m (raw-ship ~bud ~)
;< ~ bind:m (raw-ship ~marbud ~) ;< ~ 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 (check-file-touched ~marbud %home file)
;< ~ bind:m end-simple ;< ~ bind:m end-simple
(pure:m *vase) (pure:m *vase)

View File

@ -29,15 +29,15 @@
%^ cat 3 (get-val /mar/js/hoon) %^ cat 3 (get-val /mar/js/hoon)
' ~& > new-val=new-val .' ' ~& > new-val=new-val .'
=/ js-contents =/ js-contents
%^ cat 3 (get-val /app/publish/js/index/js) %^ cat 3 (get-val /app/landscape/js/channel/js)
'extra' 'extra'
=/ files =/ files
:~ [/sys/zuse/hoon zuse-contents] :~ [/sys/zuse/hoon zuse-contents]
[/mar/js/hoon mar-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)) ;< ~ 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 ++ aqua-path
|= =path |= =path

View File

@ -1393,6 +1393,42 @@
"tslib": "^1.11.1" "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": { "@reach/menu-button": {
"version": "0.10.1", "version": "0.10.1",
"resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.10.1.tgz", "resolved": "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.10.1.tgz",
@ -1443,6 +1479,53 @@
"tslib": "^1.11.1" "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": { "@reach/utils": {
"version": "0.10.1", "version": "0.10.1",
"resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.10.1.tgz", "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.10.1.tgz",
@ -1555,15 +1638,15 @@
"@styled-system/css": "^5.1.5" "@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": { "@tlon/indigo-react": {
"version": "1.1.12", "version": "1.1.15",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.1.12.tgz", "resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.1.15.tgz",
"integrity": "sha512-XBJjHwaslEwZA2r09qnoh84BeVLnd/jwZRkhq71KNABnRD+QRtg/dYNvtswueML4Km89Vx9QBtCIEIeujzrblw==", "integrity": "sha512-Ao+1hAJjN5y1gDyT7GIUgXORPXTIpZKVVtrS++ZGYBemYMSq3oJFMIZertsSZbDHuh/TsVPenJrMUZBpV60law=="
"requires": {
"@reach/menu-button": "^0.10.0",
"@styled-system/css": "^5.1.5",
"@types/styled-system__css": "^5.0.5"
}
}, },
"@types/anymatch": { "@types/anymatch": {
"version": "1.3.1", "version": "1.3.1",
@ -1667,14 +1750,6 @@
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true "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": { "@types/tapable": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz",
@ -3328,7 +3403,8 @@
"csstype": { "csstype": {
"version": "2.6.10", "version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==",
"dev": true
}, },
"cyclist": { "cyclist": {
"version": "1.0.1", "version": "1.0.1",
@ -5949,6 +6025,15 @@
"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" "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": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -5985,6 +6070,11 @@
"p-is-promise": "^2.0.0" "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": { "memory-fs": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@ -7491,6 +7581,15 @@
"tiny-warning": "^1.0.0" "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": { "readable-stream": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -9257,6 +9356,11 @@
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
"dev": true "dev": true
}, },
"unquote": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
"integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ="
},
"unset-value": { "unset-value": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
@ -9565,7 +9669,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -9586,12 +9691,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -9606,17 +9713,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -9733,7 +9843,8 @@
"inherits": { "inherits": {
"version": "2.0.4", "version": "2.0.4",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -9745,6 +9856,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -9759,6 +9871,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -9766,12 +9879,14 @@
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -9790,6 +9905,7 @@
"version": "0.5.3", "version": "0.5.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
@ -9851,7 +9967,8 @@
"npm-normalize-package-bin": { "npm-normalize-package-bin": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"npm-packlist": { "npm-packlist": {
"version": "1.4.8", "version": "1.4.8",
@ -9879,7 +9996,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -9891,6 +10009,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -9968,7 +10087,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -10004,6 +10124,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -10023,6 +10144,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -10066,12 +10188,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -10552,7 +10676,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -10573,12 +10698,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -10593,17 +10720,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -10720,7 +10850,8 @@
"inherits": { "inherits": {
"version": "2.0.4", "version": "2.0.4",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -10732,6 +10863,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -10746,6 +10878,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -10753,12 +10886,14 @@
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -10777,6 +10912,7 @@
"version": "0.5.3", "version": "0.5.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
@ -10838,7 +10974,8 @@
"npm-normalize-package-bin": { "npm-normalize-package-bin": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"npm-packlist": { "npm-packlist": {
"version": "1.4.8", "version": "1.4.8",
@ -10866,7 +11003,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -10878,6 +11016,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -10955,7 +11094,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -10991,6 +11131,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -11010,6 +11151,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -11053,12 +11195,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },

View File

@ -5,13 +5,17 @@
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.10.5", "@babel/runtime": "^7.10.5",
"@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.1", "@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", "classnames": "^2.2.6",
"codemirror": "^5.51.0", "codemirror": "^5.51.0",
"css-loader": "^3.5.3", "css-loader": "^3.5.3",
"formik": "^2.1.4", "formik": "^2.1.4",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1", "moment": "^2.20.1",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
@ -20,6 +24,7 @@
"react-dom": "^16.8.6", "react-dom": "^16.8.6",
"react-markdown": "^4.3.1", "react-markdown": "^4.3.1",
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-window": "^1.8.5",
"remark-disable-tokenizers": "^1.0.24", "remark-disable-tokenizers": "^1.0.24",
"style-loader": "^1.2.1", "style-loader": "^1.2.1",
"styled-components": "^5.1.0", "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/indigo-static.css';
import './css/fonts.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 LaunchApp from './apps/launch/app';
import ChatApp from './apps/chat/app'; import ChatApp from './apps/chat/app';
@ -16,7 +17,7 @@ import LinksApp from './apps/links/app';
import PublishApp from './apps/publish/app'; import PublishApp from './apps/publish/app';
import StatusBar from './components/StatusBar'; import StatusBar from './components/StatusBar';
import NotFound from './components/404'; import ErrorComponent from './components/Error';
import GlobalStore from './store/store'; import GlobalStore from './store/store';
import GlobalSubscription from './subscription/global'; import GlobalSubscription from './subscription/global';
@ -85,7 +86,7 @@ class App extends React.Component {
const associations = this.state.associations ? this.state.associations : { contacts: {} }; const associations = this.state.associations ? this.state.associations : { contacts: {} };
const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : []; const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : [];
const { state } = this; const { state } = this;
const theme = state.dark ? paperDark : light; const theme = state.dark ? dark : light;
return ( return (
<ThemeProvider theme={theme}> <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> </Switch>
</Content> </Content>
</Router> </Router>

View File

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

View File

@ -40,7 +40,7 @@ export class ChatInput extends Component {
super(props); super(props);
this.state = { this.state = {
message: '', message: props.message,
patpSearch: null patpSearch: null
}; };
@ -57,24 +57,6 @@ export class ChatInput extends Component {
this.editor = null; 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', { moment.updateLocale('en', {
relativeTime : { relativeTime : {
past: function(input) { past: function(input) {
@ -99,6 +81,10 @@ export class ChatInput extends Component {
}); });
} }
componentWillUnmount() {
this.props.onUnmount(this.state.message);
}
nextAutocompleteSuggestion(backward = false) { nextAutocompleteSuggestion(backward = false) {
const { patpSuggestions } = this.state; const { patpSuggestions } = this.state;
let idx = patpSuggestions.findIndex(s => s === this.state.selectedSuggestion); let idx = patpSuggestions.findIndex(s => s === this.state.selectedSuggestion);
@ -150,6 +136,9 @@ export class ChatInput extends Component {
if(patpSearch !== null) { if(patpSearch !== null) {
this.patpAutocomplete(value, false); this.patpAutocomplete(value, false);
} }
this.setState({
message: value
});
} }
getLetterType(letter) { getLetterType(letter) {
@ -209,8 +198,35 @@ export class ChatInput extends Component {
return; return;
} }
let message = []; let message = [];
editorMessage.split(' ').map((each) => { let isInCodeBlock = false;
if (this.isUrl(each)) { 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 {
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) { if (message.length > 0) {
message = message.join(' '); message = message.join(' ');
message = this.getLetterType(message); message = this.getLetterType(message);
@ -222,7 +238,7 @@ export class ChatInput extends Component {
); );
message = []; message = [];
} }
const URL = this.getLetterType(each); const URL = this.getLetterType(str);
props.api.chat.message( props.api.chat.message(
props.station, props.station,
`~${window.ship}`, `~${window.ship}`,
@ -230,10 +246,14 @@ export class ChatInput extends Component {
URL URL
); );
} else { } else {
return message.push(each); message.push(str);
} }
}); });
}
});
if (message.length > 0) { if (message.length > 0) {
message = message.join(' '); message = message.join(' ');
message = this.getLetterType(message); message = this.getLetterType(message);
@ -246,8 +266,24 @@ export class ChatInput extends Component {
message = []; message = [];
} }
// perf: // perf testing:
// setTimeout(this.closure, 2000); /*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(''); this.editor.setValue('');
} }
@ -364,6 +400,7 @@ export class ChatInput extends Component {
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 72px)' }} style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 72px)' }}
> >
<CodeEditor <CodeEditor
value={this.props.message}
options={options} options={options}
editorDidMount={(editor) => { editorDidMount={(editor) => {
this.editor = 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,196 +1,56 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { OverlaySigil } from './overlay-sigil'; import { OverlaySigil } from './overlay-sigil';
import MessageContent from './message-content';
import { uxToHex, cite, writeText } from '../../../../lib/util'; import { uxToHex, cite, writeText } from '../../../../lib/util';
import moment from 'moment'; 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 { export class Message extends Component {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
unfold: false,
copied: 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() { render() {
const { props, state } = this; const { props, state } = this;
const pending = props.msg.pending ? ' o-40' : ''; 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 paddingTop = props.paddingTop ? { 'paddingTop': '6px' } : '';
const datestamp =
if (props.renderSigil) { '~' + moment.unix(props.msg.when / 1000).format('YYYY.M.D');
const timestamp = moment.unix(props.msg.when / 1000).format('hh:mm a');
const contact = props.msg.author in props.contacts const contact = props.msg.author in props.contacts
? props.contacts[props.msg.author] : false; ? props.contacts[props.msg.author] : false;
@ -209,15 +69,7 @@ export class Message extends Component {
} }
return ( return (
<div <div className="flex w-100">
ref={this.containerRef}
className={
'w-100 f7 pl3 pt4 pr3 cf flex lh-copy ' + ' ' + pending
}
style={{
minHeight: 'min-content'
}}
>
<OverlaySigil <OverlaySigil
ship={props.msg.author} ship={props.msg.author}
contact={contact} contact={contact}
@ -227,14 +79,15 @@ export class Message extends Component {
group={props.group} group={props.group}
className="fl pr3 v-top bg-white bg-gray0-d" className="fl pr3 v-top bg-white bg-gray0-d"
/> />
<div <div className="fr clamp-message white-d"
className="fr clamp-message white-d" style={{ flexGrow: 1, marginTop: -8 }}>
style={{ flexGrow: 1, marginTop: -8 }}
>
<div className="hide-child" style={paddingTop}> <div className="hide-child" style={paddingTop}>
<p className="v-mid f9 gray2 dib mr3 c-default"> <p className={`v-mid f9 gray2 dib mr3 c-default`}>
<span <span
className={'pointer ' + (contact.nickname || state.copied ? null : 'mono')} className={
'mw5 dib truncate pointer ' +
(contact.nickname || state.copied ? '' : 'mono')
}
onClick={() => { onClick={() => {
writeText(props.msg.author); writeText(props.msg.author);
this.setState({ copied: true }); this.setState({ copied: true });
@ -247,29 +100,28 @@ export class Message extends Component {
{state.copied && 'Copied' || name} {state.copied && 'Copied' || name}
</span> </span>
</p> </p>
<p className="v-mid mono f9 gray2 dib">{timestamp}</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> <p className={`v-mid mono f9 ml2 gray2 dib child dn-s`}>
{datestamp}
</p>
</div> </div>
{this.renderContent()} <MessageContent letter={props.msg.letter} />
</div> </div>
</div> </div>
); );
} else { }
const timestamp = moment.unix(props.msg.when / 1000).format('hh:mm');
renderWithoutSigil(timestamp) {
const { props } = this;
return ( return (
<div <div className="flex w-100">
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> <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 }}> <div className="fr f7 clamp-message white-d pr3 lh-copy"
{this.renderContent()} style={{ flexGrow: 1 }}>
<MessageContent letter={props.msg.letter} />
</div> </div>
</div> </div>
); );
} }
}
} }

View File

@ -46,7 +46,7 @@ export class ProfileOverlay extends Component {
if (!(top || bottom)) { if (!(top || bottom)) {
bottom = `-${Math.round(OVERLAY_HEIGHT / 2)}px`; 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; const isOwn = window.ship === ship;
@ -79,7 +79,7 @@ export class ProfileOverlay extends Component {
</div> </div>
<div className="pv3 pl3 pr2"> <div className="pv3 pl3 pr2">
{contact && contact.nickname && ( {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> <div className="mono gray2">{cite(`~${ship}`)}</div>
{!isOwn && ( {!isOwn && (

View File

@ -6,6 +6,7 @@ import { Spinner } from '../../../components/Spinner';
import { ChatTabBar } from './lib/chat-tabbar'; import { ChatTabBar } from './lib/chat-tabbar';
import { InviteSearch } from '../../../components/InviteSearch'; import { InviteSearch } from '../../../components/InviteSearch';
import SidebarSwitcher from '../../../components/SidebarSwitch'; import SidebarSwitcher from '../../../components/SidebarSwitch';
import Toggle from '../../../components/toggle';
export class SettingsScreen extends Component { export class SettingsScreen extends Component {
constructor(props) { constructor(props) {
@ -195,17 +196,11 @@ export class SettingsScreen extends Component {
} else { } else {
let inclusiveToggle = <div />; let inclusiveToggle = <div />;
if (state.targetGroup) { 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 = ( inclusiveToggle = (
<div className="mt4"> <div className="mt4">
<input <Toggle
type="checkbox" boolean={state.inclusive}
style={{ WebkitAppearance: 'none', width: 28 }} change={this.changeInclusive}
className={inclusiveClasses}
onChange={this.changeInclusive}
/> />
<span className="dib f9 white-d inter ml3"> <span className="dib f9 white-d inter ml3">
Add all members to group Add all members to group

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import Welcome from './lib/welcome'; import Welcome from './lib/welcome';
import { alphabetiseAssociations } from '../../../lib/util'; import { alphabetiseAssociations } from '../../../lib/util';
import { SidebarInvite } from './lib/sidebar-invite'; import SidebarInvite from '../../../components/SidebarInvite';
import { GroupItem } from './lib/group-item'; import { GroupItem } from './lib/group-item';
export class Sidebar extends Component { export class Sidebar extends Component {
@ -51,10 +51,10 @@ export class Sidebar extends Component {
.map((uid) => { .map((uid) => {
return ( return (
<SidebarInvite <SidebarInvite
uid={uid}
key={uid} key={uid}
invite={props.invites[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 React, { Component } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component { export class Skeleton extends Component {
render() { render() {
@ -61,7 +62,9 @@ export class Skeleton extends Component {
width: 'calc(100% - 300px)' width: 'calc(100% - 300px)'
}} }}
> >
<ErrorBoundary>
{this.props.children} {this.props.children}
</ErrorBoundary>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -1,8 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Spinner } from '../../../../components/Spinner'; import { Spinner } from '../../../../components/Spinner';
import { Toggle } from '../../../../components/toggle';
import { GroupView } from '../../../../components/Group'; import { GroupView } from '../../../../components/Group';
import { deSig, uxToHex, writeText } from '../../../../lib/util'; import { deSig, uxToHex, writeText } from '../../../../lib/util';
import { roleForShip, resourceFromPath } from '../../../../lib/group';
export class GroupDetail extends Component { export class GroupDetail extends Component {
constructor(props) { constructor(props) {
@ -15,6 +17,7 @@ export class GroupDetail extends Component {
}; };
this.changeTitle = this.changeTitle.bind(this); this.changeTitle = this.changeTitle.bind(this);
this.changeDescription = this.changeDescription.bind(this); this.changeDescription = this.changeDescription.bind(this);
this.changePolicy = this.changePolicy.bind(this);
} }
componentDidMount() { componentDidMount() {
@ -47,6 +50,17 @@ export class GroupDetail extends Component {
this.setState({ description: event.target.value }); 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() { renderDetail() {
const { props } = this; const { props } = this;
@ -175,7 +189,8 @@ export class GroupDetail extends Component {
const { group, association } = props; 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'; 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> </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"> <p className="f9 gray2 mb2">
Permanently delete this group. All current members will no longer see this group. Permanently delete this group. All current members will no longer see this group.
</p> </p>

View File

@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { GroupItem } from './group-item'; import { GroupItem } from './group-item';
import { Sigil } from '../../../../lib/sigil'; import { Sigil } from '../../../../lib/sigil';
import { SidebarInvite } from './sidebar-invite'; import SidebarInvite from '../../../../components/SidebarInvite';
import { Welcome } from './welcome'; import { Welcome } from './welcome';
import { cite } from '../../../../lib/util'; import { cite } from '../../../../lib/util';
@ -48,10 +48,18 @@ export class GroupSidebar extends Component {
return ( return (
<SidebarInvite <SidebarInvite
key={uid} key={uid}
api={props.api}
invite={invite} invite={invite}
uid={uid} onAccept={() => {
history={props.history} 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 { Link } from 'react-router-dom';
import { InviteSearch, Invites } from '../../../components/InviteSearch'; import { InviteSearch, Invites } from '../../../components/InviteSearch';
import { Spinner } from '../../../components/Spinner'; import { Spinner } from '../../../components/Spinner';
import { Toggle } from '../../../components/toggle';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import { Groups, GroupPolicy, Resource } from '../../../types/group-update'; import { Groups, GroupPolicy, Resource } from '../../../types/group-update';
import { Contacts, Rolodex } from '../../../types/contact-update'; import { Contacts, Rolodex } from '../../../types/contact-update';
@ -129,9 +130,6 @@ export class NewScreen extends Component<NewScreenProps, NewScreenState> {
</span> </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 ( return (
<div className='h-100 w-100 mw6 pa3 pt4 overflow-x-hidden bg-gray0-d white-d flex flex-column'> <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} onChange={this.descriptionChange}
/> />
<div className='mv7'> <div className='mv7'>
<input <Toggle
type='checkbox' boolean={this.state.privacy}
style={{ WebkitAppearance: 'none', width: 28 }} change={this.groupPrivacyChange}
onChange={this.groupPrivacyChange}
className={privacySwitchClasses}
/> />
<span className='dib f9 white-d inter ml3'>Private Group</span> <span className='dib f9 white-d inter ml3'>Private Group</span>
<p className='f9 gray2 pt1' style={{ paddingLeft: 40 }}> <p className='f9 gray2 pt1' style={{ paddingLeft: 40 }}>

View File

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

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { GroupItem } from './group-item'; import { GroupItem } from './group-item';
import { SidebarInvite } from './sidebar-invite'; import SidebarInvite from '../../../../components/SidebarInvite';
import { Welcome } from './welcome'; import { Welcome } from './welcome';
import { alphabetiseAssociations } from '../../../../lib/util'; import { alphabetiseAssociations } from '../../../../lib/util';
@ -17,9 +17,9 @@ export class ChannelsSidebar extends Component {
return ( return (
<SidebarInvite <SidebarInvite
key={uid} key={uid}
uid={uid}
invite={props.invites[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 React, { Component } from 'react';
import { ChannelsSidebar } from './lib/channel-sidebar'; import { ChannelsSidebar } from './lib/channel-sidebar';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component { export class Skeleton extends Component {
render() { render() {
@ -40,7 +41,9 @@ export class Skeleton extends Component {
flexGrow: 1 flexGrow: 1
}} }}
> >
{this.props.children} <ErrorBoundary>
{props.children}
</ErrorBoundary>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@ -25,6 +25,29 @@ export class NewPost extends Component {
postSubmit() { postSubmit() {
const { state } = this; 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) { if (state.submit && !state.disabled) {
const newNote = { const newNote = {
'new-note': { 'new-note': {

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { writeText } from '../../../../lib/util';
import { Spinner } from '../../../../components/Spinner'; import { Spinner } from '../../../../components/Spinner';
import { InviteSearch } from '../../../../components/InviteSearch'; import { InviteSearch } from '../../../../components/InviteSearch';
import Toggle from '../../../../components/toggle';
export class Settings extends Component { export class Settings extends Component {
constructor(props) { constructor(props) {
@ -133,21 +133,15 @@ export class Settings extends Component {
// don't give the option to make inclusive if we don't own the target // don't give the option to make inclusive if we don't own the target
// group // group
const targetOwned = (state.targetGroup) const targetOwned = (state.targetGroup)
? state.targetGroup.slice(0, window.ship.length+3) === `/~${window.ship}/` ? Boolean(state.targetGroup.includes(`/~${window.ship}/`))
: false; : false;
let inclusiveToggle = <div />; let inclusiveToggle = <div />;
if (targetOwned) { 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 = ( inclusiveToggle = (
<div className="mt4"> <div className="mt4">
<input <Toggle
type="checkbox" boolean={state.inclusive}
style={{ WebkitAppearance: 'none', width: 28 }} change={this.changeInclusive}
className={inclusiveClasses}
onChange={this.changeInclusive}
/> />
<span className="dib f9 white-d inter ml3"> <span className="dib f9 white-d inter ml3">
Add all members to group Add all members to group
@ -201,10 +195,6 @@ export class Settings extends Component {
} }
render() { 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) { if (this.props.host.slice(1) === window.ship) {
return ( return (
<div className="flex-column"> <div className="flex-column">
@ -274,11 +264,9 @@ export class Settings extends Component {
/> />
</div> </div>
<div className="mv6"> <div className="mv6">
<input <Toggle
type="checkbox" boolean={this.state.comments}
style={{ WebkitAppearance: 'none', width: 28 }} change={this.changeComments}
className={commentsSwitchClasses}
onChange={this.changeComments}
/> />
<span className="dib f9 white-d inter ml3">Comments</span> <span className="dib f9 white-d inter ml3">Comments</span>
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}> <p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
@ -293,7 +281,7 @@ export class Settings extends Component {
</div> </div>
); );
} else { } 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 React, { Component } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { SidebarInvite } from './sidebar-invite'; import SidebarInvite from '../../../../components/SidebarInvite';
import { Welcome } from './welcome'; import { Welcome } from './welcome';
import { GroupItem } from './group-item'; import { GroupItem } from './group-item';
import { alphabetiseAssociations } from '../../../../lib/util'; import { alphabetiseAssociations } from '../../../../lib/util';
@ -19,13 +19,13 @@ export class Sidebar extends Component {
const sidebarInvites = !(props.invites && props.invites['/publish']) const sidebarInvites = !(props.invites && props.invites['/publish'])
? null ? null
: Object.keys(props.invites['/publish']) : Object.keys(props.invites['/publish'])
.map((uid, i) => { .map((uid) => {
return ( return (
<SidebarInvite <SidebarInvite
uid={uid} key={uid}
invite={props.invites['/publish'][uid]} invite={props.invites['/publish'][uid]}
api={this.props.api} onAccept={() => props.api.invite.accept('/publish', uid)}
key={i} onDecline={() => props.api.invite.decline('/publish', uid)}
/> />
); );
}); });

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { GroupView } from '../../../../components/Group'; import { GroupView } from '../../../../components/Group';
import { resourceFromPath } from '../../../../lib/group';
export class Subscribers extends Component { export class Subscribers extends Component {
constructor(props) { constructor(props) {
@ -7,6 +8,7 @@ export class Subscribers extends Component {
this.redirect = this.redirect.bind(this); this.redirect = this.redirect.bind(this);
this.addUser = this.addUser.bind(this); this.addUser = this.addUser.bind(this);
this.removeUser = this.removeUser.bind(this); this.removeUser = this.removeUser.bind(this);
this.addAll = this.addAll.bind(this);
} }
addUser(who, path) { addUser(who, path) {
@ -21,6 +23,18 @@ export class Subscribers extends Component {
window.location.href = url; 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() { render() {
const path = this.props.notebook['writers-group-path']; const path = this.props.notebook['writers-group-path'];
const group = path ? this.props.groups[path] : null; const group = path ? this.props.groups[path] : null;
@ -45,17 +59,25 @@ export class Subscribers extends Component {
]; ];
return ( return (
<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 <GroupView
permissions permissions
resourcePath={path} resourcePath={path}
group={group} group={group}
tags={tags} tags={tags}
appTags={appTags} appTags={appTags}
contacts={props.contacts} contacts={this.props.contacts}
groups={props.groups} groups={this.props.groups}
associations={props.associations} associations={this.props.associations}
api={this.props.api} api={this.props.api}
/> />
</div>
); );
} }
} }

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Sidebar } from './lib/sidebar'; import { Sidebar } from './lib/sidebar';
import ErrorBoundary from '../../../components/ErrorBoundary';
export class Skeleton extends Component { export class Skeleton extends Component {
render() { render() {
@ -36,7 +37,9 @@ export class Skeleton extends Component {
flexGrow: 1 flexGrow: 1
}} }}
> >
<ErrorBoundary>
{props.children} {props.children}
</ErrorBoundary>
</div> </div>
</div> </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 React, { Component } from 'react';
import _, { capitalize } from 'lodash'; import _, { capitalize } from 'lodash';
import { FixedSizeList as List } from 'react-window';
import { Dropdown } from '../apps/publish/components/lib/dropdown'; import { Dropdown } from '../apps/publish/components/lib/dropdown';
import { cite, deSig } from '../lib/util'; import { cite, deSig } from '../lib/util';
import { roleForShip, resourceFromPath } from '../lib/group'; import { roleForShip, resourceFromPath } from '../lib/group';
@ -143,7 +145,7 @@ export class GroupView extends Component<
isAdmin(): boolean { isAdmin(): boolean {
const us = `~${window.ship}`; 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); const resource = resourceFromPath(this.props.resourcePath);
return resource.ship == us || role === 'admin'; return resource.ship == us || role === 'admin';
} }
@ -156,10 +158,15 @@ export class GroupView extends Component<
return options; return options;
} }
const role = roleForShip(group, ship); const role = roleForShip(group, ship);
const myRole = roleForShip(group, window.ship);
if (role === 'admin' || resource.ship === ship) { if (role === 'admin' || resource.ship === ship) {
return []; 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) }); options.unshift({ text: 'Ban', onSelect: () => this.banUser(ship) });
} }
if (this.isAdmin() && !role) { if (this.isAdmin() && !role) {
@ -199,14 +206,11 @@ export class GroupView extends Component<
}); });
} }
renderMembers() { memberElements() {
const { group, permissions } = this.props; const { group, permissions } = this.props;
const { members } = group; const { members } = group;
const isAdmin = this.isAdmin(); const isAdmin = this.isAdmin();
return ( return Array.from(members).map((ship) => {
<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 role = roleForShip(group, deSig(ship));
const onRoleRemove = const onRoleRemove =
role && isAdmin role && isAdmin
@ -218,7 +222,6 @@ export class GroupView extends Component<
const options = this.optionsForShip(ship, missing); const options = this.optionsForShip(ship, missing);
return ( return (
<div key={ship} className='flex flex-column pv3'>
<GroupMember ship={ship} options={options}> <GroupMember ship={ship} options={options}>
{((permissions && role) || present.length > 0) && ( {((permissions && role) || present.length > 0) && (
<div className='flex mt1'> <div className='flex mt1'>
@ -240,11 +243,8 @@ export class GroupView extends Component<
</div> </div>
)} )}
</GroupMember> </GroupMember>
</div>
);
})}
</div>
); );
})
} }
setInvites(invites: Invites) { setInvites(invites: Invites) {
@ -321,6 +321,7 @@ export class GroupView extends Component<
render() { render() {
const { group, resourcePath, className } = this.props; const { group, resourcePath, className } = this.props;
const resource = resourceFromPath(resourcePath); const resource = resourceFromPath(resourcePath);
const memberElements = this.memberElements();
return ( return (
<div className={className}> <div className={className}>
@ -332,7 +333,17 @@ export class GroupView extends Component<
</div> </div>
{'invite' in group.policy && this.renderInvites(group.policy)} {'invite' in group.policy && this.renderInvites(group.policy)}
{'open' in group.policy && this.renderBanned(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 <Spinner
awaiting={this.state.awaiting} awaiting={this.state.awaiting}

View File

@ -1,33 +1,26 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Invite } from '../types/invite-update';
export class SidebarInvite extends Component { export class SidebarInvite extends Component<{invite: Invite, onAccept: Function, onDecline: Function}, {}> {
onAccept() {
this.props.api.invite.accept('/chat', this.props.uid);
}
onDecline() {
this.props.api.invite.decline('/chat', this.props.uid);
}
render() { render() {
const { props } = this; const { props } = this;
return ( 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'> <div className='w-100 v-mid'>
<p className="dib f8 mono gray4-d"> <p className="dib f8 mono gray4-d">
{props.invite.path} {props.invite.text ? props.invite.text : props.invite.path}
</p> </p>
</div> </div>
<a <a
className="dib pointer pa2 f9 bg-green2 white mt4" className="dib pointer pa2 f9 bg-green2 white mt4"
onClick={this.onAccept.bind(this)} onClick={this.props.onAccept.bind(this)}
> >
Accept Invite Accept Invite
</a> </a>
<a <a
className="dib pointer ml4 pa2 f9 bg-black bg-gray0-d white mt4" 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 Decline
</a> </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, Group,
Tags, Tags,
GroupPolicy, GroupPolicy,
GroupPolicyDiff,
OpenPolicyDiff, OpenPolicyDiff,
OpenPolicy, OpenPolicy,
InvitePolicyDiff, InvitePolicyDiff,
@ -24,7 +25,6 @@ function decodeGroup(group: Enc<Group>): Group {
tags: decodeTags(group.tags), tags: decodeTags(group.tags),
policy: decodePolicy(group.policy), policy: decodePolicy(group.policy),
}; };
console.log(res);
return res; return res;
} }
@ -179,6 +179,8 @@ export default class GroupReducer<S extends GroupState> {
this.openChangePolicy(diff.open, policy); this.openChangePolicy(diff.open, policy);
} else if ('invite' in policy && 'invite' in diff) { } else if ('invite' in policy && 'invite' in diff) {
this.inviteChangePolicy(diff.invite, policy); this.inviteChangePolicy(diff.invite, policy);
} else if ('replace' in diff) {
state.groups[resourcePath].policy = diff.replace;
} else { } else {
console.log('bad policy diff'); console.log('bad policy diff');
} }

View File

@ -50,7 +50,6 @@ export default class InviteReducer<S extends InviteState> {
accepted(json: InviteUpdate, state: S) { accepted(json: InviteUpdate, state: S) {
const data = _.get(json, 'accepted', false); const data = _.get(json, 'accepted', false);
if (data) { if (data) {
console.log(data);
delete state.invites[data.path][data.uid]; 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) { reduce(json: Cage, state: S) {
let data = json['metadata-update'] let data = json['metadata-update']
if (data) { if (data) {
console.log('data: ', data);
this.associations(data, state); this.associations(data, state);
this.add(data, state); this.add(data, state);
this.update(data, state); this.update(data, state);
this.remove(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); 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;