Commit Graph

10 Commits

Author SHA1 Message Date
Alejandro
7036fb5539 Fix chat composer placeholder and text alignment (#84)
* Fix chat composer placeholder and text alignment

- Adjusted .ProseMirror min-height from 2rem to 1.25rem to match container
- Added flexbox layout to .ProseMirror for proper vertical centering
- Removed float:left and height:0 from placeholder causing misalignment
- Moved padding from editor props to wrapper div
- Updated EditorContent to use flex items-center for alignment

Resolves vertical alignment issues in chat composer input field.

* Fix cursor placement in chat composer placeholder

- Changed from flexbox to line-height for vertical centering
- Removed flex from .ProseMirror to fix cursor positioning
- Set line-height: 1.25rem to match min-height for proper alignment
- Removed flex items-center from EditorContent className

This ensures the cursor appears at the correct position when focusing
the input field, rather than after the placeholder text.

* Fix cursor placement on mobile devices

- Made placeholder absolutely positioned to prevent it from affecting cursor
- Added position: relative to .ProseMirror container
- This ensures cursor appears at the start of input on mobile browsers

The absolute positioning removes the placeholder from the normal layout flow,
preventing mobile browsers from placing the cursor after the pseudo-element.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 22:07:04 +01:00
Alejandro
1356afe9ea Fix newline rendering in chat messages (#80)
* Fix newline rendering in chat messages

The Text component was not properly rendering newlines in chat messages.
The previous implementation had buggy logic that only rendered <br /> for
empty lines and used an inconsistent mix of spans and divs for non-empty
lines, which didn't create proper line breaks between consecutive text lines.

Changes:
- Render each line in a span with <br /> between consecutive lines
- Remove unused useMemo import and fix React hooks violation
- Simplify logic for better maintainability

This ensures that multi-line messages in chat (and other text content)
display correctly with proper line breaks.

Fixes rendering of newlines in NIP-29 groups and NIP-53 live chat.

* Preserve newlines when sending chat messages

The MentionEditor's serializeContent function was not handling hardBreak
nodes created by Shift+Enter. This caused newlines within messages to be
lost during serialization, even though the editor displayed them correctly.

Changes:
- Add hardBreak node handling in serializeContent
- Preserve newlines (\n) from Shift+Enter keypresses
- Ensure multi-line messages are sent with proper line breaks

With this fix and the previous Text.tsx fix, newlines are now properly:
1. Captured when typing (Shift+Enter creates hardBreak)
2. Preserved when sending (hardBreak serialized as \n)
3. Rendered when displaying (Text component renders \n as <br />)

* Make Enter insert newline on mobile devices

On mobile devices, pressing Enter now inserts a newline (hardBreak) instead
of submitting the message. This provides better UX since mobile keyboards
don't have easy access to Shift+Enter for multiline input.

Behavior:
- Desktop: Enter submits, Shift+Enter inserts newline (unchanged)
- Mobile: Enter inserts newline, Cmd/Ctrl+Enter submits
- Mobile detection: Uses touch support API (ontouchstart or maxTouchPoints)

Users can still submit messages on mobile using:
1. The Send button (primary method)
2. Ctrl+Enter keyboard shortcut (if available)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 20:50:46 +01:00
Alejandro
9ef1fefd3d feat: BLOSSOM (#75)
* Add Blossom blob storage integration

- Add blossom-client-sdk dependency for blob storage operations
- Create blossom.ts service with upload, list, check, mirror, delete primitives
- Add kind 10063 server list fetching and parsing
- Create blossom-parser.ts for command argument parsing with subcommands
- Add BLOSSOM command to man.ts with subcommands:
  - servers: Show configured Blossom servers
  - check: Check server health
  - upload: Upload files to user's servers
  - list: List blobs for a user
  - mirror: Mirror blobs between servers
  - delete: Delete blobs from servers
- Create BlossomViewer component with views for each subcommand
- Wire up BlossomViewer in WindowRenderer
- Add Blossom servers dropdown to ProfileViewer header
- Upload primitives can be called programmatically for use in other components

* Enhance Blossom viewer with server selection and blob details

- Add server selection checkboxes to upload view for choosing target servers
- Add BlobDetailView with media preview (image/video/audio) and metadata display
- Add 'blob' subcommand to view individual blob details
- Remove unused 'check' subcommand

* Add Blossom upload dialog with chat integration

- Create BlossomUploadDialog component with file picker, server selection, and preview
- Create useBlossomUpload hook for easy integration in any component
- Add insertText method to MentionEditor for programmatic text insertion
- Integrate upload button (paperclip icon) in chat composer
- Supports image, video, and audio uploads with drag-and-drop

* Add rich blob attachments with imeta tags for chat

- Add BlobAttachment TipTap extension with inline preview (thumbnail for images, icons for video/audio)
- Store full blob metadata (sha256, url, mimeType, size, server) in editor nodes
- Convert blob nodes to URLs in content with NIP-92 imeta tags when sending
- Add insertBlob method to MentionEditor for programmatic blob insertion
- Update NIP-29 and NIP-53 adapters to include imeta tags with blob metadata
- Pass blob attachments through entire send flow (editor -> ChatViewer -> adapter)

* Add fallback public Blossom servers for users without server list

- Add well-known public servers as fallbacks (blossom.primal.net, nostr.download, files.v0l.io)
- Use fallbacks when user has no kind 10063 server list configured
- Show "Public Servers" label with Globe icon when using fallbacks
- Inform user that no server list was found
- Select first fallback server by default (vs all user servers)

* Fix: Don't show fallback servers when not logged in

Blossom uploads require signed auth events, so users must be logged in.
The 'Account required' message is already shown in this case.

* Remove files.v0l.io from fallback servers

* Add rich renderer for kind 10063 Blossom server list

- Create BlossomServerListRenderer.tsx with feed and detail views
- Show user's configured Blossom servers with clickable links
- Clicking a server opens the Blossom window with server info
- Register renderers for kind 10063 (BUD-03)
- Fix lint error by renaming useFallbackServers to applyFallbackServers

* Add individual server view and NIP-05 support for blossom commands

- Add 'server' subcommand to view info about a specific Blossom server
- Update BlossomServerListRenderer to open server view on click
- Make blossom parser async to support NIP-05 resolution in 'list' command
- Add kind 10063 (Blossom Server List) to EVENT_KINDS constants with BUD-03 reference
- Update command examples with NIP-05 identifier support

* Add comprehensive tests for blossom-parser

- 34 test cases covering all subcommands (servers, server, upload, list, blob, mirror, delete)
- Tests for NIP-05 resolution, npub/nprofile decoding, $me alias
- Tests for error handling and input validation
- Tests for case insensitivity and command aliases (ls, view, rm)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 17:16:31 +01:00
Alejandro
797510107b Fix slash command autocomplete and add bookmark commands (#73)
* fix: slash autocomplete only at input start + add bookmark commands

- Fix slash command autocomplete to only trigger when / is at the
  beginning of input text (position 1 in TipTap), not in the middle
  of messages
- Add /bookmark command to add NIP-29 group to user's kind 10009 list
- Add /unbookmark command to remove group from user's kind 10009 list

* fix: normalize relay URLs when checking group list bookmarks

Use normalizeRelayURL for comparing relay URLs in bookmark/unbookmark
commands to handle differences in trailing slashes, casing, and protocol
prefixes between stored tags and conversation metadata.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 12:39:22 +01:00
Alejandro
4ee385ea6c Add actions support to chat adapter protocol (#67)
* feat: add protocol-specific actions to chat adapters

Extends the chat adapter system with support for slash commands and
protocol-specific actions without parameters.

New features:
- ChatAction type system for defining simple commands
- Base adapter getActions() and executeAction() methods
- NIP-29 /join and /leave slash commands
- Slash command parser for detecting /commands
- ChatViewer integration with toast notifications

Example usage in NIP-29 groups:
  /join  - Request to join the group
  /leave - Leave the group

The action system is extensible and can be enhanced with parameterized
actions (e.g., /kick <user>, /ban <user>) in future iterations.

Tests: All 804 tests passing
Build: Successful
Lint: No errors

* feat: add autocomplete for slash commands

Extends the chat system with autocomplete UI for slash commands, making
actions discoverable and easier to execute.

New features:
- SlashCommandSuggestionList component with keyboard navigation
- Slash command search integrated into MentionEditor
- Autocomplete popup shows command name and description
- Arrow keys for navigation, Enter to select, Escape to close
- Filters available commands based on typed text

When typing "/" in chat, users now see:
  /join   Request to join the group
  /leave  Leave the group

The autocomplete uses the same TipTap suggestion system as @mentions
and :emoji:, providing a consistent UX across all autocomplete features.

Tests: All 804 tests passing
Build: Successful
Lint: 1 warning fixed (unused parameter)

* feat: execute slash commands immediately on autocomplete selection

When selecting a slash command from the autocomplete menu, the action
now executes immediately and clears the input, providing a more
streamlined UX.

Changes:
- Added onCommandExecute prop to MentionEditor
- Modified slash command suggestion to call onCommandExecute on selection
- Clears editor content immediately after selection
- Added handleCommandExecute callback in ChatViewer
- Executes action and shows toast notifications

Before: Type "/" → select "/join" → press Enter → executes
After: Type "/" → select "/join" → executes immediately

This matches the expected behavior for command selection - when you
choose a command from autocomplete, you want to execute it, not just
insert it into the text field.

Tests: All 804 tests passing
Build: Successful
Lint: No new errors

* feat: make chat actions context-aware and filter by membership status

Actions are now intelligently filtered based on the user's membership in
the conversation, providing a cleaner and more intuitive UX.

Changes:
- Added GetActionsOptions type with conversation and activePubkey
- Updated base adapter getActions() to accept optional context
- Modified NIP-29 adapter to filter actions based on membership:
  - /join: only shown when user is NOT a member/admin
  - /leave: only shown when user IS a member
- Updated ChatViewer to pass conversation and user context to searchCommands
- Moved searchCommands callback after conversation is defined

This prevents showing irrelevant commands like "/join" when you're already
a member, or "/leave" when you haven't joined yet. The autocomplete menu
now only displays actions that are actually executable in the current context.

Implementation notes:
- NIP-29 uses getAllActions() fallback when context unavailable
- Membership determined by checking conversation.participants array
- Other protocols return empty array by default (no actions yet)

Tests: All 804 tests passing
Build: Successful
Lint: No new errors

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 11:49:32 +01:00
Alejandro
cf1f384691 Make chat composer more compact (#68)
* ui: make chat composer more compact

- Reduce input and button height from 2.5rem to 1.75rem (h-7)
- Reduce padding from px-3 py-1.5 to px-2 py-1
- Use text-sm for consistent sizing with chat messages
- Make Send button smaller with text-xs and smaller icon
- Tighten gap between input and button

* ui: remove bottom padding from chat composer

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 22:21:03 +01:00
Alejandro
035fd829d5 Add Ctrl+Enter shortcut to send messages (#55)
* fix: allow Ctrl/Cmd+Enter to submit messages when suggestion popup is open

The suggestion list components were intercepting all Enter key presses,
including Ctrl+Enter and Cmd+Enter, preventing message submission when
the autocomplete popup was visible. Now these modifier combinations
pass through to the editor's submit handler.

* fix: make Ctrl/Cmd+Enter work reliably and prevent text overflow

- Add TipTap Extension with addKeyboardShortcuts for Mod-Enter and Enter
  which has higher priority than the suggestion plugin, ensuring
  Ctrl/Cmd+Enter always submits even when autocomplete is open
- Use ref to access handleSubmit from extension without recreating it
- Add whitespace-nowrap to prevent text wrapping in single-line input
- Add overflow-hidden to container and overflow-x-auto to editor content
  to handle long text gracefully with horizontal scrolling

* fix: handle Ctrl/Cmd+Enter directly in suggestion onKeyDown handlers

The TipTap suggestion plugin intercepts key events at the plugin level,
which runs before extension keyboard shortcuts. Even returning false
from the suggestion's onKeyDown didn't properly propagate events.

Now the suggestion handlers directly handle Ctrl/Cmd+Enter by:
1. Capturing the editor reference from onStart (where it's available)
2. Checking for Ctrl/Cmd+Enter in onKeyDown
3. Closing the popup and calling handleSubmitRef.current()

Also moved handleSubmitRef to the top of the component so it can be
accessed from within the suggestion config closures.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 12:02:47 +01:00
Alejandro
2bad592a3a feat: emoji autocompletion (#54)
* feat: add NIP-30 emoji autocompletion to editor

Implement emoji autocomplete triggered by `:` in the MentionEditor:

- EmojiSearchService: flexsearch-based indexing for emoji shortcodes
- useEmojiSearch hook: loads Unicode emojis + user's custom emoji (kind 10030/30030)
- EmojiSuggestionList: grid-based suggestion UI with keyboard nav
- Update MentionEditor with second Mention extension for emoji
- Serialize emoji as `:shortcode:` format with NIP-30 emoji tags
- Update chat adapters to include emoji tags in messages

Sources:
- Unicode: ~300 common emojis with shortcodes
- Custom: user's emoji list (kind 10030) and referenced sets (kind 30030)
- Context: emoji tags from events being replied to

* feat: add rich emoji preview in editor

Emoji inserted via the autocomplete now display as actual images/characters
instead of :shortcode: text:

- Custom emoji: renders as inline <img> with proper sizing
- Unicode emoji: renders as text with emoji font sizing
- Both show :shortcode: on hover via title attribute

CSS styles ensure proper vertical alignment with surrounding text.

* fix: store emoji url and source attributes in node schema

The TipTap Mention extension only defines `id` and `label` by default.
Added `addAttributes()` to EmojiMention extension to also store `url`
and `source` attributes, fixing emoji tags not being included in sent
messages.

* fix: improve emoji node rendering in editor

- Remove redundant renderLabel (nodeView handles display)
- Add renderText for proper clipboard behavior
- Make nodeView more robust with null checks
- Add fallback to shortcode if image fails to load
- Unicode emoji shows character, custom shows image

* fix: serialize unicode emoji as actual characters, not shortcodes

When sending messages:
- Unicode emoji (😄, 🔥) → outputs 😄, 🔥 (the actual character)
- Custom emoji (:pepe:) → outputs :pepe: with emoji tag for rendering

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 11:30:52 +01:00
Alejandro
bff51857d6 feat: message ux improvements (#52)
* feat(chat): add Ctrl+Enter shortcut to send messages

Allows users to send messages using Ctrl+Enter (or Cmd+Enter on Mac)
in addition to the existing Enter key shortcut.

* fix(chat): disable autofocus on message editor

Prevents keyboard from popping up automatically on mobile devices.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 10:43:59 +01:00
Alejandro Gómez
0b20a628e2 feat: add mention editor and NIP-29 chat enhancements
Implements rich text editing with profile mentions, NIP-29 system messages,
day markers, and naddr support for a more complete chat experience.

Editor Features:
- TipTap-based rich text editor with @mention autocomplete
- FlexSearch-powered profile search (case-insensitive)
- Converts mentions to nostr:npub URIs on submission
- Keyboard navigation (Arrow keys, Enter, Escape)
- Fixed Enter key and Send button submission

NIP-29 Chat Improvements:
- System messages for join/leave events (kinds 9000, 9001, 9021, 9022)
- Styled system messages aligned left with muted text
- Shows "joined" instead of "was added" for consistency
- Accepts kind 39000 naddr (group metadata addresses)
- Day markers between messages from different days
- Day markers use locale-aware formatting (short month, no year)

Components:
- src/components/editor/MentionEditor.tsx - TipTap editor with mention support
- src/components/editor/ProfileSuggestionList.tsx - Autocomplete dropdown
- src/services/profile-search.ts - FlexSearch service for profile indexing
- src/hooks/useProfileSearch.ts - React hook for profile search

Dependencies:
- @tiptap/react, @tiptap/starter-kit, @tiptap/extension-mention
- @tiptap/extension-placeholder, @tiptap/suggestion
- flexsearch@0.7.43, tippy.js@6.3.7

Tests:
- Added 6 new tests for naddr parsing in NIP-29 adapter
- All 710 tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 10:26:38 +01:00