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:
Aryan 2026-05-20 22:14:01 +05:30 committed by GitHub
parent dc5196526f
commit 9510293ac3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
245 changed files with 37538 additions and 12460 deletions

View file

@ -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;