mirror of
https://github.com/edufeed-org/edufeed-web.git
synced 2025-12-07 23:34:34 +00:00
add edu-feed
This commit is contained in:
parent
570eed0f41
commit
960200d410
11 changed files with 3755 additions and 230 deletions
2723
package-lock.json
generated
Normal file
2723
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,7 @@
|
|||
"dependencies": {
|
||||
"@noble/secp256k1": "^2.1.0",
|
||||
"bech32": "^2.0.0",
|
||||
"js-confetti": "^0.12.0",
|
||||
"nostr-tools": "^2.7.2",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2"
|
||||
|
|
|
|||
BIN
resources/public/assets/edu-feed-logo.webp
Normal file
BIN
resources/public/assets/edu-feed-logo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
|
|
@ -981,6 +981,70 @@ html {
|
|||
--tw-border-opacity: 0.2;
|
||||
}
|
||||
|
||||
.collapse:not(td):not(tr):not(colgroup) {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
position: relative;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
grid-template-rows: auto 0fr;
|
||||
transition: grid-template-rows 0.2s;
|
||||
width: 100%;
|
||||
border-radius: var(--rounded-box, 1rem);
|
||||
}
|
||||
|
||||
.collapse-title,
|
||||
.collapse > input[type="checkbox"],
|
||||
.collapse > input[type="radio"],
|
||||
.collapse-content {
|
||||
grid-column-start: 1;
|
||||
grid-row-start: 1;
|
||||
}
|
||||
|
||||
.collapse > input[type="checkbox"],
|
||||
.collapse > input[type="radio"] {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.collapse-content {
|
||||
visibility: hidden;
|
||||
grid-column-start: 1;
|
||||
grid-row-start: 2;
|
||||
min-height: 0px;
|
||||
transition: visibility 0.2s;
|
||||
transition: padding 0.2s ease-out,
|
||||
background-color 0.2s ease-out;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
cursor: unset;
|
||||
}
|
||||
|
||||
.collapse[open],
|
||||
.collapse-open,
|
||||
.collapse:focus:not(.collapse-close) {
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.collapse:not(.collapse-close):has(> input[type="checkbox"]:checked),
|
||||
.collapse:not(.collapse-close):has(> input[type="radio"]:checked) {
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.collapse[open] > .collapse-content,
|
||||
.collapse-open > .collapse-content,
|
||||
.collapse:focus:not(.collapse-close) > .collapse-content,
|
||||
.collapse:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-content,
|
||||
.collapse:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-content {
|
||||
visibility: visible;
|
||||
min-height: -moz-fit-content;
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
|
@ -1117,18 +1181,6 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
@supports (color: color-mix(in oklab, black, black)) {
|
||||
.btn-outline.btn-primary:hover {
|
||||
background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
|
||||
|
|
@ -1372,22 +1424,6 @@ html {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-action {
|
||||
display: flex;
|
||||
margin-top: 1.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-toggle {
|
||||
position: fixed;
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) {
|
||||
overflow: hidden;
|
||||
scrollbar-gutter: stable;
|
||||
|
|
@ -1473,6 +1509,20 @@ html {
|
|||
border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity)));
|
||||
}
|
||||
|
||||
.badge-primary {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.badge-outline.badge-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btm-nav > *.disabled,
|
||||
.btm-nav > *[disabled] {
|
||||
pointer-events: none;
|
||||
|
|
@ -1506,10 +1556,6 @@ html {
|
|||
border-color: var(--btn-color, var(--fallback-b2));
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
--btn-color: var(--fallback-p);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
--btn-color: var(--fallback-wa);
|
||||
}
|
||||
|
|
@ -1520,11 +1566,6 @@ html {
|
|||
}
|
||||
|
||||
@supports (color: color-mix(in oklab, black, black)) {
|
||||
.btn-outline.btn-primary.btn-active {
|
||||
background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning.btn-active {
|
||||
background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
|
||||
|
|
@ -1542,17 +1583,7 @@ html {
|
|||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||
outline-color: var(--fallback-p,oklch(var(--p)/1));
|
||||
}
|
||||
|
||||
@supports (color: oklch(0% 0 0)) {
|
||||
.btn-primary {
|
||||
--btn-color: var(--p);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
--btn-color: var(--wa);
|
||||
}
|
||||
|
|
@ -1602,16 +1633,6 @@ html {
|
|||
background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary.btn-active {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning {
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)));
|
||||
|
|
@ -1800,6 +1821,130 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
details.collapse {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
details.collapse summary {
|
||||
position: relative;
|
||||
display: block;
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
details.collapse summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse:focus-visible {
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
outline-offset: 2px;
|
||||
outline-color: var(--fallback-bc,oklch(var(--bc)/1));
|
||||
}
|
||||
|
||||
.collapse:has(.collapse-title:focus-visible),
|
||||
.collapse:has(> input[type="checkbox"]:focus-visible),
|
||||
.collapse:has(> input[type="radio"]:focus-visible) {
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
outline-offset: 2px;
|
||||
outline-color: var(--fallback-bc,oklch(var(--bc)/1));
|
||||
}
|
||||
|
||||
.collapse-arrow > .collapse-title:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 0.5rem;
|
||||
width: 0.5rem;
|
||||
--tw-translate-y: -100%;
|
||||
--tw-rotate: 45deg;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
transition-duration: 0.2s;
|
||||
top: 1.9rem;
|
||||
inset-inline-end: 1.4rem;
|
||||
content: "";
|
||||
transform-origin: 75% 75%;
|
||||
box-shadow: 2px 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.collapse-plus > .collapse-title:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 0.5rem;
|
||||
width: 0.5rem;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
top: 0.9rem;
|
||||
inset-inline-end: 1.4rem;
|
||||
content: "+";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"],
|
||||
.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked),
|
||||
.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title {
|
||||
cursor: unset;
|
||||
}
|
||||
|
||||
.collapse-title {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:where(.collapse > input[type="checkbox"]),
|
||||
:where(.collapse > input[type="radio"]) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.collapse-title,
|
||||
:where(.collapse > input[type="checkbox"]),
|
||||
:where(.collapse > input[type="radio"]) {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
padding-inline-end: 3rem;
|
||||
min-height: 3.75rem;
|
||||
transition: background-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.collapse[open] > :where(.collapse-content),
|
||||
.collapse-open > :where(.collapse-content),
|
||||
.collapse:focus:not(.collapse-close) > :where(.collapse-content),
|
||||
.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content),
|
||||
.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content) {
|
||||
padding-bottom: 1rem;
|
||||
transition: padding 0.2s ease-out,
|
||||
background-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.collapse[open].collapse-arrow > .collapse-title:after,
|
||||
.collapse-open.collapse-arrow > .collapse-title:after,
|
||||
.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after,
|
||||
.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after,
|
||||
.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after {
|
||||
--tw-translate-y: -50%;
|
||||
--tw-rotate: 225deg;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.collapse[open].collapse-plus > .collapse-title:after,
|
||||
.collapse-open.collapse-plus > .collapse-title:after,
|
||||
.collapse-plus:focus:not(.collapse-close) > .collapse-title:after,
|
||||
.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after,
|
||||
.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after {
|
||||
content: "−";
|
||||
}
|
||||
|
||||
.dropdown.dropdown-open .dropdown-content,
|
||||
.dropdown:focus .dropdown-content,
|
||||
.dropdown:focus-within .dropdown-content {
|
||||
|
|
@ -2051,12 +2196,6 @@ html {
|
|||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.modal-action > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
@keyframes modal-pop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
|
@ -2162,10 +2301,6 @@ html {
|
|||
padding-right: 0.438rem;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-circle:where(.btn-xs) {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
|
|
@ -2384,6 +2519,14 @@ html {
|
|||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -2392,6 +2535,10 @@ html {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
.m-1 {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
|
@ -2416,14 +2563,22 @@ html {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-64 {
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.min-h-\[620px\] {
|
||||
min-height: 620px;
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
|
@ -2436,6 +2591,10 @@ html {
|
|||
width: 13rem;
|
||||
}
|
||||
|
||||
.w-64 {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.w-96 {
|
||||
width: 24rem;
|
||||
}
|
||||
|
|
@ -2456,6 +2615,22 @@ html {
|
|||
flex: none;
|
||||
}
|
||||
|
||||
@keyframes flyIn {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-flyIn {
|
||||
animation: flyIn 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -2484,14 +2659,20 @@ html {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.break-all {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
|
@ -2526,11 +2707,26 @@ html {
|
|||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.bg-base-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||
}
|
||||
|
||||
.bg-green-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.object-contain {
|
||||
-o-object-fit: contain;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.object-cover {
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
|
@ -2554,6 +2750,10 @@ html {
|
|||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text-black {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
|
|
@ -2584,3 +2784,7 @@ html {
|
|||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.hover\:underline:hover {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 []})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{}))
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
[]
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,18 @@
|
|||
module.exports = {
|
||||
content: ["./src/**/*.{html,js,cljs}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
keyframes: {
|
||||
flyIn: {
|
||||
'0%': { transform: 'translateX(-100%)', opacity: '0' },
|
||||
'100%': { transform: 'translateX(0)', opacity: '1' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
flyIn: 'flyIn 0.5s ease-out forwards',
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
plugins: [require("daisyui")],
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue