跳转到内容

User:What7what8/darkmodefixtool.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
//WIP
//TODO: Fix bug (link in temp dont work cuz replaceBetween)
//Fixed: Fix big bug (unable to use when page has gallery element in wikitext, Example:元素周期表, 等离子体)
//TODO: Fix big bug (make buggy change at some page, Example: 氧)
$(document).ready(function() {
	$.getJSON(mw.config.get("wgServer") + mw.config.get("wgScriptPath") + "/api.php?action=query&prop=revisions&format=json&titles=" + mw.config.get("wgPageName") + "&rvlimit=1&rvslots=*&rvprop=content|ids|timestamp&meta=tokens&type=csrf", function(data) {
		let token = data.query.tokens.csrftoken
		var js2 = data.query.pages[mw.config.get("wgArticleId")].revisions[0];
		let baserevid = js2.revid
		let basetimestamp = js2.timestamp
		const wikitext = js2.slots.main['*'];
		/*if (/<gallery.*?>[\s\S]*?<\/gallery>/gim.test(wikitext)){
			mw.notify("this page is too powerful for this small tool")
			return;
		}*/
		// REGEX
		var imgLink = /(?:File:|Image:)([\s\S]+?(?:\.png|\.jpg|\.svg|\.jpeg|\.gif|\.tiff|\.webp|\.xcf|\.pdf))/gim;
		var imgTemp = /([\s\S]+?(\.png|\.jpg|\.svg|\.jpeg|\.gif|\.tiff|\.webp|\.xcf|\.pdf))/gim;
		var linkAndTemp = /\[\[[\s\S]+?\]\]|{{[\s\S]+?}}/gim;

		function waitForElm(selector) {
			return new Promise(resolve => {
				if (document.querySelector(selector)) {
					return resolve(document.querySelector(selector));
				}
				const observer = new MutationObserver(mutations => {
					if (document.querySelector(selector)) {
						observer.disconnect();
						resolve(document.querySelector(selector));
					}
				});
				observer.observe(document.body, {
					childList: true,
					subtree: true
				});
			});
		}

		function isRangeInRange(innerStart, innerEnd, outerStart, outerEnd) {
			return innerStart >= outerStart && innerEnd <= outerEnd;
		}

		function replaceBetween(input, replacement) {
			replacement = replacement.filter(function(currentValue, index, array) {
				let [text, start, end] = currentValue;
				if (input.substring(start, end) === text) {
					return false
				}
				for (let i = 0; i < array.length; i++) {
					let [text1, start1, end1] = array[i];
					if (i !== index && isRangeInRange(start, end, start1, end1)) {
						return false
					}
				}
				return true
			})
			let res = [];
			let i = 0;
			for (let k = 0; k < replacement.length; k++) {
				let [text, start, end] = replacement[k];
				res.push(input.slice(i, start) + text);
				i = end;
			}
			res.push(input.slice(i));
			return res.join("")
		}

		function extractNestedStrings(inputStr, pattern = "[]", include = false) {
			//extract template or link from wikitext, example: 'abc[[a[[b]][[[aaa]]][abc]cde[[f[[g]]]]]]abc' -> ['b', '[aaa', 'g', 'f[[g]]', 'a[[b]][[[aaa]]][abc]cde[[f[[g]]]]']
			let skipIndex = [] 
			let skipPatt = /(?:<\s*?nowiki[\s\S]*?>[\s\S]*?<\s*?\/\s*?nowiki\s*?>)|(?:<\s*?math[\s\S]*?>[\s\S]*?<\s*?\/\s*?math\s*?>)|<!--[\s\S]*?-->/igm;
			while (mathEle = skipPatt.exec(inputStr)) {
				skipIndex.push([mathEle.index,skipPatt.lastIndex])
			}
			var openIndices = [];
			var openBracketCount = 0;
			var closeBracketCount = 0;
			var result = [];
			main: for (var i = 0; i < inputStr.length; i++) {
				for (var j = 0; j < skipIndex.length; j++) {
					if (i >= skipIndex[j][0] && i < skipIndex[j][1]) {
						continue main;
					}
				} 
				var char = inputStr.charAt(i);
				if (char === pattern.charAt(0)) {
					openBracketCount++;
					if (openBracketCount === 2) {
						openBracketCount = 0;
						openIndices.push(include ? i - 1 : i + 1);
					}
				} else {
					openBracketCount = 0;
				}
				if (char === pattern.charAt(1)) {
					closeBracketCount++;
					if (closeBracketCount === 2) {
						closeBracketCount = 0;
						result.push(
							[inputStr.substring(openIndices[openIndices.length - 1], include ? i + 1 : i - 1),
								openIndices[openIndices.length - 1],
								include ? i + 1 : i - 1
							]);
						openIndices.pop();
					}
				} else {
					closeBracketCount = 0;
				}
			}
			return result;
		}

		function filterImage(mode) {
			var result = [];
			var pattern = (mode === 'temp') ? '{}' : '[]';
			const elist = extractNestedStrings(wikitext , pattern);
			for (var i = 0; i < elist.length; i++) {
				const [ele, start, end] = elist[i];
				const e = ele.replaceAll("{{skin-invert}}", "|class=skin-invert").replaceAll(linkAndTemp, "")
				console.log((ele.includes('gallery')) ? ele : '')
				//need rewrite this
				var args = e.split("|");
				for (var j = 0; j < args.length; j++) {
					var arg = args[j];
					var r = [...arg.split("=").at(-1).matchAll(((mode === 'temp') ? imgTemp : imgLink))]
					if (r.length !== 0 && !r[0][1].includes("://")) {
						let inverted = false
						if (mode === 'temp') {
							if (arg.includes("skin-invert") || (j + 1 !== args.length && /class\s*=.*(skin-invert|skin-invert-image)/.test(args[j + 1]))) {
								inverted = true
							}
						} else {
							if (e.includes('skin-invert')) {
								inverted = true
							}
						}
						var secondInvertWarn = 0;
						if (e.includes('skin-invert') && mode === 'temp') {
							secondInvertWarn = 1;
							if (!inverted) {
								secondInvertWarn = 2;
							}
						}
						let code = ele.replaceAll("\n", "</nowiki><br /><nowiki>").replaceAll(r[0][1], '</nowiki><span style="color: orange">' + r[0][1] + '</span><nowiki>').replaceAll("skin-invert-image", '</nowiki><span style="color: cyan">skin-invert-image</span><nowiki>').replaceAll(/skin-invert(?!-image)/g, '</nowiki><span style="color: cyan">skin-invert</span><nowiki>')
						result.push({
							'target': r[0][1].replace("File:", "").replace("Image:", ""),
							'inverted': inverted,
							'warn': [mode === 'temp' ? e.includes("class") : false, secondInvertWarn],
							'code': (mode === 'temp') ? '{{' + code + '}}' : '[[' + code + ']]'
						});
					}
				}
			}
			return result;
		}

		function genOutput(mode) {
			let gen = ""
			let list = filterImage(mode)
			for (var i = 0; i < list.length; i++) {
				var e = list[i];
				var warn = '';
				if (e.warn[0] === true) {
					warn += '{{Ombox|type = notice|text = 留意反色有否干擾原來的class}}';
				}
				if (e.warn[1] === 1) {
					warn += '{{Ombox|type = notice|text = 留意有否二次反色}}';
				} else if (e.warn[1] === 2) {
					warn += '{{Ombox|type = content|text = 注意可能二次反色}}';
				}
				gen += '{{hr|5}}' + warn + '{{HideH|代碼:}}<nowiki>' + e.code + '</nowiki>{{HideF}} <div style="background-color: ' + (mode === 'temp' ? '#202122' : "#101418") + ';"><div class="toolimg' + (e.inverted ? ' skin-invert' : '') + '" data-target="' + e.target + '" data-before-change-invert=' + e.inverted + ' data-mode=' + mode + '> [[File:' + e.target + '|250x250px]]</div></div>';
			}
			return gen
		}
		var output = genOutput("temp") + genOutput("link");
		var url = mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php';
		var myobj = {
			"action": "parse",
			"format": "json",
			"formatversion": 2,
			"text": output,
			"pst": "true",
			"disableeditsection": "true",
			"disabletoc": "true",
			"uselang": mw.config.get("wgPageViewLanguage"),
			"useskin": "vector-2022",
			"contentmodel": "wikitext",
			"prop": "text"
		};
		$.ajax({
			url: url,
			type: 'POST',
			data: myobj,
			success: function(data) {
				$('body').append('<div id="toolimg-panel">' + data.parse.text + '</div>')
				$(".toolimg").each(function(index) {
					$(this).after('<input type="checkbox" id="toolimg-checkbox-' + index + '"/><label style="color: white;" for="toolimg-checkbox-' + index + '">反色</label><button type="button" id="toolimg-eyedropper-' + index + '">測試背景顏色</button>')
					if ($(this).hasClass("skin-invert")) {
						$('#toolimg-checkbox-' + index)[0].checked = true;
					} else $('#toolimg-checkbox-' + index)[0].checked = false;
					let toolimg = $(this)
					$('#toolimg-checkbox-' + index).on('change', function() {
						if (this.checked) {
							if (!toolimg.hasClass("skin-invert")) toolimg.addClass("skin-invert")
						} else {
							if (toolimg.hasClass("skin-invert")) toolimg.removeClass("skin-invert")
						}
					});
					const eyeDropper = new EyeDropper()
					$('#toolimg-eyedropper-' + index).on('click', async function() {
						try {
							const selectedColor = await eyeDropper.open();
							toolimg.parent().attr("style", "background-color: " + selectedColor.sRGBHex + ";")
						} catch (err) {
							console.log('eye dropper cancelled')
						}
					})
				})
				try{
					$("#toolimg-panel").dialog({
						autoOpen: false,
						height: 1080 * 0.5,
						buttons: {
							"提交": function() {
								let changelist = []
								$(".toolimg").each(function(index) {
									changelist.push([$(this).attr('data-target'), $(this).attr("data-mode"), $(this).hasClass("skin-invert"), $(this).attr('data-before-change-invert') === 'true' ? true : false]);
								})
								let newwikitext = wikitext
								let linklist = extractNestedStrings(wikitext, "[]", true)
								let templist = extractNestedStrings(wikitext, "{}", true)
								let lastReach = 0
								let replacementList = []
								for (let s = 0; s < templist.length; s++) {
									let [temp, start, end] = templist[s]
									let templistArg = temp.split("|")
									for (let i = 0; i < templistArg.length; i++) {
										for (let j = lastReach; j < changelist.length; j++) {
											let [target, mode, afterChangeInvert, beforeChangeInvert] = changelist[j]
											if (templistArg[i].includes(target) && mode === 'temp') {
												//change inverted to not invert
												if (afterChangeInvert === false && beforeChangeInvert === true) {
													if (templistArg[i].includes("{{skin-invert}}")) {
														templistArg[i] = templistArg[i].replace("{{skin-invert}}", "")
													} else if (i + 1 !== templistArg.length && /class\s*=.*skin-invert/.test(templistArg[i + 1])) {
														templistArg[i] = templistArg[i].replace("skin-invert-image", "").replace("skin-invert", "")
													}
													//change not inverted to invert
												} else if (afterChangeInvert === true && beforeChangeInvert === false) {
													if (i + 1 !== templistArg.length && /class\s*=.*/.test(templistArg[i + 1]) && !(/class\s*=.*skin-invert/.test(templistArg[i + 1]))) {
														templistArg[i + 1] += " skin-invert"
													} else if (!templistArg[i].includes("{{skin-invert}}")) {
														templistArg[i] = templistArg[i].replace(target, target + "{{skin-invert}}")
													}
												}
												lastReach++
												continue
											}
										}
									}
									replacementList.push([templistArg.join('|'), start, end])
								}
								for (let i = 0; i < linklist.length; i++) {
									let [link, start, end] = linklist[i]
									for (let j = lastReach; j < changelist.length; j++) {
										let [target, mode, afterChangeInvert, beforeChangeInvert] = changelist[j]
										if (link.includes(target) && mode === 'link') {
											//change inverted to not invert
											if (afterChangeInvert === false && beforeChangeInvert === true) {
												if (link.includes("{{skin-invert}}")) {
													link = link.replace("{{skin-invert}}", "")
												} else if (/class\s*=.*skin-invert/.test(link)) {
													link = link.replace("skin-invert-image", "").replace("skin-invert", "")
												}
												//change not inverted to invert
											} else if (afterChangeInvert === true && beforeChangeInvert === false) {
												if (/class\s*=.*/.test(link) && !(/class\s*=.*skin-invert/.test(link))) {
													link = link.replace(/class\s*=(.*?)\|/, "class=$1 skin-invert-image|")
												} else {
													link = link.replace(target, target + "|class=skin-invert-image")
												}
											}
											lastReach++
											continue
										}
									}
									replacementList.push([link, start, end])
								}
								$(this).dialog("close");
								var editurl = mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php';
								var editobj = {
									"action": "edit",
									"title": mw.config.get("wgPageName"),
									"format": "json",
									"formatversion": 2,
									"variant": "zh",
									"text": replaceBetween(newwikitext, replacementList),
									"summary": "dark mode fix (skin-invert)",
									"token": token,
									"basetimestamp": basetimestamp,
									"baserevid": baserevid,
									"nocreate": true,
									"minor": true,
									"uselang": "zh",
									"useskin": "vector-2022"
								};
								$.ajax({
									url: editurl,
									type: 'POST',
									data: editobj,
									success: function(data) {
										location.reload();
										mw.notify("修改成功了!!!!!")
									}
								});
							}
						}
					})
			} catch (err) {
				$("#toolimg-panel").css('display','none');
				console.error(err)
			}
			},
			error: function(error) {
				console.error(error);
			}
		});
	});
	$(document).ajaxStop(function() {
		if (!$("#darkmodefix").length && $('#toolimg-panel').length) {
			mw.util.addPortlet('darkmodefix', 'Dark Mode Fix Tool', '#skin-client-prefs-skin-theme');
			const p = mw.util.addPortletLink('darkmodefix', "javascript:;", 'Skin-invert Image Tool')
			let open = false
			$(p).on("click", function() {
				if ($('#toolimg-panel').length) {
					if (!open) {
						$("#toolimg-panel").dialog("open");
						open = true
					} else {
						$("#toolimg-panel").dialog("close");
						open = false
					}
				}
			});
		} else {
			$("#toolimg-panel").css('display','none');
		}
	});
})