* docs(spec): annotation Share tool + customizable toolbar (#4014)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(plan): implementation plan for Share tool + customizable toolbar (#4014)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(annotator): add 'share' annotation tool type and button (#4014)
* feat(annotator): add pure toolbar order/visibility helpers (#4014)
* feat(annotator): add annotationToolbarItems view setting (#4014)
* feat(annotator): add shareSelectedText ladder helper (#4014)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(annotator): render Share tool and honor toolbar order in selection popup (#4014)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(settings): add drag-and-drop annotation toolbar customizer (#4014)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(settings): open the toolbar customizer from the Behavior panel (#4014)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(i18n): extract and translate annotation share/toolbar strings (#4014)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(annotator): extract canShareText helper, preserve hidden Share on cross-platform edit (#4014)
Addresses final-review findings: de-duplicate the triplicated canShare
definition into share.ts::canShareText, trim ShareCapableService to the
fields actually read, and stop the toolbar customizer from dropping a
synced 'share' tool when edited on a non-share-capable device.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(settings): WYSIWYG drag-and-drop toolbar customizer (#4014)
Rework the customizer per live testing:
- Render 'In toolbar' as a faithful, content-width, start-aligned preview of
the real selection popup (gray bar, icon-only buttons); 'Available' tools
show as labeled chips.
- Multi-container dnd-kit pattern: in-place dragging (no DragOverlay, which a
transformed modal offsets), pointerWithin collision so empty zones accept
drops, live onDragOver reparent, itemsRef to dodge dnd-kit's drag-start
handler-capture stale closure.
- Add 'Add all' (canonical predefined order) and 'Clear all' shortcuts.
- Align zone labels with the SubPageHeader breadcrumb.
- Empty toolbar now suppresses the selection popup entirely (no empty bar),
while still allowing highlight-edit/notes popups.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(i18n): translate Add all / Clear all toolbar shortcuts (#4014)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(annotator): size selection popup to visible tool count (#4014)
With the customizable toolbar a fixed-width popup looked sparse for a 2-3
tool toolbar (buttons spread to the corners). Size the popup to the number
of visible tools (responsive) capped at the previous max; annotated
selections keep the max width since they show highlight options / notes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(settings): reword empty-toolbar hint to 'No tools, drag one here' (#4014)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(annotator): render default tools (not Share) in popup layout screenshot (#4014)
The visual regression test rendered every annotationToolButtons entry, so
adding the Share tool shifted the toolbar to 9 buttons and broke the
baselines. Share is hidden by default (added via Customize Toolbar), so the
popup screenshot should mirror the default-enabled set — filter to
DEFAULT_ANNOTATION_TOOLBAR_ITEMS, keeping the existing baselines valid.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Highlight Current Sentence" keyboard action (default Shift+M, in the
Text to Speech shortcut section) that persists the sentence TTS is reading
aloud as a normal highlight using the user's default style/color — no text
selection, eyes-off, silent, and idempotent (a repeat press on the same
sentence is a no-op rather than a duplicate).
Flow: the shortcut handler in useBookShortcuts dispatches tts-highlight-sentence
→ useTTSControl (which owns the TTSController) resolves the current sentence via
the new TTSController.getSpokenSentence() and relays create-tts-highlight
→ Annotator builds the BookNote with the pure, unit-tested buildTTSSentenceHighlight
helper and persists/renders it like any other highlight.
Closes#4085
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Snap the reading ruler to real rendered text lines instead of stepping by a
fixed arithmetic height, so the band always frames whole lines.
- Snap to actual line geometry from the relocate range; the band is sized
dynamically to the text block plus symmetric padding (round(fontSize *
lineHeight * 0.3)), capped at (lines + 1) line heights so a tall image inside
a block can't expand it to cover the whole figure.
- Drop block/container rects (Range.getClientRects aggregates multi-line element
borders) so paragraphs aren't merged into one giant line and skipped.
- Column-aware in multi-column layouts: the band spans one column at a time and
advances column by column.
- Confine the band to lines at least half visible within the viewport.
- Scrolled mode: snap to lines, and at a view edge scroll the view and realign
the band to the start/end of the new view (works for vertical-rl too, which
scrolls horizontally); paging snaps the view edge between lines so text isn't
cut or repeated.
- Vertical writing mode: correct band centering and drag direction; Up/Down keys
move the ruler while Left/Right turn pages (taps always move the ruler).
- Page turns keep the first/last line: forward lands on the first line of the new
page, backward on the last line; the relayout re-snap anchors on the band's
leading edge so it never skips a line.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>