Merge branch 'os1-rc' into contacts-group-validate

This commit is contained in:
A Arroyo 2020-02-05 17:31:45 -08:00
commit dc673e4bb4
51 changed files with 80574 additions and 986 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:34c491ba1d70c1485a295fb04602609fb477a20fa9aa293077a8d7069e33101f
size 12396834
oid sha256:12ccd71ef3ac0c6cda7502fdf88d76fe227e19c5ba9e9abfb5a2ec9c4572f098
size 20382757

View File

@ -235,7 +235,7 @@
|= [=wire upd=chat-update]
^- (quip card state)
?+ -.upd [~ all-state]
%create (notice-create +.upd)
%create (notice-create (path-to-target path.upd))
%delete [[(show-delete:sh-out (path-to-target path.upd)) ~] all-state]
%message (read-envelope (path-to-target path.upd) envelope.upd)
%messages (read-envelopes (path-to-target path.upd) envelopes.upd)

View File

@ -137,7 +137,7 @@
?. =(u.ship our.bol)
~
:: scry permissions to check if write is permitted
?. (permitted-scry [(scot %p src.bol) %chat (weld path.act /write)])
?. (permitted-scry [(scot %p src.bol) path.act])
~
=: author.envelope.act src.bol
when.envelope.act now.bol
@ -159,19 +159,18 @@
:_ state
%+ weld
[%pass chat-path %agent [our.bol %chat-store] %watch chat-path]~
(create-permission [%chat path.act] security.act)
(create-permission path.act security.act)
::
%add-synced
?> (team:title our.bol src.bol)
?: (~(has by synced) [(scot %p ship.act) path.act])
[~ state]
=. synced (~(put by synced) [(scot %p ship.act) path.act] ship.act)
?: (~(has by synced) path.act) [~ state]
=. synced (~(put by synced) path.act ship.act)
?. ask-history.act
=/ chat-path [%mailbox (scot %p ship.act) path.act]
=/ chat-path [%mailbox path.act]
:_ state
[%pass chat-path %agent [ship.act %chat-hook] %watch chat-path]~
:: TODO: only ask for backlog from previous point
=/ chat-history [%backlog (scot %p ship.act) (weld path.act /0)]
=/ chat-history [%backlog (weld path.act /0)]
:_ state
[%pass chat-history %agent [ship.act %chat-hook] %watch chat-history]~
::
@ -185,7 +184,7 @@
%- zing
:~ (pull-wire [%backlog (weld path.act /0)])
(pull-wire [%mailbox path.act])
(delete-permission [%chat path.act])
~[(permission-poke [%delete [%chat path.act]])]
[%give %kick [%mailbox path.act]~ ~]~
==
?. |(=(u.ship src.bol) (team:title our.bol src.bol))
@ -202,10 +201,10 @@
?> ?=(^ pax)
?> (~(has by synced) pax)
:: scry permissions to check if read is permitted
?> (permitted-scry [(scot %p src.bol) %chat (weld pax /read)])
?> (permitted-scry [(scot %p src.bol) pax])
=/ box (chat-scry pax)
?~ box !!
[%give %fact ~ %chat-update !>([%create (slav %p i.pax) pax])]~
[%give %fact ~ %chat-update !>([%create pax])]~
::
++ watch-backlog
|= pax=path
@ -220,16 +219,12 @@
?> ?=([* ^] pas)
?> (~(has by synced) pas)
:: scry permissions to check if read is permitted
?> (permitted-scry [(scot %p src.bol) %chat (weld pas /read)])
=/ box (chat-scry pas)
?~ box !!
:- [%give %fact ~ %chat-update !>([%create (slav %p i.pas) pas])]
?> (permitted-scry [(scot %p src.bol) pas])
%- zing
:~
?: ?&(?=(^ backlog-start) (~(got by allow-history) pas))
(paginate-messages pas u.box u.backlog-start)
~
[%give %kick [%backlog pax]~ `src.bol]~
:~ [%give %fact ~ %chat-update !>([%create pas])]~
?. ?&(?=(^ backlog-start) (~(has by allow-history) pas)) ~
(paginate-messages pas (need (chat-scry pas)) u.backlog-start)
[%give %kick [%backlog pax]~ `src.bol]~
==
::
++ paginate-messages
@ -267,10 +262,7 @@
[~ state]
::
%accepted
=/ ask-history
?~ (chat-scry [(scot %p ship.invite.fact) path.invite.fact])
%.y
%.n
=/ ask-history ?~((chat-scry path.invite.fact) %.y %.n)
:_ state
[(chat-view-poke [%join ship.invite.fact path.invite.fact ask-history])]~
==
@ -279,9 +271,7 @@
|= [wir=wire fact=permission-update]
^- (quip card _state)
:_ state
?- -.fact
%create ~
%delete ~
?+ -.fact ~
%add (handle-permissions [%add path.fact who.fact])
%remove (handle-permissions [%remove path.fact who.fact])
==
@ -290,10 +280,6 @@
|= [kind=?(%add %remove) pax=path who=(set ship)]
^- (list card)
?> ?=([* *] pax)
?. =(%chat i.pax) ~
:: check path to see if this is a %read permission
?. =(%read (snag (dec (lent pax)) `(list @t)`pax))
~
%- zing
%+ turn ~(tap in who)
|= =ship
@ -314,11 +300,7 @@
++ handle-local
|= fact=chat-update
^- (quip card _state)
?- -.fact
%keys [~ state]
%read [~ state]
%config [~ state]
%create [~ state]
?+ -.fact [~ state]
%delete
?. (~(has by synced) path.fact)
[~ state]
@ -337,17 +319,14 @@
++ handle-foreign
|= fact=chat-update
^- (quip card _state)
?- -.fact
%keys [~ state]
%read [~ state]
%config [~ state]
?+ -.fact [~ state]
%create
:_ state
?> ?=([* ^] path.fact)
=/ shp (~(get by synced) path.fact)
?~ shp ~
?. =(src.bol u.shp) ~
[(chat-poke [%create ship.fact t.path.fact])]~
[(chat-poke [%create path.fact])]~
::
%delete
?> ?=([* ^] path.fact)
@ -394,20 +373,17 @@
=/ mailbox=(unit mailbox) (chat-scry t.wir)
=/ chat-history
%+ welp backlog+t.wir
?~ mailbox
/0
/(scot %ud (lent envelopes.u.mailbox))
?~(mailbox /0 /(scot %ud (lent envelopes.u.mailbox)))
:_ state
[%pass chat-history %agent [ship %chat-hook] %watch chat-history]~
::
?: ?=([%backlog @ *] wir)
=/ pax `path`(oust [(dec (lent t.wir)) 1] `(list @ta)`t.wir)
?. (~(has by synced) pax) [~ state]
=/ mailbox=(unit mailbox) (chat-scry pax)
=. pax ?~(mailbox wir [%mailbox pax])
:_ state
[%pass pax %agent [(slav %p i.t.wir) %chat-hook] %watch pax]~
!!
?. ?=([%backlog @ *] wir) !!
=/ pax `path`(oust [(dec (lent t.wir)) 1] `(list @ta)`t.wir)
?. (~(has by synced) pax) [~ state]
=/ mailbox=(unit mailbox) (chat-scry pax)
=. pax ?~(mailbox wir [%mailbox pax])
:_ state
[%pass pax %agent [(slav %p i.t.wir) %chat-hook] %watch pax]~
::
++ watch-ack
|= [wir=wire saw=(unit tang)]
@ -451,37 +427,9 @@
++ create-permission
|= [pax=path sec=rw-security]
^- (list card)
=/ read-perm (weld pax /read)
=/ write-perm (weld pax /write)
?- sec
%channel
:~ (permission-poke (sec-to-perm read-perm %black))
(permission-poke (sec-to-perm write-perm %black))
==
::
%village
:~ (permission-poke (sec-to-perm read-perm %white))
(permission-poke (sec-to-perm write-perm %white))
==
::
%journal
:~ (permission-poke (sec-to-perm read-perm %black))
(permission-poke (sec-to-perm write-perm %white))
==
::
%mailbox
:~ (permission-poke (sec-to-perm read-perm %white))
(permission-poke (sec-to-perm write-perm %black))
==
==
::
++ delete-permission
|= pax=path
^- (list card)
=/ read-perm (weld pax /read)
=/ write-perm (weld pax /write)
:~ (permission-poke [%delete read-perm])
(permission-poke [%delete write-perm])
?+ sec ~
%channel ~[(permission-poke (sec-to-perm pax %black))]
%village ~[(permission-poke (sec-to-perm pax %white))]
==
::
++ sec-to-perm

View File

@ -59,8 +59,7 @@
[%updates ~] ~
[%mailbox @ *]
?> (~(has by inbox) t.path)
=/ =ship (slav %p i.t.path)
(give %chat-update !>([%create ship t.t.path]))
(give %chat-update !>([%create t.path]))
==
[cards this]
::
@ -159,11 +158,10 @@
|= act=chat-action
^- (quip card _state)
?> ?=(%create -.act)
=/ pax [(scot %p ship.act) path.act]
?: (~(has by inbox) pax)
?: (~(has by inbox) path.act)
[~ state]
:- (send-diff pax act)
state(inbox (~(put by inbox) pax *mailbox))
:- (send-diff path.act act)
state(inbox (~(put by inbox) path.act *mailbox))
::
++ handle-delete
|= act=chat-action

View File

