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:
John O'Keefe committed 2026-10-21 13:18:24 -04:00
1 parent 8aeecaa0ef
commit 77cd268f4e
37 files changed
+1151 -1853

No files matched your search

+53 -114
View File
@@ -1,10 +1,7 @@
import { Alpine } from "./alpine";
import { showToast } from "./toast";
import { createWebSocket } from "./websocket";
import {
initLibrarySwitcher,
switchWithTransition,
} from "./library-switcher";
import { initLibrarySwitcher, switchWithTransition } from "./library-switcher";
let collectionId: string | null = null;
@@ -18,10 +15,7 @@ function initializeCollectionWebSocket(): void {
createWebSocket({
onMessage: (message) => {
if (
message.type === "collection_updated" &&
message.data.collection_id === collectionId
) {
if (message.type === "collection_updated" && message.data.collection_id === collectionId) {
const actionText =
message.data.action === "books_added"
? `Added ${message.data.count || 0} book(s)`
@@ -100,10 +94,7 @@ function renderRules(rules: CollectionRule[]): void {
.join("");
}
async function createRule(
collectionId: string,
rule: Partial<CollectionRule>,
): Promise<void> {
async function createRule(collectionId: string, rule: Partial<CollectionRule>): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
@@ -137,13 +128,10 @@ async function deleteRule(collectionId: string, ruleId: string): Promise<void> {
if (!confirm("Are you sure you want to delete this rule?")) return;
try {
const response = await fetch(
`/api/collections/${collectionId}/rules/${ruleId}`,
{
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
},
);
const response = await fetch(`/api/collections/${collectionId}/rules/${ruleId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Rule deleted", "success");
@@ -155,25 +143,19 @@ async function deleteRule(collectionId: string, ruleId: string): Promise<void> {
}
}
async function testRule(
collectionId: string,
rule: Partial<CollectionRule>,
): Promise<void> {
async function testRule(collectionId: string, rule: Partial<CollectionRule>): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch(
`/api/collections/${collectionId}/rules/test`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(rule),
const response = await fetch(`/api/collections/${collectionId}/rules/test`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
);
body: JSON.stringify(rule),
});
if (response.ok) {
const results = await response.json();
@@ -229,9 +211,7 @@ const borderClasses: Record<string, string> = {
};
function selectColor(color: string): void {
const colorInput = document.getElementById(
"collection-color",
) as HTMLInputElement;
const colorInput = document.getElementById("collection-color") as HTMLInputElement;
if (colorInput) {
colorInput.value = color;
}
@@ -239,9 +219,7 @@ function selectColor(color: string): void {
(btn as HTMLElement).style.outline = "none";
(btn as HTMLElement).style.outlineOffset = "0";
});
const selectedBtn = document.querySelector(
`.color-option[data-color="${color}"]`,
) as HTMLElement;
const selectedBtn = document.querySelector(`.color-option[data-color="${color}"]`) as HTMLElement;
if (selectedBtn) {
selectedBtn.style.outline = "3px solid var(--text-primary)";
selectedBtn.style.outlineOffset = "3px";
@@ -256,9 +234,7 @@ function closeCollectionModal(): void {
}
function initColorSelection(): void {
const colorInput = document.getElementById(
"collection-color",
) as HTMLInputElement;
const colorInput = document.getElementById("collection-color") as HTMLInputElement;
if (colorInput && colorInput.value) {
selectColor(colorInput.value);
}
@@ -291,9 +267,7 @@ function setupHTMXModalInit(): void {
function showAllIcons(): void {
populateIconGrid();
const searchInput = document.getElementById(
"icon-search",
) as HTMLInputElement;
const searchInput = document.getElementById("icon-search") as HTMLInputElement;
if (searchInput) {
searchInput.value = "";
}
@@ -359,12 +333,8 @@ function populateIconGrid(): void {
}
function selectIcon(icon: string): void {
const iconInput = document.getElementById(
"collection-icon",
) as HTMLInputElement;
const searchInput = document.getElementById(
"icon-search",
) as HTMLInputElement;
const iconInput = document.getElementById("collection-icon") as HTMLInputElement;
const searchInput = document.getElementById("icon-search") as HTMLInputElement;
if (iconInput) iconInput.value = icon;
if (searchInput) searchInput.value = icon;
@@ -374,9 +344,7 @@ function selectIcon(icon: string): void {
(btn as HTMLElement).style.backgroundColor = "";
});
const selectedBtn = document.querySelector(
`.icon-btn[data-emoji="${icon}"]`,
) as HTMLElement;
const selectedBtn = document.querySelector(`.icon-btn[data-emoji="${icon}"]`) as HTMLElement;
if (selectedBtn) {
selectedBtn.style.outline = "2px solid var(--text-primary)";
selectedBtn.style.backgroundColor = "var(--bg-secondary)";
@@ -385,9 +353,7 @@ function selectIcon(icon: string): void {
function filterIcons(searchTerm: string): void {
const iconGrid = document.getElementById("icon-grid");
const iconInput = document.getElementById(
"collection-icon",
) as HTMLInputElement;
const iconInput = document.getElementById("collection-icon") as HTMLInputElement;
if (iconInput) iconInput.value = searchTerm;
if (!iconGrid) return;
const buttons = iconGrid.querySelectorAll(".icon-btn");
@@ -400,9 +366,7 @@ function filterIcons(searchTerm: string): void {
const matches =
searchTerm === "" ||
emoji.includes(searchTerm) ||
keywords.some((keyword) =>
keyword.toLowerCase().includes(lowerSearchTerm),
);
keywords.some((keyword) => keyword.toLowerCase().includes(lowerSearchTerm));
(btn as HTMLElement).style.display = matches ? "" : "none";
});
@@ -427,11 +391,10 @@ function initCollectionsPage(): void {
// Rendering ownership: the grid is server-rendered markup. Swap
// in the templ partial instead of re-rendering from JSON.
const param = libraryId ? `?library_id=${encodeURIComponent(libraryId)}` : "";
await window.htmx.ajax(
"GET",
`/collections/${collId}/books-grid${param}`,
{ target: "#books-container", swap: "innerHTML" },
);
await window.htmx.ajax("GET", `/collections/${collId}/books-grid${param}`, {
target: "#books-container",
swap: "innerHTML",
});
const container = document.getElementById("books-container");
if (container) Alpine.initTree(container);
});
@@ -461,13 +424,10 @@ async function doRemoveBook(id: string): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch(
`/api/collections/${getCollectionId()}/books/${id}`,
{
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
},
);
const response = await fetch(`/api/collections/${getCollectionId()}/books/${id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
showToast("Removed from collection", "success");
setTimeout(() => location.reload(), 500);
@@ -483,17 +443,14 @@ async function doBulkRemove(ids: string[]): Promise<void> {
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch(
`/api/collections/${getCollectionId()}/books/bulk-remove`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ book_ids: ids }),
const response = await fetch(`/api/collections/${getCollectionId()}/books/bulk-remove`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
);
body: JSON.stringify({ book_ids: ids }),
});
if (response.ok) {
showToast(`Removed ${ids.length} book(s)`, "success");
setTimeout(() => location.reload(), 500);
@@ -506,19 +463,14 @@ async function doBulkRemove(ids: string[]): Promise<void> {
}
function filterCollectionBooks(): void {
const input = document.getElementById(
"collection-search",
) as HTMLInputElement;
const input = document.getElementById("collection-search") as HTMLInputElement;
if (!input) return;
const query = input.value.toLowerCase();
const cards = document.querySelectorAll<HTMLElement>(
".collection-book-card",
);
const cards = document.querySelectorAll<HTMLElement>(".collection-book-card");
cards.forEach((card) => {
const title = (card.dataset.title || "").toLowerCase();
const author = (card.dataset.author || "").toLowerCase();
card.style.display =
title.includes(query) || author.includes(query) ? "" : "none";
card.style.display = title.includes(query) || author.includes(query) ? "" : "none";
});
}
@@ -601,19 +553,11 @@ Alpine.data("collections", () => ({
// resulting order. The DOM cards are the server-rendered truth for CURRENT
// order; the API is the truth for SAVED order. On failure the grid refresh
// from the templ partial so the UI never lies.
async function moveCollectionBook(
this: any,
mediaId: string,
direction: number,
): Promise<void> {
async function moveCollectionBook(this: any, mediaId: string, direction: number): Promise<void> {
const container = document.getElementById("books-container");
if (!container) return;
const cards = Array.from(
container.querySelectorAll<HTMLElement>(".collection-book-card"),
);
const index = cards.findIndex(
(c) => c.dataset.mediaId === mediaId,
);
const cards = Array.from(container.querySelectorAll<HTMLElement>(".collection-book-card"));
const index = cards.findIndex((c) => c.dataset.mediaId === mediaId);
const target = index + direction;
if (index < 0 || target < 0 || target >= cards.length) return;
@@ -624,26 +568,21 @@ async function moveCollectionBook(
container.insertBefore(cards[target], cards[index]);
}
const orderedIds = Array.from(
container.querySelectorAll<HTMLElement>(".collection-book-card"),
)
const orderedIds = Array.from(container.querySelectorAll<HTMLElement>(".collection-book-card"))
.map((c) => c.dataset.mediaId)
.filter((id): id is string => Boolean(id));
const token = localStorage.getItem("token");
if (!token) return;
try {
const response = await fetch(
`/api/collections/${getCollectionId()}/items/order`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ item_ids: orderedIds }),
const response = await fetch(`/api/collections/${getCollectionId()}/items/order`, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
);
body: JSON.stringify({ item_ids: orderedIds }),
});
if (!response.ok) {
showToast("Failed to save the new order", "error");
// Rendering ownership: the templ partial re-renders the true order.