Feat/155 pinned article for user page#164
Conversation
|
@DSanich is attempting to deploy a commit to the NodeTec Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis pull request introduces pinned articles functionality for user profiles. A new AuthorArticlesFeed component feature fetches and displays pinned articles with management controls. Supporting Nostr protocol functions handle fetching, publishing, and type definitions for pinned article data. Changes
Sequence DiagramsequenceDiagram
participant User
participant AuthorFeed as AuthorArticlesFeed<br/>Component
participant FetchModule as Nostr Fetch<br/>Module
participant PublishModule as Nostr Publish<br/>Module
participant Relay as Relay
User->>AuthorFeed: Visit author profile
AuthorFeed->>FetchModule: fetchPinnedArticles(pubkey, relay)
FetchModule->>Relay: Subscribe to kind 10001 event
Relay-->>FetchModule: EVENT (pinned articles data)
FetchModule-->>AuthorFeed: PinnedArticles object
AuthorFeed->>AuthorFeed: Render pinned article block<br/>above feed
User->>AuthorFeed: Click pin/unpin button
AuthorFeed->>AuthorFeed: Set isPinning = true<br/>(guard concurrent ops)
AuthorFeed->>PublishModule: publishPinnedArticles(pinnedArticles, relays)
PublishModule->>PublishModule: Build kind 10001 event<br/>with article references
PublishModule->>Relay: Publish event to relays
Relay-->>PublishModule: Publish confirmation
PublishModule-->>AuthorFeed: PublishResult[]
AuthorFeed->>FetchModule: fetchPinnedArticles (refetch)
FetchModule->>Relay: Subscribe to updated event
Relay-->>FetchModule: EVENT (updated data)
FetchModule-->>AuthorFeed: Updated PinnedArticles
AuthorFeed->>AuthorFeed: Refresh UI, set isPinning = false<br/>Show success toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/author/AuthorArticlesFeed.tsx`:
- Around line 157-159: filteredBlogs already filters out the current pinnedBlog
(const filteredBlogs = pinnedBlog ? blogs.filter((blog) => blog.id !==
pinnedBlog.id) : blogs), so the conditional inside filteredBlogs.map that checks
pinnedBlog?.id === blog.id and renders the "Unpin article" DropdownMenuItem is
unreachable; remove that entire branch (the conditional and the DropdownMenuItem
for "Unpin article") from the map to avoid dead code, or if you intended to keep
it as a safety net instead of removing, replace it with a short inline comment
near the filteredBlogs declaration saying it purposefully excludes the pinned
article to guard against duplicates/relay delays; target symbols: filteredBlogs,
pinnedBlog, filteredBlogs.map, and the DropdownMenuItem that renders "Unpin
article".
🧹 Nitpick comments (3)
src/lib/nostr/types.ts (1)
151-186: Duplicated 'a' tag parsing logic — consider extracting a shared helper.The tag-parsing reduce block in
eventToPinnedArticles(lines 161–178) is nearly identical to the one ineventToStack(lines 121–136), differing only by thekind === 30023filter. Extracting a sharedparseATags(tags: string[][], kindFilter?: number): StackItem[]helper would eliminate this duplication.♻️ Example shared helper
+function parseATags(tags: string[][], kindFilter?: number): StackItem[] { + return tags + .filter((t) => t[0] === 'a') + .reduce<StackItem[]>((acc, t) => { + const parts = t[1]?.split(':'); + if (!parts || parts.length < 3) return acc; + const [kindStr, pubkey, identifier] = parts; + const kind = parseInt(kindStr, 10); + if (isNaN(kind) || !pubkey || !identifier) return acc; + if (kindFilter !== undefined && kind !== kindFilter) return acc; + acc.push({ kind, pubkey, identifier, relay: t[2] }); + return acc; + }, []); +}Then use it in both
eventToStackandeventToPinnedArticles.src/lib/nostr/publish.ts (1)
504-517: Consider reusingStackItemfromtypes.tsinstead of an inline type.The inline type for
pinnedArticlesitems ({ kind: number; pubkey: string; identifier: string; relay?: string }) is identical to theStackIteminterface. Reusing it improves consistency and reduces surface area for drift.♻️ Proposed change
+import type { NostrEvent, StackItem } from './types'; // ... export async function publishPinnedArticles({ pinnedArticles, relays, secretKey, }: { - pinnedArticles: Array<{ - kind: number; - pubkey: string; - identifier: string; - relay?: string; - }>; + pinnedArticles: StackItem[]; relays: string[]; secretKey?: string; }): Promise<PublishResult[]> {src/components/author/AuthorArticlesFeed.tsx (1)
371-510: Consider extracting the pinned article block into a dedicated component.The IIFE at lines 377–508 is ~130 lines of inline rendering logic with its own computed variables. Extracting it into a
PinnedArticleCardcomponent (or similar) would improve readability and make the parent component easier to follow.
| const filteredBlogs = pinnedBlog | ||
| ? blogs.filter((blog) => blog.id !== pinnedBlog.id) | ||
| : blogs; |
There was a problem hiding this comment.
The "Unpin article" branch in the regular feed dropdown appears unreachable.
filteredBlogs (line 157–159) already excludes blogs whose id matches pinnedBlog.id. Therefore, inside filteredBlogs.map(...) at line 513, the condition pinnedBlog?.id === blog.id on line 551 should never be true, making the "Unpin article" DropdownMenuItem (lines 552–562) dead code.
If this is intentional as a safety net for edge cases (e.g., relay propagation delays causing ID mismatches), a brief inline comment would clarify intent. Otherwise, the branch can be removed.
Also applies to: 549-578
🤖 Prompt for AI Agents
In `@src/components/author/AuthorArticlesFeed.tsx` around lines 157 - 159,
filteredBlogs already filters out the current pinnedBlog (const filteredBlogs =
pinnedBlog ? blogs.filter((blog) => blog.id !== pinnedBlog.id) : blogs), so the
conditional inside filteredBlogs.map that checks pinnedBlog?.id === blog.id and
renders the "Unpin article" DropdownMenuItem is unreachable; remove that entire
branch (the conditional and the DropdownMenuItem for "Unpin article") from the
map to avoid dead code, or if you intended to keep it as a safety net instead of
removing, replace it with a short inline comment near the filteredBlogs
declaration saying it purposefully excludes the pinned article to guard against
duplicates/relay delays; target symbols: filteredBlogs, pinnedBlog,
filteredBlogs.map, and the DropdownMenuItem that renders "Unpin article".
Add pinned article feature to author profile page
Summary
Implements pinned article functionality for author profile pages using NIP-51 (Nostr Lists). Users can now pin a single article to the top of their profile, making it the first thing visitors see.
fixed #155
Summary by CodeRabbit