@ -4,6 +4,7 @@
/- *permission-store,
*permission-hook,
*group-store,
*invite-store,
*permission-group-hook,
*chat-hook
/+ *server, *chat-json, default-agent
@ -186,53 +187,95 @@
++ poke-json
|= jon=json
^- (list card)
?. =(src.bol our.bol)
~
?> (team:title our.bol src.bol)
(poke-chat-view-action (json-to-view-action jon))
::
++ poke-chat-view-action
|= act=chat-view-action
^- (list card)
?. =(src.bol our.bol)
~
|^
?> (team:title our.bol src.bol)
?- -.act
%create
=/ pax [(scot %p our.bol) path.act]
=/ group-read=path [%chat (weld pax /read)]
=/ group-write=path [%chat (weld pax /write)]
?^ (chat-scry path.act)
~& %chat-already-exists
~
%- zing
:~ :~ (group-poke [%bundle group-read])
(group-poke [%bundle group-write])
(group-poke [%add read.act group-read])
(group-poke [%add write.act group-write])
(chat-poke [%create our.bol path.act])
(chat-hook-poke [%add-owned pax security.act allow-history.act])
==
(create-security [%chat pax] security.act)
:~ (permission-hook-poke [%add-owned group-read group-read])
(permission-hook-poke [%add-owned group-write group-read])
==
:~ (create-chat path.act security.act allow-history.act)
(create-managed-group path.act security.act members.act)
(create-security path.act security.act)
~[(permission-hook-poke [%add-owned path.act path.act])]
%+ turn ~(tap in members.act)
|= =ship
(send-invite-poke path.act ship)
==
::
%delete
=/ group-read [%chat (weld path.act /read)]
=/ group-write [%chat (weld path.act /write)]
:~ (chat-hook-poke [%remove path.act])
(permission-hook-poke [%remove group-read])
(permission-hook-poke [%remove group-write])
(group-poke [%unbundle group-read])
(group-poke [%unbundle group-write])
(permission-hook-poke [%remove path.act])
(chat-poke [%delete path.act])
==
::
%join
=/ group-read [%chat (scot %p ship.act) (weld path.act /read)]
=/ group-write [%chat (scot %p ship.act) (weld path.act /write)]
:~ (chat-hook-poke [%add-synced ship.act path.act ask-history.act])
(permission-hook-poke [%add-synced ship.act group-write])
(permission-hook-poke [%add-synced ship.act group-read])
(permission-hook-poke [%add-synced ship.act path.act])
==
==
::
++ create-chat
|= [=path security=rw-security history=?]
^- (list card)
:~ [(chat-poke [%create path])]
[(chat-hook-poke [%add-owned path security history])]
==
::
++ create-managed-group
|= [=path security=rw-security ships=(set ship)]
^- (list card)
~& [path security ships]
?. =(security %village)
:: if blacklist, do nothing but create the group if it isn't there
::
?~((group-scry path) ~[(group-poke [%bundle path])] ~)
:: if whitelist, check if group exists already. if yes, do nothing
::
?^ (group-scry path) ~
:: if group does not exist, send contact-view %create
::
~[(contact-view-poke [%create path ships])]
::
++ create-security
|= [pax=path sec=rw-security]
^- (list card)
?+ sec ~
%channel
~[(perm-group-hook-poke [%associate pax [[pax %black] ~ ~]])]
::
%village
~[(perm-group-hook-poke [%associate pax [[pax %white] ~ ~]])]
==
::
++ contact-view-poke
|= act=[%create =path ships=(set ship)]
^- card
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
::
++ send-invite-poke
|= [=path =ship]
^- card
=/ =invite
:* our.bol %chat-hook
path ship ''
==
=/ act=invite-action [%invite /chat (shaf %msg-uid eny.bol) invite]
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
::
++ chat-scry
|= pax=path
^- (unit mailbox)
=. pax ;:(weld /=chat-store/(scot %da now.bol)/mailbox pax /noun)
.^((unit mailbox) %gx pax)
--
::
++ diff-chat-update
|= upd=chat-update
@ -284,30 +327,9 @@
^- chat-configs
.^(chat-configs %gx /=chat-store/(scot %da now.bol)/configs/noun)
::
++ create-security
|= [pax=path sec=rw-security]
^- (list card)
=/ read (weld pax /read)
=/ write (weld pax /write)
?- sec
%channel
:~ (perm-group-hook-poke [%associate read [[read %black] ~ ~]])
(perm-group-hook-poke [%associate write [[write %black] ~ ~]])
==
::
%village
:~ (perm-group-hook-poke [%associate read [[read %white] ~ ~]])
(perm-group-hook-poke [%associate write [[write %white] ~ ~]])
==
::
%journal
:~ (perm-group-hook-poke [%associate read [[read %black] ~ ~]])
(perm-group-hook-poke [%associate write [[write %white] ~ ~]])
==
::
%mailbox
:~ (perm-group-hook-poke [%associate read [[read %white] ~ ~]])
(perm-group-hook-poke [%associate write [[write %black] ~ ~]])
==
==
++ group-scry
|= pax=path
^- (unit group)
.^((unit group) %gx ;:(weld /=group-store/(scot %da now.bol) pax /noun))
::
--

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -67,6 +67,9 @@
?> (team:title our.bowl src.bowl)
?+ mark (on-poke:def mark vase)
%json [(poke-json:cc !<(json vase)) this]
%contact-view-action
[(poke-contact-view-action:cc !<(contact-view-action vase)) this]
::
%handle-http-request
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
:_ this

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -18,8 +18,7 @@
retry-timers=(map target @dr)
==
::
::TODO revert to annotations with new link-store subscription model
+$ what-target ?(%local-pages %allotations)
+$ what-target ?(%local-pages %annotations)
+$ target
$: what=what-target
who=ship
@ -68,9 +67,9 @@
=^ cards state
(take-groups-sign:do sign)
[cards this]
?: ?=([%links @ @ ^] wire)
?: ?=([%links ?(%local-pages %annotations) @ ^] wire)
=^ cards state
(take-links-sign:do (wire-to-target t.wire) sign)
(take-link-sign:do (wire-to-target t.wire) sign)
[cards this]
?: ?=([%forward ^] wire)
=^ cards state
@ -170,7 +169,7 @@
$(whos t.whos)
(end-link-subscriptions i.whos pax.upd)
:+ (start-link-subscription %local-pages i.whos pax.upd)
(start-link-subscription %allotations i.whos pax.upd)
(start-link-subscription %annotations i.whos pax.upd)
$(whos t.whos)
==
::
@ -184,13 +183,16 @@
%agent
[who.target %link-proxy-hook]
%watch
[what where]:target
?- what.target
%local-pages [what where]:target
%annotations [what %$ where]:target
==
==
::
++ end-link-subscriptions
|= [who=ship where=path]
^- (list card)
|^ ~[(end %local-pages) (end %allotations)]
|^ ~[(end %local-pages) (end %annotations)]
::
++ end
|= what=what-target
@ -203,7 +205,7 @@
==
--
::
++ take-links-sign
++ take-link-sign
|= [=target =sign:agent:gall]
^- (quip card _state)
?- -.sign
@ -224,6 +226,10 @@
=* mark p.cage.sign
=* vase q.cage.sign
?+ mark ~|([dap.bowl %unexpected-mark mark] !!)
%link-initial
%- handle-link-initial
[who.target where.target !<(initial vase)]
::
%link-update
%- handle-link-update
[who.target where.target !<(update vase)]
@ -268,6 +274,40 @@
(snoc where.target %noun)
==
::
++ do-link-action
|= [=wire =action]
^- card
:* %pass
wire
%agent
[our.bowl %link-store]
%poke
%link-action
!>(action)
==
::
++ handle-link-initial
|= [who=ship where=path =initial]
^- (quip card _state)
?> =(src.bowl who)
?+ -.initial ~|([dap.bowl %unexpected-initial -.initial] !!)
%local-pages
=/ =pages (~(got by pages.initial) where)
(handle-link-update who where [%local-pages where pages])
::
%annotations
=/ urls=(list [=url =notes])
~(tap by (~(got by notes.initial) where))
=| cards=(list card)
|- ^- (quip card _state)
?~ urls [cards state]
=^ caz state
^- (quip card _state)
=, i.urls
(handle-link-update who where [%annotations where url notes])
$(urls t.urls, cards (weld cards caz))
==
::
++ handle-link-update
|= [who=ship where=path =update]
^- (quip card _state)
@ -277,28 +317,17 @@
%local-pages
%+ turn pages.update
|= =page
^- card
:* %pass
[%forward -.update (scot %p who) where]
%agent
[our.bowl %link-store]
%poke
%link-action
!>([%hear where src.bowl page])
==
%+ do-link-action
[%forward %local-page (scot %p who) where]
[%hear where who page]
::
%annotations
%+ turn notes.update
|= =note
^- card
:* %pass
[%forward -.update (scot %p who) where]
%agent
[our.bowl %link-store]
%poke
%link-action
!>([%read where url.update src.bowl note])
==
%+ do-link-action
[%forward %annotation (scot %p who) where]
[%read where url.update who note]
==
::
++ take-forward-sign

View File

