refactor(editor): replace DOM manipulation with React node views and floating-ui#253
Merged
purrgrammer merged 10 commits intomainfrom Mar 3, 2026
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…floating-ui - Convert all inline node views (emoji, blob attachment, event preview) from imperative document.createElement() to React components via ReactNodeViewRenderer - Replace tippy.js with @floating-ui/react-dom for suggestion popup positioning - Create useSuggestionRenderer hook bridging Tiptap suggestion callbacks to React state - Extract shared EmojiMention, SubmitShortcut, and inline node extensions to separate files - Extract types (EmojiTag, BlobAttachment, SerializedContent) to editor/types.ts - Extract serialization logic to editor/utils/serialize.ts - Remove redundant DOM keydown listener from RichEditor (handled by SubmitShortcut extension) - Remove tippy.js dependency (-1045 lines net, RichEditor 632→297, MentionEditor 1038→354) https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
…le click behavior - Replace UserName component in ProfileSuggestionList with plain text display so clicking a suggestion autocompletes instead of opening their profile (UserName has an onClick that calls addWindow and stopPropagation) - Add react-virtuoso to ProfileSuggestionList for efficient lazy rendering of up to 20 search results with fixed item height scrolling - Add profile avatars with lazy loading and initial-letter fallback - Fix SuggestionPopover positioning with autoUpdate for scroll/resize tracking - Add size middleware to constrain popover max-height to available viewport space https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
…st with Virtuoso Replace the 8-column grid layout with a vertical list matching the profile suggestion style — each row shows the emoji preview alongside its :shortcode: name. Uses react-virtuoso with fixedItemHeight for lazy rendering and smooth keyboard-driven scrolling through large emoji sets. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
When backspacing over a mention-based node, Tiptap inserts the node's mentionSuggestionChar attribute as undo text. The EmojiMention extension inherits Mention's default of '@', so deleting an emoji left '@' instead of ':'. Fix by explicitly setting mentionSuggestionChar: ':' in the emoji command's attrs for both RichEditor and MentionEditor. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
Tests all 8 custom extensions using headless TipTap Editor instances in jsdom environment (TipTap has no official testing package): - EmojiMention: schema, renderText (unicode vs custom), mentionSuggestionChar attribute handling, backspace behavior regression test - BlobAttachmentRichNode/InlineNode: schema (block vs inline), attributes, renderText URL serialization, parseHTML selectors - NostrEventPreviewRichNode/InlineNode: schema, renderText encoding for note/nevent/naddr back to nostr: URIs - SubmitShortcut: Mod-Enter always submits, Enter behavior with enterSubmits flag - FilePasteHandler: media type filtering (image/video/audio), non-media rejection, mixed paste filtering, edge cases (no files, no callback) - NostrPasteHandler: bech32 regex matching (npub/note/nevent/naddr/nprofile), nostr: prefix handling, URL exclusion, node creation (mention vs preview), surrounding text preservation, multiple entities - Serialization: formatBlobSize, serializeRichContent (emoji tag extraction, blob dedup, address refs), serializeInlineContent (mention→nostr: URI, emoji→shortcode, blob→URL, event preview encoding) 90 new tests total. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
…al testing NostrPasteHandler fixes: - Punctuation after bech32 now matches (npub1..., npub1...! etc.) Changed trailing lookahead from (?=$|\s) to (?=$|\s|[.,!?;:)\]}>]) - Fixed double-space between entities — unconditional " " after every entity caused doubled spaces. Now only adds trailing space when entity is at the very end of pasted text (for cursor positioning). - Tightened regex character class from [\w] to [a-z0-9] to match actual bech32 charset (rejects uppercase, underscore) - Wrapped dispatch in try/catch to handle block-node-at-inline-position errors gracefully (falls back to default paste) Serialization fix: - serializeRichContent now guards blob collection with `url && sha256` matching the defensive checks already in serializeInlineContent. Previously null sha256 would corrupt the dedup Set and null url would produce invalid BlobAttachment entries. Added 22 new edge case tests: - Paste handler: punctuation boundaries, double-space regression, malformed bech32 fallback, uppercase rejection, error resilience - Serialization: empty editor, null sha256/url blobs, invalid pubkey fallback, missing mention attrs, inline dedup, multi-paragraph https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
Both suggestion dropdowns use Virtuoso for virtualized rendering, so they can handle large result sets without performance issues. The previous limits (20 profiles, 24 emojis) were too restrictive — users with many custom emojis sharing a substring or large contact lists couldn't scroll to find the right match. Raised both limits to 200 to allow thorough browsing while still bounding the result set. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
Replace the fixed 1-row grid (8 emojis) with a scrollable virtualized list matching the editor's EmojiSuggestionList look & feel: - Search box at top with magnifying glass icon - Virtuoso-backed scrollable list (8 visible items, unlimited results) - Each row shows emoji icon + :shortcode: label - Keyboard navigation: arrow keys to select, Enter to confirm - Mouse hover highlights, click selects - Frequently used emojis still shown first when no search query - Narrower dialog (max-w-xs) for a compact picker feel https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u
…ptions - Add optional `address` field to EmojiTag in editor/types.ts to match NIP-30 changes from main (30030 emoji set address) - Extend GroupMessageOptions with MetaTagOptions to fix type error in GroupMessageBlueprint's setMetaTags call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…roll - Restore `address` attribute in shared EmojiMention extension (emoji.ts) that was dropped during refactor — required for NIP-30 emoji set tracking - Extract `address` from emoji nodes in both serializeRichContent and serializeInlineContent so it makes it into published events - Fix MentionEditorProps.onSubmit signature: use EmojiTag[] (not the narrower inline type) so address field flows through to callers - Restore UserName component in ProfileSuggestionList for proper display with Grimoire member badges and supporter flame - Remove scrollbar when all items fit: set overflow:hidden on Virtuoso when items.length <= MAX_VISIBLE (profile list, emoji list, emoji picker dialog) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8b74c04 to
5e2e2e9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
imperative document.createElement() to React components via ReactNodeViewRenderer
https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u