add edu-feed

This commit is contained in:
@s.roertgen 2024-08-20 21:19:55 +02:00
parent 570eed0f41
commit 960200d410
11 changed files with 3755 additions and 230 deletions

View file

@ -6,6 +6,7 @@
:show-add-event false
:events #{}
:pk nil
:sk nil
:list-kinds [30001 30004]
:default-relays [{:name "strfry-1"
:uri "http://localhost:7777"
@ -19,10 +20,16 @@
:uri "http://localhost:4445"
:id (random-uuid)
:status "disconnected"}
{:name "damus"
:uri "wss://relay.damus.io"
; {:name "damus"
; :uri "wss://relay.damus.io"
; :status "disconnected"}
{:name "SC24"
:uri "wss://relay.sc24.steffen-roertgen.de"
:status "disconnected"}
]
:selected-events #{}
:selected-lists #{}
:selected-list-ids #{}
:show-lists-modal false
:show-create-list-modal false
:show-event-data-modal false
:sockets []})

View file

@ -8,7 +8,12 @@
[promesa.core :as p]
[wscljs.client :as ws]
[wscljs.format :as fmt]))
[wscljs.format :as fmt]
[clojure.string :as str]
[clojure.set :as set]
["js-confetti" :as jsConfetti]
))
(def list-kinds [30001 30004])
@ -38,11 +43,8 @@
;; TODO if EOSE retrieved end connection identified by uri
(fn-traced [{:keys [db]} [_ [uri raw-event]]]
(let [event (nth raw-event 2 raw-event)]
(println uri raw-event event)
(when (and
(= (first raw-event) "EVENT")
;;(not (some #(= (:id event) (:id %)) (get db :events {})))
)
(= (first raw-event) "EVENT"))
{:db (update db :events conj event)}))))
(defn handlers
@ -50,7 +52,6 @@
{:on-message (fn [e] (re-frame/dispatch [::save-event [ws-uri (-> (.-data e)
js/JSON.parse
(js->clj :keywordize-keys true))]]))
; :on-open #(prn "Opening a new connection")
:on-open #(re-frame/dispatch [::load-events ws-uri])
:on-close #(prn "Closing a connection")
:on-error (fn [e] (.log js/console "Error with uri: " ws-uri (clj->js e))
@ -78,8 +79,8 @@
(let [sockets (re-frame/subscribe [::subs/sockets])
target-ws (first (filter #(= ws-uri (:uri %)) @sockets))]
(ws/send (:socket target-ws) ["REQ" "424242" {:kinds [30142]
:limit 10}] fmt/json)
(ws/send (:socket target-ws) ["REQ" "424242" {:kinds [30004 30142]
:limit 100}] fmt/json)
; (ws/close (:socket (first @sockets))) ;; should be handled otherwise (?)
)))
@ -119,9 +120,7 @@
(fn [ws-uri]
(let [sockets (re-frame/subscribe [::subs/sockets])
target-ws (first (filter #(= ws-uri (:uri %)) @sockets))]
(re-frame/dispatch [::create-websocket target-ws])
; (ws/create (:uri target-ws) (handlers (:uri target-ws)))
)))
(re-frame/dispatch [::create-websocket target-ws]))))
;; TODO use id to close socket
;; add connection status to socket
@ -163,7 +162,6 @@
(fn [[sockets signedEvent]]
(let [connected-sockets (filter #(= "connected" (:status %)) sockets)]
(doseq [socket connected-sockets]
(.log js/console "sending to relay" signedEvent)
(ws/send (:socket socket) ["EVENT" signedEvent] fmt/json)))))
(re-frame/reg-event-db
@ -195,19 +193,21 @@
(fn-traced [cofx [_ resource]]
(let [event {:kind 30142
:created_at (:now cofx)
:content "hello world"
:content ""
:tags [["d" (:id resource)]
["author" "" (:author resource)]]}]
{::publish-resource-fx event})))
{::sign-and-publish-event event})))
;; TODO maybe we need some validation before publishing
;; TODO rename function to sth like send-to-relays
(re-frame/reg-fx
::publish-resource-fx
::sign-and-publish-event
(fn [unsignedEvent]
(p/let [_ (js/console.log (clj->js unsignedEvent))
signedEvent (.nostr.signEvent js/window (clj->js unsignedEvent))]
(re-frame/dispatch [::send-to-relays signedEvent]))))
(if (nostr/valid-unsigned-nostr-event? unsignedEvent)
(p/let [_ (js/console.log (clj->js unsignedEvent))
signedEvent (.nostr.signEvent js/window (clj->js unsignedEvent))
_ (js/console.log "Signed event: " (clj->js signedEvent))]
(re-frame/dispatch [::send-to-relays signedEvent]))
(.error js/console "Event is not a valid nostr event: " (clj->js unsignedEvent)))))
(re-frame/reg-event-fx
::login-with-extension
@ -220,15 +220,16 @@
(p/let [pk (.nostr.getPublicKey js/window)]
(re-frame/dispatch [::save-pk pk]))))
(re-frame/reg-event-db
(re-frame/reg-event-fx
::save-pk
(fn-traced [db [_ pk]]
(assoc db :pk pk)))
(fn-traced [{:keys [db]} [_ pk]]
{:db (assoc db :pk pk)
:dispatch [::get-lists-for-npub (nostr/get-npub-from-pk pk)]}))
(re-frame/reg-event-db
::logout
(fn-traced [db _]
(assoc db :pk nil)))
(assoc db :pk nil :sk nil)))
(re-frame/reg-cofx
:now
@ -239,9 +240,11 @@
[json-string created_at]
(let [parsed-json (js->clj (js/JSON.parse json-string) :keywordize-keys true)
tags (into [["d" (:id parsed-json)]
["r" (:id parsed-json)]
["id" (:id parsed-json)]
["name" (:name parsed-json)]
["image" (:image parsed-json)]]
["description" (:description parsed-json "")]
["image" (:image parsed-json "")]]
cat [(map (fn [e] ["about" (:id e) (-> e :prefLabel :de)]) (:about parsed-json))
(map (fn [e] ["inLanguage" e]) (:inLanguage parsed-json))])
event {:kind 30142
@ -255,43 +258,66 @@
[(re-frame/inject-cofx :now)]
(fn-traced [cofx [_ json-string]]
(let [event (convert-amb-to-nostr-event json-string (:now cofx))]
{::publish-resource-fx event})))
(re-frame/reg-event-db
::toggle-selected-events
(fn [db [_ event]]
(js/console.log "toggling selected event with id" (:id event))
(if (some #(= event (:id %)) (:selected-events db))
(assoc db :selected-events (filter #(not= event (:id %)) (:selected-events db)))
(update db :selected-events conj event))))
{::sign-and-publish-event event})))
(re-frame/reg-event-fx
::add-resources-to-list
::toggle-selected-events
(fn [{:keys [db]} [_ event]]
(let [selected-event-ids (set (map (fn [e] (:id e)) (:selected-events db)))]
(if (and (seq (:selected-events db))
(contains? selected-event-ids (:id event)))
{:db (assoc db :selected-events (filter #(not= (:id event) (:id %)) (:selected-events db)))}
{:db (update db :selected-events conj event)}))))
(re-frame/reg-event-db
::toggle-selected-list-ids
(fn [db [_ id]]
(println "Toggle list ids: " id)
(let [in-selected-list-ids (contains? (:selected-list-ids db) id)]
(if in-selected-list-ids
(update db :selected-list-ids disj id)
(update db :selected-list-ids conj id)))))
(re-frame/reg-event-fx
::add-metadata-event-to-list
[(re-frame/inject-cofx :now)]
(fn [cofx [_ [list resources-to-add]]]
(let [tags (into [["d" (:d list)
"name" (:name list)]]
(map (fn [e] (cond
(= 1 (:kind e)) ["e" (:id e)]
(= 30142 (:kind e)) ["a" (str "30142:" (:id e) ":" (second (first (filter #(= "d" (first %)) (:tags e)))))]))
resources-to-add))
_ (.log js/console (clj->js tags))
(let [existing-tags (:tags list)
existing-tags-set (set existing-tags)
tags-to-add (filter #(not (contains? existing-tags-set %))
(map (fn [e] (cond
(= 1 (:kind e)) ["e" (:id e)]
(= 30142 (:kind e)) (nostr/build-kind-30142-tag e)))
resources-to-add))
new-tags (vec (concat existing-tags tags-to-add))
event {:kind 30004
:created_at (:now cofx)
:content ""
:tags tags}]
{::publish-resource-fx event})))
:tags new-tags}]
{::sign-and-publish-event event})))
(re-frame/reg-event-fx
::add-metadata-events-to-lists
(fn [cofx [_ [events lists]]]
(let [dispatch-events (mapv (fn [l] [::add-metadata-event-to-list [l events]]) lists)
_ (.log js/console (clj->js dispatch-events))]
{:fx [[:dispatch-n dispatch-events]]})))
(defn sanitize-subscription-id [s]
(str/join "" (take 64 s)))
(defn make-sub-id [prefix id]
(-> (str prefix id)
(sanitize-subscription-id)))
(re-frame/reg-event-fx
::get-lists-for-npub
(fn [cofx [_ [sockets npub]]]
(println "query for lists")
(fn [{:keys [db]} [_ npub]]
(let [query-for-lists ["REQ"
"RAND24" ;; TODO maybe make this more explicit later
(make-sub-id "lists-" npub) ;; TODO maybe make this more explicit later
{:authors [(nostr/get-pk-from-npub npub)]
:kinds list-kinds}]]
(.log js/console (clj->js query-for-lists))
:kinds list-kinds}]
sockets (:sockets db)]
{::request-from-relay [sockets query-for-lists]
:dispatch [::get-deleted-lists-for-npub [sockets npub]]})))
@ -299,16 +325,16 @@
::get-deleted-lists-for-npub
(fn [cofx [_ [sockets npub]]]
(let [query-for-deleted-lists ["REQ"
"RAND24" ;; TODO maybe make this more explicit later
(make-sub-id "deleted-lists-" npub) ;; TODO maybe make this more explicit later
{:authors [(nostr/get-pk-from-npub npub)]
:kinds [5]}]]
(.log js/console "Query for deleted lists" (clj->js query-for-deleted-lists))
{::request-from-relay [sockets query-for-deleted-lists]})))
(comment)
(re-frame/reg-fx
::request-from-relay
(fn [[sockets query]]
(println "requesting from relay this query: " query)
(doall
(for [s (filter (fn [s] (= "connected" (:status s))) sockets)]
(ws/send (:socket s) query fmt/json)))))
@ -322,6 +348,26 @@
{::request-from-relay [sockets query]})))
(defn cleanup-list-name [s]
(-> s
(str/replace #"\s" "-")
(str/replace #"[^a-zA-Z0-9]" "-")))
(comment
(cleanup-list-name "this is gönna be an awesüm+ l]st"))
(re-frame/reg-event-fx
::create-new-list
[(re-frame/inject-cofx :now)]
(fn [cofx [_ name]]
(let [tags [["d" (cleanup-list-name name)]
["name" name]]
create-list-event {:kind 30004
:created_at (:now cofx)
:content ""
:tags tags}]
{::sign-and-publish-event create-list-event})))
(re-frame/reg-event-fx
::delete-list
[(re-frame/inject-cofx :now)]
@ -336,5 +382,66 @@
["a" (str (:kind l) ":" (:pubkey l) ":" (second (first (filter
#(= "d" (first %))
(:tags l)))))])]}]
(println deletion-event)
{::publish-resource-fx deletion-event})))
{::sign-and-publish-event deletion-event})))
(re-frame/reg-event-db
::toggle-show-lists-modal
(fn [db _]
(assoc db :show-lists-modal (not (:show-lists-modal db)))))
(re-frame/reg-event-db
::toggle-show-create-list-modal
(fn [db _]
(assoc db :show-create-list-modal (not (:show-create-list-modal db)))))
(re-frame/reg-event-db
::toggle-show-event-data-modal
(fn [db [_ event]]
(assoc db
:show-event-data-modal (not (:show-event-data-modal db))
:selected-event event)))
(re-frame/reg-event-fx
::delete-event-from-list
[(re-frame/inject-cofx :now)]
(fn [cofx [_ [event list]]]
(let [filtered-tags (filterv (fn [t] (not= (:id event) (nostr/extract-id-from-tag (second t)))) (:tags list))
_ (println (:tags list))
_ (.log js/console "Filtered Tags: " (clj->js filtered-tags))
event {:kind 30004
:created_at (:now cofx)
:content ""
:tags filtered-tags}]
{::sign-and-publish-event event})))
(re-frame/reg-event-db
::create-sk
(fn [db [_]]
(let [sk (nostr/generate-sk)
pk (nostr/get-pk-from-sk sk)]
(assoc db :sk sk :pk pk))))
(re-frame/reg-fx
::get-amb-json-from-uri
(fn [uri]
(p/let [raw-html (js/fetch uri {:headers {"Access-Control-Allow-Origin" "*"}})]
(println raw-html))))
(comment
(p/->> (js/fetch "https://oersi.org/resources/aHR0cHM6Ly9lZ292LWNhbXB1cy5vcmcvY291cnNlcy9hcmJlaXRlbnVuZGZ1ZWhyZW5fdXBfMjAyMi0x")
(println)))
(re-frame/reg-event-fx
::publish-amb-uri-as-nostr-event
(fn [db [_ uri]]
{::get-amb-json-from-uri uri}))
(re-frame/reg-event-fx
::add-confetti
(fn [_ _]
;; Initialize jsConfetti instance if needed
(let [confetti-instance (new jsConfetti)]
;; Trigger the confetti
(.addConfetti confetti-instance ))
;; No further effects needed
{}))

