diff --git a/packages/app/src/Element/Note.tsx b/packages/app/src/Element/Note.tsx index 04925cc..eac4178 100644 --- a/packages/app/src/Element/Note.tsx +++ b/packages/app/src/Element/Note.tsx @@ -11,7 +11,15 @@ import Pin from "Icons/Pin"; import { parseZap } from "Element/Zap"; import ProfileImage from "Element/ProfileImage"; import Text from "Element/Text"; -import { eventLink, getReactions, dedupeByPubkey, hexToBech32, normalizeReaction, Reaction } from "Util"; +import { + eventLink, + getReactions, + dedupeByPubkey, + tagFilterOfTextRepost, + hexToBech32, + normalizeReaction, + Reaction, +} from "Util"; import NoteFooter, { Translation } from "Element/NoteFooter"; import NoteTime from "Element/NoteTime"; import { useUserProfiles } from "Feed/ProfileFeed"; @@ -98,7 +106,14 @@ export default function Note(props: NoteProps) { }, [reactions]); const positive = groupReactions[Reaction.Positive]; const negative = groupReactions[Reaction.Negative]; - const reposts = useMemo(() => dedupeByPubkey(getReactions(related, ev.Id, EventKind.Repost)), [related, ev]); + const reposts = useMemo( + () => + dedupeByPubkey([ + ...getReactions(related, ev.Id, EventKind.TextNote).filter(e => e.tags.some(tagFilterOfTextRepost(e, ev.Id))), + ...getReactions(related, ev.Id, EventKind.Repost), + ]), + [related, ev] + ); const zaps = useMemo(() => { const sortedZaps = getReactions(related, ev.Id, EventKind.ZapReceipt) .map(parseZap) diff --git a/packages/app/src/Element/NoteReaction.tsx b/packages/app/src/Element/NoteReaction.tsx index 7b4091e..c1de29b 100644 --- a/packages/app/src/Element/NoteReaction.tsx +++ b/packages/app/src/Element/NoteReaction.tsx @@ -30,7 +30,12 @@ export default function NoteReaction(props: NoteReactionProps) { return null; }, [ev]); - if (ev.Kind !== EventKind.Reaction && ev.Kind !== EventKind.Repost) { + if ( + ev.Kind !== EventKind.Reaction && + ev.Kind !== EventKind.Repost && + (ev.Kind !== EventKind.TextNote || + ev.Tags.every((a, i) => a.Event !== refEvent || a.Marker !== "mention" || ev.Content !== `#[${i}]`)) + ) { return null; } @@ -52,7 +57,7 @@ export default function NoteReaction(props: NoteReactionProps) { const root = extractRoot(); const isOpMuted = root && isMuted(root.pubkey); const opt = { - showHeader: ev?.Kind === EventKind.Repost, + showHeader: ev?.Kind === EventKind.Repost || ev?.Kind === EventKind.TextNote, showFooter: false, }; diff --git a/packages/app/src/Element/Timeline.tsx b/packages/app/src/Element/Timeline.tsx index aa7c321..d5b2999 100644 --- a/packages/app/src/Element/Timeline.tsx +++ b/packages/app/src/Element/Timeline.tsx @@ -4,7 +4,7 @@ import { useCallback, useMemo } from "react"; import { useInView } from "react-intersection-observer"; import ArrowUp from "Icons/ArrowUp"; -import { dedupeByPubkey } from "Util"; +import { dedupeByPubkey, tagFilterOfTextRepost } from "Util"; import ProfileImage from "Element/ProfileImage"; import useTimelineFeed, { TimelineSubject } from "Feed/TimelineFeed"; import { TaggedRawEvent } from "@snort/nostr"; @@ -72,6 +72,10 @@ export default function Timeline({ return } pubkey={e.pubkey} className="card" />; } case EventKind.TextNote: { + const eRef = e.tags.find(tagFilterOfTextRepost(e))?.at(1); + if (eRef) { + return a.id === eRef)} />; + } return ; } case EventKind.ZapReceipt: { diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index a52f5b4..e8b16e9 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { u256 } from "@snort/nostr"; import { EventKind, Subscriptions } from "@snort/nostr"; -import { unixNow, unwrap } from "Util"; +import { unixNow, unwrap, tagFilterOfTextRepost } from "Util"; import useSubscription from "Feed/Subscription"; import { useSelector } from "react-redux"; import { RootState } from "State/Store"; @@ -147,11 +147,19 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel } return s; }); - const reposts = main.store.notes + const repostsByKind6 = main.store.notes .filter(a => a.kind === EventKind.Repost && a.content === "") .map(a => a.tags.find(b => b[0] === "e")) .filter(a => a) .map(a => unwrap(a)[1]); + const repostsByKind1 = main.store.notes + .filter( + a => (a.kind === EventKind.Repost || a.kind === EventKind.TextNote) && a.tags.some(tagFilterOfTextRepost(a)) + ) + .map(a => a.tags.find(tagFilterOfTextRepost(a))) + .filter(a => a) + .map(a => unwrap(a)[1]); + const reposts = [...repostsByKind6, ...repostsByKind1]; if (reposts.length > 0) { setTrackingParentEvents(s => { if (reposts.some(a => !s.includes(a))) { diff --git a/packages/app/src/Notifications.ts b/packages/app/src/Notifications.ts index 1c1ef3f..09c5a8f 100644 --- a/packages/app/src/Notifications.ts +++ b/packages/app/src/Notifications.ts @@ -6,10 +6,14 @@ import type { NotificationRequest } from "State/Login"; import { MetadataCache, UsersDb } from "State/Users"; import { getDisplayName } from "Element/ProfileImage"; import { MentionRegex } from "Const"; +import { tagFilterOfTextRepost } from "Util"; export async function makeNotification(db: UsersDb, ev: TaggedRawEvent): Promise { switch (ev.kind) { case EventKind.TextNote: { + if (ev.tags.some(tagFilterOfTextRepost(ev))) { + return null; + } const pubkeys = new Set([ev.pubkey, ...ev.tags.filter(a => a[0] === "p").map(a => a[1])]); const users = await db.bulkGet(Array.from(pubkeys)); const fromUser = users.find(a => a?.pubkey === ev.pubkey); diff --git a/packages/app/src/Util.ts b/packages/app/src/Util.ts index 7456ee5..1ebe29d 100644 --- a/packages/app/src/Util.ts +++ b/packages/app/src/Util.ts @@ -191,3 +191,8 @@ export function getNewest(rawNotes: TaggedRawEvent[]) { return notes[0]; } } + +export function tagFilterOfTextRepost(note: TaggedRawEvent, id?: u256): (tag: string[], i: number) => boolean { + return (tag, i) => + tag[0] === "e" && tag[3] === "mention" && note.content === `#[${i}]` && (id ? tag[1] === id : true); +}