@ -99,10 +99,17 @@
++ permitted
|= [who=ship =path]
^- ?
:: we only expose /local-pages and /annotations,
:: and only to ships in the relevant group
:: we only expose group-specific /local-pages and /annotations,
:: and only to ships in the relevant group.
:: (no url-specific annotations subscriptions, either.)
::
?. ?=([?(%local-pages %annotations %allotations) ^] path) |
=/ target=(unit ^path)
?: ?=([%local-pages ^] path)
`t.path
?: ?=([%annotations ~ ^] path)
`t.t.path
~
?~ target |
=; group
?& ?=(^ group)
(~(has in u.group) who)
@ -112,7 +119,7 @@
(scot %p our.bowl)
%group-store
(scot %da now.bowl)
(snoc t.path %noun)
(snoc u.target %noun)
==
::
:: groups subscription
@ -158,21 +165,22 @@
::
?: =(our.bowl i.whos)
$(whos t.whos)
%+ weld $(whos t.whos)
:_ $(whos t.whos)
::NOTE this depends kind of unfortunately on the fact that we only accept
:: subscriptions to /local-pages/* paths. it'd be more correct if we
:: subscriptions to /local-pages//* paths. it'd be more correct if we
:: "just" looked at all paths in the map, and found the matching ones.
::TODO what exactly did i mean by this?
:~ (kick-proxy i.whos [%local-pages pax.upd])
(kick-proxy i.whos [%annotations pax.upd])
%+ kick-proxies i.whos
:~ [%local-pages pax.upd]
[%annotations '' pax.upd]
==
::
:: proxy subscriptions
::
++ kick-proxy
|= [who=ship =path]
++ kick-proxies
|= [who=ship paths=(list path)]
^- card
[%give %kick ~[path] `who]
[%give %kick paths `who]
::
++ handle-proxy-sign
|= [=wire =sign:agent:gall]
@ -204,15 +212,14 @@
++ initial-response
|= =path
^- card
=; initial=update
[%give %fact ~ %link-update !>(initial)]
=; =initial
[%give %fact ~ %link-initial !>(initial)]
?+ path !!
[%local-pages ^]
[%local-pages path .^(pages %gx path)]
[%local-pages .^((map ^path pages) %gx path)]
::
[%annotations @ ^]
=+ (split-discussion-path t.path)
[%annotations path url .^(notes %gx path)]
[%annotations ~ ^]
[%annotations .^((per-path-url notes) %gx '' t.t.path)]
==
::
++ start-proxy

View File

@ -1,358 +0,0 @@
:: link-server: accessing link-store via eyre
::
:: only accepts requests authenticated as the host ship.
::
:: GET requests:
:: /~link/local-pages/[some-path].json?p=0
:: our submissions on path, with optional pagination
::
:: POST requests:
:: /~link/add/[some-path]
:: send {title url} json, will save link at path
::
/+ *link, *server, default-agent, verb
::
|%
+$ state-0
$: %0
~
::NOTE this means we could get away with just producing cards everywhere,
:: never producing new state outside of the agent interface core.
:: we opt to keep ^-(quip card _state) in place for most logic arms
:: because it doesn't cost much, results in unsurprising code, and
:: makes adding any state in the future easier.
==
::
+$ card card:agent:gall
--
::
=| state-0
=* state -
::
%+ verb |
^- agent:gall
=<
|_ =bowl:gall
+* this .
do ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card _this)
:- [start-serving:do launch-tile:do ~]
this
::
++ on-save !>(state)
++ on-load
|= old=vase
^- (quip card _this)
[~ this(state !<(state-0 old))]
::
++ on-watch
|= =path
^- (quip card _this)
?: ?=([%http-response *] path)
[~ this]
?: =(/primary path)
[~ this]
(on-watch:def path)
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?. ?=(%handle-http-request mark)
(on-poke:def mark vase)
:_ this
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
(handle-http-request:do eyre-id inbound-request)
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?. ?=(%bound +<.sign-arvo)
(on-arvo:def wire sign-arvo)
[~ this]
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
?. ?=(%poke-ack -.sign)
(on-agent:def wire sign)
?~ p.sign [~ this]
=/ =tank
leaf+"{(trip dap.bowl)} failed writing to %link-store"
%- (slog tank u.p.sign)
[~ this]
::
++ on-peek on-peek:def
++ on-leave on-leave:def
++ on-fail on-fail:def
--
::
|_ =bowl:gall
::
++ start-serving
^- card
[%pass / %arvo %e %connect [~ /'~link'] dap.bowl]
::
++ launch-tile
^- card
(launch-poke [%link-server-hook /primary '/~link/js/tile.js'])
::
::
++ launch-poke
|= act=[@tas path @t]
^- card
[%pass / %agent [our.bowl %launch] %poke %launch-action !>(act)]
::
++ do-action
|= =action
^- card
[%pass / %agent [our.bowl %link-store] %poke %link-action !>(action)]
::
++ do-save
|= [=path title=@t =url]
^- card
(do-action %save path title url)
::
++ do-note
|= [=path =url udon=@t]
^- card
(do-action %note path url udon)
::
++ handle-http-request
|= [eyre-id=@ta =inbound-request:eyre]
^- (list card)
::NOTE we don't use +require-authorization because it's too restrictive
:: on the flow we want here.
::
?. ?& authenticated.inbound-request
=(src.bowl our.bowl)
==
(give-simple-payload:app eyre-id [[403 ~] ~])
:: request-line: parsed url + params
::
=/ =request-line
%- parse-request-line
url.request.inbound-request
=* req-head header-list.request.inbound-request
=; [cards=(list card) =simple-payload:http]
%+ weld cards
(give-simple-payload:app eyre-id simple-payload)
?+ method.request.inbound-request [~ not-found:gen]
%'OPTIONS'
[~ (include-cors-headers req-head [[200 ~] ~])]
::
%'GET'
[~ (handle-get req-head request-line)]
::
%'POST'
(handle-post req-head request-line body.request.inbound-request)
==
::
++ handle-post
|= [request-headers=header-list:http =request-line body=(unit octs)]
^- [(list card) simple-payload:http]
=; [success=? cards=(list card)]
:- cards
%+ include-cors-headers
request-headers
::TODO it would be more correct to wait for the %poke-ack instead of
:: sending this response right away... but link-store pokes can't
:: actually fail right now, so it's fine.
[[?:(success 200 400) ~] `*octs]
?~ body [| ~]
=/ jon=(unit json) (de-json:html q.u.body)
?~ jon [| ~]
?+ request-line [| ~]
[[~ [%'~link' %save ~]] ~]
^- [? (list card)]
=/ page=(unit [=path title=@t =url])
%. u.jon
=, dejs-soft:format
=+ (su ;~(pfix net (more net urs:ab)))
(ot path+- title+so url+so ~)
?~ page [| ~]
[& [(do-save u.page) ~]]
::
[[~ [%'~link' %note ~]] ~]
^- [? (list card)]
=/ note=(unit [=path =url udon=@t])
%. u.jon
=, dejs-soft:format
=+ (su ;~(pfix net (more net urs:ab)))
(ot path+- url+so udon+so ~)
?~ note [| ~]
[& [(do-note u.note) ~]]
==
::
++ handle-get
|= [request-headers=header-list:http =request-line]
:: if we request base path, return index.html
::
?: ?=([[~ [%'~link' ~]] *] request-line)
$(request-line request-line(ext `%html, site [%'~link' /index]))
%+ include-cors-headers
request-headers
^- simple-payload:http
:: args: map of params
:: p: pagination index
::
=/ args
%- ~(gas by *(map @t @t))
args.request-line
=/ p=(unit @ud)
%+ biff (~(get by args) 'p')
(curr rush dim:ag)
?+ request-line
:: for the default case, try to load file from clay
::
?~ ext.request-line
:: for extension-less requests, always just serve the index.html.
:: that way the js can load and figure out how to deal with that route.
::
$(request-line [[`%html ~[%'~link' 'index']] args.request-line])
=/ file=(unit octs)
?. ?=([%'~link' *] site.request-line) ~
(get-file-at /app/link [t.site u.ext]:request-line)
?~ file not-found:gen
?+ u.ext.request-line not-found:gen
%html (html-response:gen u.file)
%js (js-response:gen u.file)
%css (css-response:gen u.file)
%png (png-response:gen u.file)
==
:: submissions by recency as json, including comment counts
::
[[[~ %json] [%'~link' %submissions ^]] *]
%- json-response:gen
%- json-to-octs ::TODO include in +json-response:gen
%+ page-to-json
(get-submissions t.t.site.request-line p)
|= [=submission comments=@ud]
^- json
=+ s=(submission:en-json submission)
?> ?=([%o *] s)
o+(~(put by p.s) 'commentCount' (numb:enjs:format comments))
:: local links by recency as json
::
[[[~ %json] [%'~link' %local-pages ^]] *]
%- json-response:gen
%- json-to-octs ::TODO include in +json-response:gen
%+ page-to-json
(get-local-pages t.t.site.request-line p)
page:en-json
:: comments by recency as json
::
[[[~ %json] [%'~link' %discussions @ ^]] *]
%- json-response:gen
%- json-to-octs ::TODO include in +json-response:gen
%+ page-to-json
(get-discussions t.t.site.request-line p)
comment:en-json
==
::
++ include-cors-headers
|= [request-headers=header-list:http =simple-payload:http]
^+ simple-payload
=* out-heads headers.response-header.simple-payload
=; =header-list:http
|-
?~ header-list simple-payload
=* new-head i.header-list
=. out-heads
(set-header:http key.new-head value.new-head out-heads)
$(header-list t.header-list)
=/ origin=@t
=/ headers=(map @t @t)
(~(gas by *(map @t @t)) request-headers)
(~(gut by headers) 'origin' '*')
:~ 'Access-Control-Allow-Origin'^origin
'Access-Control-Allow-Credentials'^'true'
'Access-Control-Request-Method'^'OPTIONS, GET, POST'
'Access-Control-Allow-Methods'^'OPTIONS, GET, POST'
'Access-Control-Allow-Headers'^'content-type'
==
::
++ page-size 25
++ get-paginated
|* [l=(list) p=(unit @ud)]
^- [total=@ud pages=@ud page=_l]
:+ (lent l)
+((div (lent l) page-size))
?~ p l
%+ scag page-size
%+ slag (mul u.p page-size)
l
::
++ page-to-json
=, enjs:format
|* $: [total-items=@ud total-pages=@ud page=(list)]
item-to-json=$-(* json)
==
^- json
%- pairs
:~ 'total-items'^(numb total-items)
'total-pages'^(numb total-pages)
'page'^a+(turn page item-to-json)
==
::
++ get-submissions
|= [=path p=(unit @ud)]
^- [@ud @ud (list [submission comments=@ud])]
=- (get-paginated - p)
%+ turn
%+ scry-for submissions
[%submissions path]
|= =submission
:- submission
%- lent
%+ scry-for comments
:- %discussions
%+ snoc path
%- crip
(en-base64:mimes:html url.submission)
::
++ get-local-pages
|= [=path p=(unit @ud)]
^- [@ud @ud pages]
=- (get-paginated - p)
%+ scry-for pages
[%local-pages path]
::
++ get-discussions
|= [=path p=(unit @ud)]
^- [@ud @ud comments]
=- (get-paginated - p)
%+ scry-for comments
[%discussions path]
::
++ get-file-at
|= [base=path file=path ext=@ta]
^- (unit octs)
:: only expose html, css and js files for now
::
?. ?=(?(%html %css %js %png) ext)
~
=/ =path
:* (scot %p our.bowl)
q.byk.bowl
(scot %da now.bowl)
(snoc (weld base file) ext)
==
?. .^(? %cu path)
~
%- some
%- as-octs:mimes:html
.^(@ %cx path)
::
++ scry-for
|* [=mold =path]
.^ mold
%gx
(scot %p our.bowl)
%link-store
(scot %da now.bowl)
(snoc `^path`path %noun)
==
--

View File

@ -5,20 +5,33 @@
:: links, arbitrary paths are probably fair game, but could trip up
:: primitive ui implementations.
::
:: urls in paths are expected to be encoded using +wood, for @ta sanity.
:: use /lib/link's +build-discussion-path.
::
:: see link-listen-hook to see what's synced in, and similarly
:: see link-proxy-hook to see what's exposed.
::
:: scry and subscription paths:
::
:: urls
:: /local-pages/[some-group] all pages we saved by recency
:: /submissions/[some-group] all submissions by recency
:: comments
:: /allotations/[some-group] TMP all our comments in group
:: /annotations/[some-group]/[b64(url)] all our comments on url by recency
:: /discussions/[some-group]/[b64(url)] all known comments on url by recency
:: (map path pages)
:: /local-pages our saved pages
:: /local-pages/some-path our saved pages on path
::
::TODO continue work from m/uplink-broad branch!
:: (map path submissions)
:: /submissions all submissions we've seen
:: /submissions/some-path all submissions we've seen on path
::
:: (map path (map url notes))
:: /annotations our comments
:: /annotations/wood-url our comments on url
:: /annotations/wood-url/some-path our comments on url on path
:: /annotations//some-path our comments on path
::
:: (map path (map url comments))
:: /discussions all comments
:: /discussions/wood-url all comments on url
:: /discussions/wood-url/some-path all comments on url on path
:: /discussions//some-path all comments on path
::
/+ *link, default-agent, verb, dbug
::
@ -27,7 +40,7 @@
$: %0
by-group=(map path links)
by-site=(map site (list [path submission]))
discussions=(map path (map url discussion))
discussions=(per-path-url discussion)
==
::
+$ links
@ -81,25 +94,48 @@
?+ path (on-peek:def path)
[%y ?(%local-pages %submissions) ~]
``noun+!>(~(key by by-group))
::
[%x %local-pages ^]
::
[%x %local-pages *]
``noun+!>((get-local-pages:do t.t.path))
::
[%x %submissions ^]
::
[%x %submissions *]
``noun+!>((get-submissions:do t.t.path))
::
[%y ?(%annotations %discussions) *]
=/ [spath=^path surl=url]
(break-discussion-path t.t.path)
=- ``noun+!>(-)
::
[%y ?(%annotations %discussions) ~]
``noun+!>(~(key by discussions))
?: =(~ surl)
:: no url, provide urls that have comments
::
^- (set url)
?~ spath
:: no path, find urls accross all paths
::
%- ~(rep by discussions)
|= [[* discussions=(map url discussion)] urls=(set url)]
%- ~(uni in urls)
~(key by discussions)
:: specified path, find urls for that specific path
::
%~ key by
(~(gut by discussions) spath *(map url *))
:: specified url and path, nothing to list here
::
[%y ?(%annotations %discussions) ^]
=/ urls (~(get by discussions) t.t.path)
?~ urls ~
``noun+!>(~(key by u.urls))
?^ spath !!
:: no path, find paths with comments for this url
::
[%x %annotations @ ^]
^- (set ^path)
%- ~(rep by discussions)
|= [[=^path urls=(map url discussion)] paths=(set ^path)]
?. (~(has by urls) surl) paths
(~(put in paths) path)
::
[%x %annotations *]
``noun+!>((get-annotations:do t.t.path))
::
[%x %discussions @ ^]
::
[%x %discussions *]
``noun+!>((get-discussions:do t.t.path))
==
::
@ -109,29 +145,25 @@
?> (team:title [our src]:bowl) ::TODO /lib/store
:_ this
|^ ?+ path (on-watch:def path)
[%local-pages ^]
%+ give %link-update
[%local-pages t.path (get-local-pages:do t.path)]
[%local-pages *]
%+ give %link-initial
^- initial
[%local-pages (get-local-pages:do t.path)]
::
[%submissions ^]
%+ give %link-update
[%submissions t.path (get-submissions:do t.path)]
[%submissions *]
%+ give %link-initial
^- initial
[%submissions (get-submissions:do t.path)]
::
[%allotations ^]
%+ turn
%~ tap by
(~(gut by discussions) t.path *(map url discussion))
|= [=url =discussion]
%+ give-single %link-update
[%annotations t.path url ours.discussion]
[%annotations *]
%+ give %link-initial
^- initial
[%annotations (get-annotations:do t.path)]
::
[%annotations @ ^]
%+ give %link-update
[%annotations t.path (get-annotations:do t.path)]
::
[%discussions @ ^]
%+ give %link-update
[%discussions t.path (get-discussions:do t.path)]
[%discussions *]
%+ give %link-initial
^- initial
[%discussions (get-discussions:do t.path)]
==
::
++ give
@ -186,7 +218,9 @@
:_ state
:_ cards
:+ %give %fact
:+ [%local-pages path]~
:+ :~ /local-pages
[%local-pages path]
==
%link-update
!>([%local-pages path [page]~])
:: +note-note: save a note for a url
@ -210,13 +244,15 @@
::
:_ state
^- (list card)
=/ fact
:- %link-update
!>([%annotations path url [note]~])
:* [%give %fact [%annotations (snoc path url)]~ fact]
[%give %fact [%allotations path]~ fact]
cards
==
:_ cards
:+ %give %fact
:+ :~ /annotations
[%annotations %$ path]
[%annotations (build-discussion-path url)]
[%annotations (build-discussion-path path url)]
==
%link-update
!>([%annotations path url [note]~])
:: +hear-submission: record page someone else saved
::
++ hear-submission
@ -237,7 +273,9 @@
:_ state
:_ ~
:+ %give %fact
:+ [%submissions path]~
:+ :~ /submissions
[%submissions path]
==
%link-update
!>([%submissions path [submission]~])
:: +read-comment: record a comment someone else made
@ -255,42 +293,102 @@
:: send updates to subscribers
::
:_ state
^- (list card)
=/ fact
:- %link-update
!>([%discussions path url [comment]~])
:~ [%give %fact [%discussions (snoc path url)]~ fact]
[%give %fact [%discussions path]~ fact]
==
:_ ~
:+ %give %fact
:+ :~ /discussions
[%discussions '' path]
[%discussions (build-discussion-path url)]
[%discussions (build-discussion-path path url)]
==
%link-update
!>([%discussions path url [comment]~])
::
:: reading
::
++ get-local-pages
|= =path
^- pages
^- (map ^path pages)
?~ path
:: all paths
::
%- ~(run by by-group)
|=(links ours)
:: specific path
::
%+ ~(put by *(map ^path pages)) path
ours:(~(gut by by-group) path *links)
::
++ get-submissions
|= =path
^- submissions
^- (map ^path submissions)
?~ path
:: all paths
::
%- ~(run by by-group)
|=(links submissions)
:: specific path
::
%+ ~(put by *(map ^path submissions)) path
submissions:(~(gut by by-group) path *links)
::
::
++ get-annotations
|= =path
^- notes
ours:(get-discussion path)
^- (per-path-url notes)
=/ args=[=^path =url]
(break-discussion-path path)
|^ ?~ path
:: all paths
::
(~(run by discussions) get-ours)
:: specific path
::
%+ ~(put by *(per-path-url notes)) path.args
%- get-ours
%+ ~(gut by discussions) path.args
*(map url discussion)
::
++ get-ours
|= m=(map url discussion)
^- (map url notes)
?: =(~ url.args)
:: all urls
::
%- ~(run by m)
|=(discussion ours)
:: specific url
::
%+ ~(put by *(map url notes)) url.args
ours:(~(gut by m) url.args *discussion)
--
::
++ get-discussions
|= =path
^- comments
comments:(get-discussion path)
::
++ get-discussion
|= =path
^- discussion
=/ [=^path =url]
(split-discussion-path path)
=- (~(gut by -) url *discussion)
%+ ~(gut by discussions) path
*(map ^url discussion)
^- (per-path-url comments)
=/ args=[=^path =url]
(break-discussion-path path)
|^ ?~ path
:: all paths
::
(~(run by discussions) get-comments)
:: specific path
::
%+ ~(put by *(per-path-url comments)) path.args
%- get-comments
%+ ~(gut by discussions) path.args
*(map url discussion)
::
++ get-comments
|= m=(map url discussion)
^- (map url comments)
?: =(~ url.args)
:: all urls
::
%- ~(run by m)
|=(discussion comments)
:: specific url
::
%+ ~(put by *(map url comments)) url.args
comments:(~(gut by m) url.args *discussion)
--
--

309
pkg/arvo/app/link-view.hoon Normal file
View File

@ -0,0 +1,309 @@
:: link-view: frontend endpoints
::
:: endpoints, mapping onto link-store's paths. p is for page as in pagination.
:: updates only work for page 0.
:: as with link-store, urls are expected to use +wood encoding.
::
:: /json/[p]/submissions pages for all groups
:: /json/[p]/submissions/[some-group] page for one group
:: /json/[p]/discussions/[wood-url]/[some-group] page for url in group
::
/+ *link, *server, default-agent, verb
::
|%
+$ state-0
$: %0
~
==
::
+$ card card:agent:gall
--
::
=| state-0
=* state -
::
%+ verb |
^- agent:gall
=<
|_ =bowl:gall
+* this .
do ~(. +> bowl)
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card _this)
:_ this
:~ [%pass /connect %arvo %e %connect [~ /'~link'] dap.bowl]
[%pass /submissions %agent [our.bowl %link-store] %watch /submissions]
[%pass /discussions %agent [our.bowl %link-store] %watch /discussions]
::
=+ [%link-server-hook /tile '/~link/js/tile.js']
[%pass /launch %agent [our.bowl %launch] %poke %launch-action !>(-)]
==
::
++ on-save !>(state)
::
++ on-load
|= old=vase
^- (quip card _this)
[~ this(state !<(state-0 old))]
::
++ on-poke
|= [=mark =vase]
^- (quip card _this)
?> (team:title our.bowl src.bowl)
:_ this
?+ mark (on-poke:def mark vase)
%handle-http-request
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
%+ give-simple-payload:app eyre-id
%+ require-authorization:app inbound-request
handle-http-request:do
::
%link-action
[(handle-action:do !<(action vase)) ~]
==
::
++ on-watch
|= =path
^- (quip card _this)
?: ?=([%http-response *] path)
[~ this]
?. ?=([%json @ @ *] path)
(on-watch:def path)
=/ p=@ud (slav %ud i.t.path)
?+ t.t.path (on-watch:def path)
[%tile ~]
[~ this]
::
[%submissions ~]
:_ this
(give-initial-submissions:do p ~)
::
[%submissions ^]
:_ this
(give-initial-submissions:do p t.t.t.path)
::
[%discussions @ ^]
:_ this
(give-initial-discussions:do p (break-discussion-path t.t.t.path))
==
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
?+ -.sign (on-agent:def wire sign)
%kick
:_ this
[%pass wire %agent [our.bowl %link-store] %watch wire]~
::
%fact
=* mark p.cage.sign
=* vase q.cage.sign
?+ mark (on-agent:def wire sign)
%link-initial [~ this]
%link-update [~[(send-update:do !<(update vase))] this]
==
==
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card _this)
?. ?=([%e %bound *] sign-arvo)
(on-arvo:def wire sign-arvo)
~? !accepted.sign-arvo
[dap.bowl "bind rejected!" binding.sign-arvo]
[~ this]
::
++ on-peek on-peek:def
++ on-leave on-leave:def
++ on-fail on-fail:def
--
::
|_ =bowl:gall
++ page-size 25
++ get-paginated
|* [p=(unit @ud) l=(list)]
^- [total=@ud pages=@ud page=_l]
:+ (lent l)
%+ add (div (lent l) page-size)
(min 1 (mod (lent l) page-size))
?~ p l
%+ scag page-size
%+ slag (mul u.p page-size)
l
::
++ page-to-json
=, enjs:format
|* $: page-number=@ud
[total-items=@ud total-pages=@ud page=(list)]
item-to-json=$-(* json)
==
^- json
%- pairs
:~ 'totalItems'^(numb total-items)
'totalPages'^(numb total-pages)
'pageNumber'^(numb page-number)
'page'^a+(turn page item-to-json)
==
::
++ handle-http-request
|= =inbound-request:eyre
^- simple-payload:http
?. =(src.bowl our.bowl)
[[403 ~] ~]
:: request-line: parsed url + params
::
=/ =request-line
%- parse-request-line
url.request.inbound-request
=* req-head header-list.request.inbound-request
?+ method.request.inbound-request not-found:gen
%'GET'
(handle-get req-head request-line)
==
::
++ handle-get
|= [request-headers=header-list:http =request-line]
^- simple-payload:http
:: try to load file from clay
::
?~ ext.request-line
:: for extension-less requests, always just serve the index.html.
:: that way the js can load and figure out how to deal with that route.
::
$(request-line [[`%html ~[%'~link' 'index']] args.request-line])
=/ file=(unit octs)
?. ?=([%'~link' *] site.request-line) ~
(get-file-at /app/link [t.site u.ext]:request-line)
?~ file not-found:gen
?+ u.ext.request-line not-found:gen
%html (html-response:gen u.file)
%js (js-response:gen u.file)
%css (css-response:gen u.file)
%png (png-response:gen u.file)
==
::
++ get-file-at
|= [base=path file=path ext=@ta]
^- (unit octs)
:: only expose html, css and js files for now
::
?. ?=(?(%html %css %js %png) ext)
~
=/ =path
:* (scot %p our.bowl)
q.byk.bowl
(scot %da now.bowl)
(snoc (weld base file) ext)
==
?. .^(? %cu path)
~
%- some
%- as-octs:mimes:html
.^(@ %cx path)
::
++ handle-action
|= =action
^- card
[%pass /action %agent [our.bowl %link-store] %poke %link-action !>(action)]
:: +give-initial-submissions: page of submissions on path
::
:: for the / path, give page for every path
::
:: result is in the shape of: {
:: "/some/path": {
:: totalItems: 1,
:: totalPages: 1,
:: pageNumber: 0,
:: page: [
:: { commentCount: 1, ...restOfTheSubmission }
:: ]
:: },
:: "/maybe/more": { etc }
:: }
::
++ give-initial-submissions
|= [p=@ud =path]
^- (list card)
:_ ?: =(0 p) ~
[%give %kick ~ ~]~
=; =json
[%give %fact ~ %json !>(json)]
%+ frond:enjs:format 'initial-submissions'
%- pairs:enjs:format
%+ turn
%~ tap by
%+ scry-for (map ^path submissions)
[%submissions path]
|= [=^path =submissions]
^- [@t json]
:- (spat path)
%^ page-to-json p
%+ get-paginated `p
submissions
|= =submission
^- json
=/ =json (submission:en-json submission)
?> ?=([%o *] json)
=; comment-count=@ud
o+(~(put by p.json) 'commentCount' (numb:enjs:format comment-count))
:: get comment count
::
%- lent
~| [path url.submission]
^- comments
=- (~(got by (~(got by -) path)) url.submission)
%+ scry-for (per-path-url comments)
:- %discussions
(build-discussion-path path url.submission)
::
++ give-initial-discussions
|= [p=@ud =path =url]
^- (list card)
:_ ?: =(0 p) ~
[%give %kick ~ ~]~
=; =json
[%give %fact ~ %json !>(json)]
%+ frond:enjs:format 'initial-discussions'
%^ page-to-json p
%+ get-paginated `p
=- (~(got by (~(got by -) path)) url)
%+ scry-for (per-path-url comments)
[%discussions (build-discussion-path path url)]
comment:en-json
::
++ send-update
|= =update
^- card
?+ -.update ~|([dap.bowl %unexpected-update -.update] !!)
%submissions
%+ give-json
(update:en-json update)
:~ /json/0/submissions
(weld /json/0/submissions path.update)
==
::
%discussions
%+ give-json
(update:en-json update)
:_ ~
%+ weld /json/0/discussions
(build-discussion-path [path url]:update)
==
::
++ give-json
|= [=json paths=(list path)]
^- card
[%give %fact paths %json !>(json)]
::
++ scry-for
|* [=mold =path]
.^ mold
%gx
(scot %p our.bowl)
%link-store
(scot %da now.bowl)
(snoc `^path`path %noun)
==
--

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -116,6 +116,7 @@
(snoc (recreate-permissions perm-paths associate) (watch-group group))
::
=/ grp (group-scry group)
~& associate+grp
=. u.perms (~(uni in u.perms) perm-paths)
:_ state(relation (~(put by relation) group u.perms))
%+ weld

View File

@ -142,11 +142,7 @@
?: ?=(%read -.upd)
[%read (pairs [%path (path path.upd)]~)]
?: ?=(%create -.upd)
:- %create
%- pairs
:~ [%ship (ship ship.upd)]
[%path (path path.upd)]
==
[%create (pairs [%path (path path.upd)]~)]
?: ?=(%delete -.upd)
[%delete (pairs [%path (path path.upd)]~)]
?: ?=(%config -.upd)
@ -174,10 +170,7 @@
==
::
++ create
%- ot
:~ [%ship (su ;~(pfix sig fed:ag))]
[%path pa]
==
(ot [%path pa]~)
::
++ delete
(ot [%path pa]~)
@ -233,8 +226,7 @@
%- ot
:~ [%path pa]
[%security sec]
[%read (as (su ;~(pfix sig fed:ag)))]
[%write (as (su ;~(pfix sig fed:ag)))]
[%members (as (su ;~(pfix sig fed:ag)))]
[%allow-history bo]
==
::

View File

@ -115,7 +115,7 @@
%link-store
%link-proxy-hook
%link-listen-hook
%link-server-hook
%link-view
==
::
++ deft-fish :: default connects

View File

@ -22,18 +22,110 @@
%| (rsh 3 1 (scot %if p.host))
==
::
++ split-discussion-path
++ build-discussion-path
|= args=$@(url [=path =url])
|^ ^- path
?@ args ~[(encode-url-for-path args)]
:_ path.args
(encode-url-for-path url.args)
::
++ encode-url-for-path
|= =url
(scot %ta (wood url))
--
::
++ break-discussion-path
|= =path
^- [=^path =url]
~| [%path-too-short path]
?> (gth (lent path) 1) ::TODO ?= would TMI
=/ end=@ud (dec (lent path))
:- (scag end path)
(de-base64:mimes:html (snag end path))
?~ path [/ '']
:- t.path
?: =(~ i.path) ''
~| path
(woad (slav %ta i.path))
::
:: zip sorted a into sorted b, maintaining sort order
::TODO stdlib
++ merge-sorted
|* [sort=$-([* *] ?) a=(list) b=(list)]
|- ^- ?(_a _b)
?~ a b
?~ b a
?: (sort i.a i.b)
[i.a $(a t.a)]
[i.b $(b t.b)]
::
++ merge
|%
++ pages
::TODO we would just use +cury here but it don't work
|= [a=^pages b=^pages]
^+ a
%+ merge-sorted
|= [a=page b=page]
(gth time.a time.b)
[a b]
::
++ submissions
|= [a=^submissions b=^submissions]
^+ a
%+ merge-sorted
|= [a=submission b=submission]
(gth time.a time.b)
[a b]
::
++ notes
|= [a=^notes b=^notes]
^+ a
%+ merge-sorted
|= [a=note b=note]
(gth time.a time.b)
[a b]
::
++ comments
|= [a=^comments b=^comments]
^+ a
%+ merge-sorted
|= [a=comment b=comment]
(gth time.a time.b)
[a b]
--
::
++ en-json
=, enjs:format
|%
++ update
|= upd=^update
^- json
%- frond
:- -.upd
?- -.upd
%local-pages
%- pairs
:~ 'path'^(path path.upd)
'pages'^a+(turn pages.upd page)
==
::
%submissions
%- pairs
:~ 'path'^(path path.upd)
'pages'^a+(turn submissions.upd submission)
==
::
%annotations
%- pairs
:~ 'path'^(path path.upd)
'url'^s+url.upd
'notes'^a+(turn notes.upd note)
==
::
%discussions
%- pairs
:~ 'path'^(path path.upd)
'url'^s+url.upd
'comments'^a+(turn comments.upd comment)
==
==
::
++ submission
|= sub=^submission
^- json
@ -47,16 +139,22 @@
%- pairs
:~ 'title'^s+title.page
'url'^s+url.page
'timestamp'^(time time.page)
'time'^(time time.page)
==
::
++ comment
|= =^comment
^- json
=+ n=(note +.comment)
?> ?=([%o *] n)
o+(~(put by p.n) 'ship' (ship ship.comment))
::
++ note
|= =^note
^- json
%- pairs
:~ 'ship'^(ship ship.comment)
'time'^(time time.comment)
'udon'^s+udon.comment ::TODO convert?
:~ 'time'^(time time.note)
'udon'^s+udon.note ::TODO convert?
==
--
::

View File

@ -0,0 +1,16 @@
:: link: subscription updates
::
::TODO this should include json conversion once mark performance improves
/+ *link
|_ =action
++ grow
|%
++ noun action
--
::
++ grab
|%
++ noun ^action
++ json action:de-json
--
--

View File

@ -0,0 +1,14 @@
:: link: initial subscription result
::
/- *link
|_ =initial
++ grow
|%
++ noun initial
--
::
++ grab
|%
++ noun ^initial
--
--

View File

@ -1,6 +1,5 @@
:: link: subscription updates
::
::TODO this should include json conversion once mark performance improves
/- *link
|_ =update
++ grow

View File

@ -31,7 +31,7 @@
+$ chat-configs (map path config)
::
+$ chat-base
$% [%create =ship =path] :: %create: create a mailbox at ~ship/path
$% [%create =path] :: %create: create a mailbox at path
[%delete =path] :: %delete: delete a mailbox at path
[%message =path =envelope] :: %message: append a message to mailbox
[%read =path] :: %read: set mailbox to read

View File

@ -4,8 +4,7 @@
$% $: %create
=path
security=rw-security
read=(set ship)
write=(set ship)
members=(set ship)
allow-history=?
==
[%delete =path]

View File

@ -23,6 +23,7 @@
page
==
:: +note: a comment on some url
::
+$ note
$: =time
udon=@t
@ -40,6 +41,12 @@
+$ notes (list note)
+$ comments (list comment)
::
:: state builder
::
++ per-path-url
|$ [value]
(map path (map url value))
::
:: +action: local actions
::
+$ action
@ -60,6 +67,15 @@
::
[%read =path =url comment]
==
::
:: +initial: local result
::
+$ initial
$% [%local-pages pages=(map path pages)]
[%submissions submissions=(map path submissions)]
[%annotations notes=(per-path-url notes)]
[%discussions comments=(per-path-url comments)]
==
:: +update: local updates
::
::NOTE we include paths/urls to support the "subscribed to all" case

View File

@ -1549,7 +1549,6 @@
(emit-event channel-id [(en-json:html json)]~)
::
%kick
~& [%recieved-quit-from-gall channel-id]
=/ =json
=, enjs:format
%- pairs :~

View File

@ -129,10 +129,10 @@ class UrbitApi {
this.action("chat-view", "json", data);
}
chatViewCreate(path, security, read, write, allowHistory) {
chatViewCreate(path, security, members, allowHistory) {
this.chatViewAction({
create: {
path, security, read, write,
path, security, members,
'allow-history': allowHistory
}
});

View File

@ -19,7 +19,7 @@ export class SidebarInvite extends Component {
<div className='pa3'>
<div className='w-100 v-mid'>
<p className="dib f8 mono gray4-d">
{props.invite.text}
{props.invite.path}
</p>
</div>
<a

View File

@ -124,18 +124,6 @@ export class NewScreen extends Component {
this.textarea.value = '';
}
// TODO: don't do this, it's shitty
let writeAud;
let readAud;
if (state.security === 'village') {
aud.push(`~${window.ship}`);
readAud = aud.slice(); // white list
writeAud = aud.slice(); // white list
} else if (state.security === 'channel') {
readAud = []; // black list
writeAud = []; // black list
}
this.setState({
error: false,
success: true,
@ -145,14 +133,12 @@ export class NewScreen extends Component {
}
}, () => {
props.setSpinner(true);
// if we want a "proper group" that can be managed from the contacts UI,
// we make a path of the form /~zod/cool-group
// if not, we make a path of the form /~/~zod/free-chat
props.api.chatView.create(
station, state.security, readAud, writeAud, state.allowHistory
`/~${window.ship}${station}`, state.security, aud, state.allowHistory
);
aud.forEach((ship) => {
if (ship !== `~${window.ship}`) {
props.api.invite.invite(station, ship);
}
});
});
}
@ -258,4 +244,4 @@ export class NewScreen extends Component {
</div>
);
}
}
}

View File

@ -54,7 +54,6 @@ export class Sidebar extends Component {
})
.map((obj) => {
let unread = props.unreads[obj.box];
return (
<SidebarItem
key={obj.box + '/' + obj.when}

View File

@ -44,7 +44,7 @@ export class ChatUpdateReducer {
create(json, state) {
let data = _.get(json, 'create', false);
if (data) {
state.inbox[`/~${data.ship}${data.path}`] = {
state.inbox[data.path] = {
envelopes: [],
config: {
read: 0,

View File

@ -2148,7 +2148,8 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -2172,13 +2173,15 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2195,19 +2198,22 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -2338,7 +2344,8 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -2352,6 +2359,7 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2368,6 +2376,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2376,13 +2385,15 @@
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -2403,6 +2414,7 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2491,7 +2503,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -2505,6 +2518,7 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2600,7 +2614,8 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -2642,6 +2657,7 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -2663,6 +2679,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -2711,13 +2728,15 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true
"dev": true,
"optional": true
}
}
},

View File

@ -91,135 +91,84 @@ class UrbitApi {
});
}
async getComments(path, url, page, index) {
let endpoint = "/~link/discussions" + path + "/" + window.btoa(url) + ".json?p=0";
let promise = await fetch(endpoint);
if (promise.ok) {
let comments = {
"link-update": {
comments: {
path: path,
page: page,
index: index
}
}
};
comments["link-update"].comments.data = await promise.json();
store.handleEvent(comments);
}
getComments(path, url) {
return this.getCommentsPage.bind(this)(path, url, 0);
}
async getCommentsPage(path, url, page, index, commentPage) {
let endpoint = "/~link/discussions" + path + "/" + window.btoa(url) + ".json?p=" + commentPage;
let promise = await fetch(endpoint);
if (promise.ok) {
let responseData = await promise.json();
let update = {
"link-update": {
commentPage: {
path: path,
linkPage: page,
index: index,
comPageNo: commentPage,
data: responseData.page
getCommentsPage(path, url, page) {
//TODO factor out
// encode the url into @ta-safe format, using logic from +wood
let strictUrl = '';
for (let i = 0; i < url.length; i++) {
const char = url[i];
let add = '';
switch (char) {
case ' ':
add = '.';
break;
case '.':
add = '~.';
break;
case '~':
add = '~~';
break;
default:
const charCode = url.charCodeAt(i);
if (
(charCode >= 97 && charCode <= 122) || // a-z
(charCode >= 48 && charCode <= 57) || // 0-9
char === '-'
) {
add = char;
} else {
//TODO behavior for unicode doesn't match +wood's,
// but we can probably get away with that for now.
add = '~' + charCode.toString(16) + '.';
}
}
};
store.handleEvent(update);
}
strictUrl = strictUrl + add;
}
}
strictUrl = '~.' + strictUrl;
async getPage(path, page) {
let endpoint = "/~link/submissions" + path + ".json?p=" + page;
let promise = await fetch(endpoint);
if (promise.ok) {
let resolvedPage = await promise.json();
let update = {
"link-update": {
page: {
[path]: {
page: page,
links: resolvedPage.page
}
}
const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
(res) => {
if (res.data['initial-discussions']) {
// these aren't returned with the response,
// so this ensures the reducers know them.
res.data['initial-discussions'].path = path;
res.data['initial-discussions'].url = url;
}
};
store.handleEvent(update);
}
}
async postLink(path, url, title) {
let json =
{'path': path,
'title': title,
'url': url
};
let endpoint = "/~link/save";
let post = await fetch(endpoint, {
method: "POST",
credentials: 'include',
headers: {
'Content-Type': 'application/json'
store.handleEvent(res);
},
body: JSON.stringify(json)
});
if (post.ok) {
let update = {
"link-update": {
add: {
[path]: {
title: title,
url: url,
timestamp: moment.now(),
ship: window.ship,
commentCount: 0
}
}
}
};
store.handleEvent(update);
return true;
} else {
return false;
}
console.error,
()=>{} // no-op on quit
);
}
async postComment(path, url, comment, page, index) {
let json = {
path: path,
url: url,
udon: comment
}
getPage(path, page) {
const endpoint = '/json/' + page + '/submissions' + path;
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
(dat)=>{store.handleEvent(dat)},
console.error,
()=>{} // no-op on quit
);
}
let endpoint = "/~link/note";
let post = await fetch(endpoint, {
method: "POST",
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(json)
linkAction(data) {
return this.action("link-store", "link-action", data);
}
postLink(path, url, title) {
return this.linkAction({
'save': { path, url, title }
});
}
if (post.ok) {
let update = {
"link-update": {
commentAdd: {
path: path,
url: url,
udon: comment,
page: page,
index: index,
time: moment.now()
}
}
};
store.handleEvent(update);
return true;
} else {
return false;
}
postComment(path, url, comment, page, index) {
return this.linkAction({
'note': { path, url, udon: comment }
});
}
sidebarToggle() {

View File

@ -17,7 +17,7 @@ export class ChannelsSidebar extends Component {
.map((path) => {
let name = "Private"
let selected = (props.selected === path);
let linkCount = !!props.links[path] ? props.links[path]['total-items'] : 0;
let linkCount = !!props.links[path] ? props.links[path].totalItems : 0;
return (
<ChannelsItem
key={path}
@ -40,8 +40,8 @@ export class ChannelsSidebar extends Component {
name = name.substr(nameSeparator + 1);
let selected = (props.selected === path);
let linkCount = !!props.links[path] ? props.links[path]['total-items'] : 0;
let linkCount = !!props.links[path] ? props.links[path].totalItems : 0;
return (
<ChannelsItem
key={path}
@ -56,9 +56,9 @@ export class ChannelsSidebar extends Component {
let activeClasses = (this.props.active === "channels") ? " " : "dn-s ";
let hiddenClasses = true;
// probably a more concise way to write this
if (this.props.popout) {
hiddenClasses = false;
} else {
@ -73,10 +73,10 @@ export class ChannelsSidebar extends Component {
: "dn")}>
<a className="db dn-m dn-l dn-xl f8 pb3 pl3" href="/"> Landscape</a>
<div className="overflow-y-scroll h-100">
<h2 className={`f8 f9-m f9-l f9-xl
pt1 pt4-m pt4-l pt4-xl
<h2 className={`f8 f9-m f9-l f9-xl
pt1 pt4-m pt4-l pt4-xl
pr4 pb3 pb2-m pb2-l pb2-xl
pl3 pl4-m pl4-l pl4-xl
pl3 pl4-m pl4-l pl4-xl
black-s gray2 white-d c-default
bb bn-m bn-l bn-xl b--gray4 mb2 mb0-m mb0-l mb0-xl`}>
Your Collections

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import { CommentItem } from './comment-item';
import { CommentsPagination } from './comments-pagination';
import { uxToHex } from '../../lib/util';
import { uxToHex } from '../../lib/util';
import { api } from '../../api';
export class Comments extends Component {
@ -10,12 +10,13 @@ export class Comments extends Component {
componentDidMount() {
let page = "page" + this.props.commentPage;
let comments = !!this.props.comments;
if ((!comments[page]) && (page !== "page0")) {
if ((page !== "page0") &&
(!comments || !this.props.comments[page]) &&
(this.props.path && this.props.url)
) {
api.getCommentsPage(
this.props.path,
this.props.url,
this.props.linkPage,
this.props.linkIndex,
this.props.path,
this.props.url,
this.props.commentPage);
}
}
@ -24,12 +25,10 @@ export class Comments extends Component {
let page = "page" + this.props.commentPage;
if (prevProps !== this.props) {
if (!!this.props.comments) {
if ((page !== "page0") && (!this.props.comments[page])) {
if ((page !== "page0") && !this.props.comments[page] && this.props.url) {
api.getCommentsPage(
this.props.path,
this.props.url,
this.props.linkPage,
this.props.linkIndex,
this.props.path,
this.props.url,
this.props.commentPage);
}
}
@ -38,7 +37,7 @@ export class Comments extends Component {
render() {
let props = this.props;
let page = "page" + props.commentPage;
let commentsObj = !!props.comments
@ -50,7 +49,7 @@ export class Comments extends Component {
: {};
let total = !!props.comments
? props.comments["total-pages"]
? props.comments.totalPages
: {};
let commentsList = Object.keys(commentsPage)
@ -59,7 +58,7 @@ export class Comments extends Component {
let commentObj = commentsPage[entry]
let { ship, time, udon } = commentObj;
let members = !!props.members
let members = !!props.members
? props.members
: {};

View File

@ -46,7 +46,7 @@ export class LinkItem extends Component {
}
let comments = props.comments + " comment" + ((props.comments === 1) ? "" : "s");
return (
<div className="w-100 pv3 flex">
<Sigil
@ -63,8 +63,8 @@ export class LinkItem extends Component {
</p>
</a>
<div className="w-100 pt1">
<span className={"f9 pr2 v-mid " + mono}>{(props.nickname)
? props.nickname
<span className={"f9 pr2 v-mid " + mono}>{(props.nickname)
? props.nickname
: "~" + props.ship}</span>
<span className="f9 inter gray2 pr3 v-mid">{this.state.timeSinceLinkPost}</span>
<Link to=

View File

@ -19,11 +19,9 @@ export class LinkSubmit extends Component {
let title = (this.state.linkTitle)
? this.state.linkTitle
: this.state.linkValue;
let request = api.postLink(this.props.path, link, title);
if (request) {
this.setState({linkValue: "", linkTitle: ""})
}
api.postLink(this.props.path, link, title).then((r) => {
this.setState({linkValue: "", linkTitle: ""});
});
}
setLinkValid(link) {
@ -58,7 +56,7 @@ export class LinkSubmit extends Component {
let activeClasses = (this.state.linkValid)
? "green2 pointer"
: "gray2";
return (
<div className="relative ba b--gray4 b--gray2-d br1 w-100 mb6">
<textarea

View File

@ -18,34 +18,30 @@ export class LinkDetail extends Component {
this.setComment = this.setComment.bind(this);
}
componentDidMount() {
// if we have no preloaded data, and we aren't expecting it, get it
if (this.props.page != 0 && (!this.props.data || !this.props.data.url)) {
api.getPage(this.props.path, this.props.page);
// if we have preloaded our data,
// but no comments, grab the comments
if (!!this.props.data.url) {
let props = this.props;
let comments = !!props.data.comments;
if (!comments) {
api.getComments(props.path, props.data.url, props.page, props.link);
}
} else if (!this.props.comments && this.props.data.url) {
api.getCommentsPage(this.props.path, this.props.data.url, this.props.commentPage);
}
this.updateTimeSinceNewestMessageInterval = setInterval( () => {
this.setState({timeSinceLinkPost: this.getTimeSinceLinkPost()});
}, 60000);
}
componentDidUpdate(prevProps) {
// if we came to this page *directly*,
// load the comments -- DidMount will fail
if (this.props.data.url !== prevProps.data.url) {
let props = this.props;
let comments = !!this.props.data.comments;
if (!comments && this.props.data.url) {
api.getComments(props.path, props.data.url, props.page, props.link);
}
if ( (this.props.data.url !== prevProps.data.url) &&
(!this.props.comments && this.props.data.url)
) {
api.getCommentsPage(this.props.path, this.props.data.url, this.props.commentPage);
}
if (this.props.data.timestamp !== prevProps.data.timestamp) {
@ -70,10 +66,10 @@ export class LinkDetail extends Component {
let url = this.props.data.url || "";
let request = api.postComment(
this.props.path,
url,
this.state.comment,
this.props.page,
this.props.path,
url,
this.state.comment,
this.props.page,
this.props.link
);
@ -85,7 +81,7 @@ export class LinkDetail extends Component {
setComment(event) {
this.setState({comment: event.target.value});
}
render() {
let props = this.props;
let popout = (props.popout) ? "/popout" : "";
@ -106,7 +102,7 @@ export class LinkDetail extends Component {
let commentCount = props.data.commentCount || 0;
let comments = commentCount + " comment" + ((commentCount === 1) ? "" : "s");
let nickname = !!props.members[props.data.ship]
? props.members[props.data.ship].nickname
: "";
@ -120,18 +116,18 @@ export class LinkDetail extends Component {
let activeClasses = (this.state.comment)
? "black b--black pointer"
: "gray2 b--gray2";
return (
<div className="h-100 w-100 overflow-hidden flex flex-column">
<div
className={`pl3 pt2 flex relative overflow-x-scroll
className={`pl3 pt2 flex relative overflow-x-scroll
overflow-x-auto-l overflow-x-auto-xl flex-shrink-0
bb bn-m bn-l bn-xl b--gray4`}
style={{ height: 48 }}>
<SidebarSwitcher
sidebarShown={props.sidebarShown}
popout={props.popout}/>
<Link
<Link
className="dib f8 fw4 v-top pt2 gray2"
to={"/~link" + popout + props.path + "/" + props.page}>
{"<- Collection index"}
@ -158,8 +154,8 @@ export class LinkDetail extends Component {
</p>
</a>
<div className="w-100 pt1">
<span className={"f9 pr2 white-d v-mid " + nameClass}>{(nickname)
? nickname
<span className={"f9 pr2 white-d v-mid " + nameClass}>{(nickname)
? nickname
: "~" + ship}
</span>
<span className="f9 inter gray2 pr3 v-mid">
@ -184,7 +180,7 @@ export class LinkDetail extends Component {
onChange={this.setComment}
value={this.state.comment}
/>
<button className={"f8 bg-gray0-d white-d ml2 absolute "
<button className={"f8 bg-gray0-d white-d ml2 absolute "
+ activeClasses}
disabled={!this.state.comment}
onClick={this.onClickPost.bind(this)}
@ -195,11 +191,11 @@ export class LinkDetail extends Component {
Post
</button>
</div>
<Comments
path={props.path}
<Comments
path={props.path}
key={props.path + props.commentPage}
comments={props.data.comments}
commentPage={props.commentPage}
comments={props.comments}
commentPage={props.commentPage}
members={props.members}
popout={props.popout}
url={props.data.url}
@ -212,6 +208,5 @@ export class LinkDetail extends Component {
)
}
}
export default LinkDetail;

View File

@ -13,15 +13,12 @@ import { uxToHex } from '../lib/util';
export class Links extends Component {
componentDidMount() {
let linkPage = "page" + this.props.page;
if ((this.props.page !== 0) && (!this.props.links[linkPage])) {
api.getPage(this.props.path, this.props.page);
}
this.componentDidUpdate();
}
componentDidUpdate() {
let linkPage = "page" + this.props.page;
if ((this.props.page !== 0) && (!this.props.links[linkPage])) {
if ((this.props.page != 0) && (!this.props.links[linkPage])) {
api.getPage(this.props.path, this.props.page);
}
}
@ -41,7 +38,7 @@ export class Links extends Component {
: 0;
let totalPages = !!props.links
? Number(props.links["total-pages"])
? Number(props.links.totalPages)
: 1;
let LinkList = Object.keys(links)
@ -95,7 +92,7 @@ export class Links extends Component {
<Link to="/~link/">{"⟵ All Channels"}</Link>
</div>
<div
className={`pl3 pt2 flex relative overflow-x-scroll
className={`pl3 pt2 flex relative overflow-x-scroll
overflow-x-auto-l overflow-x-auto-xl flex-shrink-0
bb bn-m bn-l bn-xl b--gray4`}
style={{ height: 48 }}>
@ -104,7 +101,7 @@ export class Links extends Component {
popout={props.popout}/>
<Link to={`/~link` + popout + props.path} className="pt2">
<h2
className={`dib f8 fw4 v-top ` +
className={`dib f8 fw4 v-top ` +
(props.path.includes("/~/")
? ""
: "mono")}>

View File

@ -32,14 +32,15 @@ export class Root extends Component {
let paths = !!state.contacts ? state.contacts : {};
let links = !!state.links ? state.links : {};
let comments = !!state.comments ? state.comments : {};
return (
<BrowserRouter>
<Route exact path="/~link"
render={ (props) => {
return (
<Skeleton
active="channels"
<Skeleton
active="channels"
paths={paths}
rightPanelHide={true}
sidebarShown={true}
@ -58,7 +59,7 @@ export class Root extends Component {
render={ (props) => {
// groups/contacts and link channels are the same thing in ver 1
let groupPath =
let groupPath =
`/${props.match.params.ship}/${props.match.params.channel}`;
let groupMembers = paths[groupPath] || {};
@ -66,8 +67,8 @@ export class Root extends Component {
let popout = props.match.url.includes("/popout/");
let channelLinks = !!links[groupPath]
? links[groupPath]
let channelLinks = !!links[groupPath]
? links[groupPath]
: {};
return (
@ -95,7 +96,7 @@ export class Root extends Component {
/>
<Route exact path="/~link/(popout)?/:ship/:channel/:page/:index/(comments)?/:commentpage?"
render={ (props) => {
let groupPath =
let groupPath =
`/${props.match.params.ship}/${props.match.params.channel}`;
let popout = props.match.url.includes("/popout/");
@ -106,8 +107,13 @@ export class Root extends Component {
let page = props.match.params.page || 0;
let data = !!links[groupPath]
? links[groupPath]["page" + page][index]
: {};
? !!links[groupPath]["page" + page]
? links[groupPath]["page" + page][index]
: {}
: {};
let coms = !comments[groupPath]
? undefined
: comments[groupPath][data.url];
let commentPage = props.match.params.commentpage || 0;
@ -130,6 +136,7 @@ export class Root extends Component {
popout={popout}
sidebarShown={state.sidebarShown}
data={data}
comments={coms}
commentPage={commentPage}
/>
</Skeleton>

View File

@ -14,7 +14,7 @@ export class InitialReducer {
state.groups[group] = new Set(data[group]);
}
}
data = _.get(json, 'link', false);
if (data) {
let name = Object.keys(data)[0];

View File

@ -1,92 +1,141 @@
import _ from 'lodash';
const PAGE_SIZE = 25;
export class LinkUpdateReducer {
reduce(json, state) {
let data = _.get(json, 'link-update', false);
if (data) {
this.add(data, state);
this.comments(data, state);
this.commentAdd(data, state);
this.commentPage(data, state);
this.page(data, state);
}
this.submissionsPage(json, state);
this.submissionsUpdate(json, state);
this.discussionsPage(json, state);
this.discussionsUpdate(json, state);
}
add(json, state) {
// pin ok'd link POSTs to top of page0
let data = _.get(json, 'add', false);
submissionsPage(json, state) {
let data = _.get(json, 'initial-submissions', false);
if (data) {
let path = Object.keys(data)[0];
let tempArray = state.links[path].page0;
tempArray.unshift(data[path]);
state.links[path].page0 = tempArray;
}
}
// { "initial-submissions": {
// "/~ship/group": {
// page: [{ship, timestamp, title, url}]
// page-number: 0
// total-items: 1
// total-pages: 1
// }
// } }
comments(json, state) {
let data = _.get(json, 'comments', false);
if (data) {
let path = data.path;
let page = "page" + data.page;
let index = data.index;
let storage = state.links[path][page][index];
for (var path of Object.keys(data)) {
const here = data[path];
const page = "page" + here.pageNumber;
storage.comments = {};
storage.comments["page0"] = data.data.page;
storage.comments["total-items"] = data.data["total-items"];
storage.comments["total-pages"] = data.data["total-pages"];
// if we didn't have any state for this path yet, initialize.
if (!state.links[path]) {
state.links[path] = {};
}
state.links[path][page][index] = storage;
}
}
commentAdd(json, state) {
let data = _.get(json, 'commentAdd', false);
if (data) {
let path = data.path;
let page = "page" + data.page;
let index = data.index;
let ship = window.ship;
let time = data.time;
let udon = data.udon;
let tempObj = {
'ship': ship,
'time': time,
'udon': udon
// since data contains an up-to-date full version of the page,
// we can safely overwrite the one in state.
state.links[path][page] = here.page;
state.links[path].totalPages = here.totalPages;
state.links[path].totalItems = here.totalItems;
}
let tempArray = state.links[path][page][index].comments["page0"];
tempArray.unshift(tempObj);
state.links[path][page][index].comments["page0"] = tempArray;
}
}
commentPage(json, state) {
let data = _.get(json, 'commentPage', false);
submissionsUpdate(json, state) {
let data = _.get(json, 'submissions', false);
if (data) {
let path = data.path;
let linkPage = "page" + data.linkPage;
let linkIndex = data.index;
let commentPage = "page" + data.comPageNo;
// { "submissions": {
// path: /~ship/group
// pages: [{ship, timestamp, title, url}]
// } }
if (!state.links[path]) {
return false;
}
const path = data.path;
state.links[path][linkPage][linkIndex].comments[commentPage] = data.data;
// stub in a comment count, which is more or less guaranteed to be 0
data.pages = data.pages.map(submission => {
submission.commentCount = 0;
return submission;
});
// add the new submissions to state, update totals
state.links[path] = this._addNewItems(
data.pages, state.links[path]
);
}
}
page(json, state) {
let data = _.get(json, 'page', false);
discussionsPage(json, state) {
let data = _.get(json, 'initial-discussions', false);
if (data) {
let path = Object.keys(data)[0];
let page = "page" + data[path].page;
if (!state.links[path]) {
state.links[path] = {};
// { "initial-discussions": {
// path: "/~ship/group"
// url: https://urbit.org/
// page: [{ship, timestamp, title, url}]
// page-number: 0
// total-items: 1
// total-pages: 1
// } }
const path = data.path;
const url = data.url;
const page = "page" + data.pageNumber;
// if we didn't have any state for this path yet, initialize.
if (!state.comments[path]) {
state.comments[path] = {};
}
state.links[path][page] = data[path].links;
if (!state.comments[path][url]) {
state.comments[path][url] = {};
}
let here = state.comments[path][url];
// since data contains an up-to-date full version of the page,
// we can safely overwrite the one in state.
here[page] = data.page;
here.totalPages = data.totalPages;
here.totalItems = data.totalItems;
}
}
discussionsUpdate(json, state) {
let data = _.get(json, 'discussions', false);
if (data) {
// { "discussions": {
// path: /~ship/path
// url: 'https://urbit.org'
// comments: [{ship, timestamp, udon}]
// } }
const path = data.path;
const url = data.url;
// add new comments to state, update totals
state.comments[path][url] = this._addNewItems(
data.comments, state.comments[path][url]
);
}
}
//
_addNewItems(items, pages = {}, page = 0) {
//TODO kinda want to refactor this, have it just be number indexes
const i = "page" + page;
//TODO but if there's more on the page than just the things we're
// pushing onto it, we won't load that in. should do an
// additional check (+ maybe load) on page-nav, right?
if (!pages[i]) {
pages[i] = [];
}
pages[i] = items.concat(pages[i]);
if (pages[i].length <= PAGE_SIZE) {
pages.totalPages = page + 1;
pages.totalItems = (page * PAGE_SIZE) + pages[i].length;
return pages;
}
// overflow into next page
const tail = pages[i].slice(PAGE_SIZE);
pages[i].length = PAGE_SIZE;
return this._addNewItems(tail, pages, page+1);
}
}

View File

@ -46,6 +46,6 @@ export class PermissionUpdateReducer {
}
}
}
}

View File

@ -11,6 +11,7 @@ class Store {
contacts: {},
groups: {},
links: {},
comments: {},
permissions: {},
sidebarShown: true,
spinner: false
@ -23,24 +24,6 @@ class Store {
this.setState = () => {};
}
async loadLinks(json) {
// if initial contacts, queue up getting these paths from link-store
let data = _.get(json, 'group-initial', false);
if (data) {
for (let each of Object.keys(data)) {
let linkUrl = "/~link/submissions" + each + ".json?p=0";
let promise = await fetch(linkUrl);
if (promise.ok) {
let resolvedData = {}
resolvedData.link = {};
resolvedData.link[each] = {};
resolvedData.link[each] = await promise.json();
this.handleEvent(resolvedData);
}
}
}
}
setStateHandler(setState) {
this.setState = setState;
}
@ -53,8 +36,7 @@ class Store {
json = data;
}
console.log(json);
this.loadLinks(json);
console.log('event', json);
this.initialReducer.reduce(json, this.state);
this.permissionUpdateReducer.reduce(json, this.state);
this.localReducer.reduce(json, this.state);

View File

@ -1,9 +1,6 @@
import { api } from '/api';
import { store } from '/store';
import urbitOb from 'urbit-ob';
export class Subscription {
start() {
if (api.authTokens) {
@ -24,6 +21,9 @@ export class Subscription {
this.handleEvent.bind(this),
this.handleError.bind(this),
this.handleQuitAndResubscribe.bind(this));
// open a subscription for all submissions
api.getPage('', 0);
}
handleEvent(diff) {

View File

@ -176,3 +176,38 @@ a {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.md h1, .md h2, .md h3, .md h4, .md h5, .md p, .md a, .md ul, .md blockquote,.md code,.md pre {
font-size: 14px;
margin-bottom: 16px;
}
.md h2, .md h3, .md h4, .md h5, .md p, .md a, .md ul {
font-weight: 400;
}
.md h1 {
font-weight: 600;
}
.md h2, .md h3, .md h4, .md h5 {
color:var(--gray);
}
.md p {
line-height: 1.5;
}
.md code, .md pre {
font-family: "Source Code Pro", mono;
}
.md ul>li {
line-height: 1.5;
}
.md a {
border-bottom-style: solid;
border-bottom-width: 1px;
}
md img {
margin-bottom: 8px;
}

View File

@ -45,7 +45,7 @@ export class Comments extends Component {
</textarea>
</div>
<button onClick={this.commentSubmit} className="f9 pa2 bg-white br1 ba b--gray2 gray2">
<button onClick={this.commentSubmit} className="f9 pa2 bg-white br1 ba b--gray2 gray2 pointer">
Add comment
</button>
</div>

View File

@ -51,7 +51,7 @@ export class NoteNavigation extends Component {
}
return (
<div className="flex mt4">
<div className="flex pt4">
{prevComponent}
{nextComponent}
</div>

View File

@ -2,8 +2,7 @@ import React, { Component } from 'react';
import { Comments } from './comments';
import { NoteNavigation } from './note-navigation';
import moment from 'moment';
import ReactMarkdown from 'react-markdown'
// import test from 'test.json';
import ReactMarkdown from 'react-markdown';
//TODO ask for note if we don't have it
//TODO initialise note if no state
@ -129,13 +128,14 @@ export class Note extends Component {
<div className="w-100 mw6">
<div className="flex flex-column">
<div className="f9 mb1">{title}</div>
<div className="flex">
<div className="di f9 mono gray2 mr2">{author}</div>
<div className="di f9 gray2">{date}</div>
<div className="flex mb6">
<div className="di f9 mono gray2 mr2">{author}</div>
<div className="di f9 gray2">{date}</div>
</div>
</div>
<ReactMarkdown source={newfile} />
<div className="md">
<ReactMarkdown source={newfile} />
</div>
<NoteNavigation
prev={prev}
next={next}