Merge branch 'release/next-js' into ixv/fix-unread-resubscribe

This commit is contained in:
Matilde Park 2021-01-05 17:34:28 -05:00
commit 136efa76a3
88 changed files with 10325 additions and 3224 deletions

4
.github/actions/glob/Dockerfile vendored Normal file
View File

@ -0,0 +1,4 @@
FROM jaredtobin/janeway:v0.13.2
COPY entrypoint.sh /entrypoint.sh
EXPOSE 22/tcp
ENTRYPOINT ["/entrypoint.sh"]

25
.github/actions/glob/action.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: 'glob'
description: 'Create a glob and deploy it to a moon'
inputs:
ship:
description: "Ship to deploy to"
required: true
credentials:
description: "base64-encoded GCP Service Account credentials"
required: true
ssh-sec-key:
description: "A base64-encoded SSH secret key for the container to use"
required: true
ssh-pub-key:
description: "The corresponding base64-encoded SSH public key"
required: true
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.ship }}
- ${{ inputs.credentials }}
- ${{ inputs.ssh-sec-key }}
- ${{ inputs.ssh-pub-key }}

32
.github/actions/glob/entrypoint.sh vendored Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
cd "$GITHUB_WORKSPACE" || exit
echo "$2" | base64 -d > service-account
echo "$3" | base64 -d > id_ssh
echo "$4" | base64 -d > id_ssh.pub
chmod 600 service-account
chmod 600 id_ssh
chmod 600 id_ssh.pub
janeway release glob --dev --no-pill \
--credentials service-account \
--ssh-key id_ssh \
--do-it-live \
| bash
SHORTHASH=$(git rev-parse --short HEAD)
janeway release prepare-ota arvo-glob-"$SHORTHASH" "$1" \
--credentials service-account \
--ssh-key id_ssh \
--do-it-live \
| bash
janeway release perform-ota "$1" \
--credentials service-account \
--ssh-key id_ssh \
--do-it-live \
| bash

View File

@ -50,7 +50,7 @@ jobs:
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: mars
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix-build -A urbit --arg enableStatic true
@ -73,7 +73,7 @@ jobs:
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: mars
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix-build -A hs.urbit-king.components.exes.urbit-king --arg enableStatic true

23
.github/workflows/glob.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: glob
on:
push:
branches:
- 'release/next-js'
pull_request:
branches:
- 'release/next-js'
jobs:
glob:
runs-on: ubuntu-latest
name: "Create and deploy a glob to ~lomlyx-lopsem-nidsut-tomdun"
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: ./.github/actions/glob
with:
ship: 'lomlyx-lopsem-nidsut-tomdun'
credentials: ${{ secrets.JANEWAY_SERVICE_KEY }}
ssh-sec-key: ${{ secrets.JANEWAY_SSH_SEC_KEY }}
ssh-pub-key: ${{ secrets.JANEWAY_SSH_PUB_KEY }}

View File

@ -20,7 +20,7 @@ jobs:
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: mars
name: ares
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- uses: google-github-actions/setup-gcloud@v0.2.0

View File

@ -36,7 +36,10 @@ If you're interested in Urbit development, keep reading.
## Development
[![Build Status](https://travis-ci.org/urbit/urbit.svg?branch=master)][trav]
[![License][license-badge]][license]
[![Build][build-badge]][build]
[![Nix][nix-badge]][nix]
[![Cachix][cachix-badge]][cachix]
Urbit uses [Nix][nix] to manage builds. On Linux and macOS you can install Nix
via:
@ -45,6 +48,16 @@ via:
curl -L https://nixos.org/nix/install | sh
```
You can optionally setup Nix to pull build artefacts from the binary cache
that continuous integration uses. This will improve build times and avoid
unnecessary recompilations of common dependencies. Once Nix has been installed
you can setup Cachix via:
```
nix-env -iA cachix -f https://cachix.org/api/v1/install
cachix use ares
```
The Makefile in the project's root directory contains useful phony targets for
building, installing, testing, and so on. You can use it to avoid dealing with
Nix explicitly.
@ -69,8 +82,14 @@ git lfs install
git lfs pull
```
[trav]: https://github.com/urbit/urbit.git
[nix]: https://nixos.org/nix/
[license]: https://raw.githubusercontent.com/urbit/urbit/master/LICENSE.txt
[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[build]: https://github.com/urbit/urbit/actions
[build-badge]: https://github.com/urbit/urbit/workflows/build/badge.svg
[cachix]: https://ares.cachix.org
[cachix-badge]: https://img.shields.io/badge/cachix-ares-purple.svg
[nix]: https://nixos.org
[nix-badge]: https://img.shields.io/badge/builtwith-nix-purple.svg
[git-lfs]: https://git-lfs.github.com
## Contributing
@ -85,4 +104,4 @@ You might also be interested in joining the [urbit-dev][list] mailing list.
[list]: https://groups.google.com/a/urbit.org/forum/#!forum/dev
[cont]: https://github.com/urbit/urbit/blob/master/CONTRIBUTING.md
[lcont]: https://github.com/urbit/urbit/blob/master/pkg/interface/CONTRIBUTING.md
[lcont]: https://github.com/urbit/urbit/blob/master/pkg/interface/CONTRIBUTING.md

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:35dab0c5d317e753c3f7baa3f95823bc5a75ac968c8791ef571f654880e1a88a
size 8002166
oid sha256:17eb2f5a123f5ad29b0cc9ff9069540c349dd97c6133a9ea33cbf81e0bfa4d6b
size 8483784

View File

@ -11,7 +11,7 @@
Note that on linux the previous command is equivalent to:
$ nix-build -A urbit --argstr crossSystem x86_64-unknown-linux-musl \
--arg enableSatic true
--arg enableStatic true
Static urbit-king binary:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v3.e7ep6.1ki6b.qpuiq.22qs6.qcbi7
++ hash 0v5.hvt1e.ie7it.b7i7l.1r7jj.dn9ib
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -22,16 +22,16 @@
dep ~(. (default:pull-hook this config) bowl)
gra ~(. graph bowl)
::
++ on-init on-init:def
++ on-save !>(~)
++ on-load on-load:def
++ on-poke on-poke:def
++ on-peek on-peek:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
++ on-agent on-agent:def
++ on-watch on-watch:def
++ on-leave on-leave:def
++ on-init on-init:def
++ on-save !>(~)
++ on-load on-load:def
++ on-poke on-poke:def
++ on-peek on-peek:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
++ on-agent on-agent:def
++ on-watch on-watch:def
++ on-leave on-leave:def
++ on-pull-nack
|= [=resource =tang]
^- (quip card _this)

View File

@ -297,6 +297,8 @@
|^
=/ [=graph:store mark=(unit mark:store)]
(~(got by graphs) resource)
~| "cannot add duplicate nodes to {<resource>}"
?< (check-for-duplicates graph ~(key by nodes))
=/ =update-log:store (~(got by update-logs) resource)
=. update-log
(put:orm-log update-log time [%0 time [%add-nodes resource nodes]])
@ -311,6 +313,31 @@
(add-node-list resource graph mark (sort-nodes nodes))
==
::
++ check-for-duplicates
|= [=graph:store nodes=(set index:store)]
^- ?
=/ node-list ~(tap in nodes)
|-
?~ node-list %.n
?: (has-node graph i.node-list) %.y
$(node-list t.node-list)
::
++ has-node
|= [=graph:store =index:store]
^- ?
=/ node=(unit node:store) ~
|-
?~ index
?=(^ node)
?~ t.index
?=(^ (get:orm graph i.index))
=. node (get:orm graph i.index)
?~ node %.n
?- -.children.u.node
%empty %.n
%graph $(graph p.children.u.node, index t.index)
==
::
++ sort-nodes
|= nodes=(map index:store node:store)
^- (list [index:store node:store])
@ -566,7 +593,9 @@
?< (~(has by archive) resource)
?> (~(has by graphs) resource)
=/ updates=(list [=time upd=logged-update:store])
(tap:orm-log update-log)
:: updates are time-ordered with most recent first
:: process with earliest first
(bap:orm-log update-log)
=| cards=(list card)
|- ^- (quip card _state)
?~ updates

View File

@ -1,7 +1,7 @@
:: hark-graph-hook: notifications for graph-store [landscape]
::
/- store=hark-store, post, group-store, metadata-store, hook=hark-graph-hook
/+ resource, metadata, default-agent, dbug, graph-store, graph, grouplib=group
/- post, group-store, metadata-store, hook=hark-graph-hook, store=hark-store
/+ resource, metadata, default-agent, dbug, graph-store, graph, grouplib=group, store=hark-store
::
::
~% %hark-graph-hook-top ..part ~
@ -19,31 +19,27 @@
==
::
+$ notif-kind
[name=@t parent-lent=@ud mode=?(%each %count) watch=?]
[name=@t parent-lent=@ud mode=?(%each %count %none) watch=?]
::
++ scry
|* [[our=@p now=@da] =mold p=path]
?> ?=(^ p)
?> ?=(^ t.p)
.^(mold i.p (scot %p our) i.t.p (scot %da now) t.t.p)
::
++ scry-conversion
|= [[our=@p now=@da] desk=term =mark]
~+
%^ scry [our now]
tube:clay
/cc/[desk]/[mark]/notification-kind
::
--
::
=| state-0
=* state -
::
=>
|_ =bowl:gall
::
++ 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
|= [paths=(list path) =update:hook]
^- (list card)
[%give %fact paths hark-graph-hook-update+!>(update)]~
::
++ watch-graph
^- card
[%pass /graph %agent [our.bowl %graph-store] %watch /updates]
--
=<
%- agent:dbug
^- agent:gall
~% %hark-graph-hook-agent ..card ~
@ -162,8 +158,38 @@
(graph-update !<(update:graph-store q.cage.sign))
[cards this]
==
::
++ graph-update
|= =update:graph-store
^- (quip card _state)
?+ -.q.update `state
%add-graph (add-graph resource.q.update)
::
?(%remove-graph %archive-graph)
(remove-graph resource.q.update)
::
%add-nodes
=* rid resource.q.update
(check-nodes ~(val by nodes.q.update) rid)
==
::
++ remove-graph
|= rid=resource
=/ unwatched
%- ~(gas in *_watching)
%+ skim ~(tap in watching)
|= [r=resource idx=index:graph-store]
=(r rid)
:_ state(watching (~(dif in watching) unwatched))
^- (list card)
:- (poke-hark:ha %remove-graph rid)
%- zing
%+ turn ~(tap in unwatched)
|= [r=resource =index:graph-store]
(give:ha ~[/updates] %ignore r index)
::
++ add-graph
|= [rid=resource =graph:graph-store]
|= rid=resource
^- (quip card _state)
=/ group-rid=(unit resource)
(group-from-app-resource:met %graph rid)
@ -176,146 +202,29 @@
|(is-hidden &(watch-on-self =(our.bowl entity.rid)))
?. should-watch
`state
:- (give:ha ~[/updates] %listen [rid ~])
state(watching (~(put in watching) [rid ~]))
=/ graph=graph:graph-store :: graph in subscription is bunted
(get-graph-mop:gra rid)
=/ node=(unit node:graph-store)
(bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node))
=^ cards state
(check-nodes (drop node) rid)
:_ state(watching (~(put in watching) [rid ~]))
(weld cards (give:ha ~[/updates] %listen [rid ~]))
::
++ graph-update
|= =update:graph-store
^- (quip card _state)
?: ?=(%add-graph -.q.update)
(add-graph resource.q.update graph.q.update)
?. ?=(%add-nodes -.q.update)
[~ state]
=* rid resource.q.update
(check-nodes ~(tap by nodes.q.update) rid)
::
++ check-nodes
|= $: nodes=(list [p=index:graph-store q=node:graph-store])
|= $: nodes=(list node:graph-store)
rid=resource
==
=/ group=resource
(need (group-from-app-resource:met %graph rid))
=/ =metadata:metadata-store
(need (peek-metadata:met %graph group rid))
=+ %+ scry:ha
,mark=(unit mark)
/gx/graph-store/graph-mark/(scot %p entity.rid)/[name.rid]/noun
=+ %+ scry:ha
,=tube:clay
/cc/[q.byk.bowl]/[(fall mark %graph-validator-link)]/notification-kind
=| cards=(list card)
|^
?~ nodes
[cards state]
=* index p.i.nodes
=* node q.i.nodes
=^ node-cards state
(check-node node tube)
%_ $
nodes t.nodes
cards (weld node-cards cards)
==
::
++ check-node-children
|= [=node:graph-store =tube:clay]
^- (quip card _state)
?: ?=(%empty -.children.node)
[~ state]
=/ children=(list [=atom =node:graph-store])
(tap:orm:graph-store p.children.node)
=| cards=(list card)
|- ^- (quip card _state)
?~ children
[cards state]
=^ new-cards state
(check-node node.i.children tube)
%_ $
cards (weld cards new-cards)
children t.children
==
::
++ check-node
|= [=node:graph-store =tube:clay]
^- (quip card _state)
=^ child-cards state
(check-node-children node tube)
=+ !< notif-kind=(unit notif-kind)
(tube !>([0 post.node]))
?~ notif-kind
[child-cards state]
=/ desc=@t
?: (is-mention contents.post.node)
%mention
name.u.notif-kind
=/ parent=index:post
(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 watch]:u.notif-kind)
:_ state
(weld child-cards self-cards)
:_ state
%+ weld child-cards
%+ weld
%^ update-unread-count
mode.u.notif-kind notif-index
[time-sent index]:post.node
?. ?| =(desc %mention)
(~(has in watching) [rid parent])
==
~
=/ =contents:store
[%graph (limo post.node ~)]
~[(add-unread notif-index [time-sent.post.node %.n contents])]
::
++ is-mention
|= contents=(list content:post)
^- ?
?. mentions %.n
?~ contents %.n
?. ?=(%mention -.i.contents)
$(contents t.contents)
?: =(our.bowl ship.i.contents)
%.y
$(contents t.contents)
::
++ self-post
|= $: =node:graph-store
=index:store
mode=?(%count %each)
watch=?
==
^- (quip card _state)
=| cards=(list card)
=? cards ?=(%count mode)
:_ cards
(poke-hark %read-count index)
?. &(watch watch-on-self)
[cards state]
:- 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=?(%count %each %none) =index:store time=@da ref=index:graph-store]
?- mode
%count ~[(poke-hark %unread-count index time)]
%each ~[(poke-hark %unread-each index ref time)]
%none ~
==
::
++ add-unread
|= [=index:store =notification:store]
(poke-hark %add-note index notification)
::
--
=/ group=(unit resource)
(group-from-app-resource:met %graph rid)
?~ group
~& no-group+rid
`state
=/ metadata=(unit metadata:metadata-store)
(peek-metadata:met %graph u.group rid)
?~ metadata `state
abet:check:(abed:handle-update:ha rid nodes u.group module.u.metadata)
--
::
++ on-peek on-peek:def
@ -334,4 +243,160 @@
==
++ on-fail on-fail:def
--
::
|_ =bowl:gall
::
::
++ give
|= [paths=(list path) =update:hook]
^- (list card)
[%give %fact paths hark-graph-hook-update+!>(update)]~
::
++ watch-graph
^- card
[%pass /graph %agent [our.bowl %graph-store] %watch /updates]
::
++ poke-hark
|= =action:store
^- card
=- [%pass / %agent [our.bowl %hark-store] %poke -]
hark-action+!>(action)
::
++ is-mention
|= contents=(list content:post)
^- ?
?. mentions %.n
?~ contents %.n
?. ?=(%mention -.i.contents)
$(contents t.contents)
?: =(our.bowl ship.i.contents)
%.y
$(contents t.contents)
::
++ handle-update
|_ $: rid=resource :: input
updates=(list node:graph-store)
group=resource
module=term
hark-pokes=(list action:store) :: output
new-watches=(list index:graph-store)
==
++ update-core .
::
++ abed
|= [r=resource upds=(list node:graph-store) grp=resource mod=term]
update-core(rid r, updates upds, group grp, module mod)
::
++ get-conversion
^- tube:clay
=+ %^ scry [our now]:bowl
,mark=(unit mark)
/gx/graph-store/graph-mark/(scot %p entity.rid)/[name.rid]/noun
?~ mark
|=(v=vase !>(~))
(scry-conversion [our now]:bowl q.byk.bowl u.mark)
::
++ abet
^- (quip card _state)
:_ state(watching (~(uni in watching) (silt (turn new-watches (lead rid)))))
^- (list card)
%+ welp (turn (flop hark-pokes) poke-hark)
%- zing
%+ turn (flop new-watches)
|=(=index:graph-store (give ~[/updates] [%listen rid index]))
::
++ hark
|= =action:store
^+ update-core
update-core(hark-pokes [action hark-pokes])
::
++ new-watch
|= =index:graph-store
update-core(new-watches [index new-watches])
::
++ check
|- ^+ update-core
?~ updates
update-core
=/ core=_update-core
(check-node i.updates)
=. updates.core t.updates
$(update-core core)
::
++ check-node-children
|= =node:graph-store
^+ update-core
?: ?=(%empty -.children.node)
update-core
=/ children=(list [=atom =node:graph-store])
(tap:orm:graph-store p.children.node)
|- ^+ update-core
?~ children
update-core
=. update-core (check-node node.i.children)
$(children t.children)
::
++ check-node
|= =node:graph-store
^+ update-core
=. update-core (check-node-children node)
=+ !< notif-kind=(unit notif-kind)
(get-conversion !>([0 post.node]))
?~ notif-kind
update-core
=/ desc=@t
?: (is-mention contents.post.node)
%mention
name.u.notif-kind
=* not-kind u.notif-kind
=/ parent=index:post
(scag parent-lent.not-kind index.post.node)
=/ notif-index=index:store
[%graph group rid module desc parent]
?: =(our.bowl author.post.node)
(self-post node notif-index [mode watch]:not-kind)
=. update-core
(update-unread-count not-kind notif-index [time-sent index]:post.node)
=? update-core
?| =(desc %mention)
(~(has in watching) [rid parent])
==
=/ =contents:store
[%graph (limo post.node ~)]
(add-unread notif-index [time-sent.post.node %.n contents])
update-core
::
++ update-unread-count
|= [=notif-kind =index:store time=@da ref=index:graph-store]
=/ =stats-index:store
(to-stats-index:store index)
?- mode.notif-kind
%count (hark %unread-count stats-index time)
%each (hark %unread-each stats-index ref time)
%none update-core
==
::
++ self-post
|= $: =node:graph-store
=index:store
mode=?(%count %each %none)
watch=?
==
^+ update-core
?: ?=(%none mode) update-core
=/ =stats-index:store
(to-stats-index:store index)
=. update-core
(hark %seen-index time-sent.post.node stats-index)
=? update-core ?=(%count mode)
(hark %read-count stats-index)
=? update-core &(watch watch-on-self)
(new-watch index.post.node)
update-core
::
++ add-unread
|= [=index:store =notification:store]
(hark %add-note index notification)
::
--
--

