Files
MangaCircus/public/settings-nav.js
MaashkinoozheandClaude Opus 5.5 74842c59e0 Moments: per-user saved pages and regions with one category (BL-0055)
Reader: bookmark button and M save the page (Flow: page at the reading
line, Book: the spread) with an optional dragged region, exactly one
category (Twist, Hot, Awesome, Cute, Funny, Feels) and a note. I now
toggles the bars; ? lists all keys. Keys dispatch through public/keys.js,
which also feeds the new Settings -> Keyboard shortcuts section.

Manga page: "Your moments" strip of crops (story or recent order,
category filters), viewer with step, full page, edit, delete and
"Open in reader" (read.html ...&p=). Moments point at pages by file
name and show "page moved" when the file is gone. Private per user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:30:24 +01:00

74 lines
4.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// Two navigation levels; scraper tabs stay within their section, not another menu.
const SETTINGS_SECTIONS = [
{ id: 'user/reading', group: 'User', title: 'Reading', hint: 'Reader modes, page spacing and zoom.' },
{ id: 'user/reading-hours', group: 'User', title: 'Reading hours', hint: 'When this server allows chapter reading.' },
{ id: 'user/reading-time', group: 'User', title: 'Reading time & privacy', hint: 'Your clock, Reading Story and personal data.' },
{ id: 'user/shortcuts', group: 'User', title: 'Keyboard shortcuts', hint: 'Every key the reader understands. Press ? in the reader for the same list.' },
{ id: 'user/notifications', group: 'User', title: 'Notifications', hint: 'Chapter alerts and your optional webhook.' },
{ id: 'user/account', group: 'User', title: 'Account', hint: 'Your account and password.' },
{ id: 'admin/scraper', group: 'Admin', title: 'Scraper', hint: 'Live activity, downloads, update checks and sources.' },
{ id: 'admin/peering', group: 'Admin', title: 'Peering', hint: 'Pair servers and manage their permissions.', href: 'peers.html#admin' },
{ id: 'admin/peer-catalog', group: 'Admin', title: 'Peer catalogs', hint: 'Approve titles to share and browse peer libraries.', href: 'peer-catalog.html' },
{ id: 'admin/library', group: 'Admin', title: 'Server & library', hint: 'Library location, scan status and maintenance.' },
{ id: 'admin/users', group: 'Admin', title: 'Users & access', hint: 'Manage accounts, passwords and administrators.' },
{ id: 'admin/collections', group: 'Admin', title: 'Collections & metadata', hint: 'Group manga and review their relationships.', href: 'collections.html' },
{ id: 'admin/health', group: 'Admin', title: 'Service health', hint: 'Service status, incidents and recovery.', href: 'health.html' },
];
const SCRAPER_TABS = ['activity', 'queue', 'updates', 'sources'];
const READING_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
function readingDayText(schedule, index) {
const previous = (index + 6) % 7;
const carried = schedule.days[previous].filter(([start, end]) => end < start)
.map(([, end]) => `00:00–${end} (from ${READING_DAYS[previous]})`);
const starting = schedule.days[index].map(([start, end]) => `${start}–${end}${end < start ? ' (next day)' : ''}`);
return [...carried, ...starting].join(' · ') || 'Closed all day';
}
function settingsRoute(hash, admin) {
const parts = String(hash || '').replace(/^#/, '').split('/');
const candidate = parts.slice(0, 2).join('/'), tab = parts[2];
const section = SETTINGS_SECTIONS.find(item => item.id === candidate && (admin || item.group === 'User'));
return { id: section?.id || 'user/reading', tab: SCRAPER_TABS.includes(tab) ? tab : tab === 'hosters' ? 'sources' : 'activity' };
}
function settingsShell(me, active = 'user/reading') {
const main = $('main'); main.textContent = '';
const nav = h('nav', { class: 'settings-menu', 'aria-label': 'Settings sections' });
const links = new Map();
for (const group of me.admin ? ['User', 'Admin'] : ['User']) {
nav.append(h('div', { class: 'settings-menu-group' }, h('h2', {}, group),
SETTINGS_SECTIONS.filter(section => section.group === group).map(section => {
const link = h('a', { href: section.href || 'settings.html#' + section.id }, section.title);
links.set(section.id, link);
return link;
})));
}
const crumb = h('p', { class: 'settings-breadcrumb muted small' });
const title = h('h1', { tabindex: '-1' });
const titleRow = h('div', { class: 'settings-title-row' }, title);
const hint = h('p', { class: 'muted settings-hint' });
const content = h('div', { class: 'settings-content' });
main.append(h('div', { class: 'settings-layout' }, nav,
h('div', { class: 'settings-workspace' }, crumb, titleRow, hint, content)));
function select(id, focus = false) {
const section = SETTINGS_SECTIONS.find(item => item.id === id);
if (!section || (section.group === 'Admin' && !me.admin)) return;
for (const [key, link] of links) {
if (key === id) link.setAttribute('aria-current', 'page');
else link.removeAttribute('aria-current');
}
crumb.textContent = 'Settings / ' + section.group;
title.textContent = section.title;
hint.textContent = section.hint;
document.title = section.title + ' · Settings · MangaCircus';
if (focus) title.focus({ preventScroll: true });
}
select(active);
return { content, titleRow, select };
}
if (typeof module !== 'undefined') module.exports = { SETTINGS_SECTIONS, SCRAPER_TABS, settingsRoute, readingDayText };