mirror of
https://github.com/layer-systems/website.git
synced 2026-09-12 13:43:01 +02:00
feat: highlight text in the Reader (NIP-84) (#31)
* feat: highlight text in the Reader (NIP-84) Adds NIP-84 highlights (kind 9802) to the article reader: - Selecting text in an article shows a floating "Highlight" button (src/apps/articles/HighlightLayer.tsx), publishing the selected plain text tagged to the article (`a`) and its author (`p`, role "author"). - Existing highlights for the article are listed underneath it, with the highlighter's identity and timestamp. Closes #23 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto * fix: harden selection handling and clamp the highlight button Per review: - Guard sel.rangeCount === 0 before calling getRangeAt(0), which throws otherwise. - Scope containment by the range's commonAncestorContainer instead of just anchorNode, so a selection that starts inside the article but is dragged out past its boundary is correctly rejected. - Clamp the floating button's top so a selection near the top of the viewport doesn't push it off-screen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto * fix: don't highlight against a malformed address, gate the listener Per a "needs a closer look" review pass: - articles/index.tsx now passes an empty string, not a malformed "kind:pubkey:" address, when an article has no d tag. HighlightLayer treats a falsy address as "highlighting isn't available here." - The selectionchange listener is only registered when both user and address are present (in the effect's deps), instead of always running selection tracking regardless of whether a highlight could ever be published. - handleHighlight and the floating button both guard on address too, not just selection, so stale selection state from before a prop change went missing can't still trigger a publish. - docs/apps.md corrected: the saved text comes from Selection.toString() (window.getSelection()), not Range.toString(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto --------- Co-authored-by: highperfocused <highperfocused@pm.me> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,12 +96,19 @@ export default function ExampleApp({ setTitle }: AppProps) {
|
||||
| Feed | `feed` | — | kind 1 timeline, Following/Global, composer (⌘↵ publishes) |
|
||||
| Profile | `profile` | `pubkey`, `relays?` | kind 0 metadata, the author's notes, follow/unfollow |
|
||||
| Note | `notes` | `id?`, `relays?` | One note and its replies, or a blank local draft when `id` is absent. **Not** a singleton |
|
||||
| Reader | `articles` | `pubkey?`, `identifier?`, `kind?`, `relays?` | NIP-23 long-form, `react-markdown` |
|
||||
| Reader | `articles` | `pubkey?`, `identifier?`, `kind?`, `relays?` | NIP-23 long-form, `react-markdown`, NIP-84 highlights |
|
||||
| Bookmarks | `bookmarks` | — | NIP-51 kind 10003 list — bookmarked notes and articles |
|
||||
| Relays | `relays` | — | Connection state, subscription count, measured latency |
|
||||
| Settings | `settings` | — | Theme, relay list, Blossom servers, account, session |
|
||||
| About | `about` | — | What this is, the app list, the shortcuts |
|
||||
|
||||
### Highlighting selects against the DOM, not the markdown source
|
||||
|
||||
`HighlightLayer` (`src/apps/articles/HighlightLayer.tsx`) tracks `window.getSelection()`
|
||||
against the rendered article, not the raw markdown — the highlighted text saved to a kind
|
||||
9802 event is whatever that `Selection`'s `.toString()` returns, i.e. the plain-text content
|
||||
the reader actually saw, not markdown syntax.
|
||||
|
||||
### Bookmarks are one whole-list replacement, like follow lists
|
||||
|
||||
kind 10003 is a replaceable event: publishing it replaces the entire list. `useToggleBookmark`
|
||||
|
||||
Reference in New Issue
Block a user