mirror of
https://github.com/urbit/shrub.git
synced 2024-12-20 01:01:37 +03:00
hark: separate unread tracking from notifications
This commit is contained in:
parent
5d7fa0463c
commit
b7b4000986
@ -1,214 +1,21 @@
|
|||||||
:: hark-chat-hook: notifications for chat-store [landscape]
|
:: hark-chat-hook: notifications for chat-store [landscape]
|
||||||
::
|
::
|
||||||
/- store=hark-store, post, group-store, metadata-store, hook=hark-chat-hook
|
/+ default-agent
|
||||||
/+ resource, metadata, default-agent, dbug, chat-store, grpl=group
|
|
||||||
::
|
::
|
||||||
~% %hark-chat-hook-top ..is ~
|
|
||||||
|%
|
|
||||||
+$ card card:agent:gall
|
|
||||||
+$ versioned-state
|
|
||||||
$% state-0
|
|
||||||
==
|
|
||||||
::
|
|
||||||
+$ state-0
|
|
||||||
$: %0
|
|
||||||
watching=(set path)
|
|
||||||
mentions=_&
|
|
||||||
==
|
|
||||||
::
|
|
||||||
--
|
|
||||||
::
|
|
||||||
=| state-0
|
|
||||||
=* state -
|
|
||||||
::
|
|
||||||
=>
|
|
||||||
|_ =bowl:gall
|
|
||||||
::
|
|
||||||
++ give
|
|
||||||
|= [paths=(list path) =update:hook]
|
|
||||||
^- (list card)
|
|
||||||
[%give %fact paths hark-chat-hook-update+!>(update)]~
|
|
||||||
::
|
|
||||||
++ watch-chat
|
|
||||||
^- card
|
|
||||||
[%pass /chat %agent [our.bowl %chat-store] %watch /all]
|
|
||||||
--
|
|
||||||
%- agent:dbug
|
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
~% %hark-chat-hook-agent ..card ~
|
|
||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* this .
|
+* this .
|
||||||
ha ~(. +> bowl)
|
|
||||||
def ~(. (default-agent this %|) bowl)
|
def ~(. (default-agent this %|) bowl)
|
||||||
met ~(. metadata bowl)
|
++ on-init [~ this]
|
||||||
grp ~(. grpl bowl)
|
++ on-save !>(~)
|
||||||
::
|
|
||||||
++ on-init
|
|
||||||
:_ this
|
|
||||||
~[watch-chat:ha]
|
|
||||||
::
|
|
||||||
++ on-save !>(state)
|
|
||||||
++ on-load
|
++ on-load
|
||||||
|= old=vase
|
|= =vase
|
||||||
^- (quip card _this)
|
`this
|
||||||
:_ this(state !<(state-0 old))
|
++ on-arvo on-arvo:def
|
||||||
?: (~(has by wex.bowl) [/chat our.bowl %chat-store])
|
++ on-agent on-agent:def
|
||||||
~
|
++ on-poke on-poke:def
|
||||||
~[watch-chat:ha]
|
++ on-peek on-peek:def
|
||||||
::
|
++ on-watch on-watch:def
|
||||||
++ on-watch
|
|
||||||
|= =path
|
|
||||||
^- (quip card _this)
|
|
||||||
=^ cards state
|
|
||||||
?+ path (on-watch:def path)
|
|
||||||
::
|
|
||||||
[%updates ~]
|
|
||||||
:_ state
|
|
||||||
%+ give:ha ~
|
|
||||||
:* %initial
|
|
||||||
watching
|
|
||||||
==
|
|
||||||
==
|
|
||||||
[cards this]
|
|
||||||
::
|
|
||||||
++ on-poke
|
|
||||||
~/ %hark-chat-hook-poke
|
|
||||||
|= [=mark =vase]
|
|
||||||
^- (quip card _this)
|
|
||||||
|^
|
|
||||||
?> (team:title our.bowl src.bowl)
|
|
||||||
=^ cards state
|
|
||||||
?+ mark (on-poke:def mark vase)
|
|
||||||
%hark-chat-hook-action
|
|
||||||
(hark-chat-hook-action !<(action:hook vase))
|
|
||||||
==
|
|
||||||
[cards this]
|
|
||||||
::
|
|
||||||
++ hark-chat-hook-action
|
|
||||||
|= =action:hook
|
|
||||||
^- (quip card _state)
|
|
||||||
|^
|
|
||||||
:- (give:ha ~[/updates] action)
|
|
||||||
?- -.action
|
|
||||||
%listen (listen +.action)
|
|
||||||
%ignore (ignore +.action)
|
|
||||||
%set-mentions (set-mentions +.action)
|
|
||||||
==
|
|
||||||
++ listen
|
|
||||||
|= chat=path
|
|
||||||
^+ state
|
|
||||||
state(watching (~(put in watching) chat))
|
|
||||||
::
|
|
||||||
++ ignore
|
|
||||||
|= chat=path
|
|
||||||
^+ state
|
|
||||||
state(watching (~(del in watching) chat))
|
|
||||||
::
|
|
||||||
++ set-mentions
|
|
||||||
|= ment=?
|
|
||||||
^+ state
|
|
||||||
state(mentions ment)
|
|
||||||
--
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ on-agent
|
|
||||||
~/ %hark-chat-hook-agent
|
|
||||||
|= [=wire =sign:agent:gall]
|
|
||||||
^- (quip card _this)
|
|
||||||
|^
|
|
||||||
?+ -.sign (on-agent:def wire sign)
|
|
||||||
%kick
|
|
||||||
:_ this
|
|
||||||
?. ?=([%chat ~] wire)
|
|
||||||
~
|
|
||||||
~[watch-chat:ha]
|
|
||||||
::
|
|
||||||
%fact
|
|
||||||
?. ?=(%chat-update p.cage.sign)
|
|
||||||
(on-agent:def wire sign)
|
|
||||||
=^ cards state
|
|
||||||
(chat-update !<(update:chat-store q.cage.sign))
|
|
||||||
[cards this]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ chat-update
|
|
||||||
|= =update:chat-store
|
|
||||||
^- (quip card _state)
|
|
||||||
?+ -.update `state
|
|
||||||
%initial (process-initial +.update)
|
|
||||||
%create (process-new +.update)
|
|
||||||
::
|
|
||||||
%message
|
|
||||||
:_ state
|
|
||||||
(process-envelope path.update envelope.update)
|
|
||||||
::
|
|
||||||
%messages
|
|
||||||
:_ state
|
|
||||||
%- zing
|
|
||||||
(turn envelopes.update (cury process-envelope path.update))
|
|
||||||
==
|
|
||||||
++ process-initial
|
|
||||||
|= =inbox:chat-store
|
|
||||||
^- (quip card _state)
|
|
||||||
=/ keys=(list path)
|
|
||||||
~(tap in ~(key by inbox))
|
|
||||||
=| cards=(list card)
|
|
||||||
|-
|
|
||||||
?~ keys
|
|
||||||
[cards state]
|
|
||||||
=* path i.keys
|
|
||||||
=^ cs state
|
|
||||||
(process-new path)
|
|
||||||
$(cards (weld cards cs), keys t.keys)
|
|
||||||
::
|
|
||||||
++ process-new
|
|
||||||
|= chat=path
|
|
||||||
^- (quip card _state)
|
|
||||||
=/ groups=(list path)
|
|
||||||
(groups-from-resource:met %chat chat)
|
|
||||||
?~ groups
|
|
||||||
`state
|
|
||||||
?: (is-managed-path:grp i.groups)
|
|
||||||
`state
|
|
||||||
`state(watching (~(put in watching) chat))
|
|
||||||
::
|
|
||||||
++ is-mention
|
|
||||||
|= =envelope:chat-store
|
|
||||||
?. ?=(%text -.letter.envelope) %.n
|
|
||||||
?& mentions
|
|
||||||
?= ^
|
|
||||||
(find (scow %p our.bowl) (trip text.letter.envelope))
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ is-notification
|
|
||||||
|= [=path =envelope:chat-store]
|
|
||||||
?& (~(has in watching) path)
|
|
||||||
!=(author.envelope our.bowl)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ process-envelope
|
|
||||||
|= [=path =envelope:chat-store]
|
|
||||||
^- (list card)
|
|
||||||
=/ mention=?
|
|
||||||
(is-mention envelope)
|
|
||||||
?. ?|(mention (is-notification path envelope))
|
|
||||||
~
|
|
||||||
=/ =index:store
|
|
||||||
[%chat path mention]
|
|
||||||
=/ =contents:store
|
|
||||||
[%chat ~[envelope]]
|
|
||||||
~[(poke-store %add index when.envelope %.n contents)]
|
|
||||||
::
|
|
||||||
++ poke-store
|
|
||||||
|= =action:store
|
|
||||||
^- card
|
|
||||||
=- [%pass /store %agent [our.bowl %hark-store] %poke -]
|
|
||||||
hark-action+!>(action)
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ on-peek on-peek:def
|
|
||||||
::
|
|
||||||
++ on-leave on-leave:def
|
++ on-leave on-leave:def
|
||||||
++ on-arvo on-arvo:def
|
|
||||||
++ on-fail on-fail:def
|
++ on-fail on-fail:def
|
||||||
--
|
--
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
/- store=hark-store, post, group-store, metadata-store, hook=hark-graph-hook
|
/- store=hark-store, post, group-store, metadata-store, hook=hark-graph-hook
|
||||||
/+ resource, metadata, default-agent, dbug, graph-store
|
/+ resource, metadata, default-agent, dbug, graph-store
|
||||||
::
|
::
|
||||||
|
::
|
||||||
~% %hark-graph-hook-top ..is ~
|
~% %hark-graph-hook-top ..is ~
|
||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
@ -57,7 +58,10 @@
|
|||||||
++ on-load
|
++ on-load
|
||||||
|= old=vase
|
|= old=vase
|
||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
`this(state !<(state-0 old))
|
:_ this(state !<(state-0 old))
|
||||||
|
?: (~(has by wex.bowl) [/graph our.bowl %graph-store])
|
||||||
|
~
|
||||||
|
~[watch-graph:ha]
|
||||||
::
|
::
|
||||||
++ on-watch
|
++ on-watch
|
||||||
|= =path
|
|= =path
|
||||||
@ -204,12 +208,7 @@
|
|||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
=^ child-cards state
|
=^ child-cards state
|
||||||
(check-node-children node tube)
|
(check-node-children node tube)
|
||||||
?: =(our.bowl author.post.node)
|
=+ !< notif-kind=(unit [name=@t parent-lent=@ud mode=?(%each %since)])
|
||||||
=^ self-cards state
|
|
||||||
(self-post node)
|
|
||||||
:_ state
|
|
||||||
(weld child-cards self-cards)
|
|
||||||
=+ !< notif-kind=(unit [name=@t parent-lent=@ud])
|
|
||||||
(tube !>([0 post.node]))
|
(tube !>([0 post.node]))
|
||||||
?~ notif-kind
|
?~ notif-kind
|
||||||
[child-cards state]
|
[child-cards state]
|
||||||
@ -219,17 +218,25 @@
|
|||||||
name.u.notif-kind
|
name.u.notif-kind
|
||||||
=/ parent=index:post
|
=/ parent=index:post
|
||||||
(scag parent-lent.u.notif-kind index.post.node)
|
(scag parent-lent.u.notif-kind index.post.node)
|
||||||
|
=/ notif-index=index:store
|
||||||
|
[%graph group rid module.metadata desc parent]
|
||||||
|
?: =(our.bowl author.post.node)
|
||||||
|
=^ self-cards state
|
||||||
|
(self-post node notif-index mode.u.notif-kind)
|
||||||
|
:_ state
|
||||||
|
(weld child-cards self-cards)
|
||||||
|
:_ state
|
||||||
|
%+ weld child-cards
|
||||||
|
:- %^ update-unread-count
|
||||||
|
mode.u.notif-kind notif-index
|
||||||
|
[time-sent index]:post.node
|
||||||
?. ?| =(desc %mention)
|
?. ?| =(desc %mention)
|
||||||
(~(has in watching) [rid parent])
|
(~(has in watching) [rid parent])
|
||||||
==
|
==
|
||||||
[child-cards state]
|
~
|
||||||
=/ notif-index=index:store
|
|
||||||
[%graph group rid module.metadata desc]
|
|
||||||
=/ =contents:store
|
=/ =contents:store
|
||||||
[%graph (limo post.node ~)]
|
[%graph (limo post.node ~)]
|
||||||
:_ state
|
~[(add-unread notif-index [time-sent.post.node %.n contents])]
|
||||||
%+ snoc child-cards
|
|
||||||
(add-unread notif-index [time-sent.post.node %.n contents])
|
|
||||||
::
|
::
|
||||||
++ is-mention
|
++ is-mention
|
||||||
|= contents=(list content:post)
|
|= contents=(list content:post)
|
||||||
@ -243,17 +250,35 @@
|
|||||||
$(contents t.contents)
|
$(contents t.contents)
|
||||||
::
|
::
|
||||||
++ self-post
|
++ self-post
|
||||||
|= =node:graph-store
|
|= $: =node:graph-store
|
||||||
|
=index:store
|
||||||
|
mode=?(%since %each)
|
||||||
|
==
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
|
=| cards=(list card)
|
||||||
|
=? cards ?=(%since mode)
|
||||||
|
:_ cards
|
||||||
|
(poke-hark %read-since index index.post.node)
|
||||||
?. ?=(%.y watch-on-self)
|
?. ?=(%.y watch-on-self)
|
||||||
[~ state]
|
[cards state]
|
||||||
`state(watching (~(put in watching) [rid index.post.node]))
|
:- cards
|
||||||
|
state(watching (~(put in watching) [rid index.post.node]))
|
||||||
|
::
|
||||||
|
++ poke-hark
|
||||||
|
|= =action:store
|
||||||
|
^- card
|
||||||
|
=- [%pass / %agent [our.bowl %hark-store] %poke -]
|
||||||
|
hark-action+!>(action)
|
||||||
|
::
|
||||||
|
++ update-unread-count
|
||||||
|
|= [mode=?(%since %each) =index:store time=@da ref=index:graph-store]
|
||||||
|
?: ?=(%since mode)
|
||||||
|
(poke-hark %unread-since index time)
|
||||||
|
(poke-hark %unread-each index ref time)
|
||||||
::
|
::
|
||||||
++ add-unread
|
++ add-unread
|
||||||
|= [=index:store =notification:store]
|
|= [=index:store =notification:store]
|
||||||
^- card
|
(poke-hark %add-note index notification)
|
||||||
=- [%pass / %agent [our.bowl %hark-store] %poke -]
|
|
||||||
hark-action+!>([%add index notification])
|
|
||||||
::
|
::
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
|
@ -1,31 +1,47 @@
|
|||||||
:: hark-store: notifications [landscape]
|
:: hark-store: notifications and unread counts [landscape]
|
||||||
::
|
::
|
||||||
|
:: hark-store can store unread counts differently, depending on the
|
||||||
|
:: resource.
|
||||||
|
:: - last seen. This way, hark-store simply stores an index into
|
||||||
|
:: graph-store, which represents the last "seen" item, useful for
|
||||||
|
:: high-volume applications which are intrinsically time-ordered. i.e.
|
||||||
|
:: chats, comments
|
||||||
|
:: - each. Hark-store will store an index for each item that is unread.
|
||||||
|
:: Usefull for non-linear, low-volume applications, i.e. blogs,
|
||||||
|
:: collections
|
||||||
|
::
|
||||||
/- store=hark-store, post, group-store, metadata-store
|
/- store=hark-store, post, group-store, metadata-store
|
||||||
/+ resource, metadata, default-agent, dbug, graph-store
|
/+ resource, metadata, default-agent, dbug, graph-store
|
||||||
::
|
::
|
||||||
|
::
|
||||||
~% %hark-store-top ..is ~
|
~% %hark-store-top ..is ~
|
||||||
|%
|
|%
|
||||||
+$ card card:agent:gall
|
+$ card card:agent:gall
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-0
|
$% state:state-zero:store
|
||||||
|
state-1
|
||||||
==
|
==
|
||||||
|
+$ unread-stats
|
||||||
|
[indices=(set index:graph-store) last=@da]
|
||||||
::
|
::
|
||||||
+$ state-0
|
+$ state-1
|
||||||
$: %0
|
$: %1
|
||||||
|
unreads-each=(jug index:store index:graph-store)
|
||||||
|
unreads-since=(map index:store index:graph-store)
|
||||||
|
last-seen=(map index:store @da)
|
||||||
=notifications:store
|
=notifications:store
|
||||||
archive=notifications:store
|
archive=notifications:store
|
||||||
last-seen=@da
|
current-timebox=@da
|
||||||
dnd=_|
|
dnd=_|
|
||||||
==
|
==
|
||||||
+$ inflated-state
|
+$ inflated-state
|
||||||
$: state-0
|
$: state-1
|
||||||
cache
|
cache
|
||||||
==
|
==
|
||||||
:: $cache: useful to have precalculated, but can be derived from state
|
:: $cache: useful to have precalculated, but can be derived from state
|
||||||
:: albeit expensively
|
:: albeit expensively
|
||||||
+$ cache
|
+$ cache
|
||||||
$: unread-count=@ud
|
$: by-index=(jug index:store @da)
|
||||||
by-index=(jug index:store @da)
|
|
||||||
~
|
~
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -54,16 +70,26 @@
|
|||||||
|= =old=vase
|
|= =old=vase
|
||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
=/ old
|
=/ old
|
||||||
!<(state-0 old-vase)
|
!<(versioned-state old-vase)
|
||||||
=. notifications.old
|
=| cards=(list card)
|
||||||
(gas:orm *notifications:store (tap:orm notifications.old))
|
|-
|
||||||
=. archive.old
|
?- -.old
|
||||||
(gas:orm *notifications:store (tap:orm archive.old))
|
%1
|
||||||
`this(-.state old, +.state (inflate-cache old))
|
[cards this(+.state (inflate-cache:ha old), -.state old)]
|
||||||
|
::
|
||||||
|
%0
|
||||||
|
|
||||||
|
%_ $
|
||||||
|
::
|
||||||
|
old
|
||||||
|
*state-1
|
||||||
|
==
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-watch
|
++ on-watch
|
||||||
|= =path
|
|= =path
|
||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
|
?> (team:title [src our]:bowl)
|
||||||
|^
|
|^
|
||||||
?+ path (on-watch:def path)
|
?+ path (on-watch:def path)
|
||||||
::
|
::
|
||||||
@ -76,26 +102,41 @@
|
|||||||
^- update:store
|
^- update:store
|
||||||
:- %more
|
:- %more
|
||||||
^- (list update:store)
|
^- (list update:store)
|
||||||
:- unreads
|
:+ give-unreads
|
||||||
:+ [%set-dnd dnd]
|
[%set-dnd dnd]
|
||||||
[%count unread-count]
|
|
||||||
%+ weld
|
%+ weld
|
||||||
%+ turn
|
%+ turn
|
||||||
%+ scag 3
|
|
||||||
(tap-nonempty:ha archive)
|
(tap-nonempty:ha archive)
|
||||||
(timebox-update &)
|
(timebox-update &)
|
||||||
%+ turn
|
%+ turn
|
||||||
%+ scag 3
|
|
||||||
(tap-nonempty:ha notifications)
|
(tap-nonempty:ha notifications)
|
||||||
(timebox-update |)
|
(timebox-update |)
|
||||||
::
|
::
|
||||||
++ unreads
|
++ give-since-unreads
|
||||||
|
^- (list [index:store index-stats:store])
|
||||||
|
%+ turn
|
||||||
|
~(tap by unreads-since)
|
||||||
|
|= [=index:store since=index:graph-store]
|
||||||
|
:* index
|
||||||
|
~(wyt in (~(gut by by-index) index ~))
|
||||||
|
[%since since]
|
||||||
|
(~(gut by last-seen) index *time)
|
||||||
|
==
|
||||||
|
++ give-each-unreads
|
||||||
|
^- (list [index:store index-stats:store])
|
||||||
|
%+ turn
|
||||||
|
~(tap by unreads-each)
|
||||||
|
|= [=index:store indices=(set index:graph-store)]
|
||||||
|
:* index
|
||||||
|
~(wyt in (~(gut by by-index) index ~))
|
||||||
|
[%each indices]
|
||||||
|
(~(gut by last-seen) index *time)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ give-unreads
|
||||||
^- update:store
|
^- update:store
|
||||||
:- %unreads
|
:- %unreads
|
||||||
^- (list [index:store @ud])
|
(weld give-each-unreads give-since-unreads)
|
||||||
%+ turn
|
|
||||||
~(tap by by-index)
|
|
||||||
|=([=index:store =(set @da)] [index ~(wyt in set)])
|
|
||||||
::
|
::
|
||||||
++ timebox-update
|
++ timebox-update
|
||||||
|= archived=?
|
|= archived=?
|
||||||
@ -139,6 +180,7 @@
|
|||||||
=^ cards state
|
=^ cards state
|
||||||
?+ mark (on-poke:def mark vase)
|
?+ mark (on-poke:def mark vase)
|
||||||
%hark-action (hark-action !<(action:store vase))
|
%hark-action (hark-action !<(action:store vase))
|
||||||
|
%noun ~& +.state [~ state]
|
||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
@ -146,46 +188,44 @@
|
|||||||
|= =action:store
|
|= =action:store
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
|^
|
|^
|
||||||
?- -.action
|
?- -.action
|
||||||
%add (add +.action)
|
%add-note (add-note +.action)
|
||||||
%archive (do-archive +.action)
|
%archive (do-archive +.action)
|
||||||
%seen seen
|
::
|
||||||
%read (read +.action)
|
%read-each (read-each +.action)
|
||||||
%read-index (read-index +.action)
|
%unread-each (unread-each +.action)
|
||||||
%unread (unread +.action)
|
::
|
||||||
%set-dnd (set-dnd +.action)
|
%read-since (read-since +.action)
|
||||||
|
%unread-since (unread-since +.action)
|
||||||
|
::
|
||||||
|
%read-note (read-note +.action)
|
||||||
|
%unread-note (unread-note +.action)
|
||||||
|
::
|
||||||
|
%read-all read-all
|
||||||
|
::
|
||||||
|
%set-dnd (set-dnd +.action)
|
||||||
|
%seen seen
|
||||||
==
|
==
|
||||||
++ add
|
::
|
||||||
|
++ add-note
|
||||||
|= [=index:store =notification:store]
|
|= [=index:store =notification:store]
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
=/ =timebox:store
|
=/ =timebox:store
|
||||||
(gut-orm:ha notifications last-seen)
|
(gut-orm:ha notifications current-timebox)
|
||||||
=/ existing-notif
|
=/ existing-notif
|
||||||
(~(get by timebox) index)
|
(~(get by timebox) index)
|
||||||
=/ new=notification:store
|
=/ new=notification:store
|
||||||
?~ existing-notif
|
?~ existing-notif
|
||||||
notification
|
notification
|
||||||
(merge-notification:ha u.existing-notif notification)
|
(merge-notification:ha u.existing-notif notification)
|
||||||
|
=. read.new %.y
|
||||||
=/ new-timebox=timebox:store
|
=/ new-timebox=timebox:store
|
||||||
(~(put by timebox) index new)
|
(~(put by timebox) index new)
|
||||||
:- (give:ha [/updates]~ %added last-seen index new)
|
:- (give:ha [/updates]~ %added current-timebox index new)
|
||||||
%_ state
|
%_ state
|
||||||
+ ?~(existing-notif (upd-unreads:ha index last-seen %.n) +.state)
|
+ ?~(existing-notif (upd-unreads:ha index current-timebox %.n) +.state)
|
||||||
notifications (put:orm notifications last-seen new-timebox)
|
notifications (put:orm notifications current-timebox new-timebox)
|
||||||
==
|
==
|
||||||
++ read-index
|
|
||||||
|= =index:store
|
|
||||||
^- (quip card _state)
|
|
||||||
=/ times=(list @da)
|
|
||||||
~(tap in (~(gut by by-index) index ~))
|
|
||||||
=| cards=(list card)
|
|
||||||
|-
|
|
||||||
?~ times
|
|
||||||
[cards state]
|
|
||||||
=* time i.times
|
|
||||||
=^ crds state
|
|
||||||
(read time index)
|
|
||||||
$(cards (weld cards crds), times t.times)
|
|
||||||
::
|
::
|
||||||
++ do-archive
|
++ do-archive
|
||||||
|= [time=@da =index:store]
|
|= [time=@da =index:store]
|
||||||
@ -210,29 +250,118 @@
|
|||||||
(~(put by archive-box) index notification(read %.y))
|
(~(put by archive-box) index notification(read %.y))
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ read
|
++ unread-each
|
||||||
|
|= [=index:store unread=index:graph-store time=@da]
|
||||||
|
:- (give:ha ~[/updates] %unread-each index unread time)
|
||||||
|
%_ state
|
||||||
|
unreads-each
|
||||||
|
%+ jub index
|
||||||
|
|= indices=(set index:graph-store)
|
||||||
|
(~(put in indices) unread)
|
||||||
|
::
|
||||||
|
last-seen
|
||||||
|
(~(put by last-seen) index time)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ jub
|
||||||
|
|= [=index:store f=$-((set index:graph-store) (set index:graph-store))]
|
||||||
|
^- (jug index:store index:graph-store)
|
||||||
|
=/ val=(set index:graph-store)
|
||||||
|
(~(gut by unreads-each) index ~)
|
||||||
|
(~(put by unreads-each) index (f val))
|
||||||
|
::
|
||||||
|
++ read-each
|
||||||
|
|= [=index:store ref=index:graph-store]
|
||||||
|
=/ to-dismiss=(list @da)
|
||||||
|
%+ skim
|
||||||
|
~(tap in (~(get ju by-index) index))
|
||||||
|
|= time=@da
|
||||||
|
=/ =timebox:store
|
||||||
|
(gut-orm notifications time)
|
||||||
|
=/ not=(unit notification:store)
|
||||||
|
(~(get by timebox) index)
|
||||||
|
?~ not %.n
|
||||||
|
?> ?=(%graph -.contents.u.not)
|
||||||
|
(lien list.contents.u.not |=(p=post:post =(index.p ref)))
|
||||||
|
=| cards=(list card)
|
||||||
|
|-
|
||||||
|
?^ to-dismiss
|
||||||
|
=^ crds state
|
||||||
|
(read-note i.to-dismiss index)
|
||||||
|
$(cards (weld cards crds), to-dismiss t.to-dismiss)
|
||||||
|
:- (weld cards (give:ha ~[/updates] %read-each index ref))
|
||||||
|
%_ state
|
||||||
|
::
|
||||||
|
unreads-each
|
||||||
|
%+ jub index
|
||||||
|
|= indices=(set index:graph-store)
|
||||||
|
(~(del in indices) ref)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ read-note
|
||||||
|= [time=@da =index:store]
|
|= [time=@da =index:store]
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:- (give:ha [/updates]~ %read time index)
|
:- (give:ha [/updates]~ %read time index)
|
||||||
%_ state
|
%_ state
|
||||||
+ (upd-unreads:ha index time %.y)
|
+ (upd-unreads:ha index time %.y)
|
||||||
unread-count (dec unread-count)
|
|
||||||
notifications (change-read-status:ha time index %.y)
|
notifications (change-read-status:ha time index %.y)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ unread
|
++ unread-note
|
||||||
|= [time=@da =index:store]
|
|= [time=@da =index:store]
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:- (give:ha [/updates]~ %unread time index)
|
:- (give:ha [/updates]~ %unread-note time index)
|
||||||
%_ state
|
%_ state
|
||||||
+ (upd-unreads:ha index time %.n)
|
+ (upd-unreads:ha index time %.n)
|
||||||
unread-count +(unread-count)
|
|
||||||
notifications (change-read-status:ha time index %.n)
|
notifications (change-read-status:ha time index %.n)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
++ read-since
|
||||||
|
|= [=index:store since=index:graph-store]
|
||||||
|
^- (quip card _state)
|
||||||
|
=^ cards state
|
||||||
|
(read-index index)
|
||||||
|
:- %+ weld cards
|
||||||
|
(give:ha [/updates]~ %read-since index since)
|
||||||
|
%_ state
|
||||||
|
unreads-since (~(put by unreads-since) index since)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ read-boxes
|
||||||
|
|= [boxes=(set @da) =index:store]
|
||||||
|
^+ state
|
||||||
|
=/ boxes=(list @da)
|
||||||
|
~(tap in boxes)
|
||||||
|
|-
|
||||||
|
?~ boxes state
|
||||||
|
=* box i.boxes
|
||||||
|
=^ cards state
|
||||||
|
(read-note box index)
|
||||||
|
$(boxes t.boxes)
|
||||||
|
::
|
||||||
|
++ read-index
|
||||||
|
|= =index:store
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ boxes=(set @da)
|
||||||
|
(~(get ju by-index) index)
|
||||||
|
:- (give:ha ~[/updates] %read-index index)
|
||||||
|
(read-boxes boxes index)
|
||||||
|
::
|
||||||
|
++ read-all
|
||||||
|
^- (quip card _state)
|
||||||
|
`state
|
||||||
|
::
|
||||||
|
++ unread-since
|
||||||
|
|= [=index:store time=@da]
|
||||||
|
^- (quip card _state)
|
||||||
|
:- (give:ha [/updates]~ %unread-since index time)
|
||||||
|
%_ state
|
||||||
|
last-seen (~(put by last-seen) index time)
|
||||||
|
==
|
||||||
|
::
|
||||||
++ seen
|
++ seen
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
:_ state(last-seen now.bowl)
|
:_ state(current-timebox now.bowl)
|
||||||
:~ cancel-autoseen:ha
|
:~ cancel-autoseen:ha
|
||||||
autoseen-timer:ha
|
autoseen-timer:ha
|
||||||
==
|
==
|
||||||
@ -254,7 +383,7 @@
|
|||||||
?. ?=([%autoseen ~] wire)
|
?. ?=([%autoseen ~] wire)
|
||||||
(on-arvo:def wire sign-arvo)
|
(on-arvo:def wire sign-arvo)
|
||||||
?> ?=([%b %wake *] sign-arvo)
|
?> ?=([%b %wake *] sign-arvo)
|
||||||
:_ this(last-seen now.bowl)
|
:_ this(current-timebox now.bowl)
|
||||||
~[autoseen-timer:ha]
|
~[autoseen-timer:ha]
|
||||||
::
|
::
|
||||||
++ on-fail on-fail:def
|
++ on-fail on-fail:def
|
||||||
@ -262,12 +391,6 @@
|
|||||||
|_ =bowl:gall
|
|_ =bowl:gall
|
||||||
+* met ~(. metadata bowl)
|
+* met ~(. metadata bowl)
|
||||||
::
|
::
|
||||||
++ tap-nonempty
|
|
||||||
|= =notifications:store
|
|
||||||
^- (list [@da timebox:store])
|
|
||||||
%+ skip (tap:orm notifications)
|
|
||||||
|=([@da =timebox:store] =(0 ~(wyt by timebox)))
|
|
||||||
::
|
|
||||||
++ merge-notification
|
++ merge-notification
|
||||||
|= [existing=notification:store new=notification:store]
|
|= [existing=notification:store new=notification:store]
|
||||||
^- notification:store
|
^- notification:store
|
||||||
@ -275,15 +398,15 @@
|
|||||||
::
|
::
|
||||||
%chat
|
%chat
|
||||||
?> ?=(%chat -.contents.new)
|
?> ?=(%chat -.contents.new)
|
||||||
existing(list.contents (weld list.contents.existing list.contents.new))
|
existing(read %.n, list.contents (weld list.contents.existing list.contents.new))
|
||||||
::
|
::
|
||||||
%graph
|
%graph
|
||||||
?> ?=(%graph -.contents.new)
|
?> ?=(%graph -.contents.new)
|
||||||
existing(list.contents (weld list.contents.existing list.contents.new))
|
existing(read %.n, list.contents (weld list.contents.existing list.contents.new))
|
||||||
::
|
::
|
||||||
%group
|
%group
|
||||||
?> ?=(%group -.contents.new)
|
?> ?=(%group -.contents.new)
|
||||||
existing(list.contents (weld list.contents.existing list.contents.new))
|
existing(read %.n, list.contents (weld list.contents.existing list.contents.new))
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ change-read-status
|
++ change-read-status
|
||||||
@ -318,12 +441,18 @@
|
|||||||
++ autoseen-interval ~h3
|
++ autoseen-interval ~h3
|
||||||
++ cancel-autoseen
|
++ cancel-autoseen
|
||||||
^- card
|
^- card
|
||||||
[%pass /autoseen %arvo %b %rest (add last-seen autoseen-interval)]
|
[%pass /autoseen %arvo %b %rest (add current-timebox autoseen-interval)]
|
||||||
::
|
::
|
||||||
++ autoseen-timer
|
++ autoseen-timer
|
||||||
^- card
|
^- card
|
||||||
[%pass /autoseen %arvo %b %wait (add now.bowl autoseen-interval)]
|
[%pass /autoseen %arvo %b %wait (add now.bowl autoseen-interval)]
|
||||||
::
|
::
|
||||||
|
++ scry
|
||||||
|
|* [=mold p=path]
|
||||||
|
?> ?=(^ p)
|
||||||
|
?> ?=(^ t.p)
|
||||||
|
.^(mold i.p (scot %p our.bowl) i.t.p (scot %da now.bowl) t.t.p)
|
||||||
|
::
|
||||||
++ give
|
++ give
|
||||||
|= [paths=(list path) update=update:store]
|
|= [paths=(list path) update=update:store]
|
||||||
^- (list card)
|
^- (list card)
|
||||||
@ -341,8 +470,29 @@
|
|||||||
~(put ju by-index)
|
~(put ju by-index)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
++ group-for-index
|
||||||
|
|= =index:store
|
||||||
|
^- (unit resource)
|
||||||
|
?. ?=(%graph -.index)
|
||||||
|
~
|
||||||
|
`group.index
|
||||||
|
::
|
||||||
|
++ give-dirtied-unreads
|
||||||
|
|= [=index:store =update:store]
|
||||||
|
^- (list card)
|
||||||
|
=/ group
|
||||||
|
(group-for-index index)
|
||||||
|
?~ group ~
|
||||||
|
(give ~[group+(en-path:resource u.group)] update)
|
||||||
|
::
|
||||||
|
++ tap-nonempty
|
||||||
|
|= =notifications:store
|
||||||
|
^- (list [@da timebox:store])
|
||||||
|
%+ skim (tap:orm notifications)
|
||||||
|
|=([@da =timebox:store] !=(~(wyt by timebox) 0))
|
||||||
|
::
|
||||||
++ inflate-cache
|
++ inflate-cache
|
||||||
|= state-0
|
|= state-1
|
||||||
^+ +.state
|
^+ +.state
|
||||||
=/ nots=(list [p=@da =timebox:store])
|
=/ nots=(list [p=@da =timebox:store])
|
||||||
(tap:orm notifications)
|
(tap:orm notifications)
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
graph+dejs-path:resource
|
graph+dejs-path:resource
|
||||||
module+so
|
module+so
|
||||||
description+so
|
description+so
|
||||||
|
index+(su ;~(pfix net (more net dem)))
|
||||||
==
|
==
|
||||||
:: parse date as @ud
|
:: parse date as @ud
|
||||||
:: TODO: move to zuse
|
:: TODO: move to zuse
|
||||||
@ -53,16 +54,23 @@
|
|||||||
|= jon=json
|
|= jon=json
|
||||||
[*^index *notification]
|
[*^index *notification]
|
||||||
::
|
::
|
||||||
|
++ read-graph-index
|
||||||
|
%- ot
|
||||||
|
:~ index+index
|
||||||
|
target+(su ;~(pfix net (more net dem)))
|
||||||
|
==
|
||||||
|
::
|
||||||
++ action
|
++ action
|
||||||
^- $-(json ^action)
|
^- $-(json ^action)
|
||||||
%- of
|
%- of
|
||||||
:~ seen+ul
|
:~ seen+ul
|
||||||
archive+notif-ref
|
archive+notif-ref
|
||||||
unread+notif-ref
|
unread-note+notif-ref
|
||||||
read+notif-ref
|
read-note+notif-ref
|
||||||
add+add
|
add-note+add
|
||||||
set-dnd+bo
|
set-dnd+bo
|
||||||
read-index+index
|
read-since+read-graph-index
|
||||||
|
read-each+read-graph-index
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
@ -79,25 +87,46 @@
|
|||||||
%timebox (timebox +.upd)
|
%timebox (timebox +.upd)
|
||||||
%set-dnd b+dnd.upd
|
%set-dnd b+dnd.upd
|
||||||
%count (numb count.upd)
|
%count (numb count.upd)
|
||||||
%unreads (unreads unreads.upd)
|
|
||||||
%more (more +.upd)
|
%more (more +.upd)
|
||||||
|
%read-each (read-graph +.upd)
|
||||||
|
%read-since (read-graph +.upd)
|
||||||
|
%unread-each (unread-each +.upd)
|
||||||
|
%unread-since (unread-since +.upd)
|
||||||
|
%unreads (unreads +.upd)
|
||||||
::
|
::
|
||||||
?(%archive %read %unread)
|
?(%archive %read-note %unread-note)
|
||||||
(notif-ref +.upd)
|
(notif-ref +.upd)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ unreads
|
++ unreads
|
||||||
|= l=(list [^index @ud])
|
|= l=(list [^index ^index-stats])
|
||||||
^- json
|
^- json
|
||||||
:- %a
|
:- %a
|
||||||
^- (list json)
|
^- (list json)
|
||||||
%+ turn l
|
%+ turn l
|
||||||
|= [idx=^index unread=@ud]
|
|= [idx=^index stats=^index-stats]
|
||||||
%- pairs
|
%- pairs
|
||||||
:~ unread+(numb unread)
|
:~ unread+(numb unread)
|
||||||
index+(index idx)
|
index+(index idx)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
++ unread
|
||||||
|
|= =^unreads
|
||||||
|
%+ frond
|
||||||
|
-.unreads
|
||||||
|
?- -.unreads
|
||||||
|
%since (index:enjs:graph-store index.unreads)
|
||||||
|
%each a+(turn ~(tap by indices.unreads) index:enjs:graph-store)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ index-stats
|
||||||
|
|= stats=^index-stats
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ unreads+(unread unreads.stats)
|
||||||
|
notifications+(numb notifications.stats)
|
||||||
|
last+(time last-seen.stats)
|
||||||
|
==
|
||||||
++ added
|
++ added
|
||||||
|= [tim=@da idx=^index not=^notification]
|
|= [tim=@da idx=^index not=^notification]
|
||||||
^- json
|
^- json
|
||||||
@ -139,13 +168,19 @@
|
|||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ graph-index
|
++ graph-index
|
||||||
|= [group=resource graph=resource module=@t description=@t]
|
|= $: group=resource
|
||||||
|
graph=resource
|
||||||
|
module=@t
|
||||||
|
description=@t
|
||||||
|
idx=index:graph-store
|
||||||
|
==
|
||||||
^- json
|
^- json
|
||||||
%- pairs
|
%- pairs
|
||||||
:~ group+s+(enjs-path:resource group)
|
:~ group+s+(enjs-path:resource group)
|
||||||
graph+s+(enjs-path:resource graph)
|
graph+s+(enjs-path:resource graph)
|
||||||
module+s+module
|
module+s+module
|
||||||
description+s+description
|
description+s+description
|
||||||
|
index+(index:enjs:graph-store idx)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ group-index
|
++ group-index
|
||||||
@ -221,6 +256,28 @@
|
|||||||
^- json
|
^- json
|
||||||
(indexed-notification index notification)
|
(indexed-notification index notification)
|
||||||
==
|
==
|
||||||
|
::
|
||||||
|
++ read-graph
|
||||||
|
|= [=^index target=index:graph-store]
|
||||||
|
%- pairs
|
||||||
|
:~ index+(^index index)
|
||||||
|
target+(index:enjs:graph-store target)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ unread-each
|
||||||
|
|= [=^index target=index:graph-store tim=@da]
|
||||||
|
%- pairs
|
||||||
|
:~ index+(^index index)
|
||||||
|
target+(index:enjs:graph-store target)
|
||||||
|
last+(time tim)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ unread-since
|
||||||
|
|= [=^index tim=@da]
|
||||||
|
%- pairs
|
||||||
|
:~ index+(^index index)
|
||||||
|
last+(time tim)
|
||||||
|
==
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
++ noun i
|
++ noun i
|
||||||
++ notification-kind
|
++ notification-kind
|
||||||
?+ index.p.i ~
|
?+ index.p.i ~
|
||||||
[@ ~] `[%link 0]
|
[@ ~] `[%link 0 %each]
|
||||||
[@ @ @ ~] `[%comment 1]
|
[@ @ @ ~] `[%comment 1 %since]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
++ grab
|
++ grab
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
::
|
::
|
||||||
++ notification-kind
|
++ notification-kind
|
||||||
?+ index.p.i ~
|
?+ index.p.i ~
|
||||||
[@ %1 @ ~] `[%note 0]
|
[@ %1 @ ~] `[%note 0 %each]
|
||||||
[@ %2 @ @ ~] `[%comment 1]
|
[@ %2 @ @ ~] `[%comment 1 %since]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
++ grab
|
++ grab
|
||||||
|
@ -1,8 +1,37 @@
|
|||||||
/- *resource, graph-store, post, group-store, metadata-store, chat-store
|
/- chat-store, graph-store, post, *resource, group-store, metadata-store
|
||||||
^?
|
^?
|
||||||
|%
|
|%
|
||||||
|
++ state-zero
|
||||||
|
|%
|
||||||
|
+$ state
|
||||||
|
$: %0
|
||||||
|
notifications=notifications
|
||||||
|
archive=notifications
|
||||||
|
last-seen=@da
|
||||||
|
dnd=_|
|
||||||
|
==
|
||||||
|
::
|
||||||
|
+$ notifications
|
||||||
|
((mop @da timebox) gth)
|
||||||
|
::
|
||||||
|
+$ timebox
|
||||||
|
(map index notification)
|
||||||
|
::
|
||||||
|
+$ index
|
||||||
|
$% [%graph group=resource graph=resource module=@t description=@t]
|
||||||
|
[%group group=resource description=@t]
|
||||||
|
[%chat chat=path mention=?]
|
||||||
|
==
|
||||||
|
--
|
||||||
|
::
|
||||||
+$ index
|
+$ index
|
||||||
$% [%graph group=resource graph=resource module=@t description=@t]
|
$% $: %graph
|
||||||
|
group=resource
|
||||||
|
graph=resource
|
||||||
|
module=@t
|
||||||
|
description=@t
|
||||||
|
=index:graph-store
|
||||||
|
==
|
||||||
[%group group=resource description=@t]
|
[%group group=resource description=@t]
|
||||||
[%chat chat=path mention=?]
|
[%chat chat=path mention=?]
|
||||||
==
|
==
|
||||||
@ -29,24 +58,42 @@
|
|||||||
((mop @da timebox) gth)
|
((mop @da timebox) gth)
|
||||||
::
|
::
|
||||||
+$ action
|
+$ action
|
||||||
$% [%add =index =notification]
|
$% [%add-note =index =notification]
|
||||||
[%archive time=@da index]
|
[%archive time=@da index]
|
||||||
[%read time=@da index]
|
::
|
||||||
[%read-index index]
|
[%unread-since =index time=@da]
|
||||||
[%unread time=@da index]
|
[%read-since =index =index:graph-store]
|
||||||
|
::
|
||||||
|
[%unread-each =index ref=index:graph-store time=@da]
|
||||||
|
[%read-each index ref=index:graph-store]
|
||||||
|
::
|
||||||
|
[%read-note time=@da index]
|
||||||
|
[%unread-note time=@da index]
|
||||||
|
::
|
||||||
|
[%read-all ~]
|
||||||
[%set-dnd dnd=?]
|
[%set-dnd dnd=?]
|
||||||
[%seen ~]
|
[%seen ~]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ indexed-notification
|
+$ indexed-notification
|
||||||
[index notification]
|
[index notification]
|
||||||
|
::
|
||||||
|
+$ index-stats
|
||||||
|
[notifications=@ud =unreads last-seen=@da]
|
||||||
|
::
|
||||||
|
+$ unreads
|
||||||
|
$% [%since =index:graph-store]
|
||||||
|
[%each indices=(set index:graph-store)]
|
||||||
|
==
|
||||||
::
|
::
|
||||||
+$ update
|
+$ update
|
||||||
$% action
|
$% action
|
||||||
[%more more=(list update)]
|
[%more more=(list update)]
|
||||||
[%added time=@da =index =notification]
|
[%added time=@da =index =notification]
|
||||||
|
[%read-index =index]
|
||||||
|
[%read time=@da =index]
|
||||||
[%timebox time=@da archived=? =(list [index notification])]
|
[%timebox time=@da archived=? =(list [index notification])]
|
||||||
[%count count=@ud]
|
[%count count=@ud]
|
||||||
[%unreads unreads=(list [index @ud])]
|
[%unreads unreads=(list [index index-stats])]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import BaseApi from "./base";
|
import BaseApi from "./base";
|
||||||
import { StoreState } from "../store/type";
|
import { StoreState } from "../store/type";
|
||||||
import { dateToDa, decToUd } from "../lib/util";
|
import { dateToDa, decToUd } from "../lib/util";
|
||||||
import {NotifIndex, IndexedNotification} from "~/types";
|
import {NotifIndex, IndexedNotification, Association, GraphNotifDescription} from "~/types";
|
||||||
import { BigInteger } from 'big-integer';
|
import { BigInteger } from 'big-integer';
|
||||||
import {getParentIndex} from "../lib/notification";
|
import {getParentIndex} from "../lib/notification";
|
||||||
|
|
||||||
@ -71,6 +71,48 @@ export class HarkApi extends BaseApi<StoreState> {
|
|||||||
return this.actOnNotification('unread', time, index);
|
return this.actOnNotification('unread', time, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markSinceAsRead(association: Association, parent: string, description: GraphNotifDescription, since: string) {
|
||||||
|
return this.harkAction(
|
||||||
|
{ 'read-since': {
|
||||||
|
index: { graph: {
|
||||||
|
graph: association['app-path'],
|
||||||
|
group: association['group-path'],
|
||||||
|
module: association.metadata.module,
|
||||||
|
description,
|
||||||
|
index: parent
|
||||||
|
} },
|
||||||
|
target: since
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
markEachAsRead(association: Association, parent: string, child: string, description: GraphNotifDescription, mod: string) {
|
||||||
|
return this.harkAction({
|
||||||
|
'read-each': {
|
||||||
|
index:
|
||||||
|
{ graph:
|
||||||
|
{ graph: association['app-path'],
|
||||||
|
group: association['group-path'],
|
||||||
|
description,
|
||||||
|
module: mod,
|
||||||
|
index: parent
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: child
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dec(index: NotifIndex, ref: string) {
|
||||||
|
return this.harkAction({
|
||||||
|
dec: {
|
||||||
|
index,
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
seen() {
|
seen() {
|
||||||
return this.harkAction({ seen: null });
|
return this.harkAction({ seen: null });
|
||||||
}
|
}
|
||||||
|
17
pkg/interface/src/logic/lib/hark.ts
Normal file
17
pkg/interface/src/logic/lib/hark.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import bigInt, { BigInteger } from "big-integer";
|
||||||
|
import f from "lodash/fp";
|
||||||
|
import { Unreads } from "~/types";
|
||||||
|
|
||||||
|
export function getLastSeen(
|
||||||
|
unreads: Unreads,
|
||||||
|
path: string,
|
||||||
|
index: string
|
||||||
|
): BigInteger | undefined {
|
||||||
|
const lastSeenIdx = unreads.graph?.[path]?.[index]?.unreads;
|
||||||
|
if (!(typeof lastSeenIdx === "string")) {
|
||||||
|
return bigInt.zero;
|
||||||
|
}
|
||||||
|
return f.flow(f.split("/"), f.last, (x) => (!!x ? bigInt(x) : undefined))(
|
||||||
|
lastSeenIdx
|
||||||
|
);
|
||||||
|
}
|
@ -3,19 +3,14 @@ import {
|
|||||||
NotifIndex,
|
NotifIndex,
|
||||||
NotificationGraphConfig,
|
NotificationGraphConfig,
|
||||||
GroupNotificationsConfig,
|
GroupNotificationsConfig,
|
||||||
|
UnreadStats,
|
||||||
} from "~/types";
|
} from "~/types";
|
||||||
import { makePatDa } from "~/logic/lib/util";
|
import { makePatDa } from "~/logic/lib/util";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { StoreState } from "../store/type";
|
import {StoreState} from "../store/type";
|
||||||
|
|
||||||
|
type HarkState = Pick<StoreState, "notifications" | "notificationsGraphConfig" | "notificationsGroupConfig" | "unreads" | "notificationsChatConfig">;
|
||||||
|
|
||||||
type HarkState = Pick<StoreState,
|
|
||||||
"notificationsChatConfig"
|
|
||||||
| "notificationsGroupConfig"
|
|
||||||
| "notificationsGraphConfig"
|
|
||||||
| "notifications"
|
|
||||||
| "notificationsCount"
|
|
||||||
| "archivedNotifications"
|
|
||||||
| "unreads">;
|
|
||||||
|
|
||||||
export const HarkReducer = (json: any, state: HarkState) => {
|
export const HarkReducer = (json: any, state: HarkState) => {
|
||||||
const data = _.get(json, "harkUpdate", false);
|
const data = _.get(json, "harkUpdate", false);
|
||||||
@ -141,29 +136,90 @@ function reduce(data: any, state: HarkState) {
|
|||||||
dnd(data, state);
|
dnd(data, state);
|
||||||
added(data, state);
|
added(data, state);
|
||||||
unreads(data, state);
|
unreads(data, state);
|
||||||
|
readEach(data, state);
|
||||||
|
readSince(data, state);
|
||||||
|
unreadSince(data, state);
|
||||||
|
unreadEach(data, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readEach(json: any, state: HarkState) {
|
||||||
|
const data = _.get(json, 'read-each');
|
||||||
|
if(data) {
|
||||||
|
updateUnreads(state, data.index, u => u.delete(data.target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readSince(json: any, state: HarkState) {
|
||||||
|
const data = _.get(json, 'read-since');
|
||||||
|
if(data) {
|
||||||
|
updateUnreadSince(state, data.index, data.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unreadSince(json: any, state: HarkState) {
|
||||||
|
const data = _.get(json, 'unread-since');
|
||||||
|
if(data) {
|
||||||
|
updateNotificationStats(state, data.index, 'last', () => data.last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unreadEach(json: any, state: HarkState) {
|
||||||
|
const data = _.get(json, 'unread-each');
|
||||||
|
if(data) {
|
||||||
|
updateNotificationStats(state, data.index, 'last', () => data.last);
|
||||||
|
updateUnreads(state, data.index, us => us.add(data.target))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unreads(json: any, state: HarkState) {
|
function unreads(json: any, state: HarkState) {
|
||||||
const data = _.get(json, 'unreads');
|
const data = _.get(json, 'unreads');
|
||||||
if(data) {
|
if(data) {
|
||||||
data.forEach(({ index, unread }) => {
|
data.forEach(({ index, stats }) => {
|
||||||
updateUnreads(state, index, x => x + unread);
|
const { unreads, notifications, last } = stats;
|
||||||
|
updateNotificationStats(state, index, 'notifications', x => x + notifications);
|
||||||
|
updateNotificationStats(state, index, 'last', () => last);
|
||||||
|
if('since' in unreads) {
|
||||||
|
updateUnreadSince(state, index, unreads.since);
|
||||||
|
} else {
|
||||||
|
unreads.each.forEach((u: string) => {
|
||||||
|
updateUnreads(state, index, s => s.add(u));
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUnreads(state: HarkState, index: NotifIndex, f: (u: number) => number) {
|
function updateUnreadSince(state: HarkState, index: NotifIndex, since: string) {
|
||||||
|
if(!('graph' in index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], since);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>) => void) {
|
||||||
|
if(!('graph' in index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
|
||||||
|
f(unreads);
|
||||||
|
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'notifications' | 'unreads' | 'last', f: (x: number) => number) {
|
||||||
|
if(statField === 'notifications') {
|
||||||
state.notificationsCount = f(state.notificationsCount);
|
state.notificationsCount = f(state.notificationsCount);
|
||||||
if('graph' in index) {
|
}
|
||||||
const curr = state.unreads.graph[index.graph.graph] || 0;
|
if('graph' in index) {
|
||||||
state.unreads.graph[index.graph.graph] = f(curr);
|
const curr = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
|
||||||
} else if('group' in index) {
|
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
|
||||||
const curr = state.unreads.group[index.group.group] || 0;
|
} else if('group' in index) {
|
||||||
state.unreads.group[index.group.group] = f(curr);
|
const curr = _.get(state.unreads.group, [index.group.group, statField], 0);
|
||||||
} else if('chat' in index) {
|
_.set(state.unreads.group, [index.group.group, statField], f(curr));
|
||||||
const curr = state.unreads.chat[index.chat.chat] || 0
|
} else if('chat' in index) {
|
||||||
state.unreads.chat[index.chat.chat] = f(curr);
|
const curr = _.get(state.unreads.chat, [index.chat.chat, statField], 0);
|
||||||
}
|
_.set(state.unreads.chat, [index.chat.chat, statField], f(curr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function added(json: any, state: HarkState) {
|
function added(json: any, state: HarkState) {
|
||||||
@ -178,12 +234,12 @@ function added(json: any, state: HarkState) {
|
|||||||
);
|
);
|
||||||
if (arrIdx !== -1) {
|
if (arrIdx !== -1) {
|
||||||
if(timebox[arrIdx]?.notification?.read) {
|
if(timebox[arrIdx]?.notification?.read) {
|
||||||
updateUnreads(state, index, x => x+1);
|
updateNotificationStats(state, index, 'notifications', x => x+1);
|
||||||
}
|
}
|
||||||
timebox[arrIdx] = { index, notification };
|
timebox[arrIdx] = { index, notification };
|
||||||
state.notifications.set(time, timebox);
|
state.notifications.set(time, timebox);
|
||||||
} else {
|
} else {
|
||||||
updateUnreads(state, index, x => x+1);
|
updateNotificationStats(state, index, 'notifications', x => x+1);
|
||||||
state.notifications.set(time, [...timebox, { index, notification }]);
|
state.notifications.set(time, [...timebox, { index, notification }]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,9 +256,7 @@ const timebox = (json: any, state: HarkState) => {
|
|||||||
const data = _.get(json, "timebox", false);
|
const data = _.get(json, "timebox", false);
|
||||||
if (data) {
|
if (data) {
|
||||||
const time = makePatDa(data.time);
|
const time = makePatDa(data.time);
|
||||||
if (data.archive) {
|
if (!data.archive) {
|
||||||
state.archivedNotifications.set(time, data.notifications);
|
|
||||||
} else {
|
|
||||||
state.notifications.set(time, data.notifications);
|
state.notifications.set(time, data.notifications);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,7 +316,7 @@ function read(json: any, state: HarkState) {
|
|||||||
const data = _.get(json, "read", false);
|
const data = _.get(json, "read", false);
|
||||||
if (data) {
|
if (data) {
|
||||||
const { time, index } = data;
|
const { time, index } = data;
|
||||||
updateUnreads(state, index, x => x-1);
|
updateNotificationStats(state, index, 'notifications', x => x-1);
|
||||||
setRead(time, index, true, state);
|
setRead(time, index, true, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,7 +325,7 @@ function unread(json: any, state: HarkState) {
|
|||||||
const data = _.get(json, "unread", false);
|
const data = _.get(json, "unread", false);
|
||||||
if (data) {
|
if (data) {
|
||||||
const { time, index } = data;
|
const { time, index } = data;
|
||||||
updateUnreads(state, index, x => x+1);
|
updateNotificationStats(state, index, 'notifications', x => x+1);
|
||||||
setRead(time, index, false, state);
|
setRead(time, index, false, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,7 +348,7 @@ function archive(json: any, state: HarkState) {
|
|||||||
const readCount = archived.filter(
|
const readCount = archived.filter(
|
||||||
({ notification }) => !notification.read
|
({ notification }) => !notification.read
|
||||||
).length;
|
).length;
|
||||||
updateUnreads(state, index, x => x - readCount);
|
updateNotificationStats(state, index, 'notifications', x => x - readCount);
|
||||||
state.archivedNotifications.set(time, [
|
state.archivedNotifications.set(time, [
|
||||||
...archiveBox,
|
...archiveBox,
|
||||||
...archived.map(({ notification, index }) => ({
|
...archived.map(({ notification, index }) => ({
|
||||||
|
@ -106,12 +106,12 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
|||||||
mentions: false,
|
mentions: false,
|
||||||
watching: [],
|
watching: [],
|
||||||
},
|
},
|
||||||
notificationsCount: 0,
|
|
||||||
unreads: {
|
unreads: {
|
||||||
graph: {},
|
|
||||||
group: {},
|
|
||||||
chat: {},
|
chat: {},
|
||||||
}
|
graph: {},
|
||||||
|
group: {}
|
||||||
|
},
|
||||||
|
notificationsCount: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ export interface StoreState {
|
|||||||
notificationsGroupConfig: GroupNotificationsConfig;
|
notificationsGroupConfig: GroupNotificationsConfig;
|
||||||
notificationsChatConfig: string[];
|
notificationsChatConfig: string[];
|
||||||
notificationsCount: number,
|
notificationsCount: number,
|
||||||
|
unreads: Unreads;
|
||||||
doNotDisturb: boolean;
|
doNotDisturb: boolean;
|
||||||
unreads: Unreads;
|
unreads: Unreads;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
|||||||
this.subscribe('/updates', 'hark-store');
|
this.subscribe('/updates', 'hark-store');
|
||||||
this.subscribe('/updates', 'hark-graph-hook');
|
this.subscribe('/updates', 'hark-graph-hook');
|
||||||
this.subscribe('/updates', 'hark-group-hook');
|
this.subscribe('/updates', 'hark-group-hook');
|
||||||
this.subscribe('/updates', 'hark-chat-hook');
|
this.startApp('chat');
|
||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
|
@ -55,7 +55,7 @@ export interface Inbox {
|
|||||||
[chatName: string]: Mailbox;
|
[chatName: string]: Mailbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Mailbox {
|
export interface Mailbox {
|
||||||
config: MailboxConfig;
|
config: MailboxConfig;
|
||||||
envelopes: Envelope[];
|
envelopes: Envelope[];
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,20 @@ import { GroupUpdate } from "./group-update";
|
|||||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||||
import { Envelope } from './chat-update';
|
import { Envelope } from './chat-update';
|
||||||
|
|
||||||
type GraphNotifDescription = "link" | "comment" | "note" | "mention";
|
export type GraphNotifDescription = "link" | "comment" | "note" | "mention";
|
||||||
|
|
||||||
|
export interface UnreadStats {
|
||||||
|
unreads: Set<string>;
|
||||||
|
notifications: number;
|
||||||
|
last: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GraphNotifIndex {
|
export interface GraphNotifIndex {
|
||||||
graph: string;
|
graph: string;
|
||||||
group: string;
|
group: string;
|
||||||
description: GraphNotifDescription;
|
description: GraphNotifDescription;
|
||||||
module: string;
|
module: string;
|
||||||
|
index: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupNotifIndex {
|
export interface GroupNotifIndex {
|
||||||
@ -61,9 +68,9 @@ export interface NotificationGraphConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Unreads {
|
export interface Unreads {
|
||||||
chat: Record<string, number>;
|
chat: Record<string, UnreadStats>;
|
||||||
group: Record<string, number>;
|
graph: Record<string, Record<string, UnreadStats>>;
|
||||||
graph: Record<string, number>;
|
group: Record<string, UnreadStats>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WatchedIndex {
|
interface WatchedIndex {
|
||||||
|
@ -3,7 +3,7 @@ import { Box, Text, Col } from "@tlon/indigo-react";
|
|||||||
import f from "lodash/fp";
|
import f from "lodash/fp";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
import { Associations, Association, Unreads } from "~/types";
|
import { Associations, Association, Unreads, UnreadStats } from "~/types";
|
||||||
import { alphabeticalOrder } from "~/logic/lib/util";
|
import { alphabeticalOrder } from "~/logic/lib/util";
|
||||||
import Tile from "../components/tiles/tile";
|
import Tile from "../components/tiles/tile";
|
||||||
|
|
||||||
@ -14,32 +14,29 @@ interface GroupsProps {
|
|||||||
const sortGroupsAlph = (a: Association, b: Association) =>
|
const sortGroupsAlph = (a: Association, b: Association) =>
|
||||||
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||||
|
|
||||||
const getKindUnreads = (associations: Associations) => (path: string) => (
|
|
||||||
kind: "chat" | "graph"
|
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string): number =>
|
||||||
): ((unreads: Unreads) => number) =>
|
|
||||||
f.flow(
|
f.flow(
|
||||||
(x) => x[kind],
|
(x) => x['graph'],
|
||||||
f.pickBy((_v, key) => associations[kind]?.[key]?.["group-path"] === path),
|
f.pickBy((_v, key) => associations.graph?.[key]["group-path"] === path),
|
||||||
f.values,
|
f.map((x: Record<string, UnreadStats>) => 0), // x?.['/']?.unreads?.size),
|
||||||
f.reduce(f.add, 0)
|
f.reduce(f.add, 0)
|
||||||
);
|
)(unreads);
|
||||||
|
|
||||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||||
const { associations, unreads, ...boxProps } = props;
|
const { associations, unreads, inbox, ...boxProps } = props;
|
||||||
|
|
||||||
const groups = Object.values(associations?.contacts || {})
|
const groups = Object.values(associations?.contacts || {})
|
||||||
.filter((e) => e?.["group-path"] in props.groups)
|
.filter((e) => e?.["group-path"] in props.groups)
|
||||||
.sort(sortGroupsAlph);
|
.sort(sortGroupsAlph);
|
||||||
const getUnreads = getKindUnreads(associations || {});
|
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{groups.map((group) => {
|
{groups.map((group) => {
|
||||||
const path = group?.["group-path"];
|
const path = group?.["group-path"];
|
||||||
const unreadCount = (["chat", "graph"] as const)
|
const unreadCount = graphUnreads(path)
|
||||||
.map(getUnreads(path))
|
|
||||||
.map((f) => f(unreads))
|
|
||||||
.reduce(f.add, 0);
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
unreads={unreadCount}
|
unreads={unreadCount}
|
||||||
@ -55,6 +52,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
|||||||
interface GroupProps {
|
interface GroupProps {
|
||||||
path: string;
|
path: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
updates?: number;
|
||||||
unreads: number;
|
unreads: number;
|
||||||
}
|
}
|
||||||
function Group(props: GroupProps) {
|
function Group(props: GroupProps) {
|
||||||
|
@ -32,6 +32,7 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
groups,
|
groups,
|
||||||
associations,
|
associations,
|
||||||
graphKeys,
|
graphKeys,
|
||||||
|
unreads,
|
||||||
s3,
|
s3,
|
||||||
hideAvatars,
|
hideAvatars,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
@ -68,6 +69,7 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
exact
|
exact
|
||||||
path={relativePath("")}
|
path={relativePath("")}
|
||||||
render={(props) => {
|
render={(props) => {
|
||||||
|
const graphUnreads = unreads.graph?.[appPath]?.['/']?.unreads || new Set();
|
||||||
return (
|
return (
|
||||||
<Col width="100%" p={4} alignItems="center" maxWidth="768px">
|
<Col width="100%" p={4} alignItems="center" maxWidth="768px">
|
||||||
<Col width="100%" flexShrink='0'>
|
<Col width="100%" flexShrink='0'>
|
||||||
@ -80,6 +82,8 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
key={date.toString()}
|
key={date.toString()}
|
||||||
resource={resourcePath}
|
resource={resourcePath}
|
||||||
node={node}
|
node={node}
|
||||||
|
contacts={contactDetails}
|
||||||
|
unread={graphUnreads.has(node.post.index)}
|
||||||
nickname={contact?.nickname}
|
nickname={contact?.nickname}
|
||||||
hideAvatars={hideAvatars}
|
hideAvatars={hideAvatars}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
@ -118,6 +122,8 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
<LinkPreview
|
<LinkPreview
|
||||||
resourcePath={resourcePath}
|
resourcePath={resourcePath}
|
||||||
post={node.post}
|
post={node.post}
|
||||||
|
association={association}
|
||||||
|
api={api}
|
||||||
nickname={contact?.nickname}
|
nickname={contact?.nickname}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
commentNumber={node.children.size}
|
commentNumber={node.children.size}
|
||||||
@ -128,6 +134,8 @@ export function LinkResource(props: LinkResourceProps) {
|
|||||||
name={name}
|
name={name}
|
||||||
comments={node}
|
comments={node}
|
||||||
resource={resourcePath}
|
resource={resourcePath}
|
||||||
|
association={association}
|
||||||
|
unreads={unreads}
|
||||||
contacts={contactDetails}
|
contacts={contactDetails}
|
||||||
api={api}
|
api={api}
|
||||||
hideAvatars={hideAvatars}
|
hideAvatars={hideAvatars}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Row, Col, Anchor, Box, Text, BaseImage } from '@tlon/indigo-react';
|
import { Row, Col, Anchor, Box, Text, BaseImage } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { cite } from '~/logic/lib/util';
|
import { cite } from '~/logic/lib/util';
|
||||||
|
import { Author } from "~/views/apps/publish/components/Author";
|
||||||
|
|
||||||
import { roleForShip } from '~/logic/lib/group';
|
import { roleForShip } from '~/logic/lib/group';
|
||||||
|
|
||||||
@ -12,6 +13,8 @@ export const LinkItem = (props) => {
|
|||||||
node,
|
node,
|
||||||
nickname,
|
nickname,
|
||||||
avatar,
|
avatar,
|
||||||
|
contacts,
|
||||||
|
unread,
|
||||||
resource,
|
resource,
|
||||||
hideAvatars,
|
hideAvatars,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
@ -26,6 +29,7 @@ export const LinkItem = (props) => {
|
|||||||
const author = node.post.author;
|
const author = node.post.author;
|
||||||
const index = node.post.index.split('/')[1];
|
const index = node.post.index.split('/')[1];
|
||||||
const size = node.children ? node.children.size : 0;
|
const size = node.children ? node.children.size : 0;
|
||||||
|
const date = node.post['time-sent'];
|
||||||
const contents = node.post.contents;
|
const contents = node.post.contents;
|
||||||
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
||||||
|
|
||||||
@ -57,18 +61,22 @@ export const LinkItem = (props) => {
|
|||||||
<Text display='inline-block' overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{contents[0].text}</Text>
|
<Text display='inline-block' overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{contents[0].text}</Text>
|
||||||
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} ↗</Text>
|
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} ↗</Text>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
<Box width="100%">
|
<Row alignItems="center" width="100%">
|
||||||
<Text
|
<Author
|
||||||
fontFamily={showNickname ? 'sans' : 'mono'} pr={2}
|
contacts={contacts}
|
||||||
|
ship={author}
|
||||||
|
hideAvatars={hideAvatars}
|
||||||
|
hideNicknames={hideNicknames}
|
||||||
|
unread={unread}
|
||||||
|
date={date}
|
||||||
>
|
>
|
||||||
{showNickname ? nickname : cite(author) }
|
|
||||||
</Text>
|
|
||||||
<Link to={`${baseUrl}/${index}`}>
|
<Link to={`${baseUrl}/${index}`}>
|
||||||
<Text color="gray">{size} comments</Text>
|
<Text ml="2" color="gray">{size} comments</Text>
|
||||||
</Link>
|
</Link>
|
||||||
{(ourRole === 'admin' || node.post.author === window.ship)
|
{(ourRole === 'admin' || node.post.author === window.ship)
|
||||||
&& (<Text color='red' ml='2' cursor='pointer' onClick={() => api.graph.removeNodes(`~${ship}`, name, [node.post.index])}>Delete</Text>)}
|
&& (<Text color='red' ml='2' cursor='pointer' onClick={() => api.graph.removeNodes(`~${ship}`, name, [node.post.index])}>Delete</Text>)}
|
||||||
</Box>
|
</Author>
|
||||||
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { cite } from '~/logic/lib/util';
|
import { cite } from '~/logic/lib/util';
|
||||||
import RemoteContent from '~/views/components/RemoteContent';
|
import RemoteContent from '~/views/components/RemoteContent';
|
||||||
|
|
||||||
@ -20,6 +20,10 @@ export const LinkPreview = (props) => {
|
|||||||
const timeSent =
|
const timeSent =
|
||||||
moment.unix(props.post['time-sent'] / 1000).format('hh:mm a');
|
moment.unix(props.post['time-sent'] / 1000).format('hh:mm a');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => props.api.hark.markEachAsRead(props.association, '/', props.post.index, 'link', 'link');
|
||||||
|
}, [props.association, props.post.index]);
|
||||||
|
|
||||||
const embed = (
|
const embed = (
|
||||||
<RemoteContent
|
<RemoteContent
|
||||||
unfold={true}
|
unfold={true}
|
||||||
|
@ -36,6 +36,7 @@ export function PublishResource(props: PublishResourceProps) {
|
|||||||
match={props.match}
|
match={props.match}
|
||||||
location={props.location}
|
location={props.location}
|
||||||
hideAvatars={props.hideAvatars}
|
hideAvatars={props.hideAvatars}
|
||||||
|
unreads={props.unreads}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
remoteContentPolicy={props.remoteContentPolicy}
|
remoteContentPolicy={props.remoteContentPolicy}
|
||||||
graphs={props.graphs}
|
graphs={props.graphs}
|
||||||
|
@ -13,6 +13,7 @@ interface AuthorProps {
|
|||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
unread?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Author(props: AuthorProps) {
|
export function Author(props: AuthorProps) {
|
||||||
@ -54,7 +55,7 @@ export function Author(props: AuthorProps) {
|
|||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</Box>
|
</Box>
|
||||||
<Box ml={2} color="gray">
|
<Box ml={2} color={props.unread ? "blue" : "gray"}>
|
||||||
{dateFmt}
|
{dateFmt}
|
||||||
</Box>
|
</Box>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -10,12 +10,14 @@ import { NoteNavigation } from "./NoteNavigation";
|
|||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
||||||
import { Author } from "./Author";
|
import { Author } from "./Author";
|
||||||
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy } from "~/types";
|
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy, Association, Unreads } from "~/types";
|
||||||
|
|
||||||
interface NoteProps {
|
interface NoteProps {
|
||||||
ship: string;
|
ship: string;
|
||||||
book: string;
|
book: string;
|
||||||
note: GraphNode;
|
note: GraphNode;
|
||||||
|
unreads: Unreads;
|
||||||
|
association: Association;
|
||||||
notebook: Graph;
|
notebook: Graph;
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
@ -39,8 +41,13 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
|||||||
props.history.push(rootUrl);
|
props.history.push(rootUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const comments = getComments(note);
|
const comments = getComments(note);
|
||||||
const [revNum, title, body, post] = getLatestRevision(note);
|
const [revNum, title, body, post] = getLatestRevision(note);
|
||||||
|
useEffect(() => {
|
||||||
|
api.hark.markEachAsRead(props.association, '/', post.index, 'note', 'publish');
|
||||||
|
}, [props.association]);
|
||||||
|
|
||||||
|
|
||||||
const noteId = bigInt(note.post.index.split('/')[1]);
|
const noteId = bigInt(note.post.index.split('/')[1]);
|
||||||
|
|
||||||
@ -108,8 +115,10 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
|||||||
<Comments
|
<Comments
|
||||||
ship={ship}
|
ship={ship}
|
||||||
name={props.book}
|
name={props.book}
|
||||||
|
unreads={props.unreads}
|
||||||
comments={comments}
|
comments={comments}
|
||||||
contacts={props.contacts}
|
contacts={props.contacts}
|
||||||
|
association={props.association}
|
||||||
api={props.api}
|
api={props.api}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
hideAvatars={props.hideAvatars}
|
hideAvatars={props.hideAvatars}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
getLatestRevision,
|
getLatestRevision,
|
||||||
getSnippet,
|
getSnippet,
|
||||||
} from "~/logic/lib/publish";
|
} from "~/logic/lib/publish";
|
||||||
|
import {Unreads} from "~/types";
|
||||||
|
|
||||||
interface NotePreviewProps {
|
interface NotePreviewProps {
|
||||||
host: string;
|
host: string;
|
||||||
@ -21,6 +22,7 @@ interface NotePreviewProps {
|
|||||||
contact?: Contact;
|
contact?: Contact;
|
||||||
hideNicknames?: boolean;
|
hideNicknames?: boolean;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
unreads: Unreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WrappedBox = styled(Box)`
|
const WrappedBox = styled(Box)`
|
||||||
@ -52,10 +54,8 @@ export function NotePreview(props: NotePreviewProps) {
|
|||||||
const date = moment(post["time-sent"]).fromNow();
|
const date = moment(post["time-sent"]).fromNow();
|
||||||
const url = `${props.baseUrl}/note/${post.index.split("/")[1]}`;
|
const url = `${props.baseUrl}/note/${post.index.split("/")[1]}`;
|
||||||
|
|
||||||
// stubbing pending notification-store
|
const [rev, title, body, content] = getLatestRevision(node);
|
||||||
const isRead = true;
|
const isUnread = props.unreads.graph?.[`/ship/${props.host}/${props.book}`]?.['/']?.unreads?.has(content.index);
|
||||||
|
|
||||||
const [rev, title, body] = getLatestRevision(node);
|
|
||||||
|
|
||||||
const snippet = getSnippet(body);
|
const snippet = getSnippet(body);
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export function NotePreview(props: NotePreviewProps) {
|
|||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</Box>
|
</Box>
|
||||||
<Box color={isRead ? "gray" : "green"} mr={3}>
|
<Box color={isUnread ? "blue" : "gray"} mr={3}>
|
||||||
{date}
|
{date}
|
||||||
</Box>
|
</Box>
|
||||||
<Box mr={3}>{commentDesc}</Box>
|
<Box mr={3}>{commentDesc}</Box>
|
||||||
|
@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router-dom";
|
|||||||
import Note from "./Note";
|
import Note from "./Note";
|
||||||
import { EditPost } from "./EditPost";
|
import { EditPost } from "./EditPost";
|
||||||
|
|
||||||
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy } from "~/types";
|
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy, Association } from "~/types";
|
||||||
|
|
||||||
interface NoteRoutesProps {
|
interface NoteRoutesProps {
|
||||||
ship: string;
|
ship: string;
|
||||||
@ -19,6 +19,7 @@ interface NoteRoutesProps {
|
|||||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
|
association: Association;
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
rootUrl?: string;
|
rootUrl?: string;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { Groups } from "~/types/group-update";
|
|||||||
import { Contacts, Rolodex } from "~/types/contact-update";
|
import { Contacts, Rolodex } from "~/types/contact-update";
|
||||||
import GlobalApi from "~/logic/api/global";
|
import GlobalApi from "~/logic/api/global";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Associations, Graph, Association } from "~/types";
|
import { Associations, Graph, Association, Unreads } from "~/types";
|
||||||
import { deSig } from "~/logic/lib/util";
|
import { deSig } from "~/logic/lib/util";
|
||||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ interface NotebookProps {
|
|||||||
hideNicknames: boolean;
|
hideNicknames: boolean;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
rootUrl: string;
|
rootUrl: string;
|
||||||
associations: Associations;
|
unreads: Unreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotebookState {
|
interface NotebookState {
|
||||||
@ -84,6 +84,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
|
|||||||
book={book}
|
book={book}
|
||||||
contacts={!!notebookContacts ? notebookContacts : {}}
|
contacts={!!notebookContacts ? notebookContacts : {}}
|
||||||
hideNicknames={hideNicknames}
|
hideNicknames={hideNicknames}
|
||||||
|
unreads={props.unreads}
|
||||||
baseUrl={props.baseUrl}
|
baseUrl={props.baseUrl}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Col } from "@tlon/indigo-react";
|
import { Col } from "@tlon/indigo-react";
|
||||||
import { NotePreview } from "./NotePreview";
|
import { NotePreview } from "./NotePreview";
|
||||||
import { Contacts, Graph } from "~/types";
|
import { Contacts, Graph, Unreads } from "~/types";
|
||||||
|
|
||||||
interface NotebookPostsProps {
|
interface NotebookPostsProps {
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
@ -10,6 +10,7 @@ interface NotebookPostsProps {
|
|||||||
book: string;
|
book: string;
|
||||||
hideNicknames?: boolean;
|
hideNicknames?: boolean;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
unreads: Unreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotebookPosts(props: NotebookPostsProps) {
|
export function NotebookPosts(props: NotebookPostsProps) {
|
||||||
@ -22,6 +23,7 @@ export function NotebookPosts(props: NotebookPostsProps) {
|
|||||||
key={date.toString()}
|
key={date.toString()}
|
||||||
host={props.host}
|
host={props.host}
|
||||||
book={props.book}
|
book={props.book}
|
||||||
|
unreads={props.unreads}
|
||||||
contact={props.contacts[node.post.author]}
|
contact={props.contacts[node.post.author]}
|
||||||
node={node}
|
node={node}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
Groups,
|
Groups,
|
||||||
Contacts,
|
Contacts,
|
||||||
Rolodex,
|
Rolodex,
|
||||||
LocalUpdateRemoteContentPolicy
|
LocalUpdateRemoteContentPolicy,
|
||||||
|
Unreads
|
||||||
} from "~/types";
|
} from "~/types";
|
||||||
import { Center, LoadingSpinner } from "@tlon/indigo-react";
|
import { Center, LoadingSpinner } from "@tlon/indigo-react";
|
||||||
import { Notebook as INotebook } from "~/types/publish-update";
|
import { Notebook as INotebook } from "~/types/publish-update";
|
||||||
@ -26,6 +27,7 @@ interface NotebookRoutesProps {
|
|||||||
book: string;
|
book: string;
|
||||||
graphs: Graphs;
|
graphs: Graphs;
|
||||||
notebookContacts: Contacts;
|
notebookContacts: Contacts;
|
||||||
|
unreads: Unreads;
|
||||||
contacts: Rolodex;
|
contacts: Rolodex;
|
||||||
groups: Groups;
|
groups: Groups;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@ -102,8 +104,10 @@ export function NotebookRoutes(
|
|||||||
ship={ship}
|
ship={ship}
|
||||||
note={note}
|
note={note}
|
||||||
notebook={graph}
|
notebook={graph}
|
||||||
|
unreads={props.unreads}
|
||||||
noteId={noteIdNum}
|
noteId={noteIdNum}
|
||||||
contacts={notebookContacts}
|
contacts={notebookContacts}
|
||||||
|
association={props.association}
|
||||||
hideAvatars={props.hideAvatars}
|
hideAvatars={props.hideAvatars}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
remoteContentPolicy={props.remoteContentPolicy}
|
remoteContentPolicy={props.remoteContentPolicy}
|
||||||
|
@ -21,6 +21,7 @@ interface CommentItemProps {
|
|||||||
comment: GraphNode;
|
comment: GraphNode;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
|
unread: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
ship: string;
|
ship: string;
|
||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
@ -50,6 +51,7 @@ export function CommentItem(props: CommentItemProps) {
|
|||||||
contacts={contacts}
|
contacts={contacts}
|
||||||
ship={post?.author}
|
ship={post?.author}
|
||||||
date={post?.['time-sent']}
|
date={post?.['time-sent']}
|
||||||
|
unread={props.unread}
|
||||||
hideAvatars={props.hideAvatars}
|
hideAvatars={props.hideAvatars}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
>
|
>
|
||||||
|
@ -6,14 +6,15 @@ import CommentInput from './CommentInput';
|
|||||||
import { Contacts } from '~/types/contact-update';
|
import { Contacts } from '~/types/contact-update';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { FormikHelpers } from 'formik';
|
import { FormikHelpers } from 'formik';
|
||||||
import { GraphNode } from '~/types/graph-update';
|
import { GraphNode, LocalUpdateRemoteContentPolicy, Unreads, Association } from '~/types';
|
||||||
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
|
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
|
||||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||||
import { LocalUpdateRemoteContentPolicy } from '~/types';
|
|
||||||
import { scanForMentions } from '~/logic/lib/graph';
|
import { scanForMentions } from '~/logic/lib/graph';
|
||||||
|
import { getLastSeen } from '~/logic/lib/hark';
|
||||||
|
|
||||||
interface CommentsProps {
|
interface CommentsProps {
|
||||||
comments: GraphNode;
|
comments: GraphNode;
|
||||||
|
association: Association;
|
||||||
name: string;
|
name: string;
|
||||||
ship: string;
|
ship: string;
|
||||||
editCommentId: string;
|
editCommentId: string;
|
||||||
@ -26,7 +27,7 @@ interface CommentsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Comments(props: CommentsProps) {
|
export function Comments(props: CommentsProps) {
|
||||||
const { comments, ship, name, api, baseUrl, history} = props;
|
const { association, comments, ship, name, api, history } = props;
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (
|
||||||
{ comment },
|
{ comment },
|
||||||
@ -87,6 +88,20 @@ export function Comments(props: CommentsProps) {
|
|||||||
return val;
|
return val;
|
||||||
}, '');
|
}, '');
|
||||||
}
|
}
|
||||||
|
const parentIndex = `/${comments?.post.index.slice(1).split('/')[0]}`;
|
||||||
|
|
||||||
|
const children = Array.from(comments.children);
|
||||||
|
|
||||||
|
|
||||||
|
const [latestIdx, latest] = children?.[0] || [];
|
||||||
|
useEffect(() => {
|
||||||
|
return latest
|
||||||
|
? () => api.hark.markSinceAsRead(association, parentIndex, 'comment', latest.post.index)
|
||||||
|
: () => {};
|
||||||
|
}, [association])
|
||||||
|
|
||||||
|
|
||||||
|
const lastSeen = getLastSeen(props.unreads, association['app-path'], parentIndex) || latestIdx || bigInt.zero;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
@ -99,7 +114,7 @@ export function Comments(props: CommentsProps) {
|
|||||||
initial={commentContent}
|
initial={commentContent}
|
||||||
/>
|
/>
|
||||||
) : null )}
|
) : null )}
|
||||||
{Array.from(comments.children).reverse()
|
{children.reverse()
|
||||||
.map(([idx, comment]) => {
|
.map(([idx, comment]) => {
|
||||||
return (
|
return (
|
||||||
<CommentItem
|
<CommentItem
|
||||||
@ -109,6 +124,7 @@ export function Comments(props: CommentsProps) {
|
|||||||
api={api}
|
api={api}
|
||||||
name={name}
|
name={name}
|
||||||
ship={ship}
|
ship={ship}
|
||||||
|
unread={lastSeen.lt(idx)}
|
||||||
hideNicknames={props.hideNicknames}
|
hideNicknames={props.hideNicknames}
|
||||||
hideAvatars={props.hideAvatars}
|
hideAvatars={props.hideAvatars}
|
||||||
remoteContentPolicy={props.remoteContentPolicy}
|
remoteContentPolicy={props.remoteContentPolicy}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useCallback } from "react";
|
import { useEffect, useCallback } from "react";
|
||||||
import { Inbox, ChatHookUpdate, Notebooks, Graphs } from "~/types";
|
import { Inbox, ChatHookUpdate, Notebooks, Graphs, UnreadStats } from "~/types";
|
||||||
import { SidebarItemStatus, SidebarAppConfig } from "./types";
|
import { SidebarItemStatus, SidebarAppConfig } from "./types";
|
||||||
|
|
||||||
export function useChat(
|
export function useChat(
|
||||||
@ -42,11 +42,11 @@ export function useChat(
|
|||||||
export function useGraphModule(
|
export function useGraphModule(
|
||||||
graphKeys: Set<string>,
|
graphKeys: Set<string>,
|
||||||
graphs: Graphs,
|
graphs: Graphs,
|
||||||
graphUnreads: Record<string, number>
|
graphUnreads: Record<string, Record<string, UnreadStats>>
|
||||||
): SidebarAppConfig {
|
): SidebarAppConfig {
|
||||||
const getStatus = useCallback(
|
const getStatus = useCallback(
|
||||||
(s: string) => {
|
(s: string) => {
|
||||||
if((graphUnreads[s] || 0) > 0) {
|
if((graphUnreads?.[s]?.['/']?.unreads?.size || 0) > 0) {
|
||||||
return 'unread';
|
return 'unread';
|
||||||
}
|
}
|
||||||
const [, , host, name] = s.split("/");
|
const [, , host, name] = s.split("/");
|
||||||
@ -57,18 +57,22 @@ export function useGraphModule(
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
[graphs, graphKeys]
|
[graphs, graphKeys, graphUnreads]
|
||||||
);
|
);
|
||||||
|
|
||||||
const lastUpdated = useCallback((s: string) => {
|
const lastUpdated = useCallback((s: string) => {
|
||||||
// cant get link timestamps without loading posts
|
// cant get link timestamps without loading posts
|
||||||
|
const last = graphUnreads?.[s]?.['/']?.last;
|
||||||
|
if(last) {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
const stat = getStatus(s);
|
const stat = getStatus(s);
|
||||||
if(stat === 'unsubscribed') {
|
if(stat === 'unsubscribed') {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
}, [getStatus]);
|
}, [getStatus, graphUnreads]);
|
||||||
|
|
||||||
return { getStatus, lastUpdated };
|
return { getStatus, lastUpdated };
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ export default class Landscape extends Component<LandscapeProps, {}> {
|
|||||||
document.title = 'OS1 - Landscape';
|
document.title = 'OS1 - Landscape';
|
||||||
|
|
||||||
this.props.subscription.startApp('groups');
|
this.props.subscription.startApp('groups');
|
||||||
this.props.subscription.startApp('chat');
|
|
||||||
this.props.subscription.startApp('graph');
|
this.props.subscription.startApp('graph');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user