From dcf43cfce17898cc4ab7cd820fdc9677e92707e0 Mon Sep 17 00:00:00 2001 From: Gigi Date: Wed, 8 Oct 2025 09:44:45 +0100 Subject: [PATCH] feat: add web bookmark creation (NIP-B0, kind:39701) - Created webBookmarkService for creating web bookmarks - Added AddBookmarkModal component with URL, title, and description fields - Added plus button to sidebar header (visible when logged in) - Modal validates URL format and publishes to relays - Auto-refreshes bookmarks after creation - Styled modal with dark theme matching app design - Follows NIP-B0 spec: URL in 'd' tag, title and summary tags --- src/components/AddBookmarkModal.tsx | 131 +++++++++++++++++++++++++ src/components/SidebarHeader.tsx | 35 ++++++- src/index.css | 145 ++++++++++++++++++++++++++++ src/services/webBookmarkService.ts | 68 +++++++++++++ 4 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/components/AddBookmarkModal.tsx create mode 100644 src/services/webBookmarkService.ts diff --git a/src/components/AddBookmarkModal.tsx b/src/components/AddBookmarkModal.tsx new file mode 100644 index 00000000..2e1af562 --- /dev/null +++ b/src/components/AddBookmarkModal.tsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faTimes } from '@fortawesome/free-solid-svg-icons' +import IconButton from './IconButton' + +interface AddBookmarkModalProps { + onClose: () => void + onSave: (url: string, title?: string, description?: string) => Promise +} + +const AddBookmarkModal: React.FC = ({ onClose, onSave }) => { + const [url, setUrl] = useState('') + const [title, setTitle] = useState('') + const [description, setDescription] = useState('') + const [isSaving, setIsSaving] = useState(false) + const [error, setError] = useState(null) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setError(null) + + if (!url.trim()) { + setError('URL is required') + return + } + + // Validate URL + try { + new URL(url) + } catch { + setError('Please enter a valid URL') + return + } + + try { + setIsSaving(true) + await onSave( + url.trim(), + title.trim() || undefined, + description.trim() || undefined + ) + onClose() + } catch (err) { + console.error('Failed to save bookmark:', err) + setError(err instanceof Error ? err.message : 'Failed to save bookmark') + } finally { + setIsSaving(false) + } + } + + return ( +
+
e.stopPropagation()}> +
+

Add Bookmark

+ +
+ +
+
+ + setUrl(e.target.value)} + placeholder="https://example.com" + disabled={isSaving} + autoFocus + /> +
+ +
+ + setTitle(e.target.value)} + placeholder="Optional title" + disabled={isSaving} + /> +
+ +
+ +