Update v1.0.49 (#330)
* Added PDF top tab strip visibility toggle and fixed WebView hit test NPE * Refactored desktop reader screens and state management into specialized components * Added image gallery to reader sidebar and refactored desktop PDF UI components * Implemented EPUB image gallery * Refactored reader and library models to use shared common types, centralizing file type resolution and texture management while removing redundant mapping logic * Standardized UI styling and refactored app navigation layout * Implemented auto-hiding reader chrome and activity tracking in desktop * Refactored reader panels into distinct left and right modal layers with platform-specific sizing and keyboard navigation support. * Added PPTX support for desktop and refactored parsing into a shared module * Implement paid AI features and account management for the desktop application. * Implement AI Hub and enhanced Cloud TTS integration for Desktop * Implement streaming support for AI definition and summarization features * Implement support for password-protected PDFs and file actions in the desktop reader. * Implement cloud synchronization for desktop using Firestore and Google Drive * Implement PDF reflow and "Text View" for the desktop reader * Refactor OPDS logic to use SharedOpdsController * Optimize PDF tile rendering performance * Implement two-page spread support for PDF pagination * Implement two-page spread support for the PDF viewer * Improved shared spread zoom in PDF viewer * Improve PDF spread navigation with fling support and configurable page gaps * Add brightness control to PDF and EPUB readers * Refactor folder synchronization to use shared logic engine * Implement safe string formatting and validation for localized resources * Implement TTS chunk skip navigation * Implement deep-linking and playback controls for TTS media sessions * Implement start index for TTS playback * Improve TTS navigation, prefetching, and notification duration reporting * Implement TTS mini playback bar for background reading * Implement multi-window reader support for the desktop application * Improve desktop modal window management and visibility syncing * Implement localized string support for Desktop and shared UI * Implement language selection and persistence for Desktop * Implement plural string support for Desktop and migrate hardcoded counts to plurals.xml * Implement localized banner messages and UI strings using resource-backed SharedText * Implement compact badge styling for small book covers * Refactor PDF native interaction and improve HTML import memory safety * fix language persistence * Refactor reader overflow menus to use section-based logic * Refactor PDF layout remapping and improve text box interaction * Improve CFI resolution and TTS resume accuracy using dynamic chunk offsets * Centralize PDF annotation export mapping and improve metadata handling * Add support for threaded comments in PDF highlight annotations * Flatten highlight comments into a single thread for PDF export and allow author editing * Integrate page slider into reader chrome and persist toggle state * Handle fragments and queries in EPUB chapter paths * Implement dynamic, theme-aware coloring for the reader slider * Implement customizable app-wide font preference * Implement one-hand zoom gestures in the PDF viewer * Implement File Information dialog for PDF and EPUB readers * Bump version to 1.0.49 (53) * Refactor PDF reader logic into modular components * Add ProGuard rules to prevent R8 optimization issues in EPUB reader screens * Add option to use PDF filenames as display names * Fix preservation of PDF filename display preference in library projection
This commit is contained in:
parent
dc5196526f
commit
9510293ac3
245 changed files with 37538 additions and 12460 deletions
|
|
@ -2043,6 +2043,56 @@
|
|||
`);
|
||||
}
|
||||
|
||||
function parseReaderChunkInt(value, fallback) {
|
||||
const parsed = parseInt(value, 10);
|
||||
return isNaN(parsed) ? fallback : parsed;
|
||||
}
|
||||
|
||||
function getReaderChunkIndex(chunkElement) {
|
||||
return parseReaderChunkInt(chunkElement && chunkElement.dataset ? chunkElement.dataset.chunkIndex : null, 0);
|
||||
}
|
||||
|
||||
function getReaderChunkElementStartIndex(chunkElement) {
|
||||
const chunkIndex = getReaderChunkIndex(chunkElement);
|
||||
return parseReaderChunkInt(
|
||||
chunkElement && chunkElement.dataset ? chunkElement.dataset.elementStartIndex : null,
|
||||
chunkIndex * 20
|
||||
);
|
||||
}
|
||||
|
||||
function getReaderChunkElementCount(chunkElement) {
|
||||
return parseReaderChunkInt(
|
||||
chunkElement && chunkElement.dataset ? chunkElement.dataset.elementCount : null,
|
||||
20
|
||||
);
|
||||
}
|
||||
|
||||
function findReaderChunkForElementIndex(container, childNodeIndex) {
|
||||
const chunks = Array.from(container.querySelectorAll(".chunk-container"));
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const chunkElement = chunks[i];
|
||||
const elementStartIndex = getReaderChunkElementStartIndex(chunkElement);
|
||||
const elementCount = getReaderChunkElementCount(chunkElement);
|
||||
if (elementCount <= 0) continue;
|
||||
if (childNodeIndex >= elementStartIndex && childNodeIndex < elementStartIndex + elementCount) {
|
||||
return {
|
||||
chunkElement: chunkElement,
|
||||
chunkIndex: getReaderChunkIndex(chunkElement),
|
||||
indexInChunk: childNodeIndex - elementStartIndex
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackChunkIndex = Math.floor(childNodeIndex / 20);
|
||||
const fallbackChunkElement = container.querySelector(`.chunk-container[data-chunk-index="${fallbackChunkIndex}"]`);
|
||||
if (!fallbackChunkElement) return null;
|
||||
return {
|
||||
chunkElement: fallbackChunkElement,
|
||||
chunkIndex: fallbackChunkIndex,
|
||||
indexInChunk: childNodeIndex % 20
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCfiPath(rootElement, path, requestChunkIfMissing = false) {
|
||||
let currentNode = rootElement;
|
||||
const steps = path.substring(1).split("/").map(Number);
|
||||
|
|
@ -2054,10 +2104,12 @@
|
|||
// Handle virtualized content container specially
|
||||
if (currentNode.id === 'content-container') {
|
||||
const childNodeIndex = (cfiIndex - 2) / 2;
|
||||
let chunkIndex = Math.floor(childNodeIndex / 20);
|
||||
let indexInChunk = childNodeIndex % 20;
|
||||
const chunkLookup = findReaderChunkForElementIndex(currentNode, childNodeIndex);
|
||||
if (!chunkLookup) return null;
|
||||
|
||||
let chunkElement = currentNode.querySelector(`.chunk-container[data-chunk-index="${chunkIndex}"]`);
|
||||
let chunkIndex = chunkLookup.chunkIndex;
|
||||
let indexInChunk = chunkLookup.indexInChunk;
|
||||
let chunkElement = chunkLookup.chunkElement;
|
||||
if (chunkElement) {
|
||||
if (chunkElement.innerHTML === "") {
|
||||
if (window.virtualization && window.virtualization.chunksData[chunkIndex]) {
|
||||
|
|
@ -2179,8 +2231,8 @@
|
|||
continue;
|
||||
}
|
||||
|
||||
let chunkIndex = parseInt(parentNode.dataset.chunkIndex, 10);
|
||||
let elementsInPrecedingChunks = chunkIndex * 20;
|
||||
let chunkIndex = getReaderChunkIndex(parentNode);
|
||||
let elementsInPrecedingChunks = getReaderChunkElementStartIndex(parentNode);
|
||||
|
||||
let trueIndex = elementsInPrecedingChunks + indexInChunk;
|
||||
let cfiIndex = trueIndex * 2 + 2;
|
||||
|
|
@ -2295,6 +2347,119 @@
|
|||
console.log(TAG_BM + ": " + msg);
|
||||
}
|
||||
|
||||
function normalizeReaderImageSourceForMatch(value) {
|
||||
if (!value) return "";
|
||||
var normalized = String(value).split("#")[0].split("?")[0].replace(/\\/g, "/");
|
||||
try {
|
||||
normalized = decodeURIComponent(normalized);
|
||||
} catch (e) {}
|
||||
if (normalized.indexOf("file://") === 0) {
|
||||
normalized = normalized.substring("file://".length);
|
||||
}
|
||||
return normalized.toLowerCase();
|
||||
}
|
||||
|
||||
function getReaderImageSourceCandidates(element) {
|
||||
if (!element) return [];
|
||||
var values = [
|
||||
element.currentSrc,
|
||||
element.src,
|
||||
element.href && element.href.baseVal,
|
||||
element.getAttribute && element.getAttribute("src"),
|
||||
element.getAttribute && element.getAttribute("href"),
|
||||
element.getAttribute && element.getAttribute("xlink:href"),
|
||||
element.getAttribute && element.getAttribute("data-src"),
|
||||
];
|
||||
return values.filter(function (value, index, array) {
|
||||
return value && array.indexOf(value) === index;
|
||||
});
|
||||
}
|
||||
|
||||
function readerImageCandidateMatches(candidate, normalizedTargets) {
|
||||
var normalizedCandidate = normalizeReaderImageSourceForMatch(candidate);
|
||||
if (!normalizedCandidate) return false;
|
||||
var candidateName = normalizedCandidate.substring(normalizedCandidate.lastIndexOf("/") + 1);
|
||||
|
||||
return normalizedTargets.some(function (target) {
|
||||
if (!target) return false;
|
||||
var targetName = target.substring(target.lastIndexOf("/") + 1);
|
||||
return (
|
||||
normalizedCandidate === target ||
|
||||
normalizedCandidate.endsWith("/" + targetName) ||
|
||||
target.endsWith("/" + candidateName) ||
|
||||
(candidateName && targetName && candidateName === targetName)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function findReaderImageElementsBySource(source, originalSource) {
|
||||
var normalizedTargets = [source, originalSource]
|
||||
.map(normalizeReaderImageSourceForMatch)
|
||||
.filter(Boolean);
|
||||
return getReaderImageElements().filter(function (element) {
|
||||
return getReaderImageSourceCandidates(element).some(function (candidate) {
|
||||
return readerImageCandidateMatches(candidate, normalizedTargets);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function findReaderImageChunkIndex(source, originalSource) {
|
||||
if (!window.virtualization || !window.virtualization.chunksData) return -1;
|
||||
var normalizedTargets = [source, originalSource]
|
||||
.map(normalizeReaderImageSourceForMatch)
|
||||
.filter(Boolean);
|
||||
var targetNames = normalizedTargets
|
||||
.map(function (target) {
|
||||
return target.substring(target.lastIndexOf("/") + 1);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
for (var i = 0; i < window.virtualization.chunksData.length; i++) {
|
||||
var chunkHtml = window.virtualization.chunksData[i];
|
||||
if (!chunkHtml) continue;
|
||||
var normalizedChunk = normalizeReaderImageSourceForMatch(chunkHtml);
|
||||
if (
|
||||
normalizedTargets.some(function (target) {
|
||||
return normalizedChunk.indexOf(target) !== -1;
|
||||
}) ||
|
||||
targetNames.some(function (name) {
|
||||
return normalizedChunk.indexOf(name) !== -1;
|
||||
})
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
window.scrollToReaderImageSource = function(source, ordinal, originalSource) {
|
||||
var safeOrdinal = Math.max(0, parseInt(ordinal || 0, 10) || 0);
|
||||
var matches = findReaderImageElementsBySource(source, originalSource);
|
||||
|
||||
if (!matches.length) {
|
||||
var chunkIndex = findReaderImageChunkIndex(source, originalSource);
|
||||
if (chunkIndex >= 0) {
|
||||
var chunkDiv = document.querySelector('.chunk-container[data-chunk-index="' + chunkIndex + '"]');
|
||||
if (chunkDiv && chunkDiv.innerHTML === "" && window.virtualization && window.virtualization.chunksData[chunkIndex]) {
|
||||
chunkDiv.innerHTML = window.virtualization.chunksData[chunkIndex];
|
||||
chunkDiv.style.height = "";
|
||||
}
|
||||
matches = findReaderImageElementsBySource(source, originalSource);
|
||||
}
|
||||
}
|
||||
|
||||
var target = matches[Math.min(safeOrdinal, Math.max(0, matches.length - 1))];
|
||||
if (!target) return false;
|
||||
|
||||
var rect = target.getBoundingClientRect();
|
||||
var targetScrollY = window.scrollY + rect.top - (window.VIEWPORT_PADDING_TOP + 10);
|
||||
window.scrollTo({ top: Math.max(0, targetScrollY), behavior: "auto" });
|
||||
setTimeout(function () {
|
||||
if (window.reportScrollState) window.reportScrollState();
|
||||
}, 80);
|
||||
return true;
|
||||
};
|
||||
|
||||
window.scrollToCfi = function(cfi) {
|
||||
let cleanCfi = cfi;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue