mirror of
https://github.com/urbit/shrub.git
synced 2024-12-23 19:05:48 +03:00
Merge branch 'release/next-js' into ixv/fix-unread-resubscribe
This commit is contained in:
commit
136efa76a3
4
.github/actions/glob/Dockerfile
vendored
Normal file
4
.github/actions/glob/Dockerfile
vendored
Normal 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
25
.github/actions/glob/action.yml
vendored
Normal 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
32
.github/actions/glob/entrypoint.sh
vendored
Executable 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
|
||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -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
23
.github/workflows/glob.yml
vendored
Normal 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 }}
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -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
|
||||
|
27
README.md
27
README.md
@ -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
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:35dab0c5d317e753c3f7baa3f95823bc5a75ac968c8791ef571f654880e1a88a
|
||||
size 8002166
|
||||
oid sha256:17eb2f5a123f5ad29b0cc9ff9069540c349dd97c6133a9ea33cbf81e0bfa4d6b
|
||||
size 8483784
|
||||
|
@ -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
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
::
|
||||
--
|
||||
--
|
||||
|
@ -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)
|
||||
--
|
||||
|
@ -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>
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
==
|
||||
--
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -75,7 +75,7 @@
|
||||
info+(tape ~(ram re tank))
|
||||
==
|
||||
::
|
||||
?(%bel %clr %nex)
|
||||
?(%bel %clr %nex %bye)
|
||||
(frond %act %s -.sef)
|
||||
==
|
||||
--
|
||||
|
@ -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])]
|
||||
==
|
||||
--
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
::
|
||||
|
@ -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
|
||||
|
@ -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) [~ ~]
|
||||
|
@ -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
|
||||
|
10920
pkg/interface/dbug/package-lock.json
generated
10920
pkg/interface/dbug/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
)} />
|
||||
|
16
pkg/interface/package-lock.json
generated
16
pkg/interface/package-lock.json
generated
@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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,
|
||||
})),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
58
pkg/interface/src/logic/state/local.tsx
Normal file
58
pkg/interface/src/logic/state/local.tsx
Normal 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 };
|
@ -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: [],
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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));
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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']);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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>
|
||||
|
@ -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}`}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
))
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
}}
|
||||
>
|
||||
|
@ -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);
|
||||
}}
|
||||
>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
@ -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']);
|
@ -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']);
|
@ -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']);
|
@ -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>;
|
||||
},
|
||||
|
@ -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" />
|
||||
|
@ -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']));
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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 (
|
||||
<>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user