Files
readest/apps/readest-app/src/app/reader/components/annotator/AnnotationTools.tsx
T
Huang Xin 67c22c770b feat(reader): Share intent + customizable annotation toolbar (#4014) (#4570)
* 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>
2026-06-13 14:11:59 +02:00

105 lines
2.5 KiB
TypeScript

import { IconType } from 'react-icons';
import { FiSearch } from 'react-icons/fi';
import { FiCopy } from 'react-icons/fi';
import { FiShare } from 'react-icons/fi';
import { PiHighlighterFill } from 'react-icons/pi';
import { BsPencilSquare } from 'react-icons/bs';
import { BsTranslate } from 'react-icons/bs';
import { TbHexagonLetterD } from 'react-icons/tb';
import { FaHeadphones } from 'react-icons/fa6';
import { IoIosBuild } from 'react-icons/io';
import { AnnotationToolType } from '@/types/annotator';
import { stubTranslation as _ } from '@/utils/misc';
type AnnotationToolButton = {
type: AnnotationToolType;
label: string;
tooltip: string;
Icon: IconType;
quickAction?: boolean;
};
function createAnnotationToolButtons<T extends AnnotationToolType>(
buttons: AnnotationToolType extends T
? {
[K in T]: {
type: K;
label: string;
tooltip: string;
Icon: IconType;
quickAction?: boolean;
};
}[T][]
: never,
): AnnotationToolButton[] {
return buttons;
}
export const annotationToolButtons = createAnnotationToolButtons([
{
type: 'copy',
label: _('Copy'),
tooltip: _('Copy text after selection'),
Icon: FiCopy,
quickAction: true,
},
{
type: 'highlight',
label: _('Highlight'),
tooltip: _('Highlight text after selection'),
Icon: PiHighlighterFill,
quickAction: true,
},
{
type: 'annotate',
label: _('Annotate'),
tooltip: _('Annotate text after selection'),
Icon: BsPencilSquare,
},
{
type: 'search',
label: _('Search'),
tooltip: _('Search text after selection'),
Icon: FiSearch,
quickAction: true,
},
{
type: 'dictionary',
label: _('Dictionary'),
tooltip: _('Look up text in dictionary after selection'),
Icon: TbHexagonLetterD,
quickAction: true,
},
{
type: 'translate',
label: _('Translate'),
tooltip: _('Translate text after selection'),
Icon: BsTranslate,
quickAction: true,
},
{
type: 'tts',
label: _('Speak'),
tooltip: _('Read text aloud after selection'),
Icon: FaHeadphones,
quickAction: true,
},
{
type: 'proofread',
label: _('Proofread'),
tooltip: _('Proofread text after selection'),
Icon: IoIosBuild,
},
{
type: 'share',
label: _('Share'),
tooltip: _('Share text after selection'),
Icon: FiShare,
quickAction: true,
},
]);
export const annotationToolQuickActions = annotationToolButtons.filter(
(button) => button.quickAction,
);