diff --git a/ideas.txt b/ideas.txt new file mode 100644 index 0000000..6b1479b --- /dev/null +++ b/ideas.txt @@ -0,0 +1,89 @@ +1. When I open a ThreadDrawer, the "Replying to:..." blurb at the top should render as full event. And, if that replied-to event is also a reply, it's OP should also be rendered as a full event. And so on, up the hierarchy, until we get to an event that isn't a reference or reply to any other (no e-tag or q-tag or a-tag). I want to see the entire discussion tree, so that the event I clicked in the Feed view is displayed in complete context. + +2. Fix the Threads list loading so slowly. I should immediately be seeing what is in cache, and then you update the cache and add anything missing, in a second sweep. And make sure updating doesn't cause the page the jump around or create endless loops. + +3. Make sure that pinning and bookmarking (from the event "..." menu) actually create/update and publish the list events. + +4. Add a delete event menu item to the event "..." menu, that publishes a deletion request to all available relays. + +5. Always render a pretty OpenGraph card, for URLs, if they provide one. Unless the URL is in the middle of a list, paragraph, or otherwise part of some larger structure. + +6. Make sure that highlights work, according to NIP-84. refer to ../jumble for a working version. + +Some example events: + +{ + "id": "93bea17f71ed9ea7f6832e3be7e617b3387e0700193cfcebaf3ffbc2e6f48a7f", + "pubkey": "17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4", + "created_at": 1769023343, + "kind": 9802, + "tags": [ + [ + "e", + "6f854ade40cf3f24046249e650f55b33add3ee1526c00cc93cc7dfc80b8dc121", + "source" + ] + ], + "content": "not real wisdom, being a pretense of knowing the unknown", + "sig": "150279e733e16fa85439916f9f5b8108898a35cbf18062638dfc94e7a38f4a2faae8ce918750ef327fc16b7e7ca8739b1e8aff3b9dd238363d08eec423abba83" +} + +{ + "id": "1cd2017dd33a2efddffb9814c1993cf62e6d8a8e2e90af40973b6d4d1ea509f0", + "pubkey": "a9434ee165ed01b286becfc2771ef1705d3537d051b387288898cc00d5c885be", + "created_at": 1769288219, + "kind": 9802, + "tags": [ + [ + "p", + "a9434ee165ed01b286becfc2771ef1705d3537d051b387288898cc00d5c885be" + ], + [ + "a", + "30023:a9434ee165ed01b286becfc2771ef1705d3537d051b387288898cc00d5c885be:comparing-community-specs" + ], + [ + "context", + "A single publication can be targeted to up to 12 communities via one Targeted Publication event. The creator's intended audience is explicit and transparent — anyone can see which communities a piece of content was meant for. This can serve as an organic disovery route for related Communities + lowers the bar for bootstrapping new ones." + ], + [ + "alt", + "This highlight was made by https://primal.net web client" + ] + ], + "content": " The creator's intended audience is explicit and transparent — anyone can see which communities a piece of content was meant for. This can serve as an organic disovery route for related Communities + lowers the bar for bootstrapping new ones.", + "sig": "b490a12fbc1ab0063c6ddb3ae091212a4fcf76fdf9581d5f0291f24a9443b45d9f11d70e8035ea9c61b95ad47952c46ceeffa6dbb0fa5351bc51aad2e3d54add" +} + +In the first highlight event, there is simply the content field, which should be rendered as a quote, with a link to the original source (event or URL) below it. If the URL provides OpenGraph data, display it and add the hyperlink to it. For events: display a card with "A note from: " and then the "title", "image", and "summary" tags, if available. Make the card a clickable hyperlink to the event's /event page. + +7. Make #hashtags and t-tag topic buttons clickable. Clicking on one should launch a /topics/nameOfHashtag page, that reveals an event list of everything on the relays that includes that topic as a hashtag or a t-tag. + +8. Display a metadata card, at the top of the page, when rendering any replaceable event in /event . Render tags like "image", "description", "summary", "author", "title", etc. + +9. Add an Asciidoctor library to the packages. Use that for rendering kinds 30818 and 30041. All other kinds use Markdown. + +10. If a /event page is opened for a 30040 event, make sure that you analyze and then lazy-load the entire event-index hierarchy (see ../nips-silberengel/NKBIP-01.adoc) into the cache and then into the view. The index can use a-tags or e-tags, or a mix of both. Handle both types of tags and make sure to render the events in the original order. Retry any missing events, after the first loading pass, but don't loop infinitely. + +11. Display a metadata card, at the top, for the OP 30040. Only display metadata for nested events, if they differ from the OP. + +12. Please note that kind 30040 events typically contain 30041s, but they can actually contain any type of event the creator wants. Make sure to render each one according to its kind (markdown or asciidoc). + +13. Both the metadata card and the section events should have their "title" displayed (if none is provided, render the d-tag without hyphens and in Title Case) and have a "..." menu. The section events should have a new menu item: "Open in a new window" that opens the section as a /event in the browser. The index OP should have a new menu item: "Label this as a book" that creates a "general" 1985 label with "booklist". + +14. If an event opened in /event has been highlighted, render the highlight on the displayed text. (for 30040s, this needs to run after the publication has finished loading, or it won't find the text). Hovering over the highlight should display the user-badge of the person who created the highlight, with a button "View the highlight". Clicking the button should make the highlight open to the right, in a thread panel. + +15. There should be a /replaceable/d-tag-placed-here url path that searches for all replaceable events that have that d-tag and lists them in a list. Clicking one should display it in thread-panel on the right. + +16. Add a main menu item, to the right of Feeds: Write +it should open to a page offering two choicees: find an existing event to edit, create a new event + +Clicking find should then demand they enter an event id (hex id, nevent, naddr, note) and click "Find". +The event should be searched for in cache and then the relays, (return the newest version found) and the json rendered, below a hyperlink to the related /event page. +They should be able to click an "Edit" button, and then the event is displayed as a form, where they can add/edit/delete tags and change the content. Don't render id, kind, pubkey, sig, created_at as those are to be generated when they click "Publish". Publish to cache and to the standard write-relays. Publishing should reveal the standard success/failure message for the relays. If none were successful, allow them to attempt to republish from cache. If successful, wait 5 seconds and then, open the event in the /event page. + +Clicking create should ask them to enter a kind they would like to write: 1, 11, 9802, 1222, 20, 21, 22, 30023, 30818, 30817, 30041, 30040 (metadata-only, no sections added, they can do that manually in the edit function, add that as a help-text), 1068 + +17. If the user is looking at their own profile page, display a menu item "Adjust profile events" that opens a left-side panel that allows them select one of the following events to create/update: 0, 3, 30315, 10133, 10002, 10432, 10001, 10003, 10895, 10015, 10030, 30030, 10000, 30008. Selecting one should open an appropriate form and preload it with any event found in cache or on the relays. Publish to cache and to the standard write-relays. Publishing should reveal the standard success/failure message for the relays. If none were successful, allow them to attempt to republish from cache. If successful, wait 5 seconds and then, open the event in the /event page. + +18. Make sure the /event page can handle metadata-only (no "content") events gracefully, displaying their tag-lists. diff --git a/public/healthz.json b/public/healthz.json index ada9cc0..cfb080d 100644 --- a/public/healthz.json +++ b/public/healthz.json @@ -2,7 +2,7 @@ "status": "ok", "service": "aitherboard", "version": "0.1.0", - "buildTime": "2026-02-04T04:42:40.028Z", + "buildTime": "2026-02-04T07:23:20.145Z", "gitCommit": "unknown", - "timestamp": 1770180160028 + "timestamp": 1770189800145 } \ No newline at end of file diff --git a/src/lib/components/EventMenu.svelte b/src/lib/components/EventMenu.svelte index c659faa..9367959 100644 --- a/src/lib/components/EventMenu.svelte +++ b/src/lib/components/EventMenu.svelte @@ -14,6 +14,9 @@ toggleHighlight } from '../services/user-actions.js'; import { eventMenuStore } from '../services/event-menu-store.js'; + import { sessionManager } from '../services/auth/session-manager.js'; + import RelatedEventsModal from './modals/RelatedEventsModal.svelte'; + import { KIND } from '../types/kind-lookup.js'; interface Props { event: NostrEvent; @@ -24,6 +27,7 @@ let menuOpen = $state(false); let jsonModalOpen = $state(false); + let relatedEventsModalOpen = $state(false); let publicationModalOpen = $state(false); let publicationResults = $state<{ success: string[]; failed: Array<{ relay: string; error: string }> } | null>(null); let broadcasting = $state(false); @@ -36,7 +40,10 @@ let menuId = $derived(event.id); // Check if this is a note with content (kind 1 or kind 11) - let isContentNote = $derived(event.kind === 1 || event.kind === 11); + let isContentNote = $derived(event.kind === KIND.SHORT_TEXT_NOTE || event.kind === KIND.DISCUSSION_THREAD); + + // Check if user is logged in + let isLoggedIn = $derived(sessionManager.isLoggedIn()); // Track pin/bookmark/highlight state let pinnedState = $state(false); @@ -172,6 +179,11 @@ closeMenu(); } + function viewRelatedEvents() { + relatedEventsModalOpen = true; + closeMenu(); + } + async function broadcastEvent() { broadcasting = true; closeMenu(); @@ -260,6 +272,11 @@ + {#if isLoggedIn} + + {/if} @@ -296,6 +313,7 @@ + diff --git a/src/lib/modules/comments/CommentForm.svelte b/src/lib/modules/comments/CommentForm.svelte index 3ffcd34..e78e49c 100644 --- a/src/lib/modules/comments/CommentForm.svelte +++ b/src/lib/modules/comments/CommentForm.svelte @@ -44,17 +44,17 @@ // If replying to a parent event, check its kind if (parentEvent) { // If parent is kind 1, reply with kind 1 - if (parentEvent.kind === 1) return 1; + if (parentEvent.kind === KIND.SHORT_TEXT_NOTE) return KIND.SHORT_TEXT_NOTE; // Everything else gets kind 1111 - return 1111; + return KIND.COMMENT; } // If replying to root, check root kind if (rootEvent) { // If root is kind 1, reply with kind 1 - if (rootEvent.kind === 1) return 1; + if (rootEvent.kind === KIND.SHORT_TEXT_NOTE) return KIND.SHORT_TEXT_NOTE; // Everything else gets kind 1111 - return 1111; + return KIND.COMMENT; } // Default to kind 1111 if we can't determine diff --git a/src/lib/modules/comments/CommentThread.svelte b/src/lib/modules/comments/CommentThread.svelte index c01e466..9f727c9 100644 --- a/src/lib/modules/comments/CommentThread.svelte +++ b/src/lib/modules/comments/CommentThread.svelte @@ -7,6 +7,7 @@ import { relayManager } from '../../services/nostr/relay-manager.js'; import { onMount } from 'svelte'; import type { NostrEvent } from '../../types/nostr.js'; + import { KIND } from '../../types/kind-lookup.js'; interface Props { threadId: string; // The event ID of the root event @@ -25,7 +26,7 @@ let nestedSubscriptionActive = $state(false); // Track if nested subscription is active let isProcessingUpdate = $state(false); // Prevent recursive update processing - const isKind1 = $derived(event?.kind === 1); + const isKind1 = $derived(event?.kind === KIND.SHORT_TEXT_NOTE); const rootKind = $derived(event?.kind || null); onMount(async () => { @@ -76,7 +77,7 @@ */ function getParentEventId(replyEvent: NostrEvent): string | null { // For kind 1111, check both uppercase and lowercase E and A tags - if (replyEvent.kind === 1111) { + if (replyEvent.kind === KIND.COMMENT) { // Check uppercase E tag first (NIP-22 standard for root) const eTag = replyEvent.tags.find((t) => t[0] === 'E'); if (eTag && eTag[1]) { @@ -112,7 +113,7 @@ } // For kind 1, 1244, 9735: check e tag - if (replyEvent.kind === 1 || replyEvent.kind === 1244 || replyEvent.kind === 9735) { + if (replyEvent.kind === KIND.SHORT_TEXT_NOTE || replyEvent.kind === KIND.VOICE_REPLY || replyEvent.kind === KIND.ZAP_RECEIPT) { // For kind 1, check all e tags (NIP-10) const eTags = replyEvent.tags.filter((t) => t[0] === 'e' && t[1] && t[1] !== replyEvent.id); // Prefer e tag with 'reply' marker, otherwise use first e tag @@ -130,7 +131,7 @@ * For other kinds: checks e tag */ function referencesRoot(replyEvent: NostrEvent): boolean { - if (replyEvent.kind === 1111) { + if (replyEvent.kind === KIND.COMMENT) { // Check uppercase E tag (NIP-22 standard for root) const eTag = replyEvent.tags.find((t) => t[0] === 'E'); if (eTag && eTag[1] === threadId) return true; @@ -201,16 +202,16 @@ } // Add the reply to the appropriate map - if (reply.kind === 1111) { + if (reply.kind === KIND.COMMENT) { commentsMap.set(reply.id, reply); hasNewReplies = true; - } else if (reply.kind === 1) { + } else if (reply.kind === KIND.SHORT_TEXT_NOTE) { kind1RepliesMap.set(reply.id, reply); hasNewReplies = true; - } else if (reply.kind === 1244) { + } else if (reply.kind === KIND.VOICE_REPLY) { yakBacksMap.set(reply.id, reply); hasNewReplies = true; - } else if (reply.kind === 9735) { + } else if (reply.kind === KIND.ZAP_RECEIPT) { zapReceiptsMap.set(reply.id, reply); hasNewReplies = true; } @@ -236,14 +237,14 @@ } const allRelays = relayManager.getProfileReadRelays(); - const replyFilters: any[] = [ - { kinds: [1111], '#e': [threadId] }, - { kinds: [1111], '#E': [threadId] }, - { kinds: [1111], '#a': [threadId] }, - { kinds: [1111], '#A': [threadId] }, - { kinds: [1], '#e': [threadId] }, - { kinds: [1244], '#e': [threadId] }, - { kinds: [9735], '#e': [threadId] } + const replyFilters: any[] = [ + { kinds: [KIND.COMMENT], '#e': [threadId] }, + { kinds: [KIND.COMMENT], '#E': [threadId] }, + { kinds: [KIND.COMMENT], '#a': [threadId] }, + { kinds: [KIND.COMMENT], '#A': [threadId] }, + { kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId] }, + { kinds: [KIND.VOICE_REPLY], '#e': [threadId] }, + { kinds: [KIND.ZAP_RECEIPT], '#e': [threadId] } ]; // fetchEvents with useCache:true returns cached data immediately if available, @@ -276,10 +277,10 @@ const rootReplies = allReplies.filter(reply => referencesRoot(reply)); // Separate by type - comments = rootReplies.filter(e => e.kind === 1111); - kind1Replies = rootReplies.filter(e => e.kind === 1); - yakBacks = rootReplies.filter(e => e.kind === 1244); - zapReceipts = rootReplies.filter(e => e.kind === 9735); + comments = rootReplies.filter(e => e.kind === KIND.COMMENT); + kind1Replies = rootReplies.filter(e => e.kind === KIND.SHORT_TEXT_NOTE); + yakBacks = rootReplies.filter(e => e.kind === KIND.VOICE_REPLY); + zapReceipts = rootReplies.filter(e => e.kind === KIND.ZAP_RECEIPT); loading = false; // Hide loading now that we have data (cached or fresh) @@ -320,11 +321,11 @@ // Use a single subscription that covers all reply IDs const nestedFilters: any[] = [ - { kinds: [1111], '#e': Array.from(allReplyIds) }, - { kinds: [1111], '#E': Array.from(allReplyIds) }, - { kinds: [1], '#e': Array.from(allReplyIds) }, - { kinds: [1244], '#e': Array.from(allReplyIds) }, - { kinds: [9735], '#e': Array.from(allReplyIds) } + { kinds: [KIND.COMMENT], '#e': Array.from(allReplyIds) }, + { kinds: [KIND.COMMENT], '#E': Array.from(allReplyIds) }, + { kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(allReplyIds) }, + { kinds: [KIND.VOICE_REPLY], '#e': Array.from(allReplyIds) }, + { kinds: [KIND.ZAP_RECEIPT], '#e': Array.from(allReplyIds) } ]; nostrClient.fetchEvents( @@ -361,16 +362,16 @@ if (allReplyIds.size > 0) { const nestedFilters: any[] = [ // Fetch nested kind 1111 comments - check both e/E and a/A tags - { kinds: [1111], '#e': Array.from(allReplyIds) }, - { kinds: [1111], '#E': Array.from(allReplyIds) }, - { kinds: [1111], '#a': Array.from(allReplyIds) }, - { kinds: [1111], '#A': Array.from(allReplyIds) }, + { kinds: [KIND.COMMENT], '#e': Array.from(allReplyIds) }, + { kinds: [KIND.COMMENT], '#E': Array.from(allReplyIds) }, + { kinds: [KIND.COMMENT], '#a': Array.from(allReplyIds) }, + { kinds: [KIND.COMMENT], '#A': Array.from(allReplyIds) }, // Fetch nested kind 1 replies - { kinds: [1], '#e': Array.from(allReplyIds) }, + { kinds: [KIND.SHORT_TEXT_NOTE], '#e': Array.from(allReplyIds) }, // Fetch nested yak backs - { kinds: [1244], '#e': Array.from(allReplyIds) }, + { kinds: [KIND.VOICE_REPLY], '#e': Array.from(allReplyIds) }, // Fetch nested zap receipts - { kinds: [9735], '#e': Array.from(allReplyIds) } + { kinds: [KIND.ZAP_RECEIPT], '#e': Array.from(allReplyIds) } ]; const nestedReplies = await nostrClient.fetchEvents( @@ -381,16 +382,16 @@ // Add new replies by type for (const reply of nestedReplies) { - if (reply.kind === 1111 && !comments.some(c => c.id === reply.id)) { + if (reply.kind === KIND.COMMENT && !comments.some(c => c.id === reply.id)) { comments.push(reply); hasNewReplies = true; - } else if (reply.kind === 1 && !kind1Replies.some(r => r.id === reply.id)) { + } else if (reply.kind === KIND.SHORT_TEXT_NOTE && !kind1Replies.some(r => r.id === reply.id)) { kind1Replies.push(reply); hasNewReplies = true; - } else if (reply.kind === 1244 && !yakBacks.some(y => y.id === reply.id)) { + } else if (reply.kind === KIND.VOICE_REPLY && !yakBacks.some(y => y.id === reply.id)) { yakBacks.push(reply); hasNewReplies = true; - } else if (reply.kind === 9735 && !zapReceipts.some(z => z.id === reply.id)) { + } else if (reply.kind === KIND.ZAP_RECEIPT && !zapReceipts.some(z => z.id === reply.id)) { zapReceipts.push(reply); hasNewReplies = true; } @@ -536,21 +537,21 @@ // Always fetch kind 1111 comments - check both e and E tags, and a and A tags replyFilters.push( - { kinds: [1111], '#e': [threadId] }, // Lowercase e tag - { kinds: [1111], '#E': [threadId] }, // Uppercase E tag (NIP-22) - { kinds: [1111], '#a': [threadId] }, // Lowercase a tag (some clients use wrong tags) - { kinds: [1111], '#A': [threadId] } // Uppercase A tag (NIP-22 for addressable events) + { kinds: [KIND.COMMENT], '#e': [threadId] }, // Lowercase e tag + { kinds: [KIND.COMMENT], '#E': [threadId] }, // Uppercase E tag (NIP-22) + { kinds: [KIND.COMMENT], '#a': [threadId] }, // Lowercase a tag (some clients use wrong tags) + { kinds: [KIND.COMMENT], '#A': [threadId] } // Uppercase A tag (NIP-22 for addressable events) ); // For kind 1 events, fetch kind 1 replies // Also fetch kind 1 replies for any event (some apps use kind 1 for everything) - replyFilters.push({ kinds: [1], '#e': [threadId] }); + replyFilters.push({ kinds: [KIND.SHORT_TEXT_NOTE], '#e': [threadId] }); // Fetch yak backs (kind 1244) - voice replies - replyFilters.push({ kinds: [1244], '#e': [threadId] }); + replyFilters.push({ kinds: [KIND.VOICE_REPLY], '#e': [threadId] }); // Fetch zap receipts (kind 9735) - replyFilters.push({ kinds: [9735], '#e': [threadId] }); + replyFilters.push({ kinds: [KIND.ZAP_RECEIPT], '#e': [threadId] }); // Don't use cache when reloading after publishing - we want fresh data const allReplies = await nostrClient.fetchEvents( @@ -563,10 +564,10 @@ const rootReplies = allReplies.filter(reply => referencesRoot(reply)); // Separate by type - comments = rootReplies.filter(e => e.kind === 1111); - kind1Replies = rootReplies.filter(e => e.kind === 1); - yakBacks = rootReplies.filter(e => e.kind === 1244); - zapReceipts = rootReplies.filter(e => e.kind === 9735); + comments = rootReplies.filter(e => e.kind === KIND.COMMENT); + kind1Replies = rootReplies.filter(e => e.kind === KIND.SHORT_TEXT_NOTE); + yakBacks = rootReplies.filter(e => e.kind === KIND.VOICE_REPLY); + zapReceipts = rootReplies.filter(e => e.kind === KIND.ZAP_RECEIPT); // Recursively fetch all nested replies (non-blocking - let it run in background) fetchNestedReplies().then(() => { @@ -592,15 +593,15 @@ function getAllowedReplyKind(targetEvent: NostrEvent | null): number { if (!targetEvent) { // If replying to root, check root kind - if (isKind1) return 1; - return 1111; + if (isKind1) return KIND.SHORT_TEXT_NOTE; + return KIND.COMMENT; } // If target is kind 1, allow kind 1 reply - if (targetEvent.kind === 1) return 1; + if (targetEvent.kind === KIND.SHORT_TEXT_NOTE) return KIND.SHORT_TEXT_NOTE; // Everything else gets kind 1111 - return 1111; + return KIND.COMMENT; } // Calculate total comment count (includes all reply types) diff --git a/src/lib/modules/feed/FeedPage.svelte b/src/lib/modules/feed/FeedPage.svelte index 70f33ae..ddc472a 100644 --- a/src/lib/modules/feed/FeedPage.svelte +++ b/src/lib/modules/feed/FeedPage.svelte @@ -5,6 +5,7 @@ import ThreadDrawer from './ThreadDrawer.svelte'; import type { NostrEvent } from '../../types/nostr.js'; import { onMount, tick } from 'svelte'; + import { KIND } from '../../types/kind-lookup.js'; let posts = $state([]); let loading = $state(true); @@ -108,7 +109,7 @@ } const relays = relayManager.getFeedReadRelays(); - const filters = [{ kinds: [1], limit: 20 }]; + const filters = [{ kinds: [KIND.SHORT_TEXT_NOTE], limit: 20 }]; // Subscribe to new kind 1 events subscriptionId = nostrClient.subscribe( @@ -146,7 +147,7 @@ : Math.floor(Date.now() / 1000) - 60; // Last minute if no posts const filters = [{ - kinds: [1], + kinds: [KIND.SHORT_TEXT_NOTE], limit: 50, since: newestTimestamp + 1 // Only get events newer than what we have }]; @@ -205,7 +206,7 @@ const relays = relayManager.getFeedReadRelays(); // Load initial feed - use cache for fast initial load - const filters = [{ kinds: [1], limit: 20 }]; + const filters = [{ kinds: [KIND.SHORT_TEXT_NOTE], limit: 20 }]; const events = await nostrClient.fetchEvents( filters, relays, @@ -275,7 +276,7 @@ const relays = relayManager.getFeedReadRelays(); const filters = [{ - kinds: [1], + kinds: [KIND.SHORT_TEXT_NOTE], limit: 20, until: oldestTimestamp || undefined }]; @@ -407,8 +408,8 @@ // Batch fetch all reactions for all posts in one query const allReactions = await nostrClient.fetchEvents( [ - { kinds: [7], '#e': eventIds, limit: 1000 }, - { kinds: [7], '#E': eventIds, limit: 1000 } + { kinds: [KIND.REACTION], '#e': eventIds, limit: 1000 }, + { kinds: [KIND.REACTION], '#E': eventIds, limit: 1000 } ], reactionRelays, { useCache: true, cacheResults: true, timeout: 10000 } diff --git a/src/lib/modules/feed/FeedPost.svelte b/src/lib/modules/feed/FeedPost.svelte index 16d0dbd..653f18a 100644 --- a/src/lib/modules/feed/FeedPost.svelte +++ b/src/lib/modules/feed/FeedPost.svelte @@ -10,7 +10,7 @@ import { relayManager } from '../../services/nostr/relay-manager.js'; import { onMount } from 'svelte'; import type { NostrEvent } from '../../types/nostr.js'; - import { getKindInfo } from '../../types/kind-lookup.js'; + import { getKindInfo, KIND } from '../../types/kind-lookup.js'; import { stripMarkdown } from '../../services/text-utils.js'; interface Props { @@ -37,7 +37,7 @@ // Calculate votes as derived values to avoid infinite loops // Deduplicate by pubkey - each user should only count once per vote type let upvotes = $derived.by(() => { - if (post.kind !== 11) return 0; + if (post.kind !== KIND.DISCUSSION_THREAD) return 0; const reactionEvents = reactions; if (!reactionEvents || !Array.isArray(reactionEvents)) return 0; @@ -73,7 +73,7 @@ }); let downvotes = $derived.by(() => { - if (post.kind !== 11) return 0; + if (post.kind !== KIND.DISCUSSION_THREAD) return 0; const reactionEvents = reactions; if (!reactionEvents || !Array.isArray(reactionEvents)) return 0; @@ -120,7 +120,7 @@ const zapRelays = relayManager.getZapReceiptReadRelays(); const filters = [{ - kinds: [9735], + kinds: [KIND.ZAP_RECEIPT], '#e': [post.id] }]; @@ -238,7 +238,7 @@ try { const relays = relayManager.getFeedReadRelays(); const events = await nostrClient.fetchEvents( - [{ kinds: [1], ids: [replyEventId] }], + [{ kinds: [KIND.SHORT_TEXT_NOTE], ids: [replyEventId] }], relays, { useCache: true, cacheResults: true } ); @@ -372,7 +372,7 @@ {#if getClientName()} via {getClientName()} {/if} - {#if post.kind === 11} + {#if post.kind === KIND.DISCUSSION_THREAD} {@const topics = getTopics()} {#if topics.length === 0} General @@ -388,7 +388,7 @@
- {#if post.kind === 11 && (upvotes > 0 || downvotes > 0)} + {#if post.kind === KIND.DISCUSSION_THREAD && (upvotes > 0 || downvotes > 0)} {#if upvotes > 0} ⬆️ {upvotes} @@ -431,7 +431,7 @@ {#if getClientName()} via {getClientName()} {/if} - {#if post.kind === 11} + {#if post.kind === KIND.DISCUSSION_THREAD} {@const topics = post.tags.filter((t) => t[0] === 't').map((t) => t[1])} {#if topics.length === 0} General @@ -463,7 +463,7 @@
- {#if post.kind === 11} + {#if post.kind === KIND.DISCUSSION_THREAD} {#if upvotes > 0 || downvotes > 0} diff --git a/src/lib/modules/feed/Reply.svelte b/src/lib/modules/feed/Reply.svelte index 6beb1d9..9873e6b 100644 --- a/src/lib/modules/feed/Reply.svelte +++ b/src/lib/modules/feed/Reply.svelte @@ -92,7 +92,7 @@ via {getClientName()} {/if}
- +
diff --git a/src/lib/modules/profiles/PaymentAddresses.svelte b/src/lib/modules/profiles/PaymentAddresses.svelte index 0122366..7bfb40f 100644 --- a/src/lib/modules/profiles/PaymentAddresses.svelte +++ b/src/lib/modules/profiles/PaymentAddresses.svelte @@ -27,7 +27,7 @@ // Fetch kind 10133 (payment targets) const paymentEvents = await nostrClient.fetchEvents( - [{ kinds: [10133], authors: [pubkey], limit: 1 }], + [{ kinds: [KIND.PAYMENT_ADDRESSES], authors: [pubkey], limit: 1 }], [...config.defaultRelays, ...config.profileRelays], { useCache: true, cacheResults: true } ); diff --git a/src/lib/modules/profiles/ProfilePage.svelte b/src/lib/modules/profiles/ProfilePage.svelte index 7c51db6..5b0784f 100644 --- a/src/lib/modules/profiles/ProfilePage.svelte +++ b/src/lib/modules/profiles/ProfilePage.svelte @@ -12,6 +12,7 @@ import { page } from '$app/stores'; import { nip19 } from 'nostr-tools'; import type { NostrEvent } from '../../types/nostr.js'; + import { KIND } from '../../types/kind-lookup.js'; let profile = $state(null); let userStatus = $state(null); @@ -72,7 +73,7 @@ // Fetch current user's posts from cache first (fast) const currentUserPosts = await nostrClient.fetchEvents( - [{ kinds: [1], authors: [currentUserPubkey], limit: 50 }], + [{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [currentUserPubkey], limit: 50 }], interactionRelays, { useCache: true, cacheResults: true, timeout: 2000 } // Short timeout for cache ); @@ -88,8 +89,8 @@ const interactionEvents = await Promise.race([ nostrClient.fetchEvents( [ - { kinds: [1], authors: [profilePubkey], '#e': Array.from(currentUserPostIds).slice(0, 20), limit: 20 }, // Limit IDs to avoid huge queries - { kinds: [1], authors: [profilePubkey], '#p': [currentUserPubkey], limit: 20 } + { kinds: [KIND.SHORT_TEXT_NOTE], authors: [profilePubkey], '#e': Array.from(currentUserPostIds).slice(0, 20), limit: 20 }, // Limit IDs to avoid huge queries + { kinds: [KIND.SHORT_TEXT_NOTE], authors: [profilePubkey], '#p': [currentUserPubkey], limit: 20 } ], interactionRelays, { useCache: true, cacheResults: true, timeout: 5000 } @@ -349,7 +350,7 @@ // Load posts first (needed for response filtering) const feedEvents = await nostrClient.fetchEvents( - [{ kinds: [1], authors: [pubkey], limit: 20 }], + [{ kinds: [KIND.SHORT_TEXT_NOTE], authors: [pubkey], limit: 20 }], profileRelays, { useCache: true, cacheResults: true, timeout: 5000 } ); @@ -358,7 +359,7 @@ // Load responses in parallel with posts (but filter after posts are loaded) const userPostIds = new Set(posts.map(p => p.id)); const responseEvents = await nostrClient.fetchEvents( - [{ kinds: [1], '#p': [pubkey], limit: 50 }], // Fetch more to account for filtering + [{ kinds: [KIND.SHORT_TEXT_NOTE], '#p': [pubkey], limit: 50 }], // Fetch more to account for filtering responseRelays, { useCache: true, cacheResults: true, timeout: 5000 } ); diff --git a/src/lib/modules/reactions/FeedReactionButtons.svelte b/src/lib/modules/reactions/FeedReactionButtons.svelte index 70c8e68..70b6b0e 100644 --- a/src/lib/modules/reactions/FeedReactionButtons.svelte +++ b/src/lib/modules/reactions/FeedReactionButtons.svelte @@ -5,6 +5,7 @@ import { relayManager } from '../../services/nostr/relay-manager.js'; import { onMount } from 'svelte'; import type { NostrEvent } from '../../types/nostr.js'; + import { KIND } from '../../types/kind-lookup.js'; import { resolveCustomEmojis, fetchEmojiSet, resolveEmojiShortcode } from '../../services/nostr/nip30-emoji.js'; import EmojiPicker from '../../components/content/EmojiPicker.svelte'; import emojiData from 'unicode-emoji-json/data-ordered-emoji.json'; @@ -110,12 +111,12 @@ allReactionsMap.clear(); const reactionsWithLowerE = await nostrClient.fetchEvents( - [{ kinds: [7], '#e': [event.id] }], + [{ kinds: [KIND.REACTION], '#e': [event.id] }], reactionRelays, { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate } ); const reactionsWithUpperE = await nostrClient.fetchEvents( - [{ kinds: [7], '#E': [event.id] }], + [{ kinds: [KIND.REACTION], '#E': [event.id] }], reactionRelays, { useCache: true, cacheResults: true, onUpdate: handleReactionUpdate } ); @@ -191,7 +192,7 @@ // Fetch deletion events (kind 5) to filter out deleted reactions const reactionRelays = relayManager.getProfileReadRelays(); const deletionEvents = await nostrClient.fetchEvents( - [{ kinds: [5], authors: Array.from(new Set(reactions.map(r => r.pubkey))) }], + [{ kinds: [KIND.EVENT_DELETION], authors: Array.from(new Set(reactions.map(r => r.pubkey))) }], reactionRelays, { useCache: true } ); @@ -258,7 +259,7 @@ // For kind 11 events (or kind 1111 replies to kind 11), normalize reactions: only + and - allowed // Backward compatibility: ⬆️/↑ = +, ⬇️/↓ = - - if (event.kind === 11 || forceUpvoteDownvote) { + if (event.kind === KIND.DISCUSSION_THREAD || forceUpvoteDownvote) { if (content === '⬆️' || content === '↑') { content = '+'; } else if (content === '⬇️' || content === '↓') { @@ -379,7 +380,7 @@ } // For kind 11 events (or kind 1111 replies to kind 11), only allow + and - (upvote/downvote) - if ((event.kind === 11 || forceUpvoteDownvote) && content !== '+' && content !== '-') { + if ((event.kind === KIND.DISCUSSION_THREAD || forceUpvoteDownvote) && content !== '+' && content !== '-') { return; } @@ -389,7 +390,7 @@ if (userReactionEventId) { try { const deletionEvent: Omit = { - kind: 5, + kind: KIND.EVENT_DELETION, pubkey: sessionManager.getCurrentPubkey()!, created_at: Math.floor(Date.now() / 1000), tags: [['e', userReactionEventId]], @@ -437,12 +438,12 @@ } // For kind 11 (or kind 1111 replies to kind 11): if user has the opposite vote, delete it first - if ((event.kind === 11 || forceUpvoteDownvote) && userReaction && userReaction !== content) { + if ((event.kind === KIND.DISCUSSION_THREAD || forceUpvoteDownvote) && userReaction && userReaction !== content) { // Delete the existing vote first if (userReactionEventId) { try { const deletionEvent: Omit = { - kind: 5, + kind: KIND.EVENT_DELETION, pubkey: sessionManager.getCurrentPubkey()!, created_at: Math.floor(Date.now() / 1000), tags: [['e', userReactionEventId]], @@ -485,7 +486,7 @@ } const reactionEvent: Omit = { - kind: 7, + kind: KIND.REACTION, pubkey: sessionManager.getCurrentPubkey()!, created_at: Math.floor(Date.now() / 1000), tags, @@ -635,7 +636,7 @@
- {#if event.kind === 11 || forceUpvoteDownvote} + {#if event.kind === KIND.DISCUSSION_THREAD || forceUpvoteDownvote}
- {#if event.kind !== 11} + {#if event.kind !== KIND.DISCUSSION_THREAD} {#each getAllReactions() as { content, count }}
- {#if event.kind === 11 || event.kind === 1111} + {#if event.kind === KIND.DISCUSSION_THREAD || event.kind === KIND.COMMENT}