mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 11:33:41 +03:00
Merge branch 'release/next-userspace' into la/group-feed
This commit is contained in:
commit
58bf89d834
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a8b19cbe89f770f8d6c1e05972be7a3a01545b93b0f2d4523809e7df18635f3c
|
||||
size 9462938
|
||||
oid sha256:384f4a66399e32fac3698465d69ddfea1e80f8245f6a5f5cc9e24ff437cc3f61
|
||||
size 9556792
|
||||
|
@ -169,7 +169,7 @@
|
||||
::
|
||||
%fact
|
||||
?+ p.cage.sign ~|([dap.bowl %bad-sub-mark wire p.cage.sign] !!)
|
||||
%graph-update-0
|
||||
%graph-update-1
|
||||
%- on-graph-update:tc
|
||||
!<(update:graph q.cage.sign)
|
||||
==
|
||||
@ -758,9 +758,9 @@
|
||||
::TODO move creation into lib?
|
||||
%^ act %out-message
|
||||
%graph-push-hook
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
!> ^- update:graph
|
||||
:+ %0 now.bowl
|
||||
:- now.bowl
|
||||
:+ %add-nodes audience
|
||||
%- ~(put by *(map index:post node:graph))
|
||||
:- ~[now.bowl]
|
||||
@ -1185,7 +1185,15 @@
|
||||
?- -.content
|
||||
%text txt+(trip text.content)
|
||||
%url url+url.content
|
||||
%reference txt+"[reference to msg in {~(phat tr resource.uid.content)}]"
|
||||
::
|
||||
%reference
|
||||
?- -.reference.content
|
||||
%graph
|
||||
txt+"[reference to msg in {~(phat tr resource.uid.reference.content)}]"
|
||||
::
|
||||
%group
|
||||
txt+"[reference to msg in {~(phat tr group.reference.content)}]"
|
||||
==
|
||||
::
|
||||
%mention
|
||||
?. =(ship.content our-self) txt+(scow %p ship.content)
|
||||
|
@ -154,7 +154,7 @@
|
||||
++ poke-graph-store
|
||||
|= =update:graph-store
|
||||
^- card
|
||||
(poke-our %graph-store %graph-update-0 !>(update))
|
||||
(poke-our %graph-store %graph-update-1 !>(update))
|
||||
::
|
||||
++ nobody
|
||||
^- @p
|
||||
@ -190,7 +190,7 @@
|
||||
cards
|
||||
:_ cards
|
||||
%- poke-graph-store
|
||||
:+ %0 now.bol
|
||||
:- now.bol
|
||||
archive-graph+rid
|
||||
==
|
||||
?: =(our.bol ship)
|
||||
|
@ -247,7 +247,7 @@
|
||||
++ add-graph
|
||||
|= [rid=resource =mailbox:store]
|
||||
%- poke-graph-store
|
||||
:+ %0 now.bol
|
||||
:- now.bol
|
||||
:+ %add-graph rid
|
||||
:- (mailbox-to-graph mailbox)
|
||||
[`%graph-validator-chat %.y]
|
||||
@ -255,7 +255,7 @@
|
||||
++ archive-graph
|
||||
|= rid=resource
|
||||
%- poke-graph-store
|
||||
[%0 now.bol %archive-graph rid]
|
||||
[now.bol %archive-graph rid]
|
||||
::
|
||||
++ nobody
|
||||
^- @p
|
||||
@ -298,7 +298,7 @@
|
||||
++ poke-graph-store
|
||||
|= =update:graph-store
|
||||
^- card
|
||||
[%pass / %agent [our.bol %graph-store] %poke %graph-update-0 !>(update)]
|
||||
[%pass / %agent [our.bol %graph-store] %poke %graph-update-1 !>(update)]
|
||||
::
|
||||
++ letter-to-contents
|
||||
|= =letter:store
|
||||
|
@ -9,7 +9,7 @@
|
||||
update:store
|
||||
%graph-update
|
||||
%graph-push-hook
|
||||
0 0
|
||||
1 1
|
||||
%.n
|
||||
==
|
||||
--
|
||||
@ -41,9 +41,9 @@
|
||||
%- (slog leaf+"nacked {<resource>}" tang)
|
||||
:_ this
|
||||
?. (~(has in get-keys:gra) resource) ~
|
||||
=- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update-0 -]~
|
||||
=- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update-1 -]~
|
||||
!> ^- update:store
|
||||
[%0 now.bowl [%archive-graph resource]]
|
||||
[now.bowl [%archive-graph resource]]
|
||||
::
|
||||
++ on-pull-kick
|
||||
|= =resource
|
||||
|
@ -12,7 +12,7 @@
|
||||
update:store
|
||||
%graph-update
|
||||
%graph-pull-hook
|
||||
0 0
|
||||
1 1
|
||||
==
|
||||
::
|
||||
+$ agent (push-hook:push-hook config)
|
||||
@ -185,7 +185,7 @@
|
||||
(get-graph:gra resource)
|
||||
=/ =time (slav %da i.path)
|
||||
=/ =update-log:store (get-update-log-subset:gra resource time)
|
||||
[%0 now.bowl [%run-updates resource update-log]]
|
||||
[now.bowl [%run-updates resource update-log]]
|
||||
::
|
||||
++ take-update
|
||||
|= =vase
|
||||
|
@ -10,18 +10,20 @@
|
||||
$% state-0
|
||||
state-1
|
||||
state-2
|
||||
state-3
|
||||
==
|
||||
::
|
||||
+$ state-0 [%0 network:store]
|
||||
+$ state-1 [%1 network:store]
|
||||
+$ state-2 [%2 network:store]
|
||||
+$ state-0 [%0 network:zero:store]
|
||||
+$ state-1 [%1 network:zero:store]
|
||||
+$ state-2 [%2 network:zero:store]
|
||||
+$ state-3 [%3 network:store]
|
||||
::
|
||||
++ orm orm:store
|
||||
++ orm-log orm-log:store
|
||||
+$ debug-input [%validate-graph =resource:store]
|
||||
--
|
||||
::
|
||||
=| state-2
|
||||
=| state-3
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -60,134 +62,149 @@
|
||||
::
|
||||
graphs.old
|
||||
%- ~(run by graphs.old)
|
||||
|= [=graph:store q=(unit mark)]
|
||||
^- [graph:store (unit mark)]
|
||||
:- (convert-unix-timestamped-graph graph)
|
||||
|= [=graph:zero:store q=(unit mark)]
|
||||
^- [graph:zero:store (unit mark)]
|
||||
:- (convert-unix-timestamped-graph:zero-load graph)
|
||||
?^ q q
|
||||
`%graph-validator-link
|
||||
::
|
||||
update-logs.old
|
||||
%- ~(run by update-logs.old)
|
||||
|=(a=* *update-log:store)
|
||||
|=(a=* *update-log:zero:store)
|
||||
==
|
||||
::
|
||||
%1
|
||||
%_ $
|
||||
-.old %2
|
||||
graphs.old (~(run by graphs.old) change-revision-graph)
|
||||
graphs.old (~(run by graphs.old) change-revision-graph:zero-load)
|
||||
::
|
||||
update-logs.old
|
||||
%- ~(run by update-logs.old)
|
||||
|=(a=* *update-log:store)
|
||||
|=(a=* *update-log:zero:store)
|
||||
==
|
||||
::
|
||||
%2 [cards this(state old)]
|
||||
%2
|
||||
%_ $
|
||||
-.old %3
|
||||
update-logs.old (~(run by update-logs.old) update-log-to-one:store)
|
||||
graphs.old (~(run by graphs.old) marked-graph-to-one:store)
|
||||
archive.old (~(run by archive.old) marked-graph-to-one:store)
|
||||
==
|
||||
::
|
||||
%3 [cards this(state old)]
|
||||
==
|
||||
::
|
||||
++ change-revision-graph
|
||||
|= [=graph:store q=(unit mark)]
|
||||
^- [graph:store (unit mark)]
|
||||
|^
|
||||
:_ q
|
||||
?+ q graph
|
||||
[~ %graph-validator-link] convert-links
|
||||
[~ %graph-validator-publish] convert-publish
|
||||
==
|
||||
::
|
||||
++ convert-links
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm graph)
|
||||
|= [=atom =node:store]
|
||||
^- [^atom node:store]
|
||||
:: top-level
|
||||
++ zero-load
|
||||
:: =* infinitely recurses
|
||||
=, store=zero:store
|
||||
=, orm=orm:zero:store
|
||||
=, orm-log=orm-log:zero:store
|
||||
|%
|
||||
++ change-revision-graph
|
||||
|= [=graph:store q=(unit mark)]
|
||||
^- [graph:store (unit mark)]
|
||||
|^
|
||||
:_ q
|
||||
?+ q graph
|
||||
[~ %graph-validator-link] convert-links
|
||||
[~ %graph-validator-publish] convert-publish
|
||||
==
|
||||
::
|
||||
:+ atom post.node
|
||||
?: ?=(%empty -.children.node)
|
||||
[%empty ~]
|
||||
:- %graph
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm p.children.node)
|
||||
|= [=^atom =node:store]
|
||||
^- [^^atom node:store]
|
||||
:: existing comments get turned into containers for revisions
|
||||
::
|
||||
:^ atom
|
||||
post.node(contents ~, hash ~)
|
||||
%graph
|
||||
%+ gas:orm *graph:store
|
||||
:_ ~ :- %1
|
||||
:_ [%empty ~]
|
||||
post.node(index (snoc index.post.node atom), hash ~)
|
||||
::
|
||||
++ convert-publish
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm graph)
|
||||
|= [=atom =node:store]
|
||||
^- [^atom node:store]
|
||||
:: top-level
|
||||
::
|
||||
:+ atom post.node
|
||||
?: ?=(%empty -.children.node)
|
||||
[%empty ~]
|
||||
:- %graph
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm p.children.node)
|
||||
|= [=^atom =node:store]
|
||||
^- [^^atom node:store]
|
||||
:: existing container for publish note revisions
|
||||
::
|
||||
?+ atom !!
|
||||
%1 [atom node]
|
||||
%2
|
||||
++ convert-links
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm graph)
|
||||
|= [=atom =node:store]
|
||||
^- [^atom node:store]
|
||||
:: top-level
|
||||
::
|
||||
:+ atom post.node
|
||||
?: ?=(%empty -.children.node)
|
||||
[%empty ~]
|
||||
:- %graph
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm p.children.node)
|
||||
|= [=^^atom =node:store]
|
||||
^- [^^^atom node:store]
|
||||
:+ atom post.node(contents ~, hash ~)
|
||||
:- %graph
|
||||
|= [=^atom =node:store]
|
||||
^- [^^atom node:store]
|
||||
:: existing comments get turned into containers for revisions
|
||||
::
|
||||
:^ atom
|
||||
post.node(contents ~, hash ~)
|
||||
%graph
|
||||
%+ gas:orm *graph:store
|
||||
:_ ~ :- %1
|
||||
:_ ~ :- %0
|
||||
:_ [%empty ~]
|
||||
post.node(index (snoc index.post.node atom), hash ~)
|
||||
==
|
||||
::
|
||||
++ convert-publish
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm graph)
|
||||
|= [=atom =node:store]
|
||||
^- [^atom node:store]
|
||||
:: top-level
|
||||
::
|
||||
:+ atom post.node
|
||||
?: ?=(%empty -.children.node)
|
||||
[%empty ~]
|
||||
:- %graph
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm p.children.node)
|
||||
|= [=^atom =node:store]
|
||||
^- [^^atom node:store]
|
||||
:: existing container for publish note revisions
|
||||
::
|
||||
?+ atom !!
|
||||
%1 [atom node]
|
||||
%2
|
||||
:+ atom post.node
|
||||
?: ?=(%empty -.children.node)
|
||||
[%empty ~]
|
||||
:- %graph
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn (tap:orm p.children.node)
|
||||
|= [=^^atom =node:store]
|
||||
^- [^^^atom node:store]
|
||||
:+ atom post.node(contents ~, hash ~)
|
||||
:- %graph
|
||||
%+ gas:orm *graph:store
|
||||
:_ ~ :- %1
|
||||
:_ [%empty ~]
|
||||
post.node(index (snoc index.post.node atom), hash ~)
|
||||
==
|
||||
--
|
||||
::
|
||||
++ maybe-unix-to-da
|
||||
|= =atom
|
||||
^- @
|
||||
:: (bex 127) is roughly 226AD
|
||||
?. (lte atom (bex 127))
|
||||
atom
|
||||
(add ~1970.1.1 (div (mul ~s1 atom) 1.000))
|
||||
::
|
||||
++ convert-unix-timestamped-node
|
||||
|= =node:store
|
||||
^- node:store
|
||||
=. index.post.node
|
||||
(convert-unix-timestamped-index index.post.node)
|
||||
?. ?=(%graph -.children.node)
|
||||
node
|
||||
:+ post.node
|
||||
%graph
|
||||
(convert-unix-timestamped-graph p.children.node)
|
||||
::
|
||||
++ convert-unix-timestamped-index
|
||||
|= =index:store
|
||||
(turn index maybe-unix-to-da)
|
||||
::
|
||||
++ convert-unix-timestamped-graph
|
||||
|= =graph:store
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn
|
||||
(tap:orm graph)
|
||||
|= [=atom =node:store]
|
||||
^- [^atom node:store]
|
||||
:- (maybe-unix-to-da atom)
|
||||
(convert-unix-timestamped-node node)
|
||||
--
|
||||
::
|
||||
++ maybe-unix-to-da
|
||||
|= =atom
|
||||
^- @
|
||||
:: (bex 127) is roughly 226AD
|
||||
?. (lte atom (bex 127))
|
||||
atom
|
||||
(add ~1970.1.1 (div (mul ~s1 atom) 1.000))
|
||||
::
|
||||
++ convert-unix-timestamped-node
|
||||
|= =node:store
|
||||
^- node:store
|
||||
=. index.post.node
|
||||
(convert-unix-timestamped-index index.post.node)
|
||||
?. ?=(%graph -.children.node)
|
||||
node
|
||||
:+ post.node
|
||||
%graph
|
||||
(convert-unix-timestamped-graph p.children.node)
|
||||
::
|
||||
++ convert-unix-timestamped-index
|
||||
|= =index:store
|
||||
(turn index maybe-unix-to-da)
|
||||
::
|
||||
++ convert-unix-timestamped-graph
|
||||
|= =graph:store
|
||||
%+ gas:orm *graph:store
|
||||
%+ turn
|
||||
(tap:orm graph)
|
||||
|= [=atom =node:store]
|
||||
^- [^atom node:store]
|
||||
:- (maybe-unix-to-da atom)
|
||||
(convert-unix-timestamped-node node)
|
||||
--
|
||||
::
|
||||
++ on-watch
|
||||
@ -205,9 +222,9 @@
|
||||
[cards this]
|
||||
::
|
||||
++ give
|
||||
|= =update-0:store
|
||||
|= =action:store
|
||||
^- (list card)
|
||||
[%give %fact ~ [%graph-update-0 !>([%0 now.bowl update-0])]]~
|
||||
[%give %fact ~ [%graph-update-1 !>([now.bowl action])]]~
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
@ -217,10 +234,10 @@
|
||||
|^
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=^ cards state
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%graph-update-0 (graph-update !<(update:store vase))
|
||||
%noun (debug !<(debug-input vase))
|
||||
%import (poke-import q.vase)
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%graph-update-1 (graph-update !<(update:store vase))
|
||||
%noun (debug !<(debug-input vase))
|
||||
%import (poke-import q.vase)
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
@ -228,7 +245,6 @@
|
||||
|= =update:store
|
||||
^- (quip card _state)
|
||||
|^
|
||||
?> ?=(%0 -.update)
|
||||
=? p.update =(p.update *time) now.bowl
|
||||
?- -.q.update
|
||||
%add-graph (add-graph p.update +.q.update)
|
||||
@ -261,7 +277,7 @@
|
||||
== ==
|
||||
?> (validate-graph graph mark)
|
||||
=/ =logged-update:store
|
||||
[%0 time %add-graph resource graph mark overwrite]
|
||||
[time %add-graph resource graph mark overwrite]
|
||||
=/ =update-log:store
|
||||
(gas:orm-log ~ [time logged-update] ~)
|
||||
:_ %_ state
|
||||
@ -307,7 +323,7 @@
|
||||
?< (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]])
|
||||
(put:orm-log update-log time [time [%add-nodes resource nodes]])
|
||||
::
|
||||
:- (give [/updates]~ [%add-nodes resource nodes])
|
||||
%_ state
|
||||
@ -423,7 +439,7 @@
|
||||
(~(got by graphs) resource)
|
||||
=/ =update-log:store (~(got by update-logs) resource)
|
||||
=. update-log
|
||||
(put:orm-log update-log time [%0 time [%remove-nodes resource indices]])
|
||||
(put:orm-log update-log time [time [%remove-nodes resource indices]])
|
||||
=/ [affected-indices=(set index:store) new-graph=graph:store]
|
||||
(remove-indices resource graph (sort ~(tap in indices) by-lent))
|
||||
::
|
||||
@ -510,7 +526,7 @@
|
||||
(~(got by graphs) resource)
|
||||
=/ =update-log:store (~(got by update-logs) resource)
|
||||
=. update-log
|
||||
(put:orm-log update-log time [%0 time [%add-signatures uid signatures]])
|
||||
(put:orm-log update-log time [time [%add-signatures uid signatures]])
|
||||
::
|
||||
:- (give [/updates]~ [%add-signatures uid signatures])
|
||||
%_ state
|
||||
@ -555,7 +571,7 @@
|
||||
=. update-log
|
||||
%^ put:orm-log update-log
|
||||
time
|
||||
[%0 time [%remove-signatures uid signatures]]
|
||||
[time [%remove-signatures uid signatures]]
|
||||
::
|
||||
:- (give [/updates]~ [%remove-signatures uid signatures])
|
||||
%_ state
|
||||
@ -658,9 +674,9 @@
|
||||
$(cards (weld cards crds), updates t.updates)
|
||||
::
|
||||
++ give
|
||||
|= [paths=(list path) update=update-0:store]
|
||||
|= [paths=(list path) update=action:store]
|
||||
^- (list card)
|
||||
[%give %fact paths [%graph-update-0 !>([%0 now.bowl update])]]~
|
||||
[%give %fact paths [%graph-update-1 !>([now.bowl update])]]~
|
||||
--
|
||||
::
|
||||
++ debug
|
||||
@ -695,7 +711,7 @@
|
||||
|= arc=*
|
||||
^- (quip card _state)
|
||||
|^
|
||||
=/ sty=state-2 [%2 (remake-network ;;(tree-network +.arc))]
|
||||
=/ sty=state-3 [%3 (remake-network ;;(tree-network +.arc))]
|
||||
:_ sty
|
||||
%+ turn ~(tap by graphs.sty)
|
||||
|= [rid=resource:store =marked-graph:store]
|
||||
@ -724,8 +740,7 @@
|
||||
+$ tree-update-logs (tree [resource:store tree-update-log])
|
||||
+$ tree-update-log (tree [time tree-logged-update])
|
||||
+$ tree-logged-update
|
||||
$: %0
|
||||
p=time
|
||||
$: p=time
|
||||
$= q
|
||||
$% [%add-graph =resource:store =tree-graph mark=(unit ^mark) ow=?]
|
||||
[%add-nodes =resource:store nodes=(tree [index:store tree-node])]
|
||||
@ -806,7 +821,7 @@
|
||||
++ remake-logged-update
|
||||
|= t=tree-logged-update
|
||||
^- logged-update:store
|
||||
:+ %0 p.t
|
||||
:- p.t
|
||||
?- -.q.t
|
||||
%add-graph
|
||||
:* %add-graph
|
||||
@ -862,16 +877,16 @@
|
||||
``noun+!>(q.u.result)
|
||||
::
|
||||
[%x %keys ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
!>(`update:store`[%0 now.bowl [%keys ~(key by graphs)]])
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!>(`update:store`[now.bowl [%keys ~(key by graphs)]])
|
||||
::
|
||||
[%x %tags ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
!>(`update:store`[%0 now.bowl [%tags ~(key by tag-queries)]])
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!>(`update:store`[now.bowl [%tags ~(key by tag-queries)]])
|
||||
::
|
||||
[%x %tag-queries ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
!>(`update:store`[%0 now.bowl [%tag-queries tag-queries]])
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!>(`update:store`[now.bowl [%tag-queries tag-queries]])
|
||||
::
|
||||
[%x %graph @ @ ~]
|
||||
=/ =ship (slav %p i.t.t.path)
|
||||
@ -879,10 +894,9 @@
|
||||
=/ result=(unit marked-graph:store)
|
||||
(~(get by graphs) [ship term])
|
||||
?~ result [~ ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0
|
||||
now.bowl
|
||||
:- now.bowl
|
||||
[%add-graph [ship term] `graph:store`p.u.result q.u.result %.y]
|
||||
::
|
||||
:: note: near-duplicate of /x/graph
|
||||
@ -895,10 +909,9 @@
|
||||
?~ result
|
||||
~& no-archived-graph+[ship term]
|
||||
[~ ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0
|
||||
now.bowl
|
||||
:- now.bowl
|
||||
[%add-graph [ship term] `graph:store`p.u.result q.u.result %.y]
|
||||
::
|
||||
[%x %export ~]
|
||||
@ -912,9 +925,9 @@
|
||||
=/ graph=(unit marked-graph:store)
|
||||
(~(get by graphs) [ship term])
|
||||
?~ graph [~ ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0 now.bowl
|
||||
:- now.bowl
|
||||
:+ %add-nodes
|
||||
[ship term]
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
@ -939,10 +952,9 @@
|
||||
(turn t.t.t.t.path (cury slav %ud))
|
||||
=/ node=(unit node:store) (get-node ship term index)
|
||||
?~ node [~ ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0
|
||||
now.bowl
|
||||
:- now.bowl
|
||||
:+ %add-nodes
|
||||
[ship term]
|
||||
(~(gas by *(map index:store node:store)) [index u.node] ~)
|
||||
@ -959,10 +971,9 @@
|
||||
=/ graph
|
||||
(get-node-children ship term parent)
|
||||
?~ graph [~ ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0
|
||||
now.bowl
|
||||
:- now.bowl
|
||||
:+ %add-nodes
|
||||
[ship term]
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
@ -990,10 +1001,9 @@
|
||||
=/ children
|
||||
(get-node-children ship term index)
|
||||
?~ children [~ ~]
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0
|
||||
now.bowl
|
||||
:- now.bowl
|
||||
:+ %add-nodes
|
||||
[ship term]
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
@ -1017,10 +1027,9 @@
|
||||
?- -.children.u.node
|
||||
%empty [~ ~]
|
||||
%graph
|
||||
:- ~ :- ~ :- %graph-update-0
|
||||
:- ~ :- ~ :- %graph-update-1
|
||||
!> ^- update:store
|
||||
:+ %0
|
||||
now.bowl
|
||||
:- now.bowl
|
||||
:+ %add-nodes
|
||||
[ship term]
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
|
@ -182,7 +182,7 @@
|
||||
~[watch-graph:ha]
|
||||
::
|
||||
%fact
|
||||
?. ?=(%graph-update-0 p.cage.sign)
|
||||
?. ?=(%graph-update-1 p.cage.sign)
|
||||
(on-agent:def wire sign)
|
||||
=^ cards state
|
||||
(graph-update !<(update:graph-store q.cage.sign))
|
||||
|
@ -24,11 +24,12 @@
|
||||
state-3
|
||||
state-4
|
||||
state-5
|
||||
state-6
|
||||
==
|
||||
+$ unread-stats
|
||||
[indices=(set index:graph-store) last=@da]
|
||||
::
|
||||
+$ base-state
|
||||
+$ base-state
|
||||
$: unreads-each=(jug stats-index:store index:graph-store)
|
||||
unreads-count=(map stats-index:store @ud)
|
||||
last-seen=(map stats-index:store @da)
|
||||
@ -45,13 +46,16 @@
|
||||
[%3 state-two:store]
|
||||
::
|
||||
+$ state-4
|
||||
[%4 base-state]
|
||||
[%4 state-three:store]
|
||||
::
|
||||
+$ state-5
|
||||
[%5 base-state]
|
||||
[%5 state-three:store]
|
||||
::
|
||||
+$ state-6
|
||||
[%6 base-state]
|
||||
::
|
||||
+$ inflated-state
|
||||
$: state-5
|
||||
$: state-6
|
||||
cache
|
||||
==
|
||||
:: $cache: useful to have precalculated, but can be derived from state
|
||||
@ -92,9 +96,16 @@
|
||||
=| cards=(list card)
|
||||
|^
|
||||
?- -.old
|
||||
%5
|
||||
%6
|
||||
:- (flop cards)
|
||||
this(-.state old, +.state (inflate-cache:ha old))
|
||||
::
|
||||
%5
|
||||
%_ $
|
||||
-.old %6
|
||||
notifications.old (convert-notifications-4 notifications.old)
|
||||
archive.old (convert-notifications-4 archive.old)
|
||||
==
|
||||
::
|
||||
%4
|
||||
%_ $
|
||||
@ -149,15 +160,59 @@
|
||||
==
|
||||
==
|
||||
::
|
||||
++ convert-notifications-3
|
||||
|= old=notifications:state-two:store
|
||||
++ convert-notifications-4
|
||||
|= old=notifications:state-three:store
|
||||
%+ gas:orm *notifications:store
|
||||
^- (list [@da timebox:store])
|
||||
%+ murn
|
||||
(tap:orm:state-two:store old)
|
||||
|= [time=@da =timebox:state-two:store]
|
||||
(tap:orm:state-three:store old)
|
||||
|= [time=@da =timebox:state-three:store]
|
||||
^- (unit [@da timebox:store])
|
||||
=/ new-timebox=timebox:store
|
||||
(convert-timebox-4 timebox)
|
||||
?: =(0 ~(wyt by new-timebox))
|
||||
~
|
||||
`[time new-timebox]
|
||||
::
|
||||
++ convert-timebox-4
|
||||
|= =timebox:state-three:store
|
||||
^- timebox:store
|
||||
%- ~(gas by *timebox:store)
|
||||
^- (list [index:store notification:store])
|
||||
%+ murn
|
||||
~(tap by timebox)
|
||||
|= [=index:store =notification:state-three:store]
|
||||
^- (unit [index:store notification:store])
|
||||
=/ new-notification=(unit notification:store)
|
||||
(convert-notification-4 notification)
|
||||
?~ new-notification ~
|
||||
`[index u.new-notification]
|
||||
::
|
||||
++ convert-notification-4
|
||||
|= =notification:state-three:store
|
||||
^- (unit notification:store)
|
||||
?: ?=(%group -.contents.notification)
|
||||
`notification
|
||||
=/ con=(list post:post)
|
||||
(convert-graph-contents-4 list.contents.notification)
|
||||
?: =(~ con) ~
|
||||
=, notification
|
||||
`[date read %graph con]
|
||||
::
|
||||
++ convert-graph-contents-4
|
||||
|= con=(list post:post-zero:post)
|
||||
^- (list post:post)
|
||||
(turn con post-to-one:graph-store)
|
||||
::
|
||||
++ convert-notifications-3
|
||||
|= old=notifications:state-two:store
|
||||
%+ gas:orm:state-three:store *notifications:state-three:store
|
||||
^- (list [@da timebox:state-three:store])
|
||||
%+ murn
|
||||
(tap:orm:state-two:store old)
|
||||
|= [time=@da =timebox:state-two:store]
|
||||
^- (unit [@da timebox:state-three:store])
|
||||
=/ new-timebox=timebox:state-three:store
|
||||
(convert-timebox-3 timebox)
|
||||
?: =(0 ~(wyt by new-timebox))
|
||||
~
|
||||
@ -165,21 +220,21 @@
|
||||
::
|
||||
++ convert-timebox-3
|
||||
|= =timebox:state-two:store
|
||||
^- timebox:store
|
||||
%- ~(gas by *timebox:store)
|
||||
^- (list [index:store notification:store])
|
||||
^- timebox:state-three:store
|
||||
%- ~(gas by *timebox:state-three:store)
|
||||
^- (list [index:state-three:store notification:state-three:store])
|
||||
%+ murn
|
||||
~(tap by timebox)
|
||||
|= [=index:store =notification:state-two:store]
|
||||
^- (unit [index:store notification:store])
|
||||
=/ new-notification=(unit notification:store)
|
||||
^- (unit [index:store notification:state-three:store])
|
||||
=/ new-notification=(unit notification:state-three:store)
|
||||
(convert-notification-3 notification)
|
||||
?~ new-notification ~
|
||||
`[index u.new-notification]
|
||||
::
|
||||
++ convert-notification-3
|
||||
|= =notification:state-two:store
|
||||
^- (unit notification:store)
|
||||
^- (unit notification:state-three:store)
|
||||
?: ?=(%graph -.contents.notification)
|
||||
`notification
|
||||
=/ con=(list group-contents:store)
|
||||
@ -778,7 +833,7 @@
|
||||
==
|
||||
::
|
||||
++ inflate-cache
|
||||
|= state-5
|
||||
|= state-6
|
||||
^+ +.state
|
||||
=. +.state
|
||||
*cache
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=resource mark=(unit mark) overwrite=? ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%add-graph resource (gas:orm ~ ~) mark overwrite]]
|
||||
[now [%add-graph resource (gas:orm ~ ~) mark overwrite]]
|
||||
|
@ -12,9 +12,9 @@
|
||||
contents.post contents
|
||||
==
|
||||
::
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
:+ %0 now
|
||||
:- now
|
||||
:+ %add-nodes [our name]
|
||||
%- ~(gas by *(map index node))
|
||||
~[[[now]~ [post [%empty ~]]]]
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[[=resource =index] =signatures ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%add-signatures [resource index] signatures]]
|
||||
[now [%add-signatures [resource index] signatures]]
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=term =resource ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%add-tag term resource]]
|
||||
[now [%add-tag term resource]]
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=resource ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%archive-graph resource]]
|
||||
[now [%archive-graph resource]]
|
||||
|
@ -4,7 +4,7 @@
|
||||
|= $: [now=@da eny=@uvJ bec=beak]
|
||||
[[=ship graph=term ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
=/ our (scot %p p.bec)
|
||||
=/ wen (scot %da now)
|
||||
=/ who (scot %p ship)
|
||||
|
@ -4,6 +4,6 @@
|
||||
|= $: [now=@da eny=@uvJ bec=beak]
|
||||
[[graph=term =path ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
=- ~& update=- -
|
||||
.^(=update:graph-store %cx path)
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=resource ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%remove-graph resource]]
|
||||
[now [%remove-graph resource]]
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=resource indices=(set index) ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%remove-nodes resource indices]]
|
||||
[now [%remove-nodes resource indices]]
|
||||
|
@ -6,6 +6,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[[=resource =index] =signatures ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%remove-signatures [resource index] signatures]]
|
||||
[now [%remove-signatures [resource index] signatures]]
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=term =resource ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%remove-tag term resource]]
|
||||
[now [%remove-tag term resource]]
|
||||
|
@ -5,6 +5,6 @@
|
||||
|= $: [now=@da eny=@uvJ =beak]
|
||||
[[=resource ~] ~]
|
||||
==
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
^- update
|
||||
[%0 now [%unarchive-graph resource]]
|
||||
[now [%unarchive-graph resource]]
|
||||
|
@ -66,7 +66,7 @@
|
||||
=/ real=(set resource:re)
|
||||
=/ upd=update:ga
|
||||
%+ scry update:ga
|
||||
[%x %graph-store /keys/graph-update-0]
|
||||
[%x %graph-store /keys/graph-update-1]
|
||||
?> ?=(%keys -.q.upd)
|
||||
resources.q.upd
|
||||
:: count activity per channel
|
||||
|
@ -5,6 +5,101 @@
|
||||
=, sur
|
||||
=, pos
|
||||
|%
|
||||
::
|
||||
++ update-log-to-one
|
||||
|= =update-log:zero
|
||||
^- ^update-log
|
||||
%+ gas:orm-log *^update-log
|
||||
%+ turn (tap:orm-log:zero update-log)
|
||||
|= [=time =logged-update:zero]
|
||||
:- time
|
||||
:- p.logged-update
|
||||
(logged-update-to-one q.logged-update)
|
||||
::
|
||||
++ logged-update-to-one
|
||||
|= upd=logged-update-0:zero
|
||||
?+ -.upd upd
|
||||
%add-graph upd(graph (graph-to-one graph.upd))
|
||||
%add-nodes upd(nodes (~(run by nodes.upd) node-to-one))
|
||||
==
|
||||
::
|
||||
++ node-to-one
|
||||
|= =node:zero
|
||||
(node:(upgrade ,post:zero ,post) node post-to-one)
|
||||
::
|
||||
++ graph-to-one
|
||||
|= =graph:zero
|
||||
(graph:(upgrade ,post:zero ,post) graph post-to-one)
|
||||
::
|
||||
++ marked-graph-to-one
|
||||
|= [=graph:zero m=(unit mark)]
|
||||
[(graph-to-one graph) m]
|
||||
::
|
||||
++ post-to-one
|
||||
|= p=post:zero
|
||||
^- post
|
||||
p(contents (contents-to-one contents.p))
|
||||
::
|
||||
++ contents-to-one
|
||||
|= cs=(list content:zero)
|
||||
^- (list content)
|
||||
%+ murn cs
|
||||
|= =content:zero
|
||||
^- (unit ^content)
|
||||
?: ?=(%reference -.content) ~
|
||||
`content
|
||||
::
|
||||
++ upgrade
|
||||
|* [in-pst=mold out-pst=mold]
|
||||
=>
|
||||
|%
|
||||
++ in-orm
|
||||
((ordered-map atom in-node) gth)
|
||||
+$ in-node
|
||||
[post=in-pst children=in-internal-graph]
|
||||
+$ in-graph
|
||||
((mop atom in-node) gth)
|
||||
+$ in-internal-graph
|
||||
$~ [%empty ~]
|
||||
$% [%graph p=in-graph]
|
||||
[%empty ~]
|
||||
==
|
||||
::
|
||||
++ out-orm
|
||||
((ordered-map atom out-node) gth)
|
||||
+$ out-node
|
||||
[post=out-pst children=out-internal-graph]
|
||||
+$ out-graph
|
||||
((mop atom out-node) gth)
|
||||
+$ out-internal-graph
|
||||
$~ [%empty ~]
|
||||
$% [%graph p=out-graph]
|
||||
[%empty ~]
|
||||
==
|
||||
--
|
||||
|%
|
||||
::
|
||||
++ graph
|
||||
|= $: gra=in-graph
|
||||
fn=$-(in-pst out-pst)
|
||||
==
|
||||
^- out-graph
|
||||
%+ gas:out-orm *out-graph
|
||||
^- (list [atom out-node])
|
||||
%+ turn (tap:in-orm gra)
|
||||
|= [a=atom n=in-node]
|
||||
^- [atom out-node]
|
||||
[a (node n fn)]
|
||||
::
|
||||
++ node
|
||||
|= [nod=in-node fn=$-(in-pst out-pst)]
|
||||
^- out-node
|
||||
:- (fn post.nod)
|
||||
^- out-internal-graph
|
||||
?: ?=(%empty -.children.nod)
|
||||
[%empty ~]
|
||||
[%graph (graph p.children.nod fn)]
|
||||
--
|
||||
:: NOTE: move these functions to zuse
|
||||
++ nu :: parse number as hex
|
||||
|= jon=json
|
||||
@ -78,7 +173,7 @@
|
||||
%mention (frond %mention (ship ship.c))
|
||||
%text (frond %text s+text.c)
|
||||
%url (frond %url s+url.c)
|
||||
%reference (frond %reference (uid uid.c))
|
||||
%reference (frond %reference (reference +.c))
|
||||
%code
|
||||
%+ frond %code
|
||||
%- pairs
|
||||
@ -95,6 +190,28 @@
|
||||
==
|
||||
==
|
||||
::
|
||||
++ reference
|
||||
|= ref=^reference
|
||||
|^
|
||||
%+ frond -.ref
|
||||
?- -.ref
|
||||
%graph (graph +.ref)
|
||||
%group (group +.ref)
|
||||
==
|
||||
::
|
||||
++ graph
|
||||
|= [grp=res gra=res idx=^index]
|
||||
%- pairs
|
||||
:~ graph+s+(enjs-path:res gra)
|
||||
group+s+(enjs-path:res grp)
|
||||
index+(index idx)
|
||||
==
|
||||
::
|
||||
++ group
|
||||
|= grp=res
|
||||
s+(enjs-path:res grp)
|
||||
--
|
||||
::
|
||||
++ post
|
||||
|= p=^post
|
||||
^- json
|
||||
@ -114,7 +231,7 @@
|
||||
|^ (frond %graph-update (pairs ~[(encode q.upd)]))
|
||||
::
|
||||
++ encode
|
||||
|= upd=update-0
|
||||
|= upd=action
|
||||
^- [cord json]
|
||||
?- -.upd
|
||||
%add-graph
|
||||
@ -247,9 +364,8 @@
|
||||
++ update
|
||||
|= jon=json
|
||||
^- ^update
|
||||
:- %0
|
||||
:- *time
|
||||
^- update-0
|
||||
^- action
|
||||
=< (decode jon)
|
||||
|%
|
||||
++ decode
|
||||
@ -333,10 +449,25 @@
|
||||
:~ [%mention (su ;~(pfix sig fed:ag))]
|
||||
[%text so]
|
||||
[%url so]
|
||||
[%reference uid]
|
||||
[%reference reference]
|
||||
[%code eval]
|
||||
==
|
||||
::
|
||||
++ reference
|
||||
|^
|
||||
%- of
|
||||
:~ graph+graph
|
||||
group+dejs-path:res
|
||||
==
|
||||
::
|
||||
++ graph
|
||||
%- ot
|
||||
:~ group+dejs-path:res
|
||||
graph+dejs-path:res
|
||||
index+index
|
||||
==
|
||||
--
|
||||
::
|
||||
++ tang
|
||||
|= jon=^json
|
||||
^- ^tang
|
||||
|
@ -32,6 +32,47 @@
|
||||
%run-updates ~[resource.q.update]
|
||||
==
|
||||
::
|
||||
++ upgrade
|
||||
|* [pst=mold out-pst=mold]
|
||||
=>
|
||||
|%
|
||||
++ orm
|
||||
((ordered-map atom node) gth)
|
||||
+$ node
|
||||
[post=pst children=internal-graph]
|
||||
+$ graph
|
||||
((mop atom node) gth)
|
||||
+$ internal-graph
|
||||
$~ [%empty ~]
|
||||
$% [%graph p=graph]
|
||||
[%empty ~]
|
||||
==
|
||||
::
|
||||
++ out-orm
|
||||
((ordered-map atom out-node) gth)
|
||||
+$ out-node
|
||||
[post=out-pst children=out-internal-graph]
|
||||
+$ out-graph
|
||||
((mop atom out-node) gth)
|
||||
+$ out-internal-graph
|
||||
$~ [%empty ~]
|
||||
$% [%graph p=out-graph]
|
||||
[%empty ~]
|
||||
==
|
||||
--
|
||||
|
||||
|= $: gra=graph
|
||||
fn=$-(pst out-pst)
|
||||
==
|
||||
^- out-graph
|
||||
%- gas:out-orm
|
||||
%+ turn (tap:orm gra)
|
||||
|= [=atom =node]
|
||||
:- (fn post.node)
|
||||
?: ?=(%empty -.children.node)
|
||||
[%empty ~]
|
||||
$(gra p.children.node)
|
||||
::
|
||||
++ get-graph
|
||||
|= res=resource
|
||||
^- update:store
|
||||
@ -43,7 +84,6 @@
|
||||
^- graph:store
|
||||
=/ =update:store
|
||||
(get-graph res)
|
||||
?> ?=(%0 -.update)
|
||||
?> ?=(%add-graph -.q.update)
|
||||
graph.q.update
|
||||
::
|
||||
@ -54,7 +94,6 @@
|
||||
%+ weld
|
||||
/node-siblings/younger/(scot %p entity.res)/[name.res]/all
|
||||
(turn index (cury scot %ud))
|
||||
?> ?=(%0 -.update)
|
||||
?> ?=(%add-nodes -.q.update)
|
||||
nodes.q.update
|
||||
::
|
||||
@ -65,7 +104,6 @@
|
||||
%+ weld
|
||||
/node/(scot %p entity.res)/[name.res]
|
||||
(turn index (cury scot %ud))
|
||||
?> ?=(%0 -.update)
|
||||
?> ?=(%add-nodes -.q.update)
|
||||
?> ?=(^ nodes.q.update)
|
||||
q.n.nodes.q.update
|
||||
@ -99,7 +137,6 @@
|
||||
^- resources
|
||||
=+ %+ scry-for ,=update:store
|
||||
/keys
|
||||
?> ?=(%0 -.update)
|
||||
?> ?=(%keys -.q.update)
|
||||
resources.q.update
|
||||
::
|
||||
|
@ -246,7 +246,6 @@
|
||||
?: (is-root:ver mark)
|
||||
:_ this
|
||||
(forward-update:hc mark vase)
|
||||
::
|
||||
=^ cards push-hook
|
||||
(on-poke:og mark vase)
|
||||
[cards this]
|
||||
|
@ -1,20 +1,18 @@
|
||||
/+ *graph-store
|
||||
=* as-octs as-octs:mimes:html
|
||||
::
|
||||
|_ upd=update
|
||||
|_ upd=update:zero
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ json (update:enjs upd)
|
||||
++ graph-update upd
|
||||
++ mime [/application/x-urb-graph-update (as-octs (jam upd))]
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun update
|
||||
++ json update:dejs
|
||||
++ mime |=([* =octs] ;;(update (cue q.octs)))
|
||||
++ noun update:zero
|
||||
++ mime |=([* =octs] ;;(update:zero (cue q.octs)))
|
||||
--
|
||||
--
|
||||
|
19
pkg/arvo/mar/graph/update-1.hoon
Normal file
19
pkg/arvo/mar/graph/update-1.hoon
Normal file
@ -0,0 +1,19 @@
|
||||
/+ *graph-store
|
||||
=* as-octs as-octs:mimes:html
|
||||
::
|
||||
|_ upd=update
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ json (update:enjs upd)
|
||||
++ mime [/application/x-urb-graph-update (as-octs (jam upd))]
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun update
|
||||
++ json update:dejs
|
||||
++ mime |=([* =octs] ;;(update (cue q.octs)))
|
||||
--
|
||||
--
|
@ -1,20 +1,18 @@
|
||||
/+ *graph-store
|
||||
=* as-octs as-octs:mimes:html
|
||||
::
|
||||
|_ upd=update
|
||||
|_ upd=update:zero
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun upd
|
||||
++ json (update:enjs upd)
|
||||
++ graph-update-0 upd
|
||||
++ mime [/application/x-urb-graph-update (as-octs (jam upd))]
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun update
|
||||
++ json update:dejs
|
||||
++ mime |=([* =octs] ;;(update (cue q.octs)))
|
||||
++ noun update:zero
|
||||
++ mime |=([* =octs] ;;(update:zero (cue q.octs)))
|
||||
--
|
||||
--
|
||||
|
@ -12,6 +12,76 @@
|
||||
:: %yes: May add a node or remove node
|
||||
+$ permission-level
|
||||
?(%no %self %yes)
|
||||
::
|
||||
++ zero
|
||||
=< [. post-zero]
|
||||
=, post-zero
|
||||
|%
|
||||
::
|
||||
++ orm ((ordered-map atom node) gth)
|
||||
++ orm-log ((ordered-map time logged-update) gth)
|
||||
::
|
||||
+$ graph ((mop atom node) gth)
|
||||
+$ marked-graph [p=graph q=(unit mark)]
|
||||
::
|
||||
+$ node [=post children=internal-graph]
|
||||
+$ graphs (map resource marked-graph)
|
||||
::
|
||||
+$ tag-queries (jug term resource)
|
||||
::
|
||||
+$ update-log ((mop time logged-update) gth)
|
||||
+$ update-logs (map resource update-log)
|
||||
::
|
||||
::
|
||||
+$ internal-graph
|
||||
$~ [%empty ~]
|
||||
$% [%graph p=graph]
|
||||
[%empty ~]
|
||||
==
|
||||
::
|
||||
+$ network
|
||||
$: =graphs
|
||||
=tag-queries
|
||||
=update-logs
|
||||
archive=graphs
|
||||
validators=(set mark)
|
||||
==
|
||||
::
|
||||
+$ update
|
||||
$% [%0 p=time q=update-0]
|
||||
==
|
||||
::
|
||||
+$ logged-update
|
||||
$% [%0 p=time q=logged-update-0]
|
||||
==
|
||||
::
|
||||
+$ logged-update-0
|
||||
$% [%add-graph =resource =graph mark=(unit mark) overwrite=?]
|
||||
[%add-nodes =resource nodes=(map index node)]
|
||||
[%remove-nodes =resource indices=(set index)]
|
||||
[%add-signatures =uid =signatures]
|
||||
[%remove-signatures =uid =signatures]
|
||||
==
|
||||
::
|
||||
+$ update-0
|
||||
$% logged-update-0
|
||||
[%remove-graph =resource]
|
||||
::
|
||||
[%add-tag =term =resource]
|
||||
[%remove-tag =term =resource]
|
||||
::
|
||||
[%archive-graph =resource]
|
||||
[%unarchive-graph =resource]
|
||||
[%run-updates =resource =update-log]
|
||||
::
|
||||
:: NOTE: cannot be sent as pokes
|
||||
::
|
||||
[%keys =resources]
|
||||
[%tags tags=(set term)]
|
||||
[%tag-queries =tag-queries]
|
||||
==
|
||||
--
|
||||
|
||||
+$ graph ((mop atom node) gth)
|
||||
+$ marked-graph [p=graph q=(unit mark)]
|
||||
::
|
||||
@ -38,15 +108,12 @@
|
||||
validators=(set mark)
|
||||
==
|
||||
::
|
||||
+$ update
|
||||
$% [%0 p=time q=update-0]
|
||||
==
|
||||
+$ update [p=time q=action]
|
||||
::
|
||||
+$ logged-update
|
||||
$% [%0 p=time q=logged-update-0]
|
||||
==
|
||||
+$ logged-update [p=time q=logged-action]
|
||||
|
||||
::
|
||||
+$ logged-update-0
|
||||
+$ logged-action
|
||||
$% [%add-graph =resource =graph mark=(unit mark) overwrite=?]
|
||||
[%add-nodes =resource nodes=(map index node)]
|
||||
[%remove-nodes =resource indices=(set index)]
|
||||
@ -54,8 +121,8 @@
|
||||
[%remove-signatures =uid =signatures]
|
||||
==
|
||||
::
|
||||
+$ update-0
|
||||
$% logged-update-0
|
||||
+$ action
|
||||
$% logged-action
|
||||
[%remove-graph =resource]
|
||||
::
|
||||
[%add-tag =term =resource]
|
||||
|
@ -33,7 +33,7 @@
|
||||
==
|
||||
::
|
||||
+$ contents
|
||||
$% [%graph =(list post:post)]
|
||||
$% [%graph =(list post:post-zero:post)]
|
||||
[%group =(list group-contents)]
|
||||
[%chat =(list envelope:chat-store)]
|
||||
==
|
||||
@ -75,7 +75,7 @@
|
||||
[date=@da read=? =contents]
|
||||
::
|
||||
+$ contents
|
||||
$% [%graph =(list post:post)]
|
||||
$% [%graph =(list post:post-zero:post)]
|
||||
[%group =(list group-contents)]
|
||||
==
|
||||
::
|
||||
@ -90,6 +90,38 @@
|
||||
::
|
||||
--
|
||||
::
|
||||
++ state-three
|
||||
=< state
|
||||
|%
|
||||
+$ state
|
||||
$: unreads-each=(jug stats-index index:graph-store)
|
||||
unreads-count=(map stats-index @ud)
|
||||
last-seen=(map stats-index @da)
|
||||
=notifications
|
||||
archive=notifications
|
||||
current-timebox=@da
|
||||
dnd=_|
|
||||
==
|
||||
::
|
||||
++ orm
|
||||
((ordered-map @da timebox) gth)
|
||||
::
|
||||
+$ notification
|
||||
[date=@da read=? =contents]
|
||||
::
|
||||
+$ contents
|
||||
$% [%graph =(list post:post-zero:post)]
|
||||
[%group =(list group-contents)]
|
||||
==
|
||||
::
|
||||
+$ timebox
|
||||
(map index notification)
|
||||
::
|
||||
+$ notifications
|
||||
((mop @da timebox) gth)
|
||||
::
|
||||
--
|
||||
::
|
||||
+$ index
|
||||
$% $: %graph
|
||||
group=resource
|
||||
|
@ -1,5 +1,27 @@
|
||||
/- *resource
|
||||
|%
|
||||
::
|
||||
++ post-zero
|
||||
|%
|
||||
::
|
||||
+$ content
|
||||
$% [%text text=cord]
|
||||
[%mention =ship]
|
||||
[%url url=cord]
|
||||
[%code expression=cord output=(list tank)]
|
||||
[%reference =uid]
|
||||
==
|
||||
::
|
||||
+$ post
|
||||
$: author=ship
|
||||
=index
|
||||
time-sent=time
|
||||
contents=(list content)
|
||||
hash=(unit hash)
|
||||
=signatures
|
||||
==
|
||||
--
|
||||
|
||||
+$ index (list atom)
|
||||
+$ uid [=resource =index]
|
||||
::
|
||||
@ -26,13 +48,16 @@
|
||||
contents=(list content)
|
||||
==
|
||||
::
|
||||
+$ reference
|
||||
$% [%graph group=resource =uid]
|
||||
[%group group=resource]
|
||||
==
|
||||
::
|
||||
+$ content
|
||||
$% [%text text=cord]
|
||||
[%mention =ship]
|
||||
[%url url=cord]
|
||||
[%code expression=cord output=(list tank)]
|
||||
[%reference =uid]
|
||||
:: TODO: maybe use a cask?
|
||||
::[%cage =cage]
|
||||
[%reference =reference]
|
||||
==
|
||||
--
|
||||
|
@ -10,7 +10,6 @@
|
||||
;< =update:store bind:m
|
||||
%+ scry:strandio update:store
|
||||
/gx/graph-store/graph/(scot %p entity.rid)/[name.rid]/noun
|
||||
?> ?=(%0 -.update)
|
||||
?> ?=(%add-graph -.q.update)
|
||||
(pure:m graph.q.update)
|
||||
--
|
||||
@ -33,7 +32,7 @@
|
||||
=/ hashes (nodes-to-pending-indices nodes.q.update)
|
||||
;< ~ bind:m
|
||||
%^ poke-our %graph-push-hook
|
||||
%graph-update-0
|
||||
%graph-update-1
|
||||
!>(update)
|
||||
(pure:m !>(`action:graph-view`[%pending-indices hashes]))
|
||||
::
|
||||
|
@ -39,9 +39,9 @@
|
||||
==
|
||||
;< ~ bind:m
|
||||
%+ poke-our %graph-store
|
||||
:- %graph-update
|
||||
:- %graph-update-1
|
||||
!> ^- update:graph
|
||||
[%0 now.bowl %add-graph feed-rid *graph:graph `%graph-validator-post %&]
|
||||
[now.bowl %add-graph feed-rid *graph:graph `%graph-validator-post %&]
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-push-hook %push-hook-action !>([%add feed-rid]))
|
||||
;< ~ bind:m
|
||||
|
@ -52,9 +52,9 @@
|
||||
=/ overwrite=?
|
||||
?=(%policy -.associated.action)
|
||||
=/ =update:graph
|
||||
[%0 now.bowl %add-graph rid.action *graph:graph mark.action overwrite]
|
||||
[now.bowl %add-graph rid.action *graph:graph mark.action overwrite]
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-store graph-update-0+!>(update))
|
||||
(poke-our %graph-store graph-update-1+!>(update))
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-push-hook %push-hook-action !>([%add rid.action]))
|
||||
::
|
||||
|
@ -36,7 +36,7 @@
|
||||
^- form:m
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-store %graph-update-0 !>([%0 now.bowl %remove-graph rid]))
|
||||
(poke-our %graph-store %graph-update-1 !>([now.bowl %remove-graph rid]))
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-push-hook %push-hook-action !>([%remove rid]))
|
||||
;< ~ bind:m
|
||||
|
@ -39,7 +39,7 @@
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-pull-hook %pull-hook-action !>([%remove rid]))
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-store %graph-update-0 !>([%0 now [%remove-graph rid]]))
|
||||
(poke-our %graph-store %graph-update-1 !>([now [%remove-graph rid]]))
|
||||
(pure:m ~)
|
||||
--
|
||||
::
|
||||
|
@ -17,7 +17,7 @@
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
:: unarchive graph and share it
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-store %graph-update-0 !>([%0 now.bowl %unarchive-graph rid]))
|
||||
(poke-our %graph-store %graph-update-1 !>([now.bowl %unarchive-graph rid]))
|
||||
;< ~ bind:m
|
||||
(poke-our %graph-push-hook %push-hook-action !>([%add rid]))
|
||||
::
|
||||
|
@ -70,9 +70,9 @@
|
||||
;< ~ bind:m
|
||||
%+ raw-poke
|
||||
[our.bowl %graph-store]
|
||||
:- %graph-update-0
|
||||
:- %graph-update-1
|
||||
!> ^- update:gra
|
||||
[%0 now.bowl [%archive-graph app-resource]]
|
||||
[now.bowl [%archive-graph app-resource]]
|
||||
;< ~ bind:m
|
||||
%+ raw-poke
|
||||
[our.bowl %graph-pull-hook]
|
||||
|
@ -13,8 +13,8 @@
|
||||
=/ =index:post [id]~
|
||||
=/ =post:post [our index wen [%text body]~ ~ ~]
|
||||
=/ =node:graph-store [post %empty ~]
|
||||
=/ act=update:graph-store [%0 wen %add-nodes rid (my [index node] ~)]
|
||||
(poke-app our %graph-push-hook %graph-update-0 act)
|
||||
=/ act=update:graph-store [wen %add-nodes rid (my [index node] ~)]
|
||||
(poke-app our %graph-push-hook %graph-update-1 act)
|
||||
--
|
||||
::
|
||||
^- thread:spider
|
||||
|
@ -13,8 +13,8 @@
|
||||
=/ =index:post [id]~
|
||||
=/ =post:post [our index wen [%text body]~ ~ ~]
|
||||
=/ =node:graph-store [post %empty ~]
|
||||
=/ act=update:graph-store [%0 wen %add-nodes rid (my [index node] ~)]
|
||||
(poke-app our %graph-push-hook %graph-update-0 act)
|
||||
=/ act=update:graph-store [wen %add-nodes rid (my [index node] ~)]
|
||||
(poke-app our %graph-push-hook %graph-update-1 act)
|
||||
--
|
||||
::
|
||||
^- thread:spider
|
||||
|
@ -83,7 +83,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
joiningGraphs = new Set<string>();
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('graph-store', 'graph-update-0', action);
|
||||
return this.action('graph-store', 'graph-update-1', action);
|
||||
}
|
||||
|
||||
private viewAction(threadName: string, action: any) {
|
||||
@ -91,7 +91,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
private hookAction(ship: Patp, action: any): Promise<any> {
|
||||
return this.action('graph-push-hook', 'graph-update-0', action);
|
||||
return this.action('graph-push-hook', 'graph-update-1', action);
|
||||
}
|
||||
|
||||
createManagedGraph(
|
||||
@ -227,7 +227,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
};
|
||||
|
||||
const pendingPromise = this.spider(
|
||||
'graph-update-0',
|
||||
'graph-update-1',
|
||||
'graph-view-action',
|
||||
'graph-add-nodes',
|
||||
action
|
||||
@ -350,15 +350,17 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
getNode(ship: string, resource: string, index: string) {
|
||||
const idx = index.split('/').map(numToUd).join('/');
|
||||
return this.scry<any>(
|
||||
async getNode(ship: string, resource: string, index: string) {
|
||||
const idx = index.split('/').map(decToUd).join('/');
|
||||
const data = await this.scry<any>(
|
||||
'graph-store',
|
||||
`/node/${ship}/${resource}${idx}`
|
||||
).then((node) => {
|
||||
this.store.handleEvent({
|
||||
data: node
|
||||
});
|
||||
);
|
||||
const node = data['graph-update'];
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
"graph-update-loose": node
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
93
pkg/interface/src/logic/lib/permalinks.ts
Normal file
93
pkg/interface/src/logic/lib/permalinks.ts
Normal file
@ -0,0 +1,93 @@
|
||||
|
||||
import {
|
||||
Association,
|
||||
resourceFromPath,
|
||||
Group,
|
||||
ReferenceContent,
|
||||
} from "@urbit/api";
|
||||
|
||||
export function getPermalinkForGraph(
|
||||
group: string,
|
||||
graph: string,
|
||||
index = ""
|
||||
) {
|
||||
const groupLink = getPermalinkForAssociatedGroup(group);
|
||||
const { ship, name } = resourceFromPath(graph);
|
||||
return `${groupLink}/graph/${ship}/${name}${index}`;
|
||||
}
|
||||
|
||||
function getPermalinkForAssociatedGroup(group: string) {
|
||||
const { ship, name } = resourceFromPath(group);
|
||||
return `web+urbit-graph://group/${ship}/${name}`;
|
||||
}
|
||||
|
||||
|
||||
type Permalink = GraphPermalink | GroupPermalink;
|
||||
|
||||
interface GroupPermalink {
|
||||
type: "group";
|
||||
group: string;
|
||||
link: string;
|
||||
}
|
||||
interface GraphPermalink {
|
||||
type: "graph";
|
||||
link: string;
|
||||
graph: string;
|
||||
group: string;
|
||||
index: string;
|
||||
}
|
||||
|
||||
function parseGraphPermalink(
|
||||
link: string,
|
||||
group: string,
|
||||
segments: string[]
|
||||
): GraphPermalink | null {
|
||||
const [kind, ship, name, ...index] = segments;
|
||||
if (kind !== "graph") {
|
||||
return null;
|
||||
}
|
||||
const graph = `/ship/${ship}/${name}`;
|
||||
return {
|
||||
type: "graph",
|
||||
link: link.slice(11),
|
||||
graph,
|
||||
group,
|
||||
index: `/${index.join("/")}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function referenceToPermalink({ reference }: ReferenceContent): Permalink {
|
||||
if('graph' in reference) {
|
||||
const { graph, group, index } = reference.graph;
|
||||
const link = `web+urbit-graph://group${group.slice(5)}/graph${graph.slice(5)}${index}`;
|
||||
return {
|
||||
type: 'graph',
|
||||
link,
|
||||
...reference.graph
|
||||
};
|
||||
} else {
|
||||
const link = `web+urbit-graph://group${reference.group.slice(5)}`;
|
||||
return {
|
||||
type: 'group',
|
||||
link,
|
||||
...reference
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePermalink(url: string): Permalink | null {
|
||||
const [kind, ...rest] = url.slice(12).split("/");
|
||||
if (kind === "group") {
|
||||
const [ship, name, ...graph] = rest;
|
||||
const group = `/ship/${ship}/${name}`;
|
||||
if (graph.length > 0) {
|
||||
return parseGraphPermalink(url, group, graph);
|
||||
}
|
||||
return {
|
||||
type: "group",
|
||||
group,
|
||||
link: url.slice(11),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { parsePermalink, permalinkToReference } from "~/logic/lib/permalinks";
|
||||
|
||||
const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source));
|
||||
const URL_REGEX = new RegExp(String(/^(([\w\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source));
|
||||
|
||||
const isUrl = (string) => {
|
||||
try {
|
||||
@ -44,7 +45,20 @@ const tokenizeMessage = (text) => {
|
||||
isInCodeBlock = false;
|
||||
}
|
||||
|
||||
if (isUrl(str) && !isInCodeBlock) {
|
||||
if(isRef(str) && !isInCodeBlock) {
|
||||
if (message.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
messages.push({ text: message.join(' ') });
|
||||
}
|
||||
const link = parsePermalink(str);
|
||||
if(!link) {
|
||||
messages.push({ url: str });
|
||||
} else {
|
||||
const reference = permalinkToReference(link);
|
||||
messages.push({ reference });
|
||||
}
|
||||
message = [];
|
||||
} else if (isUrl(str) && !isInCodeBlock) {
|
||||
if (message.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
messages.push({ text: message.join(' ') });
|
||||
|
20
pkg/interface/src/logic/lib/useCopy.ts
Normal file
20
pkg/interface/src/logic/lib/useCopy.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { writeText } from "./util";
|
||||
import { useCallback, useState, useMemo } from "react";
|
||||
|
||||
export function useCopy(copied: string, display: string) {
|
||||
const [didCopy, setDidCopy] = useState(false);
|
||||
const doCopy = useCallback(() => {
|
||||
writeText(copied);
|
||||
setDidCopy(true);
|
||||
setTimeout(() => {
|
||||
setDidCopy(false);
|
||||
}, 2000);
|
||||
}, [copied]);
|
||||
|
||||
const copyDisplay = useMemo(() => (didCopy ? "Copied" : display), [
|
||||
didCopy,
|
||||
display,
|
||||
]);
|
||||
|
||||
return { copyDisplay, doCopy };
|
||||
}
|
@ -1,30 +1,46 @@
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
import { useMemo, useCallback } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import _ from "lodash";
|
||||
|
||||
function mergeQuery(search: URLSearchParams, added: Record<string, string>) {
|
||||
_.forIn(added, (v, k) => {
|
||||
if (v) {
|
||||
search.append(k, v);
|
||||
} else {
|
||||
search.delete(k);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useQuery() {
|
||||
const { search } = useLocation();
|
||||
const { search, pathname } = useLocation();
|
||||
|
||||
const query = useMemo(() => new URLSearchParams(search), [search]);
|
||||
|
||||
const appendQuery = useCallback(
|
||||
(q: Record<string, string>) => {
|
||||
const newQuery = new URLSearchParams(search);
|
||||
_.forIn(q, (value, key) => {
|
||||
if (!value) {
|
||||
newQuery.delete(key);
|
||||
} else {
|
||||
newQuery.append(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return newQuery.toString();
|
||||
(added: Record<string, string>) => {
|
||||
const q = new URLSearchParams(search);
|
||||
mergeQuery(q, added);
|
||||
return q.toString();
|
||||
},
|
||||
[search]
|
||||
);
|
||||
|
||||
const toQuery = useCallback(
|
||||
(params: Record<string, string>, path = pathname) => {
|
||||
const q = new URLSearchParams(search);
|
||||
mergeQuery(q, params);
|
||||
return {
|
||||
pathname: path,
|
||||
search: q.toString(),
|
||||
};
|
||||
},
|
||||
[search, pathname]
|
||||
);
|
||||
|
||||
return {
|
||||
query,
|
||||
appendQuery
|
||||
appendQuery,
|
||||
toQuery,
|
||||
};
|
||||
}
|
||||
|
@ -16,8 +16,27 @@ export const GraphReducer = (json) => {
|
||||
removeNodes
|
||||
]);
|
||||
}
|
||||
const loose = _.get(json, 'graph-update-loose', false);
|
||||
if(loose) {
|
||||
reduceState<GraphState, any>(useGraphState, loose, [addNodesLoose]);
|
||||
}
|
||||
};
|
||||
|
||||
const addNodesLoose = (json: any, state: GraphState): GraphState => {
|
||||
const data = _.get(json, 'add-nodes', false);
|
||||
if(data) {
|
||||
const { resource: { ship, name }, nodes } = data;
|
||||
const resource = `${ship}/${name}`;
|
||||
|
||||
const indices = _.get(state.looseNodes, [resource], {});
|
||||
_.forIn(nodes, (node, index) => {
|
||||
indices[index] = processNode(node);
|
||||
});
|
||||
_.set(state.looseNodes, [resource], indices);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
const keys = (json, state: GraphState): GraphState => {
|
||||
const data = _.get(json, 'keys', false);
|
||||
if (data) {
|
||||
@ -29,29 +48,31 @@ const keys = (json, state: GraphState): GraphState => {
|
||||
return state;
|
||||
};
|
||||
|
||||
const processNode = (node) => {
|
||||
// is empty
|
||||
if (!node.children) {
|
||||
node.children = new BigIntOrderedMap();
|
||||
return node;
|
||||
}
|
||||
|
||||
// is graph
|
||||
let converted = new BigIntOrderedMap();
|
||||
for (let idx in node.children) {
|
||||
let item = node.children[idx];
|
||||
let index = bigInt(idx);
|
||||
|
||||
converted.set(
|
||||
index,
|
||||
processNode(item)
|
||||
);
|
||||
}
|
||||
node.children = converted;
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
const addGraph = (json, state: GraphState): GraphState => {
|
||||
|
||||
const _processNode = (node) => {
|
||||
// is empty
|
||||
if (!node.children) {
|
||||
node.children = new BigIntOrderedMap();
|
||||
return node;
|
||||
}
|
||||
|
||||
// is graph
|
||||
let converted = new BigIntOrderedMap();
|
||||
for (let idx in node.children) {
|
||||
let item = node.children[idx];
|
||||
let index = bigInt(idx);
|
||||
|
||||
converted.set(
|
||||
index,
|
||||
_processNode(item)
|
||||
);
|
||||
}
|
||||
node.children = converted;
|
||||
return node;
|
||||
};
|
||||
|
||||
const data = _.get(json, 'add-graph', false);
|
||||
if (data) {
|
||||
@ -68,7 +89,7 @@ const addGraph = (json, state: GraphState): GraphState => {
|
||||
let item = data.graph[idx];
|
||||
let index = bigInt(idx);
|
||||
|
||||
let node = _processNode(item);
|
||||
let node = processNode(item);
|
||||
|
||||
state.graphs[resource].set(
|
||||
index,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import produce from "immer";
|
||||
import { compose } from "lodash/fp";
|
||||
import create, { State, UseStore } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { persist, devtools } from "zustand/middleware";
|
||||
|
||||
|
||||
export const stateSetter = <StateType>(
|
||||
@ -53,12 +53,16 @@ export const createState = <StateType extends BaseState<any>>(
|
||||
name: string,
|
||||
properties: Omit<StateType, 'set'>,
|
||||
blacklist: string[] = []
|
||||
): UseStore<StateType> => create(persist((set, get) => ({
|
||||
// TODO why does this typing break?
|
||||
set: fn => stateSetter(fn, set),
|
||||
...properties
|
||||
}), {
|
||||
blacklist,
|
||||
name: stateStorageKey(name),
|
||||
version: 1, // TODO version these according to base hash
|
||||
}));
|
||||
): UseStore<StateType> => {
|
||||
const storageKey = stateStorageKey(name);
|
||||
|
||||
return create(devtools(persist((set, get) => ({
|
||||
// TODO why does this typing break?
|
||||
set: fn => stateSetter(fn, set),
|
||||
...properties
|
||||
}), {
|
||||
blacklist,
|
||||
name: storageKey,
|
||||
version: 1, // TODO version these according to base hash
|
||||
}), storageKey));
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { Graphs, decToUd, numToUd } from "@urbit/api";
|
||||
import { Graphs, decToUd, numToUd, GraphNode } from "@urbit/api";
|
||||
|
||||
import { BaseState, createState } from "./base";
|
||||
|
||||
export interface GraphState extends BaseState<GraphState> {
|
||||
graphs: Graphs;
|
||||
graphKeys: Set<string>;
|
||||
looseNodes: {
|
||||
[graph: string]: {
|
||||
[index: string]: GraphNode;
|
||||
}
|
||||
};
|
||||
pendingIndices: Record<string, any>;
|
||||
graphTimesentMap: Record<string, any>;
|
||||
// getKeys: () => Promise<void>;
|
||||
@ -21,6 +26,7 @@ export interface GraphState extends BaseState<GraphState> {
|
||||
const useGraphState = createState<GraphState>('Graph', {
|
||||
graphs: {},
|
||||
graphKeys: new Set(),
|
||||
looseNodes: {},
|
||||
pendingIndices: {},
|
||||
graphTimesentMap: {},
|
||||
// getKeys: async () => {
|
||||
@ -122,6 +128,6 @@ const useGraphState = createState<GraphState>('Graph', {
|
||||
// });
|
||||
// graphReducer(node);
|
||||
// },
|
||||
}, ['graphs', 'graphKeys']);
|
||||
}, ['graphs', 'graphKeys', 'looseNodes']);
|
||||
|
||||
export default useGraphState;
|
||||
export default useGraphState;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Path, JoinRequests, Group } from "@urbit/api";
|
||||
import { Path, JoinRequests, Association, Group } from "@urbit/api";
|
||||
|
||||
import { BaseState, createState } from "./base";
|
||||
import {useCallback} from "react";
|
||||
|
||||
export interface GroupState extends BaseState<GroupState> {
|
||||
groups: {
|
||||
[groupPath: string]: Group;
|
||||
};
|
||||
[group: string]: Group;
|
||||
}
|
||||
pendingJoin: JoinRequests;
|
||||
};
|
||||
|
||||
@ -19,4 +19,10 @@ export function useGroup(group: string) {
|
||||
return useGroupState(useCallback(s => s.groups[group], [group]));
|
||||
}
|
||||
|
||||
export function useGroupForAssoc(association: Association) {
|
||||
return useGroupState(
|
||||
useCallback(s => s.groups[association.group] as Group | undefined, [association])
|
||||
);
|
||||
}
|
||||
|
||||
export default useGroupState;
|
||||
|
@ -2,6 +2,7 @@ import React, { useRef, useCallback, useEffect, useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
@ -20,6 +21,8 @@ import useContactState from '~/logic/state/contact';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import {Post} from '@urbit/api';
|
||||
import {getPermalinkForGraph} from '~/logic/lib/permalinks';
|
||||
|
||||
type ChatResourceProps = StoreState & {
|
||||
association: Association;
|
||||
@ -78,19 +81,20 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
|
||||
const scrollTo = new URLSearchParams(location.search).get('msg');
|
||||
|
||||
useEffect(() => {
|
||||
const clear = () => {
|
||||
props.history.replace(location.pathname);
|
||||
};
|
||||
setTimeout(clear, 10000);
|
||||
return clear;
|
||||
}, [station]);
|
||||
|
||||
const [showBanner, setShowBanner] = useState(false);
|
||||
const [hasLoadedAllowed, setHasLoadedAllowed] = useState(false);
|
||||
const [recipients, setRecipients] = useState([]);
|
||||
|
||||
const res = resourceFromPath(groupPath);
|
||||
const onReply = useCallback((msg: Post) => {
|
||||
const url = getPermalinkForGraph(
|
||||
props.association.group,
|
||||
props.association.resource,
|
||||
msg.index
|
||||
);
|
||||
const message = `${url}\n~${msg.author} : `;
|
||||
setUnsent(s => ({...s, [props.association.resource]: message }));
|
||||
}, [props.association, group, setUnsent]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -162,9 +166,10 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
pendingSize={Object.keys(graphTimesentMap[graphPath] || {}).length}
|
||||
group={group}
|
||||
ship={owner}
|
||||
onReply={onReply}
|
||||
station={station}
|
||||
api={props.api}
|
||||
scrollTo={scrollTo ? parseInt(scrollTo, 10) : undefined}
|
||||
scrollTo={scrollTo ? bigInt(scrollTo) : undefined}
|
||||
/>
|
||||
{ canWrite && (
|
||||
<ChatInput
|
||||
|
@ -41,6 +41,10 @@ import Timestamp from '~/views/components/Timestamp';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import { useIdlingState } from '~/logic/lib/idling';
|
||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||
import {useCopy} from '~/logic/lib/useCopy';
|
||||
import {PermalinkEmbed} from '../../permalinks/embed';
|
||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
||||
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
@ -49,6 +53,7 @@ interface DayBreakProps {
|
||||
shimTop?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
|
||||
<Row
|
||||
px={2}
|
||||
@ -135,13 +140,15 @@ const MessageActionItem = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MessageActions = ({ api, history, msg, group }) => {
|
||||
const MessageActions = ({ api, onReply, association, history, msg, group }) => {
|
||||
const isAdmin = () => group.tags.role.admin.has(window.ship);
|
||||
const isOwn = () => msg.author === window.ship;
|
||||
const { doCopy, copyDisplay } = useCopy(`web+urbit-graph://group${association.group.slice(5)}/graph${association.resource.slice(5)}${msg.index}`, 'Copy Message Link');
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderRadius={1}
|
||||
background='white'
|
||||
backgroundColor='white'
|
||||
border='1px solid'
|
||||
borderColor='lightGray'
|
||||
position='absolute'
|
||||
@ -149,21 +156,11 @@ const MessageActions = ({ api, history, msg, group }) => {
|
||||
right={2}
|
||||
>
|
||||
<Row>
|
||||
{isOwn() ? (
|
||||
<Box
|
||||
padding={1}
|
||||
size={'24px'}
|
||||
cursor='pointer'
|
||||
onClick={(e) => console.log(e)}
|
||||
>
|
||||
<Icon icon='NullIcon' size={3} />
|
||||
</Box>
|
||||
) : null}
|
||||
<Box
|
||||
padding={1}
|
||||
size={'24px'}
|
||||
cursor='pointer'
|
||||
onClick={(e) => console.log(e)}
|
||||
onClick={() => onReply(msg)}
|
||||
>
|
||||
<Icon icon='Chat' size={3} />
|
||||
</Box>
|
||||
@ -185,25 +182,22 @@ const MessageActions = ({ api, history, msg, group }) => {
|
||||
borderColor='lightGray'
|
||||
boxShadow='0px 0px 0px 3px'
|
||||
>
|
||||
{isOwn() ? (
|
||||
<MessageActionItem onClick={(e) => console.log(e)}>
|
||||
Edit Message
|
||||
</MessageActionItem>
|
||||
) : null}
|
||||
<MessageActionItem onClick={(e) => console.log(e)}>
|
||||
<MessageActionItem onClick={() => onReply(msg)}>
|
||||
Reply
|
||||
</MessageActionItem>
|
||||
<MessageActionItem onClick={(e) => console.log(e)}>
|
||||
Copy Message Link
|
||||
<MessageActionItem onClick={doCopy}>
|
||||
{copyDisplay}
|
||||
</MessageActionItem>
|
||||
{isAdmin() || isOwn() ? (
|
||||
{false && (isAdmin() || isOwn()) ? (
|
||||
<MessageActionItem onClick={(e) => console.log(e)} color='red'>
|
||||
Delete Message
|
||||
</MessageActionItem>
|
||||
) : null}
|
||||
<MessageActionItem onClick={(e) => console.log(e)}>
|
||||
View Signature
|
||||
</MessageActionItem>
|
||||
{false && (
|
||||
<MessageActionItem onClick={(e) => console.log(e)}>
|
||||
View Signature
|
||||
</MessageActionItem>
|
||||
)}
|
||||
</Col>
|
||||
}
|
||||
>
|
||||
@ -218,17 +212,19 @@ const MessageActions = ({ api, history, msg, group }) => {
|
||||
|
||||
const MessageWrapper = (props) => {
|
||||
const { hovering, bind } = useHovering();
|
||||
const showHover = (props.transcluded === 0) && hovering && !props.hideHover;
|
||||
return (
|
||||
<Box
|
||||
py='1'
|
||||
backgroundColor={
|
||||
hovering && !props.hideHover ? 'washedGray' : 'transparent'
|
||||
backgroundColor={props.highlighted
|
||||
? showHover ? 'lightBlue' : 'washedBlue'
|
||||
: showHover ? 'washedGray' : 'transparent'
|
||||
}
|
||||
position='relative'
|
||||
{...bind}
|
||||
>
|
||||
{props.children}
|
||||
{/* {hovering ? <MessageActions {...props} /> : null} */}
|
||||
{showHover ? <MessageActions {...props} /> : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -240,6 +236,7 @@ interface ChatMessageProps {
|
||||
isLastRead: boolean;
|
||||
group: Group;
|
||||
association: Association;
|
||||
transcluded?: number;
|
||||
className?: string;
|
||||
isPending: boolean;
|
||||
style?: unknown;
|
||||
@ -252,6 +249,7 @@ interface ChatMessageProps {
|
||||
renderSigil?: boolean;
|
||||
hideHover?: boolean;
|
||||
innerRef: (el: HTMLDivElement | null) => void;
|
||||
onReply?: (msg: Post) => void;
|
||||
}
|
||||
|
||||
class ChatMessage extends Component<ChatMessageProps> {
|
||||
@ -283,7 +281,9 @@ class ChatMessage extends Component<ChatMessageProps> {
|
||||
highlighted,
|
||||
showOurContact,
|
||||
fontSize,
|
||||
hideHover
|
||||
hideHover,
|
||||
onReply = () => {},
|
||||
transcluded = 0
|
||||
} = this.props;
|
||||
|
||||
let { renderSigil } = this.props;
|
||||
@ -321,7 +321,9 @@ class ChatMessage extends Component<ChatMessageProps> {
|
||||
scrollWindow,
|
||||
highlighted,
|
||||
fontSize,
|
||||
hideHover
|
||||
hideHover,
|
||||
transcluded,
|
||||
onReply
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
@ -332,9 +334,9 @@ class ChatMessage extends Component<ChatMessageProps> {
|
||||
<Box
|
||||
ref={this.props.innerRef}
|
||||
pt={renderSigil ? 2 : 0}
|
||||
width="100%"
|
||||
pb={isLastMessage ? '20px' : 0}
|
||||
className={containerClass}
|
||||
backgroundColor={highlighted ? 'blue' : 'white'}
|
||||
style={style}
|
||||
>
|
||||
{dayBreak && !isLastRead ? (
|
||||
@ -531,12 +533,13 @@ export const Message = ({
|
||||
api,
|
||||
scrollWindow,
|
||||
timestampHover,
|
||||
transcluded,
|
||||
...rest
|
||||
}) => {
|
||||
const { hovering, bind } = useHovering();
|
||||
const contacts = useContactState((state) => state.contacts);
|
||||
return (
|
||||
<Box position='relative' {...rest}>
|
||||
<Box width="100%" position='relative' {...rest}>
|
||||
{timestampHover ? (
|
||||
<Text
|
||||
display={hovering ? 'block' : 'none'}
|
||||
@ -553,7 +556,7 @@ export const Message = ({
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Box {...bind}>
|
||||
<Box width="100%" {...bind}>
|
||||
{msg.contents.map((content, i) => {
|
||||
switch (Object.keys(content)[0]) {
|
||||
case 'text':
|
||||
@ -567,9 +570,18 @@ export const Message = ({
|
||||
/>
|
||||
);
|
||||
case 'code':
|
||||
return <CodeContent key={i} content={content} />;
|
||||
case 'url':
|
||||
return <CodeContent key={i} content={content} />;
|
||||
case 'reference':
|
||||
const { link } = referenceToPermalink(content);
|
||||
return (
|
||||
<PermalinkEmbed
|
||||
link={link}
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
/>
|
||||
);
|
||||
case 'url':
|
||||
return (
|
||||
<Box
|
||||
key={i}
|
||||
flexShrink={0}
|
||||
|
@ -41,7 +41,8 @@ type ChatWindowProps = RouteComponentProps<{
|
||||
ship: Patp;
|
||||
station: any;
|
||||
api: GlobalApi;
|
||||
scrollTo?: number;
|
||||
scrollTo?: BigInteger;
|
||||
onReply: (msg: Post) => void;
|
||||
};
|
||||
|
||||
interface ChatWindowState {
|
||||
@ -87,10 +88,13 @@ class ChatWindow extends Component<
|
||||
componentDidMount() {
|
||||
this.calculateUnreadIndex();
|
||||
setTimeout(() => {
|
||||
if (this.props.scrollTo) {
|
||||
this.scrollToUnread();
|
||||
}
|
||||
this.setState({ initialized: true });
|
||||
this.setState({ initialized: true }, () => {
|
||||
if(this.props.scrollTo) {
|
||||
this.virtualList.scrollToIndex(this.props.scrollTo);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}, this.INITIALIZATION_MAX_TIME);
|
||||
}
|
||||
@ -211,7 +215,8 @@ class ChatWindow extends Component<
|
||||
graph,
|
||||
history,
|
||||
groups,
|
||||
associations
|
||||
associations,
|
||||
onReply
|
||||
} = this.props;
|
||||
const { unreadMarkerRef } = this;
|
||||
const messageProps = {
|
||||
@ -222,7 +227,8 @@ class ChatWindow extends Component<
|
||||
history,
|
||||
api,
|
||||
groups,
|
||||
associations
|
||||
associations,
|
||||
onReply
|
||||
};
|
||||
|
||||
const msg = graph.get(index)?.post;
|
||||
@ -240,7 +246,7 @@ class ChatWindow extends Component<
|
||||
const isLastMessage = index.eq(
|
||||
graph.peekLargest()?.[0] ?? bigInt.zero
|
||||
);
|
||||
const highlighted = false; // this.state.unreadIndex.eq(index);
|
||||
const highlighted = index.eq(this.props.scrollTo ?? bigInt.zero);
|
||||
const keys = graph.keys().reverse();
|
||||
const graphIdx = keys.findIndex((idx) => idx.eq(index));
|
||||
const prevIdx = keys[graphIdx + 1];
|
||||
@ -278,7 +284,8 @@ class ChatWindow extends Component<
|
||||
groups,
|
||||
associations,
|
||||
showOurContact,
|
||||
pendingSize
|
||||
pendingSize,
|
||||
onReply,
|
||||
} = this.props;
|
||||
|
||||
const unreadMarkerRef = this.unreadMarkerRef;
|
||||
|
@ -8,6 +8,7 @@ import { Row, BaseTextArea, Box } from '@tlon/indigo-react';
|
||||
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/addon/hint/show-hint';
|
||||
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
|
||||
@ -142,6 +143,10 @@ export default class ChatEditor extends Component {
|
||||
}
|
||||
|
||||
messageChange(editor, data, value) {
|
||||
if(value.endsWith('/')) {
|
||||
console.log('showing');
|
||||
editor.showHint(['test', 'foo']);
|
||||
}
|
||||
if (this.state.message !== '' && value == '') {
|
||||
this.setState({
|
||||
message: value
|
||||
|
@ -4,10 +4,12 @@ import { Center, Text } from "@tlon/indigo-react";
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
|
||||
const GraphApp = (props) => {
|
||||
const associations= useMetadataState(state => state.associations);
|
||||
const graphKeys = useGraphState(state => state.graphKeys);
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const history = useHistory();
|
||||
|
||||
const { api } = props;
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import {useHistory} from 'react-router-dom';
|
||||
|
||||
|
||||
const ScrollbarLessBox = styled(Box)`
|
||||
@ -50,6 +51,7 @@ export default function LaunchApp(props) {
|
||||
const [hashText, setHashText] = useState(baseHash);
|
||||
const [exitingTut, setExitingTut] = useState(false);
|
||||
const associations = useMetadataState(s => s.associations);
|
||||
const history = useHistory();
|
||||
const hashBox = (
|
||||
<Box
|
||||
position={["relative", "absolute"]}
|
||||
@ -229,7 +231,7 @@ export default function LaunchApp(props) {
|
||||
</Box>
|
||||
<Box alignSelf="flex-start" display={["block", "none"]}>{hashBox}</Box>
|
||||
</ScrollbarLessBox>
|
||||
<Box display={["none", "block"]}>{hashBox}</Box>
|
||||
<Box onClick={() => history.push('/~graph/graph/ship/~bitpyx-dildus/infrastructure-digests/170141184504958869914231288036524556288/2/170141184504958917566472168072435204096') } display={["none", "block"]}>{hashBox}</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={relativePath('/:index(\\d+)/:commentId?')}
|
||||
path={relativePath('/index/:index')}
|
||||
render={(props) => {
|
||||
const index = bigInt(props.match.params.index);
|
||||
const editCommentId = props.match.params.commentId || null;
|
||||
@ -105,6 +105,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
resource={resourcePath}
|
||||
node={node}
|
||||
baseUrl={resourceUrl}
|
||||
association={association}
|
||||
group={group}
|
||||
path={resource?.group}
|
||||
api={api}
|
||||
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useCallback, ReactElement } from '
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Row, Col, Anchor, Box, Text, Icon, Action } from '@tlon/indigo-react';
|
||||
import { GraphNode, Group, Rolodex, Unreads } from '@urbit/api';
|
||||
import { GraphNode, Group, Rolodex, Unreads, Association } from '@urbit/api';
|
||||
|
||||
import { writeText } from '~/logic/lib/util';
|
||||
import Author from '~/views/components/Author';
|
||||
@ -11,17 +11,16 @@ import GlobalApi from '~/logic/api/global';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import {useCopy} from '~/logic/lib/useCopy';
|
||||
import {usePermalinkForGraph, getPermalinkForGraph} from '~/logic/lib/permalinks';
|
||||
|
||||
interface LinkItemProps {
|
||||
node: GraphNode;
|
||||
resource: string;
|
||||
api: GlobalApi;
|
||||
group: Group;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
association: Association;
|
||||
resource: string; api: GlobalApi; group: Group; path: string; }
|
||||
export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
const {
|
||||
association,
|
||||
node,
|
||||
resource,
|
||||
api,
|
||||
@ -32,10 +31,11 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const remoteRef = useRef<typeof RemoteContent | null>(null);
|
||||
const index = node.post.index.split('/')[1];
|
||||
|
||||
const markRead = useCallback(() => {
|
||||
api.hark.markEachAsRead(props.association, '/', `/${index}`, 'link', 'link');
|
||||
}, [props.association, index]);
|
||||
}, [association, index]);
|
||||
|
||||
useEffect(() => {
|
||||
function onBlur() {
|
||||
@ -59,7 +59,6 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
);
|
||||
|
||||
const author = node.post.author;
|
||||
const index = node.post.index.split('/')[1];
|
||||
const size = node.children ? node.children.size : 0;
|
||||
const contents = node.post.contents;
|
||||
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
||||
@ -70,16 +69,22 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
const ourRole = group ? roleForShip(group, window.ship) : undefined;
|
||||
const [ship, name] = resource.split('/');
|
||||
|
||||
const [locationText, setLocationText] = useState('Copy Link Location');
|
||||
|
||||
const copyLocation = () => {
|
||||
setLocationText('Copied');
|
||||
writeText(contents[1].url);
|
||||
setTimeout(() => {
|
||||
setLocationText('Copy Link Location');
|
||||
}, 2000);
|
||||
};
|
||||
const permalink = getPermalinkForGraph(
|
||||
association.group,
|
||||
association.resource,
|
||||
`/${index}`
|
||||
);
|
||||
|
||||
const { doCopy: doCopyLink, copyDisplay: locationText } = useCopy(
|
||||
contents[1].url,
|
||||
'Copy Link Location'
|
||||
);
|
||||
const { doCopy: doCopyNode, copyDisplay: nodeText } = useCopy(
|
||||
permalink,
|
||||
'Copy Node Permalink'
|
||||
);
|
||||
|
||||
const deleteLink = () => {
|
||||
if (confirm('Are you sure you want to delete this link?')) {
|
||||
api.graph.removeNodes(`~${ship}`, name, [node.post.index]);
|
||||
@ -157,7 +162,7 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
/>
|
||||
<Box ml="auto">
|
||||
<Link
|
||||
to={node.post.pending ? '#' : `${baseUrl}/${index}`}
|
||||
to={node.post.pending ? '#' : `${baseUrl}/index/${index}`}
|
||||
style={{ cursor: node.post.pending ? 'default' : 'pointer' }}>
|
||||
<Box display='flex'>
|
||||
<Icon color={commColor} icon='Chat' />
|
||||
@ -173,8 +178,12 @@ export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
options={
|
||||
<Col backgroundColor="white" border={1} borderRadius={1} borderColor="lightGray">
|
||||
<Row alignItems="center" p={1}>
|
||||
<Action bg="white" m={1} color="black" onClick={copyLocation}>{locationText}</Action>
|
||||
<Action bg="white" m={1} color="black" onClick={doCopyLink}>{locationText}</Action>
|
||||
</Row>
|
||||
<Row alignItems="center" p={1}>
|
||||
<Action bg="white" m={1} color="black" onClick={doCopyNode}>{nodeText}</Action>
|
||||
</Row>
|
||||
|
||||
{(ourRole === 'admin' || node.post.author === window.ship) &&
|
||||
<Row alignItems="center" p={1}>
|
||||
<Action bg="white" m={1} color="red" destructive onClick={deleteLink}>Delete Link</Action>
|
||||
|
@ -20,6 +20,7 @@ import { MentionText } from '~/views/components/MentionText';
|
||||
import ChatMessage from '../chat/components/ChatMessage';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
function getGraphModuleIcon(module: string) {
|
||||
if (module === 'link') {
|
||||
@ -71,13 +72,12 @@ const GraphUrl = ({ url, title }) => (
|
||||
</Box>
|
||||
);
|
||||
|
||||
const GraphNodeContent = ({
|
||||
export const GraphNodeContent = ({
|
||||
group,
|
||||
association,
|
||||
post,
|
||||
mod,
|
||||
description,
|
||||
index,
|
||||
remoteContentPolicy
|
||||
}) => {
|
||||
const { contents } = post;
|
||||
const idx = index.slice(1).split('/');
|
||||
@ -140,6 +140,7 @@ const GraphNodeContent = ({
|
||||
containerClass='items-top cf hide-child'
|
||||
group={group}
|
||||
groups={{}}
|
||||
association={association}
|
||||
associations={{ graph: {}, groups: {} }}
|
||||
msg={post}
|
||||
fontSize='0'
|
||||
@ -173,6 +174,9 @@ function getNodeUrl(
|
||||
const [linkId] = idx;
|
||||
return `${graphUrl}/${linkId}`;
|
||||
} else if (mod === 'chat') {
|
||||
if(idx.length > 0) {
|
||||
return `${graphUrl}?msg=${idx[0]}`;
|
||||
}
|
||||
return graphUrl;
|
||||
} else if( mod === 'post') {
|
||||
const [last, ...rest] = idx.reverse();
|
||||
@ -199,6 +203,9 @@ const GraphNode = ({
|
||||
const contacts = useContactState((state) => state.contacts);
|
||||
|
||||
const nodeUrl = getNodeUrl(mod, group?.hidden, groupPath, graph, index);
|
||||
const association = useMetadataState(
|
||||
useCallback(s => s.associations.graph[graph], [graph])
|
||||
);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (!read) {
|
||||
@ -207,11 +214,6 @@ const GraphNode = ({
|
||||
history.push(nodeUrl);
|
||||
}, [read, onRead]);
|
||||
|
||||
const showNickname = useShowNickname(contacts?.[`~${author}`]);
|
||||
const nickname =
|
||||
contacts?.[`~${author}`]?.nickname && showNickname
|
||||
? contacts[`~${author}`].nickname
|
||||
: cite(author);
|
||||
return (
|
||||
<Row onClick={onClick} gapX='2' pt={showContact ? 2 : 0}>
|
||||
<Col flexGrow={1} alignItems='flex-start'>
|
||||
@ -223,6 +225,7 @@ const GraphNode = ({
|
||||
post={post}
|
||||
mod={mod}
|
||||
description={description}
|
||||
association={association}
|
||||
index={index}
|
||||
group={group}
|
||||
remoteContentPolicy={{}}
|
||||
|
162
pkg/interface/src/views/apps/permalinks/TranscludedNode.tsx
Normal file
162
pkg/interface/src/views/apps/permalinks/TranscludedNode.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import React from "react";
|
||||
import { Anchor, Icon, Box, Row, Col, Text } from "@tlon/indigo-react";
|
||||
import ChatMessage from "../chat/components/ChatMessage";
|
||||
import { Association, GraphNode } from "@urbit/api";
|
||||
import { useGroupForAssoc } from "~/logic/state/group";
|
||||
import { MentionText } from "~/views/components/MentionText";
|
||||
import Author from "~/views/components/Author";
|
||||
import { NoteContent } from "../publish/components/Note";
|
||||
import bigInt from "big-integer";
|
||||
import { getSnippet } from "~/logic/lib/publish";
|
||||
import { NotePreviewContent } from "../publish/components/NotePreview";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
|
||||
function TranscludedLinkNode(props: {
|
||||
node: GraphNode;
|
||||
assoc: Association;
|
||||
transcluded: number;
|
||||
api: GlobalApi;
|
||||
}) {
|
||||
const { node, api, assoc, transcluded } = props;
|
||||
const idx = node.post.index.slice(1).split("/");
|
||||
|
||||
switch (idx.length) {
|
||||
case 1:
|
||||
const [{ text }, { url }] = node.post.contents;
|
||||
return (
|
||||
<Box borderRadius="2" p="2" bg="scales.black05">
|
||||
<Anchor underline={false} target="_blank" color="black" href={url}>
|
||||
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
||||
{text}
|
||||
</Anchor>
|
||||
</Box>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<TranscludedComment
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
node={node}
|
||||
assoc={assoc}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function TranscludedComment(props: {
|
||||
node: GraphNode;
|
||||
assoc: Association;
|
||||
api: GlobalApi;
|
||||
transcluded: number;
|
||||
}) {
|
||||
const { assoc, node, api, transcluded } = props;
|
||||
const group = useGroupForAssoc(assoc)!;
|
||||
|
||||
const comment = node.children?.peekLargest()![1]!;
|
||||
return (
|
||||
<Col>
|
||||
<Author
|
||||
p="2"
|
||||
showImage
|
||||
ship={comment.post.author}
|
||||
date={comment.post?.["time-sent"]}
|
||||
group={group}
|
||||
/>
|
||||
<Box p="2">
|
||||
<MentionText
|
||||
api={api}
|
||||
transcluded={transcluded}
|
||||
content={comment.post.contents}
|
||||
group={group}
|
||||
/>
|
||||
</Box>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
function TranscludedPublishNode(props: {
|
||||
node: GraphNode;
|
||||
assoc: Association;
|
||||
api: GlobalApi;
|
||||
transcluded: number;
|
||||
}) {
|
||||
const { node, assoc, transcluded, api } = props;
|
||||
const group = useGroupForAssoc(assoc)!;
|
||||
const idx = node.post.index.slice(1).split("/");
|
||||
switch (idx.length) {
|
||||
case 1:
|
||||
const post = node.children
|
||||
?.get(bigInt.one)
|
||||
?.children?.peekLargest()?.[1]!;
|
||||
return (
|
||||
<Col gapY="2">
|
||||
<Author
|
||||
px="2"
|
||||
showImage
|
||||
ship={post.post.author}
|
||||
date={post.post?.["time-sent"]}
|
||||
group={group}
|
||||
/>
|
||||
<Text px="2" fontSize="2" fontWeight="medium">
|
||||
{post.post.contents[0]?.text}
|
||||
</Text>
|
||||
<Box p="2">
|
||||
<NotePreviewContent
|
||||
snippet={getSnippet(post?.post.contents[1]?.text)}
|
||||
/>
|
||||
</Box>
|
||||
</Col>
|
||||
);
|
||||
|
||||
case 3:
|
||||
return (
|
||||
<TranscludedComment
|
||||
transcluded={transcluded}
|
||||
api={api}
|
||||
node={node}
|
||||
assoc={assoc}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function TranscludedNode(props: {
|
||||
assoc: Association;
|
||||
node: GraphNode;
|
||||
transcluded: number;
|
||||
api: GlobalApi;
|
||||
}) {
|
||||
const { node, assoc, transcluded } = props;
|
||||
const group = useGroupForAssoc(assoc)!;
|
||||
switch (assoc.metadata.module) {
|
||||
case "chat":
|
||||
return (
|
||||
<Row width="100%" flexShrink={0} flexGrow={1} flexWrap="wrap">
|
||||
<ChatMessage
|
||||
width="100%"
|
||||
renderSigil
|
||||
transcluded={transcluded + 1}
|
||||
containerClass="items-top cf hide-child"
|
||||
association={assoc}
|
||||
group={group}
|
||||
groups={{}}
|
||||
msg={node.post}
|
||||
fontSize="0"
|
||||
ml="0"
|
||||
mr="0"
|
||||
pt="2"
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
case "publish":
|
||||
return <TranscludedPublishNode {...props} />;
|
||||
case "link":
|
||||
return <TranscludedLinkNode {...props} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
151
pkg/interface/src/views/apps/permalinks/app.tsx
Normal file
151
pkg/interface/src/views/apps/permalinks/app.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import useMetadataState from "~/logic/state/metadata";
|
||||
import useGroupState from "~/logic/state/group";
|
||||
import {
|
||||
Switch,
|
||||
Route,
|
||||
Redirect,
|
||||
useLocation,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
import { makeResource, Association } from "@urbit/api";
|
||||
import { getGraphPermalink } from "./graphIndex";
|
||||
import { useQuery } from "~/logic/lib/useQuery";
|
||||
import useGraphState from "~/logic/state/graph";
|
||||
|
||||
interface ResourceRouteProps {
|
||||
ship: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function PermalinkRoutes(props: {}) {
|
||||
const groups = useGroupState((s) => s.groups);
|
||||
const { query, toQuery } = useQuery();
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path="/perma/group/:ship/:name"
|
||||
render={({ match, history, location }) => {
|
||||
const { ship, name } = match.params as ResourceRouteProps;
|
||||
console.log(ship);
|
||||
const { url } = match;
|
||||
const path = `/ship/${ship}/${name}`;
|
||||
const group = groups[path];
|
||||
if(!group) {
|
||||
if (Object.keys(groups).length > 0) {
|
||||
console.log(groups);
|
||||
const redir = location.pathname;
|
||||
const to = toQuery({ redir }, `/~landscape/join/${ship}/${name}`);
|
||||
return <Redirect to={to} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return <GroupRoutes url={url} group={path} />;
|
||||
}}
|
||||
/>
|
||||
<Route path="/perma" render={() => <FallbackRoutes query={query} />} />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
function FallbackRoutes(props: { query: URLSearchParams }) {
|
||||
const { query } = props;
|
||||
|
||||
if (query.has("ext")) {
|
||||
const ext = query.get("ext")!;
|
||||
console.log(ext);
|
||||
const url = `/perma${ext.slice(11)}`;
|
||||
console.log(url);
|
||||
return <Redirect to={{ pathname: url }} />;
|
||||
}
|
||||
return <Redirect to="/~404" />;
|
||||
}
|
||||
|
||||
function GroupRoutes(props: { group: string; url: string }) {
|
||||
const { group, url } = props;
|
||||
const makePath = (s: string) => url + s;
|
||||
const associations = useMetadataState((s) => s.associations);
|
||||
const graphKeys = useGraphState(s => s.graphKeys);
|
||||
const { toQuery } = useQuery();
|
||||
const groupUrl = `/~landscape${group}`;
|
||||
console.log(group);
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path={makePath("/graph/:ship/:name")}
|
||||
render={({ match, location }) => {
|
||||
const { ship, name } = match.params as ResourceRouteProps;
|
||||
const path = `/ship/${ship}/${name}`;
|
||||
const association = associations.graph[path];
|
||||
const { url: routeUrl } = match;
|
||||
if(!association) {
|
||||
return null;
|
||||
}
|
||||
console.log(graphKeys);
|
||||
if(!graphKeys.has(`${ship.slice(1)}/${name}`)) {
|
||||
if(graphKeys.size > 0) {
|
||||
return <Redirect
|
||||
to={toQuery(
|
||||
{ auto: 'y', redir: location.pathname },
|
||||
`${groupUrl}/join/${association.metadata.module}${path}`
|
||||
)}
|
||||
/>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return <GraphIndexRoutes url={routeUrl} association={association} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={makePath("")}
|
||||
render={() => {
|
||||
return <Redirect to={groupUrl} />;
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
export function GraphIndexRoutes(props: {
|
||||
association: Association;
|
||||
url: string;
|
||||
index?: string;
|
||||
}) {
|
||||
const { index = "", association, url } = props;
|
||||
const makePath = (s: string) => url + s;
|
||||
const group = useGroupState(
|
||||
useCallback((s) => s.groups[association.group], [association])
|
||||
);
|
||||
|
||||
if(!group) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path={makePath("/:id")}
|
||||
render={({ match }) => {
|
||||
const newIndex = `${index}/${match.params.id}`;
|
||||
const { url: newUrl } = match;
|
||||
return (
|
||||
<GraphIndexRoutes
|
||||
association={association}
|
||||
url={newUrl}
|
||||
index={newIndex}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route path={makePath("")}>
|
||||
<Redirect to={getGraphPermalink(association, group, index)} />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
178
pkg/interface/src/views/apps/permalinks/embed.tsx
Normal file
178
pkg/interface/src/views/apps/permalinks/embed.tsx
Normal file
@ -0,0 +1,178 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
parsePermalink,
|
||||
GraphPermalink as IGraphPermalink,
|
||||
getPermalinkForGraph,
|
||||
usePermalinkForGraph,
|
||||
} from "~/logic/lib/permalinks";
|
||||
import {
|
||||
Action,
|
||||
Box,
|
||||
Text,
|
||||
BaseAnchor,
|
||||
Row,
|
||||
Icon,
|
||||
Col,
|
||||
} from "@tlon/indigo-react";
|
||||
import { GroupLink } from "~/views/components/GroupLink";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { getModuleIcon } from "~/logic/lib/util";
|
||||
import useMetadataState from "~/logic/state/metadata";
|
||||
import { Association, resourceFromPath } from "@urbit/api";
|
||||
import { Link } from "react-router-dom";
|
||||
import useGraphState from "~/logic/state/graph";
|
||||
import { GraphNodeContent } from "../notifications/graph";
|
||||
import { TranscludedNode } from "./TranscludedNode";
|
||||
|
||||
function GroupPermalink(props: { group: string; api: GlobalApi }) {
|
||||
const { group, api } = props;
|
||||
return (
|
||||
<GroupLink
|
||||
resource={group}
|
||||
api={api}
|
||||
pl="2"
|
||||
border="1"
|
||||
borderRadius="2"
|
||||
borderColor="washedGray"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function GraphPermalink(
|
||||
props: IGraphPermalink & {
|
||||
api: GlobalApi;
|
||||
transcluded: number;
|
||||
pending?: boolean;
|
||||
}
|
||||
) {
|
||||
const { pending, link, graph, group, index, api, transcluded } = props;
|
||||
const { ship, name } = resourceFromPath(graph);
|
||||
const node = useGraphState(
|
||||
useCallback((s) => s.looseNodes?.[`${ship.slice(1)}/${name}`]?.[index], [
|
||||
graph,
|
||||
index,
|
||||
])
|
||||
);
|
||||
const [errored, setErrored] = useState(false);
|
||||
const association = useMetadataState(
|
||||
useCallback((s) => s.associations.graph[graph] as Association | null, [
|
||||
graph,
|
||||
])
|
||||
);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (pending) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.graph.getNode(ship, name, index);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setErrored(true);
|
||||
}
|
||||
})();
|
||||
}, [pending, graph, index]);
|
||||
const showTransclusion = !!(association && node && transcluded < 1);
|
||||
const permalink = getPermalinkForGraph(group, graph, index);
|
||||
|
||||
return (
|
||||
<Col
|
||||
width="100%"
|
||||
my="1"
|
||||
bg="white"
|
||||
border="1"
|
||||
borderColor="lightGray"
|
||||
borderRadius="2"
|
||||
>
|
||||
{showTransclusion && (
|
||||
<Box p="2">
|
||||
<TranscludedNode
|
||||
api={api}
|
||||
transcluded={transcluded + 1}
|
||||
node={node}
|
||||
assoc={association!}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{!!association ? (
|
||||
<PermalinkDetails
|
||||
known
|
||||
showTransclusion={showTransclusion}
|
||||
icon={getModuleIcon(association.metadata.module)}
|
||||
title={association.metadata.title}
|
||||
permalink={permalink}
|
||||
/>
|
||||
) : (
|
||||
<PermalinkDetails
|
||||
icon="Groups"
|
||||
title={graph.slice(5)}
|
||||
permalink={permalink}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
function PermalinkDetails(props: {
|
||||
title: string;
|
||||
icon: any;
|
||||
permalink: string;
|
||||
showTransclusion?: boolean;
|
||||
known?: boolean;
|
||||
}) {
|
||||
const { title, icon, permalink, known, showTransclusion } = props;
|
||||
const rowTransclusionStyle = showTransclusion
|
||||
? {
|
||||
borderTop: "1",
|
||||
borderTopColor: "washedGray",
|
||||
my: "1",
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
<Row
|
||||
{...rowTransclusionStyle}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
px="2"
|
||||
py="1"
|
||||
>
|
||||
<Row gapX="2" alignItems="center">
|
||||
<Icon icon={icon} />
|
||||
<Text lineHeight="20px" mono={!known}>
|
||||
{title}
|
||||
</Text>
|
||||
</Row>
|
||||
<Link to={`/perma${permalink.slice(11)}`}>
|
||||
<Text color="blue">Go to link</Text>
|
||||
</Link>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export function PermalinkEmbed(props: {
|
||||
link: string;
|
||||
association?: Association;
|
||||
api: GlobalApi;
|
||||
transcluded: number;
|
||||
}) {
|
||||
const permalink = parsePermalink(props.link);
|
||||
|
||||
if (!permalink) {
|
||||
return <BaseAnchor href={props.link}>{props.link}</BaseAnchor>;
|
||||
}
|
||||
|
||||
switch (permalink.type) {
|
||||
case "group":
|
||||
return <GroupPermalink group={permalink.group} api={props.api} />;
|
||||
case "graph":
|
||||
return (
|
||||
<GraphPermalink
|
||||
transcluded={props.transcluded}
|
||||
{...permalink}
|
||||
api={props.api}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
122
pkg/interface/src/views/apps/permalinks/graphIndex.tsx
Normal file
122
pkg/interface/src/views/apps/permalinks/graphIndex.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { Switch, Route, Redirect } from "react-router-dom";
|
||||
import { Association, Group } from "@urbit/api";
|
||||
|
||||
export function getGraphPermalink(
|
||||
assoc: Association,
|
||||
group: Group,
|
||||
index: string
|
||||
) {
|
||||
const mod = assoc.metadata.module;
|
||||
const groupPath = group.hidden
|
||||
? "/~landscape/home"
|
||||
: `/~landscape${assoc.group}`;
|
||||
if (mod === "chat") {
|
||||
return getChatPermalink(
|
||||
group.hidden ? "/~landscape/messages" : `/~landscape${assoc.group}`,
|
||||
assoc,
|
||||
index
|
||||
);
|
||||
} else if (mod === "publish") {
|
||||
return getPublishPermalink(groupPath, assoc, index);
|
||||
} else if (mod === "link") {
|
||||
return getLinkPermalink(groupPath, assoc, index);
|
||||
}
|
||||
return "/~404";
|
||||
}
|
||||
|
||||
function getPublishPermalink(
|
||||
groupPath: string,
|
||||
assoc: Association,
|
||||
index: string
|
||||
) {
|
||||
const idx = index.split("/").slice(1);
|
||||
const base = `${groupPath}/resource/publish${assoc.resource}`;
|
||||
let isComment = false;
|
||||
const res = _.reduce(
|
||||
idx,
|
||||
(acc, val, i) => {
|
||||
if (i === 0) {
|
||||
return {...acc, pathname: `${acc.pathname}/note/${val}` };
|
||||
} else if (i === 1 && val === '2') {
|
||||
isComment = true;
|
||||
return acc;
|
||||
} else if (i === 2 && isComment) {
|
||||
return { ...acc, search: `?selected=${val}` };
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ pathname: base }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
function getLinkPermalink(
|
||||
groupPath: string,
|
||||
assoc: Association,
|
||||
index: string
|
||||
) {
|
||||
const idx = index.split("/").slice(1);
|
||||
const base = `${groupPath}/resource/link${assoc.resource}`;
|
||||
const res = _.reduce(
|
||||
idx,
|
||||
(acc, val, i) => {
|
||||
console.log(acc);
|
||||
if (i === 0) {
|
||||
return {...acc, pathname: `${acc.pathname}/index/${val}` };
|
||||
} else if (i === 1) {
|
||||
return {...acc, search: `?selected=${val}` };
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ pathname: base }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
function getChatPermalink(
|
||||
groupPath: string,
|
||||
assoc: Association,
|
||||
index: string
|
||||
) {
|
||||
const idx = index.split("/").slice(1);
|
||||
if (idx.length === 0) {
|
||||
return `${groupPath}/resource/chat${assoc.resource}`;
|
||||
}
|
||||
return `${groupPath}/resource/chat${assoc.resource}?msg=${idx[0]}`;
|
||||
}
|
||||
|
||||
export function GraphIndexRoute(props: {
|
||||
association: Association;
|
||||
group: Group;
|
||||
index: string;
|
||||
url: string;
|
||||
}) {
|
||||
const { url, index, association, group } = props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${url}/:id`}
|
||||
render={({ match }) => {
|
||||
const newUrl = `${url}/${match.params.id}`;
|
||||
const newIndex = `${index}/${match.params.id}`;
|
||||
return (
|
||||
<GraphIndexRoute
|
||||
group={group}
|
||||
url={newUrl}
|
||||
association={association}
|
||||
index={newIndex}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route path="">
|
||||
<Redirect
|
||||
to={getGraphPermalink(association, group, index)}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Text, Col, Anchor, Row } from '@tlon/indigo-react';
|
||||
import { Box, Text, Col, Anchor, Row, Action } from '@tlon/indigo-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
@ -12,6 +12,9 @@ import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import Author from '~/views/components/Author';
|
||||
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from '@urbit/api';
|
||||
import {useCopy} from '~/logic/lib/useCopy';
|
||||
import {usePermalinkForGraph, getPermalinkForGraph} from '~/logic/lib/permalinks';
|
||||
import {useQuery} from '~/logic/lib/useQuery';
|
||||
|
||||
interface NoteProps {
|
||||
ship: string;
|
||||
@ -25,19 +28,28 @@ interface NoteProps {
|
||||
group: Group;
|
||||
}
|
||||
|
||||
const renderers = {
|
||||
link: ({ href, children }) => {
|
||||
return (
|
||||
<Anchor display="inline" target="_blank" href={href}>{children}</Anchor>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export function NoteContent({ body }) {
|
||||
return (
|
||||
|
||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
||||
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export function Note(props: NoteProps & RouteComponentProps) {
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
const { notebook, note, ship, book, api, rootUrl, baseUrl, group } = props;
|
||||
const editCommentId = props.match.params.commentId;
|
||||
|
||||
const renderers = {
|
||||
link: ({ href, children }) => {
|
||||
return (
|
||||
<Anchor display="inline" target="_blank" href={href}>{children}</Anchor>
|
||||
)
|
||||
}
|
||||
};
|
||||
const { association, notebook, note, ship, book, api, rootUrl, baseUrl, group } = props;
|
||||
|
||||
const deletePost = async () => {
|
||||
setDeleting(true);
|
||||
@ -46,6 +58,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
props.history.push(rootUrl);
|
||||
};
|
||||
|
||||
const { query } = useQuery();
|
||||
const comments = getComments(note);
|
||||
const [revNum, title, body, post] = getLatestRevision(note);
|
||||
const index = note.post.index.split('/');
|
||||
@ -59,40 +72,34 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
const ourRole = roleForShip(group, window.ship);
|
||||
if (window.ship === note?.post?.author) {
|
||||
adminLinks.push(
|
||||
<Link
|
||||
style={{ 'display': 'inline-block' }}
|
||||
to={`${baseUrl}/edit`}
|
||||
>
|
||||
<Text
|
||||
color="blue"
|
||||
ml={2}
|
||||
>
|
||||
Update
|
||||
</Text>
|
||||
<Link to={`${baseUrl}/edit`}>
|
||||
<Action>Update</Action>
|
||||
</Link>
|
||||
)
|
||||
};
|
||||
|
||||
if (window.ship === note?.post?.author || ourRole === "admin") {
|
||||
adminLinks.push(
|
||||
<Text
|
||||
color="red"
|
||||
display='inline-block'
|
||||
ml={2}
|
||||
onClick={deletePost}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Action destructive onClick={deletePost}>
|
||||
Delete
|
||||
</Text>
|
||||
</Action>
|
||||
)
|
||||
};
|
||||
|
||||
const permalink = getPermalinkForGraph(
|
||||
association.group,
|
||||
association.resource,
|
||||
`/${noteId.toString()}`
|
||||
);
|
||||
|
||||
const { doCopy, copyDisplay } = useCopy(permalink, 'Copy Link');
|
||||
|
||||
const windowRef = React.useRef(null);
|
||||
useEffect(() => {
|
||||
if (windowRef.current) {
|
||||
if (windowRef.current && !query.has('selected')) {
|
||||
windowRef.current.parentElement.scrollTop = 0;
|
||||
}
|
||||
}, [windowRef, note]);
|
||||
}, [note, windowRef]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -118,13 +125,15 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
ship={post?.author}
|
||||
date={post?.['time-sent']}
|
||||
group={group}
|
||||
/>
|
||||
<Text ml={1}>{adminLinks}</Text>
|
||||
>
|
||||
<Row px="2" gapX="2" alignItems="flex-end">
|
||||
<Action bg="white" onClick={doCopy}>{copyDisplay}</Action>
|
||||
{adminLinks}
|
||||
</Row>
|
||||
</Author>
|
||||
</Row>
|
||||
</Col>
|
||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
||||
<ReactMarkdown source={body} linkTarget={'_blank'} renderers={renderers} />
|
||||
</Box>
|
||||
<NoteContent body={body} />
|
||||
<NoteNavigation
|
||||
notebook={notebook}
|
||||
noteId={noteId}
|
||||
@ -138,7 +147,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
association={props.association}
|
||||
api={props.api}
|
||||
baseUrl={baseUrl}
|
||||
editCommentId={editCommentId}
|
||||
history={props.history}
|
||||
group={group}
|
||||
/>
|
||||
|
@ -27,6 +27,27 @@ const WrappedBox = styled(Box)`
|
||||
overflow-wrap: break-word;
|
||||
`;
|
||||
|
||||
export function NotePreviewContent({ snippet }) {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
unwrapDisallowed
|
||||
allowedTypes={['text', 'root', 'break', 'paragraph', 'image']}
|
||||
renderers={{
|
||||
image: props => (
|
||||
<Box
|
||||
backgroundImage={`url(${props.src})`}
|
||||
style={{ backgroundSize: 'cover',
|
||||
backgroundPosition: "center" }}
|
||||
>
|
||||
<Image src={props.src} opacity="0" maxHeight="300px"/>
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
source={snippet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotePreview(props: NotePreviewProps) {
|
||||
const { node, group } = props;
|
||||
const { post } = node;
|
||||
@ -66,23 +87,8 @@ export function NotePreview(props: NotePreviewProps) {
|
||||
>
|
||||
<WrappedBox mb={2}><Text bold>{title}</Text></WrappedBox>
|
||||
<WrappedBox>
|
||||
<Text fontSize='14px' lineHeight='tall'>
|
||||
<ReactMarkdown
|
||||
unwrapDisallowed
|
||||
allowedTypes={['text', 'root', 'break', 'paragraph', 'image']}
|
||||
renderers={{
|
||||
image: props => (
|
||||
<Box
|
||||
backgroundImage={`url(${props.src})`}
|
||||
style={{ backgroundSize: 'cover',
|
||||
backgroundPosition: "center" }}
|
||||
>
|
||||
<Image src={props.src} opacity="0" maxHeight="300px"/>
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
source={snippet}
|
||||
/>
|
||||
<Text fontSize='14px' lineHeight='tall'>
|
||||
<NotePreviewContent snippet={snippet} />
|
||||
</Text>
|
||||
</WrappedBox>
|
||||
</Col>
|
||||
|
@ -14,6 +14,7 @@ import { Sigil } from '~/logic/lib/sigil';
|
||||
import Timestamp from './Timestamp';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import ProfileOverlay from './ProfileOverlay';
|
||||
import {PropFunc} from '~/types';
|
||||
|
||||
interface AuthorProps {
|
||||
ship: string;
|
||||
@ -26,8 +27,17 @@ interface AuthorProps {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
export default function Author(props: AuthorProps): ReactElement {
|
||||
const { ship, date, showImage, fullNotIcon } = props;
|
||||
export default function Author(props: AuthorProps & PropFunc<typeof Box>): ReactElement {
|
||||
const {
|
||||
ship = '',
|
||||
date,
|
||||
showImage,
|
||||
fullNotIcon,
|
||||
children,
|
||||
unread,
|
||||
group,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const showAsCol = props.showAsCol || false;
|
||||
const time = props.time || false;
|
||||
@ -75,7 +85,7 @@ export default function Author(props: AuthorProps): ReactElement {
|
||||
>
|
||||
{name}
|
||||
</Box>
|
||||
<Timestamp stamp={stamp} fontSize={1} time={time} ml={2} color={props.unread ? 'blue' : 'gray'} />
|
||||
<Timestamp stamp={stamp} fontSize={1} time={time} ml={2} color={unread ? 'blue' : 'gray'} />
|
||||
</Col>
|
||||
) : (
|
||||
<>
|
||||
@ -89,7 +99,7 @@ export default function Author(props: AuthorProps): ReactElement {
|
||||
>
|
||||
{name}
|
||||
</Box>
|
||||
<Timestamp stamp={stamp} fontSize={1} time={time} ml={2} color={props.unread ? 'blue' : 'gray'} />
|
||||
<Timestamp stamp={stamp} fontSize={1} time={time} ml={2} color={unread ? 'blue' : 'gray'} />
|
||||
</>
|
||||
);
|
||||
|
||||
@ -107,7 +117,7 @@ export default function Author(props: AuthorProps): ReactElement {
|
||||
) : sigil;
|
||||
|
||||
return (
|
||||
<Row alignItems='center' width='auto'>
|
||||
<Row height="20px" {...rest} alignItems='center' width='auto'>
|
||||
<Box
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@ -124,7 +134,7 @@ export default function Author(props: AuthorProps): ReactElement {
|
||||
)}
|
||||
</Box>
|
||||
{rowOrCol}
|
||||
{props.children}
|
||||
{children}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React, {useEffect, useRef, useCallback} from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Box, Row, Text } from '@tlon/indigo-react';
|
||||
import { Box, Row, Text, Action } from '@tlon/indigo-react';
|
||||
import { Contacts } from '@urbit/api/contacts';
|
||||
import { GraphNode } from '@urbit/api/graph';
|
||||
import { Group } from '@urbit/api';
|
||||
@ -12,6 +12,9 @@ import Author from '~/views/components/Author';
|
||||
import { MentionText } from '~/views/components/MentionText';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { getLatestCommentRevision } from '~/logic/lib/publish';
|
||||
import {useCopy} from '~/logic/lib/useCopy';
|
||||
import { getPermalinkForGraph} from '~/logic/lib/permalinks';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const ClickBox = styled(Box)`
|
||||
cursor: pointer;
|
||||
@ -27,10 +30,15 @@ interface CommentItemProps {
|
||||
ship: string;
|
||||
api: GlobalApi;
|
||||
group: Group;
|
||||
highlighted: boolean;
|
||||
}
|
||||
|
||||
export function CommentItem(props: CommentItemProps): ReactElement {
|
||||
const { ship, name, api, comment, group } = props;
|
||||
const association = useMetadataState(
|
||||
useCallback(s => s.associations.graph[`/ship/${ship}/${name}`], [ship,name])
|
||||
);
|
||||
const ref = useRef<HTMLElement | null>(null);
|
||||
const [, post] = getLatestCommentRevision(comment);
|
||||
const disabled = props.pending;
|
||||
|
||||
@ -40,34 +48,46 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
||||
|
||||
const commentIndexArray = (comment.post?.index || '/').split('/');
|
||||
const commentIndex = commentIndexArray[commentIndexArray.length - 1];
|
||||
const updateUrl = `${props.baseUrl}/${commentIndex}`;
|
||||
|
||||
const adminLinks: JSX.Element[] = [];
|
||||
const ourRole = roleForShip(group, window.ship);
|
||||
if (window.ship == post?.author && !disabled) {
|
||||
adminLinks.push(
|
||||
<Link to={updateUrl}>
|
||||
<Text
|
||||
color="blue"
|
||||
ml={2}
|
||||
>
|
||||
<Link to={{ pathname: props.baseUrl, search: `?edit=${commentIndex}`}}>
|
||||
<Action>
|
||||
Update
|
||||
</Text>
|
||||
</Action>
|
||||
</Link>
|
||||
)
|
||||
};
|
||||
|
||||
if ((window.ship == post?.author || ourRole == "admin") && !disabled) {
|
||||
adminLinks.push(
|
||||
<ClickBox display="inline-block" color="red" onClick={onDelete}>
|
||||
<Text color='red'>Delete</Text>
|
||||
</ClickBox>
|
||||
<Action onClick={onDelete} destructive>
|
||||
Delete
|
||||
</Action>
|
||||
)
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(ref.current && props.highlighted) {
|
||||
ref.current.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
}, [ref, props.highlighted]);
|
||||
const history = useHistory();
|
||||
|
||||
const { copyDisplay, doCopy } = useCopy(
|
||||
getPermalinkForGraph(
|
||||
association.group,
|
||||
association.resource,
|
||||
post.index.split('/').slice(0, -1).join('/')
|
||||
),
|
||||
'Copy Link'
|
||||
);
|
||||
|
||||
return (
|
||||
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
|
||||
<Row bg="white" my={3}>
|
||||
<Box ref={ref} mb={4} opacity={post?.pending ? '60%' : '100%'}>
|
||||
<Row px="1" my={3}>
|
||||
<Author
|
||||
showImage
|
||||
ship={post?.author}
|
||||
@ -75,13 +95,21 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
||||
unread={props.unread}
|
||||
group={group}
|
||||
>
|
||||
<Row alignItems="center">
|
||||
<Row px="2" gapX="2" alignItems="center">
|
||||
<Action bg="white" onClick={doCopy}>{copyDisplay}</Action>
|
||||
{adminLinks}
|
||||
</Row>
|
||||
</Author>
|
||||
</Row>
|
||||
<Box mb={2}>
|
||||
<Box
|
||||
borderRadius="1"
|
||||
p="1"
|
||||
mb="1"
|
||||
backgroundColor={props.highlighted ? 'washedBlue' : 'white'}
|
||||
>
|
||||
<MentionText
|
||||
transcluded={0}
|
||||
api={api}
|
||||
group={group}
|
||||
content={post?.contents}
|
||||
/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import bigInt from 'big-integer';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import { CommentItem } from './CommentItem';
|
||||
@ -14,13 +14,14 @@ import { getUnreadCount } from '~/logic/lib/hark';
|
||||
import { PropFunc } from '~/types/util';
|
||||
import { isWriter } from '~/logic/lib/group';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import {useQuery} from '~/logic/lib/useQuery';
|
||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
||||
|
||||
interface CommentsProps {
|
||||
comments: GraphNode;
|
||||
association: Association;
|
||||
name: string;
|
||||
ship: string;
|
||||
editCommentId: string;
|
||||
baseUrl: string;
|
||||
api: GlobalApi;
|
||||
group: Group;
|
||||
@ -32,7 +33,6 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
||||
comments,
|
||||
ship,
|
||||
name,
|
||||
editCommentId,
|
||||
api,
|
||||
history,
|
||||
baseUrl,
|
||||
@ -40,6 +40,18 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { query } = useQuery();
|
||||
const selectedComment = useMemo(() => {
|
||||
const id = query.get('selected')
|
||||
return id ? bigInt(id) : null;
|
||||
}, [query]);
|
||||
|
||||
const editCommentId = useMemo(() => {
|
||||
const id = query.get('edit')
|
||||
return id || '';
|
||||
}, [query]);
|
||||
|
||||
|
||||
const onSubmit = async (
|
||||
{ comment },
|
||||
actions: FormikHelpers<{ comment: string }>
|
||||
@ -95,7 +107,10 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
||||
val = val + curr.url;
|
||||
} else if ('code' in curr) {
|
||||
val = val + curr.code.expression;
|
||||
} else if ('reference' in curr) {
|
||||
val = `${val}web+urbit-graph:/${referenceToPermalink(curr).link}`;
|
||||
}
|
||||
|
||||
return val;
|
||||
}, '');
|
||||
}
|
||||
@ -116,8 +131,8 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
||||
|
||||
return (
|
||||
<Col {...rest}>
|
||||
{( !props.editCommentId && canComment ? <CommentInput onSubmit={onSubmit} /> : null )}
|
||||
{( props.editCommentId ? (
|
||||
{( !editCommentId && canComment ? <CommentInput onSubmit={onSubmit} /> : null )}
|
||||
{( editCommentId ? (
|
||||
<CommentInput
|
||||
onSubmit={onEdit}
|
||||
label='Edit Comment'
|
||||
@ -126,9 +141,11 @@ export function Comments(props: CommentsProps & PropFunc<typeof Col>) {
|
||||
/>
|
||||
) : null )}
|
||||
{children.reverse()
|
||||
.map(([idx, comment], i) => {
|
||||
.map(([idx, comment], i) => {
|
||||
const highlighted = selectedComment?.eq(idx) ?? false;
|
||||
return (
|
||||
<CommentItem
|
||||
highlighted={highlighted}
|
||||
comment={comment}
|
||||
key={idx.toString()}
|
||||
api={api}
|
||||
|
@ -99,7 +99,7 @@ export function InviteItem(props: InviteItemProps) {
|
||||
}
|
||||
}, [invite]);
|
||||
|
||||
if(status.hidden) {
|
||||
if(status?.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,15 @@ import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
|
||||
import OverlaySigil from '~/views/components/OverlaySigil';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
|
||||
interface MentionTextProps {
|
||||
contact?: Contact;
|
||||
content: Content[];
|
||||
group: Group;
|
||||
transcluded: number;
|
||||
api: GlobalApi;
|
||||
}
|
||||
export function MentionText(props: MentionTextProps) {
|
||||
const { content, contact, group, ...rest } = props;
|
||||
@ -25,6 +29,9 @@ export function MentionText(props: MentionTextProps) {
|
||||
return accum + `[~${c.mention}]`;
|
||||
} else if ('url' in c) {
|
||||
return accum + `\n ${c.url}`;
|
||||
} else if ('reference' in c) {
|
||||
const { link } = referenceToPermalink(c);
|
||||
return accum + `\n [${link}]`;
|
||||
}
|
||||
return accum;
|
||||
}, '')}
|
||||
|
@ -7,6 +7,7 @@ import { RemoteContentPolicy } from '~/types/local-update';
|
||||
import { VirtualContextProps, withVirtual } from "~/logic/lib/virtualContext";
|
||||
import { IS_IOS } from '~/logic/lib/platform';
|
||||
import withState from '~/logic/lib/withState';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
type RemoteContentProps = VirtualContextProps & {
|
||||
url: string;
|
||||
@ -124,6 +125,14 @@ return;
|
||||
|
||||
wrapInLink(contents) {
|
||||
const { style } = this.props;
|
||||
if(this.props.url.startsWith('arvo://')) {
|
||||
return (
|
||||
<Link to={this.props.url.slice(6)}>
|
||||
{contents}
|
||||
</Link>
|
||||
);
|
||||
|
||||
}
|
||||
return (<BaseAnchor
|
||||
href={this.props.url}
|
||||
flexShrink={0}
|
||||
|
@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown';
|
||||
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
||||
import { Anchor, Text } from '@tlon/indigo-react';
|
||||
import { isValidPatp } from 'urbit-ob';
|
||||
import { PermalinkEmbed } from "~/views/apps/permalinks/embed"
|
||||
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
@ -22,7 +23,7 @@ const DISABLED_BLOCK_TOKENS = [
|
||||
|
||||
const DISABLED_INLINE_TOKENS = [];
|
||||
|
||||
const RichText = React.memo(({ disableRemoteContent, ...props }) => (
|
||||
const RichText = React.memo(({ disableRemoteContent, api, ...props }) => (
|
||||
<ReactMarkdown
|
||||
{...props}
|
||||
renderers={{
|
||||
@ -43,6 +44,9 @@ const RichText = React.memo(({ disableRemoteContent, ...props }) => (
|
||||
const linkText = String(linkProps.children[0].props.children);
|
||||
if (isValidPatp(linkText)) {
|
||||
return <Mention contact={props.contact || {}} group={props.group} ship={deSig(linkText)} />;
|
||||
} else if(linkText.startsWith('web+urbit-graph://')) {
|
||||
return <PermalinkEmbed pending={props.pending} link={linkText} transcluded={props.transcluded} api={api}/>;
|
||||
|
||||
}
|
||||
return linkText;
|
||||
},
|
||||
|
@ -8,11 +8,14 @@ interface AsyncButtonProps {
|
||||
children: ReactNode;
|
||||
name?: string;
|
||||
onClick: (e: React.MouseEvent) => Promise<void>;
|
||||
/** Manual override */
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export function StatelessAsyncButton({
|
||||
children,
|
||||
onClick,
|
||||
loading,
|
||||
name = '',
|
||||
disabled = false,
|
||||
...rest
|
||||
@ -29,16 +32,16 @@ export function StatelessAsyncButton({
|
||||
onClick={handleClick}
|
||||
{...rest}
|
||||
>
|
||||
{state === 'error' ? (
|
||||
'Error'
|
||||
) : state === 'loading' ? (
|
||||
{(state === 'loading' || loading) ? (
|
||||
<LoadingSpinner
|
||||
foreground={
|
||||
rest.primary ? 'white' : rest.destructive ? 'red' : 'black'
|
||||
}
|
||||
background="gray"
|
||||
/>
|
||||
) : state === 'success' ? (
|
||||
) : state === 'error' ? (
|
||||
'Error'
|
||||
) : state === 'success' ? (
|
||||
'Done'
|
||||
) : (
|
||||
children
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
import { Box, Text, Button, Col, Center } from '@tlon/indigo-react';
|
||||
import RichText from '~/views/components/RichText';
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from './StatelessAsyncButton';
|
||||
import { Graphs } from '@urbit/api';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import {useQuery} from '~/logic/lib/useQuery';
|
||||
|
||||
interface UnjoinedResourceProps {
|
||||
association: Association;
|
||||
@ -31,12 +32,14 @@ function isJoined(path: string) {
|
||||
export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||
const { api } = props;
|
||||
const history = useHistory();
|
||||
const { query } = useQuery();
|
||||
const rid = props.association.resource;
|
||||
const appName = props.association['app-name'];
|
||||
|
||||
const { title, description, config } = props.association.metadata;
|
||||
const graphKeys = useGraphState(state => state.graphKeys);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const waiter = useWaitForProps({ ...props, graphKeys });
|
||||
const app = useMemo(() => config.graph || appName, [props.association]);
|
||||
|
||||
@ -44,7 +47,8 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||
const [, , ship, name] = rid.split('/');
|
||||
await api.graph.joinGraph(ship, name);
|
||||
await waiter(isJoined(rid));
|
||||
history.push(`${props.baseUrl}/resource/${app}${rid}`);
|
||||
const redir = query.get('redir') ?? `${props.baseUrl}/resource/${app}${rid}`;
|
||||
history.push(redir);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -53,6 +57,17 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||
}
|
||||
}, [props.association, graphKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if(query.has('auto')) {
|
||||
setLoading(true);
|
||||
await onJoin();
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
})();
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
<Center p={6}>
|
||||
<Col
|
||||
@ -72,6 +87,7 @@ export function UnjoinedResource(props: UnjoinedResourceProps) {
|
||||
<StatelessAsyncButton
|
||||
name={rid}
|
||||
primary
|
||||
loading={loading}
|
||||
width="fit-content"
|
||||
onClick={onJoin}
|
||||
>
|
||||
|
@ -11,8 +11,9 @@ import Settings from '~/views/apps/settings/settings';
|
||||
import ErrorComponent from '~/views/components/Error';
|
||||
import Notifications from '~/views/apps/notifications/notifications';
|
||||
import GraphApp from '../../apps/graph/app';
|
||||
import { PermalinkRoutes } from '~/views/apps/permalinks/app';
|
||||
|
||||
import { useMigrateSettings } from '~/logic/lib/migrateSettings';
|
||||
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
||||
|
||||
|
||||
export const Container = styled(Box)`
|
||||
@ -25,12 +26,23 @@ export const Container = styled(Box)`
|
||||
|
||||
export const Content = (props) => {
|
||||
|
||||
const doMigrate = useMigrateSettings();
|
||||
const [hasProtocol, setHasProtocol] = useLocalStorageState(
|
||||
'registeredProtocol', false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
doMigrate();
|
||||
}, 10000);
|
||||
}, []);
|
||||
console.log('a');
|
||||
if(!hasProtocol && window?.navigator?.registerProtocolHandler) {
|
||||
try {
|
||||
window.navigator.registerProtocolHandler('web+urbit-graph', '/perma?ext=%s', 'Urbit Links');
|
||||
console.log('registered protocol');
|
||||
setHasProtocol(true);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}, [hasProtocol]);
|
||||
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -89,6 +101,7 @@ export const Content = (props) => {
|
||||
)}
|
||||
/>
|
||||
<GraphApp path="/~graph" {...props} />
|
||||
<PermalinkRoutes {...props} />
|
||||
<Route
|
||||
render={p => (
|
||||
<ErrorComponent
|
||||
|
@ -25,6 +25,7 @@ import { GroupSummary } from './GroupSummary';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import {TUTORIAL_GROUP_RESOURCE} from '~/logic/lib/tutorialModal';
|
||||
import {useQuery} from '~/logic/lib/useQuery';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
group: Yup.string()
|
||||
@ -71,7 +72,9 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
||||
MetadataUpdatePreview | string | null
|
||||
>(null);
|
||||
|
||||
const waiter = useWaitForProps({ associations, groups }, _.isString(preview) ? 1 : 5000);
|
||||
const waiter = useWaitForProps({ associations, groups }, _.isString(preview) ? 1 : 30000);
|
||||
|
||||
const { query } = useQuery();
|
||||
|
||||
const onConfirm = useCallback(async (group: string) => {
|
||||
const [,,ship,name] = group.split('/');
|
||||
@ -86,6 +89,11 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
||||
|| group in (p.associations?.groups ?? {}));
|
||||
});
|
||||
|
||||
if(query.has('redir')) {
|
||||
const redir = query.get('redir')!;
|
||||
history.push(redir);
|
||||
}
|
||||
|
||||
if(groups?.[group]?.hidden) {
|
||||
const { metadata } = associations.graph[group];
|
||||
history.push(`/~landscape/home/resource/${metadata.config.graph}${group}`);
|
||||
|
@ -6,6 +6,7 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
a, a:any-link {
|
||||
a, a:any-link, a:-webkit-any-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
color: unset;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ class Landscape extends Component<LandscapeProps, Record<string, never>> {
|
||||
<Route path="/~landscape/join/:ship?/:name?"
|
||||
render={(routeProps) => {
|
||||
const { ship, name } = routeProps.match.params;
|
||||
const autojoin = ship && name ? `${ship}/${name}` : null;
|
||||
const autojoin = ship && name ? `${ship}/${name}` : undefined;
|
||||
return (
|
||||
<Body>
|
||||
<Box maxWidth="300px">
|
||||
@ -158,4 +158,4 @@ class Landscape extends Component<LandscapeProps, Record<string, never>> {
|
||||
|
||||
export default withState(Landscape, [
|
||||
[useHarkState, ['notificationsCount']]
|
||||
]);
|
||||
]);
|
||||
|
@ -15,8 +15,21 @@ export interface CodeContent {
|
||||
}
|
||||
|
||||
export interface ReferenceContent {
|
||||
uid: string;
|
||||
reference: GraphReference | GroupReference;
|
||||
}
|
||||
|
||||
export interface GraphReference {
|
||||
graph: {
|
||||
graph: string;
|
||||
group: string;
|
||||
index: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GroupReference {
|
||||
group: string;
|
||||
}
|
||||
|
||||
export interface MentionContent {
|
||||
mention: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user