Add ebook reader features: view modes, dictionary, copy handler
- view-modes.ts: Paginated, scrolled, single-column, double-column modes - dictionary-popup.ts: Dictionary lookup with popup definitions - copy-handler.ts: Copy text with automatic citation formatting - Add en-US.json dictionary for offline word lookups
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
// Different viewing modes for ebooks
|
||||
|
||||
type ViewMode = "paginated" | "scrolled" | "single-column" | "double-column";
|
||||
|
||||
// Different viewing modes for ebooks
|
||||
// Procedural implementation (no OOP)
|
||||
|
||||
type ViewMode = "paginated" | "scrolled" | "single-column" | "double-column";
|
||||
|
||||
interface ViewModeState {
|
||||
currentMode: ViewMode;
|
||||
currentPage: number;
|
||||
}
|
||||
|
||||
function setViewMode(container: HTMLElement, mode: ViewMode): void {
|
||||
const content = container.querySelector(".ebook-content");
|
||||
if (!content) return;
|
||||
|
||||
content.classList.remove(
|
||||
"paginated",
|
||||
"scrolled",
|
||||
"single-column",
|
||||
"double-column",
|
||||
);
|
||||
|
||||
switch (mode) {
|
||||
case "paginated":
|
||||
applyPaginatedMode(container, content as HTMLElement);
|
||||
break;
|
||||
case "scrolled":
|
||||
applyScrolledMode(container, content as HTMLElement);
|
||||
break;
|
||||
case "single-column":
|
||||
applySingleColumn(content as HTMLElement);
|
||||
break;
|
||||
case "double-column":
|
||||
applyDoubleColumn(content as HTMLElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyPaginatedMode(
|
||||
container: HTMLElement,
|
||||
element: HTMLElement,
|
||||
): void {
|
||||
element.classList.add("paginated");
|
||||
|
||||
element.style.height = "100vh";
|
||||
element.style.overflow = "hidden";
|
||||
element.style.columnCount = "1";
|
||||
element.style.columnGap = "0";
|
||||
|
||||
enablePagination(container, element);
|
||||
}
|
||||
|
||||
function applyScrolledMode(container: HTMLElement, element: HTMLElement): void {
|
||||
element.classList.add("scrolled");
|
||||
|
||||
element.style.height = "auto";
|
||||
element.style.overflowY = "auto";
|
||||
element.style.columnCount = "1";
|
||||
|
||||
disablePagination(container);
|
||||
}
|
||||
|
||||
function applySingleColumn(element: HTMLElement): void {
|
||||
element.classList.add("single-column");
|
||||
|
||||
element.style.columnCount = "1";
|
||||
element.style.columnGap = "0";
|
||||
element.style.maxWidth = "800px";
|
||||
element.style.margin = "0 auto";
|
||||
}
|
||||
|
||||
function applyDoubleColumn(element: HTMLElement): void {
|
||||
element.classList.add("double-column");
|
||||
|
||||
element.style.columnCount = "2";
|
||||
element.style.columnGap = "60px";
|
||||
element.style.columnRule = "1px solid var(--text-secondary)";
|
||||
element.style.maxWidth = "1400px";
|
||||
element.style.margin = "0 auto";
|
||||
}
|
||||
|
||||
function enablePagination(container: HTMLElement, element: HTMLElement): void {
|
||||
const totalHeight = element.scrollHeight;
|
||||
const pageHeight = element.clientHeight;
|
||||
const pageCount = Math.ceil(totalHeight / pageHeight);
|
||||
|
||||
addPaginationControls(container, pageCount);
|
||||
}
|
||||
|
||||
function disablePagination(container: HTMLElement): void {
|
||||
const controls = container.querySelector(".pagination-controls");
|
||||
controls?.remove();
|
||||
}
|
||||
|
||||
function addPaginationControls(
|
||||
container: HTMLElement,
|
||||
pageCount: number,
|
||||
): ViewModeState {
|
||||
let currentPage = 1;
|
||||
|
||||
const controls = document.createElement("div");
|
||||
controls.className =
|
||||
"pagination-controls fixed bottom-0 left-0 right-0 bg-opacity-95 backdrop-blur border-t";
|
||||
controls.innerHTML = `
|
||||
<button class="prev-page" ${currentPage === 1 ? "disabled" : ""}>← Previous</button>
|
||||
<span class="page-info">Page ${currentPage} of ${pageCount}</span>
|
||||
<button class="next-page" ${currentPage === pageCount ? "disabled" : ""}>Next →</button>
|
||||
`;
|
||||
|
||||
controls.querySelector(".prev-page")?.addEventListener("click", () => {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
goToPage(container, currentPage);
|
||||
}
|
||||
});
|
||||
|
||||
controls.querySelector(".next-page")?.addEventListener("click", () => {
|
||||
if (currentPage < pageCount) {
|
||||
currentPage++;
|
||||
goToPage(container, currentPage);
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(controls);
|
||||
|
||||
return { currentMode: "paginated", currentPage };
|
||||
}
|
||||
|
||||
function goToPage(container: HTMLElement, pageNumber: number): void {
|
||||
const content = container.querySelector(".ebook-content") as HTMLElement;
|
||||
if (!content) return;
|
||||
|
||||
const pageHeight = content.clientHeight;
|
||||
const scrollTop = (pageNumber - 1) * pageHeight;
|
||||
|
||||
content.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: "smooth",
|
||||
});
|
||||
|
||||
const pageInfo = container.querySelector(".page-info");
|
||||
if (pageInfo) {
|
||||
pageInfo.textContent = `Page ${pageNumber} of ${getTotalPageCount(container)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getTotalPageCount(container: HTMLElement): number {
|
||||
const content = container.querySelector(".ebook-content") as HTMLElement;
|
||||
if (!content) return 1;
|
||||
|
||||
const totalHeight = content.scrollHeight;
|
||||
const pageHeight = content.clientHeight;
|
||||
|
||||
return Math.ceil(totalHeight / pageHeight);
|
||||
}
|
||||
Reference in New Issue
Block a user