use throw for cleaner code

This commit is contained in:
Niki Wix Skaarup 2025-04-02 23:04:55 +02:00
parent c4e857822e
commit 9d9eda0b23
Signed by: nikiskaarup
GPG key ID: FC2F1B116F6E788C

View file

@ -2,7 +2,7 @@
// @name manga reading
// @namespace https://userscripts.skaarup.dev
// @homepageURL https://userscripts.skaarup.dev
// @version 3.1
// @version 3.2
// @author nws
// @description Adds nearly complete keyboard navigation and cleans up the user interface of manganato
// @updateURL https://userscripts.skaarup.dev/scripts/manga-reading.user.js
@ -57,6 +57,8 @@ import mangaReadingNotificationContainer from './templates/manga-reading-notific
import mangaReadingNotification from './templates/manga-reading-notification.html';
async function initMangaReading() {
const logName = `${GM.info.script.name} -`;
function disableScrolling() {
document.documentElement.style.top = `${-document.documentElement.scrollTop}px`;
document.documentElement.style.position = 'fixed';
@ -74,10 +76,10 @@ async function initMangaReading() {
document.documentElement.scrollTop = scrollTop;
document.body.scrollTop = scrollTop;
}
function loadStyle(name: string, inline: boolean, shadowRoot: ShadowRoot | null) {
function loadStyle(name: string, inline: boolean, shadowRoot: ShadowRoot) {
const data = GM_getResourceText(name);
if (!data) {
console.warn(`Resource ${name} not found`);
console.warn(`${logName} Resource ${name} not found`);
return;
}
@ -88,7 +90,7 @@ async function initMangaReading() {
style.innerHTML = data;
if (inline) document.head.append(style);
else shadowRoot?.appendChild(style);
else shadowRoot.appendChild(style);
}
function getMr() {
@ -128,19 +130,20 @@ async function initMangaReading() {
);
const outerFrame = document.createElement('mr-config-container');
if (!outerFrame.shadowRoot) throw new Error('Shadow root not found');
const shadowRoot = outerFrame.shadowRoot;
if (!shadowRoot) return;
loadStyle('stylesheet', false, shadowRoot);
const frame = shadowRoot.children[0];
if (!frame) return;
if (!frame) throw new Error('Frame not found');
const backdrop = frame.querySelector<HTMLDivElement>('[data-mr-backdrop]');
if (!backdrop) return;
if (!backdrop) throw new Error('Backdrop not found');
backdrop.onclick = closeConfig;
const subConfigTarget = frame.querySelector<HTMLDivElement>('[data-mr-sub-config-target]');
if (!subConfigTarget) throw new Error('Sub config target not found');
const closeConfigButtons = frame.querySelectorAll<HTMLButtonElement>('[data-mr-close]');
for (let i = 0; i < closeConfigButtons.length; i++) {
@ -173,7 +176,7 @@ async function initMangaReading() {
callback
};
ui.subConfigTarget?.appendChild(container);
ui.subConfigTarget.appendChild(container);
}
function unregisterConfig(info: vm_infoType) {
@ -200,7 +203,7 @@ async function initMangaReading() {
disableScrolling();
shadowRoot?.querySelector<HTMLInputElement>('input')?.focus();
shadowRoot.querySelector<HTMLInputElement>('input')?.focus();
}
function closeConfig() {
@ -242,9 +245,9 @@ async function initMangaReading() {
}
function attachFocusEvent() {
console.log(`${GM.info.script.name} - Attaching focus event...`);
ui.frame?.addEventListener('keydown', keydownEventListener as any);
console.log(`${GM.info.script.name} - Attached focus events.`);
console.log(`${logName} Attaching focus event...`);
ui.frame.addEventListener('keydown', keydownEventListener as any);
console.log(`${logName} Attached focus events.`);
}
function noModifier(e: KeyboardEvent) {
@ -263,19 +266,6 @@ async function initMangaReading() {
return e.altKey && !(e.ctrlKey || e.shiftKey);
}
async function globalShortcuts(event: KeyboardEvent) {
switch (true) {
case event.code === 'Backslash' && ctrlModifier(event):
// setDebugging(!configGlobals.debugging);
// ui.debuggingCheckbox.checked = configGlobals.debugging;
return true;
case event.code === 'Semicolon' && ctrlModifier(event):
// reloadResources();
return true;
}
return false;
}
async function configOpenShortcuts(event: KeyboardEvent) {
if (event.code === 'Escape' && noModifier(event)) {
closeConfig();
@ -316,9 +306,9 @@ async function initMangaReading() {
}
function attachShortcutEvents() {
console.log(`${GM.info.script.name} - Attaching shortcut events...`);
console.log(`${logName} Attaching shortcut events...`);
document.addEventListener('keyup', keyupEventListener);
console.log(`${GM.info.script.name} - Attached shortcut events.`);
console.log(`${logName} Attached shortcut events.`);
}
function registerKeyUp(shortcutType: shortcutType, ...shortcuts: shortcut[]) {
@ -337,42 +327,37 @@ async function initMangaReading() {
}
function registerKeyUps() {
const namePrefix = 'nwsLib';
registerKeyUp('Global', {
name: `${namePrefix} - global`,
callback: globalShortcuts
});
registerKeyUp('ConfigOpen', {
name: `${namePrefix} - config open`,
name: `${logName} config open`,
callback: configOpenShortcuts
});
registerKeyUp('ConfigClosed', {
name: `${namePrefix} - config closed`,
name: `${logName} config closed`,
callback: configClosedShortcuts
});
}
async function onInit(callback: () => Promise<void>, postCallback: () => Promise<void>) {
try {
console.log(`${GM.info.script.name} - Loading...`);
console.log(`${logName} Loading...`);
const id = GM.registerMenuCommand(`Configure ${GM.info.script.name}`, () => {
openConfig();
});
registerKeyUps();
attachFocusEvent();
attachShortcutEvents();
console.log(`${GM.info.script.name} - Loaded.`);
console.log(`${logName} Loaded.`);
await callback();
if (postCallback !== undefined) {
await postCallback();
}
} catch (e) {
console.error(`${GM.info.script.name} - Error:`, e);
console.error(`${logName} Error:`, e);
}
}
async function init(callback: () => Promise<void>, postCallback: () => Promise<void>) {
console.log(`${GM.info.script.name} - Initializing...`);
console.log(`${logName} Initializing...`);
switch (document.readyState) {
case 'complete':
await onInit(callback, postCallback);
@ -412,7 +397,6 @@ async function initMangaReading() {
};
}
const mr = getMr();
if (!mr) return;
const globals: manga_reading.globalsType = {
nextUrl: '',
@ -461,22 +445,23 @@ async function initMangaReading() {
notificationList.style.setProperty('--z-index', '999999999');
function initToastContainer() {
console.log(`${GM.info.script.name} - Initializing Toast Container.`);
if (!mr) return;
console.log(`${logName} Initializing Toast Container.`);
for (let i = 0; i < mr.shadowRoot.children.length; i++) {
const element = mr.shadowRoot.children[i];
if (element?.tagName.toLowerCase() === 'style') {
if (!element) continue;
if (element.tagName.toLowerCase() === 'style') {
shadowRoot.appendChild(element.cloneNode(true));
}
}
console.log(`${GM.info.script.name} - Initialized Toast Container.`);
console.log(`${logName} Initialized Toast Container.`);
}
function insertToastContainer() {
console.log(`${GM.info.script.name} - Inserting Toast Container.`);
console.log(`${logName} Inserting Toast Container.`);
document.body.appendChild(outerFrame);
console.log(`${GM.info.script.name} - Inserted Toast Container.`);
console.log(`${logName} Inserted Toast Container.`);
}
const notificationTemp = document.createElement('div');
@ -489,21 +474,39 @@ async function initMangaReading() {
const notificationButton = notificationButtonTemp.children[0] as HTMLDivElement;
const temp = document.createElement('div');
temp.innerHTML = atob(mangaReadingConfig);
const configElement = temp.children[0] as HTMLDivElement;
const ui = {
ptApiUrl: configElement.querySelector<HTMLInputElement>('[data-mr-pt-api-url]'),
ptApiBearerToken: configElement.querySelector<HTMLInputElement>(
const ptApiUrl = configElement.querySelector<HTMLInputElement>('[data-mr-pt-api-url]');
if (!ptApiUrl) throw new Error('PT API URL not found');
const ptApiBearerToken = configElement.querySelector<HTMLInputElement>(
'[data-mr-pt-api-bearer-token]'
),
titleList: configElement.querySelector<HTMLTextAreaElement>('[data-mr-title-list]'),
subTitle: configElement.querySelector<HTMLSpanElement>('[data-mr-title-current]'),
btnAdd: configElement.querySelector<HTMLButtonElement>('[data-mr-title-current-add]'),
btnRemove: configElement.querySelector<HTMLButtonElement>('[data-mr-title-current-remove]'),
btnSave: configElement.querySelector<HTMLButtonElement>('[data-mr-title-list-save]'),
btnReset: configElement.querySelector<HTMLButtonElement>('[data-mr-title-list-reset]')
);
if (!ptApiBearerToken) throw new Error('PT API Bearer Token not found');
const titleList = configElement.querySelector<HTMLTextAreaElement>('[data-mr-title-list]');
if (!titleList) throw new Error('Title List not found');
const subTitle = configElement.querySelector<HTMLSpanElement>('[data-mr-title-current]');
if (!subTitle) throw new Error('Subtitle not found');
const btnAdd = configElement.querySelector<HTMLButtonElement>('[data-mr-title-current-add]');
if (!btnAdd) throw new Error('Add button not found');
const btnRemove = configElement.querySelector<HTMLButtonElement>(
'[data-mr-title-current-remove]'
);
if (!btnRemove) throw new Error('Remove button not found');
const btnSave = configElement.querySelector<HTMLButtonElement>('[data-mr-title-list-save]');
if (!btnSave) throw new Error('Save button not found');
const btnReset = configElement.querySelector<HTMLButtonElement>('[data-mr-title-list-reset]');
if (!btnReset) throw new Error('Reset button not found');
const ui = {
ptApiUrl,
ptApiBearerToken,
titleList,
subTitle,
btnAdd,
btnRemove,
btnSave,
btnReset
};
function escapeRegExp(input: string) {
@ -513,13 +516,12 @@ async function initMangaReading() {
window.location = url as string & Location;
}
function setTitleList() {
if (!ui.titleList) return;
ui.titleList.value = globals.titleList.join('\r\n');
}
function setPTUi() {
if (ui.ptApiUrl) ui.ptApiUrl.value = globals.ptApi.url;
if (ui.ptApiBearerToken) ui.ptApiBearerToken.value = globals.ptApi.bearerToken;
ui.ptApiUrl.value = globals.ptApi.url;
ui.ptApiBearerToken.value = globals.ptApi.bearerToken;
}
function atNeither() {
@ -565,6 +567,7 @@ async function initMangaReading() {
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (!image) continue;
image.style.width = `${(pageWidth / image.width) * 100}%`;
}
@ -575,8 +578,9 @@ async function initMangaReading() {
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (!image) continue;
image.style.width = `${(pageWidth / image.width) * 100}%`;
image.style.width = `${(window?.visualViewport?.height ?? 1 / image.height) * 100}%`;
image.style.width = `${(window.visualViewport?.height ?? 1 / image.height) * 100}%`;
}
return;
@ -585,22 +589,17 @@ async function initMangaReading() {
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (!image) continue;
image.style.width = image.style.width = `${input}%`;
}
}
function setSubTitle() {
if (!ui.subTitle) return;
ui.subTitle.innerText = atChapterOrManga() ? globals.currentTitle : 'No title';
}
function registerConfig() {
setSubTitle();
if (!mr) return;
if (!ui.btnRemove) return;
if (!ui.btnAdd) return;
if (!ui.btnReset) return;
if (!ui.btnSave) return;
ui.btnRemove.onclick = removeTitle;
ui.btnRemove.disabled = atNeither();
@ -609,7 +608,6 @@ async function initMangaReading() {
ui.btnAdd.disabled = atNeither();
ui.btnReset.onclick = () => {
if (!ui.titleList) return;
ui.titleList.value = globals.titleList.join('\r\n');
};
@ -622,28 +620,26 @@ async function initMangaReading() {
setTitleList();
setPTUi();
if (!ui.btnRemove) return;
if (!ui.btnAdd) return;
ui.btnRemove.disabled = atNeither();
ui.btnAdd.disabled = atNeither();
});
}
function addTitle() {
const trimmedValue = ui.titleList?.value.trim() ?? '';
const trimmedValue = ui.titleList.value.trim();
const taTitleListValue = trimmedValue.split(/\r?\n/);
if (taTitleListValue.includes(globals.currentTitle)) return;
if (taTitleListValue.includes(globals.currentTitle)) {
return;
}
const curTAVal = trimmedValue.length > 0 ? `${trimmedValue}\r\n` : '';
if (!ui.titleList) return;
ui.titleList.value = curTAVal + globals.currentTitle;
}
async function saveTitles() {
globals.titleList = [...new Set(ui.titleList?.value.trim().split(/\r?\n/).sort())];
globals.titleList = [...new Set(ui.titleList.value.trim().split(/\r?\n/).sort())];
await GM.setValue(key.titleList, JSON.stringify(globals.titleList));
if (atChapter()) {
@ -652,15 +648,13 @@ async function initMangaReading() {
}
async function savePtApi() {
globals.ptApi.url = ui.ptApiUrl?.value.trim() ?? '';
globals.ptApi.bearerToken = ui.ptApiBearerToken?.value.trim() ?? '';
globals.ptApi.url = ui.ptApiUrl.value.trim();
globals.ptApi.bearerToken = ui.ptApiBearerToken.value.trim();
await GM.setValue(key.ptAPi, JSON.stringify(globals.ptApi));
}
function removeTitle() {
if (!ui.titleList) return;
const curTAVal = ui.titleList.value.trim() ?? '';
const curTAVal = ui.titleList.value.trim();
const regex = new RegExp(`${escapeRegExp(globals.currentTitle)}\\r?\\n?`, 'gi');
ui.titleList.value = curTAVal.replace(regex, '');
}
@ -679,6 +673,7 @@ async function initMangaReading() {
const latestChapterLink = latestChapter?.querySelector<HTMLAnchorElement>('& > span > a');
if (!latestChapterLink) return;
setLocation(latestChapterLink.href);
}
@ -695,7 +690,7 @@ async function initMangaReading() {
}
function findUrls() {
console.log(`${GM.info.script.name} - Finding URLs...`);
console.log(`${logName} Finding URLs...`);
const links = document.querySelectorAll<HTMLAnchorElement>('div.breadcrumb > p > span > a');
const titleLink = links[1];
@ -705,7 +700,7 @@ async function initMangaReading() {
setSubTitle();
if (!atChapter() || titleLink === undefined) {
console.log(`${GM.info.script.name} - Found URLs.`);
console.log(`${logName} Found URLs.`);
return;
}
@ -729,39 +724,39 @@ async function initMangaReading() {
globals.prevUrl = titleLink.href;
}
console.log(`${GM.info.script.name} - Found URLs.`);
console.log(`${logName} Found URLs.`);
}
async function loadTitleList() {
console.log(`${GM.info.script.name} - Loading title list...`);
console.log(`${logName} Loading title list...`);
const value = await GM.getValue(key.titleList, JSON.stringify({ titleList: [] }));
if (typeof value !== 'string') {
console.error('Invalid titleList value:', value);
console.error(`${logName} Invalid titleList value: ${value}`);
globals.titleList = [];
return;
}
globals.titleList = JSON.parse(value);
console.log(`${GM.info.script.name} - Loaded title list.`);
console.log(`${logName} Loaded title list.`);
}
async function loadPtApi() {
console.log(`${GM.info.script.name} - Loading progress tracker api...`);
console.log(`${logName} Loading progress tracker api...`);
const value = await GM.getValue(key.ptAPi, JSON.stringify({ url: '', bearerToken: '' }));
if (typeof value !== 'string') {
console.error('Invalid ptApi value:', value);
console.error(`${logName} Invalid ptApi value: ${value}`);
globals.ptApi = { url: '', bearerToken: '' };
return;
}
globals.ptApi = JSON.parse(value);
console.log(`${GM.info.script.name} - Loaded progress tracker api.`);
console.log(`${logName} Loaded progress tracker api.`);
}
function manganatoSiteOverrides() {
@ -793,9 +788,9 @@ async function initMangaReading() {
}
function siteOverrides() {
console.log(`${GM.info.script.name} - Applying site overrides...`);
console.log(`${logName} Applying site overrides...`);
manganatoSiteOverrides();
console.log(`${GM.info.script.name} - Applied site overrides.`);
console.log(`${logName} Applied site overrides.`);
}
function setAt() {
@ -804,7 +799,7 @@ async function initMangaReading() {
const atChapter = new RegExp(/\/manga\/[\w.\-~%]+\/chapter-[\d.-]+/).test(path);
const atManga = new RegExp(/\/manga\/[\w.\-~%]+$/).test(path);
console.log(`${GM.info.script.name} - Setting at...`, { atChapter, atManga });
console.log(`${logName} Setting at...`, { atChapter, atManga });
if (atChapter) globals.at = 'chapter';
else if (atManga) globals.at = 'manga';
@ -820,7 +815,6 @@ async function initMangaReading() {
'.panel_page_number > .group_page > a.page_select'
);
console.log('pageSelected', pageSelected);
if (!pageSelected) return;
const previous = pageSelected.previousElementSibling as HTMLAnchorElement | null;
@ -960,13 +954,13 @@ async function initMangaReading() {
});
if (!response.ok) {
console.error('Failed to fetch bookmarks');
console.error(`${logName} Failed to fetch bookmarks`);
return [];
}
return response.json();
} catch (e) {
console.error('Failed to fetch bookmarks');
console.error(`${logName} Failed to fetch bookmarks`);
console.error(e);
return [];
}
@ -980,12 +974,12 @@ async function initMangaReading() {
});
if (!response.ok) {
console.error('Failed to delete bookmark');
console.error(`${logName} Failed to delete bookmark`);
return false;
}
return true;
} catch (e) {
console.error('Failed to delete bookmarks');
console.error(`${logName} Failed to delete bookmarks`);
console.error(e);
return false;
}
@ -996,7 +990,7 @@ async function initMangaReading() {
body: { name: string; href: string }
) {
try {
console.log(`${GM.info.script.name} - Creating or updating bookmark`, options.input, body);
console.log(`${logName} Creating or updating bookmark`, options.input, body);
const response = await nwsFetch(options.input, {
method: 'PUT',
body: JSON.stringify(body),
@ -1004,7 +998,7 @@ async function initMangaReading() {
});
if (!response.ok) {
console.error(`${GM.info.script.name} - Failed to create or update bookmark`);
console.error(`${logName} Failed to create or update bookmark`);
return { success: false, created: false };
}
@ -1013,12 +1007,12 @@ async function initMangaReading() {
json.success = true;
return json;
} catch (e) {
console.error(`${GM.info.script.name} - Failed to parse response`);
console.error(`${logName} Failed to parse response`);
console.error(e);
return { success: false, created: false };
}
} catch (e) {
console.error(`${GM.info.script.name} - Failed to create or update bookmarks`);
console.error(`${logName} Failed to create or update bookmarks`);
console.error(e);
return { success: false, created: false };
}
@ -1036,12 +1030,12 @@ async function initMangaReading() {
});
if (!response.ok) {
console.error(`${GM.info.script.name} - Failed to check bookmark`);
console.error(`${logName} Failed to check bookmark`);
return false;
}
return true;
} catch (e) {
console.error(`${GM.info.script.name} - Failed to check bookmark`);
console.error(`${logName} Failed to check bookmark`);
console.error(e);
return false;
}
@ -1099,7 +1093,7 @@ async function initMangaReading() {
const content = notification.querySelector<HTMLDivElement>('[data-content]');
if (title === null || description === null || content === null) {
console.error(`${GM.info.script.name} - Notification elements not found.`);
console.error(`${logName} Notification elements not found.`);
return;
}
@ -1362,28 +1356,25 @@ async function initMangaReading() {
}
function registerKeyUps() {
mr?.shortcut.keyUp.register('ConfigClosed', {
name: `${GM.info.script.name} - config closed`,
mr.shortcut.keyUp.register('ConfigClosed', {
name: `${logName} config closed`,
callback: configClosedShortcuts
});
}
async function checkFirstRun() {
console.log(`${GM.info.script.name} - First run check...`);
console.log(`${logName} First run check...`);
const value = await GM.getValue(key.firstRun, true);
if (value) {
console.log(`${GM.info.script.name} - First run detected.`);
console.log(`${logName} First run detected.`);
GM.setValue(key.firstRun, false);
GM.notification(
'First run setup complete',
`${GM.info.script.name} - ${GM.info.script.name}`
);
GM.notification('First run setup complete', `${logName} ${GM.info.script.name}`);
}
console.log(`${GM.info.script.name} - First run checked.`);
console.log(`${logName} First run checked.`);
}
async function onInit() {
console.log(`${GM.info.script.name} - ${GM.info.script.name} - Loading...`);
console.log(`${logName} ${GM.info.script.name} - Loading...`);
await checkFirstRun();
registerKeyUps();
setAt();
@ -1394,19 +1385,27 @@ async function initMangaReading() {
removeMargins();
}
siteOverrides();
console.log(`${GM.info.script.name} - ${GM.info.script.name} - Loaded.`);
console.log(`${logName} ${GM.info.script.name} - Loaded.`);
}
async function postInit() {
console.log(`${GM.info.script.name} - ${GM.info.script.name} - Post Loading...`);
console.log(`${logName} ${GM.info.script.name} - Post Loading...`);
initToastContainer();
insertToastContainer();
loadStyle('stylesheet', false, shadowRoot);
loadStyle('overrides', true, shadowRoot);
console.log(`${GM.info.script.name} - ${GM.info.script.name} - Post Loaded.`);
console.log(`${logName} ${GM.info.script.name} - Post Loaded.`);
}
registerConfig();
mr.init(onInit, postInit);
}
initMangaReading();
async function mangaReadingSafeStart() {
try {
await initMangaReading();
} catch (e) {
console.error(`Error initializing userscript - ${GM.info.script.name}:\n`, e);
}
}
mangaReadingSafeStart();