From da7e6bf6dec98b58911bd57c4ebbcc99c6fb2a5b Mon Sep 17 00:00:00 2001 From: Miyuki <50022810+Boxkun@users.noreply.github.com> Date: Tue, 8 Jul 2025 02:29:08 +0800 Subject: [PATCH 01/99] Update pvcep_rules.js Add GIF rules for Gelbooru --- Picviewer CE+/pvcep_rules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index cd40402fd3c..ad9b6e3b70e 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -930,7 +930,7 @@ var siteInfo = [ url: /gelbooru\.com/, src: /(thumbnails|samples)\/(.*)\/(thumbnail|sample)_/i, r: /.*\/(thumbnails|samples)\/(.*)\/(thumbnail|sample)_(.*)\..*/i, - s: ["https://img4.gelbooru.com/images/$2/$4.png","https://img4.gelbooru.com/images/$2/$4.jpg","https://img4.gelbooru.com/images/$2/$4.jpeg"] + s: ["https://img4.gelbooru.com/images/$2/$4.png","https://img4.gelbooru.com/images/$2/$4.jpg","https://img4.gelbooru.com/images/$2/$4.jpeg","https://img4.gelbooru.com/images/$2/$4.gif"] }, { name: "donmai", From c55d0703088768187a3c859e14dcbf32d47f55c9 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 27 Oct 2025 21:53:59 +0900 Subject: [PATCH 02/99] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 3b841f72517..4ede058bfda 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.10.22.1 +// @version 2025.10.27.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 @@ -13627,6 +13627,9 @@ ImgOps | https://imgops.com/#b#`; }break; case 'error':{ removeListener(); + iRReadyFn = iRReadyFn.filter(function(item) { + return item !== readyHandler + }); if(error){ error.call(img,e); }; @@ -13703,13 +13706,8 @@ ImgOps | https://imgops.com/#b#`; setTimeout(() => { if (aborted) return; if (typeof img.width == 'number' && img.width && img.height) { - go('load', { - type:'load', - target: img, - }); } else { errorBlobList[orgSrc] = true; - go('error',e); } }, 0); }, From 6fb19d626a91e8511dc110c781c11b6c8b7de143 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Mon, 27 Oct 2025 12:54:15 +0000 Subject: [PATCH 03/99] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index e25371e7230..68e33c7db3d 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.10.22.1 +// @version 2025.10.27.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 @@ -13627,6 +13627,9 @@ ImgOps | https://imgops.com/#b#`; }break; case 'error':{ removeListener(); + iRReadyFn = iRReadyFn.filter(function(item) { + return item !== readyHandler + }); if(error){ error.call(img,e); }; @@ -13703,13 +13706,8 @@ ImgOps | https://imgops.com/#b#`; setTimeout(() => { if (aborted) return; if (typeof img.width == 'number' && img.width && img.height) { - go('load', { - type:'load', - target: img, - }); } else { errorBlobList[orgSrc] = true; - go('error',e); } }, 0); }, From 64a3ec226ac5dbeabee0e1e05a82ece3b064ccc4 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 27 Oct 2025 22:16:14 +0900 Subject: [PATCH 04/99] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 122 ++++++++++++++++++---------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 4ede058bfda..445482c9340 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12458,32 +12458,39 @@ ImgOps | https://imgops.com/#b#`; function parseTrustedTypes(cspString) { const policies = new Set(); + let allowDuplicates = false; + let ttDirectiveFound = false; const ttRegex = /trusted-types\s+([^;]+)/gi; let match; + while ((match = ttRegex.exec(cspString)) !== null) { + ttDirectiveFound = true; match[1].trim().split(/\s+/) .forEach(name => { - if (name !== "'allow-duplicates'" && name !== "'none'") { - policies.add(name.replace(/'/g, '')); - } - }); + if (name === "'allow-duplicates'") { + allowDuplicates = true; + } else if (name !== "'none'") { + policies.add(name.replace(/'/g, '')); + } + }); } - return Array.from(policies); + return { names: policies, allowDuplicates: allowDuplicates, ttDirectiveFound: ttDirectiveFound }; } - async function getAvailablePolicyNamesOptimized() { - if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.getPolicyNames) { - const existingNames = unsafeWindow.trustedTypes.getPolicyNames(); - if (existingNames.length > 0) { - return new Set(existingNames); - } - } + async function getCspTrustedTypesInfo() { + const combinedPolicies = new Set(); + let combinedAllowDuplicates = false; + let combinedTtDirectiveFound = false; const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (meta) { - const metaNames = parseTrustedTypes(meta.content); - if (metaNames.length > 0) { - return new Set(metaNames); + const metaResult = parseTrustedTypes(meta.content); + metaResult.names.forEach(name => combinedPolicies.add(name)); + if (metaResult.allowDuplicates) { + combinedAllowDuplicates = true; + } + if (metaResult.ttDirectiveFound) { + combinedTtDirectiveFound = true; } } @@ -12493,19 +12500,31 @@ ImgOps | https://imgops.com/#b#`; url: window.location.href, onload: function(response) { const cspHeader = response.responseHeaders.split('\r\n') - .filter(h => h.toLowerCase().startsWith('content-security-policy:')) - .map(h => h.substring(26).trim()) - .join('; '); - - const headerNames = parseTrustedTypes(cspHeader); - if (headerNames.length > 0) { - resolve(new Set(headerNames)); - } else { - resolve(new Set()); + .filter(h => h.toLowerCase().startsWith('content-security-policy:')) + .map(h => h.substring(26).trim()) + .join('; '); + + const headerResult = parseTrustedTypes(cspHeader); + headerResult.names.forEach(name => combinedPolicies.add(name)); + if (headerResult.allowDuplicates) { + combinedAllowDuplicates = true; } + if (headerResult.ttDirectiveFound) { + combinedTtDirectiveFound = true; + } + + resolve({ + names: combinedPolicies, + allowDuplicates: combinedAllowDuplicates, + ttDirectiveFound: combinedTtDirectiveFound + }); }, onerror: function(error) { - resolve(new Set()); + resolve({ + names: combinedPolicies, + allowDuplicates: combinedAllowDuplicates, + ttDirectiveFound: combinedTtDirectiveFound + }); } }); }); @@ -12521,33 +12540,48 @@ ImgOps | https://imgops.com/#b#`; } async function createPolicy() { - if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced()) { - const allowedNames = await getAvailablePolicyNamesOptimized(); + if (!(unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced())) { + return; + } + + const { names: allowedNames, allowDuplicates, ttDirectiveFound } = await getCspTrustedTypesInfo(); + + if (ttDirectiveFound && !allowDuplicates) { + debug("CSP Trusted Types is enforced without 'allow-duplicates'. " + + "Skipping policy creation to avoid conflicts with the page."); + return; + } - if (allowedNames.size === 0) { - escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy('pvcep_default', { + const MY_POLICY_NAME = 'pvcep_default'; + + try { + escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy(MY_POLICY_NAME, { + createHTML: (string, sink) => string, + createScriptURL: string => string, + createScript: string => string + }); + 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; - } - - for (const name of allowedNames) { - if (name === '*') continue; - try { - escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy(name, { - createHTML: (string, sink) => string, - createScriptURL: string => string, - createScript: string => string - }); - break; - } catch (e) { - console.warn(`create '${name}' failed`); - return; - } + } catch (e) { + debug(`create '${name}' failed, trying next...`); } } + debug("Could not create any trusted types policy."); } let escapeHTMLPolicy = null; From b26cbb3d63939d1009ca004e098cfc35448ca25e Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 27 Oct 2025 22:23:52 +0900 Subject: [PATCH 05/99] Update pagetual.user.js --- Pagetual/pagetual.user.js | 127 +++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index c644b963b10..ec17e5b593c 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -4562,32 +4562,40 @@ function parseTrustedTypes(cspString) { const policies = new Set(); + let allowDuplicates = false; + let ttDirectiveFound = false; const ttRegex = /trusted-types\s+([^;]+)/gi; let match; + while ((match = ttRegex.exec(cspString)) !== null) { - match[1].trim().split(/\s+/) - .forEach(name => { - if (name !== "'allow-duplicates'" && name !== "'none'") { - policies.add(name.replace(/'/g, '')); - } - }); - } - return Array.from(policies); - } + ttDirectiveFound = true; - async function getAvailablePolicyNamesOptimized() { - if (_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.getPolicyNames) { - const existingNames = _unsafeWindow.trustedTypes.getPolicyNames(); - if (existingNames.length > 0) { - return new Set(existingNames); + const policyNames = match[1].trim().split(/\s+/); + for (const name of policyNames) { + if (name === "'allow-duplicates'") { + allowDuplicates = true; + } else if (name !== "'none'") { + policies.add(name.replace(/'/g, '')); + } } } + return { names: policies, allowDuplicates: allowDuplicates, ttDirectiveFound: ttDirectiveFound }; + } + + async function getCspTrustedTypesInfo() { + const combinedPolicies = new Set(); + let combinedAllowDuplicates = false; + let combinedTtDirectiveFound = false; const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (meta) { - const metaNames = parseTrustedTypes(meta.content); - if (metaNames.length > 0) { - return new Set(metaNames); + const metaResult = parseTrustedTypes(meta.content); + metaResult.names.forEach(name => combinedPolicies.add(name)); + if (metaResult.allowDuplicates) { + combinedAllowDuplicates = true; + } + if (metaResult.ttDirectiveFound) { + combinedTtDirectiveFound = true; } } @@ -4597,19 +4605,31 @@ url: window.location.href, onload: function(response) { const cspHeader = response.responseHeaders.split('\r\n') - .filter(h => h.toLowerCase().startsWith('content-security-policy:')) - .map(h => h.substring(26).trim()) - .join('; '); + .filter(h => h.toLowerCase().startsWith('content-security-policy:')) + .map(h => h.substring(26).trim()) + .join('; '); - const headerNames = parseTrustedTypes(cspHeader); - if (headerNames.length > 0) { - resolve(new Set(headerNames)); - } else { - resolve(new Set()); + const headerResult = parseTrustedTypes(cspHeader); + headerResult.names.forEach(name => combinedPolicies.add(name)); + if (headerResult.allowDuplicates) { + combinedAllowDuplicates = true; } + if (headerResult.ttDirectiveFound) { + combinedTtDirectiveFound = true; + } + + resolve({ + names: combinedPolicies, + allowDuplicates: combinedAllowDuplicates, + ttDirectiveFound: combinedTtDirectiveFound + }); }, onerror: function(error) { - resolve(new Set()); + resolve({ + names: combinedPolicies, + allowDuplicates: combinedAllowDuplicates, + ttDirectiveFound: combinedTtDirectiveFound + }); } }); }); @@ -4625,29 +4645,48 @@ } async function createPolicy() { - if (_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced()) { - const allowedNames = await getAvailablePolicyNamesOptimized(); + if (!(_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced())) { + return; + } - if (allowedNames.size === 0) { - escapeHTMLPolicy = _unsafeWindow.trustedTypes.createPolicy('pagetual_default', { - createHTML: (string, sink) => string - }); - return; + const { names: allowedNames, allowDuplicates, ttDirectiveFound } = await getCspTrustedTypesInfo(); + + if (ttDirectiveFound && !allowDuplicates) { + debug("CSP Trusted Types is enforced without 'allow-duplicates'. " + + "Skipping policy creation to avoid conflicts with the page."); + return; + } + + const MY_POLICY_NAME = 'pvcep_default'; + + try { + escapeHTMLPolicy = _unsafeWindow.trustedTypes.createPolicy(MY_POLICY_NAME, { + createHTML: (string, sink) => string, + createScriptURL: string => string, + createScript: string => string + }); + return; + } catch (e) { + } + + const existingPolicies = new Set(_unsafeWindow.trustedTypes.getPolicyNames()); + for (const name of allowedNames) { + if (name === '*' || existingPolicies.has(name)) { + continue; } - for (const name of allowedNames) { - if (name === '*') continue; - try { - escapeHTMLPolicy = _unsafeWindow.trustedTypes.createPolicy(name, { - createHTML: (string, sink) => string - }); - break; - } catch (e) { - console.warn(`create '${name}' failed`); - return; - } + 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."); } let escapeHTMLPolicy = null; From bc2b255c97e11fbe6e98de20b0cd872320a7216b Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Mon, 27 Oct 2025 13:24:16 +0000 Subject: [PATCH 06/99] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 122 ++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 68e33c7db3d..7dbfcbdc63a 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -12458,32 +12458,39 @@ ImgOps | https://imgops.com/#b#`; function parseTrustedTypes(cspString) { const policies = new Set(); + let allowDuplicates = false; + let ttDirectiveFound = false; const ttRegex = /trusted-types\s+([^;]+)/gi; let match; + while ((match = ttRegex.exec(cspString)) !== null) { + ttDirectiveFound = true; match[1].trim().split(/\s+/) .forEach(name => { - if (name !== "'allow-duplicates'" && name !== "'none'") { - policies.add(name.replace(/'/g, '')); - } - }); + if (name === "'allow-duplicates'") { + allowDuplicates = true; + } else if (name !== "'none'") { + policies.add(name.replace(/'/g, '')); + } + }); } - return Array.from(policies); + return { names: policies, allowDuplicates: allowDuplicates, ttDirectiveFound: ttDirectiveFound }; } - async function getAvailablePolicyNamesOptimized() { - if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.getPolicyNames) { - const existingNames = unsafeWindow.trustedTypes.getPolicyNames(); - if (existingNames.length > 0) { - return new Set(existingNames); - } - } + async function getCspTrustedTypesInfo() { + const combinedPolicies = new Set(); + let combinedAllowDuplicates = false; + let combinedTtDirectiveFound = false; const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (meta) { - const metaNames = parseTrustedTypes(meta.content); - if (metaNames.length > 0) { - return new Set(metaNames); + const metaResult = parseTrustedTypes(meta.content); + metaResult.names.forEach(name => combinedPolicies.add(name)); + if (metaResult.allowDuplicates) { + combinedAllowDuplicates = true; + } + if (metaResult.ttDirectiveFound) { + combinedTtDirectiveFound = true; } } @@ -12493,19 +12500,31 @@ ImgOps | https://imgops.com/#b#`; url: window.location.href, onload: function(response) { const cspHeader = response.responseHeaders.split('\r\n') - .filter(h => h.toLowerCase().startsWith('content-security-policy:')) - .map(h => h.substring(26).trim()) - .join('; '); - - const headerNames = parseTrustedTypes(cspHeader); - if (headerNames.length > 0) { - resolve(new Set(headerNames)); - } else { - resolve(new Set()); + .filter(h => h.toLowerCase().startsWith('content-security-policy:')) + .map(h => h.substring(26).trim()) + .join('; '); + + const headerResult = parseTrustedTypes(cspHeader); + headerResult.names.forEach(name => combinedPolicies.add(name)); + if (headerResult.allowDuplicates) { + combinedAllowDuplicates = true; } + if (headerResult.ttDirectiveFound) { + combinedTtDirectiveFound = true; + } + + resolve({ + names: combinedPolicies, + allowDuplicates: combinedAllowDuplicates, + ttDirectiveFound: combinedTtDirectiveFound + }); }, onerror: function(error) { - resolve(new Set()); + resolve({ + names: combinedPolicies, + allowDuplicates: combinedAllowDuplicates, + ttDirectiveFound: combinedTtDirectiveFound + }); } }); }); @@ -12521,33 +12540,48 @@ ImgOps | https://imgops.com/#b#`; } async function createPolicy() { - if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced()) { - const allowedNames = await getAvailablePolicyNamesOptimized(); + if (!(unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced())) { + return; + } + + const { names: allowedNames, allowDuplicates, ttDirectiveFound } = await getCspTrustedTypesInfo(); + + if (ttDirectiveFound && !allowDuplicates) { + debug("CSP Trusted Types is enforced without 'allow-duplicates'. " + + "Skipping policy creation to avoid conflicts with the page."); + return; + } - if (allowedNames.size === 0) { - escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy('pvcep_default', { + const MY_POLICY_NAME = 'pvcep_default'; + + try { + escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy(MY_POLICY_NAME, { + createHTML: (string, sink) => string, + createScriptURL: string => string, + createScript: string => string + }); + 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; - } - - for (const name of allowedNames) { - if (name === '*') continue; - try { - escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy(name, { - createHTML: (string, sink) => string, - createScriptURL: string => string, - createScript: string => string - }); - break; - } catch (e) { - console.warn(`create '${name}' failed`); - return; - } + } catch (e) { + debug(`create '${name}' failed, trying next...`); } } + debug("Could not create any trusted types policy."); } let escapeHTMLPolicy = null; From 5943163ee64646b532110c19b40d8a13cb08ab79 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 28 Oct 2025 17:13:06 +0900 Subject: [PATCH 07/99] Update pagetual.user.js --- Pagetual/pagetual.user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index ec17e5b593c..58cca3fb32b 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -6361,6 +6361,7 @@ root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], absolute_regex = /^\w+\:\/\//; this.updateUrl = false; + src = src.replace(/^\/(\.\.\/)+/, "/"); while (src.indexOf("../") === 0) { src = src.substr(3); root_page = root_page.replace(/\/[^\/]+\/$/, "/"); From 15e5418c4f45eff54610ed8f439e69b0f1edf44a Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 28 Oct 2025 17:14:41 +0900 Subject: [PATCH 08/99] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 445482c9340..cbae4e915b1 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12155,6 +12155,7 @@ ImgOps | https://imgops.com/#b#`; var root_page = /^[^\?#]*\//.exec(url)[0], root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], absolute_regex = /^\w+\:\/\//; + src = src.replace(/^\/(\.\.\/)+/, "/"); while (src.indexOf("../") === 0) { src = src.substr(3); root_page = root_page.replace(/\/[^\/]+\/$/, "/"); From 0e020afc9e5bd71019aa539f5c5dda98290d0ff4 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 28 Oct 2025 08:15:08 +0000 Subject: [PATCH 09/99] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 7dbfcbdc63a..1395cd9b877 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -12155,6 +12155,7 @@ ImgOps | https://imgops.com/#b#`; var root_page = /^[^\?#]*\//.exec(url)[0], root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], absolute_regex = /^\w+\:\/\//; + src = src.replace(/^\/(\.\.\/)+/, "/"); while (src.indexOf("../") === 0) { src = src.substr(3); root_page = root_page.replace(/\/[^\/]+\/$/, "/"); From fa88630f8df16716d43257cd1593fbb5c0041772 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 1 Nov 2025 22:16:09 +0900 Subject: [PATCH 10/99] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index 34f9148c807..76038f2c4bd 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1481,7 +1481,7 @@ var siteInfo = [ { name: "nhentai", url: /\bnhentai\./i, - r: [/(cdn\..*\d+)t(\.[a-z]+)$/, /\/\/\w+(\..*\/)(\d+)t(\.[a-z]+)$/i], + r: [/(cdn\..*\d+)t(\.[a-z]+)$/, /\/\/\w+(\..*\/)(\d+)t(\.[a-z]+)(\.[a-z]+)?$/i], s: ["$1$2","//i$1$2$3"], example: "http://nhentai.net/g/113475/" }, From f12ebf9f5cde43256dc2d0e143a9924163494777 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 1 Nov 2025 22:17:19 +0900 Subject: [PATCH 11/99] 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 cbae4e915b1..6b4ffe5ed28 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.10.27.1 +// @version 2025.11.1.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 @@ -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/1669339/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1687504/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 0591ec63869128ac18b59f2bd9322c409a6bf690 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sat, 1 Nov 2025 13:17:31 +0000 Subject: [PATCH 12/99] 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 1395cd9b877..272140f784a 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.10.27.1 +// @version 2025.11.1.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 @@ -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=1669339 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1687504 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1653424 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 5045d524c40bdc6bcf02056383322285b8abea51 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 3 Nov 2025 21:23:13 +0900 Subject: [PATCH 13/99] Update searchJumperDefaultConfig.js --- SearchJumper/searchJumperDefaultConfig.js | 467 ++++++++++++++++++++++ 1 file changed, 467 insertions(+) diff --git a/SearchJumper/searchJumperDefaultConfig.js b/SearchJumper/searchJumperDefaultConfig.js index c45395818f7..12f00de2c20 100644 --- a/SearchJumper/searchJumperDefaultConfig.js +++ b/SearchJumper/searchJumperDefaultConfig.js @@ -421,6 +421,473 @@ switch (lang) { } ]; break; + case "ja": + sitesConfig = [ + { + "description": "検索エンジンの主分類", + "icon": "search", + "sites": [ + { + "keywords": "textarea[name='q']", + "match": "https://www\\.google\\..*/search((?!udm=2).)*$", + "name": "Google", + "url": "https://www.google.co.jp/search?q=%s&ie=utf-8&oe=utf-8" + }, + { + "match": "https://search\\.yahoo\\.co\\.jp/search", + "name": "Yahoo! JAPAN", + "url": "https://search.yahoo.co.jp/search?p=%s" + }, + { + "match": "^https://(www|cn|global)\\.bing\\.com/search", + "name": "Bing", + "url": "https://www.bing.com/search?q=%s" + }, + { + "name": "Goo", + "url": "https://service.smt.docomo.ne.jp/portal/search/web/result.html?q=%s" + }, + { + "match": "https://duckduckgo\\.com", + "name": "DuckDuckGo", + "url": "https://duckduckgo.com/?q=%s" + }, + { + "match": "https://www\\.ecosia\\.org/search", + "name": "Ecosia", + "url": "https://www.ecosia.org/search?q=%s" + }, + { + "match": "https://www\\.perplexity\\.ai/search", + "name": "Perplexity", + "url": "https://www.perplexity.ai/search?q=%s" + }, + { + "icon": "https://www.amazon.co.jp/favicon.ico", + "name": "Amazon.co.jpで検索", + "url": "https://www.amazon.co.jp/s?k=%s" + }, + { + "icon": "https://www.rakuten.co.jp/favicon.ico", + "name": "楽天市場で検索", + "url": "https://search.rakuten.co.jp/search/mall/%s/" + } + ], + "type": "検索" + }, + { + "icon": "sitemap", + "openInNewTab": true, + "selectTxt": true, + "sites": [ + { + "name": "Googleで検索", + "url": "[\"Google\"]" + }, + { + "name": "📄 コピー", + "nobatch": true, + "url": "c:%sr" + }, + { + "name": "📝 貼り付け", + "url": "paste:" + }, + { + "name": "🔆 ページ内検索", + "url": "find:%sr" + }, + { + "name": "Googleサイト内検索", + "url": "https://www.google.co.jp/search?q=%s%20site%3A%h" + }, + { + "name": "AIに質問", + "url": "[\"この内容を解説 (Gemini)\"]" + }, + { + "name": "Yahoo! サイト内検索", + "url": "https://search.yahoo.co.jp/search?p=%s%20site%3A%h" + }, + { + "icon": "https://hoothin.com/qrcode/favicon.svg", + "name": "テキストをQRコードに変換", + "url": "https://hoothin.com/qrcode#%s" + }, + { + "name": "ウィキペディアプレビュー", + "url": "showTips:https://ja.wikipedia.org/wiki/%s\n
\na>img|src}\"/>\n{.mw-parser-output>p}\n
" + }, + { + "name": "Metacriticスコア", + "url": "showTips:https://www.metacritic.com/search/%s/\n
\n\n
\n

