style: format web/src with oxfmt defaults (via vp fmt)
One-time mechanical sweep, isolated from any functional change. - oxfmt defaults: double quotes, semicolons, 2-space indent, LF, printWidth 100, trailing commas, bracket spacing, arrow parens. - vite.config.ts gains an fmt.ignorePatterns block for web/static: vendored/minified assets (htmx.min.js, compiled css) must never be reformatted. - Verified: rebuild after the sweep produces byte-identical JS/CSS bundles; only sourcemaps changed (they embed sourcesContent).
This commit is contained in:
1 parent
8aeecaa0ef
commit
77cd268f4e
37 files changed
+1151
-1853
No files matched your search
+46
-115
@@ -7,9 +7,7 @@ const SCROLL_AMOUNT = 300;
|
||||
let scanListenerRegistered = false;
|
||||
|
||||
function scrollCarousel(collectionId: string, direction: number): void {
|
||||
const track = document.getElementById(
|
||||
`carousel-track-${collectionId}`,
|
||||
) as HTMLElement;
|
||||
const track = document.getElementById(`carousel-track-${collectionId}`) as HTMLElement;
|
||||
if (!track) return;
|
||||
|
||||
const scrollAmount = direction * SCROLL_AMOUNT;
|
||||
@@ -17,20 +15,15 @@ function scrollCarousel(collectionId: string, direction: number): void {
|
||||
}
|
||||
|
||||
async function openDashboardSettings(): Promise<void> {
|
||||
const librarySelect = document.getElementById(
|
||||
"library-select",
|
||||
) as HTMLSelectElement;
|
||||
const librarySelect = document.getElementById("library-select") as HTMLSelectElement;
|
||||
const libraryId = librarySelect?.value || "";
|
||||
|
||||
// Empty select = the All Libraries view: its preferences are the GLOBAL
|
||||
// scope — the server returns that row when library_id is absent.
|
||||
const query = libraryId ? `?library_id=${encodeURIComponent(libraryId)}` : "";
|
||||
const response = await fetch(
|
||||
`/api/dashboard/preferences${query}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
|
||||
},
|
||||
);
|
||||
const response = await fetch(`/api/dashboard/preferences${query}`, {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const prefs = await response.json();
|
||||
@@ -38,11 +31,8 @@ async function openDashboardSettings(): Promise<void> {
|
||||
// Scope indicator: make it unmistakable what a save will customize.
|
||||
const scopeEl = document.getElementById("dashboard-scope-label");
|
||||
if (scopeEl) {
|
||||
const optionText =
|
||||
librarySelect?.selectedOptions[0]?.textContent || libraryId;
|
||||
scopeEl.textContent = (
|
||||
libraryId ? optionText : optionText || "All Libraries"
|
||||
)
|
||||
const optionText = librarySelect?.selectedOptions[0]?.textContent || libraryId;
|
||||
scopeEl.textContent = (libraryId ? optionText : optionText || "All Libraries")
|
||||
.replace(/\s*\(\d+\)$/, "")
|
||||
.trim();
|
||||
}
|
||||
@@ -52,16 +42,12 @@ async function openDashboardSettings(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById(
|
||||
"dashboard-settings-modal",
|
||||
) as HTMLElement;
|
||||
const modal = document.getElementById("dashboard-settings-modal") as HTMLElement;
|
||||
modal?.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function applyPreferencesToModal(prefs: any): void {
|
||||
const collectionList = document.getElementById(
|
||||
"collection-list",
|
||||
) as HTMLElement;
|
||||
const collectionList = document.getElementById("collection-list") as HTMLElement;
|
||||
if (!collectionList) return;
|
||||
|
||||
const slider = document.querySelector(
|
||||
@@ -73,16 +59,12 @@ function applyPreferencesToModal(prefs: any): void {
|
||||
display.textContent = String(prefs.items_per_section);
|
||||
}
|
||||
|
||||
const items = collectionList.querySelectorAll(
|
||||
"[data-collection-id]",
|
||||
) as NodeListOf<HTMLElement>;
|
||||
const items = collectionList.querySelectorAll("[data-collection-id]") as NodeListOf<HTMLElement>;
|
||||
const orderedItems: HTMLElement[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
const collectionId = item.dataset.collectionId;
|
||||
const checkbox = item.querySelector(
|
||||
'input[type="checkbox"]',
|
||||
) as HTMLInputElement;
|
||||
const checkbox = item.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||
|
||||
const isHidden = prefs.hidden_collections.includes(collectionId);
|
||||
if (checkbox) checkbox.checked = !isHidden;
|
||||
@@ -99,32 +81,25 @@ function applyPreferencesToModal(prefs: any): void {
|
||||
}
|
||||
|
||||
function closeDashboardSettings(): void {
|
||||
const modal = document.getElementById(
|
||||
"dashboard-settings-modal",
|
||||
) as HTMLElement;
|
||||
const modal = document.getElementById("dashboard-settings-modal") as HTMLElement;
|
||||
modal?.classList.add("hidden");
|
||||
}
|
||||
|
||||
async function fetchAndRenderSections(libraryId: string): Promise<void> {
|
||||
const param = libraryId ? `library_id=${libraryId}` : "";
|
||||
const response = await fetch(
|
||||
`/api/dashboard/sections?${param}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
const response = await fetch(`/api/dashboard/sections?${param}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
);
|
||||
});
|
||||
if (!response.ok) throw new Error("Failed to load sections");
|
||||
const data = await response.json();
|
||||
renderDashboardCollections(data.sections);
|
||||
}
|
||||
|
||||
async function saveDashboardSettings(): Promise<void> {
|
||||
const collectionList = document.getElementById(
|
||||
"collection-list",
|
||||
) as HTMLElement;
|
||||
const collectionList = document.getElementById("collection-list") as HTMLElement;
|
||||
if (!collectionList) return;
|
||||
const collectionItems = collectionList.querySelectorAll(
|
||||
"[data-collection-id]",
|
||||
@@ -133,9 +108,7 @@ async function saveDashboardSettings(): Promise<void> {
|
||||
const collectionOrder: string[] = [];
|
||||
collectionItems.forEach((item) => {
|
||||
const collectionId = item.dataset.collectionId;
|
||||
const checkbox = item.querySelector(
|
||||
'input[type="checkbox"]',
|
||||
) as HTMLInputElement;
|
||||
const checkbox = item.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
||||
if (collectionId) {
|
||||
collectionOrder.push(collectionId);
|
||||
if (checkbox && !checkbox.checked) {
|
||||
@@ -144,12 +117,9 @@ async function saveDashboardSettings(): Promise<void> {
|
||||
}
|
||||
});
|
||||
const itemsPerCollection =
|
||||
(document.querySelector("#items-count-display") as HTMLElement)
|
||||
?.textContent || "20";
|
||||
(document.querySelector("#items-count-display") as HTMLElement)?.textContent || "20";
|
||||
try {
|
||||
const librarySelect = document.getElementById(
|
||||
"library-select",
|
||||
) as HTMLSelectElement;
|
||||
const librarySelect = document.getElementById("library-select") as HTMLSelectElement;
|
||||
const libraryId = librarySelect?.value || "";
|
||||
const response = await apiPut("/dashboard/preferences", {
|
||||
// Empty select = the All Libraries view: an explicit JSON null is
|
||||
@@ -255,9 +225,7 @@ function renderSectionHTML(section: SectionData): string {
|
||||
style="scrollbar-width: none; -ms-overflow-style: none;">
|
||||
${
|
||||
section.items.length > 0
|
||||
? section.items
|
||||
.map((item) => renderBookCard(item))
|
||||
.join("")
|
||||
? section.items.map((item) => renderBookCard(item)).join("")
|
||||
: '<div class="text-center py-8 w-full" style="color: var(--text-secondary);"><p>No items in this collection</p></div>'
|
||||
}
|
||||
</div>
|
||||
@@ -279,9 +247,7 @@ function renderSectionHTML(section: SectionData): string {
|
||||
}
|
||||
|
||||
function renderDashboardCollections(sections: SectionData[]): void {
|
||||
const container = document.getElementById(
|
||||
"collections-container",
|
||||
) as HTMLElement;
|
||||
const container = document.getElementById("collections-container") as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
const currentWood = container.getAttribute("data-wood");
|
||||
@@ -342,7 +308,6 @@ function badgeTitleFor(badge: string): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
function renderBookCard(book: BookInfo): string {
|
||||
const coverUrl = book.cover_image_path || "/static/placeholder-book.svg";
|
||||
const actionHref = book.has_conflict
|
||||
@@ -354,11 +319,7 @@ function renderBookCard(book: BookInfo): string {
|
||||
: isAudio
|
||||
? `Listen to ${book.title}`
|
||||
: `Read ${book.title}`;
|
||||
const actionTitle = book.has_conflict
|
||||
? "Resolve progress conflict"
|
||||
: isAudio
|
||||
? "Listen"
|
||||
: "Read";
|
||||
const actionTitle = book.has_conflict ? "Resolve progress conflict" : isAudio ? "Listen" : "Read";
|
||||
const actionIcon = isAudio ? HEADPHONES_ICON : BOOK_OPEN_ICON;
|
||||
const badgeIcon = badgeIconFor(book.badge || "");
|
||||
|
||||
@@ -393,20 +354,13 @@ function renderBookCard(book: BookInfo): string {
|
||||
}
|
||||
|
||||
async function reloadPage(): Promise<void> {
|
||||
const librarySelect = document.getElementById(
|
||||
"library-select",
|
||||
) as HTMLSelectElement;
|
||||
const librarySelect = document.getElementById("library-select") as HTMLSelectElement;
|
||||
const libraryId = librarySelect?.value || "";
|
||||
|
||||
await switchWithTransition("collections-container", () =>
|
||||
fetchAndRenderSections(libraryId),
|
||||
);
|
||||
await switchWithTransition("collections-container", () => fetchAndRenderSections(libraryId));
|
||||
}
|
||||
|
||||
function updateItemsCount(
|
||||
input: HTMLInputElement,
|
||||
displayTarget: string,
|
||||
): void {
|
||||
function updateItemsCount(input: HTMLInputElement, displayTarget: string): void {
|
||||
const displayElement = document.getElementById(displayTarget) as HTMLElement;
|
||||
if (displayElement) {
|
||||
displayElement.textContent = input.value;
|
||||
@@ -414,9 +368,7 @@ function updateItemsCount(
|
||||
}
|
||||
|
||||
function initDragAndDrop(): void {
|
||||
const collectionList = document.getElementById(
|
||||
"collection-list",
|
||||
) as HTMLElement;
|
||||
const collectionList = document.getElementById("collection-list") as HTMLElement;
|
||||
if (!collectionList) return;
|
||||
|
||||
let draggedItem: HTMLElement | null = null;
|
||||
@@ -440,11 +392,7 @@ function initDragAndDrop(): void {
|
||||
collectionList.addEventListener("dragover", (e: Event) => {
|
||||
e.preventDefault();
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.classList.contains("collection-item") &&
|
||||
target !== draggedItem &&
|
||||
draggedItem
|
||||
) {
|
||||
if (target.classList.contains("collection-item") && target !== draggedItem && draggedItem) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
const midY = rect.top + rect.height / 2;
|
||||
if ((e as DragEvent).clientY < midY) {
|
||||
@@ -509,8 +457,7 @@ function initDashboard() {
|
||||
|
||||
switch (action) {
|
||||
case "scroll-carousel": {
|
||||
const collectionId =
|
||||
target.dataset.collectionId || actionElem?.dataset.collectionId;
|
||||
const collectionId = target.dataset.collectionId || actionElem?.dataset.collectionId;
|
||||
const direction = parseInt(
|
||||
target.dataset.direction || actionElem?.dataset.direction || "0",
|
||||
);
|
||||
@@ -531,8 +478,7 @@ function initDashboard() {
|
||||
break;
|
||||
|
||||
case "restore-system-collection": {
|
||||
const colName =
|
||||
actionElem?.dataset.collectionName || target.dataset.collectionName;
|
||||
const colName = actionElem?.dataset.collectionName || target.dataset.collectionName;
|
||||
const colTitle =
|
||||
actionElem?.dataset.collectionTitle ||
|
||||
target.dataset.collectionTitle ||
|
||||
@@ -565,55 +511,40 @@ function initDashboard() {
|
||||
if (!scanListenerRegistered) {
|
||||
scanListenerRegistered = true;
|
||||
window.addEventListener("bookhoard:scan-complete", async () => {
|
||||
const librarySelect = document.getElementById(
|
||||
"library-select",
|
||||
) as HTMLSelectElement;
|
||||
const librarySelect = document.getElementById("library-select") as HTMLSelectElement;
|
||||
const libraryId = librarySelect?.value || "";
|
||||
|
||||
try {
|
||||
const param = libraryId ? `library_id=${libraryId}` : "";
|
||||
const response = await fetch(
|
||||
`/api/dashboard/sections?${param}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
const response = await fetch(`/api/dashboard/sections?${param}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
);
|
||||
});
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
|
||||
const container = document.getElementById(
|
||||
"collections-container",
|
||||
) as HTMLElement;
|
||||
const container = document.getElementById("collections-container") as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
const freshSectionIds = new Set(
|
||||
(data.sections as SectionData[]).map((s) => s.id),
|
||||
);
|
||||
const freshSectionIds = new Set((data.sections as SectionData[]).map((s) => s.id));
|
||||
|
||||
for (const section of data.sections as SectionData[]) {
|
||||
const track = document.getElementById(
|
||||
`carousel-track-${section.id}`,
|
||||
);
|
||||
const track = document.getElementById(`carousel-track-${section.id}`);
|
||||
|
||||
if (!track) {
|
||||
container.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
renderSectionHTML(section),
|
||||
);
|
||||
container.insertAdjacentHTML("beforeend", renderSectionHTML(section));
|
||||
continue;
|
||||
}
|
||||
|
||||
track.innerHTML = section.items.length > 0
|
||||
? section.items.map((item) => renderBookCard(item)).join("")
|
||||
: '<div class="text-center py-8 w-full" style="color: var(--text-secondary);"><p>No items in this collection</p></div>';
|
||||
track.innerHTML =
|
||||
section.items.length > 0
|
||||
? section.items.map((item) => renderBookCard(item)).join("")
|
||||
: '<div class="text-center py-8 w-full" style="color: var(--text-secondary);"><p>No items in this collection</p></div>';
|
||||
}
|
||||
|
||||
const existingSections = container.querySelectorAll<HTMLElement>(
|
||||
".dashboard-collection",
|
||||
);
|
||||
const existingSections = container.querySelectorAll<HTMLElement>(".dashboard-collection");
|
||||
existingSections.forEach((sec) => {
|
||||
const secId = sec.getAttribute("data-collection-id");
|
||||
if (secId && !freshSectionIds.has(secId)) {
|
||||
|
||||
Reference in new issue
Block a user