Lettore 0.3.3: paginazione per contenuto + versione nelle impostazioni

- Tap avanti/indietro per PAGINA DI TESTO (offset in caratteri dai text node):
  positionJs (sopra) + charsInViewportJs (nella viewport) + scrollToCharJs;
  fine capitolo = offset >= chapterTextLength (C++), mai dallo scroll del
  WebView. window.scrollY non riflette lo scroll reale e contentHeight/
  scrollPosition nativi non sono scrivibili su Qt 5.6 (0.3.1 e 0.3.2 falliti)
- SettingsPage: label 'Version' in fondo (appVersion da setApplicationVersion)
- Traduzione IT aggiunta; log build aggiornato
This commit is contained in:
2026-08-21 10:47:40 +02:00
parent fc2d4d9d3d
commit 00877a0223
5 changed files with 113 additions and 45 deletions
+74 -31
View File
@@ -15,6 +15,9 @@ Page {
// posizione di lettura nel capitolo corrente, in caratteri di testo // posizione di lettura nel capitolo corrente, in caratteri di testo
// (conteggio JS sui text node: stabile al variare di font/margini) // (conteggio JS sui text node: stabile al variare di font/margini)
property int charOffset: 0 property int charOffset: 0
// caratteri di testo per pagina (stimati dalla viewport corrente): usato
// per il passo di pagina e per la label "pagina corrente/totale"
property int charsPerPage: 1
// offset da ripristinare al load del capitolo (-1 = nessuno) // offset da ripristinare al load del capitolo (-1 = nessuno)
property int restoreCharOffset: -1 property int restoreCharOffset: -1
// ultimo offset assoluto salvato (evita scritture continue) // ultimo offset assoluto salvato (evita scritture continue)
@@ -144,9 +147,11 @@ Page {
page.restoreCharOffset = -1 page.restoreCharOffset = -1
if (off >= book.chapterTextLength(page.currentChapter)) { if (off >= book.chapterTextLength(page.currentChapter)) {
// oltre la fine = vai all'ultima pagina (usato da // oltre la fine = vai all'ultima pagina (usato da
// prevPage per non saltare la coda del capitolo) // prevPage per non saltare la coda del capitolo):
web.scrollPosition = Qt.point(0, Math.max(1, web.contentHeight - web.height)) // scrollTo oltre il limite porta in fondo al documento
readScrollPosition() web.runJavaScript(
"window.scrollTo(0,9999999);true",
function() { readScrollPosition() })
} else { } else {
web.runJavaScript(page.scrollToCharJs(off), function() { web.runJavaScript(page.scrollToCharJs(off), function() {
readScrollPosition() readScrollPosition()
@@ -275,6 +280,28 @@ Page {
+ "return o;})()" + "return o;})()"
} }
// JS che conta i caratteri di testo NELLA viewport corrente (per il passo
// di pagina): stessa tecnica di positionJs, ma sulla fascia [0, innerHeight]
function charsInViewportJs() {
return "(function(){"
+ "var b=document.body;"
+ "var vh=window.innerHeight;"
+ "var c=0;"
+ "var it=document.createTreeWalker(b,NodeFilter.SHOW_TEXT);"
+ "var n;"
+ "while((n=it.nextNode())){"
+ "var p=n.parentNode;"
+ "if(!p||p.tagName==='SCRIPT'||p.tagName==='STYLE'||p.tagName==='NOSCRIPT')continue;"
+ "var r=document.createRange();r.selectNodeContents(n);"
+ "var rect=r.getBoundingClientRect();"
+ "if(rect.bottom<=0||rect.top>=vh)continue;"
+ "var top=Math.max(0,rect.top),bot=Math.min(vh,rect.bottom);"
+ "var h=rect.bottom-rect.top;"
+ "if(h>0)c+=Math.round(n.data.length*(bot-top)/h);"
+ "}"
+ "return c;})()"
}
// JS che porta in cima alla viewport il carattere 'off' del capitolo // JS che porta in cima alla viewport il carattere 'off' del capitolo
function scrollToCharJs(off) { function scrollToCharJs(off) {
return "(function(){" return "(function(){"
@@ -296,16 +323,17 @@ Page {
function readScrollPosition() { function readScrollPosition() {
if (!web.loaded) if (!web.loaded)
return return
// posizione letta dalle proprietà NATIVE del WebView (affidabili),
// il JS serve solo a contare i caratteri per il bookmark
var vh = Math.max(1, web.height)
var max = Math.max(1, web.contentHeight - vh)
var y = Math.max(0, Math.min(max, web.scrollPosition.y))
page.chapterFraction = y / max
page.pageIndex = Math.floor(y / vh) + 1
page.pageCount = Math.max(1, Math.floor(max / vh) + 1)
web.runJavaScript(page.positionJs(), function(o) { web.runJavaScript(page.positionJs(), function(o) {
page.charOffset = o || 0 page.charOffset = o || 0
// avanzamento per CARATTERI: stabile e indipendente dallo scroll reale
var total = book.chapterStartChar(book.chapterCount)
var start = book.chapterStartChar(page.currentChapter)
if (total > 0)
page.chapterFraction = (start + page.charOffset) / total
var len = book.chapterTextLength(page.currentChapter)
var pp = Math.max(1, page.charsPerPage)
page.pageIndex = Math.floor(page.charOffset / pp) + 1
page.pageCount = Math.max(1, Math.ceil(len / pp))
maybeSave(false) maybeSave(false)
}) })
} }
@@ -346,36 +374,44 @@ Page {
loadChapter() loadChapter()
} }
// pagina successiva: una viewport avanti, usando le proprietà native del // pagina successiva: avanza di una "pagina di testo" (i caratteri visibili
// WebView (contentHeight/scrollPosition) — niente JS di navigazione, quindi // nella viewport corrente). La fine del capitolo è determinata dal CONFRONTO
// nessuna dipendenza da runJavaScript/scrollY per il cambio capitolo. // CON LA LUNGHEZZA IN CARATTERI (nota in C++), non dallo scroll reale del
// A fine capitolo (y >= max-2) passa al capitolo successivo. // WebView — quindi il passaggio al capitolo successivo è sempre affidabile.
function nextPage() { function nextPage() {
if (!web.loaded || !page.ready) if (!web.loaded || !page.ready)
return return
var vh = Math.max(1, web.height) web.runJavaScript(page.positionJs(), function(o) {
var max = Math.max(1, web.contentHeight - vh) page.charOffset = o || 0
var y = web.scrollPosition.y web.runJavaScript(page.charsInViewportJs(), function(c) {
if (y >= max - 2) { page.charsPerPage = Math.max(1, Math.round(c || 1))
// fine capitolo (o capitolo non scrollabile) -> avanti var target = page.charOffset + page.charsPerPage
if (target >= book.chapterTextLength(page.currentChapter)) {
// fine capitolo: segna la coda come letta e passa avanti
page.charOffset = Math.max(0, book.chapterTextLength(page.currentChapter) - 1)
maybeSave(true)
gotoChapter(page.currentChapter + 1) gotoChapter(page.currentChapter + 1)
return } else {
} web.runJavaScript(page.scrollToCharJs(target), function() {
web.scrollPosition = Qt.point(0, Math.min(max, y + vh))
readScrollPosition() readScrollPosition()
})
}
})
})
} }
// pagina precedente: una viewport indietro; all'inizio -> capitolo prima // pagina precedente: torna indietro di una pagina di testo; dall'inizio del
// SULL'ULTIMA PAGINA (non salta la coda del capitolo) // capitolo va al capitolo prima SULL'ULTIMA PAGINA (non salta la coda)
function prevPage() { function prevPage() {
if (!web.loaded || !page.ready) if (!web.loaded || !page.ready)
return return
var vh = Math.max(1, web.height) web.runJavaScript(page.positionJs(), function(o) {
var y = web.scrollPosition.y page.charOffset = o || 0
if (y > 1) { web.runJavaScript(page.charsInViewportJs(), function(c) {
web.scrollPosition = Qt.point(0, Math.max(0, y - vh)) page.charsPerPage = Math.max(1, Math.round(c || 1))
readScrollPosition() var target = page.charOffset - page.charsPerPage
} else if (page.currentChapter > 0) { if (target <= 0) {
if (page.currentChapter > 0) {
maybeSave(true) maybeSave(true)
page.currentChapter -= 1 page.currentChapter -= 1
page.chapterFraction = 0 page.chapterFraction = 0
@@ -386,6 +422,13 @@ Page {
page.lastSavedAbs = -1 page.lastSavedAbs = -1
loadChapter() loadChapter()
} }
} else {
web.runJavaScript(page.scrollToCharJs(target), function() {
readScrollPosition()
})
}
})
})
} }
function fontPx() { function fontPx() {
+7
View File
@@ -150,6 +150,13 @@ Page {
} }
Item { width: 1; height: Theme.paddingLarge } Item { width: 1; height: Theme.paddingLarge }
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Version") + " " + appVersion
color: Theme.secondaryColor
font.pixelSize: Theme.fontSizeSmall
}
} }
} }
+9 -1
View File
@@ -1,6 +1,6 @@
Name: harbour-calibreweb Name: harbour-calibreweb
Summary: Client OPDS per Calibre Web Summary: Client OPDS per Calibre Web
Version: 0.3.2 Version: 0.3.3
Release: 1 Release: 1
Group: Qt/Qt Group: Qt/Qt
License: MIT License: MIT
@@ -24,6 +24,14 @@ reflow del testo, margini configurabili e posizione salvata
in locale. in locale.
%changelog %changelog
* Fri Aug 21 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.3.3-1
- Paginazione per CONTENUTO: il tap avanza di una pagina di testo (offset in
caratteri calcolato sui text node del DOM), la fine del capitolo e' decisa
confrontando l'offset con la lunghezza del capitolo nota in C++ — nessuna
dipendenza dallo scroll del WebView (window.scrollY non riflette lo scroll
reale; contentHeight/scrollPosition nativi non scrivibili su Qt 5.6)
- Impostazioni: numero di versione in fondo alla pagina (label "Version")
- Fix definitivo del cambio capitolo avanti/indietro
* Fri Aug 21 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.3.2-1 * Fri Aug 21 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.3.2-1
- Fix definitivo cambio capitolo: la navigazione non usa piu' window.scrollY/ - Fix definitivo cambio capitolo: la navigazione non usa piu' window.scrollY/
scrollBy via runJavaScript (lo scroll reale del WebView Sailfish non e' sempre scrollBy via runJavaScript (lo scroll reale del WebView Sailfish non e' sempre
+6 -1
View File
@@ -1,4 +1,5 @@
#include <QDebug> #include <QDebug>
#include <QCoreApplication>
#include <QGuiApplication> #include <QGuiApplication>
#include <QLocale> #include <QLocale>
#include <QQuickView> #include <QQuickView>
@@ -20,6 +21,8 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
// l'unica zona config persistente della sandbox di Sailfish 5.1. // l'unica zona config persistente della sandbox di Sailfish 5.1.
app->setOrganizationName(QStringLiteral("harbour")); app->setOrganizationName(QStringLiteral("harbour"));
app->setApplicationName(QStringLiteral("calibreweb")); app->setApplicationName(QStringLiteral("calibreweb"));
// versione mostrata nelle impostazioni (tenere allineata allo spec)
app->setApplicationVersion(QStringLiteral("0.3.3"));
Settings settings; Settings settings;
@@ -53,7 +56,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QQuickView *view = SailfishApp::createView(); QQuickView *view = SailfishApp::createView();
qDebug() << "harbour-calibreweb v0.2.0 build" << __DATE__ << __TIME__; qDebug() << "harbour-calibreweb v0.3.3 build" << __DATE__ << __TIME__;
qmlRegisterType<EpubBook>("harbour.calibreweb", 1, 0, "EpubBook"); qmlRegisterType<EpubBook>("harbour.calibreweb", 1, 0, "EpubBook");
@@ -63,6 +66,8 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings); view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
view->rootContext()->setContextProperty(QStringLiteral("apiClient"), &apiClient); view->rootContext()->setContextProperty(QStringLiteral("apiClient"), &apiClient);
view->rootContext()->setContextProperty(QStringLiteral("downloader"), &downloader); view->rootContext()->setContextProperty(QStringLiteral("downloader"), &downloader);
view->rootContext()->setContextProperty(QStringLiteral("appVersion"),
QCoreApplication::applicationVersion());
view->setSource(SailfishApp::pathTo(QStringLiteral("qml/harbour-calibreweb.qml"))); view->setSource(SailfishApp::pathTo(QStringLiteral("qml/harbour-calibreweb.qml")));
view->show(); view->show();
+5
View File
@@ -279,6 +279,11 @@
<source>Enter the server address</source> <source>Enter the server address</source>
<translation>Inserisci l'indirizzo del server</translation> <translation>Inserisci l'indirizzo del server</translation>
</message> </message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="156" />
<source>Version</source>
<translation type="finished">Versione</translation>
</message>
<message> <message>
<location filename="../qml/pages/SettingsPage.qml" line="168" /> <location filename="../qml/pages/SettingsPage.qml" line="168" />
<source>Testing…</source> <source>Testing…</source>