[add] better xpath generation, [add] wake lock, [add] device sorting,
[fix] better theme management
This commit is contained in:
parent
4da3f19c1a
commit
5d9c0804bd
@ -53,7 +53,7 @@ func NewApi(db *database.DBManager, c *config.Config) *API {
|
|||||||
// Configure Cookie Session Store
|
// Configure Cookie Session Store
|
||||||
store := cookie.NewStore(newToken)
|
store := cookie.NewStore(newToken)
|
||||||
store.Options(sessions.Options{
|
store.Options(sessions.Options{
|
||||||
MaxAge: 60 * 60 * 24 * 7,
|
MaxAge: 60 * 60 * 24 * 7,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
SameSite: http.SameSiteStrictMode,
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
@ -35,6 +35,7 @@ class EBookReader {
|
|||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
this.initDevice();
|
this.initDevice();
|
||||||
|
this.initWakeLock();
|
||||||
this.initThemes();
|
this.initThemes();
|
||||||
this.initRenditionListeners();
|
this.initRenditionListeners();
|
||||||
this.initDocumentListeners();
|
this.initDocumentListeners();
|
||||||
@ -49,13 +50,13 @@ class EBookReader {
|
|||||||
if (!currentCFI) this.bookState.currentWord = 0;
|
if (!currentCFI) this.bookState.currentWord = 0;
|
||||||
await this.rendition.display(currentCFI);
|
await this.rendition.display(currentCFI);
|
||||||
|
|
||||||
// Restore Theme
|
|
||||||
this.setTheme(this.readerSettings.theme || "tan");
|
|
||||||
|
|
||||||
let getStats = function () {
|
let getStats = function () {
|
||||||
// Start Timer
|
// Start Timer
|
||||||
this.bookState.pageStart = Date.now();
|
this.bookState.pageStart = Date.now();
|
||||||
|
|
||||||
|
// Restore Theme
|
||||||
|
this.setTheme(this.readerSettings.theme || "tan");
|
||||||
|
|
||||||
// Get Stats
|
// Get Stats
|
||||||
let stats = this.getBookStats();
|
let stats = this.getBookStats();
|
||||||
this.updateBookStats(stats);
|
this.updateBookStats(stats);
|
||||||
@ -84,6 +85,46 @@ class EBookReader {
|
|||||||
this.saveSettings();
|
this.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a hack and maintains a wake lock. It will
|
||||||
|
* automatically disable if there's been no input for
|
||||||
|
* 10 minutes.
|
||||||
|
*
|
||||||
|
* Ideally we use "navigator.wakeLock", but there's a
|
||||||
|
* bug in Safari (as of iOS 17.03) when intalled as a
|
||||||
|
* PWA that doesn't allow it to work [0]
|
||||||
|
*
|
||||||
|
* Unfortunate downside is iOS indicates that "No Sleep"
|
||||||
|
* is playing in both the Control Center and Lock Screen.
|
||||||
|
* iOS also stops any background sound.
|
||||||
|
*
|
||||||
|
* [0] https://progressier.com/pwa-capabilities/screen-wake-lock
|
||||||
|
**/
|
||||||
|
initWakeLock() {
|
||||||
|
// Setup Wake Lock (Adding to DOM Necessary - iOS 17.03)
|
||||||
|
let timeoutID = null;
|
||||||
|
let wakeLock = new NoSleep();
|
||||||
|
|
||||||
|
// Override Standalone (Modified No-Sleep)
|
||||||
|
if (window.navigator.standalone) {
|
||||||
|
Object.assign(wakeLock.noSleepVideo.style, {
|
||||||
|
position: "absolute",
|
||||||
|
top: "-100%",
|
||||||
|
});
|
||||||
|
document.body.append(wakeLock.noSleepVideo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Action Required (Manual bubble up from iFrame)
|
||||||
|
document.addEventListener("wakelock", function () {
|
||||||
|
// 10 Minute Timeout
|
||||||
|
if (timeoutID) clearTimeout(timeoutID);
|
||||||
|
timeoutID = setTimeout(wakeLock.disable, 1000 * 60 * 10);
|
||||||
|
|
||||||
|
// Enable
|
||||||
|
wakeLock.enable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register all themes with reader
|
* Register all themes with reader
|
||||||
**/
|
**/
|
||||||
@ -92,20 +133,37 @@ class EBookReader {
|
|||||||
THEMES.forEach((theme) =>
|
THEMES.forEach((theme) =>
|
||||||
this.rendition.themes.register(theme, THEME_FILE)
|
this.rendition.themes.register(theme, THEME_FILE)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let themeLinkEl = document.createElement("link");
|
||||||
|
themeLinkEl.setAttribute("id", "themes");
|
||||||
|
themeLinkEl.setAttribute("rel", "stylesheet");
|
||||||
|
themeLinkEl.setAttribute("href", THEME_FILE);
|
||||||
|
document.head.append(themeLinkEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set theme & meta theme color
|
* Set theme & meta theme color
|
||||||
**/
|
**/
|
||||||
setTheme(themeName) {
|
setTheme(themeName) {
|
||||||
|
// Update Settings
|
||||||
|
this.readerSettings.theme = themeName;
|
||||||
|
this.saveSettings();
|
||||||
|
|
||||||
|
// Set Reader Theme
|
||||||
this.rendition.themes.select(themeName);
|
this.rendition.themes.select(themeName);
|
||||||
|
|
||||||
|
// Get Reader Theme
|
||||||
let themeColorEl = document.querySelector("[name='theme-color']");
|
let themeColorEl = document.querySelector("[name='theme-color']");
|
||||||
let backgroundColor = window.getComputedStyle(
|
let themeStyleSheet = document.querySelector("#themes").sheet;
|
||||||
this.rendition.getContents()[0].content
|
let themeStyleRule = Array.from(themeStyleSheet.cssRules).find(
|
||||||
).backgroundColor;
|
(item) => item.selectorText == "." + themeName
|
||||||
|
);
|
||||||
|
|
||||||
|
// Match Reader Theme
|
||||||
|
if (!themeStyleRule) return;
|
||||||
|
let backgroundColor = themeStyleRule.style.backgroundColor;
|
||||||
themeColorEl.setAttribute("content", backgroundColor);
|
themeColorEl.setAttribute("content", backgroundColor);
|
||||||
document.body.style.backgroundColor = backgroundColor;
|
document.body.style.backgroundColor = backgroundColor;
|
||||||
this.saveSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,6 +214,16 @@ class EBookReader {
|
|||||||
"*": { "font-size": "var(--editor-font-size) !important" },
|
"*": { "font-size": "var(--editor-font-size) !important" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ------------------------------------------------ //
|
||||||
|
// ---------------- Wake Lock Hack ---------------- //
|
||||||
|
// ------------------------------------------------ //
|
||||||
|
let wakeLockListener = function () {
|
||||||
|
doc.window.parent.document.dispatchEvent(new CustomEvent("wakelock"));
|
||||||
|
};
|
||||||
|
renderDoc.addEventListener("click", wakeLockListener);
|
||||||
|
renderDoc.addEventListener("gesturechange", wakeLockListener);
|
||||||
|
renderDoc.addEventListener("touchstart", wakeLockListener);
|
||||||
|
|
||||||
// ------------------------------------------------ //
|
// ------------------------------------------------ //
|
||||||
// ---------------- Resize Helpers ---------------- //
|
// ---------------- Resize Helpers ---------------- //
|
||||||
// ------------------------------------------------ //
|
// ------------------------------------------------ //
|
||||||
@ -356,10 +424,11 @@ class EBookReader {
|
|||||||
// "t" Key (Theme Cycle)
|
// "t" Key (Theme Cycle)
|
||||||
if ((e.keyCode || e.which) == 84) {
|
if ((e.keyCode || e.which) == 84) {
|
||||||
let currentThemeIdx = THEMES.indexOf(this.readerSettings.theme);
|
let currentThemeIdx = THEMES.indexOf(this.readerSettings.theme);
|
||||||
if (THEMES.length == currentThemeIdx + 1)
|
let newTheme =
|
||||||
this.readerSettings.theme = THEMES[0];
|
THEMES.length == currentThemeIdx + 1
|
||||||
else this.readerSettings.theme = THEMES[currentThemeIdx + 1];
|
? THEMES[0]
|
||||||
this.setTheme(this.readerSettings.theme);
|
: THEMES[currentThemeIdx + 1];
|
||||||
|
this.setTheme(newTheme);
|
||||||
}
|
}
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
false
|
false
|
||||||
@ -370,9 +439,7 @@ class EBookReader {
|
|||||||
item.addEventListener(
|
item.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
function (event) {
|
function (event) {
|
||||||
this.readerSettings.theme = event.target.innerText;
|
this.setTheme(event.target.innerText);
|
||||||
|
|
||||||
this.setTheme(this.readerSettings.theme);
|
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
);
|
);
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
@ -633,24 +700,46 @@ class EBookReader {
|
|||||||
|
|
||||||
// Get first visible node
|
// Get first visible node
|
||||||
let contents = this.rendition.getContents()[0];
|
let contents = this.rendition.getContents()[0];
|
||||||
let currentNode = contents.range(currentPos.start.cfi).startContainer
|
let node = contents.range(currentPos.start.cfi).startContainer;
|
||||||
.parentNode;
|
|
||||||
|
|
||||||
// Walk upwards and build progress until body
|
// Walk upwards and build progress until body
|
||||||
let childPos = "";
|
let childPos = "";
|
||||||
while (currentNode.nodeName != "BODY") {
|
while (node.nodeName != "BODY") {
|
||||||
let relativeIndex =
|
let ownValue;
|
||||||
Array.from(currentNode.parentNode.children)
|
|
||||||
.filter((item) => item.nodeName == currentNode.nodeName)
|
|
||||||
.indexOf(currentNode) + 1;
|
|
||||||
|
|
||||||
// E.g: /div[10]
|
switch (node.nodeType) {
|
||||||
let itemPos =
|
case Node.ELEMENT_NODE:
|
||||||
"/" + currentNode.nodeName.toLowerCase() + "[" + relativeIndex + "]";
|
let relativeIndex =
|
||||||
|
Array.from(node.parentNode.children)
|
||||||
|
.filter((item) => item.nodeName == node.nodeName)
|
||||||
|
.indexOf(node) + 1;
|
||||||
|
|
||||||
// Prepend childPos & Update currentNode refernce
|
ownValue = node.nodeName.toLowerCase() + "[" + relativeIndex + "]";
|
||||||
childPos = itemPos + childPos;
|
break;
|
||||||
currentNode = currentNode.parentNode;
|
case Node.ATTRIBUTE_NODE:
|
||||||
|
ownValue = "@" + node.nodeName;
|
||||||
|
break;
|
||||||
|
case Node.TEXT_NODE:
|
||||||
|
case Node.CDATA_SECTION_NODE:
|
||||||
|
ownValue = "text()";
|
||||||
|
break;
|
||||||
|
case Node.PROCESSING_INSTRUCTION_NODE:
|
||||||
|
ownValue = "processing-instruction()";
|
||||||
|
break;
|
||||||
|
case Node.COMMENT_NODE:
|
||||||
|
ownValue = "comment()";
|
||||||
|
break;
|
||||||
|
case Node.DOCUMENT_NODE:
|
||||||
|
ownValue = "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ownValue = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend childPos & Update node reference
|
||||||
|
childPos = "/" + ownValue + childPos;
|
||||||
|
node = node.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return derived progress
|
// Return derived progress
|
||||||
|
365
assets/reader/no-sleep.js
Normal file
365
assets/reader/no-sleep.js
Normal file
File diff suppressed because one or more lines are too long
@ -135,7 +135,8 @@ SELECT
|
|||||||
CAST(STRFTIME('%Y-%m-%d %H:%M:%S', devices.last_synced, users.time_offset) AS TEXT) AS last_synced
|
CAST(STRFTIME('%Y-%m-%d %H:%M:%S', devices.last_synced, users.time_offset) AS TEXT) AS last_synced
|
||||||
FROM devices
|
FROM devices
|
||||||
JOIN users ON users.id = devices.user_id
|
JOIN users ON users.id = devices.user_id
|
||||||
WHERE users.id = $user_id;
|
WHERE users.id = $user_id
|
||||||
|
ORDER BY devices.last_synced DESC;
|
||||||
|
|
||||||
-- name: GetDocument :one
|
-- name: GetDocument :one
|
||||||
SELECT * FROM documents
|
SELECT * FROM documents
|
||||||
|
@ -401,6 +401,7 @@ SELECT
|
|||||||
FROM devices
|
FROM devices
|
||||||
JOIN users ON users.id = devices.user_id
|
JOIN users ON users.id = devices.user_id
|
||||||
WHERE users.id = ?1
|
WHERE users.id = ?1
|
||||||
|
ORDER BY devices.last_synced DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetDevicesRow struct {
|
type GetDevicesRow struct {
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 dark:bg-gray-800">
|
<body class="bg-gray-100 dark:bg-gray-800">
|
||||||
<main class="relative overflow-hidden">
|
<main class="relative overflow-hidden h-[100dvh]">
|
||||||
<div
|
<div
|
||||||
id="top-bar"
|
id="top-bar"
|
||||||
class="transition-all duration-200 absolute z-10 bg-gray-100 dark:bg-gray-800 w-full px-2"
|
class="transition-all duration-200 absolute z-10 bg-gray-100 dark:bg-gray-800 w-full px-2"
|
||||||
@ -193,9 +193,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-[100dvh] px-4 pb-24 overflow-auto md:px-6 lg:ml-48">
|
{{block "content" .}}{{end}}
|
||||||
{{block "content" .}}{{end}}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
{{template "base.html" .}} {{define "title"}}Reader{{end}} {{define "header"}}
|
{{template "base.html" .}} {{define "title"}}Reader{{end}} {{define "header"}}
|
||||||
<a href="../">Documents</a>
|
<a href="../">Documents</a>
|
||||||
{{end}} {{define "content"}}
|
{{end}} {{define "content"}}
|
||||||
|
|
||||||
|
<div id="viewer" class="w-full h-screen"></div>
|
||||||
|
|
||||||
<script src="../../assets/reader/platform.js"></script>
|
<script src="../../assets/reader/platform.js"></script>
|
||||||
<script src="../../assets/reader/jszip.min.js"></script>
|
<script src="../../assets/reader/jszip.min.js"></script>
|
||||||
<script src="../../assets/reader/epub.min.js"></script>
|
<script src="../../assets/reader/epub.min.js"></script>
|
||||||
|
<script src="../../assets/reader/no-sleep.js"></script>
|
||||||
<script src="../../assets/reader/index.js"></script>
|
<script src="../../assets/reader/index.js"></script>
|
||||||
<div id="viewer" class="w-full h-[100dvh] absolute top-0 left-0"></div>
|
|
||||||
<div id="hiddden-viewer" class="hidden"></div>
|
|
||||||
<script>
|
<script>
|
||||||
let currentReader = new EBookReader("./file", {
|
let currentReader = new EBookReader("./file", {
|
||||||
id: "{{ .Data.ID }}",
|
id: "{{ .Data.ID }}",
|
||||||
|
Loading…
Reference in New Issue
Block a user