View file

@ -1,8 +1,11 @@
(ns ied.nostr
(:require
[promesa.core :as p]
[clojure.string :as str]
["@noble/secp256k1" :as secp]
["nostr-tools/nip19" :as nip19]))
["nostr-tools/nip19" :as nip19]
["nostr-tools/pure" :as nostr]
[cljs.core :as c]))
(defn event-to-serialized-json [m]
(let [event [0
@ -36,8 +39,32 @@
(defn signEvent [hashedEvent sk]
(.sign secp hashedEvent sk))
(defn generate-sk []
;; Generate a secure (private) key as byte array
(.utils.randomPrivateKey secp))
(defn sk-as-hex [sk]
(byte-array-to-hex sk))
(defn sk-as-nsec [sk]
;; let nsec = nip19.nsecEncode(sk)
;; sk should be byte-array
(.nsecEncode nip19 sk))
(defn nsec-as-sk [nsec]
;; byte array is returned
(.-data (.decode nip19 nsec)))
(defn get-pk-from-sk [sk]
(.getPublicKey nostr sk))
(comment
(.getPublicKey secp (.utils.randomPrivateKey secp)))
(let [nsec "nsec16f87vq2xvvus3qxxtmdhgvq6pyfmn7nr9ck9yw8sc8ar7xradmss66z5fz"]
(sk-as-hex (nsec-as-sk nsec)))
(.getPublicKey secp (.utils.randomPrivateKey secp))
(byte-array-to-hex (.utils.randomPrivateKey secp))
(get-pk-from-sk (generate-sk)))
(comment
(p/let [event {:content "hello world"
@ -61,6 +88,105 @@
(defn get-pk-from-npub [npub]
(.-data (.decode nip19 npub)))
;; TODO make multimethod?
(defn get-list-name [list]
(or (second (first (filter #(= "title" (first %)) (:tags list))))
(second (first (filter #(= "alt" (first %)) (:tags list))))
(second (first (filter #(= "d" (first %)) (:tags list))))
(str "No name found for List-ID: " (:id list))))
(defn get-d-id-from-event [event]
(second (first (filter #(= "d" (first %)) (:tags event)))))
(defn get-name-from-metadata-event [event]
(or (second (first (filter #(= "name" (first %)) (:tags event))))
(second (first (filter #(= "id" (first %)) (:tags event))))
(str "No name found for Metadata-Event: " (:id event))))
(defn get-image-from-metadata-event [event]
(or (let [img-url (second (first (filter #(= "image" (first %)) (:tags event))))]
(if (= "" img-url ) false img-url))
"/assets/edu-feed-logo.webp"))
(defn get-description-from-metadata-event [event]
(or (second (first (filter #(= "description" (first %)) (:tags event))))
(str "No description found for Metadata-Event: " (:id event))))
(defn get-about-tags-from-metadata-event [event]
(filter #(= "about" (first %)) (:tags event)))
(defn get-about-names-from-metadata-event [event]
(->> (get-about-tags-from-metadata-event event)
(map #(nth % 2 nil))))
(comment
(get-npub-from-pk "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")
(get-pk-from-npub "npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6"))
(defn valid-timestamp? [t]
(and (integer? t)
(pos? t)
(< t (* 1e10))))
(defn valid-kind? [k]
(integer? k))
(defn valid-tags? [tags]
(and (vector? tags)
(every? (fn [tag]
(and
(vector? tag)
(every? some? tag)))
tags)))
(defn valid-unsigned-nostr-event? [event]
(let [{:keys [created_at kind tags content]} event]
(and
(valid-timestamp? created_at)
(valid-kind? kind)
(valid-tags? tags)
(string? content))))
(comment
(valid-tags? [["hello" "there"] ["fa" ""]]))
(defn get-tag-value [tags tag-prefix]
(some #(when (= tag-prefix (first %)) (second %)) tags))
(defn sort-lists [events]
(sort-by (fn [event]
(let [tags (:tags event)
name-value (get-tag-value tags "name")
d-value (get-tag-value tags "d")]
(or name-value d-value)))
events))
;; TODO rename to ..."from-d-tag"
(defn extract-id-from-tag
[s]
(let [parts (str/split s #":")]
(if (>= (count parts) 2)
(second parts)
s)))
(comment
(extract-id-from-tag "30142:29b2dc8b83e3f8c79a9ee2535b4adb6105b90af893612e72f675a5d16f8544b5:https://wtcs.pressbooks.pub/digitalliteracy/"))
(defn list-contains-metadata-event? [list event]
(let [tags (:tags list)
metadata-event-ids-in-list (set (map extract-id-from-tag (filter (fn [t] (= "a" (first t))) tags)))
event-id (:id event)]
(.log js/console "metadata event ids in list" metadata-event-ids-in-list)
(and
(seq metadata-event-ids-in-list)
(contains? metadata-event-ids-in-list event-id))))
(defn build-kind-30142-tag [event]
["a" (str "30142:" (:id event) ":" (second (first (filter #(= "d" (first %)) (:tags event)))))])
(defn get-event-ids-from-list [list]
(->> (:tags list)
(filter #(= (or "a" "e") (first %))) ;; just a and e tags
(map second) ;; just the id
(map extract-id-from-tag)
(set)))

View file

@ -13,13 +13,17 @@
["/"
{"" :home
"add-resource" :add-resource
"keys" :keys
"feed" :event-feed
"about" :about
"settings" :settings
["" [#"npub1[ac-hj-np-z02-9]{58}" :npub]] {"" :npub-view
"/" :npub-view}}]))
"/" :npub-view}}]))
(comment
(parse "/npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma/"))
(parse "/npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma/")
(apply url-for [:npub-view :npub "npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma"])
(apply url-for [:home]))
(defn parse
[url]
@ -39,7 +43,7 @@
(defn navigate!
[handler]
(pushy/set-token! history (url-for handler)))
(pushy/set-token! history (apply url-for handler)))
(defn start!
[]

View file

@ -2,7 +2,8 @@
(:require
[re-frame.core :as re-frame]
[clojure.string :as str]
[clojure.set :as set]))
[clojure.set :as set]
[ied.nostr :as nostr]))
(re-frame/reg-sub
::name
@ -19,17 +20,17 @@
(fn [db _]
(:sockets db)))
(re-frame/reg-sub
::connected-sockets
:<- [::sockets]
(fn [[sockets]]
(filter #(= (:status %) "connected") sockets)))
(re-frame/reg-sub
::events
(fn [db _]
(:events db)))
(re-frame/reg-sub
::metadata-events
(fn [db _]
(sort-by :created_at #(> %1 %2) (filter (fn [e] (= 30142 (:kind e))) (:events db)))))
(re-frame/reg-sub
::show-add-event
(fn [db _]
@ -40,6 +41,16 @@
(fn [db _]
(:pk db)))
(re-frame/reg-sub
::npub
(fn [db _]
(nostr/get-npub-from-pk (:pk db))))
(re-frame/reg-sub
::nsec
(fn [db _]
(nostr/sk-as-nsec (:sk db))))
(re-frame/reg-sub
::default-relays
(fn [db _]
@ -50,6 +61,18 @@
(fn [db _]
(:selected-events db)))
(re-frame/reg-sub
::selected-list-ids
(fn [db]
(:selected-list-ids db)))
(re-frame/reg-sub
::selected-lists
:<- [::lists-of-user]
:<- [::selected-list-ids]
(fn [[lists-of-user selected-list-ids]]
(filter #(contains? selected-list-ids (:id %)) lists-of-user)))
(re-frame/reg-sub
::route-params
(fn [db _]
@ -64,15 +87,24 @@
[tags]
(second (first (filter (fn [t] (= "d" (first t))) tags))))
(comment
(get-d-id-from-tags [["d" "https://wtcs.pressbopub/digitalliteracy/"]]))
(defn d-id-not-in-deleted-list-ids
[d-id deleted-list-ids]
(println d-id
deleted-list-ids)
(not (contains? deleted-list-ids d-id)))
(defn most-recent-by-d-tag
[events]
(->> events
(group-by (fn [event]
(some (fn [[tag-type tag-value]]
(when (= tag-type "d")
tag-value))
(:tags event))))
(map (fn [[d-tag events]]
(apply max-key :created_at events)))
(into [])))
(re-frame/reg-sub
::lists
:<- [::list-kinds]
@ -80,9 +112,38 @@
:<- [::deleted-list-ids]
(fn [[list-kinds events deleted-lists]]
(let [all-lists (filter #(and (some #{(:kind %)} list-kinds)
(d-id-not-in-deleted-list-ids (get-d-id-from-tags (:tags %)) deleted-lists))
events)]
all-lists)))
#_(d-id-not-in-deleted-list-ids (get-d-id-from-tags (:tags %)) deleted-lists))
events)
most-recent-lists (most-recent-by-d-tag all-lists)]
(.log js/console "all lists: " (clj->js all-lists))
(.log js/console "most recent lists: " (clj->js most-recent-lists))
most-recent-lists)))
(re-frame/reg-sub
::feed-events
:<- [::metadata-events]
:<- [::lists]
(fn [[md-events lists]]
(sort-by :created_at #(> %1 %2) (concat md-events lists))))
(re-frame/reg-sub
::lists-of-user
:<- [::pk]
:<- [::lists]
(fn [[pk lists]]
(set (filter #(= pk (:pubkey %)) lists))))
(re-frame/reg-sub
::lists-for-npub
:<- [::route-params]
:<- [::lists]
(fn [[route-params lists]]
(let [npub (:npub route-params)]
(set (filter #(= (nostr/get-pk-from-npub npub) (:pubkey %)) lists)))))
(comment
(seq [1])
(seq #{1}))
(defn extract-d-id-from-tags
[s]
@ -102,19 +163,12 @@
::deleted-list-ids
(fn [db _]
(let [kind-5-events (filter (fn [e] (= 5 (:kind e))) (:events db))
;; TODO find list events after the timestamp of the last kind5 event
;; get d-tags of that events
;; filter that d-tags out of the deleted-list-ids
deleted-list-ids (get-d-ids-from-events kind-5-events)]
(.log js/console "kind-5-events: " (clj->js kind-5-events))
(.log js/console "Deleted list-ids " (clj->js deleted-list-ids))
deleted-list-ids)))
(defn extract-id-from-tags
[s]
(println s)
(let [parts (str/split s #":")]
(if (>= (count parts) 2)
(second parts)
s)))
(re-frame/reg-sub
::missing-events-from-lists
:<- [::events]
@ -123,14 +177,34 @@
(let [event-ids-from-list-tags (->> (into [] cat (map (fn [l] (:tags l)) lists))
(filter #(= (or "a" "e") (first %))) ;; just a and e tags
(map second) ;; just the id
(map extract-id-from-tags)
(map nostr/extract-id-from-tag)
(set))
event-ids (set (map #(:id %) events))
missing-events (set/difference event-ids-from-list-tags event-ids)]
(.log js/console "Missing IDs: " (clj->js missing-events))
; (.log js/console "Missing IDs: " (clj->js missing-events))
missing-events)))
(comment
(>= 2 (count (str/split "3013:fjkldj:https://jfdajdfklö" #":")))
(re-frame/reg-sub
::show-lists-modal
(fn [db _]
(:show-lists-modal db)))
(extract-id-from-tags "30142:e2d8b8e3381386976a57091199d23:https://wtcs.pressbooks.pub/digitalliteracy/"))
(re-frame/reg-sub
::show-create-list-modal
(fn [db _]
(:show-create-list-modal db)))
(re-frame/reg-sub
::show-event-data-modal
(fn [db _]
(:show-event-data-modal db)))
(re-frame/reg-sub
::selected-event
(fn [db _]
(:selected-event db)))
(re-frame/reg-sub
::events-in-list
(fn [db [_ event-ids]]
(filter (fn [e] (contains? event-ids (:id e))) (:events db))))

View file

@ -1,9 +1,11 @@
(ns ied.views
(:require
[re-frame.core :as re-frame]
[cljs.pprint :refer [pprint]]
[ied.events :as events]
[ied.routes :as routes]
[ied.subs :as subs]
[ied.nostr :as nostr]
[reagent.core :as reagent]))
;; add resource form
@ -47,31 +49,121 @@
:on-click #(re-frame/dispatch [::events/convert-amb-and-publish-as-nostr-event (:json-string @s)])}
"Publish as Nostr Event"]])))
(defn add-resosurce-by-uri []
(let [uri (reagent/atom {:uri ""})]
(fn []
[:form {:on-submit (fn [e] (.preventDefault e))}
[:label {:for "uri"} "URI: "]
[:input {:id "uri"
:on-change (fn [e]
(swap! uri assoc :uri (-> e .-target .-value)))}]
[:button {:class "btn btn-warning"
:on-click #(re-frame/dispatch [::events/publish-amb-uri-as-nostr-event (:uri @uri)])}
"Publish as Nostr Event"]])))
;; event data modal
(defn event-data-modal []
(let [visible? @(re-frame/subscribe [::subs/show-event-data-modal])
selected-event @(re-frame/subscribe [::subs/selected-event])]
(when visible?
[:dialog {:open visible? :class "modal"}
[:div {:class "modal-box relative flex flex-col"}
[:h3 {:class "text-lg font-bold"} (nostr/get-name-from-metadata-event selected-event)]
[:pre (with-out-str (pprint selected-event))]
[:p {:class "py-4"} "Press ESC key or click outside to close"]]
[:form {:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal])
:method "dialog" :class "modal-backdrop"}
[:button "close"]]])))
;; metadata event component
(defn metadata-event-component [event]
(let [selected-events @(re-frame/subscribe [::subs/selected-events])]
[:div
{:class "card bg-base-100 w-96 shadow-xl min-h-[620px]"}
[:figure
[:img
{:class "h-48 object-cover"
:src
(nostr/get-image-from-metadata-event event)
:alt ""}]]
[:div
{:class "card-body"}
[:a {:href (nostr/get-d-id-from-event event)
:class "card-title hover:underline"}
(nostr/get-name-from-metadata-event event)]
(doall
(for [about (nostr/get-about-names-from-metadata-event event)]
[:div {:class "badge badge-primary m-1 truncate "
:key about} about]))
[:p {:class "break-all"}
(nostr/get-description-from-metadata-event event)]
[:button {:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal event])} "Show Event Data"]
[:div
{:class "card-actions justify-end"}
[:div
{:class "form-control"}
[:label
{:class "cursor-pointer label"}
[:span {:class "label-text"} ""]
[:input
{:type "checkbox"
:checked (contains? (set (map #(:id %) selected-events)) (:id event))
:class "checkbox checkbox-success"
:on-change #(re-frame/dispatch [::events/toggle-selected-events event])}]]]]]]))
;; events
(defn events-panel []
(let [events (re-frame/subscribe [::subs/events])
(let [events @(re-frame/subscribe [::subs/metadata-events])
selected-events @(re-frame/subscribe [::subs/selected-events])
show-add-event (re-frame/subscribe [::subs/show-add-event])]
[:div {:class "border-2 rounded"}
[:p {:on-click #(re-frame/dispatch [::events/toggle-show-add-event])}
(if @show-add-event "X" "Add Resource!")]
(when @show-add-event
[add-resource-form])
[:p (str "Num of events: " (count @events))]
(if (> (count @events) 0)
(doall
(for [event @events]
[:li {:key (:id event)} (get event :content "")
[:input {:type "checkbox"
:on-click #(re-frame/dispatch [::events/toggle-selected-events event])}]]))
[:p (str "Num of events: " (count events))]
(if (> (count events) 0)
[:div {:class "flex flex-wrap justify-center gap-2"}
(doall
(for [event events]
[:div {:key (:id event)}
[metadata-event-component event]]))]
[:p "no events there"])
[:button {:class "btn"
:disabled (not (boolean (seq selected-events)))
:on-click #(re-frame/dispatch [::events/add-resources-to-list [{:d "unique-id-1"
:name "Test List SC"}
selected-events]])}
:on-click #(re-frame/dispatch [::events/add-metadata-event-to-list [{:d "unique-id-1"
:name "Test List SC"}
selected-events]])}
"Add To Lists"]]))
;; event feed component
(defn event-feed-component [event]
(let [_ () #_(re-frame/dispatch [::events/add-confetti])]
[:div
{:class "animate-flyIn card bg-base-100 w-64 h-64 shadow-xl border border-white border-w "}
[:p (:kind event)]
[:figure
[:img
{:class "h-48 object-contain"
:src
(nostr/get-image-from-metadata-event event)
:alt ""}]]
[:div
{:class "card-body"}
[:button {:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal event])} "Show Event Data"]]]))
(defn event-feed-panel []
(let [events @(re-frame/subscribe [::subs/feed-events])]
[:div {:class ""}
[:h1 "Event Feed"]
[:p (str "Num of events: " (count events))]
(if (> (count events) 0)
[:div {:class "flex flex-row gap-2"}
(doall
(for [event events]
[:div {:key (:id event)}
[event-feed-component event]]))]
[:p "no events there"])]))
(defmethod routes/panels :event-feed-panel [] [event-feed-panel])
;; relays
(defn add-relay-form
[name uri]
@ -95,7 +187,6 @@
[:button {:on-click #(re-frame/dispatch [::events/create-websocket {:name (:name @s)
:id (random-uuid)
:uri (:uri @s)}])}
"Add Relay"]])))
(defn relays-panel
@ -126,34 +217,198 @@
;(re-frame/dispatch [::events/connect-to-default-relays])
)]))
;; Header
;; checkmark
(defn checkmark []
[:svg
{:version "1.1",
:class "fa-icon ml-auto mr-2 svelte-1mc5hvj",
:width "16",
:height "16",
:aria-label "",
:role "presentation",
:viewBox "0 0 1792 1792",
; :style "color: black;"
}
[:path
{:d
"M1671 566q0 40-28 68l-724 724-136 136q-28 28-68 28t-68-28l-136-136-362-362q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 295 656-657q28-28 68-28t68 28l136 136q28 28 28 68z"}]])
(defn header []
(let [pk (re-frame/subscribe [::subs/pk])]
;; Add to lists modal
(defn create-list-modal []
(let [name (reagent/atom "")
visible? @(re-frame/subscribe [::subs/show-create-list-modal])]
(when visible?
[:dialog {:open visible? :class "modal"}
[:div {:class "modal-box relative flex flex-col"}
[:h3 {:class "text-lg font-bold"} "Hello!"]
[:p {:class "py-4"} "Press ESC key or click outside to close"]
[:input
{:type "text"
:on-change (fn [e] (reset! name (-> e .-target .-value)))
:placeholder "List Name"
:class "input input-bordered w-full max-w-xs"}]
[:button {:class "btn"
:on-click #(re-frame/dispatch [::events/create-new-list @name])} "Create New List"]]
[:form {:on-click #(re-frame/dispatch [::events/toggle-show-create-list-modal])
:method "dialog" :class "modal-backdrop"}
[:button "close"]]])))
(defn add-to-lists-modal []
(let [selected-list-ids @(re-frame/subscribe [::subs/selected-list-ids])
selected-lists @(re-frame/subscribe [::subs/selected-lists])
selected-metadata-events @(re-frame/subscribe [::subs/selected-events])
visible? @(re-frame/subscribe [::subs/show-lists-modal])
lists @(re-frame/subscribe [::subs/lists-of-user])
sorted-lists (nostr/sort-lists lists)]
(when visible?
[:dialog {:open visible? :class "modal"}
[:div {:class "modal-box relative flex flex-col"}
[:h3 {:class "text-lg font-bold"}
"Hello!"]
[:p {:class "py-4"}
"Press ESC key or click outside to close"]
(doall
(for [l lists]
(let [in-selected-lists (or (and (seq selected-metadata-events)
(every? (fn [e] (nostr/list-contains-metadata-event? l e)) selected-metadata-events))
(contains? selected-list-ids (:id l)))
div-class (str "m-2 flex flex-row items-center rounded border border-solid border-white p-2 "
(if in-selected-lists
"bg-green-500 text-black"
"hover:bg-orange-500 hover:text-black"))]
[:div {:class div-class
:key (nostr/get-list-name l)
:on-click #(re-frame/dispatch [::events/toggle-selected-list-ids (:id l)])}
[:p {:class "text-xl font-bold"}
(nostr/get-list-name l)]
(when in-selected-lists
[checkmark])])))
[:div {:class "flex flex-row"}
[:button {:on-click #(re-frame/dispatch [::events/add-metadata-events-to-lists [selected-metadata-events selected-lists]])
:class "btn"}
"Add Resources To Lists"]
[:button {:class "btn ml-auto mr-0"
:on-click #(re-frame/dispatch [::events/toggle-show-create-list-modal])}
"Create New List"]]]
[:form {:on-click #(re-frame/dispatch [::events/toggle-show-lists-modal])
:method "dialog"
:class "modal-backdrop"}
[:button
"close"]]])))
;; shopping cart
(defn shopping-cart []
(let [selected-events @(re-frame/subscribe [::subs/selected-events])]
[:div {:class "dropdown dropdown-end"}
[:div
{:tabIndex "0", :role "button", :class "btn btn-ghost btn-circle"}
[:div
{:class "indicator"}
[:svg
{:xmlns "http://www.w3.org/2000/svg",
:class "h-5 w-5",
:fill "none",
:viewBox "0 0 24 24",
:stroke "currentColor"}
[:path
{:stroke-linecap "round",
:stroke-linejoin "round",
:stroke-width "2",
:d
"M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"}]]
[:span {:class "badge badge-sm indicator-item"} (count selected-events)]]]
[:div
{:tabIndex "0",
:class
"card card-compact dropdown-content bg-base-100 z-[1] mt-3 w-52 shadow"}
[:div
{:class "card-body"}
[:span {:class "text-lg font-bold"}
(str (count selected-events)
" Items")]
[:div {:class "card-actions"}
[:button {:class "btn"
:on-click #(re-frame/dispatch [::events/toggle-show-lists-modal])}
"Add To Lists"]]]]]))
(defn user-menu []
(let [pk @(re-frame/subscribe [::subs/pk])]
(if pk
[:div
{:class "dropdown dropdown-end"}
[:div
{:tabIndex "0",
:role "button",
:class "btn btn-ghost btn-circle avatar"}
[:div
{:class "w-10 rounded-full"}
[:img
{:alt "Tailwind CSS Navbar component",
:src
"https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp"}]]]
[:ul
{:tabIndex "0",
:class
"menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow"}
[:li
[:a
{:on-click #(re-frame/dispatch [::events/navigate [:npub-view :npub (nostr/get-npub-from-pk pk)]])}
"Profile"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:settings]])} "Settings"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:keys]])} "Keys"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/logout])} "Logout"]]]]
[:div
{:class "dropdown dropdown-end"}
[:div {:tabIndex "0", :role "button", :class "btn m-1"} "Login"]
[:ul
{:tabIndex "0",
:class
"dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"}
[:li [:a {:on-click #(re-frame/dispatch [::events/create-sk])}
"... Anonymously"]]
[:li [:a {:on-click #(re-frame/dispatch [::events/login-with-extension])}
"... with Extension"]]]])))
;; Header
(defn new-header []
(let [selected-events @(re-frame/subscribe [::subs/selected-events])]
[:div
[:a {:on-click #(re-frame/dispatch [::events/navigate :home])}
"home"]
"|"
[:a {:on-click #(re-frame/dispatch [::events/navigate :add-resource])}
"add resource"]
"|"
[:a {:on-click #(re-frame/dispatch [::events/navigate :about])}
"about"]
"|"
[:a {:on-click #(re-frame/dispatch [::events/navigate :settings])}
"settings"]
"|"
(if @pk
[:a {:on-click #(re-frame/dispatch [::events/logout])} "logout"]
[:a {:on-click #(re-frame/dispatch [::events/login-with-extension])} "login"])]))
{:class "navbar bg-base-100"}
[:div
{:class "flex-1"}
[:a {:class "btn btn-ghost text-xl"
:on-click #(re-frame/dispatch [::events/navigate [:home]])}
"edufeed"]
[:a {:class "btn btn-circle"
:on-click #(re-frame/dispatch [::events/navigate [:event-feed]])} "Event-Feed"]]
[:div
{:class "flex-none"}
[:a {:class "btn btn-circle"
:on-click #(re-frame/dispatch [::events/navigate [:add-resource]])} "+"]
[shopping-cart]
[user-menu]]]))
;; Keys Panel
(defn keys-panel []
(let [npub @(re-frame/subscribe [::subs/npub])
nsec @(re-frame/subscribe [::subs/nsec])] [:div
[:h1 "Keys"]
[:p (str "Your Npub: " npub)]
[:p (str "Your Nsec: " nsec)]]))
(defmethod routes/panels :keys-panel [] [keys-panel])
;; Add Resource Panel
(defn add-resource-panel []
[:div
[:h1 "Add Resource"]
[add-resource-form]
[add-resource-by-json]])
[add-resource-by-json]
[add-resosurce-by-uri]])
(defmethod routes/panels :add-resource-panel [] [add-resource-panel])
@ -167,12 +422,8 @@
;; Home
(defn home-panel []
(let [name (re-frame/subscribe [::subs/name])
events (re-frame/subscribe [::subs/events])]
(let [events (re-frame/subscribe [::subs/events])]
[:div
[:h1
(str "Hello from " @name ". This is the Home Page.")]
[events-panel]
[:p (count @events)]]))
@ -183,39 +434,56 @@
[:div
[:h1 "This is the About Page."]
[:div
[:a {:on-click #(re-frame/dispatch [::events/navigate :home])}
[:a {:on-click #(re-frame/dispatch [::events/navigate [:home]])}
"go to Home Page"]]])
(defmethod routes/panels :about-panel [] [about-panel])
;; npub
;; Lists
(defn list-component [list]
(let [pk @(re-frame/subscribe [::subs/pk])
ids-in-list (nostr/get-event-ids-from-list list)
events-in-list @(re-frame/subscribe [::subs/events-in-list ids-in-list]) ;; TODO should be sorted after appeareande in tags
]
[:div {:key (:id list)
:class "p-2"}
[:details
{:class "collapse bg-base-200"}
[:summary
{:class "collapse-title text-xl font-medium"}
(nostr/get-list-name list)]
[:div {:class "collapse-content"}
(doall
(for [event events-in-list]
[:div {:key (:id event)}
[metadata-event-component event]
(when pk
[:button {:class "btn btn-warning"
:on-click #(re-frame/dispatch [::events/delete-event-from-list [event list]])}
"Delete Resource from List"])]))
(when pk [:button {:class "btn btn-error"
:on-click #(re-frame/dispatch [::events/delete-list list])} "Delete List"])]]]))
;; npub / Profile
(defn npub-view-panel []
(let [sockets @(re-frame/subscribe [::subs/sockets])
route-params @(re-frame/subscribe [::subs/route-params])
lists @(re-frame/subscribe [::subs/lists])
lists @(re-frame/subscribe [::subs/lists-for-npub])
_ (when (not (seq lists)) (re-frame/dispatch [::events/get-lists-for-npub (:npub route-params)]))
missing-events-from-lists @(re-frame/subscribe [::subs/missing-events-from-lists])
_ (when (seq missing-events-from-lists) (re-frame/dispatch [::events/query-for-event-ids [sockets missing-events-from-lists]]))]
[:div
[:h1 (str "Hello Npub: " (:npub route-params))]
(if-not (seq lists)
[:p "No lists there...yet"]
[:div {:class "p-2"} "Got some lists"
(doall
(for [l lists]
[:div {:class "p-2"
:key (:id l)}
[:li
[:p (str "ID: " (:id l))]
[:p (str "Name: " (first (filter #(= "d" (first %)) (:tags l))))]
;; TODO filter tags for already being in events
;; for all that are not send a query
[:p (str "Tags: " (:tags l))]
[:button {:class "btn btn-error"
:on-click #(re-frame/dispatch [::events/delete-list l])} "Delete List"]]]))])
[:button {:class "btn"
:on-click
#(re-frame/dispatch [::events/get-lists-for-npub [sockets (:npub route-params)]])} "Load lists"]]))
(doall
(for [l lists]
^{:key (:id l)} [list-component l])))
[:button {:class "btn ml-auto mr-0"
:on-click #(re-frame/dispatch [::events/toggle-show-create-list-modal])}
"Create New List"]]))
(defmethod routes/panels :npub-view-panel [] [npub-view-panel])
@ -223,10 +491,10 @@
(defn main-panel []
(let [active-panel (re-frame/subscribe [::subs/active-panel])]
[:div
[header]
[new-header]
[:div
(routes/panels @active-panel)]]))
(routes/panels @active-panel)
[add-to-lists-modal]
[create-list-modal]
[event-data-modal]]]))
(comment
(.log js/console "Hello World"))