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:
+86
-43
@@ -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,46 +374,61 @@ 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
|
||||||
gotoChapter(page.currentChapter + 1)
|
if (target >= book.chapterTextLength(page.currentChapter)) {
|
||||||
return
|
// fine capitolo: segna la coda come letta e passa avanti
|
||||||
}
|
page.charOffset = Math.max(0, book.chapterTextLength(page.currentChapter) - 1)
|
||||||
web.scrollPosition = Qt.point(0, Math.min(max, y + vh))
|
maybeSave(true)
|
||||||
readScrollPosition()
|
gotoChapter(page.currentChapter + 1)
|
||||||
|
} else {
|
||||||
|
web.runJavaScript(page.scrollToCharJs(target), function() {
|
||||||
|
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) {
|
||||||
maybeSave(true)
|
if (page.currentChapter > 0) {
|
||||||
page.currentChapter -= 1
|
maybeSave(true)
|
||||||
page.chapterFraction = 0
|
page.currentChapter -= 1
|
||||||
page.charOffset = 0
|
page.chapterFraction = 0
|
||||||
// sentinella "fine capitolo": va oltre la lunghezza, così
|
page.charOffset = 0
|
||||||
// onLoadedChanged porta all'ultima pagina
|
// sentinella "fine capitolo": va oltre la lunghezza, così
|
||||||
page.restoreCharOffset = book.chapterTextLength(page.currentChapter) + 1
|
// onLoadedChanged porta all'ultima pagina
|
||||||
page.lastSavedAbs = -1
|
page.restoreCharOffset = book.chapterTextLength(page.currentChapter) + 1
|
||||||
loadChapter()
|
page.lastSavedAbs = -1
|
||||||
}
|
loadChapter()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
web.runJavaScript(page.scrollToCharJs(target), function() {
|
||||||
|
readScrollPosition()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function fontPx() {
|
function fontPx() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user