73 lines
4.4 KiB
JavaScript
73 lines
4.4 KiB
JavaScript
'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/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 };
|