Windows ga (#358)
* Enhance Cloud TTS with navigation controls and shared UI overlay in desktop app * Refactor Cloud TTS voice settings and remove standalone settings overlay on desktop app * Persist reader window state and improve slider interaction in desktop app * Improve PDF page transitions and refine focus management in desktop app * Refactor scrollbar interaction and adjust desktop modal focus handling * Improve PDF sidecar synchronization and cross-platform metadata compatibility * Refactor PDF annotation comment logic to shared module and implement Desktop UI * Refactor reader screen to use tap-to-toggle and full-width styling in desktop app * Refactor reader workspace layout and chrome-panel interactions * Implement global search keyboard shortcuts and focusable chrome layers * Implement flavor-specific legal links and update the About UI * Refactor reader UI controls on desktop app * Enhance desktop folder sync with background metadata extraction and improved error handling * Refactor Library UI and remove redundant Home tab in desktop app * Add custom tooltips to reader icon buttons in desktop app * Integrate app theme controls into reader interfaces on desktop * Add right-to-left pagination support and improve focus restoration on desktop app * Improve EPUB pagination geometry and diagnostic logging for layout cutoffs on desktop app * Update desktop reader defaults and implement settings migration * Implement block-based position tracking in ReaderLocator * Enhance EPUB highlighting reliability in desktop app * Add support for custom reader themes and update highlight palette logic in desktop app * Replace the Tools panel with a "More" dropdown menu and refactor account UI * Implement account profile header in desktop sidebar * Implement cloud sync reliability improvements and sidebar toggle on desktop app * Improve EPUB annotation synchronization and highlight mapping accuracy in desktop app * Integrate WebView2 for EPUB vertical rendering on Windows * Refactor reader layout logic and enhance WebView2 diagnostics * Improve vertical reading layout and WebView2 resizing on Desktop * Refine vertical reading mode layout and margin handling * Enhance reader locator precision and Desktop mode-switching reliability * Implement chapter-level caching and warm-start pagination in desktop app * Replace bundled KCEF with native system webviews via SWT * Refactor EPUB page info bar visibility and layout logic * Improve PDF toolbar persistence and fix tab reactivation logic * Enable multi-selection and bulk operations for custom fonts * Refactor instrumentation tests * Add EPUB UI test fixture and initial instrumentation tests * Expand EpubReader UI tests and improve accessibility * Add instrumentation tests and test tags for library and reader screens * Enhance OPDS parser logic and catalog integration * Add support for toggling local synchronization on a per-folder basis. * Implement tri-state sizing for the TTS overlay * Persist TTS overlay size across sessions * Refactor reader brightness control and add incremental step buttons * Improve CSS support, pagination control, and style-aware semantic caching * Improve link handling, interaction, and diagnostics in the paginated reader * crash fixes * Implement persistent pending removal for external files * Implement book-specific word replacements * Add native vertical reading mode with custom renderer * Implement text selection and navigation improvements for the native vertical reader * Implement locator-based navigation and improved vertical scrolling in native vertical mode in epub * Implement lazy loading and chapter prefetching for native vertical reader * Improve window lifecycle and disposal handling on Desktop * Optimize vertical reading performance in desktop app * Enhance TTS start accuracy and diagnostic logging on desktop * Refactor AI settings visibility on desktop * Improve pagination height measurement and enhance cutoff diagnostics * Implement lifecycle management and improve justified text splitting for pagination * Refine AI usage tracking and force AI feature visibility on Desktop * Add descriptive context comments and usage examples to string and plural resources. * Optimize performance and memory usage in search and state mapping * Replace reader page sliders with minimal slider and navigation controls * Add support for CBT comic archives * Harden file path validation and XML parsing to prevent security vulnerabilities * Implement local account profile caching and optimize desktop performance * Improve desktop persistence reliability and add Linux secure storage support * Improved PDF zoom stability and layout prediction during zoom commits * Improved PDF spread layout prediction, reader focus restoration, and account profile caching * Enhance highlight precision and scoping using block-local offsets and CFIs * Enhance cloud book content synchronization and background downloads * Implement granular timestamp tracking for reading positions and PDF annotations * Restrict diagnostic logging and stack traces to debug builds * Refine PDF page gaps and reader chrome interaction logic * Refactor PDF highlight rendering and overhaul Desktop sidebar UI * Implement a new interaction dock and undo/redo history for PDF annotations in desktop * Enhance PDF color picker and improve navigation scroll restoration * Add highlight palette customization and improve selection menu UI in desktop app epub reader * Enhance desktop shelf management and library organization
This commit is contained in:
parent
5971eaa571
commit
83dcafa4b6
444 changed files with 47279 additions and 8096 deletions
|
|
@ -2924,6 +2924,29 @@
|
|||
};
|
||||
|
||||
const HL_LOG_TAG = "HIGHLIGHT_DEBUG";
|
||||
const HL_RENDER_LOG_TAG = "AndroidHighlightRenderDiag";
|
||||
|
||||
function hlRenderPreview(value, maxLength) {
|
||||
if (value === undefined || value === null) return "";
|
||||
var text = String(value).replace(/\s+/g, " ");
|
||||
var max = maxLength || 100;
|
||||
return text.length > max ? text.substring(0, max) : text;
|
||||
}
|
||||
|
||||
function hlRenderLocatorLabel(locator) {
|
||||
locator = locator || {};
|
||||
return "locatorChapter=" + (locator.chapterIndex === undefined || locator.chapterIndex === null ? "null" : locator.chapterIndex) +
|
||||
" locatorPage=" + (locator.pageIndex === undefined || locator.pageIndex === null ? "null" : locator.pageIndex) +
|
||||
" locatorOffsets=" + (locator.startOffset === undefined || locator.startOffset === null ? "null" : locator.startOffset) +
|
||||
".." + (locator.endOffset === undefined || locator.endOffset === null ? "null" : locator.endOffset) +
|
||||
" locatorBlock=" + (locator.blockIndex === undefined || locator.blockIndex === null ? "null" : locator.blockIndex) +
|
||||
" locatorChar=" + (locator.charOffset === undefined || locator.charOffset === null ? "null" : locator.charOffset) +
|
||||
" locatorCfi=" + hlRenderPreview(locator.cfi || "", 120);
|
||||
}
|
||||
|
||||
function hlRenderLog(message) {
|
||||
console.log(HL_RENDER_LOG_TAG + ": " + message);
|
||||
}
|
||||
|
||||
window.HighlightBridgeHelper = {
|
||||
updateHighlightStyle: function (cfi, newColorClass, colorId) {
|
||||
|
|
@ -3280,11 +3303,13 @@
|
|||
try {
|
||||
var highlights = JSON.parse(jsonArrayString);
|
||||
var self = this;
|
||||
hlRenderLog("webview_restore_start count=" + highlights.length);
|
||||
|
||||
highlights.forEach(function (h) {
|
||||
self.applyHighlight(h.cfi, h.text, h.cssClass);
|
||||
self.applyHighlightObject(h);
|
||||
});
|
||||
} catch (e) {
|
||||
hlRenderLog("webview_restore_error error=" + hlRenderPreview(e && e.message ? e.message : e, 160));
|
||||
console.log(
|
||||
`$ {
|
||||
HL_LOG_TAG
|
||||
|
|
@ -3295,136 +3320,376 @@
|
|||
}
|
||||
},
|
||||
|
||||
applyHighlight: function (cfi, text, cssClass) {
|
||||
applyHighlightObject: function (highlight) {
|
||||
if (!highlight) return;
|
||||
hlRenderLog(
|
||||
"webview_apply_object id=" + (highlight.id || "") +
|
||||
" cfi=" + hlRenderPreview(highlight.cfi || "", 120) +
|
||||
" textLen=" + String(highlight.text || "").length +
|
||||
" text='" + hlRenderPreview(highlight.text || "", 80) + "' " +
|
||||
hlRenderLocatorLabel(highlight.locator || {})
|
||||
);
|
||||
this.applyHighlight(highlight.cfi, highlight.text, highlight.cssClass, highlight.locator || null);
|
||||
},
|
||||
|
||||
highlightTextRoot: function () {
|
||||
return document.getElementById("content-container") || document.body;
|
||||
},
|
||||
|
||||
highlightTextNodes: function (root) {
|
||||
var nodes = [];
|
||||
if (!root) return nodes;
|
||||
var walker = document.createTreeWalker(
|
||||
root,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
{
|
||||
acceptNode: function (node) {
|
||||
if (!node || !node.nodeValue) return NodeFilter.FILTER_REJECT;
|
||||
var parent = node.parentElement;
|
||||
if (!parent) return NodeFilter.FILTER_REJECT;
|
||||
if (parent.closest && parent.closest("script, style, noscript")) return NodeFilter.FILTER_REJECT;
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
},
|
||||
false,
|
||||
);
|
||||
while (walker.nextNode()) nodes.push(walker.currentNode);
|
||||
return nodes;
|
||||
},
|
||||
|
||||
rangeFromTextOffsets: function (root, startOffset, endOffset) {
|
||||
var start = parseInt(startOffset, 10);
|
||||
var end = parseInt(endOffset, 10);
|
||||
if (!root || !isFinite(start) || !isFinite(end) || end <= start) {
|
||||
hlRenderLog(
|
||||
"webview_range_offsets_skip reason=invalid_offsets start=" + startOffset +
|
||||
" end=" + endOffset + " hasRoot=" + !!root
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
var nodes = this.highlightTextNodes(root);
|
||||
var cursor = 0;
|
||||
var startNode = null;
|
||||
var startInNode = 0;
|
||||
var endNode = null;
|
||||
var endInNode = 0;
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var value = nodes[i].nodeValue || "";
|
||||
var next = cursor + value.length;
|
||||
if (!startNode && start >= cursor && start <= next) {
|
||||
startNode = nodes[i];
|
||||
startInNode = Math.max(0, Math.min(value.length, start - cursor));
|
||||
}
|
||||
if (startNode && end >= cursor && end <= next) {
|
||||
endNode = nodes[i];
|
||||
endInNode = Math.max(0, Math.min(value.length, end - cursor));
|
||||
break;
|
||||
}
|
||||
cursor = next;
|
||||
}
|
||||
|
||||
if (!startNode || !endNode) {
|
||||
hlRenderLog(
|
||||
"webview_range_offsets_skip reason=missing_boundary start=" + start +
|
||||
" end=" + end + " nodes=" + nodes.length + " textCursor=" + cursor
|
||||
);
|
||||
return null;
|
||||
}
|
||||
var range = document.createRange();
|
||||
range.setStart(startNode, startInNode);
|
||||
range.setEnd(endNode, endInNode);
|
||||
if (range.collapsed) {
|
||||
hlRenderLog("webview_range_offsets_skip reason=collapsed start=" + start + " end=" + end);
|
||||
return null;
|
||||
}
|
||||
hlRenderLog(
|
||||
"webview_range_offsets_result start=" + start + " end=" + end +
|
||||
" text='" + hlRenderPreview(range.toString(), 80) + "'"
|
||||
);
|
||||
return range;
|
||||
},
|
||||
|
||||
rangeMatchesText: function (range, text) {
|
||||
if (!range || !text) return true;
|
||||
var actual = (range.toString() || "").trim();
|
||||
var expected = String(text || "").trim();
|
||||
if (!expected) return true;
|
||||
return actual === expected || actual.replace(/\s+/g, " ") === expected.replace(/\s+/g, " ");
|
||||
},
|
||||
|
||||
rangeFromVisibleTextSearch: function (text, locator) {
|
||||
if (!text) {
|
||||
hlRenderLog("webview_text_search_skip reason=empty_text " + hlRenderLocatorLabel(locator));
|
||||
return null;
|
||||
}
|
||||
var root = this.highlightTextRoot();
|
||||
var nodes = this.highlightTextNodes(root);
|
||||
if (!nodes.length) {
|
||||
hlRenderLog("webview_text_search_skip reason=no_nodes " + hlRenderLocatorLabel(locator));
|
||||
return null;
|
||||
}
|
||||
|
||||
var fullText = nodes.map(function (node) { return node.nodeValue || ""; }).join("");
|
||||
var expected = String(text);
|
||||
var candidates = [];
|
||||
var index = fullText.indexOf(expected);
|
||||
while (index !== -1) {
|
||||
candidates.push(index);
|
||||
index = fullText.indexOf(expected, index + 1);
|
||||
}
|
||||
if (!candidates.length) {
|
||||
var lowerFull = fullText.toLowerCase();
|
||||
var lowerExpected = expected.toLowerCase();
|
||||
index = lowerFull.indexOf(lowerExpected);
|
||||
while (index !== -1) {
|
||||
candidates.push(index);
|
||||
index = lowerFull.indexOf(lowerExpected, index + 1);
|
||||
}
|
||||
}
|
||||
if (!candidates.length && expected.length > 20) {
|
||||
var partial = expected.substring(0, Math.min(expected.length, 40));
|
||||
index = fullText.indexOf(partial);
|
||||
while (index !== -1) {
|
||||
candidates.push(index);
|
||||
index = fullText.indexOf(partial, index + 1);
|
||||
}
|
||||
}
|
||||
if (!candidates.length) {
|
||||
hlRenderLog(
|
||||
"webview_text_search_skip reason=no_candidates textLen=" + expected.length +
|
||||
" text='" + hlRenderPreview(expected, 80) + "' " + hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
var preferred = locator && locator.startOffset !== undefined && locator.startOffset !== null
|
||||
? parseInt(locator.startOffset, 10)
|
||||
: candidates[0];
|
||||
if (!isFinite(preferred)) preferred = candidates[0];
|
||||
var best = candidates.reduce(function (currentBest, candidate) {
|
||||
return Math.abs(candidate - preferred) < Math.abs(currentBest - preferred) ? candidate : currentBest;
|
||||
}, candidates[0]);
|
||||
hlRenderLog(
|
||||
"webview_text_search_candidate count=" + candidates.length +
|
||||
" preferred=" + preferred + " best=" + best +
|
||||
" text='" + hlRenderPreview(expected, 80) + "' " + hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return this.rangeFromTextOffsets(root, best, best + expected.length);
|
||||
},
|
||||
|
||||
rangeFromLocator: function (locator, text) {
|
||||
if (!locator) {
|
||||
hlRenderLog("webview_locator_skip reason=missing_locator");
|
||||
return null;
|
||||
}
|
||||
if (locator.cfi && String(locator.cfi).charAt(0) === "/") {
|
||||
hlRenderLog(
|
||||
"webview_locator_skip reason=structural_cfi_prefers_cfi " +
|
||||
hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
var root = this.highlightTextRoot();
|
||||
var range = this.rangeFromTextOffsets(root, locator.startOffset, locator.endOffset);
|
||||
if (range && this.rangeMatchesText(range, text)) {
|
||||
hlRenderLog(
|
||||
"webview_locator_result matched=true text='" + hlRenderPreview(range.toString(), 80) + "' " +
|
||||
hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return range;
|
||||
}
|
||||
hlRenderLog(
|
||||
"webview_locator_skip reason=" + (range ? "text_mismatch" : "range_missing") +
|
||||
" rangeText='" + hlRenderPreview(range ? range.toString() : "", 80) + "' " +
|
||||
"expected='" + hlRenderPreview(text || "", 80) + "' " + hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return null;
|
||||
},
|
||||
|
||||
rangeFromCfi: function (cfi, text) {
|
||||
if (!cfi || cfi.indexOf("desktop:") === 0) {
|
||||
hlRenderLog("webview_cfi_skip reason=unsupported cfi=" + hlRenderPreview(cfi || "", 120));
|
||||
return null;
|
||||
}
|
||||
var sourceCfi = String(cfi).split("|")[0];
|
||||
const location = window.getNodeAndOffsetFromCfi(sourceCfi);
|
||||
if (!location || !location.node) {
|
||||
hlRenderLog("webview_cfi_skip reason=missing_location cfi=" + hlRenderPreview(cfi, 120));
|
||||
return null;
|
||||
}
|
||||
|
||||
let startNode = location.node;
|
||||
let startOffset = location.offset;
|
||||
|
||||
if (startNode.nodeType === Node.TEXT_NODE) {
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
|
||||
walker.currentNode = startNode;
|
||||
|
||||
while (startNode && startOffset >= startNode.nodeValue.length) {
|
||||
if (startOffset === startNode.nodeValue.length) {
|
||||
const next = walker.nextNode();
|
||||
|
||||
if (next) {
|
||||
startOffset -= startNode.nodeValue.length;
|
||||
startNode = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
startOffset -= startNode.nodeValue.length;
|
||||
startNode = walker.nextNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text && text.length > 0 && startNode && startNode.nodeType === Node.TEXT_NODE) {
|
||||
const nodeVal = startNode.nodeValue;
|
||||
const substring = nodeVal.substring(startOffset, startOffset + text.length);
|
||||
|
||||
if (substring !== text && substring.trim() !== text.trim()) {
|
||||
hlRenderLog(
|
||||
"webview_cfi_text_mismatch cfi=" + hlRenderPreview(cfi, 120) +
|
||||
" startOffset=" + startOffset +
|
||||
" actual='" + hlRenderPreview(substring, 80) + "'" +
|
||||
" expected='" + hlRenderPreview(text, 80) + "'"
|
||||
);
|
||||
console.log("HIGHLIGHT_DEBUG: Text mismatch at CFI. Searching nearby.");
|
||||
const foundIndex = nodeVal.indexOf(text);
|
||||
|
||||
if (foundIndex !== -1) {
|
||||
hlRenderLog(
|
||||
"webview_cfi_text_adjust reason=full_match oldStartOffset=" + startOffset +
|
||||
" newStartOffset=" + foundIndex + " cfi=" + hlRenderPreview(cfi, 120)
|
||||
);
|
||||
startOffset = foundIndex;
|
||||
} else {
|
||||
const partial = text.substring(0, Math.min(text.length, 20));
|
||||
const partialIndex = nodeVal.indexOf(partial);
|
||||
|
||||
if (partialIndex !== -1) {
|
||||
hlRenderLog(
|
||||
"webview_cfi_text_adjust reason=partial_match oldStartOffset=" + startOffset +
|
||||
" newStartOffset=" + partialIndex + " cfi=" + hlRenderPreview(cfi, 120)
|
||||
);
|
||||
startOffset = partialIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startNode) {
|
||||
hlRenderLog("webview_cfi_skip reason=missing_start_node cfi=" + hlRenderPreview(cfi, 120));
|
||||
return null;
|
||||
}
|
||||
|
||||
const range = document.createRange();
|
||||
|
||||
if (startNode.nodeType === Node.TEXT_NODE && startOffset > startNode.nodeValue.length) {
|
||||
startOffset = Math.max(0, startNode.nodeValue.length - 1);
|
||||
}
|
||||
|
||||
range.setStart(startNode, startOffset);
|
||||
|
||||
const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
|
||||
treeWalker.currentNode = startNode;
|
||||
|
||||
let remainingLen = text.length;
|
||||
let endNode = treeWalker.currentNode;
|
||||
let endOffset = startOffset;
|
||||
|
||||
while (remainingLen > 0 && endNode) {
|
||||
let avail = endNode.nodeValue.length - endOffset;
|
||||
|
||||
if (avail >= remainingLen) {
|
||||
endOffset += remainingLen;
|
||||
remainingLen = 0;
|
||||
} else {
|
||||
remainingLen -= avail;
|
||||
endNode = treeWalker.nextNode();
|
||||
endOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!endNode) {
|
||||
hlRenderLog("webview_cfi_skip reason=missing_end_node cfi=" + hlRenderPreview(cfi, 120));
|
||||
return null;
|
||||
}
|
||||
range.setEnd(endNode, endOffset);
|
||||
if (range.collapsed) {
|
||||
hlRenderLog("webview_cfi_skip reason=collapsed cfi=" + hlRenderPreview(cfi, 120));
|
||||
return null;
|
||||
}
|
||||
hlRenderLog(
|
||||
"webview_cfi_result cfi=" + hlRenderPreview(cfi, 120) +
|
||||
" text='" + hlRenderPreview(range.toString(), 80) + "'"
|
||||
);
|
||||
return range;
|
||||
},
|
||||
|
||||
applyHighlight: function (cfi, text, cssClass, locator) {
|
||||
try {
|
||||
cssClass = cssClass || "user-highlight-yellow";
|
||||
var alreadyApplied = false;
|
||||
var spans = document.querySelectorAll(`span[data-cfi]`);
|
||||
for (var i = 0; i < spans.length; i++) {
|
||||
if ((spans[i].getAttribute("data-cfi") || "").split(";;").includes(cfi)) {
|
||||
if (cfi && (spans[i].getAttribute("data-cfi") || "").split(";;").includes(cfi)) {
|
||||
alreadyApplied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alreadyApplied) return;
|
||||
|
||||
const location = window.getNodeAndOffsetFromCfi(cfi);
|
||||
if (!location || !location.node) return;
|
||||
|
||||
let startNode = location.node;
|
||||
let startOffset = location.offset;
|
||||
|
||||
if (startNode.nodeType === Node.TEXT_NODE) {
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
|
||||
walker.currentNode = startNode;
|
||||
|
||||
while (startNode && startOffset >= startNode.nodeValue.length) {
|
||||
if (startOffset === startNode.nodeValue.length) {
|
||||
const next = walker.nextNode();
|
||||
|
||||
if (next) {
|
||||
startOffset -= startNode.nodeValue.length;
|
||||
startNode = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
startOffset -= startNode.nodeValue.length;
|
||||
startNode = walker.nextNode();
|
||||
}
|
||||
}
|
||||
if (alreadyApplied) {
|
||||
hlRenderLog(
|
||||
"webview_apply_skip reason=already_applied cfi=" + hlRenderPreview(cfi || "", 120) +
|
||||
" textLen=" + String(text || "").length + " " + hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Text Verification / Healing
|
||||
if (text && text.length > 0 && startNode && startNode.nodeType === Node.TEXT_NODE) {
|
||||
const nodeVal = startNode.nodeValue;
|
||||
// Check if text matches at exact offset
|
||||
const substring = nodeVal.substring(startOffset, startOffset + text.length);
|
||||
|
||||
// Allow for some whitespace looseness (trim comparison)
|
||||
if (substring !== text && substring.trim() !== text.trim()) {
|
||||
console.log(`$ {
|
||||
HL_LOG_TAG
|
||||
}
|
||||
|
||||
: Text mismatch at CFI. Searching nearby... Expected: '${text.substring(0, 10)}...', Found: '${substring.substring(0, 10)}...' `);
|
||||
|
||||
// Try finding the text in the whole node
|
||||
const foundIndex = nodeVal.indexOf(text);
|
||||
|
||||
if (foundIndex !== -1) {
|
||||
console.log(`$ {
|
||||
HL_LOG_TAG
|
||||
}
|
||||
|
||||
: Found text elsewhere in node. Adjusting offset from $ {
|
||||
startOffset
|
||||
}
|
||||
|
||||
to $ {
|
||||
foundIndex
|
||||
}
|
||||
|
||||
.`);
|
||||
startOffset = foundIndex;
|
||||
} else {
|
||||
// Simple fuzzy: Try finding first 20 chars
|
||||
const partial = text.substring(0, Math.min(text.length, 20));
|
||||
const partialIndex = nodeVal.indexOf(partial);
|
||||
|
||||
if (partialIndex !== -1) {
|
||||
console.log(`$ {
|
||||
HL_LOG_TAG
|
||||
}
|
||||
|
||||
: Found partial match. Adjusting offset.`);
|
||||
startOffset = partialIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
var hasPreciseLocator = locator &&
|
||||
locator.startOffset !== undefined && locator.startOffset !== null &&
|
||||
locator.endOffset !== undefined && locator.endOffset !== null &&
|
||||
parseInt(locator.endOffset, 10) > parseInt(locator.startOffset, 10);
|
||||
hlRenderLog(
|
||||
"webview_apply_start cfi=" + hlRenderPreview(cfi || "", 120) +
|
||||
" textLen=" + String(text || "").length +
|
||||
" cssClass=" + cssClass +
|
||||
" hasPreciseLocator=" + !!hasPreciseLocator + " " +
|
||||
hlRenderLocatorLabel(locator)
|
||||
);
|
||||
var rangeSource = "locator";
|
||||
var sourceCfi = (locator && locator.cfi) || cfi;
|
||||
var hasSourceCfi = sourceCfi && String(sourceCfi).charAt(0) === "/";
|
||||
var range = hasSourceCfi ? null : this.rangeFromLocator(locator, text);
|
||||
if (!range && hasSourceCfi) {
|
||||
rangeSource = "cfi";
|
||||
range = this.rangeFromCfi(sourceCfi, text || "");
|
||||
}
|
||||
|
||||
if (!startNode) return;
|
||||
|
||||
const range = document.createRange();
|
||||
|
||||
// Set Start
|
||||
if (startNode.nodeType === Node.TEXT_NODE) {
|
||||
// Ensure offset is valid
|
||||
if (startOffset > startNode.nodeValue.length) {
|
||||
startOffset = Math.max(0, startNode.nodeValue.length - 1);
|
||||
}
|
||||
if (!range) {
|
||||
rangeSource = "locator";
|
||||
range = this.rangeFromLocator(locator, text);
|
||||
}
|
||||
|
||||
range.setStart(startNode, startOffset);
|
||||
|
||||
const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
|
||||
treeWalker.currentNode = startNode;
|
||||
|
||||
let currentNode = treeWalker.currentNode;
|
||||
let remainingOffset = startOffset;
|
||||
let remainingLen = text.length;
|
||||
let endNode = currentNode;
|
||||
let endOffset = startOffset;
|
||||
|
||||
while (remainingLen > 0 && endNode) {
|
||||
let avail = endNode.nodeValue.length - endOffset;
|
||||
|
||||
if (avail >= remainingLen) {
|
||||
endOffset += remainingLen;
|
||||
remainingLen = 0;
|
||||
} else {
|
||||
remainingLen -= avail;
|
||||
endNode = treeWalker.nextNode();
|
||||
endOffset = 0;
|
||||
}
|
||||
if (!range && !hasPreciseLocator && !hasSourceCfi) {
|
||||
rangeSource = "text_search";
|
||||
range = this.rangeFromVisibleTextSearch(text || "", locator);
|
||||
}
|
||||
|
||||
if (endNode) {
|
||||
range.setEnd(endNode, endOffset);
|
||||
var normalizedRange = this.normalizeRangeBoundaries(range);
|
||||
this.highlightRangeSafe(normalizedRange, cssClass, cfi);
|
||||
if (!range) {
|
||||
hlRenderLog(
|
||||
"webview_apply_skip reason=no_range cfi=" + hlRenderPreview(cfi || "", 120) +
|
||||
" hasPreciseLocator=" + !!hasPreciseLocator + " " + hlRenderLocatorLabel(locator)
|
||||
);
|
||||
return;
|
||||
}
|
||||
var normalizedRange = this.normalizeRangeBoundaries(range);
|
||||
this.highlightRangeSafe(normalizedRange, cssClass, cfi);
|
||||
hlRenderLog(
|
||||
"webview_apply_result applied=true cfi=" + hlRenderPreview(cfi || "", 120) +
|
||||
" source=" + rangeSource +
|
||||
" renderedText='" + hlRenderPreview(normalizedRange.toString(), 80) + "'"
|
||||
);
|
||||
} catch (e) {
|
||||
hlRenderLog("webview_apply_error error=" + hlRenderPreview(e && e.message ? e.message : e, 160));
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue