Greasy Fork is available in English.

Image Names

Load Image Pokemon Name By HyperBeam and RenjiYuusei

// ==UserScript==
// @name         Image Names
// @namespace    http://tampermonkey.net/
// @version      5.0.1
// @description  Load Image Pokemon Name By HyperBeam and RenjiYuusei
// @run-at       document-end
// @author       @HyperBeam & @RenjiYuusei
// @match        *://sangtacviet.vip/truyen/*
// @license      GPL-3.0
// @icon64       https://sangtacviet.vip/favicon.png
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
	'use strict';

	//Chiều rộng bảng edit name STV
	const NAME_EDITOR_MOBILE_WIDTH = '100%';
	const NAME_EDITOR_PC_WIDTH = '50%';

	const STYLES = {
		'.pokemon-image': {
			width: '50px',
			height: '50px',
		},
		'.pokemon-type': {
			width: '25px',
			height: '25px',
		},
		'.pokemon-ball': {
			width: '35px',
			height: '35px',
		},
		'.type': {
			fontWeight: 'bold',
			textShadow: '1px 1px 0 #222, -1px -1px 0 #222, 1px -1px 0 #222, -1px 1px 0 #222',
		},
		'i:has(img), i:has(span)': {
			whiteSpace: 'nowrap',
		},
		'i > img': {
			display: 'inline-block',
			marginTop: '-5px',
		},
		'#configBox': {
			zIndex: '100',
		},
	};

	const TYPE_COLOR = {
		Normal: '#ededed',
		Fire: '#ff9236',
		Water: '#75d0ff',
		Electric: '#ffcd2c',
		Grass: '#70ffa2',
		Ice: '#4dffee',
		Fighting: '#ff4f61',
		Poison: '#ff4ae5',
		Ground: '#ebbb8a',
		Flying: '#c6d7ff',
		Psychic: '#ff9999',
		Bug: '#d7ff71',
		Rock: '#fff7bd',
		Ghost: '#a689ff',
		Dragon: '#6a90ff',
		Dark: '#dfd0ff',
		Steel: '#36c4d3',
		Fairy: '#ffa5e2',
	};

	const BUTTON_STYLES = {
		fontSize: '14px',
		outline: 'none',
		borderRadius: '100%',
		height: '50px',
		width: '50px',
		marginBottom: '10px',
		cursor: 'pointer',
		border: '1px solid #ccc',
		backgroundColor: '#f0f0f0',
		transition: 'background-color 0.3s',
	};

	const INPUT_STYLE = {
		width: '200px',
		height: '8px',
		borderRadius: '5px',
		outline: 'none',
		marginLeft: '5px',
	};

	const RESIZE_INPUT_MIN_SIZE = 20;
	const RESIZE_INPUT_MAX_SIZE = 80;
	const RESIZE_INPUT_STEP = 10;

	const DEFAULT_CONFIG = {
		showCopyButton: true,
		showNamesButton: true,
		showReloadButton: true,
		showResizeInput: true,
	};

	let config = { ...DEFAULT_CONFIG };

	function loadConfig() {
		const savedConfig = GM_getValue('imageNamesConfig');
		if (savedConfig) {
			config = { ...DEFAULT_CONFIG, ...JSON.parse(savedConfig) };
		}
	}

	function saveConfig() {
		GM_setValue('imageNamesConfig', JSON.stringify(config));
	}

	let imageSize = '50';

	function loadImageSize() {
		const savedImageSize = GM_getValue('imageSize');
		if (savedImageSize) {
			imageSize = JSON.parse(savedImageSize);
		}
	}

	function saveImageSize() {
		GM_setValue('imageSize', JSON.stringify(imageSize));
	}

	function updateStyle() {
		STYLES['.pokemon-image'].width = imageSize;
		STYLES['.pokemon-image'].height = imageSize;
	}

	function applyStyles() {
		updateStyle();

		document.querySelectorAll('i').forEach(tag => {
			if (/<img|<span/.test(tag.textContent)) {
				tag.innerHTML = tag.textContent;
			}
		});

		Object.entries(STYLES).forEach(([selector, styles]) => {
			document.querySelectorAll(selector).forEach(element => {
				Object.assign(element.style, styles);
			});
		});

		Object.entries(TYPE_COLOR).forEach(([selector, color]) => {
			Array.from(document.getElementsByClassName(selector)).forEach(element => {
				element.style.color = color;
			});
		});
	}

	function createButton({ text, onClickFunction }) {
		const button = document.createElement('button');
		Object.assign(button.style, BUTTON_STYLES);

		button.textContent = text;
		button.addEventListener('mouseover', () => (button.style.backgroundColor = '#e0e0e0'));
		button.addEventListener('mouseout', () => (button.style.backgroundColor = '#f0f0f0'));
		button.addEventListener('click', onClickFunction);

		return button;
	}

	function createInput({ name, type, event, handler }) {
		const input = document.createElement('input');
		Object.assign(input.style, INPUT_STYLE);

		input.name = name;
		input.type = type;
		input.addEventListener(event, handler);

		return input;
	}

	function clickButton(selector) {
		document.querySelector(`button[onclick='${selector}']`)?.click();
	}

	function createBoxMenu() {
		const boxMenu = document.createElement('div');
		Object.assign(boxMenu.style, {
			position: 'fixed',
			bottom: '100px',
			right: '10px',
			display: 'flex',
			alignItems: 'end',
			flexDirection: 'column',
			zIndex: '1',
			width: '0',
		});

		const buttons = [
			{ condition: 'showCopyButton', text: 'Copy', onClickFunction: copyToClipboard },
			{ condition: 'showNamesButton', text: 'Names', onClickFunction: () => clickButton('showNS()') },
			{ condition: 'showReloadButton', text: 'Reload', onClickFunction: () => clickButton('excute()') },
			{ condition: true, text: 'Config', onClickFunction: createConfigMenu },
		];

		buttons.forEach(({ condition, ...props }) => {
			if (config[condition] || condition === true) {
				boxMenu.appendChild(createButton(props));
			}
		});

		if (config.showResizeInput) {
			const props = {
				name: 'resize',
				type: 'range',
				event: 'input',
				handler: event => {
					imageSize = event.target.value;
					saveImageSize();
					applyStyles();
				},
			};

			const input = createInput(props);
			const resizeInput = createResizeInput(input);

			boxMenu.appendChild(resizeInput);
		}

		document.body.appendChild(boxMenu);
	}

	function createResizeInput(input) {
		input.value = imageSize;
		input.min = RESIZE_INPUT_MIN_SIZE;
		input.max = RESIZE_INPUT_MAX_SIZE;
		input.step = RESIZE_INPUT_STEP;
		input.style.appearance = 'auto';
		input.style.display = 'flex';

		const dataList = document.createElement('datalist');
		dataList.id = 'size-list';

		for (let i = RESIZE_INPUT_MIN_SIZE; i <= RESIZE_INPUT_MAX_SIZE; i += RESIZE_INPUT_STEP) {
			const option = document.createElement('option');
			option.value = i;
			dataList.appendChild(option);
		}

		input.setAttribute('list', 'size-list');

		const resizeInput = document.createElement('label');
		resizeInput.appendChild(document.createTextNode(`Resize:`));
		resizeInput.appendChild(dataList);
		resizeInput.appendChild(input);
		return resizeInput;
	}

	async function copyToClipboard(e) {
		try {
			const copyText = document.querySelector('#namewd')?.value || '';
			await navigator.clipboard.writeText(copyText);
			e.target.textContent = 'Copied!';
		} catch (err) {
			e.target.textContent = 'Error!';
		} finally {
			setTimeout(() => {
				e.target.textContent = 'Copy';
			}, 2000);
		}
	}

	function createConfigMenu() {
		const modal = document.createElement('div');
		Object.assign(modal.style, {
			position: 'fixed',
			top: '0',
			left: '0',
			width: '100%',
			height: '100%',
			backgroundColor: 'rgba(0, 0, 0, 0.5)',
			display: 'flex',
			justifyContent: 'center',
			alignItems: 'center',
			zIndex: '1000',
		});

		const menuContent = document.createElement('div');
		Object.assign(menuContent.style, {
			backgroundColor: 'white',
			padding: '20px',
			borderRadius: '10px',
			maxWidth: '300px',
			width: '90%',
		});

		const title = document.createElement('h2');
		title.textContent = 'Configuration';
		title.style.marginTop = '0';
		menuContent.appendChild(title);

		Object.keys(config).forEach(key => {
			const label = document.createElement('label');
			label.style.display = 'block';
			label.style.marginBottom = '10px';

			const checkbox = document.createElement('input');
			checkbox.type = 'checkbox';
			checkbox.checked = config[key];
			checkbox.id = key;
			checkbox.style.appearance = 'auto';
			checkbox.addEventListener('change', e => {
				config[key] = e.target.checked;
			});

			label.appendChild(checkbox);
			label.appendChild(document.createTextNode(' ' + createLabelName(key)));
			menuContent.appendChild(label);
		});

		const buttonContainer = document.createElement('div');
		buttonContainer.style.display = 'flex';
		buttonContainer.style.justifyContent = 'space-between';
		buttonContainer.style.marginTop = '20px';

		const saveButton = createButton({
			text: 'Save',
			onClickFunction: () => {
				saveConfig();
				modal.remove();
				location.reload();
			},
		});
		saveButton.style.borderRadius = '5px';
		saveButton.style.marginRight = '10px';
		saveButton.style.flex = '1';

		const cancelButton = createButton({ text: 'Cancel', onClickFunction: () => modal.remove() });
		cancelButton.style.borderRadius = '5px';
		cancelButton.style.flex = '1';

		buttonContainer.appendChild(saveButton);
		buttonContainer.appendChild(cancelButton);
		menuContent.appendChild(buttonContainer);

		modal.appendChild(menuContent);
		document.body.appendChild(modal);
	}

	function createLabelName(key) {
		return key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
	}

	function styleNameSettings() {
		const namewdf = document.querySelector('#namewdf');
		const namewd = document.querySelector('#namewd');

		if (window.innerWidth <= 600) {
			// Mobile styles
			namewdf.style.width = NAME_EDITOR_MOBILE_WIDTH;
		} else {
			// PC styles
			namewdf.style.width = NAME_EDITOR_PC_WIDTH;
		}

		Object.assign(namewd.style, {
			fontSize: '12px',
			lineHeight: '1.8',
			padding: '10px',
			outline: 'none',
			height: '60vh',
		});
	}

	function autoReload() {
		const actions = [
			"addSuperName('hv','z')",
			"addSuperName('hv','f')",
			"addSuperName('hv','s')",
			"addSuperName('hv','l')",
			"addSuperName('hv','a')",
			"addSuperName('el')",
			"addSuperName('vp')",
			"addSuperName('kn')",
			"addSuperName('el')",
			"addSuperName('hv','a')",
			'saveNS();excute();',
			'excute()',
		];

		actions.forEach(action => {
			const button = document.querySelector(`[onclick="${action}"]`);
			if (button) {
				button.addEventListener('click', applyStyles);
			}
		});
	}

	function init() {
		loadConfig();
		loadImageSize();
		applyStyles();
		createBoxMenu();
		styleNameSettings();
		autoReload();
	}

	requestAnimationFrame(() => {
		setTimeout(init, 2000);
	});
})();