From c80ed60d65d9bcb743dbf61abeb696a025564146 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 26 Dec 2025 10:20:46 +0900 Subject: [PATCH 01/68] Update pagetual.user.js --- Pagetual/pagetual.user.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 32c0e0c844c..d406513e3b3 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -31,7 +31,7 @@ // @name:da Pagetual // @name:fr-CA Pagetual // @namespace hoothin -// @version 1.9.37.125 +// @version 1.9.37.126 // @description Perpetual pages - powerful auto-pager script. Auto fetching next paginated web pages and inserting into current page for infinite scroll. Support thousands of web sites without any rule. // @description:zh-CN 终极自动翻页 - 加载并拼接下一分页内容至当前页尾,智能适配任意网页 // @description:zh-TW 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -11419,14 +11419,6 @@ let action = e.data.action; let detail = e.data.detail; switch (action) { - case "config": - if (!detail || typeof detail !== 'object') return; - rulesData = { - ...rulesData, - ...detail - } - storage.setItem("rulesData", rulesData); - break; case "nextPage": if (detail === "" || detail === null) return; detail = parseInt(detail) || 0; From bfaf086c77d77203714460445373a8d6b827e460 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 29 Dec 2025 20:52:10 +0900 Subject: [PATCH 02/68] sideControllerPos --- Pagetual/README.md | 2 +- Pagetual/pagetual.user.js | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index b41b3d04643..c58d65bd3fc 100644 --- a/Pagetual/README.md +++ b/Pagetual/README.md @@ -1,4 +1,4 @@ -[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.125](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.127](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") == *Pagetual - Perpetual pages. Auto loading paginated web pages for 90% of all web sites !* diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index d406513e3b3..ddc0c1d1acf 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -31,7 +31,7 @@ // @name:da Pagetual // @name:fr-CA Pagetual // @namespace hoothin -// @version 1.9.37.126 +// @version 1.9.37.127 // @description Perpetual pages - powerful auto-pager script. Auto fetching next paginated web pages and inserting into current page for infinite scroll. Support thousands of web sites without any rule. // @description:zh-CN 终极自动翻页 - 加载并拼接下一分页内容至当前页尾,智能适配任意网页 // @description:zh-TW 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -153,7 +153,7 @@ } const noRuleTest = false; - const lang = navigator.appName === "Netscape" ? navigator.language : navigator.userLanguage; + var langName = navigator.appName === "Netscape" ? navigator.language : navigator.userLanguage; const langData = [ { // English translation update by github.com/https433, admin@abby0666.xyz. @@ -4135,6 +4135,7 @@ }); var i18nData = langData[0].lang; function setLang(la) { + langName = la; for (let i = 0; i < langData.length; i++) { let lang = langData[i]; if (lang && lang.match.indexOf(la) !== -1) { @@ -4148,7 +4149,7 @@ } } } - setLang(lang); + setLang(langName); var enableDebug = true; var _GM_xmlhttpRequest, _GM_registerMenuCommand, _GM_notification, _GM_addStyle, _GM_openInTab, _GM_info, _GM_setClipboard; function i18n(name, param) { @@ -4314,7 +4315,8 @@ }); } const isMobile = ('ontouchstart' in document.documentElement && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)); - const configPage = [`https://pagetual.hoothin.com/${lang === 'zh-CN' ? 'cn/' : ''}rule.html`, + const cnConfigPage = "https://pagetual.hoothin.com/cn/rule.html"; + const configPage = ["https://pagetual.hoothin.com/rule.html", "https://github.com/hoothin/UserScripts/tree/master/Pagetual", "https://hoothin.github.io/UserScripts/Pagetual/"]; const firstRunPage = "https://pagetual.hoothin.com/firstRun"; @@ -7969,7 +7971,7 @@ e.stopPropagation(); }, true); - let initX, initY, moving = false; + let initX, initY, perX, perY, moving = false; let removeTimer; move.addEventListener("click", e => { if (!moving) { @@ -8013,10 +8015,10 @@ if (moving) { let windowHeight = window.innerHeight || document.documentElement.clientHeight; let windowWidth = window.innerWidth || document.documentElement.clientWidth; - initX = (clientX(e) - 10 + 40) / windowWidth * 100; - initY = (clientY(e) - 83 + 83) / windowHeight * 100; - this.frame.style.top = `calc(${initY}% - 83px)`; - this.frame.style.left = `calc(${initX}% - 40px)`; + perX = (clientX(e) - 10 + 40) / windowWidth * 100; + perY = (clientY(e) - 83 + 83) / windowHeight * 100; + this.frame.style.top = `calc(${perY}% - 83px)`; + this.frame.style.left = `calc(${perX}% - 40px)`; } else if (Math.abs(clientX(e) - initX) + Math.abs(clientY(e) - initY) > 5) { moving = true; clearTimeout(removeTimer); @@ -8028,8 +8030,8 @@ document.removeEventListener("mouseup", mouseUpHandler, true); document.removeEventListener("touchmove", mouseMoveHandler, true); document.removeEventListener("touchend", mouseUpHandler, true); - if (moving) { - rulesData.sideControllerPos = {x: parseInt(initX), y: parseInt(initY)}; + if (moving && perX && perY && perX > 0 && perX < 100 && perY > 0 && perY < 100) { + rulesData.sideControllerPos = {x: parseInt(perX), y: parseInt(perY)}; storage.setItem("rulesData", rulesData); } }; @@ -9565,9 +9567,9 @@ let rulesExample = document.querySelector("#user-content-rules-example+a,#rules-example>a"); if (rulesExample) { rulesExample.innerText = i18n("rulesExample"); - if (lang == "zh-CN") { + if (langName == "zh-CN") { rulesExample.href = rulesExample.href.replace("en", "cn"); - } else if (lang == "zh-TW" || lang == "zh-HK") { + } else if (langName == "zh-TW" || langName == "zh-HK") { rulesExample.href = rulesExample.href.replace("/en", ""); } } @@ -10463,6 +10465,9 @@ } if (rulesData.lang) { setLang(rulesData.lang); + if (langName === 'zh-CN') { + configPage.unshift(cnConfigPage); + } } if (rulesData.firstRun && storage.supportCrossSave()) { rulesData.firstRun = false; From 0f2a15d7f58ebae861091894d32226f41b7a7127 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 1 Jan 2026 10:36:34 +0900 Subject: [PATCH 03/68] =?UTF-8?q?=E5=B9=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Switch Traditional Chinese and Simplified Chinese.user.js | 2 +- .../lib/package.json | 2 +- .../lib/stcasc.lib.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js b/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js index e2228c3ba3d..086c092c202 100644 --- a/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js +++ b/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js @@ -385,7 +385,7 @@ '干':[ '幹', ['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'], - ['干','干系','天干','干涉','干扰','干戈','相干'] + ['干','干系','天干','干涉','干扰','干戈','相干','不干','干你什么','干你事','干你的事','干他什么','干他事','干他的事','干她什么','干她事','干她的事'] ], '了':[ '了', diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 415ffcc676a..94f23191a74 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json +++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json @@ -1,6 +1,6 @@ { "name": "switch-chinese", - "version": "1.0.13", + "version": "1.0.14", "description": "繁簡轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", "types": "stcasc.d.ts", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js index cfe8e268376..135cc540db3 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -329,7 +329,7 @@ const sc2tc = { '干':[ '幹', ['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'], - ['干','干系','天干','干涉','干扰','干戈','相干'] + ['干','干系','天干','干涉','干扰','干戈','相干','干你什么','干你事','干你的事','干他什么','干他事','干他的事','干她什么','干她事','干她的事'] ], '了':[ '了', From 9ba001343e1f6bf7e7e28465456225bfdb26001d Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 1 Jan 2026 10:43:23 +0900 Subject: [PATCH 04/68] fix --- .../Switch Traditional Chinese and Simplified Chinese.user.js | 2 +- .../lib/package.json | 2 +- Switch Traditional Chinese and Simplified Chinese/lib/readme.md | 2 ++ .../lib/stcasc.lib.js | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js b/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js index 086c092c202..6ee429ba0f1 100644 --- a/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js +++ b/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js @@ -385,7 +385,7 @@ '干':[ '幹', ['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'], - ['干','干系','天干','干涉','干扰','干戈','相干','不干','干你什么','干你事','干你的事','干他什么','干他事','干他的事','干她什么','干她事','干她的事'] + ['干','干系','天干','干涉','干扰','干戈','相干','不干','干我什麽','干我事','干我的事','干你什麽','干你事','干你的事','干他什麽','干他事','干他的事','干她什麽','干她事','干她的事'] ], '了':[ '了', diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 94f23191a74..18fda07895d 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json +++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json @@ -1,6 +1,6 @@ { "name": "switch-chinese", - "version": "1.0.14", + "version": "1.0.15", "description": "繁簡轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", "types": "stcasc.d.ts", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index 895ac2dc06a..39d5ed194ec 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -1,5 +1,7 @@ # switch-chinese +[Demo](https://tool.hoothin.com/chinese-converter) + [简体中文](#简体中文) | [繁體中文](#繁體中文) | [English](#english) --- diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js index 135cc540db3..fc76ef6c8b1 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -329,7 +329,7 @@ const sc2tc = { '干':[ '幹', ['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'], - ['干','干系','天干','干涉','干扰','干戈','相干','干你什么','干你事','干你的事','干他什么','干他事','干他的事','干她什么','干她事','干她的事'] + ['干','干系','天干','干涉','干扰','干戈','相干','干我什麽','干我事','干我的事','干你什麽','干你事','干你的事','干他什麽','干他事','干他的事','干她什麽','干她事','干她的事'] ], '了':[ '了', From 2b928ed6978db6ccbe3e2707e69da75b5ec66c25 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 1 Jan 2026 12:41:21 +0900 Subject: [PATCH 05/68] fix --- ...nal Chinese and Simplified Chinese.user.js | 18 ++++++++++++----- .../lib/package.json | 2 +- .../lib/readme.md | 2 +- .../lib/stcasc.lib.js | 20 +++++++++++++------ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js b/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js index 6ee429ba0f1..38bafb561f1 100644 --- a/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js +++ b/Switch Traditional Chinese and Simplified Chinese/Switch Traditional Chinese and Simplified Chinese.user.js @@ -385,7 +385,7 @@ '干':[ '幹', ['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'], - ['干','干系','天干','干涉','干扰','干戈','相干','不干','干我什麽','干我事','干我的事','干你什麽','干你事','干你的事','干他什麽','干他事','干他的事','干她什麽','干她事','干她的事'] + ['干','干系','天干','干涉','干扰','干戈','相干','不干','干我什','干我事','干我的事','干你什','干你事','干你的事','干他什','干他事','干他的事','干她什','干她事','干她的事'] ], '了':[ '了', @@ -867,11 +867,15 @@ var curOther=others[k],fadd=curOther.indexOf(char),badd=curOther.length-1-fadd,x=0; var processChar=char; while(fadd-->0){ - if(char_f[x])processChar=char_f[x]+processChar; + if (!char_f[x]) break; + processChar=char_f[x]+processChar; + x++; } x=0; while(badd-->0){ - if(char_b[x])processChar+=char_b[x]; + if (!char_b[x]) break; + processChar+=char_b[x]; + x++; } if(processChar.indexOf(curOther) != -1){ newChar=otherChar; @@ -940,11 +944,15 @@ var curOther=others[k],fadd=curOther.indexOf(char),badd=curOther.length-1-fadd,x=0; var processChar=char; while(fadd-->0){ - if(char_f[x])processChar=char_f[x]+processChar; + if (!char_f[x]) break; + processChar=char_f[x]+processChar; + x++; } x=0; while(badd-->0){ - if(char_b[x])processChar+=char_b[x]; + if (!char_b[x]) break; + processChar+=char_b[x]; + x++; } if(processChar.indexOf(curOther) != -1){ newChar=otherChar; diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 18fda07895d..489310114ed 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json +++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json @@ -1,6 +1,6 @@ { "name": "switch-chinese", - "version": "1.0.15", + "version": "1.0.16", "description": "繁簡轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", "types": "stcasc.d.ts", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index 39d5ed194ec..670521a96db 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -1,6 +1,6 @@ # switch-chinese -[Demo](https://tool.hoothin.com/chinese-converter) +[Online Demo](https://tool.hoothin.com/chinese-converter) [简体中文](#简体中文) | [繁體中文](#繁體中文) | [English](#english) diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js index fc76ef6c8b1..2256dc7222a 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -329,7 +329,7 @@ const sc2tc = { '干':[ '幹', ['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'], - ['干','干系','天干','干涉','干扰','干戈','相干','干我什麽','干我事','干我的事','干你什麽','干你事','干你的事','干他什麽','干他事','干他的事','干她什麽','干她事','干她的事'] + ['干','干系','天干','干涉','干扰','干戈','相干','干我什','干我事','干我的事','干你什','干你事','干你的事','干他什','干他事','干他的事','干她什','干她事','干她的事'] ], '了':[ '了', @@ -677,11 +677,15 @@ function traditionalizedString(orgStr, format) { var curOther = others[k], fadd = curOther.indexOf(char), badd = curOther.length - 1 - fadd, x = 0; var processChar = char; while (fadd-- > 0) { - if (char_f[x]) processChar = char_f[x] + processChar; + if (!char_f[x]) break; + processChar = char_f[x] + processChar; + x++; } x = 0; while (badd-- > 0) { - if (char_b[x]) processChar += char_b[x]; + if (!char_b[x]) break; + processChar += char_b[x]; + x++; } if (processChar.indexOf(curOther) != -1) { newChar = otherChar; @@ -800,11 +804,15 @@ function simplizedString(orgStr, format) { var curOther = others[k], fadd = curOther.indexOf(char), badd = curOther.length - 1 - fadd, x = 0; var processChar = char; while (fadd-- > 0) { - if (char_f[x]) processChar = char_f[x] + processChar; + if (!char_f[x]) break; + processChar = char_f[x] + processChar; + x++; } x = 0; while (badd-- > 0) { - if (char_b[x]) processChar += char_b[x]; + if (!char_b[x]) break; + processChar += char_b[x]; + x++; } if (processChar.indexOf(curOther) != -1) { newChar = otherChar; @@ -977,4 +985,4 @@ function stcasc(cache, custom, disableTerms) { return {simplized, traditionalized, detect, cache}; } -export default stcasc; \ No newline at end of file +export default stcasc; From 5fb569f412a06949bf31f1fea80ba8f42b920f01 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 5 Jan 2026 13:22:41 +0900 Subject: [PATCH 06/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 56f4021ccb6..e1eb2944b58 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -15,7 +15,7 @@ // @version 2025.12.19.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts -// @homepage https://github.com/hoothin/UserScripts/tree/master/Picviewer%20CE%2B +// @homepage https://pv.hoothin.com/ // @supportURL https://github.com/hoothin/UserScripts/issues // @connect www.google.com // @connect www.google.com.hk @@ -26450,7 +26450,7 @@ ImgOps | https://imgops.com/#b#`; GM_config.init({ id: 'pv-prefs', title: GM_config.create('a', { - href: 'https://hoothin.github.io/UserScripts/Picviewer%20CE%2B', + href: 'https://pv.hoothin.com/', target: '_blank', textContent: 'Picviewer CE+ '+i18n("config"), title: i18n("openHomePage") From ad6e08ca418246df5843a7663c0ae20dc2463f66 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Mon, 5 Jan 2026 04:22:57 +0000 Subject: [PATCH 07/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 8ae3445d5de..28cb754cfda 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -15,7 +15,7 @@ // @version 2025.12.19.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts -// @homepage https://github.com/hoothin/UserScripts/tree/master/Picviewer%20CE%2B +// @homepage https://pv.hoothin.com/ // @supportURL https://github.com/hoothin/UserScripts/issues // @connect www.google.com // @connect www.google.com.hk @@ -26450,7 +26450,7 @@ ImgOps | https://imgops.com/#b#`; GM_config.init({ id: 'pv-prefs', title: GM_config.create('a', { - href: 'https://hoothin.github.io/UserScripts/Picviewer%20CE%2B', + href: 'https://pv.hoothin.com/', target: '_blank', textContent: 'Picviewer CE+ '+i18n("config"), title: i18n("openHomePage") From 4f7c6820bfa49a75504a2628038d3e27f2369b6a Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 5 Jan 2026 21:42:07 +0900 Subject: [PATCH 08/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 248 +++++++++++++++++++++++++++- 1 file changed, 242 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index e1eb2944b58..a72abbdb16c 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2025.12.19.1 +// @version 2026.1.5.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -12055,6 +12055,186 @@ ImgOps | https://imgops.com/#b#`; } } else GM_fetch = fetch; + class ImageSizeFetcher { + constructor(gmXhr, config = {}) { + if (!gmXhr) throw new Error("GM_xmlhttpRequest is required"); + this.gmXhr = gmXhr; + this.cache = new Map(); + this.queue = []; + this.activeCount = 0; + this.concurrency = config.concurrency || 3; + this.timeout = config.timeout || 5000; + } + + async getSize(url, options = {}) { + const { force = false, strategy = 'auto' } = options; + if (!url) return null; + + if (!force && this.cache.has(url)) { + return this.cache.get(url); + } + + if (url.startsWith('data:')) { + const size = this._calculateBase64Size(url); + this.cache.set(url, size); + return size; + } + + if (url.startsWith('blob:')) { + return this._getBlobSize(url); + } + + if (strategy === 'local-only' || strategy === 'auto') { + const perfSize = this._getFromPerformance(url); + if (perfSize !== null) { + this.cache.set(url, perfSize); + return perfSize; + } + if (strategy === 'local-only') return null; + } + + return this._scheduleRequest(url); + } + + _calculateBase64Size(dataUrl) { + const base64Str = dataUrl.split(',')[1]; + if (!base64Str) return 0; + const len = base64Str.length; + const padding = (base64Str.match(/=/g) || []).length; + return (len * 3 / 4) - padding; + } + + async _getBlobSize(blobUrl) { + try { + const response = await fetch(blobUrl); + const blob = await response.blob(); + return blob.size; + } catch (e) { + console.error('Blob fetch failed', e); + return null; + } + } + + _getFromPerformance(url) { + const entries = performance.getEntriesByName(url); + if (entries.length > 0) { + const entry = entries[entries.length - 1]; + if (entry.decodedBodySize > 0) return entry.decodedBodySize; + if (entry.encodedBodySize > 0) return entry.encodedBodySize; + } + return null; + } + + _scheduleRequest(url) { + return new Promise((resolve, reject) => { + this.queue.push({ url, resolve, reject }); + this._processQueue(); + }); + } + + _processQueue() { + if (this.activeCount >= this.concurrency || this.queue.length === 0) { + return; + } + + this.activeCount++; + const { url, resolve, reject } = this.queue.shift(); + + this._fetchByHead(url) + .then(size => { + if (size !== null) this.cache.set(url, size); + resolve(size); + }) + .catch(err => { + console.warn(`Fetch size failed for ${url}`, err); + resolve(null); + }) + .finally(() => { + this.activeCount--; + this._processQueue(); + }); + } + + _fetchByHead(url) { + return new Promise((resolve, reject) => { + this.gmXhr({ + method: "HEAD", + url: url, + timeout: this.timeout, + onload: (response) => { + const lenStr = response.responseHeaders.match(/content-length:\s*(\d+)/i); + if (lenStr && lenStr[1]) { + resolve(parseInt(lenStr[1], 10)); + } else { + resolve(null); + } + }, + onerror: (err) => reject(err), + ontimeout: () => reject(new Error("Timeout")) + }); + }); + } + } + + function formatBytes(bytes) { + if (bytes === null || typeof bytes === "undefined" || isNaN(bytes) || bytes < 0) return ""; + const units = ["B", "KB", "MB", "GB", "TB"]; + let size = bytes; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + const precision = size >= 100 ? 0 : (size >= 10 ? 1 : 2); + const value = size.toFixed(precision).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1"); + return value + " " + units[unitIndex]; + } + + function updateGalleryHeaderSize(gallery, img, strategy) { + if (!gallery || !gallery.eleMaps || !gallery.eleMaps['head-left-img-info-size']) return; + const sizeSpan = gallery.eleMaps['head-left-img-info-size']; + sizeSpan.textContent = ""; + sizeSpan.title = ""; + if (!img || img.nodeName !== 'IMG') return; + const sizeUrl = img.src; + if (!sizeUrl) return; + imageSizeFetcher.getSize(sizeUrl, { strategy: strategy || 'auto' }).then(size => { + if (size === null) return; + if (gallery.img !== img || img.src !== sizeUrl) return; + if (img.naturalWidth && img.naturalHeight) { + gallery.eleMaps['head-left-img-info-resolution'].textContent = img.naturalWidth + " x " + img.naturalHeight; + } + const sizeText = formatBytes(size); + sizeSpan.textContent = "(" + sizeText + ")"; + sizeSpan.title = sizeText; + }); + } + + function updateViewmoreSizeLabel(labelNode, baseText, url, strategy, baseTextGetter) { + if (!labelNode || !url) return; + const initText = typeof baseTextGetter === "function" ? baseTextGetter() : baseText; + labelNode.textContent = initText; + imageSizeFetcher.getSize(url, { strategy: strategy || 'local-only' }).then(size => { + if (size === null) return; + const sizeText = formatBytes(size); + const currentBase = typeof baseTextGetter === "function" ? baseTextGetter() : baseText; + labelNode.textContent = currentBase + " | " + sizeText; + }); + } + + var viewmoreAutoSizeThrottle = { last: 0 }; + function scheduleViewmoreAutoSize(labelNode, baseText, url, baseTextGetter) { + if (!labelNode || !url) return; + var now = Date.now(); + var delay = Math.max(0, 800 - (now - viewmoreAutoSizeThrottle.last)); + setTimeout(() => { + viewmoreAutoSizeThrottle.last = Date.now(); + updateViewmoreSizeLabel(labelNode, baseText, url, 'auto', baseTextGetter); + }, delay); + } + + var imageSizeFetcher = new ImageSizeFetcher(_GM_xmlhttpRequest, { concurrency: 3, timeout: 5000 }); + var canvas = document.createElement('CANVAS'); if (document.body) { if (canvas.style) canvas.style.display = "none"; @@ -13939,6 +14119,7 @@ ImgOps | https://imgops.com/#b#`; ''+ ''+ '0 x 0'+ + ''+ '(1 / 1)'+ '(100%)'+ ''+ @@ -14325,6 +14506,7 @@ ImgOps | https://imgops.com/#b#`; 'head-left-img-info', 'head-left-img-info-description', 'head-left-img-info-resolution', + 'head-left-img-info-size', 'head-left-img-info-count', 'head-left-img-info-scaling', @@ -16289,8 +16471,28 @@ ImgOps | https://imgops.com/#b#`; dlSpan.onclick=clickCb; var topP=document.createElement('p'); topP.className="pv-top-banner"; - topP.innerHTML=createHTML(img.naturalWidth+' x '+img.naturalHeight); + var baseTextGetter = () => (img.naturalWidth + ' x ' + img.naturalHeight); + var baseText = baseTextGetter(); + topP.textContent = baseText; + updateViewmoreSizeLabel(topP, baseText, curNode.dataset.src, 'local-only', baseTextGetter); topP.title=dlSpan.title; + let hoverTimer = null; + let autoFetched = false; + let onEnter = () => { + if (autoFetched || topP.textContent.indexOf("|") !== -1) return; + hoverTimer = setTimeout(() => { + autoFetched = true; + scheduleViewmoreAutoSize(topP, baseText, curNode.dataset.src, baseTextGetter); + }, 1000); + }; + let onLeave = () => { + if (hoverTimer) { + clearTimeout(hoverTimer); + hoverTimer = null; + } + }; + imgSpan.addEventListener("mouseenter", onEnter, true); + imgSpan.addEventListener("mouseleave", onLeave, true); var checkBox=document.createElement('input'); checkBox.type="checkbox"; let self=this; @@ -17000,6 +17202,7 @@ ImgOps | https://imgops.com/#b#`; this.imgNaturalSize=imgNaturalSize; this.eleMaps['head-left-img-info-resolution'].textContent=imgNaturalSize.w + ' x ' + imgNaturalSize.h; + updateGalleryHeaderSize(this, img, 'auto'); var thumbnails=this.eleMaps['sidebar-thumbnails-container'].childNodes,i=0; thumbnails=Array.prototype.slice.call(thumbnails).filter(function(thumbnail){ if(thumbnail.style.display=="none"){ @@ -17037,6 +17240,7 @@ ImgOps | https://imgops.com/#b#`; this.imgError=true; this.img.style.display='none'; this.eleMaps['img_broken'].style.display='inline-block'; + this.eleMaps['head-left-img-info-size'].textContent=''; dataset(relatedThumb,'naturalSize',JSON.stringify({w: 0, h: 0})); }else{ var srcs=dataset(relatedThumb, 'srcs'); @@ -17049,6 +17253,7 @@ ImgOps | https://imgops.com/#b#`; self.imgNaturalSize=imgNaturalSize; self.eleMaps['head-left-img-info-resolution'].textContent=imgNaturalSize.w + ' x ' + imgNaturalSize.h; + updateGalleryHeaderSize(self, this, 'auto'); dataset(relatedThumb,'naturalSize',JSON.stringify(imgNaturalSize)); let key = imgNaturalSize.w + "x" + imgNaturalSize.h; self.sizeMap[key] = (self.sizeMap[key] || 0) + 1; @@ -17584,6 +17789,7 @@ ImgOps | https://imgops.com/#b#`; //清空dom this.eleMaps['sidebar-thumbnails-container'].innerHTML=createHTML(''); this.eleMaps['head-left-img-info-resolution'].textContent='0 x 0'; + this.eleMaps['head-left-img-info-size'].textContent=''; this.eleMaps['head-left-img-info-count'].textContent='(1 / 1)'; this.eleMaps['head-left-img-info-scaling'].textContent='(100%)'; //隐藏滚动条 @@ -18890,6 +19096,10 @@ ImgOps | https://imgops.com/#b#`; .pv-gallery-head-left-img-info{\ cursor:help;\ }\ + .pv-gallery-head-left-img-info-size {\ + margin-left: 6px;\ + margin-right: 6px;\ + }\ .pv-gallery-head-left-img-info-description {\ margin-left: 10px;\ margin-right: 10px;\ @@ -20624,6 +20834,8 @@ ImgOps | https://imgops.com/#b#`; this.initPos = initPos || false; this.preview = !!preview; this.isImg = this.img.nodeName.toUpperCase() == 'IMG'; + this.imgSizeBytes = null; + this.imgSizeText = ""; this.init(); if (data && this.isImg) { this.img.src = location.protocol == "https" ? data.src.replace(/^http:/,"https:") : data.src; @@ -20838,6 +21050,8 @@ ImgOps | https://imgops.com/#b#`; img.onload = function(e) { if (self.removed) return; self.loaded = true; + self.imgSizeBytes = null; + self.imgSizeText = ""; container.style.background=''; if (self.preview && img.naturalHeight == 1 && img.naturalWidth == 1) { self.remove(); @@ -20849,6 +21063,7 @@ ImgOps | https://imgops.com/#b#`; w:img.naturalWidth, }; self.setToolBadge('zoom',self.zoomLevel); + self.fetchImgSize(self.img.src, 'auto'); if (self==uniqueImgWin && prefs.floatBar.globalkeys.previewFollowMouse) { self.followPos(uniqueImgWinInitX, uniqueImgWinInitY); } else { @@ -20884,7 +21099,8 @@ ImgOps | https://imgops.com/#b#`; } if (imgNaturalSize.h && imgNaturalSize.w) { container.style.background=''; - setSearchState(`${img.naturalWidth} x ${img.naturalHeight}`, self.imgState); + self.updateImgState(1); + self.fetchImgSize(self.img.src, 'auto'); } if (!this.isImg) { if (/^video$/i.test(img.nodeName)) { @@ -22775,14 +22991,34 @@ ImgOps | https://imgops.com/#b#`; document.addEventListener('mousemove',moveHandler,true); document.addEventListener('mouseup',mouseupHandler,true); }, + updateImgState:function(zoomValue){ + if (!this.imgState || !this.img || !this.img.naturalWidth) return; + var sizeText = this.imgSizeText ? " (" + this.imgSizeText + ")" : ""; + var zoomText = (typeof zoomValue === "number" && zoomValue !== 1) ? " (" + parseInt(zoomValue * 100) + "%)" : ""; + var indexText = (this.curIndex >= 0 && this.data && this.data.all) ? " [" + (this.curIndex + 1) + "/" + this.data.all.length + "]" : ""; + setSearchState("" + this.img.naturalWidth + " x " + this.img.naturalHeight + "" + sizeText + zoomText + indexText, this.imgState); + this.descriptionSpan && this.imgState.appendChild(this.descriptionSpan); + }, + fetchImgSize:function(url, strategy){ + if (!this.isImg) return; + var sizeUrl = url || (this.img ? this.img.src : ""); + if (!sizeUrl) return; + var self = this; + imageSizeFetcher.getSize(sizeUrl, { strategy: strategy || 'auto' }).then(size => { + if (size === null) return; + if (!self.img || self.img.src !== sizeUrl) return; + self.imgSizeBytes = size; + self.imgSizeText = formatBytes(size); + self.updateImgState(self.zoomLevel || 1); + }); + }, setToolBadge:function(tool,content){ var scale=0; switch(tool){ case 'zoom':{ scale=2; if (this.img.naturalWidth) { - setSearchState(`${this.img.naturalWidth} x ${this.img.naturalHeight}` + (content !== 1 ? ` (${parseInt(content * 100)}%)` : "") + (this.curIndex >=0 ? ` [${this.curIndex + 1}/${this.data.all.length}]` : ""), this.imgState); - this.descriptionSpan && this.imgState.appendChild(this.descriptionSpan); + this.updateImgState(content); } }break; case 'rotate':{ @@ -27778,4 +28014,4 @@ ImgOps | https://imgops.com/#b#`; init2(); } -})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow)); \ No newline at end of file +})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow)); From 296751100dc26af05a6c016942f252d64989c1f3 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Mon, 5 Jan 2026 12:42:28 +0000 Subject: [PATCH 09/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 248 ++++++++++++++++++++++++++++++++++++- 1 file changed, 242 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 28cb754cfda..207a6a3ddf1 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2025.12.19.1 +// @version 2026.1.5.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -12055,6 +12055,186 @@ ImgOps | https://imgops.com/#b#`; } } else GM_fetch = fetch; + class ImageSizeFetcher { + constructor(gmXhr, config = {}) { + if (!gmXhr) throw new Error("GM_xmlhttpRequest is required"); + this.gmXhr = gmXhr; + this.cache = new Map(); + this.queue = []; + this.activeCount = 0; + this.concurrency = config.concurrency || 3; + this.timeout = config.timeout || 5000; + } + + async getSize(url, options = {}) { + const { force = false, strategy = 'auto' } = options; + if (!url) return null; + + if (!force && this.cache.has(url)) { + return this.cache.get(url); + } + + if (url.startsWith('data:')) { + const size = this._calculateBase64Size(url); + this.cache.set(url, size); + return size; + } + + if (url.startsWith('blob:')) { + return this._getBlobSize(url); + } + + if (strategy === 'local-only' || strategy === 'auto') { + const perfSize = this._getFromPerformance(url); + if (perfSize !== null) { + this.cache.set(url, perfSize); + return perfSize; + } + if (strategy === 'local-only') return null; + } + + return this._scheduleRequest(url); + } + + _calculateBase64Size(dataUrl) { + const base64Str = dataUrl.split(',')[1]; + if (!base64Str) return 0; + const len = base64Str.length; + const padding = (base64Str.match(/=/g) || []).length; + return (len * 3 / 4) - padding; + } + + async _getBlobSize(blobUrl) { + try { + const response = await fetch(blobUrl); + const blob = await response.blob(); + return blob.size; + } catch (e) { + console.error('Blob fetch failed', e); + return null; + } + } + + _getFromPerformance(url) { + const entries = performance.getEntriesByName(url); + if (entries.length > 0) { + const entry = entries[entries.length - 1]; + if (entry.decodedBodySize > 0) return entry.decodedBodySize; + if (entry.encodedBodySize > 0) return entry.encodedBodySize; + } + return null; + } + + _scheduleRequest(url) { + return new Promise((resolve, reject) => { + this.queue.push({ url, resolve, reject }); + this._processQueue(); + }); + } + + _processQueue() { + if (this.activeCount >= this.concurrency || this.queue.length === 0) { + return; + } + + this.activeCount++; + const { url, resolve, reject } = this.queue.shift(); + + this._fetchByHead(url) + .then(size => { + if (size !== null) this.cache.set(url, size); + resolve(size); + }) + .catch(err => { + console.warn(`Fetch size failed for ${url}`, err); + resolve(null); + }) + .finally(() => { + this.activeCount--; + this._processQueue(); + }); + } + + _fetchByHead(url) { + return new Promise((resolve, reject) => { + this.gmXhr({ + method: "HEAD", + url: url, + timeout: this.timeout, + onload: (response) => { + const lenStr = response.responseHeaders.match(/content-length:\s*(\d+)/i); + if (lenStr && lenStr[1]) { + resolve(parseInt(lenStr[1], 10)); + } else { + resolve(null); + } + }, + onerror: (err) => reject(err), + ontimeout: () => reject(new Error("Timeout")) + }); + }); + } + } + + function formatBytes(bytes) { + if (bytes === null || typeof bytes === "undefined" || isNaN(bytes) || bytes < 0) return ""; + const units = ["B", "KB", "MB", "GB", "TB"]; + let size = bytes; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + const precision = size >= 100 ? 0 : (size >= 10 ? 1 : 2); + const value = size.toFixed(precision).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1"); + return value + " " + units[unitIndex]; + } + + function updateGalleryHeaderSize(gallery, img, strategy) { + if (!gallery || !gallery.eleMaps || !gallery.eleMaps['head-left-img-info-size']) return; + const sizeSpan = gallery.eleMaps['head-left-img-info-size']; + sizeSpan.textContent = ""; + sizeSpan.title = ""; + if (!img || img.nodeName !== 'IMG') return; + const sizeUrl = img.src; + if (!sizeUrl) return; + imageSizeFetcher.getSize(sizeUrl, { strategy: strategy || 'auto' }).then(size => { + if (size === null) return; + if (gallery.img !== img || img.src !== sizeUrl) return; + if (img.naturalWidth && img.naturalHeight) { + gallery.eleMaps['head-left-img-info-resolution'].textContent = img.naturalWidth + " x " + img.naturalHeight; + } + const sizeText = formatBytes(size); + sizeSpan.textContent = "(" + sizeText + ")"; + sizeSpan.title = sizeText; + }); + } + + function updateViewmoreSizeLabel(labelNode, baseText, url, strategy, baseTextGetter) { + if (!labelNode || !url) return; + const initText = typeof baseTextGetter === "function" ? baseTextGetter() : baseText; + labelNode.textContent = initText; + imageSizeFetcher.getSize(url, { strategy: strategy || 'local-only' }).then(size => { + if (size === null) return; + const sizeText = formatBytes(size); + const currentBase = typeof baseTextGetter === "function" ? baseTextGetter() : baseText; + labelNode.textContent = currentBase + " | " + sizeText; + }); + } + + var viewmoreAutoSizeThrottle = { last: 0 }; + function scheduleViewmoreAutoSize(labelNode, baseText, url, baseTextGetter) { + if (!labelNode || !url) return; + var now = Date.now(); + var delay = Math.max(0, 800 - (now - viewmoreAutoSizeThrottle.last)); + setTimeout(() => { + viewmoreAutoSizeThrottle.last = Date.now(); + updateViewmoreSizeLabel(labelNode, baseText, url, 'auto', baseTextGetter); + }, delay); + } + + var imageSizeFetcher = new ImageSizeFetcher(_GM_xmlhttpRequest, { concurrency: 3, timeout: 5000 }); + var canvas = document.createElement('CANVAS'); if (document.body) { if (canvas.style) canvas.style.display = "none"; @@ -13939,6 +14119,7 @@ ImgOps | https://imgops.com/#b#`; ''+ ''+ '0 x 0'+ + ''+ '(1 / 1)'+ '(100%)'+ ''+ @@ -14325,6 +14506,7 @@ ImgOps | https://imgops.com/#b#`; 'head-left-img-info', 'head-left-img-info-description', 'head-left-img-info-resolution', + 'head-left-img-info-size', 'head-left-img-info-count', 'head-left-img-info-scaling', @@ -16289,8 +16471,28 @@ ImgOps | https://imgops.com/#b#`; dlSpan.onclick=clickCb; var topP=document.createElement('p'); topP.className="pv-top-banner"; - topP.innerHTML=createHTML(img.naturalWidth+' x '+img.naturalHeight); + var baseTextGetter = () => (img.naturalWidth + ' x ' + img.naturalHeight); + var baseText = baseTextGetter(); + topP.textContent = baseText; + updateViewmoreSizeLabel(topP, baseText, curNode.dataset.src, 'local-only', baseTextGetter); topP.title=dlSpan.title; + let hoverTimer = null; + let autoFetched = false; + let onEnter = () => { + if (autoFetched || topP.textContent.indexOf("|") !== -1) return; + hoverTimer = setTimeout(() => { + autoFetched = true; + scheduleViewmoreAutoSize(topP, baseText, curNode.dataset.src, baseTextGetter); + }, 1000); + }; + let onLeave = () => { + if (hoverTimer) { + clearTimeout(hoverTimer); + hoverTimer = null; + } + }; + imgSpan.addEventListener("mouseenter", onEnter, true); + imgSpan.addEventListener("mouseleave", onLeave, true); var checkBox=document.createElement('input'); checkBox.type="checkbox"; let self=this; @@ -17000,6 +17202,7 @@ ImgOps | https://imgops.com/#b#`; this.imgNaturalSize=imgNaturalSize; this.eleMaps['head-left-img-info-resolution'].textContent=imgNaturalSize.w + ' x ' + imgNaturalSize.h; + updateGalleryHeaderSize(this, img, 'auto'); var thumbnails=this.eleMaps['sidebar-thumbnails-container'].childNodes,i=0; thumbnails=Array.prototype.slice.call(thumbnails).filter(function(thumbnail){ if(thumbnail.style.display=="none"){ @@ -17037,6 +17240,7 @@ ImgOps | https://imgops.com/#b#`; this.imgError=true; this.img.style.display='none'; this.eleMaps['img_broken'].style.display='inline-block'; + this.eleMaps['head-left-img-info-size'].textContent=''; dataset(relatedThumb,'naturalSize',JSON.stringify({w: 0, h: 0})); }else{ var srcs=dataset(relatedThumb, 'srcs'); @@ -17049,6 +17253,7 @@ ImgOps | https://imgops.com/#b#`; self.imgNaturalSize=imgNaturalSize; self.eleMaps['head-left-img-info-resolution'].textContent=imgNaturalSize.w + ' x ' + imgNaturalSize.h; + updateGalleryHeaderSize(self, this, 'auto'); dataset(relatedThumb,'naturalSize',JSON.stringify(imgNaturalSize)); let key = imgNaturalSize.w + "x" + imgNaturalSize.h; self.sizeMap[key] = (self.sizeMap[key] || 0) + 1; @@ -17584,6 +17789,7 @@ ImgOps | https://imgops.com/#b#`; //清空dom this.eleMaps['sidebar-thumbnails-container'].innerHTML=createHTML(''); this.eleMaps['head-left-img-info-resolution'].textContent='0 x 0'; + this.eleMaps['head-left-img-info-size'].textContent=''; this.eleMaps['head-left-img-info-count'].textContent='(1 / 1)'; this.eleMaps['head-left-img-info-scaling'].textContent='(100%)'; //隐藏滚动条 @@ -18890,6 +19096,10 @@ ImgOps | https://imgops.com/#b#`; .pv-gallery-head-left-img-info{\ cursor:help;\ }\ + .pv-gallery-head-left-img-info-size {\ + margin-left: 6px;\ + margin-right: 6px;\ + }\ .pv-gallery-head-left-img-info-description {\ margin-left: 10px;\ margin-right: 10px;\ @@ -20624,6 +20834,8 @@ ImgOps | https://imgops.com/#b#`; this.initPos = initPos || false; this.preview = !!preview; this.isImg = this.img.nodeName.toUpperCase() == 'IMG'; + this.imgSizeBytes = null; + this.imgSizeText = ""; this.init(); if (data && this.isImg) { this.img.src = location.protocol == "https" ? data.src.replace(/^http:/,"https:") : data.src; @@ -20838,6 +21050,8 @@ ImgOps | https://imgops.com/#b#`; img.onload = function(e) { if (self.removed) return; self.loaded = true; + self.imgSizeBytes = null; + self.imgSizeText = ""; container.style.background=''; if (self.preview && img.naturalHeight == 1 && img.naturalWidth == 1) { self.remove(); @@ -20849,6 +21063,7 @@ ImgOps | https://imgops.com/#b#`; w:img.naturalWidth, }; self.setToolBadge('zoom',self.zoomLevel); + self.fetchImgSize(self.img.src, 'auto'); if (self==uniqueImgWin && prefs.floatBar.globalkeys.previewFollowMouse) { self.followPos(uniqueImgWinInitX, uniqueImgWinInitY); } else { @@ -20884,7 +21099,8 @@ ImgOps | https://imgops.com/#b#`; } if (imgNaturalSize.h && imgNaturalSize.w) { container.style.background=''; - setSearchState(`${img.naturalWidth} x ${img.naturalHeight}`, self.imgState); + self.updateImgState(1); + self.fetchImgSize(self.img.src, 'auto'); } if (!this.isImg) { if (/^video$/i.test(img.nodeName)) { @@ -22775,14 +22991,34 @@ ImgOps | https://imgops.com/#b#`; document.addEventListener('mousemove',moveHandler,true); document.addEventListener('mouseup',mouseupHandler,true); }, + updateImgState:function(zoomValue){ + if (!this.imgState || !this.img || !this.img.naturalWidth) return; + var sizeText = this.imgSizeText ? " (" + this.imgSizeText + ")" : ""; + var zoomText = (typeof zoomValue === "number" && zoomValue !== 1) ? " (" + parseInt(zoomValue * 100) + "%)" : ""; + var indexText = (this.curIndex >= 0 && this.data && this.data.all) ? " [" + (this.curIndex + 1) + "/" + this.data.all.length + "]" : ""; + setSearchState("" + this.img.naturalWidth + " x " + this.img.naturalHeight + "" + sizeText + zoomText + indexText, this.imgState); + this.descriptionSpan && this.imgState.appendChild(this.descriptionSpan); + }, + fetchImgSize:function(url, strategy){ + if (!this.isImg) return; + var sizeUrl = url || (this.img ? this.img.src : ""); + if (!sizeUrl) return; + var self = this; + imageSizeFetcher.getSize(sizeUrl, { strategy: strategy || 'auto' }).then(size => { + if (size === null) return; + if (!self.img || self.img.src !== sizeUrl) return; + self.imgSizeBytes = size; + self.imgSizeText = formatBytes(size); + self.updateImgState(self.zoomLevel || 1); + }); + }, setToolBadge:function(tool,content){ var scale=0; switch(tool){ case 'zoom':{ scale=2; if (this.img.naturalWidth) { - setSearchState(`${this.img.naturalWidth} x ${this.img.naturalHeight}` + (content !== 1 ? ` (${parseInt(content * 100)}%)` : "") + (this.curIndex >=0 ? ` [${this.curIndex + 1}/${this.data.all.length}]` : ""), this.imgState); - this.descriptionSpan && this.imgState.appendChild(this.descriptionSpan); + this.updateImgState(content); } }break; case 'rotate':{ @@ -27778,4 +28014,4 @@ ImgOps | https://imgops.com/#b#`; init2(); } -})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow)); \ No newline at end of file +})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow)); From a9cbde28aa16e7983c91dbe8e82bd8042f22cad1 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 10 Jan 2026 11:25:32 +0900 Subject: [PATCH 10/68] 1.9.37.128 --- Pagetual/README.md | 2 +- Pagetual/pagetual.user.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index c58d65bd3fc..6077f2266b0 100644 --- a/Pagetual/README.md +++ b/Pagetual/README.md @@ -1,4 +1,4 @@ -[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.127](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.128](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") == *Pagetual - Perpetual pages. Auto loading paginated web pages for 90% of all web sites !* diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index ddc0c1d1acf..c84d06d613c 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -31,7 +31,7 @@ // @name:da Pagetual // @name:fr-CA Pagetual // @namespace hoothin -// @version 1.9.37.127 +// @version 1.9.37.128 // @description Perpetual pages - powerful auto-pager script. Auto fetching next paginated web pages and inserting into current page for infinite scroll. Support thousands of web sites without any rule. // @description:zh-CN 终极自动翻页 - 加载并拼接下一分页内容至当前页尾,智能适配任意网页 // @description:zh-TW 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -137,6 +137,9 @@ if (document.body && getComputedStyle(document.body).display === 'none') { document.body.style.display = 'block'; } + Element.prototype.scrollIntoView = function() { + console.log('ScrollIntoView blocked.'); + }; return; } @@ -12648,12 +12651,12 @@ } let eles = ruleParser.getPageElement(iframeDoc, emuIframe.contentWindow, true), checkItem; if (eles && eles.length > 0) { - eles = [].filter.call(eles, ele => {return ele && !compareNodeName(ele, ["style", "script", "meta"])}); - if (compareNodeName(eles[0], ["ul"]) || eles.length == 1) checkItem = eles[0]; - else if (eles[0].parentNode == eles[1].parentNode) { - checkItem = eles[0].parentNode; + const filterEles = [].filter.call(eles, ele => {return ele && !compareNodeName(ele, ["style", "script", "meta"])}); + if (compareNodeName(filterEles[0], ["ul"]) || filterEles.length == 1) checkItem = filterEles[0]; + else if (filterEles[0].parentNode == filterEles[1].parentNode) { + checkItem = filterEles[0].parentNode; } else { - checkItem = eles[0]; + checkItem = filterEles[0]; } } if (!checkItem) { From a86577325dc95639a0c77e3dc956fa8782b562ce Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 13 Jan 2026 12:51:06 +0900 Subject: [PATCH 11/68] stitch --- Picviewer CE+/pvcep_lang.js | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Picviewer CE+/pvcep_lang.js b/Picviewer CE+/pvcep_lang.js index adf87fa2c25..113b1cfa052 100644 --- a/Picviewer CE+/pvcep_lang.js +++ b/Picviewer CE+/pvcep_lang.js @@ -29,6 +29,11 @@ const langData = [ closeBtn: "Cancel", invertBtn: "Invert", compareBtn: "Compare", + stitch: "Stitch", + stitchDone: "Done", + stitchCancel: "Cancel", + stitchHorizontal: "Horizontal", + stitchVertical: "Vertical", selectAllBtn: "Select All", closeBtnTips: "cancel this setting and restore all options", resetLink: "Restore default settings", @@ -297,6 +302,11 @@ const langData = [ closeBtn: "%D8%A5%D9%84%D8%BA%D8%A7%D8%A1", invertBtn: "%D8%B9%D9%83%D8%B3", compareBtn: "%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9", + stitch: "%D8%AF%D9%85%D8%AC", + stitchDone: "%D8%AA%D9%85", + stitchCancel: "%D8%A5%D9%84%D8%BA%D8%A7%D8%A1", + stitchHorizontal: "%D8%A3%D9%81%D9%82%D9%8A", + stitchVertical: "%D8%B9%D9%85%D9%88%D8%AF%D9%8A", selectAllBtn: "%D8%AA%D8%AD%D8%AF%D9%8A%D8%AF%20%D8%A7%D9%84%D9%83%D9%84", closeBtnTips: "%D8%A5%D9%84%D8%BA%D8%A7%D8%A1%20%D9%87%D8%B0%D8%A7%20%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%20%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D8%A7%D8%AF%D8%A9%20%D8%AC%D9%85%D9%8A%D8%B9%20%D8%A7%D9%84%D8%AE%D9%8A%D8%A7%D8%B1%D8%A7%D8%AA", resetLink: "%D8%A7%D8%B3%D8%AA%D8%B9%D8%A7%D8%AF%D8%A9%20%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA%20%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9", @@ -563,6 +573,11 @@ const langData = [ closeBtn: "取消", invertBtn: "反选", compareBtn: "对比", + stitch: "拼接", + stitchDone: "完成", + stitchCancel: "取消", + stitchHorizontal: "横排", + stitchVertical: "竖排", selectAllBtn: "全选", closeBtnTips: "取消本次设置,所有选项还原", resetLink: "恢复默认设置", @@ -829,6 +844,11 @@ const langData = [ closeBtn: "取消", invertBtn: "反選", compareBtn: "對比", + stitch: "拼接", + stitchDone: "完成", + stitchCancel: "取消", + stitchHorizontal: "橫排", + stitchVertical: "豎排", selectAllBtn: "全選", closeBtnTips: "取消本次設置,所有選項還原", resetLink: "恢復默認設置", @@ -1096,6 +1116,11 @@ const langData = [ closeBtn: "Sair", invertBtn: "Inverter", compareBtn: "Comparar", + stitch: "Montar", + stitchDone: "Concluir", + stitchCancel: "Cancelar", + stitchHorizontal: "Horizontal", + stitchVertical: "Vertical", selectAllBtn: "Selecionar tudo", closeBtnTips: "Sair sem aplicar as mudanças", resetLink: "Restaurar Padrões", @@ -1363,6 +1388,11 @@ const langData = [ closeBtn: "Cancelar", invertBtn: "Inverter", compareBtn: "Comparar", + stitch: "Montar", + stitchDone: "Concluir", + stitchCancel: "Cancelar", + stitchHorizontal: "Horizontal", + stitchVertical: "Vertical", selectAllBtn: "Selecione tudo", closeBtnTips: "Descarta as alterações e restaura as configurações anteriores", resetLink: "Restaurar definições originais", @@ -1630,6 +1660,11 @@ const langData = [ closeBtn: "Закрыть", invertBtn: "Откатить", compareBtn: "Сравнивать", + stitch: "Склеить", + stitchDone: "Готово", + stitchCancel: "Отмена", + stitchHorizontal: "Горизонтально", + stitchVertical: "Вертикально", selectAllBtn: "Выбрать все", closeBtnTips: "Отменяет эту настройку и восстанавливает все параметры", resetLink: "Восстановить по умолчанию", @@ -1897,6 +1932,11 @@ const langData = [ closeBtn: "İptal Et", invertBtn: "Tersine Çevir", compareBtn: "Kıyaslamak", + stitch: "Birleştir", + stitchDone: "Tamam", + stitchCancel: "İptal", + stitchHorizontal: "Yatay", + stitchVertical: "Dikey", selectAllBtn: "Hepsini seç", closeBtnTips: "bu ayarı iptal et ve tüm ayarları varsayılan haline döndür", resetLink: "Ayarları varsayılana döndür", @@ -2163,6 +2203,11 @@ const langData = [ closeBtn: "キャンセル", invertBtn: "反転", compareBtn: "比較", + stitch: "結合", + stitchDone: "完了", + stitchCancel: "キャンセル", + stitchHorizontal: "横", + stitchVertical: "縦", selectAllBtn: "全て選択", closeBtnTips: "設定をキャンセルし、すべてのオプションを復元します", resetLink: "初期設定に戻す", @@ -2430,6 +2475,11 @@ const langData = [ closeBtn: "Закрити", invertBtn: "Відновити", compareBtn: "Порівняти", + stitch: "З'єднати", + stitchDone: "Готово", + stitchCancel: "Скасувати", + stitchHorizontal: "Горизонтально", + stitchVertical: "Вертикально", selectAllBtn: "Вибрати все", closeBtnTips: "Скасовує ці налаштування та відновлює усі параметри", resetLink: "Скинути налаштування", From 960e78fa8c7e22fbe2fd73bd24d660b9c33e89fb Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 13 Jan 2026 13:28:55 +0900 Subject: [PATCH 12/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 659 +++++++++++++++++++++++++++- 1 file changed, 657 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index a72abbdb16c..f2e12a8cf84 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12784,7 +12784,7 @@ ImgOps | https://imgops.com/#b#`; // 默认设置,请到设置界面修改 prefs={ floatBar:{//浮动工具栏相关设置. - butonOrder:['actual','current','gallery','magnifier','download'],//按钮排列顺序'actual'(实际的图片),'current'(当前显示的图片),'magnifier'(放大镜观察),'gallery'(图集),'search'(搜索原图) + butonOrder:['actual','current','gallery','magnifier','download','stitch'],//按钮排列顺序'actual'(实际的图片),'current'(当前显示的图片),'magnifier'(放大镜观察),'gallery'(图集),'search'(搜索原图) additionalFeature: 'open', invertAdditionalFeature: false, listenBg:true,//监听背景图 @@ -12992,6 +12992,7 @@ ImgOps | https://imgops.com/#b#`; gallery:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAP1BMVEUAAAD///94eHgZGRn39/fz8/POzs6xsbGSkpJ9fX1UVFQpKSnb29vJycmmpqafn5+UlJSNjY0/Pz8hISENDQ2fWpEMAAAAcUlEQVQoz43SWQrEIBRE0Xe7M8/T/tcaNSKCKUh95nCDiIaYYa9zUDQRiiaCalANqkE0n6BuBLArWBTUAsa2/3yqfwYdzAl+GeCWAN9YM7nvo4chgoXmgjP8CdoEvqmA/oCQRHgat2p7YM2gvHb9GMRu7acCGLmlyNoAAAAASUVORK5CYII=', search:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAXVBMVEUAAAD///+MjIwmJibNzc2UlJTd3d2lpaUKCgrT09O/v78YGBjj4+MvLy8PDw/IyMh5eXn5+fn29vby8vLo6OjDw8O7u7u3t7ewsLCcnJx1dXVubm4+Pj43NzdkZGStc/JSAAAA4ElEQVQoz52RWW7DMAxE9bR635fYcXr/Y5aW7TYIGqDI/EjUAzRDUvFGCvWn/gHMOnmfNeYFJLonqnPVM8hrIA3B7of5BXkK47bXWwbe/ACp3OWqwSYnaI73+zStSST6AKYjE/sbQCZkpi0j0HSlUl/gPdwleM8SgSfIRzd8laRkcl0oIihYIkiVqhnl6hgicPRGrMHW0Ej4gXCYt8xiPoIw6TtANL8CVtpanSu1xvARJHYnpxpIq6tzU8D8UKIywHCNZCcpUDuXteDL57HngVMhf1nUQ9uisG47y492/kbfyJQHZ5yu1AMAAAAASUVORK5CYII=', download:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAASFBMVEUAAAD///86Ojq8vLxXV1ciIiKcnJympqZjY2MxMTHc3NywsLBDQ0NPT08VFRV5eXn39/fw8PDZ2dnV1dXKysqUlJTl5eUNDQ1EnTQhAAAAtUlEQVQoz33QWRKDIBAE0Gl2AXFP7n/TDKIEk5j+wKp+ZQ0D4SYE+pkDkrMe3rr0ATEYpUkrE+IFouyppJexgRR6IQQAPodlAqeAMyRoByIyjo8DrGpA2Td43YD2FfJHokR2hOsf8uy1v87oZOnrjHorFu7rreoexKLzhiFlqJsPxJL7dcZagWU532oG0ABNzj7y6wpwVAOgJ8AztgyhhTRyM0TsUQ0MuRi3AqaBayp85T/c5AVMKwUv6mnXTQAAAABJRU5ErkJggg==', + stitch:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAgMAAACdGdVrAAAACVBMVEUAAAD///9XV1cawLSAAAAAKUlEQVQI12MIBQMGBwYgYMRBqQawRgAp0QDWEOwUHu349EGNxqsd6kAArN4RJ/MiMK0AAAAASUVORK5CYII=', downloadSvgBtn:'Download', video:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHZlcnNpb249IjEuMSI+PHBhdGggZD0iTTUxMiA2NEMyNjUuNiA2NCA2NCAyNjUuNiA2NCA1MTJzMjAxLjYgNDQ4IDQ0OCA0NDggNDQ4LTIwMS42IDQ0OC00NDhTNzU4LjQgNjQgNTEyIDY0ek02OTEuMiA1NDRsLTI1NiAxNTYuOEM0MjguOCA3MDQgNDIyLjQgNzA0IDQxNiA3MDRjLTYuNCAwLTkuNiAwLTE2LTMuMkMzOTAuNCA2OTQuNCAzODQgNjg0LjggMzg0IDY3MkwzODQgMzUyYzAtMTIuOCA2LjQtMjIuNCAxNi0yOC44IDkuNi02LjQgMjIuNC02LjQgMzIgMGwyNTYgMTY2LjRjOS42IDYuNCAxNiAxNiAxNiAyOC44QzcwNCA1MjggNzAwLjggNTQwLjggNjkxLjIgNTQ0eiIgZmlsbD0id2hpdGUiLz48L3N2Zz4=', audio:'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTc2OCA5MzguNjY2NjY3SDI1NmE4NS4zMzMzMzMgODUuMzMzMzMzIDAgMCAxLTg1LjMzMzMzMy04NS4zMzMzMzRWMTcwLjY2NjY2N2E4NS4zMzMzMzMgODUuMzMzMzMzIDAgMCAxIDg1LjMzMzMzMy04NS4zMzMzMzRoNDIuNjY2NjY3djI5OC42NjY2NjdsMTA2LjY2NjY2Ni02NEw1MTIgMzg0Vjg1LjMzMzMzM2gyNTZhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMSA4NS4zMzMzMzMgODUuMzMzMzM0djY4Mi42NjY2NjZhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMS04NS4zMzMzMzMgODUuMzMzMzM0bS0yMTMuMzMzMzMzLTI5OC42NjY2NjdhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMC04NS4zMzMzMzQgODUuMzMzMzMzIDg1LjMzMzMzMyA4NS4zMzMzMzMgMCAwIDAgODUuMzMzMzM0IDg1LjMzMzMzNCA4NS4zMzMzMzMgODUuMzMzMzMzIDAgMCAwIDg1LjMzMzMzMy04NS4zMzMzMzR2LTIxMy4zMzMzMzNoMTI4di04NS4zMzMzMzNoLTE3MC42NjY2Njd2MjI0Ljg1MzMzM2MtMTIuMzczMzMzLTcuMjUzMzMzLTI3LjMwNjY2Ny0xMS41Mi00Mi42NjY2NjYtMTEuNTJ6IiBmaWxsPSJ3aGl0ZSIvPjwvc3ZnPg==', @@ -13236,6 +13237,22 @@ ImgOps | https://imgops.com/#b#`; } function downloadImg(url, name, type, over) { + if (/^blob:/.test(url)) { + const blob = getBlob(url); + if (blob) { + let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); + if (ext === "none") ext = "png"; + try { + saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); + over && over(); + return; + } catch (e) { + _GM_download(url, name, type); + over && over(); + return; + } + } + } if(canvas && (/^data:/.test(url) || url.split("/")[2] == document.domain)){ urlToBlobWithFetch(url, (blob, ext)=>{ if(!blob){ @@ -20991,6 +21008,7 @@ ImgOps | https://imgops.com/#b#`; self.remove(); },false); + var maxButton=container.querySelector('.pv-pic-window-max'); maxButton.style.cssText='top: -22px;right: 46px;'; this.maxButton=maxButton; @@ -23626,6 +23644,621 @@ ImgOps | https://imgops.com/#b#`; } }, true); + function StitcherC() { + this.selecting = false; + this.previewing = false; + this.selected = new Set(); + this.overlay = null; + this.preview = null; + this.layout = 'column'; + this.dragging = false; + this.startX = 0; + this.startY = 0; + } + + StitcherC.prototype = { + addStyle: function() { + if (StitcherC.style) { + if (!StitcherC.style.parentNode) { + StitcherC.style = _GM_addStyle(StitcherC.style.innerText); + } + return; + } + StitcherC.style = _GM_addStyle('\ + .pv-stitch-overlay {\ + position: fixed;\ + top: 0;\ + left: 0;\ + width: 100%;\ + height: 100%;\ + z-index:'+(prefs.imgWindow.zIndex + 5)+';\ + pointer-events: none;\ + }\ + .pv-stitch-overlay * {\ + box-sizing: border-box;\ + }\ + .pv-stitch-panel {\ + position: fixed;\ + top: 12px;\ + left: 12px;\ + display: flex;\ + gap: 8px;\ + padding: 6px;\ + background: rgba(0,0,0,0.65);\ + border: 1px solid rgba(255,255,255,0.2);\ + border-radius: 6px;\ + pointer-events: auto;\ + }\ + .pv-stitch-panel-inline {\ + position: absolute;\ + top: 8px;\ + right: 8px;\ + left: auto;\ + }\ + .pv-stitch-action {\ + width: 28px;\ + height: 28px;\ + display: flex;\ + align-items: center;\ + justify-content: center;\ + color: #fff;\ + background: rgba(255,255,255,0.12);\ + border-radius: 4px;\ + cursor: pointer;\ + }\ + .pv-stitch-action:hover {\ + background: rgba(255,255,255,0.25);\ + }\ + .pv-stitch-action.active {\ + background: rgba(255,255,255,0.4);\ + }\ + .pv-stitch-action>svg {\ + width: 18px;\ + height: 18px;\ + fill: none;\ + stroke: currentColor;\ + stroke-width: 3;\ + stroke-linecap: round;\ + stroke-linejoin: round;\ + }\ + .pv-stitch-box {\ + position: fixed;\ + border: 1px dashed rgba(255,255,255,0.9);\ + background: rgba(79,179,255,0.18);\ + pointer-events: none;\ + display: none;\ + }\ + .pv-stitch-selected {\ + outline: 3px solid rgba(79,179,255,0.95);\ + outline-offset: -3px;\ + box-shadow: 0 0 0 3px rgba(79,179,255,0.9), 0 0 12px rgba(79,179,255,0.85);\ + filter: brightness(0.78) saturate(1.15);\ + }\ + .pv-stitch-selected-parent {\ + outline: 3px solid rgba(79,179,255,0.95);\ + outline-offset: -3px;\ + box-shadow: 0 0 0 3px rgba(79,179,255,0.9), 0 0 12px rgba(79,179,255,0.85);\ + }\ + .pv-stitch-preview {\ + pointer-events: auto;\ + background: rgba(0,0,0,0.6);\ + }\ + .pv-stitch-stage {\ + position: fixed;\ + top: 50%;\ + left: 50%;\ + transform: translate(-50%, -50%);\ + max-width: 90vw;\ + max-height: 80vh;\ + overflow: auto;\ + padding: 12px;\ + background: rgba(0,0,0,0.75);\ + border: 1px solid rgba(255,255,255,0.2);\ + border-radius: 8px;\ + pointer-events: auto;\ + }\ + .pv-stitch-items {\ + display: flex;\ + gap: 10px;\ + }\ + .pv-stitch-items-horizontal {\ + flex-direction: row;\ + }\ + .pv-stitch-items-vertical {\ + flex-direction: column;\ + }\ + .pv-stitch-item {\ + padding: 6px;\ + background: rgba(0,0,0,0.55);\ + border: 1px solid rgba(255,255,255,0.2);\ + border-radius: 6px;\ + cursor: move;\ + }\ + .pv-stitch-item.dragging {\ + opacity: 0.4;\ + }\ + .pv-stitch-item img {\ + display: block;\ + max-width: 220px;\ + max-height: 220px;\ + }\ + .pv-stitch-selecting, .pv-stitch-selecting * {\ + user-select: none;\ + }\ + '); + }, + openSelect: function() { + if (this.selecting || this.previewing) return; + this.addStyle(); + this.selecting = true; + if (floatBar) floatBar.hide(); + document.documentElement.classList.add('pv-stitch-selecting'); + const overlay = document.createElement('div'); + overlay.className = 'pv-stitch-overlay pv-stitch-select'; + overlay.innerHTML = createHTML( + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + '
' + ); + document.body.appendChild(overlay); + this.overlay = overlay; + this.box = overlay.querySelector('.pv-stitch-box'); + const panel = overlay.querySelector('.pv-stitch-panel'); + this.placePanel(panel); + overlay.querySelector('.pv-stitch-action-done').addEventListener('click', () => { + if (!this.selected.size) { + this.closeSelect(); + return; + } + const selected = Array.from(this.selected); + this.closeSelect(false); + this.openPreview(selected); + }); + overlay.querySelector('.pv-stitch-action-cancel').addEventListener('click', () => { + this.closeSelect(); + }); + + this.onSelectMouseDown = (e) => { + if (!this.selecting) return; + if (panel.contains(e.target)) return; + if (e.button !== 0 && e.button !== 2) return; + e.preventDefault(); + e.stopPropagation(); + this.dragging = true; + panel.style.display = 'none'; + this.dragMode = (e.button === 2 || e.altKey || e.shiftKey) ? 'remove' : 'add'; + this.startX = e.clientX; + this.startY = e.clientY; + this.updateBox(e.clientX, e.clientY); + this.box.style.display = 'block'; + }; + this.onSelectMouseMove = (e) => { + if (!this.dragging) return; + e.preventDefault(); + e.stopPropagation(); + this.updateBox(e.clientX, e.clientY); + }; + this.onSelectMouseUp = (e) => { + if (!this.dragging) return; + e.preventDefault(); + e.stopPropagation(); + this.dragging = false; + const rect = this.getBoxRect(e.clientX, e.clientY); + this.box.style.display = 'none'; + if (rect.width < 4 && rect.height < 4) { + const img = this.findImageAt(e.clientX, e.clientY); + if (img) this.toggleSelection(img, this.dragMode); + panel.style.display = ''; + this.placePanelNear(panel, e.clientX, e.clientY); + return; + } + this.applySelection(rect, this.dragMode); + panel.style.display = ''; + this.placePanelNear(panel, e.clientX, e.clientY); + }; + this.onSelectClick = (e) => { + if (!this.selecting) return; + if (panel.contains(e.target)) return; + e.preventDefault(); + e.stopPropagation(); + }; + this.onSelectContext = (e) => { + if (!this.selecting) return; + if (panel.contains(e.target)) return; + e.preventDefault(); + e.stopPropagation(); + }; + this.onKeyDown = (e) => { + if (e.key !== 'Escape') return; + if (this.selecting) this.closeSelect(); + if (this.previewing) this.closePreview(); + }; + document.addEventListener('mousedown', this.onSelectMouseDown, true); + document.addEventListener('mousemove', this.onSelectMouseMove, true); + document.addEventListener('mouseup', this.onSelectMouseUp, true); + document.addEventListener('click', this.onSelectClick, true); + document.addEventListener('contextmenu', this.onSelectContext, true); + document.addEventListener('keydown', this.onKeyDown, true); + }, + closeSelect: function(clear = true) { + if (!this.selecting) return; + this.selecting = false; + document.documentElement.classList.remove('pv-stitch-selecting'); + if (this.overlay && this.overlay.parentNode) { + this.overlay.parentNode.removeChild(this.overlay); + } + this.overlay = null; + this.box = null; + if (clear) this.clearSelection(); + document.removeEventListener('mousedown', this.onSelectMouseDown, true); + document.removeEventListener('mousemove', this.onSelectMouseMove, true); + document.removeEventListener('mouseup', this.onSelectMouseUp, true); + document.removeEventListener('click', this.onSelectClick, true); + document.removeEventListener('contextmenu', this.onSelectContext, true); + if (!this.previewing) document.removeEventListener('keydown', this.onKeyDown, true); + }, + clearSelection: function() { + this.selected.forEach((img) => { + img.classList.remove('pv-stitch-selected'); + this.toggleParentClass(img, false); + }); + this.selected.clear(); + }, + updateBox: function(x, y) { + const rect = this.getBoxRect(x, y); + this.box.style.left = rect.left + 'px'; + this.box.style.top = rect.top + 'px'; + this.box.style.width = rect.width + 'px'; + this.box.style.height = rect.height + 'px'; + }, + getBoxRect: function(x, y) { + const left = Math.min(this.startX, x); + const top = Math.min(this.startY, y); + const width = Math.abs(this.startX - x); + const height = Math.abs(this.startY - y); + return {left, top, width, height, right: left + width, bottom: top + height}; + }, + applySelection: function(rect, mode) { + const imgs = this.getSelectableImages(); + imgs.forEach((img) => { + const r = img.getBoundingClientRect(); + if (rect.right < r.left || rect.left > r.right || rect.bottom < r.top || rect.top > r.bottom) return; + if (mode === 'remove') this.removeSelection(img); + else this.addSelection(img); + }); + }, + addSelection: function(img) { + if (this.selected.has(img)) return; + this.selected.add(img); + img.classList.add('pv-stitch-selected'); + this.toggleParentClass(img, true); + }, + removeSelection: function(img) { + if (!this.selected.has(img)) return; + this.selected.delete(img); + img.classList.remove('pv-stitch-selected'); + this.toggleParentClass(img, false); + }, + toggleSelection: function(img, mode) { + if (mode === 'remove') { + this.removeSelection(img); + return; + } + if (this.selected.has(img)) this.removeSelection(img); + else this.addSelection(img); + }, + toggleParentClass: function(img, isAdd) { + const parent = img && img.parentNode; + if (!parent || parent.nodeType !== 1) return; + const tag = parent.tagName; + if (tag === 'BODY' || tag === 'HTML') return; + parent.classList.toggle('pv-stitch-selected-parent', isAdd); + }, + findImageAt: function(x, y) { + const els = document.elementsFromPoint(x, y); + for (let i = 0; i < els.length; i++) { + const el = els[i]; + if (this.overlay && this.overlay.contains(el)) continue; + if (el.tagName === 'IMG') return el; + } + return null; + }, + getSelectableImages: function() { + const imgs = document.querySelectorAll('img'); + const list = []; + for (let i = 0; i < imgs.length; i++) { + const img = imgs[i]; + if (!img.src && !img.currentSrc) continue; + const style = unsafeWindow.getComputedStyle(img); + if (style.display === 'none' || style.visibility === 'hidden') continue; + const rect = img.getBoundingClientRect(); + if (rect.width < 4 || rect.height < 4) continue; + list.push(img); + } + return list; + }, + getImgSrc: function(img) { + const original = this.getOriginalImgSrc(img); + if (original) return original; + return img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('data-original') || img.getAttribute('data-lazy-src') || img.getAttribute('data-zoom-image') || img.getAttribute('data-large'); + }, + getOriginalImgSrc: function(img) { + if (!img) return ""; + let imgPA, imgPE = []; + let imgPN = img.parentNode; + while (imgPN && imgPN.nodeType === 1 && imgPN.nodeName !== "BODY") { + if (!imgPA && imgPN.nodeName === "A") imgPA = imgPN; + imgPE.push(imgPN); + imgPN = imgPN.parentNode; + } + try { + if (matchedRule && matchedRule.rules && matchedRule.rules.length > 0 && matchedRule.getImage) { + let src = matchedRule.getImage(img, imgPA, imgPE); + if (Array.isArray(src)) src = src[0]; + if (src && src !== img.src) return src; + } + } catch (e) {} + try { + if (tprules && tprules[0]) { + const src = tprules[0].call(img); + if (src) return src; + } + } catch (e) {} + if (imgPA && imgPA.href && imageReg.test(imgPA.href) && imgPA.href !== img.src) { + return imgPA.href; + } + return ""; + }, + openPreview: function(selected) { + const srcs = []; + selected.forEach((img) => { + const src = this.getImgSrc(img); + if (src) srcs.push(src); + img.classList.remove('pv-stitch-selected'); + this.toggleParentClass(img, false); + }); + this.selected.clear(); + if (!srcs.length) return; + if (this.previewing) return; + this.previewing = true; + this.addStyle(); + const overlay = document.createElement('div'); + overlay.className = 'pv-stitch-overlay pv-stitch-preview'; + const isVertical = this.layout === 'column'; + overlay.innerHTML = createHTML( + '
'+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
' + ); + document.body.appendChild(overlay); + this.preview = overlay; + const itemsCon = overlay.querySelector('.pv-stitch-items'); + const stage = overlay.querySelector('.pv-stitch-stage'); + const actionPanel = overlay.querySelector('.pv-stitch-panel-action'); + this.placeActionPanel(stage, actionPanel); + srcs.forEach((src) => { + const item = document.createElement('div'); + item.className = 'pv-stitch-item'; + item.setAttribute('draggable', 'true'); + item.dataset.src = src; + const img = document.createElement('img'); + img.src = src; + img.addEventListener('load', () => this.placeActionPanel(stage, actionPanel)); + item.appendChild(img); + itemsCon.appendChild(item); + }); + setTimeout(() => this.placeActionPanel(stage, actionPanel), 0); + let dragItem = null; + itemsCon.addEventListener('dragstart', (e) => { + const item = e.target.closest('.pv-stitch-item'); + if (!item) return; + dragItem = item; + dragItem.classList.add('dragging'); + e.dataTransfer.effectAllowed = 'move'; + }); + itemsCon.addEventListener('dragend', () => { + if (dragItem) dragItem.classList.remove('dragging'); + dragItem = null; + }); + itemsCon.addEventListener('dragover', (e) => { + if (!dragItem) return; + const target = e.target.closest('.pv-stitch-item'); + if (!target || target === dragItem) return; + e.preventDefault(); + const rect = target.getBoundingClientRect(); + const before = this.layout === 'row' ? e.clientX < rect.left + rect.width / 2 : e.clientY < rect.top + rect.height / 2; + itemsCon.insertBefore(dragItem, before ? target : target.nextSibling); + }); + + const btnHorizontal = overlay.querySelector('.pv-stitch-action-horizontal'); + const btnVertical = overlay.querySelector('.pv-stitch-action-vertical'); + btnHorizontal.addEventListener('click', () => { + this.setLayout('row', itemsCon, btnHorizontal, btnVertical); + this.placeActionPanel(stage, actionPanel); + }); + btnVertical.addEventListener('click', () => { + this.setLayout('column', itemsCon, btnHorizontal, btnVertical); + this.placeActionPanel(stage, actionPanel); + }); + overlay.querySelector('.pv-stitch-action-done').addEventListener('click', async () => { + const ordered = Array.from(itemsCon.querySelectorAll('.pv-stitch-item')).map(item => item.dataset.src); + this.closePreview(); + await this.renderStitch(ordered, this.layout); + }); + overlay.querySelector('.pv-stitch-action-cancel').addEventListener('click', () => { + this.closePreview(); + }); + document.addEventListener('keydown', this.onKeyDown, true); + }, + closePreview: function() { + if (!this.previewing) return; + this.previewing = false; + if (this.preview && this.preview.parentNode) { + this.preview.parentNode.removeChild(this.preview); + } + this.preview = null; + if (!this.selecting) document.removeEventListener('keydown', this.onKeyDown, true); + }, + placePanel: function(panel) { + if (!panel) return; + requestAnimationFrame(() => { + const rect = panel.getBoundingClientRect(); + const pad = 8; + const left = Math.min(Math.max(pad, (window.innerWidth - rect.width) / 2), window.innerWidth - rect.width - pad); + let top = Math.round(window.innerHeight * 0.72); + if (top + rect.height > window.innerHeight - pad) { + top = window.innerHeight - rect.height - pad; + } + if (top < pad) top = pad; + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + }); + }, + placePanelNear: function(panel, x, y) { + if (!panel) return; + requestAnimationFrame(() => { + const rect = panel.getBoundingClientRect(); + const pad = 8; + const maxX = Math.max(pad, window.innerWidth - rect.width - pad); + const maxY = Math.max(pad, window.innerHeight - rect.height - pad); + let left = x + 12; + let top = y + 12; + if (left > maxX) left = x - rect.width - 12; + if (top > maxY) top = y - rect.height - 12; + left = Math.min(Math.max(pad, left), maxX); + top = Math.min(Math.max(pad, top), maxY); + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + }); + }, + placeActionPanel: function(stage, panel) { + if (!stage || !panel) return; + requestAnimationFrame(() => { + const stageRect = stage.getBoundingClientRect(); + const panelRect = panel.getBoundingClientRect(); + const pad = 8; + const centerX = stageRect.left + stageRect.width / 2 - panelRect.width / 2; + let top = stageRect.bottom + 12; + let left = centerX; + if (top + panelRect.height > window.innerHeight - pad) { + top = stageRect.top - panelRect.height - 12; + } + left = Math.min(Math.max(pad, left), window.innerWidth - panelRect.width - pad); + top = Math.min(Math.max(pad, top), window.innerHeight - panelRect.height - pad); + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + }); + }, + setLayout: function(layout, itemsCon, btnHorizontal, btnVertical) { + this.layout = layout === 'column' ? 'column' : 'row'; + itemsCon.classList.toggle('pv-stitch-items-horizontal', this.layout === 'row'); + itemsCon.classList.toggle('pv-stitch-items-vertical', this.layout === 'column'); + btnHorizontal.classList.toggle('active', this.layout === 'row'); + btnVertical.classList.toggle('active', this.layout === 'column'); + }, + loadImageForStitch: function(src) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + let revoke = null; + const onLoad = () => resolve({img, revoke}); + const onError = () => reject(new Error('load failed')); + img.onload = onLoad; + img.onerror = onError; + if (/^data:|^blob:/i.test(src)) { + img.src = src; + return; + } + _GM_xmlhttpRequest({ + method: 'GET', + url: src, + responseType: 'blob', + onload: (response) => { + if (response.response instanceof Blob) { + const blobUrl = URL.createObjectURL(response.response); + revoke = () => URL.revokeObjectURL(blobUrl); + img.src = blobUrl; + return; + } + img.src = src; + }, + onerror: onError + }); + }); + }, + renderStitch: async function(srcs, layout) { + if (!srcs || !srcs.length) return; + try { + const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); + let totalW = 0; + let totalH = 0; + if (layout === 'column') { + loaded.forEach(({img}) => { + totalW = Math.max(totalW, img.naturalWidth || img.width); + totalH += img.naturalHeight || img.height; + }); + } else { + loaded.forEach(({img}) => { + totalW += img.naturalWidth || img.width; + totalH = Math.max(totalH, img.naturalHeight || img.height); + }); + } + const canvas = document.createElement('canvas'); + canvas.width = Math.max(1, totalW); + canvas.height = Math.max(1, totalH); + const ctx = canvas.getContext('2d'); + let offset = 0; + loaded.forEach(({img}) => { + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + if (layout === 'column') { + ctx.drawImage(img, 0, offset, w, h); + offset += h; + } else { + ctx.drawImage(img, offset, 0, w, h); + offset += w; + } + }); + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + loaded.forEach(({revoke}) => revoke && revoke()); + if (!blob) return; + const blobUrl = unsafeWindow.URL.createObjectURL(blob); + const outImg = new Image(); + outImg.src = blobUrl; + outImg.title = document.title || ''; + outImg.alt = outImg.title; + const data = {img: outImg, imgSrc: blobUrl, src: blobUrl, srcs: [blobUrl]}; + new ImgWindowC(outImg, data, true); + } catch (e) { + console.warn('[Picviewer CE+] stitch failed', e); + } + } + }; + + var stitcher = new StitcherC(); + var lastPopupLoading; // 载入动画 function LoadingAnimC(data, buttonType, waitImgLoad, openInTopWindow, initPos) { @@ -24109,7 +24742,7 @@ ImgOps | https://imgops.com/#b#`; var container=document.createElement('span'); container.id='pv-float-bar-container'; getBody(document).appendChild(container); - for(let i=0;i<5;i++){ + for(let i=0;i win.remove(true)); + } + if (stitcher) stitcher.openSelect(); + return; + } if (this.data.imgSrc.indexOf("blob:") === 0) { let blobUrl = await getBase64FromBlobUrl(this.data.imgSrc); if (blobUrl) { @@ -27530,6 +28179,12 @@ ImgOps | https://imgops.com/#b#`; } _GM_registerMenuCommand(i18n("openConfig"), openPrefs); _GM_registerMenuCommand(i18n("openGallery"), openGallery); + _GM_registerMenuCommand(i18n("stitch"), () => { + if (ImgWindowC.all && ImgWindowC.all.length) { + ImgWindowC.all.slice(0).forEach(win => win.remove(true)); + } + if (stitcher) stitcher.openSelect(); + }); _GM_registerMenuCommand(i18n("hideIcon") + (hideIcon ? "☑️" : ""), () => { hideIcon=!hideIcon; storage.setListItem("hideIcon", location.hostname, hideIcon); From 29b5494cc727007e8319a0792839e7f2e8dafa0e Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 13 Jan 2026 13:30:22 +0900 Subject: [PATCH 13/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index f2e12a8cf84..b49a6c450e5 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2026.1.5.1 +// @version 2026.1.13.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -47,7 +47,7 @@ // @grant unsafeWindow // @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js // @require https://update.greasyfork.org/scripts/438080/1714183/pvcep_rules.js -// @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js +// @require https://update.greasyfork.org/scripts/440698/1733533/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* From 9a4929966013ac1468b94b66115ed63fdb6084ee Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 13 Jan 2026 04:30:34 +0000 Subject: [PATCH 14/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 663 ++++++++++++++++++++++++++++++++++++- 1 file changed, 659 insertions(+), 4 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 207a6a3ddf1..5ca8e4bcf44 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2026.1.5.1 +// @version 2026.1.13.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -47,7 +47,7 @@ // @grant unsafeWindow // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=23710 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1714183 -// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1653424 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1733533 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -12784,7 +12784,7 @@ ImgOps | https://imgops.com/#b#`; // 默认设置,请到设置界面修改 prefs={ floatBar:{//浮动工具栏相关设置. - butonOrder:['actual','current','gallery','magnifier','download'],//按钮排列顺序'actual'(实际的图片),'current'(当前显示的图片),'magnifier'(放大镜观察),'gallery'(图集),'search'(搜索原图) + butonOrder:['actual','current','gallery','magnifier','download','stitch'],//按钮排列顺序'actual'(实际的图片),'current'(当前显示的图片),'magnifier'(放大镜观察),'gallery'(图集),'search'(搜索原图) additionalFeature: 'open', invertAdditionalFeature: false, listenBg:true,//监听背景图 @@ -12992,6 +12992,7 @@ ImgOps | https://imgops.com/#b#`; gallery:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAP1BMVEUAAAD///94eHgZGRn39/fz8/POzs6xsbGSkpJ9fX1UVFQpKSnb29vJycmmpqafn5+UlJSNjY0/Pz8hISENDQ2fWpEMAAAAcUlEQVQoz43SWQrEIBRE0Xe7M8/T/tcaNSKCKUh95nCDiIaYYa9zUDQRiiaCalANqkE0n6BuBLArWBTUAsa2/3yqfwYdzAl+GeCWAN9YM7nvo4chgoXmgjP8CdoEvqmA/oCQRHgat2p7YM2gvHb9GMRu7acCGLmlyNoAAAAASUVORK5CYII=', search:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAXVBMVEUAAAD///+MjIwmJibNzc2UlJTd3d2lpaUKCgrT09O/v78YGBjj4+MvLy8PDw/IyMh5eXn5+fn29vby8vLo6OjDw8O7u7u3t7ewsLCcnJx1dXVubm4+Pj43NzdkZGStc/JSAAAA4ElEQVQoz52RWW7DMAxE9bR635fYcXr/Y5aW7TYIGqDI/EjUAzRDUvFGCvWn/gHMOnmfNeYFJLonqnPVM8hrIA3B7of5BXkK47bXWwbe/ACp3OWqwSYnaI73+zStSST6AKYjE/sbQCZkpi0j0HSlUl/gPdwleM8SgSfIRzd8laRkcl0oIihYIkiVqhnl6hgicPRGrMHW0Ej4gXCYt8xiPoIw6TtANL8CVtpanSu1xvARJHYnpxpIq6tzU8D8UKIywHCNZCcpUDuXteDL57HngVMhf1nUQ9uisG47y492/kbfyJQHZ5yu1AMAAAAASUVORK5CYII=', download:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAASFBMVEUAAAD///86Ojq8vLxXV1ciIiKcnJympqZjY2MxMTHc3NywsLBDQ0NPT08VFRV5eXn39/fw8PDZ2dnV1dXKysqUlJTl5eUNDQ1EnTQhAAAAtUlEQVQoz33QWRKDIBAE0Gl2AXFP7n/TDKIEk5j+wKp+ZQ0D4SYE+pkDkrMe3rr0ATEYpUkrE+IFouyppJexgRR6IQQAPodlAqeAMyRoByIyjo8DrGpA2Td43YD2FfJHokR2hOsf8uy1v87oZOnrjHorFu7rreoexKLzhiFlqJsPxJL7dcZagWU532oG0ABNzj7y6wpwVAOgJ8AztgyhhTRyM0TsUQ0MuRi3AqaBayp85T/c5AVMKwUv6mnXTQAAAABJRU5ErkJggg==', + stitch:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAgMAAACdGdVrAAAACVBMVEUAAAD///9XV1cawLSAAAAAKUlEQVQI12MIBQMGBwYgYMRBqQawRgAp0QDWEOwUHu349EGNxqsd6kAArN4RJ/MiMK0AAAAASUVORK5CYII=', downloadSvgBtn:'Download', video:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHZlcnNpb249IjEuMSI+PHBhdGggZD0iTTUxMiA2NEMyNjUuNiA2NCA2NCAyNjUuNiA2NCA1MTJzMjAxLjYgNDQ4IDQ0OCA0NDggNDQ4LTIwMS42IDQ0OC00NDhTNzU4LjQgNjQgNTEyIDY0ek02OTEuMiA1NDRsLTI1NiAxNTYuOEM0MjguOCA3MDQgNDIyLjQgNzA0IDQxNiA3MDRjLTYuNCAwLTkuNiAwLTE2LTMuMkMzOTAuNCA2OTQuNCAzODQgNjg0LjggMzg0IDY3MkwzODQgMzUyYzAtMTIuOCA2LjQtMjIuNCAxNi0yOC44IDkuNi02LjQgMjIuNC02LjQgMzIgMGwyNTYgMTY2LjRjOS42IDYuNCAxNiAxNiAxNiAyOC44QzcwNCA1MjggNzAwLjggNTQwLjggNjkxLjIgNTQ0eiIgZmlsbD0id2hpdGUiLz48L3N2Zz4=', audio:'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTc2OCA5MzguNjY2NjY3SDI1NmE4NS4zMzMzMzMgODUuMzMzMzMzIDAgMCAxLTg1LjMzMzMzMy04NS4zMzMzMzRWMTcwLjY2NjY2N2E4NS4zMzMzMzMgODUuMzMzMzMzIDAgMCAxIDg1LjMzMzMzMy04NS4zMzMzMzRoNDIuNjY2NjY3djI5OC42NjY2NjdsMTA2LjY2NjY2Ni02NEw1MTIgMzg0Vjg1LjMzMzMzM2gyNTZhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMSA4NS4zMzMzMzMgODUuMzMzMzM0djY4Mi42NjY2NjZhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMS04NS4zMzMzMzMgODUuMzMzMzM0bS0yMTMuMzMzMzMzLTI5OC42NjY2NjdhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMC04NS4zMzMzMzQgODUuMzMzMzMzIDg1LjMzMzMzMyA4NS4zMzMzMzMgMCAwIDAgODUuMzMzMzM0IDg1LjMzMzMzNCA4NS4zMzMzMzMgODUuMzMzMzMzIDAgMCAwIDg1LjMzMzMzMy04NS4zMzMzMzR2LTIxMy4zMzMzMzNoMTI4di04NS4zMzMzMzNoLTE3MC42NjY2Njd2MjI0Ljg1MzMzM2MtMTIuMzczMzMzLTcuMjUzMzMzLTI3LjMwNjY2Ny0xMS41Mi00Mi42NjY2NjYtMTEuNTJ6IiBmaWxsPSJ3aGl0ZSIvPjwvc3ZnPg==', @@ -13236,6 +13237,22 @@ ImgOps | https://imgops.com/#b#`; } function downloadImg(url, name, type, over) { + if (/^blob:/.test(url)) { + const blob = getBlob(url); + if (blob) { + let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); + if (ext === "none") ext = "png"; + try { + saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); + over && over(); + return; + } catch (e) { + _GM_download(url, name, type); + over && over(); + return; + } + } + } if(canvas && (/^data:/.test(url) || url.split("/")[2] == document.domain)){ urlToBlobWithFetch(url, (blob, ext)=>{ if(!blob){ @@ -20991,6 +21008,7 @@ ImgOps | https://imgops.com/#b#`; self.remove(); },false); + var maxButton=container.querySelector('.pv-pic-window-max'); maxButton.style.cssText='top: -22px;right: 46px;'; this.maxButton=maxButton; @@ -23626,6 +23644,621 @@ ImgOps | https://imgops.com/#b#`; } }, true); + function StitcherC() { + this.selecting = false; + this.previewing = false; + this.selected = new Set(); + this.overlay = null; + this.preview = null; + this.layout = 'column'; + this.dragging = false; + this.startX = 0; + this.startY = 0; + } + + StitcherC.prototype = { + addStyle: function() { + if (StitcherC.style) { + if (!StitcherC.style.parentNode) { + StitcherC.style = _GM_addStyle(StitcherC.style.innerText); + } + return; + } + StitcherC.style = _GM_addStyle('\ + .pv-stitch-overlay {\ + position: fixed;\ + top: 0;\ + left: 0;\ + width: 100%;\ + height: 100%;\ + z-index:'+(prefs.imgWindow.zIndex + 5)+';\ + pointer-events: none;\ + }\ + .pv-stitch-overlay * {\ + box-sizing: border-box;\ + }\ + .pv-stitch-panel {\ + position: fixed;\ + top: 12px;\ + left: 12px;\ + display: flex;\ + gap: 8px;\ + padding: 6px;\ + background: rgba(0,0,0,0.65);\ + border: 1px solid rgba(255,255,255,0.2);\ + border-radius: 6px;\ + pointer-events: auto;\ + }\ + .pv-stitch-panel-inline {\ + position: absolute;\ + top: 8px;\ + right: 8px;\ + left: auto;\ + }\ + .pv-stitch-action {\ + width: 28px;\ + height: 28px;\ + display: flex;\ + align-items: center;\ + justify-content: center;\ + color: #fff;\ + background: rgba(255,255,255,0.12);\ + border-radius: 4px;\ + cursor: pointer;\ + }\ + .pv-stitch-action:hover {\ + background: rgba(255,255,255,0.25);\ + }\ + .pv-stitch-action.active {\ + background: rgba(255,255,255,0.4);\ + }\ + .pv-stitch-action>svg {\ + width: 18px;\ + height: 18px;\ + fill: none;\ + stroke: currentColor;\ + stroke-width: 3;\ + stroke-linecap: round;\ + stroke-linejoin: round;\ + }\ + .pv-stitch-box {\ + position: fixed;\ + border: 1px dashed rgba(255,255,255,0.9);\ + background: rgba(79,179,255,0.18);\ + pointer-events: none;\ + display: none;\ + }\ + .pv-stitch-selected {\ + outline: 3px solid rgba(79,179,255,0.95);\ + outline-offset: -3px;\ + box-shadow: 0 0 0 3px rgba(79,179,255,0.9), 0 0 12px rgba(79,179,255,0.85);\ + filter: brightness(0.78) saturate(1.15);\ + }\ + .pv-stitch-selected-parent {\ + outline: 3px solid rgba(79,179,255,0.95);\ + outline-offset: -3px;\ + box-shadow: 0 0 0 3px rgba(79,179,255,0.9), 0 0 12px rgba(79,179,255,0.85);\ + }\ + .pv-stitch-preview {\ + pointer-events: auto;\ + background: rgba(0,0,0,0.6);\ + }\ + .pv-stitch-stage {\ + position: fixed;\ + top: 50%;\ + left: 50%;\ + transform: translate(-50%, -50%);\ + max-width: 90vw;\ + max-height: 80vh;\ + overflow: auto;\ + padding: 12px;\ + background: rgba(0,0,0,0.75);\ + border: 1px solid rgba(255,255,255,0.2);\ + border-radius: 8px;\ + pointer-events: auto;\ + }\ + .pv-stitch-items {\ + display: flex;\ + gap: 10px;\ + }\ + .pv-stitch-items-horizontal {\ + flex-direction: row;\ + }\ + .pv-stitch-items-vertical {\ + flex-direction: column;\ + }\ + .pv-stitch-item {\ + padding: 6px;\ + background: rgba(0,0,0,0.55);\ + border: 1px solid rgba(255,255,255,0.2);\ + border-radius: 6px;\ + cursor: move;\ + }\ + .pv-stitch-item.dragging {\ + opacity: 0.4;\ + }\ + .pv-stitch-item img {\ + display: block;\ + max-width: 220px;\ + max-height: 220px;\ + }\ + .pv-stitch-selecting, .pv-stitch-selecting * {\ + user-select: none;\ + }\ + '); + }, + openSelect: function() { + if (this.selecting || this.previewing) return; + this.addStyle(); + this.selecting = true; + if (floatBar) floatBar.hide(); + document.documentElement.classList.add('pv-stitch-selecting'); + const overlay = document.createElement('div'); + overlay.className = 'pv-stitch-overlay pv-stitch-select'; + overlay.innerHTML = createHTML( + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + '
' + ); + document.body.appendChild(overlay); + this.overlay = overlay; + this.box = overlay.querySelector('.pv-stitch-box'); + const panel = overlay.querySelector('.pv-stitch-panel'); + this.placePanel(panel); + overlay.querySelector('.pv-stitch-action-done').addEventListener('click', () => { + if (!this.selected.size) { + this.closeSelect(); + return; + } + const selected = Array.from(this.selected); + this.closeSelect(false); + this.openPreview(selected); + }); + overlay.querySelector('.pv-stitch-action-cancel').addEventListener('click', () => { + this.closeSelect(); + }); + + this.onSelectMouseDown = (e) => { + if (!this.selecting) return; + if (panel.contains(e.target)) return; + if (e.button !== 0 && e.button !== 2) return; + e.preventDefault(); + e.stopPropagation(); + this.dragging = true; + panel.style.display = 'none'; + this.dragMode = (e.button === 2 || e.altKey || e.shiftKey) ? 'remove' : 'add'; + this.startX = e.clientX; + this.startY = e.clientY; + this.updateBox(e.clientX, e.clientY); + this.box.style.display = 'block'; + }; + this.onSelectMouseMove = (e) => { + if (!this.dragging) return; + e.preventDefault(); + e.stopPropagation(); + this.updateBox(e.clientX, e.clientY); + }; + this.onSelectMouseUp = (e) => { + if (!this.dragging) return; + e.preventDefault(); + e.stopPropagation(); + this.dragging = false; + const rect = this.getBoxRect(e.clientX, e.clientY); + this.box.style.display = 'none'; + if (rect.width < 4 && rect.height < 4) { + const img = this.findImageAt(e.clientX, e.clientY); + if (img) this.toggleSelection(img, this.dragMode); + panel.style.display = ''; + this.placePanelNear(panel, e.clientX, e.clientY); + return; + } + this.applySelection(rect, this.dragMode); + panel.style.display = ''; + this.placePanelNear(panel, e.clientX, e.clientY); + }; + this.onSelectClick = (e) => { + if (!this.selecting) return; + if (panel.contains(e.target)) return; + e.preventDefault(); + e.stopPropagation(); + }; + this.onSelectContext = (e) => { + if (!this.selecting) return; + if (panel.contains(e.target)) return; + e.preventDefault(); + e.stopPropagation(); + }; + this.onKeyDown = (e) => { + if (e.key !== 'Escape') return; + if (this.selecting) this.closeSelect(); + if (this.previewing) this.closePreview(); + }; + document.addEventListener('mousedown', this.onSelectMouseDown, true); + document.addEventListener('mousemove', this.onSelectMouseMove, true); + document.addEventListener('mouseup', this.onSelectMouseUp, true); + document.addEventListener('click', this.onSelectClick, true); + document.addEventListener('contextmenu', this.onSelectContext, true); + document.addEventListener('keydown', this.onKeyDown, true); + }, + closeSelect: function(clear = true) { + if (!this.selecting) return; + this.selecting = false; + document.documentElement.classList.remove('pv-stitch-selecting'); + if (this.overlay && this.overlay.parentNode) { + this.overlay.parentNode.removeChild(this.overlay); + } + this.overlay = null; + this.box = null; + if (clear) this.clearSelection(); + document.removeEventListener('mousedown', this.onSelectMouseDown, true); + document.removeEventListener('mousemove', this.onSelectMouseMove, true); + document.removeEventListener('mouseup', this.onSelectMouseUp, true); + document.removeEventListener('click', this.onSelectClick, true); + document.removeEventListener('contextmenu', this.onSelectContext, true); + if (!this.previewing) document.removeEventListener('keydown', this.onKeyDown, true); + }, + clearSelection: function() { + this.selected.forEach((img) => { + img.classList.remove('pv-stitch-selected'); + this.toggleParentClass(img, false); + }); + this.selected.clear(); + }, + updateBox: function(x, y) { + const rect = this.getBoxRect(x, y); + this.box.style.left = rect.left + 'px'; + this.box.style.top = rect.top + 'px'; + this.box.style.width = rect.width + 'px'; + this.box.style.height = rect.height + 'px'; + }, + getBoxRect: function(x, y) { + const left = Math.min(this.startX, x); + const top = Math.min(this.startY, y); + const width = Math.abs(this.startX - x); + const height = Math.abs(this.startY - y); + return {left, top, width, height, right: left + width, bottom: top + height}; + }, + applySelection: function(rect, mode) { + const imgs = this.getSelectableImages(); + imgs.forEach((img) => { + const r = img.getBoundingClientRect(); + if (rect.right < r.left || rect.left > r.right || rect.bottom < r.top || rect.top > r.bottom) return; + if (mode === 'remove') this.removeSelection(img); + else this.addSelection(img); + }); + }, + addSelection: function(img) { + if (this.selected.has(img)) return; + this.selected.add(img); + img.classList.add('pv-stitch-selected'); + this.toggleParentClass(img, true); + }, + removeSelection: function(img) { + if (!this.selected.has(img)) return; + this.selected.delete(img); + img.classList.remove('pv-stitch-selected'); + this.toggleParentClass(img, false); + }, + toggleSelection: function(img, mode) { + if (mode === 'remove') { + this.removeSelection(img); + return; + } + if (this.selected.has(img)) this.removeSelection(img); + else this.addSelection(img); + }, + toggleParentClass: function(img, isAdd) { + const parent = img && img.parentNode; + if (!parent || parent.nodeType !== 1) return; + const tag = parent.tagName; + if (tag === 'BODY' || tag === 'HTML') return; + parent.classList.toggle('pv-stitch-selected-parent', isAdd); + }, + findImageAt: function(x, y) { + const els = document.elementsFromPoint(x, y); + for (let i = 0; i < els.length; i++) { + const el = els[i]; + if (this.overlay && this.overlay.contains(el)) continue; + if (el.tagName === 'IMG') return el; + } + return null; + }, + getSelectableImages: function() { + const imgs = document.querySelectorAll('img'); + const list = []; + for (let i = 0; i < imgs.length; i++) { + const img = imgs[i]; + if (!img.src && !img.currentSrc) continue; + const style = unsafeWindow.getComputedStyle(img); + if (style.display === 'none' || style.visibility === 'hidden') continue; + const rect = img.getBoundingClientRect(); + if (rect.width < 4 || rect.height < 4) continue; + list.push(img); + } + return list; + }, + getImgSrc: function(img) { + const original = this.getOriginalImgSrc(img); + if (original) return original; + return img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('data-original') || img.getAttribute('data-lazy-src') || img.getAttribute('data-zoom-image') || img.getAttribute('data-large'); + }, + getOriginalImgSrc: function(img) { + if (!img) return ""; + let imgPA, imgPE = []; + let imgPN = img.parentNode; + while (imgPN && imgPN.nodeType === 1 && imgPN.nodeName !== "BODY") { + if (!imgPA && imgPN.nodeName === "A") imgPA = imgPN; + imgPE.push(imgPN); + imgPN = imgPN.parentNode; + } + try { + if (matchedRule && matchedRule.rules && matchedRule.rules.length > 0 && matchedRule.getImage) { + let src = matchedRule.getImage(img, imgPA, imgPE); + if (Array.isArray(src)) src = src[0]; + if (src && src !== img.src) return src; + } + } catch (e) {} + try { + if (tprules && tprules[0]) { + const src = tprules[0].call(img); + if (src) return src; + } + } catch (e) {} + if (imgPA && imgPA.href && imageReg.test(imgPA.href) && imgPA.href !== img.src) { + return imgPA.href; + } + return ""; + }, + openPreview: function(selected) { + const srcs = []; + selected.forEach((img) => { + const src = this.getImgSrc(img); + if (src) srcs.push(src); + img.classList.remove('pv-stitch-selected'); + this.toggleParentClass(img, false); + }); + this.selected.clear(); + if (!srcs.length) return; + if (this.previewing) return; + this.previewing = true; + this.addStyle(); + const overlay = document.createElement('div'); + overlay.className = 'pv-stitch-overlay pv-stitch-preview'; + const isVertical = this.layout === 'column'; + overlay.innerHTML = createHTML( + '
'+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + ''+ + '
'+ + '
' + ); + document.body.appendChild(overlay); + this.preview = overlay; + const itemsCon = overlay.querySelector('.pv-stitch-items'); + const stage = overlay.querySelector('.pv-stitch-stage'); + const actionPanel = overlay.querySelector('.pv-stitch-panel-action'); + this.placeActionPanel(stage, actionPanel); + srcs.forEach((src) => { + const item = document.createElement('div'); + item.className = 'pv-stitch-item'; + item.setAttribute('draggable', 'true'); + item.dataset.src = src; + const img = document.createElement('img'); + img.src = src; + img.addEventListener('load', () => this.placeActionPanel(stage, actionPanel)); + item.appendChild(img); + itemsCon.appendChild(item); + }); + setTimeout(() => this.placeActionPanel(stage, actionPanel), 0); + let dragItem = null; + itemsCon.addEventListener('dragstart', (e) => { + const item = e.target.closest('.pv-stitch-item'); + if (!item) return; + dragItem = item; + dragItem.classList.add('dragging'); + e.dataTransfer.effectAllowed = 'move'; + }); + itemsCon.addEventListener('dragend', () => { + if (dragItem) dragItem.classList.remove('dragging'); + dragItem = null; + }); + itemsCon.addEventListener('dragover', (e) => { + if (!dragItem) return; + const target = e.target.closest('.pv-stitch-item'); + if (!target || target === dragItem) return; + e.preventDefault(); + const rect = target.getBoundingClientRect(); + const before = this.layout === 'row' ? e.clientX < rect.left + rect.width / 2 : e.clientY < rect.top + rect.height / 2; + itemsCon.insertBefore(dragItem, before ? target : target.nextSibling); + }); + + const btnHorizontal = overlay.querySelector('.pv-stitch-action-horizontal'); + const btnVertical = overlay.querySelector('.pv-stitch-action-vertical'); + btnHorizontal.addEventListener('click', () => { + this.setLayout('row', itemsCon, btnHorizontal, btnVertical); + this.placeActionPanel(stage, actionPanel); + }); + btnVertical.addEventListener('click', () => { + this.setLayout('column', itemsCon, btnHorizontal, btnVertical); + this.placeActionPanel(stage, actionPanel); + }); + overlay.querySelector('.pv-stitch-action-done').addEventListener('click', async () => { + const ordered = Array.from(itemsCon.querySelectorAll('.pv-stitch-item')).map(item => item.dataset.src); + this.closePreview(); + await this.renderStitch(ordered, this.layout); + }); + overlay.querySelector('.pv-stitch-action-cancel').addEventListener('click', () => { + this.closePreview(); + }); + document.addEventListener('keydown', this.onKeyDown, true); + }, + closePreview: function() { + if (!this.previewing) return; + this.previewing = false; + if (this.preview && this.preview.parentNode) { + this.preview.parentNode.removeChild(this.preview); + } + this.preview = null; + if (!this.selecting) document.removeEventListener('keydown', this.onKeyDown, true); + }, + placePanel: function(panel) { + if (!panel) return; + requestAnimationFrame(() => { + const rect = panel.getBoundingClientRect(); + const pad = 8; + const left = Math.min(Math.max(pad, (window.innerWidth - rect.width) / 2), window.innerWidth - rect.width - pad); + let top = Math.round(window.innerHeight * 0.72); + if (top + rect.height > window.innerHeight - pad) { + top = window.innerHeight - rect.height - pad; + } + if (top < pad) top = pad; + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + }); + }, + placePanelNear: function(panel, x, y) { + if (!panel) return; + requestAnimationFrame(() => { + const rect = panel.getBoundingClientRect(); + const pad = 8; + const maxX = Math.max(pad, window.innerWidth - rect.width - pad); + const maxY = Math.max(pad, window.innerHeight - rect.height - pad); + let left = x + 12; + let top = y + 12; + if (left > maxX) left = x - rect.width - 12; + if (top > maxY) top = y - rect.height - 12; + left = Math.min(Math.max(pad, left), maxX); + top = Math.min(Math.max(pad, top), maxY); + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + }); + }, + placeActionPanel: function(stage, panel) { + if (!stage || !panel) return; + requestAnimationFrame(() => { + const stageRect = stage.getBoundingClientRect(); + const panelRect = panel.getBoundingClientRect(); + const pad = 8; + const centerX = stageRect.left + stageRect.width / 2 - panelRect.width / 2; + let top = stageRect.bottom + 12; + let left = centerX; + if (top + panelRect.height > window.innerHeight - pad) { + top = stageRect.top - panelRect.height - 12; + } + left = Math.min(Math.max(pad, left), window.innerWidth - panelRect.width - pad); + top = Math.min(Math.max(pad, top), window.innerHeight - panelRect.height - pad); + panel.style.left = left + 'px'; + panel.style.top = top + 'px'; + }); + }, + setLayout: function(layout, itemsCon, btnHorizontal, btnVertical) { + this.layout = layout === 'column' ? 'column' : 'row'; + itemsCon.classList.toggle('pv-stitch-items-horizontal', this.layout === 'row'); + itemsCon.classList.toggle('pv-stitch-items-vertical', this.layout === 'column'); + btnHorizontal.classList.toggle('active', this.layout === 'row'); + btnVertical.classList.toggle('active', this.layout === 'column'); + }, + loadImageForStitch: function(src) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + let revoke = null; + const onLoad = () => resolve({img, revoke}); + const onError = () => reject(new Error('load failed')); + img.onload = onLoad; + img.onerror = onError; + if (/^data:|^blob:/i.test(src)) { + img.src = src; + return; + } + _GM_xmlhttpRequest({ + method: 'GET', + url: src, + responseType: 'blob', + onload: (response) => { + if (response.response instanceof Blob) { + const blobUrl = URL.createObjectURL(response.response); + revoke = () => URL.revokeObjectURL(blobUrl); + img.src = blobUrl; + return; + } + img.src = src; + }, + onerror: onError + }); + }); + }, + renderStitch: async function(srcs, layout) { + if (!srcs || !srcs.length) return; + try { + const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); + let totalW = 0; + let totalH = 0; + if (layout === 'column') { + loaded.forEach(({img}) => { + totalW = Math.max(totalW, img.naturalWidth || img.width); + totalH += img.naturalHeight || img.height; + }); + } else { + loaded.forEach(({img}) => { + totalW += img.naturalWidth || img.width; + totalH = Math.max(totalH, img.naturalHeight || img.height); + }); + } + const canvas = document.createElement('canvas'); + canvas.width = Math.max(1, totalW); + canvas.height = Math.max(1, totalH); + const ctx = canvas.getContext('2d'); + let offset = 0; + loaded.forEach(({img}) => { + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + if (layout === 'column') { + ctx.drawImage(img, 0, offset, w, h); + offset += h; + } else { + ctx.drawImage(img, offset, 0, w, h); + offset += w; + } + }); + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + loaded.forEach(({revoke}) => revoke && revoke()); + if (!blob) return; + const blobUrl = unsafeWindow.URL.createObjectURL(blob); + const outImg = new Image(); + outImg.src = blobUrl; + outImg.title = document.title || ''; + outImg.alt = outImg.title; + const data = {img: outImg, imgSrc: blobUrl, src: blobUrl, srcs: [blobUrl]}; + new ImgWindowC(outImg, data, true); + } catch (e) { + console.warn('[Picviewer CE+] stitch failed', e); + } + } + }; + + var stitcher = new StitcherC(); + var lastPopupLoading; // 载入动画 function LoadingAnimC(data, buttonType, waitImgLoad, openInTopWindow, initPos) { @@ -24109,7 +24742,7 @@ ImgOps | https://imgops.com/#b#`; var container=document.createElement('span'); container.id='pv-float-bar-container'; getBody(document).appendChild(container); - for(let i=0;i<5;i++){ + for(let i=0;i win.remove(true)); + } + if (stitcher) stitcher.openSelect(); + return; + } if (this.data.imgSrc.indexOf("blob:") === 0) { let blobUrl = await getBase64FromBlobUrl(this.data.imgSrc); if (blobUrl) { @@ -27530,6 +28179,12 @@ ImgOps | https://imgops.com/#b#`; } _GM_registerMenuCommand(i18n("openConfig"), openPrefs); _GM_registerMenuCommand(i18n("openGallery"), openGallery); + _GM_registerMenuCommand(i18n("stitch"), () => { + if (ImgWindowC.all && ImgWindowC.all.length) { + ImgWindowC.all.slice(0).forEach(win => win.remove(true)); + } + if (stitcher) stitcher.openSelect(); + }); _GM_registerMenuCommand(i18n("hideIcon") + (hideIcon ? "☑️" : ""), () => { hideIcon=!hideIcon; storage.setListItem("hideIcon", location.hostname, hideIcon); From 24d209fab8700ffa90905187533a8e02709250a5 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 13 Jan 2026 13:40:24 +0900 Subject: [PATCH 15/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index b49a6c450e5..e9ed452c7bb 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -24165,6 +24165,9 @@ ImgOps | https://imgops.com/#b#`; if (top + panelRect.height > window.innerHeight - pad) { top = stageRect.top - panelRect.height - 12; } + if (top + panelRect.height > window.innerHeight - pad || top < pad) { + top = window.innerHeight - panelRect.height - pad; + } left = Math.min(Math.max(pad, left), window.innerWidth - panelRect.width - pad); top = Math.min(Math.max(pad, top), window.innerHeight - panelRect.height - pad); panel.style.left = left + 'px'; From f35b1d7166aa90ffe66698e97e1cea9e8a878401 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 13 Jan 2026 05:01:31 +0000 Subject: [PATCH 16/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 5ca8e4bcf44..f0ee22da405 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -24165,6 +24165,9 @@ ImgOps | https://imgops.com/#b#`; if (top + panelRect.height > window.innerHeight - pad) { top = stageRect.top - panelRect.height - 12; } + if (top + panelRect.height > window.innerHeight - pad || top < pad) { + top = window.innerHeight - panelRect.height - pad; + } left = Math.min(Math.max(pad, left), window.innerWidth - panelRect.width - pad); top = Math.min(Math.max(pad, top), window.innerHeight - panelRect.height - pad); panel.style.left = left + 'px'; From 8b008d9d1c7ccc8857d6cd712bb6477f50381e09 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 13 Jan 2026 16:09:41 +0900 Subject: [PATCH 17/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 118 +++++++++++++++++----------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index e9ed452c7bb..69b14bfd5a8 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -23984,8 +23984,6 @@ ImgOps | https://imgops.com/#b#`; return list; }, getImgSrc: function(img) { - const original = this.getOriginalImgSrc(img); - if (original) return original; return img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('data-original') || img.getAttribute('data-lazy-src') || img.getAttribute('data-zoom-image') || img.getAttribute('data-large'); }, getOriginalImgSrc: function(img) { @@ -24016,15 +24014,22 @@ ImgOps | https://imgops.com/#b#`; return ""; }, openPreview: function(selected) { - const srcs = []; + const sources = []; selected.forEach((img) => { - const src = this.getImgSrc(img); - if (src) srcs.push(src); + const current = this.getImgSrc(img); + const original = this.getOriginalImgSrc(img); + const displaySrc = current || original; + if (displaySrc) { + sources.push({ + current: displaySrc, + original: original && original !== displaySrc ? original : '' + }); + } img.classList.remove('pv-stitch-selected'); this.toggleParentClass(img, false); }); this.selected.clear(); - if (!srcs.length) return; + if (!sources.length) return; if (this.previewing) return; this.previewing = true; this.addStyle(); @@ -24058,13 +24063,14 @@ ImgOps | https://imgops.com/#b#`; const stage = overlay.querySelector('.pv-stitch-stage'); const actionPanel = overlay.querySelector('.pv-stitch-panel-action'); this.placeActionPanel(stage, actionPanel); - srcs.forEach((src) => { + sources.forEach((source) => { const item = document.createElement('div'); item.className = 'pv-stitch-item'; item.setAttribute('draggable', 'true'); - item.dataset.src = src; + item.dataset.src = source.current; + if (source.original) item.dataset.original = source.original; const img = document.createElement('img'); - img.src = src; + img.src = source.current; img.addEventListener('load', () => this.placeActionPanel(stage, actionPanel)); item.appendChild(img); itemsCon.appendChild(item); @@ -24103,7 +24109,10 @@ ImgOps | https://imgops.com/#b#`; this.placeActionPanel(stage, actionPanel); }); overlay.querySelector('.pv-stitch-action-done').addEventListener('click', async () => { - const ordered = Array.from(itemsCon.querySelectorAll('.pv-stitch-item')).map(item => item.dataset.src); + const ordered = Array.from(itemsCon.querySelectorAll('.pv-stitch-item')).map(item => ({ + current: item.dataset.src, + original: item.dataset.original || '' + })); this.closePreview(); await this.renderStitch(ordered, this.layout); }); @@ -24211,49 +24220,68 @@ ImgOps | https://imgops.com/#b#`; }); }); }, - renderStitch: async function(srcs, layout) { - if (!srcs || !srcs.length) return; - try { - const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); - let totalW = 0; - let totalH = 0; + createStitchBlobUrl: async function(srcs, layout) { + const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); + let totalW = 0; + let totalH = 0; + if (layout === 'column') { + loaded.forEach(({img}) => { + totalW = Math.max(totalW, img.naturalWidth || img.width); + totalH += img.naturalHeight || img.height; + }); + } else { + loaded.forEach(({img}) => { + totalW += img.naturalWidth || img.width; + totalH = Math.max(totalH, img.naturalHeight || img.height); + }); + } + const canvas = document.createElement('canvas'); + canvas.width = Math.max(1, totalW); + canvas.height = Math.max(1, totalH); + const ctx = canvas.getContext('2d'); + let offset = 0; + loaded.forEach(({img}) => { + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; if (layout === 'column') { - loaded.forEach(({img}) => { - totalW = Math.max(totalW, img.naturalWidth || img.width); - totalH += img.naturalHeight || img.height; - }); + ctx.drawImage(img, 0, offset, w, h); + offset += h; } else { - loaded.forEach(({img}) => { - totalW += img.naturalWidth || img.width; - totalH = Math.max(totalH, img.naturalHeight || img.height); - }); + ctx.drawImage(img, offset, 0, w, h); + offset += w; } - const canvas = document.createElement('canvas'); - canvas.width = Math.max(1, totalW); - canvas.height = Math.max(1, totalH); - const ctx = canvas.getContext('2d'); - let offset = 0; - loaded.forEach(({img}) => { - const w = img.naturalWidth || img.width; - const h = img.naturalHeight || img.height; - if (layout === 'column') { - ctx.drawImage(img, 0, offset, w, h); - offset += h; - } else { - ctx.drawImage(img, offset, 0, w, h); - offset += w; - } - }); - const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); - loaded.forEach(({revoke}) => revoke && revoke()); - if (!blob) return; - const blobUrl = unsafeWindow.URL.createObjectURL(blob); + }); + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + loaded.forEach(({revoke}) => revoke && revoke()); + if (!blob) return ''; + return unsafeWindow.URL.createObjectURL(blob); + }, + renderStitch: async function(items, layout) { + if (!items || !items.length) return; + try { + const currentSrcs = items.map(item => item.current || item.src || item); + if (currentSrcs.some(src => !src)) return; + const blobUrl = await this.createStitchBlobUrl(currentSrcs, layout); + if (!blobUrl) return; const outImg = new Image(); outImg.src = blobUrl; outImg.title = document.title || ''; outImg.alt = outImg.title; const data = {img: outImg, imgSrc: blobUrl, src: blobUrl, srcs: [blobUrl]}; - new ImgWindowC(outImg, data, true); + const imgWin = new ImgWindowC(outImg, data, true); + const finalSrcs = items.map((item, index) => item.original || currentSrcs[index]); + const needsReplace = finalSrcs.some((src, index) => src && src !== currentSrcs[index]); + if (!needsReplace) return; + this.createStitchBlobUrl(finalSrcs, layout).then((finalBlobUrl) => { + if (!finalBlobUrl) return; + if (!imgWin || imgWin.removed) { + unsafeWindow.URL.revokeObjectURL(finalBlobUrl); + return; + } + imgWin.changeData({img: imgWin.img, imgSrc: finalBlobUrl, src: finalBlobUrl, srcs: [finalBlobUrl]}); + }).catch((e) => { + console.warn('[Picviewer CE+] stitch replace failed', e); + }); } catch (e) { console.warn('[Picviewer CE+] stitch failed', e); } From c8320dd05cd0416c308486a00bf91f16fe7c47c7 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 13 Jan 2026 07:09:58 +0000 Subject: [PATCH 18/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 118 +++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index f0ee22da405..427fe3c7eda 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -23984,8 +23984,6 @@ ImgOps | https://imgops.com/#b#`; return list; }, getImgSrc: function(img) { - const original = this.getOriginalImgSrc(img); - if (original) return original; return img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('data-original') || img.getAttribute('data-lazy-src') || img.getAttribute('data-zoom-image') || img.getAttribute('data-large'); }, getOriginalImgSrc: function(img) { @@ -24016,15 +24014,22 @@ ImgOps | https://imgops.com/#b#`; return ""; }, openPreview: function(selected) { - const srcs = []; + const sources = []; selected.forEach((img) => { - const src = this.getImgSrc(img); - if (src) srcs.push(src); + const current = this.getImgSrc(img); + const original = this.getOriginalImgSrc(img); + const displaySrc = current || original; + if (displaySrc) { + sources.push({ + current: displaySrc, + original: original && original !== displaySrc ? original : '' + }); + } img.classList.remove('pv-stitch-selected'); this.toggleParentClass(img, false); }); this.selected.clear(); - if (!srcs.length) return; + if (!sources.length) return; if (this.previewing) return; this.previewing = true; this.addStyle(); @@ -24058,13 +24063,14 @@ ImgOps | https://imgops.com/#b#`; const stage = overlay.querySelector('.pv-stitch-stage'); const actionPanel = overlay.querySelector('.pv-stitch-panel-action'); this.placeActionPanel(stage, actionPanel); - srcs.forEach((src) => { + sources.forEach((source) => { const item = document.createElement('div'); item.className = 'pv-stitch-item'; item.setAttribute('draggable', 'true'); - item.dataset.src = src; + item.dataset.src = source.current; + if (source.original) item.dataset.original = source.original; const img = document.createElement('img'); - img.src = src; + img.src = source.current; img.addEventListener('load', () => this.placeActionPanel(stage, actionPanel)); item.appendChild(img); itemsCon.appendChild(item); @@ -24103,7 +24109,10 @@ ImgOps | https://imgops.com/#b#`; this.placeActionPanel(stage, actionPanel); }); overlay.querySelector('.pv-stitch-action-done').addEventListener('click', async () => { - const ordered = Array.from(itemsCon.querySelectorAll('.pv-stitch-item')).map(item => item.dataset.src); + const ordered = Array.from(itemsCon.querySelectorAll('.pv-stitch-item')).map(item => ({ + current: item.dataset.src, + original: item.dataset.original || '' + })); this.closePreview(); await this.renderStitch(ordered, this.layout); }); @@ -24211,49 +24220,68 @@ ImgOps | https://imgops.com/#b#`; }); }); }, - renderStitch: async function(srcs, layout) { - if (!srcs || !srcs.length) return; - try { - const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); - let totalW = 0; - let totalH = 0; + createStitchBlobUrl: async function(srcs, layout) { + const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); + let totalW = 0; + let totalH = 0; + if (layout === 'column') { + loaded.forEach(({img}) => { + totalW = Math.max(totalW, img.naturalWidth || img.width); + totalH += img.naturalHeight || img.height; + }); + } else { + loaded.forEach(({img}) => { + totalW += img.naturalWidth || img.width; + totalH = Math.max(totalH, img.naturalHeight || img.height); + }); + } + const canvas = document.createElement('canvas'); + canvas.width = Math.max(1, totalW); + canvas.height = Math.max(1, totalH); + const ctx = canvas.getContext('2d'); + let offset = 0; + loaded.forEach(({img}) => { + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; if (layout === 'column') { - loaded.forEach(({img}) => { - totalW = Math.max(totalW, img.naturalWidth || img.width); - totalH += img.naturalHeight || img.height; - }); + ctx.drawImage(img, 0, offset, w, h); + offset += h; } else { - loaded.forEach(({img}) => { - totalW += img.naturalWidth || img.width; - totalH = Math.max(totalH, img.naturalHeight || img.height); - }); + ctx.drawImage(img, offset, 0, w, h); + offset += w; } - const canvas = document.createElement('canvas'); - canvas.width = Math.max(1, totalW); - canvas.height = Math.max(1, totalH); - const ctx = canvas.getContext('2d'); - let offset = 0; - loaded.forEach(({img}) => { - const w = img.naturalWidth || img.width; - const h = img.naturalHeight || img.height; - if (layout === 'column') { - ctx.drawImage(img, 0, offset, w, h); - offset += h; - } else { - ctx.drawImage(img, offset, 0, w, h); - offset += w; - } - }); - const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); - loaded.forEach(({revoke}) => revoke && revoke()); - if (!blob) return; - const blobUrl = unsafeWindow.URL.createObjectURL(blob); + }); + const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); + loaded.forEach(({revoke}) => revoke && revoke()); + if (!blob) return ''; + return unsafeWindow.URL.createObjectURL(blob); + }, + renderStitch: async function(items, layout) { + if (!items || !items.length) return; + try { + const currentSrcs = items.map(item => item.current || item.src || item); + if (currentSrcs.some(src => !src)) return; + const blobUrl = await this.createStitchBlobUrl(currentSrcs, layout); + if (!blobUrl) return; const outImg = new Image(); outImg.src = blobUrl; outImg.title = document.title || ''; outImg.alt = outImg.title; const data = {img: outImg, imgSrc: blobUrl, src: blobUrl, srcs: [blobUrl]}; - new ImgWindowC(outImg, data, true); + const imgWin = new ImgWindowC(outImg, data, true); + const finalSrcs = items.map((item, index) => item.original || currentSrcs[index]); + const needsReplace = finalSrcs.some((src, index) => src && src !== currentSrcs[index]); + if (!needsReplace) return; + this.createStitchBlobUrl(finalSrcs, layout).then((finalBlobUrl) => { + if (!finalBlobUrl) return; + if (!imgWin || imgWin.removed) { + unsafeWindow.URL.revokeObjectURL(finalBlobUrl); + return; + } + imgWin.changeData({img: imgWin.img, imgSrc: finalBlobUrl, src: finalBlobUrl, srcs: [finalBlobUrl]}); + }).catch((e) => { + console.warn('[Picviewer CE+] stitch replace failed', e); + }); } catch (e) { console.warn('[Picviewer CE+] stitch failed', e); } From 4b73ec962e79cd8cb106d413364646577399bf82 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 17 Jan 2026 18:26:04 +0900 Subject: [PATCH 19/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 47 ++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 69b14bfd5a8..b11d15cc288 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2026.1.13.1 +// @version 2026.1.17.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -24224,15 +24224,33 @@ ImgOps | https://imgops.com/#b#`; const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); let totalW = 0; let totalH = 0; + let maxW = 0; + let maxH = 0; + loaded.forEach(({img}) => { + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + if (w > maxW) maxW = w; + if (h > maxH) maxH = h; + }); if (layout === 'column') { loaded.forEach(({img}) => { - totalW = Math.max(totalW, img.naturalWidth || img.width); - totalH += img.naturalHeight || img.height; + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + const ratio = w ? (maxW / w) : 1; + const drawW = maxW || w; + const drawH = h * ratio; + totalW = Math.max(totalW, drawW); + totalH += drawH; }); } else { loaded.forEach(({img}) => { - totalW += img.naturalWidth || img.width; - totalH = Math.max(totalH, img.naturalHeight || img.height); + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + const ratio = h ? (maxH / h) : 1; + const drawH = maxH || h; + const drawW = w * ratio; + totalW += drawW; + totalH = Math.max(totalH, drawH); }); } const canvas = document.createElement('canvas'); @@ -24244,11 +24262,17 @@ ImgOps | https://imgops.com/#b#`; const w = img.naturalWidth || img.width; const h = img.naturalHeight || img.height; if (layout === 'column') { - ctx.drawImage(img, 0, offset, w, h); - offset += h; + const ratio = w ? (maxW / w) : 1; + const drawW = maxW || w; + const drawH = h * ratio; + ctx.drawImage(img, 0, offset, drawW, drawH); + offset += drawH; } else { - ctx.drawImage(img, offset, 0, w, h); - offset += w; + const ratio = h ? (maxH / h) : 1; + const drawH = maxH || h; + const drawW = w * ratio; + ctx.drawImage(img, offset, 0, drawW, drawH); + offset += drawW; } }); const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); @@ -28266,7 +28290,7 @@ ImgOps | https://imgops.com/#b#`; var configStyle = document.createElement("style"); configStyle.textContent = "#pv-prefs { display: initial; }"; configStyle.type = 'text/css'; - if (location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/") { + if ((location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/") || (location.hostname == "pv.hoothin.com" && location.pathname.indexOf("open-settings") !== -1)) { openPrefs(); } else if (location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/gallery.html") { let gallery = new GalleryC(); @@ -28382,7 +28406,7 @@ ImgOps | https://imgops.com/#b#`; setTimeout(()=>{ if (GM_config.frame && GM_config.frame.contentDocument.body.innerHTML === "") { - _GM_openInTab("https://hoothin.github.io/UserScripts/Picviewer%20CE+/", {active:true}); + _GM_openInTab("https://pv.hoothin.com/open-settings", {active:true}); return; } if (GM_config.frame && GM_config.frame.style && GM_config.frame.style.display == "none") { @@ -28420,6 +28444,7 @@ ImgOps | https://imgops.com/#b#`; try { if (localStorage && localStorage.setItem) { if (!storage.getItem('inited')) { + _GM_openInTab("https://pv.hoothin.com/first-run"); localStorage.setItem('picviewerCE.config.curTab', 4); storage.setItem('inited', true); } From c02f019c2cad42361ace182d397c17806b111823 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sat, 17 Jan 2026 09:26:18 +0000 Subject: [PATCH 20/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 47 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 427fe3c7eda..5ae597ad9f5 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2026.1.13.1 +// @version 2026.1.17.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -24224,15 +24224,33 @@ ImgOps | https://imgops.com/#b#`; const loaded = await Promise.all(srcs.map(src => this.loadImageForStitch(src))); let totalW = 0; let totalH = 0; + let maxW = 0; + let maxH = 0; + loaded.forEach(({img}) => { + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + if (w > maxW) maxW = w; + if (h > maxH) maxH = h; + }); if (layout === 'column') { loaded.forEach(({img}) => { - totalW = Math.max(totalW, img.naturalWidth || img.width); - totalH += img.naturalHeight || img.height; + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + const ratio = w ? (maxW / w) : 1; + const drawW = maxW || w; + const drawH = h * ratio; + totalW = Math.max(totalW, drawW); + totalH += drawH; }); } else { loaded.forEach(({img}) => { - totalW += img.naturalWidth || img.width; - totalH = Math.max(totalH, img.naturalHeight || img.height); + const w = img.naturalWidth || img.width; + const h = img.naturalHeight || img.height; + const ratio = h ? (maxH / h) : 1; + const drawH = maxH || h; + const drawW = w * ratio; + totalW += drawW; + totalH = Math.max(totalH, drawH); }); } const canvas = document.createElement('canvas'); @@ -24244,11 +24262,17 @@ ImgOps | https://imgops.com/#b#`; const w = img.naturalWidth || img.width; const h = img.naturalHeight || img.height; if (layout === 'column') { - ctx.drawImage(img, 0, offset, w, h); - offset += h; + const ratio = w ? (maxW / w) : 1; + const drawW = maxW || w; + const drawH = h * ratio; + ctx.drawImage(img, 0, offset, drawW, drawH); + offset += drawH; } else { - ctx.drawImage(img, offset, 0, w, h); - offset += w; + const ratio = h ? (maxH / h) : 1; + const drawH = maxH || h; + const drawW = w * ratio; + ctx.drawImage(img, offset, 0, drawW, drawH); + offset += drawW; } }); const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')); @@ -28266,7 +28290,7 @@ ImgOps | https://imgops.com/#b#`; var configStyle = document.createElement("style"); configStyle.textContent = "#pv-prefs { display: initial; }"; configStyle.type = 'text/css'; - if (location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/") { + if ((location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/") || (location.hostname == "pv.hoothin.com" && location.pathname.indexOf("open-settings") !== -1)) { openPrefs(); } else if (location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/gallery.html") { let gallery = new GalleryC(); @@ -28382,7 +28406,7 @@ ImgOps | https://imgops.com/#b#`; setTimeout(()=>{ if (GM_config.frame && GM_config.frame.contentDocument.body.innerHTML === "") { - _GM_openInTab("https://hoothin.github.io/UserScripts/Picviewer%20CE+/", {active:true}); + _GM_openInTab("https://pv.hoothin.com/open-settings", {active:true}); return; } if (GM_config.frame && GM_config.frame.style && GM_config.frame.style.display == "none") { @@ -28420,6 +28444,7 @@ ImgOps | https://imgops.com/#b#`; try { if (localStorage && localStorage.setItem) { if (!storage.getItem('inited')) { + _GM_openInTab("https://pv.hoothin.com/first-run"); localStorage.setItem('picviewerCE.config.curTab', 4); storage.setItem('inited', true); } From 3ef164265f2bff8373c9af59c1e30174bd12583a Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 19 Jan 2026 09:43:22 +0900 Subject: [PATCH 21/68] Update README.md --- Pagetual/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index 6077f2266b0..b24092e63dc 100644 --- a/Pagetual/README.md +++ b/Pagetual/README.md @@ -2,7 +2,7 @@ == *Pagetual - Perpetual pages. Auto loading paginated web pages for 90% of all web sites !* -🔧CONFIGURATION PAGE +🔧CONFIGURATION PAGE

