summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--NOTES.md (renamed from wtfdesk/NOTES.md)3
-rw-r--r--desk/app/nostrill.hoon40
-rw-r--r--desk/lib/constants.hoon1
-rw-r--r--desk/lib/json/nostr.hoon16
-rw-r--r--desk/lib/json/nostrill.hoon53
-rw-r--r--desk/lib/nostrill.hoon35
-rw-r--r--desk/lib/shim.hoon3
-rw-r--r--desk/lib/trill/feed.hoon51
-rw-r--r--desk/sur/nostrill.hoon25
-rw-r--r--front/.envrc10
-rw-r--r--front/.gitignore10
-rw-r--r--front/devenv.lock103
-rw-r--r--front/devenv.nix57
-rw-r--r--front/devenv.yaml15
-rw-r--r--front/src/App.tsx2
-rw-r--r--front/src/Router.tsx2
-rw-r--r--front/src/components/composer/Composer.tsx (renamed from front/src/components/feed/Composer.tsx)37
-rw-r--r--front/src/components/composer/Snippets.tsx62
-rw-r--r--front/src/components/feed/PostList.tsx3
-rw-r--r--front/src/components/feed/Quote.tsx37
-rw-r--r--front/src/components/layout/Sidebar.tsx2
-rw-r--r--front/src/components/post/Body.tsx (renamed from front/src/components/feed/Body.tsx)2
-rw-r--r--front/src/components/post/Card.tsx (renamed from front/src/components/feed/Card.tsx)0
-rw-r--r--front/src/components/post/External.tsx (renamed from front/src/components/feed/External.tsx)0
-rw-r--r--front/src/components/post/Footer.tsx (renamed from front/src/components/feed/Footer.tsx)9
-rw-r--r--front/src/components/post/Header.tsx (renamed from front/src/components/feed/Header.tsx)9
-rw-r--r--front/src/components/post/Loader.tsx (renamed from front/src/components/feed/PostData.tsx)0
-rw-r--r--front/src/components/post/Media.tsx (renamed from front/src/components/feed/Media.tsx)0
-rw-r--r--front/src/components/post/Post.tsx (renamed from front/src/components/feed/Post.tsx)15
-rw-r--r--front/src/components/post/PostWrapper.tsx14
-rw-r--r--front/src/components/post/Quote.tsx64
-rw-r--r--front/src/components/post/RP.tsx (renamed from front/src/components/feed/RP.tsx)2
-rw-r--r--front/src/components/post/Reactions.tsx (renamed from front/src/components/feed/Reactions.tsx)0
-rw-r--r--front/src/components/post/StatsModal.tsx (renamed from front/src/components/feed/StatsModal.tsx)0
-rw-r--r--front/src/components/post/wrappers/Nostr.tsx15
-rw-r--r--front/src/components/post/wrappers/NostrIcon.tsx (renamed from front/src/components/feed/NostrIcon.tsx)0
-rw-r--r--front/src/components/snippets/Snippets.tsx395
-rw-r--r--front/src/logic/api.ts2
-rw-r--r--front/src/logic/nostril.ts36
-rw-r--r--front/src/logic/nostrill.ts118
-rw-r--r--front/src/logic/requests/nostrill.ts (renamed from front/src/logic/requests/nostril.ts)20
-rw-r--r--front/src/pages/Feed.tsx12
-rw-r--r--front/src/pages/User.tsx2
-rw-r--r--front/src/state/state.ts47
-rw-r--r--front/src/styles/feed.css4
-rw-r--r--front/src/types/nostr.ts3
-rw-r--r--front/src/types/nostril.ts6
-rw-r--r--front/src/types/nostrill.ts23
-rw-r--r--front/src/types/trill.ts2
-rw-r--r--front/src/types/ui.ts35
51 files changed, 809 insertions, 595 deletions
diff --git a/.gitignore b/.gitignore
index 681dbf2..13bf55e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
oldfront
+backupdesk
.claude
+
diff --git a/wtfdesk/NOTES.md b/NOTES.md
index e6d4743..79406c2 100644
--- a/wtfdesk/NOTES.md
+++ b/NOTES.md
@@ -31,6 +31,9 @@ https://nostr-nips.com/#standardized-tags
- 'a' for event coordinates
- 'g' for geohash
+# Things to note
+- We put the nostr pubkey at the hash field of the trill-post
+- No signatures
# TODO
diff --git a/desk/app/nostrill.hoon b/desk/app/nostrill.hoon
index 575fa3e..e311b5f 100644
--- a/desk/app/nostrill.hoon
+++ b/desk/app/nostrill.hoon
@@ -83,10 +83,15 @@
++ handle-post |= poke=post-poke:ui:sur
?- -.poke
%add
- =/ sp (build-sp:trill our.bowl our.bowl content.poke)
- =/ p (build-post:trill now.bowl pubkey.poke sp)
+ =/ sp (build-sp:trill our.bowl our.bowl content.poke)
+ =/ p (build-post:trill now.bowl pub.i.keys sp)
=. state (add-to-feed:mutat p)
- `this
+ =/ profile (~(get by profiles) pub.i.keys)
+ =/ pw [p (some pub.i.keys) ~ ~ profile]
+ =/ =fact:ui:sur [%post %add pw]
+ =/ card (update-ui:cards fact)
+ :_ this :~(card)
+
%rt `this
%del `this
==
@@ -140,7 +145,6 @@
`this
%http
- ~& pending=pending
`this
%rt :: relay test
=^ cards state get-posts:shimm
@@ -157,20 +161,18 @@
:: (get-profiles:shimm +.ids)
:: (get-engagement:shimm -.ids)
[cards this]
- %rt1
- =/ l ~(tap by pending)
- =/ l (scag 1 l)
- =| cards=(list card:agent:gall)
- |-
- ?~ l
- ~& cards=(lent cards) [cards this]
- =/ [sub-id=@t pf=filter:nsur done=filter:nsur] i.l
- =/ diff (diff-filters:nlib pf done)
- :: ~& > diff=diff
- ?~ authors.pf $(l t.l)
- =^ cs state (populate-profiles:mutat u.authors.pf)
+ :: %rt1
+ :: =| cards=(list card:agent:gall)
+ :: |-
+ :: ?~ l
+ :: ~& cards=(lent cards) [cards this]
+ :: =/ [sub-id=@t pf=filter:nsur done=filter:nsur] i.l
+ :: =/ diff (diff-filters:nlib pf done)
+ :: :: ~& > diff=diff
+ :: ?~ authors.pf $(l t.l)
+ :: =^ cs state (populate-profiles:mutat u.authors.pf)
- $(l t.l, cards (weld cards cs))
+ :: $(l t.l, cards (weld cards cs))
%rt2
=/ poasts (tap:norm:sur nostr-feed)
@@ -198,6 +200,10 @@
::
=^ cards state (populate-profiles:mutat pks)
[cards this]
+ %ui
+ =/ =fact:ui:sur [%post %add *post-wrapper:sur]
+ =/ card (update-ui:cards fact)
+ :_ this :~(card)
==
::
diff --git a/desk/lib/constants.hoon b/desk/lib/constants.hoon
index c7f72b7..9beab32 100644
--- a/desk/lib/constants.hoon
+++ b/desk/lib/constants.hoon
@@ -1,3 +1,4 @@
|%
+++ feed-page-size 100
++ http-delay 3.000
--
diff --git a/desk/lib/json/nostr.hoon b/desk/lib/json/nostr.hoon
index 9c36eb0..6f93c1c 100644
--- a/desk/lib/json/nostr.hoon
+++ b/desk/lib/json/nostr.hoon
@@ -38,9 +38,11 @@
::
++ raw-event |= raw-event:sur
:: WTF nostr doesn't want the prefix on the pubkey
- =/ pubkeyt (scow:sr %ux pubkey)
+ =/ scw scow:sr
+ =/ pubkeyt (scw(min-chars 64) %ux pubkey)
?~ pubkeyt !!
- =/ pubkeyj [%s (crip t.pubkeyt)]
+ :: =/ pubkeyj [%s (crip t.pubkeyt)]
+ =/ pubkeyj [%s (crip pubkeyt)]
:- %a :~
[%n '0']
pubkeyj
@@ -51,13 +53,13 @@
==
++ event
|= e=event:sur ^- json
- =/ pubkeyt (scow:sr %ux pubkey.e)
- ?~ pubkeyt !!
- =/ pubkeyj [%s (crip t.pubkeyt)]
+ :: =/ pubkeyt (scow:sr %ux pubkey.e)
+ :: ?~ pubkeyt !!
+ :: =/ pubkeyj [%s (crip t.pubkeyt)]
%: pairs
id+(hex:en:common id.e)
- :: pubkey+(hex:en:common pubkey.e)
- pubkey+pubkeyj
+ pubkey+(hex:en:common pubkey.e)
+ :: pubkey+pubkeyj
sig+(hex:en:common sig.e)
['created_at' (numb created-at.e)]
kind+(numb kind.e)
diff --git a/desk/lib/json/nostrill.hoon b/desk/lib/json/nostrill.hoon
index 43f7708..bd34acc 100644
--- a/desk/lib/json/nostrill.hoon
+++ b/desk/lib/json/nostrill.hoon
@@ -6,6 +6,7 @@
|%
:: UI comms
++ state |= state-0:sur ^- json
+ %+ frond %state
%: pairs
relays+(en-relays relays)
key+(hex:en:common pub.i.keys)
@@ -38,27 +39,60 @@
++ en-profiles |= m=(map @ux user-meta:nsur)
%- pairs
%+ turn ~(tap by m) |= [key=@ux p=user-meta:nsur]
- :- (crip (scow:sr %ux key)) (user-meta:en:nostr p)
+ =/ jkey (hex:en:common key)
+ ?> ?=(%s -.jkey)
+ :- +.jkey (user-meta:en:nostr p)
++ enfollowing
|= m=(map @ux feed:feed)
^- json
%- pairs %+ turn ~(tap by m) |= [key=@ux f=feed:feed]
- :- (crip (scow:sr %ux key)) (feed:en:trill f)
+ =/ jkey (hex:en:common key)
+ ?> ?=(%s -.jkey)
+ :- +.jkey (feed:en:trill f)
++ engraph
|= m=(map @ux (set follow:sur))
^- json
%- pairs %+ turn ~(tap by m) |= [key=@ux s=(set follow:sur)]
- :- (crip (scow:sr %ux key))
- :- %a %+ turn ~(tap in s) |= f=follow:sur
- %- pairs
- :~ pubkey+(hex:en:common pubkey.f)
- name+s+name.f
- :- %relay ?~ relay.f ~ s+u.relay.f
- ==
+ =/ jkey (hex:en:common key)
+ ?> ?=(%s -.jkey)
+ :- +.jkey
+ :- %a %+ turn ~(tap in s) |= f=follow:sur
+ %- pairs
+ :~ pubkey+(hex:en:common pubkey.f)
+ name+s+name.f
+ :- %relay ?~ relay.f ~ s+u.relay.f
+ ==
+
+ :: ui facts
+ ++ fact |= f=fact:ui:sur ^- json
+ %+ frond %fact
+ %+ frond -.f
+ ?- -.f
+ %post (postfact +.f)
+ %enga (enga +.f)
+ ==
+ ++ postfact |= pf=post-fact:ui:sur ^- json
+ %+ frond -.pf
+ (post-wrapper +.pf)
+ ++ enga |= [pw=post-wrapper:sur reaction=*]
+ ^- json
+ ~
+ ++ post-wrapper |= p=post-wrapper:sur
+ %- pairs
+ :~ post+(poast:en:trill post.p)
+ ['nostrMeta' (nostr-meta nostr-meta.p)]
+ ==
+ ++ nostr-meta |= p=nostr-meta:sur
+ =| l=(list [@t json])
+ =. l ?~ pub.p l :_ l ['pubkey' (hex:en:common u.pub.p)]
+ =. l ?~ ev-id.p l :_ l ['eventId' (hex:en:common u.ev-id.p)]
+ =. l ?~ relay.p l :_ l ['relay' %s u.relay.p]
+ =. l ?~ pr.p l :_ l ['profile' (user-meta:en:nostr u.pr.p)]
+ %- pairs l
--
++ de
=, dejs-soft:format
@@ -94,7 +128,6 @@
==
++ de-post
%- ot :~
- pubkey+hex:de:common
content+so
==
++ de-rt
diff --git a/desk/lib/nostrill.hoon b/desk/lib/nostrill.hoon
index c7e940c..0570dbc 100644
--- a/desk/lib/nostrill.hoon
+++ b/desk/lib/nostrill.hoon
@@ -1,5 +1,5 @@
-/- post=trill-post, nsur=nostr, sur=nostrill
-/+ trill=trill-post, nostr, sr=sortug
+/- post=trill-post, nsur=nostr, sur=nostrill, gate=trill-gate
+/+ trill=trill-post, nostr, sr=sortug, jsonlib=json-nostrill
|%
::
++ default-state |= =bowl:gall ^- state:sur
@@ -47,9 +47,40 @@
signature
==
event
+
+++ event-to-post
+ |= [=event:nsur profile=(unit user-meta:nsur) relay=(unit @t)]
+ ^- post-wrapper:sur
+
+ =/ cl (tokenize:trill content.event)
+ =/ ts (from-unix:jikan:sr created-at.event)
+ =/ cm=content-map:post (init-content-map:trill cl ts)
+
+ :: TODO more about @ps and stuff
+ =/ p=post:post :*
+ id=ts
+ host=`@p`pubkey.event
+ author=`@p`pubkey.event
+ thread=ts
+ parent=~
+ children=~
+ contents=cm
+ read=*lock:gate
+ write=*lock:gate
+ *engagement:post
+ 0v0
+ *signature:post
+ tags=~
+ ==
+ =/ meta [(some pubkey.event) (some id.event) relay profile]
+ [p meta]
+
++ cards
|_ =bowl:gall
++ shim-binding ^- card:agent:gall
[%pass /binding %arvo %e %connect [~ /nostr-shim] dap.bowl]
+ ++ update-ui |= =fact:ui:sur ^- card:agent:gall
+ =/ jon (fact:en:jsonlib fact)
+ [%give %fact ~[/ui] %json !>(jon)]
--
--
diff --git a/desk/lib/shim.hoon b/desk/lib/shim.hoon
index 4afdf2b..f2e0b8a 100644
--- a/desk/lib/shim.hoon
+++ b/desk/lib/shim.hoon
@@ -105,9 +105,6 @@
=/ sub-id (gen-sub-id:nlib eny.bowl)
=/ kinds (silt ~[0])
=/ total=filter:nsur [~ `pubkeys `kinds ~ ~ ~ ~]
- =/ chunk (silt (scag 10 ~(tap in pubkeys)))
- =/ =filter:nsur [~ `chunk `kinds ~ ~ ~ ~]
- =. pending.state (~(put by pending.state) sub-id [total *filter:nsur])
=/ req=http-req:shim:nsur [relay http-delay:constants sub-id ~[total]]
=/ =card (send-http req)
:- :~(card) state
diff --git a/desk/lib/trill/feed.hoon b/desk/lib/trill/feed.hoon
new file mode 100644
index 0000000..c21feb3
--- /dev/null
+++ b/desk/lib/trill/feed.hoon
@@ -0,0 +1,51 @@
+/- feed=trill-feed, sur=nostrill
+/+ sr=sortug, constants
+|%
+++ latest-page |= f=feed:feed ^- fc:feed
+ =/ nodelist (tap:orm:feed f)
+ =/ subset (scag feed-page-size:constants nodelist)
+ ?~ subset [f ~ ~]
+ =/ start `id.i.subset
+ =/ rev (flop subset)
+ ?~ rev [f ~ ~]
+ =/ end `id.i.rev
+ =/ nf (gas:orm:feed *feed:feed subset)
+ [nf start end]
+::
+++ latest-page-nostr |= f=nostr-feed:sur ^- nfc:sur
+ =/ nodelist (tap:norm:sur f)
+ =/ subset (scag feed-page-size:constants nodelist)
+ ?~ subset [f ~ ~]
+ =/ start `id.i.subset
+ =/ rev (flop subset)
+ ?~ rev [f ~ ~]
+ =/ end `id.i.rev
+ =/ nf (gas:norm:sur *nostr-feed:sur subset)
+ [nf start end]
+::
+:: NOTE START IS OLD, END IS NEW
+
+++ subset
+|= [=fc:feed replies=? now=@da] ^- fc:feed
+ ?: ?&(?=(%~ start.fc) ?=(%~ end.fc)) (latest-page feed.fc)
+
+ =/ count feed-page-size:constants
+ =/ start ?~ start.fc 0 u.start.fc
+ =/ end ?~ end.fc now u.end.fc
+ =/ nodelist (tap:orm:feed feed.fc)
+
+ =/ threads %+ skim nodelist
+ |= [=id:post =post:post] ^- ?
+ ?. replies
+ ?&
+ ?= %~ parent.post
+ (lte id start) (gte id end)
+ ==
+ ?& (lte id start) (gte id end) ==
+ =/ thread-count (lent threads)
+ =/ result=(list [id:post post:post]) ?: newest (scag count threads) (flop (scag count (flop threads)))
+ =/ cursors=[(unit @da) (unit @da)] ?~ result [~ ~] ?~ threads [~ ~] :-
+ ?: .=((head result) (head threads)) ~ `id:(head result)
+ ?: .=((rear result) (rear threads)) ~ `id:(rear result)
+ [(gas:orm:feed *feed:feed result) -.cursors +.cursors]
+--
diff --git a/desk/sur/nostrill.hoon b/desk/sur/nostrill.hoon
index ad82661..a9ef8f3 100644
--- a/desk/sur/nostrill.hoon
+++ b/desk/sur/nostrill.hoon
@@ -1,4 +1,4 @@
-/- trill=trill-feed, nostr
+/- trill=trill-feed, tp=trill-post, nostr
|%
+$ state state-0
+$ state-0
@@ -14,13 +14,21 @@
profiles=(map @ux user-meta:nostr)
following=(map @ux =feed:trill)
follow-graph=(map @ux (set follow))
- :: for http requests
- pending=(map @t [pending=filter:nostr done=filter:nostr])
:: TODO global feed somehow?
==
+$ nostr-feed ((mop @ud event:nostr) gth)
++ norm ((on @ud event:nostr) gth)
++$ nfc [feed=nostr-feed start=cursor:trill end=cursor:trill]
+
++$ post-wrapper [=post:tp nostr-meta=nostr-meta]
++$ nostr-meta
+$: pub=(unit @ux)
+ ev-id=(unit @ux)
+ relay=(unit @t)
+ pr=(unit user-meta:nostr)
+==
+
+$ follow [pubkey=@ux name=@t relay=(unit @t)]
++ ui
|%
@@ -33,7 +41,7 @@
[%rela relay-poke]
==
+$ post-poke
- $% [%add pubkey=@ux content=@t]
+ $% [%add content=@t]
[%rt id=@ux pubkey=@ux relay=@t] :: NIP-18
[%del pubkey=@ux]
==
@@ -48,5 +56,14 @@
+$ relay-poke
$% [%send host=@p id=@ relays=(list @t)]
==
+ :: facts
+ +$ fact
+ $% [%post post-fact]
+ [%enga p=post-wrapper reaction=*]
+ ==
+ +$ post-fact
+ $% [%add post-wrapper]
+ [%del post-wrapper]
+ ==
--
--
diff --git a/front/.envrc b/front/.envrc
new file mode 100644
index 0000000..7e9a2d6
--- /dev/null
+++ b/front/.envrc
@@ -0,0 +1,10 @@
+export DIRENV_WARN_TIMEOUT=20s
+
+eval "$(devenv direnvrc)"
+
+# `use devenv` supports the same options as the `devenv shell` command.
+#
+# To silence the output, use `--quiet`.
+#
+# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true
+use devenv
diff --git a/front/.gitignore b/front/.gitignore
index 247c9a3..356ff08 100644
--- a/front/.gitignore
+++ b/front/.gitignore
@@ -32,3 +32,13 @@ devenv.local.nix
# pre-commit
.pre-commit-config.yaml
+
+# Devenv
+.devenv*
+devenv.local.nix
+
+# direnv
+.direnv
+
+# pre-commit
+.pre-commit-config.yaml
diff --git a/front/devenv.lock b/front/devenv.lock
new file mode 100644
index 0000000..19bac94
--- /dev/null
+++ b/front/devenv.lock
@@ -0,0 +1,103 @@
+{
+ "nodes": {
+ "devenv": {
+ "locked": {
+ "dir": "src/modules",
+ "lastModified": 1758064567,
+ "owner": "cachix",
+ "repo": "devenv",
+ "rev": "bc697443a9653586e5be5150b4458f3096a93f67",
+ "type": "github"
+ },
+ "original": {
+ "dir": "src/modules",
+ "owner": "cachix",
+ "repo": "devenv",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1747046372,
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "git-hooks": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1757974173,
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "rev": "302af509428169db34f268324162712d10559f74",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "git-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1755783167,
+ "owner": "cachix",
+ "repo": "devenv-nixpkgs",
+ "rev": "4a880fb247d24fbca57269af672e8f78935b0328",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "rolling",
+ "repo": "devenv-nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "devenv": "devenv",
+ "git-hooks": "git-hooks",
+ "nixpkgs": "nixpkgs",
+ "pre-commit-hooks": [
+ "git-hooks"
+ ]
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/front/devenv.nix b/front/devenv.nix
new file mode 100644
index 0000000..e4e3748
--- /dev/null
+++ b/front/devenv.nix
@@ -0,0 +1,57 @@
+{
+ pkgs,
+ lib,
+ config,
+ inputs,
+ ...
+}: {
+ # https://devenv.sh/basics/
+ env.GREET = "devenv";
+
+ # https://devenv.sh/packages/
+ packages = with pkgs; [
+ git
+ nodePackages.typescript-language-server
+ nodePackages.prettier
+ ];
+
+ # https://devenv.sh/languages/
+ # languages.rust.enable = true;
+ languages.javascript = {
+ enable = true;
+ bun.enable = true;
+ };
+
+ # https://devenv.sh/processes/
+ # processes.cargo-watch.exec = "cargo-watch";
+
+ # https://devenv.sh/services/
+ # services.postgres.enable = true;
+
+ # https://devenv.sh/scripts/
+ scripts.hello.exec = ''
+ echo hello from $GREET
+ '';
+
+ enterShell = ''
+ hello
+ git --version
+ '';
+
+ # https://devenv.sh/tasks/
+ # tasks = {
+ # "myproj:setup".exec = "mytool build";
+ # "devenv:enterShell".after = [ "myproj:setup" ];
+ # };
+
+ # https://devenv.sh/tests/
+ enterTest = ''
+ echo "Running tests"
+ git --version | grep --color=auto "${pkgs.git.version}"
+ '';
+
+ # https://devenv.sh/git-hooks/
+ # git-hooks.hooks.shellcheck.enable = true;
+
+ # See full reference at https://devenv.sh/reference/options/
+}
diff --git a/front/devenv.yaml b/front/devenv.yaml
new file mode 100644
index 0000000..116a2ad
--- /dev/null
+++ b/front/devenv.yaml
@@ -0,0 +1,15 @@
+# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
+inputs:
+ nixpkgs:
+ url: github:cachix/devenv-nixpkgs/rolling
+
+# If you're using non-OSS software, you can set allowUnfree to true.
+# allowUnfree: true
+
+# If you're willing to use a package that's vulnerable
+# permittedInsecurePackages:
+# - "openssl-1.1.1w"
+
+# If you have more than one devenv you can merge them
+#imports:
+# - ./backend
diff --git a/front/src/App.tsx b/front/src/App.tsx
index 60ca66a..f921bbf 100644
--- a/front/src/App.tsx
+++ b/front/src/App.tsx
@@ -13,7 +13,7 @@ const queryClient = new QueryClient();
function App() {
const [loading, setLoading] = useState(true);
- console.log("NOSTRIL INIT");
+ console.log("NOSTRILL INIT");
const { init, modal } = useLocalState();
useEffect(() => {
init().then((_res: any) => {
diff --git a/front/src/Router.tsx b/front/src/Router.tsx
index b7b033e..83d212f 100644
--- a/front/src/Router.tsx
+++ b/front/src/Router.tsx
@@ -8,7 +8,7 @@ import { Switch, Router, Redirect, Route } from "wouter";
export default function r() {
return (
<Switch>
- <Router base="/apps/nostril">
+ <Router base="/apps/nostrill">
<Sidebar />
<main>
<Route path="/" component={toGlobal} />
diff --git a/front/src/components/feed/Composer.tsx b/front/src/components/composer/Composer.tsx
index 27da392..795188e 100644
--- a/front/src/components/feed/Composer.tsx
+++ b/front/src/components/composer/Composer.tsx
@@ -1,9 +1,11 @@
-import { openLock } from "@/logic/bunts";
-import { HASHTAGS_REGEX } from "@/logic/constants";
import useLocalState from "@/state/state";
-import type { Poast, SentPoast } from "@/types/trill";
+import type { Poast } from "@/types/trill";
import Sigil from "@/components/Sigil";
-import { useState } from "react";
+import { useState, type FormEvent } from "react";
+import type { ComposerData } from "@/types/ui";
+import Snippets, { ReplySnippet } from "./Snippets";
+import toast from "react-hot-toast";
+import { useLocation } from "wouter";
function Composer({
isAnon,
@@ -12,10 +14,12 @@ function Composer({
isAnon?: boolean;
replying?: Poast;
}) {
- const { api, keys } = useLocalState();
+ const [loc, navigate] = useLocation();
+ const { api, composerData } = useLocalState();
const our = api!.airlock.our!;
const [input, setInput] = useState(replying ? `${replying}: ` : "");
- async function poast() {
+ async function poast(e: FormEvent<HTMLFormElement>) {
+ e.preventDefault();
// TODO
// const parent = replying ? replying : null;
// const tokens = tokenize(input);
@@ -30,22 +34,33 @@ function Composer({
// tags: input.match(HASHTAGS_REGEX) || [],
// };
// TODO make it user choosable
- const pubkey = keys[0]!;
- await api!.addPost(pubkey, input);
+ const res = await api!.addPost(input);
+ if (res) {
+ setInput("");
+ toast.success("post sent");
+ navigate(`/feed/${our}`);
+ }
}
const placeHolder = isAnon ? "> be me" : "What's going on in Urbit";
return (
- <div id="composer">
+ <form id="composer" onSubmit={poast}>
<div className="sigil">
<Sigil patp={our} size={48} />
</div>
+
+ {composerData && composerData.type === "reply" && (
+ <ReplySnippet post={composerData?.post} />
+ )}
<input
value={input}
onInput={(e) => setInput(e.currentTarget.value)}
placeholder={placeHolder}
/>
- <button onClick={poast}>Post</button>
- </div>
+ {composerData && composerData.type === "quote" && (
+ <Snippets post={composerData?.post} />
+ )}
+ <button type="submit">Post</button>
+ </form>
);
}
diff --git a/front/src/components/composer/Snippets.tsx b/front/src/components/composer/Snippets.tsx
new file mode 100644
index 0000000..30498d0
--- /dev/null
+++ b/front/src/components/composer/Snippets.tsx
@@ -0,0 +1,62 @@
+import Quote from "@/components/post/Quote";
+import type { ComposerData, SPID } from "@/types/ui";
+import { NostrSnippet } from "../post/wrappers/Nostr";
+
+export default Snippets;
+function Snippets({ post }: { post: SPID }) {
+ return (
+ <ComposerSnippet>
+ <PostSnippet post={post} />
+ </ComposerSnippet>
+ );
+}
+
+export function ComposerSnippet({
+ onClick,
+ children,
+}: {
+ onClick?: any;
+ children: any;
+}) {
+ function onc(e: React.MouseEvent) {
+ e.stopPropagation();
+ onClick();
+ }
+ return (
+ <div className="composer-snippet">
+ <div className="pop-snippet-icon cp" role="link" onClick={onc}></div>
+ {children}
+ </div>
+ );
+}
+function PostSnippet({ post }: { post: SPID }) {
+ if ("trill" in post) return <Quote data={post.trill} nest={0} />;
+ else if ("nostr" in post) return <NostrSnippet {...post.nostr} />;
+ // else if ("twatter" in post)
+ // return (
+ // <div id={`composer-${type}`}>
+ // <Tweet tweet={post.post} quote={true} />
+ // </div>
+ // );
+ // else if ("rumors" in post)
+ // return (
+ // <div id={`composer-${type}`}>
+ // <div className="rumor-quote f1">
+ // <img src={rumorIcon} alt="" />
+ // <Body poast={post.post} refetch={() => {}} />
+ // <span>{date_diff(post.post.time, "short")}</span>
+ // </div>
+ // </div>
+ // );
+ else return <></>;
+}
+
+export function ReplySnippet({ post }: { post: SPID }) {
+ if ("trill" in post)
+ return (
+ <div id="reply">
+ <Quote data={post.trill} nest={0} />
+ </div>
+ );
+ else return <div />;
+}
diff --git a/front/src/components/feed/PostList.tsx b/front/src/components/feed/PostList.tsx
index 3d41ff8..b09a0e9 100644
--- a/front/src/components/feed/PostList.tsx
+++ b/front/src/components/feed/PostList.tsx
@@ -1,4 +1,4 @@
-import TrillPost from "./Post";
+import TrillPost from "@/components/post/Post";
import type { FC } from "@/types/trill";
// import { useEffect } from "react";
// import { useQueryClient } from "@tanstack/react-query";
@@ -22,6 +22,7 @@ function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) {
{Object.keys(data.feed)
.sort()
.reverse()
+ .slice(0, 50)
.map((i) => (
<TrillPost key={i} poast={data.feed[i]} refetch={refetch} />
))}
diff --git a/front/src/components/feed/Quote.tsx b/front/src/components/feed/Quote.tsx
deleted file mode 100644
index d71be40..0000000
--- a/front/src/components/feed/Quote.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { FullNode } from "@/types/trill";
-import { date_diff } from "@/logic/utils";
-import { useLocation } from "wouter";
-import Body from "./Body";
-import Sigil from "../Sigil";
-import { toFlat } from "./RP";
-
-function Quote({
- data,
- refetch,
- nest,
-}: {
- data: FullNode;
- refetch?: Function;
- nest: number;
-}) {
- const [_, navigate] = useLocation();
- function gotoQuote(e: React.MouseEvent) {
- e.stopPropagation();
- navigate(`/feed/${data.host}/${data.id}`);
- }
- return (
- <div onMouseUp={gotoQuote} className="quote-in-post">
- <header className="btw">
- (
- <div className="quote-author flex">
- <Sigil patp={data.author} size={20} />
- {data.author}
- </div>
- )<span>{date_diff(data.time, "short")}</span>
- </header>
- <Body poast={toFlat(data)} nest={nest} refetch={refetch!} />
- </div>
- );
-}
-
-export default Quote;
diff --git a/front/src/components/layout/Sidebar.tsx b/front/src/components/layout/Sidebar.tsx
index 1568421..4055454 100644
--- a/front/src/components/layout/Sidebar.tsx
+++ b/front/src/components/layout/Sidebar.tsx
@@ -22,7 +22,7 @@ function SlidingMenu() {
<div id="left-menu">
<div id="logo">
<img src={logo} />
- <h3> Nostril </h3>
+ <h3> Nostrill </h3>
</div>
<h3>Feeds</h3>
<div className="opt" role="link" onClick={() => goto(`/feed/global`)}>
diff --git a/front/src/components/feed/Body.tsx b/front/src/components/post/Body.tsx
index 2f11962..2e4e2f8 100644
--- a/front/src/components/feed/Body.tsx
+++ b/front/src/components/post/Body.tsx
@@ -12,7 +12,7 @@ import Media from "./Media";
import JSONContent, { YoutubeSnippet } from "./External";
import { useLocation } from "wouter";
import Quote from "./Quote";
-import PostData from "./PostData";
+import PostData from "./Loader";
import Card from "./Card.tsx";
import type { Ship } from "@/types/urbit.ts";
diff --git a/front/src/components/feed/Card.tsx b/front/src/components/post/Card.tsx
index 37f4911..37f4911 100644
--- a/front/src/components/feed/Card.tsx
+++ b/front/src/components/post/Card.tsx
diff --git a/front/src/components/feed/External.tsx b/front/src/components/post/External.tsx
index 0ea1500..0ea1500 100644
--- a/front/src/components/feed/External.tsx
+++ b/front/src/components/post/External.tsx
diff --git a/front/src/components/feed/Footer.tsx b/front/src/components/post/Footer.tsx
index 938a8c7..3b48241 100644
--- a/front/src/components/feed/Footer.tsx
+++ b/front/src/components/post/Footer.tsx
@@ -8,7 +8,9 @@ import { useLocation } from "wouter";
import { displayCount } from "@/logic/utils";
import { TrillReactModal, stringToReact } from "./Reactions";
import toast from "react-hot-toast";
-import NostrIcon from "./NostrIcon";
+import NostrIcon from "./wrappers/NostrIcon";
+// TODO abstract this somehow
+
function Footer({ poast, refetch }: PostProps) {
const [_showMenu, setShowMenu] = useState(false);
const [location, navigate] = useLocation();
@@ -17,14 +19,13 @@ function Footer({ poast, refetch }: PostProps) {
const our = api!.airlock.our!;
function doReply(e: React.MouseEvent) {
e.stopPropagation();
- setComposerData({ type: "reply", post: { service: "trill", post: poast } });
- navigate("/composer");
+ setComposerData({ type: "reply", post: { trill: poast } });
}
function doQuote(e: React.MouseEvent) {
e.stopPropagation();
setComposerData({
type: "quote",
- post: { service: "trill", post: poast },
+ post: { trill: poast },
});
navigate("/composer");
}
diff --git a/front/src/components/feed/Header.tsx b/front/src/components/post/Header.tsx
index 7658bfb..e541fa5 100644
--- a/front/src/components/feed/Header.tsx
+++ b/front/src/components/post/Header.tsx
@@ -1,8 +1,13 @@
import { date_diff } from "@/logic/utils";
import type { PostProps } from "./Post";
import { useLocation } from "wouter";
+import useLocalState from "@/state/state";
function Header(props: PostProps) {
const [_, navigate] = useLocation();
+ const { profiles } = useLocalState();
+ const profile = profiles.get(props.poast.author);
+ // console.log("profile", profile);
+ // console.log(props.poast.author.length, "length");
function go(e: React.MouseEvent) {
e.stopPropagation();
}
@@ -12,7 +17,9 @@ function Header(props: PostProps) {
if (!sel) navigate(`/feed/${poast.host}/${poast.id}`);
}
const { poast } = props;
- const name = (
+ const name = profile ? (
+ profile.name
+ ) : (
<div className="name cp">
<p className="p-only">{poast.author}</p>
</div>
diff --git a/front/src/components/feed/PostData.tsx b/front/src/components/post/Loader.tsx
index f3c4715..f3c4715 100644
--- a/front/src/components/feed/PostData.tsx
+++ b/front/src/components/post/Loader.tsx
diff --git a/front/src/components/feed/Media.tsx b/front/src/components/post/Media.tsx
index 04ea156..04ea156 100644
--- a/front/src/components/feed/Media.tsx
+++ b/front/src/components/post/Media.tsx
diff --git a/front/src/components/feed/Post.tsx b/front/src/components/post/Post.tsx
index 1211a97..e61efb0 100644
--- a/front/src/components/feed/Post.tsx
+++ b/front/src/components/post/Post.tsx
@@ -9,6 +9,7 @@ import RP from "./RP";
import ShipModal from "../modals/ShipModal";
import type { Ship } from "@/types/urbit";
import Sigil from "../Sigil";
+import type { UserProfile } from "@/types/nostrill";
export interface PostProps {
poast: Poast;
@@ -17,11 +18,11 @@ export interface PostProps {
rtat?: number;
rtid?: PostID;
nest?: number;
- refetch: Function;
+ refetch?: Function;
+ profile?: UserProfile;
}
function Post(props: PostProps) {
const { poast } = props;
- console.log({ poast });
if (!poast || poast.contents === null) {
return null;
}
@@ -45,7 +46,7 @@ function Post(props: PostProps) {
export default Post;
function TrillPost(props: PostProps) {
- const { poast, fake } = props;
+ const { poast, profile, fake } = props;
const { setModal } = useLocalState();
const [_, navigate] = useLocation();
function openThread(_e: React.MouseEvent) {
@@ -57,8 +58,12 @@ function TrillPost(props: PostProps) {
e.stopPropagation();
setModal(<ShipModal ship={poast.author} />);
}
- const avatar = (
- <div className="avatar-w sigil cp" role="link" onMouseUp={openModal}>
+ const avatar = profile ? (
+ <div className="avatar cp" role="link" onMouseUp={openModal}>
+ <img src={profile.picture} />
+ </div>
+ ) : (
+ <div className="avatar sigil cp" role="link" onMouseUp={openModal}>
<Sigil patp={poast.author} size={42} />
</div>
);
diff --git a/front/src/components/post/PostWrapper.tsx b/front/src/components/post/PostWrapper.tsx
new file mode 100644
index 0000000..c4e754f
--- /dev/null
+++ b/front/src/components/post/PostWrapper.tsx
@@ -0,0 +1,14 @@
+import useLocalState from "@/state/state";
+import type { NostrPost, PostWrapper } from "@/types/nostrill";
+
+export default Post;
+function Post(pw: PostWrapper) {
+ if ("nostr" in pw) return <NostrPost post={pw.nostr} />;
+ else return <TrillPost post={pw.urbit.post} nostr={pw.urbit.nostr} />;
+}
+
+function NostrPost({ post, event, relay }: NostrPost) {
+ const { profiles } = useLocalState();
+ const profile = profiles.get(event.pubkey);
+ return <></>;
+}
diff --git a/front/src/components/post/Quote.tsx b/front/src/components/post/Quote.tsx
new file mode 100644
index 0000000..28149f0
--- /dev/null
+++ b/front/src/components/post/Quote.tsx
@@ -0,0 +1,64 @@
+import type { FullNode, Poast } from "@/types/trill";
+import { date_diff } from "@/logic/utils";
+import { useLocation } from "wouter";
+import Body from "./Body";
+import Sigil from "../Sigil";
+
+// function Quote({
+// data,
+// refetch,
+// nest,
+// }: {
+// data: FullNode;
+// refetch?: Function;
+// nest: number;
+// }) {
+// const [_, navigate] = useLocation();
+// function gotoQuote(e: React.MouseEvent) {
+// e.stopPropagation();
+// navigate(`/feed/${data.host}/${data.id}`);
+// }
+// return (
+// <div onMouseUp={gotoQuote} className="quote-in-post">
+// <header className="btw">
+// (
+// <div className="quote-author flex">
+// <Sigil patp={data.author} size={20} />
+// {data.author}
+// </div>
+// )<span>{date_diff(data.time, "short")}</span>
+// </header>
+// <Body poast={toFlat(data)} nest={nest} refetch={refetch!} />
+// </div>
+// );
+// }
+function Quote({
+ data,
+ refetch,
+ nest,
+}: {
+ data: Poast;
+ refetch?: Function;
+ nest: number;
+}) {
+ const [_, navigate] = useLocation();
+ function gotoQuote(e: React.MouseEvent) {
+ e.stopPropagation();
+ navigate(`/feed/${data.host}/${data.id}`);
+ }
+ return (
+ <div onMouseUp={gotoQuote} className="quote-in-post">
+ <header className="btw">
+ (
+ <div className="quote-author flex">
+ <Sigil patp={data.author} size={20} />
+ {data.author}
+ </div>
+ )<span>{date_diff(data.time, "short")}</span>
+ </header>
+ <Body poast={data} nest={nest} refetch={refetch!} />
+ </div>
+ );
+}
+
+export default Quote;
diff --git a/front/src/components/feed/RP.tsx b/front/src/components/post/RP.tsx
index dc733cc..27fa02d 100644
--- a/front/src/components/feed/RP.tsx
+++ b/front/src/components/post/RP.tsx
@@ -1,7 +1,7 @@
import Post from "./Post";
import type { Ship } from "@/types/urbit";
import type { Poast, FullNode, ID } from "@/types/trill";
-import PostData from "./PostData";
+import PostData from "./Loader";
export default function (props: {
host: string;
id: string;
diff --git a/front/src/components/feed/Reactions.tsx b/front/src/components/post/Reactions.tsx
index 58662cd..58662cd 100644
--- a/front/src/components/feed/Reactions.tsx
+++ b/front/src/components/post/Reactions.tsx
diff --git a/front/src/components/feed/StatsModal.tsx b/front/src/components/post/StatsModal.tsx
index 4720b2a..4720b2a 100644
--- a/front/src/components/feed/StatsModal.tsx
+++ b/front/src/components/post/StatsModal.tsx
diff --git a/front/src/components/post/wrappers/Nostr.tsx b/front/src/components/post/wrappers/Nostr.tsx
new file mode 100644
index 0000000..bdc5ba9
--- /dev/null
+++ b/front/src/components/post/wrappers/Nostr.tsx
@@ -0,0 +1,15 @@
+import type { NostrMetadata, NostrPost } from "@/types/nostrill";
+import Post from "../Post";
+import useLocalState from "@/state/state";
+
+export default NostrPost;
+function NostrPost({ data }: { data: NostrPost }) {
+ const { profiles } = useLocalState();
+ const profile = profiles.get(data.event.pubkey);
+
+ return <Post poast={data.post} profile={profile} />;
+}
+
+export function NostrSnippet({ eventId, pubkey, relay }: NostrMetadata) {
+ return <div>wtf</div>;
+}
diff --git a/front/src/components/feed/NostrIcon.tsx b/front/src/components/post/wrappers/NostrIcon.tsx
index 0c368fb..0c368fb 100644
--- a/front/src/components/feed/NostrIcon.tsx
+++ b/front/src/components/post/wrappers/NostrIcon.tsx
diff --git a/front/src/components/snippets/Snippets.tsx b/front/src/components/snippets/Snippets.tsx
deleted file mode 100644
index 68f5446..0000000
--- a/front/src/components/snippets/Snippets.tsx
+++ /dev/null
@@ -1,395 +0,0 @@
-import { fetchTweet, lurkTweet } from "@/logic/twatter/calls";
-import { pokeDister, scryDister, scryGangs } from "@/logic/requests/tlon";
-import { useEffect, useState } from "react";
-import Tweet from "@/sections/twatter/Tweet";
-import { toFlat } from "@/sections/feed/thread/helpers";
-import PostData from "@/sections/feed/PostData";
-import Post from "@/sections/feed/post/Post";
-import { FullNode, SortugRef } from "@/types/trill";
-import { useQuery, useQueryClient } from "@tanstack/react-query";
-import { subscribe, unsub } from "@/logic/requests/generic";
-import { AppData, GroupMetadata } from "@/types/tlon";
-import comet from "@/assets/icons/comet.svg";
-import Sigil from "@/ui/Sigil";
-import { PollLoader } from "@/sections/feed/poll/Show";
-import { parseThread, parseTweet } from "@/logic/twatter/parser";
-import { Tweet as TweetType } from "@/types/twatter";
-import { scryRadio } from "@/logic/requests/nostril";
-import useLocalState from "@/state/state";
-import { RadioTower, ScheduledRadio, radioLink } from "@/logic/requests/radio";
-import { Ship } from "@/types/urbit";
-import { RADIO } from "@/logic/constants";
-import { SigilOnly } from "../Avatar";
-import { date_diff } from "@/logic/utils";
-import ShipsModal from "../modals/ShipsModal";
-
-export function TrillSnippet({ r }: { r: SortugRef }) {
- const { ship, path } = r;
- return PostData({ host: ship, id: path.slice(1) })(TrillSnippetMarkup);
-}
-function TrillSnippetMarkup({
- data,
- refetch,
-}: {
- data: FullNode;
- refetch: Function;
-}) {
- return (
- <div className="trill-snippet">
- <Post poast={toFlat(data)} refetch={refetch} />
- </div>
- );
-}
-// <div
-// onClick={() => {
-// if (pop) pop(link);
-// }}
-// className="chat-snippet trill-snippet"
-// >
-// Post not found
-// </div>
-// );
-
-export function TweetSnippet({
- link,
- giveBack,
-}: {
- link: string;
- giveBack?: Function;
-}) {
- const id = link.split("/")[5];
- const { isLoading, isError, data } = useQuery({
- queryKey: ["twatter-thread", id],
- queryFn: () => lurkTweet(id),
- });
- const [tw, setTw] = useState<TweetType>();
- useEffect(() => {
- if (data && "thread-lurk" in data) {
- const js = JSON.parse(data["thread-lurk"]).data.tweetResult;
- if (JSON.stringify(js) === "{}") return;
- if (giveBack) giveBack(JSON.stringify(parseTweet(js.result)));
- }
- }, [data]);
- if (isLoading || isError)
- return (
- <div className="tweet-snippet">
- <p>Fetching Tweet from your Urbit...</p>
- </div>
- );
- else {
- if ("no-coki" in data)
- return (
- <div id="cookie-error" className="x-center">
- <p className="">Your Twitter cookie isn't working correctly.</p>
- <a href="/cookies">Check it out</a>
- </div>
- );
- if ("fail" in data)
- return (
- <p>
- Bad request. Please send some feedback (here) of what you were trying
- to fetch.
- </p>
- );
- if ("thread-lurk" in data) {
- const js = JSON.parse(data["thread-lurk"]).data.tweetResult;
- if (JSON.stringify(js) === "{}")
- return null; // TODO wtf
- else
- return (
- <div className="tweet-snippet">
- <Tweet tweet={parseTweet(js.result)} quote={true} />
- </div>
- );
- }
- // else {
- // const head = parseThread(JSON.parse(data.thread));
- // const tweet = head.thread.tweets[0]
- // giveBack(JSON.stringify(tweet))
- // return (
- // <div className="tweet-snippet">
- // <Tweet tweet={tweet} quote={true} />
- // </div>
- // );
- // }
- }
-}
-
-export function AppSnippet({ r }: { r: SortugRef }) {
- async function sub() {
- if (!subn) {
- const s = await subscribe(
- "treaty",
- "/treaties",
- (data: { add: AppData }) => {
- if ("ini" in data) {
- const app = Object.values(data.ini).find((d) => d.desk === name);
- setApp(app);
- }
- if ("add" in data && data.add.desk === name) setApp(data.add);
- if (appData) unsub(subn);
- },
- );
- setSub(s);
- const res = await pokeDister(ship);
- }
- }
- const { ship, path } = r;
- const name = path.slice(1);
- const [appData, setApp] = useState<AppData>();
- const [subn, setSub] = useState<number>();
- const { isLoading, data, isError } = useQuery({
- queryKey: ["dister", ship],
- queryFn: () => scryDister(ship),
- });
- if (isLoading || isError) return <div className="reference">...</div>;
- else {
- const app = Object.values(data.ini).find((d) => d.desk === name);
- if (!app && !appData) sub();
- const a = app
- ? app
- : appData
- ? appData
- : { title: name, image: comet, info: "", ship };
- return (
- <div className="reference app-ref">
- <AppDiv app={a} />
- </div>
- );
- }
-}
-function AppDiv({ app }: { app: Partial<AppData> }) {
- return (
- <>
- <img src={app.image} alt="" />
- <div className="text">
- <p className="app-name">{app.title}</p>
- <p className="app-info">{app.info}</p>
- <p className="app-host">App from {app.ship}</p>
- </div>
- <p className="ref-ship">
- <Sigil patp={app.ship} size={40} />
- </p>
- </>
- );
-}
-
-export function TlonSnippet({ r }: { r: SortugRef }) {
- if (r.type === "app") return <AppSnippet r={r} />;
- if (r.type === "groups") return <GroupSnippet r={r} />;
-}
-export function GroupSnippet({ r }: { r: SortugRef }) {
- const queryClient = useQueryClient();
- async function sub() {
- if (!subn) {
- const path = `/gangs/index/${ship}`;
- const s = await subscribe("groups", path, (data: any) => {
- const key = `${ship}/${name}`;
- const val = data[key];
- queryClient.setQueryData(["gangs"], (old: any) => {
- return { ...old, [key]: { preview: val } };
- });
- });
- setSub(s);
- }
- }
- const { ship, path } = r;
- const name = path.slice(1);
- const [groupData, setGroup] = useState<GroupMetadata>();
- const [subn, setSub] = useState<number>();
- const { isLoading, data, isError } = useQuery({
- queryKey: ["gangs"],
- queryFn: scryGangs,
- });
- if (isLoading || isError) return <div className="reference">...</div>;
- else {
- const group = data[`${ship}/${name}`];
- if (!group && !groupData) sub();
- const a =
- group && group.preview
- ? group.preview.meta
- : groupData
- ? groupData
- : { title: name, image: comet, cover: "", description: "" };
- return (
- <div className="reference app-ref">
- {a.image.startsWith("#") ? (
- <div
- className="group-color"
- style={{ backgroundColor: a.image }}
- ></div>
- ) : (
- <img src={a.image} alt="" />
- )}
- <div className="text">
- <p className="app-name">{a.title}</p>
- <p className="app-info">
- {a.description.length > 25
- ? a.description.substring(0, 25) + "..."
- : a.description}
- </p>
- <p className="group-host">Group by {ship}</p>
- </div>
- {/* <p className="ref-ship">
- <Sigil patp={ship} size={40} />
- </p> */}
- </div>
- );
- }
-}
-
-export function PollSnippet({ r }: { r: SortugRef }) {
- return (
- <div className="poll-snippet">
- <PollLoader ship={r.ship} id={r.path.slice(1)} />
- </div>
- );
-}
-
-export function SnippetHandler(props: { r: SortugRef }) {
- if (props.r.type === "trill") return <TrillSnippet r={props.r} />;
- if (props.r.type === "trill-polls") return <PollSnippet r={props.r} />;
- if (props.r.type === "app") return <AppSnippet r={props.r} />;
- if (props.r.type === "groups") return <GroupSnippet r={props.r} />;
-}
-
-export function RadioSnippet({ ship }: { ship: Ship }) {
- const { our } = useLocalState();
- return ship === our ? <OwnRadio /> : <DudesRadio ship={ship} />;
-}
-
-function DudesRadio({ ship }: { ship }) {
- function onc() {
- radioLink(ship);
- }
- const { radioTowers } = useLocalState();
- const tower = radioTowers.find((t) => t.location === ship);
- if (!tower)
- return (
- <div role="link" onMouseUp={onc} className="radio-snippet">
- <p className="img">{RADIO}</p>
- <div className="radio-text">
- <p>Radio data not published. Click and check.</p>;
- </div>
- </div>
- );
- else
- return (
- <div role="link" onMouseUp={onc} className="radio-snippet">
- <p className="img">{RADIO}</p>
- <div className="radio-text">
- <p>Radio Session. Playing: {tower.description}</p>
- <p>Started {new Date(tower.time).toLocaleString()}</p>
- </div>
- <div>
- <SigilOnly p={ship} size={42} />
- <span className="viewers">
- {tower.viewers}
- <span>👀</span>
- </span>
- </div>
- </div>
- );
-}
-
-function OwnRadio() {
- const { currentRadio, our, setModal, radioTowers } = useLocalState();
- const [scheduled, setS] = useState<ScheduledRadio | null>(null);
- function onc() {
- radioLink(our);
- }
- useEffect(() => {
- scryRadio().then((r) => {
- if (r) setS(r.radio);
- });
- }, []);
- function showViewers() {
- const modal = (
- <ShipsModal
- ships={currentRadio.viewers}
- header={`People watching your %radio show`}
- />
- );
- setModal(modal);
- }
- if (scheduled && scheduled.time > Date.now())
- return (
- <div role="link" onMouseUp={onc} className="radio-snippet">
- <p className="img">{RADIO}</p>
- <div className="radio-text">
- <p>
- Radio Session. Playing:
- <a className="radio-link" href={scheduled.url}>
- {scheduled.desc}
- </a>
- </p>
- <p>Starting at {new Date(scheduled.time).toLocaleString()}</p>
- </div>
- <div>
- <SigilOnly p={our} size={42} />
- </div>
- </div>
- );
- else if (!currentRadio)
- return (
- <div role="link" onMouseUp={onc} className="radio-snippet">
- <p className="img">{RADIO}</p>
- <div className="radio-text">
- <p>Radio unavailable</p>
- </div>
- </div>
- );
- else
- return (
- <div role="link" onMouseUp={onc} className="radio-snippet">
- <p className="img">{RADIO}</p>
- <div className="radio-text">
- <p>
- Radio Session. Playing:
- <a className="radio-link" href={currentRadio.stream}>
- {currentRadio.description}
- </a>
- </p>
- {/* <p>Started {date_diff(currentRadio.time, "long")}</p> */}
- </div>
- <div>
- <SigilOnly p={our} size={42} />
- <span onClick={showViewers} className="viewers">
- {currentRadio?.viewers?.length || ""}
- <span>👀</span>
- </span>
- </div>
- </div>
- );
-
- // return (
- // {scheduled > Date.now()
- // ? (<>
- // <p>
- // Radio Session. Playing:
- // <a className="radio-link" target="_blank" href={currentRadio.stream}>
- // {currentRadio.description}
- // </a>
- // </p>
-
- // <p>Starting at {new Date(scheduled).toLocaleString()}</p>
- // </>
-
- // ): scheduled !== 0()
-
- // }
- // <p>
- // Radio Session. Playing:
- // <a className="radio-link" target="_blank" href={currentRadio.stream}>
- // {currentRadio.description}
- // </a>
- // </p>
- // {scheduled && scheduled > Date.now() ? (
- // <p>Starting at {new Date(scheduled).toLocaleString()}</p>
- // ) : scheduled !== 0 ? (
- // <p>Started {date_diff(new Date(scheduled), "long")}. Click to join.</p>
- // ) : (
- // <p>Unscheduled session. Click to join.</p>
- // )}
- // );
-}
diff --git a/front/src/logic/api.ts b/front/src/logic/api.ts
index b8acba2..52635e5 100644
--- a/front/src/logic/api.ts
+++ b/front/src/logic/api.ts
@@ -8,7 +8,7 @@ export async function start(): Promise<Urbit> {
const ship = await res.text();
airlock.ship = ship.slice(1);
airlock.our = ship;
- airlock.desk = "nostril";
+ airlock.desk = "nostrill";
await airlock.poke({ app: "hood", mark: "helm-hi", json: "opening airlock" });
await airlock.eventSource();
return airlock;
diff --git a/front/src/logic/nostril.ts b/front/src/logic/nostril.ts
deleted file mode 100644
index 4e5549d..0000000
--- a/front/src/logic/nostril.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import type { Event } from "@/types/nostr";
-import type { FC, FlatFeed, Poast } from "@/types/trill";
-import { engagementBunt, openLock } from "./bunts";
-export function eventsToFc(relayData: Record<string, Event[]>): FC {
- const start = null;
- const end = null;
- const feed = Object.values(relayData).reduce((acc: FlatFeed, events) => {
- const poasts = events.map(eventToPoast);
- for (const p of poasts) {
- if (p) acc[p.id] = p;
- }
- return acc;
- }, {});
- return { feed, start, end };
-}
-export function eventToPoast(event: Event): Poast | null {
- if (event.kind !== 1) return null;
- const contents = [{ paragraph: [{ text: event.content }] }];
- const ts = event.created_at * 1000;
- const id = `${ts}`;
- const poast: Poast = {
- id,
- host: event.pubkey,
- author: event.pubkey,
- contents,
- thread: id,
- parent: null,
- read: openLock,
- write: openLock,
- tags: [],
- time: ts,
- engagement: engagementBunt,
- children: [],
- };
- return poast;
-}
diff --git a/front/src/logic/nostrill.ts b/front/src/logic/nostrill.ts
new file mode 100644
index 0000000..bf9212d
--- /dev/null
+++ b/front/src/logic/nostrill.ts
@@ -0,0 +1,118 @@
+import type { Event } from "@/types/nostr";
+import type { Content, FC, Poast } from "@/types/trill";
+import { engagementBunt, openLock } from "./bunts";
+export function eventsToFc(postEvents: Event[]): FC {
+ const fc = postEvents.reduce(
+ (acc: FC, event: Event) => {
+ const p = eventToPoast(event);
+ if (!p) return acc;
+ acc.feed[p.id] = p;
+ if (!acc.start || event.created_at < Number(acc.start)) acc.start = p.id;
+ if (!acc.end || event.created_at > Number(acc.end)) acc.end = p.id;
+ return acc;
+ },
+ { feed: {}, start: null, end: null } as FC,
+ );
+ return fc;
+}
+export function eventToPoast(event: Event): Poast | null {
+ if (event.kind !== 1) return null;
+ const contents: Content = [{ paragraph: [{ text: event.content }] }];
+ const ts = event.created_at * 1000;
+ const id = `${ts}`;
+ const poast: Poast = {
+ id,
+ host: event.pubkey,
+ author: event.pubkey,
+ contents,
+ thread: id,
+ parent: null,
+ read: openLock,
+ write: openLock,
+ tags: [],
+ time: ts,
+ engagement: engagementBunt,
+ children: [],
+ };
+ for (const tag of event.tags) {
+ const f = tag[0];
+ if (!f) continue;
+ const ff = f.toLowerCase();
+ console.log("tag", ff);
+ if (ff === "e") {
+ const [, eventId, _relayURL, marker, _pubkey, ..._] = tag;
+ // TODO
+ if (marker === "root") poast.thread = eventId;
+ else if (marker === "reply") poast.parent = eventId;
+ }
+ //
+ if (ff === "r")
+ contents.push({
+ paragraph: [{ link: { show: tag[1]!, href: tag[1]! } }],
+ });
+ if (ff === "p")
+ contents.push({
+ paragraph: [{ ship: tag[1]! }],
+ });
+ if (ff === "q")
+ contents.push({
+ ref: {
+ type: "nostr",
+ ship: tag[1]!,
+ path: tag[2] || "" + `/${tag[3] || ""}`,
+ },
+ });
+ }
+ return poast;
+}
+
+// NOTE common tags:
+// imeta
+// client
+// nonce
+// proxy
+
+// export function parseEventTags(event: Event) {
+// const effects: any[] = [];
+// for (const tag of event.tags) {
+// const f = tag[0];
+// if (!f) continue;
+// const ff = f.toLowerCase();
+// switch (ff) {
+// case "p": {
+// const [, pubkey, relayURL, ..._] = tag;
+// // people mention
+// break;
+// }
+// case "e": {
+// // marker to be "root" or "reply"
+// // event mention
+// break;
+// }
+// case "q": {
+// const [, eventId, relayURL, pubkey, ..._] = tag;
+// // event mention
+// break;
+// }
+// case "t": {
+// const [, hashtag, ..._] = tag;
+// // event mention
+// break;
+// }
+// case "r": {
+// const [, url, ..._] = tag;
+// // event mention
+// break;
+// }
+// case "alt": {
+// const [, summary, ..._] = tag;
+// // event mention
+// break;
+// }
+// default: {
+// break;
+// }
+// }
+// }
+// return effects;
+// }
diff --git a/front/src/logic/requests/nostril.ts b/front/src/logic/requests/nostrill.ts
index 6f0edcf..6334c34 100644
--- a/front/src/logic/requests/nostril.ts
+++ b/front/src/logic/requests/nostrill.ts
@@ -1,8 +1,8 @@
import type Urbit from "urbit-api";
-import type { Cursor, PostID, SentPoast } from "@/types/trill";
+import type { Cursor, PostID } from "@/types/trill";
import type { Ship } from "@/types/urbit";
import { FeedPostCount } from "../constants";
-import type { UserProfile } from "@/types/nostril";
+import type { UserProfile } from "@/types/nostrill";
// Subscribe
type Handler = (date: any) => void;
@@ -12,24 +12,24 @@ export default class IO {
this.airlock = airlock;
}
private async poke(json: any) {
- return this.airlock.poke({ app: "nostril", mark: "json", json });
+ return this.airlock.poke({ app: "nostrill", mark: "json", json });
}
private async scry(path: string) {
- return this.airlock.scry({ app: "nostril", path });
+ return this.airlock.scry({ app: "nostrill", path });
}
private async sub(path: string, handler: Handler) {
const err = (err: any, _id: string) =>
- console.log(err, "error on nostril subscription");
+ console.log(err, "error on nostrill subscription");
const quit = (data: any) =>
- console.log(data, "nostril subscription kicked");
+ console.log(data, "nostrill subscription kicked");
const res = await this.airlock.subscribe({
- app: "nostril",
+ app: "nostrill",
path,
event: handler,
err,
quit,
});
- console.log(res, "subscribed to nostril agent");
+ console.log(res, "subscribed to nostrill agent");
}
async unsub(sub: number) {
return await this.airlock.unsubscribe(sub);
@@ -65,8 +65,8 @@ export default class IO {
async pokeAlive() {
return await this.poke({ alive: true });
}
- async addPost(pubkey: string, content: string) {
- const json = { add: { pubkey, content } };
+ async addPost(content: string) {
+ const json = { add: { content } };
return this.poke({ post: json });
}
// async addPost(post: SentPoast, gossip: boolean) {
diff --git a/front/src/pages/Feed.tsx b/front/src/pages/Feed.tsx
index e29033e..65dee64 100644
--- a/front/src/pages/Feed.tsx
+++ b/front/src/pages/Feed.tsx
@@ -1,17 +1,17 @@
// import spinner from "@/assets/icons/spinner.svg";
import "@/styles/trill.css";
+import "@/styles/feed.css";
import UserFeed from "./User";
import PostList from "@/components/feed/PostList";
import useLocalState from "@/state/state";
-import { useParams, useLocation } from "wouter";
+import { useParams } from "wouter";
import spinner from "@/assets/triangles.svg";
import { useState } from "react";
-import Composer from "@/components/feed/Composer";
+import Composer from "@/components/composer/Composer";
// import UserFeed from "./User";
import { P404 } from "@/Router";
-import { useQuery } from "@tanstack/react-query";
import { isValidPatp } from "urbit-ob";
-import { eventsToFc } from "@/logic/nostril";
+import { eventsToFc } from "@/logic/nostrill";
type FeedType = "global" | "following" | "nostr";
function Loader() {
@@ -88,8 +88,8 @@ function Global() {
return <p>Error</p>;
}
function Nostr() {
- const { relays } = useLocalState();
- const feed = eventsToFc(relays);
+ const { nostrFeed } = useLocalState();
+ const feed = eventsToFc(nostrFeed);
console.log({ feed });
const refetch = () => feed;
return <PostList data={feed} refetch={refetch} />;
diff --git a/front/src/pages/User.tsx b/front/src/pages/User.tsx
index fc727e4..a1e26f1 100644
--- a/front/src/pages/User.tsx
+++ b/front/src/pages/User.tsx
@@ -1,4 +1,5 @@
// import spinner from "@/assets/icons/spinner.svg";
+import Composer from "@/components/composer/Composer";
import PostList from "@/components/feed/PostList";
import useLocalState from "@/state/state";
import type { Ship } from "@/types/urbit";
@@ -10,6 +11,7 @@ function UserFeed({ p }: { p: Ship }) {
if (p === api!.airlock.our)
return (
<div id="feed-proper">
+ <Composer />
<PostList data={feed!} refetch={refetch} />
</div>
);
diff --git a/front/src/state/state.ts b/front/src/state/state.ts
index 28f3fb2..01b8ea1 100644
--- a/front/src/state/state.ts
+++ b/front/src/state/state.ts
@@ -1,11 +1,11 @@
import type { JSX } from "react";
import { start } from "@/logic/api";
-import IO from "@/logic/requests/nostril";
+import IO from "@/logic/requests/nostrill";
import type { ComposerData } from "@/types/ui";
import { create } from "zustand";
-import type { UserProfile } from "@/types/nostril";
+import type { UserProfile } from "@/types/nostrill";
import type { Event } from "@/types/nostr";
-import type { FC } from "@/types/trill";
+import type { FC, Poast } from "@/types/trill";
// TODO handle airlock connection issues
// the SSE pipeline has a "status-update" event FWIW
// type AirlockState = "connecting" | "connected" | "failed";
@@ -18,7 +18,8 @@ export type LocalState = {
setModal: (modal: JSX.Element | null) => void;
composerData: ComposerData | null;
setComposerData: (c: ComposerData | null) => void;
- keys: string[];
+ key: string;
+ nostrFeed: Event[];
relays: Record<string, Event[]>;
profiles: Map<string, UserProfile>; // pubkey key
following: Map<string, FC>;
@@ -26,7 +27,7 @@ export type LocalState = {
};
const creator = create<LocalState>();
-const useLocalState = creator((set, _get) => ({
+const useLocalState = creator((set, get) => ({
isNew: false,
api: null,
init: async () => {
@@ -35,22 +36,38 @@ const useLocalState = creator((set, _get) => ({
console.log({ api });
await api.subscribeStore((data) => {
console.log("store sub", data);
- const { feed, following, relays, profiles, keys } = data;
+ if ("state" in data) {
+ const { feed, nostr, following, relays, profiles, key } = data.state;
+ const flwing = new Map(Object.entries(following as Record<string, FC>));
+ flwing.set(api!.airlock.our!, feed);
+ set({
+ relays,
+ nostrFeed: nostr,
+ profiles: new Map(Object.entries(profiles)),
+ following: flwing,
+ key,
+ });
+ } else if ("fact" in data) {
+ if ("post" in data.fact) {
+ if ("add" in data.fact.post) {
+ const post: Poast = data.fact.post.add.post;
+ const following = get().following;
+ const curr = following.get(post.author);
+ const fc = curr ? curr : { feed: {}, start: null, end: null };
+ fc.feed[post.id] = post;
+ following.set(post.author, fc);
- const flwing = new Map(Object.entries(following as Record<string, FC>));
- flwing.set(api!.airlock.our!, feed);
- set({
- relays,
- profiles: new Map(Object.entries(profiles)),
- following: flwing,
- keys,
- });
+ set({ following });
+ }
+ }
+ }
});
set({ api });
},
- keys: [],
+ key: "",
profiles: new Map(),
relays: {},
+ nostrFeed: [],
following: new Map(),
followers: [],
UISettings: {},
diff --git a/front/src/styles/feed.css b/front/src/styles/feed.css
new file mode 100644
index 0000000..417f94b
--- /dev/null
+++ b/front/src/styles/feed.css
@@ -0,0 +1,4 @@
+.avatar,
+.avatar img {
+ width: 64px;
+} \ No newline at end of file
diff --git a/front/src/types/nostr.ts b/front/src/types/nostr.ts
index 0ccfaf3..90610d1 100644
--- a/front/src/types/nostr.ts
+++ b/front/src/types/nostr.ts
@@ -8,4 +8,5 @@ export type Event = {
content: string;
};
-export type Tag = any[];
+export type NostrEvent = Event;
+export type Tag = string[];
diff --git a/front/src/types/nostril.ts b/front/src/types/nostril.ts
deleted file mode 100644
index 65a6194..0000000
--- a/front/src/types/nostril.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export type UserProfile = {
- name: string;
- picture: string; // URL
- about: string;
- other: Record<string, string>;
-};
diff --git a/front/src/types/nostrill.ts b/front/src/types/nostrill.ts
new file mode 100644
index 0000000..bcd3628
--- /dev/null
+++ b/front/src/types/nostrill.ts
@@ -0,0 +1,23 @@
+import type { NostrEvent } from "./nostr";
+import type { Poast } from "./trill";
+
+export type UserProfile = {
+ name: string;
+ picture: string; // URL
+ about: string;
+ other: Record<string, string>;
+};
+
+export type PostWrapper =
+ | { nostr: NostrPost }
+ | { urbit: { post: Poast; nostr?: NostrMetadata } };
+export type NostrPost = {
+ relay: string;
+ event: NostrEvent;
+ post: Poast;
+};
+export type NostrMetadata = {
+ pubkey?: string;
+ eventId: string;
+ relay?: string;
+};
diff --git a/front/src/types/trill.ts b/front/src/types/trill.ts
index e0936ad..984b1f3 100644
--- a/front/src/types/trill.ts
+++ b/front/src/types/trill.ts
@@ -108,7 +108,7 @@ export type ExternalContent = {
content: string;
};
};
-export type ExternalApp = "twatter" | "insta" | "anon" | "rumors";
+export type ExternalApp = "twatter" | "insta" | "anon" | "rumors" | "nostr";
export interface TwatterReference {
json: {
origin: "twatter";
diff --git a/front/src/types/ui.ts b/front/src/types/ui.ts
index d964d84..c0c61a1 100644
--- a/front/src/types/ui.ts
+++ b/front/src/types/ui.ts
@@ -1,6 +1,6 @@
-import {Poast } from "./trill";
-import { Tweet } from "./twatter";
-import { Ship } from "./urbit";
+import type { NostrMetadata } from "./nostrill";
+import type { Poast } from "./trill";
+import type { Tweet } from "./twatter";
export type Timestamp = number;
export type UrbitTime = string;
@@ -9,19 +9,19 @@ export interface ComposerData {
type: "quote" | "reply";
post: SPID;
}
-export type SPID = TrillPID | TwatterPID | RumorsPID;
+export type SPID = TrillPID | NostrPID | TwatterPID | RumorsPID;
export interface TrillPID {
- service: "trill";
- post: Poast;
+ trill: Poast;
+}
+export interface NostrPID {
+ nostr: NostrMetadata;
}
export interface TwatterPID {
- service: "twatter";
- post: Tweet;
+ twatter: Tweet;
}
export interface RumorsPID {
- service: "rumors";
- post: Poast
+ rumors: Poast;
}
export interface Guanxi {
trill: Relationship;
@@ -36,14 +36,15 @@ export type BucketCreds = {
bucket: string;
origin: string; // this is the endpoint
region: string;
- }, creds: {
+ };
+ creds: {
credentials: {
accessKey: string;
secretKey: string;
- }
- }
-}
+ };
+ };
+};
-export type DateStruct = {year: number, month: number, day: number}
-export type ChatQuoteParams = {p: Ship, nest: string, id: string}
-export type ReactGrouping = Array<{react: string, ships: Ship[]}> \ No newline at end of file
+export type DateStruct = { year: number; month: number; day: number };
+export type ChatQuoteParams = { p: Ship; nest: string; id: string };
+export type ReactGrouping = Array<{ react: string; ships: Ship[] }>;