View File

@ -10,7 +10,7 @@
:: Usefull for non-linear, low-volume applications, i.e. blogs,
:: collections
::
/- post, group-store, metadata-store
/- post, group-store, metadata-store, store=hark-store
/+ resource, metadata, default-agent, dbug, graph-store, graphl=graph, verb, store=hark-store
::
::
@ -19,29 +19,31 @@
+$ card card:agent:gall
+$ versioned-state
$% state:state-zero:store
state-1
state:state-one:store
state-2
==
+$ unread-stats
[indices=(set index:graph-store) last=@da]
::
+$ state-1
$: %1
unreads-each=(jug index:store index:graph-store)
unreads-count=(map index:store @ud)
last-seen=(map index:store @da)
+$ state-2
$: %2
unreads-each=(jug stats-index:store index:graph-store)
unreads-count=(map stats-index:store @ud)
last-seen=(map stats-index:store @da)
=notifications:store
archive=notifications:store
current-timebox=@da
dnd=_|
==
::
+$ inflated-state
$: state-1
$: state-2
cache
==
:: $cache: useful to have precalculated, but can be derived from state
:: albeit expensively
+$ cache
$: by-index=(jug index:store @da)
$: by-index=(jug stats-index:store @da)
~
==
::
@ -76,15 +78,30 @@
=| cards=(list card)
|^
?- -.old
%2
:- cards
this(-.state old, +.state (inflate-cache:ha old))
::
%1
[cards this(+.state (inflate-cache:ha old), -.state old)]
%_ $
::
old
%* . *state-2
unreads-each ((convert-unread ,(set index:graph-store)) uni-by unreads-each.old)
unreads-count ((convert-unread ,@ud) add unreads-count.old)
last-seen ((convert-unread ,@da) max last-seen.old)
notifications notifications.old
archive archive.old
current-timebox current-timebox.old
dnd dnd.old
==
==
::
%0
%_ $
::
old
%* . *state-1
%* . *state:state-one:store
notifications (convert-notifications-1 notifications.old)
archive (convert-notifications-1 archive.old)
current-timebox current-timebox.old
@ -92,6 +109,28 @@
==
==
==
:: discard publish edits
++ uni-by
|= [a=(set index:graph-store) b=(set index:graph-store)]
=/ merged
(~(uni in a) b)
%- ~(gas in *(set index:graph-store))
%+ skip ~(tap in merged)
|=(=index:graph-store &(=((lent index) 3) !=(-:(flop index) 1)))
::
++ convert-unread
|* value=mold
|= [combine=$-([value value] value) unreads=(map index:store value)]
^- (map stats-index:store value)
%+ roll
~(tap in unreads)
|= [[=index:store val=value] out=(map stats-index:store value)]
=/ old=value
(~(gut by unreads) index (combine))
=/ =stats-index:store
(to-stats-index:store index)
(~(put by out) stats-index (combine old val))
::
++ convert-notifications-1
|= old=notifications:state-zero:store
%+ gas:orm *notifications:store
@ -159,48 +198,35 @@
^- update:store
:- %more
^- (list update:store)
:+ give-unreads
[%set-dnd dnd]
%+ weld
%+ turn
(tap-nonempty:ha archive)
(timebox-update &)
%+ turn
(tap-nonempty:ha notifications)
(timebox-update |)
:- give-unreads
[%set-dnd dnd]~
::
++ give-since-unreads
^- (list [index:store index-stats:store])
^- (list [stats-index:store stats:store])
%+ turn
~(tap by unreads-count)
|= [=index:store count=@ud]
?> ?=(%graph -.index)
:* index
~(wyt in (~(gut by by-index) index ~))
|= [=stats-index:store count=@ud]
?> ?=(%graph -.stats-index)
:* stats-index
~(wyt in (~(gut by by-index) stats-index ~))
[%count count]
(~(gut by last-seen) index *time)
(~(gut by last-seen) stats-index *time)
==
++ give-each-unreads
^- (list [index:store index-stats:store])
^- (list [stats-index:store stats:store])
%+ turn
~(tap by unreads-each)
|= [=index:store indices=(set index:graph-store)]
:* index
~(wyt in (~(gut by by-index) index ~))
|= [=stats-index:store indices=(set index:graph-store)]
:* stats-index
~(wyt in (~(gut by by-index) stats-index ~))
[%each indices]
(~(gut by last-seen) index *time)
(~(gut by last-seen) stats-index *time)
==
::
++ give-unreads
^- update:store
:- %unreads
(weld give-each-unreads give-since-unreads)
::
++ timebox-update
|= archived=?
|= [time=@da =timebox:store]
^- update:store
[%timebox time archived ~(tap by timebox)]
--
::
++ on-peek
@ -238,209 +264,14 @@
=^ cards state
?+ mark (on-poke:def mark vase)
%hark-action (hark-action !<(action:store vase))
%noun ~& +.state [~ state]
%noun ~& +.state [~ state]
==
[cards this]
::
++ hark-action
|= =action:store
^- (quip card _state)
|^
?- -.action
%add-note (add-note +.action)
%archive (do-archive +.action)
::
%read-each (read-each +.action)
%unread-each (unread-each +.action)
::
%read-count (read-count +.action)
%unread-count (unread-count +.action)
::
%read-note (read-note +.action)
%unread-note (unread-note +.action)
::
%read-all read-all
::
%set-dnd (set-dnd +.action)
%seen seen
==
::
++ add-note
|= [=index:store =notification:store]
^- (quip card _state)
=/ =timebox:store
(gut-orm:ha notifications current-timebox)
=/ existing-notif
(~(get by timebox) index)
=/ new=notification:store
?~ existing-notif
notification
(merge-notification:ha u.existing-notif notification)
=/ new-read=?
?~ existing-notif
%.y
read.u.existing-notif
=. read.new %.n
=/ new-timebox=timebox:store
(~(put by timebox) index new)
:- (give:ha [/updates]~ %added current-timebox index new)
%_ state
+ ?.(new-read +.state (upd-unreads:ha index current-timebox %.n))
notifications (put:orm notifications current-timebox new-timebox)
==
::
++ do-archive
|= [time=@da =index:store]
^- (quip card _state)
=/ =timebox:store
(gut-orm:ha notifications time)
=/ =notification:store
(~(got by timebox) index)
=/ new-timebox=timebox:store
(~(del by timebox) index)
:- (give:ha [/updates]~ %archive time index)
%_ state
+ ?.(read.notification (upd-unreads:ha index time %.y) +.state)
::
notifications
(put:orm notifications time new-timebox)
::
archive
%^ jub-orm:ha archive time
|= archive-box=timebox:store
^- timebox:store
(~(put by archive-box) index notification(read %.y))
==
::
++ 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]
^- (quip card _state)
:- (give:ha [/updates]~ %read-note time index)
%_ state
+ (upd-unreads:ha index time %.y)
notifications (change-read-status:ha time index %.y)
==
::
++ unread-note
|= [time=@da =index:store]
^- (quip card _state)
:- (give:ha [/updates]~ %unread-note time index)
%_ state
+ (upd-unreads:ha index time %.n)
notifications (change-read-status:ha time index %.n)
==
::
++ read-count
|= =index:store
^- (quip card _state)
=^ cards state
(read-index index)
:- %+ weld cards
(give:ha [/updates]~ %read-count index)
%_ state
unreads-count (~(put by unreads-count) index 0)
==
::
++ read-boxes
|= [boxes=(set @da) =index:store]
^- (quip card _state)
=/ boxes=(list @da)
~(tap in boxes)
=| crds=(list card)
|-
?~ boxes [crds state]
=* box i.boxes
=^ cards state
(read-note box index)
$(boxes t.boxes, crds (welp crds cards))
::
++ read-index
|= =index:store
^- (quip card _state)
=/ boxes=(set @da)
(~(get ju by-index) index)
=^ cards state
(read-boxes boxes index)
:_ state
%+ welp cards
(give:ha ~[/updates] %read-index index)
::
++ read-all
^- (quip card _state)
`state
::
++ unread-count
|= [=index:store time=@da]
^- (quip card _state)
:- (give:ha [/updates]~ %unread-count index time)
=/ curr=@ud
(~(gut by unreads-count) index 0)
%_ state
last-seen (~(put by last-seen) index time)
unreads-count (~(put by unreads-count) index +(curr))
==
::
++ seen
^- (quip card _state)
:_ state(current-timebox now.bowl)
:~ cancel-autoseen:ha
autoseen-timer:ha
==
::
++ set-dnd
|= d=?
^- (quip card _state)
:_ state(dnd d)
(give:ha [/updates]~ %set-dnd d)
--
abet:translate:(abed:poke-engine:ha action)
--
::
++ on-agent on-agent:def
@ -459,35 +290,291 @@
--
|_ =bowl:gall
+* met ~(. metadata bowl)
++ poke-engine
|_ [in=action:store out=(list update:store) cards=(list card)]
++ poke-core .
::
++ abed
|= =action:store poke-core(in action)
::
++ abet
^- (quip card _state)
:_ state
%+ snoc (flop cards)
[%give %fact ~[/updates] %hark-update !>([%more (flop out)])]
::
++ give
|= =update:store poke-core(out [update out])
::
++ emit
|= =card poke-core(cards [card cards])
::
++ translate
^+ poke-core
?+ -.in poke-core
::
%add-note (add-note +.in)
%archive (do-archive +.in)
::
%unread-count (unread-count +.in)
%read-count (read-count +.in)
::
%read-each (read-each +.in)
%unread-each (unread-each +.in)
::
%read-note (read-note +.in)
%unread-note (unread-note +.in)
::
%seen-index (seen-index +.in)
%remove-graph (remove-graph +.in)
%set-dnd (set-dnd +.in)
%seen seen
==
::
:: +| %note
::
:: notification tracking
++ upd-cache
|= [read=? time=@da =index:store]
poke-core(+.state (^upd-cache read time index))
::
++ put-notifs
|= [time=@da =timebox:store]
poke-core(notifications (put:orm notifications time timebox))
::
++ add-note
|= [=index:store =notification:store]
^+ poke-core
=/ =timebox:store
(gut-orm notifications current-timebox)
=/ existing-notif
(~(get by timebox) index)
=/ new=notification:store
(merge-notification existing-notif notification)
=/ new-read=?
?~ existing-notif %.y
read.u.existing-notif
=/ new-timebox=timebox:store
(~(put by timebox) index new)
=. poke-core (put-notifs current-timebox new-timebox)
=? poke-core new-read
(upd-cache %.n current-timebox index)
(give %added current-timebox index new)
::
++ do-archive
|= [time=@da =index:store]
^+ poke-core
=/ =timebox:store
(gut-orm notifications time)
=/ =notification:store
(~(got by timebox) index)
=/ new-timebox=timebox:store
(~(del by timebox) index)
=? poke-core !read.notification
(upd-cache %.y time index)
=. poke-core
(put-notifs time new-timebox)
=. archive
%^ jub-orm archive time
|= archive-box=timebox:store
(~(put by archive-box) index notification(read %.y))
(give %archive time index)
::
++ change-read-status
|= [time=@da =index:store read=?]
=. poke-core (upd-cache read time index)
%_ poke-core
notifications
%^ jub-orm notifications time
|= =timebox:store
%+ ~(jab by timebox) index
|= n=notification:store
?>(!=(read read.n) n(read read))
==
::
++ read-note
|= [time=@da =index:store]
%. [%read-note time index]
give:(change-read-status time index %.y)
::
++ unread-note
|= [time=@da =index:store]
%. [%unread-note time index]
give:(change-read-status time index %.n)
::
:: +| %each
::
:: each unread tracking
::
++ unread-each
|= [=stats-index:store unread=index:graph-store time=@da]
=. poke-core (seen-index time stats-index)
%+ jub-unreads-each:(give %unread-each stats-index unread time)
stats-index
|= indices=(set index:graph-store)
(~(put ^in indices) unread)
::
++ read-index-each
|= [=stats-index:store ref=index:graph-store]
%+ read-index stats-index
%+ skim
~(tap ^in (~(get ju by-index) stats-index))
|= time=@da
=/ =timebox:store
(gut-orm notifications time)
%+ roll
~(tap ^in timebox)
|= [[=index:store not=notification:store] out=?]
?: out out
?. (stats-index-is-index:store stats-index index) out
?. ?=(%graph -.index) out
?. ?=(%graph -.contents.not) out
(lien list.contents.not |=(p=post:post =(index.p ref)))
::
++ read-each
|= [=stats-index:store ref=index:graph-store]
=. poke-core (read-index-each stats-index ref)
%+ jub-unreads-each:(give %read-each stats-index ref)
stats-index
|= indices=(set index:graph-store)
(~(del ^in indices) ref)
::
++ jub-unreads-each
|= $: =stats-index:store
f=$-((set index:graph-store) (set index:graph-store))
==
poke-core(unreads-each (jub stats-index f))
::
++ unread-count
|= [=stats-index:store time=@da]
=/ new-count
+((~(gut by unreads-count) stats-index 0))
=. unreads-count
(~(put by unreads-count) stats-index new-count)
(seen-index:(give %unread-count stats-index time) time stats-index)
::
++ read-count
|= =stats-index:store
=. unreads-count (~(put by unreads-count) stats-index 0)
=/ times=(list @da)
~(tap ^in (~(get ju by-index) stats-index))
(give:(read-index stats-index times) %read-count stats-index)
::
++ read-index
|= [=stats-index:store times=(list @da)]
|-
?~ times poke-core
=/ core
(read-stats-index i.times stats-index)
$(poke-core core, times t.times)
::
++ read-stats-index
|= [time=@da =stats-index:store]
=/ keys
~(tap ^in ~(key by (gut-orm notifications time)))
|- ^+ poke-core
?~ keys
poke-core
?. (stats-index-is-index:store stats-index i.keys)
$(keys t.keys)
=/ core
(read-note time i.keys)
$(poke-core core, keys t.keys)
::
++ seen-index
|= [time=@da =stats-index:store]
=/ new-time=@da
(max time (~(gut by last-seen) stats-index 0))
=. last-seen
(~(put by last-seen) stats-index new-time)
(give %seen-index new-time stats-index)
::
++ remove-graph
|= rid=resource
|^
=/ indices get-stats-indices
=. poke-core
(give %remove-graph rid)
=. poke-core
(remove-notifications indices)
=. unreads-count
((dif-map-by-key ,@ud) unreads-count indices)
=. unreads-each
%+ (dif-map-by-key ,(set index:graph-store))
unreads-each indices
=. last-seen
((dif-map-by-key ,@da) last-seen indices)
=. by-index
((dif-map-by-key ,(set @da)) by-index indices)
poke-core
::
++ get-stats-indices
%- ~(gas ^in *(set stats-index:store))
%+ skim
;: weld
~(tap ^in ~(key by unreads-count))
~(tap ^in ~(key by last-seen))
~(tap ^in ~(key by unreads-each))
~(tap ^in ~(key by by-index))
==
|= =stats-index:store
?. ?=(%graph -.stats-index) %.n
=(graph.stats-index rid)
::
++ dif-map-by-key
|* value=mold
|= [=(map stats-index:store value) =(set stats-index:store)]
=/ to-remove ~(tap ^in set)
|-
?~ to-remove map
=. map
(~(del by map) i.to-remove)
$(to-remove t.to-remove)
::
++ remove-notifications
|= =(set stats-index:store)
^+ poke-core
=/ indices
~(tap ^in set)
|-
?~ indices poke-core
=/ times=(list @da)
~(tap ^in (~(get ju by-index) i.indices))
=. poke-core
(read-index i.indices times)
$(indices t.indices)
--
::
++ seen
=> (emit cancel-autoseen)
=> (emit autoseen-timer)
poke-core(current-timebox now.bowl)
::
++ set-dnd
|= d=?
(give:poke-core(dnd d) %set-dnd d)
--
::
++ merge-notification
|= [existing=notification:store new=notification:store]
|= [existing=(unit notification:store) new=notification:store]
^- notification:store
?- -.contents.existing
?~ existing new
?- -.contents.u.existing
::
%graph
?> ?=(%graph -.contents.new)
existing(read %.n, list.contents (weld list.contents.existing list.contents.new))
u.existing(read %.n, list.contents (weld list.contents.u.existing list.contents.new))
::
%group
?> ?=(%group -.contents.new)
existing(read %.n, list.contents (weld list.contents.existing list.contents.new))
u.existing(read %.n, list.contents (weld list.contents.u.existing list.contents.new))
==
::
++ change-read-status
|= [time=@da =index:store read=?]
^+ notifications
%^ jub-orm notifications time
|= =timebox:store
%+ ~(jab by timebox) index
|= =notification:store
?> !=(read read.notification)
notification(read read)
:: +key-orm: +key:by for ordered maps
++ key-orm
|= =notifications:store
^- (list @da)
(turn (tap:orm notifications) |=([key=@da =timebox:store] key))
(turn (tap:orm notifications) |=([@da *] +<-))
:: +jub-orm: combo +jab/+gut for ordered maps
:: TODO: move to zuse.hoon
++ jub-orm
@ -496,6 +583,12 @@
=/ =timebox:store
(fun (gut-orm notifications time))
(put:orm notifications time timebox)
++ jub
|= [=stats-index:store f=$-((set index:graph-store) (set index:graph-store))]
^- (jug stats-index:store index:graph-store)
=/ val=(set index:graph-store)
(~(gut by unreads-each) stats-index ~)
(~(put by unreads-each) stats-index (f val))
:: +gut-orm: +gut:by for ordered maps
:: TODO: move to zuse.hoon
++ gut-orm
@ -523,47 +616,32 @@
^- (list card)
[%give %fact paths [%hark-update !>(update)]]~
::
++ upd-unreads
|= [=index:store time=@da read=?]
^+ +.state
%_ +.state
::
by-index
%. [index time]
?: read
~(del 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))
::
++ upd-cache
|= [read=? time=@da =index:store]
^+ +.state
%_ +.state
::
by-index
%. [(to-stats-index:store index) time]
?: read
~(del ju by-index)
~(put ju by-index)
==
::
++ inflate-cache
|= state-1
|= state-2
^+ +.state
=/ nots=(list [p=@da =timebox:store])
(tap:orm notifications)
|- =* outer $
?~ nots
+.state
?~ nots +.state
=/ unreads ~(tap by timebox.i.nots)
|- =* inner $
?~ unreads
@ -573,6 +651,6 @@
?: read.notification
inner(unreads t.unreads)
=. +.state
(upd-unreads index p.i.nots %.n)
(upd-cache %.n p.i.nots index)
inner(unreads t.unreads)
--

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.47da5d209b0117cee942.js"></script>
<script src="/~landscape/js/bundle/index.2ddb586104e8758c6863.js"></script>
</body>
</html>

View File

@ -392,17 +392,20 @@
++ handle-stop-thread
|= [=tid nice=?]
^- (quip card ^state)
=/ =yarn (~(got by tid.state) tid)
?: (has-yarn running.state yarn)
=/ yarn=(unit yarn) (~(get by tid.state) tid)
?~ yarn
~& %stopping-nonexistent-thread
[~ state]
?: (has-yarn running.state u.yarn)
?: nice
(thread-done yarn *vase)
(thread-fail yarn %cancelled ~)
?: (~(has by starting.state) yarn)
(thread-done u.yarn *vase)
(thread-fail u.yarn %cancelled ~)
?: (~(has by starting.state) u.yarn)
(thread-fail-not-running tid %stopped-before-started ~)
~& [%thread-not-started yarn]
~& [%thread-not-started u.yarn]
?: nice
(thread-done yarn *vase)
(thread-fail yarn %cancelled ~)
(thread-done u.yarn *vase)
(thread-fail u.yarn %cancelled ~)
::
++ take-input
|= [=yarn input=(unit input:strand)]

View File

@ -17,6 +17,15 @@
%+ scry-for update:store
/graph/(scot %p entity.res)/[name.res]
::
++ get-graph-mop
|= res=resource
^- graph:store
=/ =update:store
(get-graph res)
?> ?=(%0 -.update)
?> ?=(%add-graph -.q.update)
graph.q.update
::
++ gut-younger-node-siblings
|= [res=resource =index:store]
^- (map index:store node:store)

View File

@ -27,6 +27,17 @@
description+so
index+(su ;~(pfix fas (more fas dem)))
==
::
++ stats-index
%- of
:~ graph+graph-stats-index
group+dejs-path:resource
==
++ graph-stats-index
%- ot
:~ graph+dejs-path:resource
index+graph-store-index
==
:: parse date as @ud
:: TODO: move to zuse
++ sd
@ -41,6 +52,8 @@
:~ time+sd
index+index
==
++ graph-store-index
(su ;~(pfix fas (more fas dem)))
::
++ add
|= jon=json
@ -48,8 +61,8 @@
::
++ read-graph-index
%- ot
:~ index+index
target+(su ;~(pfix fas (more fas dem)))
:~ index+stats-index
target+graph-store-index
==
::
++ action
@ -61,7 +74,7 @@
read-note+notif-ref
add-note+add
set-dnd+bo
read-count+index
read-count+stats-index
read-each+read-graph-index
==
--
@ -81,25 +94,44 @@
%count (numb count.upd)
%more (more +.upd)
%read-each (read-each +.upd)
%read-count (index +.upd)
%read-count (stats-index +.upd)
%unread-each (unread-each +.upd)
%unread-count (unread-count +.upd)
%remove-graph s+(enjs-path:resource +.upd)
%seen-index (seen-index +.upd)
%unreads (unreads +.upd)
::
?(%archive %read-note %unread-note)
(notif-ref +.upd)
==
::
++ stats-index
|= s=^stats-index
%+ frond -.s
|^
?- -.s
%graph (graph-stats-index +.s)
%group s+(enjs-path:resource +.s)
==
::
++ graph-stats-index
|= [graph=resource =index:graph-store]
%- pairs
:~ graph+s+(enjs-path:resource graph)
index+(index:enjs:graph-store index)
==
--
::
++ unreads
|= l=(list [^index ^index-stats])
|= l=(list [^stats-index ^stats])
^- json
:- %a
^- (list json)
%+ turn l
|= [idx=^index stats=^index-stats]
|= [idx=^stats-index s=^stats]
%- pairs
:~ stats+(index-stats stats)
index+(index idx)
:~ stats+(stats s)
index+(stats-index idx)
==
::
++ unread
@ -113,13 +145,13 @@
(numb num.unreads)
==
::
++ index-stats
|= stats=^index-stats
++ stats
|= s=^stats
^- json
%- pairs
:~ unreads+(unread unreads.stats)
notifications+(numb notifications.stats)
last+(time last-seen.stats)
:~ unreads+(unread unreads.s)
notifications+(numb notifications.s)
last+(time last-seen.s)
==
++ added
|= [tim=@da idx=^index not=^notification]
@ -137,6 +169,13 @@
:~ time+s+(scot %ud tim)
index+(index idx)
==
++ seen-index
|= [tim=@da idx=^stats-index]
^- json
%- pairs
:~ time+(time tim)
index+(stats-index idx)
==
::
++ more
|= upds=(list ^update)
@ -236,26 +275,45 @@
==
::
++ read-each
|= [=^index target=index:graph-store]
|= [s=^stats-index target=index:graph-store]
%- pairs
:~ index+(^index index)
:~ index+(stats-index s)
target+(index:enjs:graph-store target)
==
::
++ unread-each
|= [=^index target=index:graph-store tim=@da]
|= [s=^stats-index target=index:graph-store tim=@da]
%- pairs
:~ index+(^index index)
:~ index+(stats-index s)
target+(index:enjs:graph-store target)
last+(time tim)
==
::
++ unread-count
|= [=^index tim=@da]
|= [s=^stats-index tim=@da]
%- pairs
:~ index+(^index index)
:~ index+(stats-index s)
last+(time tim)
==
--
--
::
++ to-stats-index
|= =index
^- stats-index
?- -.index
%graph [%graph graph.index index.index]
%group [%group group.index]
==
++ stats-index-is-index
|= [=stats-index =index]
?- -.index
%graph
?. ?=(%graph -.stats-index) %.n
=([graph index]:index [graph index]:stats-index)
::
%group
?. ?=(%group -.stats-index) %.n
=(group:index group:stats-index)
==
--

View File

@ -564,7 +564,12 @@
::
++ se-klin :: disconnect app
|= gyl=gill:gall
+>(eel (~(del in eel) gyl))
=/ gil=(unit gill:gall) se-agon
=. eel (~(del in eel) gyl)
?~ gil +>.$
?: =(gyl u.gil)
+>.$(inx 0)
(se-alas u.gil)
::
++ se-link :: connect to app
|= gyl=gill:gall
@ -788,6 +793,7 @@
?- fec
[%bel *] ta-bel
[%blk *] +>
[%bye *] +>(..ta (se-klin gyl))
[%clr *] +>(..ta (se-blit fec))
[%det *] (ta-got +.fec)
[%err *] (ta-err p.fec)

View File

@ -268,7 +268,6 @@
%+ weld
(push-updates:hc q.cage.sign)
cards
==
++ on-leave
|= =path
@ -374,6 +373,8 @@
=/ prefix=path
resource+(en-path:resource u.rid)
=/ paths=(list path)
%~ tap in
%- silt
%+ turn
(incoming-subscriptions prefix)
|=([ship pax=path] pax)

View File

@ -7,7 +7,7 @@
?+ index.p.i ~
[@ ~] `[%link 0 %each %.y]
[@ @ %1 ~] `[%comment 1 %count %.n]
[@ @ @ ~] `[%edit-comment 1 %count %.n]
[@ @ @ ~] `[%edit-comment 1 %none %.n]
==
--
++ grab

View File

@ -9,9 +9,9 @@
++ notification-kind
?+ index.p.i ~
[@ %1 %1 ~] `[%note 0 %each %.n]
[@ %1 @ ~] `[%edit-note 0 %each %.n]
[@ %1 @ ~] `[%edit-note 0 %none %.n]
[@ %2 @ %1 ~] `[%comment 1 %count %.n]
[@ %2 @ @ ~] `[%edit-comment 1 %count %.n]
[@ %2 @ @ ~] `[%edit-comment 1 %none %.n]
==
--
++ grab

View File

@ -75,7 +75,7 @@
info+(tape ~(ram re tank))
==
::
?(%bel %clr %nex)
?(%bel %clr %nex %bye)
(frond %act %s -.sef)
==
--

View File

@ -35,6 +35,20 @@
[date=@da read=? =contents]
--
::
++ state-one
|%
+$ state
$: %1
unreads-each=(jug index index:graph-store)
unreads-count=(map index @ud)
last-seen=(map index @da)
=notifications
archive=notifications
current-timebox=@da
dnd=_|
==
--
::
+$ index
$% $: %graph
group=resource
@ -70,24 +84,33 @@
$% [%add-note =index =notification]
[%archive time=@da index]
::
[%unread-count =index =time]
[%read-count =index]
[%unread-count =stats-index =time]
[%read-count =stats-index]
::
[%unread-each =index ref=index:graph-store time=@da]
[%read-each index ref=index:graph-store]
::
[%unread-each =stats-index ref=index:graph-store time=@da]
[%read-each =stats-index ref=index:graph-store]
::
[%read-note time=@da index]
[%unread-note time=@da index]
::
[%seen-index time=@da =stats-index]
[%remove-graph =resource]
::
[%read-all ~]
[%set-dnd dnd=?]
[%seen ~]
==
::
++ stats-index
$% [%graph graph=resource =index:graph-store]
[%group group=resource]
==
::
+$ indexed-notification
[index notification]
::
+$ index-stats
+$ stats
[notifications=@ud =unreads last-seen=@da]
::
+$ unreads
@ -99,10 +122,9 @@
$% action
[%more more=(list update)]
[%added time=@da =index =notification]
[%read-index =index]
[%read time=@da =index]
[%timebox time=@da archived=? =(list [index notification])]
[%count count=@ud]
[%unreads unreads=(list [index index-stats])]
[%clear =stats-index]
[%unreads unreads=(list [stats-index stats])]
==
--

View File

@ -30,6 +30,7 @@
+$ sole-effect :: app to sole
$% [%bel ~] :: beep
[%blk p=@ud q=@c] :: blink+match char at
[%bye ~] :: close session
[%clr ~] :: clear screen
[%det sole-change] :: edit command
[%err p=@ud] :: error point

View File

@ -8363,7 +8363,7 @@
[%bust *] ~(example ax %base p.gen)
[%ktcl *] ~(factory ax p.gen)
[%dbug *] q.gen
[%eror *] ~>(%slog.[0 leaf=p.gen] !!)
[%eror *] ~_((crip p.gen) !!)
::
[%knit *] ::
:+ %tsgr [%ktts %v %$ 1] :: => v=.
@ -10679,6 +10679,7 @@
==
:: ::
++ redo :: refurbish faces
~/ %redo
|= $: :: ref: raw payload
::
ref=type

View File

@ -871,12 +871,12 @@
?^ dud
?+ -.task
(on-crud:event-core -.task tang.u.dud)
%hear (on-hole:event-core [lane blob]:task)
%hear (on-hear:event-core lane.task blob.task dud)
==
::
?- -.task
%born on-born:event-core
%hear (on-hear:event-core [lane blob]:task)
%hear (on-hear:event-core [lane blob ~]:task)
%heed (on-heed:event-core ship.task)
%init on-init:event-core
%jilt (on-jilt:event-core ship.task)
@ -1194,15 +1194,15 @@
=/ =channel [[our ship] now channel-state -.peer-state]
abet:on-jilt:(make-peer-core peer-state channel)
:: +on-hear: handle raw packet receipt
:: +on-hole: handle packet crash notification
::
++ on-hear |=([l=lane b=blob] (on-hear-packet l (decode-packet b) ok=&))
++ on-hole |=([l=lane b=blob] (on-hear-packet l (decode-packet b) ok=|))
++ on-hear
|= [l=lane b=blob d=(unit goof)]
(on-hear-packet l (decode-packet b) d)
:: +on-hear-packet: handle mildly processed packet receipt
::
++ on-hear-packet
~/ %on-hear-packet
|= [=lane =packet ok=?]
|= [=lane =packet dud=(unit goof)]
^+ event-core
::
?: =(our sndr.packet)
@ -1226,7 +1226,7 @@
::
++ on-hear-forward
~/ %on-hear-forward
|= [=lane =packet ok=?]
|= [=lane =packet dud=(unit goof)]
^+ event-core
%- %^ trace for.veb sndr.packet
|.("forward: {<sndr.packet>} -> {<rcvr.packet>}")
@ -1246,7 +1246,7 @@
::
++ on-hear-open
~/ %on-hear-open
|= [=lane =packet ok=?]
|= [=lane =packet dud=(unit goof)]
^+ event-core
:: assert the comet can't pretend to be a moon or other address
::
@ -1283,7 +1283,7 @@
::
++ on-hear-shut
~/ %on-hear-shut
|= [=lane =packet ok=?]
|= [=lane =packet dud=(unit goof)]
^+ event-core
=/ sndr-state (~(get by peers.ames-state) sndr.packet)
:: if we don't know them, maybe enqueue a jael %public-keys request
@ -1338,7 +1338,7 @@
:: perform peer-specific handling of packet
::
=/ peer-core (make-peer-core peer-state channel)
abet:(on-hear-shut-packet:peer-core lane shut-packet ok)
abet:(on-hear-shut-packet:peer-core lane shut-packet dud)
:: +on-take-boon: receive request to give message to peer
::
++ on-take-boon
@ -1373,7 +1373,7 @@
|. ^- tape
=/ sndr [our our-life.channel]
=/ rcvr [ship her-life.channel]
"plea {<sndr^rcvr^bone^vane.plea^path.plea>}"
"plea {<sndr^rcvr^bone=bone^vane.plea^path.plea>}"
::
abet:(on-memo:(make-peer-core peer-state channel) bone plea %plea)
:: +on-take-wake: receive wakeup or error notification from behn
@ -1897,7 +1897,7 @@
:: +on-hear-shut-packet: handle receipt of ack or message fragment
::
++ on-hear-shut-packet
|= [=lane =shut-packet ok=?]
|= [=lane =shut-packet dud=(unit goof)]
^+ peer-core
:: update and print connection status
::
@ -1906,12 +1906,15 @@
=/ =bone bone.shut-packet
::
?: ?=(%& -.meat.shut-packet)
(run-message-sink bone %hear lane shut-packet ok)
:: ignore .ok for |message-pump; just try again on error
(run-message-sink bone %hear lane shut-packet ?=(~ dud))
:: Just try again on error, printing trace
::
:: Note this implies that vanes should never crash on %done,
:: since we have no way to continue using the flow if they do.
::
=+ ?~ dud ~
%. ~
(slog leaf+"ames: crashed on message ack" >mote.u.dud< tang.u.dud)
(run-message-pump bone %hear [message-num +.meat]:shut-packet)
:: +on-memo: handle request to send message
::
@ -2197,11 +2200,12 @@
?. ?=([%hear * * ok=%.n] task)
:: fresh boon; give message to client vane
::
%- (trace msg.veb |.("boon {<her.channel^bone -.task>}"))
%- (trace msg.veb |.("boon {<her.channel^bone=bone -.task>}"))
peer-core
:: we previously crashed on this message; notify client vane
::
%- (trace msg.veb |.("crashed on boon {<her.channel^bone -.task>}"))
%- %+ trace msg.veb
|.("crashed on boon {<her.channel^bone=bone -.task>}")
boon-to-lost
:: +boon-to-lost: convert all boons to losts
::
@ -2219,7 +2223,7 @@
++ on-sink-nack-trace
|= [=message-num message=*]
^+ peer-core
%- (trace msg.veb |.("nack trace {<her.channel^bone>}"))
%- (trace msg.veb |.("nack trace {<her.channel^bone=bone>}"))
::
=+ ;; =naxplanation message
:: ack nack-trace message (only applied if we don't later crash)
@ -2236,7 +2240,7 @@
++ on-sink-plea
|= [=message-num message=*]
^+ peer-core
%- (trace msg.veb |.("plea {<her.channel^bone>}"))
%- (trace msg.veb |.("plea {<her.channel^bone=bone>}"))
:: is this the first time we're trying to process this message?
::
?. ?=([%hear * * ok=%.n] task)
@ -2346,7 +2350,8 @@
:: ignore duplicate message acks
::
?: (lth message-num current.state)
%- (trace snd.veb |.("duplicate done {<current.state message-num>}"))
%- %+ trace snd.veb
|.("duplicate done {<current=current.state message-num=message-num>}")
message-pump
:: ignore duplicate and future acks
::
@ -2381,6 +2386,19 @@
::
=. queued-message-acks.state
(~(del by queued-message-acks.state) current.state)
:: clear all packets from this message from the packet pump
::
:: Note we did this when the original packet came in, a few lines
:: above. It's not clear why, but it doesn't always clear the
:: packets when it's not the current message. As a workaround,
:: we clear the packets again when we catch up to this packet.
::
:: This is slightly inefficient because we run this twice for
:: each packet and it may emit a few unnecessary packets, but
:: but it's not incorrect. pump-metrics are updated only once,
:: at the time when we actually delete the packet.
::
=. message-pump (run-packet-pump %done current.state lag=*@dr)
:: give %done to vane if we're ready
::
?- -.u.cur
@ -2646,7 +2664,7 @@
=(0 (mod counter.metrics.state 20))
==
same
(trace snd.veb |.("{<[fragment-num show:gauge]>}"))
(trace snd.veb |.("send: {<[fragment=fragment-num show:gauge]>}"))
:: .resends is backward, so fold backward and emit
::
=. packet-pump
@ -2705,7 +2723,7 @@
=- =. metrics.state metrics.-
=. live.state live.-
::
%- (trace snd.veb |.("done {<message-num^show:gauge>}"))
%- (trace snd.veb |.("done {<message-num=message-num^show:gauge>}"))
(fast-resend-after-ack message-num `fragment-num`0)
::
^+ [metrics=metrics.state live=live.state]
@ -2936,7 +2954,8 @@
:: ignore messages from far future; limit to 10 in progress
::
?: (gte seq (add 10 last-acked.state))
%- (trace odd.veb |.("future %hear {<seq^last-acked.state>}"))
%- %+ trace odd.veb
|.("future %hear {<seq=seq^last-acked=last-acked.state>}")
message-sink
::
=/ is-last-fragment=? =(+(fragment-num) num-fragments)
@ -2946,12 +2965,13 @@
?. is-last-fragment
:: single packet ack
::
%- (trace rcv.veb |.("send dupe ack {<seq^fragment-num>}"))
%- %+ trace rcv.veb
|.("send dupe ack {<seq=seq^fragment-num=fragment-num>}")
(give %send seq %& fragment-num)
:: whole message (n)ack
::
=/ ok=? !(~(has in nax.state) seq)
%- (trace rcv.veb |.("send dupe message ack {<seq>} ok={<ok>}"))
%- (trace rcv.veb |.("send dupe message ack {<seq=seq>} ok={<ok>}"))
(give %send seq %| ok lag=`@dr`0)
:: last-acked<seq<=last-heard; heard message, unprocessed
::
@ -2965,15 +2985,18 @@
%- %+ trace rcv.veb
|. ^- tape
=/ data
:* her.channel seq
fragment-num num-fragments
:* her.channel seq=seq
fragment-num=fragment-num num-fragments=num-fragments
la=last-acked.state lh=last-heard.state
==
"hear last in-progress {<data>}"
message-sink
:: ack all other packets
::
%- (trace rcv.veb |.("send ack-1 {<seq^fragment-num^num-fragments>}"))
%- %+ trace rcv.veb |.
=/ data
[seq=seq fragment-num=fragment-num num-fragments=num-fragments]
"send ack-1 {<data>}"
(give %send seq %& fragment-num)
:: last-heard<seq<10+last-heard; this is a packet in a live message
::
@ -2996,10 +3019,12 @@
?: already-heard-fragment
?: is-last-fragment
%- %+ trace rcv.veb |.
=/ data [her.channel seq last-heard.state last-acked.state]
=/ data
[her.channel seq=seq lh=last-heard.state la=last-acked.state]
"hear last dupe {<data>}"
message-sink
%- (trace rcv.veb |.("send dupe ack {<her.channel^seq^fragment-num>}"))
%- %+ trace rcv.veb
|.("send dupe ack {<her.channel^seq=seq^fragment-num=fragment-num>}")
(give %send seq %& fragment-num)
:: new fragment; store in state and check if message is done
::
@ -3014,7 +3039,10 @@
:: ack any packet other than the last one, and continue either way
::
=? message-sink !is-last-fragment
%- (trace rcv.veb |.("send ack-2 {<seq^fragment-num^num-fragments>}"))
%- %+ trace rcv.veb |.
=/ data
[seq=seq fragment-num=fragment-num num-fragments=num-fragments]
"send ack-2 {<data>}"
(give %send seq %& fragment-num)
:: enqueue all completed messages starting at +(last-heard.state)
::
@ -3037,7 +3065,7 @@
=. live-messages.state (~(del by live-messages.state) seq)
::
%- %+ trace msg.veb
|.("hear {<her.channel>} {<seq>} {<num-fragments.u.live>}kb")
|.("hear {<her.channel>} {<seq=seq>} {<num-fragments.u.live>}kb")
=/ message=* (assemble-fragments [num-fragments fragments]:u.live)
=. message-sink (enqueue-to-vane seq message)
::

View File

@ -206,6 +206,8 @@
~> %slog.[0 leaf+"gall: pupa call dud"]
(mean >mote.u.dud< tang.u.dud)
=/ task ((harden task:gall) wrapped-task)
?: ?=(%vega -.task)
[~ pupal-gate]
(molt duct `[duct %slip %g task])
::
++ scry scry:adult-core
@ -367,6 +369,7 @@
%- ~(gas in *(set [care:clay path]))
:* [%z /sys/hoon/hoon]
[%z /sys/arvo/hoon]
[%z /sys/lull/hoon]
[%z /sys/zuse/hoon]
[%z /sys/vane/gall/hoon]
%+ murn ~(tap by yokes.state)
@ -1064,9 +1067,17 @@
:: We convert from cards to duct-indexed moves when resolving
:: them in Arvo.
::
:: We accept %huck to "fake" being a message to a ship but
:: actually send it to a vane.
::
+$ neet
$% neat
[%huck [=ship name=term] =note-arvo]
==
::
++ ap-from-internal
~/ %ap-from-internal
|= card=(wind neat gift:agent)
|= card=(wind neet gift:agent)
^- (list move)
::
?- -.card
@ -1122,16 +1133,19 @@
%pass
=/ =duct system-duct.state
=/ =wire p.card
=/ =neat q.card
=/ =neet q.card
=. wire
?: ?=(%agent -.neat)
[%out (scot %p ship.neat) name.neat wire]
[(scot %p attributing.agent-routes) wire]
?- -.neet
%agent [%out (scot %p ship.neet) name.neet wire]
%huck [%out (scot %p ship.neet) name.neet wire]
%arvo [(scot %p attributing.agent-routes) wire]
==
=. wire [%use agent-name nonce.current-agent wire]
=/ =note-arvo
?- -.neat
%arvo note-arvo.neat
%agent [%g %deal [our ship.neat] [name deal]:neat]
?- -.neet
%arvo note-arvo.neet
%huck note-arvo.neet
%agent [%g %deal [our ship.neet] [name deal]:neet]
==
[duct %pass wire note-arvo]~
==
@ -1308,10 +1322,10 @@
:: +ap-pass: request action.
::
++ ap-pass
|= [=path =neat]
|= [=path =neet]
^+ ap-core
=/ internal-moves
(ap-from-internal %pass path neat)
(ap-from-internal %pass path neet)
ap-core(agent-moves (weld internal-moves agent-moves))
:: +ap-reinstall: reinstall.
::
@ -1525,8 +1539,7 @@
::
=. ap-core
(ap-pass wire %agent dock %leave ~)
=/ way [%out (scot %p p.dock) q.dock wire]
(ap-pass way %arvo %b %huck `sign-arvo`[%gall %unto %kick ~])
(ap-pass wire %huck dock %b %huck `sign-arvo`[%gall %unto %kick ~])
:: +ap-mule: run virtualized with intercepted scry, preserving type
::
:: Compare +mute and +mule. Those pass through scry, which

