Files
readest/apps/readest-app/src/app/reader/utils/annotatorUtil.ts
T
Huang Xin a2d17e6a79 fix: clear highlight overlay when deleting annotation from sidebar, closes #3756 (#3758)
The sidebar delete handler always removed annotations through the note
bubble overlay key (`NOTE_PREFIX + cfi`), but highlights are keyed by
the raw CFI. Deleting a highlight from the sidebar therefore left its
overlay drawn until the book was reopened, while the popup path — which
lets `wrappedFoliateView` default the value to the CFI — worked.

Introduce `removeBookNoteOverlays` that mirrors the draw filters in
`Annotator.tsx` and clears every overlay a BookNote can own (highlight
and/or note bubble), and route the sidebar delete through it.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:25:58 +02:00

89 lines
3.2 KiB
TypeScript

import { HIGHLIGHT_COLOR_HEX } from '@/services/constants';
import { BookNote, DEFAULT_HIGHLIGHT_COLORS, HighlightColor } from '@/types/book';
import { SystemSettings } from '@/types/settings';
import { FoliateView, NOTE_PREFIX } from '@/types/view';
import { Point } from '@/utils/sel';
export const isDefaultHighlightColor = (
color: HighlightColor,
): color is (typeof DEFAULT_HIGHLIGHT_COLORS)[number] => {
return (DEFAULT_HIGHLIGHT_COLORS as readonly string[]).includes(color);
};
export const getHighlightColorHex = (
settings: SystemSettings,
color?: HighlightColor,
): string | undefined => {
if (!color) return undefined;
if (color.startsWith('#')) return color;
const customColors = settings.globalReadSettings.customHighlightColors;
return customColors?.[color] ?? HIGHLIGHT_COLOR_HEX[color];
};
/**
* Returns a user-defined label for the given color, or `undefined` when none is set.
* Callers that want to fall back to a translated default name should handle that in
* the component layer (where `useTranslation` is available).
*/
export const getHighlightColorLabel = (
settings: SystemSettings,
color: HighlightColor,
): string | undefined => {
const { defaultHighlightLabels, userHighlightColors } = settings.globalReadSettings;
if (color.startsWith('#')) {
const hex = color.trim().toLowerCase();
const entry = userHighlightColors?.find((c) => c.hex === hex);
return entry?.label?.trim() || undefined;
}
if (isDefaultHighlightColor(color)) {
return defaultHighlightLabels?.[color]?.trim() || undefined;
}
return undefined;
};
export function getExternalDragHandle(
currentStart: Point,
currentEnd: Point,
externalDragPoint?: Point | null,
): 'start' | 'end' | null {
if (!externalDragPoint) return null;
const distToStart = Math.hypot(
externalDragPoint.x - currentStart.x,
externalDragPoint.y - currentStart.y,
);
const distToEnd = Math.hypot(
externalDragPoint.x - currentEnd.x,
externalDragPoint.y - currentEnd.y,
);
return distToStart < distToEnd ? 'start' : 'end';
}
export function toParentViewportPoint(doc: Document, x: number, y: number): Point {
const frameElement = doc.defaultView?.frameElement;
const frameRect = frameElement?.getBoundingClientRect() ?? { top: 0, left: 0 };
return { x: x + frameRect.left, y: y + frameRect.top };
}
/**
* Remove any overlays drawn for a BookNote from the given view.
*
* A single BookNote can have up to two overlays attached:
* - a highlight/underline/squiggly overlay (keyed by the raw CFI)
* - a note bubble overlay (keyed by `${NOTE_PREFIX}${cfi}`)
*
* The set of overlays drawn is defined by the progress-sync effect in
* Annotator.tsx, and this helper mirrors those filters so that deleting
* an annotation from the sidebar clears every overlay that was drawn
* for it, not just the note bubble.
*/
export function removeBookNoteOverlays(view: FoliateView | null, note: BookNote): void {
if (!view) return;
if (note.type !== 'annotation') return;
if (note.style) {
view.addAnnotation({ ...note, value: note.cfi }, true);
}
if (note.note && note.note.trim().length > 0) {
view.addAnnotation({ ...note, value: `${NOTE_PREFIX}${note.cfi}` }, true);
}
}