diff --git a/.cursor/rules/nostr-native-blog-post-aka-long-form-kind.mdc b/.cursor/rules/nostr-native-blog-post-aka-long-form-kind.mdc new file mode 100644 index 00000000..3dca9096 --- /dev/null +++ b/.cursor/rules/nostr-native-blog-post-aka-long-form-kind.mdc @@ -0,0 +1,3 @@ +--- +alwaysApply: true +--- diff --git a/dist/index.html b/dist/index.html index fd9b3399..9e91ca23 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5,7 +5,7 @@ Boris - Nostr Bookmarks - + diff --git a/src/components/BookmarkItem.tsx b/src/components/BookmarkItem.tsx index c82ce4de..1f74590b 100644 --- a/src/components/BookmarkItem.tsx +++ b/src/components/BookmarkItem.tsx @@ -15,7 +15,7 @@ import { CardView } from './BookmarkViews/CardView' interface BookmarkItemProps { bookmark: IndividualBookmark index: number - onSelectUrl?: (url: string) => void + onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][] }) => void viewMode?: ViewMode } @@ -68,10 +68,20 @@ export const BookmarkItem: React.FC = ({ bookmark, index, onS } const handleReadNow = (event: React.MouseEvent) => { + event.preventDefault() + + // For kind:30023 articles, pass the bookmark data instead of URL + if (bookmark.kind === 30023) { + if (onSelectUrl) { + onSelectUrl('', { id: bookmark.id, kind: bookmark.kind, tags: bookmark.tags }) + } + return + } + + // For regular bookmarks with URLs if (!hasUrls) return const firstUrl = extractedUrls[0] if (onSelectUrl) { - event.preventDefault() onSelectUrl(firstUrl) } else { window.open(firstUrl, '_blank') diff --git a/src/components/BookmarkList.tsx b/src/components/BookmarkList.tsx index 3e96f31b..5d87c924 100644 --- a/src/components/BookmarkList.tsx +++ b/src/components/BookmarkList.tsx @@ -9,7 +9,7 @@ import { ViewMode } from './Bookmarks' interface BookmarkListProps { bookmarks: Bookmark[] - onSelectUrl?: (url: string) => void + onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][] }) => void isCollapsed: boolean onToggleCollapse: () => void onLogout: () => void diff --git a/src/components/BookmarkViews/CardView.tsx b/src/components/BookmarkViews/CardView.tsx index 278e5ce8..b9620b1f 100644 --- a/src/components/BookmarkViews/CardView.tsx +++ b/src/components/BookmarkViews/CardView.tsx @@ -13,7 +13,7 @@ interface CardViewProps { index: number hasUrls: boolean extractedUrls: string[] - onSelectUrl?: (url: string) => void + onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][] }) => void getIconForUrlType: IconGetter firstUrlClassification: { buttonText: string } | null authorNpub: string @@ -141,11 +141,11 @@ export const CardView: React.FC = ({ {getAuthorDisplayName()} - {hasUrls && firstUrlClassification && ( + {(hasUrls && firstUrlClassification) || bookmark.kind === 30023 ? ( - )} + ) : null} ) diff --git a/src/components/BookmarkViews/CompactView.tsx b/src/components/BookmarkViews/CompactView.tsx index 0be7301f..e763e309 100644 --- a/src/components/BookmarkViews/CompactView.tsx +++ b/src/components/BookmarkViews/CompactView.tsx @@ -11,7 +11,7 @@ interface CompactViewProps { index: number hasUrls: boolean extractedUrls: string[] - onSelectUrl?: (url: string) => void + onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][] }) => void getIconForUrlType: IconGetter firstUrlClassification: { buttonText: string } | null } @@ -25,8 +25,15 @@ export const CompactView: React.FC = ({ getIconForUrlType, firstUrlClassification }) => { + const isArticle = bookmark.kind === 30023 + const isClickable = hasUrls || isArticle + const handleCompactClick = () => { - if (hasUrls && onSelectUrl) { + if (!onSelectUrl) return + + if (isArticle) { + onSelectUrl('', { id: bookmark.id, kind: bookmark.kind, tags: bookmark.tags }) + } else if (hasUrls) { onSelectUrl(extractedUrls[0]) } } @@ -34,10 +41,10 @@ export const CompactView: React.FC = ({ return (
{bookmark.isPrivate ? ( @@ -55,13 +62,20 @@ export const CompactView: React.FC = ({
)} {formatDate(bookmark.created_at)} - {hasUrls && ( + {isClickable && ( )}
diff --git a/src/components/BookmarkViews/LargeView.tsx b/src/components/BookmarkViews/LargeView.tsx index c0c1b33c..a189f6b0 100644 --- a/src/components/BookmarkViews/LargeView.tsx +++ b/src/components/BookmarkViews/LargeView.tsx @@ -10,7 +10,7 @@ interface LargeViewProps { index: number hasUrls: boolean extractedUrls: string[] - onSelectUrl?: (url: string) => void + onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][] }) => void getIconForUrlType: IconGetter firstUrlClassification: { buttonText: string } | null previewImage: string | null @@ -34,6 +34,8 @@ export const LargeView: React.FC = ({ getAuthorDisplayName, handleReadNow }) => { + const isArticle = bookmark.kind === 30023 + return (
{hasUrls && ( @@ -80,12 +82,12 @@ export const LargeView: React.FC = ({ )} - {hasUrls && firstUrlClassification && ( + {(hasUrls && firstUrlClassification) || isArticle ? ( - )} + ) : null}
diff --git a/src/components/Bookmarks.tsx b/src/components/Bookmarks.tsx index a3cee56f..63a244de 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react' import { useParams } from 'react-router-dom' +import { nip19 } from 'nostr-tools' import { Hooks } from 'applesauce-react' import { useEventStore } from 'applesauce-react/hooks' import { RelayPool } from 'applesauce-relay' @@ -113,17 +114,48 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { } } - const handleSelectUrl = async (url: string) => { + const handleSelectUrl = async (url: string, bookmark?: { id: string; kind: number; tags: string[][] }) => { + if (!relayPool) return + setSelectedUrl(url) setReaderLoading(true) setReaderContent(undefined) setShowSettings(false) if (settings.collapseOnArticleOpen !== false) setIsCollapsed(true) + try { - const content = await fetchReadableContent(url) - setReaderContent(content) + // Check if this is a kind:30023 article + if (bookmark && bookmark.kind === 30023) { + // For articles, construct an naddr and fetch using article service + const dTag = bookmark.tags.find(t => t[0] === 'd')?.[1] || '' + + // Try to get author from tags first, then fall back to bookmark id as pubkey + const author = bookmark.tags.find(t => t[0] === 'author')?.[1] || + (bookmark.id.length === 64 ? bookmark.id : undefined) + + if (dTag !== undefined) { + const pointer = { + identifier: dTag, + kind: 30023, + pubkey: author || bookmark.id, + } + const naddr = nip19.naddrEncode(pointer) + const article = await fetchArticleByNaddr(relayPool, naddr) + setReaderContent({ + title: article.title, + markdown: article.markdown, + url: `nostr:${naddr}` + }) + } else { + throw new Error('Invalid article reference - missing d tag') + } + } else { + // For regular URLs, fetch readable content + const content = await fetchReadableContent(url) + setReaderContent(content) + } } catch (err) { - console.warn('Failed to fetch readable content:', err) + console.warn('Failed to fetch content:', err) } finally { setReaderLoading(false) }