feat(reader): send end-anchor CFI for EPUB highlights

Web highlights stored only epubcfi_start, so devices received
degenerate pos0 == pos1 (zero-length) highlight ranges. The reader
now collapses the selection range to its end point for a second CFI
and stores it as epubcfi_end (PDF rect anchors reuse the JSON anchor
for both ends).
This commit is contained in:
2026-08-18 19:13:51 -04:00
parent 1585aa1073
commit 97e546b2a4
+16
View File
@@ -412,6 +412,7 @@ document.addEventListener("alpine:init", () => {
note: string;
color: string;
cfi: string;
cfiEnd: string;
percentage: number;
pdfPage: number;
pdfRects: number[][];
@@ -443,6 +444,7 @@ document.addEventListener("alpine:init", () => {
y: 0,
text: "",
cfi: "",
cfiEnd: "",
id: "",
color: "#ffd54f",
note: "",
@@ -701,8 +703,15 @@ document.addEventListener("alpine:init", () => {
const text = sel.toString().replace(/\s+/g, " ").trim();
if (!text) return;
let cfi: string;
let cfiEnd: string;
try {
cfi = this.view.getCFI(index, range);
// Collapse to the end point for a distinct end anchor —
// KOReader sync renders the highlight box from pos0/pos1, and
// pos1 == pos0 would be a degenerate (zero-length) range.
const endRange = range.cloneRange();
endRange.collapse(false);
cfiEnd = this.view.getCFI(index, endRange);
} catch {
return;
}
@@ -715,6 +724,7 @@ document.addEventListener("alpine:init", () => {
y: (iframeRect?.top ?? 0) + rect.top,
text,
cfi,
cfiEnd,
});
};
doc.addEventListener(
@@ -827,6 +837,7 @@ document.addEventListener("alpine:init", () => {
y: (iframeRect?.top ?? 0) + rect.top,
text: h.text,
cfi: h.cfi,
cfiEnd: h.cfiEnd,
id: h.id,
color: h.color,
note: h.note,
@@ -1070,6 +1081,7 @@ document.addEventListener("alpine:init", () => {
y: number;
text: string;
cfi: string;
cfiEnd?: string;
id?: string;
color?: string;
note?: string;
@@ -1080,6 +1092,7 @@ document.addEventListener("alpine:init", () => {
p.mode = opts.mode;
p.text = opts.text;
p.cfi = opts.cfi;
p.cfiEnd = opts.cfiEnd ?? "";
p.id = opts.id ?? "";
p.color = opts.color || "#ffd54f";
p.note = opts.note ?? "";
@@ -1147,6 +1160,7 @@ document.addEventListener("alpine:init", () => {
note: r.note_text ?? "",
color: r.color ?? "#ffff00",
cfi,
cfiEnd: r.epubcfi_end ?? "",
percentage: r.percentage_start ?? 0,
pdfPage,
pdfRects,
@@ -1205,6 +1219,7 @@ document.addEventListener("alpine:init", () => {
start_position: "",
end_position: "",
epubcfi_start: p.pdfPage >= 0 ? pdfAnchor : p.cfi,
epubcfi_end: p.pdfPage >= 0 ? pdfAnchor : p.cfiEnd,
color,
note_text: "",
percentage_start: this.lastRelocateDetail?.fraction ?? 0,
@@ -1263,6 +1278,7 @@ document.addEventListener("alpine:init", () => {
start_position: "",
end_position: "",
epubcfi_start: anchor,
epubcfi_end: p.pdfPage >= 0 ? anchor : p.cfiEnd,
color: p.color,
note_text: p.note,
}),