[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 // 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,

View File

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

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

View File

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

View File

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

View File

@ -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 }}",