Merge pull request #2233 from urbit/m/uplink-os1

link: subscriptions for the frontend
This commit is contained in:
matildepark 2020-02-05 15:30:53 -05:00 committed by GitHub
commit 9727fab259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 80397 additions and 792 deletions

View File

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

View File

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

View File

@ -99,10 +99,17 @@
++ permitted ++ permitted
|= [who=ship =path] |= [who=ship =path]
^- ? ^- ?
:: we only expose /local-pages and /annotations, :: we only expose group-specific /local-pages and /annotations,
:: and only to ships in the relevant group :: 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
?& ?=(^ group) ?& ?=(^ group)
(~(has in u.group) who) (~(has in u.group) who)
@ -112,7 +119,7 @@
(scot %p our.bowl) (scot %p our.bowl)
%group-store %group-store
(scot %da now.bowl) (scot %da now.bowl)
(snoc t.path %noun) (snoc u.target %noun)
== ==
:: ::
:: groups subscription :: groups subscription
@ -158,21 +165,22 @@
:: ::
?: =(our.bowl i.whos) ?: =(our.bowl i.whos)
$(whos t.whos) $(whos t.whos)
%+ weld $(whos t.whos) :_ $(whos t.whos)
::NOTE this depends kind of unfortunately on the fact that we only accept ::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. :: "just" looked at all paths in the map, and found the matching ones.
::TODO what exactly did i mean by this? ::TODO what exactly did i mean by this?
:~ (kick-proxy i.whos [%local-pages pax.upd]) %+ kick-proxies i.whos
(kick-proxy i.whos [%annotations pax.upd]) :~ [%local-pages pax.upd]
[%annotations '' pax.upd]
== ==
:: ::
:: proxy subscriptions :: proxy subscriptions
:: ::
++ kick-proxy ++ kick-proxies
|= [who=ship =path] |= [who=ship paths=(list path)]
^- card ^- card
[%give %kick ~[path] `who] [%give %kick paths `who]
:: ::
++ handle-proxy-sign ++ handle-proxy-sign
|= [=wire =sign:agent:gall] |= [=wire =sign:agent:gall]
@ -204,15 +212,14 @@
++ initial-response ++ initial-response
|= =path |= =path
^- card ^- card
=; initial=update =; =initial
[%give %fact ~ %link-update !>(initial)] [%give %fact ~ %link-initial !>(initial)]
?+ path !! ?+ path !!
[%local-pages ^] [%local-pages ^]
[%local-pages path .^(pages %gx path)] [%local-pages .^((map ^path pages) %gx path)]
:: ::
[%annotations @ ^] [%annotations ~ ^]
=+ (split-discussion-path t.path) [%annotations .^((per-path-url notes) %gx '' t.t.path)]
[%annotations path url .^(notes %gx path)]
== ==
:: ::
++ start-proxy ++ 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 :: links, arbitrary paths are probably fair game, but could trip up
:: primitive ui implementations. :: 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-listen-hook to see what's synced in, and similarly
:: see link-proxy-hook to see what's exposed. :: see link-proxy-hook to see what's exposed.
:: ::
:: scry and subscription paths: :: scry and subscription paths:
:: ::
:: urls :: (map path pages)
:: /local-pages/[some-group] all pages we saved by recency :: /local-pages our saved pages
:: /submissions/[some-group] all submissions by recency :: /local-pages/some-path our saved pages on path
:: 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
:: ::
::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 /+ *link, default-agent, verb, dbug
:: ::
@ -27,7 +40,7 @@
$: %0 $: %0
by-group=(map path links) by-group=(map path links)
by-site=(map site (list [path submission])) by-site=(map site (list [path submission]))
discussions=(map path (map url discussion)) discussions=(per-path-url discussion)
== ==
:: ::
+$ links +$ links
@ -81,25 +94,48 @@
?+ path (on-peek:def path) ?+ path (on-peek:def path)
[%y ?(%local-pages %submissions) ~] [%y ?(%local-pages %submissions) ~]
``noun+!>(~(key by by-group)) ``noun+!>(~(key by by-group))
:: ::
[%x %local-pages ^] [%x %local-pages *]
``noun+!>((get-local-pages:do t.t.path)) ``noun+!>((get-local-pages:do t.t.path))
:: ::
[%x %submissions ^] [%x %submissions *]
``noun+!>((get-submissions:do t.t.path)) ``noun+!>((get-submissions:do t.t.path))
::
[%y ?(%annotations %discussions) *]
=/ [spath=^path surl=url]
(break-discussion-path t.t.path)
=- ``noun+!>(-)
:: ::
[%y ?(%annotations %discussions) ~] ?: =(~ surl)
``noun+!>(~(key by discussions)) :: 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) ^] ?^ spath !!
=/ urls (~(get by discussions) t.t.path) :: no path, find paths with comments for this url
?~ urls ~
``noun+!>(~(key by u.urls))
:: ::
[%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)) ``noun+!>((get-annotations:do t.t.path))
:: ::
[%x %discussions @ ^] [%x %discussions *]
``noun+!>((get-discussions:do t.t.path)) ``noun+!>((get-discussions:do t.t.path))
== ==
:: ::
@ -109,29 +145,25 @@
?> (team:title [our src]:bowl) ::TODO /lib/store ?> (team:title [our src]:bowl) ::TODO /lib/store
:_ this :_ this
|^ ?+ path (on-watch:def path) |^ ?+ path (on-watch:def path)
[%local-pages ^] [%local-pages *]
%+ give %link-update %+ give %link-initial
[%local-pages t.path (get-local-pages:do t.path)] ^- initial
[%local-pages (get-local-pages:do t.path)]
:: ::
[%submissions ^] [%submissions *]
%+ give %link-update %+ give %link-initial
[%submissions t.path (get-submissions:do t.path)] ^- initial
[%submissions (get-submissions:do t.path)]
:: ::
[%allotations ^] [%annotations *]
%+ turn %+ give %link-initial
%~ tap by ^- initial
(~(gut by discussions) t.path *(map url discussion)) [%annotations (get-annotations:do t.path)]
|= [=url =discussion]
%+ give-single %link-update
[%annotations t.path url ours.discussion]
:: ::
[%annotations @ ^] [%discussions *]
%+ give %link-update %+ give %link-initial
[%annotations t.path (get-annotations:do t.path)] ^- initial
:: [%discussions (get-discussions:do t.path)]
[%discussions @ ^]
%+ give %link-update
[%discussions t.path (get-discussions:do t.path)]
== ==
:: ::
++ give ++ give
@ -186,7 +218,9 @@
:_ state :_ state
:_ cards :_ cards
:+ %give %fact :+ %give %fact
:+ [%local-pages path]~ :+ :~ /local-pages
[%local-pages path]
==
%link-update %link-update
!>([%local-pages path [page]~]) !>([%local-pages path [page]~])
:: +note-note: save a note for a url :: +note-note: save a note for a url
@ -210,13 +244,15 @@
:: ::
:_ state :_ state
^- (list card) ^- (list card)
=/ fact :_ cards
:- %link-update :+ %give %fact
!>([%annotations path url [note]~]) :+ :~ /annotations
:* [%give %fact [%annotations (snoc path url)]~ fact] [%annotations %$ path]
[%give %fact [%allotations path]~ fact] [%annotations (build-discussion-path url)]
cards [%annotations (build-discussion-path path url)]
== ==
%link-update
!>([%annotations path url [note]~])
:: +hear-submission: record page someone else saved :: +hear-submission: record page someone else saved
:: ::
++ hear-submission ++ hear-submission
@ -237,7 +273,9 @@
:_ state :_ state
:_ ~ :_ ~
:+ %give %fact :+ %give %fact
:+ [%submissions path]~ :+ :~ /submissions
[%submissions path]
==
%link-update %link-update
!>([%submissions path [submission]~]) !>([%submissions path [submission]~])
:: +read-comment: record a comment someone else made :: +read-comment: record a comment someone else made
@ -255,42 +293,102 @@
:: send updates to subscribers :: send updates to subscribers
:: ::
:_ state :_ state
^- (list card) :_ ~
=/ fact :+ %give %fact
:- %link-update :+ :~ /discussions
!>([%discussions path url [comment]~]) [%discussions '' path]
:~ [%give %fact [%discussions (snoc path url)]~ fact] [%discussions (build-discussion-path url)]
[%give %fact [%discussions path]~ fact] [%discussions (build-discussion-path path url)]
== ==
%link-update
!>([%discussions path url [comment]~])
:: ::
:: reading :: reading
:: ::
++ get-local-pages ++ get-local-pages
|= =path |= =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) ours:(~(gut by by-group) path *links)
:: ::
++ get-submissions ++ get-submissions
|= =path |= =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) submissions:(~(gut by by-group) path *links)
:: ::
::
++ get-annotations ++ get-annotations
|= =path |= =path
^- notes ^- (per-path-url notes)
ours:(get-discussion path) =/ 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 ++ get-discussions
|= =path |= =path
^- comments ^- (per-path-url comments)
comments:(get-discussion path) =/ args=[=^path =url]
:: (break-discussion-path path)
++ get-discussion |^ ?~ path
|= =path :: all paths
^- discussion ::
=/ [=^path =url] (~(run by discussions) get-comments)
(split-discussion-path path) :: specific path
=- (~(gut by -) url *discussion) ::
%+ ~(gut by discussions) path %+ ~(put by *(per-path-url comments)) path.args
*(map ^url discussion) %- 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

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

