- // ==UserScript==
- // @name MangaDex Customizer
- // @namespace https://github.com/rRoler/UserScripts
- // @version 1.0.2
- // @description Customize MangaDex title pages by adding custom alt titles, changing the main title and cover, and adding custom tags\links. All data is stored inside userscript storage.
- // @author Roler
- // @icon https://www.google.com/s2/favicons?sz=64&domain=mangadex.org
- // @match https://mangadex.org/*
- // @match https://canary.mangadex.dev/*
- // @match https://demo.komga.org/*
- // @supportURL https://github.com/rRoler/UserScripts/issues
- // @require https://cdnjs.cloudflare.com/ajax/libs/validator/13.12.0/validator.min.js#sha256-d2c75e3159ceac9c14dcc8a7aeb09ea30970de6c321c89070e5b0157842c5c88
- // @grant GM_getValue
- // @grant GM_setValue
- // @run-at document-end
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- const userScriptId = `mdc-${crypto.randomUUID()}`;
-
- const storage = {
- mangadex: {
- titles: {
- custom_sections: {
- id: 'mangadex_titles_custom_sections',
- defaultValue: 'array'
- },
- data: {
- id: 'mangadex_titles_data',
- defaultValue: 'object',
- custom_sections: {
- id: 'custom_sections',
- defaultValue: 'array'
- },
- alt_titles: {
- id: 'alt_titles',
- defaultValue: 'array'
- },
- main_title: {
- id: 'main_title',
- defaultValue: 'string'
- },
- main_cover: {
- id: 'main_cover',
- defaultValue: 'string'
- }
- },
- }
- }
- };
-
- const createStorageDefaultValue = (type) => {
- switch (type) {
- case 'array':
- return [];
- case 'object':
- return {};
- default:
- return '';
- }
- };
- const getStorage = (section) => GM_getValue(section.id, createStorageDefaultValue(section.defaultValue));
- const setStorage = (section, value) => GM_setValue(section.id, value);
-
- const isMd = /^mangadex\.org|canary\.mangadex\.dev$/.test(window.location.hostname);
- const mdTitleOptions = {
- altTitle: {
- add: mdAddAltTitleOptions
- },
- customSection: {
- add: mdAddCustomSectionOptions
- },
- volumeCover: {
- add: mdAddVolumeCoverOptions,
- tab: 'art',
- dynamic: true
- }
- };
-
- const mdGetTitleStorage = (titleId, section) => {
- const storedData = getStorage(storage.mangadex.titles.data);
- return storedData[titleId] && storedData[titleId][section.id] || createStorageDefaultValue(section.defaultValue);
- }
- const mdSetTitleStorage = (titleId, section, value, del = false, append = false) => {
- const storedData = getStorage(storage.mangadex.titles.data);
- if (!storedData[titleId]) storedData[titleId] = {};
-
- if (append) {
- if (!storedData[titleId][section.id]) storedData[titleId][section.id] = [];
- if (del) {
- const index = storedData[titleId][section.id].indexOf(value);
- if (index > -1) storedData[titleId][section.id].splice(index, 1);
- } else {
- storedData[titleId][section.id].push(value);
- }
- } else {
- if (del) delete storedData[titleId][section.id];
- else storedData[titleId][section.id] = value;
- }
-
- try {
- if (storedData[titleId][section.id] && Object.keys(storedData[titleId][section.id]).length < 1)
- delete storedData[titleId][section.id];
- if (Object.keys(storedData[titleId]).length < 1)
- delete storedData[titleId];
- } catch (e) {}
-
- setStorage(storage.mangadex.titles.data, storedData);
- }
-
- const mdGetTitleId = (url = window.location.pathname) => {
- const titleIdMatch = url.match(/\/(?:title|manga|covers)\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/);
- return titleIdMatch && titleIdMatch[1];
- }
-
- const mdGetCoverFileName = (url) => {
- const fileNameMatch = url.match(/\/covers\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[A-Za-z]+)(\.[0-9]+\.[A-Za-z]+)?/);
- return {
- fileName: fileNameMatch && fileNameMatch[1],
- size: fileNameMatch && fileNameMatch[2]
- }
- }
-
- const mdGetAltTitlesSectionElement = (infoElement) => {
- const fullWidthSections = infoElement.querySelectorAll('.w-full');
- return Array.from(fullWidthSections).find(section => section.querySelector('.alt-title'));
- }
-
- const mdGetInfoElement = (titleId) => {
- let infoElement = document.querySelector('.flex.flex-wrap.gap-x-4.gap-y-2');
- if (!infoElement) return;
- infoElement = window.getComputedStyle(infoElement).display === 'none' ? document.querySelector(`[id="${titleId}"]`) : infoElement;
- if (!infoElement) return;
- return infoElement;
- }
-
- const komgaGetSeriesId = (url = window.location.pathname) => {
- const seriesIdMatch = url.match(/\/series\/([0-9A-Za-z]+)/);
- return seriesIdMatch && seriesIdMatch[1];
- }
-
- let mdTitleOptionsLoaded = false;
- let komgaCurrentSeriesId;
- let scriptErrored = false;
- observeElement(async (mutations, observer) => {
- if (scriptErrored) {
- observer.disconnect();
- alert('The MangaDex Customizer userscript has encountered an error.\nPlease reload the page or disable the userscript if this error persists.');
- return;
- }
-
- if (isMd && !window.location.pathname.includes('edit')) {
- if (!document.querySelector('.md-content')) return;
-
- const titleId = mdGetTitleId();
-
- if (titleId) {
- const currentTabMatch = window.location.search.match(/tab=([a-z]+)/);
- const currentTab = currentTabMatch && currentTabMatch[1] || 'chapters';
-
- for (const optionId in mdTitleOptions) {
- const option = mdTitleOptions[optionId];
-
- if (!option.tab || option.tab === currentTab) {
- if (option.dynamic || !option.loaded || option.loadedId !== titleId || option.loadedTab !== currentTab) {
- try {
- option.loaded = option.add(titleId);
- if (option.loaded) {
- option.loadedId = titleId;
- option.loadedTab = currentTab;
- mdTitleOptionsLoaded = true;
- }
- } catch (e) {
- console.error(e);
- scriptErrored = true;
- return;
- }
- }
- }
- }
- } else if (mdTitleOptionsLoaded) {
- for (const optionId in mdTitleOptions) {
- const option = mdTitleOptions[optionId];
-
- option.loaded = false;
- option.loadedId = '';
- option.loadedTab = '';
- if (option.storage) delete option.storage;
- }
- mdTitleOptionsLoaded = false;
- }
-
- try {
- mdReplaceTitles();
- mdReplaceVolumeCovers(titleId);
- } catch (e) {
- console.error(e);
- scriptErrored = true;
- }
- } else {
- if (!document.querySelector('.container')) return;
-
- const seriesId = komgaGetSeriesId();
-
- if (seriesId) {
- if (seriesId === komgaCurrentSeriesId) return;
-
- try {
- if (komgaAutoMatch(seriesId)) komgaCurrentSeriesId = seriesId;
- } catch (e) {
- console.error(e);
- scriptErrored = true;
- }
- } else {
- komgaCurrentSeriesId = '';
- }
- }
- });
-
- function mdAddCustomSectionOptions(titleId) {
- const infoElement = mdGetInfoElement(titleId);
- if (!infoElement) return false;
-
- const infoSectionElement = infoElement.querySelector('.mb-2:not(.hidden)');
- if (!infoSectionElement) return false;
- const sectionInfoElement = infoSectionElement.querySelector('div.flex.flex-wrap');
- if (!sectionInfoElement) return false;
- const sectionInfoLinkElement = sectionInfoElement.querySelector('a');
- if (!sectionInfoLinkElement) return false;
- const altTitlesSectionElement = mdGetAltTitlesSectionElement(infoElement);
- if (!altTitlesSectionElement) return false;
-
- const createSectionElement = (sectionData, required = false) => {
- const sectionIdAttribute = `${userScriptId}-section-id`;
- const sectionExists = !!document.querySelector(`[${sectionIdAttribute}="${sectionData.id}"]`);
- if (sectionExists) return;
-
- const newInfoSectionElement = infoSectionElement.cloneNode(true);
- newInfoSectionElement.setAttribute(sectionIdAttribute, sectionData.id);
-
- const newInfoNameElement = newInfoSectionElement.querySelector('div.font-bold');
- const newInfoElement = newInfoSectionElement.querySelector('div.flex.flex-wrap');
-
- newInfoNameElement.textContent = sectionData.name + (required ? '' : ' ');
- newInfoElement.querySelectorAll('a').forEach(element => element.remove());
-
- if (required) return newInfoSectionElement;
-
- const newInfoRemoveElement = document.createElement('span');
- newInfoRemoveElement.textContent = '⨯';
- newInfoRemoveElement.classList.add('cursor-pointer');
- newInfoRemoveElement.addEventListener('click', () => {
- if (!confirm(`Are you sure you want to delete this section?\n\n${sectionData.name}`)) return;
-
- const storedSections = getStorage(storage.mangadex.titles.custom_sections);
- const storedSectionIndex = storedSections.findIndex(section => section.id === sectionData.id);
-
- if (storedSectionIndex > -1) {
- storedSections.splice(storedSectionIndex, 1);
- setStorage(storage.mangadex.titles.custom_sections, storedSections);
- }
-
- newInfoSectionElement.remove();
- });
- newInfoNameElement.appendChild(newInfoRemoveElement);
-
- return newInfoSectionElement;
- }
-
- const createSectionButton = (sectionData, value, sectionInfoLinkElement) => {
- const newLink = sectionInfoLinkElement.cloneNode(true);
- const newLinkText = newLink.querySelector('span');
- newLink.href = '#';
- newLink.classList.add('gap-1');
- newLinkText.textContent = value;
-
- const newLinkRemove = document.createElement('span');
- newLinkRemove.textContent = '⨯';
- newLinkRemove.addEventListener('click', (event) => {
- event.preventDefault();
- event.stopPropagation();
-
- if (!confirm(`Are you sure you want to delete this ${sectionData.name}?\n\n${value}`)) return;
-
- const storedTitleSections = mdGetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections);
- const storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id);
-
- if (storedSectionIndex > -1) {
- const sectionValues = storedTitleSections[storedSectionIndex].values || [];
- const sectionValueIndex = sectionValues.findIndex(_value => _value === value);
- if (sectionValueIndex > -1) {
- sectionValues.splice(sectionValueIndex, 1);
- storedTitleSections[storedSectionIndex].values = sectionValues;
- }
- if (sectionValues.length < 1) {
- storedTitleSections.splice(storedSectionIndex, 1);
- }
- }
-
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections, storedTitleSections);
- newLink.remove();
- });
- newLink.appendChild(newLinkRemove);
-
- try {
- const valueMatch = value.match(/^\[([\s\w\-]+)]\((https?:\/\/.*)\)$/)
- const urlValue = valueMatch && valueMatch[2] ? valueMatch[2] : value
- if (!validator.isURL(urlValue)) throw new Error('Invalid URL');
- const url = new URL(urlValue);
- newLink.href = url.href;
- newLinkText.textContent = valueMatch && valueMatch[2] ? valueMatch[1] : url.hostname;
- return newLink;
- } catch (e) {}
-
- newLink.addEventListener('click', (event) => {
- event.preventDefault();
- event.stopPropagation();
-
- alert(value);
- });
-
- return newLink;
- };
-
- const createSectionLink = (sectionData, sectionElement) => {
- const newInfoElement = sectionElement.querySelector('div.flex.flex-wrap');
- const newInfoLinkElement = sectionInfoLinkElement.cloneNode(true);
- const newInfoLinkIconElement = newInfoLinkElement.querySelector('svg');
- const newInfoLinkTextElement = newInfoLinkElement.querySelector('span');
-
- newInfoLinkElement.target = '_blank';
- newInfoLinkElement.rel = 'noopener noreferrer';
- if (newInfoLinkIconElement) newInfoLinkIconElement.remove();
-
- const storedTitleSections = mdGetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections);
- const storedSectionData = storedTitleSections.find(section => section.id === sectionData.id) || {};
- const storedSectionDataValues = storedSectionData.values || [];
-
- storedSectionDataValues.forEach(value => {
- const newLink = createSectionButton(sectionData, value, newInfoLinkElement);
- if (!newLink) return;
- newInfoElement.appendChild(newLink);
- });
-
- newInfoLinkTextElement.textContent = `+`;
- newInfoLinkElement.href = '#';
- newInfoLinkElement.addEventListener('click', (event) => {
- event.preventDefault();
- event.stopPropagation();
-
- const storedSections = getStorage(storage.mangadex.titles.custom_sections);
- if (!storedSections.some(section => section.id === sectionData.id)) {
- storedSections.push(sectionData);
- setStorage(storage.mangadex.titles.custom_sections, storedSections);
- }
-
- const value = prompt(`Enter new ${sectionData.name} value`);
- if (!value) return;
-
- const newLink = createSectionButton(sectionData, value, newInfoLinkElement);
- if (!newLink) return;
-
- const storedTitleSections = mdGetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections);
- let storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id);
- if (storedSectionIndex < 0) {
- storedTitleSections.push({ id: sectionData.id });
- storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id);
- }
- const sectionValues = storedTitleSections[storedSectionIndex].values || [];
-
- sectionValues.push(value);
- storedTitleSections[storedSectionIndex].values = sectionValues;
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections, storedTitleSections);
- newInfoElement.insertBefore(newLink, newInfoLinkElement);
- });
- newInfoElement.appendChild(newInfoLinkElement);
-
- return newInfoElement;
- }
-
- const createSection = (sectionData) => {
- const newSectionElement = createSectionElement(sectionData);
- if (!newSectionElement) return;
- const newSectionLinkElement = createSectionLink(sectionData, newSectionElement);
- newSectionElement.appendChild(newSectionLinkElement);
- infoElement.insertBefore(newSectionElement, altTitlesSectionElement);
- }
-
- const addNewSectionElement = createSectionElement({ id: 'add_local_section', name: 'Custom Sections +' }, true);
- if (addNewSectionElement) {
- addNewSectionElement.querySelector('div.flex.flex-wrap').remove();
- const addNewSectionTextElement = addNewSectionElement.querySelector('div.font-bold');
- addNewSectionTextElement.classList.remove('mb-2');
- addNewSectionTextElement.classList.add('cursor-pointer');
- addNewSectionTextElement.style.setProperty('width', 'fit-content');
- addNewSectionElement.classList.remove('mb-2');
- addNewSectionElement.classList.add('w-full');
- addNewSectionTextElement.addEventListener('click', () => {
- const storedSections = getStorage(storage.mangadex.titles.custom_sections);
- const sectionName = prompt('Enter new section name');
- const trimmedSectionName = sectionName && sectionName.trim();
- if (!trimmedSectionName) return;
-
- const sectionData = {
- id: trimmedSectionName.replace(/\s/g, '_').toLowerCase(),
- name: trimmedSectionName
- }
-
- if (storedSections.some(section => section.id === sectionData.id)) return;
- storedSections.push(sectionData);
- setStorage(storage.mangadex.titles.custom_sections, storedSections);
-
- createSection(sectionData);
- });
-
- infoElement.insertBefore(addNewSectionElement, altTitlesSectionElement);
- }
-
- const storedSections = getStorage(storage.mangadex.titles.custom_sections);
- storedSections.forEach(createSection);
-
- return true;
- }
-
- function mdAddAltTitleOptions(titleId) {
- const infoElement = mdGetInfoElement(titleId);
- if (!infoElement) return false;
-
- if (!infoElement.querySelector('a')) return false;
-
- let altTitlesSectionElement = mdGetAltTitlesSectionElement(infoElement);
- if (!altTitlesSectionElement) {
- altTitlesSectionElement = document.createElement('div');
- altTitlesSectionElement.classList.add('w-full');
- infoElement.appendChild(altTitlesSectionElement);
-
- const altTitlesSectionTextElement = document.createElement('div');
- altTitlesSectionTextElement.classList.add('font-bold', 'mb-1');
- altTitlesSectionTextElement.textContent = 'Alternative Titles';
- altTitlesSectionElement.appendChild(altTitlesSectionTextElement);
-
- const altTitleElement = document.createElement('div');
- altTitleElement.classList.add('mb-1', 'flex', 'gap-x-2', 'alt-title');
- altTitlesSectionElement.appendChild(altTitleElement);
- }
- const altTitlesSectionLoadedAttribute = `${userScriptId}-alt-title-section-loaded`;
- if (altTitlesSectionElement.hasAttribute(altTitlesSectionLoadedAttribute)) return true;
- altTitlesSectionElement.setAttribute(altTitlesSectionLoadedAttribute, 'true');
-
- const altTitlesSectionTextElement = altTitlesSectionElement.querySelector('div.font-bold');
- const altTitlesElements = altTitlesSectionElement.querySelectorAll('.alt-title');
- const altTitleElement = altTitlesElements[0].cloneNode(true);
- if (!mdTitleOptions.altTitle.storage) mdTitleOptions.altTitle.storage = [];
-
- const addAltTitleStar = altTitleElement => {
- const storedTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title);
- const altTitleTextElement = altTitleElement.querySelector('span');
- if (!altTitleTextElement) return;
- const setTitleObject = {
- selected: storedTitle === altTitleTextElement.textContent,
- element: altTitleElement,
- starElement: document.createElement('span'),
- value: altTitleTextElement.textContent
- }
-
- setTitleObject.starElement.textContent = setTitleObject.selected ? '★' : '☆';
- setTitleObject.starElement.classList.add('cursor-pointer');
- if (setTitleObject.selected) mdReplaceTitles(titleId);
-
- setTitleObject.starElement.addEventListener('click', () => {
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.main_title, setTitleObject.value, setTitleObject.selected);
-
- mdReplaceTitles(titleId, setTitleObject.selected);
-
- setTitleObject.selected = !setTitleObject.selected;
- mdTitleOptions.altTitle.storage.forEach(_setTitleObject => {
- _setTitleObject.selected = _setTitleObject.value === setTitleObject.value && setTitleObject.selected;
- _setTitleObject.starElement.textContent = _setTitleObject.selected ? '★' : '☆';
- });
- });
-
- mdTitleOptions.altTitle.storage.push(setTitleObject);
- altTitleElement.prepend(setTitleObject.starElement);
- };
-
- const createAltTitle = (value) => {
- if (!altTitlesElements[0].querySelector('span')) altTitlesElements[0].remove();
- const newAltTitleElement = altTitleElement.cloneNode(true);
- const newAltTitleIconElement = newAltTitleElement.querySelector('div');
- let newAltTitleTextElement = newAltTitleElement.querySelector('span');
- if (!newAltTitleTextElement) {
- newAltTitleTextElement = document.createElement('span');
- newAltTitleElement.appendChild(newAltTitleTextElement);
- }
- const removeCustomAltTitleElement = document.createElement('span');
-
- if (newAltTitleIconElement) newAltTitleIconElement.remove();
- newAltTitleTextElement.textContent = value;
- removeCustomAltTitleElement.textContent = '⨯';
- removeCustomAltTitleElement.classList.add('cursor-pointer');
- removeCustomAltTitleElement.addEventListener('click', () => {
- if (!confirm(`Are you sure you want to delete this title?\n\n${value}`)) return;
-
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles, value, true, true);
-
- const setTitleObjectIndex = mdTitleOptions.altTitle.storage.findIndex(setTitleObject => setTitleObject.value === value);
- if (setTitleObjectIndex > -1) mdTitleOptions.altTitle.storage.splice(setTitleObjectIndex, 1);
-
- const storedAltTitles = mdGetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles);
- const storedMainTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title);
- if (storedMainTitle === value && !storedAltTitles.some(altTitle => altTitle === value)) {
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.main_title, value, true);
- mdReplaceTitles(titleId, true);
- }
-
- newAltTitleElement.remove();
- });
- newAltTitleElement.appendChild(removeCustomAltTitleElement);
- addAltTitleStar(newAltTitleElement);
- altTitlesSectionElement.appendChild(newAltTitleElement);
- };
-
- altTitlesElements.forEach(addAltTitleStar);
-
- altTitlesSectionTextElement.textContent = `${altTitlesSectionTextElement.textContent} +`
- altTitlesSectionTextElement.classList.add('cursor-pointer');
- altTitlesSectionTextElement.style.setProperty('width', 'fit-content');
- altTitlesSectionTextElement.addEventListener('click', () => {
- const value = prompt('Enter new title');
- if (!value) return;
-
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles, value, false, true);
-
- createAltTitle(value);
- });
-
- const storedAltTitles = mdGetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles);
- if (storedAltTitles) storedAltTitles.forEach(createAltTitle);
- const storedTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title);
- if (storedTitle && !mdTitleOptions.altTitle.storage.some(setTitleObject => setTitleObject.selected)) {
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles, storedTitle, false, true);
- createAltTitle(storedTitle);
- }
-
- return true;
- }
-
- function mdReplaceTitles(titleId, useDefaultTitle) {
- if (titleId) {
- const titlePageTitleElement = document.querySelector('div.title > p');
- if (!titlePageTitleElement) return;
-
- const defaultTitleAttribute = `${userScriptId}-default-title`;
- if (!titlePageTitleElement.hasAttribute(defaultTitleAttribute))
- titlePageTitleElement.setAttribute(defaultTitleAttribute, titlePageTitleElement.textContent);
-
- const defaultTitle = useDefaultTitle && titlePageTitleElement.getAttribute(defaultTitleAttribute);
- const storedMainTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title);
- titlePageTitleElement.textContent = defaultTitle || storedMainTitle || 'undefined';
-
- return;
- }
-
- const titleLinkElements = document.querySelectorAll(
- 'a.title, a.chapter-feed__title, .dense-manga-container a, .swiper-slide a, .manga-draft-container a, a[class=""]'
- );
- titleLinkElements.forEach(titleLinkElement => {
- const titleReplacedAttribute = `${userScriptId}-title-replaced`;
- if (titleLinkElement.hasAttribute(titleReplacedAttribute)) return;
- titleLinkElement.setAttribute(titleReplacedAttribute, 'true');
-
- let textElement = titleLinkElement;
- const hasTextNode = () => textElement && textElement.childNodes && Array.from(textElement.childNodes).some(text => text.data);
- if (!hasTextNode()) textElement = titleLinkElement.querySelector('span, h6');
- if (!hasTextNode() && titleLinkElement.parentElement)
- textElement = titleLinkElement.parentElement.querySelector('span, h2, div.font-bold');
- if (!hasTextNode()) return;
-
- if (textElement.parentElement && textElement.parentElement.tagName === 'BUTTON') return;
-
- const mdTitleId = mdGetTitleId(titleLinkElement.getAttribute('href'));
- if (!mdTitleId) return;
-
- const storedMainTitle = mdGetTitleStorage(mdTitleId, storage.mangadex.titles.data.main_title);
- if (!storedMainTitle) return;
-
- textElement.childNodes.forEach((text) => {
- if (text.data) text.data = storedMainTitle;
- });
- });
- }
-
- function mdAddVolumeCoverOptions(titleId) {
- if (document.querySelector('div[role="alert"]')) return true;
- if (document.querySelectorAll(`a[href*="covers/${titleId}"]`).length < 2) return false;
- const volumeCoverLoadedAttribute = `${userScriptId}-volume-cover-loaded`;
- const volumeCoverLinkElements = document.querySelectorAll(`a[href*="covers/${titleId}"]:not([${volumeCoverLoadedAttribute}])`);
- if (!mdTitleOptions.volumeCover.storage) mdTitleOptions.volumeCover.storage = [];
-
- volumeCoverLinkElements.forEach(volumeCoverLinkElement => {
- volumeCoverLinkElement.setAttribute(volumeCoverLoadedAttribute, 'true');
-
- const volumeSubtitleElement = volumeCoverLinkElement.querySelector('.subtitle');
- if (!volumeSubtitleElement) return;
- volumeSubtitleElement.textContent = ` ${volumeSubtitleElement.textContent}`;
-
- const volumeCoverLink = volumeCoverLinkElement.getAttribute('href');
- if (!volumeCoverLink) return;
- const volumeCoverFilename = mdGetCoverFileName(volumeCoverLink);
- if (!volumeCoverFilename.fileName) return;
-
- const storedVolumeCover = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_cover);
- const setCoverObject = {
- selected: volumeCoverFilename.fileName === storedVolumeCover,
- element: volumeCoverLinkElement,
- starElement: document.createElement('span'),
- value: volumeCoverFilename.fileName
- }
-
- setCoverObject.starElement.textContent = setCoverObject.selected ? '★' : '☆';
- setCoverObject.starElement.classList.add('cursor-pointer');
- setCoverObject.starElement.addEventListener('click', (event) => {
- event.preventDefault();
- event.stopPropagation();
-
- mdSetTitleStorage(titleId, storage.mangadex.titles.data.main_cover, setCoverObject.value, setCoverObject.selected);
-
- setCoverObject.selected = !setCoverObject.selected;
- mdTitleOptions.volumeCover.storage.forEach(_setCoverObject => {
- _setCoverObject.selected = _setCoverObject.value === setCoverObject.value && setCoverObject.selected;
- _setCoverObject.starElement.textContent = _setCoverObject.selected ? '★' : '☆';
- });
-
- mdReplaceVolumeCovers(titleId, !setCoverObject.selected);
- });
- volumeSubtitleElement.prepend(setCoverObject.starElement);
-
- mdTitleOptions.volumeCover.storage.push(setCoverObject);
- });
-
- return true;
- }
-
- function mdReplaceVolumeCovers(titleId, useDefault) {
- const coverLinkElement = document.querySelector(`.md-content > .manga a[href*="covers/${titleId}"]`);
- const replaceCoverUrl = (titleId, urlToReplace, storedCover) => {
- if (!titleId || !storedCover) return;
- const urlToReplaceFilename = mdGetCoverFileName(urlToReplace);
- if (!urlToReplaceFilename.size) urlToReplaceFilename.size = '';
- const newUrl = `https://mangadex.org/covers/${titleId}/${storedCover}${urlToReplaceFilename.size}`;
- if (newUrl !== urlToReplace) return newUrl;
- }
-
- if (coverLinkElement) {
- const defaultCoverAttribute = `${userScriptId}-default-cover`;
- if (!coverLinkElement.hasAttribute(defaultCoverAttribute)) {
- const coverLinkFileName = mdGetCoverFileName(coverLinkElement.getAttribute('href'));
- if (coverLinkFileName.fileName) coverLinkElement.setAttribute(defaultCoverAttribute, coverLinkFileName.fileName);
- }
-
- const storedCover = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_cover);
- const defaultCover = useDefault && coverLinkElement.getAttribute(defaultCoverAttribute);
- const newCover = defaultCover || storedCover;
-
- if (newCover) {
- const newCoverLinkUrl = replaceCoverUrl(titleId, coverLinkElement.getAttribute('href'), newCover);
- if (newCoverLinkUrl) coverLinkElement.setAttribute('href', newCoverLinkUrl);
-
- const coverImageElement = coverLinkElement.querySelector(`img[src*="covers/${titleId}"]`);
- if (coverImageElement) {
- const newCoverImageUrl = replaceCoverUrl(titleId, coverImageElement.getAttribute('src'), newCover);
- if (newCoverImageUrl) coverImageElement.setAttribute('src', newCoverImageUrl);
- }
-
- const bannerImageElement = document.querySelector('.banner-image');
- if (bannerImageElement) {
- const newBannerImageUrl = replaceCoverUrl(titleId, bannerImageElement.style.getPropertyValue('background-image'), newCover);
- if (newBannerImageUrl) bannerImageElement.style.setProperty('background-image', `url("${newBannerImageUrl}")`);
- }
- }
- }
-
- const coverLoadedAttribute = `${userScriptId}-cover-loaded`;
- const imageElements = document.querySelectorAll(`img:not([${coverLoadedAttribute}])`);
- imageElements.forEach(imageElement => {
- imageElement.setAttribute(coverLoadedAttribute, 'true');
- const imageUrl = imageElement.getAttribute('src');
- if (!imageUrl) return;
- const mdTitleId = mdGetTitleId(imageUrl);
- if (!mdTitleId || mdTitleId === titleId) return;
- const storedCover = mdGetTitleStorage(mdTitleId, storage.mangadex.titles.data.main_cover);
- const newCoverUrl = replaceCoverUrl(mdTitleId, imageUrl, storedCover);
- if (newCoverUrl) imageElement.setAttribute('src', newCoverUrl);
- });
- }
-
- function komgaAutoMatch(seriesId) {
- if (!document.querySelector(`.v-image__image[style*="${seriesId}"]`)) return false;
-
- const linkElements = document.querySelectorAll(`a.v-chip--link`);
- if (linkElements < 1) return false;
-
- const sectionData = {
- id: 'local_links',
- name: 'Local Links'
- }
-
- const storedSections = getStorage(storage.mangadex.titles.custom_sections);
- if (!storedSections.some(section => section.id === sectionData.id)) {
- storedSections.push(sectionData);
- setStorage(storage.mangadex.titles.custom_sections, storedSections);
- }
-
- linkElements.forEach(link => {
- const mdTitleId = mdGetTitleId(link.href);
- if (!mdTitleId) return;
-
- const storedTitleSections = mdGetTitleStorage(mdTitleId, storage.mangadex.titles.data.custom_sections);
- let storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id);
- if (storedSectionIndex < 0) {
- storedTitleSections.push({ id: sectionData.id });
- storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id);
- }
-
- const sectionValues = storedTitleSections[storedSectionIndex].values || [];
- if (sectionValues.some(link => seriesId === komgaGetSeriesId(link))) return;
-
- const sectionLink = `[Komga](${window.location.href.replace(/\?.*$/, '')})`;
- sectionValues.push(sectionLink);
- storedTitleSections[storedSectionIndex].values = sectionValues;
- mdSetTitleStorage(mdTitleId, storage.mangadex.titles.data.custom_sections, storedTitleSections);
- });
-
- return true;
- }
-
- function observeElement(onChange, element = document.body) {
- const observer = new MutationObserver(onChange);
-
- onChange();
- observer.observe(element, {
- childList: true,
- subtree: true,
- });
- }
- })();