[add] service worker & offline reader
This commit is contained in:
1
assets/reader/epub.min.js
vendored
1
assets/reader/epub.min.js
vendored
File diff suppressed because one or more lines are too long
261
assets/reader/index.html
Normal file
261
assets/reader/index.html
Normal file
@@ -0,0 +1,261 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
id="viewport"
|
||||
name="viewport"
|
||||
content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta
|
||||
name="apple-mobile-web-app-status-bar-style"
|
||||
content="black-translucent"
|
||||
/>
|
||||
<meta name="theme-color" content="#D2B48C" />
|
||||
|
||||
<title>Book Manager - Reader</title>
|
||||
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
|
||||
<!-- Libraries -->
|
||||
<script src="/assets/lib/platform.js"></script>
|
||||
<script src="/assets/lib/jszip.min.js"></script>
|
||||
<script src="/assets/lib/epub.min.js"></script>
|
||||
<script src="/assets/lib/no-sleep.js"></script>
|
||||
<script src="/assets/lib/idb-keyval.js"></script>
|
||||
<script src="/assets/lib/sw-helper.js"></script>
|
||||
|
||||
<!-- Reader -->
|
||||
<script src="/assets/index.js"></script>
|
||||
<script src="/assets/reader/index.js"></script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
overscroll-behavior-y: none;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: calc(100% + env(safe-area-inset-top));
|
||||
}
|
||||
|
||||
#viewer {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
/* For Webkit-based browsers (Chrome, Safari and Opera) */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* For IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
#bottom-bar {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
#top-bar {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
#top-bar:not(.top-0) {
|
||||
top: calc((8em + env(safe-area-inset-top)) * -1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 dark:bg-gray-800">
|
||||
<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"
|
||||
>
|
||||
<div class="w-full h-32 flex items-center justify-around relative">
|
||||
<div class="text-gray-500 absolute top-6 left-4 flex flex-col gap-4">
|
||||
<a href="#">
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M20.5355 3.46447C19.0711 2 16.714 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355C22 19.0711 22 16.714 22 12C22 7.28595 22 4.92893 20.5355 3.46447ZM14.0303 8.46967C14.3232 8.76256 14.3232 9.23744 14.0303 9.53033L11.5607 12L14.0303 14.4697C14.3232 14.7626 14.3232 15.2374 14.0303 15.5303C13.7374 15.8232 13.2626 15.8232 12.9697 15.5303L9.96967 12.5303C9.82902 12.3897 9.75 12.1989 9.75 12C9.75 11.8011 9.82902 11.6103 9.96967 11.4697L12.9697 8.46967C13.2626 8.17678 13.7374 8.17678 14.0303 8.46967Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100 close-top-bar"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22ZM8.96965 8.96967C9.26254 8.67678 9.73742 8.67678 10.0303 8.96967L12 10.9394L13.9696 8.96969C14.2625 8.6768 14.7374 8.6768 15.0303 8.96969C15.3232 9.26258 15.3232 9.73746 15.0303 10.0303L13.0606 12L15.0303 13.9697C15.3232 14.2625 15.3232 14.7374 15.0303 15.0303C14.7374 15.3232 14.2625 15.3232 13.9696 15.0303L12 13.0607L10.0303 15.0303C9.73744 15.3232 9.26256 15.3232 8.96967 15.0303C8.67678 14.7374 8.67678 14.2626 8.96967 13.9697L10.9393 12L8.96965 10.0303C8.67676 9.73744 8.67676 9.26256 8.96965 8.96967Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-10 h-full p-4 pl-14 rounded">
|
||||
<div class="h-full my-auto relative">
|
||||
<a href="#">
|
||||
<img
|
||||
class="rounded object-cover h-full"
|
||||
src="/assets/images/no-cover.jpg"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-7 justify-around dark:text-white text-sm">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="inline-flex shrink-0 items-center">
|
||||
<div>
|
||||
<p class="text-gray-400">Title</p>
|
||||
<p
|
||||
class="font-medium whitespace-nowrap text-ellipsis overflow-hidden max-w-[50dvw]"
|
||||
>
|
||||
"N/A"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-flex shrink-0 items-center">
|
||||
<div>
|
||||
<p class="text-gray-400">Author</p>
|
||||
<p
|
||||
class="font-medium whitespace-nowrap text-ellipsis overflow-hidden max-w-[50dvw]"
|
||||
>
|
||||
"N/A"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="bottom-bar"
|
||||
class="-bottom-28 transition-all duration-200 absolute z-10 bg-gray-100 dark:bg-gray-800 items-center flex w-full overflow-y-scroll snap-x snap-mandatory no-scrollbar"
|
||||
>
|
||||
<div
|
||||
class="items-center flex flex-col w-screen h-full flex-none snap-center p-2"
|
||||
>
|
||||
<div
|
||||
class="flex flex-wrap gap-2 justify-around w-full dark:text-white pb-2"
|
||||
>
|
||||
<div class="flex justify-center gap-2 w-full md:w-fit">
|
||||
<p class="text-gray-400 text-xs">Chapter:</p>
|
||||
<p id="chapter-name-status" class="text-xs">N/A</p>
|
||||
</div>
|
||||
<div class="inline-flex gap-2">
|
||||
<p class="text-gray-400 text-xs">Chapter Pages:</p>
|
||||
<p id="chapter-status" class="text-xs">N/A</p>
|
||||
</div>
|
||||
<div class="inline-flex gap-2">
|
||||
<p class="text-gray-400 text-xs">Progress:</p>
|
||||
<p id="progress-status" class="text-xs">N/A</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-[90%] h-2 rounded border border-gray-500">
|
||||
<div
|
||||
id="progress-bar-status"
|
||||
class="w-0 bg-green-200 h-full rounded-l"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="items-center flex flex-col w-screen h-full flex-none snap-center p-2"
|
||||
>
|
||||
<p class="text-gray-400">Theme</p>
|
||||
<div class="flex justify-around w-full gap-4 p-2 text-sm">
|
||||
<div
|
||||
class="color-scheme cursor-pointer rounded border border-white bg-[#fff] text-[#000] grow text-center"
|
||||
>
|
||||
light
|
||||
</div>
|
||||
<div
|
||||
class="color-scheme cursor-pointer rounded border border-white bg-[#d2b48c] text-[#333] grow text-center"
|
||||
>
|
||||
tan
|
||||
</div>
|
||||
<div
|
||||
class="color-scheme cursor-pointer rounded border border-white bg-[#1f2937] text-[#fff] grow text-center"
|
||||
>
|
||||
blue
|
||||
</div>
|
||||
<div
|
||||
class="color-scheme cursor-pointer rounded border border-white bg-[#232323] text-[#fff] grow text-center"
|
||||
>
|
||||
gray
|
||||
</div>
|
||||
<div
|
||||
class="color-scheme cursor-pointer rounded border border-white bg-[#000] text-[#ccc] grow text-center"
|
||||
>
|
||||
black
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="items-center flex flex-col w-screen h-full flex-none snap-center p-2"
|
||||
>
|
||||
<p class="text-gray-400">Font</p>
|
||||
<div class="flex justify-around w-full gap-4 p-2 text-sm">
|
||||
<div
|
||||
class="font-family cursor-pointer rounded border border-white grow text-center dark:text-white"
|
||||
>
|
||||
Serif
|
||||
</div>
|
||||
<div
|
||||
class="font-family cursor-pointer rounded border border-white grow text-center dark:text-white"
|
||||
>
|
||||
Open Sans
|
||||
</div>
|
||||
<div
|
||||
class="font-family cursor-pointer rounded border border-white grow text-center dark:text-white"
|
||||
>
|
||||
Arbutus Slab
|
||||
</div>
|
||||
<div
|
||||
class="font-family cursor-pointer rounded border border-white grow text-center dark:text-white"
|
||||
>
|
||||
Lato
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="items-center flex flex-col w-screen h-full flex-none snap-center p-2"
|
||||
>
|
||||
<p class="text-gray-400">Font Size</p>
|
||||
<div class="flex justify-around w-full gap-4 p-2 text-sm">
|
||||
<div
|
||||
class="font-size cursor-pointer rounded border border-white grow text-center dark:text-white"
|
||||
>
|
||||
-
|
||||
</div>
|
||||
<div
|
||||
class="font-size cursor-pointer rounded border border-white grow text-center dark:text-white"
|
||||
>
|
||||
+
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewer" class="w-full h-full"></div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,59 @@
|
||||
const THEMES = ["light", "tan", "blue", "gray", "black"];
|
||||
const THEME_FILE = "/assets/reader/readerThemes.css";
|
||||
|
||||
async function initReader() {
|
||||
let documentData;
|
||||
let filePath;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
const documentID = urlParams.get("id");
|
||||
const localID = urlParams.get("local");
|
||||
|
||||
if (documentID) {
|
||||
// Get Server / Cached Document
|
||||
let progressResp = await fetch("/documents/" + documentID + "/progress");
|
||||
documentData = await progressResp.json();
|
||||
|
||||
// Update Local Cache
|
||||
let localCache = await IDB.get("PROGRESS-" + documentID);
|
||||
if (localCache) {
|
||||
documentData.progress = localCache.progress;
|
||||
documentData.percentage = Math.round(localCache.percentage * 10000) / 100;
|
||||
}
|
||||
|
||||
filePath = "/documents/" + documentID + "/file";
|
||||
} else if (localID) {
|
||||
// Get Local Document
|
||||
// TODO:
|
||||
// - IDB FileID
|
||||
// - IDB Metadata
|
||||
} else {
|
||||
throw new Error("Invalid");
|
||||
}
|
||||
|
||||
populateMetadata(documentData);
|
||||
window.currentReader = new EBookReader(filePath, documentData);
|
||||
}
|
||||
|
||||
function populateMetadata(data) {
|
||||
let documentLocation = data.id.startsWith("local-")
|
||||
? "/offline"
|
||||
: "/documents/" + data.id;
|
||||
|
||||
let documentCoverLocation = data.id.startsWith("local-")
|
||||
? "/assets/images/no-cover.jpg"
|
||||
: "/documents/" + data.id + "/cover";
|
||||
|
||||
let [backEl, coverEl] = document.querySelectorAll("a");
|
||||
backEl.setAttribute("href", documentLocation);
|
||||
coverEl.setAttribute("href", documentLocation);
|
||||
coverEl.firstElementChild.setAttribute("src", documentCoverLocation);
|
||||
|
||||
let [titleEl, authorEl] = document.querySelectorAll("#top-bar p + p");
|
||||
titleEl.innerText = data.title;
|
||||
authorEl.innerText = data.author;
|
||||
}
|
||||
|
||||
class EBookReader {
|
||||
bookState = {
|
||||
currentWord: 0,
|
||||
@@ -61,7 +114,7 @@ class EBookReader {
|
||||
|
||||
// Get Stats
|
||||
let stats = this.getBookStats();
|
||||
this.updateBookStats(stats);
|
||||
this.updateBookStatElements(stats);
|
||||
}.bind(this);
|
||||
|
||||
// Register Content Hook
|
||||
@@ -381,25 +434,41 @@ class EBookReader {
|
||||
// ------------------------------------------------ //
|
||||
// --------------- Bottom & Top Bar --------------- //
|
||||
// ------------------------------------------------ //
|
||||
let emSize = parseFloat(getComputedStyle(renderDoc.body).fontSize);
|
||||
renderDoc.addEventListener("click", function (event) {
|
||||
let barPixels = emSize * 5;
|
||||
renderDoc.addEventListener(
|
||||
"click",
|
||||
function (event) {
|
||||
// Get Window Dimensions
|
||||
let windowWidth = window.innerWidth;
|
||||
let windowHeight = window.innerHeight;
|
||||
|
||||
let top = barPixels;
|
||||
let bottom = window.innerHeight - top;
|
||||
// Calculate X & Y Hot Zones
|
||||
let barPixels = windowHeight * 0.2;
|
||||
let pagePixels = windowWidth * 0.2;
|
||||
|
||||
let left = barPixels / 2;
|
||||
let right = window.innerWidth - left;
|
||||
// Calculate Top & Bottom Thresholds
|
||||
let top = barPixels;
|
||||
let bottom = window.innerHeight - top;
|
||||
|
||||
if (event.clientY < top) handleSwipeDown();
|
||||
else if (event.clientY > bottom) handleSwipeUp();
|
||||
else if (event.screenX < left) prevPage();
|
||||
else if (event.screenX > right) nextPage();
|
||||
else {
|
||||
bottomBar.classList.remove("bottom-0");
|
||||
topBar.classList.remove("top-0");
|
||||
}
|
||||
});
|
||||
// Calculate Left & Right Thresholds
|
||||
let left = pagePixels;
|
||||
let right = windowWidth - left;
|
||||
|
||||
// Calculate Relative Coords
|
||||
let leftOffset = this.views().container.scrollLeft;
|
||||
let yCoord = event.clientY;
|
||||
let xCoord = event.clientX - leftOffset;
|
||||
|
||||
// Handle Event
|
||||
if (yCoord < top) handleSwipeDown();
|
||||
else if (yCoord > bottom) handleSwipeUp();
|
||||
else if (xCoord < left) prevPage();
|
||||
else if (xCoord > right) nextPage();
|
||||
else {
|
||||
bottomBar.classList.remove("bottom-0");
|
||||
topBar.classList.remove("top-0");
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
renderDoc.addEventListener(
|
||||
"wheel",
|
||||
@@ -506,7 +575,6 @@ class EBookReader {
|
||||
"click",
|
||||
function (event) {
|
||||
let colorScheme = event.target.innerText;
|
||||
console.log(colorScheme);
|
||||
this.setTheme({ colorScheme });
|
||||
}.bind(this)
|
||||
);
|
||||
@@ -565,26 +633,8 @@ class EBookReader {
|
||||
* Progresses to the next page & monitors reading activity
|
||||
**/
|
||||
async nextPage() {
|
||||
// Flush Activity
|
||||
this.flushActivity();
|
||||
|
||||
// Get Elapsed Time
|
||||
let elapsedTime = Date.now() - this.bookState.pageStart;
|
||||
|
||||
// Update Current Word
|
||||
let pageWords = await this.getVisibleWordCount();
|
||||
let startingWord = this.bookState.currentWord;
|
||||
let percentRead = pageWords / this.bookState.words;
|
||||
this.bookState.currentWord += pageWords;
|
||||
|
||||
// Add Read Event
|
||||
this.bookState.readActivity.push({
|
||||
percentRead,
|
||||
startingWord,
|
||||
pageWords,
|
||||
elapsedTime,
|
||||
startTime: this.bookState.pageStart,
|
||||
});
|
||||
// Create Activity
|
||||
await this.createActivity();
|
||||
|
||||
// Render Next Page
|
||||
await this.rendition.next();
|
||||
@@ -594,24 +644,16 @@ class EBookReader {
|
||||
|
||||
// Update Stats
|
||||
let stats = this.getBookStats();
|
||||
this.updateBookStats(stats);
|
||||
this.updateBookStatElements(stats);
|
||||
|
||||
// Update & Flush Progress
|
||||
let currentCFI = await this.rendition.currentLocation();
|
||||
let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
|
||||
this.bookState.progress = xpath;
|
||||
this.bookState.progressElement = element;
|
||||
|
||||
this.flushProgress();
|
||||
// Create Progress
|
||||
this.createProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Progresses to the previous page & monitors reading activity
|
||||
**/
|
||||
async prevPage() {
|
||||
// Flush Activity
|
||||
this.flushActivity();
|
||||
|
||||
// Render Previous Page
|
||||
await this.rendition.prev();
|
||||
|
||||
@@ -624,14 +666,10 @@ class EBookReader {
|
||||
|
||||
// Update Stats
|
||||
let stats = this.getBookStats();
|
||||
this.updateBookStats(stats);
|
||||
this.updateBookStatElements(stats);
|
||||
|
||||
// Update & Flush Progress
|
||||
let currentCFI = await this.rendition.currentLocation();
|
||||
let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
|
||||
this.bookState.progress = xpath;
|
||||
this.bookState.progressElement = element;
|
||||
this.flushProgress();
|
||||
// Create Progress
|
||||
this.createProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -652,88 +690,95 @@ class EBookReader {
|
||||
this.highlightPositionMarker();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize and flush activity
|
||||
**/
|
||||
async flushActivity() {
|
||||
// Process & Reset Activity
|
||||
let allActivity = this.bookState.readActivity;
|
||||
this.bookState.readActivity = [];
|
||||
|
||||
async createActivity() {
|
||||
// WPM MAX & MIN
|
||||
const WPM_MAX = 2000;
|
||||
const WPM_MIN = 100;
|
||||
|
||||
let normalizedActivity = allActivity
|
||||
// Exclude Fast WPM
|
||||
.filter((item) => item.pageWords / (item.elapsedTime / 60000) < WPM_MAX)
|
||||
.map((item) => {
|
||||
let pageWPM = item.pageWords / (item.elapsedTime / 60000);
|
||||
// Get Elapsed Time
|
||||
let pageStart = this.bookState.pageStart;
|
||||
let elapsedTime = Date.now() - pageStart;
|
||||
|
||||
// Min WPM
|
||||
if (pageWPM < WPM_MIN) {
|
||||
// TODO - Exclude Event?
|
||||
item.elapsedTime = (item.pageWords / WPM_MIN) * 60000;
|
||||
}
|
||||
// Update Current Word
|
||||
let pageWords = await this.getVisibleWordCount();
|
||||
let startingWord = this.bookState.currentWord;
|
||||
let percentRead = pageWords / this.bookState.words;
|
||||
this.bookState.currentWord += pageWords;
|
||||
|
||||
item.pages = Math.round(1 / item.percentRead);
|
||||
let pageWPM = pageWords / (elapsedTime / 60000);
|
||||
|
||||
item.page = Math.round(
|
||||
(item.startingWord * item.pages) / this.bookState.words
|
||||
);
|
||||
// Exclude Ridiculous WPM
|
||||
// if (pageWPM >= WPM_MAX) return;
|
||||
|
||||
// Estimate Accuracy Loss (Debugging)
|
||||
// let wordLoss = Math.abs(
|
||||
// item.pageWords - this.bookState.words / item.pages
|
||||
// );
|
||||
// console.log("Word Loss:", wordLoss);
|
||||
// Ensure WPM Minimum
|
||||
if (pageWPM < WPM_MIN) elapsedTime = (pageWords / WPM_MIN) * 60000;
|
||||
|
||||
return {
|
||||
document: this.bookState.id,
|
||||
duration: Math.round(item.elapsedTime / 1000),
|
||||
start_time: Math.round(item.startTime / 1000),
|
||||
page: item.page,
|
||||
pages: item.pages,
|
||||
};
|
||||
});
|
||||
let totalPages = Math.round(1 / percentRead);
|
||||
|
||||
if (normalizedActivity.length == 0) return;
|
||||
|
||||
console.log("Flushing Activity...");
|
||||
let currentPage = Math.round(
|
||||
(startingWord * totalPages) / this.bookState.words
|
||||
);
|
||||
|
||||
// Create Activity Event
|
||||
let activityEvent = {
|
||||
device_id: this.readerSettings.deviceID,
|
||||
device: this.readerSettings.deviceName,
|
||||
activity: normalizedActivity,
|
||||
activity: [
|
||||
{
|
||||
document: this.bookState.id,
|
||||
duration: Math.round(elapsedTime / 1000),
|
||||
start_time: Math.round(pageStart / 1000),
|
||||
page: currentPage,
|
||||
pages: totalPages,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Flush Activity
|
||||
fetch("/api/ko/activity", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(activityEvent),
|
||||
})
|
||||
.then(async (r) =>
|
||||
console.log("Flushed Activity:", {
|
||||
response: r,
|
||||
json: await r.json(),
|
||||
data: activityEvent,
|
||||
})
|
||||
)
|
||||
.catch((e) =>
|
||||
console.error("Activity Flush Failed:", {
|
||||
error: e,
|
||||
data: activityEvent,
|
||||
})
|
||||
);
|
||||
// Flush -> Offline Cache IDB
|
||||
this.flushActivity(activityEvent).catch(async (e) => {
|
||||
console.error("[createActivity] Activity Flush Failed:", {
|
||||
error: e,
|
||||
data: activityEvent,
|
||||
});
|
||||
|
||||
// Get & Update Activity
|
||||
let existingActivity = await IDB.get("ACTIVITY", { activity: [] });
|
||||
existingActivity.device_id = activityEvent.device_id;
|
||||
existingActivity.device = activityEvent.device;
|
||||
existingActivity.activity.push(...activityEvent.activity);
|
||||
|
||||
// Update IDB
|
||||
await IDB.set("ACTIVITY", existingActivity);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush progress to the API. Called when the page changes.
|
||||
* Normalize and flush activity
|
||||
**/
|
||||
async flushProgress() {
|
||||
console.log("Flushing Progress...");
|
||||
flushActivity(activityEvent) {
|
||||
console.log("[flushActivity] Flushing Activity...");
|
||||
|
||||
// Create Progress Event
|
||||
// Flush Activity
|
||||
return fetch("/api/ko/activity", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(activityEvent),
|
||||
}).then(async (r) =>
|
||||
console.log("[flushActivity] Flushed Activity:", {
|
||||
response: r,
|
||||
json: await r.json(),
|
||||
data: activityEvent,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async createProgress() {
|
||||
// Update Pointers
|
||||
let currentCFI = await this.rendition.currentLocation();
|
||||
let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
|
||||
this.bookState.progress = xpath;
|
||||
this.bookState.progressElement = element;
|
||||
|
||||
// Create Event
|
||||
let progressEvent = {
|
||||
document: this.bookState.id,
|
||||
device_id: this.readerSettings.deviceID,
|
||||
@@ -745,24 +790,35 @@ class EBookReader {
|
||||
progress: this.bookState.progress,
|
||||
};
|
||||
|
||||
// Flush -> Offline Cache IDB
|
||||
this.flushProgress(progressEvent).catch(async (e) => {
|
||||
console.error("[createProgress] Progress Flush Failed:", {
|
||||
error: e,
|
||||
data: progressEvent,
|
||||
});
|
||||
|
||||
// Update IDB
|
||||
await IDB.set("PROGRESS-" + progressEvent.document, progressEvent);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush progress to the API. Called when the page changes.
|
||||
**/
|
||||
flushProgress(progressEvent) {
|
||||
console.log("[flushProgress] Flushing Progress...");
|
||||
|
||||
// Flush Progress
|
||||
fetch("/api/ko/syncs/progress", {
|
||||
return fetch("/api/ko/syncs/progress", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(progressEvent),
|
||||
})
|
||||
.then(async (r) =>
|
||||
console.log("Flushed Progress:", {
|
||||
response: r,
|
||||
json: await r.json(),
|
||||
data: progressEvent,
|
||||
})
|
||||
)
|
||||
.catch((e) =>
|
||||
console.error("Progress Flush Failed:", {
|
||||
error: e,
|
||||
data: progressEvent,
|
||||
})
|
||||
);
|
||||
}).then(async (r) =>
|
||||
console.log("[flushProgress] Flushed Progress:", {
|
||||
response: r,
|
||||
json: await r.json(),
|
||||
data: progressEvent,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -770,7 +826,8 @@ class EBookReader {
|
||||
**/
|
||||
sectionProgress() {
|
||||
let visibleItems = this.rendition.manager.visible();
|
||||
if (visibleItems.length == 0) return console.log("No Items");
|
||||
if (visibleItems.length == 0)
|
||||
return console.log("[sectionProgress] No Items");
|
||||
let visibleSection = visibleItems[0];
|
||||
let visibleIndex = visibleSection.index;
|
||||
let pagesPerBlock = visibleSection.layout.divisor;
|
||||
@@ -812,7 +869,7 @@ class EBookReader {
|
||||
/**
|
||||
* Update elements with stats
|
||||
**/
|
||||
updateBookStats(data) {
|
||||
updateBookStatElements(data) {
|
||||
if (!data) return;
|
||||
|
||||
let chapterStatus = document.querySelector("#chapter-status");
|
||||
@@ -1076,3 +1133,5 @@ class EBookReader {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initReader);
|
||||
|
||||
15
assets/reader/jszip.min.js
vendored
15
assets/reader/jszip.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user