View File

@ -22,18 +22,110 @@
%| (rsh 3 1 (scot %if p.host)) %| (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
^- [=^path =url] ^- [=^path =url]
~| [%path-too-short path] ?~ path [/ '']
?> (gth (lent path) 1) ::TODO ?= would TMI :- t.path
=/ end=@ud (dec (lent path)) ?: =(~ i.path) ''
:- (scag end path) ~| path
(de-base64:mimes:html (snag end 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 ++ en-json
=, enjs:format =, 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 ++ submission
|= sub=^submission |= sub=^submission
^- json ^- json
@ -47,16 +139,22 @@
%- pairs %- pairs
:~ 'title'^s+title.page :~ 'title'^s+title.page
'url'^s+url.page 'url'^s+url.page
'timestamp'^(time time.page) 'time'^(time time.page)
== ==
:: ::
++ comment ++ comment
|= =^comment |= =^comment
^- json ^- json
=+ n=(note +.comment)
?> ?=([%o *] n)
o+(~(put by p.n) 'ship' (ship ship.comment))
::
++ note
|= =^note
^- json
%- pairs %- pairs
:~ 'ship'^(ship ship.comment) :~ 'time'^(time time.note)
'time'^(time time.comment) 'udon'^s+udon.note ::TODO convert?
'udon'^s+udon.comment ::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 :: link: subscription updates
:: ::
::TODO this should include json conversion once mark performance improves
/- *link /- *link
|_ =update |_ =update
++ grow ++ grow

View File

@ -23,6 +23,7 @@
page page
== ==
:: +note: a comment on some url :: +note: a comment on some url
::
+$ note +$ note
$: =time $: =time
udon=@t udon=@t
@ -40,6 +41,12 @@
+$ notes (list note) +$ notes (list note)
+$ comments (list comment) +$ comments (list comment)
:: ::
:: state builder
::
++ per-path-url
|$ [value]
(map path (map url value))
::
:: +action: local actions :: +action: local actions
:: ::
+$ action +$ action
@ -60,6 +67,15 @@
:: ::
[%read =path =url comment] [%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 :: +update: local updates
:: ::
::NOTE we include paths/urls to support the "subscribed to all" case ::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)]~) (emit-event channel-id [(en-json:html json)]~)
:: ::
%kick %kick
~& [%recieved-quit-from-gall channel-id]
=/ =json =/ =json
=, enjs:format =, enjs:format
%- pairs :~ %- pairs :~

View File

@ -2148,7 +2148,8 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -2172,13 +2173,15 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"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"
@ -2195,19 +2198,22 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -2338,7 +2344,8 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "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", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -2368,6 +2376,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -2376,13 +2385,15 @@
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
"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"
@ -2403,6 +2414,7 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -2491,7 +2503,8 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -2505,6 +2518,7 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -2600,7 +2614,8 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -2642,6 +2657,7 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"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",
@ -2663,6 +2679,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -2711,13 +2728,15 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "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) { getComments(path, url) {
let endpoint = "/~link/discussions" + path + "/" + window.btoa(url) + ".json?p=0"; return this.getCommentsPage.bind(this)(path, url, 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);
}
} }
async getCommentsPage(path, url, page, index, commentPage) { getCommentsPage(path, url, page) {
let endpoint = "/~link/discussions" + path + "/" + window.btoa(url) + ".json?p=" + commentPage; //TODO factor out
let promise = await fetch(endpoint); // encode the url into @ta-safe format, using logic from +wood
if (promise.ok) { let strictUrl = '';
let responseData = await promise.json(); for (let i = 0; i < url.length; i++) {
let update = { const char = url[i];
"link-update": { let add = '';
commentPage: { switch (char) {
path: path, case ' ':
linkPage: page, add = '.';
index: index, break;
comPageNo: commentPage, case '.':
data: responseData.page 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) + '.';
} }
} }
}; strictUrl = strictUrl + add;
store.handleEvent(update);
} }
} strictUrl = '~.' + strictUrl;
async getPage(path, page) { const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
let endpoint = "/~link/submissions" + path + ".json?p=" + page; this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
let promise = await fetch(endpoint); (res) => {
if (promise.ok) { if (res.data['initial-discussions']) {
let resolvedPage = await promise.json(); // these aren't returned with the response,
let update = { // so this ensures the reducers know them.
"link-update": { res.data['initial-discussions'].path = path;
page: { res.data['initial-discussions'].url = url;
[path]: {
page: page,
links: resolvedPage.page
}
}
} }
}; store.handleEvent(res);
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'
}, },
body: JSON.stringify(json) console.error,
}); ()=>{} // no-op on quit
);
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;
}
} }
async postComment(path, url, comment, page, index) { getPage(path, page) {
let json = { const endpoint = '/json/' + page + '/submissions' + path;
path: path, this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
url: url, (dat)=>{store.handleEvent(dat)},
udon: comment console.error,
} ()=>{} // no-op on quit
);
}
let endpoint = "/~link/note"; linkAction(data) {
let post = await fetch(endpoint, { return this.action("link-store", "link-action", data);
method: "POST", }
credentials: 'include',
headers: { postLink(path, url, title) {
'Content-Type': 'application/json' return this.linkAction({
}, 'save': { path, url, title }
body: JSON.stringify(json)
}); });
}
if (post.ok) { postComment(path, url, comment, page, index) {
let update = { return this.linkAction({
"link-update": { 'note': { path, url, udon: comment }
commentAdd: { });
path: path,
url: url,
udon: comment,
page: page,
index: index,
time: moment.now()
}
}
};
store.handleEvent(update);
return true;
} else {
return false;
}
} }
sidebarToggle() { sidebarToggle() {

View File

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

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import { CommentItem } from './comment-item'; import { CommentItem } from './comment-item';
import { CommentsPagination } from './comments-pagination'; import { CommentsPagination } from './comments-pagination';
import { uxToHex } from '../../lib/util'; import { uxToHex } from '../../lib/util';
import { api } from '../../api'; import { api } from '../../api';
export class Comments extends Component { export class Comments extends Component {
@ -10,12 +10,13 @@ export class Comments extends Component {
componentDidMount() { componentDidMount() {
let page = "page" + this.props.commentPage; let page = "page" + this.props.commentPage;
let comments = !!this.props.comments; 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( api.getCommentsPage(
this.props.path, this.props.path,
this.props.url, this.props.url,
this.props.linkPage,
this.props.linkIndex,
this.props.commentPage); this.props.commentPage);
} }
} }
@ -24,12 +25,10 @@ export class Comments extends Component {
let page = "page" + this.props.commentPage; let page = "page" + this.props.commentPage;
if (prevProps !== this.props) { if (prevProps !== this.props) {
if (!!this.props.comments) { if (!!this.props.comments) {
if ((page !== "page0") && (!this.props.comments[page])) { if ((page !== "page0") && !this.props.comments[page] && this.props.url) {
api.getCommentsPage( api.getCommentsPage(
this.props.path, this.props.path,
this.props.url, this.props.url,
this.props.linkPage,
this.props.linkIndex,
this.props.commentPage); this.props.commentPage);
} }
} }
@ -38,7 +37,7 @@ export class Comments extends Component {
render() { render() {
let props = this.props; let props = this.props;
let page = "page" + props.commentPage; let page = "page" + props.commentPage;
let commentsObj = !!props.comments let commentsObj = !!props.comments
@ -50,7 +49,7 @@ export class Comments extends Component {
: {}; : {};
let total = !!props.comments let total = !!props.comments
? props.comments["total-pages"] ? props.comments.totalPages
: {}; : {};
let commentsList = Object.keys(commentsPage) let commentsList = Object.keys(commentsPage)
@ -59,7 +58,7 @@ export class Comments extends Component {
let commentObj = commentsPage[entry] let commentObj = commentsPage[entry]
let { ship, time, udon } = commentObj; let { ship, time, udon } = commentObj;
let members = !!props.members let members = !!props.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"); let comments = props.comments + " comment" + ((props.comments === 1) ? "" : "s");
return ( return (
<div className="w-100 pv3 flex"> <div className="w-100 pv3 flex">
<Sigil <Sigil
@ -63,8 +63,8 @@ export class LinkItem extends Component {
</p> </p>
</a> </a>
<div className="w-100 pt1"> <div className="w-100 pt1">
<span className={"f9 pr2 v-mid " + mono}>{(props.nickname) <span className={"f9 pr2 v-mid " + mono}>{(props.nickname)
? props.nickname ? props.nickname
: "~" + props.ship}</span> : "~" + props.ship}</span>
<span className="f9 inter gray2 pr3 v-mid">{this.state.timeSinceLinkPost}</span> <span className="f9 inter gray2 pr3 v-mid">{this.state.timeSinceLinkPost}</span>
<Link to= <Link to=

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,92 +1,141 @@
import _ from 'lodash'; import _ from 'lodash';
const PAGE_SIZE = 25;
export class LinkUpdateReducer { export class LinkUpdateReducer {
reduce(json, state) { reduce(json, state) {
let data = _.get(json, 'link-update', false); this.submissionsPage(json, state);
if (data) { this.submissionsUpdate(json, state);
this.add(data, state); this.discussionsPage(json, state);
this.comments(data, state); this.discussionsUpdate(json, state);
this.commentAdd(data, state);
this.commentPage(data, state);
this.page(data, state);
}
} }
add(json, state) { submissionsPage(json, state) {
// pin ok'd link POSTs to top of page0 let data = _.get(json, 'initial-submissions', false);
let data = _.get(json, 'add', false);
if (data) { if (data) {
let path = Object.keys(data)[0]; // { "initial-submissions": {
let tempArray = state.links[path].page0; // "/~ship/group": {
tempArray.unshift(data[path]); // page: [{ship, timestamp, title, url}]
state.links[path].page0 = tempArray; // page-number: 0
} // total-items: 1
} // total-pages: 1
// }
// } }
comments(json, state) { for (var path of Object.keys(data)) {
let data = _.get(json, 'comments', false); const here = data[path];
if (data) { const page = "page" + here.pageNumber;
let path = data.path;
let page = "page" + data.page;
let index = data.index;
let storage = state.links[path][page][index];
storage.comments = {}; // if we didn't have any state for this path yet, initialize.
storage.comments["page0"] = data.data.page; if (!state.links[path]) {
storage.comments["total-items"] = data.data["total-items"]; state.links[path] = {};
storage.comments["total-pages"] = data.data["total-pages"]; }
state.links[path][page][index] = storage; // 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;
commentAdd(json, state) { state.links[path].totalItems = here.totalItems;
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
} }
let tempArray = state.links[path][page][index].comments["page0"];
tempArray.unshift(tempObj);
state.links[path][page][index].comments["page0"] = tempArray;
} }
} }
commentPage(json, state) { submissionsUpdate(json, state) {
let data = _.get(json, 'commentPage', false); let data = _.get(json, 'submissions', false);
if (data) { if (data) {
let path = data.path; // { "submissions": {
let linkPage = "page" + data.linkPage; // path: /~ship/group
let linkIndex = data.index; // pages: [{ship, timestamp, title, url}]
let commentPage = "page" + data.comPageNo; // } }
if (!state.links[path]) { const path = data.path;
return false;
}
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) { discussionsPage(json, state) {
let data = _.get(json, 'page', false); let data = _.get(json, 'initial-discussions', false);
if (data) { if (data) {
let path = Object.keys(data)[0]; // { "initial-discussions": {
let page = "page" + data[path].page; // path: "/~ship/group"
if (!state.links[path]) { // url: https://urbit.org/
state.links[path] = {}; // 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: {}, contacts: {},
groups: {}, groups: {},
links: {}, links: {},
comments: {},
permissions: {}, permissions: {},
sidebarShown: true, sidebarShown: true,
spinner: false spinner: false
@ -23,24 +24,6 @@ class Store {
this.setState = () => {}; 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) { setStateHandler(setState) {
this.setState = setState; this.setState = setState;
} }
@ -53,8 +36,7 @@ class Store {
json = data; json = data;
} }
console.log(json); console.log('event', json);
this.loadLinks(json);
this.initialReducer.reduce(json, this.state); this.initialReducer.reduce(json, this.state);
this.permissionUpdateReducer.reduce(json, this.state); this.permissionUpdateReducer.reduce(json, this.state);
this.localReducer.reduce(json, this.state); this.localReducer.reduce(json, this.state);

View File

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