Bump to v0.6.1 — native file upload, mention names, connection stability

- Native file picker (+) in compose box uploads via Rust backend (reqwest)
- Pasting a local file path auto-uploads instead of inserting text
- @mentions resolve to profile display names via useProfile hook
- Connection indicator uses 15s grace period before showing offline
- Upload uses correct nostr.build v2 API; Rust-side multipart for native picks
- Content parser extracted to src/lib/parsing.ts
This commit is contained in:
Jure
2026-03-18 14:55:29 +01:00
parent ef189932e6
commit fbf05e8f90
16 changed files with 776 additions and 253 deletions

View File

@@ -63,6 +63,7 @@ export function NoteCard({ event, focused }: NoteCardProps) {
const [liked, setLiked] = useState(() => getLiked().has(event.id));
const [liking, setLiking] = useState(false);
const [reactionCount, adjustReactionCount] = useReactionCount(event.id);
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [replyCount, adjustReplyCount] = useReplyCount(event.id);
const [copied, setCopied] = useState(false);
const zapData = useZapCount(event.id);
@@ -78,14 +79,17 @@ export function NoteCard({ event, focused }: NoteCardProps) {
const [reposted, setReposted] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const handleLike = async () => {
const REACTION_EMOJIS = ["❤️", "🤙", "🔥", "😂", "🫡", "👀", "⚡"];
const handleReact = async (emoji?: string) => {
if (!loggedIn || liked || liking) return;
setLiking(true);
setShowEmojiPicker(false);
try {
await publishReaction(event.id, event.pubkey);
const liked = getLiked();
liked.add(event.id);
localStorage.setItem(likedKey, JSON.stringify(Array.from(liked)));
await publishReaction(event.id, event.pubkey, emoji || "+");
const likedSet = getLiked();
likedSet.add(event.id);
localStorage.setItem(likedKey, JSON.stringify(Array.from(likedSet)));
setLiked(true);
adjustReactionCount(1);
} finally {
@@ -249,15 +253,34 @@ export function NoteCard({ event, focused }: NoteCardProps) {
>
reply{replyCount !== null && replyCount > 0 ? ` ${replyCount}` : ""}
</button>
<button
onClick={handleLike}
disabled={liked || liking}
className={`text-[11px] transition-colors ${
liked ? "text-accent" : "text-text-dim hover:text-accent"
} disabled:cursor-default`}
>
{liked ? "♥" : "♡"}{reactionCount !== null && reactionCount > 0 ? ` ${reactionCount}` : liked ? " liked" : " like"}
</button>
<div className="relative">
<button
onClick={() => handleReact("❤️")}
onContextMenu={(e) => { e.preventDefault(); if (!liked && !liking) setShowEmojiPicker((v) => !v); }}
disabled={liked || liking}
className={`text-[11px] transition-colors ${
liked ? "text-accent" : "text-text-dim hover:text-accent"
} disabled:cursor-default`}
>
{liked ? "♥" : "♡"}{reactionCount !== null && reactionCount > 0 ? ` ${reactionCount}` : liked ? " liked" : " like"}
</button>
{showEmojiPicker && (
<>
<div className="fixed inset-0 z-[9]" onClick={() => setShowEmojiPicker(false)} />
<div className="absolute bottom-6 left-0 bg-bg-raised border border-border shadow-lg z-10 flex gap-0.5 px-1.5 py-1">
{REACTION_EMOJIS.map((emoji) => (
<button
key={emoji}
onClick={() => handleReact(emoji)}
className="text-[16px] hover:scale-125 transition-transform px-0.5"
>
{emoji}
</button>
))}
</div>
</>
)}
</div>
<button
onClick={handleRepost}
disabled={reposting || reposted}