@@ -33,7 +33,7 @@ https://raw.githubusercontent.com/hoothin/UserScripts/master/Pagetual/pagetualRu
         Made with ❤️ by Hoothin
     
     
-        
+        
     
 
 

From cb0d902e7f7ad2c9355f98f895733799bc538700 Mon Sep 17 00:00:00 2001
From: hoothin 
Date: Mon, 19 Jan 2026 10:02:34 +0900
Subject: [PATCH 22/68] Update pagetual.user.js

---
 Pagetual/pagetual.user.js | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js
index c84d06d613c..56682e7251e 100644
--- a/Pagetual/pagetual.user.js
+++ b/Pagetual/pagetual.user.js
@@ -9328,7 +9328,7 @@
                 importBtn.style.fontSize = "20px";
                 importBtn.addEventListener("click", e => {
                     let parentNode = importBtn.parentNode;
-                    if (!parentNode) return;
+                    if (!parentNode || !e.isTrusted) return;
                     parentNode.removeChild(importBtn);
                     try {
                         let rules = parentNode.innerText.trim();
@@ -9522,11 +9522,11 @@
                   padding: 0!important;
                  }
                  #saveBtn {
-                  width: 60vw;
+                  width: var(--config-width, 60vw);
                   position: fixed;
                   z-index: 999;
                   bottom: 0;
-                  left: 20vw;
+                  left: var(--config-left, 20vw);
                   font-size: xx-large;
                   opacity: 0.6;
                   cursor: pointer;
@@ -10021,6 +10021,7 @@
         }
 
         updateP.onclick = e => {
+            if (!e.isTrusted) return;
             updateFail = false;
             //ruleParser.rules = [];
             showTips(i18n("beginUpdate"), "", 30000);
@@ -10105,8 +10106,29 @@
         saveBtn.innerHTML = i18n("save");
         saveBtn.id = "saveBtn";
         configCon.appendChild(saveBtn);
+        saveBtn.style.display = "none";
+        const syncSaveBtnLayout = () => {
+            if (!configCon || !saveBtn) return;
+            const rect = configCon.getBoundingClientRect();
+            if (rect.width > 0) {
+                document.documentElement.style.setProperty("--config-left", `${rect.left}px`);
+                document.documentElement.style.setProperty("--config-width", `${rect.width}px`);
+                saveBtn.style.display = "";
+            } else {
+                saveBtn.style.display = "none";
+            }
+        };
+        syncSaveBtnLayout();
+        window.addEventListener("resize", syncSaveBtnLayout);
+        if (window.ResizeObserver) {
+            const saveBtnResizeObserver = new ResizeObserver(() => {
+                syncSaveBtnLayout();
+            });
+            saveBtnResizeObserver.observe(configCon);
+        }
         saveBtn.onclick = e => {
             try {
+                if (!e.isTrusted) return;
                 let customRules;
                 if (editor) {
                     if (editorChanged) {

From 16cd85d93141722d41dc03e53da4262a31182343 Mon Sep 17 00:00:00 2001
From: hoothin 
Date: Tue, 20 Jan 2026 11:38:26 +0900
Subject: [PATCH 23/68] Update README.md

---
 Pagetual/README.md | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/Pagetual/README.md b/Pagetual/README.md
index b24092e63dc..a0d35bb29cf 100644
--- a/Pagetual/README.md
+++ b/Pagetual/README.md
@@ -30,10 +30,7 @@ https://raw.githubusercontent.com/hoothin/UserScripts/master/Pagetual/pagetualRu
         Send 📧email
     
     
-        Made with ❤️ by Hoothin
-    
-    
-        
+        
Made with ❤️ by Hoothin From aa81734ae9a05ee5fa5d91e798ccf12c3c74a36b Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 20 Jan 2026 17:11:16 +0900 Subject: [PATCH 24/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index b11d15cc288..a9f1ad098dd 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -27420,6 +27420,8 @@ ImgOps | https://imgops.com/#b#`; "#pv-prefs .section_header_holder { padding-right: 10px; }", "#pv-prefs textarea { width: 100%; }", "#pv-prefs .nav-tabs { white-space: nowrap; width: fit-content; max-width: 100%; margin: 20 auto; display: flex; overflow-x: auto; overflow-y: visible; }", + "#pv-prefs_buttons_holder { position: fixed; bottom: 0; width: 100%; right: 10px; background: #EEE; }", + "#pv-prefs_wrapper { padding-bottom: 70px; }" ].join('\n'), fields: { // 浮动工具栏 @@ -28055,6 +28057,9 @@ ImgOps | https://imgops.com/#b#`; }, events: { open: async function(doc, win, frame) { + if (localStorage && localStorage.getItem && localStorage.getItem('picviewerCE.config.curTab') === null) { + localStorage.setItem('picviewerCE.config.curTab', 4); + } isConfigOpen = true; let saveBtn = doc.querySelector("#"+this.id+"_saveBtn"); let closeBtn = doc.querySelector("#"+this.id+"_closeBtn"); @@ -28442,12 +28447,9 @@ ImgOps | https://imgops.com/#b#`; } }); try { - if (localStorage && localStorage.setItem) { - if (!storage.getItem('inited')) { - _GM_openInTab("https://pv.hoothin.com/first-run"); - localStorage.setItem('picviewerCE.config.curTab', 4); - storage.setItem('inited', true); - } + if (!storage.getItem('inited')) { + _GM_openInTab("https://pv.hoothin.com/first-run", {active:true}); + storage.setItem('inited', true); } } catch(e) {} if (typeof prefs.gallery.formatConversion == 'undefined') { From 6f1c5ddcdf2ecbcb8e2e87633d9ea7599de72a83 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 20 Jan 2026 08:11:35 +0000 Subject: [PATCH 25/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 5ae597ad9f5..a5658d84035 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -27420,6 +27420,8 @@ ImgOps | https://imgops.com/#b#`; "#pv-prefs .section_header_holder { padding-right: 10px; }", "#pv-prefs textarea { width: 100%; }", "#pv-prefs .nav-tabs { white-space: nowrap; width: fit-content; max-width: 100%; margin: 20 auto; display: flex; overflow-x: auto; overflow-y: visible; }", + "#pv-prefs_buttons_holder { position: fixed; bottom: 0; width: 100%; right: 10px; background: #EEE; }", + "#pv-prefs_wrapper { padding-bottom: 70px; }" ].join('\n'), fields: { // 浮动工具栏 @@ -28055,6 +28057,9 @@ ImgOps | https://imgops.com/#b#`; }, events: { open: async function(doc, win, frame) { + if (localStorage && localStorage.getItem && localStorage.getItem('picviewerCE.config.curTab') === null) { + localStorage.setItem('picviewerCE.config.curTab', 4); + } isConfigOpen = true; let saveBtn = doc.querySelector("#"+this.id+"_saveBtn"); let closeBtn = doc.querySelector("#"+this.id+"_closeBtn"); @@ -28442,12 +28447,9 @@ ImgOps | https://imgops.com/#b#`; } }); try { - if (localStorage && localStorage.setItem) { - if (!storage.getItem('inited')) { - _GM_openInTab("https://pv.hoothin.com/first-run"); - localStorage.setItem('picviewerCE.config.curTab', 4); - storage.setItem('inited', true); - } + if (!storage.getItem('inited')) { + _GM_openInTab("https://pv.hoothin.com/first-run", {active:true}); + storage.setItem('inited', true); } } catch(e) {} if (typeof prefs.gallery.formatConversion == 'undefined') { From 6d9dcf44298e4abc38d139d025794204594edecd Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 20 Jan 2026 19:37:46 +0900 Subject: [PATCH 26/68] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index d1a878995eb..25a9bade564 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -302,8 +302,8 @@ var siteInfo = [ name: '花瓣网', url: /^https?:\/\/huaban\.com\//i, ext: 'previous-2', - r: [/(.*img.hb.aicdn.com\/.*)_fw(?:236|320)$/i, /_fw\d+\w+/i], - s: ['$1_fw658', ''], + r: [/(.*img.hb.aicdn.com\/.*)_fw(?:236|320)$/i, /(\/small)(\/.*)_fw\d+\w+/i, /_fw\d+\w+/i], + s: ['$1_fw658', '$2', ''], description: './../following-sibling::p[@class="description"]', exclude: /weixin_code\.png$/i }, From 649a918643b999f38796a074b4bb48aeecbc4083 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 20 Jan 2026 19:40:47 +0900 Subject: [PATCH 27/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index a9f1ad098dd..d78cf8b0efd 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -46,7 +46,7 @@ // @grant GM.notification // @grant unsafeWindow // @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js -// @require https://update.greasyfork.org/scripts/438080/1714183/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1738227/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1733533/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 7faf1e9246f26ba4369d26ab28454064dfa6713e Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 20 Jan 2026 10:40:58 +0000 Subject: [PATCH 28/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index a5658d84035..e9ba3d4b72b 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -46,7 +46,7 @@ // @grant GM.notification // @grant unsafeWindow // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=23710 -// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1714183 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1738227 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1733533 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 3a228321e99057fd147d02084735701c4031aef5 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 23 Jan 2026 10:15:14 +0900 Subject: [PATCH 29/68] Update pvcep_lang.js --- Picviewer CE+/pvcep_lang.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Picviewer CE+/pvcep_lang.js b/Picviewer CE+/pvcep_lang.js index 113b1cfa052..b65c8122ea3 100644 --- a/Picviewer CE+/pvcep_lang.js +++ b/Picviewer CE+/pvcep_lang.js @@ -117,6 +117,8 @@ const langData = [ config: "Settings", openConfig: "Open Settings", ruleRequest: "Rule Request", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "Close Gallery", returnToGallery: "Back to the Gallery", picInfo: "Click to change", @@ -390,6 +392,8 @@ const langData = [ config: "%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA", openConfig: "%D9%81%D8%AA%D8%AD%20%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA", ruleRequest: "%D8%B7%D9%84%D8%A8%20%D8%A7%D9%84%D9%82%D9%88%D8%A7%D8%B9%D8%AF", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "%D8%A5%D8%BA%D9%84%D8%A7%D9%82%20%D8%A7%D9%84%D9%85%D8%B9%D8%B1%D8%B6", returnToGallery: "%D8%A7%D9%84%D8%B9%D9%88%D8%AF%D8%A9%20%D8%A5%D9%84%D9%89%20%D8%A7%D9%84%D9%85%D8%B9%D8%B1%D8%B6", picInfo: "%D8%A7%D9%86%D9%82%D8%B1%20%D9%84%D8%AA%D8%BA%D9%8A%D9%8A%D8%B1", @@ -661,6 +665,8 @@ const langData = [ config: "设置", openConfig: "打开设置", ruleRequest: "适配请求", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "关闭库", returnToGallery: "回到库", picInfo: "点击修改", @@ -932,6 +938,8 @@ const langData = [ config: "設置", openConfig: "打開設置", ruleRequest: "適配請求", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "關閉庫", returnToGallery: "回到庫", picInfo: "點擊修改", @@ -1204,6 +1212,8 @@ const langData = [ config: "Configurações", openConfig: "Abrir configurações", ruleRequest: "Solicitar regra", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "Fechar galeria", returnToGallery: "Voltar para a Galeria", picInfo: "Clique para editar", @@ -1476,6 +1486,8 @@ const langData = [ config: "Configurações", openConfig: "Configurações", ruleRequest: "Pedido de regra", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "Sair da Galeria", returnToGallery: "Voltar para a Galeria", picInfo: "Clique para alterar", @@ -1748,6 +1760,8 @@ const langData = [ config: "Параметры", openConfig: "Открыть параметры", ruleRequest: "Запрос правил", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "Закрыть галерею", returnToGallery: "Вернуться в галерею", picInfo: "Нажмите, чтобы изменить", @@ -2020,6 +2034,8 @@ const langData = [ config: "Ayarlar", openConfig: "Ayarları Açın", ruleRequest: "Kural Talebi", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "Galeriyi kapatın", returnToGallery: "Galeriye geri dönün", picInfo: "Değiştirmek için tıklayın", @@ -2291,6 +2307,8 @@ const langData = [ config: "設定", openConfig: "設定を開く", ruleRequest: "ルール要求", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "ギャラリーを閉じる", returnToGallery: "ギャラリーに戻る", picInfo: "クリックして変更", @@ -2563,6 +2581,8 @@ const langData = [ config: "Налаштування", openConfig: "Відкрити налаштування", ruleRequest: "Правило Запит", + disableKeyForHost: "Disable shortcuts", + restoreKeyForHost: "Restore shortcuts", closeGallery: "Закрити галерею", returnToGallery: "Повернутись у галерею", picInfo: "Натисніть, щоб змінити", From 14c3308d019091a52aab926a61f9c9d26996d975 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 23 Jan 2026 10:28:18 +0900 Subject: [PATCH 30/68] Update pvcep_lang.js --- Picviewer CE+/pvcep_lang.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Picviewer CE+/pvcep_lang.js b/Picviewer CE+/pvcep_lang.js index b65c8122ea3..2bda9d64459 100644 --- a/Picviewer CE+/pvcep_lang.js +++ b/Picviewer CE+/pvcep_lang.js @@ -392,8 +392,8 @@ const langData = [ config: "%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA", openConfig: "%D9%81%D8%AA%D8%AD%20%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA", ruleRequest: "%D8%B7%D9%84%D8%A8%20%D8%A7%D9%84%D9%82%D9%88%D8%A7%D8%B9%D8%AF", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "%D8%AA%D8%B9%D8%B7%D9%8A%D9%84%20%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%B5%D8%A7%D8%B1%D8%A7%D8%AA", + restoreKeyForHost: "%D8%A7%D8%B3%D8%AA%D8%B9%D8%A7%D8%AF%D8%A9%20%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%B5%D8%A7%D8%B1%D8%A7%D8%AA", closeGallery: "%D8%A5%D8%BA%D9%84%D8%A7%D9%82%20%D8%A7%D9%84%D9%85%D8%B9%D8%B1%D8%B6", returnToGallery: "%D8%A7%D9%84%D8%B9%D9%88%D8%AF%D8%A9%20%D8%A5%D9%84%D9%89%20%D8%A7%D9%84%D9%85%D8%B9%D8%B1%D8%B6", picInfo: "%D8%A7%D9%86%D9%82%D8%B1%20%D9%84%D8%AA%D8%BA%D9%8A%D9%8A%D8%B1", @@ -665,8 +665,8 @@ const langData = [ config: "设置", openConfig: "打开设置", ruleRequest: "适配请求", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "禁用快捷键", + restoreKeyForHost: "恢复快捷键", closeGallery: "关闭库", returnToGallery: "回到库", picInfo: "点击修改", @@ -938,8 +938,8 @@ const langData = [ config: "設置", openConfig: "打開設置", ruleRequest: "適配請求", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "停用快速鍵", + restoreKeyForHost: "恢復快速鍵", closeGallery: "關閉庫", returnToGallery: "回到庫", picInfo: "點擊修改", @@ -1212,8 +1212,8 @@ const langData = [ config: "Configurações", openConfig: "Abrir configurações", ruleRequest: "Solicitar regra", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "Desativar atalhos", + restoreKeyForHost: "Restaurar atalhos", closeGallery: "Fechar galeria", returnToGallery: "Voltar para a Galeria", picInfo: "Clique para editar", @@ -1486,8 +1486,8 @@ const langData = [ config: "Configurações", openConfig: "Configurações", ruleRequest: "Pedido de regra", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "Desativar atalhos", + restoreKeyForHost: "Restaurar atalhos", closeGallery: "Sair da Galeria", returnToGallery: "Voltar para a Galeria", picInfo: "Clique para alterar", @@ -1760,8 +1760,8 @@ const langData = [ config: "Параметры", openConfig: "Открыть параметры", ruleRequest: "Запрос правил", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "Отключить сочетания клавиш", + restoreKeyForHost: "Восстановить сочетания клавиш", closeGallery: "Закрыть галерею", returnToGallery: "Вернуться в галерею", picInfo: "Нажмите, чтобы изменить", @@ -2034,8 +2034,8 @@ const langData = [ config: "Ayarlar", openConfig: "Ayarları Açın", ruleRequest: "Kural Talebi", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "Kısayolları devre dışı bırak", + restoreKeyForHost: "Kısayolları geri yükle", closeGallery: "Galeriyi kapatın", returnToGallery: "Galeriye geri dönün", picInfo: "Değiştirmek için tıklayın", @@ -2307,8 +2307,8 @@ const langData = [ config: "設定", openConfig: "設定を開く", ruleRequest: "ルール要求", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "ショートカットを無効にする", + restoreKeyForHost: "ショートカットを元に戻す", closeGallery: "ギャラリーを閉じる", returnToGallery: "ギャラリーに戻る", picInfo: "クリックして変更", @@ -2581,8 +2581,8 @@ const langData = [ config: "Налаштування", openConfig: "Відкрити налаштування", ruleRequest: "Правило Запит", - disableKeyForHost: "Disable shortcuts", - restoreKeyForHost: "Restore shortcuts", + disableKeyForHost: "Вимкнути комбінації клавіш", + restoreKeyForHost: "Відновити комбінації клавіш", closeGallery: "Закрити галерею", returnToGallery: "Повернутись у галерею", picInfo: "Натисніть, щоб змінити", From be1e07b35b945c23cba0c7b4b2827a64c081d9e2 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 23 Jan 2026 10:29:59 +0900 Subject: [PATCH 31/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 76 ++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index d78cf8b0efd..dc08df12a53 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2026.1.17.1 +// @version 2026.1.23.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -47,7 +47,7 @@ // @grant unsafeWindow // @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js // @require https://update.greasyfork.org/scripts/438080/1738227/pvcep_rules.js -// @require https://update.greasyfork.org/scripts/440698/1733533/pvcep_lang.js +// @require https://update.greasyfork.org/scripts/440698/1740314/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -28254,6 +28254,30 @@ ImgOps | https://imgops.com/#b#`; document.head.removeChild(hideIconStyle); } }); + function buildDisableKeyPattern() { + let originPattern = location.origin.replace(/^https?/, "https?").replace(/\./g, "\\."); + let pathBase = location.pathname.replace(/[^\/]*$/, ""); + return "^" + originPattern + pathBase; + } + function isDisableKeyPatternMatched() { + let pattern = buildDisableKeyPattern(); + let list = normalizeDisableKeySites(prefs.floatBar.disableKeySites); + return list.indexOf(pattern) !== -1; + } + _GM_registerMenuCommand( + isDisableKeyPatternMatched() ? i18n("restoreKeyForHost") : i18n("disableKeyForHost"), + () => { + let pattern = buildDisableKeyPattern(); + let list = normalizeDisableKeySites(prefs.floatBar.disableKeySites); + if (list.indexOf(pattern) === -1) { + list.unshift(pattern); + saveDisableKeySites(list); + } else { + let nextList = list.filter(item => item !== pattern); + saveDisableKeySites(nextList); + } + } + ); _GM_registerMenuCommand(i18n("ruleRequest"), () => { _GM_openInTab("https://github.com/hoothin/UserScripts/issues/new?labels=Picviewer%20CE%2B&template=custom-rule-request.md&title=Request%20Picviewer%20CE%2B%20support%20for%20" + location.hostname, {active:true}); }); @@ -28346,19 +28370,47 @@ ImgOps | https://imgops.com/#b#`; } // 注册按键 - let disableKey = false; - if (prefs.floatBar.disableKeySites) { - let sitesArr = prefs.floatBar.disableKeySites.split("\n"); - for(let s = 0; s < sitesArr.length; s++) { - let siteReg = sitesArr[s].trim(); - if (new RegExp(siteReg).test(_URL)) { - disableKey = true; - break; + function normalizeDisableKeySites(value) { + if (!value) return []; + return value.split("\n").map(s => s.trim()).filter(Boolean); + } + + function isKeyDisabledForUrl(url, host, list) { + for (let i = 0; i < list.length; i++) { + let siteReg = list[i]; + try { + let reg = new RegExp(siteReg); + if (reg.test(url) || reg.test(host)) return true; + } catch (e) { } } + return false; } - if (!disableKey) { - document.addEventListener('keydown', keydown, true); + + let keydownBound = false; + function updateKeydownListener() { + let list = normalizeDisableKeySites(prefs.floatBar.disableKeySites); + let disableKey = isKeyDisabledForUrl(_URL, location.hostname, list); + if (!disableKey && !keydownBound) { + document.addEventListener('keydown', keydown, true); + keydownBound = true; + } else if (disableKey && keydownBound) { + document.removeEventListener('keydown', keydown, true); + keydownBound = false; + } + } + updateKeydownListener(); + + function saveDisableKeySites(list) { + let value = list.join("\n"); + prefs.floatBar.disableKeySites = value; + if (GM_config && GM_config.set) { + GM_config.set('floatBar.disableKeySites', value); + let field = GM_config.fields && GM_config.fields['floatBar.disableKeySites']; + if (field && field.node) field.node.value = value; + GM_config.save(); + } + updateKeydownListener(); } let canImport = false; From 95c543b6ccd8659d25c9c92bfdd66f6714b74186 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Fri, 23 Jan 2026 01:30:08 +0000 Subject: [PATCH 32/68] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 76 ++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index e9ba3d4b72b..856071eb35f 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -12,7 +12,7 @@ // @description:ja 画像を強力に閲覧できるツール。ポップアップ表示、拡大・縮小、回転、一括保存などの機能を自動で実行できます // @description:pt-BR Poderosa ferramenta de visualização de imagens on-line, que pode pop-up/dimensionar/girar/salvar em lote imagens automaticamente // @description:ru Мощный онлайн-инструмент для просмотра изображений, который может автоматически отображать/масштабировать/вращать/пакетно сохранять изображения -// @version 2026.1.17.1 +// @version 2026.1.23.1 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg== // @namespace https://github.com/hoothin/UserScripts // @homepage https://pv.hoothin.com/ @@ -47,7 +47,7 @@ // @grant unsafeWindow // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=23710 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1738227 -// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1733533 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1740314 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -28254,6 +28254,30 @@ ImgOps | https://imgops.com/#b#`; document.head.removeChild(hideIconStyle); } }); + function buildDisableKeyPattern() { + let originPattern = location.origin.replace(/^https?/, "https?").replace(/\./g, "\\."); + let pathBase = location.pathname.replace(/[^\/]*$/, ""); + return "^" + originPattern + pathBase; + } + function isDisableKeyPatternMatched() { + let pattern = buildDisableKeyPattern(); + let list = normalizeDisableKeySites(prefs.floatBar.disableKeySites); + return list.indexOf(pattern) !== -1; + } + _GM_registerMenuCommand( + isDisableKeyPatternMatched() ? i18n("restoreKeyForHost") : i18n("disableKeyForHost"), + () => { + let pattern = buildDisableKeyPattern(); + let list = normalizeDisableKeySites(prefs.floatBar.disableKeySites); + if (list.indexOf(pattern) === -1) { + list.unshift(pattern); + saveDisableKeySites(list); + } else { + let nextList = list.filter(item => item !== pattern); + saveDisableKeySites(nextList); + } + } + ); _GM_registerMenuCommand(i18n("ruleRequest"), () => { _GM_openInTab("https://github.com/hoothin/UserScripts/issues/new?labels=Picviewer%20CE%2B&template=custom-rule-request.md&title=Request%20Picviewer%20CE%2B%20support%20for%20" + location.hostname, {active:true}); }); @@ -28346,19 +28370,47 @@ ImgOps | https://imgops.com/#b#`; } // 注册按键 - let disableKey = false; - if (prefs.floatBar.disableKeySites) { - let sitesArr = prefs.floatBar.disableKeySites.split("\n"); - for(let s = 0; s < sitesArr.length; s++) { - let siteReg = sitesArr[s].trim(); - if (new RegExp(siteReg).test(_URL)) { - disableKey = true; - break; + function normalizeDisableKeySites(value) { + if (!value) return []; + return value.split("\n").map(s => s.trim()).filter(Boolean); + } + + function isKeyDisabledForUrl(url, host, list) { + for (let i = 0; i < list.length; i++) { + let siteReg = list[i]; + try { + let reg = new RegExp(siteReg); + if (reg.test(url) || reg.test(host)) return true; + } catch (e) { } } + return false; } - if (!disableKey) { - document.addEventListener('keydown', keydown, true); + + let keydownBound = false; + function updateKeydownListener() { + let list = normalizeDisableKeySites(prefs.floatBar.disableKeySites); + let disableKey = isKeyDisabledForUrl(_URL, location.hostname, list); + if (!disableKey && !keydownBound) { + document.addEventListener('keydown', keydown, true); + keydownBound = true; + } else if (disableKey && keydownBound) { + document.removeEventListener('keydown', keydown, true); + keydownBound = false; + } + } + updateKeydownListener(); + + function saveDisableKeySites(list) { + let value = list.join("\n"); + prefs.floatBar.disableKeySites = value; + if (GM_config && GM_config.set) { + GM_config.set('floatBar.disableKeySites', value); + let field = GM_config.fields && GM_config.fields['floatBar.disableKeySites']; + if (field && field.node) field.node.value = value; + GM_config.save(); + } + updateKeydownListener(); } let canImport = false; From 864431639548fd3f77a85a669e4eae0a5798a57c Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 31 Jan 2026 18:24:23 +0900 Subject: [PATCH 33/68] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 213 ++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 63 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index dc08df12a53..1cb566a73bd 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -11925,7 +11925,7 @@ var floatBar; 'use strict'; //var siteInfo = [{}]; - var debug; + var debug = console.log.bind(console); var lang; function initLang(){ let customLang=storage.getItem("customLang")||'auto'; @@ -12744,24 +12744,6 @@ ImgOps | https://imgops.com/#b#`; return; } catch (e) { } - - const existingPolicies = new Set(unsafeWindow.trustedTypes.getPolicyNames()); - for (const name of allowedNames) { - if (name === '*' || existingPolicies.has(name)) { - continue; - } - - try { - escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy(name, { - createHTML: (string, sink) => string, - createScriptURL: string => string, - createScript: string => string - }); - return; - } catch (e) { - debug(`create '${name}' failed, trying next...`); - } - } debug("Could not create any trusted types policy."); } @@ -12771,7 +12753,107 @@ ImgOps | https://imgops.com/#b#`; return doc.body || doc.querySelector('body') || doc; } function createHTML(html){ - return escapeHTMLPolicy?escapeHTMLPolicy.createHTML(html):html; + const fragment = document.createDocumentFragment(); + if (html === null || html === undefined || html === '') return fragment; + parseHTMLToFragment(String(html), fragment, document); + return fragment; + } + const SVG_NS = 'http://www.w3.org/2000/svg'; + const VOID_TAGS = { + area: true, + base: true, + br: true, + col: true, + embed: true, + hr: true, + img: true, + input: true, + link: true, + meta: true, + param: true, + source: true, + track: true, + wbr: true + }; + const HTML_ENTITIES = { + amp: '&', + lt: '<', + gt: '>', + quot: '"', + apos: "'", + nbsp: '\u00A0' + }; + function decodeEntities(text){ + return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, function(_, code){ + if (code[0] === '#') { + const isHex = code[1] === 'x' || code[1] === 'X'; + const num = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10); + if (!isNaN(num)) { + try { return String.fromCodePoint(num); } catch(e) {} + } + return '&' + code + ';'; + } + const key = code.toLowerCase(); + return (key in HTML_ENTITIES) ? HTML_ENTITIES[key] : '&' + code + ';'; + }); + } + function parseHTMLToFragment(html, fragment, doc){ + const stack = [fragment]; + const tokenRe = /|<\/?[a-zA-Z][^>]*>|[^<]+/g; + let match; + while ((match = tokenRe.exec(html))) { + const token = match[0]; + if (token[0] !== '<') { + const text = decodeEntities(token); + if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text)); + continue; + } + if (token.indexOf('|<\/?[a-zA-Z][^>]*>|[^<]+/g; + let match; + while ((match = tokenRe.exec(html))) { + const token = match[0]; + if (token[0] !== '<') { + const text = decodeEntities(token); + if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text)); + continue; + } + if (token.indexOf('|<\/?[a-zA-Z][^>]*>|[^<]+/g; + let match; + while ((match = tokenRe.exec(html))) { + const token = match[0]; + if (token[0] !== '<') { + const text = decodeEntities(token); + if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text)); + continue; } - if (metaResult.ttDirectiveFound) { - combinedTtDirectiveFound = true; + if (token.indexOf('|<\/?[a-zA-Z][^>]*>|[^<]+/g; + const tokenRe = /|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi; let match; while ((match = tokenRe.exec(html))) { const token = match[0]; @@ -4626,6 +4639,9 @@ if (token.indexOf('|<\/?[a-zA-Z][^>]*>|[^<]+/g; + const tokenRe = /|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi; let match; while ((match = tokenRe.exec(html))) { const token = match[0]; @@ -12828,6 +12841,9 @@ ImgOps | https://imgops.com/#b#`; if (token.indexOf('|<\/?[a-zA-Z][^>]*>|[^<]+/g; + const tokenRe = /|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi; let match; while ((match = tokenRe.exec(html))) { const token = match[0]; @@ -12828,6 +12841,9 @@ ImgOps | https://imgops.com/#b#`; if (token.indexOf('