- {events.map((a) => {
+ {filteredEvents.map((a) => {
switch (a.kind) {
case LIVE_STREAM_CHAT: {
return (
diff --git a/src/element/markdown.tsx b/src/element/markdown.tsx
index 0632b97..c867686 100644
--- a/src/element/markdown.tsx
+++ b/src/element/markdown.tsx
@@ -1,200 +1,22 @@
import "./markdown.css";
-import { parseNostrLink } from "@snort/system";
-import type { ReactNode } from "react";
+import { createElement } from "react";
import { useMemo } from "react";
import ReactMarkdown from "react-markdown";
-import { Address } from "element/Address";
-import { Event } from "element/Event";
-import { Mention } from "element/mention";
-import { Emoji } from "element/emoji";
import { HyperText } from "element/hypertext";
-
-const MentionRegex = /(#\[\d+\])/gi;
-const NostrPrefixRegex = /^nostr:/;
-const EmojiRegex = /:([\w-]+):/g;
-
-function extractEmoji(fragments: Fragment[], tags: string[][]) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(EmojiRegex).map((i) => {
- const t = tags.find((a) => a[0] === "emoji" && a[1] === i);
- if (t) {
- return
;
- } else {
- return i;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function extractMentions(fragments, tags) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(MentionRegex).map((match) => {
- const matchTag = match.match(/#\[(\d+)\]/);
- if (matchTag && matchTag.length === 2) {
- const idx = parseInt(matchTag[1]);
- const ref = tags?.find((a, i) => i === idx);
- if (ref) {
- switch (ref[0]) {
- case "p": {
- return
;
- }
- case "a": {
- return
;
- }
- default:
- // todo: e and t mentions
- return ref[1];
- }
- }
- return null;
- } else {
- return match;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function extractNprofiles(fragments) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(/(nostr:nprofile1[a-z0-9]+)/g).map((i) => {
- if (i.startsWith("nostr:nprofile1")) {
- try {
- const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
- return
;
- } catch (error) {
- return i;
- }
- } else {
- return i;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function extractNpubs(fragments) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(/(nostr:npub1[a-z0-9]+)/g).map((i) => {
- if (i.startsWith("nostr:npub1")) {
- try {
- const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
- return
;
- } catch (error) {
- return i;
- }
- } else {
- return i;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function extractNevents(fragments) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(/(nostr:nevent1[a-z0-9]+)/g).map((i) => {
- if (i.startsWith("nostr:nevent1")) {
- try {
- const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
- return
;
- } catch (error) {
- return i;
- }
- } else {
- return i;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function extractNaddrs(fragments) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(/(nostr:naddr1[a-z0-9]+)/g).map((i) => {
- if (i.startsWith("nostr:naddr1")) {
- try {
- const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
- return
;
- } catch (error) {
- console.error(error);
- return i;
- }
- } else {
- return i;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function extractNoteIds(fragments) {
- return fragments
- .map((f) => {
- if (typeof f === "string") {
- return f.split(/(nostr:note1[a-z0-9]+)/g).map((i) => {
- if (i.startsWith("nostr:note1")) {
- try {
- const link = parseNostrLink(i.replace(NostrPrefixRegex, ""));
- return
;
- } catch (error) {
- return i;
- }
- } else {
- return i;
- }
- });
- }
- return f;
- })
- .flat();
-}
-
-function transformText(ps, tags) {
- let fragments = extractMentions(ps, tags);
- fragments = extractNprofiles(fragments);
- fragments = extractNevents(fragments);
- fragments = extractNaddrs(fragments);
- fragments = extractNoteIds(fragments);
- fragments = extractNpubs(fragments);
- fragments = extractEmoji(fragments, tags);
-
- return fragments;
-}
+import { transformText } from "element/text";
interface MarkdownProps {
- children: ReactNode;
+ content: string;
tags?: string[];
}
-export function Markdown({ children, tags = [] }: MarkdownProps) {
+export function Markdown({
+ content,
+ tags = [],
+ element = "div",
+}: MarkdownProps) {
const components = useMemo(() => {
return {
li: ({ children, ...props }) => {
@@ -202,15 +24,15 @@ export function Markdown({ children, tags = [] }: MarkdownProps) {
},
td: ({ children }) =>
children &&
{transformText(children, tags)} | ,
- p: ({ children }) => children &&
{transformText(children, tags)}
,
+ p: ({ children }) =>
{transformText(children, tags)}
,
a: (props) => {
return
{props.children};
},
};
}, [tags]);
- return (
-
-
-
+ return createElement(
+ element,
+ { className: "markdown" },
+
{content},
);
}
diff --git a/src/element/mute-button.tsx b/src/element/mute-button.tsx
new file mode 100644
index 0000000..4206b29
--- /dev/null
+++ b/src/element/mute-button.tsx
@@ -0,0 +1,63 @@
+import { useLogin } from "hooks/login";
+import AsyncButton from "element/async-button";
+import { Login, System } from "index";
+import { MUTED } from "const";
+
+export function LoggedInMuteButton({ pubkey }: { pubkey: string }) {
+ const login = useLogin();
+ const tags = login.muted.tags;
+ const muted = tags.filter((t) => t.at(0) === "p");
+ const isMuted = muted.find((t) => t.at(1) === pubkey);
+
+ async function unmute() {
+ const pub = login?.publisher();
+ if (pub) {
+ const newMuted = tags.filter((t) => t.at(1) !== pubkey);
+ const ev = await pub.generic((eb) => {
+ eb.kind(MUTED).content(login.muted.content);
+ for (const t of newMuted) {
+ eb.tag(t);
+ }
+ return eb;
+ });
+ console.debug(ev);
+ System.BroadcastEvent(ev);
+ Login.setMuted(newMuted, login.muted.content, ev.created_at);
+ }
+ }
+
+ async function mute() {
+ const pub = login?.publisher();
+ if (pub) {
+ const newMuted = [...tags, ["p", pubkey]];
+ const ev = await pub.generic((eb) => {
+ eb.kind(MUTED).content(login.muted.content);
+ for (const tag of newMuted) {
+ eb.tag(tag);
+ }
+ return eb;
+ });
+ console.debug(ev);
+ System.BroadcastEvent(ev);
+ Login.setMuted(newMuted, login.muted.content, ev.created_at);
+ }
+ }
+
+ return (
+
+ {isMuted ? "Unmute" : "Mute"}
+
+ );
+}
+
+export function MuteButton({ pubkey }: { pubkey: string }) {
+ const login = useLogin();
+ return login?.pubkey ? (
+
+ ) : null;
+}
diff --git a/src/element/note.css b/src/element/note.css
index 2fb260e..c2289f7 100644
--- a/src/element/note.css
+++ b/src/element/note.css
@@ -4,6 +4,11 @@
border-radius: 10px;
}
+.note .note-header {
+ display: flex;
+ justify-content: space-between;
+}
+
.note .note-header .profile {
font-size: 14px;
}
@@ -17,6 +22,11 @@
margin-left: 30px;
}
-.note .note-content .markdown > p {
+.note .note-content .markdown > * {
font-size: 14px;
}
+
+.note .note-content .markdown > ul,
+.note .note-content .markdown ol {
+ margin-left: 30px;
+}
diff --git a/src/element/note.tsx b/src/element/note.tsx
index b79987d..ddf93fb 100644
--- a/src/element/note.tsx
+++ b/src/element/note.tsx
@@ -2,6 +2,7 @@ import "./note.css";
import { type NostrEvent } from "@snort/system";
import { Markdown } from "element/markdown";
+import { ExternalIconLink } from "element/external-link";
import { Profile } from "element/profile";
export function Note({ ev }: { ev: NostrEvent }) {
@@ -9,9 +10,10 @@ export function Note({ ev }: { ev: NostrEvent }) {
);
diff --git a/src/element/send-zap.tsx b/src/element/send-zap.tsx
index 07db3dc..5762cfb 100644
--- a/src/element/send-zap.tsx
+++ b/src/element/send-zap.tsx
@@ -9,10 +9,10 @@ import { bytesToHex } from "@noble/curves/abstract/utils";
import { formatSats } from "../number";
import { Icon } from "./icon";
import AsyncButton from "./async-button";
-import { Relays } from "index";
import QrCode from "./qr-code";
import { useLogin } from "hooks/login";
import Copy from "./copy";
+import { defaultRelays } from "const";
export interface LNURLLike {
get name(): string;
@@ -21,7 +21,7 @@ export interface LNURLLike {
getInvoice(
amountInSats: number,
comment?: string,
- zap?: NostrEvent
+ zap?: NostrEvent,
): Promise<{ pr?: string }>;
}
@@ -55,7 +55,7 @@ export function SendZaps({
const [comment, setComment] = useState("");
const [invoice, setInvoice] = useState("");
const login = useLogin();
-
+ const relays = Object.keys(defaultRelays);
const name = targetName ?? svc?.name;
async function loadService(lnurl: string) {
const s = new LNURL(lnurl);
@@ -78,7 +78,9 @@ export function SendZaps({
let pub = login?.publisher();
let isAnon = false;
if (!pub) {
- pub = EventPublisher.privateKey(bytesToHex(secp256k1.utils.randomPrivateKey()));
+ pub = EventPublisher.privateKey(
+ bytesToHex(secp256k1.utils.randomPrivateKey()),
+ );
isAnon = true;
}
@@ -88,7 +90,7 @@ export function SendZaps({
zap = await pub.zap(
amountInSats * 1000,
pubkey,
- Relays,
+ relays,
undefined,
comment,
(eb) => {
@@ -102,7 +104,7 @@ export function SendZaps({
eb.tag(["anon", ""]);
}
return eb;
- }
+ },
);
}
const invoice = await svc.getInvoice(amountInSats, comment, zap);
diff --git a/src/element/stream-cards.tsx b/src/element/stream-cards.tsx
index 9a53cd9..e7b8c83 100644
--- a/src/element/stream-cards.tsx
+++ b/src/element/stream-cards.tsx
@@ -9,10 +9,10 @@ import type { NostrEvent } from "@snort/system";
import { Toggle } from "element/toggle";
import { useLogin } from "hooks/login";
-import { useCards } from "hooks/cards";
+import { useCards, useUserCards } from "hooks/cards";
import { CARD, USER_CARDS } from "const";
import { toTag } from "utils";
-import { System } from "index";
+import { Login, System } from "index";
import { findTag } from "utils";
import { Icon } from "./icon";
import { ExternalLink } from "./external-link";
@@ -55,7 +55,7 @@ const CardPreview = forwardRef(
) : (
))}
-
+
);
},
@@ -130,6 +130,7 @@ function Card({ canEdit, ev, cards }: CardProps) {
});
console.debug(userCardsEv);
System.BroadcastEvent(userCardsEv);
+ Login.setCards(newTags, userCardsEv.created_at);
},
}),
[canEdit, tags, identifier],
@@ -278,18 +279,18 @@ function EditCard({ card, cards }: EditCardProps) {
async function onCancel() {
const pub = login?.publisher();
if (pub) {
+ const newTags = tags.filter((t) => !t.at(1).endsWith(`:${identifier}`));
const userCardsEv = await pub.generic((eb) => {
eb.kind(USER_CARDS).content("");
- for (const tag of tags) {
- if (!tag.at(1).endsWith(`:${identifier}`)) {
- eb.tag(tag);
- }
+ for (const tag of newTags) {
+ eb.tag(tag);
}
return eb;
});
console.debug(userCardsEv);
System.BroadcastEvent(userCardsEv);
+ Login.setCards(newTags, userCardsEv.created_at);
setIsOpen(false);
}
}
@@ -381,12 +382,11 @@ function AddCard({ cards }: AddCardProps) {
);
}
-export function StreamCards({ host }) {
+export function StreamCardEditor() {
const login = useLogin();
- const canEdit = login?.pubkey === host;
- const cards = useCards(host, canEdit);
+ const cards = useUserCards(login.pubkey, login.cards.tags, true);
const [isEditing, setIsEditing] = useState(false);
- const components = (
+ return (
<>