[add] better xpath generation, [add] wake lock, [add] device sorting,

[fix] better theme management
This commit is contained in:
Evan Reichard 2023-10-13 21:06:49 -04:00
parent 4da3f19c1a
commit 5d9c0804bd
7 changed files with 491 additions and 35 deletions

View File

@ -53,7 +53,7 @@ func NewApi(db *database.DBManager, c *config.Config) *API {
// Configure Cookie Session Store
store := cookie.NewStore(newToken)
store.Options(sessions.Options{
MaxAge: 60 * 60 * 24 * 7,
MaxAge: 60 * 60 * 24 * 7,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,

View File

@ -35,6 +35,7 @@ class EBookReader {
// Initialize
this.initDevice();
this.initWakeLock();
this.initThemes();
this.initRenditionListeners();
this.initDocumentListeners();
@ -49,13 +50,13 @@ class EBookReader {
if (!currentCFI) this.bookState.currentWord = 0;
await this.rendition.display(currentCFI);
// Restore Theme
this.setTheme(this.readerSettings.theme || "tan");
let getStats = function () {
// Start Timer
this.bookState.pageStart = Date.now();
// Restore Theme
this.setTheme(this.readerSettings.theme || "tan");
// Get Stats
let stats = this.getBookStats();
this.updateBookStats(stats);
@ -84,6 +85,46 @@ class EBookReader {
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
**/
@ -92,20 +133,37 @@ class EBookReader {
THEMES.forEach((theme) =>
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
**/
setTheme(themeName) {
// Update Settings
this.readerSettings.theme = themeName;
this.saveSettings();
// Set Reader Theme
this.rendition.themes.select(themeName);
// Get Reader Theme
let themeColorEl = document.querySelector("[name='theme-color']");
let backgroundColor = window.getComputedStyle(
this.rendition.getContents()[0].content
).backgroundColor;
let themeStyleSheet = document.querySelector("#themes").sheet;
let themeStyleRule = Array.from(themeStyleSheet.cssRules).find(
(item) => item.selectorText == "." + themeName
);
// Match Reader Theme
if (!themeStyleRule) return;
let backgroundColor = themeStyleRule.style.backgroundColor;
themeColorEl.setAttribute("content", backgroundColor);
document.body.style.backgroundColor = backgroundColor;
this.saveSettings();
}
/**
@ -156,6 +214,16 @@ class EBookReader {
"*": { "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 ---------------- //
// ------------------------------------------------ //
@ -356,10 +424,11 @@ class EBookReader {
// "t" Key (Theme Cycle)
if ((e.keyCode || e.which) == 84) {
let currentThemeIdx = THEMES.indexOf(this.readerSettings.theme);
if (THEMES.length == currentThemeIdx + 1)
this.readerSettings.theme = THEMES[0];
else this.readerSettings.theme = THEMES[currentThemeIdx + 1];
this.setTheme(this.readerSettings.theme);
let newTheme =
THEMES.length == currentThemeIdx + 1
? THEMES[0]
: THEMES[currentThemeIdx + 1];
this.setTheme(newTheme);
}
}.bind(this),
false
@ -370,9 +439,7 @@ class EBookReader {
item.addEventListener(
"click",
function (event) {
this.readerSettings.theme = event.target.innerText;
this.setTheme(this.readerSettings.theme);
this.setTheme(event.target.innerText);
}.bind(this)
);
}.bind(this)
@ -633,24 +700,46 @@ class EBookReader {
// Get first visible node
let contents = this.rendition.getContents()[0];
let currentNode = contents.range(currentPos.start.cfi).startContainer
.parentNode;
let node = contents.range(currentPos.start.cfi).startContainer;
// Walk upwards and build progress until body
let childPos = "";
while (currentNode.nodeName != "BODY") {
let relativeIndex =
Array.from(currentNode.parentNode.children)
.filter((item) => item.nodeName == currentNode.nodeName)
.indexOf(currentNode) + 1;
while (node.nodeName != "BODY") {
let ownValue;
// E.g: /div[10]
let itemPos =
"/" + currentNode.nodeName.toLowerCase() + "[" + relativeIndex + "]";
switch (node.nodeType) {
case Node.ELEMENT_NODE:
let relativeIndex =
Array.from(node.parentNode.children)
.filter((item) => item.nodeName == node.nodeName)
.indexOf(node) + 1;
// Prepend childPos & Update currentNode refernce
childPos = itemPos + childPos;
currentNode = currentNode.parentNode;
ownValue = node.nodeName.toLowerCase() + "[" + relativeIndex + "]";
break;
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

365
assets/reader/no-sleep.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -135,7 +135,8 @@ SELECT
CAST(STRFTIME('%Y-%m-%d %H:%M:%S', devices.last_synced, users.time_offset) AS TEXT) AS last_synced
FROM devices
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
SELECT * FROM documents

View File

@ -401,6 +401,7 @@ SELECT
FROM devices
JOIN users ON users.id = devices.user_id
WHERE users.id = ?1
ORDER BY devices.last_synced DESC
`
type GetDevicesRow struct {

View File

@ -56,7 +56,7 @@
</style>
</head>
<body class="bg-gray-100 dark:bg-gray-800">
<main class="relative overflow-hidden">
<main class="relative overflow-hidden h-[100dvh]">
<div
id="top-bar"
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 class="h-[100dvh] px-4 pb-24 overflow-auto md:px-6 lg:ml-48">
{{block "content" .}}{{end}}
</div>
{{block "content" .}}{{end}}
</main>
</body>
</html>

View File

@ -1,12 +1,14 @@
{{template "base.html" .}} {{define "title"}}Reader{{end}} {{define "header"}}
<a href="../">Documents</a>
{{end}} {{define "content"}}
<div id="viewer" class="w-full h-screen"></div>
<script src="../../assets/reader/platform.js"></script>
<script src="../../assets/reader/jszip.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>
<div id="viewer" class="w-full h-[100dvh] absolute top-0 left-0"></div>
<div id="hiddden-viewer" class="hidden"></div>
<script>
let currentReader = new EBookReader("./file", {
id: "{{ .Data.ID }}",