mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 13:54:20 +03:00
Merge remote-tracking branch 'origin/release/next-userspace' into ford-fusion
This commit is contained in:
commit
26ff47b93d
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e56c9761cd1d5a4a37dd8168d38fbd53b0a904c0cd208ff0fc84f009dbb3b92e
|
||||
size 16655606
|
||||
oid sha256:1f979e590824c3b7776214fe9e9c26da8ff15d342ec1d9bccd3b781c9a034184
|
||||
size 16663090
|
||||
|
@ -451,6 +451,7 @@
|
||||
::
|
||||
++ parser
|
||||
|^
|
||||
%+ stag |
|
||||
%+ knee *command |. ~+
|
||||
=- ;~(pose ;~(pfix mic -) message)
|
||||
;~ pose
|
||||
|
@ -12,8 +12,8 @@
|
||||
<body class="w-100 h-100">
|
||||
<div id="root" class="w-100 h-100">
|
||||
</div>
|
||||
<script src="/~channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~debug/js/index.js"></script>
|
||||
</body>
|
||||
|
||||
|
@ -14,8 +14,8 @@
|
||||
:: to expede this process, we prod other potential listeners when we add
|
||||
:: them to our metadata+groups definition.
|
||||
::
|
||||
/- link-listen-hook, *metadata-store, *link, group-store
|
||||
/+ mdl=metadata, default-agent, verb, dbug
|
||||
/- *link, listen-hook=link-listen-hook, *metadata-store, group-store
|
||||
/+ mdl=metadata, default-agent, verb, dbug, store=link-store
|
||||
::
|
||||
~% %link-listen-hook-top ..is ~
|
||||
|%
|
||||
@ -167,7 +167,7 @@
|
||||
?> (team:title [our src]:bowl)
|
||||
=^ cards state
|
||||
~| p.vase
|
||||
(handle-listen-action:do !<(action:link-listen-hook vase))
|
||||
(handle-listen-action:do !<(action:listen-hook vase))
|
||||
[cards this]
|
||||
==
|
||||
::
|
||||
@ -218,7 +218,7 @@
|
||||
:: user actions & updates
|
||||
::
|
||||
++ handle-listen-action
|
||||
|= =action:link-listen-hook
|
||||
|= =action:listen-hook
|
||||
^- (quip card _state)
|
||||
::NOTE no-opping where appropriate happens further down the call stack.
|
||||
:: we *could* no-op here, as %watch when we're already listening should
|
||||
@ -250,7 +250,7 @@
|
||||
$(cards (weld cards more-cards), groups t.groups)
|
||||
::
|
||||
++ send-update
|
||||
|= =update:link-listen-hook
|
||||
|= =update:listen-hook
|
||||
^- card
|
||||
[%give %fact ~[/listening] %link-listen-update !>(update)]
|
||||
::
|
||||
@ -500,11 +500,11 @@
|
||||
?+ mark ~|([dap.bowl %unexpected-mark mark] !!)
|
||||
%link-initial
|
||||
%- handle-link-initial
|
||||
[who.target where.target !<(initial vase)]
|
||||
[who.target where.target !<(initial:store vase)]
|
||||
::
|
||||
%link-update
|
||||
%- handle-link-update
|
||||
[who.target where.target !<(update vase)]
|
||||
[who.target where.target !<(update:store vase)]
|
||||
==
|
||||
==
|
||||
::
|
||||
@ -546,7 +546,7 @@
|
||||
group-path
|
||||
::
|
||||
++ do-link-action
|
||||
|= [=wire =action]
|
||||
|= [=wire =action:store]
|
||||
^- card
|
||||
:* %pass
|
||||
wire
|
||||
@ -558,7 +558,7 @@
|
||||
==
|
||||
::
|
||||
++ handle-link-initial
|
||||
|= [who=ship where=path =initial]
|
||||
|= [who=ship where=path =initial:store]
|
||||
^- (quip card _state)
|
||||
?> =(src.bowl who)
|
||||
?+ -.initial ~|([dap.bowl %unexpected-initial -.initial] !!)
|
||||
@ -580,7 +580,7 @@
|
||||
==
|
||||
::
|
||||
++ handle-link-update
|
||||
|= [who=ship where=path =update]
|
||||
|= [who=ship where=path =update:store]
|
||||
^- (quip card _state)
|
||||
?> =(src.bowl who)
|
||||
:_ state
|
||||
@ -594,11 +594,11 @@
|
||||
::
|
||||
%annotations
|
||||
%+ turn notes.update
|
||||
|= =note
|
||||
|= =^note
|
||||
^- card
|
||||
%+ do-link-action
|
||||
[%forward %annotation (scot %p who) where]
|
||||
[%read where url.update who note]
|
||||
[%read where url.update `comment`[who note]]
|
||||
==
|
||||
::
|
||||
++ take-forward-sign
|
||||
|
@ -19,8 +19,8 @@
|
||||
:: when adding support for new paths, the only things you'll likely want
|
||||
:: to touch are +permitted, +initial-response, & +kick-proxies.
|
||||
::
|
||||
/- group-store, *metadata-store
|
||||
/+ *link, metadata, default-agent, verb, dbug
|
||||
/- *link, group-store, *metadata-store
|
||||
/+ store=link-store, metadata, default-agent, verb, dbug
|
||||
~% %link-proxy-hook-top ..is ~
|
||||
|%
|
||||
+$ state-0
|
||||
@ -269,7 +269,7 @@
|
||||
++ initial-response
|
||||
|= =path
|
||||
^- card
|
||||
=; =initial
|
||||
=; =initial:store
|
||||
[%give %fact ~ %link-initial !>(initial)]
|
||||
?+ path !!
|
||||
[%local-pages ^]
|
||||
|
@ -50,7 +50,8 @@
|
||||
:: ?
|
||||
:: /seen/wood-url/some-path have we seen this here
|
||||
::
|
||||
/+ *link, default-agent, verb, dbug
|
||||
/- *link
|
||||
/+ store=link-store, default-agent, verb, dbug
|
||||
::
|
||||
|%
|
||||
+$ state-0
|
||||
@ -101,8 +102,8 @@
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
::TODO move json conversion into mark once mark performance improves
|
||||
%json (do-action:do (action:de-json !<(json vase)))
|
||||
%link-action (do-action:do !<(action vase))
|
||||
%json (do-action:do (action:dejs:store !<(json vase)))
|
||||
%link-action (do-action:do !<(action:store vase))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
@ -121,7 +122,7 @@
|
||||
::
|
||||
[%y ?(%annotations %discussions) *]
|
||||
=/ [spath=^path surl=url]
|
||||
(break-discussion-path t.t.path)
|
||||
(break-discussion-path:store t.t.path)
|
||||
=- ``noun+!>(-)
|
||||
::
|
||||
?: =(~ surl)
|
||||
@ -174,22 +175,22 @@
|
||||
|^ ?+ path (on-watch:def path)
|
||||
[%local-pages *]
|
||||
%+ give %link-initial
|
||||
^- initial
|
||||
^- initial:store
|
||||
[%local-pages (get-local-pages:do t.path)]
|
||||
::
|
||||
[%submissions *]
|
||||
%+ give %link-initial
|
||||
^- initial
|
||||
^- initial:store
|
||||
[%submissions (get-submissions:do t.path)]
|
||||
::
|
||||
[%annotations *]
|
||||
%+ give %link-initial
|
||||
^- initial
|
||||
^- initial:store
|
||||
[%annotations (get-annotations:do t.path)]
|
||||
::
|
||||
[%discussions *]
|
||||
%+ give %link-initial
|
||||
^- initial
|
||||
^- initial:store
|
||||
[%discussions (get-discussions:do t.path)]
|
||||
::
|
||||
[%seen ~]
|
||||
@ -218,7 +219,7 @@
|
||||
:: writing
|
||||
::
|
||||
++ do-action
|
||||
|= =action
|
||||
|= =action:store
|
||||
^- (quip card _state)
|
||||
?- -.action
|
||||
%save (save-page +.action)
|
||||
@ -284,8 +285,8 @@
|
||||
:+ %give %fact
|
||||
:+ :~ /annotations
|
||||
[%annotations %$ path]
|
||||
[%annotations (build-discussion-path url)]
|
||||
[%annotations (build-discussion-path path url)]
|
||||
[%annotations (build-discussion-path:store url)]
|
||||
[%annotations (build-discussion-path:store path url)]
|
||||
==
|
||||
%link-update
|
||||
!>([%annotations path url [note]~])
|
||||
@ -324,11 +325,11 @@
|
||||
?: ?=(^ (find ~[submission] submissions.links))
|
||||
[| submissions.links]
|
||||
:- &
|
||||
(submissions:merge submissions.links ~[submission])
|
||||
(submissions:merge:store submissions.links ~[submission])
|
||||
=. by-group (~(put by by-group) path links)
|
||||
:: add submission to global sites
|
||||
::
|
||||
=/ =site (site-from-url url.submission)
|
||||
=/ =site (site-from-url:store url.submission)
|
||||
=. by-site (~(add ja by-site) site [path submission])
|
||||
:: send updates to subscribers
|
||||
::
|
||||
@ -354,7 +355,7 @@
|
||||
?: ?=(^ (find ~[comment] comments.discussion))
|
||||
[| comments.discussion]
|
||||
:- &
|
||||
(comments:merge comments.discussion ~[comment])
|
||||
(comments:merge:store comments.discussion ~[comment])
|
||||
=. urls (~(put by urls) url discussion)
|
||||
=. discussions (~(put by discussions) path urls)
|
||||
:: send updates to subscribers
|
||||
@ -365,8 +366,8 @@
|
||||
:+ %give %fact
|
||||
:+ :~ /discussions
|
||||
[%discussions '' path]
|
||||
[%discussions (build-discussion-path url)]
|
||||
[%discussions (build-discussion-path path url)]
|
||||
[%discussions (build-discussion-path:store url)]
|
||||
[%discussions (build-discussion-path:store path url)]
|
||||
==
|
||||
%link-update
|
||||
!>([%discussions path url [comment]~])
|
||||
@ -420,7 +421,7 @@
|
||||
|= =path
|
||||
^- ?
|
||||
=/ [=^path =url]
|
||||
(break-discussion-path path)
|
||||
(break-discussion-path:store path)
|
||||
%. url
|
||||
%~ has in
|
||||
seen:(~(gut by by-group) path *links)
|
||||
@ -430,7 +431,7 @@
|
||||
|= =path
|
||||
^- (per-path-url notes)
|
||||
=/ args=[=^path =url]
|
||||
(break-discussion-path path)
|
||||
(break-discussion-path:store path)
|
||||
|^ ?~ path
|
||||
:: all paths
|
||||
::
|
||||
@ -460,7 +461,7 @@
|
||||
|= =path
|
||||
^- (per-path-url comments)
|
||||
=/ args=[=^path =url]
|
||||
(break-discussion-path path)
|
||||
(break-discussion-path:store path)
|
||||
|^ ?~ path
|
||||
:: all paths
|
||||
::
|
||||
|
@ -10,12 +10,12 @@
|
||||
:: /json/[n]/submission/[wood-url]/[collection] nth matching submission
|
||||
:: /json/seen mark-as-read updates
|
||||
::
|
||||
/- *link-view
|
||||
/- *link, view=link-view
|
||||
/- *invite-store, group-store
|
||||
/- link-listen-hook
|
||||
/- listen-hook=link-listen-hook
|
||||
/- group-hook, permission-hook, permission-group-hook
|
||||
/- metadata-hook, contact-view
|
||||
/+ *link, metadata, *server, default-agent, verb, dbug
|
||||
/+ store=link-store, metadata, *server, default-agent, verb, dbug
|
||||
~% %link-view-top ..is ~
|
||||
::
|
||||
|%
|
||||
@ -89,10 +89,10 @@
|
||||
:_ this
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%link-action
|
||||
[(handle-action:do !<(action vase)) ~]
|
||||
[(handle-action:do !<(action:store vase)) ~]
|
||||
::
|
||||
%link-view-action
|
||||
(handle-view-action:do !<(view-action vase))
|
||||
(handle-view-action:do !<(action:view vase))
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
@ -117,11 +117,11 @@
|
||||
::
|
||||
[%submission @ ^]
|
||||
:_ this
|
||||
(give-specific-submission:do p (break-discussion-path t.t.t.path))
|
||||
(give-specific-submission:do p (break-discussion-path:store t.t.t.path))
|
||||
::
|
||||
[%discussions @ ^]
|
||||
:_ this
|
||||
(give-initial-discussions:do p (break-discussion-path t.t.t.path))
|
||||
(give-initial-discussions:do p (break-discussion-path:store t.t.t.path))
|
||||
==
|
||||
::
|
||||
++ on-agent
|
||||
@ -145,7 +145,7 @@
|
||||
::
|
||||
%link-update
|
||||
:_ this
|
||||
:- (send-update:do !<(update vase))
|
||||
:- (send-update:do !<(update:store vase))
|
||||
?: =(/discussions wire) ~
|
||||
~[give-tile-data:do]
|
||||
==
|
||||
@ -221,12 +221,12 @@
|
||||
==
|
||||
::
|
||||
++ handle-action
|
||||
|= =action
|
||||
|= =action:store
|
||||
^- card
|
||||
[%pass /action %agent [our.bowl %link-store] %poke %link-action !>(action)]
|
||||
::
|
||||
++ handle-view-action
|
||||
|= act=view-action
|
||||
|= act=action:view
|
||||
^- (list card)
|
||||
?- -.act
|
||||
%create (handle-create +.act)
|
||||
@ -235,7 +235,7 @@
|
||||
==
|
||||
::
|
||||
++ handle-create
|
||||
|= [=path title=@t description=@t members=create-members real-group=?]
|
||||
|= [=path title=@t description=@t members=create-members:view real-group=?]
|
||||
^- (list card)
|
||||
=/ group-path=^path
|
||||
?- -.members
|
||||
@ -273,7 +273,7 @@
|
||||
::
|
||||
%^ do-poke %link-listen-hook
|
||||
%link-listen-action
|
||||
!> ^- action:link-listen-hook
|
||||
!> ^- action:listen-hook
|
||||
[%watch path]
|
||||
==
|
||||
?: ?=(%group -.members) ~
|
||||
@ -453,6 +453,7 @@
|
||||
[%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'link-update'
|
||||
%+ frond:enjs:format 'initial-submissions'
|
||||
%- pairs:enjs:format
|
||||
%+ turn
|
||||
@ -486,7 +487,7 @@
|
||||
submissions
|
||||
|= =submission
|
||||
^- json
|
||||
=/ =json (submission:en-json submission)
|
||||
=/ =json (submission:enjs:store submission)
|
||||
?> ?=([%o *] json)
|
||||
:: add in seen status
|
||||
::
|
||||
@ -494,7 +495,7 @@
|
||||
%+ ~(put by p.json) 'seen'
|
||||
:- %b
|
||||
%+ scry-for ?
|
||||
[%seen (build-discussion-path path url.submission)]
|
||||
[%seen (build-discussion-path:store path url.submission)]
|
||||
:: add in comment count
|
||||
::
|
||||
=; comment-count=@ud
|
||||
@ -507,18 +508,19 @@
|
||||
=- (~(got by (~(got by -) path)) url.submission)
|
||||
%+ scry-for (per-path-url comments)
|
||||
:- %discussions
|
||||
(build-discussion-path path url.submission)
|
||||
(build-discussion-path:store path url.submission)
|
||||
::
|
||||
++ give-specific-submission
|
||||
|= [n=@ud =path =url]
|
||||
:_ [%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'link-update'
|
||||
%+ frond:enjs:format 'submission'
|
||||
^- json
|
||||
=; sub=(unit submission)
|
||||
?~ sub ~
|
||||
(submission:en-json u.sub)
|
||||
(submission:enjs:store u.sub)
|
||||
=/ =submissions
|
||||
=- (~(got by -) path)
|
||||
%+ scry-for (map ^path submissions)
|
||||
@ -538,35 +540,39 @@
|
||||
[%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'link-update'
|
||||
%+ 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
|
||||
[%discussions (build-discussion-path:store path url)]
|
||||
comment:enjs:store
|
||||
::
|
||||
++ send-update
|
||||
|= =update
|
||||
|= =update:store
|
||||
^- card
|
||||
?+ -.update ~|([dap.bowl %unexpected-update -.update] !!)
|
||||
%submissions
|
||||
%+ give-json
|
||||
(update:en-json update)
|
||||
%+ frond:enjs:format 'link-update'
|
||||
(update:enjs:store update)
|
||||
:~ /json/0/submissions
|
||||
(weld /json/0/submissions path.update)
|
||||
==
|
||||
::
|
||||
%discussions
|
||||
%+ give-json
|
||||
(update:en-json update)
|
||||
%+ frond:enjs:format 'link-update'
|
||||
(update:enjs:store update)
|
||||
:_ ~
|
||||
%+ weld /json/0/discussions
|
||||
(build-discussion-path [path url]:update)
|
||||
(build-discussion-path:store [path url]:update)
|
||||
::
|
||||
%observation
|
||||
%+ give-json
|
||||
(update:en-json update)
|
||||
%+ frond:enjs:format 'link-update'
|
||||
(update:enjs:store update)
|
||||
~[/json/seen]
|
||||
==
|
||||
::
|
||||
|
@ -415,8 +415,13 @@
|
||||
[%subscribe @ @ ~]
|
||||
=/ who=@p (slav %p i.t.wir)
|
||||
=/ book=@tas i.t.t.wir
|
||||
=/ wen=(unit @da) (get-last-update:main who book)
|
||||
=/ pax=path
|
||||
?~ wen
|
||||
/notebook/[book]
|
||||
/notebook/[book]/(scot %da u.wen)
|
||||
:_ this
|
||||
[%pass wir %agent [who %publish] %watch /notebook/[book]]~
|
||||
[%pass wir %agent [who %publish] %watch pax]~
|
||||
::
|
||||
[%permissions ~]
|
||||
:_ this
|
||||
@ -502,6 +507,59 @@
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
::
|
||||
++ get-last-update
|
||||
|= [host=@p book-name=@tas]
|
||||
^- (unit @da)
|
||||
=/ book (~(get by books) host book-name)
|
||||
?~ book ~
|
||||
=/ wen date-created.u.book
|
||||
%- some
|
||||
%- ~(rep by notes.u.book)
|
||||
|= [[@tas =note] out=_wen]
|
||||
^- @da
|
||||
%+ max out
|
||||
%+ max last-edit.note
|
||||
%- ~(rep by comments.note)
|
||||
|= [[@da =comment] out=_out]
|
||||
(max date-created.comment out)
|
||||
::
|
||||
++ get-notebook-from-date
|
||||
|= [host=@p book-name=@tas wen=@da]
|
||||
^- notebook
|
||||
=/ book (~(got by books) host book-name)
|
||||
%= book
|
||||
notes
|
||||
%- ~(rep by notes.book)
|
||||
|= [[nom=@tas not=note] out=(map @tas note)]
|
||||
^- (map @tas note)
|
||||
?: (gth last-edit.not wen)
|
||||
(~(put by out) nom not)
|
||||
=. comments.not
|
||||
%- ~(rep by comments.not)
|
||||
|= [[nam=@da com=comment] out=(map @da comment)]
|
||||
?: (gth date-created.com wen)
|
||||
(~(put by out) nam com)
|
||||
out
|
||||
?~ comments.not
|
||||
out
|
||||
(~(put by out) nom not)
|
||||
==
|
||||
::
|
||||
++ merge-notebooks
|
||||
|= [base=notebook diff=notebook]
|
||||
^- notebook
|
||||
%= diff
|
||||
notes
|
||||
%- ~(rep by notes.diff)
|
||||
|= [[nom=@tas not=note] out=_notes.base]
|
||||
=/ base-note=(unit note) (~(get by out) nom)
|
||||
?~ base-note
|
||||
(~(put by out) nom not)
|
||||
=. comments.u.base-note
|
||||
(~(uni by comments.u.base-note) comments.not)
|
||||
(~(put by out) nom u.base-note)
|
||||
==
|
||||
::
|
||||
++ read-paths
|
||||
|= ran=rant:clay
|
||||
^- (quip card _state)
|
||||
@ -841,6 +899,19 @@
|
||||
%.n
|
||||
==
|
||||
::
|
||||
++ get-subscriber-paths
|
||||
|= [book-name=@tas who=@p]
|
||||
^- (list path)
|
||||
%+ roll ~(val by sup.bol)
|
||||
|= [[whom=@p pax=path] out=(list path)]
|
||||
?. =(who whom)
|
||||
out
|
||||
?. ?=([%notebook @ *] pax)
|
||||
out
|
||||
?. =(i.t.pax book-name)
|
||||
out
|
||||
[pax out]
|
||||
::
|
||||
++ handle-permission-update
|
||||
|= upd=permission-update
|
||||
^- (quip card _state)
|
||||
@ -861,7 +932,7 @@
|
||||
%+ turn ~(tap in who.upd)
|
||||
|= who=@p
|
||||
?. (allowed who %read u.book)
|
||||
[%give %kick [/notebook/[u.book]]~ `who]~
|
||||
[%give %kick (get-subscriber-paths u.book who) `who]~
|
||||
?: ?|(?=(%remove -.upd) (is-managed path.upd))
|
||||
~
|
||||
=/ uid (sham %publish who u.book eny.bol)
|
||||
@ -897,11 +968,15 @@
|
||||
::
|
||||
++ watch-notebook
|
||||
|= pax=path
|
||||
?> ?=([%notebook @ ~] pax)
|
||||
?> ?=([%notebook @ *] pax)
|
||||
=/ book-name i.t.pax
|
||||
?. (allowed src.bol %read book-name)
|
||||
~|("not permitted" !!)
|
||||
=/ book (~(got by books) our.bol book-name)
|
||||
=/ book
|
||||
?: ?=([%notebook @ @ ~] pax)
|
||||
=/ wen=@da (slav %da i.t.t.pax)
|
||||
(get-notebook-from-date our.bol book-name wen)
|
||||
(~(got by books) our.bol book-name)
|
||||
=/ delta=notebook-delta
|
||||
[%add-book our.bol book-name book]
|
||||
:_ state
|
||||
@ -1754,7 +1829,7 @@
|
||||
%+ turn ~(tap in dif-peeps)
|
||||
|= who=@p
|
||||
^- card
|
||||
[%give %kick [/notebook/[book.act]]~ `who]
|
||||
[%give %kick (get-subscriber-paths book.act who) `who]
|
||||
==
|
||||
::
|
||||
++ get-subscribers
|
||||
@ -1898,6 +1973,8 @@
|
||||
date-created.data.del
|
||||
==
|
||||
==
|
||||
=? data.del (~(has by books) host.del book.del)
|
||||
(merge-notebooks (~(got by books) host.del book.del) data.del)
|
||||
=^ cards state
|
||||
(emit-updates-and-state host.del book.del data.del del sty)
|
||||
:_ state
|
||||
|
@ -40,8 +40,8 @@
|
||||
::
|
||||
++ command-parser
|
||||
|= sole-id=@ta
|
||||
^+ |~(nail *(like command))
|
||||
(cold ~ (jest 'demo'))
|
||||
^+ |~(nail *(like [? command]))
|
||||
(cold [& ~] (jest 'demo'))
|
||||
::
|
||||
++ tab-list
|
||||
|= sole-id=@ta
|
||||
|
@ -1,6 +1,6 @@
|
||||
:: link-store|note: write a note on a link in a path
|
||||
::
|
||||
/- *link
|
||||
/- *link-store, *link
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=path =url note=@t ~] ~]
|
||||
|
@ -1,6 +1,6 @@
|
||||
:: link-store|save: save a link to a path
|
||||
::
|
||||
/- *link
|
||||
/- *link-store, *link
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=path title=@t =url ~] ~]
|
||||
|
@ -248,15 +248,15 @@
|
||||
::
|
||||
++ get-id
|
||||
|= [pos=@ud txt=tape]
|
||||
^- [forward=(unit term) backward=(unit term) id=(unit term)]
|
||||
=/ forward=(unit term)
|
||||
%+ scan `tape`(slag pos txt)
|
||||
;~(sfix (punt sym) (star ;~(pose prn (just `@`10))))
|
||||
=/ backward=(unit term)
|
||||
^- [forward=(unit @t) backward=(unit @t) id=(unit @t)]
|
||||
=/ seek
|
||||
;~(sfix (punt (cook crip (star prn))) (star ;~(pose prn (just `@`10))))
|
||||
=/ forward=(unit @t)
|
||||
(scan (slag pos txt) seek)
|
||||
=/ backward=(unit @t)
|
||||
%- (lift |=(t=@tas (swp 3 t)))
|
||||
%+ scan `tape`(flop (scag pos txt))
|
||||
;~(sfix (punt sym) (star ;~(pose prn (just `@`10))))
|
||||
=/ id=(unit term)
|
||||
(scan (flop (scag pos txt)) seek)
|
||||
=/ id=(unit @t)
|
||||
?~ forward
|
||||
?~ backward
|
||||
~
|
||||
|
@ -1,7 +1,10 @@
|
||||
:: link: social bookmarking
|
||||
::
|
||||
/- *link
|
||||
/- sur=link-store, *link
|
||||
::
|
||||
^?
|
||||
=< [. sur]
|
||||
=, sur
|
||||
|%
|
||||
++ site-from-url
|
||||
|= =url
|
||||
@ -92,8 +95,9 @@
|
||||
[a b]
|
||||
--
|
||||
::
|
||||
++ en-json
|
||||
++ enjs
|
||||
=, enjs:format
|
||||
^?
|
||||
|%
|
||||
++ update
|
||||
|= upd=^update
|
||||
@ -166,8 +170,9 @@
|
||||
==
|
||||
--
|
||||
::
|
||||
++ de-json
|
||||
++ dejs
|
||||
=, dejs:format
|
||||
^?
|
||||
|%
|
||||
:: +action: json into action
|
||||
::
|
@ -35,14 +35,20 @@
|
||||
|* command-type=mold
|
||||
$_ ^|
|
||||
|_ bowl:gall
|
||||
:: +command-parser: input parser for a specific session
|
||||
::
|
||||
:: if the head of the result is true, instantly run the command
|
||||
::
|
||||
++ command-parser
|
||||
|~ sole-id=@ta
|
||||
|~(nail *(like command-type))
|
||||
|~(nail *(like [? command-type]))
|
||||
:: +tab-list: autocomplete options for the session (to match +command-parser)
|
||||
::
|
||||
++ tab-list
|
||||
|~ sole-id=@ta
|
||||
:: (list [@t tank])
|
||||
*(list (option:auto tank))
|
||||
:: +on-command: called when a valid command is run
|
||||
::
|
||||
++ on-command
|
||||
|~ [sole-id=@ta command=command-type]
|
||||
@ -106,9 +112,11 @@
|
||||
|* [shoe=* command-type=mold]
|
||||
|_ =bowl:gall
|
||||
++ command-parser
|
||||
(easy *command-type)
|
||||
|= sole-id=@ta
|
||||
(easy *[? command-type])
|
||||
::
|
||||
++ tab-list
|
||||
|= sole-id=@ta
|
||||
~
|
||||
::
|
||||
++ on-command
|
||||
@ -193,9 +201,9 @@
|
||||
(~(gut by soles) sole-id *sole-share)
|
||||
|^ =^ [cards=(list card) =_cli-state] shoe
|
||||
?- -.dat.act
|
||||
%det [(apply-edit +.dat.act) shoe]
|
||||
%det (apply-edit +.dat.act)
|
||||
%clr [[~ cli-state] shoe]
|
||||
%ret run-command
|
||||
%ret try-command
|
||||
%tab [(tab +.dat.act) shoe]
|
||||
==
|
||||
:- (deal cards)
|
||||
@ -208,15 +216,19 @@
|
||||
::
|
||||
++ apply-edit
|
||||
|= =sole-change
|
||||
^- (quip card _cli-state)
|
||||
^+ [[*(list card) cli-state] shoe]
|
||||
=^ inverse cli-state
|
||||
(~(transceive sole cli-state) sole-change)
|
||||
:: res: & for fully parsed, | for parsing failure at location
|
||||
::
|
||||
=/ res=(each (unit) @ud)
|
||||
=/ res=(each (unit [run=? cmd=command-type]) @ud)
|
||||
%+ rose (tufa buf.cli-state)
|
||||
(command-parser:og sole-id)
|
||||
?: ?=(%& -.res) [~ cli-state]
|
||||
?: ?=(%& -.res)
|
||||
?. &(?=(^ p.res) run.u.p.res)
|
||||
[[~ cli-state] shoe]
|
||||
(run-command cmd.u.p.res)
|
||||
:_ shoe
|
||||
:: parsing failed
|
||||
::
|
||||
?. &(?=(%del -.inverse) =(+(p.inverse) (lent buf.cli-state)))
|
||||
@ -234,14 +246,18 @@
|
||||
[%err p.res] :: cursor to error location
|
||||
==
|
||||
::
|
||||
++ run-command
|
||||
++ try-command
|
||||
^+ [[*(list card) cli-state] shoe]
|
||||
=/ cmd=(unit command-type)
|
||||
=/ res=(unit [? cmd=command-type])
|
||||
%+ rust (tufa buf.cli-state)
|
||||
(command-parser:og sole-id)
|
||||
?~ cmd
|
||||
[[[(effect %bel ~)]~ cli-state] shoe]
|
||||
=^ cards shoe (on-command:og sole-id u.cmd)
|
||||
?^ res (run-command cmd.u.res)
|
||||
[[[(effect %bel ~)]~ cli-state] shoe]
|
||||
::
|
||||
++ run-command
|
||||
|= cmd=command-type
|
||||
^+ [[*(list card) cli-state] shoe]
|
||||
=^ cards shoe (on-command:og sole-id cmd)
|
||||
:: clear buffer
|
||||
::
|
||||
=^ clear cli-state (~(transmit sole cli-state) [%set ~])
|
||||
@ -251,7 +267,6 @@
|
||||
[%det clear]
|
||||
==
|
||||
::
|
||||
::NOTE cargo-culted
|
||||
++ tab
|
||||
|= pos=@ud
|
||||
^- (quip card _cli-state)
|
||||
@ -271,7 +286,9 @@
|
||||
%+ add pos
|
||||
(met 3 (fall forward ''))
|
||||
=| cards=(list card)
|
||||
=? cards ?=(^ options)
|
||||
:: only render the option list if we couldn't complete anything
|
||||
::
|
||||
=? cards &(?=(~ to-send) ?=(^ options))
|
||||
[(effect %tab options) cards]
|
||||
|- ^- (quip card _cli-state)
|
||||
?~ to-send
|
||||
|
@ -1,7 +1,7 @@
|
||||
:: link: subscription updates
|
||||
::
|
||||
::TODO this should include json conversion once mark performance improves
|
||||
/+ *link
|
||||
/+ *link-store
|
||||
|_ =action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
@ -12,6 +12,6 @@
|
||||
++ grab
|
||||
|%
|
||||
++ noun ^action
|
||||
++ json action:de-json
|
||||
++ json action:dejs
|
||||
--
|
||||
--
|
||||
|
@ -1,6 +1,6 @@
|
||||
:: link: initial subscription result
|
||||
::
|
||||
/- *link
|
||||
/- *link-store
|
||||
|_ =initial
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|
@ -1,6 +1,6 @@
|
||||
:: link: subscription updates
|
||||
::
|
||||
/- *link
|
||||
/- *link-store
|
||||
|_ =update
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|
@ -1,6 +1,6 @@
|
||||
/- *link-view
|
||||
=, dejs:format
|
||||
|_ act=view-action
|
||||
|_ act=action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
@ -8,7 +8,7 @@
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun view-action
|
||||
++ noun action
|
||||
++ json
|
||||
|^ %- of
|
||||
:~ %create^create
|
||||
|
@ -31,7 +31,7 @@
|
||||
++ old-parser
|
||||
;~ plug
|
||||
(key-val (jest 'owner: ~') fed:ag)
|
||||
(key-val (jest 'title: ') (cook crip (star qit)))
|
||||
(key-val (jest 'title: ') (cook crip (star prn)))
|
||||
(key-val (jest 'filename: ') sym)
|
||||
%+ key-val (jest 'comments: ')
|
||||
;~(pose (jest %open) (jest %closed) (jest %none))
|
||||
@ -45,8 +45,8 @@
|
||||
==
|
||||
++ new-parser
|
||||
;~ plug
|
||||
(key-val (jest 'title: ') (cook crip (star qit)))
|
||||
(key-val (jest 'description: ') (cook crip (star qit)))
|
||||
(key-val (jest 'title: ') (cook crip (star prn)))
|
||||
(key-val (jest 'description: ') (cook crip (star prn)))
|
||||
%+ key-val (jest 'comments: ')
|
||||
(cook |=(a=@ =(%on a)) ;~(pose (jest %on) (jest %off)))
|
||||
(key-val (jest 'writers: ') ;~(pfix net (more net urs:ab)))
|
||||
|
50
pkg/arvo/sur/link-store.hoon
Normal file
50
pkg/arvo/sur/link-store.hoon
Normal file
@ -0,0 +1,50 @@
|
||||
:: link-store: store specific types
|
||||
::
|
||||
/- *link
|
||||
^?
|
||||
|%
|
||||
::
|
||||
:: +action: local actions
|
||||
::
|
||||
+$ action
|
||||
$% :: user actions
|
||||
::
|
||||
:: %save: save page to path on our ship
|
||||
::
|
||||
[%save =path title=@t =url]
|
||||
:: %note: save a note for a url
|
||||
::
|
||||
[%note =path =url udon=@t]
|
||||
:: %seen: mark item as read (~ for all in path)
|
||||
::
|
||||
[%seen =path url=(unit url)]
|
||||
:: hook actions
|
||||
::
|
||||
:: %hear: hear about page at path on other ship
|
||||
::
|
||||
[%hear =path submission]
|
||||
:: %read: hear about note on url from ship
|
||||
::
|
||||
[%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
|
||||
::
|
||||
+$ update
|
||||
$% [%local-pages =path =pages]
|
||||
[%submissions =path =submissions]
|
||||
[%annotations =path =url =notes]
|
||||
[%discussions =path =url =comments]
|
||||
[%observation =path urls=(set url)]
|
||||
==
|
||||
--
|
@ -1,7 +1,7 @@
|
||||
:: link-view: encapsulating link management
|
||||
::
|
||||
|%
|
||||
++ view-action
|
||||
++ action
|
||||
$% :: %create: create a new link collection
|
||||
::
|
||||
:: with specified metadata and group. %ships creates a new group,
|
||||
|
@ -46,48 +46,4 @@
|
||||
++ per-path-url
|
||||
|$ [value]
|
||||
(map path (map url value))
|
||||
::
|
||||
:: +action: local actions
|
||||
::
|
||||
+$ action
|
||||
$% :: user actions
|
||||
::
|
||||
:: %save: save page to path on our ship
|
||||
::
|
||||
[%save =path title=@t =url]
|
||||
:: %note: save a note for a url
|
||||
::
|
||||
[%note =path =url udon=@t]
|
||||
:: %seen: mark item as read (~ for all in path)
|
||||
::
|
||||
[%seen =path url=(unit url)]
|
||||
:: hook actions
|
||||
::
|
||||
:: %hear: hear about page at path on other ship
|
||||
::
|
||||
[%hear =path submission]
|
||||
:: %read: hear about note on url from ship
|
||||
::
|
||||
[%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
|
||||
::
|
||||
+$ update
|
||||
$% [%local-pages =path =pages]
|
||||
[%submissions =path =submissions]
|
||||
[%annotations =path =url =notes]
|
||||
[%discussions =path =url =comments]
|
||||
[%observation =path urls=(set url)]
|
||||
==
|
||||
--
|
||||
|
@ -46,11 +46,11 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js?$/,
|
||||
test: /\.(j|t)sx?$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'],
|
||||
presets: ['@babel/preset-env', '@babel/typescript', '@babel/preset-react'],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
@ -74,7 +74,7 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js']
|
||||
extensions: ['.js', '.ts', '.tsx']
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
// devServer: {
|
||||
|
@ -15,7 +15,7 @@ module.exports = {
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'],
|
||||
presets: ['@babel/preset-env', '@babel/typescript', '@babel/preset-react'],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
|
@ -9,6 +9,7 @@ api.setAuthTokens({
|
||||
ship: window.ship
|
||||
});
|
||||
|
||||
window.urb = new window.channel();
|
||||
subscription.start();
|
||||
|
||||
ReactDOM.render((
|
||||
|
@ -33,7 +33,7 @@ export class SearchableList extends Component {
|
||||
|
||||
let items = props.items.filter(item => {
|
||||
return state.query.split(' ').reduce((match, query) => {
|
||||
return match && item.key.includes(query);
|
||||
return match && ('' + item.key).includes(query);
|
||||
}, true);
|
||||
})
|
||||
items = items.map(item =>
|
||||
@ -46,4 +46,4 @@ export class SearchableList extends Component {
|
||||
<div>{items.length === 0 ? 'none' : items}</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
265
pkg/interface/package-lock.json
generated
265
pkg/interface/package-lock.json
generated
@ -563,6 +563,23 @@
|
||||
"@babel/helper-plugin-utils": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-typescript": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.1.tgz",
|
||||
"integrity": "sha512-X/d8glkrAtra7CaQGMiGs/OGa6XgUzqPcBXCIGFCpCqnfGlT0Wfbzo/B89xHhnInTaItPK8LALblVXcUOEh95Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
|
||||
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-arrow-functions": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz",
|
||||
@ -911,6 +928,187 @@
|
||||
"@babel/helper-plugin-utils": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-typescript": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.1.tgz",
|
||||
"integrity": "sha512-v+QWKlmCnsaimLeqq9vyCsVRMViZG1k2SZTlcZvB+TqyH570Zsij8nvVUZzOASCRiQFUxkLrn9Wg/kH0zgy5OQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.10.1",
|
||||
"@babel/helper-plugin-utils": "^7.10.1",
|
||||
"@babel/plugin-syntax-typescript": "^7.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||
"integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz",
|
||||
"integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.2",
|
||||
"jsesc": "^2.5.1",
|
||||
"lodash": "^4.17.13",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz",
|
||||
"integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-function-name": "^7.10.1",
|
||||
"@babel/helper-member-expression-to-functions": "^7.10.1",
|
||||
"@babel/helper-optimise-call-expression": "^7.10.1",
|
||||
"@babel/helper-plugin-utils": "^7.10.1",
|
||||
"@babel/helper-replace-supers": "^7.10.1",
|
||||
"@babel/helper-split-export-declaration": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz",
|
||||
"integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.10.1",
|
||||
"@babel/template": "^7.10.1",
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz",
|
||||
"integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz",
|
||||
"integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz",
|
||||
"integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
|
||||
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-replace-supers": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz",
|
||||
"integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-member-expression-to-functions": "^7.10.1",
|
||||
"@babel/helper-optimise-call-expression": "^7.10.1",
|
||||
"@babel/traverse": "^7.10.1",
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz",
|
||||
"integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
|
||||
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
|
||||
"integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.1",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz",
|
||||
"integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz",
|
||||
"integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.1",
|
||||
"@babel/parser": "^7.10.1",
|
||||
"@babel/types": "^7.10.1"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz",
|
||||
"integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.1",
|
||||
"@babel/generator": "^7.10.1",
|
||||
"@babel/helper-function-name": "^7.10.1",
|
||||
"@babel/helper-split-export-declaration": "^7.10.1",
|
||||
"@babel/parser": "^7.10.1",
|
||||
"@babel/types": "^7.10.1",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.13"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz",
|
||||
"integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.1",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-unicode-regex": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz",
|
||||
@ -1024,6 +1222,24 @@
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.9.0"
|
||||
}
|
||||
},
|
||||
"@babel/preset-typescript": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.10.1.tgz",
|
||||
"integrity": "sha512-m6GV3y1ShiqxnyQj10600ZVOFrSSAa8HQ3qIUk2r+gcGtHTIRw0dJnFLt1WNXpKjtVw7yw1DAPU/6ma2ZvgJuA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.10.1",
|
||||
"@babel/plugin-transform-typescript": "^7.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
|
||||
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
|
||||
@ -1320,12 +1536,24 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/history": {
|
||||
"version": "4.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.6.tgz",
|
||||
"integrity": "sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/html-minifier-terser": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz",
|
||||
"integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.155",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz",
|
||||
"integrity": "sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
@ -1338,6 +1566,43 @@
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "16.9.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.38.tgz",
|
||||
"integrity": "sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.7.tgz",
|
||||
"integrity": "sha512-2ouP76VQafKjtuc0ShpwUebhHwJo0G6rhahW9Pb8au3tQTjYXd2jta4wv6U2tGLR/I42yuG00+UXjNYY0dTzbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router-dom": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
|
||||
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/source-list-map": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
|
||||
|
@ -34,6 +34,10 @@
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@types/lodash": "^4.14.155",
|
||||
"@types/react": "^16.9.38",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
@ -51,6 +55,8 @@
|
||||
"scripts": {
|
||||
"lint": "eslint ./**/*.js",
|
||||
"lint-file": "eslint",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
"build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
|
||||
"build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
|
||||
"start": "webpack-dev-server",
|
||||
|
@ -15,7 +15,7 @@ import PublishApp from './apps/publish/app';
|
||||
import StatusBar from './components/StatusBar';
|
||||
import NotFound from './components/404';
|
||||
|
||||
import GlobalStore from './store/global';
|
||||
import GlobalStore from './store/store';
|
||||
import GlobalSubscription from './subscription/global';
|
||||
import GlobalApi from './api/global';
|
||||
|
||||
@ -55,11 +55,11 @@ export default class App extends React.Component {
|
||||
|
||||
this.appChannel = new window.channel();
|
||||
this.api = new GlobalApi(this.ship, this.appChannel, this.store);
|
||||
this.subscription =
|
||||
new GlobalSubscription(this.store, this.api, this.appChannel);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription =
|
||||
new GlobalSubscription(this.store, this.api, this.appChannel);
|
||||
this.subscription.start();
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ export default class App extends React.Component {
|
||||
|
||||
const associations = this.state.associations ? this.state.associations : { contacts: {} };
|
||||
const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : [];
|
||||
const { state } = this;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={light}>
|
||||
@ -84,8 +85,8 @@ export default class App extends React.Component {
|
||||
render={ p => (
|
||||
<LaunchApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -93,8 +94,9 @@ export default class App extends React.Component {
|
||||
<Route path="/~chat" render={ p => (
|
||||
<ChatApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -104,6 +106,7 @@ export default class App extends React.Component {
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
subscription={this.subscription}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -111,8 +114,9 @@ export default class App extends React.Component {
|
||||
<Route path="/~groups" render={ p => (
|
||||
<GroupsApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -120,8 +124,10 @@ export default class App extends React.Component {
|
||||
<Route path="/~link" render={ p => (
|
||||
<LinksApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -129,8 +135,9 @@ export default class App extends React.Component {
|
||||
<Route path="/~publish" render={ p => (
|
||||
<PublishApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,46 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { uuid } from '../lib/util';
|
||||
|
||||
|
||||
export default class BaseApi {
|
||||
constructor(ship, channel, store) {
|
||||
this.ship = ship;
|
||||
this.channel = channel;
|
||||
this.store = store;
|
||||
this.bindPaths = [];
|
||||
}
|
||||
|
||||
subscribe(path, method, ship = this.ship, app, success, fail, quit) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.subscriptionId = this.channel.subscribe(ship, app, path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
(event) => {
|
||||
success({
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
quit(qui);
|
||||
});
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.channel.poke(window.ship, appl, mark, data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
56
pkg/interface/src/api/base.ts
Normal file
56
pkg/interface/src/api/base.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import _ from "lodash";
|
||||
import { uuid } from "../lib/util";
|
||||
import { Patp, Path } from "../types/noun";
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
export default class BaseApi<S extends object = {}> {
|
||||
bindPaths: Path[] = [];
|
||||
constructor(public ship: Patp, public channel: any, public store: BaseStore<S>) {}
|
||||
|
||||
unsubscribe(id: number) {
|
||||
this.channel.unsubscribe(id);
|
||||
|
||||
}
|
||||
|
||||
subscribe(path: Path, method, ship = this.ship, app: string, success, fail, quit) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
return this.channel.subscribe(
|
||||
this.ship,
|
||||
app,
|
||||
path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
(event) => {
|
||||
success({
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path,
|
||||
},
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
quit(qui);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
action(appl: string, mark: string, data: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.channel.poke(
|
||||
(window as any).ship,
|
||||
appl,
|
||||
mark,
|
||||
data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
import BaseApi from './base';
|
||||
import { uuid } from '../lib/util';
|
||||
|
||||
export default class ChatApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
|
||||
this.groups = {
|
||||
add: helper.groupAdd.bind(helper),
|
||||
remove: helper.groupRemove.bind(helper)
|
||||
};
|
||||
|
||||
this.chat = {
|
||||
message: helper.chatMessage.bind(helper),
|
||||
read: helper.chatRead.bind(helper)
|
||||
};
|
||||
|
||||
this.chatView = {
|
||||
create: helper.chatViewCreate.bind(helper),
|
||||
delete: helper.chatViewDelete.bind(helper),
|
||||
join: helper.chatViewJoin.bind(helper),
|
||||
groupify: helper.chatViewGroupify.bind(helper)
|
||||
};
|
||||
|
||||
this.chatHook = {
|
||||
addSynced: helper.chatHookAddSynced.bind(helper)
|
||||
};
|
||||
|
||||
this.invite = {
|
||||
accept: helper.inviteAccept.bind(helper),
|
||||
decline: helper.inviteDecline.bind(helper)
|
||||
};
|
||||
|
||||
this.metadata = {
|
||||
add: helper.metadataAdd.bind(helper)
|
||||
};
|
||||
this.sidebarToggle = helper.sidebarToggle.bind(helper);
|
||||
}
|
||||
}
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
groupsAction(data) {
|
||||
return this.action('group-store', 'group-action', data);
|
||||
}
|
||||
|
||||
groupAdd(members, path) {
|
||||
return this.groupsAction({
|
||||
add: {
|
||||
members, path
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
groupRemove(members, path) {
|
||||
this.groupsAction({
|
||||
remove: {
|
||||
members, path
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chatAction(data) {
|
||||
this.action('chat-store', 'json', data);
|
||||
}
|
||||
|
||||
addPendingMessage(msg) {
|
||||
if (this.store.state.pendingMessages.has(msg.path)) {
|
||||
this.store.state.pendingMessages.get(msg.path).unshift(msg.envelope);
|
||||
} else {
|
||||
this.store.state.pendingMessages.set(msg.path, [msg.envelope]);
|
||||
}
|
||||
|
||||
this.store.setState({
|
||||
pendingMessages: this.store.state.pendingMessages
|
||||
});
|
||||
}
|
||||
|
||||
chatMessage(path, author, when, letter) {
|
||||
const data = {
|
||||
message: {
|
||||
path,
|
||||
envelope: {
|
||||
uid: uuid(),
|
||||
number: 0,
|
||||
author,
|
||||
when,
|
||||
letter
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.action('chat-hook', 'json', data).then(() => {
|
||||
this.chatRead(path);
|
||||
});
|
||||
data.message.envelope.author = data.message.envelope.author.substr(1);
|
||||
this.addPendingMessage(data.message);
|
||||
}
|
||||
|
||||
chatRead(path, read) {
|
||||
this.chatAction({ read: { path } });
|
||||
}
|
||||
|
||||
chatHookAddSynced(ship, path, askHistory) {
|
||||
return this.action('chat-hook', 'chat-hook-action', {
|
||||
'add-synced': {
|
||||
ship,
|
||||
path,
|
||||
'ask-history': askHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chatViewAction(data) {
|
||||
return this.action('chat-view', 'json', data);
|
||||
}
|
||||
|
||||
chatViewCreate(
|
||||
title, description, appPath, groupPath,
|
||||
security, members, allowHistory
|
||||
) {
|
||||
return this.chatViewAction({
|
||||
create: {
|
||||
title,
|
||||
description,
|
||||
'app-path': appPath,
|
||||
'group-path': groupPath,
|
||||
security,
|
||||
members,
|
||||
'allow-history': allowHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chatViewDelete(path) {
|
||||
this.chatViewAction({ delete: { 'app-path': path } });
|
||||
}
|
||||
|
||||
chatViewJoin(ship, path, askHistory) {
|
||||
this.chatViewAction({
|
||||
join: {
|
||||
ship,
|
||||
'app-path': path,
|
||||
'ask-history': askHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chatViewGroupify(path, group = null, inclusive = false) {
|
||||
const action = { groupify: { 'app-path': path, existing: null } };
|
||||
if (group) {
|
||||
action.groupify.existing = {
|
||||
'group-path': group,
|
||||
inclusive: inclusive
|
||||
};
|
||||
}
|
||||
return this.chatViewAction(action);
|
||||
}
|
||||
|
||||
inviteAction(data) {
|
||||
this.action('invite-store', 'json', data);
|
||||
}
|
||||
|
||||
inviteAccept(uid) {
|
||||
this.inviteAction({
|
||||
accept: {
|
||||
path: '/chat',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteDecline(uid) {
|
||||
this.inviteAction({
|
||||
decline: {
|
||||
path: '/chat',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
}
|
||||
|
||||
metadataAdd(appPath, groupPath, title, description, dateCreated, color) {
|
||||
const creator = `~${window.ship}`;
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': 'chat'
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
159
pkg/interface/src/api/chat.ts
Normal file
159
pkg/interface/src/api/chat.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import BaseApi from './base';
|
||||
import { uuid } from '../lib/util';
|
||||
import { Letter, ChatAction, Envelope } from '../types/chat-update';
|
||||
import { Patp, Path, PatpNoSig } from '../types/noun';
|
||||
import { StoreState } from '../store/type';
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
|
||||
export default class ChatApi extends BaseApi<StoreState> {
|
||||
|
||||
/**
|
||||
* Fetch backlog
|
||||
*/
|
||||
fetchMessages(start: number, end: number, path: Path) {
|
||||
fetch(`/chat-view/paginate/${start}/${end}${path}`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
this.store.handleEvent({
|
||||
data: json
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the chat at path
|
||||
*/
|
||||
message(path: Path, author: Patp, when: string, letter: Letter): Promise<void> {
|
||||
const data: ChatAction = {
|
||||
message: {
|
||||
path,
|
||||
envelope: {
|
||||
uid: uuid(),
|
||||
number: 0,
|
||||
author,
|
||||
when,
|
||||
letter
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const promise = this.proxyHookAction(data).then(() => {
|
||||
this.read(path);
|
||||
});
|
||||
data.message.envelope.author = data.message.envelope.author.substr(1);
|
||||
this.addPendingMessage(data.message.path, data.message.envelope);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark chat as read
|
||||
*/
|
||||
read(path: Path): Promise<any> {
|
||||
return this.storeAction({ read: { path } });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a chat and setup metadata
|
||||
*/
|
||||
create(
|
||||
title: string, description: string, appPath: string, groupPath: string,
|
||||
security: any, members: PatpNoSig[], allowHistory: boolean
|
||||
): Promise<any> {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
title,
|
||||
description,
|
||||
'app-path': appPath,
|
||||
'group-path': groupPath,
|
||||
security,
|
||||
members,
|
||||
'allow-history': allowHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a chat
|
||||
*
|
||||
* If we don't host the chat, then it just leaves
|
||||
*/
|
||||
delete(path: Path) {
|
||||
this.viewAction({ delete: { 'app-path': path } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a chat
|
||||
*/
|
||||
join(ship: Patp, path: Path, askHistory: boolean): Promise<any> {
|
||||
return this.viewAction({
|
||||
join: {
|
||||
ship,
|
||||
'app-path': path,
|
||||
'ask-history': askHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Groupify a chat that we host
|
||||
*
|
||||
* Will delete the old chat, recreate it based on a proper group,
|
||||
* and invite the current whitelist to that group.
|
||||
* existing messages get moved over.
|
||||
*
|
||||
* :existing is provided, associates chat with that group instead
|
||||
* creating a new one. :inclusive indicates whether or not to add
|
||||
* chat members to the group, if they aren't there already.
|
||||
*/
|
||||
groupify(path: Path, group: Path | null = null, inclusive = false) {
|
||||
let action: any = { groupify: { 'app-path': path, existing: null } };
|
||||
if (group) {
|
||||
action.groupify.existing = {
|
||||
'group-path': group,
|
||||
inclusive: inclusive
|
||||
};
|
||||
}
|
||||
return this.viewAction(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin syncing a chat from the host
|
||||
*/
|
||||
addSynced(ship: Patp, path: Path, askHistory: boolean): Promise<any> {
|
||||
return this.action('chat-hook', 'chat-hook-action', {
|
||||
'add-synced': {
|
||||
ship,
|
||||
path,
|
||||
'ask-history': askHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private storeAction(action: ChatAction): Promise<any> {
|
||||
return this.action('chat-store', 'json', action)
|
||||
}
|
||||
|
||||
private proxyHookAction(action: ChatAction): Promise<any> {
|
||||
return this.action('chat-hook', 'json', action);
|
||||
}
|
||||
|
||||
private viewAction(action: unknown): Promise<any> {
|
||||
return this.action('chat-view', 'json', action);
|
||||
}
|
||||
|
||||
private addPendingMessage(path: Path, envelope: Envelope) {
|
||||
const pending = this.store.state.pendingMessages.get(path);
|
||||
if (pending) {
|
||||
pending.unshift(envelope);
|
||||
} else {
|
||||
this.store.state.pendingMessages.set(path, [envelope]);
|
||||
}
|
||||
|
||||
this.store.setState({
|
||||
pendingMessages: this.store.state.pendingMessages
|
||||
});
|
||||
}
|
||||
}
|
62
pkg/interface/src/api/contacts.ts
Normal file
62
pkg/interface/src/api/contacts.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path } from '../types/noun';
|
||||
import { Contact, ContactEdit } from '../types/contact-update';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
|
||||
create(path: Path, ships: Patp[] = [], title: string, description: string) {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
path,
|
||||
ships,
|
||||
title,
|
||||
description
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
share(recipient: Patp, path: Patp, ship: Patp, contact: Contact) {
|
||||
return this.viewAction({
|
||||
share: {
|
||||
recipient, path, ship, contact
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete(path: Path) {
|
||||
return this.viewAction({ delete: { path } });
|
||||
}
|
||||
|
||||
remove(path: Path, ship: Patp) {
|
||||
return this.viewAction({ remove: { path, ship } });
|
||||
}
|
||||
|
||||
|
||||
|
||||
edit(path: Path, ship: Patp, editField: ContactEdit) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
{email: ''}
|
||||
{phone: ''}
|
||||
{website: ''}
|
||||
{notes: ''}
|
||||
{color: 'fff'} // with no 0x prefix
|
||||
{avatar: null}
|
||||
{avatar: {url: ''}}
|
||||
*/
|
||||
return this.hookAction({
|
||||
edit: {
|
||||
path, ship, 'edit-field': editField
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private hookAction(data) {
|
||||
return this.action('contact-hook', 'contact-action', data);
|
||||
}
|
||||
|
||||
private viewAction(data) {
|
||||
return this.action('contact-view', 'json', data);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import BaseApi from './base';
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
setSelected(selected) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected: selected
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class GlobalApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
|
||||
this.setSelected = helper.setSelected.bind(helper);
|
||||
}
|
||||
}
|
||||
|
31
pkg/interface/src/api/global.ts
Normal file
31
pkg/interface/src/api/global.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Patp } from '../types/noun';
|
||||
import BaseApi from './base';
|
||||
import ChatApi from './chat';
|
||||
import { StoreState } from '../store/type';
|
||||
import GlobalStore from '../store/store';
|
||||
import LocalApi from './local';
|
||||
import InviteApi from './invite';
|
||||
import MetadataApi from './metadata';
|
||||
import ContactsApi from './contacts';
|
||||
import GroupsApi from './groups';
|
||||
import LaunchApi from './launch';
|
||||
import LinksApi from './links';
|
||||
import PublishApi from './publish';
|
||||
|
||||
export default class GlobalApi extends BaseApi<StoreState> {
|
||||
chat = new ChatApi(this.ship, this.channel, this.store);
|
||||
local = new LocalApi(this.ship, this.channel, this.store);
|
||||
invite = new InviteApi(this.ship, this.channel, this.store);
|
||||
metadata = new MetadataApi(this.ship, this.channel, this.store);
|
||||
contacts = new ContactsApi(this.ship, this.channel, this.store);
|
||||
groups = new GroupsApi(this.ship, this.channel, this.store);
|
||||
launch = new LaunchApi(this.ship, this.channel, this.store);
|
||||
links = new LinksApi(this.ship, this.channel, this.store);
|
||||
publish = new PublishApi(this.ship, this.channel, this.store);
|
||||
|
||||
|
||||
constructor(public ship: Patp, public channel: any, public store: GlobalStore) {
|
||||
super(ship,channel,store);
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,19 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp } from '../types/noun';
|
||||
|
||||
|
||||
export default class GroupsApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
add(path: Path, ships: Patp[] = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
add: { members: ships, path }
|
||||
});
|
||||
}
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
|
||||
this.contactHook = {
|
||||
edit: helper.contactEdit.bind(helper)
|
||||
};
|
||||
|
||||
this.contactView = {
|
||||
create: helper.contactCreate.bind(helper),
|
||||
delete: helper.contactDelete.bind(helper),
|
||||
remove: helper.contactRemove.bind(helper),
|
||||
share: helper.contactShare.bind(helper)
|
||||
};
|
||||
|
||||
this.group = {
|
||||
add: helper.groupAdd.bind(helper),
|
||||
remove: helper.groupRemove.bind(helper)
|
||||
};
|
||||
|
||||
this.metadata = {
|
||||
add: helper.metadataAdd.bind(helper)
|
||||
};
|
||||
|
||||
this.invite = {
|
||||
accept: helper.inviteAccept.bind(helper),
|
||||
decline: helper.inviteDecline.bind(helper)
|
||||
};
|
||||
remove(path: Path, ships: Patp[] = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
remove: { members: ships, path }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,17 +33,9 @@ class PrivateHelper extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
groupAdd(path, ships = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
add: { members: ships, path }
|
||||
});
|
||||
}
|
||||
|
||||
groupRemove(path, ships) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
remove: { members: ships, path }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
contactShare(recipient, path, ship, contact) {
|
||||
return this.contactViewAction({
|
27
pkg/interface/src/api/invite.ts
Normal file
27
pkg/interface/src/api/invite.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { Serial, Path } from "../types/noun";
|
||||
|
||||
export default class InviteApi extends BaseApi<StoreState> {
|
||||
accept(app: Path, uid: Serial) {
|
||||
return this.inviteAction({
|
||||
accept: {
|
||||
path: app,
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
decline(app: Path, uid: Serial) {
|
||||
return this.inviteAction({
|
||||
decline: {
|
||||
path: app,
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private inviteAction(action) {
|
||||
return this.action('invite-store', 'json', action);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import BaseApi from './base';
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
launchAction(data) {
|
||||
this.action('launch', 'launch-action', data);
|
||||
}
|
||||
|
||||
launchAdd(name, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
|
||||
this.launchAction({ add: { name, tile } });
|
||||
}
|
||||
|
||||
launchRemove(name) {
|
||||
this.launchAction({ remove: name });
|
||||
}
|
||||
|
||||
launchChangeOrder(orderedTiles = []) {
|
||||
this.launchAction({ 'change-order': orderedTiles });
|
||||
}
|
||||
|
||||
launchChangeFirstTime(firstTime = true) {
|
||||
this.launchAction({ 'change-first-time': firstTime });
|
||||
}
|
||||
|
||||
launchChangeIsShown(name, isShown = true) {
|
||||
this.launchAction({ 'change-is-shown': { name, isShown }});
|
||||
}
|
||||
|
||||
weatherAction(latlng) {
|
||||
this.action('weather', 'json', latlng);
|
||||
}
|
||||
}
|
||||
|
||||
export default class LaunchApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
|
||||
this.launch = {
|
||||
add: helper.launchAdd.bind(helper),
|
||||
remove: helper.launchRemove.bind(helper),
|
||||
changeOrder: helper.launchChangeOrder.bind(helper),
|
||||
changeFirstTime: helper.launchChangeFirstTime.bind(helper),
|
||||
changeIsShown: helper.launchChangeIsShown.bind(helper)
|
||||
};
|
||||
|
||||
this.weather = helper.weatherAction.bind(helper);
|
||||
}
|
||||
}
|
||||
|
36
pkg/interface/src/api/launch.ts
Normal file
36
pkg/interface/src/api/launch.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
|
||||
export default class LaunchApi extends BaseApi<StoreState> {
|
||||
|
||||
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
|
||||
this.launchAction({ add: { name, tile } });
|
||||
}
|
||||
|
||||
remove(name: string) {
|
||||
this.launchAction({ remove: name });
|
||||
}
|
||||
|
||||
changeOrder(orderedTiles = []) {
|
||||
this.launchAction({ 'change-order': orderedTiles });
|
||||
}
|
||||
|
||||
changeFirstTime(firstTime = true) {
|
||||
this.launchAction({ 'change-first-time': firstTime });
|
||||
}
|
||||
|
||||
changeIsShown(name: string, isShown = true) {
|
||||
this.launchAction({ 'change-is-shown': { name, isShown }});
|
||||
}
|
||||
|
||||
weather(latlng: any) {
|
||||
this.action('weather', 'json', latlng);
|
||||
}
|
||||
|
||||
private launchAction(data) {
|
||||
this.action('launch', 'launch-action', data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,222 +0,0 @@
|
||||
import { stringToTa } from '../lib/util';
|
||||
|
||||
import BaseApi from './base';
|
||||
|
||||
export default class LinksApi extends BaseApi {
|
||||
constructor(ship, channel, store) {
|
||||
super(ship, channel, store);
|
||||
this.ship = ship;
|
||||
|
||||
this.invite = {
|
||||
accept: this.inviteAccept.bind(this),
|
||||
decline: this.inviteDecline.bind(this)
|
||||
};
|
||||
|
||||
this.groups = {
|
||||
remove: this.groupRemove.bind(this)
|
||||
};
|
||||
|
||||
this.fetchLink = this.fetchLink.bind(this);
|
||||
}
|
||||
|
||||
fetchLink(path, result, fail, quit) {
|
||||
this.subscribe.bind(this)(
|
||||
path,
|
||||
'PUT',
|
||||
this.ship,
|
||||
'link-view',
|
||||
result,
|
||||
fail,
|
||||
quit
|
||||
);
|
||||
}
|
||||
|
||||
groupsAction(data) {
|
||||
this.action('group-store', 'group-action', data);
|
||||
}
|
||||
|
||||
groupRemove(path, members) {
|
||||
this.groupsAction({
|
||||
remove: {
|
||||
path,
|
||||
members
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteAction(data) {
|
||||
this.action('invite-store', 'json', data);
|
||||
}
|
||||
|
||||
inviteAccept(uid) {
|
||||
this.inviteAction({
|
||||
accept: {
|
||||
path: '/link',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteDecline(uid) {
|
||||
this.inviteAction({
|
||||
decline: {
|
||||
path: '/link',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCommentsPage(path, url, page) {
|
||||
const strictUrl = stringToTa(url);
|
||||
const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(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;
|
||||
}
|
||||
this.store.handleEvent(res);
|
||||
},
|
||||
console.error,
|
||||
() => {} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getPage(path, page) {
|
||||
const endpoint = '/json/' + page + '/submissions' + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(dat) => {
|
||||
this.store.handleEvent(dat);
|
||||
},
|
||||
console.error,
|
||||
() => {} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getSubmission(path, url, callback) {
|
||||
const strictUrl = stringToTa(url);
|
||||
const endpoint = '/json/0/submission/' + strictUrl + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(res) => {
|
||||
if (res.data.submission) {
|
||||
callback(res.data.submission);
|
||||
} else {
|
||||
console.error('unexpected submission response', res);
|
||||
}
|
||||
},
|
||||
console.error,
|
||||
() => {} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
linkViewAction(data) {
|
||||
return this.action('link-view', 'link-view-action', data);
|
||||
}
|
||||
|
||||
createCollection(path, title, description, members, realGroup) {
|
||||
// members is either {group:'/group-path'} or {'ships':[~zod]},
|
||||
// with realGroup signifying if ships should become a managed group or not.
|
||||
return this.linkViewAction({
|
||||
create: { path, title, description, members, realGroup }
|
||||
});
|
||||
}
|
||||
|
||||
deleteCollection(path) {
|
||||
return this.linkViewAction({
|
||||
delete: { path }
|
||||
});
|
||||
}
|
||||
|
||||
inviteToCollection(path, ships) {
|
||||
return this.linkViewAction({
|
||||
invite: { path, ships }
|
||||
});
|
||||
}
|
||||
|
||||
linkListenAction(data) {
|
||||
return this.action('link-listen-hook', 'link-listen-action', data);
|
||||
}
|
||||
|
||||
joinCollection(path) {
|
||||
return this.linkListenAction({ watch: path });
|
||||
}
|
||||
|
||||
removeCollection(path) {
|
||||
return this.linkListenAction({ leave: path });
|
||||
}
|
||||
|
||||
linkAction(data) {
|
||||
return this.action('link-store', 'link-action', data);
|
||||
}
|
||||
|
||||
postLink(path, url, title) {
|
||||
return this.linkAction({
|
||||
save: { path, url, title }
|
||||
});
|
||||
}
|
||||
|
||||
postComment(path, url, comment) {
|
||||
return this.linkAction({
|
||||
note: { path, url, udon: comment }
|
||||
});
|
||||
}
|
||||
|
||||
// leave url as null to mark all under path as read
|
||||
seenLink(path, url = null) {
|
||||
return this.linkAction({
|
||||
seen: { path, url }
|
||||
});
|
||||
}
|
||||
|
||||
metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
}
|
||||
|
||||
metadataAdd(appPath, groupPath, title, description, dateCreated, color) {
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': 'link'
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator: `~${window.ship}`
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected: selected
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
131
pkg/interface/src/api/links.ts
Normal file
131
pkg/interface/src/api/links.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { stringToTa } from '../lib/util';
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '../types/noun';
|
||||
|
||||
export default class LinksApi extends BaseApi<StoreState> {
|
||||
|
||||
|
||||
getCommentsPage(path: Path, url: string, page: number) {
|
||||
const strictUrl = stringToTa(url);
|
||||
const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(res) => {
|
||||
if (res.data['link-update']['initial-discussions']) {
|
||||
// these aren't returned with the response,
|
||||
// so this ensures the reducers know them.
|
||||
res.data['link-update']['initial-discussions'].path = path;
|
||||
res.data['link-update']['initial-discussions'].url = url;
|
||||
}
|
||||
this.store.handleEvent(res);
|
||||
},
|
||||
console.error,
|
||||
() => {} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getPage(path: Path, page: number) {
|
||||
const endpoint = '/json/' + page + '/submissions' + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(dat) => {
|
||||
this.store.handleEvent(dat);
|
||||
},
|
||||
console.error,
|
||||
() => {} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getSubmission(path: Path, url: string, callback) {
|
||||
const strictUrl = stringToTa(url);
|
||||
const endpoint = '/json/0/submission/' + strictUrl + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(res) => {
|
||||
if (res.data.submission) {
|
||||
callback(res.data.submission);
|
||||
} else {
|
||||
console.error('unexpected submission response', res);
|
||||
}
|
||||
},
|
||||
console.error,
|
||||
() => {} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
createCollection(path, title, description, members, realGroup) {
|
||||
// members is either {group:'/group-path'} or {'ships':[~zod]},
|
||||
// with realGroup signifying if ships should become a managed group or not.
|
||||
return this.viewAction({
|
||||
create: { path, title, description, members, realGroup }
|
||||
});
|
||||
}
|
||||
|
||||
deleteCollection(path) {
|
||||
return this.viewAction({
|
||||
delete: { path }
|
||||
});
|
||||
}
|
||||
|
||||
inviteToCollection(path, ships) {
|
||||
return this.viewAction({
|
||||
invite: { path, ships }
|
||||
});
|
||||
}
|
||||
|
||||
joinCollection(path) {
|
||||
return this.linkListenAction({ watch: path });
|
||||
}
|
||||
|
||||
removeCollection(path) {
|
||||
return this.linkListenAction({ leave: path });
|
||||
}
|
||||
|
||||
|
||||
postLink(path: Path, url: string, title: string) {
|
||||
return this.linkAction({
|
||||
save: { path, url, title }
|
||||
});
|
||||
}
|
||||
|
||||
postComment(path: Path, url: string, comment: string) {
|
||||
return this.linkAction({
|
||||
note: { path, url, udon: comment }
|
||||
});
|
||||
}
|
||||
|
||||
// leave url as null to mark all under path as read
|
||||
seenLink(path: Path, url?: string) {
|
||||
return this.linkAction({
|
||||
seen: { path, url: url || null }
|
||||
});
|
||||
}
|
||||
|
||||
private linkAction(data) {
|
||||
return this.action('link-store', 'link-action', data);
|
||||
}
|
||||
|
||||
private viewAction(data) {
|
||||
return this.action('link-view', 'link-view-action', data);
|
||||
}
|
||||
|
||||
private linkListenAction(data) {
|
||||
return this.action('link-listen-hook', 'link-listen-action', data);
|
||||
}
|
||||
|
||||
private fetchLink(path: Path, result, fail, quit) {
|
||||
this.subscribe.bind(this)(
|
||||
path,
|
||||
'PUT',
|
||||
this.ship,
|
||||
'link-view',
|
||||
result,
|
||||
fail,
|
||||
quit
|
||||
);
|
||||
}
|
||||
}
|
28
pkg/interface/src/api/local.ts
Normal file
28
pkg/interface/src/api/local.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { SelectedGroup } from "../types/local-update";
|
||||
|
||||
|
||||
|
||||
export default class LocalApi extends BaseApi<StoreState> {
|
||||
setSelected(selected: SelectedGroup[]) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
31
pkg/interface/src/api/metadata.ts
Normal file
31
pkg/interface/src/api/metadata.ts
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp } from '../types/noun';
|
||||
|
||||
export default class MetadataApi extends BaseApi<StoreState> {
|
||||
|
||||
metadataAdd(appName: string, appPath: Path, groupPath: Path, title: string, description: string, dateCreated: string, color: string) {
|
||||
const creator = `~${this.ship}`;
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': appName
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
import BaseApi from './base';
|
||||
import { PublishResponse } from '../types/publish-response';
|
||||
import { PatpNoSig } from '../types/noun';
|
||||
import { BookId, NoteId } from '../types/publish-update';
|
||||
|
||||
export default class PublishApi extends BaseApi {
|
||||
handleEvent(data) {
|
||||
handleEvent(data: PublishResponse) {
|
||||
this.store.handleEvent({ data: { 'publish-response' : data } });
|
||||
}
|
||||
|
||||
@ -16,7 +19,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotebook(host, book) {
|
||||
fetchNotebook(host: PatpNoSig, book: BookId) {
|
||||
fetch(`/publish-view/${host}/${book}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -29,7 +32,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNote(host, book, note) {
|
||||
fetchNote(host: PatpNoSig, book: BookId, note: NoteId) {
|
||||
fetch(`/publish-view/${host}/${book}/${note}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -43,7 +46,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotesPage(host, book, start, length) {
|
||||
fetchNotesPage(host: PatpNoSig, book: BookId, start: number, length: number) {
|
||||
fetch(`/publish-view/notes/${host}/${book}/${start}/${length}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -58,7 +61,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchCommentsPage(host, book, note, start, length) {
|
||||
fetchCommentsPage(host: PatpNoSig, book: BookId, note: NoteId, start: number, length: number) {
|
||||
fetch(`/publish-view/comments/${host}/${book}/${note}/${start}/${length}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -74,30 +77,8 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
groupAction(act) {
|
||||
return this.action('group-store', 'group-action', act);
|
||||
}
|
||||
|
||||
inviteAction(act) {
|
||||
return this.action('invite-store', 'invite-action', act);
|
||||
}
|
||||
|
||||
publishAction(act) {
|
||||
publishAction(act: any) {
|
||||
return this.action('publish', 'publish-action', act);
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import ChatApi from '../../api/chat';
|
||||
import ChatStore from '../../store/chat';
|
||||
import ChatSubscription from '../../subscription/chat';
|
||||
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
@ -16,19 +11,22 @@ import { SettingsScreen } from './components/settings';
|
||||
import { NewScreen } from './components/new';
|
||||
import { JoinScreen } from './components/join';
|
||||
import { NewDmScreen } from './components/new-dm';
|
||||
import { PatpNoSig } from '../../types/noun';
|
||||
import GlobalApi from '../../api/global';
|
||||
import { StoreState } from '../../store/type';
|
||||
import GlobalSubscription from '../../subscription/global';
|
||||
|
||||
type ChatAppProps = StoreState & {
|
||||
ship: PatpNoSig;
|
||||
api: GlobalApi;
|
||||
subscription: GlobalSubscription;
|
||||
};
|
||||
|
||||
export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
totalUnreads = 0;
|
||||
|
||||
export default class ChatApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new ChatStore();
|
||||
this.state = this.store.state;
|
||||
this.totalUnreads = 0;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -36,33 +34,31 @@ export default class ChatApp extends React.Component {
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
const channel = new this.props.channel();
|
||||
this.api = new ChatApi(this.props.ship, channel, this.store);
|
||||
this.props.subscription.startApp('chat');
|
||||
|
||||
this.subscription = new ChatSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
if (!this.props.sidebarShown) {
|
||||
this.props.api.local.sidebarToggle();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
this.props.subscription.stopApp('chat');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const { props } = this;
|
||||
|
||||
const messagePreviews = {};
|
||||
const unreads = {};
|
||||
let totalUnreads = 0;
|
||||
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
const associations = state.associations ? state.associations : { chat: {}, contacts: {} };
|
||||
const associations = props.associations
|
||||
? props.associations
|
||||
: { chat: {}, contacts: {} };
|
||||
|
||||
Object.keys(state.inbox).forEach((stat) => {
|
||||
const envelopes = state.inbox[stat].envelopes;
|
||||
Object.keys(props.inbox).forEach((stat) => {
|
||||
const envelopes = props.inbox[stat].envelopes;
|
||||
|
||||
if (envelopes.length === 0) {
|
||||
messagePreviews[stat] = false;
|
||||
@ -70,37 +66,54 @@ export default class ChatApp extends React.Component {
|
||||
messagePreviews[stat] = envelopes[0];
|
||||
}
|
||||
|
||||
const unread = Math.max(state.inbox[stat].config.length - state.inbox[stat].config.read, 0);
|
||||
const unread = Math.max(
|
||||
props.inbox[stat].config.length - props.inbox[stat].config.read,
|
||||
0
|
||||
);
|
||||
unreads[stat] = Boolean(unread);
|
||||
if (unread &&
|
||||
(selectedGroups.length === 0 || selectedGroups.map(((e) => {
|
||||
return e[0];
|
||||
})).includes(associations.chat?.[stat]?.['group-path']) ||
|
||||
associations.chat?.[stat]?.['group-path'].startsWith('/~/'))) {
|
||||
totalUnreads += unread;
|
||||
if (
|
||||
unread &&
|
||||
(selectedGroups.length === 0 ||
|
||||
selectedGroups
|
||||
.map((e) => {
|
||||
return e[0];
|
||||
})
|
||||
.includes(associations.chat?.[stat]?.['group-path']) ||
|
||||
associations.chat?.[stat]?.['group-path'].startsWith('/~/'))
|
||||
) {
|
||||
totalUnreads += unread;
|
||||
}
|
||||
});
|
||||
|
||||
if (totalUnreads !== this.totalUnreads) {
|
||||
document.title = totalUnreads > 0 ? `OS1 - Chat (${totalUnreads})` : 'OS1 - Chat';
|
||||
document.title =
|
||||
totalUnreads > 0 ? `OS1 - Chat (${totalUnreads})` : 'OS1 - Chat';
|
||||
this.totalUnreads = totalUnreads;
|
||||
}
|
||||
|
||||
const invites = state.invites ? state.invites : { '/chat': {}, '/contacts': {} };
|
||||
const {
|
||||
invites,
|
||||
s3,
|
||||
sidebarShown,
|
||||
inbox,
|
||||
contacts,
|
||||
permissions,
|
||||
chatSynced,
|
||||
api,
|
||||
chatInitialized,
|
||||
pendingMessages
|
||||
} = props;
|
||||
|
||||
const contacts = state.contacts ? state.contacts : {};
|
||||
const s3 = state.s3 ? state.s3 : {};
|
||||
|
||||
const renderChannelSidebar = (props, station) => (
|
||||
const renderChannelSidebar = (props, station?) => (
|
||||
<Sidebar
|
||||
inbox={state.inbox}
|
||||
inbox={inbox}
|
||||
messagePreviews={messagePreviews}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
invites={invites['/chat'] || {}}
|
||||
unreads={unreads}
|
||||
api={this.api}
|
||||
api={api}
|
||||
station={station}
|
||||
{...props}
|
||||
/>
|
||||
@ -117,14 +130,14 @@ export default class ChatApp extends React.Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
chatHideonMobile={true}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
>
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column bg-white bg-gray0-d">
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f8 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
Select, create, or join a chat to begin.
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
@ -143,15 +156,15 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
>
|
||||
<NewDmScreen
|
||||
api={this.api}
|
||||
inbox={state.inbox || {}}
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
permissions={permissions || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
chatSynced={state.chatSynced || {}}
|
||||
chatSynced={chatSynced || {}}
|
||||
autoCreate={ship}
|
||||
{...props}
|
||||
/>
|
||||
@ -169,15 +182,15 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
>
|
||||
<NewScreen
|
||||
api={this.api}
|
||||
inbox={state.inbox || {}}
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
api={api}
|
||||
inbox={inbox || {}}
|
||||
permissions={permissions || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
chatSynced={state.chatSynced || {}}
|
||||
chatSynced={chatSynced || {}}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -188,8 +201,7 @@ export default class ChatApp extends React.Component {
|
||||
exact
|
||||
path="/~chat/join/(~)?/:ship?/:station?"
|
||||
render={(props) => {
|
||||
let station =
|
||||
`/${props.match.params.ship}/${props.match.params.station}`;
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
@ -201,13 +213,13 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
>
|
||||
<JoinScreen
|
||||
api={this.api}
|
||||
inbox={state.inbox}
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
autoJoin={station}
|
||||
chatSynced={state.chatSynced || {}}
|
||||
chatSynced={chatSynced || {}}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -218,13 +230,12 @@ export default class ChatApp extends React.Component {
|
||||
exact
|
||||
path="/~chat/(popout)?/room/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station =
|
||||
`/${props.match.params.ship}/${props.match.params.station}`;
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
}
|
||||
const mailbox = state.inbox[station] || {
|
||||
const mailbox = inbox[station] || {
|
||||
config: {
|
||||
read: 0,
|
||||
length: 0
|
||||
@ -235,11 +246,11 @@ export default class ChatApp extends React.Component {
|
||||
let roomContacts = {};
|
||||
const associatedGroup =
|
||||
station in associations['chat'] &&
|
||||
'group-path' in associations.chat[station]
|
||||
'group-path' in associations.chat[station]
|
||||
? associations.chat[station]['group-path']
|
||||
: '';
|
||||
|
||||
if ((associations.chat[station]) && (associatedGroup in contacts)) {
|
||||
if (associations.chat[station] && associatedGroup in contacts) {
|
||||
roomContacts = contacts[associatedGroup];
|
||||
}
|
||||
|
||||
@ -247,10 +258,12 @@ export default class ChatApp extends React.Component {
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
|
||||
const permission =
|
||||
station in state.permissions ? state.permissions[station] : {
|
||||
who: new Set([]),
|
||||
kind: 'white'
|
||||
};
|
||||
station in permissions
|
||||
? permissions[station]
|
||||
: {
|
||||
who: new Set([]),
|
||||
kind: 'white'
|
||||
};
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
|
||||
return (
|
||||
@ -259,26 +272,25 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<ChatScreen
|
||||
chatSynced={state.chatSynced}
|
||||
chatSynced={chatSynced || {}}
|
||||
station={station}
|
||||
association={association}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
api={api}
|
||||
read={mailbox.config.read}
|
||||
length={mailbox.config.length}
|
||||
envelopes={mailbox.envelopes}
|
||||
inbox={state.inbox}
|
||||
inbox={inbox}
|
||||
contacts={roomContacts}
|
||||
permission={permission}
|
||||
pendingMessages={state.pendingMessages}
|
||||
pendingMessages={pendingMessages}
|
||||
s3={s3}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
chatInitialized={state.chatInitialized}
|
||||
sidebarShown={sidebarShown}
|
||||
chatInitialized={chatInitialized}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -295,7 +307,7 @@ export default class ChatApp extends React.Component {
|
||||
station = '/~' + station;
|
||||
}
|
||||
|
||||
const permission = state.permissions[station] || {
|
||||
const permission = permissions[station] || {
|
||||
kind: '',
|
||||
who: new Set([])
|
||||
};
|
||||
@ -309,20 +321,20 @@ export default class ChatApp extends React.Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<MemberScreen
|
||||
{...props}
|
||||
api={this.api}
|
||||
api={api}
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
contacts={contacts}
|
||||
permissions={state.permissions}
|
||||
permissions={permissions}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -332,8 +344,7 @@ export default class ChatApp extends React.Component {
|
||||
exact
|
||||
path="/~chat/(popout)?/settings/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station =
|
||||
`/${props.match.params.ship}/${props.match.params.station}`;
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
@ -341,7 +352,7 @@ export default class ChatApp extends React.Component {
|
||||
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
|
||||
const permission = state.permissions[station] || {
|
||||
const permission = permissions[station] || {
|
||||
kind: '',
|
||||
who: new Set([])
|
||||
};
|
||||
@ -355,7 +366,7 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<SettingsScreen
|
||||
@ -363,19 +374,19 @@ export default class ChatApp extends React.Component {
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
permissions={permissions || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
api={this.api}
|
||||
inbox={state.inbox}
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
@ -1,19 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { Component } from "react";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, RouteComponentProps } from "react-router-dom";
|
||||
|
||||
import { ResubscribeElement } from './lib/resubscribe-element';
|
||||
import { BacklogElement } from './lib/backlog-element';
|
||||
import { Message } from './lib/message';
|
||||
import { SidebarSwitcher } from '../../../components/SidebarSwitch';
|
||||
import { ChatTabBar } from './lib/chat-tabbar';
|
||||
import { ChatInput } from './lib/chat-input';
|
||||
import { UnreadNotice } from './lib/unread-notice';
|
||||
import { deSig } from '../../../lib/util';
|
||||
import { ResubscribeElement } from "./lib/resubscribe-element";
|
||||
import { BacklogElement } from "./lib/backlog-element";
|
||||
import { Message } from "./lib/message";
|
||||
import { SidebarSwitcher } from "../../../components/SidebarSwitch";
|
||||
import { ChatTabBar } from "./lib/chat-tabbar";
|
||||
import { ChatInput } from "./lib/chat-input";
|
||||
import { UnreadNotice } from "./lib/unread-notice";
|
||||
import { deSig } from "../../../lib/util";
|
||||
import { ChatHookUpdate } from "../../../types/chat-hook-update";
|
||||
import ChatApi from "../../../api/chat";
|
||||
import { Inbox, Envelope } from "../../../types/chat-update";
|
||||
import { Contacts } from "../../../types/contact-update";
|
||||
import { Path, Patp } from "../../../types/noun";
|
||||
import GlobalApi from "../../../api/global";
|
||||
import { Association } from "../../../types/metadata-update";
|
||||
|
||||
function getNumPending(props) {
|
||||
function getNumPending(props: any) {
|
||||
const result = props.pendingMessages.has(props.station)
|
||||
? props.pendingMessages.get(props.station).length
|
||||
: 0;
|
||||
@ -25,26 +32,32 @@ const DEFAULT_BACKLOG_SIZE = 300;
|
||||
const MAX_BACKLOG_SIZE = 1000;
|
||||
|
||||
function scrollIsAtTop(container) {
|
||||
if ((navigator.userAgent.includes("Safari") &&
|
||||
if (
|
||||
(navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
return container.scrollTop === 0;
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
return container.scrollHeight + Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10;
|
||||
return (
|
||||
container.scrollHeight + Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function scrollIsAtBottom(container) {
|
||||
if ((navigator.userAgent.includes("Safari") &&
|
||||
if (
|
||||
(navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
return container.scrollHeight - Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10;
|
||||
return (
|
||||
container.scrollHeight - Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10
|
||||
);
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
return container.scrollTop === 0;
|
||||
} else {
|
||||
@ -52,7 +65,50 @@ function scrollIsAtBottom(container) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatScreen extends Component {
|
||||
type IMessage = Envelope & { pending?: boolean };
|
||||
|
||||
type ChatScreenProps = RouteComponentProps<{
|
||||
ship: Patp;
|
||||
station: string;
|
||||
}> & {
|
||||
chatSynced: ChatHookUpdate;
|
||||
station: any;
|
||||
association: Association;
|
||||
api: GlobalApi;
|
||||
read: number;
|
||||
length: number;
|
||||
inbox: Inbox;
|
||||
contacts: Contacts;
|
||||
permission: any;
|
||||
pendingMessages: Map<Path, Envelope[]>;
|
||||
s3: any;
|
||||
popout: boolean;
|
||||
sidebarShown: boolean;
|
||||
chatInitialized: boolean;
|
||||
envelopes: Envelope[];
|
||||
};
|
||||
|
||||
interface ChatScreenState {
|
||||
numPages: number;
|
||||
scrollLocked: boolean;
|
||||
read: number;
|
||||
active: boolean;
|
||||
lastScrollHeight: number | null;
|
||||
}
|
||||
|
||||
export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
hasAskedForMessages = false;
|
||||
lastNumPending = 0;
|
||||
|
||||
scrollContainer: HTMLElement | null = null;
|
||||
|
||||
unreadMarker = null;
|
||||
scrolledToMarker = false;
|
||||
|
||||
activityTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
scrollElement: HTMLElement | null = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -65,29 +121,22 @@ export class ChatScreen extends Component {
|
||||
lastScrollHeight: null,
|
||||
};
|
||||
|
||||
this.hasAskedForMessages = false;
|
||||
this.lastNumPending = 0;
|
||||
|
||||
this.scrollContainer = null;
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
|
||||
this.unreadMarker = null;
|
||||
this.scrolledToMarker = false;
|
||||
this.setUnreadMarker = this.setUnreadMarker.bind(this);
|
||||
|
||||
this.activityTimeout = true;
|
||||
this.handleActivity = this.handleActivity.bind(this);
|
||||
this.setInactive = this.setInactive.bind(this);
|
||||
|
||||
moment.updateLocale('en', {
|
||||
moment.updateLocale("en", {
|
||||
calendar: {
|
||||
sameDay: '[Today]',
|
||||
nextDay: '[Tomorrow]',
|
||||
nextWeek: 'dddd',
|
||||
lastDay: '[Yesterday]',
|
||||
lastWeek: '[Last] dddd',
|
||||
sameElse: 'DD/MM/YYYY'
|
||||
}
|
||||
sameDay: "[Today]",
|
||||
nextDay: "[Tomorrow]",
|
||||
nextWeek: "dddd",
|
||||
lastDay: "[Yesterday]",
|
||||
lastWeek: "[Last] dddd",
|
||||
sameElse: "DD/MM/YYYY",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -104,17 +153,17 @@ export class ChatScreen extends Component {
|
||||
document.removeEventListener("mousedown", this.handleActivity, false);
|
||||
document.removeEventListener("keypress", this.handleActivity, false);
|
||||
document.removeEventListener("touchmove", this.handleActivity, false);
|
||||
if(this.activityTimeout) {
|
||||
if (this.activityTimeout) {
|
||||
clearTimeout(this.activityTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
handleActivity() {
|
||||
if(!this.state.active) {
|
||||
if (!this.state.active) {
|
||||
this.setState({ active: true });
|
||||
}
|
||||
|
||||
if(this.activityTimeout) {
|
||||
if (this.activityTimeout) {
|
||||
clearTimeout(this.activityTimeout);
|
||||
}
|
||||
|
||||
@ -139,13 +188,13 @@ export class ChatScreen extends Component {
|
||||
const unreadUnloaded = unread - props.envelopes.length;
|
||||
const excessUnread = unreadUnloaded > MAX_BACKLOG_SIZE;
|
||||
|
||||
if(!excessUnread && unreadUnloaded + 20 > DEFAULT_BACKLOG_SIZE) {
|
||||
if (!excessUnread && unreadUnloaded + 20 > DEFAULT_BACKLOG_SIZE) {
|
||||
this.askForMessages(unreadUnloaded + 20);
|
||||
} else {
|
||||
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||
}
|
||||
|
||||
if(excessUnread || props.read === props.length){
|
||||
if (excessUnread || props.read === props.length) {
|
||||
this.scrolledToMarker = true;
|
||||
this.setState(
|
||||
{
|
||||
@ -156,7 +205,7 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.setState({ scrollLocked: true, numPages: Math.ceil(unread/100) });
|
||||
this.setState({ scrollLocked: true, numPages: Math.ceil(unread / 100) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,34 +217,36 @@ export class ChatScreen extends Component {
|
||||
prevProps.match.params.ship !== props.match.params.ship
|
||||
) {
|
||||
this.receivedNewChat();
|
||||
} else if (props.chatInitialized &&
|
||||
!(props.station in props.inbox) &&
|
||||
(Boolean(props.chatSynced) && !(props.station in props.chatSynced))) {
|
||||
props.history.push('/~chat');
|
||||
} else if (
|
||||
props.envelopes.length >= prevProps.envelopes.length + 10
|
||||
props.chatInitialized &&
|
||||
!(props.station in props.inbox) &&
|
||||
Boolean(props.chatSynced) &&
|
||||
!(props.station in props.chatSynced)
|
||||
) {
|
||||
props.history.push("/~chat");
|
||||
} else if (props.envelopes.length >= prevProps.envelopes.length + 10) {
|
||||
this.hasAskedForMessages = false;
|
||||
} else if(props.length !== prevProps.length &&
|
||||
prevProps.length === prevState.read &&
|
||||
state.active
|
||||
} else if (
|
||||
props.length !== prevProps.length &&
|
||||
prevProps.length === prevState.read &&
|
||||
state.active
|
||||
) {
|
||||
this.setState({ read: props.length });
|
||||
this.props.api.chat.read(this.props.station);
|
||||
}
|
||||
|
||||
if(!prevProps.chatInitialized && props.chatInitialized) {
|
||||
if (!prevProps.chatInitialized && props.chatInitialized) {
|
||||
this.receivedNewChat();
|
||||
}
|
||||
|
||||
if (
|
||||
(props.length !== prevProps.length ||
|
||||
props.envelopes.length !== prevProps.envelopes.length ||
|
||||
getNumPending(props) !== this.lastNumPending ||
|
||||
state.numPages !== prevState.numPages)
|
||||
props.length !== prevProps.length ||
|
||||
props.envelopes.length !== prevProps.envelopes.length ||
|
||||
getNumPending(props) !== this.lastNumPending ||
|
||||
state.numPages !== prevState.numPages
|
||||
) {
|
||||
this.scrollToBottom();
|
||||
if(navigator.userAgent.includes("Firefox")) {
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
this.recalculateScrollTop();
|
||||
}
|
||||
|
||||
@ -219,7 +270,7 @@ export class ChatScreen extends Component {
|
||||
if (start > 0) {
|
||||
const end = start + size < props.length ? start + size : props.length;
|
||||
this.hasAskedForMessages = true;
|
||||
props.subscription.fetchMessages(start + 1, end, props.station);
|
||||
props.api.chat.fetchMessages(start + 1, end, props.station);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,31 +282,31 @@ export class ChatScreen extends Component {
|
||||
|
||||
// Restore chat position on FF when new messages come in
|
||||
recalculateScrollTop() {
|
||||
if(!this.scrollContainer) {
|
||||
const { lastScrollHeight } = this.state;
|
||||
if (!this.scrollContainer || !lastScrollHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lastScrollHeight } = this.state;
|
||||
const target = this.scrollContainer;
|
||||
const newScrollTop = this.scrollContainer.scrollHeight - lastScrollHeight;
|
||||
if(target.scrollTop !== 0 || newScrollTop === target.scrollTop) {
|
||||
if (target.scrollTop !== 0 || newScrollTop === target.scrollTop) {
|
||||
return;
|
||||
}
|
||||
target.scrollTop = target.scrollHeight - lastScrollHeight;
|
||||
}
|
||||
|
||||
onScroll(e) {
|
||||
if(scrollIsAtTop(e.target)) {
|
||||
if (scrollIsAtTop(e.target)) {
|
||||
// Save scroll position for FF
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
this.setState({
|
||||
lastScrollHeight: e.target.scrollHeight
|
||||
lastScrollHeight: e.target.scrollHeight,
|
||||
});
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
numPages: this.state.numPages + 1,
|
||||
scrollLocked: true
|
||||
scrollLocked: true,
|
||||
},
|
||||
() => {
|
||||
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||
@ -265,21 +316,20 @@ export class ChatScreen extends Component {
|
||||
this.dismissUnread();
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
scrollLocked: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setUnreadMarker(ref) {
|
||||
if(ref && !this.scrolledToMarker) {
|
||||
if (ref && !this.scrolledToMarker) {
|
||||
this.setState({ scrollLocked: true }, () => {
|
||||
ref.scrollIntoView({ block: 'center' });
|
||||
if(ref.offsetParent &&
|
||||
scrollIsAtBottom(ref.offsetParent)) {
|
||||
ref.scrollIntoView({ block: "center" });
|
||||
if (ref.offsetParent && scrollIsAtBottom(ref.offsetParent)) {
|
||||
this.dismissUnread();
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
scrollLocked: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -298,38 +348,32 @@ export class ChatScreen extends Component {
|
||||
|
||||
const { props, state } = this;
|
||||
|
||||
let messages = props.envelopes.slice(0);
|
||||
let messages: IMessage[] = props.envelopes.slice(0);
|
||||
const lastMsgNum = messages.length > 0 ? messages.length : 0;
|
||||
|
||||
if (messages.length > 100 * state.numPages) {
|
||||
messages = messages.slice(0, 100 * state.numPages);
|
||||
}
|
||||
|
||||
const pendingMessages = props.pendingMessages.has(props.station)
|
||||
? props.pendingMessages.get(props.station)
|
||||
: [];
|
||||
|
||||
pendingMessages.map((value) => {
|
||||
return (value.pending = true);
|
||||
});
|
||||
const pendingMessages: IMessage[] = (
|
||||
props.pendingMessages.get(props.station) || []
|
||||
).map((value) => ({ ...value, pending: true }));
|
||||
|
||||
messages = pendingMessages.concat(messages);
|
||||
|
||||
const messageElements = messages.map((msg, i) => {
|
||||
// Render sigil if previous message is not by the same sender
|
||||
const aut = ['author'];
|
||||
const aut = ["author"];
|
||||
const renderSigil =
|
||||
_.get(messages[i + 1], aut) !==
|
||||
_.get(msg, aut, msg.author);
|
||||
_.get(messages[i + 1], aut) !== _.get(msg, aut, msg.author);
|
||||
const paddingTop = renderSigil;
|
||||
const paddingBot =
|
||||
_.get(messages[i - 1], aut) !==
|
||||
_.get(msg, aut, msg.author);
|
||||
_.get(messages[i - 1], aut) !== _.get(msg, aut, msg.author);
|
||||
|
||||
const when = ['when'];
|
||||
const when = ["when"];
|
||||
const dayBreak =
|
||||
moment(_.get(messages[i+1], when)).format('YYYY.MM.DD') !==
|
||||
moment(_.get(messages[i], when)).format('YYYY.MM.DD');
|
||||
moment(_.get(messages[i + 1], when)).format("YYYY.MM.DD") !==
|
||||
moment(_.get(messages[i], when)).format("YYYY.MM.DD");
|
||||
|
||||
const messageElem = (
|
||||
<Message
|
||||
@ -343,33 +387,39 @@ export class ChatScreen extends Component {
|
||||
group={props.association}
|
||||
/>
|
||||
);
|
||||
if(unread > 0 && i === unread - 1) {
|
||||
if (unread > 0 && i === unread - 1) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'unreads'+ msg.uid} ref={this.setUnreadMarker} className="mv2 green2 flex items-center f9">
|
||||
<div
|
||||
key={"unreads" + msg.uid}
|
||||
ref={this.setUnreadMarker}
|
||||
className="mv2 green2 flex items-center f9"
|
||||
>
|
||||
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
||||
<p className="mh4">
|
||||
New messages below
|
||||
</p>
|
||||
<p className="mh4">New messages below</p>
|
||||
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
|
||||
{ dayBreak && (
|
||||
<p className="gray2 mh4">
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
{dayBreak && (
|
||||
<p className="gray2 mh4">
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
)}
|
||||
<hr style={{ width: 'calc(50% - 48px)' }} className="b--green2 ma0 bt-0" />
|
||||
<hr
|
||||
style={{ width: "calc(50% - 48px)" }}
|
||||
className="b--green2 ma0 bt-0"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else if(dayBreak) {
|
||||
} else if (dayBreak) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'daybreak' + msg.uid} className="pv3 gray2 b--gray2 flex items-center justify-center f9 ">
|
||||
<p>
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
<div
|
||||
key={"daybreak" + msg.uid}
|
||||
className="pv3 gray2 b--gray2 flex items-center justify-center f9 "
|
||||
>
|
||||
<p>{moment(_.get(messages[i], when)).calendar()}</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -378,47 +428,47 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
return (
|
||||
<div className="relative overflow-y-scroll h-100" onScroll={this.onScroll}
|
||||
ref={(e) => {
|
||||
this.scrollContainer = e;
|
||||
}}
|
||||
<div
|
||||
className="relative overflow-y-scroll h-100"
|
||||
onScroll={this.onScroll}
|
||||
ref={(e) => {
|
||||
this.scrollContainer = e;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="bg-white bg-gray0-d pt3 pb2 flex flex-column-reverse"
|
||||
style={{ resize: 'vertical' }}
|
||||
style={{ resize: "vertical" }}
|
||||
>
|
||||
<div
|
||||
ref={(el) => {
|
||||
this.scrollElement = el;
|
||||
}}
|
||||
></div>
|
||||
{(props.chatInitialized &&
|
||||
!(props.station in props.inbox)) && (
|
||||
<BacklogElement />
|
||||
{props.chatInitialized && !(props.station in props.inbox) && (
|
||||
<BacklogElement />
|
||||
)}
|
||||
{props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
messages.length > 0 ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{(
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
(messages.length > 0)
|
||||
) ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (<div />)
|
||||
}
|
||||
{messageElements}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="overflow-y-scroll bg-white bg-gray0-d pt3 pb2 flex flex-column-reverse relative"
|
||||
style={{ height: '100%', resize: 'vertical' }}
|
||||
style={{ height: "100%", resize: "vertical" }}
|
||||
onScroll={this.onScroll}
|
||||
>
|
||||
<div
|
||||
@ -426,26 +476,24 @@ ref={(e) => {
|
||||
this.scrollElement = el;
|
||||
}}
|
||||
></div>
|
||||
{(props.chatInitialized &&
|
||||
!(props.station in props.inbox)) && (
|
||||
<BacklogElement />
|
||||
{props.chatInitialized && !(props.station in props.inbox) && (
|
||||
<BacklogElement />
|
||||
)}
|
||||
{props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
messages.length > 0 ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{(
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
(messages.length > 0)
|
||||
) ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (<div />)
|
||||
}
|
||||
{messageElements}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -457,16 +505,16 @@ ref={(e) => {
|
||||
|
||||
const group = Array.from(props.permission.who.values());
|
||||
|
||||
const isinPopout = props.popout ? 'popout/' : '';
|
||||
const isinPopout = props.popout ? "popout/" : "";
|
||||
|
||||
const ownerContact = (window.ship in props.contacts)
|
||||
? props.contacts[window.ship] : false;
|
||||
const ownerContact =
|
||||
window.ship in props.contacts ? props.contacts[window.ship] : false;
|
||||
|
||||
let title = props.station.substr(1);
|
||||
|
||||
if (props.association && 'metadata' in props.association) {
|
||||
if (props.association && "metadata" in props.association) {
|
||||
title =
|
||||
props.association.metadata.title !== ''
|
||||
props.association.metadata.title !== ""
|
||||
? props.association.metadata.title
|
||||
: props.station.substr(1);
|
||||
}
|
||||
@ -475,8 +523,8 @@ ref={(e) => {
|
||||
|
||||
const unreadMsg = unread > 0 && messages[unread - 1];
|
||||
|
||||
|
||||
const showUnreadNotice = props.length !== props.read && props.read === state.read;
|
||||
const showUnreadNotice =
|
||||
props.length !== props.read && props.read === state.read;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -485,14 +533,16 @@ ref={(e) => {
|
||||
>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: '1rem' }}
|
||||
style={{ height: "1rem" }}
|
||||
>
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
<Link to="/~chat/">{"⟵ All Chats"}</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={'pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative ' +
|
||||
'overflow-x-auto overflow-y-hidden flex-shrink-0 '}
|
||||
className={
|
||||
"pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative " +
|
||||
"overflow-x-auto overflow-y-hidden flex-shrink-0 "
|
||||
}
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
@ -500,13 +550,16 @@ ref={(e) => {
|
||||
popout={props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className="pt2 white-d"
|
||||
<Link
|
||||
to={"/~chat/" + isinPopout + "room" + props.station}
|
||||
className="pt2 white-d"
|
||||
>
|
||||
<h2
|
||||
className={'dib f9 fw4 lh-solid v-top ' +
|
||||
((title === props.station.substr(1)) ? 'mono' : '')}
|
||||
style={{ width: 'max-content' }}
|
||||
className={
|
||||
"dib f9 fw4 lh-solid v-top " +
|
||||
(title === props.station.substr(1) ? "mono" : "")
|
||||
}
|
||||
style={{ width: "max-content" }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
@ -520,13 +573,13 @@ ref={(e) => {
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
{ !!unreadMsg && showUnreadNotice && (
|
||||
{!!unreadMsg && showUnreadNotice && (
|
||||
<UnreadNotice
|
||||
unread={unread}
|
||||
unreadMsg={unreadMsg}
|
||||
onRead={() => this.dismissUnread()}
|
||||
/>
|
||||
) }
|
||||
)}
|
||||
{this.chatWindow(unread)}
|
||||
<ChatInput
|
||||
api={props.api}
|
@ -45,7 +45,7 @@ export class JoinScreen extends Component {
|
||||
this.setState({
|
||||
station,
|
||||
awaiting: true
|
||||
}, () => props.api.chatView.join(ship, station, true));
|
||||
}, () => props.api.chat.join(ship, station, true));
|
||||
}
|
||||
|
||||
if (state.station in props.inbox ||
|
||||
@ -78,7 +78,7 @@ export class JoinScreen extends Component {
|
||||
station,
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.chatView.join(ship, station, true);
|
||||
props.api.chat.join(ship, station, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
|
||||
export class ResubscribeElement extends Component {
|
||||
onClickResubscribe() {
|
||||
this.props.api.chatHook.addSynced(
|
||||
this.props.api.chat.addSynced(
|
||||
this.props.host,
|
||||
this.props.station,
|
||||
true);
|
||||
|
@ -2,11 +2,11 @@ import React, { Component } from 'react';
|
||||
|
||||
export class SidebarInvite extends Component {
|
||||
onAccept() {
|
||||
this.props.api.invite.accept(this.props.uid);
|
||||
this.props.api.invite.accept('/chat', this.props.uid);
|
||||
}
|
||||
|
||||
onDecline() {
|
||||
this.props.api.invite.decline(this.props.uid);
|
||||
this.props.api.invite.decline('/chat', this.props.uid);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -63,7 +63,7 @@ export class NewDmScreen extends Component {
|
||||
},
|
||||
() => {
|
||||
const groupPath = station;
|
||||
props.api.chatView.create(
|
||||
props.api.chat.create(
|
||||
`~${window.ship} <-> ~${state.ship}`,
|
||||
'',
|
||||
station,
|
||||
|
@ -146,7 +146,7 @@ export class NewScreen extends Component {
|
||||
if (state.groups.length > 0) {
|
||||
groupPath = state.groups[0];
|
||||
}
|
||||
const submit = props.api.chatView.create(
|
||||
const submit = props.api.chat.create(
|
||||
state.title,
|
||||
state.description,
|
||||
appPath,
|
||||
|
@ -108,7 +108,8 @@ export class SettingsScreen extends Component {
|
||||
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
@ -133,7 +134,7 @@ export class SettingsScreen extends Component {
|
||||
? 'Deleting chat...'
|
||||
: 'Leaving chat...'
|
||||
}, (() => {
|
||||
props.api.chatView.delete(props.station);
|
||||
props.api.chat.delete(props.station);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -145,7 +146,7 @@ export class SettingsScreen extends Component {
|
||||
awaiting: true,
|
||||
type: 'Converting chat...'
|
||||
}, (() => {
|
||||
props.api.chatView.groupify(
|
||||
props.api.chat.groupify(
|
||||
props.station, state.targetGroup, state.inclusive
|
||||
).then(() => this.setState({ awaiting: false }));
|
||||
}));
|
||||
@ -278,7 +279,8 @@ export class SettingsScreen extends Component {
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
state.title,
|
||||
@ -307,7 +309,8 @@ export class SettingsScreen extends Component {
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
|
@ -17,9 +17,6 @@ import GroupDetail from './components/lib/group-detail';
|
||||
export default class GroupsApp extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new GroupsStore();
|
||||
this.state = this.store.state;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -27,41 +24,30 @@ export default class GroupsApp extends Component {
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
const channel = new this.props.channel();
|
||||
this.api = new GroupsApi(this.props.ship, channel, this.store);
|
||||
|
||||
this.subscription = new GroupsSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
this.props.subscription.startApp('groups')
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
this.props.subscription.stopApp('groups')
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const { props } = this;
|
||||
|
||||
const contacts = state.contacts ? state.contacts : {};
|
||||
const contacts = props.contacts || {};
|
||||
const defaultContacts =
|
||||
(Boolean(state.contacts) && '/~/default' in state.contacts) ?
|
||||
state.contacts['/~/default'] : {};
|
||||
const groups = state.groups ? state.groups : {};
|
||||
(Boolean(props.contacts) && '/~/default' in props.contacts) ?
|
||||
props.contacts['/~/default'] : {};
|
||||
const groups = props.groups ? props.groups : {};
|
||||
|
||||
const invites =
|
||||
(Boolean(state.invites) && '/contacts' in state.invites) ?
|
||||
state.invites['/contacts'] : {};
|
||||
const associations = state.associations ? state.associations : {};
|
||||
(Boolean(props.invites) && '/contacts' in props.invites) ?
|
||||
props.invites['/contacts'] : {};
|
||||
const associations = props.associations ? props.associations : {};
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
const s3 = state.s3 ? state.s3 : {};
|
||||
const s3 = props.s3 ? props.s3 : {};
|
||||
const { api } = props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
@ -72,7 +58,7 @@ export default class GroupsApp extends Component {
|
||||
activeDrawer="groups"
|
||||
selectedGroups={selectedGroups}
|
||||
history={props.history}
|
||||
api={this.api}
|
||||
api={api}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
invites={invites}
|
||||
@ -95,7 +81,7 @@ export default class GroupsApp extends Component {
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
api={api}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
invites={invites}
|
||||
@ -106,7 +92,7 @@ export default class GroupsApp extends Component {
|
||||
history={props.history}
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
api={api}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -129,7 +115,7 @@ export default class GroupsApp extends Component {
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
api={api}
|
||||
contacts={contacts}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
@ -142,7 +128,7 @@ export default class GroupsApp extends Component {
|
||||
defaultContacts={defaultContacts}
|
||||
group={group}
|
||||
activeDrawer={(detail || settings) ? 'detail' : 'contacts'}
|
||||
api={this.api}
|
||||
api={api}
|
||||
path={groupPath}
|
||||
{...props}
|
||||
/>
|
||||
@ -153,7 +139,7 @@ export default class GroupsApp extends Component {
|
||||
activeDrawer={(detail || settings) ? 'detail' : 'contacts'}
|
||||
settings={settings}
|
||||
associations={associations}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -171,7 +157,7 @@ export default class GroupsApp extends Component {
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
api={api}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
invites={invites}
|
||||
@ -185,11 +171,11 @@ export default class GroupsApp extends Component {
|
||||
group={group}
|
||||
activeDrawer="rightPanel"
|
||||
path={groupPath}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
<AddScreen
|
||||
api={this.api}
|
||||
api={api}
|
||||
groups={groups}
|
||||
path={groupPath}
|
||||
history={props.history}
|
||||
@ -215,7 +201,7 @@ export default class GroupsApp extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
api={this.api}
|
||||
api={api}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
@ -230,12 +216,12 @@ export default class GroupsApp extends Component {
|
||||
defaultContacts={defaultContacts}
|
||||
group={group}
|
||||
path={groupPath}
|
||||
api={this.api}
|
||||
api={api}
|
||||
selectedContact={shipPath}
|
||||
{...props}
|
||||
/>
|
||||
<ContactCard
|
||||
api={this.api}
|
||||
api={api}
|
||||
history={props.history}
|
||||
contact={contact}
|
||||
path={groupPath}
|
||||
@ -268,7 +254,7 @@ export default class GroupsApp extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
api={this.api}
|
||||
api={api}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
@ -283,12 +269,12 @@ export default class GroupsApp extends Component {
|
||||
defaultContacts={defaultContacts}
|
||||
group={group}
|
||||
path={groupPath}
|
||||
api={this.api}
|
||||
api={api}
|
||||
selectedContact={shipPath}
|
||||
{...props}
|
||||
/>
|
||||
<ContactCard
|
||||
api={this.api}
|
||||
api={api}
|
||||
history={props.history}
|
||||
contact={contact}
|
||||
path={groupPath}
|
||||
@ -307,7 +293,7 @@ export default class GroupsApp extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
history={props.history}
|
||||
api={this.api}
|
||||
api={api}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
@ -317,7 +303,7 @@ export default class GroupsApp extends Component {
|
||||
associations={associations}
|
||||
>
|
||||
<ContactCard
|
||||
api={this.api}
|
||||
api={api}
|
||||
history={props.history}
|
||||
path="/~/default"
|
||||
contact={me}
|
||||
|
@ -42,7 +42,7 @@ export class AddScreen extends Component {
|
||||
},
|
||||
awaiting: true
|
||||
}, () => {
|
||||
const submit = props.api.group.add(props.path, aud);
|
||||
const submit = props.api.groups.add(props.path, aud);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push('/~groups' + props.path);
|
||||
|
@ -141,7 +141,7 @@ export class ContactCard extends Component {
|
||||
type: 'Saving to group'
|
||||
},
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, {
|
||||
props.api.contacts.edit(props.path, ship, {
|
||||
avatar: {
|
||||
url: state.avatarToSet
|
||||
}})
|
||||
@ -161,7 +161,7 @@ export class ContactCard extends Component {
|
||||
|
||||
if (hexTest && hexTest[1] !== currentColor && !props.share) {
|
||||
this.setState({ awaiting: true, type: 'Saving to group' }, () => {
|
||||
props.api.contactHook.edit(
|
||||
props.api.contacts.edit(
|
||||
props.path, `~${props.ship}`, { color: hexTest[1] })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -180,7 +180,7 @@ export class ContactCard extends Component {
|
||||
const emailTestResult = emailTest.exec(state.emailToSet);
|
||||
if (emailTestResult) {
|
||||
this.setState({ awaiting: true, type: 'Saving to group' }, () => {
|
||||
props.api.contactHook.edit(
|
||||
props.api.contacts.edit(
|
||||
props.path, ship, { email: state.emailToSet })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -197,7 +197,7 @@ export class ContactCard extends Component {
|
||||
return false;
|
||||
}
|
||||
this.setState({ awaiting: true, type: 'Saving to group' }, () => {
|
||||
props.api.contactHook.edit(
|
||||
props.api.contacts.edit(
|
||||
props.path, ship, { nickname: state.nickNameToSet })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -214,7 +214,7 @@ export class ContactCard extends Component {
|
||||
return false;
|
||||
}
|
||||
this.setState({ awaiting: true, type: 'Saving to group' }, () => {
|
||||
props.api.contactHook.edit(
|
||||
props.api.contacts.edit(
|
||||
props.path, ship, { notes: state.notesToSet })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -232,7 +232,7 @@ export class ContactCard extends Component {
|
||||
const phoneTestResult = phoneTest.exec(state.phoneToSet);
|
||||
if (phoneTestResult) {
|
||||
this.setState({ awaiting: true, type: 'Saving to group' }, () => {
|
||||
props.api.contactHook.edit(
|
||||
props.api.contacts.edit(
|
||||
props.path, ship, { phone: state.phoneToSet })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -251,7 +251,7 @@ export class ContactCard extends Component {
|
||||
const websiteTestResult = websiteTest.exec(state.websiteToSet);
|
||||
if (websiteTestResult) {
|
||||
this.setState({ awaiting: true, type: 'Saving to group' }, () => {
|
||||
props.api.contactHook.edit(
|
||||
props.api.contacts.edit(
|
||||
props.path, ship, { website: state.websiteToSet })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
@ -264,7 +264,7 @@ export class ContactCard extends Component {
|
||||
this.setState(
|
||||
{ emailToSet: '', awaiting: true, type: 'Removing from group' },
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, { email: '' })
|
||||
props.api.contacts.edit(props.path, ship, { email: '' })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
@ -276,7 +276,7 @@ export class ContactCard extends Component {
|
||||
this.setState(
|
||||
{ nicknameToSet: '', awaiting: true, type: 'Removing from group' },
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, { nickname: '' })
|
||||
props.api.contacts.edit(props.path, ship, { nickname: '' })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
@ -288,7 +288,7 @@ export class ContactCard extends Component {
|
||||
this.setState(
|
||||
{ phoneToSet: '', awaiting: true, type: 'Removing from group' },
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, { phone: '' }).then(() => {
|
||||
props.api.contacts.edit(props.path, ship, { phone: '' }).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}
|
||||
@ -299,7 +299,7 @@ export class ContactCard extends Component {
|
||||
this.setState(
|
||||
{ websiteToSet: '', awaiting: true, type: 'Removing from group' },
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, { website: '' }).then(() => {
|
||||
props.api.contacts.edit(props.path, ship, { website: '' }).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}
|
||||
@ -314,7 +314,7 @@ export class ContactCard extends Component {
|
||||
type: 'Removing from group'
|
||||
},
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, { avatar: null }).then(() => {
|
||||
props.api.contacts.edit(props.path, ship, { avatar: null }).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}
|
||||
@ -325,7 +325,7 @@ export class ContactCard extends Component {
|
||||
this.setState(
|
||||
{ notesToSet: '', awaiting: true, type: 'Removing from group' },
|
||||
() => {
|
||||
props.api.contactHook.edit(props.path, ship, { notes: '' }).then(() => {
|
||||
props.api.contacts.edit(props.path, ship, { notes: '' }).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}
|
||||
@ -380,9 +380,10 @@ export class ContactCard extends Component {
|
||||
};
|
||||
|
||||
this.setState({ awaiting: true, type: 'Sharing with group' }, () => {
|
||||
props.api.contactView
|
||||
props.api.contacts
|
||||
.share(`~${props.ship}`, props.path, `~${window.ship}`, contact)
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~groups/view${props.path}/${window.ship}`);
|
||||
});
|
||||
});
|
||||
@ -402,7 +403,7 @@ export class ContactCard extends Component {
|
||||
avatar: null
|
||||
};
|
||||
|
||||
props.api.contactView.share(
|
||||
props.api.contacts.share(
|
||||
`~${props.ship}`,
|
||||
props.path,
|
||||
`~${window.ship}`,
|
||||
@ -410,7 +411,7 @@ export class ContactCard extends Component {
|
||||
);
|
||||
|
||||
this.setState({ awaiting: true, type: 'Removing from group' }, () => {
|
||||
props.api.contactView.delete(props.path).then(() => {
|
||||
props.api.contacts.delete(props.path).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push('/~groups');
|
||||
});
|
||||
@ -421,7 +422,7 @@ export class ContactCard extends Component {
|
||||
const { props } = this;
|
||||
|
||||
this.setState({ awaiting: true, type: 'Removing from group' }, () => {
|
||||
props.api.contactView.remove(props.path, `~${props.ship}`).then(() => {
|
||||
props.api.contacts.remove(props.path, `~${props.ship}`).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(`/~groups${props.path}`);
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ export class ContactSidebar extends Component {
|
||||
style={{ paddingTop: 6 }}
|
||||
onClick={() => {
|
||||
this.setState({ awaiting: true }, (() => {
|
||||
props.api.group.remove(props.path, [`~${member}`])
|
||||
props.api.groups.remove(props.path, [`~${member}`])
|
||||
.then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
|
@ -198,7 +198,8 @@ export class GroupDetail extends Component {
|
||||
onBlur={() => {
|
||||
if (groupOwner) {
|
||||
this.setState({ awaiting: true }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'contacts',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
this.state.title,
|
||||
@ -227,7 +228,8 @@ export class GroupDetail extends Component {
|
||||
onBlur={() => {
|
||||
if (groupOwner) {
|
||||
this.setState({ awaiting: true }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'contacts',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
@ -250,7 +252,7 @@ export class GroupDetail extends Component {
|
||||
onClick={() => {
|
||||
if (groupOwner) {
|
||||
this.setState({ awaiting: true, type: 'Deleting' }, (() => {
|
||||
props.api.contactView.delete(props.path).then(() => {
|
||||
props.api.contacts.delete(props.path).then(() => {
|
||||
props.history.push('/~groups');
|
||||
});
|
||||
}));
|
||||
|
@ -84,7 +84,7 @@ export class S3Upload extends Component {
|
||||
accept="image/*"
|
||||
onChange={this.onChange.bind(this)} />
|
||||
<img className="invert-d"
|
||||
src="/~groups/img/ImageUpload.png"
|
||||
src="/~landscape/img/ImageUpload.png"
|
||||
width="32"
|
||||
height="32"
|
||||
onClick={this.onClick.bind(this)} />
|
||||
|
@ -3,12 +3,12 @@ import React, { Component } from 'react';
|
||||
export class SidebarInvite extends Component {
|
||||
onAccept() {
|
||||
const { props } = this;
|
||||
props.api.invite.accept(props.uid);
|
||||
props.api.invite.accept('/contacts', props.uid);
|
||||
props.history.push(`/~groups${props.invite.path}`);
|
||||
}
|
||||
|
||||
onDecline() {
|
||||
this.props.api.invite.decline(this.props.uid);
|
||||
this.props.api.invite.decline('/contacts', this.props.uid);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -67,7 +67,7 @@ export class NewScreen extends Component {
|
||||
invites: '',
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.contactView.create(
|
||||
props.api.contacts.create(
|
||||
group,
|
||||
aud,
|
||||
this.state.title,
|
||||
|
@ -10,50 +10,28 @@ import Tiles from './components/tiles';
|
||||
import Welcome from './components/welcome';
|
||||
|
||||
export default class LaunchApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new LaunchStore();
|
||||
this.state = this.store.state;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = 'OS1 - Home';
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
const channel = new this.props.channel();
|
||||
this.api = new LaunchApi(this.props.ship, channel, this.store);
|
||||
|
||||
this.subscription = new LaunchSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
}
|
||||
componentWillUnmount() {}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<div className='v-mid ph2 dtc-m dtc-l dtc-xl flex justify-between flex-wrap' style={{ maxWidth: '40rem' }}>
|
||||
<Welcome firstTime={state.launch.firstTime} api={this.api} />
|
||||
<Welcome firstTime={props.launch.firstTime} api={props.api} />
|
||||
<Tiles
|
||||
tiles={state.launch.tiles}
|
||||
tileOrdering={state.launch.tileOrdering}
|
||||
api={this.api}
|
||||
location={state.location}
|
||||
weather={state.weather}
|
||||
tiles={props.launch.tiles}
|
||||
tileOrdering={props.launch.tileOrdering}
|
||||
api={props.api}
|
||||
location={props.userLocation}
|
||||
weather={props.weather}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -37,7 +37,7 @@ export default class WeatherTile extends React.Component {
|
||||
this.setState({ latlng }, (err) => {
|
||||
console.log(err);
|
||||
}, { maximumAge: Infinity, timeout: 10000 });
|
||||
this.props.api.weather(latlng);
|
||||
this.props.api.launch.weather(latlng);
|
||||
this.setState({ manualEntry: !this.state.manualEntry });
|
||||
} else {
|
||||
this.setState({ error: true });
|
||||
|
@ -22,10 +22,7 @@ import { makeRoutePath, amOwnerOfGroup, base64urlDecode } from '../../lib/util';
|
||||
export class LinksApp extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new LinksStore();
|
||||
this.state = this.store.state;
|
||||
this.totalUnseen = 0;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -33,38 +30,29 @@ export class LinksApp extends Component {
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
|
||||
const channel = new this.props.channel();
|
||||
this.api = new LinksApi(this.props.ship, channel, this.store);
|
||||
|
||||
this.subscription = new LinksSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
this.props.api.links.getPage('', 0);
|
||||
this.props.subscription.startApp('link');
|
||||
if (!this.props.sidebarShown) {
|
||||
this.props.api.local.sidebarToggle();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
this.props.subscription.stopApp('link');
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const { props } = this;
|
||||
|
||||
const contacts = state.contacts ? state.contacts : {};
|
||||
const groups = state.groups ? state.groups : {};
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
const groups = props.groups ? props.groups : {};
|
||||
|
||||
const associations = state.associations ? state.associations : { link: {}, contacts: {} };
|
||||
const links = state.links ? state.links : {};
|
||||
const comments = state.comments ? state.comments : {};
|
||||
const associations = props.associations ? props.associations : { link: {}, contacts: {} };
|
||||
const links = props.links ? props.links : {};
|
||||
const comments = props.linkComments ? props.linkComments : {};
|
||||
|
||||
const seen = state.seen ? state.seen : {};
|
||||
const seen = props.linksSeen ? props.linksSeen : {};
|
||||
|
||||
const totalUnseen = _.reduce(
|
||||
seen,
|
||||
@ -77,11 +65,15 @@ export class LinksApp extends Component {
|
||||
this.totalUnseen = totalUnseen;
|
||||
}
|
||||
|
||||
const invites = state.invites ?
|
||||
state.invites : {};
|
||||
const invites = props.invites ?
|
||||
props.invites : {};
|
||||
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
|
||||
const listening = props.linkListening;
|
||||
|
||||
const { api, sidebarShown } = this.props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/~link"
|
||||
@ -93,11 +85,11 @@ export class LinksApp extends Component {
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
rightPanelHide={true}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
selectedGroups={selectedGroups}
|
||||
links={links}
|
||||
listening={state.listening}
|
||||
api={this.api}
|
||||
listening={listening}
|
||||
api={api}
|
||||
>
|
||||
<MessageScreen text="Select or create a collection to begin." />
|
||||
</Skeleton>
|
||||
@ -111,17 +103,17 @@ export class LinksApp extends Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
selectedGroups={selectedGroups}
|
||||
links={links}
|
||||
listening={state.listening}
|
||||
api={this.api}
|
||||
listening={listening}
|
||||
api={api}
|
||||
>
|
||||
<NewScreen
|
||||
associations={associations}
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -134,7 +126,7 @@ export class LinksApp extends Component {
|
||||
|
||||
const autoJoin = () => {
|
||||
try {
|
||||
this.api.joinCollection(resourcePath);
|
||||
api.links.joinCollection(resourcePath);
|
||||
props.history.push(makeRoutePath(resourcePath));
|
||||
} catch(err) {
|
||||
setTimeout(autoJoin, 2000);
|
||||
@ -159,14 +151,14 @@ export class LinksApp extends Component {
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
selectedGroups={selectedGroups}
|
||||
links={links}
|
||||
listening={state.listening}
|
||||
api={this.api}
|
||||
listening={listening}
|
||||
api={api}
|
||||
>
|
||||
<MemberScreen
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
resource={resource}
|
||||
contacts={contacts}
|
||||
contactDetails={contactDetails}
|
||||
@ -175,7 +167,7 @@ export class LinksApp extends Component {
|
||||
amOwner={amOwner}
|
||||
resourcePath={resourcePath}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -198,15 +190,15 @@ export class LinksApp extends Component {
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
selectedGroups={selectedGroups}
|
||||
popout={popout}
|
||||
links={links}
|
||||
listening={state.listening}
|
||||
api={this.api}
|
||||
listening={listening}
|
||||
api={api}
|
||||
>
|
||||
<SettingsScreen
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
resource={resource}
|
||||
contacts={contacts}
|
||||
contactDetails={contactDetails}
|
||||
@ -215,7 +207,7 @@ export class LinksApp extends Component {
|
||||
amOwner={amOwner}
|
||||
resourcePath={resourcePath}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -253,13 +245,13 @@ export class LinksApp extends Component {
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
selectedGroups={selectedGroups}
|
||||
sidebarHideMobile={true}
|
||||
popout={popout}
|
||||
links={links}
|
||||
listening={state.listening}
|
||||
api={this.api}
|
||||
listening={listening}
|
||||
api={api}
|
||||
>
|
||||
<Links
|
||||
{...props}
|
||||
@ -272,8 +264,8 @@ export class LinksApp extends Component {
|
||||
resource={resource}
|
||||
amOwner={amOwner}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
api={this.api}
|
||||
sidebarShown={sidebarShown}
|
||||
api={api}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -311,13 +303,13 @@ export class LinksApp extends Component {
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
selectedGroups={selectedGroups}
|
||||
sidebarHideMobile={true}
|
||||
popout={popout}
|
||||
links={links}
|
||||
listening={state.listening}
|
||||
api={this.api}
|
||||
listening={listening}
|
||||
api={api}
|
||||
>
|
||||
<LinkDetail
|
||||
{...props}
|
||||
@ -330,11 +322,11 @@ export class LinksApp extends Component {
|
||||
groupPath={resource['group-path']}
|
||||
amOwner={amOwner}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
data={data}
|
||||
comments={coms}
|
||||
commentPage={commentPage}
|
||||
api={this.api}
|
||||
api={api}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
|
@ -16,7 +16,7 @@ export class Comments extends Component {
|
||||
this.props.comments.local[page]
|
||||
) {
|
||||
this.setState({ requested: this.props.commentPage });
|
||||
this.props.api.getCommentsPage(
|
||||
this.props.api.links.getCommentsPage(
|
||||
this.props.resourcePath,
|
||||
this.props.url,
|
||||
this.props.commentPage);
|
||||
|
@ -34,7 +34,7 @@ export class InviteElement extends Component {
|
||||
success: true,
|
||||
members: []
|
||||
}, () => {
|
||||
props.api.inviteToCollection(props.resourcePath, aud).then(() => {
|
||||
props.api.links.inviteToCollection(props.resourcePath, aud).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
});
|
||||
|
@ -34,7 +34,7 @@ export class LinkItem extends Component {
|
||||
}
|
||||
|
||||
markPostAsSeen() {
|
||||
this.props.api.seenLink(this.props.resourcePath, this.props.url);
|
||||
this.props.api.links.seenLink(this.props.resourcePath, this.props.url);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -21,7 +21,7 @@ export class LinkSubmit extends Component {
|
||||
? this.state.linkTitle
|
||||
: this.state.linkValue;
|
||||
this.setState({ disabled: true });
|
||||
this.props.api.postLink(this.props.resourcePath, link, title).then((r) => {
|
||||
this.props.api.links.postLink(this.props.resourcePath, link, title).then((r) => {
|
||||
this.setState({
|
||||
disabled: false,
|
||||
linkValue: '',
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
export class SidebarInvite extends Component {
|
||||
onAccept() {
|
||||
this.props.api.invite.accept(this.props.uid);
|
||||
this.props.api.invite.accept('/link', this.props.uid);
|
||||
}
|
||||
|
||||
onDecline() {
|
||||
this.props.api.invite.decline(this.props.uid);
|
||||
this.props.api.invite.decline('/link', this.props.uid);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -36,7 +36,7 @@ export class LinkDetail extends Component {
|
||||
componentDidUpdate(prevProps) {
|
||||
// if we have no preloaded data, and we aren't expecting it, get it
|
||||
if ((!this.state.data.title) && (this.props.api)) {
|
||||
this.props.api?.getSubmission(
|
||||
this.props.api?.links.getSubmission(
|
||||
this.props.resourcePath, this.props.url, this.updateData.bind(this)
|
||||
);
|
||||
}
|
||||
@ -69,7 +69,7 @@ export class LinkDetail extends Component {
|
||||
pending.add(this.state.comment);
|
||||
this.setState({ pending: pending, disabled: true });
|
||||
|
||||
this.props.api.postComment(
|
||||
this.props.api.links.postComment(
|
||||
this.props.resourcePath,
|
||||
url,
|
||||
this.state.comment
|
||||
|
@ -35,7 +35,7 @@ export class Links extends Component {
|
||||
!this.props.links[linkPage] || // don't have info?
|
||||
this.props.links.local[linkPage] // waiting on post confirmation?
|
||||
) {
|
||||
this.props.api?.getPage(this.props.resourcePath, this.props.page);
|
||||
this.props.api?.links.getPage(this.props.resourcePath, this.props.page);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ export class NewScreen extends Component {
|
||||
ships: [],
|
||||
disabled: true
|
||||
}, () => {
|
||||
const submit = props.api.createCollection(
|
||||
const submit = props.api.links.createCollection(
|
||||
appPath,
|
||||
state.title,
|
||||
state.description,
|
||||
|
@ -96,6 +96,7 @@ export class SettingsScreen extends Component {
|
||||
if (props.amOwner) {
|
||||
this.setState({ disabled: true });
|
||||
props.api.metadataAdd(
|
||||
'link',
|
||||
props.resourcePath,
|
||||
props.groupPath,
|
||||
resource.metadata.title,
|
||||
@ -117,7 +118,7 @@ export class SettingsScreen extends Component {
|
||||
disabled: true,
|
||||
type: 'Removing'
|
||||
});
|
||||
props.api.removeCollection(props.resourcePath)
|
||||
props.api.links.removeCollection(props.resourcePath)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
@ -133,7 +134,7 @@ export class SettingsScreen extends Component {
|
||||
disabled: true,
|
||||
type: 'Deleting'
|
||||
});
|
||||
props.api.deleteCollection(props.resourcePath)
|
||||
props.api.links.deleteCollection(props.resourcePath)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
@ -142,7 +143,7 @@ export class SettingsScreen extends Component {
|
||||
}
|
||||
|
||||
markAllAsSeen() {
|
||||
this.props.api.seenLink(this.props.resourcePath);
|
||||
this.props.api.links.seenLink(this.props.resourcePath);
|
||||
}
|
||||
|
||||
renderRemove() {
|
||||
@ -208,7 +209,8 @@ export class SettingsScreen extends Component {
|
||||
onBlur={() => {
|
||||
if (props.amOwner) {
|
||||
this.setState({ disabled: true });
|
||||
props.api.metadataAdd(
|
||||
props.api.metadata.metadataAdd(
|
||||
'link',
|
||||
props.resourcePath,
|
||||
props.groupPath,
|
||||
state.title,
|
||||
@ -238,7 +240,8 @@ export class SettingsScreen extends Component {
|
||||
onBlur={() => {
|
||||
if (props.amOwner) {
|
||||
this.setState({ disabled: true });
|
||||
props.api.metadataAdd(
|
||||
props.api.metadata.metadataAdd(
|
||||
'link',
|
||||
props.resourcePath,
|
||||
props.groupPath,
|
||||
resource.metadata.title,
|
||||
|
@ -19,15 +19,7 @@ import { EditPost } from './components/lib/edit-post';
|
||||
export default class PublishApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new PublishStore();
|
||||
this.state = this.store.state;
|
||||
this.unreadTotal = 0;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -35,31 +27,28 @@ export default class PublishApp extends React.Component {
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
this.props.subscription.startApp('publish');
|
||||
|
||||
const channel = new this.props.channel();
|
||||
this.api = new PublishApi(this.props.ship, channel, this.store);
|
||||
this.props.api.publish.fetchNotebooks();
|
||||
|
||||
if (!this.props.sidebarShown) {
|
||||
this.props.api.local.sidebarToggle();
|
||||
}
|
||||
|
||||
this.subscription = new PublishSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
this.api.fetchNotebooks();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
this.props.subscription.stopApp('publish');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const { props } = this;
|
||||
|
||||
const contacts = state.contacts ? state.contacts : {};
|
||||
const associations = state.associations ? state.associations : { contacts: {} };
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
const associations = props.associations ? props.associations : { contacts: {} };
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
|
||||
const notebooks = state.notebooks ? state.notebooks : {};
|
||||
const notebooks = props.notebooks ? props.notebooks : {};
|
||||
|
||||
const unreadTotal = _.chain(notebooks)
|
||||
.values()
|
||||
@ -80,6 +69,8 @@ export default class PublishApp extends React.Component {
|
||||
this.unreadTotal = unreadTotal;
|
||||
}
|
||||
|
||||
const { api, groups, permissions, sidebarShown } = props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/~publish"
|
||||
@ -90,12 +81,12 @@ export default class PublishApp extends React.Component {
|
||||
active={'sidebar'}
|
||||
rightPanelHide={true}
|
||||
sidebarShown={true}
|
||||
invites={state.invites}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<div className={`h-100 w-100 overflow-x-hidden flex flex-column
|
||||
bg-white bg-gray0-d dn db-ns`}
|
||||
@ -117,20 +108,20 @@ export default class PublishApp extends React.Component {
|
||||
popout={false}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<NewScreen
|
||||
associations={associations.contacts}
|
||||
notebooks={notebooks}
|
||||
groups={state.groups}
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -146,19 +137,19 @@ export default class PublishApp extends React.Component {
|
||||
popout={false}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<JoinScreen
|
||||
notebooks={notebooks}
|
||||
ship={ship}
|
||||
notebook={notebook}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -190,22 +181,22 @@ export default class PublishApp extends React.Component {
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
path={path}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<NewPost
|
||||
notebooks={notebooks}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -216,28 +207,28 @@ export default class PublishApp extends React.Component {
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
selectedGroups={selectedGroups}
|
||||
path={path}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<Notebook
|
||||
notebooks={notebooks}
|
||||
view={view}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
notebookContacts={notebookContacts}
|
||||
associations={associations.contacts}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
permissions={state.permissions}
|
||||
api={this.api}
|
||||
permissions={permissions}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -256,7 +247,7 @@ export default class PublishApp extends React.Component {
|
||||
|
||||
const bookGroupPath =
|
||||
notebooks?.[ship]?.[notebook]?.['subscribers-group-path'];
|
||||
const notebookContacts = (bookGroupPath in state.contacts)
|
||||
const notebookContacts = (bookGroupPath in contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
|
||||
const edit = Boolean(props.match.params.edit) || false;
|
||||
@ -267,23 +258,23 @@ export default class PublishApp extends React.Component {
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
selectedGroups={selectedGroups}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
path={path}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<EditPost
|
||||
notebooks={notebooks}
|
||||
book={notebook}
|
||||
note={note}
|
||||
ship={ship}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -294,25 +285,25 @@ export default class PublishApp extends React.Component {
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
sidebarShown={sidebarShown}
|
||||
invites={props.invites}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
path={path}
|
||||
api={this.api}
|
||||
api={api}
|
||||
>
|
||||
<Note
|
||||
notebooks={notebooks}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
groups={groups}
|
||||
contacts={notebookContacts}
|
||||
ship={ship}
|
||||
note={note}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
|
@ -53,7 +53,7 @@ export class Comments extends Component {
|
||||
|
||||
this.textArea.value = '';
|
||||
this.setState({ commentBody: '', awaiting: 'new' });
|
||||
const submit = this.props.api.publishAction(comment);
|
||||
const submit = this.props.api.publish.publishAction(comment);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: null });
|
||||
});
|
||||
@ -87,11 +87,11 @@ export class Comments extends Component {
|
||||
|
||||
this.setState({ awaiting: 'edit' });
|
||||
|
||||
window.api
|
||||
this.props.api.publish
|
||||
.publishAction(comment)
|
||||
.then(() => {
|
||||
this.setState({ awaiting: null, editing: null });
|
||||
});
|
||||
this.setState({ awaiting: null, editing: null });
|
||||
});
|
||||
}
|
||||
|
||||
commentDelete(idx) {
|
||||
@ -106,7 +106,7 @@ export class Comments extends Component {
|
||||
};
|
||||
|
||||
this.setState({ awaiting: { kind: 'del', what: idx } });
|
||||
window.api
|
||||
this.props.api.publish
|
||||
.publishAction(comment)
|
||||
.then(() => {
|
||||
this.setState({ awaiting: null });
|
||||
|
@ -25,17 +25,19 @@ export class EditPost extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props, state } = this;
|
||||
const contents = props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file;
|
||||
if (prevProps && prevProps.api !== props.api) {
|
||||
if (!(props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file)) {
|
||||
if (!contents) {
|
||||
props.api?.fetchNote(props.ship, props.book, props.note);
|
||||
} else if (state.body === '') {
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
const note = notebook.notes[props.note];
|
||||
const file = note.file;
|
||||
const body = file.slice(file.indexOf(';>') + 3);
|
||||
this.setState({ body: body });
|
||||
}
|
||||
}
|
||||
if (contents && state.body === '') {
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
const note = notebook.notes[props.note];
|
||||
const file = note.file;
|
||||
const body = file.slice(file.indexOf(';>') + 3);
|
||||
this.setState({ body: body });
|
||||
}
|
||||
}
|
||||
|
||||
postSubmit() {
|
||||
@ -53,7 +55,7 @@ export class EditPost extends Component {
|
||||
}
|
||||
};
|
||||
this.setState({ awaiting: true });
|
||||
this.props.api.publishAction(editNote).then(() => {
|
||||
this.props.api.publish.publishAction(editNote).then(() => {
|
||||
const editIndex = props.location.pathname.indexOf('/edit');
|
||||
const noteHref = props.location.pathname.slice(0, editIndex);
|
||||
this.setState({ awaiting: false });
|
||||
@ -95,7 +97,7 @@ export class EditPost extends Component {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="f9 h-100 relative">
|
||||
<div className="f9 h-100 relative publish">
|
||||
<div className="w-100 tl pv4 flex justify-center">
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
|
@ -95,7 +95,7 @@ export class JoinScreen extends Component {
|
||||
|
||||
// TODO: askHistory setting
|
||||
this.setState({ disable: true });
|
||||
this.props.api.publishAction(actionData).catch((err) => {
|
||||
this.props.api.publish.publishAction(actionData).catch((err) => {
|
||||
console.log(err);
|
||||
}).then(() => {
|
||||
this.setState({ awaiting: text });
|
||||
|
@ -37,14 +37,14 @@ export class NewPost extends Component {
|
||||
};
|
||||
|
||||
this.setState({ disabled: true });
|
||||
this.props.api.publishAction(newNote).then(() => {
|
||||
this.props.api.publish.publishAction(newNote).then(() => {
|
||||
this.setState({ awaiting: newNote['new-note'].note });
|
||||
}).catch((err) => {
|
||||
if (err.includes('note already exists')) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
newNote['new-note'].note += '-' + timestamp;
|
||||
this.setState({ awaiting: newNote['new-note'].note });
|
||||
this.props.api.publishAction(newNote);
|
||||
this.props.api.publish.publishAction(newNote);
|
||||
} else {
|
||||
this.setState({ disabled: false, awaiting: null });
|
||||
}
|
||||
@ -58,7 +58,7 @@ export class NewPost extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps && prevProps.api !== this.props.api) {
|
||||
this.props.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
this.props.api.publish.fetchNotebook(this.props.ship, this.props.book);
|
||||
}
|
||||
|
||||
const notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
|
@ -93,7 +93,7 @@ export class NewScreen extends Component {
|
||||
}
|
||||
};
|
||||
this.setState({ awaiting: bookId, disabled: true }, () => {
|
||||
props.api.publishAction(action).then(() => {
|
||||
props.api.publish.publishAction(action).then(() => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ export class Note extends Component {
|
||||
const { props } = this;
|
||||
if ((prevProps && prevProps.api !== props.api) || props.api) {
|
||||
if (!(props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file)) {
|
||||
props.api.fetchNote(props.ship, props.book, props.note);
|
||||
props.api.publish.fetchNote(props.ship, props.book, props.note);
|
||||
}
|
||||
|
||||
if (prevProps) {
|
||||
@ -63,7 +63,7 @@ export class Note extends Component {
|
||||
note: props.note
|
||||
}
|
||||
};
|
||||
props.api.publishAction(readAction);
|
||||
props.api.publish.publishAction(readAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,7 +92,7 @@ export class Note extends Component {
|
||||
const fullyLoaded = (loadedComments === allComments);
|
||||
|
||||
if (atBottom && !fullyLoaded) {
|
||||
this.props.api.fetchCommentsPage(this.props.ship,
|
||||
this.props.api.publish.fetchCommentsPage(this.props.ship,
|
||||
this.props.book, this.props.note, loadedComments, 30);
|
||||
}
|
||||
}
|
||||
@ -109,7 +109,7 @@ export class Note extends Component {
|
||||
const popout = (props.popout) ? 'popout/' : '';
|
||||
const baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
this.setState({ deleting: true });
|
||||
this.props.api.publishAction(deleteAction)
|
||||
this.props.api.publish.publishAction(deleteAction)
|
||||
.then(() => {
|
||||
props.history.push(baseUrl);
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ export class Notebook extends Component {
|
||||
atBottom = true;
|
||||
}
|
||||
if (!notebook.notes && this.props.api) {
|
||||
this.props.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
this.props.api.publish.fetchNotebook(this.props.ship, this.props.book);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ export class Notebook extends Component {
|
||||
const fullyLoaded = (loadedNotes === allNotes);
|
||||
|
||||
if (atBottom && !fullyLoaded) {
|
||||
this.props.api.fetchNotesPage(this.props.ship, this.props.book, loadedNotes, 30);
|
||||
this.props.api.publish.fetchNotesPage(this.props.ship, this.props.book, loadedNotes, 30);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ export class Notebook extends Component {
|
||||
if ((prevProps && (prevProps.api !== props.api)) || props.api) {
|
||||
const notebook = props.notebooks?.[props.ship]?.[props.book];
|
||||
if (!notebook?.subscribers) {
|
||||
props.api.fetchNotebook(props.ship, props.book);
|
||||
props.api.publish.fetchNotebook(props.ship, props.book);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,7 @@ export class Notebook extends Component {
|
||||
book: this.props.book
|
||||
}
|
||||
};
|
||||
this.props.api.publishAction(action);
|
||||
this.props.api.publish.publishAction(action);
|
||||
this.props.history.push('/~publish');
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ export class Settings extends Component {
|
||||
|
||||
changeComments() {
|
||||
this.setState({ comments: !this.state.comments, disabled: true }, (() => {
|
||||
this.props.api.publishAction({
|
||||
this.props.api.publish.publishAction({
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
title: this.props.notebook.title,
|
||||
@ -84,7 +84,7 @@ export class Settings extends Component {
|
||||
}
|
||||
};
|
||||
this.setState({ disabled: true, type: 'Deleting' });
|
||||
this.props.api.publishAction(action).then(() => {
|
||||
this.props.api.publish.publishAction(action).then(() => {
|
||||
this.props.history.push('/~publish');
|
||||
});
|
||||
}
|
||||
@ -108,7 +108,7 @@ export class Settings extends Component {
|
||||
disabled: true,
|
||||
type: 'Converting'
|
||||
}, (() => {
|
||||
this.props.api.publishAction({
|
||||
this.props.api.publish.publishAction({
|
||||
groupify: {
|
||||
book: props.book,
|
||||
target: state.targetGroup,
|
||||
@ -253,7 +253,7 @@ export class Settings extends Component {
|
||||
disabled={this.state.disabled}
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
this.props.api
|
||||
this.props.api.publish
|
||||
.publishAction({
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
@ -280,7 +280,7 @@ export class Settings extends Component {
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
this.props.api
|
||||
this.props.api.publish
|
||||
.publishAction({
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
|
@ -2,23 +2,11 @@ import React, { Component } from 'react';
|
||||
|
||||
export class SidebarInvite extends Component {
|
||||
onAccept() {
|
||||
const action = {
|
||||
accept: {
|
||||
path: '/publish',
|
||||
uid: this.props.uid
|
||||
}
|
||||
};
|
||||
this.props.api.inviteAction(action);
|
||||
this.props.api.invite.accept('/publish', this.props.uid);
|
||||
}
|
||||
|
||||
onDecline() {
|
||||
const action = {
|
||||
decline: {
|
||||
path: '/publish',
|
||||
uid: this.props.uid
|
||||
}
|
||||
};
|
||||
this.props.api.inviteAction(action);
|
||||
this.props.api.invite.decline('/publish', this.props.uid);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -11,23 +11,11 @@ export class Subscribers extends Component {
|
||||
}
|
||||
|
||||
addUser(who, path) {
|
||||
const action = {
|
||||
add: {
|
||||
members: [who],
|
||||
path: path
|
||||
}
|
||||
};
|
||||
this.props.api.groupAction(action);
|
||||
this.props.api.groups.add(path, [who]);
|
||||
}
|
||||
|
||||
removeUser(who, path) {
|
||||
const action = {
|
||||
remove: {
|
||||
members: [who],
|
||||
path: path
|
||||
}
|
||||
};
|
||||
this.props.api.groupAction(action);
|
||||
this.props.api.groups.remove(path, [who]);
|
||||
}
|
||||
|
||||
redirect(url) {
|
||||
|
@ -26,7 +26,7 @@ export default class GroupFilter extends Component {
|
||||
const selected = localStorage.getItem('urbit-selectedGroups');
|
||||
if (selected) {
|
||||
this.setState({ selected: JSON.parse(selected) }, (() => {
|
||||
this.props.api.setSelected(this.state.selected);
|
||||
this.props.api.local.setSelected(this.state.selected);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export class SidebarSwitcher extends Component {
|
||||
<a
|
||||
className='pointer flex-shrink-0'
|
||||
onClick={() => {
|
||||
this.props.api.sidebarToggle();
|
||||
this.props.api.local.sidebarToggle();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
|
@ -1,8 +1,14 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { ChatUpdate } from '../types/chat-update';
|
||||
import { ChatHookUpdate } from '../types/chat-hook-update';
|
||||
|
||||
export default class ChatReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'chat-update', false);
|
||||
type ChatState = Pick<StoreState, 'chatInitialized' | 'chatSynced' | 'inbox' | 'pendingMessages'>;
|
||||
|
||||
export default class ChatReducer<S extends ChatState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json['chat-update'];
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
this.pending(data, state);
|
||||
@ -13,13 +19,13 @@ export default class ChatReducer {
|
||||
this.delete(data, state);
|
||||
}
|
||||
|
||||
data = _.get(json, 'chat-hook-update', false);
|
||||
if (data) {
|
||||
this.hook(data, state);
|
||||
const hookUpdate = json['chat-hook-update'];
|
||||
if (hookUpdate) {
|
||||
this.hook(hookUpdate, state);
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.inbox = data;
|
||||
@ -27,11 +33,11 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
hook(json, state) {
|
||||
hook(json: ChatHookUpdate, state: S) {
|
||||
state.chatSynced = json;
|
||||
}
|
||||
|
||||
message(json, state) {
|
||||
message(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'message', false);
|
||||
if (data) {
|
||||
state.inbox[data.path].envelopes.unshift(data.envelope);
|
||||
@ -40,7 +46,7 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
messages(json, state) {
|
||||
messages(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'messages', false);
|
||||
if (data) {
|
||||
state.inbox[data.path].envelopes =
|
||||
@ -48,7 +54,7 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
read(json, state) {
|
||||
read(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'read', false);
|
||||
if (data) {
|
||||
state.inbox[data.path].config.read =
|
||||
@ -56,7 +62,7 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.inbox[data.path] = {
|
||||
@ -69,25 +75,25 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.inbox[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
pending(json, state) {
|
||||
pending(json: ChatUpdate, state: S) {
|
||||
const msg = _.get(json, 'message', false);
|
||||
if (!msg || !state.pendingMessages.has(msg.path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mailbox = state.pendingMessages.get(msg.path);
|
||||
const mailbox = state.pendingMessages.get(msg.path) || [];
|
||||
|
||||
for (const pendingMsg of mailbox) {
|
||||
if (msg.envelope.uid === pendingMsg.uid) {
|
||||
const index = mailbox.indexOf(pendingMsg);
|
||||
state.pendingMessages.get(msg.path).splice(index, 1);
|
||||
mailbox.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { ContactUpdate } from '../types/contact-update';
|
||||
|
||||
export default class ContactReducer {
|
||||
reduce(json, state) {
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
||||
export default class ContactReducer<S extends ContactState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'contact-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -13,28 +18,28 @@ export default class ContactReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.contacts = data;
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.contacts[data.path] = {};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.contacts[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (
|
||||
data &&
|
||||
@ -44,7 +49,7 @@ export default class ContactReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (
|
||||
data &&
|
||||
@ -55,7 +60,7 @@ export default class ContactReducer {
|
||||
}
|
||||
}
|
||||
|
||||
edit(json, state) {
|
||||
edit(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'edit', false);
|
||||
if (
|
||||
data &&
|
@ -1,8 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { GroupUpdate } from '../types/group-update';
|
||||
|
||||
export default class GroupReducer {
|
||||
type GroupState = Pick<StoreState, 'groups' | 'groupKeys'>;
|
||||
|
||||
reduce(json, state) {
|
||||
export default class GroupReducer<S extends GroupState> {
|
||||
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, "group-update", false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -15,7 +20,7 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
for (let group in data) {
|
||||
@ -24,7 +29,7 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
for (const member of data.members) {
|
||||
@ -33,7 +38,7 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
for (const member of data.members) {
|
||||
@ -42,21 +47,21 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
bundle(json, state) {
|
||||
bundle(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'bundle', false);
|
||||
if (data) {
|
||||
state.groups[data.path] = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
unbundle(json, state) {
|
||||
unbundle(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'unbundle', false);
|
||||
if (data) {
|
||||
delete state.groups[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
keys(json, state) {
|
||||
keys(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'keys', false);
|
||||
if (data) {
|
||||
state.groupKeys = new Set(data.keys);
|
@ -1,10 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { InviteUpdate } from '../types/invite-update';
|
||||
|
||||
export default class InviteReducer {
|
||||
reduce(json, state) {
|
||||
const data = _.get(json, 'invite-update', false);
|
||||
type InviteState = Pick<StoreState, "invites">;
|
||||
|
||||
|
||||
export default class InviteReducer<S extends InviteState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json['invite-update'];
|
||||
if (data) {
|
||||
console.log(data);
|
||||
this.initial(data, state);
|
||||
this.create(data, state);
|
||||
this.delete(data, state);
|
||||
@ -14,35 +19,35 @@ export default class InviteReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.invites = data;
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.invites[data.path] = {};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.invites[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
invite(json, state) {
|
||||
invite(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'invite', false);
|
||||
if (data) {
|
||||
state.invites[data.path][data.uid] = data.invite;
|
||||
}
|
||||
}
|
||||
|
||||
accepted(json, state) {
|
||||
accepted(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'accepted', false);
|
||||
if (data) {
|
||||
console.log(data);
|
||||
@ -50,7 +55,7 @@ export default class InviteReducer {
|
||||
}
|
||||
}
|
||||
|
||||
decline(json, state) {
|
||||
decline(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'decline', false);
|
||||
if (data) {
|
||||
delete state.invites[data.path][data.uid];
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { LaunchUpdate } from '../types/launch-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
export default class LaunchReducer {
|
||||
reduce(json, state) {
|
||||
type LaunchState = Pick<StoreState, 'launch' | 'weather' | 'userLocation'>;
|
||||
|
||||
export default class LaunchReducer<S extends LaunchState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'launch-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -18,32 +23,32 @@ export default class LaunchReducer {
|
||||
|
||||
const locationData = _.get(json, 'location', false);
|
||||
if (locationData) {
|
||||
state.location = locationData;
|
||||
state.userLocation = locationData;
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.launch = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeFirstTime(json, state) {
|
||||
changeFirstTime(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeFirstTime', false);
|
||||
if (data) {
|
||||
state.launch.firstTime = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeOrder(json, state) {
|
||||
changeOrder(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeOrder', false);
|
||||
if (data) {
|
||||
state.launch.tileOrdering = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeIsShown(json, state) {
|
||||
changeIsShown(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeIsShown', false);
|
||||
console.log(json, data);
|
||||
if (data) {
|
@ -1,20 +1,28 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { LinkUpdate, Pagination } from '../types/link-update';
|
||||
|
||||
// page size as expected from link-view.
|
||||
// must change in parallel with the +page-size in /app/link-view to
|
||||
// ensure sane behavior.
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
export default class LinkUpdateReducer {
|
||||
reduce(json, state) {
|
||||
this.submissionsPage(json, state);
|
||||
this.submissionsUpdate(json, state);
|
||||
this.discussionsPage(json, state);
|
||||
this.discussionsUpdate(json, state);
|
||||
this.observationUpdate(json, state);
|
||||
type LinkState = Pick<StoreState, 'linksSeen' | 'links' | 'linkListening' | 'linkComments'>;
|
||||
|
||||
export default class LinkUpdateReducer<S extends LinkState> {
|
||||
reduce(json: any, state: S) {
|
||||
const data = _.get(json, 'link-update', false);
|
||||
if(data) {
|
||||
this.submissionsPage(data, state);
|
||||
this.submissionsUpdate(data, state);
|
||||
this.discussionsPage(data, state);
|
||||
this.discussionsUpdate(data, state);
|
||||
this.observationUpdate(data, state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
submissionsPage(json, state) {
|
||||
submissionsPage(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'initial-submissions', false);
|
||||
if (data) {
|
||||
// { "initial-submissions": {
|
||||
@ -32,7 +40,12 @@ export default class LinkUpdateReducer {
|
||||
|
||||
// if we didn't have any state for this path yet, initialize.
|
||||
if (!state.links[path]) {
|
||||
state.links[path] = { local: {} };
|
||||
state.links[path] = {
|
||||
local: {},
|
||||
totalItems: here.totalItems,
|
||||
totalPages: here.totalPages,
|
||||
unseenCount: here.unseenCount
|
||||
};
|
||||
}
|
||||
|
||||
// since data contains an up-to-date full version of the page,
|
||||
@ -47,17 +60,17 @@ export default class LinkUpdateReducer {
|
||||
|
||||
// write seen status to a separate structure,
|
||||
// for easier modification later.
|
||||
if (!state.seen[path]) {
|
||||
state.seen[path] = {};
|
||||
if (!state.linksSeen[path]) {
|
||||
state.linksSeen[path] = {};
|
||||
}
|
||||
(here.page || []).map((submission) => {
|
||||
state.seen[path][submission.url] = submission.seen;
|
||||
state.linksSeen[path][submission.url] = submission.seen;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submissionsUpdate(json, state) {
|
||||
submissionsUpdate(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'submissions', false);
|
||||
if (data) {
|
||||
// { "submissions": {
|
||||
@ -70,7 +83,7 @@ export default class LinkUpdateReducer {
|
||||
// stub in a comment count, which is more or less guaranteed to be 0
|
||||
data.pages = data.pages.map((submission) => {
|
||||
submission.commentCount = 0;
|
||||
state.seen[path][submission.url] = false;
|
||||
state.linksSeen[path][submission.url] = false;
|
||||
return submission;
|
||||
});
|
||||
|
||||
@ -83,7 +96,7 @@ export default class LinkUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
discussionsPage(json, state) {
|
||||
discussionsPage(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'initial-discussions', false);
|
||||
if (data) {
|
||||
// { "initial-discussions": {
|
||||
@ -100,13 +113,17 @@ export default class LinkUpdateReducer {
|
||||
const page = data.pageNumber;
|
||||
|
||||
// if we didn't have any state for this path yet, initialize.
|
||||
if (!state.comments[path]) {
|
||||
state.comments[path] = {};
|
||||
if (!state.linkComments[path]) {
|
||||
state.linkComments[path] = {};
|
||||
}
|
||||
if (!state.comments[path][url]) {
|
||||
state.comments[path][url] = { local: {} };
|
||||
}
|
||||
const here = state.comments[path][url];
|
||||
let comments = {...{
|
||||
local: {},
|
||||
totalPages: data.totalPages,
|
||||
totalItems: data.totalItems
|
||||
}, ...state.linkComments[path][url] };
|
||||
|
||||
state.linkComments[path][url] = comments;
|
||||
const here = state.linkComments[path][url];
|
||||
|
||||
// since data contains an up-to-date full version of the page,
|
||||
// we can safely overwrite the one in state.
|
||||
@ -117,7 +134,7 @@ export default class LinkUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
discussionsUpdate(json, state) {
|
||||
discussionsUpdate(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'discussions', false);
|
||||
if (data) {
|
||||
// { "discussions": {
|
||||
@ -130,13 +147,13 @@ export default class LinkUpdateReducer {
|
||||
const url = data.url;
|
||||
|
||||
// add new comments to state, update totals
|
||||
state.comments[path][url] = this._addNewItems(
|
||||
data.comments, state.comments[path][url]
|
||||
state.linkComments[path][url] = this._addNewItems(
|
||||
data.comments || [], state.linkComments[path][url]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
observationUpdate(json, state) {
|
||||
observationUpdate(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'observation', false);
|
||||
if (data) {
|
||||
// { "observation": {
|
||||
@ -145,10 +162,10 @@ export default class LinkUpdateReducer {
|
||||
// } }
|
||||
|
||||
const path = data.path;
|
||||
if (!state.seen[path]) {
|
||||
state.seen[path] = {};
|
||||
if (!state.linksSeen[path]) {
|
||||
state.linksSeen[path] = {};
|
||||
}
|
||||
const seen = state.seen[path];
|
||||
const seen = state.linksSeen[path];
|
||||
|
||||
// mark urls as seen
|
||||
data.urls.map((url) => {
|
||||
@ -163,7 +180,7 @@ export default class LinkUpdateReducer {
|
||||
|
||||
//
|
||||
|
||||
_addNewItems(items, pages, page = 0) {
|
||||
_addNewItems<S extends { time: number }>(items: S[], pages: Pagination<S>, page = 0) {
|
||||
if (!pages) {
|
||||
pages = {
|
||||
local: {},
|
@ -1,34 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class ListenUpdateReducer {
|
||||
reduce(json, state) {
|
||||
const data = _.get(json, 'link-listen-update', false);
|
||||
if (data) {
|
||||
this.listening(data, state);
|
||||
this.watch(data, state);
|
||||
this.leave(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
listening(json, state) {
|
||||
const data = _.get(json, 'listening', false);
|
||||
if (data) {
|
||||
state.listening = new Set(data);
|
||||
}
|
||||
}
|
||||
|
||||
watch(json, state) {
|
||||
const data = _.get(json, 'watch', false);
|
||||
if (data) {
|
||||
state.listening.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
leave(json, state) {
|
||||
const data = _.get(json, 'leave', false);
|
||||
if (data) {
|
||||
state.listening.delete(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
pkg/interface/src/reducers/listen-update.ts
Normal file
39
pkg/interface/src/reducers/listen-update.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { LinkListenUpdate } from '../types/link-listen-update';
|
||||
|
||||
type LinkListenState = Pick<StoreState, 'linkListening'>;
|
||||
|
||||
export default class LinkListenReducer<S extends LinkListenState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'link-listen-update', false);
|
||||
if (data) {
|
||||
this.listening(data, state);
|
||||
this.watch(data, state);
|
||||
this.leave(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
listening(json: LinkListenUpdate, state: S) {
|
||||
const data = _.get(json, 'listening', false);
|
||||
if (data) {
|
||||
state.linkListening = new Set(data);
|
||||
}
|
||||
}
|
||||
|
||||
watch(json: LinkListenUpdate, state: S) {
|
||||
const data = _.get(json, 'watch', false);
|
||||
if (data) {
|
||||
state.linkListening.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
leave(json: LinkListenUpdate, state: S) {
|
||||
const data = _.get(json, 'leave', false);
|
||||
if (data) {
|
||||
state.linkListening.delete(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class LocalReducer {
|
||||
reduce(json, state) {
|
||||
const data = _.get(json, 'local', false);
|
||||
if (data) {
|
||||
this.sidebarToggle(data, state);
|
||||
this.setSelected(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
sidebarToggle(obj, state) {
|
||||
const data = _.has(obj, 'sidebarToggle', false);
|
||||
if (data) {
|
||||
state.sidebarShown = obj.sidebarToggle;
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(obj, state) {
|
||||
const data = _.has(obj, 'selected', false);
|
||||
if (data) {
|
||||
state.selectedGroups = obj.selected;
|
||||
}
|
||||
}
|
||||
}
|
28
pkg/interface/src/reducers/local.ts
Normal file
28
pkg/interface/src/reducers/local.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { LocalUpdate } from '../types/local-update';
|
||||
|
||||
type LocalState = Pick<StoreState, 'sidebarShown' | 'selectedGroups'>;
|
||||
|
||||
export default class LocalReducer<S extends LocalState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json['local'];
|
||||
if (data) {
|
||||
this.sidebarToggle(data, state);
|
||||
this.setSelected(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
sidebarToggle(obj: LocalUpdate, state: S) {
|
||||
if ('sidebarToggle' in obj) {
|
||||
state.sidebarShown = !state.sidebarShown;
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(obj: LocalUpdate, state: S) {
|
||||
if ('selected' in obj) {
|
||||
state.selectedGroups = obj.selected;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class MetadataReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'metadata-update', false);
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
import { MetadataUpdate } from '../types/metadata-update';
|
||||
import { Cage } from '../types/cage';
|
||||
|
||||
type MetadataState = Pick<StoreState, 'associations'>;
|
||||
|
||||
export default class MetadataReducer<S extends MetadataState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
let data = json['metadata-update']
|
||||
if (data) {
|
||||
console.log('data: ', data);
|
||||
this.associations(data, state);
|
||||
@ -13,7 +20,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
associations(json, state) {
|
||||
associations(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
@ -34,7 +41,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
@ -53,7 +60,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
update(json, state) {
|
||||
update(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
@ -72,7 +79,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { PermissionUpdate } from '../types/permission-update';
|
||||
|
||||
export default class PermissionReducer {
|
||||
reduce(json, state) {
|
||||
type PermissionState = Pick<StoreState, "permissions">;
|
||||
|
||||
export default class PermissionReducer<S extends PermissionState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'permission-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -12,7 +17,7 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
for (const perm in data) {
|
||||
@ -24,7 +29,7 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.permissions[data.path] = {
|
||||
@ -34,14 +39,14 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.permissions[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
for (const member of data.who) {
|
||||
@ -50,7 +55,7 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
for (const member of data.who) {
|
@ -1,7 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
|
||||
export default class PublishResponseReducer {
|
||||
reduce(json, state) {
|
||||
type PublishState = Pick<StoreState, 'notebooks'>;
|
||||
|
||||
export default class PublishResponseReducer<S extends PublishState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'publish-response', false);
|
||||
if (!data) { return; }
|
||||
switch(data.type) {
|
||||
@ -194,12 +198,4 @@ export default class PublishResponseReducer {
|
||||
throw Error("tried to fetch paginated comments, but we don't have the note");
|
||||
}
|
||||
}
|
||||
|
||||
sidebarToggle(json, state) {
|
||||
let data = _.has(json.data, 'sidebarToggle', false);
|
||||
if (data) {
|
||||
state.sidebarShown = json.data.sidebarToggle;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class PublishUpdateReducer {
|
||||
reduce(preJson, state){
|
||||
let json = _.get(preJson, "publish-update", false);
|
||||
switch(Object.keys(json)[0]){
|
||||
import { PublishUpdate } from '../types/publish-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../store/type';
|
||||
import { getTagFromFrond } from '../types/noun';
|
||||
|
||||
type PublishState = Pick<StoreState, 'notebooks'>;
|
||||
|
||||
|
||||
export default class PublishUpdateReducer<S extends PublishState> {
|
||||
reduce(data: Cage, state: S){
|
||||
let json = data["publish-update"];
|
||||
if(!json) {
|
||||
return;
|
||||
}
|
||||
const tag = getTagFromFrond(json);
|
||||
switch(tag){
|
||||
case "add-book":
|
||||
this.addBook(json["add-book"], state);
|
||||
break;
|
||||
@ -39,7 +51,7 @@ export default class PublishUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
addBook(json, state) {
|
||||
addBook(json, state: S) {
|
||||
let host = Object.keys(json)[0];
|
||||
let book = Object.keys(json[host])[0];
|
||||
if (state.notebooks[host]) {
|
||||
@ -49,7 +61,7 @@ export default class PublishUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
addNote(json, state) {
|
||||
addNote(json, state: S) {
|
||||
let host = Object.keys(json)[0];
|
||||
let book = Object.keys(json[host])[0];
|
||||
let noteId = json[host][book]["note-id"];
|
||||
@ -77,13 +89,13 @@ export default class PublishUpdateReducer {
|
||||
let prevNoteId = state.notebooks[host][book]["notes-by-date"][1] || null;
|
||||
state.notebooks[host][book].notes[noteId]["prev-note"] = prevNoteId
|
||||
state.notebooks[host][book].notes[noteId]["next-note"] = null;
|
||||
if (state.notebooks[host][book].notes[prevNoteId]) {
|
||||
if (prevNoteId && state.notebooks[host][book].notes[prevNoteId]) {
|
||||
state.notebooks[host][book].notes[prevNoteId]["next-note"] = noteId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addComment(json, state) {
|
||||
addComment(json, state: S) {
|
||||
let host = json.host
|
||||
let book = json.book
|
||||
let note = json.note
|
||||
@ -97,7 +109,7 @@ export default class PublishUpdateReducer {
|
||||
if (state.notebooks[host][book].notes[note].comments) {
|
||||
let limboCommentIdx =
|
||||
_.findIndex(state.notebooks[host][book].notes[note].comments, (o) => {
|
||||
let oldVal = o[Object.keys(o)[0]];
|
||||
let oldVal = o[getTagFromFrond(o)];
|
||||
let newVal = comment[Object.keys(comment)[0]];
|
||||
return (oldVal.pending &&
|
||||
(oldVal.author === newVal.author) &&
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user