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)
}