View File

@ -681,7 +681,7 @@
|
?. ?=([[@ *] *] b)
&
(lth i.i.a i.i.b)
(lth (end 3 i.i.a) (end 3 i.i.b))
--
::
++ get-source
@ -1043,7 +1043,8 @@
=/ who (slaw %p i.tyl)
?~ who [~ ~]
=/ sec (~(got by jaw.own.pki.lex) lyf.own.pki.lex)
``[%noun !>((end 6 (shaf %pass (shax sec))))]
=/ sal (add %pass step.own.pki.lex)
``[%noun !>((end 6 (shaf sal (shax sec))))]
::
%life
?. ?=([@ ~] tyl) [~ ~]

View File

@ -5293,6 +5293,17 @@
?~ a b
::
$(a l.a, b [n.a $(a r.a)])
:: +bap: convert to list, largest to smallest
::
++ bap
|= a=(tree item)
^- (list item)
::
=| b=(list item)
|- ^+ b
?~ a b
::
$(a r.a, b [n.a $(a l.a)])
:: +gas: put a list of items
::
++ gas

File diff suppressed because it is too large Load Diff

View File

@ -49,11 +49,16 @@ export class Ames extends Component {
const unsent = snd['unsent-messages'].reduce((a, b) => {
return a + b + ' bytes, ';
}, 'unsent msg sizes: ');
const queuedAcks = snd['queued-message-acks'].map(qa => {
const quacks = snd['queued-message-acks'].map(qa => {
return {key: qa['message-num'], jsx: (
qa['message-num'] + ': ' + qa.ack
)};
});
const queuedAcks = (
<SearchableList placeholder="msg num" items={quacks} />
);
const m = snd['packet-pump-state'].metrics;
const pumpMetrics = (<>
<table><tbody>

View File

@ -121,7 +121,7 @@ export class Eyre extends Component {
</>);
const subscriptionItems = c.subscriptions.map(s => {
//NOTE jsx sorta copied from /components/subscriptions
return {key: `${s.id} ${s.app} ${s.ship} ${s.path}`, jsx: (
return {key: `${s.id} ${s.ship} ${s.app} ${s.path}`, jsx: (
<div class="flex">
<div class="flex-auto" style={{maxWidth: '15%'}}>
{s.id}
@ -144,7 +144,7 @@ export class Eyre extends Component {
return {key: c.session, jsx: (
<Summary summary={summary} details={(
<SearchableList
placeholder="id, app, ship, path"
placeholder="id, ship, app, path"
items={subscriptionItems}
/>
)} />

View File

@ -1693,9 +1693,9 @@
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
},
"@tlon/indigo-react": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.5.tgz",
"integrity": "sha512-NOQTwH74l/XXMIfQ4ZzymvZuk1WK1nmO552TmXrQxBUSb7HmdlA8anG5oRrvnLJTkajLCY59McLkDca+lCcvwg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.15.tgz",
"integrity": "sha512-h9umWEzYZwyb53ujWoCQCJQwY9RUuoDaf6189+0LH3C7y9fybJe6vzbW6g2cUVH8dXA2EZkedS5nriYR0IpQbw==",
"requires": {
"@reach/menu-button": "^0.10.5",
"react": "^16.13.1",
@ -5681,6 +5681,11 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"immer": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-8.0.0.tgz",
"integrity": "sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg=="
},
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
@ -12212,6 +12217,11 @@
"synchronous-promise": "^2.0.13",
"toposort": "^2.0.2"
}
},
"zustand": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.2.0.tgz",
"integrity": "sha512-MBYFrnUdgFVi38tdQNSzVN9cPpRDf7w2HhdHGDSgBRHN7vIbUGUR3aBdVQykelXzSFR7iVj3YNBuq7B9ceMI5w=="
}
}
}

View File

@ -18,6 +18,7 @@
"css-loader": "^3.5.3",
"file-saver": "^2.0.2",
"formik": "^2.1.4",
"immer": "^8.0.0",
"lodash": "^4.17.15",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.20.1",
@ -41,7 +42,8 @@
"styled-system": "^5.1.5",
"suncalc": "^1.8.0",
"urbit-ob": "^5.0.0",
"yup": "^0.29.3"
"yup": "^0.29.3",
"zustand": "^3.2.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",

View File

@ -196,12 +196,10 @@ export class HarkApi extends BaseApi<StoreState> {
});
}
getMore(archive = false) {
const offset = this.store.state[
archive ? 'archivedNotifications' : 'notifications'
].size;
getMore() {
const offset = this.store.state['notifications']?.size || 0;
const count = 3;
return this.getSubset(offset,count, archive);
return this.getSubset(offset, count, false);
}
async getSubset(offset:number, count:number, isArchive: boolean) {

View File

@ -1,6 +1,5 @@
import BaseApi from "./base";
import { StoreState } from "../store/type";
import { BackgroundConfig, LocalUpdateRemoteContentPolicy } from "../types/local-update";
export default class LocalApi extends BaseApi<StoreState> {
getBaseHash() {
@ -9,76 +8,6 @@ export default class LocalApi extends BaseApi<StoreState> {
});
}
sidebarToggle() {
this.store.handleEvent({
data: {
local: {
sidebarToggle: true
}
}
})
}
setDark(isDark: boolean) {
this.store.handleEvent({
data: {
local: {
setDark: isDark
}
}
});
}
setOmnibox() {
this.store.handleEvent({
data: {
local: {
omniboxShown: true
},
},
});
}
setBackground(backgroundConfig: BackgroundConfig) {
this.store.handleEvent({
data: {
local: {
backgroundConfig
}
}
});
}
hideAvatars(hideAvatars: boolean) {
this.store.handleEvent({
data: {
local: {
hideAvatars
}
}
});
}
hideNicknames(hideNicknames: boolean) {
this.store.handleEvent({
data: {
local: {
hideNicknames
}
}
});
}
setRemoteContentPolicy(policy: LocalUpdateRemoteContentPolicy) {
this.store.handleEvent({
data: {
local: {
remoteContentPolicy: policy
}
}
});
}
dehydrate() {
this.store.dehydrate();
}

View File

@ -1,7 +1,9 @@
import { useEffect } from 'react';
import _ from "lodash";
import f from "lodash/fp";
import f, { memoize } from "lodash/fp";
import bigInt, { BigInteger } from "big-integer";
import { Contact } from '~/types';
import useLocalState from '../state/local';
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
@ -353,4 +355,9 @@ export function usePreventWindowUnload(shouldPreventDefault: boolean, message =
export function pluralize(text: string, isPlural = false, vowel = false) {
return isPlural ? `${text}s`: `${vowel ? 'an' : 'a'} ${text}`;
}
export function useShowNickname(contact: Contact | null): boolean {
const hideNicknames = useLocalState(state => state.hideNicknames);
return !!(contact && contact.nickname && !hideNicknames);
}

View File

@ -141,6 +141,22 @@ function reduce(data: any, state: HarkState) {
readSince(data, state);
unreadSince(data, state);
unreadEach(data, state);
seenIndex(data, state);
removeGraph(data, state);
}
function removeGraph(json: any, state: HarkState) {
const data = _.get(json, 'remove-graph');
if(data) {
delete state.unreads.graph[data];
}
}
function seenIndex(json: any, state: HarkState) {
const data = _.get(json, 'seen-index');
if(data) {
updateNotificationStats(state, data.index, 'last', () => data.time);
}
}
function readEach(json: any, state: HarkState) {
@ -153,16 +169,13 @@ function readEach(json: any, state: HarkState) {
function readSince(json: any, state: HarkState) {
const data = _.get(json, 'read-count');
if(data) {
console.log(data);
updateUnreadCount(state, data, () => 0)
}
}
function unreadSince(json: any, state: HarkState) {
const data = _.get(json, 'unread-count');
console.log(data);
if(data) {
updateNotificationStats(state, data.index, 'last', () => data.last)
updateUnreadCount(state, data.index, u => u + 1);
}
}
@ -170,7 +183,6 @@ function unreadSince(json: any, state: HarkState) {
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))
}
}
@ -179,7 +191,6 @@ function unreads(json: any, state: HarkState) {
const data = _.get(json, 'unreads');
if(data) {
clearState(state);
console.log(data);
data.forEach(({ index, stats }) => {
const { unreads, notifications, last } = stats;
updateNotificationStats(state, index, 'notifications', x => x + notifications);
@ -220,19 +231,22 @@ function clearState(state){
function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: number) => number) {
if(!('graph' in index)) {
return;
return;
}
const property = [index.graph.graph, index.graph.index, 'unreads'];
const curr = _.get(state.unreads.graph, property, 0);
_.set(state.unreads.graph, property, count(curr));
const newCount = count(curr)
_.set(state.unreads.graph, property, newCount);
}
function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>) => void) {
if(!('graph' in index)) {
return;
return;
}
const unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
const oldSize = unreads.size;
f(unreads);
const newSize = unreads.size;
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
}
@ -247,10 +261,7 @@ function updateNotificationStats(state: HarkState, index: NotifIndex, statField:
} else if('group' in index) {
const curr = _.get(state.unreads.group, [index.group.group, statField], 0);
_.set(state.unreads.group, [index.group.group, statField], f(curr));
} else if('chat' in index) {
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) {
@ -375,17 +386,5 @@ function archive(json: any, state: HarkState) {
notifIdxEqual(index, idxNotif.index)
);
state.notifications.set(time, unarchived);
const archiveBox = state.archivedNotifications.get(time) || [];
const readCount = archived.filter(
({ notification }) => !notification.read
).length;
updateNotificationStats(state, index, 'notifications', x => x - readCount);
state.archivedNotifications.set(time, [
...archiveBox,
...archived.map(({ notification, index }) => ({
notification: { ...notification, read: true },
index,
})),
]);
}
}

View File

@ -1,9 +1,9 @@
import _ from 'lodash';
import { StoreState } from '~/store/type';
import { Cage } from '~/types/cage';
import { LocalUpdate, BackgroundConfig } from '~/types/local-update';
import { LocalUpdate } from '~/types/local-update';
type LocalState = Pick<StoreState, 'sidebarShown' | 'omniboxShown' | 'baseHash' | 'hideAvatars' | 'hideNicknames' | 'background' | 'dark' | 'suspendedFocus' | 'remoteContentPolicy'>;
type LocalState = Pick<StoreState, 'baseHash'>;
export default class LocalReducer<S extends LocalState> {
rehydrate(state: S) {
@ -18,20 +18,11 @@ export default class LocalReducer<S extends LocalState> {
}
dehydrate(state: S) {
const json = _.pick(state, ['hideNicknames' , 'hideAvatars' , 'background', 'remoteContentPolicy']);
localStorage.setItem('localReducer', JSON.stringify(json));
}
reduce(json: Cage, state: S) {
const data = json['local'];
if (data) {
this.sidebarToggle(data, state);
this.setDark(data, state);
this.baseHash(data, state);
this.backgroundConfig(data, state)
this.hideAvatars(data, state)
this.hideNicknames(data, state)
this.omniboxShown(data, state);
this.remoteContentPolicy(data, state);
}
}
baseHash(obj: LocalUpdate, state: S) {
@ -39,53 +30,4 @@ export default class LocalReducer<S extends LocalState> {
state.baseHash = obj.baseHash;
}
}
omniboxShown(obj: LocalUpdate, state: S) {
if ('omniboxShown' in obj) {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
state.suspendedFocus.focus();
state.suspendedFocus = null;
} else {
state.suspendedFocus = document.activeElement;
document.activeElement?.blur();
}
}
}
sidebarToggle(obj: LocalUpdate, state: S) {
if ('sidebarToggle' in obj) {
state.sidebarShown = !state.sidebarShown;
}
}
setDark(obj: LocalUpdate, state: S) {
if('setDark' in obj) {
state.dark = obj.setDark;
}
}
backgroundConfig(obj: LocalUpdate, state: S) {
if('backgroundConfig' in obj) {
state.background = obj.backgroundConfig;
}
}
remoteContentPolicy(obj: LocalUpdate, state: S) {
if('remoteContentPolicy' in obj) {
state.remoteContentPolicy = obj.remoteContentPolicy;
}
}
hideAvatars(obj: LocalUpdate, state: S) {
if('hideAvatars' in obj) {
state.hideAvatars = obj.hideAvatars;
}
}
hideNicknames(obj: LocalUpdate, state: S) {
if( 'hideNicknames' in obj) {
state.hideNicknames = obj.hideNicknames;
}
}
}

View File

@ -0,0 +1,58 @@
import React, { ReactNode } from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import produce from 'immer';
import { BackgroundConfig, RemoteContentPolicy } from "~/types/local-update";
export interface LocalState extends State {
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: RemoteContentPolicy;
dark: boolean;
background: BackgroundConfig;
omniboxShown: boolean;
suspendedFocus?: HTMLElement;
toggleOmnibox: () => void;
set: (fn: (state: LocalState) => void) => void
};
const useLocalState = create<LocalState>(persist((set, get) => ({
dark: false,
background: undefined,
hideAvatars: false,
hideNicknames: false,
remoteContentPolicy: {
imageShown: true,
audioShown: true,
videoShown: true,
oembedShown: true,
},
omniboxShown: false,
suspendedFocus: undefined,
toggleOmnibox: () => set(produce(state => {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
state.suspendedFocus.focus();
state.suspendedFocus = undefined;
} else {
state.suspendedFocus = document.activeElement;
state.suspendedFocus.blur();
}
})),
set: fn => set(produce(fn))
}), {
name: 'localReducer'
}));
function withLocalState<P, S extends keyof LocalState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const localState = stateMemberKeys ? useLocalState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useLocalState();
return <Component ref={ref} {...localState} {...props} />
});
}
export { useLocalState as default, withLocalState };

View File

@ -51,19 +51,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
initialState(): StoreState {
return {
connection: 'connected',
sidebarShown: true,
omniboxShown: false,
suspendedFocus: null,
baseHash: null,
background: undefined,
remoteContentPolicy: {
imageShown: true,
audioShown: true,
videoShown: true,
oembedShown: true,
},
hideAvatars: false,
hideNicknames: false,
invites: {},
associations: {
contacts: {},
@ -88,7 +76,6 @@ export default class GlobalStore extends BaseStore<StoreState> {
credentials: null
},
contacts: {},
dark: false,
notifications: new BigIntOrderedMap<Timebox>(),
archivedNotifications: new BigIntOrderedMap<Timebox>(),
notificationsGroupConfig: [],

View File

@ -11,23 +11,13 @@ import {
Notifications,
NotificationGraphConfig,
GroupNotificationsConfig,
LocalUpdateRemoteContentPolicy,
BackgroundConfig,
Unreads
} from "~/types";
export interface StoreState {
// local state
sidebarShown: boolean;
omniboxShown: boolean;
suspendedFocus: HTMLInputElement | null;
dark: boolean;
connection: ConnectionStatus;
baseHash: string | null;
background: BackgroundConfig;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
hideAvatars: boolean;
hideNicknames: boolean;
// invite state
invites: Invites;

View File

@ -1,7 +1,3 @@
interface LocalUpdateSidebarToggle {
sidebarToggle: boolean;
}
interface LocalUpdateSetDark {
setDark: boolean;
}
@ -26,7 +22,7 @@ interface LocalUpdateSetOmniboxShown {
omniboxShown: boolean;
}
export interface LocalUpdateRemoteContentPolicy {
export interface RemoteContentPolicy {
imageShown: boolean;
audioShown: boolean;
videoShown: boolean;
@ -46,11 +42,10 @@ interface BackgroundConfigColor {
export type BackgroundConfig = BackgroundConfigUrl | BackgroundConfigColor | undefined;
export type LocalUpdate =
LocalUpdateSidebarToggle
| LocalUpdateSetDark
| LocalUpdateBaseHash
| LocalUpdateBackgroundConfig
| LocalUpdateHideAvatars
| LocalUpdateHideNicknames
| LocalUpdateSetOmniboxShown
| LocalUpdateRemoteContentPolicy;
| RemoteContentPolicy;

View File

@ -26,6 +26,7 @@ import GlobalSubscription from '~/logic/subscription/global';
import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { withLocalState } from '~/logic/state/local';
const Root = styled.div`
@ -86,23 +87,29 @@ class App extends React.Component {
componentDidMount() {
this.subscription.start();
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
this.api.local.setDark(this.themeWatcher.matches);
this.themeWatcher.addListener(this.updateTheme);
this.themeWatcher.onchange = this.updateTheme;
setTimeout(() => {
// Something about how the store works doesn't like changing it
// before the app has actually rendered, hence the timeout
this.updateTheme(this.themeWatcher);
}, 500);
this.api.local.getBaseHash();
this.store.rehydrate();
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
e.preventDefault();
e.stopImmediatePropagation();
this.api.local.setOmnibox();
this.props.toggleOmnibox();
});
}
componentWillUnmount() {
this.themeWatcher.removeListener(this.updateTheme);
this.themeWatcher.onchange = undefined;
}
updateTheme(e) {
this.api.local.setDark(e.matches);
this.props.set(state => {
state.dark = e.matches;
});
}
faviconString() {
@ -122,11 +129,11 @@ class App extends React.Component {
}
render() {
const { state } = this;
const { state, props } = this;
const associations = state.associations ?
state.associations : { contacts: {} };
const theme = state.dark ? dark : light;
const { background } = state;
const theme = props.dark ? dark : light;
const background = this.props.background;
const notificationsCount = state.notificationsCount || 0;
const doNotDisturb = state.doNotDisturb || false;
@ -164,7 +171,7 @@ class App extends React.Component {
notifications={state.notificationsCount}
invites={state.invites}
groups={state.groups}
show={state.omniboxShown}
show={this.props.omniboxShown}
/>
</ErrorBoundary>
<ErrorBoundary>
@ -183,5 +190,5 @@ class App extends React.Component {
}
}
export default process.env.NODE_ENV === 'production' ? App : hot(App);
export default withLocalState(process.env.NODE_ENV === 'production' ? App : hot(App));

View File

@ -38,8 +38,7 @@ export function ChatResource(props: ChatResourceProps) {
const chatInput = useRef<ChatInput>();
useEffect(() => {
const count = Math.min(150, unreadCount + 30);
console.log(`fetching ${count}`);
const count = Math.min(50, unreadCount + 15);
props.api.graph.getNewest(owner, name, count);
}, [station]);
@ -88,7 +87,6 @@ export function ChatResource(props: ChatResourceProps) {
<Col {...bind} height="100%" overflow="hidden" position="relative">
{dragging && <SubmitDragger />}
<ChatWindow
remoteContentPolicy={props.remoteContentPolicy}
mailboxSize={5}
match={props.match as any}
stationPendingMessages={[]}
@ -106,8 +104,6 @@ export function ChatResource(props: ChatResourceProps) {
ship={owner}
station={station}
api={props.api}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
location={props.location}
scrollTo={scrollTo ? parseInt(scrollTo, 10) : undefined}
/>
@ -120,7 +116,6 @@ export function ChatResource(props: ChatResourceProps) {
contacts={contacts}
onUnmount={appendUnsent}
s3={props.s3}
hideAvatars={props.hideAvatars}
placeholder="Message..."
message={unsent[station] || ''}
deleteMessage={clearUnsent}

View File

@ -10,6 +10,7 @@ import { Envelope } from '~/types/chat-update';
import { Contacts, Content } from '~/types';
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
import withS3 from '~/views/components/withS3';
import { withLocalState } from '~/logic/state/local';
type ChatInputProps = IuseS3 & {
api: GlobalApi;
@ -66,7 +67,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
inCodeMode: false
}, async () => {
const output = await props.api.graph.eval(text);
const contents: Content[] = [{ code: { output, expression: text }}];
const contents: Content[] = [{ code: { output, expression: text }}];
const post = createPost(contents);
props.api.graph.addPost(ship, name, post);
});
@ -199,4 +200,4 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
}
}
export default withS3(ChatInput, {accept: 'image/*'});
export default withLocalState(withS3(ChatInput, {accept: 'image/*'}), ['hideAvatars']);

View File

@ -4,14 +4,14 @@ import _ from "lodash";
import { Box, Row, Text, Rule } from "@tlon/indigo-react";
import OverlaySigil from '~/views/components/OverlaySigil';
import { uxToHex, cite, writeText } from '~/logic/lib/util';
import { Envelope, IMessage } from "~/types/chat-update";
import { Group, Association, Contacts, LocalUpdateRemoteContentPolicy, Post } from "~/types";
import { uxToHex, cite, writeText, useShowNickname } from '~/logic/lib/util';
import { Group, Association, Contacts, Post } from "~/types";
import TextContent from './content/text';
import CodeContent from './content/code';
import RemoteContent from '~/views/components/RemoteContent';
import { Mention } from "~/views/components/MentionText";
import styled from "styled-components";
import useLocalState from "~/logic/state/local";
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
@ -39,9 +39,6 @@ interface ChatMessageProps {
group: Group;
association: Association;
contacts: Contacts;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
className?: string;
isPending: boolean;
style?: any;
@ -76,9 +73,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
group,
association,
contacts,
hideAvatars,
hideNicknames,
remoteContentPolicy,
className = '',
isPending,
style,
@ -109,11 +103,8 @@ export default class ChatMessage extends Component<ChatMessageProps> {
msg,
timestamp,
contacts,
hideNicknames,
association,
group,
hideAvatars,
remoteContentPolicy,
measure: reboundMeasure.bind(this),
style,
containerClass,
@ -162,9 +153,6 @@ interface MessageProps {
group: Group;
association: Association;
contacts: Contacts;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
containerClass: string;
isPending: boolean;
style: any;
@ -172,105 +160,96 @@ interface MessageProps {
scrollWindow: HTMLDivElement;
};
export class MessageWithSigil extends PureComponent<MessageProps> {
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
export const MessageWithSigil = (props) => {
const {
msg,
timestamp,
contacts,
association,
group,
measure,
api,
history,
scrollWindow,
fontSize
} = props;
render() {
const {
msg,
timestamp,
contacts,
hideNicknames,
association,
group,
hideAvatars,
remoteContentPolicy,
measure,
api,
history,
scrollWindow,
fontSize
} = this.props;
const dark = useLocalState(state => state.dark);
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
const contact = msg.author in contacts ? contacts[msg.author] : false;
const showNickname = !hideNicknames && contact && contact.nickname;
const name = showNickname ? contact!.nickname : cite(msg.author);
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
const datestamp = moment.unix(msg['time-sent'] / 1000).format(DATESTAMP_FORMAT);
const contact = msg.author in contacts ? contacts[msg.author] : false;
const showNickname = useShowNickname(contact);
const name = showNickname ? contact.nickname : cite(msg.author);
const color = contact ? `#${uxToHex(contact.color)}` : dark ? '#000000' :'#FFFFFF'
const sigilClass = contact ? '' : dark ? 'mix-blend-diff' : 'mix-blend-darken';
let nameSpan = null;
let nameSpan = null;
const copyNotice = (saveName) => {
if (nameSpan !== null) {
nameSpan.innerText = 'Copied';
setTimeout(() => {
nameSpan.innerText = saveName;
}, 800);
}
};
const copyNotice = (saveName) => {
if (nameSpan !== null) {
nameSpan.innerText = 'Copied';
setTimeout(() => {
nameSpan.innerText = saveName;
}, 800);
}
};
return (
<>
<OverlaySigil
ship={msg.author}
contact={contact}
color={color}
sigilClass={sigilClass}
group={group}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
scrollWindow={scrollWindow}
history={history}
api={api}
bg="white"
className="fl pr3 v-top pt1"
/>
<Box flexGrow={1} display='block' className="clamp-message">
<Box
return (
<>
<OverlaySigil
ship={msg.author}
contact={contact}
color={color}
sigilClass={sigilClass}
group={group}
scrollWindow={scrollWindow}
history={history}
api={api}
bg="white"
className="fl pr3 v-top pt1"
/>
<Box flexGrow={1} display='block' className="clamp-message">
<Box
flexShrink={0}
className="hide-child"
pt={1}
pb={1}
display='flex'
alignItems='center'
>
<Text
fontSize={0}
mr={3}
flexShrink={0}
className="hide-child"
pt={1}
pb={1}
display='flex'
alignItems='center'
>
<Text
fontSize={0}
mr={3}
flexShrink={0}
mono={!showNickname}
fontWeight={(showNickname) ? '500' : '400'}
className={`mw5 db truncate pointer`}
ref={e => nameSpan = e}
onClick={() => {
writeText(`~${msg.author}`);
copyNotice(name);
}}
title={`~${msg.author}`}
>{name}</Text>
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
{msg.contents.map(c =>
<MessageContent
contacts={contacts}
content={c}
remoteContentPolicy={remoteContentPolicy}
measure={measure}
fontSize={fontSize}
group={group}
hideNicknames={hideNicknames}
hideAvatars={hideAvatars}
/>)}
</ContentBox>
mono={!showNickname}
fontWeight={(showNickname) ? '500' : '400'}
className={`mw5 db truncate pointer`}
ref={e => nameSpan = e}
onClick={() => {
writeText(`~${msg.author}`);
copyNotice(name);
}}
title={`~${msg.author}`}
>{name}</Text>
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
</>
);
}
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
{msg.contents.map(c =>
<MessageContent
contacts={contacts}
content={c}
measure={measure}
fontSize={fontSize}
group={group}
/>)}
</ContentBox>
</Box>
</>
);
}
const ContentBox = styled(Box)`
& > :first-child {
margin-left: 0px;
@ -278,7 +257,7 @@ const ContentBox = styled(Box)`
`;
export const MessageWithoutSigil = ({ timestamp, contacts, msg, remoteContentPolicy, measure, group, hideNicknames, hideAvatars }) => (
export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }) => (
<>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
<ContentBox flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
@ -288,15 +267,12 @@ export const MessageWithoutSigil = ({ timestamp, contacts, msg, remoteContentPol
contacts={contacts}
content={c}
group={group}
remoteContentPolicy={remoteContentPolicy}
measure={measure}
hideNicknames={hideNicknames}
hideAvatars={hideAvatars}/>))}
measure={measure}/>))}
</ContentBox>
</>
);
export const MessageContent = ({ content, contacts, remoteContentPolicy, measure, fontSize, group, hideNicknames, hideAvatars }) => {
export const MessageContent = ({ content, contacts, measure, fontSize, group }) => {
if ('code' in content) {
return <CodeContent content={content} />;
} else if ('url' in content) {
@ -304,7 +280,6 @@ export const MessageContent = ({ content, contacts, remoteContentPolicy, measure
<Box mx="2px" flexShrink={0} fontSize={fontSize ? fontSize : '14px'} lineHeight="tall" color='black'>
<RemoteContent
url={content.url}
remoteContentPolicy={remoteContentPolicy}
onLoad={measure}
imageProps={{style: {
maxWidth: '18rem',
@ -325,7 +300,7 @@ export const MessageContent = ({ content, contacts, remoteContentPolicy, measure
} else if ('text' in content) {
return <TextContent fontSize={fontSize} content={content} />;
} else if ('mention' in content) {
return <Mention group={group} ship={content.mention} contact={contacts?.[content.mention]} hideNicknames={hideNicknames} hideAvatars={hideAvatars} />
return <Mention group={group} ship={content.mention} contact={contacts?.[content.mention]} />
} else {
return null;
}

View File

@ -9,8 +9,7 @@ import { Contacts } from "~/types/contact-update";
import { Association } from "~/types/metadata-update";
import { Group } from "~/types/group-update";
import { Envelope, IMessage } from "~/types/chat-update";
import { LocalUpdateRemoteContentPolicy, Graph } from "~/types";
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
import { Graph } from "~/types";
import VirtualScroller from "~/views/components/VirtualScroller";
@ -41,9 +40,6 @@ type ChatWindowProps = RouteComponentProps<{
ship: Patp;
station: any;
api: GlobalApi;
hideNicknames: boolean;
hideAvatars: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
scrollTo?: number;
}
@ -199,14 +195,14 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
const currSize = graph.size;
if(newer && !this.loadedNewest) {
const [index] = graph.peekLargest()!;
await api.graph.getYoungerSiblings(ship,name, 100, `/${index.toString()}`)
await api.graph.getYoungerSiblings(ship,name, 20, `/${index.toString()}`)
if(currSize === graph.size) {
console.log('loaded all newest');
this.loadedNewest = true;
}
} else if(!newer && !this.loadedOldest) {
const [index] = graph.peekSmallest()!;
await api.graph.getOlderSiblings(ship,name, 100, `/${index.toString()}`)
await api.graph.getOlderSiblings(ship,name, 20, `/${index.toString()}`)
this.calculateUnreadIndex();
if(currSize === graph.size) {
console.log('loaded all oldest');
@ -253,16 +249,13 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
contacts,
mailboxSize,
graph,
hideAvatars,
hideNicknames,
remoteContentPolicy,
history
} = this.props;
const unreadMarkerRef = this.unreadMarkerRef;
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef, history, api };
const messageProps = { association, group, contacts, unreadMarkerRef, history, api };
const keys = graph.keys().reverse();
const unreadIndex = keys[this.props.unreadCount];

View File

@ -14,10 +14,7 @@ export default class GraphApp extends PureComponent {
const graphKeys = props.graphKeys || new Set([]);
const graphs = props.graphs || {};
const {
api, sidebarShown, s3,
hideAvatars, hideNicknames, remoteContentPolicy
} = this.props;
const { api } = this.props;
return (
<Switch>

View File

@ -10,7 +10,6 @@ import { RouteComponentProps } from "react-router-dom";
import { LinkItem } from "./components/LinkItem";
import LinkSubmit from "./components/LinkSubmit";
import { LinkPreview } from "./components/link-preview";
import { Comments } from "~/views/components/Comments";
import "./css/custom.css";
@ -33,9 +32,6 @@ export function LinkResource(props: LinkResourceProps) {
graphKeys,
unreads,
s3,
hideAvatars,
hideNicknames,
remoteContentPolicy,
history
} = props;
@ -85,9 +81,6 @@ export function LinkResource(props: LinkResourceProps) {
contacts={contactDetails}
unreads={unreads}
nickname={contact?.nickname}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
baseUrl={resourceUrl}
group={group}
path={resource["group-path"]}
@ -126,9 +119,6 @@ export function LinkResource(props: LinkResourceProps) {
key={node.post.index}
resource={resourcePath}
node={node}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
baseUrl={resourceUrl}
unreads={unreads}
group={group}
@ -145,9 +135,6 @@ export function LinkResource(props: LinkResourceProps) {
unreads={unreads}
contacts={contactDetails}
api={api}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
editCommentId={editCommentId}
history={props.history}
baseUrl={`${resourceUrl}/${props.match.params.index}`}

View File

@ -1,13 +1,12 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Row, Col, Anchor, Box, Text, BaseImage, Icon, Action } from '@tlon/indigo-react';
import { Row, Col, Anchor, Box, Text, Icon, Action } from '@tlon/indigo-react';
import { Sigil } from '~/logic/lib/sigil';
import { writeText } from '~/logic/lib/util';
import Author from '~/views/components/Author';
import { roleForShip } from '~/logic/lib/group';
import { Contacts, GraphNode, Group, LocalUpdateRemoteContentPolicy, Rolodex, Unreads } from '~/types';
import { Contacts, GraphNode, Group, Rolodex, Unreads } from '~/types';
import GlobalApi from '~/logic/api/global';
import { Dropdown } from '~/views/components/Dropdown';
import RemoteContent from '~/views/components/RemoteContent';
@ -15,9 +14,6 @@ import RemoteContent from '~/views/components/RemoteContent';
interface LinkItemProps {
node: GraphNode;
resource: string;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
api: GlobalApi;
group: Group;
path: string;
@ -29,9 +25,6 @@ export const LinkItem = (props: LinkItemProps) => {
const {
node,
resource,
hideAvatars,
hideNicknames,
remoteContentPolicy,
api,
group,
path,
@ -76,7 +69,6 @@ export const LinkItem = (props: LinkItemProps) => {
const markRead = () => {
api.hark.markEachAsRead(props.association, '/', `/${index}`, 'link', 'link');
console.log('mark read');
}
return (
<Box width="100%" {...rest}>
@ -97,7 +89,6 @@ export const LinkItem = (props: LinkItemProps) => {
<RemoteContent
url={contents[1].url}
text={contents[0].text}
remoteContentPolicy={remoteContentPolicy}
unfold={true}
style={{ alignSelf: 'center' }}
oembedProps={{
@ -130,12 +121,9 @@ export const LinkItem = (props: LinkItemProps) => {
<Author
showImage
contacts={contacts[path]}
contacts={contacts}
ship={author}
date={node.post['time-sent']}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
group={group}
api={api}
></Author>

View File

@ -1,84 +0,0 @@
import React from 'react';
import { Row, Col, Anchor, Box, Text, BaseImage } from '@tlon/indigo-react';
import { Sigil } from '~/logic/lib/sigil';
import { Link } from 'react-router-dom';
import { cite } from '~/logic/lib/util';
import { Author } from "~/views/apps/publish/components/Author";
import { roleForShip } from '~/logic/lib/group';
export const LinkItem = (props) => {
const {
node,
nickname,
avatar,
contacts,
unread,
resource,
hideAvatars,
hideNicknames,
api,
group
} = props;
const URLparser = new RegExp(
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
);
const author = node.post.author;
const index = node.post.index.split('/')[1];
const size = node.children ? node.children.size : 0;
const date = node.post['time-sent'];
const contents = node.post.contents;
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
const showAvatar = avatar && !hideAvatars;
const showNickname = nickname && !hideNicknames;
const img = showAvatar
? <BaseImage display='inline-block' src={props.avatar} height={36} width={36} />
: <Sigil ship={`~${author}`} size={36} color={'#' + props.color} />;
const baseUrl = props.baseUrl || `/~404/${resource}`;
const ourRole = group ? roleForShip(group, window.ship) : undefined;
const [ship, name] = resource.split('/');
return (
<Row minWidth='0' flexShrink='0' width="100%" alignItems="center" py={3} bg="white">
{img}
<Col minWidth='0' height="100%" width='100%' justifyContent="space-between" ml={2}>
<Anchor
lineHeight="tall"
display='flex'
style={{ textDecoration: 'none' }}
href={contents[1].url}
width="100%"
target="_blank"
rel="noopener noreferrer"
>
<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>
</Anchor>
<Row alignItems="center" width="100%">
<Author
contacts={contacts}
ship={author}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
unread={unread}
date={date}
>
<Link to={`${baseUrl}/${index}`}>
<Text ml="2" color="gray">{size} comments</Text>
</Link>
{(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>)}
</Author>
</Row>
</Col>
</Row>
);
};

View File

@ -1,70 +0,0 @@
import React, { useEffect } from 'react';
import { cite } from '~/logic/lib/util';
import RemoteContent from '~/views/components/RemoteContent';
import { Box, Col, Anchor, Text } from '@tlon/indigo-react';
import moment from 'moment';
const URLparser = new RegExp(
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
);
export const LinkPreview = (props) => {
const showNickname = props.nickname && !props.hideNicknames;
const author = props.post.author;
const title = props.post.contents[0].text;
const url = props.post.contents[1].url;
const hostname = URLparser.exec(url) ? URLparser.exec(url)[4] : null;
const timeSent =
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 = (
<RemoteContent
unfold={true}
renderUrl={false}
url={url}
remoteContentPolicy={props.remoteContentPolicy}
className="mw-100"
/>
);
return (
<Box pb='6' width='100%'>
<Box width='100%' textAlign='center'>{embed}</Box>
<Col flex='1 1 auto' minWidth='0' minHeight='0' pt='6'>
<Anchor href={url}
lineHeight="tall"
display='flex'
style={{ textDecoration: 'none' }}
width='100%'
target="_blank"
rel="noopener noreferrer">
<Text
display='inline-block'
overflow='hidden'
style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}
>
{title}
</Text>
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} </Text>
</Anchor>
<Box width='100%' pt='1'>
<Text fontSize='0' pr='2' display='inline-block' mono={!showNickname} title={author}>
{showNickname ? props.nickname : cite(`~${author}`)}
</Text>
<Text fontSize='0' gray pr='3' display='inline-block'>{timeSent}</Text>
<Text gray fontSize='0' display='inline-block'>
{props.commentNumber} comments
</Text>
</Box>
</Col>
</Box>
);
};

View File

@ -34,9 +34,8 @@ export function ChatNotification(props: {
contacts: Rolodex;
groups: Groups;
api: GlobalApi;
remoteContentPolicy: any;
}) {
const { index, contents, read, time, api, timebox, remoteContentPolicy } = props;
const { index, contents, read, time, api, timebox } = props;
const authors = _.map(contents, "author");
const { chat, mention } = index;
@ -90,7 +89,6 @@ export function ChatNotification(props: {
contacts={groupContacts}
fontSize='0'
pt='2'
remoteContentPolicy={remoteContentPolicy}
/>
</Link>
);

View File

@ -80,7 +80,6 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents}
contacts={contacts}
group={group}
remoteContentPolicy={remoteContentPolicy}
/>
}
return null;
@ -91,7 +90,6 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents}
group={group}
contacts={contacts}
remoteContentPolicy={remoteContentPolicy}
/>
} else if (idx[1] === "1") {
const [{ text: header }, { text: body }] = contents;
@ -132,7 +130,6 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
msg={post}
fontSize='0'
pt='2'
remoteContentPolicy={remoteContentPolicy}
/>
</Row>);
@ -219,7 +216,6 @@ const GraphNode = ({
mod={mod}
description={description}
index={index}
remoteContentPolicy={remoteContentPolicy}
group={group}
/>
</Row>
@ -239,9 +235,8 @@ export function GraphNotification(props: {
groups: Groups;
contacts: Rolodex;
api: GlobalApi;
remoteContentPolicy: any;
}) {
const { contents, index, read, time, api, timebox, remoteContentPolicy, groups } = props;
const { contents, index, read, time, api, timebox, groups } = props;
const authors = _.map(contents, "author");
const { graph, group } = index;
@ -287,7 +282,6 @@ return (
groupPath={group}
read={read}
onRead={onClick}
remoteContentPolicy={remoteContentPolicy}
/>
))}
</Col>

View File

@ -4,7 +4,7 @@ import f from "lodash/fp";
import _ from "lodash";
import moment from "moment";
import { PropFunc } from "~/types/util";
import { getContactDetails } from "~/logic/lib/util";
import { getContactDetails, useShowNickname } from "~/logic/lib/util";
import { Associations, Contact, Contacts, Rolodex } from "~/types";
const Text = (props: PropFunc<typeof Text>) => (
@ -14,7 +14,7 @@ const Text = (props: PropFunc<typeof Text>) => (
function Author(props: { patp: string; contacts: Contacts; last?: boolean }) {
const contact: Contact | undefined = props.contacts?.[props.patp];
const showNickname = !!contact?.nickname;
const showNickname = useShowNickname(contact);
const name = contact?.nickname || `~${props.patp}`;
return (

View File

@ -1,7 +1,7 @@
import React, { useEffect, useCallback } from "react";
import f from "lodash/fp";
import _ from "lodash";
import { Icon, Col, Row, Box, Text, Anchor } from "@tlon/indigo-react";
import { Icon, Col, Row, Box, Text, Anchor, Rule } from "@tlon/indigo-react";
import moment from "moment";
import { Notifications, Rolodex, Timebox, IndexedNotification, Groups } from "~/types";
import { MOMENT_CALENDAR_DATE, daToUnix, resourceAsPath } from "~/logic/lib/util";
@ -129,8 +129,10 @@ export default function Inbox(props: {
};
return (
<Col height="100%" overflowY="auto" onScroll={onScroll} >
{inviteItems(invites, api)}
<Col position="relative" height="100%" overflowY="auto" onScroll={onScroll} >
<Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky">
{inviteItems(invites, api)}
</Col>
{newNotifications && (
<DaySection
latest
@ -142,11 +144,9 @@ export default function Inbox(props: {
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
remoteContentPolicy={props.remoteContentPolicy}
api={api}
/>
)}
{_.map(
notificationsByDay,
(timeboxes, idx) =>
@ -162,7 +162,6 @@ export default function Inbox(props: {
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
remoteContentPolicy={props.remoteContentPolicy}
/>
)
)}
@ -228,7 +227,6 @@ function DaySection({
contacts={contacts}
groups={groups}
time={date}
remoteContentPolicy={remoteContentPolicy}
/>
</React.Fragment>
))

View File

@ -31,7 +31,6 @@ interface NotificationProps {
graphConfig: NotificationGraphConfig;
groupConfig: GroupNotificationsConfig;
chatConfig: string[];
remoteContentPolicy: any;
}
function getMuted(
@ -100,7 +99,7 @@ function NotificationWrapper(props: {
</StatelessAsyncAction>
{!props.archived && (
<StatelessAsyncAction name={time.toString()} onClick={onArchive} backgroundColor="transparent">
Archive
Dismiss
</StatelessAsyncAction>
)}
</Row>
@ -143,7 +142,6 @@ export function Notification(props: NotificationProps) {
timebox={props.time}
time={time}
associations={associations}
remoteContentPolicy={props.remoteContentPolicy}
/>
</Wrapper>
);
@ -184,7 +182,6 @@ export function Notification(props: NotificationProps) {
timebox={props.time}
time={time}
associations={associations}
remoteContentPolicy={props.remoteContentPolicy}
/>
</Wrapper>
);

View File

@ -70,11 +70,6 @@ export default function NotificationsScreen(props: any) {
Inbox
</HeaderLink>
</Box>
<Box>
<HeaderLink current={view} view="archive">
Archive
</HeaderLink>
</Box>
<Box>
<HeaderLink current={view} view="preferences">
Preferences
@ -115,14 +110,6 @@ export default function NotificationsScreen(props: any) {
</Box>
</Dropdown>
</Row>
{view === "archive" && (
<Inbox
{...props}
archive={props.archivedNotifications}
showArchive
filter={filter.groups}
/>
)}
{view === "preferences" && (
<NotificationPreferences
graphConfig={props.notificationsGraphConfig}

View File

@ -12,6 +12,7 @@ import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import { S3State, BackgroundConfig } from '~/types';
import { BackgroundPicker, BgType } from './BackgroundPicker';
import useLocalState, { LocalState } from '~/logic/state/local';
const formSchema = Yup.object().shape({
bgType: Yup.string()
@ -33,15 +34,13 @@ interface FormSchema {
interface DisplayFormProps {
api: GlobalApi;
dark: boolean;
background: BackgroundConfig;
hideAvatars: boolean;
hideNicknames: boolean;
s3: S3State;
}
export default function DisplayForm(props: DisplayFormProps) {
const { api, background, hideAvatars, hideNicknames, s3 } = props;
const { api, s3 } = props;
const { hideAvatars, hideNicknames, background, set: setLocalState } = useLocalState();
let bgColor, bgUrl;
if (background?.type === 'url') {
@ -72,10 +71,11 @@ export default function DisplayForm(props: DisplayFormProps) {
? { type: 'url', url: values.bgUrl || '' }
: undefined;
api.local.setBackground(bgConfig);
api.local.hideAvatars(values.avatars);
api.local.hideNicknames(values.nicknames);
api.local.dehydrate();
setLocalState((state: LocalState) => {
state.background = bgConfig;
state.hideAvatars = values.avatars;
state.hideNicknames = values.nicknames;
});
actions.setSubmitting(false);
}}
>

View File

@ -8,7 +8,7 @@ import { Formik, Form } from "formik";
import * as Yup from "yup";
import GlobalApi from "~/logic/api/global";
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
import useLocalState from "~/logic/state/local";
const formSchema = Yup.object().shape({
imageShown: Yup.boolean(),
@ -26,11 +26,12 @@ interface FormSchema {
interface RemoteContentFormProps {
api: GlobalApi;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
}
export default function RemoteContentForm(props: RemoteContentFormProps) {
const { api, remoteContentPolicy } = props;
const { api } = props;
const remoteContentPolicy = useLocalState(state => state.remoteContentPolicy);
const setRemoteContentPolicy = useLocalState(state => state.set);
const imageShown = remoteContentPolicy.imageShown;
const audioShown = remoteContentPolicy.audioShown;
const videoShown = remoteContentPolicy.videoShown;
@ -47,13 +48,9 @@ export default function RemoteContentForm(props: RemoteContentFormProps) {
} as FormSchema
}
onSubmit={(values, actions) => {
api.local.setRemoteContentPolicy({
imageShown: values.imageShown,
audioShown: values.audioShown,
videoShown: values.videoShown,
oembedShown: values.oembedShown,
setRemoteContentPolicy(state => {
Object.assign(state.remoteContentPolicy, values);
});
api.local.dehydrate();
actions.setSubmitting(false);
}}
>

View File

@ -13,12 +13,7 @@ type ProfileProps = StoreState & { api: GlobalApi; ship: string };
export default function Settings({
api,
s3,
dark,
hideAvatars,
hideNicknames,
background,
remoteContentPolicy
s3
}: ProfileProps) {
return (
<Box
@ -32,13 +27,9 @@ export default function Settings({
>
<DisplayForm
api={api}
dark={dark}
hideNicknames={hideNicknames}
hideAvatars={hideAvatars}
background={background}
s3={s3}
/>
<RemoteContentForm {...{ api, remoteContentPolicy }} />
<RemoteContentForm api={api} />
<S3Form api={api} s3={s3} />
<SecuritySettings api={api} />
</Box>

View File

@ -9,6 +9,7 @@ import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import Settings from "./components/settings";
import { ContactCard } from "~/views/landscape/components/ContactCard";
import useLocalState from "~/logic/state/local";
const SidebarItem = ({ children, view, current }) => {
const selected = current === view;
@ -42,6 +43,7 @@ const SidebarItem = ({ children, view, current }) => {
export default function ProfileScreen(props: any) {
const { ship, dark } = props;
const hideAvatars = useLocalState(state => state.hideAvatars);
return (
<>
<Helmet defer={false}>
@ -65,7 +67,7 @@ export default function ProfileScreen(props: any) {
history.replace("/~profile/identity");
}
const image = (!props?.hideAvatars && contact?.avatar)
const image = (!hideAvatars && contact?.avatar)
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
: <Sigil ship={`~${ship}`} size={80} color={sigilColor} />;
return (
@ -132,8 +134,6 @@ export default function ProfileScreen(props: any) {
path="/~/default"
api={props.api}
s3={props.s3}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
/>
</>
)}

View File

@ -35,10 +35,7 @@ export function PublishResource(props: PublishResourceProps) {
history={props.history}
match={props.match}
location={props.location}
hideAvatars={props.hideAvatars}
unreads={props.unreads}
hideNicknames={props.hideNicknames}
remoteContentPolicy={props.remoteContentPolicy}
graphs={props.graphs}
s3={props.s3}
/>

View File

@ -10,7 +10,7 @@ import { NoteNavigation } from "./NoteNavigation";
import GlobalApi from "~/logic/api/global";
import { getLatestRevision, getComments } from '~/logic/lib/publish';
import Author from "~/views/components/Author";
import { Contacts, GraphNode, Graph, LocalUpdateRemoteContentPolicy, Association, Unreads, Group } from "~/types";
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from "~/types";
interface NoteProps {
ship: string;
@ -21,9 +21,6 @@ interface NoteProps {
notebook: Graph;
contacts: Contacts;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
rootUrl: string;
baseUrl: string;
group: Group;
@ -45,12 +42,14 @@ export function Note(props: NoteProps & RouteComponentProps) {
const comments = getComments(note);
const [revNum, title, body, post] = getLatestRevision(note);
const index = note.post.index.split('/');
const noteId = bigInt(index[1]);
useEffect(() => {
api.hark.markEachAsRead(props.association, '/', post.index, 'note', 'publish');
api.hark.markEachAsRead(props.association, '/',`/${index[1]}/1/1`, 'note', 'publish');
}, [props.association]);
const noteId = bigInt(note.post.index.split('/')[1]);
let adminLinks: JSX.Element | null = null;
if (window.ship === note?.post?.author) {
@ -95,8 +94,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
<Text display="block" mb={2}>{title || ""}</Text>
<Box display="flex">
<Author
hideNicknames={props?.hideNicknames}
hideAvatars={props?.hideAvatars}
ship={post?.author}
contacts={contacts}
date={post?.["time-sent"]}
@ -121,9 +118,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
contacts={props.contacts}
association={props.association}
api={props.api}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
remoteContentPolicy={props.remoteContentPolicy}
baseUrl={baseUrl}
editCommentId={editCommentId}
history={props.history}

View File

@ -19,8 +19,6 @@ interface NotePreviewProps {
host: string;
book: string;
node: GraphNode;
hideAvatars?: boolean;
hideNicknames?: boolean;
baseUrl: string;
unreads: Unreads;
contacts: Contacts;
@ -33,7 +31,7 @@ const WrappedBox = styled(Box)`
`;
export function NotePreview(props: NotePreviewProps) {
const { node, contacts, hideAvatars, hideNicknames, group } = props;
const { node, contacts, group } = props;
const { post } = node;
if (!post) {
return null;
@ -45,7 +43,7 @@ export function NotePreview(props: NotePreviewProps) {
const [rev, title, body, content] = getLatestRevision(node);
const appPath = `/ship/${props.host}/${props.book}`;
const isUnread = props.unreads.graph?.[appPath]?.['/']?.unreads?.has(content.index);
const isUnread = props.unreads.graph?.[appPath]?.['/']?.unreads?.has(`/${noteId}/1/1`);
const snippet = getSnippet(body);
@ -84,8 +82,6 @@ export function NotePreview(props: NotePreviewProps) {
contacts={contacts}
ship={post?.author}
date={post?.['time-sent']}
hideAvatars={hideAvatars || false}
hideNicknames={hideNicknames || false}
group={group}
unread={isUnread}
api={props.api}

View File

@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router-dom";
import Note from "./Note";
import { EditPost } from "./EditPost";
import { GraphNode, Graph, Contacts, LocalUpdateRemoteContentPolicy, Association, S3State, Group } from "~/types";
import { GraphNode, Graph, Contacts, Association, S3State, Group } from "~/types";
interface NoteRoutesProps {
ship: string;
@ -16,9 +16,6 @@ interface NoteRoutesProps {
notebook: Graph;
contacts: Contacts;
api: GlobalApi;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
hideNicknames: boolean;
hideAvatars: boolean;
association: Association;
baseUrl?: string;
rootUrl?: string;

View File

@ -1,13 +1,10 @@
import React, { PureComponent } from "react";
import { Link, RouteComponentProps, Route, Switch } from "react-router-dom";
import React from "react";
import { Link, RouteComponentProps } from "react-router-dom";
import { NotebookPosts } from "./NotebookPosts";
import { roleForShip } from "~/logic/lib/group";
import { Box, Button, Text, Row, Col } from "@tlon/indigo-react";
import GlobalApi from "~/logic/api/global";
import styled from "styled-components";
import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from "~/types";
import { deSig } from "~/logic/lib/util";
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
import { useShowNickname } from "~/logic/lib/util";
interface NotebookProps {
api: GlobalApi;
@ -19,26 +16,17 @@ interface NotebookProps {
associations: Associations;
contacts: Rolodex;
groups: Groups;
hideNicknames: boolean;
hideAvatars: boolean;
baseUrl: string;
rootUrl: string;
unreads: Unreads;
}
interface NotebookState {
isUnsubscribing: boolean;
tab: string;
}
export function Notebook(props: NotebookProps & RouteComponentProps) {
const {
ship,
book,
notebookContacts,
groups,
hideNicknames,
hideAvatars,
association,
graph
} = props;
@ -46,7 +34,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
const group = groups[association?.['group-path']];
if (!group) {
return null; // Waitin on groups to populate
return null; // Waiting on groups to populate
}
const relativePath = (p: string) => props.baseUrl + p;
@ -59,7 +47,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
isWriter = isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
}
const showNickname = contact?.nickname && !hideNicknames;
const showNickname = useShowNickname(contact);
return (
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
@ -85,9 +73,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
host={ship}
book={book}
contacts={notebookContacts ? notebookContacts : {}}
hideNicknames={hideNicknames}
unreads={props.unreads}
hideAvatars={hideAvatars}
baseUrl={props.baseUrl}
api={props.api}
group={group}

View File

@ -8,13 +8,11 @@ import {
Groups,
Contacts,
Rolodex,
LocalUpdateRemoteContentPolicy,
Unreads,
S3State
} from "~/types";
import { Center, LoadingSpinner } from "@tlon/indigo-react";
import { Notebook as INotebook } from "~/types/publish-update";
import bigInt, { BigInteger } from 'big-integer';
import bigInt from 'big-integer';
import Notebook from "./Notebook";
import NewPost from "./new-post";
@ -33,10 +31,7 @@ interface NotebookRoutesProps {
groups: Groups;
baseUrl: string;
rootUrl: string;
hideAvatars: boolean;
hideNicknames: boolean;
association: Association;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
associations: Associations;
s3: S3State;
}
@ -116,9 +111,6 @@ export function NotebookRoutes(
noteId={noteIdNum}
contacts={notebookContacts}
association={props.association}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
remoteContentPolicy={props.remoteContentPolicy}
group={group}
s3={props.s3}
{...routeProps}

View File

@ -2,7 +2,7 @@ import React, {ReactNode} from "react";
import moment from "moment";
import { Row, Box } from "@tlon/indigo-react";
import { uxToHex, cite } from "~/logic/lib/util";
import { uxToHex, cite, useShowNickname } from "~/logic/lib/util";
import { Contacts, Rolodex } from "~/types/contact-update";
import OverlaySigil from "./OverlaySigil";
import { Group, Association } from "~/types";
@ -14,8 +14,6 @@ interface AuthorProps {
ship: string;
date: number;
showImage?: boolean;
hideAvatars: boolean;
hideNicknames: boolean;
children?: ReactNode;
unread?: boolean;
group: Group;
@ -23,16 +21,16 @@ interface AuthorProps {
}
export default function Author(props: AuthorProps) {
const { contacts, ship = '', date, showImage, hideAvatars, hideNicknames, group, api } = props;
const { contacts, ship = '', date, showImage, group, api } = props;
const history = useHistory();
let contact;
if (contacts) {
contact = ship in contacts ? contacts[ship] : null;
}
const color = contact?.color ? `#${uxToHex(contact?.color)}` : "#000000";
const showNickname = !props.hideNicknames && contact?.nickname;
const showNickname = useShowNickname(contact);
const name = showNickname ? contact?.nickname : cite(ship);
const name = showNickname ? contact.nickname : cite(ship);
const dateFmt = moment(date).fromNow();
return (
<Row alignItems="center" width="auto">
@ -44,8 +42,6 @@ export default function Author(props: AuthorProps) {
color={color}
sigilClass={''}
group={group}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
history={history}
api={api}
bg="white"

View File

@ -7,7 +7,7 @@ import styled from 'styled-components';
import Author from '~/views/components/Author';
import { GraphNode, TextContent } from '~/types/graph-update';
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
import { LocalUpdateRemoteContentPolicy, Group } from '~/types';
import { Group } from '~/types';
import { MentionText } from '~/views/components/MentionText';
import { getLatestCommentRevision } from '~/logic/lib/publish';
@ -25,14 +25,11 @@ interface CommentItemProps {
name: string;
ship: string;
api: GlobalApi;
hideNicknames: boolean;
hideAvatars: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
group: Group;
}
export function CommentItem(props: CommentItemProps) {
const { ship, contacts, name, api, remoteContentPolicy, comment, group } = props;
const { ship, contacts, name, api, comment, group } = props;
const [revNum, post] = getLatestCommentRevision(comment);
const disabled = props.pending || window.ship !== post?.author;
@ -53,9 +50,6 @@ export function CommentItem(props: CommentItemProps) {
ship={post?.author}
date={post?.['time-sent']}
unread={props.unread}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
remoteContentPolicy={remoteContentPolicy}
group={group}
api={api}
>
@ -81,9 +75,6 @@ export function CommentItem(props: CommentItemProps) {
contacts={contacts}
group={group}
content={post?.contents}
remoteContentPolicy={remoteContentPolicy}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
/>
</Box>
</Box>

View File

@ -6,7 +6,7 @@ import CommentInput from './CommentInput';
import { Contacts } from '~/types/contact-update';
import GlobalApi from '~/logic/api/global';
import { FormikHelpers } from 'formik';
import { Group, GraphNode, LocalUpdateRemoteContentPolicy, Unreads, Association } from '~/types';
import { Group, GraphNode, Association } from '~/types';
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
import { getLatestCommentRevision } from '~/logic/lib/publish';
import { scanForMentions } from '~/logic/lib/graph';
@ -21,9 +21,6 @@ interface CommentsProps {
baseUrl: string;
contacts: Contacts;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
group: Group;
}
@ -126,9 +123,6 @@ export function Comments(props: CommentsProps) {
name={name}
ship={ship}
unread={i >= readCount}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
remoteContentPolicy={props.remoteContentPolicy}
baseUrl={props.baseUrl}
group={group}
pending={idx.toString() === props.editCommentId}

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Invite } from '~/types/invite-update';
import { Text, Box, Button, Row } from '@tlon/indigo-react';
import { Text, Box, Button, Row, Rule } from '@tlon/indigo-react';
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import { cite } from '~/logic/lib/util';
@ -9,7 +9,8 @@ export class InviteItem extends Component<{invite: Invite, onAccept: (i: any) =>
const { props } = this;
return (
<Box width='100%' p='4' mb='4' borderBottom='1px solid lightGray' position='sticky' style={{ top: 0 }}>
<>
<Box width='100%' p='4'>
<Box width='100%' verticalAlign='middle'>
<Text display='block' pb='2' gray><Text mono>{cite(props.invite.resource.ship)}</Text> invited you to <Text fontWeight='500'>{props.invite.resource.name}</Text></Text>
</Box>
@ -34,6 +35,8 @@ export class InviteItem extends Component<{invite: Invite, onAccept: (i: any) =>
</Row>
</Box>
<Rule />
</>
);
}
}

View File

@ -5,25 +5,21 @@ import {
Contact,
Contacts,
Content,
LocalUpdateRemoteContentPolicy,
Group,
} from "~/types";
import RichText from "~/views/components/RichText";
import { cite, uxToHex } from "~/logic/lib/util";
import { ProfileOverlay } from "./ProfileOverlay";
import {useHistory} from "react-router-dom";
import { cite, useShowNickname, uxToHex } from "~/logic/lib/util";
import ProfileOverlay from "./ProfileOverlay";
import { useHistory } from "react-router-dom";
interface MentionTextProps {
contact?: Contact;
contacts?: Contacts;
content: Content[];
group: Group;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
hideNicknames: boolean;
hideAvatars: boolean;
}
export function MentionText(props: MentionTextProps) {
const { content, contacts, contact, group, hideNicknames, hideAvatars } = props;
const { content, contacts, contact, group } = props;
return (
<>
@ -33,14 +29,13 @@ export function MentionText(props: MentionTextProps) {
<RichText
inline
key={idx}
remoteContentPolicy={props.remoteContentPolicy}
>
{c.text}
</RichText>
);
} else if ("mention" in c) {
return (
<Mention key={idx} contacts={contacts || {}} contact={contact || {}} group={group} ship={c.mention} hideNicknames={hideNicknames} hideAvatars={hideAvatars} />
<Mention key={idx} contacts={contacts || {}} contact={contact || {}} group={group} ship={c.mention} />
);
}
return null;
@ -54,15 +49,14 @@ export function Mention(props: {
contact: Contact;
contacts?: Contacts;
group: Group;
hideNicknames: boolean;
hideAvatars: boolean;
}) {
const { contacts, ship, hideNicknames, hideAvatars } = props;
const { contacts, ship } = props;
let { contact } = props;
contact = (contact?.nickname) ? contact : contacts?.[ship];
const showNickname = (Boolean(contact?.nickname) && !hideNicknames);
const showNickname = useShowNickname(contact);
const name = showNickname ? contact?.nickname : cite(ship);
const [showOverlay, setShowOverlay] = useState(false);
const onDismiss = useCallback(() => {
@ -89,8 +83,6 @@ export function Mention(props: {
color={`#${uxToHex(contact?.color ?? '0x0')}`}
group={group}
onDismiss={onDismiss}
hideAvatars={hideAvatars || false}
hideNicknames={hideNicknames}
history={history}
/>
)}

View File

@ -3,12 +3,10 @@ import React, { PureComponent } from 'react';
import { Sigil } from '~/logic/lib/sigil';
import { Contact, Group } from '~/types';
import {
ProfileOverlay,
OVERLAY_HEIGHT
} from './ProfileOverlay';
import ProfileOverlay, { OVERLAY_HEIGHT } from './ProfileOverlay';
import { Box, BaseImage, ColProps } from '@tlon/indigo-react';
import { withLocalState } from '~/logic/state/local';
type OverlaySigilProps = ColProps & {
ship: string;
@ -16,12 +14,11 @@ type OverlaySigilProps = ColProps & {
color: string;
sigilClass: string;
group?: Group;
hideAvatars: boolean;
hideNicknames: boolean;
scrollWindow?: HTMLElement;
history: any;
api: any;
className: string;
hideAvatars: boolean;
}
interface OverlaySigilState {
@ -30,7 +27,7 @@ interface OverlaySigilState {
bottomSpace: number | 'auto';
}
export default class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
class OverlaySigil extends PureComponent<OverlaySigilProps, OverlaySigilState> {
public containerRef: React.Ref<HTMLDivElement>;
constructor(props) {
@ -89,11 +86,10 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
contact,
color,
group,
hideAvatars,
hideNicknames,
history,
api,
sigilClass,
hideAvatars,
...rest
} = this.props;
@ -127,8 +123,6 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
bottomSpace={state.bottomSpace}
group={group}
onDismiss={this.profileHide}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
history={history}
api={api}
{...rest}
@ -139,3 +133,5 @@ export default class OverlaySigil extends PureComponent<OverlaySigilProps, Overl
);
}
}
export default withLocalState(OverlaySigil, ['hideAvatars']);

View File

@ -1,10 +1,11 @@
import React, { PureComponent } from 'react';
import { Contact, Group } from '~/types';
import { cite } from '~/logic/lib/util';
import { cite, useShowNickname } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil';
import { Box, Col, Button, Text, BaseImage, ColProps } from '@tlon/indigo-react';
import { withLocalState } from '~/logic/state/local';
export const OVERLAY_HEIGHT = 250;
@ -17,12 +18,11 @@ type ProfileOverlayProps = ColProps & {
group?: Group;
onDismiss(): void;
hideAvatars: boolean;
hideNicknames: boolean;
history: any;
api: any;
}
export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
public popoverRef: React.Ref<typeof Col>;
constructor(props) {
@ -60,7 +60,6 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
topSpace,
bottomSpace,
group = false,
hideNicknames,
hideAvatars,
history,
onDismiss,
@ -90,7 +89,7 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
classes="brt2"
svgClass="brt2"
/>;
const showNickname = contact?.nickname && !hideNicknames;
const showNickname = useShowNickname(contact);
// TODO: we need to rethink this "top-level profile view" of other ships
/* if (!group.hidden) {
@ -147,3 +146,5 @@ export class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
);
}
}
export default withLocalState(ProfileOverlay, ['hideAvatars']);

View File

@ -1,16 +1,16 @@
import React, { PureComponent, Fragment } from 'react';
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
import { BaseAnchor, BaseImage, Box, Button, Text } from '@tlon/indigo-react';
import { hasProvider } from 'oembed-parser';
import EmbedContainer from 'react-oembed-container';
import { memoize } from 'lodash';
import { withLocalState } from '~/logic/state/local';
import { RemoteContentPolicy } from '~/types/local-update';
interface RemoteContentProps {
url: string;
text?: string;
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
unfold?: boolean;
renderUrl?: boolean;
remoteContentPolicy: RemoteContentPolicy;
imageProps?: any;
audioProps?: any;
videoProps?: any;
@ -29,7 +29,7 @@ const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i);
const AUDIO_REGEX = new RegExp(/(mp3|wav|ogg)$/i);
const VIDEO_REGEX = new RegExp(/(mov|mp4|ogv)$/i);
export default class RemoteContent extends PureComponent<RemoteContentProps, RemoteContentState> {
class RemoteContent extends PureComponent<RemoteContentProps, RemoteContentState> {
private fetchController: AbortController | undefined;
constructor(props) {
super(props);
@ -200,3 +200,5 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
}
}
}
export default withLocalState(RemoteContent, ['remoteContentPolicy']);

View File

@ -19,13 +19,21 @@ const DISABLED_BLOCK_TOKENS = [
const DISABLED_INLINE_TOKENS = [];
const RichText = React.memo(({ remoteContentPolicy, ...props }) => (
const RichText = React.memo(({ disableRemoteContent, ...props }) => (
<ReactMarkdown
{...props}
renderers={{
link: (props) => {
if (disableRemoteContent) {
props.remoteContentPolicy = {
imageShown: false,
audioShown: false,
videoShown: false,
oembedShown: false
};
}
if (hasProvider(props.href)) {
return <RemoteContent className="mw-100" url={props.href} remoteContentPolicy={remoteContentPolicy} />;
return <RemoteContent className="mw-100" url={props.href} />;
}
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...props}>{props.children}</BaseAnchor>;
},

View File

@ -4,11 +4,12 @@ import { Row, Box, Text, Icon, Button } from '@tlon/indigo-react';
import ReconnectButton from './ReconnectButton';
import { StatusBarItem } from './StatusBarItem';
import { Sigil } from '~/logic/lib/sigil';
import useLocalState from '~/logic/state/local';
const StatusBar = (props) => {
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
const metaKey = (window.navigator.platform.includes('Mac')) ? '⌘' : 'Ctrl+';
const toggleOmnibox = useLocalState(state => state.toggleOmnibox);
return (
<Box
display='grid'
@ -24,7 +25,7 @@ const StatusBar = (props) => {
<Icon icon='Spaces' color='black'/>
</Button>
<StatusBarItem mr={2} onClick={() => props.api.local.setOmnibox()}>
<StatusBarItem mr={2} onClick={() => toggleOmnibox()}>
{ !props.doNotDisturb && (props.notificationsCount > 0 || invites.length > 0) &&
(<Box display="block" right="-8px" top="-8px" position="absolute" >
<Icon color="blue" icon="Bullet" />

View File

@ -5,6 +5,7 @@ import index from '~/logic/lib/omnibox';
import Mousetrap from 'mousetrap';
import OmniboxInput from './OmniboxInput';
import OmniboxResult from './OmniboxResult';
import { withLocalState } from '~/logic/state/local';
import defaultApps from '~/logic/lib/default-apps';
@ -39,7 +40,7 @@ export class Omnibox extends Component {
}
if (prevProps && this.props.show && prevProps.show !== this.props.show) {
Mousetrap.bind('escape', () => this.props.api.local.setOmnibox());
Mousetrap.bind('escape', this.props.toggle);
document.addEventListener('mousedown', this.handleClickOutside);
const touchstart = new Event('touchstart');
this.omniInput.input.dispatchEvent(touchstart);
@ -63,7 +64,7 @@ export class Omnibox extends Component {
if (this.state.query.length > 0) {
this.setState({ query: '', results: this.initialResults(), selected: [] });
} else if (this.props.show) {
this.props.api.local.setOmnibox();
this.props.toggleOmnibox();
}
};
@ -96,7 +97,7 @@ export class Omnibox extends Component {
handleClickOutside(evt) {
if (this.props.show && !this.omniBox.contains(evt.target)) {
this.setState({ results: this.initialResults(), query: '', selected: [] }, () => {
this.props.api.local.setOmnibox();
this.props.toggleOmnibox();
});
}
}
@ -116,7 +117,7 @@ export class Omnibox extends Component {
navigate(app, link) {
const { props } = this;
this.setState({ results: this.initialResults(), query: '' }, () => {
props.api.local.setOmnibox();
props.toggleOmnibox();
if (defaultApps.includes(app.toLowerCase())
|| app === 'profile'
|| app === 'Links'
@ -299,4 +300,4 @@ export class Omnibox extends Component {
}
}
export default withRouter(Omnibox);
export default withRouter(withLocalState(Omnibox, ['toggleOmnibox', 'omniboxShown']));

View File

@ -19,6 +19,7 @@ import { ColorInput } from "~/views/components/ColorInput";
import GlobalApi from "~/logic/api/global";
import { ImageInput } from "~/views/components/ImageInput";
import { S3State } from "~/types";
import useLocalState from "~/logic/state/local";
interface ContactCardProps {
contact: Contact;
@ -26,8 +27,6 @@ interface ContactCardProps {
api: GlobalApi;
s3: S3State;
rootIdentity: Contact;
hideAvatars: boolean;
hideNicknames: boolean;
}
const formSchema = Yup.object({
@ -72,6 +71,9 @@ const emptyContact = {
};
export function ContactCard(props: ContactCardProps) {
const { hideAvatars, hideNicknames } = useLocalState(({ hideAvatars, hideNicknames }) => ({
hideAvatars, hideNicknames
}));
const us = `~${window.ship}`;
const { contact, rootIdentity } = props;
const onSubmit = async (values: any, actions: FormikHelpers<Contact>) => {
@ -114,11 +116,11 @@ export function ContactCard(props: ContactCardProps) {
};
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
const image = (!props?.hideAvatars && contact?.avatar)
const image = (!hideAvatars && contact?.avatar)
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
: <Sigil ship={us} size={32} color={hexColor} />;
const nickname = (!props.hideNicknames && contact?.nickname) ? contact.nickname : "";
const nickname = (!hideNicknames && contact?.nickname) ? contact.nickname : "";
return (
<Box p={4} height="100%" overflowY="auto">

View File

@ -68,8 +68,6 @@ export function GroupsPane(props: GroupsPaneProps) {
group={group!}
api={api}
s3={props.s3}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
notificationsGroupConfig={props.notificationsGroupConfig}
{...routeProps}

View File

@ -31,6 +31,7 @@ import { Dropdown } from '~/views/components/Dropdown';
import GlobalApi from '~/logic/api/global';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import styled from 'styled-components';
import useLocalState from '~/logic/state/local';
const TruncText = styled(Box)`
white-space: nowrap;
@ -104,10 +105,8 @@ export function Participants(props: {
group: Group;
association: Association;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
}) {
const { api, hideAvatars, hideNicknames } = props;
const { api } = props;
const tabFilters: Record<
ParticipantsTabId,
(p: Participant) => boolean
@ -232,8 +231,6 @@ export function Participants(props: {
group={props.group}
contact={c}
association={props.association}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
/>
))
) : (
@ -254,11 +251,12 @@ function Participant(props: {
group: Group;
role?: RoleTags;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
}) {
const { contact, association, group, api } = props;
const { title } = association.metadata;
const { hideAvatars, hideNicknames } = useLocalState(
({ hideAvatars, hideNicknames }) => ({ hideAvatars, hideNicknames })
);
const color = uxToHex(contact.color);
const isInvite = 'invite' in group.policy;
@ -296,13 +294,13 @@ function Participant(props: {
}, [api, association]);
const avatar =
contact?.avatar !== null && !props.hideAvatars ? (
contact?.avatar !== null && !hideAvatars ? (
<img src={contact.avatar} height={32} width={32} className="dib" />
) : (
<Sigil ship={contact.patp} size={32} color={`#${color}`} />
);
const hasNickname = contact.nickname && !props.hideNicknames;
const hasNickname = contact.nickname && !hideNicknames;
return (
<>

View File

@ -38,8 +38,6 @@ export function PopoverRoutes(
association: Association;
s3: S3State;
api: GlobalApi;
hideAvatars: boolean;
hideNicknames: boolean;
notificationsGroupConfig: GroupNotificationsConfig;
rootIdentity: Contact;
} & RouteComponentProps
@ -135,8 +133,6 @@ export function PopoverRoutes(
contacts={props.contacts}
association={props.association}
api={props.api}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
/>
)}
{view === "profile" && (
@ -144,8 +140,6 @@ export function PopoverRoutes(
contact={props.contacts[window.ship]}
rootIdentity={props.rootIdentity}
api={props.api}
hideAvatars={props.hideAvatars}
hideNicknames={props.hideNicknames}
path={props.association["group-path"]}
s3={props.s3}
/>

View File

@ -38,12 +38,6 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
const workspace =
baseUrl === "/~landscape/home" ? "/home" : association["group-path"];
const title = props.title || association?.metadata?.title;
const disableRemoteContent = {
audioShown: false,
imageShown: false,
oembedShown: false,
videoShown: false,
};
return (
<Col width="100%" height="100%" overflowY="hidden">
<Box
@ -91,9 +85,9 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
>
<RichText
color="gray"
remoteContentPolicy={disableRemoteContent}
mb="0"
display="inline-block"
disableRemoteContent
>
{association?.metadata?.description}
</RichText>