{.c-pageSiteSearch-results-item>div>p}

\n
\n{.u-text-uppercase}\n{.c-pageSiteSearch-results-item strong}\n{.c-siteReviewScore}\n
\n
\n
" + }, + { + "name": "IMDbスコア", + "url": "showTips:https://www.imdb.com/find/?q=%s&exact=true.then{.find-title-result .ipc-metadata-list-summary-item__t}\n

\n{.hero__primary-text}\n{.ipc-btn__text>div>div>div}\n

\n
\n\n
\n
{a.ipc-chip|()}
\n
Year: {h1+ul>li>.ipc-link}
\n
Director: {section>div>div>.title-pc-list>li:nth-child(1) li}
\n
Writer: {section>div>div>.title-pc-list>li:nth-child(2) li}
\n
Stars: {section>div>div>.title-pc-list>li:nth-child(3) li|()}
\n
{section>p>span}
\n
\n
" + }, + { + "name": "すべてのエンジンを展開", + "url": "https://search.hoothin.com/all#%s" + }, + { + "kwFilter": "\\d\\$|\\$\\d", + "name": "💴ドルを円に変換", + "nobatch": true, + "url": "showTips:http://apilayer.net/api/convert?from=USD&to=JPY&amount=1&access_key=%template{apilayer key} \n{name}
%sr USD = {json.result|*%sr.replace(/\\D/g,'')} JPY" + }, + { + "kwFilter": "^https?:", + "name": "📦 リンクを一括オープン", + "url": "%s[all]" + }, + { + "description": "「example.com」などのテキストリンクをサポート", + "kwFilter": "\\w\\S*\\.\\S*\\w|\\w.*[点。].*\\w", + "name": "🔗 テキストリンクを開く", + "nobatch": true, + "url": "%sr.replace(/(点|。)/g,\".\").replace(/[^\\s\\w\\-_\\.~!\\*';:@&=\\+\\$,\\/\\?#\\[\\]%]/g,\"\").replace(/ /g,\"\").replace(/^/,\"http://\").replace(/^http:\\/\\/(https?:)/,\"$1\")" + }, + { + "icon": "https://ejje.weblio.jp/favicon.ico", + "kwFilter": "^[a-zA-Z\\s]+$", + "name": "Weblio英和・和英辞典", + "url": "showTips:https://ejje.weblio.jp/content/%s\n
\n{.summaryM.midashigo}
\n{.summaryM.level_v15}
\n{.summaryM.wordclass} {.summaryM.Jtnhj}\n
" + }, + { + "kwFilter": "^[a-zA-Z]+$", + "name": "DeepL英語から日本語", + "url": "https://www.deepl.com/translator#en/ja/%s" + }, + { + "kwFilter": "^https?://.", + "name": "↩️ 短縮URLを復元", + "url": "showTips:%s\n{url}" + }, + { + "kwFilter": "^\\s*[0-9a-zA-z\\+\\/\\=]{4,}\\s*$", + "name": "🔓 base64デコード", + "url": "showTips:\n📋 %bd" + }, + { + "name": "🔒 base64エンコード", + "url": "paste:%be" + }, + { + "name": "📎 選択テキストを一括置換", + "url": "paste:%sr.replace(/%input{マッチング正規表現を入力}/g,\"%input{置換文字列を入力}\")" + }, + { + "kwFilter": "^http.*\\.(3gpp|m4v|mkv|mp4|ogv|webm)\\b", + "name": "📺 ビデオプレビュー", + "url": "showTips:\n" + }, + { + "kwFilter": "^http.*\\.(flac|m4a|mp3|oga|ogg|opus|wav)\\b", + "name": "🎵 オーディオプレビュー", + "url": "showTips:\n" + }, + { + "kwFilter": "^http.*\\.(avif|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|png|svg|webp|xbm)\\b", + "name": "🏞️ 画像プレビュー", + "url": "showTips:\n" + } + ], + "type": "選択テキスト検索" + }, + { + "icon": "eye", + "openInNewTab": true, + "selectImg": true, + "sites": [ + { + "name": "Google画像検索", + "url": "https://www.google.com/searchbyimage?sbisrc=cr_1_0_0&image_url=%T" + }, + { + "name": "Google Lens", + "url": "https://www.google.com/imghp#p{sleep(500)&click([data-propagated-experiment-ids])&[name\\=\"encoded_image\"]=%i}" + }, + { + "icon": "https://hoothin.com/qrcode/favicon.svg", + "name": "QRコードデコード", + "url": "https://hoothin.com/qrdecode#p{#fileInput=%i}" + }, + { + "name": "Google翻訳画像", + "url": "https://translate.google.com/?op=images#p{input[accept^\\=\"image\"]=%i}" + }, + { + "name": "一键抠图", + "url": "https://www.remove.bg/ja/upload#p{wait()&body=%i}" + }, + { + "icon": "https://trace.moe/favicon.png", + "name": "アニメシーン検索", + "url": "https://trace.moe/?url=%T" + }, + { + "description": "Lunapicで画像を編集", + "name": "Lunapic", + "nobatch": true, + "url": "https://www.lunapic.com/editor/index.php?action=url&url=%t" + }, + { + "name": "Bing画像検索", + "url": "https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:%T" + }, + { + "name": "TinEye", + "url": "https://www.tineye.com/search?url=%T" + }, + { + "name": "QRコード生成", + "url": "[\"QRコード生成\"]" + } + ], + "type": "画像検索" + }, + { + "icon": "list", + "openInNewTab": true, + "selectLink": true, + "selectPage": true, + "sites": [ + { + "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik05NjAgOTYwSDY0di02NGg4OTZ2NjR6IG0tNzMuNi02ODYuNGwtODQgODQtNDUuNiA0NS42TDM4NCA3NzZsLTE5MiA1NiA1Ni0xOTIgNTAyLjQtNTAyLjRjNC00IDkuNi02LjQgMTQuNC02LjQgNCAwIDggMS42IDEwLjQgNEw4ODggMjQ4YzcuMiA3LjIgNS42IDE3LjYtMS42IDI1LjZ6TTcxMiAzNTcuNkw2NjYuNCAzMTIgMzA0LjggNjczLjZsLTE4LjQgNjQgNjQtMTguNEw3MTIgMzU3LjZ6IG05Ny42LTk3LjZsLTQ1LjYtNDUuNi01MiA1MiA0NS42IDQ1LjYgNTItNTJ6Ij48L3BhdGg+PC9zdmc+", + "name": "現在のページを編集", + "nobatch": true, + "url": "javascript:(function(){document.body.setAttribute('contenteditable', 'true');alert('ウェブページ編集を有効にしました。ESCキーでキャンセル');document.onkeydown = function (e) {e = e || window.event;if(e.keyCode==27){document.body.setAttribute('contenteditable', 'false');}}})();" + }, + { + "description": "ウェブページの右クリックおよびコピー制限を解除", + "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik04MDAgNDQ4SDcwNFYzMjBjMC0xMDYuNC04NS42LTE5Mi0xOTItMTkyUzMyMCAyMTMuNiAzMjAgMzIwaDY0YzAtNzAuNCA1Ny42LTEyOCAxMjgtMTI4czEyOCA1Ny42IDEyOCAxMjh2MTI4SDIyNGMtMTcuNiAwLTMyIDE0LjQtMzIgMzJ2Mzg0YzAgMTcuNiAxNC40IDMyIDMyIDMyaDU3NmMxNy42IDAgMzItMTQuNCAzMi0zMlY0ODBjMC0xNy42LTE0LjQtMzItMzItMzJ6TTUxMiA3MzZgLTM1LjIgMC02NC0yOC44LTY0LTY0czI4LjgtNjQgNjQtNjQgNjQgMjguOCA2NCA2NC0yOC44IDY0LTY0IDY0eiI+PC9wYXRoPjwvc3ZnPg==", + "name": "制限解除", + "nobatch": true, + "url": "javascript:var d=document,b=d.body;with(b.onselectstart=b.oncopy=b.onpaste=b.onkeydown=b.oncontextmenu=b.onmousemove=b.ondragstart=d.oncopy=d.onpaste=null,d.onselectstart=d.oncontextmenu=d.onmousedown=d.onkeydown=function(){return!0},d.wrappedJSObject||d)onmouseup=null,onmousedown=null,oncontextmenu=null;for(var a=d.getElementsByTagName(\"*\"),i=a.length-1;i>=0;i--){var o=a[i];with(o.wrappedJSObject||o)onmouseup=null,onmousedown=null}var h=d.getElementsByTagName(\"head\")[0];if(h){var s=d.createElement(\"style\");s.innerHTML=\"html,*{user-select:text!important;-moz-user-select:text!important;-webkit-user-select:text!important;-webkit-user-drag:text!important;-khtml-user-select:text!important;-khtml-user-drag:text!important;pointer-events:auto!important;}\",h.appendChild(s)}Event.prototype.preventDefault=function(){};" + }, + { + "description": "拡張機能“Ignore X-Frame headers”と併用する必要があります", + "name": "🔗 リンクプレビュー", + "url": "showTips:\n\n" + }, + { + "description": "ctrl バックグラウンドタブ alt 小窓 ctrl+shift シークレットウィンドウ", + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiPjxwYXRoIGQ9Ik03MjIuOCA0NTlsLTE4LjkgMTguOS0yLjcgMi43LTQuNyA0LjgtNTIuNyA1Mi43IDI2LjMgMjYuMyA1Mi43LTUyLjcgMTg0LjQgMTg0LjQtMjEwLjcgMjEwLjgtMTg0LjQtMTg0LjQgNTIuNi01Mi43LTI2LjMtMjYuNC01Mi43IDUyLjctMjYuMyAyNi40IDIzNy4xIDIzNy4xIDI2My40LTI2My41eiIgZmlsbD0iIzA2MDAwMSIvPjxwYXRoIGQ9Ik0zMjcuNyAzNTMuNmwzNDIuNSAzNDIuNSAyNi4zLTI2LjNMMzU0IDMyNy4zeiIgZmlsbD0iIzA2MDAwMSIvPjxwYXRoIGQ9Ik0zMDEuMyA1MTEuN0wxMTYuOSAzMjcuM2wyMTAuOC0yMTAuN0w1MTIuMSAzMDFsLTUyLjcgNTIuNiAyNi4zIDI2LjQgNTIuNy01Mi43IDI2LjMtMjYuNC0yMzctMjM3TDY0LjIgMzI3LjNsMjM3LjEgMjM3LjEgMjYuMy0yNi4zIDUyLjgtNTIuN0wzNTQgNDU5eiIgZmlsbD0iIzA2MDAwMSIvPjwvc3ZnPg==", + "name": "リンクを開く", + "openInNewTab": true, + "url": "%t" + }, + { + "icon": "https://hoothin.com/qrcode/favicon.svg", + "name": "QRコード生成", + "url": "https://hoothin.com/qrcode#%U" + }, + { + "icon": "https://web.archive.org/_static/images/archive.ico", + "name": "現在のページをアーカイブ", + "nobatch": true, + "url": "https://web.archive.org/save/%u" + }, + { + "name": "万能コマンド", + "nobatch": true, + "url": "https://wn.run/%u" + }, + { + "icon": "https://is.gd/isgd_favicon.ico", + "name": "is.gd", + "url": "https://is.gd/create.php%p{url=%u&opt=0}" + }, + { + "icon": "https://docrdsfx76ssb.cloudfront.net/static/1678306332/pages/wp-content/uploads/2019/02/favicon.ico", + "name": "URL Shortener", + "url": "https://bitly.com/%p{url=%u}" + }, + { + "description": "クリップボードの内容を行ごとに分割して、現在のフォーカス入力ボックスに順次貼り付け", + "name": "⌨️ 行ごとに入力", + "url": "#p{@=%s[]}" + }, + { + "description": "クリップボード画像を検索", + "name": "Google Lens - クリップボード画像検索", + "url": "[\"Google Lens\"]" + }, + { + "name": "Mainonly by jerrylus", + "url": "javascript:(function(){var e=document.body;let n=document.head.appendChild(document.createElement(\"style\"));n.textContent=\".mainonly { outline: 2px solid red; }\";let t=CSS.supports(\"selector(:has(*))\");function o(n){n instanceof HTMLElement&&(e.classList.remove(\"mainonly\"),(e=n).classList.add(\"mainonly\"))}function i(e){o(e.target)}function l(o){if(o.preventDefault(),t)n.textContent=\":not(:has(.mainonly), .mainonly, .mainonly *) { visibility: hidden; }\";else{n.textContent=\":not(.mainonly *, .mainonly-ancestor) { visibility: hidden; }\";var i=e;do i.classList.add(\"mainonly-ancestor\");while(i=i.parentElement)}r()}function s(o){if(\"Escape\"===o.key){o.preventDefault();var i=window.scrollY||document.documentElement.scrollTop;if(n.remove(),document.removeEventListener(\"keydown\",s),r(),e?.classList.remove(\"mainonly\"),!t)for(let l of document.getElementsByClassName(\"mainonly-ancestor\"))l.classList.remove(\"mainonly-ancestor\");window.scrollTo(0,i)}}function a(n){n.preventDefault(),n.deltaY<0?o(e.parentElement):o(e.firstElementChild)}function r(){document.removeEventListener(\"mouseover\",i),document.removeEventListener(\"click\",l),document.removeEventListener(\"wheel\",a)}document.addEventListener(\"mouseover\",i),document.addEventListener(\"click\",l),document.addEventListener(\"wheel\",a,{passive:!1}),document.addEventListener(\"keydown\",s)}())" + }, + { + "kwFilter": "^http.*\\.(3gpp|m4v|mkv|mp4|ogv|webm)(\\?|#|$)", + "name": "📺 ビデオプレビュー - 現在のページ", + "url": "[\"📺 ビデオプレビュー\"]" + }, + { + "kwFilter": "^http.*\\.(flac|m4a|mp3|oga|ogg|opus|wav)(\\?|#|$)", + "name": "🎵 オーディオプレビュー - 現在のページ", + "url": "[\"🎵 オーディオプレビュー\"]" + }, + { + "kwFilter": "^http.*\\.(avif|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|png|svg|webp|xbm)(\\?|#|$)", + "name": "🏞️ 画像プレビュー - 現在のページ", + "url": "[\"🏞️ 画像プレビュー\"]" + } + ], + "type": "現在のページ" + }, + { + "icon": "robot", + "openInNewTab": 1, + "selectTxt": true, + "sites": [ + { + "icon": "https://www.gstatic.com/lamda/images/favicon_v1_150160cddff7f294ce30.svg", + "name": "この内容を解説 (Gemini)", + "url": "https://gemini.google.com/app#p{.ql-editor.textarea=以下の内容を説明してください\n`%s`} " + }, + { + "icon": "https://www.gstatic.com/lamda/images/favicon_v1_150160cddff7f294ce30.svg", + "name": "Gemini", + "url": "https://gemini.google.com/app#p{.ql-editor.textarea=%s}" + }, + { + "name": "Poe - AIチャット", + "url": "https://poe.com/#p{sleep(2000)&[class*\\=ChatMessageInputContainer]>textarea=%s&click([data-button-send])}" + }, + { + "name": "ChatGPT", + "url": "https://chat.openai.com/#p{#prompt-textarea=%s&click(#prompt-textarea+button)}" + }, + { + "name": "Futurepedia - AIツールを検索", + "url": "https://www.futurepedia.io/search?search=%s" + } + ], + "type": "AI" + }, + { + "type": "Assit", + "icon": "list-alt", + "selectTxt": true, + "selectImg": true, + "selectAudio": true, + "selectVideo": true, + "selectLink": true, + "selectPage": true, + "openInNewTab": true, + "sites": [ + { + "name": "Twitterで共有", + "url": "https://twitter.com/intent/tweet?url=%T" + }, + { + "icon": "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE3LjQ5NCAyMS40ODhjLTIuMTQgMC00LjEzOS0uNzYtNS44MDgtMi4xM0w0LjM5MiAxOS4zOXYtMi41ODNjLS41NzktMS40NDgtLjkxMi0zLjA0OC0uOTEyLTQuNzY0IDAtNS4xNiA0Ljc4OC05LjM0OCA5LjAxMi05LjM0OCA0LjIyNCAwIDkuMDEyIDQuMTg4IDkuMDEyIDkuMzQ4IDAgNS4xNi00Ljc4OCA5LjM0OC05LjAxMiA5LjM0OHptLjAwOC0xNy4zMDVjLTMuNTUyIDAtNi40MyA3LjU4My02LjQzIDcuNTgzczIuODc4IDcuNTgzIDYuNDMgNy41ODNjMy41NTIgMCA2LjQzLTcuNTgzIDYuNDMtNy41ODNzLTIuODc4LTcuNTgzLTYuNDMtNy41ODN6bS0uNjYgMTAuMTE0Yy0uMzk2IDAtLjcxNC0uMzE4LS43MTQtLjcxNHMuMzE4LS43MTQuNzE0LS43MTRjLjM5NiAwIC43MTQuMzE4LjcxNC43MTRzLS4zMTguNzE0LS43MTQuNzE0em0yLjY0IDBjLS4zOTYgMC0uNzE0LS4zMTgtLjcxNC0uNzE0cy4zMTgtLjcxNC43MTQtLjcxNGMuMzk2IDAgLjcxNC4zMTguNzE0LjcxNHMtLjMxOC43MTQtLjcxNC43MTR6bS01LjI4IDBjLS4zOTYgMC0uNzE0LS4zMTgtLjcxNC0uNzE0cy4zMTgtLjcxNC43MTQtLjcxNGMuMzk2IDAgLjcxNC4zMTguNzE0LjcxNHMtLjMxOC43MTQtLjcxNC43MTR6IiBmaWxsPSIjMDZDMzAwIj48L3BhdGg+PC9zdmc+", + "name": "LINEで共有", + "nobatch": true, + "url": "https://line.me/R/share?text=%n%20%u" + }, + { + "name": "Send by Gmail", + "url": "https://mail.google.com/mail/u/0/?tf=cm&source=mailto&body=%n %T" + }, + { + "name": "Share to Facebook", + "url": "https://www.facebook.com/sharer/sharer.php?u=%T&t=%n" + }, + { + "name": "🧮 Calculator", + "url": "calculator://" + }, + { + "name": "🔎 Everything", + "url": "ES://%s" + }, + { + "name": "🦊 Firefox", + "url": "FirefoxURL-308046B0AF4A39CB://%u" + }, + { + "name": "⏰ Clock", + "url": "ms-clock://" + }, + { + "name": "✂️ Screenclip", + "url": "ms-screenclip://" + }, + { + "name": "☑️ ToDo", + "url": "ms-todo://", + "description": "Microsoft To-Do" + }, + { + "name": "📓 Onenote", + "url": "onenote://" + }, + { + "name": "⌨️ VSCode", + "url": "vscode://%u" + }, + { + "name": "Open the link inside words", + "url": "%sr.replace(/[^\\w\\-_\\.~!\\*'\\(\\);:@&=\\+\\$,\\/\\?#\\[\\]%]/g,\"\")" + }, + { + "name": "リンクをMarkdownでコピー", + "url": "c:[%sr](%t)" + }, + { + "name": "📱 Send to phone", + "url": "https://s.hoothin.com/#p{wait(x-peer)&rclick(x-peer)&#textInput=%s&click(#textInput+div>button)}", + "icon": "https://s.hoothin.com/images/favicon-96x96.png" + }, + { + "name": "Bing Search in site", + "url": "https://www.bing.com/search?q=%s%20site%3A%h" + }, + { + "name": "Duckduckgo Search in site", + "url": "https://duckduckgo.com/?q=%s%20site%3A%h" + }, + { + "name": "Yahoo Search in site", + "url": "https://search.yahoo.com/search;?p=%s%20site%3A%h" + }, + { + "name": "Yandex Search in site", + "url": "https://yandex.com/search/?text=%s%20site%3A%h" + }, + { + "name": "Startpage Search in site", + "url": "https://www.startpage.com/sp/search?query=%s%20site%3A%h", + "icon": "https://www.startpage.com/sp/cdn/favicons/favicon-16x16--default.png" + }, + { + "name": "Preview wikipedia", + "url": "showTips:https://en.wikipedia.org/wiki/%s\n
\nimg|src}\"/>\n{.mw-parser-output>p}\n
" + }, + { + "name": "🛠️ Copy selected(pic&link)", + "url": "c:%element{}" + }, + { + "name": "🛠️ Copy selected(txt(link))", + "url": "c:%element{}.replace(/!\\[.*?\\]\\(.*?\\)/g,\"\").replace(/\\[ *\\]\\(.*?\\)\\s*/g,\"\").replace(/\\[((.|\\n)*?)\\](\\(.*?\\))/g,\"$1$3\")" + }, + { + "name": "🛠️ Copy selected({ txt | link })", + "url": "c:%element{}.replace(/!\\[.*?\\]\\(.*?\\)/g,\"\").replace(/\\[\\s*\\]\\(.*?\\)\\s*/g,\"\").replace(/\\[((.|\\n)*?)\\]\\((.*?)\\)/g,\"{ $1 | $3 }\")" + } + ] + }, + { + "icon": "recycle", + "match": "0", + "sites": [], + "type": "ごみ箱" + } + ] + break; default: sitesConfig = [ { From c34709d6bcac5cdd7e032285a63b982c3696501e Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 9 Nov 2025 18:03:17 +0900 Subject: [PATCH 14/99] Optimize switch-chinese --- .../lib/package.json | 48 +- .../lib/readme.md | 926 +++++++++++++++++- .../lib/stcasc.d.ts | 127 +++ .../lib/stcasc.lib.js | 102 +- .../lib/test.js | 114 +++ 5 files changed, 1257 insertions(+), 60 deletions(-) create mode 100644 Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts create mode 100644 Switch Traditional Chinese and Simplified Chinese/lib/test.js diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 2b4c2b72a33..6b5b53c909e 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json +++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json @@ -1,14 +1,41 @@ { "name": "switch-chinese", - "version": "1.0.6", - "description": "Convert between simplified and traditional Chinese characters. 切換正體/簡體中文,超輕量級,支援自訂詞彙與「一簡多繁」轉換,無任何相依性。", + "version": "1.0.7", + "description": "Lightweight Chinese converter library for bidirectional conversion between Simplified and Traditional Chinese with intelligent word segmentation, custom dictionary support, character detection, and multiple output formats. Zero dependencies. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", + "types": "stcasc.d.ts", "type": "module", + "exports": { + ".": { + "import": "./stcasc.lib.js", + "types": "./stcasc.d.ts" + } + }, + "files": [ + "stcasc.lib.js", + "stcasc.d.ts", + "readme.md" + ], "repository": { "type": "git", "url": "git+https://github.com/hoothin/UserScripts.git#master" }, "keywords": [ + "chinese", + "converter", + "traditional", + "simplified", + "chinese-converter", + "simplified-chinese", + "traditional-chinese", + "chinese-translation", + "chinese-detection", + "character-detection", + "text-converter", + "language-converter", + "i18n", + "localization", + "ruby-annotation", "简繁转换", "簡繁轉換", "简繁切换", @@ -22,15 +49,22 @@ "簡體中文", "繁體中文", "正體中文", - "Switch", - "Traditional", - "Chinese", - "Simplified" + "中文转换", + "中文检测", + "一简多繁", + "智能分词", + "自定义词库", + "零依赖", + "轻量级" ], "author": "Hoothin", "license": "MIT", "bugs": { "url": "https://github.com/hoothin/UserScripts/issues" }, - "homepage": "https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib" + "homepage": "https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib", + "engines": { + "node": ">=12.0.0" + } } + diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index cfe0e6f52e7..5e61732887f 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -1,73 +1,915 @@ -簡繁自由切換 -=== -> 切換正體/簡體中文,超輕量級,支援自訂詞彙與「一簡多繁」轉換,無任何相依性 +# switch-chinese + +[简体中文](#简体中文) | [繁體中文](#繁體中文) | [English](#english) + +--- + +## 简体中文 + +简繁体中文转换库 - 支持简体中文与繁体中文双向转换,基于词组的智能分词处理「一简多繁」问题 [![NPM](https://img.shields.io/npm/v/switch-chinese.svg)](https://www.npmjs.com/package/switch-chinese) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://www.npmjs.com/package/switch-chinese) -Install +### 特性 + +- **轻量级**:零依赖,体积小,性能优异 +- **智能转换**:支持基于词组的智能分词和「一简多繁」精准转换 +- **自定义词库**:允许用户自定义简繁转换词汇 +- **缓存机制**:支持字典缓存,避免重复初始化 +- **简繁检测**:自动检测文本是简体中文、繁体中文还是未知类型 +- **术语转换**:内置大陆简体与台湾正体的常用术语转换 +- **无依赖**:纯 JavaScript 实现,无需任何第三方依赖 + +### 安装 + +使用 npm 安装: + +```bash +npm install switch-chinese +``` + +使用 yarn 安装: + +```bash +yarn add switch-chinese +``` + +### 快速开始 + +#### 基础用法 + +```javascript +import stcasc from 'switch-chinese'; + +const { traditionalized, simplized, detect } = stcasc(); + +// 简体转繁体 +const tc = traditionalized('简繁转换 繁简切换 香烟 香烟袅袅'); +console.log(tc); +// 输出: 簡繁轉換 繁簡切換 香菸 香煙裊裊 + +// 繁体转简体 +const sc = simplized('繁體中文'); +console.log(sc); +// 输出: 繁体中文 +``` + +#### 检测中文类型 + +```javascript +import stcasc, { ChineseType } from 'switch-chinese'; + +const { detect } = stcasc(); + +const type1 = detect('简体中文'); +if (type1 === ChineseType.SIMPLIFIED) { + console.log('检测到简体中文'); +} + +const type2 = detect('繁體中文'); +if (type2 === ChineseType.TRADITIONAL) { + console.log('检测到繁体中文'); +} + +const type3 = detect('English'); +if (type3 === ChineseType.UNKNOWN) { + console.log('未检测到中文'); +} +``` + +ChineseType 枚举值: +- `ChineseType.SIMPLIFIED` (0): 简体中文 +- `ChineseType.TRADITIONAL` (1): 繁体中文 +- `ChineseType.UNKNOWN` (2): 未知类型 + +### 高级用法 + +#### 使用缓存优化性能 + +在多次调用时,建议使用缓存机制避免重复生成字典,提升性能: + +```javascript +import stcasc from 'switch-chinese'; + +// 第一次调用,生成字典 +let converter = stcasc(); +const cache = converter.cache; + +// 保存缓存到持久化存储(如 localStorage、文件等) +localStorage.setItem('stcasc-cache', JSON.stringify(cache)); + +// 后续调用,直接使用缓存 +const cachedData = JSON.parse(localStorage.getItem('stcasc-cache')); +converter = stcasc(cachedData); + +// 现在可以直接使用,无需重新生成字典 +const result = converter.traditionalized('简体中文'); +``` + +#### 自定义简繁转换词库 + +可以根据业务需求自定义简繁转换规则: + +```javascript +import stcasc from 'switch-chinese'; + +const customDict = { + '身份': '身分', + '转义': '跳脫', + '转换': '轉檔', + '软件': '軟體', + '硬件': '硬體', + '网络': '網路', + '服务器': '伺服器' +}; + +const { traditionalized, simplized } = stcasc({}, customDict); + +console.log(traditionalized('软件转换')); +// 输出: 軟體轉檔(使用自定义词库) +``` + +#### 禁用术语转换 + +默认情况下,库会转换一些特定术语(如「知识产权」→「智慧財產權」)。如需禁用此功能: + +```javascript +import stcasc from 'switch-chinese'; + +// 第三个参数设置为 true 以禁用术语转换 +const { traditionalized } = stcasc({}, {}, true); + +console.log(traditionalized('知识产权')); +// 输出: 知識産權(仅做字符转换,不转换术语) +``` + +#### 输出格式选项 + +库支持多种输出格式: + +```javascript +import stcasc, { OutputFormat } from 'switch-chinese'; + +const { traditionalized } = stcasc(); + +// 普通格式(默认) +const normal = traditionalized('简体中文'); +// 输出: 簡體中文 + +// 括号格式:同时显示原文和转换后的文本 +const bracket = traditionalized('简体中文', { format: OutputFormat.BRACKET }); +// 输出: 簡(简)體(体)中文 + +// Ruby 注音格式:适用于 HTML 显示 +const ruby = traditionalized('简体中文', { format: OutputFormat.RUBY }); +// 输出: 中文 +``` + +OutputFormat 枚举值: +- `OutputFormat.NORMAL` (0): 只输出转换后的结果(默认) +- `OutputFormat.BRACKET` (1): 输出「转换(原文)」格式 +- `OutputFormat.RUBY` (2): 输出 `` HTML 标签格式 + +### API 文档 + +#### stcasc(cache?, custom?, disableTerms?) + +主函数,用于创建转换器实例。 + +**参数:** + +- `cache` (Object, 可选): 缓存对象,用于避免重复生成字典 +- `custom` (Object, 可选): 自定义简繁转换词库 +- `disableTerms` (Boolean, 可选): 是否禁用术语转换,默认 `false` + +**返回值:** + +返回包含以下方法的对象: + +- `traditionalized(text, options?)`: 将简体中文转换为繁体中文 +- `simplized(text, options?)`: 将繁体中文转换为简体中文 +- `detect(text)`: 检测文本的中文类型,返回 ChineseType 枚举值 +- `cache`: 字典缓存对象 + +**Options 参数:** + +- `format` (Number, 可选): 输出格式,使用 `OutputFormat` 常量 + +#### ChineseType + +导出的常量对象,用于表示中文类型检测结果: + +```javascript +export const ChineseType = { + SIMPLIFIED: 0, // 简体中文 + TRADITIONAL: 1, // 繁体中文 + UNKNOWN: 2 // 未知类型 +}; +``` + +#### OutputFormat + +导出的常量对象,用于表示输出格式选项: + +```javascript +export const OutputFormat = { + NORMAL: 0, // 只输出转换后的结果 + BRACKET: 1, // 输出「转换(原文)」格式 + RUBY: 2 // 输出 HTML 标签格式 +}; +``` + +### 转换示例 + +#### 智能词组转换 + +该库支持基于上下文的智能词组转换,能够正确处理「一简多繁」的情况: + +```javascript +const { traditionalized } = stcasc(); + +// 智能识别词组边界 +console.log(traditionalized('香烟袅袅')); +// 输出: 香煙裊裊 + +console.log(traditionalized('里长面子')); +// 输出: 里長面子(「里长」是职务名称) + +console.log(traditionalized('吃干面')); +// 输出: 吃乾麵(「干面」是食物) + +console.log(traditionalized('把考卷发回来')); +// 输出: 把考卷發回來(「发」是动词) + +console.log(traditionalized('卷发')); +// 输出: 捲髮(「卷发」是发型) +``` + +#### 术语转换 + +内置常用的大陆简体与台湾正体术语转换: + +```javascript +const { traditionalized } = stcasc(); + +console.log(traditionalized('知识产权')); +// 输出: 智慧財產權 + +console.log(traditionalized('计算机软件')); +// 输出: 計算機軟體 + +console.log(traditionalized('网络服务器')); +// 输出: 網路伺服器 +``` + +### 技术特点 + +#### 高性能 + +- 使用字典树(Trie)优化词组匹配 +- 支持缓存机制,避免重复初始化 +- 纯 JavaScript 实现,执行效率高 + +#### 准确转换 + +- 内置大量简繁对照字符 +- 支持「一简多繁」智能识别 +- 基于词组的上下文分析 + +#### 易于集成 + +- ES Module 标准导出 +- TypeScript 类型支持(通过常量枚举) +- 零依赖,兼容性好 + +### 浏览器支持 + +支持所有现代浏览器及 Node.js 环境: + +- Chrome +- Firefox +- Safari +- Edge +- Node.js 12+ + +### 开源协议 + +MIT License + +### 相关链接 + +- [GitHub 仓库](https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib) +- [NPM 包地址](https://www.npmjs.com/package/switch-chinese) +- [问题反馈](https://github.com/hoothin/UserScripts/issues) + +### 关键词 + +简繁转换, 繁简转换, 简体中文, 繁体中文, 正体中文, 中文转换, 一简多繁, 简繁切换, 繁简切换, 中文检测, 智能分词, 自定义词库, 零依赖, 轻量级 + --- -``` shell + +## English + +Lightweight Chinese converter library for bidirectional conversion between Simplified and Traditional Chinese with intelligent word segmentation and one-to-many character mapping support. + +[![NPM](https://img.shields.io/npm/v/switch-chinese.svg)](https://www.npmjs.com/package/switch-chinese) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://www.npmjs.com/package/switch-chinese) + +### Features + +- **Lightweight**: Zero dependencies, small footprint, excellent performance +- **Intelligent Conversion**: Context-aware word segmentation and accurate one-to-many character mapping +- **Custom Dictionary**: User-defined conversion rules support +- **Caching Mechanism**: Dictionary caching to avoid repeated initialization +- **Text Detection**: Automatic detection of Simplified Chinese, Traditional Chinese, or unknown text +- **Term Conversion**: Built-in mainland China and Taiwan terminology mapping +- **Zero Dependencies**: Pure JavaScript implementation, no third-party dependencies required + +### Installation + +Install via npm: + +```bash npm install switch-chinese ``` -演示 +Install via yarn: + +```bash +yarn add switch-chinese +``` + +### Quick Start + +#### Basic Usage + +```javascript +import stcasc from 'switch-chinese'; + +const { traditionalized, simplized, detect } = stcasc(); + +// Simplified to Traditional +const tc = traditionalized('简繁转换 繁简切换 香烟 香烟袅袅'); +console.log(tc); +// Output: 簡繁轉換 繁簡切換 香菸 香煙裊裊 + +// Traditional to Simplified +const sc = simplized('繁體中文'); +console.log(sc); +// Output: 繁体中文 +``` + +#### Text Detection + +```javascript +import stcasc, { ChineseType } from 'switch-chinese'; + +const { detect } = stcasc(); + +const type1 = detect('简体中文'); +if (type1 === ChineseType.SIMPLIFIED) { + console.log('Detected Simplified Chinese'); +} + +const type2 = detect('繁體中文'); +if (type2 === ChineseType.TRADITIONAL) { + console.log('Detected Traditional Chinese'); +} + +const type3 = detect('English'); +if (type3 === ChineseType.UNKNOWN) { + console.log('No Chinese text detected'); +} +``` + +ChineseType enumeration values: +- `ChineseType.SIMPLIFIED` (0): Simplified Chinese +- `ChineseType.TRADITIONAL` (1): Traditional Chinese +- `ChineseType.UNKNOWN` (2): Unknown type + +### Advanced Usage + +#### Performance Optimization with Caching + +For multiple conversions, use caching mechanism to improve performance: + +```javascript +import stcasc from 'switch-chinese'; + +// First call, generate dictionary +let converter = stcasc(); +const cache = converter.cache; + +// Save cache to persistent storage (e.g., localStorage, file system) +localStorage.setItem('stcasc-cache', JSON.stringify(cache)); + +// Subsequent calls, use cached data +const cachedData = JSON.parse(localStorage.getItem('stcasc-cache')); +converter = stcasc(cachedData); + +// Now you can use it directly without regenerating dictionary +const result = converter.traditionalized('简体中文'); +``` + +#### Custom Conversion Dictionary + +Customize conversion rules according to your needs: + +```javascript +import stcasc from 'switch-chinese'; + +const customDict = { + '身份': '身分', + '转义': '跳脫', + '转换': '轉檔', + '软件': '軟體', + '硬件': '硬體', + '网络': '網路', + '服务器': '伺服器' +}; + +const { traditionalized, simplized } = stcasc({}, customDict); + +console.log(traditionalized('软件转换')); +// Output: 軟體轉檔 (using custom dictionary) +``` + +#### Disable Term Conversion + +By default, the library converts specific terms (e.g., "知识产权" → "智慧財產權"). To disable this feature: + +```javascript +import stcasc from 'switch-chinese'; + +// Set the third parameter to true to disable term conversion +const { traditionalized } = stcasc({}, {}, true); + +console.log(traditionalized('知识产权')); +// Output: 知識産權 (character conversion only, no term conversion) +``` + +#### Output Formats + +The library supports multiple output formats: + +```javascript +import stcasc, { OutputFormat } from 'switch-chinese'; + +const { traditionalized } = stcasc(); + +// Normal format (default) +const normal = traditionalized('简体中文'); +// Output: 簡體中文 + +// Bracket format: shows both original and converted text +const bracket = traditionalized('简体中文', { format: OutputFormat.BRACKET }); +// Output: 簡(简)體(体)中文 + +// Ruby annotation format: for HTML display +const ruby = traditionalized('简体中文', { format: OutputFormat.RUBY }); +// Output: 中文 +``` + +OutputFormat enumeration values: +- `OutputFormat.NORMAL` (0): Output converted result only (default) +- `OutputFormat.BRACKET` (1): Output in "converted(original)" format +- `OutputFormat.RUBY` (2): Output in `` HTML tag format + +### API Reference + +#### stcasc(cache?, custom?, disableTerms?) + +Main function to create a converter instance. + +**Parameters:** + +- `cache` (Object, optional): Cache object to avoid regenerating dictionary +- `custom` (Object, optional): Custom Simplified-Traditional conversion dictionary +- `disableTerms` (Boolean, optional): Whether to disable term conversion, default `false` + +**Returns:** + +An object containing the following methods: + +- `traditionalized(text, options?)`: Convert Simplified Chinese to Traditional Chinese +- `simplized(text, options?)`: Convert Traditional Chinese to Simplified Chinese +- `detect(text)`: Detect Chinese text type, returns ChineseType enumeration value +- `cache`: Dictionary cache object + +**Options parameter:** + +- `format` (Number, optional): Output format, use `OutputFormat` constants + +#### ChineseType + +Exported constant object representing text detection results: + +```javascript +export const ChineseType = { + SIMPLIFIED: 0, // Simplified Chinese + TRADITIONAL: 1, // Traditional Chinese + UNKNOWN: 2 // Unknown type +}; +``` + +#### OutputFormat + +Exported constant object representing output format options: + +```javascript +export const OutputFormat = { + NORMAL: 0, // Output converted result only + BRACKET: 1, // Output "converted(original)" format + RUBY: 2 // Output HTML tag format +}; +``` + +### Conversion Examples + +#### Intelligent Word Segmentation + +The library supports context-aware intelligent word segmentation, accurately handling one-to-many character mappings: + +```javascript +const { traditionalized } = stcasc(); + +// Intelligent phrase boundary recognition +console.log(traditionalized('香烟袅袅')); +// Output: 香煙裊裊 + +console.log(traditionalized('里长面子')); +// Output: 里長面子 ("里长" is a position title) + +console.log(traditionalized('吃干面')); +// Output: 吃乾麵 ("干面" is a food) + +console.log(traditionalized('把考卷发回来')); +// Output: 把考卷發回來 ("发" is a verb in this context) + +console.log(traditionalized('卷发')); +// Output: 捲髮 ("卷发" is a hairstyle) +``` + +#### Term Conversion + +Built-in conversion for common mainland China and Taiwan terminology: + +```javascript +const { traditionalized } = stcasc(); + +console.log(traditionalized('知识产权')); +// Output: 智慧財產權 + +console.log(traditionalized('计算机软件')); +// Output: 計算機軟體 + +console.log(traditionalized('网络服务器')); +// Output: 網路伺服器 +``` + +### Technical Highlights + +#### High Performance + +- Dictionary tree (Trie) optimization for phrase matching +- Caching mechanism support to avoid repeated initialization +- Pure JavaScript implementation for high execution efficiency + +#### Accurate Conversion + +- Extensive built-in Simplified-Traditional character mappings +- Intelligent recognition for one-to-many character conversions +- Context-based phrase analysis + +#### Easy Integration + +- ES Module standard export +- TypeScript support through constant enumerations +- Zero dependencies, excellent compatibility + +### Browser Support + +Supports all modern browsers and Node.js environments: + +- Chrome +- Firefox +- Safari +- Edge +- Node.js 12+ + +### License + +MIT License + +### Links + +- [GitHub Repository](https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib) +- [NPM Package](https://www.npmjs.com/package/switch-chinese) +- [Issue Tracker](https://github.com/hoothin/UserScripts/issues) + +### Keywords + +Chinese Converter, Simplified Chinese, Traditional Chinese, Chinese Translation, Character Detection, Text Converter, Language Converter, i18n, Localization, Ruby Annotation, One-to-Many Mapping, Intelligent Word Segmentation, Custom Dictionary, Zero Dependencies, Lightweight + --- -+ 基礎用法 -``` js -const stcasc = Stcasc(); -const sc = "简繁转换 繁简切换 香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发 知识产权"; -const tc = stcasc.traditionalized(sc); +## 繁體中文 + +簡繁體中文智能轉換庫 - 支援簡體中文與正體中文雙向轉換,基於詞組的智能分詞處理「一簡多繁」問題 + +[![NPM](https://img.shields.io/npm/v/switch-chinese.svg)](https://www.npmjs.com/package/switch-chinese) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://www.npmjs.com/package/switch-chinese) + +### 特色 + +- **輕量級**:零依賴,體積小,效能優異 +- **智能轉換**:支援基於詞組的智能分詞和「一簡多繁」精準轉換 +- **自訂詞庫**:允許使用者自訂簡繁轉換詞彙 +- **快取機制**:支援字典快取,避免重複初始化 +- **簡繁檢測**:自動檢測文字是簡體中文、繁體中文還是未知類型 +- **術語轉換**:內建大陸簡體與台灣正體的常用術語轉換 +- **無依賴**:純 JavaScript 實現,無需任何第三方依賴 + +### 安裝 + +使用 npm 安裝: + +```bash +npm install switch-chinese +``` + +使用 yarn 安裝: + +```bash +yarn add switch-chinese +``` + +### 快速開始 + +#### 基礎用法 + +```javascript +import stcasc from 'switch-chinese'; + +const { traditionalized, simplized, detect } = stcasc(); + +// 簡體轉繁體 +const tc = traditionalized('简繁转换 繁简切换 香烟 香烟袅袅'); console.log(tc); -//簡繁轉換 繁簡切換 香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 智慧財產權 +// 輸出: 簡繁轉換 繁簡切換 香菸 香煙裊裊 + +// 繁體轉簡體 +const sc = simplized('繁體中文'); +console.log(sc); +// 輸出: 繁体中文 +``` + +#### 檢測中文類型 + +```javascript +import stcasc, { ChineseType } from 'switch-chinese'; + +const { detect } = stcasc(); + +const type1 = detect('简体中文'); +if (type1 === ChineseType.SIMPLIFIED) { + console.log('檢測到簡體中文'); +} + +const type2 = detect('繁體中文'); +if (type2 === ChineseType.TRADITIONAL) { + console.log('檢測到繁體中文'); +} + +const type3 = detect('English'); +if (type3 === ChineseType.UNKNOWN) { + console.log('未檢測到中文'); +} +``` + +ChineseType 列舉值: +- `ChineseType.SIMPLIFIED` (0): 簡體中文 +- `ChineseType.TRADITIONAL` (1): 繁體中文 +- `ChineseType.UNKNOWN` (2): 未知類型 + +### 進階用法 + +#### 使用快取最佳化效能 + +在多次呼叫時,建議使用快取機制避免重複生成字典,提升效能: + +```javascript +import stcasc from 'switch-chinese'; + +// 第一次呼叫,生成字典 +let converter = stcasc(); +const cache = converter.cache; + +// 將快取儲存至持久化儲存(如 localStorage、檔案等) +localStorage.setItem('stcasc-cache', JSON.stringify(cache)); + +// 後續呼叫,直接使用快取 +const cachedData = JSON.parse(localStorage.getItem('stcasc-cache')); +converter = stcasc(cachedData); + +// 現在可以直接使用,無需重新生成字典 +const result = converter.traditionalized('简体中文'); ``` +#### 自訂簡繁轉換詞庫 + +可以根據業務需求自訂簡繁轉換規則: -+ Import +```javascript +import stcasc from 'switch-chinese'; -``` shell -import Stcasc from 'switch-chinese'; +const customDict = { + '身份': '身分', + '转义': '跳脫', + '转换': '轉檔', + '软件': '軟體', + '硬件': '硬體', + '网络': '網路', + '服务器': '伺服器' +}; + +const { traditionalized, simplized } = stcasc({}, customDict); + +console.log(traditionalized('软件转换')); +// 輸出: 軟體轉檔(使用自訂詞庫) ``` -+ 轉正體中文 +#### 停用術語轉換 + +預設情況下,函式庫會轉換一些特定術語(如「知识产权」→「智慧財產權」)。如需停用此功能: + +```javascript +import stcasc from 'switch-chinese'; -``` js -stcasc.traditionalized("简体中文"); -//簡體中文 +// 第三個參數設定為 true 以停用術語轉換 +const { traditionalized } = stcasc({}, {}, true); + +console.log(traditionalized('知识产权')); +// 輸出: 知識産權(僅做字元轉換,不轉換術語) ``` -+ 轉簡體中文 +#### 輸出格式選項 + +函式庫支援多種輸出格式: + +```javascript +import stcasc, { OutputFormat } from 'switch-chinese'; + +const { traditionalized } = stcasc(); + +// 普通格式(預設) +const normal = traditionalized('简体中文'); +// 輸出: 簡體中文 -``` js -stcasc.simplized("繁體中文"); -//繁体中文 +// 括號格式:同時顯示原文和轉換後的文字 +const bracket = traditionalized('简体中文', { format: OutputFormat.BRACKET }); +// 輸出: 簡(简)體(体)中文 + +// Ruby 注音格式:適用於 HTML 顯示 +const ruby = traditionalized('简体中文', { format: OutputFormat.RUBY }); +// 輸出: 中文 ``` -+ 添加快取,避免重複生成字典 +OutputFormat 列舉值: +- `OutputFormat.NORMAL` (0): 只輸出轉換後的結果(預設) +- `OutputFormat.BRACKET` (1): 輸出「轉換(原文)」格式 +- `OutputFormat.RUBY` (2): 輸出 `` HTML 標籤格式 + +### API 文件 + +#### stcasc(cache?, custom?, disableTerms?) + +主函式,用於建立轉換器實例。 + +**參數:** -``` js -let cache = loadCacheAtYourWay(); -let stcasc = Stcasc(cache); -saveCacheAtYourWay(stcasc.cache); +- `cache` (Object, 可選): 快取物件,用於避免重複生成字典 +- `custom` (Object, 可選): 自訂簡繁轉換詞庫 +- `disableTerms` (Boolean, 可選): 是否停用術語轉換,預設 `false` + +**回傳值:** + +回傳包含以下方法的物件: + +- `traditionalized(text, options?)`: 將簡體中文轉換為繁體中文 +- `simplized(text, options?)`: 將繁體中文轉換為簡體中文 +- `detect(text)`: 檢測文字的中文類型,回傳 ChineseType 列舉值 +- `cache`: 字典快取物件 + +**Options 參數:** + +- `format` (Number, 可選): 輸出格式,使用 `OutputFormat` 常數 + +#### ChineseType + +匯出的常數物件,用於表示中文類型檢測結果: + +```javascript +export const ChineseType = { + SIMPLIFIED: 0, // 簡體中文 + TRADITIONAL: 1, // 繁體中文 + UNKNOWN: 2 // 未知類型 +}; ``` -+ 自訂簡繁切換 +#### OutputFormat + +匯出的常數物件,用於表示輸出格式選項: -``` js -const custom = { - "身份": "身分", - "转义": "跳脫", - "转换": "轉檔", - "软件": "軟體" +```javascript +export const OutputFormat = { + NORMAL: 0, // 只輸出轉換後的結果 + BRACKET: 1, // 輸出「轉換(原文)」格式 + RUBY: 2 // 輸出 HTML 標籤格式 }; -const stcasc = Stcasc(cache, custom); ``` -+ 禁用用語轉換 +### 轉換範例 + +#### 智能詞組轉換 + +本函式庫支援基於上下文的智能詞組轉換,能夠正確處理「一簡多繁」的情況: + +```javascript +const { traditionalized } = stcasc(); + +// 智能識別詞組邊界 +console.log(traditionalized('香烟袅袅')); +// 輸出: 香煙裊裊 + +console.log(traditionalized('里长面子')); +// 輸出: 里長面子(「里長」是職務名稱) + +console.log(traditionalized('吃干面')); +// 輸出: 吃乾麵(「乾麵」是食物) + +console.log(traditionalized('把考卷发回来')); +// 輸出: 把考卷發回來(「發」是動詞) + +console.log(traditionalized('卷发')); +// 輸出: 捲髮(「捲髮」是髮型) +``` + +#### 術語轉換 -``` js -const stcasc = Stcasc({}, {}, true); -const sc = "知识产权"; -console.log(stcasc.traditionalized(sc)); -//知識産權 +內建常用的大陸簡體與台灣正體術語轉換: + +```javascript +const { traditionalized } = stcasc(); + +console.log(traditionalized('知识产权')); +// 輸出: 智慧財產權 + +console.log(traditionalized('计算机软件')); +// 輸出: 計算機軟體 + +console.log(traditionalized('网络服务器')); +// 輸出: 網路伺服器 ``` + +### 技術特點 + +#### 高效能 + +- 使用字典樹(Trie)最佳化詞組比對 +- 支援快取機制,避免重複初始化 +- 純 JavaScript 實現,執行效率高 + +#### 精準轉換 + +- 內建大量簡繁對照字元 +- 支援「一簡多繁」智能識別 +- 基於詞組的上下文分析 + +#### 易於整合 + +- ES Module 標準匯出 +- TypeScript 類型支援(透過常數列舉) +- 零依賴,相容性佳 + +### 瀏覽器支援 + +支援所有現代瀏覽器及 Node.js 環境: + +- Chrome +- Firefox +- Safari +- Edge +- Node.js 12+ + +### 開源授權 + +MIT License + +### 相關連結 + +- [GitHub 儲存庫](https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib) +- [NPM 套件位址](https://www.npmjs.com/package/switch-chinese) +- [問題回報](https://github.com/hoothin/UserScripts/issues) + +### 關鍵字 + +簡繁轉換, 繁簡轉換, 簡體中文, 繁體中文, 正體中文, 中文轉換, 一簡多繁, 簡繁切換, 繁簡切換, 中文檢測, 智能分詞, 自訂詞庫, 零依賴, 輕量級 diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts new file mode 100644 index 00000000000..a1826ffbc2b --- /dev/null +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts @@ -0,0 +1,127 @@ +/** + * Chinese character type enumeration + */ +export declare const ChineseType: { + /** Simplified Chinese */ + readonly SIMPLIFIED: 0; + /** Traditional Chinese */ + readonly TRADITIONAL: 1; + /** Unknown type */ + readonly UNKNOWN: 2; +}; + +/** + * Output format enumeration + */ +export declare const OutputFormat: { + /** Normal output - only the converted result */ + readonly NORMAL: 0; + /** Bracket format - outputs「Simplified(Traditional)」or「Traditional(Simplified)」*/ + readonly BRACKET: 1; + /** Ruby annotation format - outputs SimplifiedTraditional */ + readonly RUBY: 2; +}; + +/** + * Conversion options + */ +export interface ConversionOptions { + /** + * Output format + * @default OutputFormat.NORMAL + */ + format?: 0 | 1 | 2; +} + +/** + * Cache object for storing conversion dictionaries + */ +export interface ConversionCache { + sc2tcCombTree?: Record; + tc2scCombTree?: Record; + stDict?: Record; + tsDict?: Record; +} + +/** + * Custom dictionary mapping + * Key: source text (simplified or traditional) + * Value: target text (can be string or array of strings for multiple mappings) + */ +export type CustomDictionary = Record; + +/** + * Result object returned by stcasc function + */ +export interface StcascConverter { + /** + * Convert traditional Chinese to simplified Chinese + * @param text - Text to convert + * @param options - Conversion options + * @returns Converted simplified Chinese text + */ + simplized(text: string, options?: ConversionOptions): string; + + /** + * Convert simplified Chinese to traditional Chinese + * @param text - Text to convert + * @param options - Conversion options + * @returns Converted traditional Chinese text + */ + traditionalized(text: string, options?: ConversionOptions): string; + + /** + * Detect Chinese text type + * @param text - Text to detect + * @returns ChineseType value (0=SIMPLIFIED, 1=TRADITIONAL, 2=UNKNOWN) + */ + detect(text: string): 0 | 1 | 2; + + /** + * Cached conversion dictionaries + */ + cache: ConversionCache; +} + +/** + * Initialize Chinese converter with optional cache and custom dictionary + * + * @param cache - Optional cache object to reuse conversion dictionaries + * @param custom - Optional custom dictionary for special term conversions + * @param disableTerms - If true, disables built-in term conversions + * @returns Converter object with conversion methods + * + * @example + * ```typescript + * import stcasc from 'switch-chinese'; + * + * const converter = stcasc(); + * const traditional = converter.traditionalized('简体中文'); + * const simplified = converter.simplized('繁體中文'); + * const type = converter.detect('繁體中文'); + * ``` + * + * @example + * ```typescript + * // With custom dictionary + * const converter = stcasc({}, { + * '自定义词': '自訂詞', + * '程序': ['程式', '程序'] + * }); + * ``` + * + * @example + * ```typescript + * // With output format + * const converter = stcasc(); + * const result = converter.traditionalized('简体', { format: 1 }); + * // Output: 簡體(简体) + * ``` + */ +declare function stcasc( + cache?: ConversionCache, + custom?: CustomDictionary, + disableTerms?: boolean +): StcascConverter; + +export default stcasc; 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 5ec6942ab14..fd68babb100 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -1,4 +1,16 @@ 'use strict'; +export const ChineseType = { + SIMPLIFIED: 0, + TRADITIONAL: 1, + UNKNOWN: 2 +}; + +export const OutputFormat = { + NORMAL: 0, // 只输出转换后的结果 + BRACKET: 1, // 输出「简(繁)」或「繁(简)」格式 + RUBY: 2 // 输出 格式 +}; + const scStr = '万与丑专业丛东丝丢两严丧个丰临为为丽举么么义乌乐乔习乡书买乱争于亏云亘亚产产亩亲亵亸亿仅仆从仑仓仪们价众众优伙会伛伞伟传伡伣伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬侭俣俦俨俩俪俫俭借债倾偬偻偾偿傤傥傧储傩儿克兑兖党兰关兴兹养兽冁内冈册冗写军农冢冯冲冲决况冻净凄准凉凌减凑凛几凤处凫凭凯凶击凿刍划刘则刚创删别刬刭制刹刽刾刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勖勚匀匦匮区医华协单卖卜卢卤卧卫却卷厂厅历历厉压厌厍厐厕厘厠厢厣厦厨厩厮县叁参叆叇双发发变叙叠只台叶号叹叽吁后吓吕吗吨听启吴呆呐呒呓呕呖呗员呙呛呜周咏咙咛咝咤咨咸响哑哒哓哔哕哗哙哜哝哟唇唉唛唝唠唡唢唤啧啬啭啮啯啰啴啸喂喷喽喾嗫嗳嘘嘤嘱噜嚣团园囱围囵国图圆圣圹场坏块坚坛坛坛坛坜坝坞坟坠垄垅垆垒垦垩垫垭垯垱垲垴埘埙埚堑堕塆墙墻壊壮声壳壶壸処备复复够头夸夹夺奁奂奋奖奥奬妆妇妈妩妪妫姗姜姹娄娅娆娇娈娱娲娴婳婴婵婶媪媭嫒嫔嫱嬀嬷孙学孪宁宁宝实宠审宪宫宽宽宾寝对寻导寿将尔尘尝尧尴尸尽尽层屃屉届属屡屦屿岁岂岖岗岘岚岛岩岭岳岽岿峃峄峡峣峤峥峦峰崂崃崄崭嵘嵚嵝巅巨巩巯币布帅师帏帐帘帜带帧帮帱帻帼幂干干并并广庄庆庐庑库应庙庞废庼廏廪开异弃弑张弥弪弯弹强归当录彝彟彦彨彻征径徕御忆忏志忧念忾怀态怂怃怄怅怆怜总怼怿恋恒恳恶恶恸恹恺恻恼恽悦悫悬悭悮悯惊惧惨惩惫惬惭惮惯愠愤愦愿慑慭懑懒懔戆戋戏戗战戬户扎扑托扦执扩扪扫扬扰抚抛抟抠抡抢护报抬抻担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捂捝捞损捡换捣据捻掳掴掷掸掺掼揽揾揿搀搁搂搄搅携摄摅摆摇摈摊撄撑撵撷撸撺擜擞攒敌敍敚敛敩数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权杠条来杨杩杰松板极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桠桡桢档桤桥桦桧桨桩桪梁梦梼梾梿检棁棂棱椁椝椟椠椢椤椫椭椮楼榄榅榇榈榉榝槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴殻毁毂毕毙毡毵氇气氢氩氲汇汇汉污汤汹沟没沣沤沥沦沧沨沩沪泄泞注泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涚涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溁溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪潆潇潋潍潙潜潨潴澛澜濑濒灏灭灯灵灶灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烟烦烧烨烩烫烬热焕焖焘煴爱爷牍牦牵牺犊状犷犸犹狈狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珐珑珰珲琎琏琐琼瑶瑷瑸璎瓒瓮瓯産电画畅畴疖疗疟疠疡疬疭疮疯疱疴痈痉痒痖痨痪痫痴痹瘅瘆瘉瘗瘘瘪瘫瘾瘿癞癣癫皋皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑睾瞆瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硵硷碍碛碜碱礼祃祎祢祯祷祸禀禄禅离秃秆种秘积称秸秽秾稆税稣稳穑穞穷窃窍窎窑窜窝窥窦窭竖竞竪笃笋笔笕笺笼笾筑筚筛筜筝筹筼签签筿简箓箦箧箨箩箪箫篑篓篮篯篱簖籁籴类籼粜粝粤粪粮糁糇糍系系紧绝絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络絶绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡群翘翙翚翱耢耧耸耻聂聋职聍联聩聪肃肠肤肮肴肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑膻臜致舆舍舣舰舱舻艰艳艺节芈芗芜芦苁苇苈苋苌苍苎苏苧苹范茎茏茑茔茕茧荆荐荙荚荛荜荝荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒀蒇蒉蒋蒌蒏蓝蓟蓠蓣蓥蓦蔂蔷蔹蔺蔼蕰蕲蕴薮藓蘖虏虑虚虫虬虮虱虽虾虿蚀蚁蚂蚃蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衆衔补表衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯訚詟誉誊说说谣讠计订讣认讥讦讧讨让讪讫讬训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谉谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贜贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞跡践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯軆輼车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辟辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郏郐郑郓郦郧郸酂酝酦酱酽酾酿采释里里鈎鉴鉴銮鋭録錾钅钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钜钝钞钟钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铇铈铉铊铋铌铍铎铏铐铑铒铓铔铕铖铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链链铿销锁锂锃锄锅锆锇锈锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗锘错锚锛锜锝锞锟锠锡锢锣锤锥锦锧锨锩锪锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镄镅镆镇镈镉镊镋镌镍镎镏镐镑镒镓镔镕镖镗镘镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镵镶长门闩闪闫闬闭问闯闰闱闲闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陦陧陨险随隐隶隽难雇雏雠雳雾霁霉霡霭靓靔静面靥鞑鞒鞯鞲韦韧韨韩韪韫韬韵頽页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饣饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕駡马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓鬶魇魉鱼鱽鱾鱿鲀鲁鲂鲃鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鳤鷀鷄鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹙鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹮鹯鹰鹱鹲鹳鹴鹾麦麸麹麽黄黉黡黩黪黾鼋鼌鼍鼹齐齑齿龀龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟酸'; const tcStr = '萬與醜專業叢東絲丟兩嚴喪個豐臨為爲麗舉麽麼義烏樂喬習鄉書買亂爭於虧雲亙亞産產畝親褻嚲億僅僕從侖倉儀們價衆眾優夥會傴傘偉傳俥俔傷倀倫傖僞佇體餘傭僉俠侶僥偵側僑儈儕儂儘俁儔儼倆儷倈儉藉債傾傯僂僨償儎儻儐儲儺兒剋兌兗黨蘭關興茲養獸囅內岡冊宂寫軍農塚馮沖衝決況凍淨淒準涼淩減湊凜幾鳳處鳧憑凱兇擊鑿芻劃劉則剛創刪別剗剄製剎劊㓨劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳勗勩勻匭匱區醫華協單賣蔔盧鹵臥衛卻捲廠廳曆歷厲壓厭厙龎廁釐廁廂厴廈廚廄廝縣叄參靉靆雙發髮變敘疊隻臺葉號嘆嘰籲後嚇呂嗎噸聽啟吳獃吶嘸囈嘔嚦唄員咼嗆嗚週詠嚨嚀噝吒諮鹹響啞噠嘵嗶噦嘩噲嚌噥喲脣欸嘜嗊嘮啢嗩喚嘖嗇囀齧嘓囉嘽嘯餵噴嘍嚳囁噯噓嚶囑嚕囂團園囪圍圇國圖圓聖壙場壞塊堅壇罎罈壜壢壩塢墳墜壟壠壚壘墾堊墊埡墶壋塏堖塒壎堝塹墮壪牆牆壞壯聲殼壺壼處備複復夠頭誇夾奪奩奐奮獎奧獎妝婦媽嫵嫗媯姍薑奼婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬃嬡嬪嬙媯嬤孫學孿寧甯寶實寵審憲宮寬寛賓寢對尋導壽將爾塵嘗堯尷屍盡儘層屭屜屆屬屢屨嶼歲豈嶇崗峴嵐島巖嶺嶽崬巋嶨嶧峽嶢嶠崢巒峯嶗崍嶮嶄嶸嶔嶁巔鉅鞏巰幣佈帥師幃帳簾幟帶幀幫幬幘幗冪幹乾並併廣莊慶廬廡庫應廟龐廢廎廄廩開異棄弒張彌弳彎彈強歸當錄彜彠彥彲徹徵徑徠禦憶懺誌憂唸愾懷態慫憮慪悵愴憐總懟懌戀恆懇惡噁慟懨愷惻惱惲悅愨懸慳悞憫驚懼慘懲憊愜慚憚慣慍憤憒願懾憖懣懶懍戇戔戲戧戰戩戶紮撲託扡執擴捫掃揚擾撫拋摶摳掄搶護報擡捵擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏摀挩撈損撿換搗據撚擄摑擲撣摻摜攬搵撳攙擱摟揯攪攜攝攄擺搖擯攤攖撐攆擷擼攛㩵擻攢敵敘敓斂斆數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權槓條來楊榪傑鬆闆極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒椏橈楨檔榿橋樺檜槳樁樳樑夢檮棶槤檢梲櫺稜槨槼櫝槧槶欏樿橢槮樓欖榲櫬櫚櫸樧檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆殼毀轂畢斃氈毿氌氣氫氬氳彙匯漢汙湯洶溝沒灃漚瀝淪滄渢溈滬洩濘註淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧涗濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕濚潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦瀠瀟瀲濰溈潛潀瀦瀂瀾瀨瀕灝滅燈靈竈災燦煬爐燉煒熗點煉熾爍爛烴燭煙菸煩燒燁燴燙燼熱煥燜燾熅愛爺牘犛牽犧犢狀獷獁猶狽獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽琺瓏璫琿璡璉瑣瓊瑤璦璸瓔瓚甕甌產電畫暢疇癤療瘧癘瘍癧瘲瘡瘋皰痾癰痙癢瘂癆瘓癇癡痺癉瘮癒瘞瘻癟癱癮癭癩癬癲臯皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼睪瞶瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確磠鹼礙磧磣堿禮禡禕禰禎禱禍稟祿禪離禿稈種祕積稱稭穢穠穭稅穌穩穡穭窮竊竅窵窯竄窩窺竇窶豎競豎篤筍筆筧箋籠籩築篳篩簹箏籌篔簽籤篠簡籙簀篋籜籮簞簫簣簍籃籛籬籪籟糴類秈糶糲粵糞糧糝餱餈係繫緊絕縶糹糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺紲紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏縧繼綈績緒綾緓續綺緋綽鞝緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨羣翹翽翬翺耮耬聳恥聶聾職聹聯聵聰肅腸膚骯餚腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏羶臢緻輿捨艤艦艙艫艱豔藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇薴蘋範莖蘢蔦塋煢繭荊薦薘莢蕘蓽萴蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蒕蕆蕢蔣蔞醟藍薊蘺蕷鎣驀虆薔蘞藺藹薀蘄蘊藪蘚櫱虜慮虛蟲虯蟣蝨雖蝦蠆蝕蟻螞蠁蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁眾銜補錶襯袞襖裊褘襪襲襏裝襠褌褳襝褲襉褸襤襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶誾讋譽謄說説謡訁計訂訃認譏訐訌討讓訕訖託訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談讅誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗謚謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶贓貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗贊讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒蹟踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀體轀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭闢辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郟鄶鄭鄆酈鄖鄲酇醞醱醬釅釃釀採釋裏裡鉤鑒鑑鑾銳錄鏨釒釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鉅鈍鈔鍾鐘鈉鋇鋼鈑鈐鑰欽鈞鎢鈎鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鉋鈰鉉鉈鉍鈮鈹鐸鉶銬銠鉺鋩錏銪鋮鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鎩鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鍊鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銹銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺鍩錯錨錛錡鍀錁錕錩錫錮鑼錘錐錦鑕鍁錈鍃錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鐨鎇鏌鎮鎛鎘鑷钂鐫鎳鎿鎦鎬鎊鎰鎵鑌鎔鏢鏜鏝鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔钀鑞鑱鑲長門閂閃閆閈閉問闖閏闈閑閒閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隯隉隕險隨隱隸雋難僱雛讎靂霧霽黴霢靄靚靝靜麵靨韃鞽韉韝韋韌韍韓韙韞韜韻頹頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顔顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飈飛饗饜飠飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢罵馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢鬹魘魎魚魛魢魷魨魯魴䰾魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鰺鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鼈鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣䲘鶿雞鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鷽鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鵾鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶖鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺䴉鸇鷹鸌鸏鸛鸘鹺麥麩麴麼黃黌黶黷黲黽黿鼂鼉鼴齊齏齒齔齕齗齟齡齙齠齜齦齬齪齲齷龍龔龕龜痠'; const oc2tc = { @@ -610,12 +622,15 @@ let sc2tcComb = { var stDict = {}, tsDict = {}; var sc2tcCombTree = {}, tc2scCombTree = {}; -function traditionalized(orgStr) { +function traditionalized(orgStr, options) { + options = options || {}; + const format = options.format !== undefined ? options.format : OutputFormat.NORMAL; + if (!orgStr) return ""; var str = '', char; for (var i = 0; i < orgStr.length; i++) { char = orgStr.charAt(i); - let search = sc2tcCombTree[char], searchIndex = i, hasMatch = false; + let search = sc2tcCombTree[char], searchIndex = i, hasMatch = false, startIndex = i; while (search && searchIndex < orgStr.length) { let downTree = null; if (searchIndex < orgStr.length - 1) { @@ -625,7 +640,19 @@ function traditionalized(orgStr) { if (search.end) { hasMatch = true; i = searchIndex; - str += search.end; + if (format === OutputFormat.NORMAL) { + str += search.end; + } else { + const originalText = orgStr.substring(startIndex, searchIndex + 1); + const convertedText = search.end; + if (format === OutputFormat.BRACKET && originalText !== convertedText) { + str += convertedText + '(' + originalText + ')'; + } else if (format === OutputFormat.RUBY && originalText !== convertedText) { + str += '' + convertedText + '' + originalText + ''; + } else { + str += convertedText; + } + } } break; } @@ -675,7 +702,13 @@ function traditionalized(orgStr) { } else { newChar = tChar; } - str += newChar; + if (format === OutputFormat.BRACKET && char !== newChar) { + str += newChar + '(' + char + ')'; + } else if (format === OutputFormat.RUBY && char !== newChar) { + str += '' + newChar + '' + char + ''; + } else { + str += newChar; + } } else str += char; } else str += char; @@ -683,12 +716,15 @@ function traditionalized(orgStr) { return str; } -function simplized(orgStr) { +function simplized(orgStr, options) { + options = options || {}; + const format = options.format !== undefined ? options.format : OutputFormat.NORMAL; + if (!orgStr) return ""; var str = '', char; for (var i = 0; i < orgStr.length; i++) { char = orgStr.charAt(i); - let search = tc2scCombTree[char], searchIndex = i, hasMatch = false; + let search = tc2scCombTree[char], searchIndex = i, hasMatch = false, startIndex = i; while (search && searchIndex < orgStr.length) { let downTree = null; if (searchIndex < orgStr.length - 1) { @@ -698,7 +734,19 @@ function simplized(orgStr) { if (search.end) { hasMatch = true; i = searchIndex; - str += search.end; + if (format === OutputFormat.NORMAL) { + str += search.end; + } else { + const originalText = orgStr.substring(startIndex, searchIndex + 1); + const convertedText = search.end; + if (format === OutputFormat.BRACKET && originalText !== convertedText) { + str += convertedText + '(' + originalText + ')'; + } else if (format === OutputFormat.RUBY && originalText !== convertedText) { + str += '' + convertedText + '' + originalText + ''; + } else { + str += convertedText; + } + } } break; } @@ -748,7 +796,13 @@ function simplized(orgStr) { } else { newChar = sChar; } - str += newChar; + if (format === OutputFormat.BRACKET && char !== newChar) { + str += newChar + '(' + char + ')'; + } else if (format === OutputFormat.RUBY && char !== newChar) { + str += '' + newChar + '' + char + ''; + } else { + str += newChar; + } } else str += char; } else str += char; @@ -756,7 +810,33 @@ function simplized(orgStr) { return str; } -function Stcasc(cache, custom, disableTerms) { +function detect(text) { + if (!text) return ChineseType.UNKNOWN; + + for (let i = 0; i < text.length; i++) { + const char = text.charAt(i); + if (char.charCodeAt(0) > 10000) { + const scChar = tsDict[char]; + if (scChar && scChar !== char) { + return ChineseType.TRADITIONAL; + } + } + } + + for (let i = 0; i < text.length; i++) { + const char = text.charAt(i); + if (char.charCodeAt(0) > 10000) { + const tcChar = stDict[char]; + if (tcChar && tcChar !== char) { + return ChineseType.SIMPLIFIED; + } + } + } + + return ChineseType.UNKNOWN; +} + +function stcasc(cache, custom, disableTerms) { if (!cache) cache = {}; if (cache.sc2tcCombTree && cache.tc2scCombTree) { sc2tcCombTree = cache.sc2tcCombTree; @@ -834,7 +914,7 @@ function Stcasc(cache, custom, disableTerms) { cache.stDict = stDict; cache.tsDict = tsDict; } - return {simplized, traditionalized, cache}; + return {simplized, traditionalized, detect, cache}; } -export default Stcasc; \ No newline at end of file +export default stcasc; \ No newline at end of file diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/test.js b/Switch Traditional Chinese and Simplified Chinese/lib/test.js new file mode 100644 index 00000000000..e62bdfd428d --- /dev/null +++ b/Switch Traditional Chinese and Simplified Chinese/lib/test.js @@ -0,0 +1,114 @@ +import stcasc, { ChineseType, OutputFormat } from './stcasc.lib.js'; + +console.log('========== 简繁转换库测试 ==========\n'); + +// 初始化 +const { traditionalized, simplized, detect } = stcasc(); + +// 测试 1: 基础简体转繁体 +console.log('测试 1: 基础简体转繁体'); +const sc1 = '简繁转换 繁简切换 香烟 香烟袅袅 烟雾里 里长面子'; +const tc1 = traditionalized(sc1); +console.log('输入:', sc1); +console.log('输出:', tc1); +console.log(''); + +// 测试 2: 基础繁体转简体 +console.log('测试 2: 基础繁体转简体'); +const tc2 = '繁體中文 簡體中文'; +const sc2 = simplized(tc2); +console.log('输入:', tc2); +console.log('输出:', sc2); +console.log(''); + +// 测试 3: 检测简体中文 +console.log('测试 3: 检测简体中文'); +const text1 = '这是简体中文'; +const type1 = detect(text1); +console.log('文本:', text1); +console.log('类型:', type1 === ChineseType.SIMPLIFIED ? '简体中文' : '未知'); +console.log('枚举值:', type1); +console.log(''); + +// 测试 4: 检测繁体中文 +console.log('测试 4: 检测繁体中文'); +const text2 = '這是繁體中文'; +const type2 = detect(text2); +console.log('文本:', text2); +console.log('类型:', type2 === ChineseType.TRADITIONAL ? '繁体中文' : '未知'); +console.log('枚举值:', type2); +console.log(''); + +// 测试 5: 检测非中文 +console.log('测试 5: 检测非中文'); +const text3 = 'English Text'; +const type3 = detect(text3); +console.log('文本:', text3); +console.log('类型:', type3 === ChineseType.UNKNOWN ? '未知' : '中文'); +console.log('枚举值:', type3); +console.log(''); + +// 测试 6: 输出格式 - BRACKET(括号格式) +console.log('测试 6: 输出格式 - BRACKET(括号格式)'); +const sc3 = '简体中文转换'; +const tc3 = traditionalized(sc3, { format: OutputFormat.BRACKET }); +console.log('输入:', sc3); +console.log('输出:', tc3); +console.log(''); + +// 测试 7: 输出格式 - RUBY(HTML ruby标签格式) +console.log('测试 7: 输出格式 - RUBY(HTML ruby标签格式)'); +const sc4 = '简体中文'; +const tc4 = traditionalized(sc4, { format: OutputFormat.RUBY }); +console.log('输入:', sc4); +console.log('输出:', tc4); +console.log(''); + +// 测试 8: 繁体转简体 - BRACKET格式 +console.log('测试 8: 繁体转简体 - BRACKET格式'); +const tc5 = '繁體中文'; +const sc5 = simplized(tc5, { format: OutputFormat.BRACKET }); +console.log('输入:', tc5); +console.log('输出:', sc5); +console.log(''); + +// 测试 9: 繁体转简体 - RUBY格式 +console.log('测试 9: 繁体转简体 - RUBY格式'); +const tc6 = '繁體中文'; +const sc6 = simplized(tc6, { format: OutputFormat.RUBY }); +console.log('输入:', tc6); +console.log('输出:', sc6); +console.log(''); + +// 测试 10: 复杂文本转换 - 正常格式 +console.log('测试 10: 复杂文本转换 - 正常格式'); +const complex = '吃干面 把考卷发回来 卷发 知识产权'; +const complexTc = traditionalized(complex); +console.log('输入:', complex); +console.log('输出:', complexTc); +console.log(''); + +// 测试 11: 复杂文本转换 - BRACKET格式 +console.log('测试 11: 复杂文本转换 - BRACKET格式'); +const complexBracket = traditionalized(complex, { format: OutputFormat.BRACKET }); +console.log('输入:', complex); +console.log('输出:', complexBracket); +console.log(''); + +// 测试 12: 术语转换 +console.log('测试 12: 术语转换'); +const terms = '软件 硬盘 网络 服务器 鼠标'; +const termsTc = traditionalized(terms); +console.log('输入:', terms); +console.log('输出:', termsTc); +console.log(''); + +// 测试 13: 混合文本 +console.log('测试 13: 混合文本'); +const mixed = 'This is 简体中文 and English'; +const mixedTc = traditionalized(mixed); +console.log('输入:', mixed); +console.log('输出:', mixedTc); +console.log(''); + +console.log('========== 测试完成 =========='); From 565a2a83711a316a9a44ca0f5718ad9a3760b582 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 9 Nov 2025 21:41:22 +0900 Subject: [PATCH 15/99] Update README.md --- .../README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Switch Traditional Chinese and Simplified Chinese/README.md b/Switch Traditional Chinese and Simplified Chinese/README.md index 5b4a8effcb0..1466c414d28 100644 --- a/Switch Traditional Chinese and Simplified Chinese/README.md +++ b/Switch Traditional Chinese and Simplified Chinese/README.md @@ -34,6 +34,22 @@
'鼠标':'滑鼠'
 
+漢語拼音標注 +--- + +目前有個問題,除了配對到詞組的多音字之外,多音單字還需要一個預設拼音。 + +例如“了”應該預設是“le”而不是“liao”。 + +我改了一些,常用的幾個多音單字都改了。但我數了一下,這類多音單字總共有800多個,需要有人來幫忙整理一下。 + +如果有對幫忙有興趣的朋友,以下是整理步驟: + ++ 下載dict.txt ++ 正規搜尋`^(\S \S) \[.*\n(\1.*\n)+` ++ 刪除多餘的行,只保留一個預設讀音 ++ 將修改後的字典提交給我,也可以直接執行專案中的 run.py 來轉換檔案後查看效果 + npm 前端庫 --- From da468b4632cbe676bfaf206536855cba93ac740b Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 10 Nov 2025 20:42:22 +0900 Subject: [PATCH 16/99] update --- .../lib/package.json | 4 +- .../lib/stcasc.lib.js | 152 +++++++++--------- X-Downloader/X-Downloader.user.js | 14 +- 3 files changed, 89 insertions(+), 81 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 6b5b53c909e..623ba02340f 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json +++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json @@ -1,7 +1,7 @@ { "name": "switch-chinese", - "version": "1.0.7", - "description": "Lightweight Chinese converter library for bidirectional conversion between Simplified and Traditional Chinese with intelligent word segmentation, custom dictionary support, character detection, and multiple output formats. Zero dependencies. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", + "version": "1.0.10", + "description": "簡繁轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", "types": "stcasc.d.ts", "type": "module", 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 fd68babb100..f45a1c0867e 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -837,84 +837,90 @@ function detect(text) { } function stcasc(cache, custom, disableTerms) { - if (!cache) cache = {}; - if (cache.sc2tcCombTree && cache.tc2scCombTree) { - sc2tcCombTree = cache.sc2tcCombTree; + if (!cache) cache = {}; + if (cache.sc2tcCombTree && cache.tc2scCombTree) { + sc2tcCombTree = cache.sc2tcCombTree; tc2scCombTree = cache.tc2scCombTree; - } else { + } else { if (disableTerms) sc2tcComb = {}; - if (custom && custom.length) { - for (let sc in custom) { - sc2tcComb[sc] = custom[sc]; - } - } - function makeCombTree(key, value) { - let curTree = sc2tcCombTree; - for (let i = 0; i < key.length; i++) { - let newTree = {}; - if (i == key.length - 1) { - newTree = {"end": value}; - } - let curKey = key.charAt(i); - let branch = curTree[curKey]; - if (!branch) { - curTree[curKey] = newTree; - curTree = newTree; - } else { - curTree = branch; - } - } - curTree = tc2scCombTree; - for (let i = 0; i < value.length; i++) { - let newTree = {}; - if (i == value.length - 1) { - newTree = {"end": key}; - } - let curKey = value.charAt(i); - let branch = curTree[curKey]; - if (!branch) { - curTree[curKey] = newTree; - curTree = newTree; - } else { - curTree = branch; - } - } - } - for (let key in sc2tcComb) { - let value = sc2tcComb[key]; - if (Array.isArray(value)) { - value.forEach(v => { - makeCombTree(key, v); - }); - } else { - makeCombTree(key, value); - } - } - cache.sc2tcCombTree = sc2tcCombTree; + if (custom && custom.length) { + for (let sc in custom) { + sc2tcComb[sc] = custom[sc]; + } + } + function makeCombTree(key, value) { + let curTree = sc2tcCombTree; + for (let i = 0; i < key.length; i++) { + let curKey = key.charAt(i); + let branch = curTree[curKey]; + let newTree = {}; + if (i == key.length - 1) { + newTree = {"end": value}; + if (branch) { + branch.end = value; + } + } + if (branch) { + curTree = branch; + } else { + curTree[curKey] = newTree; + curTree = newTree; + } + } + curTree = tc2scCombTree; + for (let i = 0; i < value.length; i++) { + let curKey = value.charAt(i); + let branch = curTree[curKey]; + let newTree = {}; + if (i == value.length - 1) { + newTree = {"end": key}; + if (branch) { + branch.end = value; + } + } + if (branch) { + curTree = branch; + } else { + curTree[curKey] = newTree; + curTree = newTree; + } + } + } + for (let key in sc2tcComb) { + let value = sc2tcComb[key]; + if (Array.isArray(value)) { + value.forEach(v => { + makeCombTree(key, v); + }); + } else { + makeCombTree(key, value); + } + } + cache.sc2tcCombTree = sc2tcCombTree; cache.tc2scCombTree = tc2scCombTree; - } - if (cache.stDict && cache.tsDict) { - stDict = cache.stDict; + } + if (cache.stDict && cache.tsDict) { + stDict = cache.stDict; tsDict = cache.tsDict; - } else { - for (let i = 0; i < scStr.length; i++) { - let _sc = scStr[i]; - let _tc = tcStr[i]; - if (!stDict[_sc]) stDict[_sc] = _tc; - if (!tsDict[_tc]) tsDict[_tc] = _sc; - } - Object.keys(oc2tc).forEach(key => { - let ocList = oc2tc[key]; - for (let i = 0; i < ocList.length; i++) { - let oc = ocList[i]; - stDict[oc] = key; - tsDict[oc] = tsDict[key] || key; - } - }) - cache.stDict = stDict; + } else { + for (let i = 0; i < scStr.length; i++) { + let _sc = scStr[i]; + let _tc = tcStr[i]; + if (!stDict[_sc]) stDict[_sc] = _tc; + if (!tsDict[_tc]) tsDict[_tc] = _sc; + } + Object.keys(oc2tc).forEach(key => { + let ocList = oc2tc[key]; + for (let i = 0; i < ocList.length; i++) { + let oc = ocList[i]; + stDict[oc] = key; + tsDict[oc] = tsDict[key] || key; + } + }) + cache.stDict = stDict; cache.tsDict = tsDict; - } - return {simplized, traditionalized, detect, cache}; + } + return {simplized, traditionalized, detect, cache}; } export default stcasc; \ No newline at end of file diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index c488f1f1ef4..72531dfbd90 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -15,19 +15,20 @@ // @match https://twitter.com/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none -// @downloadURL https://update.greasyfork.org/scripts/545186/X-Downloader.user.js -// @updateURL https://update.greasyfork.org/scripts/545186/X-Downloader.meta.js +// @downloadURL https://update.greasyfork.org/scripts/545186/X-Downloader-Script.user.js +// @updateURL https://update.greasyfork.org/scripts/545186/X-Downloader-Script.meta.js // ==/UserScript== (function() { 'use strict'; - let downloadBtn = document.createElement("a"), touch = false; + let downloadBtn = document.createElement("a"), touch = false, simpleClick = false; downloadBtn.target = "_blank"; downloadBtn.style.cssText = "background: #000000aa; border-radius: 50%; transition: opacity ease 0.3s; position: absolute; top: 0; right: 0px; cursor: pointer; opacity: 0; padding: 5px;"; downloadBtn.innerHTML = ``; downloadBtn.addEventListener("mousedown", e => { let parent = downloadBtn.parentNode; if (!parent) return; + simpleClick = false; let img = parent.querySelector('[data-testid="tweetPhoto"]>img,[data-testid="card.layoutLarge.media"] img'); if (img) { let newsrc = img.src.replace("_normal.",".").replace("_200x200.",".").replace("_mini.",".").replace("_bigger.",".").replace(/_x\d+\./,"."), imgname; @@ -59,7 +60,8 @@ imgname = `${user.innerText} ${time.innerText.replace(/(.*) · (.*)/, "$2 $1")}.${ext}`; } downloadBtn.href = newsrc; - if (e.altKey || touch) { + if ((e.button === 0 && !e.ctrlKey) || touch) { + simpleClick = true; downloadByFetch(newsrc, imgname); } } else { @@ -72,7 +74,7 @@ if (parent) { downloadBtn.removeAttribute('download'); let link = parent.querySelector('a[role="link"][aria-label][href^="/"]'); - downloadBtn.href = `https://twitter.hoothin.com/?url=${encodeURIComponent(link ? link.href : document.location.href)}`; + downloadBtn.href = `https://twitter.luopo.org/?url=${encodeURIComponent(link ? link.href : document.location.href)}`; if (e.altKey || touch) { window.open(downloadBtn.href, "_blank"); } @@ -80,7 +82,7 @@ } }); downloadBtn.addEventListener("click", e => { - if (e.altKey || touch) { + if (simpleClick || e.altKey || touch) { e.preventDefault(); e.stopPropagation(); } From 56d3efea043510a3372a60bdc3f85fc353cf072b Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 10 Nov 2025 22:03:09 +0900 Subject: [PATCH 17/99] Update DownloadAllContent.user.js --- DownloadAllContent/DownloadAllContent.user.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/DownloadAllContent/DownloadAllContent.user.js b/DownloadAllContent/DownloadAllContent.user.js index e33a2ae3275..4df64b84efc 100644 --- a/DownloadAllContent/DownloadAllContent.user.js +++ b/DownloadAllContent/DownloadAllContent.user.js @@ -931,7 +931,7 @@ if (window.top != window.self) { height: 30px;line-height: 30px;display:block;color:#FFF;text-align:center;font-size: 12px;font-weight: bold;font-family: arial;background: initial; float: initial; } #txtDownQuit+div{ - position:absolute;right:0px;bottom:2px;cursor: pointer;max-width:85px; + position:absolute;right:0px;bottom:2px;cursor: pointer;display: flex; } #txtDownQuit+div>button{ background: #008aff;border: 0;padding: 5px;border-radius: 6px;color: white;float: right;margin: 1px;height: 25px;line-height: 16px;cursor: pointer;overflow: hidden; @@ -1071,7 +1071,7 @@ if (window.top != window.self) { console.warn(e); } } - function packLink(doc, item, curIndex) { + function packLink(doc, item) { if (customTitle) { try { let title = doc.querySelector(customTitle); @@ -1082,9 +1082,6 @@ if (window.top != window.self) { console.warn(e); } } - if (prefix) { - item.innerText = prefix.replace(/\$i/g, ++curIndex) + item.innerText; - } } function getIframe() { if (iframePool && iframePool.length) return iframePool.shift(); @@ -1424,11 +1421,16 @@ if (window.top != window.self) { } } rCats = rCats.filter(function(e){return e!=null}); + if (prefix) { + for(i=0;i{ - packLink(doc, aTag, i); + packLink(doc, aTag); let isHref = ""; let saveUrl = GM_getValue("saveUrl"); if (saveUrl){ From c9181342c87cc2b5871acb79f135a8c7da7b2600 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 11 Nov 2025 09:57:44 +0900 Subject: [PATCH 18/99] Update pagetual.user.js --- Pagetual/pagetual.user.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 58cca3fb32b..32c0e0c844c 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -4908,10 +4908,12 @@ getBody(doc).scrollTop = actualTop - 10; doc.documentElement.scrollTop = actualTop - 10; setTimeout(() => { - while (actualTop < maxHeight) { + if (actualTop < maxHeight) { actualTop += 200; getBody(doc).scrollTop = actualTop; doc.documentElement.scrollTop = actualTop; + getBody(doc).scrollTop = maxHeight; + doc.documentElement.scrollTop = maxHeight; } }, 0); return false; @@ -4958,7 +4960,13 @@ runPageBar(pageBar) { if (this.curSiteRule.pageBar && this.curSiteRule.pageBar !== 0) { try { - ((typeof this.curSiteRule.pageBar === 'function') ? this.curSiteRule.pageBar : Function("pageBar",'"use strict";' + this.curSiteRule.pageBar))(pageBar); + if (typeof this.curSiteRule.pageBar === 'function') { + this.curSiteRule.pageBar(pageBar); + } else if (/^pageBar\.className=['"][^'"]*['"];?$/.test(this.curSiteRule.pageBar)) { + pageBar.className = this.curSiteRule.pageBar.match(/^pageBar\.className=['"]([^'"]*)['"];?$/)[1]; + } else { + Function("pageBar",'"use strict";' + this.curSiteRule.pageBar)(pageBar); + } } catch(e) { debug(e); } From 842e9f68e7059f229197740d1314afe5edfdc2d2 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 26 Nov 2025 19:00:52 +0900 Subject: [PATCH 19/99] update --- .../lib/package.json | 4 +- .../lib/readme.md | 177 +++++++++++++++++- .../lib/stcasc.d.ts | 52 ++++- .../lib/stcasc.lib.js | 70 ++++++- .../lib/test.js | 101 ++++++++++ 5 files changed, 384 insertions(+), 20 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 623ba02340f..d179f7ced29 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json +++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json @@ -1,7 +1,7 @@ { "name": "switch-chinese", - "version": "1.0.10", - "description": "簡繁轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", + "version": "1.0.12", + "description": "繁簡轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", "types": "stcasc.d.ts", "type": "module", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index 5e61732887f..895ac2dc06a 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -14,6 +14,7 @@ - **轻量级**:零依赖,体积小,性能优异 - **智能转换**:支持基于词组的智能分词和「一简多繁」精准转换 +- **多种数据类型**:支持字符串、数组、对象的转换,自动递归处理嵌套结构 - **自定义词库**:允许用户自定义简繁转换词汇 - **缓存机制**:支持字典缓存,避免重复初始化 - **简繁检测**:自动检测文本是简体中文、繁体中文还是未知类型 @@ -82,6 +83,54 @@ ChineseType 枚举值: - `ChineseType.TRADITIONAL` (1): 繁体中文 - `ChineseType.UNKNOWN` (2): 未知类型 +#### 转换数组和对象 + +库支持转换数组和对象中的所有字符串,非字符串值保持原样: + +```javascript +import stcasc from 'switch-chinese'; + +const { traditionalized, simplized } = stcasc(); + +// 转换数组 +const arr = ['简体中文', '软件', '网络', 123, true, null]; +const arrTc = traditionalized(arr); +console.log(arrTc); +// 输出: ['簡體中文', '軟體', '網路', 123, true, null] + +// 转换对象 +const obj = { + title: '简体中文标题', + description: '这是一个简体中文描述', + count: 100, + active: true, + tags: ['软件', '网络', '服务器'] +}; +const objTc = traditionalized(obj); +console.log(objTc); +// 输出: { +// title: '簡體中文標題', +// description: '這是一個簡體中文描述', +// count: 100, +// active: true, +// tags: ['軟體', '網路', '伺服器'] +// } + +// 转换嵌套结构 +const nested = { + user: { + name: '简体名称', + profile: { + bio: '这是简体中文简介', + skills: ['软件开发', '网络管理'] + } + }, + count: 42 +}; +const nestedTc = traditionalized(nested); +// 所有字符串属性值都会被转换,数字等其他类型保持不变 +``` + ### 高级用法 #### 使用缓存优化性能 @@ -186,8 +235,14 @@ OutputFormat 枚举值: 返回包含以下方法的对象: -- `traditionalized(text, options?)`: 将简体中文转换为繁体中文 -- `simplized(text, options?)`: 将繁体中文转换为简体中文 +- `traditionalized(input, options?)`: 将简体中文转换为繁体中文 + - `input`: 可以是字符串、数组或对象 + - 字符串:直接转换返回新字符串 + - 数组:转换所有字符串元素,其他类型保持不变 + - 对象:递归转换所有字符串属性值,其他类型保持不变 +- `simplized(input, options?)`: 将繁体中文转换为简体中文 + - `input`: 可以是字符串、数组或对象 + - 支持的数据类型同 `traditionalized` - `detect(text)`: 检测文本的中文类型,返回 ChineseType 枚举值 - `cache`: 字典缓存对象 @@ -318,6 +373,7 @@ Lightweight Chinese converter library for bidirectional conversion between Simpl - **Lightweight**: Zero dependencies, small footprint, excellent performance - **Intelligent Conversion**: Context-aware word segmentation and accurate one-to-many character mapping +- **Multiple Data Types**: Supports string, array, and object conversion with automatic recursive processing - **Custom Dictionary**: User-defined conversion rules support - **Caching Mechanism**: Dictionary caching to avoid repeated initialization - **Text Detection**: Automatic detection of Simplified Chinese, Traditional Chinese, or unknown text @@ -386,6 +442,54 @@ ChineseType enumeration values: - `ChineseType.TRADITIONAL` (1): Traditional Chinese - `ChineseType.UNKNOWN` (2): Unknown type +#### Converting Arrays and Objects + +The library supports converting all strings in arrays and objects, while preserving non-string values: + +```javascript +import stcasc from 'switch-chinese'; + +const { traditionalized, simplized } = stcasc(); + +// Convert array +const arr = ['简体中文', '软件', '网络', 123, true, null]; +const arrTc = traditionalized(arr); +console.log(arrTc); +// Output: ['簡體中文', '軟體', '網路', 123, true, null] + +// Convert object +const obj = { + title: '简体中文标题', + description: '这是一个简体中文描述', + count: 100, + active: true, + tags: ['软件', '网络', '服务器'] +}; +const objTc = traditionalized(obj); +console.log(objTc); +// Output: { +// title: '簡體中文標題', +// description: '這是一個簡體中文描述', +// count: 100, +// active: true, +// tags: ['軟體', '網路', '伺服器'] +// } + +// Convert nested structures +const nested = { + user: { + name: '简体名称', + profile: { + bio: '这是简体中文简介', + skills: ['软件开发', '网络管理'] + } + }, + count: 42 +}; +const nestedTc = traditionalized(nested); +// All string property values will be converted, other types remain unchanged +``` + ### Advanced Usage #### Performance Optimization with Caching @@ -490,8 +594,14 @@ Main function to create a converter instance. An object containing the following methods: -- `traditionalized(text, options?)`: Convert Simplified Chinese to Traditional Chinese -- `simplized(text, options?)`: Convert Traditional Chinese to Simplified Chinese +- `traditionalized(input, options?)`: Convert Simplified Chinese to Traditional Chinese + - `input`: Can be a string, array, or object + - String: Directly converts and returns a new string + - Array: Converts all string elements, other types remain unchanged + - Object: Recursively converts all string property values, other types remain unchanged +- `simplized(input, options?)`: Convert Traditional Chinese to Simplified Chinese + - `input`: Can be a string, array, or object + - Supports the same data types as `traditionalized` - `detect(text)`: Detect Chinese text type, returns ChineseType enumeration value - `cache`: Dictionary cache object @@ -622,6 +732,7 @@ Chinese Converter, Simplified Chinese, Traditional Chinese, Chinese Translation, - **輕量級**:零依賴,體積小,效能優異 - **智能轉換**:支援基於詞組的智能分詞和「一簡多繁」精準轉換 +- **多種資料類型**:支援字串、陣列、物件的轉換,自動遞迴處理巢狀結構 - **自訂詞庫**:允許使用者自訂簡繁轉換詞彙 - **快取機制**:支援字典快取,避免重複初始化 - **簡繁檢測**:自動檢測文字是簡體中文、繁體中文還是未知類型 @@ -690,6 +801,54 @@ ChineseType 列舉值: - `ChineseType.TRADITIONAL` (1): 繁體中文 - `ChineseType.UNKNOWN` (2): 未知類型 +#### 轉換陣列和物件 + +函式庫支援轉換陣列和物件中的所有字串,非字串值保持原樣: + +```javascript +import stcasc from 'switch-chinese'; + +const { traditionalized, simplized } = stcasc(); + +// 轉換陣列 +const arr = ['简体中文', '软件', '网络', 123, true, null]; +const arrTc = traditionalized(arr); +console.log(arrTc); +// 輸出: ['簡體中文', '軟體', '網路', 123, true, null] + +// 轉換物件 +const obj = { + title: '简体中文标题', + description: '这是一个简体中文描述', + count: 100, + active: true, + tags: ['软件', '网络', '服务器'] +}; +const objTc = traditionalized(obj); +console.log(objTc); +// 輸出: { +// title: '簡體中文標題', +// description: '這是一個簡體中文描述', +// count: 100, +// active: true, +// tags: ['軟體', '網路', '伺服器'] +// } + +// 轉換巢狀結構 +const nested = { + user: { + name: '简体名称', + profile: { + bio: '这是简体中文简介', + skills: ['软件开发', '网络管理'] + } + }, + count: 42 +}; +const nestedTc = traditionalized(nested); +// 所有字串屬性值都會被轉換,數字等其他類型保持不變 +``` + ### 進階用法 #### 使用快取最佳化效能 @@ -794,8 +953,14 @@ OutputFormat 列舉值: 回傳包含以下方法的物件: -- `traditionalized(text, options?)`: 將簡體中文轉換為繁體中文 -- `simplized(text, options?)`: 將繁體中文轉換為簡體中文 +- `traditionalized(input, options?)`: 將簡體中文轉換為繁體中文 + - `input`: 可以是字串、陣列或物件 + - 字串:直接轉換並回傳新字串 + - 陣列:轉換所有字串元素,其他類型保持不變 + - 物件:遞迴轉換所有字串屬性值,其他類型保持不變 +- `simplized(input, options?)`: 將繁體中文轉換為簡體中文 + - `input`: 可以是字串、陣列或物件 + - 支援的資料類型同 `traditionalized` - `detect(text)`: 檢測文字的中文類型,回傳 ChineseType 列舉值 - `cache`: 字典快取物件 diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts index a1826ffbc2b..350324519b8 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts @@ -33,12 +33,20 @@ export interface ConversionOptions { format?: 0 | 1 | 2; } +/** + * Tree node for combination dictionary + */ +interface CombTreeNode { + end?: string; + [key: string]: CombTreeNode | string | undefined; +} + /** * Cache object for storing conversion dictionaries */ export interface ConversionCache { - sc2tcCombTree?: Record; - tc2scCombTree?: Record; + sc2tcCombTree?: Record; + tc2scCombTree?: Record; stDict?: Record; tsDict?: Record; } @@ -56,20 +64,52 @@ export type CustomDictionary = Record; export interface StcascConverter { /** * Convert traditional Chinese to simplified Chinese - * @param text - Text to convert + * @param text - String to convert * @param options - Conversion options - * @returns Converted simplified Chinese text + * @returns Converted simplified Chinese string */ simplized(text: string, options?: ConversionOptions): string; + /** + * Convert traditional Chinese to simplified Chinese (array version) + * @param data - Array to convert (converts all strings in the array) + * @param options - Conversion options + * @returns Array with converted strings + */ + simplized(data: T, options?: ConversionOptions): T; + + /** + * Convert traditional Chinese to simplified Chinese (object version) + * @param data - Object to convert (converts all string property values) + * @param options - Conversion options + * @returns Object with converted string values + */ + simplized>(data: T, options?: ConversionOptions): T; + /** * Convert simplified Chinese to traditional Chinese - * @param text - Text to convert + * @param text - String to convert * @param options - Conversion options - * @returns Converted traditional Chinese text + * @returns Converted traditional Chinese string */ traditionalized(text: string, options?: ConversionOptions): string; + /** + * Convert simplified Chinese to traditional Chinese (array version) + * @param data - Array to convert (converts all strings in the array) + * @param options - Conversion options + * @returns Array with converted strings + */ + traditionalized(data: T, options?: ConversionOptions): T; + + /** + * Convert simplified Chinese to traditional Chinese (object version) + * @param data - Object to convert (converts all string property values) + * @param options - Conversion options + * @returns Object with converted string values + */ + traditionalized>(data: T, options?: ConversionOptions): T; + /** * Detect Chinese text type * @param text - Text to detect 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 f45a1c0867e..3124b14018f 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -622,10 +622,7 @@ let sc2tcComb = { var stDict = {}, tsDict = {}; var sc2tcCombTree = {}, tc2scCombTree = {}; -function traditionalized(orgStr, options) { - options = options || {}; - const format = options.format !== undefined ? options.format : OutputFormat.NORMAL; - +function traditionalizedString(orgStr, format) { if (!orgStr) return ""; var str = '', char; for (var i = 0; i < orgStr.length; i++) { @@ -716,10 +713,39 @@ function traditionalized(orgStr, options) { return str; } -function simplized(orgStr, options) { +function traditionalized(input, options) { options = options || {}; const format = options.format !== undefined ? options.format : OutputFormat.NORMAL; + // Handle null/undefined + if (input == null) return input; + + // Handle string + if (typeof input === 'string') { + return traditionalizedString(input, format); + } + + // Handle array + if (Array.isArray(input)) { + return input.map(item => traditionalized(item, options)); + } + + // Handle object + if (typeof input === 'object') { + const result = {}; + for (const key in input) { + if (input.hasOwnProperty(key)) { + result[key] = traditionalized(input[key], options); + } + } + return result; + } + + // Return other types as-is (number, boolean, etc.) + return input; +} + +function simplizedString(orgStr, format) { if (!orgStr) return ""; var str = '', char; for (var i = 0; i < orgStr.length; i++) { @@ -810,6 +836,38 @@ function simplized(orgStr, options) { return str; } +function simplized(input, options) { + options = options || {}; + const format = options.format !== undefined ? options.format : OutputFormat.NORMAL; + + // Handle null/undefined + if (input == null) return input; + + // Handle string + if (typeof input === 'string') { + return simplizedString(input, format); + } + + // Handle array + if (Array.isArray(input)) { + return input.map(item => simplized(item, options)); + } + + // Handle object + if (typeof input === 'object') { + const result = {}; + for (const key in input) { + if (input.hasOwnProperty(key)) { + result[key] = simplized(input[key], options); + } + } + return result; + } + + // Return other types as-is (number, boolean, etc.) + return input; +} + function detect(text) { if (!text) return ChineseType.UNKNOWN; @@ -875,7 +933,7 @@ function stcasc(cache, custom, disableTerms) { if (i == value.length - 1) { newTree = {"end": key}; if (branch) { - branch.end = value; + branch.end = key; } } if (branch) { diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/test.js b/Switch Traditional Chinese and Simplified Chinese/lib/test.js index e62bdfd428d..55337889052 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/test.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/test.js @@ -111,4 +111,105 @@ console.log('输入:', mixed); console.log('输出:', mixedTc); console.log(''); +// ========== 新增测试:Array/Object 支持 ========== +console.log('========== Array/Object 测试 ==========\n'); + +// 测试 14: 转换数组(简体->繁体) +console.log('测试 14: 转换数组(简体->繁体)'); +const arr1 = ['简体中文', '软件', '硬盘', 123, true, null]; +const arr1Tc = traditionalized(arr1); +console.log('输入:', arr1); +console.log('输出:', arr1Tc); +console.log(''); + +// 测试 15: 转换数组(繁体->简体) +console.log('测试 15: 转换数组(繁体->简体)'); +const arr2 = ['繁體中文', '軟體', '硬碟', 456, false]; +const arr2Sc = simplized(arr2); +console.log('输入:', arr2); +console.log('输出:', arr2Sc); +console.log(''); + +// 测试 16: 转换对象(简体->繁体) +console.log('测试 16: 转换对象(简体->繁体)'); +const obj1 = { + title: '简体中文标题', + description: '这是一个简体中文描述', + count: 100, + active: true, + tags: ['软件', '网络', '服务器'] +}; +const obj1Tc = traditionalized(obj1); +console.log('输入:', JSON.stringify(obj1, null, 2)); +console.log('输出:', JSON.stringify(obj1Tc, null, 2)); +console.log(''); + +// 测试 17: 转换对象(繁体->简体) +console.log('测试 17: 转换对象(繁体->简体)'); +const obj2 = { + title: '繁體中文標題', + description: '這是一個繁體中文描述', + price: 99.99, + items: ['軟體', '硬碟'] +}; +const obj2Sc = simplized(obj2); +console.log('输入:', JSON.stringify(obj2, null, 2)); +console.log('输出:', JSON.stringify(obj2Sc, null, 2)); +console.log(''); + +// 测试 18: 转换嵌套数组 +console.log('测试 18: 转换嵌套数组'); +const nestedArr = [ + '简体中文', + ['软件', '硬盘'], + [['网络', '服务器']] +]; +const nestedArrTc = traditionalized(nestedArr); +console.log('输入:', JSON.stringify(nestedArr)); +console.log('输出:', JSON.stringify(nestedArrTc)); +console.log(''); + +// 测试 19: 转换嵌套对象 +console.log('测试 19: 转换嵌套对象'); +const nestedObj = { + user: { + name: '简体名称', + profile: { + bio: '这是简体中文简介', + skills: ['软件开发', '网络管理'] + } + }, + count: 42 +}; +const nestedObjTc = traditionalized(nestedObj); +console.log('输入:', JSON.stringify(nestedObj, null, 2)); +console.log('输出:', JSON.stringify(nestedObjTc, null, 2)); +console.log(''); + +// 测试 20: 数组转换 - BRACKET 格式 +console.log('测试 20: 数组转换 - BRACKET 格式'); +const arr3 = ['简体', '中文']; +const arr3Bracket = traditionalized(arr3, { format: OutputFormat.BRACKET }); +console.log('输入:', arr3); +console.log('输出:', arr3Bracket); +console.log(''); + +// 测试 21: 对象转换 - RUBY 格式 +console.log('测试 21: 对象转换 - RUBY 格式'); +const obj3 = { + text1: '简体', + text2: '中文' +}; +const obj3Ruby = traditionalized(obj3, { format: OutputFormat.RUBY }); +console.log('输入:', JSON.stringify(obj3, null, 2)); +console.log('输出:', JSON.stringify(obj3Ruby, null, 2)); +console.log(''); + +// 测试 22: 处理 null 和 undefined +console.log('测试 22: 处理 null 和 undefined'); +console.log('null 输入:', traditionalized(null)); +console.log('undefined 输入:', traditionalized(undefined)); +console.log('包含 null 的数组:', traditionalized(['简体', null, '中文'])); +console.log(''); + console.log('========== 测试完成 =========='); From 2c467c8f9ebaedeaeb83250f10200535bb6327b9 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 26 Nov 2025 19:09:42 +0900 Subject: [PATCH 20/99] Update Switch Traditional Chinese and Simplified Chinese.user.js --- ... Traditional Chinese and Simplified Chinese.user.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 c4009b12925..9bae3c6027a 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 @@ -6,7 +6,7 @@ // @namespace hoothin // @supportURL https://github.com/hoothin/UserScripts // @homepageURL https://github.com/hoothin/UserScripts -// @version 1.2.7.10 +// @version 1.2.7.11 // @description 任意轉換網頁中的簡體中文與正體中文(默認簡體→正體),顯示漢字對應漢語拼音,自訂任意替換文本 // @description:zh-CN 任意转换网页中的简体中文与繁体中文(默认繁体→简体),显示汉字对应汉语拼音,自定义任意替换文本 // @description:ja ウェブページ上の簡体字中国語と繁体字中国語を自由に変換し、中国語の文字にひらがなを表示し、任意の文字を置き換えることができます。 @@ -1217,14 +1217,14 @@ curLang = isSimple; } curLang=!curLang; - activeEle.innerHTML=curLang?traditionalized(activeEle.innerHTML):simplized(activeEle.innerHTML); - activeEle.value=curLang?traditionalized(activeEle.value):simplized(activeEle.value); + activeEle.innerHTML=curLang?simplized(activeEle.innerHTML):traditionalized(activeEle.innerHTML); + activeEle.value=curLang?simplized(activeEle.value):traditionalized(activeEle.value); }else if("INPUT"==activeEle.nodeName.toUpperCase()){ if (curInput != activeEle) { curLang = isSimple; } curLang=!curLang; - activeEle.value=curLang?traditionalized(activeEle.value):simplized(activeEle.value); + activeEle.value=curLang?simplized(activeEle.value):traditionalized(activeEle.value); }else{ var selecter; if(window.getSelection()){ @@ -1916,7 +1916,7 @@ if(i==value.length-1){ newTree={"end":key}; if(branch){ - branch.end=value; + branch.end=key; } } if(branch){ From c6161ea41f6370c12de99304357d6794b046e275 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 28 Nov 2025 09:45:39 +0900 Subject: [PATCH 21/99] Update searchJumperDefaultConfig.js --- SearchJumper/searchJumperDefaultConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SearchJumper/searchJumperDefaultConfig.js b/SearchJumper/searchJumperDefaultConfig.js index 12f00de2c20..bb3efb0264d 100644 --- a/SearchJumper/searchJumperDefaultConfig.js +++ b/SearchJumper/searchJumperDefaultConfig.js @@ -1025,7 +1025,7 @@ switch (lang) { }, { "name": "💲USD to RMB", - "url": "showTips:http://apilayer.net/api/convert?from=USD&to=CNY&amount=1&access_key=%template{apilayer key} \n{name}
%sr USD = {json.result|*%sr.replace(/\\D/,'')} RMB", + "url": "showTips:http://apilayer.net/api/convert?from=USD&to=CNY&amount=1&access_key=%template{apilayer key} \n{name}
%sr USD = {json.result|*%sr.replace(/\\D/g,'')} RMB", "kwFilter": "\\d\\$|\\$\\d" }, { From 777f3688bb2f9b2a54d29b8605a81d8d261cf2cb Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 1 Dec 2025 22:50:34 +0900 Subject: [PATCH 22/99] pixiv --- DownloadAllContent/DownloadAllContent.user.js | 4 ++-- DownloadAllContent/README.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DownloadAllContent/DownloadAllContent.user.js b/DownloadAllContent/DownloadAllContent.user.js index 4df64b84efc..de600e6dc11 100644 --- a/DownloadAllContent/DownloadAllContent.user.js +++ b/DownloadAllContent/DownloadAllContent.user.js @@ -4,7 +4,7 @@ // @name:zh-TW 怠惰小説下載器 // @name:ja 怠惰小説ダウンローダー // @namespace hoothin -// @version 2.8.3.18 +// @version 2.8.3.19 // @description Lightweight web scraping script. Fetch and download main textual content from the current page, provide special support for novels // @description:zh-CN 通用网站内容爬虫抓取工具,可批量抓取任意站点的小说、论坛内容等并保存为TXT文档 // @description:zh-TW 通用網站內容爬蟲抓取工具,可批量抓取任意站點的小說、論壇內容等並保存為TXT文檔 @@ -1536,10 +1536,10 @@ if (window.top != window.self) { function getPageContent(doc, cb, url){ if(!doc)return i18n.error; - if(doc.body && !doc.body.children.length)return doc.body.innerText; if(processFunc){ return processFunc(doc, cb, url); } + if(doc.body && !doc.body.children.length)return doc.body.innerText; [].forEach.call(doc.querySelectorAll("span,div,ul"),function(item){ var thisStyle=doc.defaultView?doc.defaultView.getComputedStyle(item):item.style; if(thisStyle && (thisStyle.display=="none" || (item.nodeName=="SPAN" && thisStyle.fontSize=="0px"))){ diff --git a/DownloadAllContent/README.md b/DownloadAllContent/README.md index 46df4e75dbd..f5de4823ebd 100644 --- a/DownloadAllContent/README.md +++ b/DownloadAllContent/README.md @@ -104,6 +104,10 @@ ``` javascript main>section ul>li div>a@@@@@@var noval=JSON.parse(doc.querySelector("#meta-preload-data").content).novel;noval[Object.keys(noval)[0]].content; ``` + 新規則 + ``` javascript +main>section ul>li div>a@@novel/show\.php\?id=@@ajax/novel/@@data.json().body.content; + ``` + [📕紅薯中文網](https://g.hongshu.com/chapterlist/91735.do) > 這個站沒有目錄連結,此時可以遍歷標籤自己創建目錄連結下載 ``` javascript From 111cb84a235554e76a3aa64ed609fec10d332892 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 2 Dec 2025 15:43:55 +0900 Subject: [PATCH 23/99] 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 76038f2c4bd..dd8a8f3b675 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1091,7 +1091,7 @@ var siteInfo = [ if (!img) return; newsrc = img.src; } - return newsrc.replace(/\?.*$/i,"").replace(/\/an_webp\/([^\/]+)\/mqdefault_6s\.webp/, "/vi/$1/hqdefault.jpg"); + return newsrc.replace(/\?.*$/i,"").replace(/\/an_webp\/([^\/]+)\/mqdefault_6s\.webp/, "/vi/$1/maxresdefault.jpg").replace(/hq\w+\.jpg/, "maxresdefault.jpg"); }, getImage: function(a, p) { var newsrc=this.src; @@ -1099,7 +1099,7 @@ var siteInfo = [ newsrc = p[2].querySelector("img").src; } if(!newsrc || newsrc.indexOf("i.ytimg.com") == -1) return; - return newsrc.replace(/\?.*$/i,"").replace(/\/an_webp\/([^\/]+)\/mqdefault_6s\.webp/, "/vi/$1/hqdefault.jpg"); + return newsrc.replace(/\?.*$/i,"").replace(/\/an_webp\/([^\/]+)\/mqdefault_6s\.webp/, "/vi/$1/maxresdefault.jpg").replace(/hq\w+\.jpg/, "maxresdefault.jpg"); } }, { From 147fde5fb86d1a0b0e2ba3a0cbe3219fcad6d957 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 2 Dec 2025 15:45:30 +0900 Subject: [PATCH 24/99] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 6b4ffe5ed28..5f5d2a9ac56 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.11.1.1 +// @version 2025.12.2.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 @@ -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/1687504/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1705900/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* @@ -19510,7 +19510,7 @@ ImgOps | https://imgops.com/#b#`; transition: transform .3s ease 0s;\ transform: scale3d(1, 1, 1);\ cursor: zoom-in;\ - min-height: 88px;\ + min-height: 150px;\ border-radius: 20px;\ }\ .pv-gallery-maximize-container>.maximizeChild:hover img {\ @@ -19588,6 +19588,9 @@ ImgOps | https://imgops.com/#b#`; .pv-gallery-maximize-container.checked span>.pv-top-banner{\ opacity: 0.6;\ }\ + .pv-gallery-maximize-container.checked span>img{\ + cursor: pointer;\ + }\ .pv-gallery-maximize-container+p>input{\ width:min-content;\ border: 1px solid transparent;\ From 5d2533285915060a22334a55f0ea7ddd1ffbdb7e Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 2 Dec 2025 06:45:42 +0000 Subject: [PATCH 25/99] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 272140f784a..975811cdb79 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.11.1.1 +// @version 2025.12.2.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 @@ -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=1687504 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1705900 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1653424 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* @@ -19510,7 +19510,7 @@ ImgOps | https://imgops.com/#b#`; transition: transform .3s ease 0s;\ transform: scale3d(1, 1, 1);\ cursor: zoom-in;\ - min-height: 88px;\ + min-height: 150px;\ border-radius: 20px;\ }\ .pv-gallery-maximize-container>.maximizeChild:hover img {\ @@ -19588,6 +19588,9 @@ ImgOps | https://imgops.com/#b#`; .pv-gallery-maximize-container.checked span>.pv-top-banner{\ opacity: 0.6;\ }\ + .pv-gallery-maximize-container.checked span>img{\ + cursor: pointer;\ + }\ .pv-gallery-maximize-container+p>input{\ width:min-content;\ border: 1px solid transparent;\ From 486f78c9b36a954fa01593bd5c2f6d5a238fd586 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 4 Dec 2025 18:44:52 +0900 Subject: [PATCH 26/99] Update Switch Traditional Chinese and Simplified Chinese.user.js --- ...aditional Chinese and Simplified Chinese.user.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 9bae3c6027a..233c1ebb303 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 @@ -6,7 +6,7 @@ // @namespace hoothin // @supportURL https://github.com/hoothin/UserScripts // @homepageURL https://github.com/hoothin/UserScripts -// @version 1.2.7.11 +// @version 1.2.7.12 // @description 任意轉換網頁中的簡體中文與正體中文(默認簡體→正體),顯示漢字對應漢語拼音,自訂任意替換文本 // @description:zh-CN 任意转换网页中的简体中文与繁体中文(默认繁体→简体),显示汉字对应汉语拼音,自定义任意替换文本 // @description:ja ウェブページ上の簡体字中国語と繁体字中国語を自由に変換し、中国語の文字にひらがなを表示し、任意の文字を置き換えることができます。 @@ -1122,6 +1122,17 @@ _unsafeWindow.tc2sc = simplized; _unsafeWindow.sc2tc = traditionalized; + window.addEventListener("message", function(event) { + if (event.data && event.data.type === "switchChineseRequest") { + const receivedData = event.data.payload; + const result = receivedData.target === 'sc' ? simplized(receivedData.str) : traditionalized(receivedData.str); + window.postMessage({ + type: "switchChineseResult", + payload: result + }, "*"); + } + }); + var storage = { supportGM: typeof GM_getValue == 'function' && typeof GM_getValue('a', 'b') != 'undefined', supportGMPromise: typeof GM != 'undefined' && typeof GM.getValue == 'function' && typeof GM.getValue('a','b') != 'undefined', From 0143b6cfcf38ef6696887a7f345175139e9c9ee7 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 10 Dec 2025 13:38:45 +0900 Subject: [PATCH 27/99] update --- .../Switch Traditional Chinese and Simplified Chinese.user.js | 4 ---- .../lib/stcasc.lib.js | 4 ---- 2 files changed, 8 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 233c1ebb303..e2228c3ba3d 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 @@ -152,10 +152,6 @@ '栗', ['慄','战栗','颤栗','不寒而栗'] ], - '凄':[ - '淒', - ['悽','凄厉','凄惨','悲凄','凄苦'] - ], '沈':[ '沈', ['瀋','沈阳'] 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 3124b14018f..cfe8e268376 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -96,10 +96,6 @@ const sc2tc = { '栗', ['慄','战栗','颤栗','不寒而栗'] ], - '凄':[ - '淒', - ['悽','凄厉','凄惨','悲凄','凄苦'] - ], '沈':[ '沈', ['瀋','沈阳'] From d6655998c4c541493251af510fed657d9b1b0b37 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 10 Dec 2025 13:41:31 +0900 Subject: [PATCH 28/99] Update package.json --- .../lib/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index d179f7ced29..415ffcc676a 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.12", + "version": "1.0.13", "description": "繁簡轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。", "main": "stcasc.lib.js", "types": "stcasc.d.ts", From c35558f57dac607c5a121301852b2930e77e923c Mon Sep 17 00:00:00 2001 From: Miyuki <50022810+Boxkun@users.noreply.github.com> Date: Mon, 15 Dec 2025 05:26:45 +0800 Subject: [PATCH 29/99] Update rule for gelbooru --- Picviewer CE+/pvcep_rules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index b993719ca50..d1a878995eb 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1133,7 +1133,7 @@ var siteInfo = [ url: /gelbooru\.com/, src: /(thumbnails|samples)\/(.*)\/(thumbnail|sample)_/i, r: /.*\/(thumbnails|samples)\/(.*)\/(thumbnail|sample)_(.*)\..*/i, - s: ["https://img4.gelbooru.com/images/$2/$4.png","https://img4.gelbooru.com/images/$2/$4.jpg","https://img4.gelbooru.com/images/$2/$4.jpeg","https://img4.gelbooru.com/images/$2/$4.gif"] + s: ["https://img2.gelbooru.com/images/$2/$4.png","https://img2.gelbooru.com/images/$2/$4.jpg","https://img2.gelbooru.com/images/$2/$4.jpeg","https://img2.gelbooru.com/images/$2/$4.gif"] }, { name: "donmai", From 0e8b891a327a644d2c7a16dcb3448a5e94118275 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 19 Dec 2025 17:26:13 +0900 Subject: [PATCH 30/99] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 5f5d2a9ac56..56f4021ccb6 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.2.1 +// @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 @@ -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/1705900/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1714183/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* @@ -13727,6 +13727,7 @@ ImgOps | https://imgops.com/#b#`; if (!errorBlobList[img.src] && !/^blob:/.test(img.src)) { errorList[img.src]=true; + let orgSrc = img.src; _GM_xmlhttpRequest({ method: 'GET', url: img.src, @@ -13736,7 +13737,6 @@ ImgOps | https://imgops.com/#b#`; const releaseBlob = () => URL.revokeObjectURL(blobUrl); window.addEventListener('beforeunload', releaseBlob); - let orgSrc = img.src; img.src = blobUrl; setTimeout(() => { if (aborted) return; From e66b368c232a9e04f6fac7fb707772ecd04a482a Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Fri, 19 Dec 2025 08:26:27 +0000 Subject: [PATCH 31/99] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 975811cdb79..8ae3445d5de 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.2.1 +// @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 @@ -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=1705900 +// @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 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* @@ -13727,6 +13727,7 @@ ImgOps | https://imgops.com/#b#`; if (!errorBlobList[img.src] && !/^blob:/.test(img.src)) { errorList[img.src]=true; + let orgSrc = img.src; _GM_xmlhttpRequest({ method: 'GET', url: img.src, @@ -13736,7 +13737,6 @@ ImgOps | https://imgops.com/#b#`; const releaseBlob = () => URL.revokeObjectURL(blobUrl); window.addEventListener('beforeunload', releaseBlob); - let orgSrc = img.src; img.src = blobUrl; setTimeout(() => { if (aborted) return; From c80ed60d65d9bcb743dbf61abeb696a025564146 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 26 Dec 2025 10:20:46 +0900 Subject: [PATCH 32/99] 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 33/99] 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 34/99] =?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 35/99] 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 36/99] 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 37/99] 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 38/99] 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 39/99] 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 40/99] 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 41/99] 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 42/99] 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 43/99] 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 44/99] 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 45/99] 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 46/99] 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 47/99] 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 48/99] 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 49/99] 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 50/99] 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 51/99] 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 52/99] 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 53/99] 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 54/99] 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 55/99] 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 56/99] 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 57/99] 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 58/99] 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 59/99] 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 60/99] 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 61/99] 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 62/99] 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 63/99] 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 64/99] 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('