forked from akai/readest
67c22c770b
* 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>
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import { describe, test, expect } from 'vitest';
|
|
import { annotationToolButtons } from '@/app/reader/components/annotator/AnnotationTools';
|
|
import {
|
|
ALL_ANNOTATION_TOOL_TYPES,
|
|
DEFAULT_ANNOTATION_TOOLBAR_ITEMS,
|
|
getToolbarToolTypes,
|
|
getAvailableToolTypes,
|
|
addToolToToolbar,
|
|
removeToolFromToolbar,
|
|
reorderToolbar,
|
|
} from '@/utils/annotationToolbar';
|
|
|
|
describe('annotationToolbar helpers', () => {
|
|
test('ALL_ANNOTATION_TOOL_TYPES matches the button registry order', () => {
|
|
expect(ALL_ANNOTATION_TOOL_TYPES).toEqual(annotationToolButtons.map((b) => b.type));
|
|
});
|
|
|
|
test('default toolbar is the eight non-share tools in canonical order', () => {
|
|
expect(DEFAULT_ANNOTATION_TOOLBAR_ITEMS).toEqual([
|
|
'copy',
|
|
'highlight',
|
|
'annotate',
|
|
'search',
|
|
'dictionary',
|
|
'translate',
|
|
'tts',
|
|
'proofread',
|
|
]);
|
|
expect(DEFAULT_ANNOTATION_TOOLBAR_ITEMS).not.toContain('share');
|
|
});
|
|
|
|
test('getToolbarToolTypes preserves order and falls back to default when undefined', () => {
|
|
expect(getToolbarToolTypes(undefined, true)).toEqual(DEFAULT_ANNOTATION_TOOLBAR_ITEMS);
|
|
expect(getToolbarToolTypes(['search', 'copy'], true)).toEqual(['search', 'copy']);
|
|
});
|
|
|
|
test('getToolbarToolTypes drops share when !canShare, keeps it when canShare', () => {
|
|
expect(getToolbarToolTypes(['copy', 'share'], false)).toEqual(['copy']);
|
|
expect(getToolbarToolTypes(['copy', 'share'], true)).toEqual(['copy', 'share']);
|
|
});
|
|
|
|
test('getToolbarToolTypes drops unknown/duplicate entries', () => {
|
|
expect(getToolbarToolTypes(['copy', 'copy', 'bogus' as never], true)).toEqual(['copy']);
|
|
});
|
|
|
|
test('getAvailableToolTypes returns canonical-order complement', () => {
|
|
expect(getAvailableToolTypes(['copy'], true)).toEqual([
|
|
'highlight',
|
|
'annotate',
|
|
'search',
|
|
'dictionary',
|
|
'translate',
|
|
'tts',
|
|
'proofread',
|
|
'share',
|
|
]);
|
|
});
|
|
|
|
test('getAvailableToolTypes hides share when !canShare', () => {
|
|
expect(getAvailableToolTypes(['copy'], false)).not.toContain('share');
|
|
});
|
|
|
|
test('addToolToToolbar appends by default and is a no-op when present', () => {
|
|
expect(addToolToToolbar(['copy'], 'share')).toEqual(['copy', 'share']);
|
|
expect(addToolToToolbar(['copy', 'share'], 'share')).toEqual(['copy', 'share']);
|
|
});
|
|
|
|
test('addToolToToolbar inserts at the given index', () => {
|
|
expect(addToolToToolbar(['copy', 'search'], 'share', 1)).toEqual(['copy', 'share', 'search']);
|
|
});
|
|
|
|
test('removeToolFromToolbar removes the tool', () => {
|
|
expect(removeToolFromToolbar(['copy', 'share'], 'share')).toEqual(['copy']);
|
|
expect(removeToolFromToolbar(['copy'], 'share')).toEqual(['copy']);
|
|
});
|
|
|
|
test('reorderToolbar moves a tool to another tool position', () => {
|
|
expect(reorderToolbar(['copy', 'highlight', 'search'], 'search', 'copy')).toEqual([
|
|
'search',
|
|
'copy',
|
|
'highlight',
|
|
]);
|
|
expect(reorderToolbar(['copy', 'search'], 'copy', 'copy')).toEqual(['copy', 'search']);
|
|
});
|
|
});
|