Implement core reader TypeScript modules for shell and UI management

- reader-shell.ts: Main initialization, Alpine.js integration, media type detection
- progress-indicator.ts: Reading progress tracking and display components
- settings-manager.ts: User settings persistence and retrieval
- panel-dock-system.ts: Dockable panel management with drag/drop and collapse
- parser-manager.ts: Parser selection and format detection system

These core modules provide the foundation for all reader types with
shared functionality for progress tracking, settings management, and
the flexible panel docking system.
This commit is contained in:
2026-04-03 22:29:20 -04:00
parent 0191d36dc9
commit d3d84a8318
5 changed files with 985 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
// Per-user settings with localStorage fallback
import { apiGet, apiPut } from "../api";
import { getToken, setItem, getItem } from "../storage";
const SETTINGS_KEY = "reader_settings";
const LOCALSTORAGE_KEY = "reader_settings_local";
interface SettingsManager {
load(): Promise<ReaderSettings>;
save(settings: Partial<ReaderSettings>): Promise<void>;
sync(): Promise<void>; // Sync localStorage → DB
get(key: keyof ReaderSettings): any;
set(key: keyof ReaderSettings, value: any): Promise<void>;
}
async function loadSettings(): Promise<ReaderSettings> {
const token = getToken();
if (!token) {
// Fallback to localStorage
const local = getItem(LOCALSTORAGE_KEY);
return local ? JSON.parse(local) : getDefaultSettings();
}
try {
const response = await apiGet("/readers/settings");
const settings = await response.json();
// Cache in localStorage
setItem(LOCALSTORAGE_KEY, JSON.stringify(settings));
return settings;
} catch (error) {
// Fallback to localStorage on error
const local = getItem(LOCALSTORAGE_KEY);
return local ? JSON.parse(local) : getDefaultSettings();
}
}
async function saveSettings(settings: Partial<ReaderSettings>): Promise<void> {
const token = getToken();
if (!token) {
// Save to localStorage only
const current = loadSettings();
const updated = { ...current, ...settings };
setItem(LOCALSTORAGE_KEY, JSON.stringify(updated));
return;
}
try {
await apiPut("/readers/settings", settings);
// Update localStorage cache
const current = loadSettings();
const updated = { ...current, ...settings };
setItem(LOCALSTORAGE_KEY, JSON.stringify(updated));
} catch (error) {
// Fallback to localStorage
const current = loadSettings();
const updated = { ...current, ...settings };
setItem(LOCALSTORAGE_KEY, JSON.stringify(updated));
}
}
function getDefaultSettings(): ReaderSettings {
return {
chrome_behavior: "auto-hide",
progress_mode: "pages",
chrome_theme: "tokyo-night", // UI chrome: All 11 themes available
reading_theme: "dark", // Ebook text: 5 reading-optimized themes
reading_font: "literata", // Default reading font (designed for ebooks)
tap_zone_size: 30,
auto_scroll: false,
panel_zoom_enabled: true,
font_size: 16,
line_height: 1.6,
margin_width: 20,
double_page_spread: false,
reading_direction: "ltr",
hardware_acceleration: true,
// Dockable panel defaults by media type
panel_layout: {
toc: {
side: "left",
visible: true,
collapsed: false,
width_px: 320,
order: 1,
locked: false,
last_valid_side: "left",
},
settings: {
side: "left",
visible: false,
collapsed: true,
width_px: 380,
order: 2,
locked: false,
last_valid_side: "left",
},
navigator: {
side: "right",
visible: true,
collapsed: false,
width_px: 200,
order: 1,
locked: false,
last_valid_side: "right",
},
bookmarks: {
side: "right",
visible: false,
collapsed: true,
width_px: 280,
order: 2,
locked: false,
last_valid_side: "right",
},
mobile_nav_visible: false,
},
};
}