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 001/252] 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 c25a273ffd6d151e02794b822e1379c97487ec4b Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 9 Aug 2025 23:53:24 +0900 Subject: [PATCH 002/252] Create X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 68 +++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 X-Downloader/X-Downloader.user.js diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js new file mode 100644 index 00000000000..57e111f7f39 --- /dev/null +++ b/X-Downloader/X-Downloader.user.js @@ -0,0 +1,68 @@ +// ==UserScript== +// @name X-Downloader +// @name:zh-CN X-Downloader +// @name:zh-TW X-Downloader +// @name:ja X-Downloader +// @namespace hoothin +// @version 2025-08-09 +// @description Enhances your Twitter (X) experience by adding a convenient download button to images and videos (GIFs), enabling easy, one-click saving of media. +// @description:zh-CN 优化你的 Twitter (X) 浏览体验,直接在图片和视频(GIF)上添加一个便捷的下载按钮,一键轻松保存喜欢的媒体内容。 +// @description:zh-TW 優化您的 Twitter (X) 瀏覽體驗,直接在圖片及影片(GIF)上新增一個便捷的下載按鈕,一鍵輕鬆儲存喜愛的媒體內容。 +// @description:ja Twitter (X) の画像や動画(GIF)に便利なダウンロードボタンを追加し、ワンクリックでお気に入りのメディアを簡単に保存できるようにします。 +// @author hoothin +// @match https://x.com/* +// @match https://twitter.com/* +// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== +// @grant none +// ==/UserScript== + +(function() { + 'use strict'; + let downloadBtn = document.createElement("div"); + downloadBtn.style.cssText = "background: #000000aa; border-radius: 50%; transition: opacity ease 0.3s; position: absolute; top: 0; right: 0px; cursor: pointer; opacity: 0.5; padding: 5px;"; + downloadBtn.innerHTML = ``; + downloadBtn.addEventListener("click", e => { + e.preventDefault(); + e.stopPropagation(); + let parent = downloadBtn.parentNode; + if (!parent) return; + let img = parent.querySelector('[data-testid="tweetPhoto"]>img'); + if (img) { + let newsrc = img.src.replace("_normal.",".").replace("_200x200.",".").replace("_mini.",".").replace("_bigger.",".").replace(/_x\d+\./,"."); + if (/\.svg$/.test(newsrc)) return; + if (newsrc == img.src) { + newsrc=newsrc.replace(/\?format=/i, ".").replace(/\&name=/i, ":").replace(/\.(?=[^\.\/]*$)/, "?format=").replace( /(:large|:medium|:small|:orig|:thumb|:[\dx]+)/i, ""); + if (newsrc != img.src) { + newsrc = newsrc + "&name=orig"; + } + } + window.open(newsrc, "_blank"); + } else { + while(parent) { + if (parent.nodeName == "ARTICLE" && parent.dataset && parent.dataset.testid == "tweet") { + break; + } + parent = parent.parentNode; + } + if (parent) { + let link = parent.querySelector('a[role="link"][aria-label]'); + window.open(`https://twitter.hoothin.com/?url=${encodeURIComponent(link ? link.href : document.location.href)}`, "_blank"); + } + } + }); + downloadBtn.addEventListener("mouseenter", () => { + downloadBtn.style.opacity = 1; + }); + downloadBtn.addEventListener("mouseleave", () => { + downloadBtn.style.opacity = 0.5; + }); + document.addEventListener("mouseenter", e => { + if (e.target.dataset && e.target.dataset.testid == "tweetPhoto") { + e.target.parentNode.appendChild(downloadBtn); + } else if (e.target.firstElementChild) { + if (e.target.firstElementChild.getAttribute("role") == "progressbar") { + e.target.parentNode.parentNode.appendChild(downloadBtn); + } + } + }, true); +})(); \ No newline at end of file From 2e54cf9bb193294e71b6aceda7537ec27db8db15 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 10 Aug 2025 08:33:19 +0900 Subject: [PATCH 003/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index dbe73d0134b..bccf054ae16 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -8143,7 +8143,7 @@ const initStyle = `text-indent: initial;display: contents;right: unset;left: unset;top: unset;bottom: unset;inset: unset;clear: both;cy: initial;d: initial;dominant-baseline: initial;empty-cells: initial;fill: initial;fill-opacity: initial;fill-rule: initial;filter: initial;flex: initial;flex-flow: initial;float: initial;flood-color: initial;flood-opacity: initial;grid: initial;grid-area: initial;height: initial;hyphens: initial;image-orientation: initial;image-rendering: initial;inline-size: initial;inset-block: initial;inset-inline: initial;isolation: initial;letter-spacing: initial;lighting-color: initial;line-break: initial;list-style: initial;margin-block: initial;margin: 0px 5px;margin-inline: initial;marker: initial;mask: initial;mask-type: initial;max-block-size: initial;max-height: initial;max-inline-size: initial;max-width: initial;min-block-size: initial;min-height: initial;min-inline-size: initial;min-width: initial;mix-blend-mode: initial;object-fit: initial;object-position: initial;offset: initial;opacity: initial;order: initial;orphans: initial;outline: initial;outline-offset: initial;overflow-anchor: initial;overflow-clip-margin: initial;overflow-wrap: initial;overflow: initial;overscroll-behavior-block: initial;overscroll-behavior-inline: initial;overscroll-behavior: initial;padding-block: initial;padding: initial;padding-inline: initial;page: initial;page-orientation: initial;paint-order: initial;perspective: initial;perspective-origin: initial;pointer-events: initial;position: relative;quotes: initial;r: initial;resize: initial;ruby-position: initial;rx: initial;ry: initial;scroll-behavior: initial;scroll-margin-block: initial;scroll-margin: initial;scroll-margin-inline: initial;scroll-padding-block: initial;scroll-padding: initial;scroll-padding-inline: initial;scroll-snap-align: initial;scroll-snap-stop: initial;scroll-snap-type: initial;scrollbar-gutter: initial;shape-image-threshold: initial;shape-margin: initial;shape-outside: initial;shape-rendering: initial;size: initial;speak: initial;stop-color: initial;stop-opacity: initial;stroke: initial;stroke-dasharray: initial;stroke-dashoffset: initial;stroke-linecap: initial;stroke-linejoin: initial;stroke-miterlimit: initial;stroke-opacity: initial;stroke-width: initial;tab-size: initial;table-layout: initial;text-align: initial;text-align-last: initial;text-anchor: initial;text-combine-upright: initial;text-decoration: initial;text-decoration-skip-ink: initial;text-indent: initial;text-overflow: initial;text-shadow: initial;text-size-adjust: initial;text-transform: initial;text-underline-offset: initial;text-underline-position: initial;touch-action: initial;transform: initial;transform-box: initial;transform-origin: initial;transform-style: initial;transition: initial;user-select: initial;vector-effect: initial;vertical-align: initial;visibility: initial;border-spacing: initial;-webkit-border-image: initial;-webkit-box-align: initial;-webkit-box-decoration-break: initial;-webkit-box-direction: initial;-webkit-box-flex: initial;-webkit-box-ordinal-group: initial;-webkit-box-orient: initial;-webkit-box-pack: initial;-webkit-box-reflect: initial;-webkit-highlight: initial;-webkit-hyphenate-character: initial;-webkit-line-break: initial;-webkit-line-clamp: initial;-webkit-mask-box-image: initial;-webkit-mask: initial;-webkit-mask-composite: initial;-webkit-perspective-origin-x: initial;-webkit-perspective-origin-y: initial;-webkit-print-color-adjust: initial;-webkit-rtl-ordering: initial;-webkit-ruby-position: initial;-webkit-tap-highlight-color: initial;-webkit-text-combine: initial;-webkit-text-decorations-in-effect: initial;-webkit-text-emphasis: initial;-webkit-text-emphasis-position: initial;-webkit-text-fill-color: initial;-webkit-text-security: initial;-webkit-text-stroke: initial;-webkit-transform-origin-x: initial;-webkit-transform-origin-y: initial;-webkit-transform-origin-z: initial;-webkit-user-drag: initial;-webkit-user-modify: initial;white-space: initial;widows: initial;width: initial;will-change: initial;word-break: initial;word-spacing: initial;x: initial;y: initial;`; const pageTextStyle = `opacity: 1!important;text-indent: initial;padding: unset;border: none;background: unset!important;line-height: 30px;text-decoration: none;user-select: none;visibility: visible;position: initial;width: auto;max-width: 80%; white-space: nowrap; text-overflow: ellipsis;overflow: hidden;height: auto;float: none;clear: both;margin: 0px;text-align: center;display: inline-block;font-weight: bold;font-style: normal;font-size: 16px;letter-spacing: initial;vertical-align: top;color: rgb(85, 85, 95)!important;`; - const corsTips = "Blocked by CORS, try to install an extension like 'Ignore X-Frame headers'."; + const corsTips = "Blocked by CORS, try to install an extension like 'Ignore X-Frame options'."; var sideControllerIcon = ''; var tipsWords = document.createElement("div"); From 5f57be14f57b4b2146e8961f58b80596a20bca6e Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 10 Aug 2025 09:58:03 +0900 Subject: [PATCH 004/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 71 +++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 57e111f7f39..1f516513296 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -4,9 +4,10 @@ // @name:zh-TW X-Downloader // @name:ja X-Downloader // @namespace hoothin -// @version 2025-08-09 +// @version 2025-08-10 +// @license MIT // @description Enhances your Twitter (X) experience by adding a convenient download button to images and videos (GIFs), enabling easy, one-click saving of media. -// @description:zh-CN 优化你的 Twitter (X) 浏览体验,直接在图片和视频(GIF)上添加一个便捷的下载按钮,一键轻松保存喜欢的媒体内容。 +// @description:zh-CN 优化你的推特 (X) 浏览体验,直接在图片和视频(GIF)上添加一个便捷的下载按钮,一键轻松保存喜欢的媒体内容。 // @description:zh-TW 優化您的 Twitter (X) 瀏覽體驗,直接在圖片及影片(GIF)上新增一個便捷的下載按鈕,一鍵輕鬆儲存喜愛的媒體內容。 // @description:ja Twitter (X) の画像や動画(GIF)に便利なダウンロードボタンを追加し、ワンクリックでお気に入りのメディアを簡単に保存できるようにします。 // @author hoothin @@ -14,21 +15,22 @@ // @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 // ==/UserScript== (function() { 'use strict'; - let downloadBtn = document.createElement("div"); + let downloadBtn = document.createElement("a"); + 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.5; padding: 5px;"; downloadBtn.innerHTML = ``; - downloadBtn.addEventListener("click", e => { - e.preventDefault(); - e.stopPropagation(); + downloadBtn.addEventListener("mousedown", e => { let parent = downloadBtn.parentNode; if (!parent) return; let img = parent.querySelector('[data-testid="tweetPhoto"]>img'); if (img) { - let newsrc = img.src.replace("_normal.",".").replace("_200x200.",".").replace("_mini.",".").replace("_bigger.",".").replace(/_x\d+\./,"."); + let newsrc = img.src.replace("_normal.",".").replace("_200x200.",".").replace("_mini.",".").replace("_bigger.",".").replace(/_x\d+\./,"."), imgname; if (/\.svg$/.test(newsrc)) return; if (newsrc == img.src) { newsrc=newsrc.replace(/\?format=/i, ".").replace(/\&name=/i, ":").replace(/\.(?=[^\.\/]*$)/, "?format=").replace( /(:large|:medium|:small|:orig|:thumb|:[\dx]+)/i, ""); @@ -36,7 +38,30 @@ newsrc = newsrc + "&name=orig"; } } - window.open(newsrc, "_blank"); + while(parent) { + if (parent.nodeName == "ARTICLE" && parent.dataset && parent.dataset.testid == "tweet") { + break; + } + parent = parent.parentNode; + } + if (parent) { + const time = parent.querySelector('time[datetime]'); + const user = parent.querySelector('[role="link"]>div>div>span>span'); + let formatMatch = img.src.match(/format=(\w+)/), ext = "jpg"; + if (formatMatch) { + ext = formatMatch[1]; + } else { + formatMatch = newsrc.match(/\.(\w+)/); + if (formatMatch) { + ext = formatMatch[1]; + } + } + imgname = `${user.innerText} ${time.innerText.replace(/(.*) · (.*)/, "$2 $1")}.${ext}`; + } + downloadBtn.href = newsrc; + if (e.altKey) { + downloadByFetch(newsrc, imgname); + } } else { while(parent) { if (parent.nodeName == "ARTICLE" && parent.dataset && parent.dataset.testid == "tweet") { @@ -45,17 +70,45 @@ parent = parent.parentNode; } if (parent) { + downloadBtn.removeAttribute('download'); let link = parent.querySelector('a[role="link"][aria-label]'); - window.open(`https://twitter.hoothin.com/?url=${encodeURIComponent(link ? link.href : document.location.href)}`, "_blank"); + downloadBtn.href = `https://twitter.hoothin.com/?url=${encodeURIComponent(link ? link.href : document.location.href)}`; + if (e.altKey) { + window.open(downloadBtn.href, "_blank"); + } } } }); + downloadBtn.addEventListener("click", e => { + if (e.altKey) { + e.preventDefault(); + e.stopPropagation(); + } + }); downloadBtn.addEventListener("mouseenter", () => { downloadBtn.style.opacity = 1; }); downloadBtn.addEventListener("mouseleave", () => { downloadBtn.style.opacity = 0.5; }); + async function downloadByFetch(imageUrl, filename) { + try { + const response = await fetch(imageUrl); + if (!response.ok) throw new Error('CORS request failed'); + const blob = await response.blob(); + const blobUrl = URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.href = blobUrl; + tempLink.setAttribute('download', filename); + document.body.appendChild(tempLink); + tempLink.click(); + document.body.removeChild(tempLink); + URL.revokeObjectURL(blobUrl); + } catch (error) { + console.error('error:', error); + window.open(imageUrl, '_blank'); + } + } document.addEventListener("mouseenter", e => { if (e.target.dataset && e.target.dataset.testid == "tweetPhoto") { e.target.parentNode.appendChild(downloadBtn); From 27233b6a855e91668acabd657989dbc0633581b9 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 10 Aug 2025 10:49:43 +0900 Subject: [PATCH 005/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 1f516513296..d62db8e4e63 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -109,13 +109,19 @@ window.open(imageUrl, '_blank'); } } - document.addEventListener("mouseenter", e => { + const addBtn = e => { if (e.target.dataset && e.target.dataset.testid == "tweetPhoto") { e.target.parentNode.appendChild(downloadBtn); + } else if (e.target.dataset && /^video\-player/.test(e.target.dataset.testid)) { + e.target.parentNode.appendChild(downloadBtn); } else if (e.target.firstElementChild) { if (e.target.firstElementChild.getAttribute("role") == "progressbar") { e.target.parentNode.parentNode.appendChild(downloadBtn); } + } else if (e.target.parentNode && e.target.parentNode.dataset && e.target.parentNode.dataset.testid == "tweetPhoto") { + e.target.parentNode.parentNode.appendChild(downloadBtn); } - }, true); + }; + document.addEventListener("mouseenter", addBtn, true); + document.addEventListener("touchstart", addBtn, true); })(); \ No newline at end of file From 63f0131bfb98f642f75bf6cb60191e9a08caeb3f Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 10 Aug 2025 13:51:53 +0900 Subject: [PATCH 006/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index d62db8e4e63..1f1fac960e7 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -28,7 +28,7 @@ downloadBtn.addEventListener("mousedown", e => { let parent = downloadBtn.parentNode; if (!parent) return; - let img = parent.querySelector('[data-testid="tweetPhoto"]>img'); + 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; if (/\.svg$/.test(newsrc)) return; @@ -110,7 +110,9 @@ } } const addBtn = e => { - if (e.target.dataset && e.target.dataset.testid == "tweetPhoto") { + if (e.target.dataset && e.target.dataset.testid == "card.layoutLarge.media") { + e.target.parentNode.appendChild(downloadBtn); + } else if (e.target.dataset && e.target.dataset.testid == "tweetPhoto") { e.target.parentNode.appendChild(downloadBtn); } else if (e.target.dataset && /^video\-player/.test(e.target.dataset.testid)) { e.target.parentNode.appendChild(downloadBtn); From 077a058325d1c4ec8f2dd65aead04d44e81f3b73 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 10 Aug 2025 21:29:00 +0900 Subject: [PATCH 007/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 1f1fac960e7..f05bea2b7df 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -116,10 +116,6 @@ e.target.parentNode.appendChild(downloadBtn); } else if (e.target.dataset && /^video\-player/.test(e.target.dataset.testid)) { e.target.parentNode.appendChild(downloadBtn); - } else if (e.target.firstElementChild) { - if (e.target.firstElementChild.getAttribute("role") == "progressbar") { - e.target.parentNode.parentNode.appendChild(downloadBtn); - } } else if (e.target.parentNode && e.target.parentNode.dataset && e.target.parentNode.dataset.testid == "tweetPhoto") { e.target.parentNode.parentNode.appendChild(downloadBtn); } From 2d7d813e5d57909df763bd41e153cd69de42a409 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 12 Aug 2025 12:49:34 +0900 Subject: [PATCH 008/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index bccf054ae16..e8d06b0fbcc 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -1472,7 +1472,7 @@ const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/438684(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Pagetual|issues)|^https:\/\/pagetual\.hoothin\.com\/.*firstRun\.html/i; const allOfBody = "body>*"; const mainSel = ["article,.article","[role=main],main,.main,#main","#results"]; - const nextTextReg1 = new RegExp("\u005e\u7ffb\u003f\u005b\u4e0b\u540e\u5f8c\u6b21\u005d\u005b\u4e00\u30fc\u2500\u0031\u005d\u003f\u005b\u9875\u9801\u5f20\u5f35\u005d\u007c\u005e\u006e\u0065\u0078\u0074\u005b\u005c\u0073\u005f\u002d\u005d\u003f\u0070\u0061\u0067\u0065\u005c\u0073\u002a\u005b\u203a\u003e\u2192\u00bb\u005d\u003f\u0024\u007c\u6b21\u306e\u30da\u30fc\u30b8\u007c\u005e\u6b21\u3078\u003f\u0024\u007c\u0412\u043f\u0435\u0440\u0435\u0434\u007c\u005e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435", "i"); + const nextTextReg1 = new RegExp("\u005e\u7ffb\u003f\u005b\u4e0b\u540e\u5f8c\u6b21\u005d\u005b\u4e00\u30fc\u2500\u0031\u005d\u003f\u005b\u9875\u9801\u5f20\u5f35\u005d\u007c\u005e\u006e\u0065\u0078\u0074\u005b\u005c\u0073\u005f\u002d\u005d\u003f\u0070\u0061\u0067\u0065\u005c\u0073\u002a\u005b\u203a\u003e\u2192\u00bb\u005d\u003f\u0024\u007c\u6b21\u306e\u30da\u30fc\u30b8\u007c\u005e\u6b21\u3078\u0024\u007c\u0412\u043f\u0435\u0440\u0435\u0434\u007c\u005e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435", "i"); const nextTextReg2 = new RegExp("\u005e\u0028\u005b\u4e0b\u540e\u5f8c\u6b21\u005d\u005b\u4e00\u30fc\u2500\u0031\u005d\u003f\u005b\u7ae0\u8bdd\u8a71\u8282\u7bc0\u5e45\u005d\u007c\u006e\u0065\u0078\u0074\u002e\u003f\u0063\u0068\u0061\u0070\u0074\u0065\u0072\u0029\u0028\u005b\u003a\uff1a\u005c\u002d\u005f\u2014\u005c\u0073\u005c\u002e\u3002\u003e\u0023\u00b7\u005c\u005b\u3010\u3001\uff08\u005c\u0028\u002f\u002c\uff0c\uff1b\u003b\u2192\u005d\u007c\u0024\u0029", "i"); const nextTextReg3 = /^(next\s*(»|>>|>|›|→|❯|\d+)?|>|▶|>|›|→|❯)\s*$/i; const prevReg = new RegExp("\u005e\u005c\u0073\u002a\u0028\u005b\u4e0a\u524d\u9996\u5c3e\u005d\u007c\u0070\u0072\u0065\u0076\u007c\u0065\u006e\u0064\u0029", "i"); From ca3e256c8295f08840260fa229f80d8784f9edd0 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 13 Aug 2025 19:01:51 +0900 Subject: [PATCH 009/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index f05bea2b7df..b121fd83e1a 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -1,10 +1,10 @@ // ==UserScript== -// @name X-Downloader -// @name:zh-CN X-Downloader -// @name:zh-TW X-Downloader -// @name:ja X-Downloader +// @name X-Downloader-Script +// @name:zh-CN X-Downloader-Script +// @name:zh-TW X-Downloader-Script +// @name:ja X-Downloader-Script // @namespace hoothin -// @version 2025-08-10 +// @version 2025-08-13 // @license MIT // @description Enhances your Twitter (X) experience by adding a convenient download button to images and videos (GIFs), enabling easy, one-click saving of media. // @description:zh-CN 优化你的推特 (X) 浏览体验,直接在图片和视频(GIF)上添加一个便捷的下载按钮,一键轻松保存喜欢的媒体内容。 @@ -89,7 +89,7 @@ downloadBtn.style.opacity = 1; }); downloadBtn.addEventListener("mouseleave", () => { - downloadBtn.style.opacity = 0.5; + downloadBtn.style.opacity = 0.1; }); async function downloadByFetch(imageUrl, filename) { try { From d64b63843bd7e75f29e56c2c9910a3141c5d5389 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 13 Aug 2025 19:07:23 +0900 Subject: [PATCH 010/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index b121fd83e1a..456b6534c7a 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -23,7 +23,7 @@ 'use strict'; let downloadBtn = document.createElement("a"); 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.5; padding: 5px;"; + 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; @@ -109,15 +109,21 @@ window.open(imageUrl, '_blank'); } } + const show = (ele) => { + ele.appendChild(downloadBtn); + setTimeout(() => { + downloadBtn.style.opacity = 0.6; + }, 0); + }; const addBtn = e => { if (e.target.dataset && e.target.dataset.testid == "card.layoutLarge.media") { - e.target.parentNode.appendChild(downloadBtn); + show(e.target.parentNode); } else if (e.target.dataset && e.target.dataset.testid == "tweetPhoto") { - e.target.parentNode.appendChild(downloadBtn); + show(e.target.parentNode); } else if (e.target.dataset && /^video\-player/.test(e.target.dataset.testid)) { - e.target.parentNode.appendChild(downloadBtn); + show(e.target.parentNode); } else if (e.target.parentNode && e.target.parentNode.dataset && e.target.parentNode.dataset.testid == "tweetPhoto") { - e.target.parentNode.parentNode.appendChild(downloadBtn); + show(e.target.parentNode.parentNode); } }; document.addEventListener("mouseenter", addBtn, true); From 43b3c9aab2edf1917ec18c8e78cb5fab3ca12711 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 13 Aug 2025 19:09:24 +0900 Subject: [PATCH 011/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 456b6534c7a..9120b855507 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -89,7 +89,9 @@ downloadBtn.style.opacity = 1; }); downloadBtn.addEventListener("mouseleave", () => { - downloadBtn.style.opacity = 0.1; + setTimeout(() => { + downloadBtn.style.opacity = 0.1; + }, 100); }); async function downloadByFetch(imageUrl, filename) { try { From 7258a7119f02a0a6c8a4e4f544e82efe69e4a1a9 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 13 Aug 2025 19:43:52 +0900 Subject: [PATCH 012/252] Update readme.md --- .../lib/readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index 0384226f00b..81554448ece 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 @@ 簡繁自由切換 === -> 切換簡體中文與正體中文 +> 切換正體/簡體中文,超輕量級,支援自訂詞彙與「一簡多繁」轉換,無任何相依性 [![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) @@ -10,9 +10,9 @@ + 基礎用法 ``` js -import Stcasc from './stcasc.lib.js'; -let stcasc = Stcasc(); -console.log(stcasc.traditionalized("香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发")); +const stcasc = Stcasc(); +const sc = "香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发"; +console.log(stcasc.traditionalized(sc)); //香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 ``` From 8a4f32764cd17f3422e199e6586f93a3502a398d Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 13 Aug 2025 20:05:43 +0900 Subject: [PATCH 013/252] sc2tcComb --- .../lib/package.json | 2 +- .../lib/readme.md | 18 ++++++++++++++++-- .../lib/stcasc.lib.js | 5 +++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index bb07bc51b0b..69c75edbaac 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.2", + "version": "1.0.3", "description": "Convert between simplified and traditional Chinese characters", "main": "stcasc.lib.js", "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 81554448ece..b330d713d23 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -10,10 +10,18 @@ + 基礎用法 ``` js +import Stcasc from './stcasc.lib.js'; const stcasc = Stcasc(); -const sc = "香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发"; +const sc = "香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发 知识产权"; console.log(stcasc.traditionalized(sc)); -//香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 +//香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 智慧財產權 +``` + +``` js +const stcasc = Stcasc({}, {}, true); +const sc = "知识产权"; +console.log(stcasc.traditionalized(sc)); +//知識産權 ``` + 透過 npm 安裝 @@ -56,3 +64,9 @@ const custom = { }; let stcasc = Stcasc(cache, custom); ``` + ++ 禁用用語轉換 + +``` js +let stcasc = Stcasc({}, {}, true); +``` 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 f1fdb131004..5ec6942ab14 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js +++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js @@ -473,7 +473,7 @@ const tc2sc = { ['了','瞭望','瞭然','瞭解','瞭若指掌','瞭如指掌'] ] }; -const sc2tcComb = { +let sc2tcComb = { '香烟袅袅':'香煙裊裊', '袅袅香烟':'裊裊香煙', '补丁':'補靪', @@ -756,12 +756,13 @@ function simplized(orgStr) { return str; } -function Stcasc(cache, custom) { +function Stcasc(cache, custom, disableTerms) { if (!cache) cache = {}; if (cache.sc2tcCombTree && cache.tc2scCombTree) { sc2tcCombTree = cache.sc2tcCombTree; tc2scCombTree = cache.tc2scCombTree; } else { + if (disableTerms) sc2tcComb = {}; if (custom && custom.length) { for (let sc in custom) { sc2tcComb[sc] = custom[sc]; From 4cc82475f7d5691487a2f52952e184d60997915f Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 14 Aug 2025 14:47:24 +0900 Subject: [PATCH 014/252] 1.9.37.122 --- Pagetual/README.md | 2 +- Pagetual/pagetual.user.js | 40 +++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index f5fec67cd21..f9ac544ca9e 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.121](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.122](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 e8d06b0fbcc..7074a186f1a 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.121 +// @version 1.9.37.122 // @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 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -8014,32 +8014,32 @@ margin-top: 30px!important; pointer-events: all; } - .pagetual_pageBar span>svg { + .pagetual_pageBar a>svg { -moz-transition:transform 0.5s ease, opacity 0.3s ease; -webkit-transition:transform 0.5s ease, opacity 0.3s ease; transition:transform 0.5 ease, opacity 0.3s ease; opacity: 0; } - .pagetual_pageBar.stop span>svg{ + .pagetual_pageBar.stop a>svg{ opacity: 1; } - .pagetual_pageBar.stop span>svg>path{ + .pagetual_pageBar.stop a>svg>path{ transform: scale(.8); transform-origin: center; -moz-transition:transform 0.3s ease; -webkit-transition:transform 0.3s ease; transition:transform 0.3 ease; } - .pagetual_pageBar.stop:hover span>svg>path{ + .pagetual_pageBar.stop:hover a>svg>path{ transform: unset; } - .pagetual_pageBar:hover span>svg { + .pagetual_pageBar:hover a>svg { opacity: 1; } - .pagetual_pageBar span>svg.upSvg:hover { + .pagetual_pageBar a>svg.upSvg:hover { transform: rotate(360deg) scale3d(1.2, 1.2, 1.2); } - .pagetual_pageBar span>svg.downSvg:hover { + .pagetual_pageBar a>svg.downSvg:hover { transform: rotate(540deg) scale3d(1.2, 1.2, 1.2)!important; } .pagetual_pageBar .pagetual_pageNum{ @@ -8773,8 +8773,8 @@ inLi = compareNodeName(example, ["li"]) || (example.nextElementSibling && compareNodeName(example.nextElementSibling, ["li"])); } let pageBar = document.createElement(inTable ? "tr" : (inLi ? "li" : "div")); - let upSpan = document.createElement("span"); - let downSpan = document.createElement("span"); + let upSpan = document.createElement("a"); + let downSpan = document.createElement("a"); let pageText = document.createElement("a"); let pageNum; pageBar.className = isHideBar ? "pagetual_pageBar autopagerize_page_info hide" : "pagetual_pageBar autopagerize_page_info"; @@ -8787,6 +8787,7 @@ upSpan.innerHTML = createHTML(upSvg); upSpan.children[0].style.cssText = upSvgCSS; upSpan.title = i18n("toTop"); + upSpan.href = ruleParser.initUrl || ''; downSpan.innerHTML = createHTML(downSvg); downSpan.children[0].style.cssText = downSvgCSS; downSpan.title = i18n("toBottom"); @@ -9006,21 +9007,32 @@ } upSpan.addEventListener("click", e => { + e.stopPropagation(); + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + if (e.altKey) location.href = upSpan.href; + return; + } getBody(document).scrollTop = 0; document.documentElement.scrollTop = 0; e.preventDefault(); - e.stopPropagation(); + }); + downSpan.addEventListener("mousedown", e => { + if (ruleParser.nextLinkHref && ruleParser.nextLinkHref != '#') { + downSpan.href = ruleParser.nextLinkHref; + } else downSpan.href = ''; }); downSpan.addEventListener("click", e => { - if (!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) { - changeStop(true); + e.stopPropagation(); + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + if (e.altKey && downSpan.getAttribute('href')) location.href = upSpan.href; + return; } + changeStop(true); pageBar.title = i18n(isPause ? "enable" : "disable"); scrollH = Math.max(document.documentElement.scrollHeight, getBody(document).scrollHeight); getBody(document).scrollTop = scrollH || 9999999; document.documentElement.scrollTop = scrollH || 9999999; e.preventDefault(); - e.stopPropagation(); }); pageBar.addEventListener("click", e => { changeStop(!isPause); From c80bb58a9360085dff46c673bfc6719ea58590fb Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 15 Aug 2025 19:54:02 +0900 Subject: [PATCH 015/252] update --- Picviewer CE+/pvcep_rules.js | 4 ++-- .../lib/package.json | 10 +++++++--- .../lib/readme.md | 17 +++++++---------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index 28a323f93ef..d19952f0a12 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1939,8 +1939,8 @@ var siteInfo = [ { name: "Google review", url: /\bgoogle\.com\//, - r: /=w\d+\-h\d+\-\w/, - s: "=s3072-v1" + r: [/=w\d+\-h\d+\-\w/, /=s\d+/], + s: ["=s0-v1", "=s0"] }, { name: "MAL Anime/Manga", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 69c75edbaac..37afff9f862 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.3", - "description": "Convert between simplified and traditional Chinese characters", + "version": "1.0.4", + "description": "Convert between simplified and traditional Chinese characters. 切換正體/簡體中文,超輕量級,支援自訂詞彙與「一簡多繁」轉換,無任何相依性。", "main": "stcasc.lib.js", "type": "module", "repository": { @@ -17,7 +17,11 @@ "繁体中文", "簡體中文", "繁體中文", - "正體中文" + "正體中文", + "简繁切换", + "簡繁切換", + "繁简转换", + "繁簡轉換" ], "author": "Hoothin", "license": "MIT", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index b330d713d23..eba1b85e44f 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -17,16 +17,10 @@ console.log(stcasc.traditionalized(sc)); //香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 智慧財產權 ``` -``` js -const stcasc = Stcasc({}, {}, true); -const sc = "知识产权"; -console.log(stcasc.traditionalized(sc)); -//知識産權 -``` + 透過 npm 安裝 -``` +``` shell npm install switch-chinese import Stcasc from 'switch-chinese'; ``` @@ -45,7 +39,7 @@ stcasc.simplized("正體中文"); //正体中文 ``` -+ 添加快取 ++ 添加快取,避免重複生成字典 ``` js let cache = loadCacheAtYourWay(); @@ -62,11 +56,14 @@ const custom = { "转换": "轉檔", "软件": "軟體" }; -let stcasc = Stcasc(cache, custom); +const stcasc = Stcasc(cache, custom); ``` + 禁用用語轉換 ``` js -let stcasc = Stcasc({}, {}, true); +const stcasc = Stcasc({}, {}, true); +const sc = "知识产权"; +console.log(stcasc.traditionalized(sc)); +//知識産權 ``` From b759fdf3518507999940ffc7f48a6a2735447a49 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 15 Aug 2025 19:55:59 +0900 Subject: [PATCH 016/252] 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 bd6f8ec5d61..76c454c3d42 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/1636990/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1642363/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1427239/pvcep_lang.js // @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js // @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js From 3c476228f79fbea7f5fa33f4779d259ffc496250 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 17 Aug 2025 09:28:16 +0900 Subject: [PATCH 017/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 9120b855507..a5d69582c7d 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -4,7 +4,7 @@ // @name:zh-TW X-Downloader-Script // @name:ja X-Downloader-Script // @namespace hoothin -// @version 2025-08-13 +// @version 2025-08-17 // @license MIT // @description Enhances your Twitter (X) experience by adding a convenient download button to images and videos (GIFs), enabling easy, one-click saving of media. // @description:zh-CN 优化你的推特 (X) 浏览体验,直接在图片和视频(GIF)上添加一个便捷的下载按钮,一键轻松保存喜欢的媒体内容。 @@ -71,7 +71,7 @@ } if (parent) { downloadBtn.removeAttribute('download'); - let link = parent.querySelector('a[role="link"][aria-label]'); + let link = parent.querySelector('a[role="link"][aria-label][href^="/"]'); downloadBtn.href = `https://twitter.hoothin.com/?url=${encodeURIComponent(link ? link.href : document.location.href)}`; if (e.altKey) { window.open(downloadBtn.href, "_blank"); From 395a4b62261fab68311405554dcab39d0e23a92e Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 17 Aug 2025 10:20:46 +0900 Subject: [PATCH 018/252] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index d19952f0a12..f6782e7b598 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1027,10 +1027,25 @@ var siteInfo = [ }, { name: "Rule34", - url: /\brule34\.xxx/, + url: /\b(rule34\.xxx|realbooru\.com)/, src: /\/(thumbnails|samples)\/(.*)\/(thumbnail|sample)_/i, - r: /\/(thumbnails|samples)\/(.*)\/(thumbnail|sample)_(.*)\..*/i, - s: ["/images/$2/$4.jpeg","/images/$2/$4.png","/images/$2/$4.jpg"] + xhr: { + url: function(a, p) { + if (!a) return; + const re = /^https?:\/\/(?:www\.)?(?:(realbooru\.com|rule34\.xxx))\/index\.php\?page=post&s=view&id=(\d+).*/; + const m = a.href.match(re); + if (m) { + return m[1] == "rule34.xxx" ? `https://api.${m[1]}/index.php?page=dapi&s=post&q=index&id=${m[2]}&json=1` : `https://${m[1]}/index.php?page=dapi&s=post&q=index&id=${m[2]}&json=1`; + } + }, + query: function(html, doc, url) { + try { + const o = JSON.parse(html); + let url = o[0]; + return url.file_url || `https://${location.hostname}/images/${url.directory}/${url.image}`; + } catch { } + } + } }, { name: "Photosight", From 1782dd939d5c2efd37ecadd44aaa77abbc5b5b87 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 17 Aug 2025 10:21:32 +0900 Subject: [PATCH 019/252] 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 76c454c3d42..19f0b3fe339 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.8.2.1 +// @version 2025.8.17.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/1642363/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1643244/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1427239/pvcep_lang.js // @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js // @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js From bbf451f4c4f8d81e3749d5902ea8c1e855d41f4c Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 18 Aug 2025 22:26:47 +0900 Subject: [PATCH 020/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 38 +++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index a5d69582c7d..15905cb48dd 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -4,7 +4,7 @@ // @name:zh-TW X-Downloader-Script // @name:ja X-Downloader-Script // @namespace hoothin -// @version 2025-08-17 +// @version 2025-08-18 // @license MIT // @description Enhances your Twitter (X) experience by adding a convenient download button to images and videos (GIFs), enabling easy, one-click saving of media. // @description:zh-CN 优化你的推特 (X) 浏览体验,直接在图片和视频(GIF)上添加一个便捷的下载按钮,一键轻松保存喜欢的媒体内容。 @@ -21,7 +21,7 @@ (function() { 'use strict'; - let downloadBtn = document.createElement("a"); + let downloadBtn = document.createElement("a"), touch = 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 = ``; @@ -59,7 +59,7 @@ imgname = `${user.innerText} ${time.innerText.replace(/(.*) · (.*)/, "$2 $1")}.${ext}`; } downloadBtn.href = newsrc; - if (e.altKey) { + if (e.altKey || touch) { downloadByFetch(newsrc, imgname); } } else { @@ -114,7 +114,7 @@ const show = (ele) => { ele.appendChild(downloadBtn); setTimeout(() => { - downloadBtn.style.opacity = 0.6; + downloadBtn.style.opacity = touch ? 0.8 : 0.6; }, 0); }; const addBtn = e => { @@ -128,6 +128,34 @@ show(e.target.parentNode.parentNode); } }; + function findFirstVisibleElement(selector) { + const elements = document.querySelectorAll(selector); + const firstVisibleElement = Array.from(elements).find(el => { + const rect = el.getBoundingClientRect(); + return rect.top < window.innerHeight && rect.top > 0 && rect.bottom >= 0; + }); + return firstVisibleElement; + } + let checkTimer; + const touchCheck = e => { + clearTimeout(checkTimer); + if (e.target == downloadBtn) return; + checkTimer = setTimeout(() => { + let target = findFirstVisibleElement("[data-testid='card.layoutLarge.media']"); + if (target) { + return show(target.parentNode); + } + target = findFirstVisibleElement("[data-testid='tweetPhoto']"); + if (target) { + return show(target.parentNode); + } + target = findFirstVisibleElement("[data-testid^='video-player']"); + if (target) { + return show(target.parentNode); + } + }, 100); + }; document.addEventListener("mouseenter", addBtn, true); - document.addEventListener("touchstart", addBtn, true); + document.addEventListener("touchstart", e => {touch = true; addBtn(e);}, true); + document.addEventListener("touchend", touchCheck, true); })(); \ No newline at end of file From 04e946a3ab617b8bd9335b70fd9130e3da0a32ab Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 19 Aug 2025 09:38:50 +0900 Subject: [PATCH 021/252] 1.9.37.123 --- Pagetual/README.md | 2 +- Pagetual/pagetual.user.js | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index f9ac544ca9e..d10465d01b4 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.122](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.123](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 7074a186f1a..b4381a9b27c 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.122 +// @version 1.9.37.123 // @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 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -2438,7 +2438,7 @@ } this.curSiteRule.pageElement = tempSel + (targetChild ? ">*" : ""); break; - } + } else pageElement = null; } if (!pageElement || pageElement.length === 0) { let pageElementSelTrim = pageElementSel.replace(/:nth-of-type\(\d+\)/g, ""); @@ -2464,8 +2464,8 @@ pageElement = pageElement.children; } this.curSiteRule.pageElement = pageElementSelTrim + (targetChild ? ">*" : ""); - } - } + } else pageElement = null; + } else pageElement = null; } } } @@ -8702,7 +8702,11 @@ } } if (loadmoreBtn && !ruleParser.curSiteRule.loadMore && loadmoreBtn.dataset.ajax !== "true") { - let href = loadmoreBtn.getAttribute("href"); + let href = loadmoreBtn.getAttribute("href"), i = 0, pa = loadmoreBtn.parentNode; + while (!href && i++ < 5 && pa) { + href = pa.getAttribute && pa.getAttribute("href"); + pa = pa.parentNode; + } if (href && href != "/" && !ruleParser.hrefIsJs(href)) { loadmoreBtn = null; } From 50faed9af511e2c76d3a13c5262378852bc20350 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 19 Aug 2025 18:21:18 +0900 Subject: [PATCH 022/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 15905cb48dd..02291b35eef 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -128,11 +128,14 @@ show(e.target.parentNode.parentNode); } }; + function isElementVisible(el) { + const rect = el.getBoundingClientRect(); + return rect.top < window.innerHeight && rect.top > 0 && rect.bottom >= 0; + } function findFirstVisibleElement(selector) { const elements = document.querySelectorAll(selector); const firstVisibleElement = Array.from(elements).find(el => { - const rect = el.getBoundingClientRect(); - return rect.top < window.innerHeight && rect.top > 0 && rect.bottom >= 0; + return isElementVisible(el); }); return firstVisibleElement; } @@ -141,6 +144,7 @@ clearTimeout(checkTimer); if (e.target == downloadBtn) return; checkTimer = setTimeout(() => { + if (isElementVisible(downloadBtn)) return; let target = findFirstVisibleElement("[data-testid='card.layoutLarge.media']"); if (target) { return show(target.parentNode); From 72bb0a237b467a21a883de15c7ce999ea81dc310 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 19 Aug 2025 18:25:54 +0900 Subject: [PATCH 023/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 02291b35eef..163600fb25b 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -80,7 +80,7 @@ } }); downloadBtn.addEventListener("click", e => { - if (e.altKey) { + if (e.altKey || touch) { e.preventDefault(); e.stopPropagation(); } From f9aa7ac563e26b559f88ae185c6dbf95ee4f16e3 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 19 Aug 2025 18:27:41 +0900 Subject: [PATCH 024/252] Update X-Downloader.user.js --- X-Downloader/X-Downloader.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js index 163600fb25b..c488f1f1ef4 100644 --- a/X-Downloader/X-Downloader.user.js +++ b/X-Downloader/X-Downloader.user.js @@ -73,7 +73,7 @@ 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)}`; - if (e.altKey) { + if (e.altKey || touch) { window.open(downloadBtn.href, "_blank"); } } From b9190596919c285d3f051f8710e2eb7a57f97fe8 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 21 Aug 2025 12:08:16 +0900 Subject: [PATCH 025/252] Update README.md --- Picviewer CE+/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index 89d22cddfd0..9a99e07cd7a 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -197,6 +197,8 @@ Feel free to share your own custom rules on Greasy Fork! - getImage - getExtSrc + Learn from https://github.com/hoothin/UserScripts/blob/master/Picviewer%20CE%2B/pvcep_rules.js + ## Blank Gallery Page From 8ce1d22b9bcf36ff223e4a04b7f570fc7503c375 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 22 Aug 2025 16:23:48 +0900 Subject: [PATCH 026/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 19f0b3fe339..d088c6e3f83 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.8.17.1 +// @version 2025.8.22.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 @@ -25789,7 +25789,9 @@ ImgOps | https://imgops.com/#b#`; selectionChanging = false; const selection = window.getSelection(); selectionStr = selection.toString(); - if (selectionStr && selectionStr.length < 500 && imageReg.test(selectionStr)) { + if (selectionStr && selectionStr.length < 500) selectionStr = selectionStr.trim().replace(/^t?t?p?s?:/, "https:"); + else selectionStr = ''; + if (selectionStr && imageReg.test(selectionStr)) { const range = selection.getRangeAt(0); selectionClientRect = range.getBoundingClientRect(); } else { From 2c7aeae3ce3dade28a118785ae3eaa6e267539d4 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 27 Aug 2025 21:35:54 +0900 Subject: [PATCH 027/252] update --- Pagetual/pagetual.user.js | 2 +- .../lib/package.json | 22 +++++++++++-------- .../lib/readme.md | 20 ++++++++++------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index b4381a9b27c..518decd7fbb 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -8674,7 +8674,7 @@ } const loadmoreReg = /^\s*((点击?)?(这里)?((看|加载|展开)(更多|剩余)|继续加载)|(點擊?)?(這裡)?((看|加載|展開)(更多|剩餘)|繼續加載)|load\s*more|もっと読み込む)[\.…▼\s\d%]*$/i; - const defaultLoadmoreSel = ".loadMore,.LoadMore,[class^='load-more'],[class*=' load-more'],.show-more,[class*='_show-more'],[class*='-show-more'],button.show_more,button[data-testid='more-results-button'],#btn_preview_remain,.view-more-btn"; + const defaultLoadmoreSel = ".loadMore,.LoadMore,[class^='load-more'],[class*=' load-more'],.show-more,button.show_more,button[data-testid='more-results-button'],#btn_preview_remain,.view-more-btn"; function getLoadMore(doc, loadmoreBtn) { if (!loadmoreBtn || !getBody(doc).contains(loadmoreBtn) || /less/.test(loadmoreBtn.innerText)) loadmoreBtn = null; let loadMoreSel = ruleParser.curSiteRule.loadMore; diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json index 37afff9f862..2b4c2b72a33 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.4", + "version": "1.0.6", "description": "Convert between simplified and traditional Chinese characters. 切換正體/簡體中文,超輕量級,支援自訂詞彙與「一簡多繁」轉換,無任何相依性。", "main": "stcasc.lib.js", "type": "module", @@ -9,19 +9,23 @@ "url": "git+https://github.com/hoothin/UserScripts.git#master" }, "keywords": [ - "Switch", - "Traditional", - "Chinese", - "Simplified", + "简繁转换", + "簡繁轉換", + "简繁切换", + "簡繁切換", + "繁简转换", + "繁簡轉換", + "繁简切换", + "繁簡切換", "简体中文", "繁体中文", "簡體中文", "繁體中文", "正體中文", - "简繁切换", - "簡繁切換", - "繁简转换", - "繁簡轉換" + "Switch", + "Traditional", + "Chinese", + "Simplified" ], "author": "Hoothin", "license": "MIT", diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md index eba1b85e44f..cfe0e6f52e7 100644 --- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md +++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md @@ -4,24 +4,28 @@ [![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 +--- +``` shell +npm install switch-chinese +``` 演示 --- + 基礎用法 ``` js -import Stcasc from './stcasc.lib.js'; const stcasc = Stcasc(); -const sc = "香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发 知识产权"; -console.log(stcasc.traditionalized(sc)); -//香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 智慧財產權 +const sc = "简繁转换 繁简切换 香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发 知识产权"; +const tc = stcasc.traditionalized(sc); +console.log(tc); +//簡繁轉換 繁簡切換 香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 智慧財產權 ``` -+ 透過 npm 安裝 ++ Import ``` shell -npm install switch-chinese import Stcasc from 'switch-chinese'; ``` @@ -35,8 +39,8 @@ stcasc.traditionalized("简体中文"); + 轉簡體中文 ``` js -stcasc.simplized("正體中文"); -//正体中文 +stcasc.simplized("繁體中文"); +//繁体中文 ``` + 添加快取,避免重複生成字典 From a25ae67125715dd36e9b28a513a0835c114c71b8 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 28 Aug 2025 17:20:04 +0900 Subject: [PATCH 028/252] Update BingBgForBaidu.user.js --- BingBgForBaidu/BingBgForBaidu.user.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/BingBgForBaidu/BingBgForBaidu.user.js b/BingBgForBaidu/BingBgForBaidu.user.js index 1e10922d55b..c33755d652d 100644 --- a/BingBgForBaidu/BingBgForBaidu.user.js +++ b/BingBgForBaidu/BingBgForBaidu.user.js @@ -2,7 +2,7 @@ // @name 百Bing图 // @name:en BingBgForBaidu // @namespace hoothin -// @version 2.3.41 +// @version 2.3.42 // @description 给百度首页换上Bing的背景图,并添加背景图链接与日历组件 // @description:en Just change the background image of baidu.com to bing.com // @author hoothin @@ -95,11 +95,11 @@ //riliLink.innerHTML=""+$(".op-calendar-new-right-date,.op-calendar-pc-right-date",iframe.contentDocument).html()+""; riliLink.onmouseenter=function(){ clearTimeout(t); - $(iframe).show(200); - iframeDoc.scrollTop(137); - iframeDoc.scrollLeft(134); - iframe.width=592; - iframe.height=665; + $(iframe).show(200); + iframeDoc.scrollTop(125); + iframeDoc.scrollLeft(145); + iframe.width=618; + iframe.height=615; }; riliLink.onmouseleave=function(){ clearTimeout(t); From e767acee94d67ab5516ff85f22bb9fa48657124c Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 28 Aug 2025 18:54:57 +0900 Subject: [PATCH 029/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 2852 +++++++++++++++++++++++++++++++++++++ 1 file changed, 2852 insertions(+) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 518decd7fbb..7838725e7f5 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -282,6 +282,130 @@ lastPageTips: "Show tips when reaching the last page" } }, + { + name: "한국어", + match: ["ko"], + lang: { + enableDebug: "콘솔에 디버그 출력 활성화", + updateNotification: "규칙 업데이트 후 알림", + disable: "일시적으로 비활성화", + disableSite: "비활성화 상태 전환", + disableSiteTips: "이 사이트에서 비활성화됨.", + enableSiteTips: "이 사이트에서 활성화됨.", + enable: "✅자동 페이지 넘김 활성화", + tempActive: "일시적으로 활성", + toTop: "맨 위로 이동.", + toBottom: "맨 아래로 이동.", + current: "현재 페이지.", + forceIframe: "다음 페이지 강제 결합", + cancelForceIframe: "강제 결합 취소", + configure: "Pagetual 설정", + firstUpdate: "기본 규칙 목록을 초기화하려면 여기를 클릭하세요", + update: "온라인 규칙 업데이트", + click2update: "URL에서 규칙을 지금 업데이트하려면 클릭하세요", + loadNow: "다음 페이지 자동 로드", + loadConfirm: "몇 페이지를 로드하시겠습니까? (0은 무한을 의미)", + noNext: "다음 링크를 찾을 수 없습니다. 새 규칙을 만들어 주세요", + passSec: "#t#초 전에 업데이트됨", + passMin: "#t#분 전에 업데이트됨", + passHour: "#t#시간 전에 업데이트됨", + passDay: "#t#일 전에 업데이트됨", + cantDel: "내장된 규칙은 삭제할 수 없습니다", + confirmDel: "이 규칙을 정말로 삭제하시겠습니까?", + updateSucc: "업데이트 성공", + beginUpdate: "업데이트를 시작합니다. 잠시만 기다려 주세요", + customUrls: "Pagetual 또는 AutoPagerize 규칙 URL 가져오기, 한 줄에 하나의 URL.", + customRules: "사용자 정의 규칙을 입력하세요. ✍️규칙 기여하기", + save: "저장", + loadingText: "로딩 중...", + opacity: "불투명도", + opacityPlaceholder: "0: 구분선 숨기기", + hideBar: "페이지 구분선 숨기기", + hideBarButNoStop: "숨기지만 중지하지 않음", + dbClick2Stop: "일시 중지하려면 빈 공간을 두 번 클릭하세요", + sortTitle: "정렬은 다음 규칙 업데이트 후에 적용됩니다", + autoRun: "자동 활성화 (블랙리스트 모드)", + autoLoadNum: "미리 로드할 페이지 수", + turnRate: "바닥글로부터 페이지 높이의 【X】배 미만일 때 다음 페이지로 넘기기", + inputPageNum: "이동할 페이지 번호를 입력하세요", + enableHistory: "페이지 넘김 후 방문 기록 저장", + enableHistoryAfterInsert: "페이지 결합 직후 방문 기록 저장, 그렇지 않으면 탐색 후 저장", + contentVisibility: "렌더링 성능 향상을 위해 content-visibility 자동 전환", + initRun: "페이지 열람 직후 자동 넘김 시작", + preload: "속도 향상을 위해 다음 페이지 미리 로드", + click2ImportRule: "기본 규칙 링크를 가져오려면 클릭하고 업데이트가 완료될 때까지 기다리세요: ", + forceAllBody: "페이지의 전체 본문을 결합하시겠습니까?", + openInNewTab: "추가된 URL을 새 탭에서 열기", + importSucc: "가져오기 완료", + import: "가져오기", + editCurrent: "현재 웹사이트 규칙 편집", + editBlacklist: "URL 블랙리스트 편집, 한 줄에 하나씩 입력, [?,*] 와일드카드 지원.", + upBtnImg: "맨 위로 가기 아이콘", + downBtnImg: "맨 아래로 가기 아이콘", + sideControllerIcon: "사이드바 아이콘", + loadingTextTitle: "로딩", + dbClick2StopCtrl: "Ctrl 키", + dbClick2StopAlt: "Alt 키", + dbClick2StopShift: "Shift 키", + dbClick2StopMeta: "Meta 키", + dbClick2StopKey: "단축키", + pageElementCss: "주요 페이지 요소에 대한 사용자 정의 스타일", + customCss: "사용자 정의 전체 CSS", + firstAlert: "기본 규칙을 가져오지 않았습니다. 가져올 적절한 규칙을 선택해주세요", + picker: "Pagetual 요소 선택기", + closePicker: "Pagetual 선택기 닫기", + pickerPlaceholder: "요소 선택기 (고급 사용자 전용, 그렇지 않으면 비워두세요)", + pickerCheck: "선택기 확인 및 복사", + switchSelector: "클릭하여 요소 전환", + gotoEdit: "현재 선택기로 규칙 편집하러 가기", + manualMode: "결합 비활성화, 오른쪽 화살표 키를 사용하여 수동으로 다음 페이지로 이동 (또는 'pagetual.next' 이벤트 전달)", + clickMode: "결합 비활성화, 페이지 끝까지 스크롤하면 다음 페이지 자동 클릭", + pageBarMenu: "페이지 바 중앙을 클릭하여 선택기 메뉴 열기", + nextSwitch: "다음 링크 전환", + arrowToScroll: "왼쪽 화살표를 눌러 뒤로 스크롤하고 오른쪽 화살표를 눌러 페이지 이동", + sideController: "사이드바에 페이징 제어 바 표시", + sideControllerScroll: "스크롤 시 표시/숨김", + sideControllerAlways: "항상 표시", + hideLoadingIcon: "로딩 애니메이션 숨기기", + hideBarArrow: "페이지 바 화살표 숨기기", + duplicate: "중복된 Pagetual이 설치되었습니다. 스크립트 관리자를 확인하세요!", + forceStateIframe: "전체 페이지를 iframe으로 삽입", + forceStateDynamic: "iframe을 통해 동적 콘텐츠 로드", + forceStateDisable: "이 사이트에서 페이지 넘김 비활성화", + autoScrollRate: "스크롤 속도 (1~1000)", + disableAutoScroll: "자동 스크롤 중지", + enableAutoScroll: "자동 스크롤 활성화", + toggleAutoScroll: "자동 스크롤 전환", + ruleRequest: "규칙 요청", + page: "페이지 ", + prevPage: "이전 페이지", + nextPage: "다음 페이지", + errorRulesMustBeArray: "규칙은 배열이어야 합니다!", + errorJson: "JSON 오류, 다시 확인해주세요!", + editSuccess: "성공적으로 편집되었습니다", + errorWrongUrl: "잘못된 URL, 다시 확인해주세요!", + errorAlreadyExists: "규칙이 이미 존재합니다!", + settingsSaved: "설정이 저장되었습니다. 확인하려면 새로고침하세요", + iframe: "iframe으로 강제 분할", + dynamic: "동적 로딩", + reloadPage: "편집 완료, 지금 새로고침하시겠습니까?", + copied: "복사됨", + noValidContent: "유효한 콘텐츠를 찾을 수 없습니다. 보안 문자(Captcha)가 있을 수 있습니다", + outOfDate: "스크립트가 오래되었습니다. 최신 버전으로 업데이트해주세요.", + hideBarTips: "페이지네이션 바 숨기기, 몰입형 경험 전환", + setConfigPage: "현재 페이지를 기본 설정 페이지로 지정", + wedata2github: "wedata 주소를 github 저장소의 미러 주소로 변경", + addOtherProp: "규칙 속성 추가", + addNextSelector: "선택기 콘텐츠를 nextLink로 추가", + addPageSelector: "선택기 콘텐츠를 pageElement로 추가", + propName: "규칙 속성 이름 입력", + propValue: "규칙 속성 값 입력", + customFirst: "로컬 사용자 정의 규칙 캐시 무시", + rulesExample: "규칙 예시", + lastPage: "마지막 페이지에 도달했습니다", + lastPageTips: "마지막 페이지 도달 시 팁 표시" + } + }, { name: "Deutsch", match: ["de", "de-AT", "de-CH", "de-DE", "de-LI", "de-LU"], @@ -406,6 +530,2486 @@ lastPageTips: "Tipps anzeigen, wenn die letzte Seite erreicht ist" } }, + { + name: "ไทย", + match: ["th"], + lang: { + enableDebug: "เปิดใช้งานเอาต์พุตการดีบักไปยังคอนโซล", + updateNotification: "การแจ้งเตือนหลังจากอัปเดตกฎ", + disable: "ปิดใช้งานชั่วคราว", + disableSite: "สลับสถานะการปิดใช้งาน", + disableSiteTips: "ปิดใช้งานบนไซต์นี้", + enableSiteTips: "เปิดใช้งานบนไซต์นี้", + enable: "✅เปิดใช้งานการเปลี่ยนหน้าอัตโนมัติ", + tempActive: "ใช้งานชั่วคราว", + toTop: "กลับไปด้านบน", + toBottom: "ไปที่ด้านล่าง", + current: "หน้าปัจจุบัน", + forceIframe: "บังคับให้เข้าร่วมหน้าถัดไป", + cancelForceIframe: "ยกเลิกการเข้าร่วมบังคับ", + configure: "กำหนดค่า Pagetual", + firstUpdate: "คลิกที่นี่เพื่อเริ่มต้นรายการกฎเริ่มต้น", + update: "อัปเดตกฎออนไลน์", + click2update: "คลิกเพื่ออัปเดตกฎจาก url ทันที", + loadNow: "โหลดหน้าถัดไปโดยอัตโนมัติ", + loadConfirm: "คุณต้องการโหลดกี่หน้า (0 หมายถึงไม่สิ้นสุด)", + noNext: "ไม่พบลิงก์ถัดไป โปรดสร้างกฎใหม่", + passSec: "อัปเดตเมื่อ #t# วินาทีที่แล้ว", + passMin: "อัปเดตเมื่อ #t# นาทีที่แล้ว", + passHour: "อัปเดตเมื่อ #t# ชั่วโมงที่แล้ว", + passDay: "อัปเดตเมื่อ #t# วันที่แล้ว", + cantDel: "ไม่สามารถลบกฎในตัวได้", + confirmDel: "คุณแน่ใจหรือไม่ว่าต้องการลบกฎนี้", + updateSucc: "อัปเดตสำเร็จ", + beginUpdate: "เริ่มอัปเดต โปรดรอสักครู่", + customUrls: "นำเข้า URL กฎ Pagetual หรือ AutoPagerize หนึ่ง URL ต่อบรรทัด", + customRules: "ป้อนกฎที่กำหนดเอง ✍️มีส่วนร่วมในกฎ", + save: "บันทึก", + loadingText: "กำลังโหลด...", + opacity: "ความทึบ", + opacityPlaceholder: "0: ซ่อนตัวเว้นวรรค", + hideBar: "ซ่อนตัวเว้นวรรคของหน้า", + hideBarButNoStop: "ซ่อนแต่ไม่หยุด", + dbClick2Stop: "ดับเบิลคลิกที่พื้นที่ว่างเพื่อหยุดชั่วคราว", + sortTitle: "การเรียงลำดับจะมีผลหลังจากการอัปเดตกฎครั้งถัดไป", + autoRun: "เปิดใช้งานอัตโนมัติ (โหมดบัญชีดำ)", + autoLoadNum: "จำนวนหน้าที่โหลดล่วงหน้า", + turnRate: "เปลี่ยนหน้าถัดไปเมื่ออยู่ห่างจากส่วนท้ายน้อยกว่า【X】เท่าของความสูงของหน้า", + inputPageNum: "ป้อนหมายเลขหน้าเพื่อข้ามไป", + enableHistory: "เขียนประวัติการเข้าชมหลังจากการเปลี่ยนหน้า", + enableHistoryAfterInsert: "เขียนประวัติการเข้าชมทันทีหลังจากต่อเข้าด้วยกัน มิฉะนั้นจะเขียนหลังจากการเข้าชม", + contentVisibility: "สลับ content-visibility โดยอัตโนมัติเพื่อปรับปรุงประสิทธิภาพการเรนเดอร์", + initRun: "เปลี่ยนหน้าทันทีหลังจากเปิด", + preload: "โหลดหน้าถัดไปล่วงหน้าเพื่อเพิ่มความเร็ว", + click2ImportRule: "คลิกเพื่อนำเข้าลิงก์กฎพื้นฐาน แล้วรอจนกว่าการอัปเดตจะเสร็จสมบูรณ์: ", + forceAllBody: "เข้าร่วมเนื้อหาทั้งหมดของหน้าหรือไม่", + openInNewTab: "เปิด URL ของส่วนเพิ่มเติมในแท็บใหม่", + importSucc: "นำเข้าสำเร็จ", + import: "นำเข้า", + editCurrent: "แก้ไขกฎสำหรับเว็บไซต์ปัจจุบัน", + editBlacklist: "แก้ไขบัญชีดำของ URL หนึ่งรายการต่อบรรทัด รองรับ [?,*] wildcards", + upBtnImg: "ไอคอนกลับไปด้านบน", + downBtnImg: "ไอคอนไปที่ส่วนท้าย", + sideControllerIcon: "ไอคอนของแถบด้านข้าง", + loadingTextTitle: "กำลังโหลด", + dbClick2StopCtrl: "ปุ่ม Ctrl", + dbClick2StopAlt: "ปุ่ม Alt", + dbClick2StopShift: "ปุ่ม Shift", + dbClick2StopMeta: "ปุ่ม Meta", + dbClick2StopKey: "ปุ่มลัด", + pageElementCss: "สไตล์ที่กำหนดเองสำหรับองค์ประกอบหน้าหลัก", + customCss: "CSS ที่กำหนดเองทั้งหมด", + firstAlert: "คุณยังไม่ได้นำเข้ากฎพื้นฐาน โปรดเลือกกฎที่เหมาะสมเพื่อนำเข้า", + picker: "ตัวเลือกองค์ประกอบ Pagetual", + closePicker: "ปิดตัวเลือก Pagetual", + pickerPlaceholder: "ตัวเลือกองค์ประกอบ (สำหรับผู้ใช้ขั้นสูงเท่านั้น เว้นว่างไว้)", + pickerCheck: "ตรวจสอบตัวเลือกและคัดลอก", + switchSelector: "คลิกเพื่อสลับองค์ประกอบ", + gotoEdit: "ไปที่การแก้ไขกฎด้วยตัวเลือกปัจจุบัน", + manualMode: "ปิดใช้งานการต่อเข้าด้วยกัน เลื่อนหน้าถัดไปด้วยตนเองโดยใช้ปุ่มลูกศรขวา (หรือส่งเหตุการณ์ 'pagetual.next')", + clickMode: "ปิดใช้งานการต่อเข้าด้วยกัน คลิกหน้าถัดไปโดยอัตโนมัติเมื่อเลื่อนไปที่ท้ายหน้า", + pageBarMenu: "คลิกที่กึ่งกลางของแถบหน้าเพื่อเปิดเมนูตัวเลือก", + nextSwitch: "สลับลิงก์ถัดไป", + arrowToScroll: "กดลูกศรซ้ายเพื่อเลื่อนกลับและลูกศรขวาเพื่อเลื่อนหน้า", + sideController: "แสดงแถบควบคุมการแบ่งหน้าในแถบด้านข้าง", + sideControllerScroll: "สลับการเลื่อน", + sideControllerAlways: "แสดงเสมอ", + hideLoadingIcon: "ซ่อนภาพเคลื่อนไหวการโหลด", + hideBarArrow: "ซ่อนลูกศรสำหรับแถบหน้า", + duplicate: "มีการติดตั้ง Pagetual ที่ซ้ำกัน ตรวจสอบผู้จัดการสคริปต์ของคุณ!", + forceStateIframe: "ฝังหน้าเต็มเป็น iframe", + forceStateDynamic: "โหลดเนื้อหาแบบไดนามิกผ่าน iframe", + forceStateDisable: "ปิดใช้งานการเปลี่ยนหน้าบนไซต์นี้", + autoScrollRate: "ความเร็วในการเลื่อน (1~1000)", + disableAutoScroll: "หยุดการเลื่อนอัตโนมัติ", + enableAutoScroll: "เปิดใช้งานการเลื่อนอัตโนมัติ", + toggleAutoScroll: "สลับการเลื่อนอัตโนมัติ", + ruleRequest: "ขอกฎ", + page: "หน้า ", + prevPage: "หน้าก่อนหน้า", + nextPage: "หน้าถัดไป", + errorRulesMustBeArray: "กฎต้องเป็นอาร์เรย์!", + errorJson: "ข้อผิดพลาด JSON ตรวจสอบอีกครั้ง!", + editSuccess: "แก้ไขสำเร็จ", + errorWrongUrl: "URL ไม่ถูกต้อง ตรวจสอบอีกครั้ง!", + errorAlreadyExists: "มีกฎอยู่แล้ว!", + settingsSaved: "บันทึกการตั้งค่าแล้ว รีเฟรชเพื่อดู", + iframe: "บังคับแยกโดย iframe", + dynamic: "การโหลดแบบไดนามิก", + reloadPage: "แก้ไขเสร็จแล้ว โหลดใหม่ตอนนี้หรือไม่", + copied: "คัดลอกแล้ว", + noValidContent: "ไม่พบเนื้อหาที่ถูกต้อง อาจมี Captcha", + outOfDate: "สคริปต์ล้าสมัย โปรดอัปเดตเป็นเวอร์ชันล่าสุด", + hideBarTips: "ซ่อนแถบเลขหน้า สลับประสบการณ์ที่สมจริง", + setConfigPage: "ตั้งค่าหน้าปัจจุบันเป็นหน้าการกำหนดค่าเริ่มต้น", + wedata2github: "เปลี่ยนที่อยู่ wedata เป็นที่อยู่มิเรอร์ในที่เก็บ github", + addOtherProp: "เพิ่มคุณสมบัติของกฎ", + addNextSelector: "เพิ่มเนื้อหาตัวเลือกเป็น nextLink", + addPageSelector: "เพิ่มเนื้อหาตัวเลือกเป็น pageElement", + propName: "ป้อนชื่อคุณสมบัติของกฎ", + propValue: "ป้อนค่าคุณสมบัติของกฎ", + customFirst: "ไม่สนใจแคชสำหรับกฎที่กำหนดเองในเครื่อง", + rulesExample: "ตัวอย่างกฎ", + lastPage: "ถึงหน้าสุดท้ายแล้ว", + lastPageTips: "แสดงเคล็ดลับเมื่อถึงหน้าสุดท้าย" + } + }, + { + name: "Norsk (Bokmål)", + match: ["nb", "nb-NO"], + lang: { + enableDebug: "Aktiver feilsøkingsutdata til konsollen", + updateNotification: "Varsling etter at regler er oppdatert", + disable: "Deaktiver midlertidig", + disableSite: "Veksle deaktivert tilstand", + disableSiteTips: "Deaktivert på dette nettstedet.", + enableSiteTips: "Aktivert på dette nettstedet.", + enable: "✅Aktiver automatisk sidevending", + tempActive: "Midlertidig aktiv", + toTop: "Tilbake til toppen.", + toBottom: "Gå til bunnen.", + current: "Gjeldende side.", + forceIframe: "Tving til å koble til neste side", + cancelForceIframe: "Avbryt tvungen tilkobling", + configure: "Konfigurer Pagetual", + firstUpdate: "Klikk her for å initialisere standard regelliste", + update: "Oppdater nettbaserte regler", + click2update: "Klikk for å oppdatere regler fra url nå", + loadNow: "Last neste automatisk", + loadConfirm: "Hvor mange sider vil du laste? (0 betyr uendelig)", + noNext: "Ingen neste lenke funnet, vennligst opprett en ny regel", + passSec: "Oppdatert for #t# sekunder siden", + passMin: "Oppdatert for #t# minutter siden", + passHour: "Oppdatert for #t# timer siden", + passDay: "Oppdatert for #t# dager siden", + cantDel: "Kan ikke slette innebygde regler", + confirmDel: "Er du sikker på at du vil slette denne regelen?", + updateSucc: "Oppdatering vellykket", + beginUpdate: "Starter oppdatering, vent et øyeblikk", + customUrls: "Importer Pagetual eller AutoPagerize regel-url, én url per linje.", + customRules: "Skriv inn egendefinerte regler. ✍️Bidra med regler", + save: "Lagre", + loadingText: "Laster nå...", + opacity: "Opasitet", + opacityPlaceholder: "0: skjul avstandsstykke", + hideBar: "Skjul pagineringsavstandsstykket", + hideBarButNoStop: "Skjul, men ikke stopp", + dbClick2Stop: "Dobbeltklikk på det tomme området for å pause", + sortTitle: "Sortering trer i kraft etter neste regeloppdatering", + autoRun: "Auto-aktiver (svartelistemodus)", + autoLoadNum: "Antall for forhåndslastede sider", + turnRate: "Snu til neste side når det er mindre enn 【X】 ganger sidehøyden fra bunnteksten", + inputPageNum: "Skriv inn sidetall for å hoppe", + enableHistory: "Skriv nettleserlogg etter sidevending", + enableHistoryAfterInsert: "Skriv nettleserlogg umiddelbart etter spleising, ellers skriv etter surfing", + contentVisibility: "Bytt automatisk innholdssynlighet for å forbedre gjengivelsesytelsen", + initRun: "Snu sider umiddelbart etter åpning", + preload: "Forhåndslast neste side for å øke hastigheten", + click2ImportRule: "Klikk for å importere grunnregellenke, og vent deretter til oppdateringen er fullført: ", + forceAllBody: "Koble til hele sidens kropp?", + openInNewTab: "Åpne url-er for tillegg i ny fane", + importSucc: "Import fullført", + import: "Importer", + editCurrent: "Rediger regel for gjeldende nettsted", + editBlacklist: "Rediger url-svartelisten, én oppføring per linje, støtter [?,*] jokertegn.", + upBtnImg: "Ikon for tilbake til toppen", + downBtnImg: "Ikon for å gå til bunntekst", + sideControllerIcon: "Ikon for sidefelt", + loadingTextTitle: "Laster", + dbClick2StopCtrl: "Ctrl-tast", + dbClick2StopAlt: "Alt-tast", + dbClick2StopShift: "Shift-tast", + dbClick2StopMeta: "Meta-tast", + dbClick2StopKey: "Hurtigtast", + pageElementCss: "Egendefinert stil for hovedsideelementer", + customCss: "Egendefinert komplett css", + firstAlert: "Du har ikke importert grunnregelen, vennligst velg riktig regel å importere", + picker: "Pagetual elementvelger", + closePicker: "Lukk Pagetual-velger", + pickerPlaceholder: "Elementvelger, (Kun avanserte brukere, la stå tomt ellers)", + pickerCheck: "Sjekk velger og kopier", + switchSelector: "Klikk for å bytte element", + gotoEdit: "Gå til redigeringsregel med gjeldende velger", + manualMode: "Deaktiver spleising, gå manuelt frem til neste side med høyre piltast (eller send hendelsen 'pagetual.next')", + clickMode: "Deaktiver spleising, klikk automatisk på neste side når du ruller til slutten av siden", + pageBarMenu: "Klikk på midten av sidelinjen for å åpne velgermenyen", + nextSwitch: "Bytt neste lenke", + arrowToScroll: "Trykk venstre piltast for å rulle tilbake og høyre piltast for å gå frem en side", + sideController: "Vis pagineringskontrollinjen i sidefeltet", + sideControllerScroll: "Rulleveksling", + sideControllerAlways: "Vis alltid", + hideLoadingIcon: "Skjul lasteanimasjon", + hideBarArrow: "Skjul pil for sidelinje", + duplicate: "Duplikat Pagetual har blitt installert, sjekk skriptbehandleren din!", + forceStateIframe: "Bygg inn hele siden som iframe", + forceStateDynamic: "Last dynamisk innhold via iframe", + forceStateDisable: "Deaktiver sidevending på dette nettstedet", + autoScrollRate: "Rullehastighet (1~1000)", + disableAutoScroll: "Stopp automatisk rulling", + enableAutoScroll: "Aktiver automatisk rulling", + toggleAutoScroll: "Veksle automatisk rulling", + ruleRequest: "Regelforespørsel", + page: "Side ", + prevPage: "Forrige side", + nextPage: "Neste side", + errorRulesMustBeArray: "Regler må være en matrise!", + errorJson: "JSON-feil, sjekk igjen!", + editSuccess: "Redigering vellykket", + errorWrongUrl: "Feil url, sjekk igjen!", + errorAlreadyExists: "En regel eksisterer allerede!", + settingsSaved: "Innstillingene er lagret, oppdater for å se", + iframe: "Tvunget delt av iframe", + dynamic: "Dynamisk lasting", + reloadPage: "Redigering fullført, last på nytt nå?", + copied: "Kopiert", + noValidContent: "Ingen gyldig innhold funnet, en Captcha kan være til stede", + outOfDate: "Skriptet er utdatert, vennligst oppdater til den nyeste versjonen.", + hideBarTips: "Skjul pagineringslinjen, veksle mellom oppslukende opplevelse", + setConfigPage: "Angi gjeldende side som standard konfigurasjonsside", + wedata2github: "Endre wedata-adressen til speiladressen i github-depotet", + addOtherProp: "Legg til regelegenskaper", + addNextSelector: "Legg til velgerinnhold som nextLink", + addPageSelector: "Legg til velgerinnhold som pageElement", + propName: "Skriv inn regelegenskapsnavn", + propValue: "Skriv inn regelegenskapsverdi", + customFirst: "Ignorer hurtigbuffer for lokale egendefinerte regler", + rulesExample: "Regeleksempel", + lastPage: "Nådde den siste siden", + lastPageTips: "Vis tips når du når den siste siden" + } + }, + { + name: "Svenska", + match: ["sv"], + lang: { + enableDebug: "Aktivera felsökningsutdata till konsolen", + updateNotification: "Meddelande efter att regler har uppdaterats", + disable: "Inaktivera tillfälligt", + disableSite: "Växla inaktiverat läge", + disableSiteTips: "Inaktiverad på denna webbplats.", + enableSiteTips: "Aktiverad på denna webbplats.", + enable: "✅Aktivera automatisk sidvändning", + tempActive: "Tillfälligt aktiv", + toTop: "Tillbaka till toppen.", + toBottom: "Gå till botten.", + current: "Nuvarande sida.", + forceIframe: "Tvinga att ansluta till nästa sida", + cancelForceIframe: "Avbryt tvångsanslutning", + configure: "Konfigurera Pagetual", + firstUpdate: "Klicka här för att initiera standardregellistan", + update: "Uppdatera onlineregler", + click2update: "Klicka för att uppdatera regler från url nu", + loadNow: "Ladda nästa automatiskt", + loadConfirm: "Hur många sidor vill du ladda? (0 betyder oändligt)", + noNext: "Ingen nästa länk hittades, skapa en ny regel", + passSec: "Uppdaterad för #t# sekunder sedan", + passMin: "Uppdaterad för #t# minuter sedan", + passHour: "Uppdaterad för #t# timmar sedan", + passDay: "Uppdaterad för #t# dagar sedan", + cantDel: "Kan inte ta bort inbyggda regler", + confirmDel: "Är du säker på att du vill ta bort denna regel?", + updateSucc: "Uppdatering lyckades", + beginUpdate: "Påbörjar uppdatering, vänta ett ögonblick", + customUrls: "Importera Pagetual eller AutoPagerize regel-url, en url per rad.", + customRules: "Ange anpassade regler. ✍️Bidra med regler", + save: "Spara", + loadingText: "Laddar nu...", + opacity: "Opacitet", + opacityPlaceholder: "0: dölj mellanrum", + hideBar: "Dölj pagineringsmellanrummet", + hideBarButNoStop: "Dölj men stoppa inte", + dbClick2Stop: "Dubbelklicka på det tomma utrymmet för att pausa", + sortTitle: "Sortering träder i kraft efter nästa regeluppdatering", + autoRun: "Auto-aktivera (svartlistningsläge)", + autoLoadNum: "Antal för förinläsning av sidor", + turnRate: "Vänd till nästa sida när det är mindre än 【X】 gånger sidhöjden från sidfoten", + inputPageNum: "Ange sidnummer för att hoppa", + enableHistory: "Skriv webbhistorik efter sidvändning", + enableHistoryAfterInsert: "Skriv webbhistorik omedelbart efter sammanfogning, annars skriv efter surfning", + contentVisibility: "Växla automatiskt innehållssynlighet för att förbättra renderingsprestanda", + initRun: "Vänd sidor omedelbart efter öppning", + preload: "Förinläs nästa sida för att påskynda", + click2ImportRule: "Klicka för att importera grundregellänk, och vänta sedan tills uppdateringen är klar: ", + forceAllBody: "Anslut hela sidans kropp?", + openInNewTab: "Öppna webbadresser för tillägg i ny flik", + importSucc: "Importen är klar", + import: "Importera", + editCurrent: "Redigera regel för aktuell webbplats", + editBlacklist: "Redigera webbadress-svartlistan, en post per rad, stöder [?,*] jokertecken.", + upBtnImg: "Ikon för tillbaka till toppen", + downBtnImg: "Ikon för att gå till sidfot", + sideControllerIcon: "Ikon för sidofältet", + loadingTextTitle: "Laddar", + dbClick2StopCtrl: "Ctrl-tangent", + dbClick2StopAlt: "Alt-tangent", + dbClick2StopShift: "Shift-tangent", + dbClick2StopMeta: "Meta-tangent", + dbClick2StopKey: "Snabbtangent", + pageElementCss: "Anpassad stil för huvudsidaelement", + customCss: "Anpassad komplett css", + firstAlert: "Du har inte importerat grundregeln, välj lämplig regel att importera", + picker: "Pagetual elementväljare", + closePicker: "Stäng Pagetual-väljare", + pickerPlaceholder: "Elementväljare, (Endast avancerade användare, lämna tomt annars)", + pickerCheck: "Kontrollera väljare och kopiera", + switchSelector: "Klicka för att byta element", + gotoEdit: "Gå till redigera regel med aktuell väljare", + manualMode: "Inaktivera sammanfogning, gå manuellt framåt till nästa sida med höger piltangent (eller skicka händelsen 'pagetual.next')", + clickMode: "Inaktivera sammanfogning, klicka automatiskt på nästa sida när du rullar till slutet av sidan", + pageBarMenu: "Klicka på mitten av sidfältet för att öppna väljarmenyn", + nextSwitch: "Byt nästa länk", + arrowToScroll: "Tryck på vänster piltangent för att rulla tillbaka och höger piltangent för att gå fram en sida", + sideController: "Visa pagineringskontrollfältet i sidofältet", + sideControllerScroll: "Rullningsväxling", + sideControllerAlways: "Visa alltid", + hideLoadingIcon: "Dölj laddningsanimation", + hideBarArrow: "Dölj pil för sidfält", + duplicate: "Duplicerad Pagetual har installerats, kontrollera din skripthanterare!", + forceStateIframe: "Bädda in hela sidan som iframe", + forceStateDynamic: "Ladda dynamiskt innehåll via iframe", + forceStateDisable: "Inaktivera sidvändning på denna webbplats", + autoScrollRate: "Rullningshastighet (1~1000)", + disableAutoScroll: "Stoppa automatisk rullning", + enableAutoScroll: "Aktivera automatisk rullning", + toggleAutoScroll: "Växla automatisk rullning", + ruleRequest: "Regelförfrågan", + page: "Sida ", + prevPage: "Föregående sida", + nextPage: "Nästa sida", + errorRulesMustBeArray: "Regler måste vara en array!", + errorJson: "JSON-fel, kontrollera igen!", + editSuccess: "Redigering lyckades", + errorWrongUrl: "Fel webbadress, kontrollera igen!", + errorAlreadyExists: "En regel finns redan!", + settingsSaved: "Inställningarna har sparats, uppdatera för att se", + iframe: "Tvingad delning av iframe", + dynamic: "Dynamisk laddning", + reloadPage: "Redigering slutförd, ladda om nu?", + copied: "Kopierad", + noValidContent: "Inget giltigt innehåll hittades, en Captcha kan finnas", + outOfDate: "Skriptet är föråldrat, uppdatera till den senaste versionen.", + hideBarTips: "Dölj pagineringsfältet, växla till en uppslukande upplevelse", + setConfigPage: "Ange aktuell sida som standardkonfigurationssida", + wedata2github: "Ändra wedata-adressen till spegeladressen i github-förvaret", + addOtherProp: "Lägg till regelegenskaper", + addNextSelector: "Lägg till väljarinnehåll som nextLink", + addPageSelector: "Lägg till väljarinnehåll som pageElement", + propName: "Ange regelegenskapsnamn", + propValue: "Ange regelegenskapsvärde", + customFirst: "Ignorera cache för lokala anpassade regler", + rulesExample: "Regelexempel", + lastPage: "Nådde sista sidan", + lastPageTips: "Visa tips när du når sista sidan" + } + }, + { + name: "Српски", + match: ["sr"], + lang: { + enableDebug: "Омогући отклањање грешака на конзоли", + updateNotification: "Обавештење након ажурирања правила", + disable: "Привремено онемогући", + disableSite: "Промени стање онемогућености", + disableSiteTips: "Онемогућено на овом сајту.", + enableSiteTips: "Омогућено на овом сајту.", + enable: "✅Омогући аутоматско окретање страница", + tempActive: "Привремено активно", + toTop: "Назад на врх.", + toBottom: "Иди на дно.", + current: "Тренутна страница.", + forceIframe: "Присили спајање следеће странице", + cancelForceIframe: "Откажи присилно спајање", + configure: "Конфигуриши Pagetual", + firstUpdate: "Кликните овде да бисте покренули подразумевану листу правила", + update: "Ажурирај правила на мрежи", + click2update: "Кликните да бисте одмах ажурирали правила са УРЛ-а", + loadNow: "Учитај следеће аутоматски", + loadConfirm: "Колико страница желите да учитате? (0 значи бесконачно)", + noNext: "Није пронађена следећа веза, креирајте ново правило", + passSec: "Ажурирано пре #t# секунди", + passMin: "Ажурирано пре #t# минута", + passHour: "Ажурирано пре #t# сати", + passDay: "Ажурирано пре #t# дана", + cantDel: "Не могу се избрисати уграђена правила", + confirmDel: "Да ли сте сигурни да желите да избришете ово правило?", + updateSucc: "Ажурирање је успело", + beginUpdate: "Започни ажурирање, сачекајте тренутак", + customUrls: "Увезите Pagetual или AutoPagerize УРЛ правила, један УРЛ по линији.", + customRules: "Унесите прилагођена правила. ✍️Допринесите правилима", + save: "Сачувај", + loadingText: "Учитавање у току...", + opacity: "Провидност", + opacityPlaceholder: "0: сакриј размак", + hideBar: "Сакриј размак за пагинацију", + hideBarButNoStop: "Сакриј, али не заустављај", + dbClick2Stop: "Двапут кликните на празан простор да бисте паузирали", + sortTitle: "Сортирање ступа на снагу након следећег ажурирања правила", + autoRun: "Аутоматско омогућавање (режим црне листе)", + autoLoadNum: "Количина за унапред учитане странице", + turnRate: "Окрените следећу страницу када је мање од 【X】 пута висине странице од подножја", + inputPageNum: "Унесите број странице за скок", + enableHistory: "Упиши историју прегледања након окретања странице", + enableHistoryAfterInsert: "Упиши историју прегледања одмах након спајања, у супротном упиши након прегледања", + contentVisibility: "Аутоматски промени видљивост садржаја да би се побољшале перформансе приказивања", + initRun: "Окрени странице одмах након отварања", + preload: "Унапред учитај следећу страницу ради убрзања", + click2ImportRule: "Кликните да бисте увезли везу основних правила, а затим сачекајте док се ажурирање не заврши: ", + forceAllBody: "Спојити цело тело странице?", + openInNewTab: "Отвори УРЛ-ове додатака у новој картици", + importSucc: "Увоз је завршен", + import: "Увези", + editCurrent: "Уреди правило за тренутни веб-сајт", + editBlacklist: "Уредите УРЛ црну листу, један унос по линији, подржава џокере [?,*].", + upBtnImg: "Икона за повратак на врх", + downBtnImg: "Икона за одлазак на подножје", + sideControllerIcon: "Икона бочне траке", + loadingTextTitle: "Учитавање", + dbClick2StopCtrl: "Тастер Ctrl", + dbClick2StopAlt: "Тастер Alt", + dbClick2StopShift: "Тастер Shift", + dbClick2StopMeta: "Тастер Meta", + dbClick2StopKey: "Тастер пречице", + pageElementCss: "Прилагођени стил за главне елементе странице", + customCss: "Прилагођени комплетан ЦСС", + firstAlert: "Нисте увезли основно правило, изаберите одговарајуће правило за увоз", + picker: "Pagetual бирач елемената", + closePicker: "Затвори Pagetual бирач", + pickerPlaceholder: "Бирач елемената, (Само напредни корисници, иначе оставите празно)", + pickerCheck: "Провери бирач и копирај", + switchSelector: "Кликните да бисте променили елемент", + gotoEdit: "Иди на уређивање правила са тренутним бирачем", + manualMode: "Онемогући спајање, ручно пређи на следећу страницу помоћу десног тастера са стрелицом (или пошаљи догађај 'pagetual.next')", + clickMode: "Онемогући спајање, аутоматски кликни на следећу страницу приликом померања до краја странице", + pageBarMenu: "Кликните на центар траке странице да бисте отворили мени бирача", + nextSwitch: "Промени следећу везу", + arrowToScroll: "Притисните леви тастер са стрелицом за померање уназад и десни тастер са стрелицом за прелазак на страницу", + sideController: "Прикажи контролну траку за пагинацију у бочној траци", + sideControllerScroll: "Промена померања", + sideControllerAlways: "Увек прикажи", + hideLoadingIcon: "Сакриј анимацију учитавања", + hideBarArrow: "Сакриј стрелицу за траку странице", + duplicate: "Дупликат Pagetual је инсталиран, проверите свој менаџер скрипти!", + forceStateIframe: "Угради целу страницу као iframe", + forceStateDynamic: "Учитај динамички садржај путем iframe-a", + forceStateDisable: "Онемогући окретање страница на овом сајту", + autoScrollRate: "Брзина померања (1~1000)", + disableAutoScroll: "Заустави аутоматско померање", + enableAutoScroll: "Омогући аутоматско померање", + toggleAutoScroll: "Промени аутоматско померање", + ruleRequest: "Захтев за правило", + page: "Страница ", + prevPage: "Претходна страница", + nextPage: "Следећа страница", + errorRulesMustBeArray: "Правила морају бити низ!", + errorJson: "ЈСОН грешка, проверите поново!", + editSuccess: "Успешно уређено", + errorWrongUrl: "Погрешан УРЛ, проверите поново!", + errorAlreadyExists: "Правило већ постоји!", + settingsSaved: "Подешавања су сачувана, освежите да бисте видели", + iframe: "Присилно подељено iframe-ом", + dynamic: "Динамичко учитавање", + reloadPage: "Уређивање је завршено, поново учитати сада?", + copied: "Копирано", + noValidContent: "Није детектован важећи садржај, можда је присутан Цаптцха", + outOfDate: "Скрипта је застарела, ажурирајте на најновију верзију.", + hideBarTips: "Сакријте траку за пагинацију, промените имерзивно искуство", + setConfigPage: "Постави тренутну страницу као подразумевану страницу за конфигурацију", + wedata2github: "Промените wedata адресу на адресу огледала у гитхуб спремишту", + addOtherProp: "Додај својства правила", + addNextSelector: "Додај садржај бирача као nextLink", + addPageSelector: "Додај садржај бирача као pageElement", + propName: "Унесите назив својства правила", + propValue: "Унесите вредност својства правила", + customFirst: "Игнориши кеш за локална прилагођена правила", + rulesExample: "Пример правила", + lastPage: "Достигнута је последња страница", + lastPageTips: "Прикажи савете приликом достизања последње странице" + } + }, + { + name: "Slovenčina", + match: ["sk"], + lang: { + enableDebug: "Povoliť výstup ladenia do konzoly", + updateNotification: "Oznámenie po aktualizácii pravidiel", + disable: "Dočasne zakázať", + disableSite: "Prepnúť stav zakázania", + disableSiteTips: "Na tejto stránke zakázané.", + enableSiteTips: "Na tejto stránke povolené.", + enable: "✅Povoliť automatické otáčanie stránok", + tempActive: "Dočasne aktívne", + toTop: "Späť na začiatok.", + toBottom: "Ísť na koniec.", + current: "Aktuálna stránka.", + forceIframe: "Vynútiť pripojenie k ďalšej stránke", + cancelForceIframe: "Zrušiť vynútené pripojenie", + configure: "Konfigurovať Pagetual", + firstUpdate: "Kliknite sem pre inicializáciu predvoleného zoznamu pravidiel", + update: "Aktualizovať online pravidlá", + click2update: "Kliknite pre aktualizáciu pravidiel z URL teraz", + loadNow: "Načítať ďalšie automaticky", + loadConfirm: "Koľko stránok chcete načítať? (0 znamená nekonečno)", + noNext: "Nenašiel sa žiadny ďalší odkaz, vytvorte nové pravidlo", + passSec: "Aktualizované pred #t# sekundami", + passMin: "Aktualizované pred #t# minútami", + passHour: "Aktualizované pred #t# hodinami", + passDay: "Aktualizované pred #t# dňami", + cantDel: "Nie je možné odstrániť vstavané pravidlá", + confirmDel: "Ste si istí, že chcete odstrániť toto pravidlo?", + updateSucc: "Aktualizácia úspešná", + beginUpdate: "Začína sa aktualizácia, chvíľu počkajte", + customUrls: "Importovať URL pravidla Pagetual alebo AutoPagerize, jedno URL na riadok.", + customRules: "Zadajte vlastné pravidlá. ✍️Prispieť pravidlami", + save: "Uložiť", + loadingText: "Načítava sa...", + opacity: "Nepriehľadnosť", + opacityPlaceholder: "0: skryť medzerník", + hideBar: "Skryť medzerník stránkovania", + hideBarButNoStop: "Skryť, ale nezastaviť", + dbClick2Stop: "Dvojitým kliknutím na prázdne miesto pozastavíte", + sortTitle: "Triedenie sa prejaví po ďalšej aktualizácii pravidiel", + autoRun: "Automatické povolenie (režim čiernej listiny)", + autoLoadNum: "Množstvo pre prednačítané stránky", + turnRate: "Otočte na ďalšiu stránku, keď je menej ako 【X】 násobok výšky stránky od pätičky", + inputPageNum: "Zadajte číslo stránky na preskočenie", + enableHistory: "Zapísať históriu prehliadania po otočení stránky", + enableHistoryAfterInsert: "Zapísať históriu prehliadania ihneď po spojení, inak zapísať po prehliadaní", + contentVisibility: "Automaticky prepínať viditeľnosť obsahu na zlepšenie výkonu vykresľovania", + initRun: "Otočiť stránky ihneď po otvorení", + preload: "Prednačítať ďalšiu stránku na zrýchlenie", + click2ImportRule: "Kliknite pre import odkazu na základné pravidlá a potom počkajte, kým sa aktualizácia nedokončí: ", + forceAllBody: "Pripojiť celé telo stránky?", + openInNewTab: "Otvoriť URL adries prídavkov v novej karte", + importSucc: "Import dokončený", + import: "Importovať", + editCurrent: "Upraviť pravidlo pre aktuálnu webovú stránku", + editBlacklist: "Upraviť čiernu listinu URL, jeden záznam na riadok, podporuje zástupné znaky [?,*].", + upBtnImg: "Ikona späť na začiatok", + downBtnImg: "Ikona ísť na pätičku", + sideControllerIcon: "Ikona bočného panela", + loadingTextTitle: "Načítavanie", + dbClick2StopCtrl: "Kláves Ctrl", + dbClick2StopAlt: "Kláves Alt", + dbClick2StopShift: "Kláves Shift", + dbClick2StopMeta: "Kláves Meta", + dbClick2StopKey: "Klávesová skratka", + pageElementCss: "Vlastný štýl pre hlavné prvky stránky", + customCss: "Vlastné kompletné CSS", + firstAlert: "Neimportovali ste základné pravidlo, vyberte prosím vhodné pravidlo na import", + picker: "Výber prvkov Pagetual", + closePicker: "Zavrieť výber Pagetual", + pickerPlaceholder: "Výber prvkov, (Iba pre pokročilých používateľov, inak nechajte prázdne)", + pickerCheck: "Skontrolovať výber a kopírovať", + switchSelector: "Kliknutím prepnete prvok", + gotoEdit: "Prejsť na úpravu pravidla s aktuálnym výberom", + manualMode: "Zakázať spájanie, manuálne prejsť na ďalšiu stránku pomocou klávesu so šípkou doprava (alebo odoslať udalosť 'pagetual.next')", + clickMode: "Zakázať spájanie, automaticky kliknúť na ďalšiu stránku pri posunutí na koniec stránky", + pageBarMenu: "Kliknutím na stred lišty stránky otvoríte menu výberu", + nextSwitch: "Prepnúť ďalší odkaz", + arrowToScroll: "Stlačením ľavej šípky sa posuniete späť a pravou šípkou prejdete na stránku", + sideController: "Zobraziť ovládací panel stránkovania v bočnom paneli", + sideControllerScroll: "Prepnúť posúvanie", + sideControllerAlways: "Vždy zobraziť", + hideLoadingIcon: "Skryť animáciu načítavania", + hideBarArrow: "Skryť šípku pre lištu stránky", + duplicate: "Duplicitný Pagetual bol nainštalovaný, skontrolujte svoj správcu skriptov!", + forceStateIframe: "Vložiť celú stránku ako iframe", + forceStateDynamic: "Načítať dynamický obsah cez iframe", + forceStateDisable: "Zakázať otáčanie stránok na tejto stránke", + autoScrollRate: "Rýchlosť posúvania (1~1000)", + disableAutoScroll: "Zastaviť automatické posúvanie", + enableAutoScroll: "Povoliť automatické posúvanie", + toggleAutoScroll: "Prepnúť automatické posúvanie", + ruleRequest: "Žiadosť o pravidlo", + page: "Stránka ", + prevPage: "Predchádzajúca stránka", + nextPage: "Ďalšia stránka", + errorRulesMustBeArray: "Pravidlá musia byť pole!", + errorJson: "Chyba JSON, skontrolujte znova!", + editSuccess: "Úspešne upravené", + errorWrongUrl: "Nesprávne URL, skontrolujte znova!", + errorAlreadyExists: "Pravidlo už existuje!", + settingsSaved: "Nastavenia sú uložené, obnovte pre zobrazenie", + iframe: "Vynútené rozdelenie pomocou iframe", + dynamic: "Dynamické načítavanie", + reloadPage: "Úprava dokončená, načítať znova?", + copied: "Skopírované", + noValidContent: "Nebol zistený žiadny platný obsah, môže byť prítomná Captcha", + outOfDate: "Skript je zastaraný, aktualizujte prosím na najnovšiu verziu.", + hideBarTips: "Skryť lištu stránkovania, prepnúť pohlcujúci zážitok", + setConfigPage: "Nastaviť aktuálnu stránku ako predvolenú konfiguračnú stránku", + wedata2github: "Zmeniť adresu wedata na zrkadlovú adresu v repozitári github", + addOtherProp: "Pridať vlastnosti pravidla", + addNextSelector: "Pridať obsah výberu ako nextLink", + addPageSelector: "Pridať obsah výberu ako pageElement", + propName: "Zadajte názov vlastnosti pravidla", + propValue: "Zadajte hodnotu vlastnosti pravidla", + customFirst: "Ignorovať vyrovnávaciu pamäť pre lokálne vlastné pravidlá", + rulesExample: "Príklad pravidiel", + lastPage: "Dosiahli ste poslednú stránku", + lastPageTips: "Zobraziť tipy pri dosiahnutí poslednej stránky" + } + }, + { + name: "Magyar", + match: ["hu"], + lang: { + enableDebug: "Hibakeresési kimenet engedélyezése a konzolon", + updateNotification: "Értesítés a szabályok frissítése után", + disable: "Ideiglenes letiltás", + disableSite: "Letiltott állapot váltása", + disableSiteTips: "Letiltva ezen az oldalon.", + enableSiteTips: "Engedélyezve ezen az oldalon.", + enable: "✅Automatikus lapozás engedélyezése", + tempActive: "Ideiglenesen aktív", + toTop: "Vissza a tetejére.", + toBottom: "Ugrás az aljára.", + current: "Jelenlegi oldal.", + forceIframe: "Következő oldal csatlakozásának kényszerítése", + cancelForceIframe: "Kényszerített csatlakozás megszakítása", + configure: "Pagetual konfigurálása", + firstUpdate: "Kattintson ide az alapértelmezett szabálylista inicializálásához", + update: "Online szabályok frissítése", + click2update: "Kattintson a szabályok URL-ről történő frissítéséhez", + loadNow: "Következő automatikus betöltése", + loadConfirm: "Hány oldalt szeretne betölteni? (0 a végtelent jelenti)", + noNext: "Nincs következő link, hozzon létre új szabályt", + passSec: "#t# másodperce frissítve", + passMin: "#t# perce frissítve", + passHour: "#t# órája frissítve", + passDay: "#t# napja frissítve", + cantDel: "Beépített szabályok nem törölhetők", + confirmDel: "Biztosan törli ezt a szabályt?", + updateSucc: "Sikeres frissítés", + beginUpdate: "Frissítés megkezdése, kérem várjon egy pillanatot", + customUrls: "Pagetual vagy AutoPagerize szabály URL importálása, soronként egy URL.", + customRules: "Adjon meg egyéni szabályokat. ✍️Szabályok beküldése", + save: "Mentés", + loadingText: "Betöltés...", + opacity: "Átlátszóság", + opacityPlaceholder: "0: elválasztó elrejtése", + hideBar: "Lapozó elválasztó elrejtése", + hideBarButNoStop: "Elrejtés, de nem leállítás", + dbClick2Stop: "Dupla kattintás az üres területre a szüneteltetéshez", + sortTitle: "A rendezés a következő szabályfrissítés után lép érvénybe", + autoRun: "Automatikus engedélyezés (feketelista mód)", + autoLoadNum: "Előtöltendő oldalak száma", + turnRate: "Lapozzon a következő oldalra, ha a lábléctől mért távolság kevesebb, mint az oldal magasságának 【X】-szerese", + inputPageNum: "Adja meg az ugrani kívánt oldalszámot", + enableHistory: "Böngészési előzmények írása lapozás után", + enableHistoryAfterInsert: "Böngészési előzmények írása azonnal az illesztés után, egyébként a böngészés után", + contentVisibility: "A content-visibility automatikus váltása a renderelési teljesítmény javítása érdekében", + initRun: "Oldalak lapozása azonnal a megnyitás után", + preload: "Következő oldal előtöltése a gyorsítás érdekében", + click2ImportRule: "Kattintson az alapszabályok linkjének importálásához, majd várja meg a frissítés befejezését: ", + forceAllBody: "Csatlakoztatja az oldal teljes törzsét?", + openInNewTab: "A kiegészítések URL-jeinek megnyitása új lapon", + importSucc: "Importálás befejezve", + import: "Importálás", + editCurrent: "Szabály szerkesztése az aktuális webhelyhez", + editBlacklist: "URL feketelista szerkesztése, soronként egy bejegyzés, támogatja a [?,*] helyettesítő karaktereket.", + upBtnImg: "Vissza a tetejére ikon", + downBtnImg: "Ugrás a lábléchez ikon", + sideControllerIcon: "Oldalsáv ikonja", + loadingTextTitle: "Betöltés", + dbClick2StopCtrl: "Ctrl billentyű", + dbClick2StopAlt: "Alt billentyű", + dbClick2StopShift: "Shift billentyű", + dbClick2StopMeta: "Meta billentyű", + dbClick2StopKey: "Gyorsbillentyű", + pageElementCss: "Egyéni stílus a fő oldalelemekhez", + customCss: "Egyéni teljes CSS", + firstAlert: "Nem importálta az alapszabályt, kérjük, válassza ki a megfelelő szabályt az importáláshoz", + picker: "Pagetual elemkiválasztó", + closePicker: "Pagetual kiválasztó bezárása", + pickerPlaceholder: "Elemválasztó (Csak haladó felhasználóknak, egyébként hagyja üresen)", + pickerCheck: "Választó ellenőrzése és másolása", + switchSelector: "Kattintson az elem váltásához", + gotoEdit: "Ugrás a szabály szerkesztéséhez az aktuális választóval", + manualMode: "Illesztés letiltása, manuális lapozás a jobb nyílbillentyűvel (vagy a 'pagetual.next' esemény küldésével)", + clickMode: "Illesztés letiltása, automatikus kattintás a következő oldalra az oldal végére görgetve", + pageBarMenu: "Kattintson az oldalsáv közepére a kiválasztó menü megnyitásához", + nextSwitch: "Következő link váltása", + arrowToScroll: "Nyomja meg a bal nyilat a visszagörgetéshez és a jobb nyilat az oldallapozáshoz", + sideController: "A lapozásvezérlő sáv megjelenítése az oldalsávon", + sideControllerScroll: "Görgetés váltása", + sideControllerAlways: "Mindig mutassa", + hideLoadingIcon: "Betöltési animáció elrejtése", + hideBarArrow: "Oldalsáv nyilának elrejtése", + duplicate: "Duplikált Pagetual telepítve van, ellenőrizze a szkriptkezelőjét!", + forceStateIframe: "Teljes oldal beágyazása iframe-ként", + forceStateDynamic: "Dinamikus tartalom betöltése iframe-en keresztül", + forceStateDisable: "Lapozás letiltása ezen az oldalon", + autoScrollRate: "Görgetési sebesség (1-1000)", + disableAutoScroll: "Automatikus görgetés leállítása", + enableAutoScroll: "Automatikus görgetés engedélyezése", + toggleAutoScroll: "Automatikus görgetés váltása", + ruleRequest: "Szabálykérés", + page: "Oldal ", + prevPage: "Előző oldal", + nextPage: "Következő oldal", + errorRulesMustBeArray: "A szabályoknak tömbnek kell lenniük!", + errorJson: "JSON hiba, ellenőrizze újra!", + editSuccess: "Sikeres szerkesztés", + errorWrongUrl: "Hibás URL, ellenőrizze újra!", + errorAlreadyExists: "Már létezik egy szabály!", + settingsSaved: "A beállítások mentve, frissítsen a megtekintéshez", + iframe: "Iframe által kényszerített felosztás", + dynamic: "Dinamikus betöltés", + reloadPage: "Szerkesztés befejezve, újratölti most?", + copied: "Másolva", + noValidContent: "Nincs érvényes tartalom, lehet, hogy Captcha van jelen", + outOfDate: "A szkript elavult, kérjük, frissítsen a legújabb verzióra.", + hideBarTips: "A lapozósáv elrejtése, magával ragadó élmény váltása", + setConfigPage: "Az aktuális oldal beállítása alapértelmezett konfigurációs oldalként", + wedata2github: "A wedata cím megváltoztatása a tükör címre a github tárolóban", + addOtherProp: "Szabálytulajdonságok hozzáadása", + addNextSelector: "Választó tartalmának hozzáadása nextLink-ként", + addPageSelector: "Választó tartalmának hozzáadása pageElement-ként", + propName: "Adja meg a szabálytulajdonság nevét", + propValue: "Adja meg a szabálytulajdonság értékét", + customFirst: "Gyorsítótár figyelmen kívül hagyása a helyi egyéni szabályoknál", + rulesExample: "Szabályok példa", + lastPage: "Elérte az utolsó oldalt", + lastPageTips: "Tippek megjelenítése az utolsó oldal elérésekor" + } + }, + { + name: "Română", + match: ["ro"], + lang: { + enableDebug: "Activați ieșirea de depanare în consolă", + updateNotification: "Notificare după actualizarea regulilor", + disable: "Dezactivați temporar", + disableSite: "Comutați starea de dezactivare", + disableSiteTips: "Dezactivat pe acest site.", + enableSiteTips: "Activat pe acest site.", + enable: "✅Activați întoarcerea automată a paginii", + tempActive: "Activ temporar", + toTop: "Înapoi sus.", + toBottom: "Mergi jos.", + current: "Pagina curentă.", + forceIframe: "Forțați alăturarea la pagina următoare", + cancelForceIframe: "Anulați alăturarea forțată", + configure: "Configurați Pagetual", + firstUpdate: "Faceți clic aici pentru a inițializa lista de reguli implicită", + update: "Actualizați regulile online", + click2update: "Faceți clic pentru a actualiza regulile de la URL acum", + loadNow: "Încărcați următoarea automat", + loadConfirm: "Câte pagini doriți să încărcați? (0 înseamnă infinit)", + noNext: "Nu s-a găsit niciun link următor, vă rugăm să creați o nouă regulă", + passSec: "Actualizat acum #t# secunde", + passMin: "Actualizat acum #t# minute", + passHour: "Actualizat acum #t# ore", + passDay: "Actualizat acum #t# zile", + cantDel: "Nu se pot șterge regulile încorporate", + confirmDel: "Sunteți sigur că doriți să ștergeți această regulă?", + updateSucc: "Actualizare reușită", + beginUpdate: "Începe actualizarea, vă rugăm așteptați un moment", + customUrls: "Importați URL-ul regulii Pagetual sau AutoPagerize, un URL pe linie.", + customRules: "Introduceți reguli personalizate. ✍️Contribuiți cu reguli", + save: "Salvați", + loadingText: "Se încarcă...", + opacity: "Opacitate", + opacityPlaceholder: "0: ascundeți distanțierul", + hideBar: "Ascundeți distanțierul de paginare", + hideBarButNoStop: "Ascundeți, dar nu opriți", + dbClick2Stop: "Faceți dublu clic pe spațiul gol pentru a întrerupe", + sortTitle: "Sortarea intră în vigoare după următoarea actualizare a regulilor", + autoRun: "Activare automată (mod listă neagră)", + autoLoadNum: "Cantitatea de pagini de preîncărcat", + turnRate: "Întoarceți la pagina următoare când este mai puțin de 【X】 ori înălțimea paginii de la subsol", + inputPageNum: "Introduceți numărul paginii pentru a sări", + enableHistory: "Scrieți istoricul de navigare după întoarcerea paginii", + enableHistoryAfterInsert: "Scrieți istoricul de navigare imediat după îmbinare, altfel scrieți după navigare", + contentVisibility: "Comutați automat vizibilitatea conținutului pentru a îmbunătăți performanța de randare", + initRun: "Întoarceți paginile imediat după deschidere", + preload: "Preîncărcați pagina următoare pentru a accelera", + click2ImportRule: "Faceți clic pentru a importa linkul regulilor de bază, apoi așteptați până la finalizarea actualizării: ", + forceAllBody: "Alăturați corpul complet al paginii?", + openInNewTab: "Deschideți URL-urile adăugirilor într-o filă nouă", + importSucc: "Import finalizat", + import: "Importați", + editCurrent: "Editați regula pentru site-ul web curent", + editBlacklist: "Editați lista neagră de URL-uri, o intrare pe linie, suportă caracterele [?,*].", + upBtnImg: "Pictogramă înapoi sus", + downBtnImg: "Pictogramă mergi la subsol", + sideControllerIcon: "Pictogramă bară laterală", + loadingTextTitle: "Se încarcă", + dbClick2StopCtrl: "Tasta Ctrl", + dbClick2StopAlt: "Tasta Alt", + dbClick2StopShift: "Tasta Shift", + dbClick2StopMeta: "Tasta Meta", + dbClick2StopKey: "Tastă de comandă rapidă", + pageElementCss: "Stil personalizat pentru elementele principale ale paginii", + customCss: "CSS complet personalizat", + firstAlert: "Nu ați importat regula de bază, vă rugăm să selectați regula corespunzătoare pentru a o importa", + picker: "Selector de elemente Pagetual", + closePicker: "Închideți selectorul Pagetual", + pickerPlaceholder: "Selector de elemente (Doar utilizatori avansați, altfel lăsați necompletat)", + pickerCheck: "Verificați selectorul și copiați", + switchSelector: "Faceți clic pentru a comuta elementul", + gotoEdit: "Mergeți la editarea regulii cu selectorul curent", + manualMode: "Dezactivați îmbinarea, avansați manual la pagina următoare folosind tasta săgeată dreapta (sau trimiteți evenimentul 'pagetual.next')", + clickMode: "Dezactivați îmbinarea, faceți clic automat pe pagina următoare la derularea până la sfârșitul paginii", + pageBarMenu: "Faceți clic în centrul barei de pagină pentru a deschide meniul selectorului", + nextSwitch: "Comutați linkul următor", + arrowToScroll: "Apăsați săgeata stânga pentru a derula înapoi și săgeata dreapta pentru a avansa pagina", + sideController: "Afișați bara de control a paginării în bara laterală", + sideControllerScroll: "Comutare derulare", + sideControllerAlways: "Afișați întotdeauna", + hideLoadingIcon: "Ascundeți animația de încărcare", + hideBarArrow: "Ascundeți săgeata pentru bara de pagină", + duplicate: "Pagetual duplicat a fost instalat, verificați managerul de scripturi!", + forceStateIframe: "Încorporați pagina completă ca iframe", + forceStateDynamic: "Încărcați conținut dinamic prin iframe", + forceStateDisable: "Dezactivați întoarcerea paginii pe acest site", + autoScrollRate: "Viteza de derulare (1~1000)", + disableAutoScroll: "Opriți derularea automată", + enableAutoScroll: "Activați derularea automată", + toggleAutoScroll: "Comutați derularea automată", + ruleRequest: "Cerere de regulă", + page: "Pagina ", + prevPage: "Pagina anterioară", + nextPage: "Pagina următoare", + errorRulesMustBeArray: "Regulile trebuie să fie un tablou!", + errorJson: "Eroare JSON, verificați din nou!", + editSuccess: "Editat cu succes", + errorWrongUrl: "URL greșit, verificați din nou!", + errorAlreadyExists: "O regulă există deja!", + settingsSaved: "Setările sunt salvate, reîmprospătați pentru a vizualiza", + iframe: "Divizare forțată de iframe", + dynamic: "Încărcare dinamică", + reloadPage: "Editare finalizată, reîncărcați acum?", + copied: "Copiat", + noValidContent: "Nu s-a detectat niciun conținut valid, este posibil să existe un Captcha", + outOfDate: "Scriptul este învechit, vă rugăm să actualizați la cea mai recentă versiune.", + hideBarTips: "Ascundeți bara de paginare, comutați experiența imersivă", + setConfigPage: "Setați pagina curentă ca pagină de configurare implicită", + wedata2github: "Schimbați adresa wedata cu adresa oglindă din depozitul github", + addOtherProp: "Adăugați proprietăți de regulă", + addNextSelector: "Adăugați conținutul selectorului ca nextLink", + addPageSelector: "Adăugați conținutul selectorului ca pageElement", + propName: "Introduceți numele proprietății regulii", + propValue: "Introduceți valoarea proprietății regulii", + customFirst: "Ignorați memoria cache pentru regulile personalizate locale", + rulesExample: "Exemplu de reguli", + lastPage: "Ați ajuns la ultima pagină", + lastPageTips: "Afișați sfaturi la atingerea ultimei pagini" + } + }, + { + name: "Suomi", + match: ["fi"], + lang: { + enableDebug: "Ota virheenkorjaustuloste käyttöön konsolissa", + updateNotification: "Ilmoitus sääntöjen päivityksen jälkeen", + disable: "Poista väliaikaisesti käytöstä", + disableSite: "Vaihda käytöstä poistettu tila", + disableSiteTips: "Poistettu käytöstä tällä sivustolla.", + enableSiteTips: "Otettu käyttöön tällä sivustolla.", + enable: "✅Ota automaattinen sivunvaihto käyttöön", + tempActive: "Väliaikaisesti aktiivinen", + toTop: "Takaisin ylös.", + toBottom: "Mene alas.", + current: "Nykyinen sivu.", + forceIframe: "Pakota liittymään seuraavalle sivulle", + cancelForceIframe: "Peruuta pakotettu liittyminen", + configure: "Määritä Pagetual", + firstUpdate: "Napsauta tästä alustaaksesi oletussääntöluettelon", + update: "Päivitä verkkosäännöt", + click2update: "Napsauta päivittääksesi säännöt URL-osoitteesta nyt", + loadNow: "Lataa seuraava automaattisesti", + loadConfirm: "Kuinka monta sivua haluat ladata? (0 tarkoittaa ääretöntä)", + noNext: "Seuraavaa linkkiä ei löytynyt, luo uusi sääntö", + passSec: "Päivitetty #t# sekuntia sitten", + passMin: "Päivitetty #t# minuuttia sitten", + passHour: "Päivitetty #t# tuntia sitten", + passDay: "Päivitetty #t# päivää sitten", + cantDel: "Sisäänrakennettuja sääntöjä ei voi poistaa", + confirmDel: "Haluatko varmasti poistaa tämän säännön?", + updateSucc: "Päivitys onnistui", + beginUpdate: "Aloitetaan päivitys, odota hetki", + customUrls: "Tuo Pagetual- tai AutoPagerize-säännön URL-osoite, yksi URL-osoite riviä kohti.", + customRules: "Syötä mukautettuja sääntöjä. ✍️Osallistu sääntöihin", + save: "Tallenna", + loadingText: "Ladataan...", + opacity: "Läpinäkyvyys", + opacityPlaceholder: "0: piilota välilevy", + hideBar: "Piilota sivutuksen välilevy", + hideBarButNoStop: "Piilota, mutta älä pysäytä", + dbClick2Stop: "Kaksoisnapsauta tyhjää tilaa keskeyttääksesi", + sortTitle: "Lajittelu tulee voimaan seuraavan sääntöpäivityksen jälkeen", + autoRun: "Ota automaattisesti käyttöön (mustan listan tila)", + autoLoadNum: "Esiladattavien sivujen määrä", + turnRate: "Vaihda seuraavalle sivulle, kun se on alle 【X】 kertaa sivun korkeuden päässä alatunnisteesta", + inputPageNum: "Syötä sivunumero siirtyäksesi", + enableHistory: "Kirjoita selaushistoria sivunvaihdon jälkeen", + enableHistoryAfterInsert: "Kirjoita selaushistoria heti liittämisen jälkeen, muuten kirjoita selauksen jälkeen", + contentVisibility: "Vaihda automaattisesti sisällön näkyvyyttä parantaaksesi renderöintisuorituskykyä", + initRun: "Vaihda sivuja heti avaamisen jälkeen", + preload: "Esilataa seuraava sivu nopeuttaaksesi", + click2ImportRule: "Napsauta tuodaksesi perussääntöjen linkin ja odota sitten, kunnes päivitys on valmis: ", + forceAllBody: "Liitetäänkö sivun koko runko?", + openInNewTab: "Avaa lisäysten URL-osoitteet uudessa välilehdessä", + importSucc: "Tuonti onnistui", + import: "Tuo", + editCurrent: "Muokkaa nykyisen verkkosivuston sääntöä", + editBlacklist: "Muokkaa URL-mustaa listaa, yksi merkintä riviä kohti, tukee [?,*] -jokerimerkkejä.", + upBtnImg: "Takaisin ylös -kuvake", + downBtnImg: "Mene alatunnisteeseen -kuvake", + sideControllerIcon: "Sivupalkin kuvake", + loadingTextTitle: "Ladataan", + dbClick2StopCtrl: "Ctrl-näppäin", + dbClick2StopAlt: "Alt-näppäin", + dbClick2StopShift: "Shift-näppäin", + dbClick2StopMeta: "Meta-näppäin", + dbClick2StopKey: "Pikakuvake", + pageElementCss: "Mukautettu tyyli pääsivun elementeille", + customCss: "Mukautettu täydellinen CSS", + firstAlert: "Et ole tuonut perussääntöä, valitse sopiva sääntö tuotavaksi", + picker: "Pagetual-elementin valitsin", + closePicker: "Sulje Pagetual-valitsin", + pickerPlaceholder: "Elementin valitsin (Vain edistyneille käyttäjille, muuten jätä tyhjäksi)", + pickerCheck: "Tarkista valitsin ja kopioi", + switchSelector: "Napsauta vaihtaaksesi elementtiä", + gotoEdit: "Siirry muokkaamaan sääntöä nykyisellä valitsimella", + manualMode: "Poista liittäminen käytöstä, siirry manuaalisesti seuraavalle sivulle oikealla nuolinäppäimellä (tai lähetä tapahtuma 'pagetual.next')", + clickMode: "Poista liittäminen käytöstä, napsauta automaattisesti seuraavaa sivua, kun vierität sivun loppuun", + pageBarMenu: "Napsauta sivupalkin keskustaa avataksesi valitsinvalikon", + nextSwitch: "Vaihda seuraava linkki", + arrowToScroll: "Paina vasenta nuolta selataksesi taaksepäin ja oikeaa nuolta siirtyäksesi sivulle", + sideController: "Näytä sivutuksen ohjauspalkki sivupalkissa", + sideControllerScroll: "Vierityksen vaihto", + sideControllerAlways: "Näytä aina", + hideLoadingIcon: "Piilota latausanimaatio", + hideBarArrow: "Piilota nuoli sivupalkille", + duplicate: "Pagetualin kaksoiskappale on asennettu, tarkista komentosarjojen hallinta!", + forceStateIframe: "Upota koko sivu iframeksi", + forceStateDynamic: "Lataa dynaamista sisältöä iframen kautta", + forceStateDisable: "Poista sivunvaihto käytöstä tällä sivustolla", + autoScrollRate: "Vieritysnopeus (1-1000)", + disableAutoScroll: "Pysäytä automaattinen vieritys", + enableAutoScroll: "Ota automaattinen vieritys käyttöön", + toggleAutoScroll: "Vaihda automaattista vieritystä", + ruleRequest: "Sääntöpyyntö", + page: "Sivu ", + prevPage: "Edellinen sivu", + nextPage: "Seuraava sivu", + errorRulesMustBeArray: "Sääntöjen on oltava taulukko!", + errorJson: "JSON-virhe, tarkista uudelleen!", + editSuccess: "Muokkaus onnistui", + errorWrongUrl: "Väärä URL-osoite, tarkista uudelleen!", + errorAlreadyExists: "Sääntö on jo olemassa!", + settingsSaved: "Asetukset on tallennettu, päivitä nähdäksesi", + iframe: "Pakotettu jako iframella", + dynamic: "Dynaaminen lataus", + reloadPage: "Muokkaus valmis, ladataanko uudelleen nyt?", + copied: "Kopioitu", + noValidContent: "Kelvollista sisältöä ei havaittu, Captcha saattaa olla läsnä", + outOfDate: "Komentosarja on vanhentunut, päivitä uusimpaan versioon.", + hideBarTips: "Piilota sivutuspalkki, vaihda immersiiviseen kokemukseen", + setConfigPage: "Aseta nykyinen sivu oletusmäärityssivuksi", + wedata2github: "Vaihda wedata-osoite github-arkiston peiliosoitteeseen", + addOtherProp: "Lisää säännön ominaisuuksia", + addNextSelector: "Lisää valitsimen sisältö nimellä nextLink", + addPageSelector: "Lisää valitsimen sisältö nimellä pageElement", + propName: "Syötä säännön ominaisuuden nimi", + propValue: "Syötä säännön ominaisuuden arvo", + customFirst: "Ohita välimuisti paikallisille mukautetuille säännöille", + rulesExample: "Sääntöesimerkki", + lastPage: "Viimeinen sivu saavutettu", + lastPageTips: "Näytä vinkkejä, kun saavutaan viimeiselle sivulle" + } + }, + { + name: "Ελληνικά", + match: ["el"], + lang: { + enableDebug: "Ενεργοποίηση εξόδου εντοπισμού σφαλμάτων στην κονσόλα", + updateNotification: "Ειδοποίηση μετά την ενημέρωση των κανόνων", + disable: "Προσωρινή απενεργοποίηση", + disableSite: "Εναλλαγή κατάστασης απενεργοποίησης", + disableSiteTips: "Απενεργοποιημένο σε αυτόν τον ιστότοπο.", + enableSiteTips: "Ενεργοποιημένο σε αυτόν τον ιστότοπο.", + enable: "✅Ενεργοποίηση αυτόματης αλλαγής σελίδας", + tempActive: "Προσωρινά ενεργό", + toTop: "Επιστροφή στην κορυφή.", + toBottom: "Μετάβαση στο κάτω μέρος.", + current: "Τρέχουσα σελίδα.", + forceIframe: "Εξαναγκασμός συμμετοχής στην επόμενη σελίδα", + cancelForceIframe: "Ακύρωση εξαναγκασμένης συμμετοχής", + configure: "Διαμόρφωση Pagetual", + firstUpdate: "Κάντε κλικ εδώ για να αρχικοποιήσετε την προεπιλεγμένη λίστα κανόνων", + update: "Ενημέρωση διαδικτυακών κανόνων", + click2update: "Κάντε κλικ για να ενημερώσετε τους κανόνες από το URL τώρα", + loadNow: "Φόρτωση του επόμενου αυτόματα", + loadConfirm: "Πόσες σελίδες θέλετε να φορτώσετε; (0 σημαίνει άπειρο)", + noNext: "Δεν βρέθηκε επόμενος σύνδεσμος, δημιουργήστε έναν νέο κανόνα", + passSec: "Ενημερώθηκε πριν από #t# δευτερόλεπτα", + passMin: "Ενημερώθηκε πριν από #t# λεπτά", + passHour: "Ενημερώθηκε πριν από #t# ώρες", + passDay: "Ενημερώθηκε πριν από #t# ημέρες", + cantDel: "Δεν είναι δυνατή η διαγραφή ενσωματωμένων κανόνων", + confirmDel: "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον κανόνα;", + updateSucc: "Η ενημέρωση ολοκληρώθηκε με επιτυχία", + beginUpdate: "Έναρξη ενημέρωσης, περιμένετε μια στιγμή", + customUrls: "Εισαγωγή URL κανόνα Pagetual ή AutoPagerize, ένα URL ανά γραμμή.", + customRules: "Εισαγωγή προσαρμοσμένων κανόνων. ✍️Συνεισφέρετε κανόνες", + save: "Αποθήκευση", + loadingText: "Φόρτωση...", + opacity: "Αδιαφάνεια", + opacityPlaceholder: "0: απόκρυψη διαχωριστικού", + hideBar: "Απόκρυψη του διαχωριστικού σελιδοποίησης", + hideBarButNoStop: "Απόκρυψη αλλά όχι διακοπή", + dbClick2Stop: "Κάντε διπλό κλικ στον κενό χώρο για παύση", + sortTitle: "Η ταξινόμηση τίθεται σε ισχύ μετά την επόμενη ενημέρωση κανόνων", + autoRun: "Αυτόματη ενεργοποίηση (λειτουργία μαύρης λίστας)", + autoLoadNum: "Ποσότητα για προφόρτωση σελίδων", + turnRate: "Μετάβαση στην επόμενη σελίδα όταν απέχει λιγότερο από 【X】 φορές το ύψος της σελίδας από το υποσέλιδο", + inputPageNum: "Εισαγάγετε τον αριθμό σελίδας για μετάβαση", + enableHistory: "Εγγραφή ιστορικού περιήγησης μετά την αλλαγή σελίδας", + enableHistoryAfterInsert: "Εγγραφή ιστορικού περιήγησης αμέσως μετά τη συγκόλληση, διαφορετικά εγγραφή μετά την περιήγηση", + contentVisibility: "Αυτόματη εναλλαγή της ορατότητας περιεχομένου για βελτίωση της απόδοσης απόδοσης", + initRun: "Αλλαγή σελίδων αμέσως μετά το άνοιγμα", + preload: "Προφόρτωση της επόμενης σελίδας για επιτάχυνση", + click2ImportRule: "Κάντε κλικ για να εισαγάγετε τον σύνδεσμο των βασικών κανόνων και, στη συνέχεια, περιμένετε μέχρι να ολοκληρωθεί η ενημέρωση: ", + forceAllBody: "Συμμετοχή ολόκληρου του σώματος της σελίδας;", + openInNewTab: "Άνοιγμα των URL των προσθηκών σε νέα καρτέλα", + importSucc: "Η εισαγωγή ολοκληρώθηκε", + import: "Εισαγωγή", + editCurrent: "Επεξεργασία κανόνα για τον τρέχοντα ιστότοπο", + editBlacklist: "Επεξεργαστείτε τη μαύρη λίστα URL, μία καταχώριση ανά γραμμή, υποστηρίζει μπαλαντέρ [?,*].", + upBtnImg: "Εικονίδιο επιστροφής στην κορυφή", + downBtnImg: "Εικονίδιο μετάβασης στο υποσέλιδο", + sideControllerIcon: "Εικονίδιο πλευρικής γραμμής", + loadingTextTitle: "Φόρτωση", + dbClick2StopCtrl: "Πλήκτρο Ctrl", + dbClick2StopAlt: "Πλήκτρο Alt", + dbClick2StopShift: "Πλήκτρο Shift", + dbClick2StopMeta: "Πλήκτρο Meta", + dbClick2StopKey: "Πλήκτρο συντόμευσης", + pageElementCss: "Προσαρμοσμένο στυλ για τα κύρια στοιχεία της σελίδας", + customCss: "Προσαρμοσμένο πλήρες CSS", + firstAlert: "Δεν έχετε εισαγάγει τον βασικό κανόνα, επιλέξτε τον κατάλληλο κανόνα για εισαγωγή", + picker: "Επιλογέας στοιχείων Pagetual", + closePicker: "Κλείσιμο του επιλογέα Pagetual", + pickerPlaceholder: "Επιλογέας στοιχείων (Μόνο για προχωρημένους χρήστες, αλλιώς αφήστε κενό)", + pickerCheck: "Έλεγχος επιλογέα και αντιγραφή", + switchSelector: "Κάντε κλικ για εναλλαγή στοιχείου", + gotoEdit: "Μετάβαση στην επεξεργασία κανόνα με τον τρέχοντα επιλογέα", + manualMode: "Απενεργοποίηση συγκόλλησης, μη αυτόματη μετάβαση στην επόμενη σελίδα χρησιμοποιώντας το δεξί βέλος (ή αποστολή συμβάντος 'pagetual.next')", + clickMode: "Απενεργοποίηση συγκόλλησης, αυτόματο κλικ στην επόμενη σελίδα κατά την κύλιση στο τέλος της σελίδας", + pageBarMenu: "Κάντε κλικ στο κέντρο της γραμμής σελίδας για να ανοίξετε το μενού επιλογέα", + nextSwitch: "Εναλλαγή επόμενου συνδέσμου", + arrowToScroll: "Πατήστε το αριστερό βέλος για κύλιση προς τα πίσω και το δεξί βέλος για μετάβαση στη σελίδα", + sideController: "Εμφάνιση της γραμμής ελέγχου σελιδοποίησης στην πλευρική γραμμή", + sideControllerScroll: "Εναλλαγή κύλισης", + sideControllerAlways: "Πάντα εμφάνιση", + hideLoadingIcon: "Απόκρυψη κινούμενης εικόνας φόρτωσης", + hideBarArrow: "Απόκρυψη βέλους για τη γραμμή σελίδας", + duplicate: "Έχει εγκατασταθεί διπλότυπο Pagetual, ελέγξτε τον διαχειριστή σεναρίων σας!", + forceStateIframe: "Ενσωμάτωση ολόκληρης της σελίδας ως iframe", + forceStateDynamic: "Φόρτωση δυναμικού περιεχομένου μέσω iframe", + forceStateDisable: "Απενεργοποίηση αλλαγής σελίδας σε αυτόν τον ιστότοπο", + autoScrollRate: "Ταχύτητα κύλισης (1~1000)", + disableAutoScroll: "Διακοπή αυτόματης κύλισης", + enableAutoScroll: "Ενεργοποίηση αυτόματης κύλισης", + toggleAutoScroll: "Εναλλαγή αυτόματης κύλισης", + ruleRequest: "Αίτημα κανόνα", + page: "Σελίδα ", + prevPage: "Προηγούμενη σελίδα", + nextPage: "Επόμενη σελίδα", + errorRulesMustBeArray: "Οι κανόνες πρέπει να είναι πίνακας!", + errorJson: "Σφάλμα JSON, ελέγξτε ξανά!", + editSuccess: "Η επεξεργασία ολοκληρώθηκε με επιτυχία", + errorWrongUrl: "Λάθος URL, ελέγξτε ξανά!", + errorAlreadyExists: "Ένας κανόνας υπάρχει ήδη!", + settingsSaved: "Οι ρυθμίσεις αποθηκεύτηκαν, ανανεώστε για προβολή", + iframe: "Εξαναγκασμένος διαχωρισμός από iframe", + dynamic: "Δυναμική φόρτωση", + reloadPage: "Η επεξεργασία ολοκληρώθηκε, επαναφόρτωση τώρα;", + copied: "Αντιγράφηκε", + noValidContent: "Δεν εντοπίστηκε έγκυρο περιεχόμενο, ενδέχεται να υπάρχει Captcha", + outOfDate: "Το σενάριο είναι ξεπερασμένο, ενημερώστε στην πιο πρόσφατη έκδοση.", + hideBarTips: "Απόκρυψη της γραμμής σελιδοποίησης, εναλλαγή καθηλωτικής εμπειρίας", + setConfigPage: "Ορισμός της τρέχουσας σελίδας ως προεπιλεγμένης σελίδας διαμόρφωσης", + wedata2github: "Αλλάξτε τη διεύθυνση wedata στη διεύθυνση καθρέφτη στο αποθετήριο github", + addOtherProp: "Προσθήκη ιδιοτήτων κανόνα", + addNextSelector: "Προσθήκη περιεχομένου επιλογέα ως nextLink", + addPageSelector: "Προσθήκη περιεχομένου επιλογέα ως pageElement", + propName: "Εισαγάγετε το όνομα ιδιότητας κανόνα", + propValue: "Εισαγάγετε την τιμή ιδιότητας κανόνα", + customFirst: "Παράβλεψη κρυφής μνήμης για τοπικούς προσαρμοσμένους κανόνες", + rulesExample: "Παράδειγμα κανόνων", + lastPage: "Φτάσατε στην τελευταία σελίδα", + lastPageTips: "Εμφάνιση συμβουλών κατά την άφιξη στην τελευταία σελίδα" + } + }, + { + name: "Esperanto", + match: ["eo"], + lang: { + enableDebug: "Aktivigi sencimigan eligon al la konzolo", + updateNotification: "Sciigo post ĝisdatigo de reguloj", + disable: "Provizore malŝalti", + disableSite: "Baskuligi malŝaltitan staton", + disableSiteTips: "Malŝaltita en ĉi tiu retejo.", + enableSiteTips: "Ŝaltita en ĉi tiu retejo.", + enable: "✅Aktivigi aŭtomatan paĝo-turnadon", + tempActive: "Provizore aktiva", + toTop: "Reen al supro.", + toBottom: "Iri al malsupro.", + current: "Nuna paĝo.", + forceIframe: "Devigi kunigon de la sekva paĝo", + cancelForceIframe: "Nuligi devigitan kunigon", + configure: "Agordi Pagetual", + firstUpdate: "Klaku ĉi tie por pravalorizi la defaŭltan regul-liston", + update: "Ĝisdatigi retajn regulojn", + click2update: "Klaku por ĝisdatigi regulojn el URL nun", + loadNow: "Ŝargi la sekvan aŭtomate", + loadConfirm: "Kiom da paĝoj vi volas ŝargi? (0 signifas senfine)", + noNext: "Neniu sekva ligilo trovita, bonvolu krei novan regulon", + passSec: "Ĝisdatigita antaŭ #t# sekundoj", + passMin: "Ĝisdatigita antaŭ #t# minutoj", + passHour: "Ĝisdatigita antaŭ #t# horoj", + passDay: "Ĝisdatigita antaŭ #t# tagoj", + cantDel: "Ne eblas forigi enkonstruitajn regulojn", + confirmDel: "Ĉu vi certas, ke vi volas forigi ĉi tiun regulon?", + updateSucc: "Ĝisdatigo sukcesis", + beginUpdate: "Komencante ĝisdatigon, bonvolu atendi momenton", + customUrls: "Importi regulan URL de Pagetual aŭ AutoPagerize, unu URL po linio.", + customRules: "Enigu proprajn regulojn. ✍️Kontribui regulojn", + save: "Konservi", + loadingText: "Ŝargante...", + opacity: "Opakeco", + opacityPlaceholder: "0: kaŝi apartigilon", + hideBar: "Kaŝi la paĝrangan apartigilon", + hideBarButNoStop: "Kaŝi sed ne haltigi", + dbClick2Stop: "Duoble-klaku sur la malplena spaco por paŭzi", + sortTitle: "Ordigo efektiviĝas post la sekva ĝisdatigo de reguloj", + autoRun: "Aŭtomata aktivigo (nigra listo reĝimo)", + autoLoadNum: "Kvanto por antaŭŝargitaj paĝoj", + turnRate: "Turnu la sekvan paĝon kiam ĝi estas malpli ol 【X】 fojojn la paĝa alteco de la piedlinio", + inputPageNum: "Enigu paĝan numeron por salti", + enableHistory: "Skribi foliumhistorion post paĝo-turnado", + enableHistoryAfterInsert: "Skribi foliumhistorion tuj post kunigo, alie skribi post foliumado", + contentVisibility: "Aŭtomate baskuligi enhavan videblecon por plibonigi bildigan rendimenton", + initRun: "Turni paĝojn tuj post malfermo", + preload: "Antaŭŝargi la sekvan paĝon por rapidigi", + click2ImportRule: "Klaku por importi bazan regulan ligilon, kaj poste atendu ĝis la ĝisdatigo finiĝos: ", + forceAllBody: "Kunigi la plenan korpon de la paĝo?", + openInNewTab: "Malfermi URL-ojn de aldonoj en nova langeto", + importSucc: "Importado finiĝis", + import: "Importi", + editCurrent: "Redakti regulon por la nuna retejo", + editBlacklist: "Redakti la nigran liston de URL-oj, unu enigo po linio, subtenas [?,*] ĵokerojn.", + upBtnImg: "Ikono por reen al supro", + downBtnImg: "Ikono por iri al piedlinio", + sideControllerIcon: "Ikono de flanka stango", + loadingTextTitle: "Ŝargante", + dbClick2StopCtrl: "Klavo Ctrl", + dbClick2StopAlt: "Klavo Alt", + dbClick2StopShift: "Klavo Shift", + dbClick2StopMeta: "Klavo Meta", + dbClick2StopKey: "Fulmoklavo", + pageElementCss: "Propra stilo por ĉefaj paĝaj elementoj", + customCss: "Propra kompleta CSS", + firstAlert: "Vi ne importis la bazan regulon, bonvolu elekti la taŭgan regulon por importi", + picker: "Pagetual elementa elektilo", + closePicker: "Fermi la elektilon de Pagetual", + pickerPlaceholder: "Elementa elektilo (Nur por spertaj uzantoj, alie lasu malplena)", + pickerCheck: "Kontroli elektilon kaj kopii", + switchSelector: "Klaku por ŝanĝi elementon", + gotoEdit: "Iri al redakto de regulo kun la nuna elektilo", + manualMode: "Malŝalti kunigon, permane antaŭeniri al la sekva paĝo per la dekstra sagoklavo (aŭ sendi eventon 'pagetual.next')", + clickMode: "Malŝalti kunigon, aŭtomate alklaki la sekvan paĝon rulumante ĝis la fino de la paĝo", + pageBarMenu: "Klaku la centron de la paĝa stango por malfermi la elektilan menuon", + nextSwitch: "Ŝanĝi la sekvan ligilon", + arrowToScroll: "Premu la maldekstran sagon por rulumigi reen kaj la dekstran sagon por antaŭeniri paĝon", + sideController: "Montri la paĝrangan kontrolstangon en la flanka stango", + sideControllerScroll: "Rulumiga baskulo", + sideControllerAlways: "Ĉiam montri", + hideLoadingIcon: "Kaŝi ŝargan animacion", + hideBarArrow: "Kaŝi sagon por la paĝa stango", + duplicate: "Duplikata Pagetual estis instalita, kontrolu vian skript-administrilon!", + forceStateIframe: "Enigi plenan paĝon kiel iframe", + forceStateDynamic: "Ŝargi dinamikan enhavon per iframe", + forceStateDisable: "Malŝalti paĝo-turnadon en ĉi tiu retejo", + autoScrollRate: "Rulumrapido (1~1000)", + disableAutoScroll: "Haltigi Aŭtomatan Rulumadon", + enableAutoScroll: "Aktivigi Aŭtomatan Rulumadon", + toggleAutoScroll: "Baskuligi Aŭtomatan Rulumadon", + ruleRequest: "Regula Peto", + page: "Paĝo ", + prevPage: "Antaŭa paĝo", + nextPage: "Sekva paĝo", + errorRulesMustBeArray: "Reguloj devas esti tabelo!", + errorJson: "JSON-eraro, Rekontrolu!", + editSuccess: "Sukcese redaktita", + errorWrongUrl: "Malĝusta URL, Rekontrolu!", + errorAlreadyExists: "Regulo jam ekzistas!", + settingsSaved: "La agordoj estas konservitaj, refreŝigu por vidi", + iframe: "Devigita disigo per iframe", + dynamic: "Dinamika ŝargado", + reloadPage: "Redakto finiĝis, ĉu reŝargi nun?", + copied: "Kopiita", + noValidContent: "Neniu valida enhavo detektita, Captcha eble ĉeestas", + outOfDate: "La skripto estas malmoderna, bonvolu ĝisdatigi al la plej nova versio.", + hideBarTips: "Kaŝi la paĝrangan stangon, baskuligi imersivan sperton", + setConfigPage: "Agordi la nunan paĝon kiel la defaŭltan agordan paĝon", + wedata2github: "Ŝanĝi la wedata-adreson al la spegula adreso en la github-deponejo", + addOtherProp: "Aldoni regulajn ecojn", + addNextSelector: "Aldoni elektilan enhavon kiel nextLink", + addPageSelector: "Aldoni elektilan enhavon kiel pageElement", + propName: "Enigu regulan econan nomon", + propValue: "Enigu regulan econan valoron", + customFirst: "Ignori kaŝmemoron por lokaj propraj reguloj", + rulesExample: "Regula Ekzemplo", + lastPage: "Atingis la lastan paĝon", + lastPageTips: "Montri konsilojn atinginte la lastan paĝon" + } + }, + { + name: "Български", + match: ["bg"], + lang: { + enableDebug: "Активиране на изход за отстраняване на грешки в конзолата", + updateNotification: "Известие след актуализиране на правилата", + disable: "Временно деактивиране", + disableSite: "Превключване на деактивирано състояние", + disableSiteTips: "Деактивирано на този сайт.", + enableSiteTips: "Активирано на този сайт.", + enable: "✅Активиране на автоматично прелистване на страници", + tempActive: "Временно активно", + toTop: "Обратно горе.", + toBottom: "Към дъното.", + current: "Текуща страница.", + forceIframe: "Принудително присъединяване към следващата страница", + cancelForceIframe: "Отказ от принудително присъединяване", + configure: "Конфигуриране на Pagetual", + firstUpdate: "Щракнете тук, за да инициализирате списъка с правила по подразбиране", + update: "Актуализиране на онлайн правилата", + click2update: "Щракнете, за да актуализирате правилата от URL сега", + loadNow: "Зареждане на следващото автоматично", + loadConfirm: "Колко страници искате да заредите? (0 означава безкрайност)", + noNext: "Няма намерена следваща връзка, моля, създайте ново правило", + passSec: "Актуализирано преди #t# секунди", + passMin: "Актуализирано преди #t# минути", + passHour: "Актуализирано преди #t# часа", + passDay: "Актуализирано преди #t# дни", + cantDel: "Не могат да се изтриват вградени правила", + confirmDel: "Сигурни ли сте, че искате да изтриете това правило?", + updateSucc: "Актуализацията е успешна", + beginUpdate: "Започва актуализация, моля, изчакайте малко", + customUrls: "Импортиране на URL на правило Pagetual или AutoPagerize, един URL на ред.", + customRules: "Въведете персонализирани правила. ✍️Допринесете с правила", + save: "Запазване", + loadingText: "Зареждане...", + opacity: "Непрозрачност", + opacityPlaceholder: "0: скриване на разделителя", + hideBar: "Скриване на разделителя за пагинация", + hideBarButNoStop: "Скриване, но не спиране", + dbClick2Stop: "Двоен клик върху празното пространство за пауза", + sortTitle: "Сортирането влиза в сила след следващата актуализация на правилата", + autoRun: "Автоматично активиране (режим на черен списък)", + autoLoadNum: "Количество за предварително заредени страници", + turnRate: "Превъртете на следващата страница, когато е на по-малко от 【X】 пъти височината на страницата от долния колонтитул", + inputPageNum: "Въведете номер на страница за прескачане", + enableHistory: "Записване на историята на сърфиране след прелистване на страница", + enableHistoryAfterInsert: "Записване на историята на сърфиране веднага след снаждане, в противен случай записване след сърфиране", + contentVisibility: "Автоматично превключване на видимостта на съдържанието за подобряване на производителността на изобразяване", + initRun: "Превъртане на страници веднага след отваряне", + preload: "Предварително зареждане на следващата страница за ускоряване", + click2ImportRule: "Щракнете, за да импортирате връзката към основните правила, и след това изчакайте, докато актуализацията приключи: ", + forceAllBody: "Присъединяване на цялото тяло на страницата?", + openInNewTab: "Отваряне на URL адресите на допълненията в нов раздел", + importSucc: "Импортирането е завършено", + import: "Импортиране", + editCurrent: "Редактиране на правило за текущия уебсайт", + editBlacklist: "Редактирайте черния списък с URL адреси, един запис на ред, поддържа заместващи символи [?,*].", + upBtnImg: "Икона за връщане горе", + downBtnImg: "Икона за преминаване към долния колонтитул", + sideControllerIcon: "Икона на страничната лента", + loadingTextTitle: "Зареждане", + dbClick2StopCtrl: "Клавиш Ctrl", + dbClick2StopAlt: "Клавиш Alt", + dbClick2StopShift: "Клавиш Shift", + dbClick2StopMeta: "Клавиш Meta", + dbClick2StopKey: "Клавишна комбинация", + pageElementCss: "Персонализиран стил за основните елементи на страницата", + customCss: "Персонализиран пълен CSS", + firstAlert: "Не сте импортирали основното правило, моля, изберете подходящото правило за импортиране", + picker: "Избор на елементи на Pagetual", + closePicker: "Затваряне на избора на Pagetual", + pickerPlaceholder: "Избор на елементи (Само за напреднали потребители, в противен случай оставете празно)", + pickerCheck: "Проверка на селектора и копиране", + switchSelector: "Щракнете, за да превключите елемент", + gotoEdit: "Отидете на редактиране на правило с текущия селектор", + manualMode: "Деактивиране на снаждането, ръчно преминаване към следващата страница с помощта на десния клавиш със стрелка (или изпращане на събитие 'pagetual.next')", + clickMode: "Деактивиране на снаждането, автоматично щракване върху следващата страница при превъртане до края на страницата", + pageBarMenu: "Щракнете в центъра на лентата на страницата, за да отворите менюто за избор", + nextSwitch: "Превключване на следваща връзка", + arrowToScroll: "Натиснете лявата стрелка, за да превъртите назад, и дясната стрелка, за да преминете напред", + sideController: "Показване на контролната лента за пагинация в страничната лента", + sideControllerScroll: "Превключване на превъртането", + sideControllerAlways: "Винаги показване", + hideLoadingIcon: "Скриване на анимацията за зареждане", + hideBarArrow: "Скриване на стрелката за лентата на страницата", + duplicate: "Инсталиран е дубликат на Pagetual, проверете вашия мениджър на скриптове!", + forceStateIframe: "Вграждане на цялата страница като iframe", + forceStateDynamic: "Зареждане на динамично съдържание чрез iframe", + forceStateDisable: "Деактивиране на прелистването на страници на този сайт", + autoScrollRate: "Скорост на превъртане (1-1000)", + disableAutoScroll: "Спиране на автоматичното превъртане", + enableAutoScroll: "Активиране на автоматичното превъртане", + toggleAutoScroll: "Превключване на автоматичното превъртане", + ruleRequest: "Искане за правило", + page: "Страница ", + prevPage: "Предишна страница", + nextPage: "Следваща страница", + errorRulesMustBeArray: "Правилата трябва да са масив!", + errorJson: "Грешка в JSON, проверете отново!", + editSuccess: "Редактирането е успешно", + errorWrongUrl: "Грешен URL, проверете отново!", + errorAlreadyExists: "Правило вече съществува!", + settingsSaved: "Настройките са запазени, опреснете, за да видите", + iframe: "Принудително разделяне от iframe", + dynamic: "Динамично зареждане", + reloadPage: "Редактирането е завършено, презареждане сега?", + copied: "Копирано", + noValidContent: "Не е открито валидно съдържание, може да има Captcha", + outOfDate: "Скриптът е остарял, моля, актуализирайте до най-новата версия.", + hideBarTips: "Скрийте лентата за пагинация, превключете поглъщащото изживяване", + setConfigPage: "Задайте текущата страница като страница за конфигурация по подразбиране", + wedata2github: "Променете адреса на wedata на огледалния адрес в хранилището на github", + addOtherProp: "Добавяне на свойства на правилото", + addNextSelector: "Добавяне на съдържание на селектора като nextLink", + addPageSelector: "Добавяне на съдържание на селектора като pageElement", + propName: "Въведете име на свойството на правилото", + propValue: "Въведете стойност на свойството на правилото", + customFirst: "Игнориране на кеша за локални персонализирани правила", + rulesExample: "Пример за правила", + lastPage: "Достигната е последната страница", + lastPageTips: "Показване на съвети при достигане на последната страница" + } + }, + { + name: "Čeština", + match: ["cs"], + lang: { + enableDebug: "Povolit výstup ladění do konzole", + updateNotification: "Oznámení po aktualizaci pravidel", + disable: "Dočasně zakázat", + disableSite: "Přepnout stav zakázání", + disableSiteTips: "Na této stránce zakázáno.", + enableSiteTips: "Na této stránce povoleno.", + enable: "✅Povolit automatické otáčení stránek", + tempActive: "Dočasně aktivní", + toTop: "Zpět nahoru.", + toBottom: "Jít dolů.", + current: "Aktuální stránka.", + forceIframe: "Vynutit připojení k další stránce", + cancelForceIframe: "Zrušit vynucené připojení", + configure: "Konfigurovat Pagetual", + firstUpdate: "Klikněte sem pro inicializaci výchozího seznamu pravidel", + update: "Aktualizovat online pravidla", + click2update: "Klikněte pro aktualizaci pravidel z URL nyní", + loadNow: "Načíst další automaticky", + loadConfirm: "Kolik stránek chcete načíst? (0 znamená nekonečno)", + noNext: "Nenalezen žádný další odkaz, vytvořte nové pravidlo", + passSec: "Aktualizováno před #t# sekundami", + passMin: "Aktualizováno před #t# minutami", + passHour: "Aktualizováno před #t# hodinami", + passDay: "Aktualizováno před #t# dny", + cantDel: "Nelze odstranit vestavěná pravidla", + confirmDel: "Jste si jisti, že chcete toto pravidlo odstranit?", + updateSucc: "Aktualizace úspěšná", + beginUpdate: "Zahajuje se aktualizace, chvíli prosím počkejte", + customUrls: "Importovat URL pravidla Pagetual nebo AutoPagerize, jedno URL na řádek.", + customRules: "Zadejte vlastní pravidla. ✍️Přispějte pravidly", + save: "Uložit", + loadingText: "Načítání...", + opacity: "Neprůhlednost", + opacityPlaceholder: "0: skrýt oddělovač", + hideBar: "Skrýt oddělovač stránkování", + hideBarButNoStop: "Skrýt, ale nezastavit", + dbClick2Stop: "Dvojitým kliknutím na prázdné místo pozastavíte", + sortTitle: "Třídění se projeví po další aktualizaci pravidel", + autoRun: "Automatické povolení (režim černé listiny)", + autoLoadNum: "Množství pro přednačtené stránky", + turnRate: "Otočte na další stránku, když je méně než 【X】 násobek výšky stránky od zápatí", + inputPageNum: "Zadejte číslo stránky pro skok", + enableHistory: "Zapsat historii procházení po otočení stránky", + enableHistoryAfterInsert: "Zapsat historii procházení ihned po spojení, jinak zapsat po procházení", + contentVisibility: "Automaticky přepínat viditelnost obsahu pro zlepšení výkonu vykreslování", + initRun: "Otočit stránky ihned po otevření", + preload: "Přednačíst další stránku pro zrychlení", + click2ImportRule: "Klikněte pro import odkazu na základní pravidla a poté počkejte, dokud se aktualizace nedokončí: ", + forceAllBody: "Připojit celé tělo stránky?", + openInNewTab: "Otevřít URL adres přídavků v nové kartě", + importSucc: "Import dokončen", + import: "Importovat", + editCurrent: "Upravit pravidlo pro aktuální webovou stránku", + editBlacklist: "Upravit černou listinu URL, jeden záznam na řádek, podporuje zástupné znaky [?,*].", + upBtnImg: "Ikona zpět nahoru", + downBtnImg: "Ikona jít do zápatí", + sideControllerIcon: "Ikona bočního panelu", + loadingTextTitle: "Načítání", + dbClick2StopCtrl: "Klávesa Ctrl", + dbClick2StopAlt: "Klávesa Alt", + dbClick2StopShift: "Klávesa Shift", + dbClick2StopMeta: "Klávesa Meta", + dbClick2StopKey: "Klávesová zkratka", + pageElementCss: "Vlastní styl pro hlavní prvky stránky", + customCss: "Vlastní kompletní CSS", + firstAlert: "Neimportovali jste základní pravidlo, vyberte prosím vhodné pravidlo k importu", + picker: "Výběr prvků Pagetual", + closePicker: "Zavřít výběr Pagetual", + pickerPlaceholder: "Výběr prvků (Pouze pro pokročilé uživatele, jinak nechte prázdné)", + pickerCheck: "Zkontrolovat výběr a kopírovat", + switchSelector: "Kliknutím přepnete prvek", + gotoEdit: "Přejít na úpravu pravidla s aktuálním výběrem", + manualMode: "Zakázat spojování, ručně přejít na další stránku pomocí klávesy se šipkou doprava (nebo odeslat událost 'pagetual.next')", + clickMode: "Zakázat spojování, automaticky kliknout na další stránku při posunutí na konec stránky", + pageBarMenu: "Kliknutím na střed lišty stránky otevřete menu výběru", + nextSwitch: "Přepnout další odkaz", + arrowToScroll: "Stisknutím levé šipky se posunete zpět a pravou šipkou přejdete na stránku", + sideController: "Zobrazit ovládací panel stránkování v bočním panelu", + sideControllerScroll: "Přepnout posouvání", + sideControllerAlways: "Vždy zobrazit", + hideLoadingIcon: "Skrýt animaci načítání", + hideBarArrow: "Skrýt šipku pro lištu stránky", + duplicate: "Duplicitní Pagetual byl nainstalován, zkontrolujte svůj správce skriptů!", + forceStateIframe: "Vložit celou stránku jako iframe", + forceStateDynamic: "Načíst dynamický obsah přes iframe", + forceStateDisable: "Zakázat otáčení stránek на této stránce", + autoScrollRate: "Rychlost posouvání (1-1000)", + disableAutoScroll: "Zastavit automatické posouvání", + enableAutoScroll: "Povolit automatické posouvání", + toggleAutoScroll: "Přepnout automatické posouvání", + ruleRequest: "Žádost o pravidlo", + page: "Stránka ", + prevPage: "Předchozí stránka", + nextPage: "Další stránka", + errorRulesMustBeArray: "Pravidla musí být pole!", + errorJson: "Chyba JSON, zkontrolujte znovu!", + editSuccess: "Úspěšně upraveno", + errorWrongUrl: "Nesprávné URL, zkontrolujte znovu!", + errorAlreadyExists: "Pravidlo již existuje!", + settingsSaved: "Nastavení jsou uložena, obnovte pro zobrazení", + iframe: "Vynucené rozdělení pomocí iframe", + dynamic: "Dynamické načítání", + reloadPage: "Úprava dokončena, načíst znovu?", + copied: "Zkopírováno", + noValidContent: "Nebyl zjištěn žádný platný obsah, může být přítomna Captcha", + outOfDate: "Skript je zastaralý, aktualizujte prosím na nejnovější verzi.", + hideBarTips: "Skrýt lištu stránkování, přepnout pohlcující zážitek", + setConfigPage: "Nastavit aktuální stránku jako výchozí konfigurační stránku", + wedata2github: "Změnit adresu wedata na zrcadlovou adresu v repozitáři github", + addOtherProp: "Přidat vlastnosti pravidla", + addNextSelector: "Přidat obsah výběru jako nextLink", + addPageSelector: "Přidat obsah výběru jako pageElement", + propName: "Zadejte název vlastnosti pravidla", + propValue: "Zadejte hodnotu vlastnosti pravidla", + customFirst: "Ignorovat mezipaměť pro místní vlastní pravidla", + rulesExample: "Příklad pravidel", + lastPage: "Dosáhli jste poslední stránky", + lastPageTips: "Zobrazit tipy při dosažení poslední stránky" + } + }, + { + name: "Tiếng Việt", + match: ["vi"], + lang: { + enableDebug: "Bật đầu ra gỡ lỗi vào bảng điều khiển", + updateNotification: "Thông báo sau khi cập nhật quy tắc", + disable: "Tạm thời vô hiệu hóa", + disableSite: "Chuyển đổi trạng thái vô hiệu hóa", + disableSiteTips: "Đã vô hiệu hóa trên trang này.", + enableSiteTips: "Đã bật trên trang này.", + enable: "✅Bật tự động chuyển trang", + tempActive: "Tạm thời hoạt động", + toTop: "Quay lại đầu trang.", + toBottom: "Đi đến cuối trang.", + current: "Trang hiện tại.", + forceIframe: "Buộc tham gia trang tiếp theo", + cancelForceIframe: "Hủy bỏ buộc tham gia", + configure: "Cấu hình Pagetual", + firstUpdate: "Nhấp vào đây để khởi tạo danh sách quy tắc mặc định", + update: "Cập nhật quy tắc trực tuyến", + click2update: "Nhấp để cập nhật quy tắc từ url ngay bây giờ", + loadNow: "Tải trang tiếp theo tự động", + loadConfirm: "Bạn muốn tải bao nhiêu trang? (0 có nghĩa là vô hạn)", + noNext: "Không tìm thấy liên kết tiếp theo, vui lòng tạo quy tắc mới", + passSec: "Đã cập nhật #t# giây trước", + passMin: "Đã cập nhật #t# phút trước", + passHour: "Đã cập nhật #t# giờ trước", + passDay: "Đã cập nhật #t# ngày trước", + cantDel: "Không thể xóa các quy tắc cài sẵn", + confirmDel: "Bạn có chắc chắn muốn xóa quy tắc này không?", + updateSucc: "Cập nhật thành công", + beginUpdate: "Bắt đầu cập nhật, vui lòng đợi một lát", + customUrls: "Nhập URL quy tắc Pagetual hoặc AutoPagerize, mỗi URL một dòng.", + customRules: "Nhập các quy tắc tùy chỉnh. ✍️Đóng góp quy tắc", + save: "Lưu", + loadingText: "Đang tải...", + opacity: "Độ mờ", + opacityPlaceholder: "0: ẩn dấu phân cách", + hideBar: "Ẩn dấu phân cách phân trang", + hideBarButNoStop: "Ẩn nhưng không dừng", + dbClick2Stop: "Nhấp đúp vào khoảng trống để tạm dừng", + sortTitle: "Việc sắp xếp có hiệu lực sau khi cập nhật quy tắc tiếp theo", + autoRun: "Tự động bật (chế độ danh sách đen)", + autoLoadNum: "Số lượng trang tải trước", + turnRate: "Chuyển sang trang tiếp theo khi còn cách chân trang chưa đến 【X】 lần chiều cao trang", + inputPageNum: "Nhập số trang để chuyển đến", + enableHistory: "Ghi lại lịch sử duyệt web sau khi chuyển trang", + enableHistoryAfterInsert: "Ghi lại lịch sử duyệt web ngay sau khi ghép nối, nếu không thì ghi lại sau khi duyệt", + contentVisibility: "Tự động chuyển đổi khả năng hiển thị nội dung để cải thiện hiệu suất hiển thị", + initRun: "Chuyển trang ngay sau khi mở", + preload: "Tải trước trang tiếp theo để tăng tốc", + click2ImportRule: "Nhấp để nhập liên kết quy tắc cơ sở, sau đó đợi cho đến khi cập nhật hoàn tất: ", + forceAllBody: "Tham gia toàn bộ nội dung của trang?", + openInNewTab: "Mở các url bổ sung trong tab mới", + importSucc: "Nhập hoàn tất", + import: "Nhập", + editCurrent: "Chỉnh sửa quy tắc cho trang web hiện tại", + editBlacklist: "Chỉnh sửa danh sách đen url, mỗi mục một dòng, hỗ trợ ký tự đại diện [?,*].", + upBtnImg: "Biểu tượng quay lại đầu trang", + downBtnImg: "Biểu tượng đi đến chân trang", + sideControllerIcon: "Biểu tượng của thanh bên", + loadingTextTitle: "Đang tải", + dbClick2StopCtrl: "Phím Ctrl", + dbClick2StopAlt: "Phím Alt", + dbClick2StopShift: "Phím Shift", + dbClick2StopMeta: "Phím Meta", + dbClick2StopKey: "Phím tắt", + pageElementCss: "Kiểu tùy chỉnh cho các phần tử trang chính", + customCss: "CSS hoàn chỉnh tùy chỉnh", + firstAlert: "Bạn chưa nhập quy tắc cơ sở, vui lòng chọn quy tắc thích hợp để nhập", + picker: "Bộ chọn phần tử Pagetual", + closePicker: "Đóng bộ chọn Pagetual", + pickerPlaceholder: "Bộ chọn phần tử (Chỉ dành cho người dùng nâng cao, nếu không thì để trống)", + pickerCheck: "Kiểm tra bộ chọn và sao chép", + switchSelector: "Nhấp để chuyển đổi phần tử", + gotoEdit: "Chuyển đến chỉnh sửa quy tắc với bộ chọn hiện tại", + manualMode: "Vô hiệu hóa việc ghép nối, chuyển đến trang tiếp theo theo cách thủ công bằng phím mũi tên phải (hoặc gửi sự kiện 'pagetual.next')", + clickMode: "Vô hiệu hóa việc ghép nối, tự động nhấp vào trang tiếp theo khi cuộn đến cuối trang", + pageBarMenu: "Nhấp vào giữa thanh trang để mở menu bộ chọn", + nextSwitch: "Chuyển đổi liên kết tiếp theo", + arrowToScroll: "Nhấn mũi tên trái để cuộn lại và mũi tên phải để chuyển trang", + sideController: "Hiển thị thanh điều khiển phân trang trong thanh bên", + sideControllerScroll: "Chuyển đổi cuộn", + sideControllerAlways: "Luôn hiển thị", + hideLoadingIcon: "Ẩn hoạt ảnh tải", + hideBarArrow: "Ẩn mũi tên cho thanh trang", + duplicate: "Pagetual trùng lặp đã được cài đặt, hãy kiểm tra trình quản lý tập lệnh của bạn!", + forceStateIframe: "Nhúng toàn bộ trang dưới dạng iframe", + forceStateDynamic: "Tải nội dung động qua iframe", + forceStateDisable: "Vô hiệu hóa việc chuyển trang trên trang này", + autoScrollRate: "Tốc độ cuộn (1~1000)", + disableAutoScroll: "Dừng cuộn tự động", + enableAutoScroll: "Bật cuộn tự động", + toggleAutoScroll: "Chuyển đổi cuộn tự động", + ruleRequest: "Yêu cầu quy tắc", + page: "Trang ", + prevPage: "Trang trước", + nextPage: "Trang tiếp theo", + errorRulesMustBeArray: "Quy tắc phải là một mảng!", + errorJson: "Lỗi JSON, hãy kiểm tra lại!", + editSuccess: "Chỉnh sửa thành công", + errorWrongUrl: "URL sai, hãy kiểm tra lại!", + errorAlreadyExists: "Một quy tắc đã tồn tại!", + settingsSaved: "Cài đặt đã được lưu, hãy làm mới để xem", + iframe: "Tách bắt buộc bằng iframe", + dynamic: "Tải động", + reloadPage: "Chỉnh sửa hoàn tất, tải lại ngay bây giờ?", + copied: "Đã sao chép", + noValidContent: "Không phát hiện thấy nội dung hợp lệ, có thể có Captcha", + outOfDate: "Tập lệnh đã lỗi thời, vui lòng cập nhật lên phiên bản mới nhất.", + hideBarTips: "Ẩn thanh phân trang, chuyển đổi trải nghiệm đắm chìm", + setConfigPage: "Đặt trang hiện tại làm trang cấu hình mặc định", + wedata2github: "Thay đổi địa chỉ wedata thành địa chỉ nhân bản trong kho lưu trữ github", + addOtherProp: "Thêm thuộc tính quy tắc", + addNextSelector: "Thêm nội dung bộ chọn làm nextLink", + addPageSelector: "Thêm nội dung bộ chọn làm pageElement", + propName: "Nhập tên thuộc tính quy tắc", + propValue: "Nhập giá trị thuộc tính quy tắc", + customFirst: "Bỏ qua bộ nhớ cache cho các quy tắc tùy chỉnh cục bộ", + rulesExample: "Ví dụ về quy tắc", + lastPage: "Đã đến trang cuối cùng", + lastPageTips: "Hiển thị mẹo khi đến trang cuối cùng" + } + }, + { + name: "Polski", + match: ["pl"], + lang: { + enableDebug: "Włącz wyjście debugowania do konsoli", + updateNotification: "Powiadomienie po aktualizacji reguł", + disable: "Tymczasowo wyłącz", + disableSite: "Przełącz stan wyłączenia", + disableSiteTips: "Wyłączone na tej stronie.", + enableSiteTips: "Włączone na tej stronie.", + enable: "✅Włącz automatyczne przewracanie stron", + tempActive: "Tymczasowo aktywne", + toTop: "Powrót na górę.", + toBottom: "Przejdź na dół.", + current: "Bieżąca strona.", + forceIframe: "Wymuś dołączenie do następnej strony", + cancelForceIframe: "Anuluj wymuszone dołączenie", + configure: "Skonfiguruj Pagetual", + firstUpdate: "Kliknij tutaj, aby zainicjować domyślną listę reguł", + update: "Aktualizuj reguły online", + click2update: "Kliknij, aby zaktualizować reguły z adresu URL teraz", + loadNow: "Załaduj następną automatycznie", + loadConfirm: "Ile stron chcesz załadować? (0 oznacza nieskończoność)", + noNext: "Nie znaleziono następnego linku, utwórz nową regułę", + passSec: "Zaktualizowano #t# sekund temu", + passMin: "Zaktualizowano #t# minut temu", + passHour: "Zaktualizowano #t# godzin temu", + passDay: "Zaktualizowano #t# dni temu", + cantDel: "Nie można usunąć wbudowanych reguł", + confirmDel: "Czy na pewno chcesz usunąć tę regułę?", + updateSucc: "Aktualizacja zakończona powodzeniem", + beginUpdate: "Rozpoczynam aktualizację, proszę czekać", + customUrls: "Importuj adres URL reguły Pagetual lub AutoPagerize, jeden adres URL na linię.", + customRules: "Wprowadź niestandardowe reguły. ✍️Współtwórz reguły", + save: "Zapisz", + loadingText: "Ładowanie...", + opacity: "Przezroczystość", + opacityPlaceholder: "0: ukryj separator", + hideBar: "Ukryj separator paginacji", + hideBarButNoStop: "Ukryj, ale nie zatrzymuj", + dbClick2Stop: "Kliknij dwukrotnie w puste miejsce, aby wstrzymać", + sortTitle: "Sortowanie zacznie obowiązywać po następnej aktualizacji reguł", + autoRun: "Automatyczne włączanie (tryb czarnej listy)", + autoLoadNum: "Ilość stron do wstępnego załadowania", + turnRate: "Przewróć na następną stronę, gdy odległość od stopki jest mniejsza niż 【X】-krotność wysokości strony", + inputPageNum: "Wprowadź numer strony, aby przejść", + enableHistory: "Zapisuj historię przeglądania po przewróceniu strony", + enableHistoryAfterInsert: "Zapisuj historię przeglądania natychmiast po połączeniu, w przeciwnym razie zapisuj po przeglądaniu", + contentVisibility: "Automatycznie przełączaj widoczność zawartości, aby poprawić wydajność renderowania", + initRun: "Przewracaj strony natychmiast po otwarciu", + preload: "Wstępnie załaduj następną stronę, aby przyspieszyć", + click2ImportRule: "Kliknij, aby zaimportować link do podstawowych reguł, a następnie poczekaj na zakończenie aktualizacji: ", + forceAllBody: "Dołączyć całą treść strony?", + openInNewTab: "Otwórz adresy URL dodatków w nowej karcie", + importSucc: "Import zakończony", + import: "Importuj", + editCurrent: "Edytuj regułę dla bieżącej witryny", + editBlacklist: "Edytuj czarną listę adresów URL, jeden wpis na linię, obsługuje symbole wieloznaczne [?,*].", + upBtnImg: "Ikona powrotu na górę", + downBtnImg: "Ikona przejścia do stopki", + sideControllerIcon: "Ikona paska bocznego", + loadingTextTitle: "Ładowanie", + dbClick2StopCtrl: "Klawisz Ctrl", + dbClick2StopAlt: "Klawisz Alt", + dbClick2StopShift: "Klawisz Shift", + dbClick2StopMeta: "Klawisz Meta", + dbClick2StopKey: "Klawisz skrótu", + pageElementCss: "Niestandardowy styl dla głównych elementów strony", + customCss: "Niestandardowy kompletny CSS", + firstAlert: "Nie zaimportowałeś podstawowej reguły, wybierz odpowiednią regułę do zaimportowania", + picker: "Selektor elementów Pagetual", + closePicker: "Zamknij selektor Pagetual", + pickerPlaceholder: "Selektor elementów (Tylko dla zaawansowanych użytkowników, w przeciwnym razie pozostaw puste)", + pickerCheck: "Sprawdź selektor i skopiuj", + switchSelector: "Kliknij, aby przełączyć element", + gotoEdit: "Przejdź do edycji reguły z bieżącym selektorem", + manualMode: "Wyłącz łączenie, ręcznie przejdź do następnej strony za pomocą klawisza strzałki w prawo (lub wyślij zdarzenie 'pagetual.next')", + clickMode: "Wyłącz łączenie, automatycznie kliknij następną stronę po przewinięciu do końca strony", + pageBarMenu: "Kliknij środek paska strony, aby otworzyć menu selektora", + nextSwitch: "Przełącz następny link", + arrowToScroll: "Naciśnij lewą strzałkę, aby przewinąć do tyłu, a prawą strzałkę, aby przejść do przodu", + sideController: "Wyświetl pasek sterowania paginacją na pasku bocznym", + sideControllerScroll: "Przełączanie przewijania", + sideControllerAlways: "Zawsze pokazuj", + hideLoadingIcon: "Ukryj animację ładowania", + hideBarArrow: "Ukryj strzałkę paska strony", + duplicate: "Zainstalowano zduplikowany Pagetual, sprawdź menedżera skryptów!", + forceStateIframe: "Osadź całą stronę jako iframe", + forceStateDynamic: "Załaduj dynamiczną zawartość przez iframe", + forceStateDisable: "Wyłącz przewracanie stron na tej stronie", + autoScrollRate: "Prędkość przewijania (1-1000)", + disableAutoScroll: "Zatrzymaj automatyczne przewijanie", + enableAutoScroll: "Włącz automatyczne przewijanie", + toggleAutoScroll: "Przełącz automatyczne przewijanie", + ruleRequest: "Żądanie reguły", + page: "Strona ", + prevPage: "Poprzednia strona", + nextPage: "Następna strona", + errorRulesMustBeArray: "Reguły muszą być tablicą!", + errorJson: "Błąd JSON, sprawdź ponownie!", + editSuccess: "Edycja zakończona pomyślnie", + errorWrongUrl: "Błędny adres URL, sprawdź ponownie!", + errorAlreadyExists: "Reguła już istnieje!", + settingsSaved: "Ustawienia zostały zapisane, odśwież, aby zobaczyć", + iframe: "Wymuszony podział przez iframe", + dynamic: "Dynamiczne ładowanie", + reloadPage: "Edycja zakończona, przeładować teraz?", + copied: "Skopiowano", + noValidContent: "Nie wykryto prawidłowej zawartości, może być obecna Captcha", + outOfDate: "Skrypt jest przestarzały, zaktualizuj do najnowszej wersji.", + hideBarTips: "Ukryj pasek paginacji, przełącz na tryb immersyjny", + setConfigPage: "Ustaw bieżącą stronę jako domyślną stronę konfiguracji", + wedata2github: "Zmień adres wedata na adres lustrzany w repozytorium github", + addOtherProp: "Dodaj właściwości reguły", + addNextSelector: "Dodaj zawartość selektora jako nextLink", + addPageSelector: "Dodaj zawartość selektora jako pageElement", + propName: "Wprowadź nazwę właściwości reguły", + propValue: "Wprowadź wartość właściwości reguły", + customFirst: "Ignoruj pamięć podręczną dla lokalnych niestandardowych reguł", + rulesExample: "Przykład reguł", + lastPage: "Osiągnięto ostatnią stronę", + lastPageTips: "Pokaż wskazówki po osiągnięciu ostatniej strony" + } + }, + { + name: "Українська", + match: ["uk"], + lang: { + enableDebug: "Увімкнути вивід налагодження в консоль", + updateNotification: "Сповіщення після оновлення правил", + disable: "Тимчасово вимкнути", + disableSite: "Перемкнути стан вимкнення", + disableSiteTips: "Вимкнено на цьому сайті.", + enableSiteTips: "Увімкнено на цьому сайті.", + enable: "✅Увімкнути автоматичне перегортання сторінок", + tempActive: "Тимчасово активний", + toTop: "Повернутися нагору.", + toBottom: "Перейти вниз.", + current: "Поточна сторінка.", + forceIframe: "Примусово приєднати наступну сторінку", + cancelForceIframe: "Скасувати примусове приєднання", + configure: "Налаштувати Pagetual", + firstUpdate: "Натисніть тут, щоб ініціалізувати стандартний список правил", + update: "Оновити онлайн-правила", + click2update: "Натисніть, щоб оновити правила з URL зараз", + loadNow: "Завантажити наступну автоматично", + loadConfirm: "Скільки сторінок ви хочете завантажити? (0 означає нескінченно)", + noNext: "Не знайдено наступного посилання, створіть нове правило", + passSec: "Оновлено #t# секунд тому", + passMin: "Оновлено #t# хвилин тому", + passHour: "Оновлено #t# годин тому", + passDay: "Оновлено #t# днів тому", + cantDel: "Неможливо видалити вбудовані правила", + confirmDel: "Ви впевнені, що хочете видалити це правило?", + updateSucc: "Оновлення успішне", + beginUpdate: "Починається оновлення, зачекайте хвилинку", + customUrls: "Імпортувати URL правила Pagetual або AutoPagerize, один URL на рядок.", + customRules: "Введіть власні правила. ✍️Додайте правила", + save: "Зберегти", + loadingText: "Завантаження...", + opacity: "Непрозорість", + opacityPlaceholder: "0: приховати роздільник", + hideBar: "Приховати роздільник пагінації", + hideBarButNoStop: "Приховати, але не зупиняти", + dbClick2Stop: "Двічі клацніть на порожньому місці, щоб призупинити", + sortTitle: "Сортування набуде чинності після наступного оновлення правил", + autoRun: "Автоматичне ввімкнення (режим чорного списку)", + autoLoadNum: "Кількість для попередньо завантажених сторінок", + turnRate: "Перегорніть на наступну сторінку, коли до нижнього колонтитула залишиться менше 【X】 висот сторінки", + inputPageNum: "Введіть номер сторінки для переходу", + enableHistory: "Записувати історію переглядів після перегортання сторінки", + enableHistoryAfterInsert: "Записувати історію переглядів одразу після з'єднання, інакше записувати після перегляду", + contentVisibility: "Автоматично перемикати видимість вмісту для покращення продуктивності рендерингу", + initRun: "Перегортати сторінки одразу після відкриття", + preload: "Попередньо завантажити наступну сторінку для прискорення", + click2ImportRule: "Натисніть, щоб імпортувати посилання на базові правила, а потім зачекайте, доки оновлення не завершиться: ", + forceAllBody: "Приєднати все тіло сторінки?", + openInNewTab: "Відкрити URL-адреси доповнень у новій вкладці", + importSucc: "Імпорт завершено", + import: "Імпортувати", + editCurrent: "Редагувати правило для поточного веб-сайту", + editBlacklist: "Редагувати чорний список URL-адрес, один запис на рядок, підтримує символи підстановки [?,*].", + upBtnImg: "Іконка повернення нагору", + downBtnImg: "Іконка переходу до нижнього колонтитула", + sideControllerIcon: "Іконка бічної панелі", + loadingTextTitle: "Завантаження", + dbClick2StopCtrl: "Клавіша Ctrl", + dbClick2StopAlt: "Клавіша Alt", + dbClick2StopShift: "Клавіша Shift", + dbClick2StopMeta: "Клавіша Meta", + dbClick2StopKey: "Клавіша швидкого доступу", + pageElementCss: "Власний стиль для основних елементів сторінки", + customCss: "Власний повний CSS", + firstAlert: "Ви не імпортували базове правило, будь ласка, виберіть відповідне правило для імпорту", + picker: "Вибір елементів Pagetual", + closePicker: "Закрити вибір Pagetual", + pickerPlaceholder: "Вибір елементів (Лише для досвідчених користувачів, інакше залиште порожнім)", + pickerCheck: "Перевірити селектор і скопіювати", + switchSelector: "Натисніть, щоб перемкнути елемент", + gotoEdit: "Перейти до редагування правила з поточним селектором", + manualMode: "Вимкнути з'єднання, вручну переходити на наступну сторінку за допомогою клавіші зі стрілкою вправо (або надіслати подію 'pagetual.next')", + clickMode: "Вимкнути з'єднання, автоматично клацати на наступну сторінку при прокручуванні до кінця сторінки", + pageBarMenu: "Натисніть на центр панелі сторінки, щоб відкрити меню вибору", + nextSwitch: "Перемкнути наступне посилання", + arrowToScroll: "Натисніть ліву стрілку, щоб прокрутити назад, і праву стрілку, щоб перейти на сторінку вперед", + sideController: "Відображати панель керування пагінацією в бічній панелі", + sideControllerScroll: "Перемикання прокрутки", + sideControllerAlways: "Завжди показувати", + hideLoadingIcon: "Приховати анімацію завантаження", + hideBarArrow: "Приховати стрілку для панелі сторінки", + duplicate: "Встановлено дублікат Pagetual, перевірте свій менеджер скриптів!", + forceStateIframe: "Вбудувати повну сторінку як iframe", + forceStateDynamic: "Завантажувати динамічний вміст через iframe", + forceStateDisable: "Вимкнути перегортання сторінок на цьому сайті", + autoScrollRate: "Швидкість прокрутки (1-1000)", + disableAutoScroll: "Зупинити автоматичну прокрутку", + enableAutoScroll: "Увімкнути автоматичну прокрутку", + toggleAutoScroll: "Перемкнути автоматичну прокрутку", + ruleRequest: "Запит на правило", + page: "Сторінка ", + prevPage: "Попередня сторінка", + nextPage: "Наступна сторінка", + errorRulesMustBeArray: "Правила повинні бути масивом!", + errorJson: "Помилка JSON, перевірте ще раз!", + editSuccess: "Відредаговано успішно", + errorWrongUrl: "Неправильна URL-адреса, перевірте ще раз!", + errorAlreadyExists: "Правило вже існує!", + settingsSaved: "Налаштування збережено, оновіть, щоб переглянути", + iframe: "Примусове розділення за допомогою iframe", + dynamic: "Динамічне завантаження", + reloadPage: "Редагування завершено, перезавантажити зараз?", + copied: "Скопійовано", + noValidContent: "Не виявлено дійсного вмісту, можливо, є Captcha", + outOfDate: "Скрипт застарів, оновіть до останньої версії.", + hideBarTips: "Приховати панель пагінації, перемкнути на захоплюючий досвід", + setConfigPage: "Встановити поточну сторінку як сторінку конфігурації за замовчуванням", + wedata2github: "Змінити адресу wedata на дзеркальну адресу в репозиторії github", + addOtherProp: "Додати властивості правила", + addNextSelector: "Додати вміст селектора як nextLink", + addPageSelector: "Додати вміст селектора як pageElement", + propName: "Введіть назву властивості правила", + propValue: "Введіть значення властивості правила", + customFirst: "Ігнорувати кеш для локальних власних правил", + rulesExample: "Приклад правил", + lastPage: "Досягнуто останньої сторінки", + lastPageTips: "Показувати поради при досягненні останньої сторінки" + } + }, + { + name: "Türkçe", + match: ["tr"], + lang: { + enableDebug: "Konsola hata ayıklama çıktısını etkinleştir", + updateNotification: "Kurallar güncellendikten sonra bildirim", + disable: "Geçici olarak devre dışı bırak", + disableSite: "Devre dışı bırakma durumunu değiştir", + disableSiteTips: "Bu sitede devre dışı bırakıldı.", + enableSiteTips: "Bu sitede etkinleştirildi.", + enable: "✅Otomatik sayfa çevirmeyi etkinleştir", + tempActive: "Geçici olarak aktif", + toTop: "Başa dön.", + toBottom: "Sona git.", + current: "Mevcut sayfa.", + forceIframe: "Sonraki sayfaya katılmaya zorla", + cancelForceIframe: "Zorla katılmayı iptal et", + configure: "Pagetual'ı yapılandır", + firstUpdate: "Varsayılan kural listesini başlatmak için buraya tıklayın", + update: "Çevrimiçi kuralları güncelle", + click2update: "Kuralları şimdi URL'den güncellemek için tıkla", + loadNow: "Sonrakini otomatik olarak yükle", + loadConfirm: "Kaç sayfa yüklemek istiyorsunuz? (0 sonsuz demektir)", + noNext: "Sonraki bağlantı bulunamadı, lütfen yeni bir kural oluşturun", + passSec: "#t# saniye önce güncellendi", + passMin: "#t# dakika önce güncellendi", + passHour: "#t# saat önce güncellendi", + passDay: "#t# gün önce güncellendi", + cantDel: "Yerleşik kurallar silinemez", + confirmDel: "Bu kuralı silmek istediğinizden emin misiniz?", + updateSucc: "Güncelleme başarılı", + beginUpdate: "Güncelleme başlıyor, lütfen bir dakika bekleyin", + customUrls: "Pagetual veya AutoPagerize kural URL'sini içe aktarın, her satıra bir URL.", + customRules: "Özel kuralları girin. ✍️Kurallara katkıda bulunun", + save: "Kaydet", + loadingText: "Yükleniyor...", + opacity: "Opaklık", + opacityPlaceholder: "0: ayırıcıyı gizle", + hideBar: "Sayfalandırma ayırıcısını gizle", + hideBarButNoStop: "Gizle ama durdurma", + dbClick2Stop: "Duraklatmak için boş alana çift tıklayın", + sortTitle: "Sıralama bir sonraki kural güncellemesinden sonra etkili olur", + autoRun: "Otomatik etkinleştir (kara liste modu)", + autoLoadNum: "Önceden yüklenecek sayfa miktarı", + turnRate: "Altbilgiden sayfa yüksekliğinin 【X】 katından daha az olduğunda sonraki sayfaya geçin", + inputPageNum: "Atlamak için sayfa numarasını girin", + enableHistory: "Sayfa çevirdikten sonra tarama geçmişini yaz", + enableHistoryAfterInsert: "Birleştirmeden hemen sonra tarama geçmişini yaz, aksi takdirde taramadan sonra yaz", + contentVisibility: "Oluşturma performansını iyileştirmek için içerik görünürlüğünü otomatik olarak değiştir", + initRun: "Açtıktan hemen sonra sayfaları çevir", + preload: "Hızlandırmak için sonraki sayfayı önceden yükle", + click2ImportRule: "Temel kurallar bağlantısını içe aktarmak için tıklayın ve ardından güncelleme tamamlanana kadar bekleyin: ", + forceAllBody: "Sayfanın tam gövdesine katılsın mı?", + openInNewTab: "Eklerin URL'lerini yeni sekmede aç", + importSucc: "İçe aktarma tamamlandı", + import: "İçe aktar", + editCurrent: "Mevcut web sitesi için kuralı düzenle", + editBlacklist: "URL kara listesini düzenleyin, her satıra bir giriş, [?,*] joker karakterlerini destekler.", + upBtnImg: "Başa dön simgesi", + downBtnImg: "Altbilgiye git simgesi", + sideControllerIcon: "Kenar çubuğu simgesi", + loadingTextTitle: "Yükleniyor", + dbClick2StopCtrl: "Ctrl tuşu", + dbClick2StopAlt: "Alt tuşu", + dbClick2StopShift: "Shift tuşu", + dbClick2StopMeta: "Meta tuşu", + dbClick2StopKey: "Kısayol tuşu", + pageElementCss: "Ana sayfa öğeleri için özel stil", + customCss: "Özel tam CSS", + firstAlert: "Temel kuralı içe aktarmadınız, lütfen içe aktarmak için uygun kuralı seçin", + picker: "Pagetual öğe seçici", + closePicker: "Pagetual seçiciyi kapat", + pickerPlaceholder: "Öğe seçici (Yalnızca ileri düzey kullanıcılar, aksi takdirde boş bırakın)", + pickerCheck: "Seçiciyi kontrol et ve kopyala", + switchSelector: "Öğeyi değiştirmek için tıkla", + gotoEdit: "Mevcut seçiciyle kuralı düzenlemeye git", + manualMode: "Birleştirmeyi devre dışı bırak, sağ ok tuşunu kullanarak sonraki sayfaya manuel olarak ilerle (veya 'pagetual.next' olayını gönder)", + clickMode: "Birleştirmeyi devre dışı bırak, sayfanın sonuna kaydırıldığında sonraki sayfayı otomatik olarak tıkla", + pageBarMenu: "Seçici menüsünü açmak için sayfa çubuğunun ortasına tıklayın", + nextSwitch: "Sonraki bağlantıyı değiştir", + arrowToScroll: "Geri kaydırmak için sol oka, sayfayı ilerletmek için sağ oka basın", + sideController: "Sayfalandırma kontrol çubuğunu kenar çubuğunda göster", + sideControllerScroll: "Kaydırmayı değiştir", + sideControllerAlways: "Her zaman göster", + hideLoadingIcon: "Yükleme animasyonunu gizle", + hideBarArrow: "Sayfa çubuğu için oku gizle", + duplicate: "Yinelenen Pagetual yüklendi, komut dosyası yöneticinizi kontrol edin!", + forceStateIframe: "Tam sayfayı iframe olarak göm", + forceStateDynamic: "Dinamik içeriği iframe aracılığıyla yükle", + forceStateDisable: "Bu sitede sayfa çevirmeyi devre dışı bırak", + autoScrollRate: "Kaydırma hızı (1-1000)", + disableAutoScroll: "Otomatik Kaydırmayı Durdur", + enableAutoScroll: "Otomatik Kaydırmayı Etkinleştir", + toggleAutoScroll: "Otomatik Kaydırmayı Değiştir", + ruleRequest: "Kural İsteği", + page: "Sayfa ", + prevPage: "Önceki sayfa", + nextPage: "Sonraki sayfa", + errorRulesMustBeArray: "Kurallar bir Dizi olmalıdır!", + errorJson: "JSON hatası, Tekrar kontrol edin!", + editSuccess: "Başarıyla düzenlendi", + errorWrongUrl: "Yanlış URL, Tekrar kontrol edin!", + errorAlreadyExists: "Bir kural zaten var!", + settingsSaved: "Ayarlar kaydedildi, görüntülemek için yenileyin", + iframe: "Iframe tarafından zorla bölündü", + dynamic: "Dinamik yükleme", + reloadPage: "Düzenleme tamamlandı, şimdi yeniden yüklensin mi?", + copied: "Kopyalandı", + noValidContent: "Geçerli içerik algılanmadı, bir Captcha olabilir", + outOfDate: "Komut dosyası güncel değil, lütfen en son sürüme güncelleyin.", + hideBarTips: "Sayfalandırma çubuğunu gizle, sürükleyici deneyime geç", + setConfigPage: "Mevcut sayfayı varsayılan yapılandırma sayfası olarak ayarla", + wedata2github: "Wedata adresini github deposundaki ayna adresine değiştirin", + addOtherProp: "Kural özellikleri ekle", + addNextSelector: "Seçici içeriğini nextLink olarak ekle", + addPageSelector: "Seçici içeriğini pageElement olarak ekle", + propName: "Kural özelliği adını girin", + propValue: "Kural özelliği değerini girin", + customFirst: "Yerel özel kurallar için önbelleği yoksay", + rulesExample: "Kurallar Örneği", + lastPage: "Son sayfaya ulaşıldı", + lastPageTips: "Son sayfaya ulaşıldığında ipuçları göster" + } + }, + { + name: "Nederlands", + match: ["nl"], + lang: { + enableDebug: "Foutopsporingsuitvoer naar console inschakelen", + updateNotification: "Melding nadat regels zijn bijgewerkt", + disable: "Tijdelijk uitschakelen", + disableSite: "Uitgeschakelde status omschakelen", + disableSiteTips: "Uitgeschakeld op deze site.", + enableSiteTips: "Ingeschakeld op deze site.", + enable: "✅Automatisch pagina's omslaan inschakelen", + tempActive: "Tijdelijk actief", + toTop: "Terug naar boven.", + toBottom: "Ga naar beneden.", + current: "Huidige pagina.", + forceIframe: "Dwingen om volgende pagina te koppelen", + cancelForceIframe: "Gedwongen koppeling annuleren", + configure: "Pagetual configureren", + firstUpdate: "Klik hier om de standaardregellijst te initialiseren", + update: "Online regels bijwerken", + click2update: "Klik om regels nu vanaf URL bij te werken", + loadNow: "Laad volgende automatisch", + loadConfirm: "Hoeveel pagina's wilt u laden? (0 betekent oneindig)", + noNext: "Geen volgende link gevonden, maak een nieuwe regel", + passSec: "#t# seconden geleden bijgewerkt", + passMin: "#t# minuten geleden bijgewerkt", + passHour: "#t# uur geleden bijgewerkt", + passDay: "#t# dagen geleden bijgewerkt", + cantDel: "Ingebouwde regels kunnen niet worden verwijderd", + confirmDel: "Weet u zeker dat u deze regel wilt verwijderen?", + updateSucc: "Update geslaagd", + beginUpdate: "Start update, een ogenblik geduld", + customUrls: "Importeer Pagetual of AutoPagerize regel-URL, één URL per regel.", + customRules: "Voer aangepaste regels in. ✍️Draag regels bij", + save: "Opslaan", + loadingText: "Laden...", + opacity: "Dekking", + opacityPlaceholder: "0: verberg scheidingsteken", + hideBar: "Verberg het pagineringsscheidingsteken", + hideBarButNoStop: "Verbergen maar niet stoppen", + dbClick2Stop: "Dubbelklik op de lege ruimte om te pauzeren", + sortTitle: "Sorteren wordt van kracht na de volgende regelupdate", + autoRun: "Automatisch inschakelen (zwarte lijst-modus)", + autoLoadNum: "Aantal voor vooraf geladen pagina's", + turnRate: "Sla de volgende pagina om wanneer deze minder dan 【X】 keer de paginahoogte van de voettekst is", + inputPageNum: "Voer paginanummer in om te springen", + enableHistory: "Schrijf browsegeschiedenis na het omslaan van de pagina", + enableHistoryAfterInsert: "Schrijf browsegeschiedenis onmiddellijk na het splitsen, anders schrijven na het browsen", + contentVisibility: "Schakel automatisch de zichtbaarheid van inhoud om de renderprestaties te verbeteren", + initRun: "Sla pagina's onmiddellijk na het openen om", + preload: "Laad de volgende pagina vooraf om te versnellen", + click2ImportRule: "Klik om de link met basisregels te importeren en wacht vervolgens tot de update is voltooid: ", + forceAllBody: "Volledige body van de pagina koppelen?", + openInNewTab: "Open URL's van toevoegingen in een nieuw tabblad", + importSucc: "Importeren voltooid", + import: "Importeren", + editCurrent: "Regel voor huidige website bewerken", + editBlacklist: "Bewerk de URL-zwarte lijst, één item per regel, ondersteunt [?,*] jokertekens.", + upBtnImg: "Pictogram terug naar boven", + downBtnImg: "Pictogram ga naar voettekst", + sideControllerIcon: "Pictogram zijbalk", + loadingTextTitle: "Laden", + dbClick2StopCtrl: "Ctrl-toets", + dbClick2StopAlt: "Alt-toets", + dbClick2StopShift: "Shift-toets", + dbClick2StopMeta: "Meta-toets", + dbClick2StopKey: "Sneltoets", + pageElementCss: "Aangepaste stijl voor hoofdpagina-elementen", + customCss: "Aangepaste volledige CSS", + firstAlert: "U heeft de basisregel niet geïmporteerd, selecteer de juiste regel om te importeren", + picker: "Pagetual-elementenkiezer", + closePicker: "Pagetual-kiezer sluiten", + pickerPlaceholder: "Elementenkiezer (Alleen voor gevorderde gebruikers, laat anders leeg)", + pickerCheck: "Controleer kiezer en kopieer", + switchSelector: "Klik om van element te wisselen", + gotoEdit: "Ga naar regel bewerken met huidige kiezer", + manualMode: "Schakel splitsen uit, ga handmatig naar de volgende pagina met de rechterpijltoets (of verstuur gebeurtenis 'pagetual.next')", + clickMode: "Schakel splitsen uit, klik automatisch op de volgende pagina bij het scrollen naar het einde van de pagina", + pageBarMenu: "Klik op het midden van de paginabalk om het kiezermenu te openen", + nextSwitch: "Wissel volgende link", + arrowToScroll: "Druk op de linkerpijl om terug te scrollen en op de rechterpijl om naar de volgende pagina te gaan", + sideController: "Toon de pagineringsbalk in de zijbalk", + sideControllerScroll: "Scroll-schakelaar", + sideControllerAlways: "Altijd tonen", + hideLoadingIcon: "Verberg laadanimatie", + hideBarArrow: "Verberg pijl voor paginabalk", + duplicate: "Dubbele Pagetual is geïnstalleerd, controleer uw scriptmanager!", + forceStateIframe: "Volledige pagina insluiten als iframe", + forceStateDynamic: "Laad dynamische inhoud via iframe", + forceStateDisable: "Schakel het omslaan van pagina's op deze site uit", + autoScrollRate: "Scrollsnelheid (1-1000)", + disableAutoScroll: "Stop automatisch scrollen", + enableAutoScroll: "Schakel automatisch scrollen in", + toggleAutoScroll: "Schakel automatisch scrollen om", + ruleRequest: "Regelverzoek", + page: "Pagina ", + prevPage: "Vorige pagina", + nextPage: "Volgende pagina", + errorRulesMustBeArray: "Regels moeten een array zijn!", + errorJson: "JSON-fout, controleer opnieuw!", + editSuccess: "Succesvol bewerkt", + errorWrongUrl: "Verkeerde URL, controleer opnieuw!", + errorAlreadyExists: "Er bestaat al een regel!", + settingsSaved: "De instellingen zijn opgeslagen, ververs om te bekijken", + iframe: "Gedwongen gesplitst door iframe", + dynamic: "Dynamisch laden", + reloadPage: "Bewerking voltooid, nu opnieuw laden?", + copied: "Gekopieerd", + noValidContent: "Geen geldige inhoud gedetecteerd, er is mogelijk een Captcha aanwezig", + outOfDate: "Het script is verouderd, update naar de nieuwste versie.", + hideBarTips: "Verberg de pagineringsbalk, schakel de meeslepende ervaring om", + setConfigPage: "Stel de huidige pagina in als de standaardconfiguratiepagina", + wedata2github: "Wijzig het wedata-adres in het spiegeladres in de github-repository", + addOtherProp: "Voeg regeleigenschappen toe", + addNextSelector: "Voeg kiezerinhoud toe als nextLink", + addPageSelector: "Voeg kiezerinhoud toe als pageElement", + propName: "Voer de naam van de regeleigenschap in", + propValue: "Voer de waarde van de regeleigenschap in", + customFirst: "Cache negeren voor lokale aangepaste regels", + rulesExample: "Voorbeeld van regels", + lastPage: "Laatste pagina bereikt", + lastPageTips: "Toon tips bij het bereiken van de laatste pagina" + } + }, + { + name: "Dansk", + match: ["da"], + lang: { + enableDebug: "Aktivér fejlfindingsoutput til konsollen", + updateNotification: "Meddelelse efter opdatering af regler", + disable: "Deaktiver midlertidigt", + disableSite: "Skift deaktiveret tilstand", + disableSiteTips: "Deaktiveret på dette websted.", + enableSiteTips: "Aktiveret på dette websted.", + enable: "✅Aktivér automatisk sidevending", + tempActive: "Midlertidigt aktiv", + toTop: "Tilbage til toppen.", + toBottom: "Gå til bunden.", + current: "Nuværende side.", + forceIframe: "Tving tilslutning til næste side", + cancelForceIframe: "Annuller tvungen tilslutning", + configure: "Konfigurer Pagetual", + firstUpdate: "Klik her for at initialisere standardregellisten", + update: "Opdater onlineregler", + click2update: "Klik for at opdatere regler fra URL nu", + loadNow: "Indlæs næste automatisk", + loadConfirm: "Hvor mange sider vil du indlæse? (0 betyder uendelig)", + noNext: "Intet næste link fundet, opret en ny regel", + passSec: "Opdateret for #t# sekunder siden", + passMin: "Opdateret for #t# minutter siden", + passHour: "Opdateret for #t# timer siden", + passDay: "Opdateret for #t# dage siden", + cantDel: "Kan ikke slette indbyggede regler", + confirmDel: "Er du sikker på, at du vil slette denne regel?", + updateSucc: "Opdatering lykkedes", + beginUpdate: "Starter opdatering, vent venligst et øjeblik", + customUrls: "Importer Pagetual- eller AutoPagerize-regel-URL, én URL pr. linje.", + customRules: "Indtast brugerdefinerede regler. ✍️Bidrag med regler", + save: "Gem", + loadingText: "Indlæser...", + opacity: "Opacitet", + opacityPlaceholder: "0: skjul afstandsstykke", + hideBar: "Skjul pagineringsafstandsstykket", + hideBarButNoStop: "Skjul, men stop ikke", + dbClick2Stop: "Dobbeltklik på det tomme rum for at sætte på pause", + sortTitle: "Sortering træder i kraft efter næste regelopdatering", + autoRun: "Automatisk aktivering (sortlistetilstand)", + autoLoadNum: "Antal for forudindlæste sider", + turnRate: "Vend til næste side, når den er mindre end 【X】 gange sidehøjden fra sidefoden", + inputPageNum: "Indtast sidetal for at hoppe", + enableHistory: "Skriv browserhistorik efter sidevending", + enableHistoryAfterInsert: "Skriv browserhistorik umiddelbart efter splejsning, ellers skriv efter browsing", + contentVisibility: "Skift automatisk indholdssynlighed for at forbedre gengivelsesydelsen", + initRun: "Vend sider umiddelbart efter åbning", + preload: "Forudindlæs næste side for at fremskynde", + click2ImportRule: "Klik for at importere link til grundregler, og vent derefter, indtil opdateringen er fuldført: ", + forceAllBody: "Tilslut hele sidens krop?", + openInNewTab: "Åbn URL'er for tilføjelser i ny fane", + importSucc: "Import fuldført", + import: "Importer", + editCurrent: "Rediger regel for nuværende websted", + editBlacklist: "Rediger URL-sortlisten, én post pr. linje, understøtter [?,*] jokertegn.", + upBtnImg: "Ikon for tilbage til toppen", + downBtnImg: "Ikon for at gå til sidefod", + sideControllerIcon: "Sidebjælkeikon", + loadingTextTitle: "Indlæser", + dbClick2StopCtrl: "Ctrl-tast", + dbClick2StopAlt: "Alt-tast", + dbClick2StopShift: "Shift-tast", + dbClick2StopMeta: "Meta-tast", + dbClick2StopKey: "Genvejstast", + pageElementCss: "Brugerdefineret stil for hovedsideelementer", + customCss: "Brugerdefineret komplet CSS", + firstAlert: "Du har ikke importeret grundreglen, vælg den relevante regel at importere", + picker: "Pagetual-elementvælger", + closePicker: "Luk Pagetual-vælger", + pickerPlaceholder: "Elementvælger (Kun avancerede brugere, ellers lad være tomt)", + pickerCheck: "Kontroller vælger og kopier", + switchSelector: "Klik for at skifte element", + gotoEdit: "Gå til redigeringsregel med nuværende vælger", + manualMode: "Deaktiver splejsning, gå manuelt frem til næste side ved hjælp af højre piletast (eller send hændelsen 'pagetual.next')", + clickMode: "Deaktiver splejsning, klik automatisk på næste side, når du ruller til slutningen af siden", + pageBarMenu: "Klik på midten af sidebjælken for at åbne vælgermenuen", + nextSwitch: "Skift næste link", + arrowToScroll: "Tryk på venstre pil for at rulle tilbage og højre pil for at gå frem en side", + sideController: "Vis pagineringskontrolbjælken i sidebjælken", + sideControllerScroll: "Rul-skift", + sideControllerAlways: "Vis altid", + hideLoadingIcon: "Skjul indlæsningsanimation", + hideBarArrow: "Skjul pil for sidebjælke", + duplicate: "Duplikat Pagetual er blevet installeret, tjek din scriptmanager!", + forceStateIframe: "Integrer hele siden som en iframe", + forceStateDynamic: "Indlæs dynamisk indhold via iframe", + forceStateDisable: "Deaktiver sidevending på dette websted", + autoScrollRate: "Rullehastighed (1-1000)", + disableAutoScroll: "Stop automatisk rulning", + enableAutoScroll: "Aktivér automatisk rulning", + toggleAutoScroll: "Skift automatisk rulning", + ruleRequest: "Regelanmodning", + page: "Side ", + prevPage: "Forrige side", + nextPage: "Næste side", + errorRulesMustBeArray: "Regler skal være en matrix!", + errorJson: "JSON-fejl, tjek igen!", + editSuccess: "Redigering lykkedes", + errorWrongUrl: "Forkert URL, tjek igen!", + errorAlreadyExists: "En regel eksisterer allerede!", + settingsSaved: "Indstillingerne er gemt, opdater for at se", + iframe: "Tvunget opdelt af iframe", + dynamic: "Dynamisk indlæsning", + reloadPage: "Redigering fuldført, genindlæs nu?", + copied: "Kopieret", + noValidContent: "Intet gyldigt indhold fundet, en Captcha kan være til stede", + outOfDate: "Scriptet er forældet, opdater venligst til den nyeste version.", + hideBarTips: "Skjul pagineringsbjælken, skift til en fordybende oplevelse", + setConfigPage: "Indstil den aktuelle side som standardkonfigurationsside", + wedata2github: "Skift wedata-adressen til spejladressen i github-depotet", + addOtherProp: "Tilføj regelegenskaber", + addNextSelector: "Tilføj vælgerindhold som nextLink", + addPageSelector: "Tilføj vælgerindhold som pageElement", + propName: "Indtast regelegenskabsnavn", + propValue: "Indtast regelegenskabsværdi", + customFirst: "Ignorer cache for lokale brugerdefinerede regler", + rulesExample: "Regeleksempel", + lastPage: "Nåede den sidste side", + lastPageTips: "Vis tips, når du når den sidste side" + } + }, + { + name: "Français (Canada)", + match: ["fr-CA"], + lang: { + enableDebug: "Activer la sortie de débogage dans la console", + updateNotification: "Notification après la mise à jour des règles", + disable: "Désactiver temporairement", + disableSite: "Basculer l'état de désactivation", + disableSiteTips: "Désactivé sur ce site.", + enableSiteTips: "Activé sur ce site.", + enable: "✅Activer le changement de page automatique", + tempActive: "Temporairement actif", + toTop: "Retour en haut.", + toBottom: "Aller en bas.", + current: "Page actuelle.", + forceIframe: "Forcer la jonction de la page suivante", + cancelForceIframe: "Annuler la jonction forcée", + configure: "Configurer Pagetual", + firstUpdate: "Cliquez ici pour initialiser la liste de règles par défaut", + update: "Mettre à jour les règles en ligne", + click2update: "Cliquez pour mettre à jour les règles depuis l'URL maintenant", + loadNow: "Charger la suite automatiquement", + loadConfirm: "Combien de pages voulez-vous charger ? (0 pour infini)", + noNext: "Aucun lien suivant trouvé, veuillez créer une nouvelle règle", + passSec: "Mis à jour il y a #t# secondes", + passMin: "Mis à jour il y a #t# minutes", + passHour: "Mis à jour il y a #t# heures", + passDay: "Mis à jour il y a #t# jours", + cantDel: "Impossible de supprimer les règles intégrées", + confirmDel: "Êtes-vous sûr de vouloir supprimer cette règle ?", + updateSucc: "Mise à jour réussie", + beginUpdate: "Début de la mise à jour, veuillez patienter", + customUrls: "Importer l'URL des règles Pagetual ou AutoPagerize, une URL par ligne.", + customRules: "Saisir des règles personnalisées. ✍️Contribuer aux règles", + save: "Enregistrer", + loadingText: "Chargement en cours...", + opacity: "Opacité", + opacityPlaceholder: "0 : masquer le séparateur", + hideBar: "Masquer le séparateur de pagination", + hideBarButNoStop: "Masquer mais ne pas arrêter", + dbClick2Stop: "Double-cliquez sur l'espace vide pour mettre en pause", + sortTitle: "Le tri prendra effet après la prochaine mise à jour des règles", + autoRun: "Activation automatique (mode liste noire)", + autoLoadNum: "Nombre de pages à précharger", + turnRate: "Passer à la page suivante lorsqu'elle est à moins de 【X】 fois la hauteur de la page du pied de page", + inputPageNum: "Entrez le numéro de page pour y accéder", + enableHistory: "Inscrire l'historique de navigation après le changement de page", + enableHistoryAfterInsert: "Inscrire l'historique de navigation immédiatement après la jonction, sinon après la navigation", + contentVisibility: "Basculer automatiquement content-visibility pour améliorer les performances de rendu", + initRun: "Changer de page immédiatement après l'ouverture", + preload: "Précharger la page suivante pour accélérer", + click2ImportRule: "Cliquez pour importer le lien des règles de base, puis attendez la fin de la mise à jour : ", + forceAllBody: "Joindre le corps complet de la page ?", + openInNewTab: "Ouvrir les URL ajoutées dans un nouvel onglet", + importSucc: "Importation terminée", + import: "Importer", + editCurrent: "Modifier la règle pour le site actuel", + editBlacklist: "Modifier la liste noire d'URL, une entrée par ligne, supporte les jokers [?,*].", + upBtnImg: "Icône de retour en haut", + downBtnImg: "Icône d'aller en bas de page", + sideControllerIcon: "Icône de la barre latérale", + loadingTextTitle: "Chargement", + dbClick2StopCtrl: "Touche Ctrl", + dbClick2StopAlt: "Touche Alt", + dbClick2StopShift: "Touche Maj", + dbClick2StopMeta: "Touche Méta", + dbClick2StopKey: "Touche de raccourci", + pageElementCss: "Style personnalisé pour les éléments principaux de la page", + customCss: "CSS complet personnalisé", + firstAlert: "Vous n'avez pas importé la règle de base, veuillez sélectionner la règle appropriée à importer", + picker: "Sélecteur d'éléments Pagetual", + closePicker: "Fermer le sélecteur Pagetual", + pickerPlaceholder: "Sélecteur d'élément (Utilisateurs avancés seulement, laissez vide sinon)", + pickerCheck: "Vérifier le sélecteur et copier", + switchSelector: "Cliquez pour changer d'élément", + gotoEdit: "Aller à l'édition de la règle avec le sélecteur actuel", + manualMode: "Désactiver la jonction, avancer manuellement à la page suivante avec la flèche droite (ou envoyer l'événement 'pagetual.next')", + clickMode: "Désactiver la jonction, cliquer automatiquement sur la page suivante en faisant défiler jusqu'à la fin de la page", + pageBarMenu: "Cliquez au centre de la barre de page pour ouvrir le menu du sélecteur", + nextSwitch: "Changer de lien suivant", + arrowToScroll: "Appuyez sur la flèche gauche pour revenir en arrière et la flèche droite pour avancer", + sideController: "Afficher la barre de contrôle de pagination dans la barre latérale", + sideControllerScroll: "Afficher au défilement", + sideControllerAlways: "Toujours afficher", + hideLoadingIcon: "Masquer l'animation de chargement", + hideBarArrow: "Masquer les flèches de la barre de page", + duplicate: "Un doublon de Pagetual a été installé, vérifiez votre gestionnaire de scripts !", + forceStateIframe: "Intégrer la page complète en tant qu'iframe", + forceStateDynamic: "Charger le contenu dynamique via iframe", + forceStateDisable: "Désactiver le changement de page sur ce site", + autoScrollRate: "Vitesse de défilement (1~1000)", + disableAutoScroll: "Arrêter le défilement auto", + enableAutoScroll: "Activer le défilement auto", + toggleAutoScroll: "Basculer le défilement auto", + ruleRequest: "Demande de règle", + page: "Page ", + prevPage: "Page préc.", + nextPage: "Page suiv.", + errorRulesMustBeArray: "Les règles doivent être un tableau (Array) !", + errorJson: "Erreur JSON, veuillez vérifier !", + editSuccess: "Modification réussie", + errorWrongUrl: "URL incorrecte, veuillez vérifier !", + errorAlreadyExists: "Une règle existe déjà !", + settingsSaved: "Les paramètres sont enregistrés, actualisez pour voir les changements", + iframe: "Division forcée par iframe", + dynamic: "Chargement dynamique", + reloadPage: "Modification terminée, recharger maintenant ?", + copied: "Copié", + noValidContent: "Aucun contenu valide détecté, un Captcha peut être présent", + outOfDate: "Le script est obsolète, veuillez mettre à jour vers la dernière version.", + hideBarTips: "Masquer la barre de pagination, basculer l'expérience immersive", + setConfigPage: "Définir la page actuelle comme page de configuration par défaut", + wedata2github: "Changer l'adresse wedata pour l'adresse miroir dans le dépôt github", + addOtherProp: "Ajouter des propriétés à la règle", + addNextSelector: "Ajouter le contenu du sélecteur comme nextLink", + addPageSelector: "Ajouter le contenu du sélecteur comme pageElement", + propName: "Entrez le nom de la propriété de la règle", + propValue: "Entrez la valeur de la propriété de la règle", + customFirst: "Ignorer le cache pour les règles personnalisées locales", + rulesExample: "Exemple de règles", + lastPage: "Dernière page atteinte", + lastPageTips: "Afficher une notification en atteignant la dernière page" + } + }, + { + name: "Bahasa Indonesia", + match: ["id"], + lang: { + enableDebug: "Aktifkan output debug ke konsol", + updateNotification: "Notifikasi setelah aturan diperbarui", + disable: "Nonaktifkan sementara", + disableSite: "Ubah status nonaktif", + disableSiteTips: "Dinonaktifkan di situs ini.", + enableSiteTips: "Diaktifkan di situs ini.", + enable: "✅Aktifkan pembalik halaman otomatis", + tempActive: "Aktif sementara", + toTop: "Kembali ke Atas.", + toBottom: "Pergi ke Bawah.", + current: "Halaman Saat Ini.", + forceIframe: "Paksa untuk menggabungkan halaman berikutnya", + cancelForceIframe: "Batalkan penggabungan paksa", + configure: "Konfigurasi Pagetual", + firstUpdate: "Klik di sini untuk menginisialisasi daftar aturan default", + update: "Perbarui aturan online", + click2update: "Klik untuk memperbarui aturan dari URL sekarang", + loadNow: "Muat berikutnya secara otomatis", + loadConfirm: "Berapa halaman yang ingin Anda muat? (0 berarti tak terbatas)", + noNext: "Tautan berikutnya tidak ditemukan, harap buat aturan baru", + passSec: "Diperbarui #t# detik yang lalu", + passMin: "Diperbarui #t# menit yang lalu", + passHour: "Diperbarui #t# jam yang lalu", + passDay: "Diperbarui #t# hari yang lalu", + cantDel: "Tidak dapat menghapus aturan bawaan", + confirmDel: "Apakah Anda yakin ingin menghapus aturan ini?", + updateSucc: "Pembaruan berhasil", + beginUpdate: "Memulai pembaruan, harap tunggu sebentar", + customUrls: "Impor URL aturan Pagetual atau AutoPagerize, satu URL per baris.", + customRules: "Masukkan aturan khusus. ✍️Kontribusi aturan", + save: "Simpan", + loadingText: "Sedang Memuat...", + opacity: "Opasitas", + opacityPlaceholder: "0: sembunyikan pemisah", + hideBar: "Sembunyikan pemisah paginasi", + hideBarButNoStop: "Sembunyikan tapi jangan hentikan", + dbClick2Stop: "Klik dua kali pada ruang kosong untuk menjeda", + sortTitle: "Pengurutan berlaku setelah pembaruan aturan berikutnya", + autoRun: "Aktifkan otomatis (mode daftar hitam)", + autoLoadNum: "Jumlah halaman pramuat", + turnRate: "Buka halaman berikutnya saat jarak dari footer kurang dari 【X】 kali tinggi halaman", + inputPageNum: "Masukkan nomor halaman untuk melompat", + enableHistory: "Tulis riwayat penjelajahan setelah membalik halaman", + enableHistoryAfterInsert: "Tulis riwayat penjelajahan segera setelah penyambungan, jika tidak, tulis setelah menjelajah", + contentVisibility: "Secara otomatis mengganti content-visibility untuk meningkatkan kinerja rendering", + initRun: "Balik halaman segera setelah dibuka", + preload: "Pramuat halaman berikutnya untuk mempercepat", + click2ImportRule: "Klik untuk mengimpor tautan aturan dasar, lalu tunggu hingga pembaruan selesai: ", + forceAllBody: "Gabungkan seluruh badan halaman?", + openInNewTab: "Buka URL tambahan di tab baru", + importSucc: "Impor selesai", + import: "Impor", + editCurrent: "Edit aturan untuk situs web saat ini", + editBlacklist: "Edit daftar hitam URL, satu entri per baris, Mendukung wildcard [?,*].", + upBtnImg: "Ikon kembali ke atas", + downBtnImg: "Ikon pergi ke footer", + sideControllerIcon: "Ikon bilah sisi", + loadingTextTitle: "Memuat", + dbClick2StopCtrl: "Tombol Ctrl", + dbClick2StopAlt: "Tombol Alt", + dbClick2StopShift: "Tombol Shift", + dbClick2StopMeta: "Tombol Meta", + dbClick2StopKey: "Tombol pintas", + pageElementCss: "Gaya kustom untuk elemen halaman utama", + customCss: "CSS kustom lengkap", + firstAlert: "Anda belum mengimpor aturan dasar, silakan pilih aturan yang sesuai untuk diimpor", + picker: "Pemilih elemen Pagetual", + closePicker: "Tutup pemilih Pagetual", + pickerPlaceholder: "Selektor elemen, (Hanya pengguna tingkat lanjut, biarkan kosong jika tidak)", + pickerCheck: "Periksa selektor dan salin", + switchSelector: "Klik untuk mengganti elemen", + gotoEdit: "Pergi ke edit aturan dengan selektor saat ini", + manualMode: "Nonaktifkan penyambungan, maju ke halaman berikutnya secara manual menggunakan tombol panah kanan (atau kirim acara 'pagetual.next')", + clickMode: "Nonaktifkan penyambungan, klik halaman berikutnya secara otomatis saat menggulir ke akhir halaman", + pageBarMenu: "Klik tengah bilah halaman untuk membuka menu pemilih", + nextSwitch: "Ganti tautan berikutnya", + arrowToScroll: "Tekan panah kiri untuk menggulir ke belakang dan panah kanan untuk maju halaman", + sideController: "Tampilkan bilah kontrol paging di bilah sisi", + sideControllerScroll: "Tampilkan saat bergulir", + sideControllerAlways: "Selalu tampilkan", + hideLoadingIcon: "Sembunyikan animasi memuat", + hideBarArrow: "Sembunyikan panah untuk bilah halaman", + duplicate: "Pagetual duplikat telah diinstal, periksa manajer skrip Anda!", + forceStateIframe: "Sematkan halaman penuh sebagai iframe", + forceStateDynamic: "Muat konten dinamis melalui iframe", + forceStateDisable: "Nonaktifkan pembalik halaman di situs ini", + autoScrollRate: "Kecepatan gulir (1~1000)", + disableAutoScroll: "Hentikan Gulir Otomatis", + enableAutoScroll: "Aktifkan Gulir Otomatis", + toggleAutoScroll: "Ubah Gulir Otomatis", + ruleRequest: "Permintaan Aturan", + page: "Halaman ", + prevPage: "Halaman seb.", + nextPage: "Halaman ber.", + errorRulesMustBeArray: "Aturan harus berupa Array!", + errorJson: "Kesalahan JSON, Periksa lagi!", + editSuccess: "Berhasil diedit", + errorWrongUrl: "URL salah, Periksa lagi!", + errorAlreadyExists: "Aturan sudah ada!", + settingsSaved: "Pengaturan disimpan, segarkan untuk melihat", + iframe: "Pemisahan paksa oleh iframe", + dynamic: "Pemuatan dinamis", + reloadPage: "Pengeditan selesai, muat ulang sekarang?", + copied: "Disalin", + noValidContent: "Tidak ada konten valid yang terdeteksi, mungkin ada Captcha", + outOfDate: "Skrip sudah usang, harap perbarui ke versi terbaru.", + hideBarTips: "Sembunyikan bilah paginasi, alihkan pengalaman imersif", + setConfigPage: "Atur halaman saat ini sebagai halaman konfigurasi default", + wedata2github: "Ubah alamat wedata ke alamat cermin di repositori github", + addOtherProp: "Tambahkan properti aturan", + addNextSelector: "Tambahkan konten selektor sebagai nextLink", + addPageSelector: "Tambahkan konten selektor sebagai pageElement", + propName: "Masukkan nama properti aturan", + propValue: "Masukkan nilai properti aturan", + customFirst: "Abaikan cache untuk aturan kustom lokal", + rulesExample: "Contoh Aturan", + lastPage: "Telah mencapai halaman terakhir", + lastPageTips: "Tampilkan tip saat mencapai halaman terakhir" + } + }, { // Traduzido por Thiago Ramos (thiagojramos@outlook.com). name: "Português (Brasil)", @@ -531,6 +3135,254 @@ lastPageTips: "Mostrar dicas ao atingir a última página" } }, + { + name: "Français", + match: ["fr"], + lang: { + enableDebug: "Activer la sortie de débogage dans la console", + updateNotification: "Notification après la mise à jour des règles", + disable: "Désactiver temporairement", + disableSite: "Basculer l'état de désactivation", + disableSiteTips: "Désactivé sur ce site.", + enableSiteTips: "Activé sur ce site.", + enable: "✅Activer le changement de page automatique", + tempActive: "Temporairement actif", + toTop: "Retour en haut.", + toBottom: "Aller en bas.", + current: "Page actuelle.", + forceIframe: "Forcer la jonction de la page suivante", + cancelForceIframe: "Annuler la jonction forcée", + configure: "Configurer Pagetual", + firstUpdate: "Cliquez ici pour initialiser la liste de règles par défaut", + update: "Mettre à jour les règles en ligne", + click2update: "Cliquez pour mettre à jour les règles depuis l'URL maintenant", + loadNow: "Charger la suite automatiquement", + loadConfirm: "Combien de pages voulez-vous charger ? (0 pour infini)", + noNext: "Aucun lien suivant trouvé, veuillez créer une nouvelle règle", + passSec: "Mis à jour il y a #t# secondes", + passMin: "Mis à jour il y a #t# minutes", + passHour: "Mis à jour il y a #t# heures", + passDay: "Mis à jour il y a #t# jours", + cantDel: "Impossible de supprimer les règles intégrées", + confirmDel: "Êtes-vous sûr de vouloir supprimer cette règle ?", + updateSucc: "Mise à jour réussie", + beginUpdate: "Début de la mise à jour, veuillez patienter", + customUrls: "Importer l'URL des règles Pagetual ou AutoPagerize, une URL par ligne.", + customRules: "Saisir des règles personnalisées. ✍️Contribuer aux règles", + save: "Enregistrer", + loadingText: "Chargement en cours...", + opacity: "Opacité", + opacityPlaceholder: "0 : masquer le séparateur", + hideBar: "Masquer le séparateur de pagination", + hideBarButNoStop: "Masquer mais ne pas arrêter", + dbClick2Stop: "Double-cliquez sur l'espace vide pour mettre en pause", + sortTitle: "Le tri prendra effet après la prochaine mise à jour des règles", + autoRun: "Activation automatique (mode liste noire)", + autoLoadNum: "Nombre de pages à précharger", + turnRate: "Passer à la page suivante lorsqu'elle est à moins de 【X】 fois la hauteur de la page du pied de page", + inputPageNum: "Entrez le numéro de page pour y accéder", + enableHistory: "Inscrire l'historique de navigation après le changement de page", + enableHistoryAfterInsert: "Inscrire l'historique de navigation immédiatement après la jonction, sinon après la navigation", + contentVisibility: "Basculer automatiquement content-visibility pour améliorer les performances de rendu", + initRun: "Changer de page immédiatement après l'ouverture", + preload: "Précharger la page suivante pour accélérer", + click2ImportRule: "Cliquez pour importer le lien des règles de base, puis attendez la fin de la mise à jour : ", + forceAllBody: "Joindre le corps complet de la page ?", + openInNewTab: "Ouvrir les URL ajoutées dans un nouvel onglet", + importSucc: "Importation terminée", + import: "Importer", + editCurrent: "Modifier la règle pour le site actuel", + editBlacklist: "Modifier la liste noire d'URL, une entrée par ligne, supporte les jokers [?,*].", + upBtnImg: "Icône de retour en haut", + downBtnImg: "Icône d'aller en bas de page", + sideControllerIcon: "Icône de la barre latérale", + loadingTextTitle: "Chargement", + dbClick2StopCtrl: "Touche Ctrl", + dbClick2StopAlt: "Touche Alt", + dbClick2StopShift: "Touche Maj", + dbClick2StopMeta: "Touche Méta", + dbClick2StopKey: "Touche de raccourci", + pageElementCss: "Style personnalisé pour les éléments principaux de la page", + customCss: "CSS complet personnalisé", + firstAlert: "Vous n'avez pas importé la règle de base, veuillez sélectionner la règle appropriée à importer", + picker: "Sélecteur d'éléments Pagetual", + closePicker: "Fermer le sélecteur Pagetual", + pickerPlaceholder: "Sélecteur d'élément (Utilisateurs avancés seulement, laissez vide sinon)", + pickerCheck: "Vérifier le sélecteur et copier", + switchSelector: "Cliquez pour changer d'élément", + gotoEdit: "Aller à l'édition de la règle avec le sélecteur actuel", + manualMode: "Désactiver la jonction, avancer manuellement à la page suivante avec la flèche droite (ou envoyer l'événement 'pagetual.next')", + clickMode: "Désactiver la jonction, cliquer automatiquement sur la page suivante en faisant défiler jusqu'à la fin de la page", + pageBarMenu: "Cliquez au centre de la barre de page pour ouvrir le menu du sélecteur", + nextSwitch: "Changer de lien suivant", + arrowToScroll: "Appuyez sur la flèche gauche pour revenir en arrière et la flèche droite pour avancer", + sideController: "Afficher la barre de contrôle de pagination dans la barre latérale", + sideControllerScroll: "Afficher au défilement", + sideControllerAlways: "Toujours afficher", + hideLoadingIcon: "Masquer l'animation de chargement", + hideBarArrow: "Masquer les flèches de la barre de page", + duplicate: "Un doublon de Pagetual a été installé, vérifiez votre gestionnaire de scripts !", + forceStateIframe: "Intégrer la page complète en tant qu'iframe", + forceStateDynamic: "Charger le contenu dynamique via iframe", + forceStateDisable: "Désactiver le changement de page sur ce site", + autoScrollRate: "Vitesse de défilement (1~1000)", + disableAutoScroll: "Arrêter le défilement auto", + enableAutoScroll: "Activer le défilement auto", + toggleAutoScroll: "Basculer le défilement auto", + ruleRequest: "Demande de règle", + page: "Page ", + prevPage: "Page préc.", + nextPage: "Page suiv.", + errorRulesMustBeArray: "Les règles doivent être un tableau (Array) !", + errorJson: "Erreur JSON, veuillez vérifier !", + editSuccess: "Modification réussie", + errorWrongUrl: "URL incorrecte, veuillez vérifier !", + errorAlreadyExists: "Une règle existe déjà !", + settingsSaved: "Les paramètres sont enregistrés, actualisez pour voir les changements", + iframe: "Division forcée par iframe", + dynamic: "Chargement dynamique", + reloadPage: "Modification terminée, recharger maintenant ?", + copied: "Copié", + noValidContent: "Aucun contenu valide détecté, un Captcha peut être présent", + outOfDate: "Le script est obsolète, veuillez mettre à jour vers la dernière version.", + hideBarTips: "Masquer la barre de pagination, basculer l'expérience immersive", + setConfigPage: "Définir la page actuelle comme page de configuration par défaut", + wedata2github: "Changer l'adresse wedata pour l'adresse miroir dans le dépôt github", + addOtherProp: "Ajouter des propriétés à la règle", + addNextSelector: "Ajouter le contenu du sélecteur comme nextLink", + addPageSelector: "Ajouter le contenu du sélecteur comme pageElement", + propName: "Entrez le nom de la propriété de la règle", + propValue: "Entrez la valeur de la propriété de la règle", + customFirst: "Ignorer le cache pour les règles personnalisées locales", + rulesExample: "Exemple de règles", + lastPage: "Dernière page atteinte", + lastPageTips: "Afficher une notification en atteignant la dernière page" + } + }, + { + name: "Italiano", + match: ["it"], + lang: { + enableDebug: "Abilita output di debug nella console", + updateNotification: "Notifica dopo l'aggiornamento delle regole", + disable: "Disabilita temporaneamente", + disableSite: "Attiva/disattiva lo stato di disabilitazione", + disableSiteTips: "Disabilitato su questo sito.", + enableSiteTips: "Abilitato su questo sito.", + enable: "✅Abilita il cambio pagina automatico", + tempActive: "Temporaneamente attivo", + toTop: "Torna in cima.", + toBottom: "Vai in fondo.", + current: "Pagina corrente.", + forceIframe: "Forza l'unione della pagina successiva", + cancelForceIframe: "Annulla unione forzata", + configure: "Configura Pagetual", + firstUpdate: "Clicca qui per inizializzare l'elenco delle regole predefinite", + update: "Aggiorna regole online", + click2update: "Clicca per aggiornare le regole dall'URL ora", + loadNow: "Carica la prossima automaticamente", + loadConfirm: "Quante pagine vuoi caricare? (0 significa infinite)", + noNext: "Nessun link 'successivo' trovato, crea una nuova regola", + passSec: "Aggiornato #t# secondi fa", + passMin: "Aggiornato #t# minuti fa", + passHour: "Aggiornato #t# ore fa", + passDay: "Aggiornato #t# giorni fa", + cantDel: "Impossibile eliminare le regole predefinite", + confirmDel: "Sei sicuro di voler eliminare questa regola?", + updateSucc: "Aggiornamento riuscito", + beginUpdate: "Inizio aggiornamento, attendere un momento prego", + customUrls: "Importa URL di regole Pagetual o AutoPagerize, un URL per riga.", + customRules: "Inserisci regole personalizzate. ✍️Contribuisci alle regole", + save: "Salva", + loadingText: "Caricamento in corso...", + opacity: "Opacità", + opacityPlaceholder: "0: nascondi separatore", + hideBar: "Nascondi il separatore di pagina", + hideBarButNoStop: "Nascondi ma non fermare", + dbClick2Stop: "Fai doppio clic sullo spazio vuoto per mettere in pausa", + sortTitle: "L'ordinamento avrà effetto dopo il prossimo aggiornamento delle regole", + autoRun: "Abilitazione automatica (modalità lista nera)", + autoLoadNum: "Numero di pagine da precaricare", + turnRate: "Gira alla pagina successiva quando la distanza dal fondo è inferiore a 【X】 volte l'altezza della pagina", + inputPageNum: "Inserisci il numero di pagina a cui saltare", + enableHistory: "Scrivi la cronologia di navigazione dopo aver girato pagina", + enableHistoryAfterInsert: "Scrivi la cronologia subito dopo l'unione, altrimenti dopo la navigazione", + contentVisibility: "Cambia automaticamente content-visibility per migliorare le prestazioni di rendering", + initRun: "Inizia a girare le pagine subito dopo l'apertura", + preload: "Precarica la pagina successiva per velocizzare", + click2ImportRule: "Clicca per importare il link delle regole di base, poi attendi il completamento dell'aggiornamento: ", + forceAllBody: "Unire l'intero corpo della pagina?", + openInNewTab: "Apri gli URL aggiunti in una nuova scheda", + importSucc: "Importazione completata", + import: "Importa", + editCurrent: "Modifica regola per il sito corrente", + editBlacklist: "Modifica la lista nera di URL, una voce per riga, supporta i caratteri jolly [?,*].", + upBtnImg: "Icona per tornare in cima", + downBtnImg: "Icona per andare in fondo", + sideControllerIcon: "Icona della barra laterale", + loadingTextTitle: "Caricamento", + dbClick2StopCtrl: "Tasto Ctrl", + dbClick2StopAlt: "Tasto Alt", + dbClick2StopShift: "Tasto Maiusc", + dbClick2StopMeta: "Tasto Meta", + dbClick2StopKey: "Tasto di scelta rapida", + pageElementCss: "Stile personalizzato per gli elementi principali della pagina", + customCss: "CSS completo personalizzato", + firstAlert: "Non hai importato la regola di base, seleziona la regola appropriata da importare", + picker: "Selettore di elementi Pagetual", + closePicker: "Chiudi selettore Pagetual", + pickerPlaceholder: "Selettore di elementi (Solo utenti esperti, altrimenti lasciare vuoto)", + pickerCheck: "Controlla selettore e copia", + switchSelector: "Clicca per cambiare elemento", + gotoEdit: "Vai alla modifica della regola con il selettore corrente", + manualMode: "Disabilita unione, avanza manualmente alla pagina successiva con la freccia destra (o invia l'evento 'pagetual.next')", + clickMode: "Disabilita unione, clicca automaticamente la pagina successiva scorrendo fino alla fine", + pageBarMenu: "Clicca al centro della barra di pagina per aprire il menu del selettore", + nextSwitch: "Cambia link successivo", + arrowToScroll: "Premi freccia sinistra per scorrere indietro e freccia destra per avanzare di pagina", + sideController: "Mostra la barra di controllo della paginazione nella barra laterale", + sideControllerScroll: "Mostra durante lo scorrimento", + sideControllerAlways: "Mostra sempre", + hideLoadingIcon: "Nascondi animazione di caricamento", + hideBarArrow: "Nascondi frecce della barra di pagina", + duplicate: "È stato installato un duplicato di Pagetual, controlla il tuo gestore di script!", + forceStateIframe: "Incorpora la pagina intera come iframe", + forceStateDynamic: "Carica contenuto dinamico tramite iframe", + forceStateDisable: "Disabilita il cambio pagina su questo sito", + autoScrollRate: "Velocità di scorrimento (1~1000)", + disableAutoScroll: "Ferma scorrimento automatico", + enableAutoScroll: "Abilita scorrimento automatico", + toggleAutoScroll: "Attiva/disattiva scorrimento automatico", + ruleRequest: "Richiesta regola", + page: "Pagina ", + prevPage: "Pagina prec.", + nextPage: "Pagina succ.", + errorRulesMustBeArray: "Le regole devono essere un Array!", + errorJson: "Errore JSON, controlla di nuovo!", + editSuccess: "Modificato con successo", + errorWrongUrl: "URL errato, controlla di nuovo!", + errorAlreadyExists: "Una regola esiste già!", + settingsSaved: "Le impostazioni sono state salvate, aggiorna per visualizzare", + iframe: "Divisione forzata tramite iframe", + dynamic: "Caricamento dinamico", + reloadPage: "Modifica completata, ricaricare ora?", + copied: "Copiato", + noValidContent: "Nessun contenuto valido rilevato, potrebbe esserci un Captcha", + outOfDate: "Lo script non è aggiornato, si prega di aggiornare all'ultima versione.", + hideBarTips: "Nascondi la barra di paginazione, attiva/disattiva l'esperienza immersiva", + setConfigPage: "Imposta la pagina corrente come pagina di configurazione predefinita", + wedata2github: "Cambia l'indirizzo wedata con l'indirizzo mirror nel repository github", + addOtherProp: "Aggiungi proprietà alla regola", + addNextSelector: "Aggiungi contenuto del selettore come nextLink", + addPageSelector: "Aggiungi contenuto del selettore come pageElement", + propName: "Inserisci il nome della proprietà della regola", + propValue: "Inserisci il valore della proprietà della regola", + customFirst: "Ignora la cache per le regole personalizzate locali", + rulesExample: "Esempio di regole", + lastPage: "Raggiunta l'ultima pagina", + lastPageTips: "Mostra un avviso quando si raggiunge l'ultima pagina" + } + }, { // Translated by SrKalopsia (srkalopsia@gmail.com). name: "Español", From 7d3f26d8a7b8a9e05607894c8526fb4ecfcd6d04 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 28 Aug 2025 19:18:21 +0900 Subject: [PATCH 030/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 7838725e7f5..ced954d0c55 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -9157,10 +9157,15 @@ if (ruleImportUrlReg.test(href) || inConfig) { let importing = false; if (!inUpdate && rulesData.uninited) { - setTimeout(() => { - if (!inUpdate && !importing) showTips(i18n("firstAlert")); - }, 3000); - showTips(i18n("firstAlert")); + let showTimes = 0; + let showFirstAlert = () => { + if (inUpdate || importing || ++showTimes > 5) return; + showTips(i18n("firstAlert"), configPage[0], 2000); + setTimeout(() => { + showFirstAlert(); + }, 3000); + }; + showFirstAlert(); } let defaultOption = document.querySelector('#discussion_rating_4'); if (defaultOption) defaultOption.checked = true; From 6bc4e318669cfa8f992963e1668d6a1f42f8c548 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 29 Aug 2025 08:46:05 +0900 Subject: [PATCH 031/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 101 ++++++++++++++-------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index d088c6e3f83..9896ffa84e6 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.8.22.1 +// @version 2025.8.29.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 @@ -25323,6 +25323,32 @@ ImgOps | https://imgops.com/#b#`; }; } } + var checkUniqueImgWin = function() { + if (canPreview) { + if (result.type != "link" && result.type != "rule" && result.src == result.imgSrc) { + if (result.imgAS.w < result.imgCS.w * 1.6 && result.imgAS.h < result.imgCS.h * 1.6) { + if (result.img && result.img.childElementCount) { + if (result.type == "force") return false; + if (prefs.floatBar.globalkeys.invertInitShow) return false; + } + var wSize = getWindowSize(); + if (prefs.floatBar.globalkeys.invertInitShow && result.imgAS.w <= wSize.w && result.imgAS.h <= wSize.h) return false; + } + } + uniqueImgWinInitX = clientX; + uniqueImgWinInitY = clientY; + if (uniqueImgWin && !uniqueImgWin.removed) { + if (uniqueImgWin.src == result.src) return true; + uniqueImgWin.remove(); + } + waitUntilMove(_target, () => { + new LoadingAnimC(result, 'popup', prefs.waitImgLoad, prefs.framesPicOpenInTopWindow); + }); + return true; + } else { + return false; + } + }; if (!result) { if (target.nodeName.toUpperCase() != 'IMG' && target.dataset.role == "img") { let img = target.parentNode.querySelector('img'); @@ -25340,13 +25366,34 @@ ImgOps | https://imgops.com/#b#`; }; } } else if (target.nodeName.toUpperCase() != 'IMG') { + if (selectionClientRect && + clientX > selectionClientRect.left && + clientX < selectionClientRect.left + selectionClientRect.width && + clientY > selectionClientRect.top && + clientY < selectionClientRect.top + selectionClientRect.height) { + result = { + src: selectionStr, + type: "link", + imgSrc: selectionStr, + noActual:true, + img: target + }; + checkUniqueImgWin(); + + if (!floatBar) { + floatBar = new FloatBarC(); + } + floatBar.start(result); + + return; + } let found = false; if (target.nodeName.toUpperCase() == "AREA") target = target.parentNode; var broEle, broImg; if (target.nodeName.toUpperCase() != 'A' && target.parentNode && target.parentNode.style && !/flex|grid|table/.test(getComputedStyle(target.parentNode).display)) { broEle = target.previousElementSibling; while (broEle) { - if (broEle.offsetWidth) { + if (broEle.offsetWidth && broEle.offsetWidth > target.offsetWidth>>1) { if (broEle.nodeName == "IMG") broImg = broEle; else if (broEle.nodeName == "PICTURE") broImg = broEle.querySelector("img"); } @@ -25357,7 +25404,7 @@ ImgOps | https://imgops.com/#b#`; else if (!broEle) { broEle = target.nextElementSibling; while (broEle) { - if (broEle.offsetWidth) { + if (broEle.offsetWidth && broEle.offsetWidth > target.offsetWidth>>1) { if (broEle.nodeName == "IMG") broImg = broEle; else if (broEle.nodeName == "PICTURE") broImg = broEle.querySelector("img"); } @@ -25582,56 +25629,8 @@ ImgOps | https://imgops.com/#b#`; } } } - var checkUniqueImgWin = function() { - if (canPreview) { - if (result.type != "link" && result.type != "rule" && result.src == result.imgSrc) { - if (result.imgAS.w < result.imgCS.w * 1.6 && result.imgAS.h < result.imgCS.h * 1.6) { - if (result.img && result.img.childElementCount) { - if (result.type == "force") return false; - if (prefs.floatBar.globalkeys.invertInitShow) return false; - } - var wSize = getWindowSize(); - if (prefs.floatBar.globalkeys.invertInitShow && result.imgAS.w <= wSize.w && result.imgAS.h <= wSize.h) return false; - } - } - uniqueImgWinInitX = clientX; - uniqueImgWinInitY = clientY; - if (uniqueImgWin && !uniqueImgWin.removed) { - if (uniqueImgWin.src == result.src) return true; - uniqueImgWin.remove(); - } - waitUntilMove(_target, () => { - new LoadingAnimC(result, 'popup', prefs.waitImgLoad, prefs.framesPicOpenInTopWindow); - }); - return true; - } else { - return false; - } - }; if (!result && target.nodeName.toUpperCase() != 'IMG') { - if (selectionClientRect && - clientX > selectionClientRect.left && - clientX < selectionClientRect.left + selectionClientRect.width && - clientY > selectionClientRect.top && - clientY < selectionClientRect.top + selectionClientRect.height) { - result = { - src: selectionStr, - type: "link", - imgSrc: selectionStr, - noActual:true, - img: target - }; - checkUniqueImgWin(); - - if (!floatBar) { - floatBar = new FloatBarC(); - } - floatBar.start(result); - - return; - } - let i = 0; while(target) { if (i++ > 5) { From 752e72345214c66be0114389672b4e95ef83df0a Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 1 Sep 2025 10:54:08 +0900 Subject: [PATCH 032/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 9896ffa84e6..5a6bd37936c 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.8.29.1 +// @version 2025.9.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 @@ -25324,16 +25324,25 @@ ImgOps | https://imgops.com/#b#`; } } var checkUniqueImgWin = function() { - if (canPreview) { - if (result.type != "link" && result.type != "rule" && result.src == result.imgSrc) { - if (result.imgAS.w < result.imgCS.w * 1.6 && result.imgAS.h < result.imgCS.h * 1.6) { - if (result.img && result.img.childElementCount) { - if (result.type == "force") return false; - if (prefs.floatBar.globalkeys.invertInitShow) return false; + let invert = !canPreview && prefs.floatBar.globalkeys.invertInitShow && prefs.floatBar.globalkeys.type == "hold"; + if (canPreview || invert) { + let forceShow = (() => { + if (result.type != "link" && result.type != "rule" && result.src == result.imgSrc) { + if (result.imgAS.w < result.imgCS.w * 1.3 && result.imgAS.h < result.imgCS.h * 1.3) { + if (result.img && result.img.childElementCount) { + if (result.type == "force") return false; + if (prefs.floatBar.globalkeys.invertInitShow) return false; + } + var wSize = getWindowSize(); + if (prefs.floatBar.globalkeys.invertInitShow && result.imgAS.w <= wSize.w && result.imgAS.h <= wSize.h) return false; } - var wSize = getWindowSize(); - if (prefs.floatBar.globalkeys.invertInitShow && result.imgAS.w <= wSize.w && result.imgAS.h <= wSize.h) return false; } + return true; + })(); + if (forceShow) { + if (invert) return false; + } else { + if (!invert) return false; } uniqueImgWinInitX = clientX; uniqueImgWinInitY = clientY; From b6146bfe083c9c4053ab97fbed4959ed6135cfb5 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 1 Sep 2025 20:34:04 +0900 Subject: [PATCH 033/252] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index f6782e7b598..d7e487e9cbf 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1981,6 +1981,12 @@ var siteInfo = [ r: "///qtn(\\..*)\\.\\w+\\.webp/i", s: "//w$1.webp" }, + { + name: "hentaizap", + url: /\bhentaizap\.com\//, + r: /t\.jpg$/, + s: ".webp" + }, { name: "Wjcodes", url: /^https:\/\/[^\.]+\.wjcodes\.com\//, From 9e82848ee490d4f17e01e4b1096fcbd4e60f66e4 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 1 Sep 2025 20:35:11 +0900 Subject: [PATCH 034/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 5a6bd37936c..2ae75355fea 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/1643244/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1652811/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1427239/pvcep_lang.js // @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js // @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js @@ -25473,17 +25473,6 @@ ImgOps | https://imgops.com/#b#`; } else if (target.parentNode.nodeName.toUpperCase() == 'IMG') { target = target.parentNode; found = true; - } else if (prefs.floatBar.listenBg && hasBg(target.parentNode)) { - target = target.parentNode; - let src = targetBg, nsrc = src, noActual = true, type = "scale"; - result = { - src: nsrc, - type: type, - imgSrc: src, - noActual:noActual, - img: target - }; - found = true; } } if (!found) { @@ -25569,6 +25558,18 @@ ImgOps | https://imgops.com/#b#`; let imgs = target.shadowRoot.querySelectorAll('img'); if (imgs.length === 1) target = imgs[0]; } + if (!found && prefs.floatBar.listenBg && hasBg(target.parentNode)) { + target = target.parentNode; + let src = targetBg, nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + } if (result && !/^data:/i.test(result.src)) { if (matchedRule.rules.length > 0 && target.nodeName.toUpperCase() != 'IMG') { let src = result.src, img = {src: src}, type, imgSrc = src; From df442daf5ccae8eb62bc5e61ac19cf1d029e9ace Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 2 Sep 2025 08:38:26 +0900 Subject: [PATCH 035/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 2ae75355fea..01c4a52e376 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.9.1.1 +// @version 2025.9.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 @@ -24355,6 +24355,7 @@ ImgOps | https://imgops.com/#b#`; function pretreatment(img, fetchImg) { if (img.removeAttribute) img.removeAttribute("loading"); if (img.nodeName.toUpperCase() != "IMG" || (!fetchImg && img.src && !img.srcset && !/^data/.test(img.src))) return; + if (img.src && !/(^data|loading|lazy)/.test(img.src)) return; let src; tprules.find(function(rule, index, array) { try { From d63bafeb7bec5655b6979ebb6896c010ad75cff5 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 2 Sep 2025 09:20:22 +0900 Subject: [PATCH 036/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index ced954d0c55..2fdb892ec35 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -7286,6 +7286,10 @@ if (compareNodeName(parent, ["table"])) { parent.parentNode.appendChild(loadingDiv); } + if (loadingDiv.previousElementSibling) { + let preStyle = _unsafeWindow.getComputedStyle(loadingDiv.previousElementSibling); + loadingDiv.style.order = preStyle.order; + } } //this.setPageTop(lastScrollTop); if (sideController.inited) { @@ -11644,6 +11648,9 @@ pageBar.classList.add("stop"); } pageBar.style.cssText = pageBarStyle; + if (exampleStyle.order) { + pageBar.style.order = exampleStyle.order; + } pageBar.title = i18n(isPause ? "enable" : "disable"); upSpan.innerHTML = createHTML(upSvg); upSpan.children[0].style.cssText = upSvgCSS; From e32c7c761691bc99b1c9c928b49a001f3a322f3e Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 2 Sep 2025 22:23:19 +0900 Subject: [PATCH 037/252] update --- Pagetual/pagetual.user.js | 2 +- Picviewer CE+/pvcep_lang.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 2fdb892ec35..2479f1e6e5e 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -5384,7 +5384,7 @@ curWidth = validSize.w; } } - if (compareNodeName(ele, ["picture"]) || !ele.innerText || ele.innerText.trim() === '') { + if (compareNodeName(ele, ["picture", "img"])) { self.curSiteRule.pageElement = geneSelector(ele.parentNode) + ">" + ele.nodeName.toLowerCase(); debug(self.curSiteRule.pageElement, 'Page element'); let eles = []; diff --git a/Picviewer CE+/pvcep_lang.js b/Picviewer CE+/pvcep_lang.js index 8243fdb7bf2..adf87fa2c25 100644 --- a/Picviewer CE+/pvcep_lang.js +++ b/Picviewer CE+/pvcep_lang.js @@ -257,7 +257,7 @@ const langData = [ customLang: "Custom language", defaultLang: "Auto detect", hideIcon: "Hide icon", - initShow: "Invert Shortcut to show preview by default", + initShow: "Show preview by default", stayOut: "Let float bar stay out of the image", galleryDownloadGap: "Download interval", formatConversion: "Convert the image format when downloading. One rule per line", @@ -791,7 +791,7 @@ const langData = [ customLang: "设定语言", defaultLang: "自动选择", hideIcon: "隐藏图标", - initShow: "反转快捷键,默认显示预览", + initShow: "默认显示预览", stayOut: "使浮动工具栏显示在图片外部", galleryDownloadGap: "下载间隔时间", formatConversion: "下载时转换图片格式,一行一条", @@ -1057,7 +1057,7 @@ const langData = [ customLang: "設定語言", defaultLang: "自動選擇", hideIcon: "隱藏圖標", - initShow: "反轉快捷鍵,默認顯示預覽", + initShow: "默認顯示預覽", stayOut: "使浮動工具欄顯示在圖片外部", galleryDownloadGap: "下載間隔時間", formatConversion: "下載時轉換圖片格式,一行一條", @@ -2391,7 +2391,7 @@ const langData = [ customLang: "カスタム言語", defaultLang: "自動検出", hideIcon: "アイコンを隠す", - initShow: "ショートカットを反転して、デフォルトでプレビューを表示", + initShow: "デフォルトでプレビューを表示", stayOut: "フロートバーを画像外に留める", galleryDownloadGap: "ダウンロード間隔", formatConversion: "ダウンロード時に画像形式を変換します。1 行に 1 行", From 4e3e7cb3d8b063b79ba3a03fab0e9e9db3585b73 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 2 Sep 2025 22:37:24 +0900 Subject: [PATCH 038/252] 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 01c4a52e376..2703c1dd221 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.9.2.1 +// @version 2025.9.2.2 // @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 @@ -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/1652811/pvcep_rules.js -// @require https://update.greasyfork.org/scripts/440698/1427239/pvcep_lang.js +// @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js // @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js // @match *://*/* @@ -25765,7 +25765,7 @@ ImgOps | https://imgops.com/#b#`; return; } if ((uniqueImgWin && !uniqueImgWin.removed && !uniqueImgWin.previewed)) { - if (canPreview) { + if (canPreview || prefs.floatBar.globalkeys.invertInitShow) { uniqueImgWinInitX = e.clientX; uniqueImgWinInitY = e.clientY; uniqueImgWin.followPos(uniqueImgWinInitX, uniqueImgWinInitY); From 6e7688f32b12e94c7d093587e026fdc7445efb9b Mon Sep 17 00:00:00 2001 From: Bug-Cache <98657967+Bug-Cache@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:29:13 +0700 Subject: [PATCH 039/252] Update pagetualRules.json https://github.com/hoothin/UserScripts/issues/998#issuecomment-3245325962 --- Pagetual/pagetualRules.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Pagetual/pagetualRules.json b/Pagetual/pagetualRules.json index fec987433dc..9d7a7207672 100644 --- a/Pagetual/pagetualRules.json +++ b/Pagetual/pagetualRules.json @@ -1,4 +1,10 @@ [ +{ + "name": "Fapello", + "url": "^https?://fapello\\.com/", + "example": "https://fapello.com/noemy-love/38/", + "pageElement": "a.uk-align-center > img" +}, { "name": "精易论坛 - 手机版 - Powered by Discuz!", "url": "^https?://bbs\\.125\\.la/", From ad0358f0c8859b8570ee2b85a479bca5711f59ef Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 4 Sep 2025 07:32:29 +0000 Subject: [PATCH 040/252] Update version of Pagetual rules to 99 --- Pagetual/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/version b/Pagetual/version index 6529ff889b0..3ad5abd03ae 100644 --- a/Pagetual/version +++ b/Pagetual/version @@ -1 +1 @@ -98 +99 From 13a5535a593b83bea7f7228a775a1e272696531b Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 4 Sep 2025 20:59:31 +0900 Subject: [PATCH 041/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 2703c1dd221..2f534ed1311 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.9.2.2 +// @version 2025.9.4.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 @@ -20652,7 +20652,12 @@ ImgOps | https://imgops.com/#b#`; if (!self.zoomed) { if (!self.imgWindow.classList.contains("pv-pic-window-scroll")) { self.zoomLevel=0; - self.zoom(1); + if (img.naturalHeight && img.naturalHeight < 100) { + let zoomLevel = 100 / img.naturalHeight; + self.zoom(zoomLevel); + } else { + self.zoom(1); + } } if (self == uniqueImgWin) { self.initMaxSize(); @@ -23971,16 +23976,10 @@ ImgOps | https://imgops.com/#b#`; if (bodyStyle.position === "static") { offsetParent = document.documentElement; - bodyPosi = { - top: 0, - bottom: windowSize.h, - left: 0, - right: windowSize.w - }; } else { offsetParent = body; - bodyPosi = offsetParent.getBoundingClientRect(); } + bodyPosi = offsetParent.getBoundingClientRect(); var scrolled=getScrolled(offsetParent); From 03e04a1735ba8e387d9d72d233597b7e559d00ee Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 4 Sep 2025 21:37:00 +0900 Subject: [PATCH 042/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 2f534ed1311..312067a3373 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -23983,10 +23983,10 @@ ImgOps | https://imgops.com/#b#`; var scrolled=getScrolled(offsetParent); - targetPosi.top = targetPosi.top - bodyPosi.top + scrolled.y; - targetPosi.left = targetPosi.left - bodyPosi.left + scrolled.x; - targetPosi.bottom = bodyPosi.bottom - targetPosi.bottom - scrolled.y; - targetPosi.right = bodyPosi.right - targetPosi.right - scrolled.x; + targetPosi.top = targetPosi.top - bodyPosi.top; + targetPosi.left = targetPosi.left - bodyPosi.left; + targetPosi.bottom = bodyPosi.bottom - targetPosi.bottom; + targetPosi.right = bodyPosi.right - targetPosi.right; var fbs = this.floatBar.style; var setPosition = { From 5a9edda1417046b8d91802aca4c1fa5356ea4433 Mon Sep 17 00:00:00 2001 From: Bug-Cache <98657967+Bug-Cache@users.noreply.github.com> Date: Fri, 5 Sep 2025 05:08:46 +0700 Subject: [PATCH 043/252] Update pagetualRules.json: woot https://github.com/hoothin/UserScripts/issues/971 --- Pagetual/pagetualRules.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Pagetual/pagetualRules.json b/Pagetual/pagetualRules.json index 9d7a7207672..0bef18b9b67 100644 --- a/Pagetual/pagetualRules.json +++ b/Pagetual/pagetualRules.json @@ -1,4 +1,12 @@ [ +{ + "name": "Woot", + "url": "^https?://www\\.woot\\.com/", + "example": "https://www.woot.com/alldeals?ref=w_ngh_et_1", + "author": "Hoothin", + "nextLink": "//div[text()='Next >']", + "pinUrl": true +}, { "name": "Fapello", "url": "^https?://fapello\\.com/", From 596ff8c94bfe8d533377c4190260bd3e72e7908f Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 4 Sep 2025 23:37:55 +0000 Subject: [PATCH 044/252] Update version of Pagetual rules to 100 --- Pagetual/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/version b/Pagetual/version index 3ad5abd03ae..29d6383b52c 100644 --- a/Pagetual/version +++ b/Pagetual/version @@ -1 +1 @@ -99 +100 From 3ad8308d640bd5e6575c6b4892913d8e149a1541 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 5 Sep 2025 18:33:26 +0900 Subject: [PATCH 045/252] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index d7e487e9cbf..8a7a30ffffa 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -188,7 +188,12 @@ var siteInfo = [ if (!a) return; var reg = /&objurl=(http.*?\.(?:jpg|jpeg|png|gif|bmp))/i; if (a.href.match(reg)) { - return decodeURIComponent(RegExp.$1); + let url = RegExp.$1; + try { + url = decodeURIComponent(url); + url = decodeURIComponent(url); + }catch(e){} + return url; } } }, From 3c6d4397d718e6ae7cba2302fba29cdabf515b02 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 5 Sep 2025 18:46:05 +0900 Subject: [PATCH 046/252] 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 8a7a30ffffa..a6bf401d6b4 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -186,7 +186,7 @@ var siteInfo = [ url: /^https?:\/\/image\.baidu\.com\/.*&word=/i, getImage: function(a) { if (!a) return; - var reg = /&objurl=(http.*?\.(?:jpg|jpeg|png|gif|bmp))/i; + var reg = /&objurl=(http.*?\.(?:jpg|jpeg|png|gif|bmp|webp))/i; if (a.href.match(reg)) { let url = RegExp.$1; try { From 1288ce74aa5ede60305c9184784e1be741bdd6d1 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 5 Sep 2025 18:47:23 +0900 Subject: [PATCH 047/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 39 +++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 312067a3373..770f1fdd59e 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.9.4.1 +// @version 2025.9.5.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/1652811/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1655189/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js // @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js @@ -12540,8 +12540,8 @@ ImgOps | https://imgops.com/#b#`; showSmallSize:true,//是否默认显示小尺寸图片 disableArrow:false, - scrollEndAndLoad: false, // 滚动主窗口到最底部,然后自动重载库的图片。还有bug,有待进一步测试 - scrollEndAndLoad_num: 3, // 最后几张图片执行 + scrollEndAndLoad: true, // 滚动主窗口到最底部,然后自动重载库的图片。 + scrollEndAndLoad_num: 6, // 最后几张图片执行 autoZoom: false, // 如果有放大,则把图片及 sidebar 部分的缩放改回 100%,增大可视面积(仅在 chrome 下有效) descriptionLength: 32, // 注释的最大宽度 @@ -14632,7 +14632,13 @@ ImgOps | https://imgops.com/#b#`; self.switchThumbVisible();//切换图片类别显隐; },true); - prefs.gallery.scrollEndAndLoad = !!storage.getListItem("scrollEndAndLoad", location.hostname); + let scrollEndAndLoad = storage.getListItem("scrollEndAndLoad", location.hostname); + if (scrollEndAndLoad === true) { + prefs.gallery.scrollEndAndLoad = true; + } else if (scrollEndAndLoad === false) { + prefs.gallery.scrollEndAndLoad = false; + } + eleMaps['head-command-drop-list-others'].querySelector('input[data-command="scrollToEndAndReload"]').checked = prefs.gallery.scrollEndAndLoad; let srcSplit, downloading=false, saveParams; async function getSaveParams() { @@ -16284,6 +16290,11 @@ ImgOps | https://imgops.com/#b#`; url: imgSrc, responseType: 'blob', onload: function(response) { + if (response.response && response.response.type == "text/html") { + self.showTips(""); + loadError(); + return; + } const blobUrl = URL.createObjectURL(response.response); self.showTips(""); let img = document.createElement("img"); @@ -16741,6 +16752,12 @@ ImgOps | https://imgops.com/#b#`; url: src, responseType: 'blob', onload: function(response) { + if (response.response && response.response.type == "text/html") { + self.errorSpan=ele; + if(preImgR)preImgR.abort(); + self.loadImg(this, ele, true); + return; + } const blobUrl = URL.createObjectURL(response.response); self.slideShow.run('loadEnd'); let img = document.createElement("img"); @@ -18052,13 +18069,13 @@ ImgOps | https://imgops.com/#b#`; } }, getAllValidImgs:async function(newer, checkListenBg){ - var validImgs = []; - var container = document.querySelector('.pv-gallery-container'), + let validImgs = []; + let container = document.querySelector('.pv-gallery-container'), preloadContainer = document.querySelector('.pv-gallery-preloaded-img-container'); - var bgReg = /.*?url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; - var body = getBody(document); - var linkMedias = []; + let bgReg = /^\s*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; + let body = getBody(document); + let linkMedias = []; function anylizeEle(total, node) { if (/^iframe$/i.test(node.nodeName)) { if (node.name == "pagetual-iframe") return total; @@ -19203,7 +19220,7 @@ ImgOps | https://imgops.com/#b#`; border-top: 0px solid transparent;\ }\ .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head{\ - opacity: 0.1;\ + opacity: 0;\ transition: opacity .3s ease;\ }\ .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-body>.pv-gallery-img-container{\ From cc56ed2b645a2ecb526ac2583509a15421fe405d Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 5 Sep 2025 19:55:02 +0900 Subject: [PATCH 048/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 770f1fdd59e..3230e421116 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.9.5.1 +// @version 2025.9.5.2 // @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 @@ -12541,7 +12541,7 @@ ImgOps | https://imgops.com/#b#`; disableArrow:false, scrollEndAndLoad: true, // 滚动主窗口到最底部,然后自动重载库的图片。 - scrollEndAndLoad_num: 6, // 最后几张图片执行 + scrollEndAndLoad_num: 5, // 最后几张图片执行 autoZoom: false, // 如果有放大,则把图片及 sidebar 部分的缩放改回 100%,增大可视面积(仅在 chrome 下有效) descriptionLength: 32, // 注释的最大宽度 @@ -12647,7 +12647,7 @@ ImgOps | https://imgops.com/#b#`; } ]; - const imageReg = /^\s*(http|ftp).*\.(avi|avif|avifs|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|jif|jfi|a?png|svgz?|webp|xbm|dib|divx|3gpp|m3u|m4v|mkv|mp4|mpe?g|ogv|webm|flv|flac|m4a|m4b|mpa|mp3|aac|cda|oga|ogg|opus|wma|wav)(&|\?|#|\/?$|\s)/i; + const imageReg = /^\s*(https?|ftp):\/\/.*?\/[^\.]*\.(avi|avif|avifs|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|jif|jfi|a?png|svgz?|webp|xbm|dib|divx|3gpp|m3u|m4v|mkv|mp4|mpe?g|ogv|webm|flv|flac|m4a|m4b|mpa|mp3|aac|cda|oga|ogg|opus|wma|wav)(&|\?|#|\/?$|\s)/i; const ruleImportHost = ["greasyfork.org", "github.com", "reddit.com"]; const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/24204(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Picviewer%20CE%2B|issues|discussions)|\.reddit\.com\/r\/PicviewerCE/i; @@ -16739,13 +16739,14 @@ ImgOps | https://imgops.com/#b#`; }; if(src!=self.lastLoading)return; + let img = this; if(e.type=='error'){ if (loadingIndicator && loadingIndicator.style) loadingIndicator.style.display=''; if (/^blob:/.test(src)) { self.errorSpan=ele; if(preImgR)preImgR.abort(); - self.loadImg(this, ele,true); + self.loadImg(img, ele,true); } else { _GM_xmlhttpRequest({ method: 'GET', @@ -16755,7 +16756,7 @@ ImgOps | https://imgops.com/#b#`; if (response.response && response.response.type == "text/html") { self.errorSpan=ele; if(preImgR)preImgR.abort(); - self.loadImg(this, ele, true); + self.loadImg(img, ele, true); return; } const blobUrl = URL.createObjectURL(response.response); @@ -16774,7 +16775,7 @@ ImgOps | https://imgops.com/#b#`; onerror: function() { self.errorSpan=ele; if(preImgR)preImgR.abort(); - self.loadImg(this, ele, true); + self.loadImg(img, ele, true); } }); } From 32aaea49bb04a8da81e6fba38e68eaa80ffa03e6 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 5 Sep 2025 20:07:26 +0900 Subject: [PATCH 049/252] 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 3230e421116..3cdd8c0b8c1 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12393,6 +12393,7 @@ ImgOps | https://imgops.com/#b#`; if (!blob.type) return urlToBlob(url, cb, forcePng, tryTimes); let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext === "none") ext = "webp"; let conversion = formatDict.get(ext); if (canvas && (conversion || forcePng)) { var self = this; From 36a07a00a7e8df34a8dd1a1e2a90f86c626f10a5 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 6 Sep 2025 10:14:40 +0900 Subject: [PATCH 050/252] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index a6bf401d6b4..1681c198b16 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -872,6 +872,9 @@ var siteInfo = [ } else if (p[3]) { let a = p[3].querySelector("a.group"); if (a) return a.href; + else if(p[3].previousElementSibling && p[3].previousElementSibling.getAttribute('slot') == 'full-post-link') { + return p[3].previousElementSibling.href; + } } }, headers: (url, self) => { From a02177731bf9d511d8bc2d24161478b9259ffe40 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 6 Sep 2025 10:15:20 +0900 Subject: [PATCH 051/252] 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 3cdd8c0b8c1..dd09ef9073c 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/1655189/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js // @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js From 621deff92a1b6b36101c4e173af59851ff106047 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 6 Sep 2025 15:48:09 +0900 Subject: [PATCH 052/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 2479f1e6e5e..f9c24858c4f 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -98,8 +98,6 @@ // @connect * // @contributionURL https://ko-fi.com/hoothin // @contributionAmount 1 -// @downloadURL https://update.greasyfork.org/scripts/438684/Pagetual.user.js -// @updateURL https://update.greasyfork.org/scripts/438684/Pagetual.meta.js // ==/UserScript== (function() { From 268e475cf6246d56df2d9a91c58a2f82ff10489b Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:04:30 +0900 Subject: [PATCH 053/252] Create build-dist.yml --- .github/workflows/build-dist.yml | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/build-dist.yml diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml new file mode 100644 index 00000000000..9da77dcadc5 --- /dev/null +++ b/.github/workflows/build-dist.yml @@ -0,0 +1,34 @@ +name: Build Picviewer CE+ Dist + +on: + push: + branches: + - master + paths: + - 'Picviewer CE*/Picviewer CE*.user.js' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Replace require paths, add timestamp, and create dist file + run: | + TIMESTAMP=$(date +%s) + + sed -e "s#// @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ + -e "s#// @require https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ + -e "s#// @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ + "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" + + - name: Commit and push dist.user.js + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" + file_pattern: 'Picviewer CE+/dist.user.js' + commit_user_name: GitHub Actions + commit_user_email: rixixi@gmail.com + commit_author: GitHub Actions \ No newline at end of file From 2051bd69250ab1a8dc305a56e001b6468dd0670d Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:05:04 +0900 Subject: [PATCH 054/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index dd09ef9073c..77b2308bb08 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -48,8 +48,6 @@ // @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js // @require https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js -// @downloadURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.user.js -// @updateURL https://greasyfork.org/scripts/24204-picviewer-ce/code/Picviewer%20CE+.meta.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* From 404af3ef40a29d304b08b05c4a2c9bf6b767da1b Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:10:07 +0900 Subject: [PATCH 055/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index 9da77dcadc5..3d96b507613 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -25,10 +25,9 @@ jobs: "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" - name: Commit and push dist.user.js - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" - file_pattern: 'Picviewer CE+/dist.user.js' - commit_user_name: GitHub Actions - commit_user_email: rixixi@gmail.com - commit_author: GitHub Actions \ No newline at end of file + run: | + git config --local user.email "rixixi@gmail.com" + git config --local user.name "hoothin-update" + git add Picviewer CE+/dist.user.js + git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" + git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} \ No newline at end of file From 361e9f97b9b9ac7456d848511eb550294f4cff5d Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:11:51 +0900 Subject: [PATCH 056/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index 3d96b507613..fb33500caf8 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -6,6 +6,7 @@ on: - master paths: - 'Picviewer CE*/Picviewer CE*.user.js' + workflow_dispatch: jobs: build: From 8f1fcf0775339dceee30477253197821eb2b893e Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:17:15 +0900 Subject: [PATCH 057/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index fb33500caf8..88c92b44c76 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -29,6 +29,6 @@ jobs: run: | git config --local user.email "rixixi@gmail.com" git config --local user.name "hoothin-update" - git add Picviewer CE+/dist.user.js + git add "Picviewer CE+/dist.user.js" git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} \ No newline at end of file From 79975ab221933a93562745f90df0d80e3ccd2194 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 7 Sep 2025 01:17:56 +0000 Subject: [PATCH 058/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 27547 +++++++++++++++++++++++++++++++++++ 1 file changed, 27547 insertions(+) create mode 100644 Picviewer CE+/dist.user.js diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js new file mode 100644 index 00000000000..77b2308bb08 --- /dev/null +++ b/Picviewer CE+/dist.user.js @@ -0,0 +1,27547 @@ +// ==UserScript== +// @name Picviewer CE+ +// @name:zh-CN Picviewer CE+ +// @name:zh-TW Picviewer CE+ +// @name:ja Picviewer CE+ +// @name:pt-BR Picviewer CE+ +// @name:ru Picviewer CE+ +// @author NLF && ywzhaiqi && hoothin +// @description Powerful picture viewing tool online, which can popup/scale/rotate/batch save pictures automatically +// @description:zh-CN 在线看图工具,支持图片翻转、旋转、缩放、弹出大图、批量保存 +// @description:zh-TW 線上看圖工具,支援圖片翻轉、旋轉、縮放、彈出大圖、批量儲存 +// @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.9.5.2 +// @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 +// @supportURL https://github.com/hoothin/UserScripts/issues +// @connect www.google.com +// @connect www.google.com.hk +// @connect www.google.co.jp +// @connect ipv4.google.com +// @connect image.baidu.com +// @connect www.tineye.com +// @connect hoothin.com +// @connect * +// @grant GM_getValue +// @grant GM_setValue +// @grant GM_deleteValue +// @grant GM_addStyle +// @grant GM_openInTab +// @grant GM_setClipboard +// @grant GM_xmlhttpRequest +// @grant GM_registerMenuCommand +// @grant GM_notification +// @grant GM_download +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.addStyle +// @grant GM.openInTab +// @grant GM.setClipboard +// @grant GM.xmlHttpRequest +// @grant GM.registerMenuCommand +// @grant GM.notification +// @grant unsafeWindow +// @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js +// @require https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js +// @match *://*/* +// @exclude http://www.toodledo.com/tasks/* +// @exclude http*://maps.google.com*/* +// @exclude *://www.google.*/_/chrome/newtab* +// @exclude *://mega.*/* +// @exclude *://*.mega.*/* +// @exclude *://onedrive.live.com/* +// @run-at document-end +// @created 2011-6-15 +// @contributionURL https://ko-fi.com/hoothin +// @contributionAmount 1 +// ==/UserScript== + +if (window.top != window.self) { + try { + if ((window.self.innerWidth && window.self.innerWidth < 250) || (window.self.innerHeight && window.self.innerHeight < 250)) { + return; + } + } catch(e) { + return; + } +} + +(function (global, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof exports !== "undefined") { + factory(); + } else { + var mod = { + exports: {} + }; + factory(); + global.FileSaver = mod.exports; + } +})(this, function () { + "use strict"; + + /* + * FileSaver.js + * A saveAs() FileSaver implementation. + * + * By Eli Grey, http://eligrey.com + * + * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) + * source : http://purl.eligrey.com/github/FileSaver.js + */ + // The one and only way of getting global scope in all environments + // https://stackoverflow.com/q/3277182/1008999 + var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : void 0; + + function bom(blob, opts) { + if (typeof opts === 'undefined') opts = { + autoBom: false + };else if (typeof opts !== 'object') { + console.warn('Deprecated: Expected third argument to be a object'); + opts = { + autoBom: !opts + }; + } // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + + if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], { + type: blob.type + }); + } + + return blob; + } + + function download(url, name, opts) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'blob'; + + xhr.onload = function () { + saveAs(xhr.response, name, opts); + }; + + xhr.onerror = function () { + console.error('could not download file'); + }; + + xhr.send(); + } + + function corsEnabled(url) { + var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker + + xhr.open('HEAD', url, false); + + try { + xhr.send(); + } catch (e) {} + + return xhr.status >= 200 && xhr.status <= 299; + } // `a.click()` doesn't work for all browsers (#465) + + + function click(node) { + try { + node.dispatchEvent(new MouseEvent('click')); + } catch (e) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); + node.dispatchEvent(evt); + } + } // Detect WebView inside a native macOS app by ruling out all browsers + // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too + // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos + + + var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent); + var URL = _global.URL || _global.webkitURL; + var revokeObjectURL = URL.revokeObjectURL; + var createObjectURL = URL.createObjectURL; + var saveAs = _global.saveAs || ( // probably in some web worker + typeof window !== 'object' || window !== _global ? function saveAs() {} + /* noop */ + // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView + : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) { + var URL = _global.URL || _global.webkitURL; + var a = document.createElement('a'); + name = name || blob.name || 'download'; + a.download = name; + a.rel = 'noopener'; // tabnabbing + // TODO: detect chrome extensions & packaged apps + // a.target = '_blank' + + if (typeof blob === 'string') { + // Support regular links + a.href = blob; + + if (a.origin !== location.origin) { + corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank'); + } else { + click(a); + } + } else { + // Support blobs + a.href = createObjectURL(blob); + setTimeout(function () { + revokeObjectURL(a.href); + }, 4E4); // 40s + + setTimeout(function () { + click(a); + }, 0); + } + } // Use msSaveOrOpenBlob as a second approach + : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) { + name = name || blob.name || 'download'; + + if (typeof blob === 'string') { + if (corsEnabled(blob)) { + download(blob, name, opts); + } else { + var a = document.createElement('a'); + a.href = blob; + a.target = '_blank'; + setTimeout(function () { + click(a); + }); + } + } else { + navigator.msSaveOrOpenBlob(bom(blob, opts), name); + } + } // Fallback to using FileReader and a popup + : function saveAs(blob, name, opts, popup) { + // Open a popup immediately do go around popup blocker + // Mostly only available on user interaction and the fileReader is async so... + popup = popup || open('', '_blank'); + + if (popup) { + popup.document.title = popup.document.body.innerText = 'downloading...'; + } + + if (typeof blob === 'string') return download(blob, name, opts); + var force = blob.type === 'application/octet-stream'; + + var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari; + + var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent); + + if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') { + // Safari doesn't allow downloading of blob URLs + var reader = new FileReader(); + + reader.onloadend = function () { + var url = reader.result; + url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;'); + if (popup) popup.location.href = url;else location = url; + popup = null; // reverse-tabnabbing #460 + }; + + reader.readAsDataURL(blob); + } else { + var url = createObjectURL(blob); + if (popup) popup.location = url;else location.href = url; + popup = null; // reverse-tabnabbing #460 + + setTimeout(function () { + revokeObjectURL(url); + }, 4E4); // 40s + } + }); + _global.saveAs = saveAs.saveAs = saveAs; + + if (typeof module !== 'undefined') { + module.exports = saveAs; + } +}); + +/*! + +JSZip v3.7.1 - A JavaScript class for generating and reading zip files + + +(c) 2009-2016 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/master/LICENSE +*/ + +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.JSZip = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = remainingBytes > 1 ? (((chr2 & 15) << 2) | (chr3 >> 6)) : 64; + enc4 = remainingBytes > 2 ? (chr3 & 63) : 64; + + output.push(_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4)); + + } + + return output.join(""); +}; + +// public method for decoding +exports.decode = function(input) { + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0, resultIndex = 0; + + var dataUrlPrefix = "data:"; + + if (input.substr(0, dataUrlPrefix.length) === dataUrlPrefix) { + // This is a common error: people give a data url + // (data:image/png;base64,iVBOR...) with a {base64: true} and + // wonders why things don't work. + // We can detect that the string input looks like a data url but we + // *can't* be sure it is one: removing everything up to the comma would + // be too dangerous. + throw new Error("Invalid base64 input, it looks like a data url."); + } + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + var totalLength = input.length * 3 / 4; + if(input.charAt(input.length - 1) === _keyStr.charAt(64)) { + totalLength--; + } + if(input.charAt(input.length - 2) === _keyStr.charAt(64)) { + totalLength--; + } + if (totalLength % 1 !== 0) { + // totalLength is not an integer, the length does not match a valid + // base64 content. That can happen if: + // - the input is not a base64 content + // - the input is *almost* a base64 content, with a extra chars at the + // beginning or at the end + // - the input uses a base64 variant (base64url for example) + throw new Error("Invalid base64 input, bad content length."); + } + var output; + if (support.uint8array) { + output = new Uint8Array(totalLength|0); + } else { + output = new Array(totalLength|0); + } + + while (i < input.length) { + + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output[resultIndex++] = chr1; + + if (enc3 !== 64) { + output[resultIndex++] = chr2; + } + if (enc4 !== 64) { + output[resultIndex++] = chr3; + } + + } + + return output; +}; + +},{"./support":30,"./utils":32}],2:[function(require,module,exports){ +'use strict'; + +var external = require("./external"); +var DataWorker = require('./stream/DataWorker'); +var Crc32Probe = require('./stream/Crc32Probe'); +var DataLengthProbe = require('./stream/DataLengthProbe'); + +/** + * Represent a compressed object, with everything needed to decompress it. + * @constructor + * @param {number} compressedSize the size of the data compressed. + * @param {number} uncompressedSize the size of the data after decompression. + * @param {number} crc32 the crc32 of the decompressed file. + * @param {object} compression the type of compression, see lib/compressions.js. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the compressed data. + */ +function CompressedObject(compressedSize, uncompressedSize, crc32, compression, data) { + this.compressedSize = compressedSize; + this.uncompressedSize = uncompressedSize; + this.crc32 = crc32; + this.compression = compression; + this.compressedContent = data; +} + +CompressedObject.prototype = { + /** + * Create a worker to get the uncompressed content. + * @return {GenericWorker} the worker. + */ + getContentWorker: function () { + var worker = new DataWorker(external.Promise.resolve(this.compressedContent)) + .pipe(this.compression.uncompressWorker()) + .pipe(new DataLengthProbe("data_length")); + + var that = this; + worker.on("end", function () { + if (this.streamInfo['data_length'] !== that.uncompressedSize) { + throw new Error("Bug : uncompressed data size mismatch"); + } + }); + return worker; + }, + /** + * Create a worker to get the compressed content. + * @return {GenericWorker} the worker. + */ + getCompressedWorker: function () { + return new DataWorker(external.Promise.resolve(this.compressedContent)) + .withStreamInfo("compressedSize", this.compressedSize) + .withStreamInfo("uncompressedSize", this.uncompressedSize) + .withStreamInfo("crc32", this.crc32) + .withStreamInfo("compression", this.compression) + ; + } +}; + +/** + * Chain the given worker with other workers to compress the content with the + * given compression. + * @param {GenericWorker} uncompressedWorker the worker to pipe. + * @param {Object} compression the compression object. + * @param {Object} compressionOptions the options to use when compressing. + * @return {GenericWorker} the new worker compressing the content. + */ +CompressedObject.createWorkerFrom = function (uncompressedWorker, compression, compressionOptions) { + return uncompressedWorker + .pipe(new Crc32Probe()) + .pipe(new DataLengthProbe("uncompressedSize")) + .pipe(compression.compressWorker(compressionOptions)) + .pipe(new DataLengthProbe("compressedSize")) + .withStreamInfo("compression", compression); +}; + +module.exports = CompressedObject; + +},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(require,module,exports){ +'use strict'; + +var GenericWorker = require("./stream/GenericWorker"); + +exports.STORE = { + magic: "\x00\x00", + compressWorker : function (compressionOptions) { + return new GenericWorker("STORE compression"); + }, + uncompressWorker : function () { + return new GenericWorker("STORE decompression"); + } +}; +exports.DEFLATE = require('./flate'); + +},{"./flate":7,"./stream/GenericWorker":28}],4:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); + +/** + * The following functions come from pako, from pako/lib/zlib/crc32.js + * released under the MIT license, see pako https://github.com/nodeca/pako/ + */ + +// Use ordinary array, since untyped makes no boost here +function makeTable() { + var c, table = []; + + for(var n =0; n < 256; n++){ + c = n; + for(var k =0; k < 8; k++){ + c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + } + table[n] = c; + } + + return table; +} + +// Create table on load. Just 255 signed longs. Not a problem. +var crcTable = makeTable(); + + +function crc32(crc, buf, len, pos) { + var t = crcTable, end = pos + len; + + crc = crc ^ (-1); + + for (var i = pos; i < end; i++ ) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + +// That's all for the pako functions. + +/** + * Compute the crc32 of a string. + * This is almost the same as the function crc32, but for strings. Using the + * same function for the two use cases leads to horrible performances. + * @param {Number} crc the starting value of the crc. + * @param {String} str the string to use. + * @param {Number} len the length of the string. + * @param {Number} pos the starting position for the crc32 computation. + * @return {Number} the computed crc32. + */ +function crc32str(crc, str, len, pos) { + var t = crcTable, end = pos + len; + + crc = crc ^ (-1); + + for (var i = pos; i < end; i++ ) { + crc = (crc >>> 8) ^ t[(crc ^ str.charCodeAt(i)) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +} + +module.exports = function crc32wrapper(input, crc) { + if (typeof input === "undefined" || !input.length) { + return 0; + } + + var isArray = utils.getTypeOf(input) !== "string"; + + if(isArray) { + return crc32(crc|0, input, input.length, 0); + } else { + return crc32str(crc|0, input, input.length, 0); + } +}; + +},{"./utils":32}],5:[function(require,module,exports){ +'use strict'; +exports.base64 = false; +exports.binary = false; +exports.dir = false; +exports.createFolders = true; +exports.date = null; +exports.compression = null; +exports.compressionOptions = null; +exports.comment = null; +exports.unixPermissions = null; +exports.dosPermissions = null; + +},{}],6:[function(require,module,exports){ +/* global Promise */ +'use strict'; + +// load the global object first: +// - it should be better integrated in the system (unhandledRejection in node) +// - the environment may have a custom Promise implementation (see zone.js) +var ES6Promise = null; +if (typeof Promise !== "undefined") { + ES6Promise = Promise; +} else { + ES6Promise = require("lie"); +} + +/** + * Let the user use/change some implementations. + */ +module.exports = { + Promise: ES6Promise +}; + +},{"lie":37}],7:[function(require,module,exports){ +'use strict'; +var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined'); + +var pako = require("pako"); +var utils = require("./utils"); +var GenericWorker = require("./stream/GenericWorker"); + +var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array"; + +exports.magic = "\x08\x00"; + +/** + * Create a worker that uses pako to inflate/deflate. + * @constructor + * @param {String} action the name of the pako function to call : either "Deflate" or "Inflate". + * @param {Object} options the options to use when (de)compressing. + */ +function FlateWorker(action, options) { + GenericWorker.call(this, "FlateWorker/" + action); + + this._pako = null; + this._pakoAction = action; + this._pakoOptions = options; + // the `meta` object from the last chunk received + // this allow this worker to pass around metadata + this.meta = {}; +} + +utils.inherits(FlateWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +FlateWorker.prototype.processChunk = function (chunk) { + this.meta = chunk.meta; + if (this._pako === null) { + this._createPako(); + } + this._pako.push(utils.transformTo(ARRAY_TYPE, chunk.data), false); +}; + +/** + * @see GenericWorker.flush + */ +FlateWorker.prototype.flush = function () { + GenericWorker.prototype.flush.call(this); + if (this._pako === null) { + this._createPako(); + } + this._pako.push([], true); +}; +/** + * @see GenericWorker.cleanUp + */ +FlateWorker.prototype.cleanUp = function () { + GenericWorker.prototype.cleanUp.call(this); + this._pako = null; +}; + +/** + * Create the _pako object. + * TODO: lazy-loading this object isn't the best solution but it's the + * quickest. The best solution is to lazy-load the worker list. See also the + * issue #446. + */ +FlateWorker.prototype._createPako = function () { + this._pako = new pako[this._pakoAction]({ + raw: true, + level: this._pakoOptions.level || -1 // default compression + }); + var self = this; + this._pako.onData = function(data) { + self.push({ + data : data, + meta : self.meta + }); + }; +}; + +exports.compressWorker = function (compressionOptions) { + return new FlateWorker("Deflate", compressionOptions); +}; +exports.uncompressWorker = function () { + return new FlateWorker("Inflate", {}); +}; + +},{"./stream/GenericWorker":28,"./utils":32,"pako":38}],8:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var GenericWorker = require('../stream/GenericWorker'); +var utf8 = require('../utf8'); +var crc32 = require('../crc32'); +var signature = require('../signature'); + +/** + * Transform an integer into a string in hexadecimal. + * @private + * @param {number} dec the number to convert. + * @param {number} bytes the number of bytes to generate. + * @returns {string} the result. + */ +var decToHex = function(dec, bytes) { + var hex = "", i; + for (i = 0; i < bytes; i++) { + hex += String.fromCharCode(dec & 0xff); + dec = dec >>> 8; + } + return hex; +}; + +/** + * Generate the UNIX part of the external file attributes. + * @param {Object} unixPermissions the unix permissions or null. + * @param {Boolean} isDir true if the entry is a directory, false otherwise. + * @return {Number} a 32 bit integer. + * + * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute : + * + * TTTTsstrwxrwxrwx0000000000ADVSHR + * ^^^^____________________________ file type, see zipinfo.c (UNX_*) + * ^^^_________________________ setuid, setgid, sticky + * ^^^^^^^^^________________ permissions + * ^^^^^^^^^^______ not used ? + * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only + */ +var generateUnixExternalFileAttr = function (unixPermissions, isDir) { + + var result = unixPermissions; + if (!unixPermissions) { + // I can't use octal values in strict mode, hence the hexa. + // 040775 => 0x41fd + // 0100664 => 0x81b4 + result = isDir ? 0x41fd : 0x81b4; + } + return (result & 0xFFFF) << 16; +}; + +/** + * Generate the DOS part of the external file attributes. + * @param {Object} dosPermissions the dos permissions or null. + * @param {Boolean} isDir true if the entry is a directory, false otherwise. + * @return {Number} a 32 bit integer. + * + * Bit 0 Read-Only + * Bit 1 Hidden + * Bit 2 System + * Bit 3 Volume Label + * Bit 4 Directory + * Bit 5 Archive + */ +var generateDosExternalFileAttr = function (dosPermissions, isDir) { + + // the dir flag is already set for compatibility + return (dosPermissions || 0) & 0x3F; +}; + +/** + * Generate the various parts used in the construction of the final zip file. + * @param {Object} streamInfo the hash with information about the compressed file. + * @param {Boolean} streamedContent is the content streamed ? + * @param {Boolean} streamingEnded is the stream finished ? + * @param {number} offset the current offset from the start of the zip file. + * @param {String} platform let's pretend we are this platform (change platform dependents fields) + * @param {Function} encodeFileName the function to encode the file name / comment. + * @return {Object} the zip parts. + */ +var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform, encodeFileName) { + var file = streamInfo['file'], + compression = streamInfo['compression'], + useCustomEncoding = encodeFileName !== utf8.utf8encode, + encodedFileName = utils.transformTo("string", encodeFileName(file.name)), + utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), + comment = file.comment, + encodedComment = utils.transformTo("string", encodeFileName(comment)), + utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), + useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, + useUTF8ForComment = utfEncodedComment.length !== comment.length, + dosTime, + dosDate, + extraFields = "", + unicodePathExtraField = "", + unicodeCommentExtraField = "", + dir = file.dir, + date = file.date; + + + var dataInfo = { + crc32 : 0, + compressedSize : 0, + uncompressedSize : 0 + }; + + // if the content is streamed, the sizes/crc32 are only available AFTER + // the end of the stream. + if (!streamedContent || streamingEnded) { + dataInfo.crc32 = streamInfo['crc32']; + dataInfo.compressedSize = streamInfo['compressedSize']; + dataInfo.uncompressedSize = streamInfo['uncompressedSize']; + } + + var bitflag = 0; + if (streamedContent) { + // Bit 3: the sizes/crc32 are set to zero in the local header. + // The correct values are put in the data descriptor immediately + // following the compressed data. + bitflag |= 0x0008; + } + if (!useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment)) { + // Bit 11: Language encoding flag (EFS). + bitflag |= 0x0800; + } + + + var extFileAttr = 0; + var versionMadeBy = 0; + if (dir) { + // dos or unix, we set the dos dir flag + extFileAttr |= 0x00010; + } + if(platform === "UNIX") { + versionMadeBy = 0x031E; // UNIX, version 3.0 + extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir); + } else { // DOS or other, fallback to DOS + versionMadeBy = 0x0014; // DOS, version 2.0 + extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir); + } + + // date + // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html + // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html + // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html + + dosTime = date.getUTCHours(); + dosTime = dosTime << 6; + dosTime = dosTime | date.getUTCMinutes(); + dosTime = dosTime << 5; + dosTime = dosTime | date.getUTCSeconds() / 2; + + dosDate = date.getUTCFullYear() - 1980; + dosDate = dosDate << 4; + dosDate = dosDate | (date.getUTCMonth() + 1); + dosDate = dosDate << 5; + dosDate = dosDate | date.getUTCDate(); + + if (useUTF8ForFileName) { + // set the unicode path extra field. unzip needs at least one extra + // field to correctly handle unicode path, so using the path is as good + // as any other information. This could improve the situation with + // other archive managers too. + // This field is usually used without the utf8 flag, with a non + // unicode path in the header (winrar, winzip). This helps (a bit) + // with the messy Windows' default compressed folders feature but + // breaks on p7zip which doesn't seek the unicode path extra field. + // So for now, UTF-8 everywhere ! + unicodePathExtraField = + // Version + decToHex(1, 1) + + // NameCRC32 + decToHex(crc32(encodedFileName), 4) + + // UnicodeName + utfEncodedFileName; + + extraFields += + // Info-ZIP Unicode Path Extra Field + "\x75\x70" + + // size + decToHex(unicodePathExtraField.length, 2) + + // content + unicodePathExtraField; + } + + if(useUTF8ForComment) { + + unicodeCommentExtraField = + // Version + decToHex(1, 1) + + // CommentCRC32 + decToHex(crc32(encodedComment), 4) + + // UnicodeName + utfEncodedComment; + + extraFields += + // Info-ZIP Unicode Path Extra Field + "\x75\x63" + + // size + decToHex(unicodeCommentExtraField.length, 2) + + // content + unicodeCommentExtraField; + } + + var header = ""; + + // version needed to extract + header += "\x0A\x00"; + // general purpose bit flag + header += decToHex(bitflag, 2); + // compression method + header += compression.magic; + // last mod file time + header += decToHex(dosTime, 2); + // last mod file date + header += decToHex(dosDate, 2); + // crc-32 + header += decToHex(dataInfo.crc32, 4); + // compressed size + header += decToHex(dataInfo.compressedSize, 4); + // uncompressed size + header += decToHex(dataInfo.uncompressedSize, 4); + // file name length + header += decToHex(encodedFileName.length, 2); + // extra field length + header += decToHex(extraFields.length, 2); + + + var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields; + + var dirRecord = signature.CENTRAL_FILE_HEADER + + // version made by (00: DOS) + decToHex(versionMadeBy, 2) + + // file header (common to file and central directory) + header + + // file comment length + decToHex(encodedComment.length, 2) + + // disk number start + "\x00\x00" + + // internal file attributes TODO + "\x00\x00" + + // external file attributes + decToHex(extFileAttr, 4) + + // relative offset of local header + decToHex(offset, 4) + + // file name + encodedFileName + + // extra field + extraFields + + // file comment + encodedComment; + + return { + fileRecord: fileRecord, + dirRecord: dirRecord + }; +}; + +/** + * Generate the EOCD record. + * @param {Number} entriesCount the number of entries in the zip file. + * @param {Number} centralDirLength the length (in bytes) of the central dir. + * @param {Number} localDirLength the length (in bytes) of the local dir. + * @param {String} comment the zip file comment as a binary string. + * @param {Function} encodeFileName the function to encode the comment. + * @return {String} the EOCD record. + */ +var generateCentralDirectoryEnd = function (entriesCount, centralDirLength, localDirLength, comment, encodeFileName) { + var dirEnd = ""; + var encodedComment = utils.transformTo("string", encodeFileName(comment)); + + // end of central dir signature + dirEnd = signature.CENTRAL_DIRECTORY_END + + // number of this disk + "\x00\x00" + + // number of the disk with the start of the central directory + "\x00\x00" + + // total number of entries in the central directory on this disk + decToHex(entriesCount, 2) + + // total number of entries in the central directory + decToHex(entriesCount, 2) + + // size of the central directory 4 bytes + decToHex(centralDirLength, 4) + + // offset of start of central directory with respect to the starting disk number + decToHex(localDirLength, 4) + + // .ZIP file comment length + decToHex(encodedComment.length, 2) + + // .ZIP file comment + encodedComment; + + return dirEnd; +}; + +/** + * Generate data descriptors for a file entry. + * @param {Object} streamInfo the hash generated by a worker, containing information + * on the file entry. + * @return {String} the data descriptors. + */ +var generateDataDescriptors = function (streamInfo) { + var descriptor = ""; + descriptor = signature.DATA_DESCRIPTOR + + // crc-32 4 bytes + decToHex(streamInfo['crc32'], 4) + + // compressed size 4 bytes + decToHex(streamInfo['compressedSize'], 4) + + // uncompressed size 4 bytes + decToHex(streamInfo['uncompressedSize'], 4); + + return descriptor; +}; + + +/** + * A worker to concatenate other workers to create a zip file. + * @param {Boolean} streamFiles `true` to stream the content of the files, + * `false` to accumulate it. + * @param {String} comment the comment to use. + * @param {String} platform the platform to use, "UNIX" or "DOS". + * @param {Function} encodeFileName the function to encode file names and comments. + */ +function ZipFileWorker(streamFiles, comment, platform, encodeFileName) { + GenericWorker.call(this, "ZipFileWorker"); + // The number of bytes written so far. This doesn't count accumulated chunks. + this.bytesWritten = 0; + // The comment of the zip file + this.zipComment = comment; + // The platform "generating" the zip file. + this.zipPlatform = platform; + // the function to encode file names and comments. + this.encodeFileName = encodeFileName; + // Should we stream the content of the files ? + this.streamFiles = streamFiles; + // If `streamFiles` is false, we will need to accumulate the content of the + // files to calculate sizes / crc32 (and write them *before* the content). + // This boolean indicates if we are accumulating chunks (it will change a lot + // during the lifetime of this worker). + this.accumulate = false; + // The buffer receiving chunks when accumulating content. + this.contentBuffer = []; + // The list of generated directory records. + this.dirRecords = []; + // The offset (in bytes) from the beginning of the zip file for the current source. + this.currentSourceOffset = 0; + // The total number of entries in this zip file. + this.entriesCount = 0; + // the name of the file currently being added, null when handling the end of the zip file. + // Used for the emitted metadata. + this.currentFile = null; + + + + this._sources = []; +} +utils.inherits(ZipFileWorker, GenericWorker); + +/** + * @see GenericWorker.push + */ +ZipFileWorker.prototype.push = function (chunk) { + + var currentFilePercent = chunk.meta.percent || 0; + var entriesCount = this.entriesCount; + var remainingFiles = this._sources.length; + + if(this.accumulate) { + this.contentBuffer.push(chunk); + } else { + this.bytesWritten += chunk.data.length; + + GenericWorker.prototype.push.call(this, { + data : chunk.data, + meta : { + currentFile : this.currentFile, + percent : entriesCount ? (currentFilePercent + 100 * (entriesCount - remainingFiles - 1)) / entriesCount : 100 + } + }); + } +}; + +/** + * The worker started a new source (an other worker). + * @param {Object} streamInfo the streamInfo object from the new source. + */ +ZipFileWorker.prototype.openedSource = function (streamInfo) { + this.currentSourceOffset = this.bytesWritten; + this.currentFile = streamInfo['file'].name; + + var streamedContent = this.streamFiles && !streamInfo['file'].dir; + + // don't stream folders (because they don't have any content) + if(streamedContent) { + var record = generateZipParts(streamInfo, streamedContent, false, this.currentSourceOffset, this.zipPlatform, this.encodeFileName); + this.push({ + data : record.fileRecord, + meta : {percent:0} + }); + } else { + // we need to wait for the whole file before pushing anything + this.accumulate = true; + } +}; + +/** + * The worker finished a source (an other worker). + * @param {Object} streamInfo the streamInfo object from the finished source. + */ +ZipFileWorker.prototype.closedSource = function (streamInfo) { + this.accumulate = false; + var streamedContent = this.streamFiles && !streamInfo['file'].dir; + var record = generateZipParts(streamInfo, streamedContent, true, this.currentSourceOffset, this.zipPlatform, this.encodeFileName); + + this.dirRecords.push(record.dirRecord); + if(streamedContent) { + // after the streamed file, we put data descriptors + this.push({ + data : generateDataDescriptors(streamInfo), + meta : {percent:100} + }); + } else { + // the content wasn't streamed, we need to push everything now + // first the file record, then the content + this.push({ + data : record.fileRecord, + meta : {percent:0} + }); + while(this.contentBuffer.length) { + this.push(this.contentBuffer.shift()); + } + } + this.currentFile = null; +}; + +/** + * @see GenericWorker.flush + */ +ZipFileWorker.prototype.flush = function () { + + var localDirLength = this.bytesWritten; + for(var i = 0; i < this.dirRecords.length; i++) { + this.push({ + data : this.dirRecords[i], + meta : {percent:100} + }); + } + var centralDirLength = this.bytesWritten - localDirLength; + + var dirEnd = generateCentralDirectoryEnd(this.dirRecords.length, centralDirLength, localDirLength, this.zipComment, this.encodeFileName); + + this.push({ + data : dirEnd, + meta : {percent:100} + }); +}; + +/** + * Prepare the next source to be read. + */ +ZipFileWorker.prototype.prepareNextSource = function () { + this.previous = this._sources.shift(); + this.openedSource(this.previous.streamInfo); + if (this.isPaused) { + this.previous.pause(); + } else { + this.previous.resume(); + } +}; + +/** + * @see GenericWorker.registerPrevious + */ +ZipFileWorker.prototype.registerPrevious = function (previous) { + this._sources.push(previous); + var self = this; + + previous.on('data', function (chunk) { + self.processChunk(chunk); + }); + previous.on('end', function () { + self.closedSource(self.previous.streamInfo); + if(self._sources.length) { + self.prepareNextSource(); + } else { + self.end(); + } + }); + previous.on('error', function (e) { + self.error(e); + }); + return this; +}; + +/** + * @see GenericWorker.resume + */ +ZipFileWorker.prototype.resume = function () { + if(!GenericWorker.prototype.resume.call(this)) { + return false; + } + + if (!this.previous && this._sources.length) { + this.prepareNextSource(); + return true; + } + if (!this.previous && !this._sources.length && !this.generatedError) { + this.end(); + return true; + } +}; + +/** + * @see GenericWorker.error + */ +ZipFileWorker.prototype.error = function (e) { + var sources = this._sources; + if(!GenericWorker.prototype.error.call(this, e)) { + return false; + } + for(var i = 0; i < sources.length; i++) { + try { + sources[i].error(e); + } catch(e) { + // the `error` exploded, nothing to do + } + } + return true; +}; + +/** + * @see GenericWorker.lock + */ +ZipFileWorker.prototype.lock = function () { + GenericWorker.prototype.lock.call(this); + var sources = this._sources; + for(var i = 0; i < sources.length; i++) { + sources[i].lock(); + } +}; + +module.exports = ZipFileWorker; + +},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(require,module,exports){ +'use strict'; + +var compressions = require('../compressions'); +var ZipFileWorker = require('./ZipFileWorker'); + +/** + * Find the compression to use. + * @param {String} fileCompression the compression defined at the file level, if any. + * @param {String} zipCompression the compression defined at the load() level. + * @return {Object} the compression object to use. + */ +var getCompression = function (fileCompression, zipCompression) { + + var compressionName = fileCompression || zipCompression; + var compression = compressions[compressionName]; + if (!compression) { + throw new Error(compressionName + " is not a valid compression method !"); + } + return compression; +}; + +/** + * Create a worker to generate a zip file. + * @param {JSZip} zip the JSZip instance at the right root level. + * @param {Object} options to generate the zip file. + * @param {String} comment the comment to use. + */ +exports.generateWorker = function (zip, options, comment) { + + var zipFileWorker = new ZipFileWorker(options.streamFiles, comment, options.platform, options.encodeFileName); + var entriesCount = 0; + try { + + zip.forEach(function (relativePath, file) { + entriesCount++; + var compression = getCompression(file.options.compression, options.compression); + var compressionOptions = file.options.compressionOptions || options.compressionOptions || {}; + var dir = file.dir, date = file.date; + + file._compressWorker(compression, compressionOptions) + .withStreamInfo("file", { + name : relativePath, + dir : dir, + date : date, + comment : file.comment || "", + unixPermissions : file.unixPermissions, + dosPermissions : file.dosPermissions + }) + .pipe(zipFileWorker); + }); + zipFileWorker.entriesCount = entriesCount; + } catch (e) { + zipFileWorker.error(e); + } + + return zipFileWorker; +}; + +},{"../compressions":3,"./ZipFileWorker":8}],10:[function(require,module,exports){ +'use strict'; + +/** + * Representation a of zip file in js + * @constructor + */ +function JSZip() { + // if this constructor is used without `new`, it adds `new` before itself: + if(!(this instanceof JSZip)) { + return new JSZip(); + } + + if(arguments.length) { + throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide."); + } + + // object containing the files : + // { + // "folder/" : {...}, + // "folder/data.txt" : {...} + // } + // NOTE: we use a null prototype because we do not + // want filenames like "toString" coming from a zip file + // to overwrite methods and attributes in a normal Object. + this.files = Object.create(null); + + this.comment = null; + + // Where we are in the hierarchy + this.root = ""; + this.clone = function() { + var newObj = new JSZip(); + for (var i in this) { + if (typeof this[i] !== "function") { + newObj[i] = this[i]; + } + } + return newObj; + }; +} +JSZip.prototype = require('./object'); +JSZip.prototype.loadAsync = require('./load'); +JSZip.support = require('./support'); +JSZip.defaults = require('./defaults'); + +// TODO find a better way to handle this version, +// a require('package.json').version doesn't work with webpack, see #327 +JSZip.version = "3.7.1"; + +JSZip.loadAsync = function (content, options) { + return new JSZip().loadAsync(content, options); +}; + +JSZip.external = require("./external"); +module.exports = JSZip; + +},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(require,module,exports){ +'use strict'; +var utils = require('./utils'); +var external = require("./external"); +var utf8 = require('./utf8'); +var ZipEntries = require('./zipEntries'); +var Crc32Probe = require('./stream/Crc32Probe'); +var nodejsUtils = require("./nodejsUtils"); + +/** + * Check the CRC32 of an entry. + * @param {ZipEntry} zipEntry the zip entry to check. + * @return {Promise} the result. + */ +function checkEntryCRC32(zipEntry) { + return new external.Promise(function (resolve, reject) { + var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe()); + worker.on("error", function (e) { + reject(e); + }) + .on("end", function () { + if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) { + reject(new Error("Corrupted zip : CRC32 mismatch")); + } else { + resolve(); + } + }) + .resume(); + }); +} + +module.exports = function (data, options) { + var zip = this; + options = utils.extend(options || {}, { + base64: false, + checkCRC32: false, + optimizedBinaryString: false, + createFolders: false, + decodeFileName: utf8.utf8decode + }); + + if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { + return external.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")); + } + + return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64) + .then(function (data) { + var zipEntries = new ZipEntries(options); + zipEntries.load(data); + return zipEntries; + }).then(function checkCRC32(zipEntries) { + var promises = [external.Promise.resolve(zipEntries)]; + var files = zipEntries.files; + if (options.checkCRC32) { + for (var i = 0; i < files.length; i++) { + promises.push(checkEntryCRC32(files[i])); + } + } + return external.Promise.all(promises); + }).then(function addFiles(results) { + var zipEntries = results.shift(); + var files = zipEntries.files; + for (var i = 0; i < files.length; i++) { + var input = files[i]; + zip.file(input.fileNameStr, input.decompressed, { + binary: true, + optimizedBinaryString: true, + date: input.date, + dir: input.dir, + comment: input.fileCommentStr.length ? input.fileCommentStr : null, + unixPermissions: input.unixPermissions, + dosPermissions: input.dosPermissions, + createFolders: options.createFolders + }); + } + if (zipEntries.zipComment.length) { + zip.comment = zipEntries.zipComment; + } + + return zip; + }); +}; + +},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(require,module,exports){ +"use strict"; + +var utils = require('../utils'); +var GenericWorker = require('../stream/GenericWorker'); + +/** + * A worker that use a nodejs stream as source. + * @constructor + * @param {String} filename the name of the file entry for this stream. + * @param {Readable} stream the nodejs stream. + */ +function NodejsStreamInputAdapter(filename, stream) { + GenericWorker.call(this, "Nodejs stream input adapter for " + filename); + this._upstreamEnded = false; + this._bindStream(stream); +} + +utils.inherits(NodejsStreamInputAdapter, GenericWorker); + +/** + * Prepare the stream and bind the callbacks on it. + * Do this ASAP on node 0.10 ! A lazy binding doesn't always work. + * @param {Stream} stream the nodejs stream to use. + */ +NodejsStreamInputAdapter.prototype._bindStream = function (stream) { + var self = this; + this._stream = stream; + stream.pause(); + stream + .on("data", function (chunk) { + self.push({ + data: chunk, + meta : { + percent : 0 + } + }); + }) + .on("error", function (e) { + if(self.isPaused) { + this.generatedError = e; + } else { + self.error(e); + } + }) + .on("end", function () { + if(self.isPaused) { + self._upstreamEnded = true; + } else { + self.end(); + } + }); +}; +NodejsStreamInputAdapter.prototype.pause = function () { + if(!GenericWorker.prototype.pause.call(this)) { + return false; + } + this._stream.pause(); + return true; +}; +NodejsStreamInputAdapter.prototype.resume = function () { + if(!GenericWorker.prototype.resume.call(this)) { + return false; + } + + if(this._upstreamEnded) { + this.end(); + } else { + this._stream.resume(); + } + + return true; +}; + +module.exports = NodejsStreamInputAdapter; + +},{"../stream/GenericWorker":28,"../utils":32}],13:[function(require,module,exports){ +'use strict'; + +var Readable = require('readable-stream').Readable; + +var utils = require('../utils'); +utils.inherits(NodejsStreamOutputAdapter, Readable); + +/** +* A nodejs stream using a worker as source. +* @see the SourceWrapper in http://nodejs.org/api/stream.html +* @constructor +* @param {StreamHelper} helper the helper wrapping the worker +* @param {Object} options the nodejs stream options +* @param {Function} updateCb the update callback. +*/ +function NodejsStreamOutputAdapter(helper, options, updateCb) { + Readable.call(this, options); + this._helper = helper; + + var self = this; + helper.on("data", function (data, meta) { + if (!self.push(data)) { + self._helper.pause(); + } + if(updateCb) { + updateCb(meta); + } + }) + .on("error", function(e) { + self.emit('error', e); + }) + .on("end", function () { + self.push(null); + }); +} + + +NodejsStreamOutputAdapter.prototype._read = function() { + this._helper.resume(); +}; + +module.exports = NodejsStreamOutputAdapter; + +},{"../utils":32,"readable-stream":16}],14:[function(require,module,exports){ +'use strict'; + +module.exports = { + /** + * True if this is running in Nodejs, will be undefined in a browser. + * In a browser, browserify won't include this file and the whole module + * will be resolved an empty object. + */ + isNode : typeof Buffer !== "undefined", + /** + * Create a new nodejs Buffer from an existing content. + * @param {Object} data the data to pass to the constructor. + * @param {String} encoding the encoding to use. + * @return {Buffer} a new Buffer. + */ + newBufferFrom: function(data, encoding) { + if (Buffer.from && Buffer.from !== Uint8Array.from) { + return Buffer.from(data, encoding); + } else { + if (typeof data === "number") { + // Safeguard for old Node.js versions. On newer versions, + // Buffer.from(number) / Buffer(number, encoding) already throw. + throw new Error("The \"data\" argument must not be a number"); + } + return new Buffer(data, encoding); + } + }, + /** + * Create a new nodejs Buffer with the specified size. + * @param {Integer} size the size of the buffer. + * @return {Buffer} a new Buffer. + */ + allocBuffer: function (size) { + if (Buffer.alloc) { + return Buffer.alloc(size); + } else { + var buf = new Buffer(size); + buf.fill(0); + return buf; + } + }, + /** + * Find out if an object is a Buffer. + * @param {Object} b the object to test. + * @return {Boolean} true if the object is a Buffer, false otherwise. + */ + isBuffer : function(b){ + return Buffer.isBuffer(b); + }, + + isStream : function (obj) { + return obj && + typeof obj.on === "function" && + typeof obj.pause === "function" && + typeof obj.resume === "function"; + } +}; + +},{}],15:[function(require,module,exports){ +'use strict'; +var utf8 = require('./utf8'); +var utils = require('./utils'); +var GenericWorker = require('./stream/GenericWorker'); +var StreamHelper = require('./stream/StreamHelper'); +var defaults = require('./defaults'); +var CompressedObject = require('./compressedObject'); +var ZipObject = require('./zipObject'); +var generate = require("./generate"); +var nodejsUtils = require("./nodejsUtils"); +var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter"); + + +/** + * Add a file in the current folder. + * @private + * @param {string} name the name of the file + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file + * @param {Object} originalOptions the options of the file + * @return {Object} the new file. + */ +var fileAdd = function(name, data, originalOptions) { + // be sure sub folders exist + var dataType = utils.getTypeOf(data), + parent; + + + /* + * Correct options. + */ + + var o = utils.extend(originalOptions || {}, defaults); + o.date = o.date || new Date(); + if (o.compression !== null) { + o.compression = o.compression.toUpperCase(); + } + + if (typeof o.unixPermissions === "string") { + o.unixPermissions = parseInt(o.unixPermissions, 8); + } + + // UNX_IFDIR 0040000 see zipinfo.c + if (o.unixPermissions && (o.unixPermissions & 0x4000)) { + o.dir = true; + } + // Bit 4 Directory + if (o.dosPermissions && (o.dosPermissions & 0x0010)) { + o.dir = true; + } + + if (o.dir) { + name = forceTrailingSlash(name); + } + if (o.createFolders && (parent = parentFolder(name))) { + folderAdd.call(this, parent, true); + } + + var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false; + if (!originalOptions || typeof originalOptions.binary === "undefined") { + o.binary = !isUnicodeString; + } + + + var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0; + + if (isCompressedEmpty || o.dir || !data || data.length === 0) { + o.base64 = false; + o.binary = true; + data = ""; + o.compression = "STORE"; + dataType = "string"; + } + + /* + * Convert content to fit. + */ + + var zipObjectContent = null; + if (data instanceof CompressedObject || data instanceof GenericWorker) { + zipObjectContent = data; + } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { + zipObjectContent = new NodejsStreamInputAdapter(name, data); + } else { + zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64); + } + + var object = new ZipObject(name, zipObjectContent, o); + this.files[name] = object; + /* + TODO: we can't throw an exception because we have async promises + (we can have a promise of a Date() for example) but returning a + promise is useless because file(name, data) returns the JSZip + object for chaining. Should we break that to allow the user + to catch the error ? + + return external.Promise.resolve(zipObjectContent) + .then(function () { + return object; + }); + */ +}; + +/** + * Find the parent folder of the path. + * @private + * @param {string} path the path to use + * @return {string} the parent folder, or "" + */ +var parentFolder = function (path) { + if (path.slice(-1) === '/') { + path = path.substring(0, path.length - 1); + } + var lastSlash = path.lastIndexOf('/'); + return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; +}; + +/** + * Returns the path with a slash at the end. + * @private + * @param {String} path the path to check. + * @return {String} the path with a trailing slash. + */ +var forceTrailingSlash = function(path) { + // Check the name ends with a / + if (path.slice(-1) !== "/") { + path += "/"; // IE doesn't like substr(-1) + } + return path; +}; + +/** + * Add a (sub) folder in the current folder. + * @private + * @param {string} name the folder's name + * @param {boolean=} [createFolders] If true, automatically create sub + * folders. Defaults to false. + * @return {Object} the new folder. + */ +var folderAdd = function(name, createFolders) { + createFolders = (typeof createFolders !== 'undefined') ? createFolders : defaults.createFolders; + + name = forceTrailingSlash(name); + + // Does this folder already exist? + if (!this.files[name]) { + fileAdd.call(this, name, null, { + dir: true, + createFolders: createFolders + }); + } + return this.files[name]; +}; + +/** +* Cross-window, cross-Node-context regular expression detection +* @param {Object} object Anything +* @return {Boolean} true if the object is a regular expression, +* false otherwise +*/ +function isRegExp(object) { + return Object.prototype.toString.call(object) === "[object RegExp]"; +} + +// return the actual prototype of JSZip +var out = { + /** + * @see loadAsync + */ + load: function() { + throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); + }, + + + /** + * Call a callback function for each entry at this folder level. + * @param {Function} cb the callback function: + * function (relativePath, file) {...} + * It takes 2 arguments : the relative path and the file. + */ + forEach: function(cb) { + var filename, relativePath, file; + /* jshint ignore:start */ + // ignore warning about unwanted properties because this.files is a null prototype object + for (filename in this.files) { + file = this.files[filename]; + relativePath = filename.slice(this.root.length, filename.length); + if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root + cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn... + } + } + /* jshint ignore:end */ + }, + + /** + * Filter nested files/folders with the specified function. + * @param {Function} search the predicate to use : + * function (relativePath, file) {...} + * It takes 2 arguments : the relative path and the file. + * @return {Array} An array of matching elements. + */ + filter: function(search) { + var result = []; + this.forEach(function (relativePath, entry) { + if (search(relativePath, entry)) { // the file matches the function + result.push(entry); + } + + }); + return result; + }, + + /** + * Add a file to the zip file, or search a file. + * @param {string|RegExp} name The name of the file to add (if data is defined), + * the name of the file to find (if no data) or a regex to match files. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded + * @param {Object} o File options + * @return {JSZip|Object|Array} this JSZip object (when adding a file), + * a file (when searching by string) or an array of files (when searching by regex). + */ + file: function(name, data, o) { + if (arguments.length === 1) { + if (isRegExp(name)) { + var regexp = name; + return this.filter(function(relativePath, file) { + return !file.dir && regexp.test(relativePath); + }); + } + else { // text + var obj = this.files[this.root + name]; + if (obj && !obj.dir) { + return obj; + } else { + return null; + } + } + } + else { // more than one argument : we have data ! + name = this.root + name; + fileAdd.call(this, name, data, o); + } + return this; + }, + + /** + * Add a directory to the zip file, or search. + * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. + * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. + */ + folder: function(arg) { + if (!arg) { + return this; + } + + if (isRegExp(arg)) { + return this.filter(function(relativePath, file) { + return file.dir && arg.test(relativePath); + }); + } + + // else, name is a new folder + var name = this.root + arg; + var newFolder = folderAdd.call(this, name); + + // Allow chaining by returning a new object with this folder as the root + var ret = this.clone(); + ret.root = newFolder.name; + return ret; + }, + + /** + * Delete a file, or a directory and all sub-files, from the zip + * @param {string} name the name of the file to delete + * @return {JSZip} this JSZip object + */ + remove: function(name) { + name = this.root + name; + var file = this.files[name]; + if (!file) { + // Look for any folders + if (name.slice(-1) !== "/") { + name += "/"; + } + file = this.files[name]; + } + + if (file && !file.dir) { + // file + delete this.files[name]; + } else { + // maybe a folder, delete recursively + var kids = this.filter(function(relativePath, file) { + return file.name.slice(0, name.length) === name; + }); + for (var i = 0; i < kids.length; i++) { + delete this.files[kids[i].name]; + } + } + + return this; + }, + + /** + * Generate the complete zip file + * @param {Object} options the options to generate the zip file : + * - compression, "STORE" by default. + * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. + * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file + */ + generate: function(options) { + throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); + }, + + /** + * Generate the complete zip file as an internal stream. + * @param {Object} options the options to generate the zip file : + * - compression, "STORE" by default. + * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. + * @return {StreamHelper} the streamed zip file. + */ + generateInternalStream: function(options) { + var worker, opts = {}; + try { + opts = utils.extend(options || {}, { + streamFiles: false, + compression: "STORE", + compressionOptions : null, + type: "", + platform: "DOS", + comment: null, + mimeType: 'application/zip', + encodeFileName: utf8.utf8encode + }); + + opts.type = opts.type.toLowerCase(); + opts.compression = opts.compression.toUpperCase(); + + // "binarystring" is preferred but the internals use "string". + if(opts.type === "binarystring") { + opts.type = "string"; + } + + if (!opts.type) { + throw new Error("No output type specified."); + } + + utils.checkSupport(opts.type); + + // accept nodejs `process.platform` + if( + opts.platform === 'darwin' || + opts.platform === 'freebsd' || + opts.platform === 'linux' || + opts.platform === 'sunos' + ) { + opts.platform = "UNIX"; + } + if (opts.platform === 'win32') { + opts.platform = "DOS"; + } + + var comment = opts.comment || this.comment || ""; + worker = generate.generateWorker(this, opts, comment); + } catch (e) { + worker = new GenericWorker("error"); + worker.error(e); + } + return new StreamHelper(worker, opts.type || "string", opts.mimeType); + }, + /** + * Generate the complete zip file asynchronously. + * @see generateInternalStream + */ + generateAsync: function(options, onUpdate) { + return this.generateInternalStream(options).accumulate(onUpdate); + }, + /** + * Generate the complete zip file asynchronously. + * @see generateInternalStream + */ + generateNodeStream: function(options, onUpdate) { + options = options || {}; + if (!options.type) { + options.type = "nodebuffer"; + } + return this.generateInternalStream(options).toNodejsStream(onUpdate); + } +}; +module.exports = out; + +},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(require,module,exports){ +/* + * This file is used by module bundlers (browserify/webpack/etc) when + * including a stream implementation. We use "readable-stream" to get a + * consistent behavior between nodejs versions but bundlers often have a shim + * for "stream". Using this shim greatly improve the compatibility and greatly + * reduce the final size of the bundle (only one stream implementation, not + * two). + */ +module.exports = require("stream"); + +},{"stream":undefined}],17:[function(require,module,exports){ +'use strict'; +var DataReader = require('./DataReader'); +var utils = require('../utils'); + +function ArrayReader(data) { + DataReader.call(this, data); + for(var i = 0; i < this.data.length; i++) { + data[i] = data[i] & 0xFF; + } +} +utils.inherits(ArrayReader, DataReader); +/** + * @see DataReader.byteAt + */ +ArrayReader.prototype.byteAt = function(i) { + return this.data[this.zero + i]; +}; +/** + * @see DataReader.lastIndexOfSignature + */ +ArrayReader.prototype.lastIndexOfSignature = function(sig) { + var sig0 = sig.charCodeAt(0), + sig1 = sig.charCodeAt(1), + sig2 = sig.charCodeAt(2), + sig3 = sig.charCodeAt(3); + for (var i = this.length - 4; i >= 0; --i) { + if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { + return i - this.zero; + } + } + + return -1; +}; +/** + * @see DataReader.readAndCheckSignature + */ +ArrayReader.prototype.readAndCheckSignature = function (sig) { + var sig0 = sig.charCodeAt(0), + sig1 = sig.charCodeAt(1), + sig2 = sig.charCodeAt(2), + sig3 = sig.charCodeAt(3), + data = this.readData(4); + return sig0 === data[0] && sig1 === data[1] && sig2 === data[2] && sig3 === data[3]; +}; +/** + * @see DataReader.readData + */ +ArrayReader.prototype.readData = function(size) { + this.checkOffset(size); + if(size === 0) { + return []; + } + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = ArrayReader; + +},{"../utils":32,"./DataReader":18}],18:[function(require,module,exports){ +'use strict'; +var utils = require('../utils'); + +function DataReader(data) { + this.data = data; // type : see implementation + this.length = data.length; + this.index = 0; + this.zero = 0; +} +DataReader.prototype = { + /** + * Check that the offset will not go too far. + * @param {string} offset the additional offset to check. + * @throws {Error} an Error if the offset is out of bounds. + */ + checkOffset: function(offset) { + this.checkIndex(this.index + offset); + }, + /** + * Check that the specified index will not be too far. + * @param {string} newIndex the index to check. + * @throws {Error} an Error if the index is out of bounds. + */ + checkIndex: function(newIndex) { + if (this.length < this.zero + newIndex || newIndex < 0) { + throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?"); + } + }, + /** + * Change the index. + * @param {number} newIndex The new index. + * @throws {Error} if the new index is out of the data. + */ + setIndex: function(newIndex) { + this.checkIndex(newIndex); + this.index = newIndex; + }, + /** + * Skip the next n bytes. + * @param {number} n the number of bytes to skip. + * @throws {Error} if the new index is out of the data. + */ + skip: function(n) { + this.setIndex(this.index + n); + }, + /** + * Get the byte at the specified index. + * @param {number} i the index to use. + * @return {number} a byte. + */ + byteAt: function(i) { + // see implementations + }, + /** + * Get the next number with a given byte size. + * @param {number} size the number of bytes to read. + * @return {number} the corresponding number. + */ + readInt: function(size) { + var result = 0, + i; + this.checkOffset(size); + for (i = this.index + size - 1; i >= this.index; i--) { + result = (result << 8) + this.byteAt(i); + } + this.index += size; + return result; + }, + /** + * Get the next string with a given byte size. + * @param {number} size the number of bytes to read. + * @return {string} the corresponding string. + */ + readString: function(size) { + return utils.transformTo("string", this.readData(size)); + }, + /** + * Get raw data without conversion, bytes. + * @param {number} size the number of bytes to read. + * @return {Object} the raw data, implementation specific. + */ + readData: function(size) { + // see implementations + }, + /** + * Find the last occurrence of a zip signature (4 bytes). + * @param {string} sig the signature to find. + * @return {number} the index of the last occurrence, -1 if not found. + */ + lastIndexOfSignature: function(sig) { + // see implementations + }, + /** + * Read the signature (4 bytes) at the current position and compare it with sig. + * @param {string} sig the expected signature + * @return {boolean} true if the signature matches, false otherwise. + */ + readAndCheckSignature: function(sig) { + // see implementations + }, + /** + * Get the next date. + * @return {Date} the date. + */ + readDate: function() { + var dostime = this.readInt(4); + return new Date(Date.UTC( + ((dostime >> 25) & 0x7f) + 1980, // year + ((dostime >> 21) & 0x0f) - 1, // month + (dostime >> 16) & 0x1f, // day + (dostime >> 11) & 0x1f, // hour + (dostime >> 5) & 0x3f, // minute + (dostime & 0x1f) << 1)); // second + } +}; +module.exports = DataReader; + +},{"../utils":32}],19:[function(require,module,exports){ +'use strict'; +var Uint8ArrayReader = require('./Uint8ArrayReader'); +var utils = require('../utils'); + +function NodeBufferReader(data) { + Uint8ArrayReader.call(this, data); +} +utils.inherits(NodeBufferReader, Uint8ArrayReader); + +/** + * @see DataReader.readData + */ +NodeBufferReader.prototype.readData = function(size) { + this.checkOffset(size); + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = NodeBufferReader; + +},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(require,module,exports){ +'use strict'; +var DataReader = require('./DataReader'); +var utils = require('../utils'); + +function StringReader(data) { + DataReader.call(this, data); +} +utils.inherits(StringReader, DataReader); +/** + * @see DataReader.byteAt + */ +StringReader.prototype.byteAt = function(i) { + return this.data.charCodeAt(this.zero + i); +}; +/** + * @see DataReader.lastIndexOfSignature + */ +StringReader.prototype.lastIndexOfSignature = function(sig) { + return this.data.lastIndexOf(sig) - this.zero; +}; +/** + * @see DataReader.readAndCheckSignature + */ +StringReader.prototype.readAndCheckSignature = function (sig) { + var data = this.readData(4); + return sig === data; +}; +/** + * @see DataReader.readData + */ +StringReader.prototype.readData = function(size) { + this.checkOffset(size); + // this will work because the constructor applied the "& 0xff" mask. + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = StringReader; + +},{"../utils":32,"./DataReader":18}],21:[function(require,module,exports){ +'use strict'; +var ArrayReader = require('./ArrayReader'); +var utils = require('../utils'); + +function Uint8ArrayReader(data) { + ArrayReader.call(this, data); +} +utils.inherits(Uint8ArrayReader, ArrayReader); +/** + * @see DataReader.readData + */ +Uint8ArrayReader.prototype.readData = function(size) { + this.checkOffset(size); + if(size === 0) { + // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of []. + return new Uint8Array(0); + } + var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = Uint8ArrayReader; + +},{"../utils":32,"./ArrayReader":17}],22:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var support = require('../support'); +var ArrayReader = require('./ArrayReader'); +var StringReader = require('./StringReader'); +var NodeBufferReader = require('./NodeBufferReader'); +var Uint8ArrayReader = require('./Uint8ArrayReader'); + +/** + * Create a reader adapted to the data. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to read. + * @return {DataReader} the data reader. + */ +module.exports = function (data) { + var type = utils.getTypeOf(data); + utils.checkSupport(type); + if (type === "string" && !support.uint8array) { + return new StringReader(data); + } + if (type === "nodebuffer") { + return new NodeBufferReader(data); + } + if (support.uint8array) { + return new Uint8ArrayReader(utils.transformTo("uint8array", data)); + } + return new ArrayReader(utils.transformTo("array", data)); +}; + +},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(require,module,exports){ +'use strict'; +exports.LOCAL_FILE_HEADER = "PK\x03\x04"; +exports.CENTRAL_FILE_HEADER = "PK\x01\x02"; +exports.CENTRAL_DIRECTORY_END = "PK\x05\x06"; +exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07"; +exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06"; +exports.DATA_DESCRIPTOR = "PK\x07\x08"; + +},{}],24:[function(require,module,exports){ +'use strict'; + +var GenericWorker = require('./GenericWorker'); +var utils = require('../utils'); + +/** + * A worker which convert chunks to a specified type. + * @constructor + * @param {String} destType the destination type. + */ +function ConvertWorker(destType) { + GenericWorker.call(this, "ConvertWorker to " + destType); + this.destType = destType; +} +utils.inherits(ConvertWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +ConvertWorker.prototype.processChunk = function (chunk) { + this.push({ + data : utils.transformTo(this.destType, chunk.data), + meta : chunk.meta + }); +}; +module.exports = ConvertWorker; + +},{"../utils":32,"./GenericWorker":28}],25:[function(require,module,exports){ +'use strict'; + +var GenericWorker = require('./GenericWorker'); +var crc32 = require('../crc32'); +var utils = require('../utils'); + +/** + * A worker which calculate the crc32 of the data flowing through. + * @constructor + */ +function Crc32Probe() { + GenericWorker.call(this, "Crc32Probe"); + this.withStreamInfo("crc32", 0); +} +utils.inherits(Crc32Probe, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +Crc32Probe.prototype.processChunk = function (chunk) { + this.streamInfo.crc32 = crc32(chunk.data, this.streamInfo.crc32 || 0); + this.push(chunk); +}; +module.exports = Crc32Probe; + +},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var GenericWorker = require('./GenericWorker'); + +/** + * A worker which calculate the total length of the data flowing through. + * @constructor + * @param {String} propName the name used to expose the length + */ +function DataLengthProbe(propName) { + GenericWorker.call(this, "DataLengthProbe for " + propName); + this.propName = propName; + this.withStreamInfo(propName, 0); +} +utils.inherits(DataLengthProbe, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +DataLengthProbe.prototype.processChunk = function (chunk) { + if(chunk) { + var length = this.streamInfo[this.propName] || 0; + this.streamInfo[this.propName] = length + chunk.data.length; + } + GenericWorker.prototype.processChunk.call(this, chunk); +}; +module.exports = DataLengthProbe; + + +},{"../utils":32,"./GenericWorker":28}],27:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var GenericWorker = require('./GenericWorker'); + +// the size of the generated chunks +// TODO expose this as a public variable +var DEFAULT_BLOCK_SIZE = 16 * 1024; + +/** + * A worker that reads a content and emits chunks. + * @constructor + * @param {Promise} dataP the promise of the data to split + */ +function DataWorker(dataP) { + GenericWorker.call(this, "DataWorker"); + var self = this; + this.dataIsReady = false; + this.index = 0; + this.max = 0; + this.data = null; + this.type = ""; + + this._tickScheduled = false; + + dataP.then(function (data) { + self.dataIsReady = true; + self.data = data; + self.max = data && data.length || 0; + self.type = utils.getTypeOf(data); + if(!self.isPaused) { + self._tickAndRepeat(); + } + }, function (e) { + self.error(e); + }); +} + +utils.inherits(DataWorker, GenericWorker); + +/** + * @see GenericWorker.cleanUp + */ +DataWorker.prototype.cleanUp = function () { + GenericWorker.prototype.cleanUp.call(this); + this.data = null; +}; + +/** + * @see GenericWorker.resume + */ +DataWorker.prototype.resume = function () { + if(!GenericWorker.prototype.resume.call(this)) { + return false; + } + + if (!this._tickScheduled && this.dataIsReady) { + this._tickScheduled = true; + utils.delay(this._tickAndRepeat, [], this); + } + return true; +}; + +/** + * Trigger a tick a schedule an other call to this function. + */ +DataWorker.prototype._tickAndRepeat = function() { + this._tickScheduled = false; + if(this.isPaused || this.isFinished) { + return; + } + this._tick(); + if(!this.isFinished) { + utils.delay(this._tickAndRepeat, [], this); + this._tickScheduled = true; + } +}; + +/** + * Read and push a chunk. + */ +DataWorker.prototype._tick = function() { + + if(this.isPaused || this.isFinished) { + return false; + } + + var size = DEFAULT_BLOCK_SIZE; + var data = null, nextIndex = Math.min(this.max, this.index + size); + if (this.index >= this.max) { + // EOF + return this.end(); + } else { + switch(this.type) { + case "string": + data = this.data.substring(this.index, nextIndex); + break; + case "uint8array": + data = this.data.subarray(this.index, nextIndex); + break; + case "array": + case "nodebuffer": + data = this.data.slice(this.index, nextIndex); + break; + } + this.index = nextIndex; + return this.push({ + data : data, + meta : { + percent : this.max ? this.index / this.max * 100 : 0 + } + }); + } +}; + +module.exports = DataWorker; + +},{"../utils":32,"./GenericWorker":28}],28:[function(require,module,exports){ +'use strict'; + +/** + * A worker that does nothing but passing chunks to the next one. This is like + * a nodejs stream but with some differences. On the good side : + * - it works on IE 6-9 without any issue / polyfill + * - it weights less than the full dependencies bundled with browserify + * - it forwards errors (no need to declare an error handler EVERYWHERE) + * + * A chunk is an object with 2 attributes : `meta` and `data`. The former is an + * object containing anything (`percent` for example), see each worker for more + * details. The latter is the real data (String, Uint8Array, etc). + * + * @constructor + * @param {String} name the name of the stream (mainly used for debugging purposes) + */ +function GenericWorker(name) { + // the name of the worker + this.name = name || "default"; + // an object containing metadata about the workers chain + this.streamInfo = {}; + // an error which happened when the worker was paused + this.generatedError = null; + // an object containing metadata to be merged by this worker into the general metadata + this.extraStreamInfo = {}; + // true if the stream is paused (and should not do anything), false otherwise + this.isPaused = true; + // true if the stream is finished (and should not do anything), false otherwise + this.isFinished = false; + // true if the stream is locked to prevent further structure updates (pipe), false otherwise + this.isLocked = false; + // the event listeners + this._listeners = { + 'data':[], + 'end':[], + 'error':[] + }; + // the previous worker, if any + this.previous = null; +} + +GenericWorker.prototype = { + /** + * Push a chunk to the next workers. + * @param {Object} chunk the chunk to push + */ + push : function (chunk) { + this.emit("data", chunk); + }, + /** + * End the stream. + * @return {Boolean} true if this call ended the worker, false otherwise. + */ + end : function () { + if (this.isFinished) { + return false; + } + + this.flush(); + try { + this.emit("end"); + this.cleanUp(); + this.isFinished = true; + } catch (e) { + this.emit("error", e); + } + return true; + }, + /** + * End the stream with an error. + * @param {Error} e the error which caused the premature end. + * @return {Boolean} true if this call ended the worker with an error, false otherwise. + */ + error : function (e) { + if (this.isFinished) { + return false; + } + + if(this.isPaused) { + this.generatedError = e; + } else { + this.isFinished = true; + + this.emit("error", e); + + // in the workers chain exploded in the middle of the chain, + // the error event will go downward but we also need to notify + // workers upward that there has been an error. + if(this.previous) { + this.previous.error(e); + } + + this.cleanUp(); + } + return true; + }, + /** + * Add a callback on an event. + * @param {String} name the name of the event (data, end, error) + * @param {Function} listener the function to call when the event is triggered + * @return {GenericWorker} the current object for chainability + */ + on : function (name, listener) { + this._listeners[name].push(listener); + return this; + }, + /** + * Clean any references when a worker is ending. + */ + cleanUp : function () { + this.streamInfo = this.generatedError = this.extraStreamInfo = null; + this._listeners = []; + }, + /** + * Trigger an event. This will call registered callback with the provided arg. + * @param {String} name the name of the event (data, end, error) + * @param {Object} arg the argument to call the callback with. + */ + emit : function (name, arg) { + if (this._listeners[name]) { + for(var i = 0; i < this._listeners[name].length; i++) { + this._listeners[name][i].call(this, arg); + } + } + }, + /** + * Chain a worker with an other. + * @param {Worker} next the worker receiving events from the current one. + * @return {worker} the next worker for chainability + */ + pipe : function (next) { + return next.registerPrevious(this); + }, + /** + * Same as `pipe` in the other direction. + * Using an API with `pipe(next)` is very easy. + * Implementing the API with the point of view of the next one registering + * a source is easier, see the ZipFileWorker. + * @param {Worker} previous the previous worker, sending events to this one + * @return {Worker} the current worker for chainability + */ + registerPrevious : function (previous) { + if (this.isLocked) { + throw new Error("The stream '" + this + "' has already been used."); + } + + // sharing the streamInfo... + this.streamInfo = previous.streamInfo; + // ... and adding our own bits + this.mergeStreamInfo(); + this.previous = previous; + var self = this; + previous.on('data', function (chunk) { + self.processChunk(chunk); + }); + previous.on('end', function () { + self.end(); + }); + previous.on('error', function (e) { + self.error(e); + }); + return this; + }, + /** + * Pause the stream so it doesn't send events anymore. + * @return {Boolean} true if this call paused the worker, false otherwise. + */ + pause : function () { + if(this.isPaused || this.isFinished) { + return false; + } + this.isPaused = true; + + if(this.previous) { + this.previous.pause(); + } + return true; + }, + /** + * Resume a paused stream. + * @return {Boolean} true if this call resumed the worker, false otherwise. + */ + resume : function () { + if(!this.isPaused || this.isFinished) { + return false; + } + this.isPaused = false; + + // if true, the worker tried to resume but failed + var withError = false; + if(this.generatedError) { + this.error(this.generatedError); + withError = true; + } + if(this.previous) { + this.previous.resume(); + } + + return !withError; + }, + /** + * Flush any remaining bytes as the stream is ending. + */ + flush : function () {}, + /** + * Process a chunk. This is usually the method overridden. + * @param {Object} chunk the chunk to process. + */ + processChunk : function(chunk) { + this.push(chunk); + }, + /** + * Add a key/value to be added in the workers chain streamInfo once activated. + * @param {String} key the key to use + * @param {Object} value the associated value + * @return {Worker} the current worker for chainability + */ + withStreamInfo : function (key, value) { + this.extraStreamInfo[key] = value; + this.mergeStreamInfo(); + return this; + }, + /** + * Merge this worker's streamInfo into the chain's streamInfo. + */ + mergeStreamInfo : function () { + for(var key in this.extraStreamInfo) { + if (!this.extraStreamInfo.hasOwnProperty(key)) { + continue; + } + this.streamInfo[key] = this.extraStreamInfo[key]; + } + }, + + /** + * Lock the stream to prevent further updates on the workers chain. + * After calling this method, all calls to pipe will fail. + */ + lock: function () { + if (this.isLocked) { + throw new Error("The stream '" + this + "' has already been used."); + } + this.isLocked = true; + if (this.previous) { + this.previous.lock(); + } + }, + + /** + * + * Pretty print the workers chain. + */ + toString : function () { + var me = "Worker " + this.name; + if (this.previous) { + return this.previous + " -> " + me; + } else { + return me; + } + } +}; + +module.exports = GenericWorker; + +},{}],29:[function(require,module,exports){ +'use strict'; + +var utils = require('../utils'); +var ConvertWorker = require('./ConvertWorker'); +var GenericWorker = require('./GenericWorker'); +var base64 = require('../base64'); +var support = require("../support"); +var external = require("../external"); + +var NodejsStreamOutputAdapter = null; +if (support.nodestream) { + try { + NodejsStreamOutputAdapter = require('../nodejs/NodejsStreamOutputAdapter'); + } catch(e) {} +} + +/** + * Apply the final transformation of the data. If the user wants a Blob for + * example, it's easier to work with an U8intArray and finally do the + * ArrayBuffer/Blob conversion. + * @param {String} type the name of the final type + * @param {String|Uint8Array|Buffer} content the content to transform + * @param {String} mimeType the mime type of the content, if applicable. + * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format. + */ +function transformZipOutput(type, content, mimeType) { + switch(type) { + case "blob" : + return utils.newBlob(utils.transformTo("arraybuffer", content), mimeType); + case "base64" : + return base64.encode(content); + default : + return utils.transformTo(type, content); + } +} + +/** + * Concatenate an array of data of the given type. + * @param {String} type the type of the data in the given array. + * @param {Array} dataArray the array containing the data chunks to concatenate + * @return {String|Uint8Array|Buffer} the concatenated data + * @throws Error if the asked type is unsupported + */ +function concat (type, dataArray) { + var i, index = 0, res = null, totalLength = 0; + for(i = 0; i < dataArray.length; i++) { + totalLength += dataArray[i].length; + } + switch(type) { + case "string": + return dataArray.join(""); + case "array": + return Array.prototype.concat.apply([], dataArray); + case "uint8array": + res = new Uint8Array(totalLength); + for(i = 0; i < dataArray.length; i++) { + res.set(dataArray[i], index); + index += dataArray[i].length; + } + return res; + case "nodebuffer": + return Buffer.concat(dataArray); + default: + throw new Error("concat : unsupported type '" + type + "'"); + } +} + +/** + * Listen a StreamHelper, accumulate its content and concatenate it into a + * complete block. + * @param {StreamHelper} helper the helper to use. + * @param {Function} updateCallback a callback called on each update. Called + * with one arg : + * - the metadata linked to the update received. + * @return Promise the promise for the accumulation. + */ +function accumulate(helper, updateCallback) { + return new external.Promise(function (resolve, reject){ + var dataArray = []; + var chunkType = helper._internalType, + resultType = helper._outputType, + mimeType = helper._mimeType; + helper + .on('data', function (data, meta) { + dataArray.push(data); + if(updateCallback) { + updateCallback(meta); + } + }) + .on('error', function(err) { + dataArray = []; + reject(err); + }) + .on('end', function (){ + try { + var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType); + resolve(result); + } catch (e) { + reject(e); + } + dataArray = []; + }) + .resume(); + }); +} + +/** + * An helper to easily use workers outside of JSZip. + * @constructor + * @param {Worker} worker the worker to wrap + * @param {String} outputType the type of data expected by the use + * @param {String} mimeType the mime type of the content, if applicable. + */ +function StreamHelper(worker, outputType, mimeType) { + var internalType = outputType; + switch(outputType) { + case "blob": + case "arraybuffer": + internalType = "uint8array"; + break; + case "base64": + internalType = "string"; + break; + } + + try { + // the type used internally + this._internalType = internalType; + // the type used to output results + this._outputType = outputType; + // the mime type + this._mimeType = mimeType; + utils.checkSupport(internalType); + this._worker = worker.pipe(new ConvertWorker(internalType)); + // the last workers can be rewired without issues but we need to + // prevent any updates on previous workers. + worker.lock(); + } catch(e) { + this._worker = new GenericWorker("error"); + this._worker.error(e); + } +} + +StreamHelper.prototype = { + /** + * Listen a StreamHelper, accumulate its content and concatenate it into a + * complete block. + * @param {Function} updateCb the update callback. + * @return Promise the promise for the accumulation. + */ + accumulate : function (updateCb) { + return accumulate(this, updateCb); + }, + /** + * Add a listener on an event triggered on a stream. + * @param {String} evt the name of the event + * @param {Function} fn the listener + * @return {StreamHelper} the current helper. + */ + on : function (evt, fn) { + var self = this; + + if(evt === "data") { + this._worker.on(evt, function (chunk) { + fn.call(self, chunk.data, chunk.meta); + }); + } else { + this._worker.on(evt, function () { + utils.delay(fn, arguments, self); + }); + } + return this; + }, + /** + * Resume the flow of chunks. + * @return {StreamHelper} the current helper. + */ + resume : function () { + utils.delay(this._worker.resume, [], this._worker); + return this; + }, + /** + * Pause the flow of chunks. + * @return {StreamHelper} the current helper. + */ + pause : function () { + this._worker.pause(); + return this; + }, + /** + * Return a nodejs stream for this helper. + * @param {Function} updateCb the update callback. + * @return {NodejsStreamOutputAdapter} the nodejs stream. + */ + toNodejsStream : function (updateCb) { + utils.checkSupport("nodestream"); + if (this._outputType !== "nodebuffer") { + // an object stream containing blob/arraybuffer/uint8array/string + // is strange and I don't know if it would be useful. + // I you find this comment and have a good usecase, please open a + // bug report ! + throw new Error(this._outputType + " is not supported by this method"); + } + + return new NodejsStreamOutputAdapter(this, { + objectMode : this._outputType !== "nodebuffer" + }, updateCb); + } +}; + + +module.exports = StreamHelper; + +},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(require,module,exports){ +'use strict'; + +exports.base64 = true; +exports.array = true; +exports.string = true; +exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"; +exports.nodebuffer = typeof Buffer !== "undefined"; +// contains true if JSZip can read/generate Uint8Array, false otherwise. +exports.uint8array = typeof Uint8Array !== "undefined"; + +if (typeof ArrayBuffer === "undefined") { + exports.blob = false; +} +else { + var buffer = new ArrayBuffer(0); + try { + exports.blob = new Blob([buffer], { + type: "application/zip" + }).size === 0; + } + catch (e) { + try { + var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder; + var builder = new Builder(); + builder.append(buffer); + exports.blob = builder.getBlob('application/zip').size === 0; + } + catch (e) { + exports.blob = false; + } + } +} + +try { + exports.nodestream = !!require('readable-stream').Readable; +} catch(e) { + exports.nodestream = false; +} + +},{"readable-stream":16}],31:[function(require,module,exports){ +'use strict'; + +var utils = require('./utils'); +var support = require('./support'); +var nodejsUtils = require('./nodejsUtils'); +var GenericWorker = require('./stream/GenericWorker'); + +/** + * The following functions come from pako, from pako/lib/utils/strings + * released under the MIT license, see pako https://github.com/nodeca/pako/ + */ + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +var _utf8len = new Array(256); +for (var i=0; i<256; i++) { + _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); +} +_utf8len[254]=_utf8len[254]=1; // Invalid sequence start + +// convert string to array (typed, when possible) +var string2buf = function (str) { + var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + if (support.uint8array) { + buf = new Uint8Array(buf_len); + } else { + buf = new Array(buf_len); + } + + // convert + for (i=0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { + c2 = str.charCodeAt(m_pos+1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +var utf8border = function(buf, max) { + var pos; + + max = max || buf.length; + if (max > buf.length) { max = buf.length; } + + // go back from last position, until start of sequence found + pos = max-1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } + + // Fuckup - very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) { return max; } + + // If we came to start of buffer - that means vuffer is too small, + // return max too. + if (pos === 0) { return max; } + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; + +// convert array to string +var buf2string = function (buf) { + var str, i, out, c, c_len; + var len = buf.length; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + var utf16buf = new Array(len*2); + + for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + // shrinkBuf(utf16buf, out) + if (utf16buf.length !== out) { + if(utf16buf.subarray) { + utf16buf = utf16buf.subarray(0, out); + } else { + utf16buf.length = out; + } + } + + // return String.fromCharCode.apply(null, utf16buf); + return utils.applyFromCharCode(utf16buf); +}; + + +// That's all for the pako functions. + + +/** + * Transform a javascript string into an array (typed if possible) of bytes, + * UTF-8 encoded. + * @param {String} str the string to encode + * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string. + */ +exports.utf8encode = function utf8encode(str) { + if (support.nodebuffer) { + return nodejsUtils.newBufferFrom(str, "utf-8"); + } + + return string2buf(str); +}; + + +/** + * Transform a bytes array (or a representation) representing an UTF-8 encoded + * string into a javascript string. + * @param {Array|Uint8Array|Buffer} buf the data de decode + * @return {String} the decoded string. + */ +exports.utf8decode = function utf8decode(buf) { + if (support.nodebuffer) { + return utils.transformTo("nodebuffer", buf).toString("utf-8"); + } + + buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf); + + return buf2string(buf); +}; + +/** + * A worker to decode utf8 encoded binary chunks into string chunks. + * @constructor + */ +function Utf8DecodeWorker() { + GenericWorker.call(this, "utf-8 decode"); + // the last bytes if a chunk didn't end with a complete codepoint. + this.leftOver = null; +} +utils.inherits(Utf8DecodeWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +Utf8DecodeWorker.prototype.processChunk = function (chunk) { + + var data = utils.transformTo(support.uint8array ? "uint8array" : "array", chunk.data); + + // 1st step, re-use what's left of the previous chunk + if (this.leftOver && this.leftOver.length) { + if(support.uint8array) { + var previousData = data; + data = new Uint8Array(previousData.length + this.leftOver.length); + data.set(this.leftOver, 0); + data.set(previousData, this.leftOver.length); + } else { + data = this.leftOver.concat(data); + } + this.leftOver = null; + } + + var nextBoundary = utf8border(data); + var usableData = data; + if (nextBoundary !== data.length) { + if (support.uint8array) { + usableData = data.subarray(0, nextBoundary); + this.leftOver = data.subarray(nextBoundary, data.length); + } else { + usableData = data.slice(0, nextBoundary); + this.leftOver = data.slice(nextBoundary, data.length); + } + } + + this.push({ + data : exports.utf8decode(usableData), + meta : chunk.meta + }); +}; + +/** + * @see GenericWorker.flush + */ +Utf8DecodeWorker.prototype.flush = function () { + if(this.leftOver && this.leftOver.length) { + this.push({ + data : exports.utf8decode(this.leftOver), + meta : {} + }); + this.leftOver = null; + } +}; +exports.Utf8DecodeWorker = Utf8DecodeWorker; + +/** + * A worker to endcode string chunks into utf8 encoded binary chunks. + * @constructor + */ +function Utf8EncodeWorker() { + GenericWorker.call(this, "utf-8 encode"); +} +utils.inherits(Utf8EncodeWorker, GenericWorker); + +/** + * @see GenericWorker.processChunk + */ +Utf8EncodeWorker.prototype.processChunk = function (chunk) { + this.push({ + data : exports.utf8encode(chunk.data), + meta : chunk.meta + }); +}; +exports.Utf8EncodeWorker = Utf8EncodeWorker; + +},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(require,module,exports){ +'use strict'; + +var support = require('./support'); +var base64 = require('./base64'); +var nodejsUtils = require('./nodejsUtils'); +var setImmediate = require('set-immediate-shim'); +var external = require("./external"); + + +/** + * Convert a string that pass as a "binary string": it should represent a byte + * array but may have > 255 char codes. Be sure to take only the first byte + * and returns the byte array. + * @param {String} str the string to transform. + * @return {Array|Uint8Array} the string in a binary format. + */ +function string2binary(str) { + var result = null; + if (support.uint8array) { + result = new Uint8Array(str.length); + } else { + result = new Array(str.length); + } + return stringToArrayLike(str, result); +} + +/** + * Create a new blob with the given content and the given type. + * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use + * an Uint8Array because the stock browser of android 4 won't accept it (it + * will be silently converted to a string, "[object Uint8Array]"). + * + * Use only ONE part to build the blob to avoid a memory leak in IE11 / Edge: + * when a large amount of Array is used to create the Blob, the amount of + * memory consumed is nearly 100 times the original data amount. + * + * @param {String} type the mime type of the blob. + * @return {Blob} the created blob. + */ +exports.newBlob = function(part, type) { + exports.checkSupport("blob"); + + try { + // Blob constructor + return new Blob([part], { + type: type + }); + } + catch (e) { + + try { + // deprecated, browser only, old way + var Builder = self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || self.MSBlobBuilder; + var builder = new Builder(); + builder.append(part); + return builder.getBlob(type); + } + catch (e) { + + // well, fuck ?! + throw new Error("Bug : can't construct the Blob."); + } + } + + +}; +/** + * The identity function. + * @param {Object} input the input. + * @return {Object} the same input. + */ +function identity(input) { + return input; +} + +/** + * Fill in an array with a string. + * @param {String} str the string to use. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated). + * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array. + */ +function stringToArrayLike(str, array) { + for (var i = 0; i < str.length; ++i) { + array[i] = str.charCodeAt(i) & 0xFF; + } + return array; +} + +/** + * An helper for the function arrayLikeToString. + * This contains static information and functions that + * can be optimized by the browser JIT compiler. + */ +var arrayToStringHelper = { + /** + * Transform an array of int into a string, chunk by chunk. + * See the performances notes on arrayLikeToString. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @param {String} type the type of the array. + * @param {Integer} chunk the chunk size. + * @return {String} the resulting string. + * @throws Error if the chunk is too big for the stack. + */ + stringifyByChunk: function(array, type, chunk) { + var result = [], k = 0, len = array.length; + // shortcut + if (len <= chunk) { + return String.fromCharCode.apply(null, array); + } + while (k < len) { + if (type === "array" || type === "nodebuffer") { + result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len)))); + } + else { + result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len)))); + } + k += chunk; + } + return result.join(""); + }, + /** + * Call String.fromCharCode on every item in the array. + * This is the naive implementation, which generate A LOT of intermediate string. + * This should be used when everything else fail. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @return {String} the result. + */ + stringifyByChar: function(array){ + var resultStr = ""; + for(var i = 0; i < array.length; i++) { + resultStr += String.fromCharCode(array[i]); + } + return resultStr; + }, + applyCanBeUsed : { + /** + * true if the browser accepts to use String.fromCharCode on Uint8Array + */ + uint8array : (function () { + try { + return support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1; + } catch (e) { + return false; + } + })(), + /** + * true if the browser accepts to use String.fromCharCode on nodejs Buffer. + */ + nodebuffer : (function () { + try { + return support.nodebuffer && String.fromCharCode.apply(null, nodejsUtils.allocBuffer(1)).length === 1; + } catch (e) { + return false; + } + })() + } +}; + +/** + * Transform an array-like object to a string. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. + * @return {String} the result. + */ +function arrayLikeToString(array) { + // Performances notes : + // -------------------- + // String.fromCharCode.apply(null, array) is the fastest, see + // see http://jsperf.com/converting-a-uint8array-to-a-string/2 + // but the stack is limited (and we can get huge arrays !). + // + // result += String.fromCharCode(array[i]); generate too many strings ! + // + // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 + // TODO : we now have workers that split the work. Do we still need that ? + var chunk = 65536, + type = exports.getTypeOf(array), + canUseApply = true; + if (type === "uint8array") { + canUseApply = arrayToStringHelper.applyCanBeUsed.uint8array; + } else if (type === "nodebuffer") { + canUseApply = arrayToStringHelper.applyCanBeUsed.nodebuffer; + } + + if (canUseApply) { + while (chunk > 1) { + try { + return arrayToStringHelper.stringifyByChunk(array, type, chunk); + } catch (e) { + chunk = Math.floor(chunk / 2); + } + } + } + + // no apply or chunk error : slow and painful algorithm + // default browser on android 4.* + return arrayToStringHelper.stringifyByChar(array); +} + +exports.applyFromCharCode = arrayLikeToString; + + +/** + * Copy the data from an array-like to an other array-like. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array. + * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated. + * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array. + */ +function arrayLikeToArrayLike(arrayFrom, arrayTo) { + for (var i = 0; i < arrayFrom.length; i++) { + arrayTo[i] = arrayFrom[i]; + } + return arrayTo; +} + +// a matrix containing functions to transform everything into everything. +var transform = {}; + +// string to ? +transform["string"] = { + "string": identity, + "array": function(input) { + return stringToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return transform["string"]["uint8array"](input).buffer; + }, + "uint8array": function(input) { + return stringToArrayLike(input, new Uint8Array(input.length)); + }, + "nodebuffer": function(input) { + return stringToArrayLike(input, nodejsUtils.allocBuffer(input.length)); + } +}; + +// array to ? +transform["array"] = { + "string": arrayLikeToString, + "array": identity, + "arraybuffer": function(input) { + return (new Uint8Array(input)).buffer; + }, + "uint8array": function(input) { + return new Uint8Array(input); + }, + "nodebuffer": function(input) { + return nodejsUtils.newBufferFrom(input); + } +}; + +// arraybuffer to ? +transform["arraybuffer"] = { + "string": function(input) { + return arrayLikeToString(new Uint8Array(input)); + }, + "array": function(input) { + return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); + }, + "arraybuffer": identity, + "uint8array": function(input) { + return new Uint8Array(input); + }, + "nodebuffer": function(input) { + return nodejsUtils.newBufferFrom(new Uint8Array(input)); + } +}; + +// uint8array to ? +transform["uint8array"] = { + "string": arrayLikeToString, + "array": function(input) { + return arrayLikeToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return input.buffer; + }, + "uint8array": identity, + "nodebuffer": function(input) { + return nodejsUtils.newBufferFrom(input); + } +}; + +// nodebuffer to ? +transform["nodebuffer"] = { + "string": arrayLikeToString, + "array": function(input) { + return arrayLikeToArrayLike(input, new Array(input.length)); + }, + "arraybuffer": function(input) { + return transform["nodebuffer"]["uint8array"](input).buffer; + }, + "uint8array": function(input) { + return arrayLikeToArrayLike(input, new Uint8Array(input.length)); + }, + "nodebuffer": identity +}; + +/** + * Transform an input into any type. + * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer. + * If no output type is specified, the unmodified input will be returned. + * @param {String} outputType the output type. + * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert. + * @throws {Error} an Error if the browser doesn't support the requested output type. + */ +exports.transformTo = function(outputType, input) { + if (!input) { + // undefined, null, etc + // an empty string won't harm. + input = ""; + } + if (!outputType) { + return input; + } + exports.checkSupport(outputType); + var inputType = exports.getTypeOf(input); + var result = transform[inputType][outputType](input); + return result; +}; + +/** + * Return the type of the input. + * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer. + * @param {Object} input the input to identify. + * @return {String} the (lowercase) type of the input. + */ +exports.getTypeOf = function(input) { + if (typeof input === "string") { + return "string"; + } + if (Object.prototype.toString.call(input) === "[object Array]") { + return "array"; + } + if (support.nodebuffer && nodejsUtils.isBuffer(input)) { + return "nodebuffer"; + } + if (support.uint8array && input instanceof Uint8Array) { + return "uint8array"; + } + if (support.arraybuffer && input instanceof ArrayBuffer) { + return "arraybuffer"; + } +}; + +/** + * Throw an exception if the type is not supported. + * @param {String} type the type to check. + * @throws {Error} an Error if the browser doesn't support the requested type. + */ +exports.checkSupport = function(type) { + var supported = support[type.toLowerCase()]; + if (!supported) { + throw new Error(type + " is not supported by this platform"); + } +}; + +exports.MAX_VALUE_16BITS = 65535; +exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1 + +/** + * Prettify a string read as binary. + * @param {string} str the string to prettify. + * @return {string} a pretty string. + */ +exports.pretty = function(str) { + var res = '', + code, i; + for (i = 0; i < (str || "").length; i++) { + code = str.charCodeAt(i); + res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase(); + } + return res; +}; + +/** + * Defer the call of a function. + * @param {Function} callback the function to call asynchronously. + * @param {Array} args the arguments to give to the callback. + */ +exports.delay = function(callback, args, self) { + setImmediate(function () { + callback.apply(self || null, args || []); + }); +}; + +/** + * Extends a prototype with an other, without calling a constructor with + * side effects. Inspired by nodejs' `utils.inherits` + * @param {Function} ctor the constructor to augment + * @param {Function} superCtor the parent constructor to use + */ +exports.inherits = function (ctor, superCtor) { + var Obj = function() {}; + Obj.prototype = superCtor.prototype; + ctor.prototype = new Obj(); +}; + +/** + * Merge the objects passed as parameters into a new one. + * @private + * @param {...Object} var_args All objects to merge. + * @return {Object} a new object with the data of the others. + */ +exports.extend = function() { + var result = {}, i, attr; + for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers + for (attr in arguments[i]) { + if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { + result[attr] = arguments[i][attr]; + } + } + } + return result; +}; + +/** + * Transform arbitrary content into a Promise. + * @param {String} name a name for the content being processed. + * @param {Object} inputData the content to process. + * @param {Boolean} isBinary true if the content is not an unicode string + * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character. + * @param {Boolean} isBase64 true if the string content is encoded with base64. + * @return {Promise} a promise in a format usable by JSZip. + */ +exports.prepareContent = function(name, inputData, isBinary, isOptimizedBinaryString, isBase64) { + + // if inputData is already a promise, this flatten it. + var promise = external.Promise.resolve(inputData).then(function(data) { + + + var isBlob = support.blob && (data instanceof Blob || ['[object File]', '[object Blob]'].indexOf(Object.prototype.toString.call(data)) !== -1); + + if (isBlob && typeof FileReader !== "undefined") { + return new external.Promise(function (resolve, reject) { + var reader = new FileReader(); + + reader.onload = function(e) { + resolve(e.target.result); + }; + reader.onerror = function(e) { + reject(e.target.error); + }; + reader.readAsArrayBuffer(data); + }); + } else { + return data; + } + }); + + return promise.then(function(data) { + var dataType = exports.getTypeOf(data); + + if (!dataType) { + return external.Promise.reject( + new Error("Can't read the data of '" + name + "'. Is it " + + "in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?") + ); + } + // special case : it's way easier to work with Uint8Array than with ArrayBuffer + if (dataType === "arraybuffer") { + data = exports.transformTo("uint8array", data); + } else if (dataType === "string") { + if (isBase64) { + data = base64.decode(data); + } + else if (isBinary) { + // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask + if (isOptimizedBinaryString !== true) { + // this is a string, not in a base64 format. + // Be sure that this is a correct "binary string" + data = string2binary(data); + } + } + } + return data; + }); +}; + +},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,"set-immediate-shim":54}],33:[function(require,module,exports){ +'use strict'; +var readerFor = require('./reader/readerFor'); +var utils = require('./utils'); +var sig = require('./signature'); +var ZipEntry = require('./zipEntry'); +var utf8 = require('./utf8'); +var support = require('./support'); +// class ZipEntries {{{ +/** + * All the entries in the zip file. + * @constructor + * @param {Object} loadOptions Options for loading the stream. + */ +function ZipEntries(loadOptions) { + this.files = []; + this.loadOptions = loadOptions; +} +ZipEntries.prototype = { + /** + * Check that the reader is on the specified signature. + * @param {string} expectedSignature the expected signature. + * @throws {Error} if it is an other signature. + */ + checkSignature: function(expectedSignature) { + if (!this.reader.readAndCheckSignature(expectedSignature)) { + this.reader.index -= 4; + var signature = this.reader.readString(4); + throw new Error("Corrupted zip or bug: unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")"); + } + }, + /** + * Check if the given signature is at the given index. + * @param {number} askedIndex the index to check. + * @param {string} expectedSignature the signature to expect. + * @return {boolean} true if the signature is here, false otherwise. + */ + isSignature: function(askedIndex, expectedSignature) { + var currentIndex = this.reader.index; + this.reader.setIndex(askedIndex); + var signature = this.reader.readString(4); + var result = signature === expectedSignature; + this.reader.setIndex(currentIndex); + return result; + }, + /** + * Read the end of the central directory. + */ + readBlockEndOfCentral: function() { + this.diskNumber = this.reader.readInt(2); + this.diskWithCentralDirStart = this.reader.readInt(2); + this.centralDirRecordsOnThisDisk = this.reader.readInt(2); + this.centralDirRecords = this.reader.readInt(2); + this.centralDirSize = this.reader.readInt(4); + this.centralDirOffset = this.reader.readInt(4); + + this.zipCommentLength = this.reader.readInt(2); + // warning : the encoding depends of the system locale + // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. + // On a windows machine, this field is encoded with the localized windows code page. + var zipComment = this.reader.readData(this.zipCommentLength); + var decodeParamType = support.uint8array ? "uint8array" : "array"; + // To get consistent behavior with the generation part, we will assume that + // this is utf8 encoded unless specified otherwise. + var decodeContent = utils.transformTo(decodeParamType, zipComment); + this.zipComment = this.loadOptions.decodeFileName(decodeContent); + }, + /** + * Read the end of the Zip 64 central directory. + * Not merged with the method readEndOfCentral : + * The end of central can coexist with its Zip64 brother, + * I don't want to read the wrong number of bytes ! + */ + readBlockZip64EndOfCentral: function() { + this.zip64EndOfCentralSize = this.reader.readInt(8); + this.reader.skip(4); + // this.versionMadeBy = this.reader.readString(2); + // this.versionNeeded = this.reader.readInt(2); + this.diskNumber = this.reader.readInt(4); + this.diskWithCentralDirStart = this.reader.readInt(4); + this.centralDirRecordsOnThisDisk = this.reader.readInt(8); + this.centralDirRecords = this.reader.readInt(8); + this.centralDirSize = this.reader.readInt(8); + this.centralDirOffset = this.reader.readInt(8); + + this.zip64ExtensibleData = {}; + var extraDataSize = this.zip64EndOfCentralSize - 44, + index = 0, + extraFieldId, + extraFieldLength, + extraFieldValue; + while (index < extraDataSize) { + extraFieldId = this.reader.readInt(2); + extraFieldLength = this.reader.readInt(4); + extraFieldValue = this.reader.readData(extraFieldLength); + this.zip64ExtensibleData[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + }, + /** + * Read the end of the Zip 64 central directory locator. + */ + readBlockZip64EndOfCentralLocator: function() { + this.diskWithZip64CentralDirStart = this.reader.readInt(4); + this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8); + this.disksCount = this.reader.readInt(4); + if (this.disksCount > 1) { + throw new Error("Multi-volumes zip are not supported"); + } + }, + /** + * Read the local files, based on the offset read in the central part. + */ + readLocalFiles: function() { + var i, file; + for (i = 0; i < this.files.length; i++) { + file = this.files[i]; + this.reader.setIndex(file.localHeaderOffset); + this.checkSignature(sig.LOCAL_FILE_HEADER); + file.readLocalPart(this.reader); + file.handleUTF8(); + file.processAttributes(); + } + }, + /** + * Read the central directory. + */ + readCentralDir: function() { + var file; + + this.reader.setIndex(this.centralDirOffset); + while (this.reader.readAndCheckSignature(sig.CENTRAL_FILE_HEADER)) { + file = new ZipEntry({ + zip64: this.zip64 + }, this.loadOptions); + file.readCentralPart(this.reader); + this.files.push(file); + } + + if (this.centralDirRecords !== this.files.length) { + if (this.centralDirRecords !== 0 && this.files.length === 0) { + // We expected some records but couldn't find ANY. + // This is really suspicious, as if something went wrong. + throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length); + } else { + // We found some records but not all. + // Something is wrong but we got something for the user: no error here. + // console.warn("expected", this.centralDirRecords, "records in central dir, got", this.files.length); + } + } + }, + /** + * Read the end of central directory. + */ + readEndOfCentral: function() { + var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END); + if (offset < 0) { + // Check if the content is a truncated zip or complete garbage. + // A "LOCAL_FILE_HEADER" is not required at the beginning (auto + // extractible zip for example) but it can give a good hint. + // If an ajax request was used without responseType, we will also + // get unreadable data. + var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER); + + if (isGarbage) { + throw new Error("Can't find end of central directory : is this a zip file ? " + + "If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"); + } else { + throw new Error("Corrupted zip: can't find end of central directory"); + } + + } + this.reader.setIndex(offset); + var endOfCentralDirOffset = offset; + this.checkSignature(sig.CENTRAL_DIRECTORY_END); + this.readBlockEndOfCentral(); + + + /* extract from the zip spec : + 4) If one of the fields in the end of central directory + record is too small to hold required data, the field + should be set to -1 (0xFFFF or 0xFFFFFFFF) and the + ZIP64 format record should be created. + 5) The end of central directory record and the + Zip64 end of central directory locator record must + reside on the same disk when splitting or spanning + an archive. + */ + if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) { + this.zip64 = true; + + /* + Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from + the zip file can fit into a 32bits integer. This cannot be solved : JavaScript represents + all numbers as 64-bit double precision IEEE 754 floating point numbers. + So, we have 53bits for integers and bitwise operations treat everything as 32bits. + see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators + and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5 + */ + + // should look for a zip64 EOCD locator + offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + if (offset < 0) { + throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator"); + } + this.reader.setIndex(offset); + this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); + this.readBlockZip64EndOfCentralLocator(); + + // now the zip64 EOCD record + if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) { + // console.warn("ZIP64 end of central directory not where expected."); + this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); + if (this.relativeOffsetEndOfZip64CentralDir < 0) { + throw new Error("Corrupted zip: can't find the ZIP64 end of central directory"); + } + } + this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); + this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); + this.readBlockZip64EndOfCentral(); + } + + var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize; + if (this.zip64) { + expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator + expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize; + } + + var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset; + + if (extraBytes > 0) { + // console.warn(extraBytes, "extra bytes at beginning or within zipfile"); + if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) { + // The offsets seem wrong, but we have something at the specified offset. + // So… we keep it. + } else { + // the offset is wrong, update the "zero" of the reader + // this happens if data has been prepended (crx files for example) + this.reader.zero = extraBytes; + } + } else if (extraBytes < 0) { + throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes."); + } + }, + prepareReader: function(data) { + this.reader = readerFor(data); + }, + /** + * Read a zip file and create ZipEntries. + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file. + */ + load: function(data) { + this.prepareReader(data); + this.readEndOfCentral(); + this.readCentralDir(); + this.readLocalFiles(); + } +}; +// }}} end of ZipEntries +module.exports = ZipEntries; + +},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utf8":31,"./utils":32,"./zipEntry":34}],34:[function(require,module,exports){ +'use strict'; +var readerFor = require('./reader/readerFor'); +var utils = require('./utils'); +var CompressedObject = require('./compressedObject'); +var crc32fn = require('./crc32'); +var utf8 = require('./utf8'); +var compressions = require('./compressions'); +var support = require('./support'); + +var MADE_BY_DOS = 0x00; +var MADE_BY_UNIX = 0x03; + +/** + * Find a compression registered in JSZip. + * @param {string} compressionMethod the method magic to find. + * @return {Object|null} the JSZip compression object, null if none found. + */ +var findCompression = function(compressionMethod) { + for (var method in compressions) { + if (!compressions.hasOwnProperty(method)) { + continue; + } + if (compressions[method].magic === compressionMethod) { + return compressions[method]; + } + } + return null; +}; + +// class ZipEntry {{{ +/** + * An entry in the zip file. + * @constructor + * @param {Object} options Options of the current file. + * @param {Object} loadOptions Options for loading the stream. + */ +function ZipEntry(options, loadOptions) { + this.options = options; + this.loadOptions = loadOptions; +} +ZipEntry.prototype = { + /** + * say if the file is encrypted. + * @return {boolean} true if the file is encrypted, false otherwise. + */ + isEncrypted: function() { + // bit 1 is set + return (this.bitFlag & 0x0001) === 0x0001; + }, + /** + * say if the file has utf-8 filename/comment. + * @return {boolean} true if the filename/comment is in utf-8, false otherwise. + */ + useUTF8: function() { + // bit 11 is set + return (this.bitFlag & 0x0800) === 0x0800; + }, + /** + * Read the local part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readLocalPart: function(reader) { + var compression, localExtraFieldsLength; + + // we already know everything from the central dir ! + // If the central dir data are false, we are doomed. + // On the bright side, the local part is scary : zip64, data descriptors, both, etc. + // The less data we get here, the more reliable this should be. + // Let's skip the whole header and dash to the data ! + reader.skip(22); + // in some zip created on windows, the filename stored in the central dir contains \ instead of /. + // Strangely, the filename here is OK. + // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes + // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators... + // Search "unzip mismatching "local" filename continuing with "central" filename version" on + // the internet. + // + // I think I see the logic here : the central directory is used to display + // content and the local directory is used to extract the files. Mixing / and \ + // may be used to display \ to windows users and use / when extracting the files. + // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394 + this.fileNameLength = reader.readInt(2); + localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir + // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding. + this.fileName = reader.readData(this.fileNameLength); + reader.skip(localExtraFieldsLength); + + if (this.compressedSize === -1 || this.uncompressedSize === -1) { + throw new Error("Bug or corrupted zip : didn't get enough information from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)"); + } + + compression = findCompression(this.compressionMethod); + if (compression === null) { // no compression found + throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")"); + } + this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize)); + }, + + /** + * Read the central part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readCentralPart: function(reader) { + this.versionMadeBy = reader.readInt(2); + reader.skip(2); + // this.versionNeeded = reader.readInt(2); + this.bitFlag = reader.readInt(2); + this.compressionMethod = reader.readString(2); + this.date = reader.readDate(); + this.crc32 = reader.readInt(4); + this.compressedSize = reader.readInt(4); + this.uncompressedSize = reader.readInt(4); + var fileNameLength = reader.readInt(2); + this.extraFieldsLength = reader.readInt(2); + this.fileCommentLength = reader.readInt(2); + this.diskNumberStart = reader.readInt(2); + this.internalFileAttributes = reader.readInt(2); + this.externalFileAttributes = reader.readInt(4); + this.localHeaderOffset = reader.readInt(4); + + if (this.isEncrypted()) { + throw new Error("Encrypted zip are not supported"); + } + + // will be read in the local part, see the comments there + reader.skip(fileNameLength); + this.readExtraFields(reader); + this.parseZIP64ExtraField(reader); + this.fileComment = reader.readData(this.fileCommentLength); + }, + + /** + * Parse the external file attributes and get the unix/dos permissions. + */ + processAttributes: function () { + this.unixPermissions = null; + this.dosPermissions = null; + var madeBy = this.versionMadeBy >> 8; + + // Check if we have the DOS directory flag set. + // We look for it in the DOS and UNIX permissions + // but some unknown platform could set it as a compatibility flag. + this.dir = this.externalFileAttributes & 0x0010 ? true : false; + + if(madeBy === MADE_BY_DOS) { + // first 6 bits (0 to 5) + this.dosPermissions = this.externalFileAttributes & 0x3F; + } + + if(madeBy === MADE_BY_UNIX) { + this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF; + // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8); + } + + // fail safe : if the name ends with a / it probably means a folder + if (!this.dir && this.fileNameStr.slice(-1) === '/') { + this.dir = true; + } + }, + + /** + * Parse the ZIP64 extra field and merge the info in the current ZipEntry. + * @param {DataReader} reader the reader to use. + */ + parseZIP64ExtraField: function(reader) { + + if (!this.extraFields[0x0001]) { + return; + } + + // should be something, preparing the extra reader + var extraReader = readerFor(this.extraFields[0x0001].value); + + // I really hope that these 64bits integer can fit in 32 bits integer, because js + // won't let us have more. + if (this.uncompressedSize === utils.MAX_VALUE_32BITS) { + this.uncompressedSize = extraReader.readInt(8); + } + if (this.compressedSize === utils.MAX_VALUE_32BITS) { + this.compressedSize = extraReader.readInt(8); + } + if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) { + this.localHeaderOffset = extraReader.readInt(8); + } + if (this.diskNumberStart === utils.MAX_VALUE_32BITS) { + this.diskNumberStart = extraReader.readInt(4); + } + }, + /** + * Read the central part of a zip file and add the info in this object. + * @param {DataReader} reader the reader to use. + */ + readExtraFields: function(reader) { + var end = reader.index + this.extraFieldsLength, + extraFieldId, + extraFieldLength, + extraFieldValue; + + if (!this.extraFields) { + this.extraFields = {}; + } + + while (reader.index + 4 < end) { + extraFieldId = reader.readInt(2); + extraFieldLength = reader.readInt(2); + extraFieldValue = reader.readData(extraFieldLength); + + this.extraFields[extraFieldId] = { + id: extraFieldId, + length: extraFieldLength, + value: extraFieldValue + }; + } + + reader.setIndex(end); + }, + /** + * Apply an UTF8 transformation if needed. + */ + handleUTF8: function() { + var decodeParamType = support.uint8array ? "uint8array" : "array"; + if (this.useUTF8()) { + this.fileNameStr = utf8.utf8decode(this.fileName); + this.fileCommentStr = utf8.utf8decode(this.fileComment); + } else { + var upath = this.findExtraFieldUnicodePath(); + if (upath !== null) { + this.fileNameStr = upath; + } else { + // ASCII text or unsupported code page + var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName); + this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray); + } + + var ucomment = this.findExtraFieldUnicodeComment(); + if (ucomment !== null) { + this.fileCommentStr = ucomment; + } else { + // ASCII text or unsupported code page + var commentByteArray = utils.transformTo(decodeParamType, this.fileComment); + this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray); + } + } + }, + + /** + * Find the unicode path declared in the extra field, if any. + * @return {String} the unicode path, null otherwise. + */ + findExtraFieldUnicodePath: function() { + var upathField = this.extraFields[0x7075]; + if (upathField) { + var extraReader = readerFor(upathField.value); + + // wrong version + if (extraReader.readInt(1) !== 1) { + return null; + } + + // the crc of the filename changed, this field is out of date. + if (crc32fn(this.fileName) !== extraReader.readInt(4)) { + return null; + } + + return utf8.utf8decode(extraReader.readData(upathField.length - 5)); + } + return null; + }, + + /** + * Find the unicode comment declared in the extra field, if any. + * @return {String} the unicode comment, null otherwise. + */ + findExtraFieldUnicodeComment: function() { + var ucommentField = this.extraFields[0x6375]; + if (ucommentField) { + var extraReader = readerFor(ucommentField.value); + + // wrong version + if (extraReader.readInt(1) !== 1) { + return null; + } + + // the crc of the comment changed, this field is out of date. + if (crc32fn(this.fileComment) !== extraReader.readInt(4)) { + return null; + } + + return utf8.utf8decode(extraReader.readData(ucommentField.length - 5)); + } + return null; + } +}; +module.exports = ZipEntry; + +},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(require,module,exports){ +'use strict'; + +var StreamHelper = require('./stream/StreamHelper'); +var DataWorker = require('./stream/DataWorker'); +var utf8 = require('./utf8'); +var CompressedObject = require('./compressedObject'); +var GenericWorker = require('./stream/GenericWorker'); + +/** + * A simple object representing a file in the zip file. + * @constructor + * @param {string} name the name of the file + * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data + * @param {Object} options the options of the file + */ +var ZipObject = function(name, data, options) { + this.name = name; + this.dir = options.dir; + this.date = options.date; + this.comment = options.comment; + this.unixPermissions = options.unixPermissions; + this.dosPermissions = options.dosPermissions; + + this._data = data; + this._dataBinary = options.binary; + // keep only the compression + this.options = { + compression : options.compression, + compressionOptions : options.compressionOptions + }; +}; + +ZipObject.prototype = { + /** + * Create an internal stream for the content of this object. + * @param {String} type the type of each chunk. + * @return StreamHelper the stream. + */ + internalStream: function (type) { + var result = null, outputType = "string"; + try { + if (!type) { + throw new Error("No output type specified."); + } + outputType = type.toLowerCase(); + var askUnicodeString = outputType === "string" || outputType === "text"; + if (outputType === "binarystring" || outputType === "text") { + outputType = "string"; + } + result = this._decompressWorker(); + + var isUnicodeString = !this._dataBinary; + + if (isUnicodeString && !askUnicodeString) { + result = result.pipe(new utf8.Utf8EncodeWorker()); + } + if (!isUnicodeString && askUnicodeString) { + result = result.pipe(new utf8.Utf8DecodeWorker()); + } + } catch (e) { + result = new GenericWorker("error"); + result.error(e); + } + + return new StreamHelper(result, outputType, ""); + }, + + /** + * Prepare the content in the asked type. + * @param {String} type the type of the result. + * @param {Function} onUpdate a function to call on each internal update. + * @return Promise the promise of the result. + */ + async: function (type, onUpdate) { + return this.internalStream(type).accumulate(onUpdate); + }, + + /** + * Prepare the content as a nodejs stream. + * @param {String} type the type of each chunk. + * @param {Function} onUpdate a function to call on each internal update. + * @return Stream the stream. + */ + nodeStream: function (type, onUpdate) { + return this.internalStream(type || "nodebuffer").toNodejsStream(onUpdate); + }, + + /** + * Return a worker for the compressed content. + * @private + * @param {Object} compression the compression object to use. + * @param {Object} compressionOptions the options to use when compressing. + * @return Worker the worker. + */ + _compressWorker: function (compression, compressionOptions) { + if ( + this._data instanceof CompressedObject && + this._data.compression.magic === compression.magic + ) { + return this._data.getCompressedWorker(); + } else { + var result = this._decompressWorker(); + if(!this._dataBinary) { + result = result.pipe(new utf8.Utf8EncodeWorker()); + } + return CompressedObject.createWorkerFrom(result, compression, compressionOptions); + } + }, + /** + * Return a worker for the decompressed content. + * @private + * @return Worker the worker. + */ + _decompressWorker : function () { + if (this._data instanceof CompressedObject) { + return this._data.getContentWorker(); + } else if (this._data instanceof GenericWorker) { + return this._data; + } else { + return new DataWorker(this._data); + } + } +}; + +var removedMethods = ["asText", "asBinary", "asNodeBuffer", "asUint8Array", "asArrayBuffer"]; +var removedFn = function () { + throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); +}; + +for(var i = 0; i < removedMethods.length; i++) { + ZipObject.prototype[removedMethods[i]] = removedFn; +} +module.exports = ZipObject; + +},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(require,module,exports){ +(function (global){ +'use strict'; +var Mutation = global.MutationObserver || global.WebKitMutationObserver; + +var scheduleDrain; + +{ + if (Mutation) { + var called = 0; + var observer = new Mutation(nextTick); + var element = global.document.createTextNode(''); + observer.observe(element, { + characterData: true + }); + scheduleDrain = function () { + element.data = (called = ++called % 2); + }; + } else if (!global.setImmediate && typeof global.MessageChannel !== 'undefined') { + var channel = new global.MessageChannel(); + channel.port1.onmessage = nextTick; + scheduleDrain = function () { + channel.port2.postMessage(0); + }; + } else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) { + scheduleDrain = function () { + + // Create a '; + if (navigator.userAgent.indexOf("Firefox") != -1) { + let c = unsafeWindow.open("", "_blank"); + c.document.write(html); + c.document.close(); + } else { + _GM_openInTab('data:text/html;charset=utf-8,' + encodeURIComponent(html),{active:true}); + } + }, + copyImages: function(isAlert) { + var nodes = this.eleMaps['sidebar-thumbnails-container'].querySelectorAll('.pv-gallery-sidebar-thumb-container[data-src]:not(.ignore)'); + var urls = []; + [].forEach.call(nodes, function(node){ + if(unsafeWindow.getComputedStyle(node).display!="none"){ + urls.push(node.dataset.src); + } + }); + + let copyData = urls.join("\n"); + + _GM_setClipboard(copyData); + + this.urlsTextarea.value = copyData; + this.urlsTextareaCon.style.display = "block"; + + if (isAlert) { + this.showTips(i18n("copySuccess",urls.length)); + } + }, + + Preload:function(ele,oriThis){ + this.ele=ele; + this.oriThis=oriThis;//主this + this.init(); + }, + Scrollbar:function(scrollbar,container,isHorizontal){ + this.scrollbar=scrollbar; + this.container=container; + this.isHorizontal=isHorizontal + this.init(); + }, + + addStyle:function(){ + if (GalleryC.style) { + if (!GalleryC.style.parentNode) { + GalleryC.style = _GM_addStyle(GalleryC.style.innerText); + this.globalSSheet = GalleryC.style.sheet; + } + return; + } + GalleryC.style=_GM_addStyle('\ + /*最外层容器*/\ + .pv-gallery-container {\ + position: fixed;\ + top: 0;\ + left: 0;\ + width: 100%;\ + height: 100%;\ + min-width:unset;\ + min-height:unset;\ + padding: 0;\ + margin: 0;\ + border: none;\ + z-index:'+(prefs.imgWindow.zIndex - 1)+';\ + background-color: transparent;\ + display: initial;\ + }\ + /*全局border-box*/\ + .pv-gallery-container span{\ + -moz-box-sizing: border-box;\ + box-sizing: border-box;\ + line-height: 1.6;\ + text-overflow: unset;\ + background-image: initial;\ + float: initial;\ + }\ + .pv-gallery-container * {\ + font-size: 14px;\ + display: initial;\ + flex-direction: row;\ + user-select: none;\ + }\ + /*点击还原的工具条*/\ + span.pv-gallery-maximize-trigger{\ + position:fixed;\ + bottom:15px;\ + left:15px;\ + display:none;\ + background:#000;\ + opacity:0.6;\ + padding-left:10px;\ + font-size:16px;\ + line-height:0;\ + color:white;\ + cursor:pointer;\ + box-shadow:3px 3px 0 0 #333;\ + z-index:899999998;\ + }\ + .pv-gallery-maximize-trigger:hover{\ + opacity:0.9;\ + }\ + span.pv-gallery-maximize-trigger-close{\ + display:inline-block;\ + padding-left:10px;\ + vertical-align:middle;\ + height:30px;\ + padding:10px 0;\ + width:24px;\ + background:url("'+prefs.icons.loadingCancle+'") center no-repeat;\ + }\ + .pv-gallery-maximize-trigger-close:hover{\ + background-color:#333;\ + }\ + span.pv-gallery-head-command-close{\ + position:absolute;\ + top:0;\ + width:40px;\ + border-left: 1px solid #333333;\ + background:transparent no-repeat center;\ + background-image:url("'+prefs.icons.loadingCancle+'");\ + }\ + .pv-gallery-maximize-container+p{\ + position: fixed;\ + width: 100%;\ + z-index: 2;\ + text-align: center;\ + pointer-events: none;\ + margin-bottom: 45px;\ + left: 0;\ + bottom: 0;\ + opacity: 0;\ + transition: all .3s ease;\ + }\ + @media only screen and (max-width: 600px) {\ + .pv-gallery-maximize-container>.maximizeChild{\ + width:calc(50vw - 5px);\ + }\ + }\ + @media only screen and (min-width: 600px) {\ + .pv-gallery-maximize-container>.maximizeChild{\ + width:calc(33vw - 5px);\ + }\ + }\ + @media only screen and (min-width: 800px) {\ + .pv-gallery-maximize-container>.maximizeChild{\ + width:calc(25vw - 5px);\ + }\ + }\ + @media only screen and (min-width: 1000px) {\ + .pv-gallery-maximize-container>.maximizeChild{\ + width:calc(20vw - 5px);\ + }\ + }\ + @media only screen and (min-width: 1130px) {\ + .pv-gallery-maximize-container>.maximizeChild{\ + width:calc(16.6vw - 6px);\ + }\ + }\ + @media only screen and (max-width: 799px) {\ + .pv-gallery-range-box>input {\ + display: none;\ + }\ + .pv-gallery-head-command-drop-list {\ + right: 10px;\ + }\ + span.pv-gallery-head {\ + white-space: nowrap;\ + }\ + span.pv-gallery-sidebar-toggle-content {\ + font-size: 25px!important;\ + }\ + span.pv-gallery-sidebar-toggle {\ + height: 25px!important;\ + opacity: 0.6;\ + border-radius: 0!important;\ + }\ + .pv-gallery-sidebar-viewmore:not(.showmore) {\ + opacity: 0!important;\ + }\ + .pv-gallery-maximize-container{\ + margin-top: 30px;\ + }\ + .pv-gallery-sidebar-viewmore.showmore{\ + transform: scale(1.5);\ + bottom: 10px;\ + }\ + .pv-gallery-maximize-container span>p{\ + opacity: 0.3;\ + }\ + .pv-gallery-maximize-container+p{\ + opacity: 0.2;\ + }\ + span.pv-gallery-head-command-close {\ + position: fixed!important;\ + right: 0!important;\ + height: 29px!important;\ + background-color: black;\ + }\ + }\ + @media only screen and (min-width: 799px) {\ + .pv-gallery-maximize-container{\ + margin-top: 30px;\ + }\ + .pv-gallery-maximize-container span>p{\ + opacity: 0;\ + }\ + }\ + span.pv-gallery-tipsWords{\ + font-size: 50px;\ + font-weight: bold;\ + font-family: "黑体", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",\ + "Oxygen", "Ubuntu", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji",\ + "Segoe UI Emoji", "Segoe UI Symbol";\ + color: #ffffff;\ + height: 70px;\ + line-height: 70px;\ + position: fixed;\ + left: 50%;\ + top: 10%;\ + margin-left: -150px;\ + padding: 0 10px;\ + z-index: 999999999;\ + background-color: #000;\ + border: 1px solid black;\ + border-radius: 10px;\ + opacity: 0;\ + filter: alpha(opacity=65);\ + box-shadow: 5px 5px 20px 0px #000;\ + -moz-transition:opacity 0.3s ease-in-out 0s;\ + -webkit-transition:opacity 0.3s ease-in-out 0s;\ + transition:opacity 0.3s ease-in-out 0s;\ + pointer-events: none;\ + overflow: hidden;\ + text-overflow: ellipsis;\ + white-space: nowrap;\ + max-width: 65%;\ + }\ + /*顶栏*/\ + span.pv-gallery-head {\ + position: absolute;\ + top: 0;\ + left: 0;\ + width: 100%;\ + min-height: 30px;\ + height: auto;\ + z-index:1;\ + background-color:rgb(0,0,0);\ + border:none;\ + border-bottom:1px solid #333333;\ + text-align:right;\ + line-height:0;\ + font-size: 14px;\ + color:#757575;\ + padding-right:42px;\ + display: block;\ + overflow-x: visible;\ + overflow-y: auto;\ + scrollbar-width: none;\ + -ms-overflow-style: none;\ + }\ + span.pv-gallery-head::-webkit-scrollbar {\ + width: 0 !important;\ + height: 0 !important;\ + }\ + .pv-gallery-head > span{\ + vertical-align:middle;\ + }\ + /*顶栏左边*/\ + span.pv-gallery-head-float-left{\ + float:left;\ + height:100%;\ + text-align:left;\ + padding-left:5px;\ + display: table;\ + }\ + .pv-gallery-head-float-left > span{\ + display:inline-block;\ + height:100%;\ + vertical-align:middle;\ + }\ + .pv-gallery-head-float-left > span > *{\ + vertical-align:middle;\ + }\ + .pv-gallery-head-left-img-info{\ + cursor:help;\ + }\ + .pv-gallery-head-left-img-info-description {\ + margin-left: 10px;\ + margin-right: 10px;\ + overflow: hidden;\ + text-overflow: ellipsis;\ + white-space: nowrap;\ + max-width: 6em;\ + display: inherit;\ + }\ + .pv-gallery-range-box{\ + display: inline-flex;\ + justify-content: center;\ + align-items: center;\ + }\ + .pv-gallery-range-box>#pinSize{\ + margin: 0 5px;\ + padding: 0px;\ + }\ + .pv-gallery-range-box>span{\ + padding: 0 5px 0 5px;\ + white-space: nowrap;\ + }\ + .pv-gallery-range-box>input{\ + background: white;\ + min-height: auto;\ + padding: 0;\ + -webkit-appearance: auto;\ + }\ + /*顶栏里面的按钮样式-开始*/\ + .pv-gallery-head-command{\ + display:inline-block;\ + cursor:pointer;\ + height:100%;\ + padding:0 8px;\ + text-align:center;\ + position:relative;\ + z-index:1;\ + vertical-align:middle;\ + -o-user-select: none;\ + -ms-user-select: none;\ + -webkit-user-select: none;\ + -moz-user-select: -moz-none;\ + user-select: none;\ + }\ + /*辅助点击事件的生成,countdown*/\ + .pv-gallery-head-command_overlayer{\ + top:0;\ + left:0;\ + right:0;\ + bottom:0;\ + position:absolute;\ + opacity:0;\ + }\ + .pv-gallery-head-command > *{\ + vertical-align:middle;\ + }\ + .pv-gallery-head-command-slide-show-countdown{\ + font-size:0.8em;\ + }\ + span.pv-gallery-head-command-slide-show-button{\ + border-radius:36px;\ + display:inline-block;\ + width:18px;\ + height:18px;\ + border:2px solid #757575;\ + margin-right:3px;\ + line-height:0;\ + }\ + .pv-gallery-head-command-slide-show-button-inner{\ + display:inline-block;\ + border:none;\ + border-top:4px solid transparent;\ + border-bottom:4px solid transparent;\ + border-left:8px solid #757575;\ + vertical-align:middle;\ + margin-left: 1px;\ + }\ + .pv-gallery-head-command-slide-show-button-inner_stop{\ + border-color:#757575;\ + margin-left: 0px;\ + }\ + span.pv-gallery-head-command-collect-icon{\ + display:inline-block;\ + height:20px;\ + width:20px;\ + background:transparent url("' + prefs.icons.fivePointedStar + '") 0 0 no-repeat;\ + }\ + span.pv-gallery-head-left-lock-icon{\ + display:inline-block;\ + height:20px;\ + width:20px;\ + cursor:pointer;\ + background:transparent url("' + prefs.icons.lock + '") 0 0 no-repeat;\ + }\ + span.pv-gallery-head-left-filter-icon{\ + display:inline-block;\ + height:20px;\ + width:20px;\ + cursor:pointer;\ + background:transparent url("' + prefs.icons.filter + '") 0 0 no-repeat;\ + }\ + .pv-gallery-head-command-collect-icon ~ .pv-gallery-head-command-collect-text::after{\ + content:"'+i18n("collect")+'";\ + }\ + .pv-gallery-head-command-collect-favorite > .pv-gallery-head-command-collect-icon{\ + background-position:-40px 0 !important;\ + }\ + .pv-gallery-head-command-collect-favorite > .pv-gallery-head-command-collect-text::after{\ + content:"'+i18n("collected")+'";\ + }\ + .pv-gallery-head-command-exit-collection{\ + color:#939300 !important;\ + display:none;\ + }\ + .pv-gallery-head-command-urlFilter{\ + color:#e9cccc !important;\ + display:none;\ + }\ + .pv-gallery-head-command:hover{\ + background-color:#272727;\ + color:#ccc;\ + }\ + /*droplist*/\ + .pv-gallery-head-command-drop-list{\ + position:fixed;\ + display:none;\ + box-shadow:0 0 3px #808080;\ + background-color:#272727;\ + line-height: 1.6;\ + text-align:left;\ + padding:10px;\ + color:#ccc;\ + margin-top:-1px;\ + z-index:9;\ + }\ + .pv-gallery-head-command-drop-list-item{\ + display:block;\ + padding:2px 5px;\ + cursor:pointer;\ + white-space:nowrap;\ + }\ + .pv-gallery-head-command-drop-list-item-collect-description{\ + cursor:default;\ + }\ + .pv-gallery-head-command-drop-list-item-collect-description > textarea{\ + resize:both;\ + width:auto;\ + height:auto;\ + background: white;\ + }\ + .pv-gallery-head-command-drop-list-item_disabled{\ + color:#757575;\ + }\ + .pv-gallery-head-command-drop-list-item input + *{\ + padding-left:3px;\ + }\ + .pv-gallery-head-command-drop-list-item input[type=number]{\ + text-align:left;\ + max-width:50px;\ + height:20px;\ + background: white;\ + color: black;\ + box-sizing: border-box;\ + display: initial;\ + margin: 0 5px;\ + padding: 0 5px;\ + }\ + .pv-gallery-head-command-drop-list-item input[type=checkbox]{\ + width:20px;\ + box-sizing: border-box;\ + display: initial;\ + margin: 0 5px;\ + opacity: 1;\ + position: initial;\ + }\ + .pv-gallery-head-command-drop-list-item > * {\ + vertical-align:middle;\ + width: auto;\ + opacity: 1;\ + height: auto;\ + padding: 0;\ + margin: 0;\ + }\ + .pv-gallery-head-command-drop-list-item label {\ + font-weight: normal;\ + display:inline;\ + font-size:unset;\ + line-height: initial;\ + color: inherit;\ + }\ + .pv-gallery-head-command-drop-list-item label:after {\ + display:none;\ + }\ + .pv-gallery-head-command-drop-list-item:hover{\ + background-color:#404040;\ + }\ + /*container*/\ + .pv-gallery-head-command-container{\ + display:inline-block;\ + height:100%;\ + position:relative;\ + }\ + /* after伪类生成标识下拉菜单的三角图标*/\ + .pv-gallery-head-command-container > .pv-gallery-head-command::after{\ + content:"";\ + display:inline-block;\ + vertical-align:middle;\ + border:none;\ + border-top:7px solid #757575;\ + border-left:5px solid transparent;\ + border-right:5px solid transparent;\ + margin-left:5px;\ + -moz-transition:all 0.3s ease-in-out 0s;\ + -webkit-transition:all 0.3s ease-in-out 0s;\ + transition:all 0.3s ease-in-out 0s;\ + }\ + .pv-gallery-head-command-container:hover{\ + box-shadow:0 0 3px #808080;\ + }\ + .pv-gallery-head-command-container:hover > .pv-gallery-head-command{\ + background-color:#272727;\ + color:#ccc;\ + }\ + .pv-gallery-head-command-container:hover > .pv-gallery-head-command::after{\ + -webkit-transform:rotate(180deg);\ + -moz-transform:rotate(180deg);\ + transform:rotate(180deg);\ + border-top:7px solid #ccc;\ + }\ + .pv-gallery-head-command-container:hover .pv-gallery-head-command-collect-icon{\ + background-position:-20px 0;\ + }\ + .pv-gallery-head-command-container:hover .pv-gallery-head-command-slide-show-button{\ + border-color:#ccc;\ + }\ + .pv-gallery-head-command-container:hover .pv-gallery-head-command-slide-show-button-inner{\ + border-left-color:#ccc;\ + }\ + .pv-gallery-head-command-container:hover .pv-gallery-head-command-slide-show-button-inner_stop{\ + border-color:#ccc;\ + }\ + .pv-gallery-head-command-container:hover > .pv-gallery-head-command-drop-list,.pv-gallery-head-command-container > .pv-gallery-head-command-drop-list.focus{\ + display:block;\ + }\ + /*顶栏里面的按钮样式-结束*/\ + .pv-gallery-body {\ + display: block;\ + height: 100%;\ + width: 100%;\ + margin: 0;\ + padding: 0;\ + border: none;\ + border-top: 30px solid transparent;\ + position: relative;\ + background-clip: padding-box;\ + z-index:0;\ + }\ + .pv-gallery-img-container {\ + display: block;\ + padding: 0;\ + margin: 0;\ + border: none;\ + height: 100%;\ + width: 100%;\ + background-clip: padding-box;\ + background-color: ' + (prefs.gallery.backgroundColor || 'rgba(20,20,20,0.75)') + ';\ + position:relative;\ + transition: background-color .3s ease;\ + }\ + .pv-gallery-img-container-top {\ + border-top: '+ prefs.gallery.sidebarSize +'px solid transparent;\ + }\ + .pv-gallery-img-container-right {\ + border-right: '+ prefs.gallery.sidebarSize +'px solid transparent;\ + }\ + .pv-gallery-img-container-bottom {\ + border-bottom: '+ prefs.gallery.sidebarSize +'px solid transparent;\ + }\ + .pv-gallery-img-container-left {\ + border-left: '+ prefs.gallery.sidebarSize +'px solid transparent;\ + }\ + /*大图区域的切换控制按钮*/\ + .pv-gallery-img-controler{\ + position:absolute;\ + top:50%;\ + height:60px;\ + width:50px;\ + margin-top:-30px;\ + cursor:pointer;\ + opacity:0.3;\ + z-index:1;\ + transition: opacity .5s ease;\ + }\ + .pv-gallery-sidebar-toggle-hide .pv-gallery-img-controler{\ + opacity:0;\ + }\ + span.pv-gallery-img-controler-pre{\ + background:rgba(70,70,70,0.8) url("'+prefs.icons.arrowLeft+'") no-repeat center;\ + left:10px;\ + }\ + span.pv-gallery-img-controler-next{\ + background:rgba(70,70,70,0.8) url("'+prefs.icons.arrowRight+'") no-repeat center;\ + right:10px;\ + }\ + .pv-gallery-img-controler:hover{\ + background-color:rgba(140,140,140,0.8);\ + opacity:0.9;\ + z-index:2;\ + }\ + /*滚动条样式--开始*/\ + .pv-gallery-scrollbar-h,\ + .pv-gallery-scrollbar-v{\ + display:none;\ + z-index:1;\ + position:absolute;\ + margin:0;\ + padding:0;\ + border:none;\ + }\ + .pv-gallery-scrollbar-h{\ + bottom:10px;\ + left:0;\ + right:0;\ + height:10px;\ + margin:0 2px;\ + }\ + .pv-gallery-scrollbar-v{\ + top:0;\ + bottom:0;\ + right:10px;\ + width:10px;\ + margin:2px 0;\ + }\ + .pv-gallery-scrollbar-h:hover{\ + height:25px;\ + bottom:0;\ + }\ + .pv-gallery-scrollbar-v:hover{\ + width:25px;\ + }\ + .pv-gallery-scrollbar-h:hover,\ + .pv-gallery-scrollbar-v:hover{\ + background-color:rgba(100,100,100,0.9);\ + z-index:2;\ + }\ + .pv-gallery-scrollbar-h-track,\ + .pv-gallery-scrollbar-v-track{\ + position:absolute;\ + top:0;\ + left:0;\ + right:0;\ + bottom:0;\ + background-color:rgba(100,100,100,0.3);\ + border:2px solid transparent;\ + }\ + .pv-gallery-scrollbar-h-handle,\ + .pv-gallery-scrollbar-v-handle{\ + position:absolute;\ + background-color:rgba(0,0,0,0.5);\ + }\ + .pv-gallery-scrollbar-h-handle{\ + height:100%;\ + }\ + .pv-gallery-scrollbar-v-handle{\ + width:100%;\ + right: 0;\ + }\ + .pv-gallery-scrollbar-h-handle:hover,\ + .pv-gallery-scrollbar-v-handle:hover{\ + background-color:rgba(80,33,33,1);\ + border: 2px solid #585757;\ + }\ + .pv-gallery-scrollbar-h-handle:active,\ + .pv-gallery-scrollbar-v-handle:active{\ + background-color:rgba(57,26,26,1);\ + border: 2px solid #878484;\ + }\ + /*滚动条样式--结束*/\ + .pv-gallery-img-content{\ + display:block;\ + width:100%;\ + height:100%;\ + overflow:hidden;\ + text-align:center;\ + padding:0;\ + border:none;\ + margin:0;\ + line-height:0;\ + font-size:0;\ + white-space:nowrap;\ + }\ + .pv-gallery-img-parent{\ + display:inline-flex;\ + align-items: center;\ + vertical-align: middle;\ + justify-content: center;\ + line-height:0;\ + }\ + .pv-gallery-img-parent video, .pv-gallery-img-parent>div{\ + background-size: cover;\ + }\ + .pv-gallery-img_broken{\ + display:none;\ + cursor:pointer;\ + }\ + .pv-gallery-img{\ + position:relative;\/*辅助e.layerX,layerY*/\ + display:inline-block;\ + vertical-align:middle;\ + width:auto;\ + height:auto;\ + padding:0;\ + border:5px solid #313131;\ + margin:1px;\ + opacity:0.3;\ + box-sizing: content-box;\ + background-color: #282828cc;\ + background-position: center 0;\ + background-repeat: no-repeat;\ + background-size: cover;\ + -webkit-background-size: cover;\ + -o-background-size: cover;\ + '+ + (prefs.gallery.transition ? ('\ + -webkit-transition: opacity 0.5s ease;\ + -moz-transition: opacity 0.5s ease;\ + transition: opacity 0.5s ease;\ + ') : '') + '\ + }\ + .pv-gallery-img_zoom-out{\ + cursor:'+support.cssCursorValue.zoomOut+';\ + }\ + .pv-gallery-img_zoom-in{\ + cursor:'+support.cssCursorValue.zoomIn+';\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide .pv-gallery-img{\ + border:0px;\ + }\ + span.pv-gallery-sidebar-toggle{\ + position:absolute;\ + line-height:12px;\ + text-align:center;\ + background-color:rgb(0,0,0);\ + color:#757575;\ + white-space:nowrap;\ + cursor:pointer;\ + z-index:2;\ + transition: background-color .3s ease, opacity .3s ease;\ + justify-content: center;\ + display:none;\ + }\ + :fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide span.pv-gallery-sidebar-toggle {\ + opacity: 0!important;\ + }\ + :-webkit-full-screen .pv-gallery-container.pv-gallery-sidebar-toggle-hide span.pv-gallery-sidebar-toggle {\ + opacity: 0!important;\ + }\ + :-ms-fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide span.pv-gallery-sidebar-toggle {\ + opacity: 0!important;\ + }\ + :fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide span.pv-gallery-sidebar-toggle:hover {\ + opacity: 1!important;\ + }\ + :-webkit-full-screen .pv-gallery-container.pv-gallery-sidebar-toggle-hide span.pv-gallery-sidebar-toggle:hover {\ + opacity: 1!important;\ + }\ + :-ms-fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide span.pv-gallery-sidebar-toggle:hover {\ + opacity: 1!important;\ + }\ + :fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head {\ + opacity: 0!important;\ + }\ + :-webkit-full-screen .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head {\ + opacity: 0!important;\ + }\ + :-ms-fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head {\ + opacity: 0!important;\ + }\ + :fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head:hover {\ + opacity: 1!important;\ + }\ + :-webkit-full-screen .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head:hover {\ + opacity: 1!important;\ + }\ + :-ms-fullscreen .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head:hover {\ + opacity: 1!important;\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-body>.pv-gallery-img-container>span.pv-gallery-sidebar-toggle{\ + opacity: 0.6;\ + padding: 35px;\ + background-color:#00000000;\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-body>.pv-gallery-img-container>span.pv-gallery-sidebar-toggle:hover{\ + opacity: 1;\ + background-color:rgb(0 0 0 / 50%);\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-body{\ + border-top: 0px solid transparent;\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head{\ + opacity: 0;\ + transition: opacity .3s ease;\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-body>.pv-gallery-img-container{\ + background-color: black;\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide>.pv-gallery-head:hover{\ + opacity: 1;\ + }\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide .pv-gallery-img-parent,\ + .pv-gallery-container.pv-gallery-sidebar-toggle-hide .pv-gallery-img{\ + cursor: none;\ + }\ + .pv-gallery-sidebar-viewmore{\ + position:absolute;\ + line-height:0;\ + text-align:center;\ + background-color:#00000030;\ + color:#a1a1a1;\ + white-space:nowrap;\ + cursor:pointer;\ + z-index: 2;\ + display:none;\ + height: 30px;\ + width:30px;\ + border-radius: 15px;\ + line-height: 2 !important;\ + font-family: auto;\ + transition: background-color .3s ease;\ + }\ + .pv-gallery-sidebar-viewmore:hover{\ + opacity: 1;\ + background-color:#000000;\ + }\ + .pv-gallery-maximize-container+p:hover,.pv-gallery-maximize-container.checked+p:hover{\ + opacity: 1;\ + }\ + .pv-gallery-maximize-container.checked+p{\ + opacity: 0.8;\ + }\ + .pv-gallery-maximize-container+p>.need-checked{\ + display: none;\ + }\ + .pv-gallery-maximize-container.checked+p>.need-checked{\ + display: inline-block;\ + }\ + .pv-gallery-maximize-container+p>input{\ + pointer-events: all;\ + color: white;\ + background-color: black;\ + border: 0;\ + opacity: 0.8;\ + padding: 5px 10px;\ + cursor: pointer;\ + margin: 1px;\ + font-size: 20px;\ + }\ + .pv-gallery-maximize-container+p>input:hover{\ + color: red;\ + }\ + .pv-gallery-maximize-container{\ + min-width: 100%;\ + display: block;\ + background: black;\ + margin-left: 3px;\ + }\ + .pv-gallery-maximize-container.pv-gallery-flex-maximize{\ + column-count: unset;\ + -moz-column-count: unset;\ + -webkit-column-count: unset;\ + display: flex;\ + flex-flow: wrap;\ + justify-content: center;\ + }\ + .pv-gallery-maximize-container.pv-gallery-flex-maximize span{\ + width: 18.5%;\ + height: 30vh;\ + }\ + .pv-gallery-maximize-container.pv-gallery-flex-maximize img{\ + position: relative;\ + width: unset;\ + max-height: 100%;\ + max-width: 100%;\ + top: 50%;\ + transform: translateY(-50%) scale3d(1, 1, 1);\ + }\ + .pv-gallery-maximize-container.pv-gallery-flex-maximize img:hover {\ + transform: translateY(-50%) scale3d(1.1, 1.1, 1.1);\ + }\ + .pv-gallery-maximize-container span{\ + -moz-page-break-inside: avoid;\ + -webkit-column-break-inside: avoid;\ + break-inside: avoid;\ + float: left;\ + margin-bottom: 15px;\ + margin-right: 15px;\ + overflow: hidden;\ + position: relative;\ + }\ + .pv-gallery-maximize-container>.maximizeChild{\ + display: inline-block;\ + vertical-align: middle;\ + text-align: center;\ + background-color: rgba(40, 40, 40, 0.5);\ + border: 5px solid #000000;\ + font-size: 0px;\ + }\ + .pv-gallery-maximize-container>.maximizeChild:hover{\ + background: linear-gradient( 45deg, rgba(255, 255, 255, 0.4) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.4) 100% ), linear-gradient( 45deg, rgba(255, 255, 255, 0.4) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.4) 100% );\ + background-size: 20px 20px;\ + background-position: 0 0, 10px 10px;\ + }\ + .pv-gallery-maximize-container>.maximizeChild.selected{\ + border: 5px solid #ff000050;\ + }\ + .pv-gallery-maximize-container img{\ + max-width: 100%;\ + transition: transform .3s ease 0s;\ + transform: scale3d(1, 1, 1);\ + cursor: zoom-in;\ + min-height: 88px;\ + border-radius: 20px;\ + }\ + .pv-gallery-maximize-container>.maximizeChild:hover img {\ + transform: scale3d(1.1, 1.1, 1.1);\ + filter: brightness(1.1) !important;\ + opacity: 1;\ + }\ + .pv-gallery-maximize-container.pv-gallery-flex-maximize>.maximizeChild:hover img {\ + transform: translateY(-50%) scale3d(1.1, 1.1, 1.1);\ + }\ + .pv-gallery-maximize-container span>p{\ + position: absolute;\ + width: 100%;\ + max-height: 40%;\ + font-size: 18px;\ + text-align: center;\ + background: #00000080;\ + color: #fff;\ + left: 0;\ + user-select: none;\ + word-break: break-all;\ + display: inline;\ + margin: 0 auto;\ + transition:all 0.2s;\ + }\ + .pv-gallery-maximize-container span>p.pv-bottom-banner{\ + bottom: 0;\ + height: 35px;\ + cursor: pointer;\ + line-height: 40px;\ + }\ + .pv-gallery-maximize-container span>p.pv-bottom-banner>svg{\ + width: 20px;\ + height: 20px;\ + vertical-align: middle;\ + fill: currentColor;\ + overflow: hidden;\ + }\ + .pv-gallery-maximize-container span>p.pv-top-banner{\ + top: 0;\ + height: 25px;\ + cursor: pointer;\ + }\ + .pv-gallery-maximize-container span:hover>p{\ + opacity: 1!important;\ + }\ + .pv-gallery-maximize-container span>p:hover{\ + background: #000000cc;\ + }\ + .pv-gallery-maximize-container span>p.pv-bottom-banner:hover{\ + color:red;\ + font-weight:bold;\ + }\ + .pv-gallery-maximize-container span>input{\ + position: absolute;\ + top: 2px;\ + z-index:1;\ + width: 20px;\ + height: 20px;\ + opacity: 0;\ + left: 0;\ + display: inline;\ + cursor: pointer;\ + }\ + .pv-gallery-maximize-container.checked img,\ + .pv-gallery-maximize-container.checked p{\ + opacity: 0.3;\ + }\ + .pv-gallery-maximize-container.checked input:checked+img {\ + opacity: 1;\ + }\ + .pv-gallery-maximize-container.checked span>input{\ + opacity: 1;\ + }\ + .pv-gallery-maximize-container.checked span>.pv-top-banner{\ + opacity: 0.6;\ + }\ + .pv-gallery-maximize-container+p>input.compareBtn{\ + display: none;\ + }\ + .pv-gallery-maximize-container.canCompare+p>input.compareBtn{\ + display: inline;\ + }\ + .pv-gallery-maximize-container span:hover>input{\ + opacity: 1;\ + }\ + .pv-gallery-maximize-scroll{\ + overflow-y: scroll;\ + overflow-x: hidden;\ + height: 100%;\ + width: 100%;\ + position: absolute;\ + display: none;\ + top: 0;\ + left: 0;\ + background: black;\ + }\ + .pv-gallery-sidebar-toggle:hover,.pv-gallery-sidebar-viewmore:hover{\ + color:#ccc;\ + }\ + .pv-gallery-sidebar-toggle-h{\ + width:80px;\ + margin-left:-40px;\ + left:50%;\ + }\ + .pv-gallery-sidebar-viewmore-h{\ + margin-left:-15px;\ + left:50%;\ + }\ + .pv-gallery-sidebar-toggle-v{\ + height:80px;\ + margin-top:-40px;\ + top:50%;\ + }\ + .pv-gallery-sidebar-viewmore-v{\ + height:30px;\ + top:6%;\ + top: calc(50% - 25px);\ + }\ + .pv-gallery-sidebar-toggle-top{\ + top:-3px;\ + }\ + .pv-gallery-sidebar-viewmore-top{\ + top:15px;\ + }\ + .pv-gallery-sidebar-toggle-right{\ + right:-3px;\ + }\ + .pv-gallery-sidebar-viewmore-right{\ + right:18px;\ + }\ + .pv-gallery-sidebar-toggle-bottom{\ + bottom:-3px;\ + }\ + .pv-gallery-sidebar-viewmore-bottom{\ + display: block;\ + bottom:15px;\ + }\ + .pv-gallery-sidebar-viewmore-bottom.showmore{\ + background-color: rgb(42, 42, 42);\ + opacity: 1;\ + }\ + .pv-gallery-sidebar-toggle-left{\ + left:-3px;\ + }\ + .pv-gallery-sidebar-viewmore-left{\ + left:18px;\ + }\ + span.pv-gallery-sidebar-toggle-content{\ + display:inline-block;\ + vertical-align:middle;\ + white-space:normal;\ + word-wrap:break-word;\ + overflow-wrap:break-word;\ + line-height:16px;\ + font-size:18px;\ + text-align:center;\ + margin-bottom:8px;\ + }\ + span.pv-gallery-sidebar-viewmore-content{\ + display:inline-block;\ + vertical-align:middle;\ + white-space:normal;\ + word-wrap:break-word;\ + overflow-wrap:break-word;\ + line-height:1;\ + font-size:16px;\ + text-align:center;\ + }\ + .pv-gallery-sidebar-toggle-content-v,.pv-gallery-sidebar-viewmore-content-v{\ + width:1.1em;\ + }\ + span.pv-gallery-sidebar-toggle-content-v{\ + line-height: 60px !important;\ + }\ + /*侧边栏开始*/\ + .pv-gallery-sidebar-container {\ + position: absolute;\ + background-color:rgb(0,0,0);\ + padding:5px;\ + border:none;\ + margin:0;\ + text-align:center;\ + line-height:0;\ + white-space:nowrap;\ + -o-user-select: none;\ + -webkit-user-select: none;\ + -moz-user-select: -moz-none;\ + user-select: none;\ + }\ + .pv-gallery-sidebar-container-h {\ + height: '+ prefs.gallery.sidebarSize +'px;\ + width: 100%;\ + }\ + .pv-gallery-sidebar-container-v {\ + width: '+ prefs.gallery.sidebarSize +'px;\ + height: 100%;\ + }\ + .pv-gallery-sidebar-container-top {\ + top: 0;\ + left: 0;\ + border-bottom:1px solid #333333;\ + }\ + .pv-gallery-sidebar-container-right {\ + top: 0;\ + right: 0;\ + border-left:1px solid #333333;\ + }\ + .pv-gallery-sidebar-container-bottom {\ + bottom: 0;\ + left: 0;\ + border-top:1px solid #333333;\ + }\ + .pv-gallery-sidebar-container-left {\ + top: 0;\ + left: 0;\ + border-right:1px solid #333333;\ + }\ + .pv-gallery-sidebar-content {\ + display: inline-block;\ + margin: 0;\ + padding: 0;\ + border: none;\ + background-clip: padding-box;\ + vertical-align:middle;\ + position:relative;\ + text-align:left;\ + }\ + .pv-gallery-sidebar-content-h {\ + height: 100%;\ + width: 90%;\ + border-left: 40px solid transparent;\ + border-right: 40px solid transparent;\ + }\ + .pv-gallery-sidebar-content-v {\ + height: 90%;\ + width: 100%;\ + border-top: 40px solid transparent;\ + border-bottom: 40px solid transparent;\ + }\ + span.pv-gallery-sidebar-controler{\ + cursor:pointer;\ + position:absolute;\ + background:rgba(255,255,255,0.1) no-repeat center;\ + }\ + .pv-gallery-sidebar-controler:hover{\ + background-color:rgba(255,255,255,0.3);\ + }\ + .pv-gallery-sidebar-controler-pre-h,\ + .pv-gallery-sidebar-controler-next-h{\ + top:0;\ + width:36px;\ + height:100%;\ + }\ + .pv-gallery-sidebar-controler-pre-v,\ + .pv-gallery-sidebar-controler-next-v{\ + left:0;\ + width:100%;\ + height:36px;\ + }\ + span.pv-gallery-sidebar-controler-pre-h {\ + left: -40px;\ + background-image: url("'+prefs.icons.arrowLeft+'");\ + }\ + span.pv-gallery-sidebar-controler-next-h {\ + right: -40px;\ + background-image: url("'+prefs.icons.arrowRight+'");\ + }\ + span.pv-gallery-sidebar-controler-pre-v {\ + top: -40px;\ + background-image: url("'+prefs.icons.arrowTop+'");\ + }\ + span.pv-gallery-sidebar-controler-next-v {\ + bottom: -40px;\ + background-image: url("'+prefs.icons.arrowBottom+'");\ + }\ + .pv-gallery-sidebar-thumbnails-container {\ + display: block;\ + overflow: hidden;\ + height: 100%;\ + width: 100%;\ + margin:0;\ + border:none;\ + padding:0;\ + line-height:0;\ + position:relative;\ + }\ + .pv-gallery-sidebar-thumbnails-container span{\ + vertical-align:middle;\ + }\ + .pv-gallery-sidebar-thumbnails-container-h{\ + border-left:1px solid #464646;\ + border-right:1px solid #464646;\ + white-space:nowrap;\ + }\ + .pv-gallery-sidebar-thumbnails-container-v{\ + border-top:1px solid #464646;\ + border-bottom:1px solid #464646;\ + white-space:normal;\ + }\ + .pv-gallery-sidebar-thumbnails-container-top {\ + padding-bottom:5px;\ + }\ + .pv-gallery-sidebar-thumbnails-container-right {\ + padding-left:5px;\ + }\ + .pv-gallery-sidebar-thumbnails-container-bottom {\ + padding-top:5px;\ + }\ + .pv-gallery-sidebar-thumbnails-container-left {\ + padding-right:5px;\ + }\ + span.pv-gallery-sidebar-thumb-container {\ + display:inline-block;\ + text-align: center;\ + border:2px solid rgb(52,52,52);\ + background: linear-gradient( 45deg, rgba(255, 255, 255, 0.4) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.4) 100% ), linear-gradient( 45deg, rgba(255, 255, 255, 0.4) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.4) 100% );\ + background-size: 20px 20px;\ + background-position: 0 0, 10px 10px;\ + cursor:pointer;\ + position:relative;\ + padding:2px;\ + font-size:0;\ + line-height:0;\ + white-space:nowrap;\ + vertical-align: middle;\ + top:0;\ + left:0;\ + -webkit-transition:all 0.2s ease-in-out;\ + transition:all 0.2s ease-in-out;\ + }\ + span.pv-gallery-sidebar-thumb-container.ignore {\ + opacity: 0.5;\ + }\ + .pv-gallery-sidebar-thumbnails-container-h .pv-gallery-sidebar-thumb-container {\ + margin:0 2px;\ + height:100%;\ + }\ + .pv-gallery-sidebar-thumbnails-container-v .pv-gallery-sidebar-thumb-container {\ + margin:2px 0;\ + width:100%;\ + }\ + .pv-gallery-sidebar-thumbnails_hide-span > .pv-gallery-sidebar-thumb-container {\ + display:none;\ + }\ + .pv-gallery-sidebar-thumb-container:hover {\ + border:2px solid rgb(57,149,211);\ + }\ + span.pv-gallery-sidebar-thumb_selected {\ + border:2px solid rgb(229,59,62);\ + }\ + .pv-gallery-sidebar-thumb_selected-top {\ + top:5px;\ + }\ + .pv-gallery-sidebar-thumb_selected-right {\ + left:-5px;\ + }\ + .pv-gallery-sidebar-thumb_selected-bottom {\ + top:-5px;\ + }\ + .pv-gallery-sidebar-thumb_selected-left {\ + left:5px;\ + }\ + span.pv-gallery-sidebar-thumb-loading{\ + position:absolute;\ + top:0;\ + left:0;\ + text-align:center;\ + width:100%;\ + height:100%;\ + display:none;\ + opacity:0.6;\ + background:black url("' + prefs.icons.loading + '") no-repeat center ;\ + }\ + .pv-gallery-sidebar-thumb-loading:hover{\ + opacity:0.8;\ + }\ + .pv-gallery-sidebar-thumb {\ + display: inline-block;\ + vertical-align: middle;\ + max-width: 100% !important;\ + max-height: 100% !important;\ + height: auto !important;\ + width: auto !important;\ + min-width: 10%;\ + min-height: 10%;\ + }\ + .pv-gallery-urls-textarea {\ + display: none;\ + position: fixed;\ + top: 10vh;\ + left: 10vw;\ + z-index: 100;\ + }\ + .pv-gallery-urls-textarea>textarea {\ + width: 80vw;\ + height: 80vh;\ + border: 10px solid #272727;\ + background: #ffffffee;\ + color: black;\ + text-wrap: nowrap;\ + }\ + span.pv-gallery-urls-textarea-close,\ + span.pv-gallery-urls-textarea-download{\ + height: 40px;\ + top: -25px;\ + background: #272727 no-repeat center;\ + color: white;\ + border-radius: 50%;\ + cursor: pointer;\ + }\ + span.pv-gallery-urls-textarea-close:hover,\ + span.pv-gallery-urls-textarea-download:hover {\ + background-color: black;\ + }\ + span.pv-gallery-urls-textarea-close {\ + position:absolute;\ + right: -25px;\ + width:40px;\ + background-image:url("'+prefs.icons.loadingCancle+'");\ + }\ + span.pv-gallery-urls-textarea-download {\ + position: absolute;\ + left: -25px;\ + width: 40px;\ + display: flex;\ + align-items: center;\ + justify-content: center;\ + }\ + span.pv-gallery-urls-textarea-download>svg {\ + height: 15px;\ + width: 15px;\ + fill: white;\ + }\ + .pv-gallery-vertical-align-helper{\ + display:inline-block;\ + vertical-align:middle;\ + width:0;\ + height:100%;\ + margin:0;\ + border:0;\ + padding:0;\ + visibility:hidden;\ + white-space:nowrap;\ + background-color:red;\ + }\ + '); + this.globalSSheet=GalleryC.style.sheet; + + var head=document.head; + var style2=document.createElement('style'); + this.thumbVisibleStyle=style2; + style2.type='text/css'; + head.appendChild(style2); + + // 让 description 的文字内容溢出用点点点(...)省略号表示 + // .pv-gallery-head-left-img-info-description { + // overflow: hidden; + // text-overflow: ellipsis; + // white-space: nowrap; + // width: 27em; + // } + }, + + }; + + + GalleryC.prototype.Preload.prototype={//预读对象 + init:function(){ + if(!this.container){//预读的图片都仍里面 + var div=document.createElement('div'); + div.className='pv-gallery-preloaded-img-container'; + div.style.display='none'; + getBody(document).appendChild(div); + GalleryC.prototype.Preload.prototype.container=div; + }; + this.max=prefs.gallery.max; + this.nextNumber=0; + this.nextEle=this.ele; + this.preNumber=0; + this.preEle=this.ele; + this.direction='pre'; + }, + preload:function(){ + var ele=this.getPreloadEle(); + if(!ele){ + return; + }; + this.waitForReady(ele); + }, + waitForReady: function(ele) { + var self = this; + var beginLoadImg = () => { + self.imgReady = imgReady(dataset(ele,'src'), { + loadEnd: function(e) { + if (self.aborted) { + return; + } + + if (e.type == 'error') { + var srcs = dataset(ele, 'srcs'); + if (srcs) srcs = srcs.split(","); + if (srcs && srcs.length > 0) { + var src = srcs.shift(); + dataset(ele, 'srcs', srcs.join(",")); + if (src) { + dataset(ele, 'src', src); + self.waitForReady(ele); + return; + } + } + } + + dataset(ele,'preloaded','true'); + if (!dataset(ele,'naturalSize') && this.naturalHeight && this.naturalWidth) { + dataset(ele,'naturalSize', JSON.stringify({ + h: this.naturalHeight, + w: this.naturalWidth, + })); + let key = this.naturalWidth + "x" + this.naturalHeight; + self.oriThis.sizeMap[key] = (self.oriThis.sizeMap[key] || 0) + 1; + if (self.oriThis.sizeMap[key] === 2) { + let option = document.createElement("option"); + option.innerText = key; + option.value = this.naturalWidth + "-" + this.naturalWidth + "x" + this.naturalHeight + "-" + this.naturalHeight; + self.oriThis.pinSize.appendChild(option); + } + } + self.container.appendChild(this); + self.preload(); + }, + time:60 * 1000,//限时一分钟,否则强制结束并开始预读下一张。 + }); + }; + var xhr = dataset(ele, 'xhr') !== 'stop' && this.oriThis.getPropBySpanMark(ele, 'xhr'); + if (xhr) { + var xhrError = function() { + dataset(ele, 'xhr', 'stop'); + dataset(ele, 'src', dataset(ele, 'thumbSrc')); + beginLoadImg(); + }; + xhrLoad.load({ + url: dataset(ele,'src'), + xhr: xhr, + cb: function(imgSrc, imgSrcs, caption, captions) { + if (imgSrc) { + dataset(ele, 'src', imgSrc); + dataset(ele, 'xhr', 'stop'); + if (caption) dataset(ele, 'description', caption); + beginLoadImg(); + if (imgSrcs && imgSrcs.length) { + let i = 0; + imgSrcs.forEach(src => { + if (src == imgSrc) return; + let img = document.createElement('img'); + img.src = src; + let cap = captions && captions[i] ? captions[i] : caption; + imgReady(img,{ + ready:function(){ + let result = findPic(img); + if (cap) result.description = cap; + self.oriThis.data.push(result); + self.oriThis._appendThumbSpans([result]); + self.oriThis.loadThumb(); + } + }); + i++; + }) + } + } else { + xhrError(); + } + }, + onerror: xhrError + }); + } else { + beginLoadImg(); + } + }, + getPreloadEle:function(){ + if((this.max<=this.nextNumber && this.max<=this.preNumber) || (!this.nextEle && !this.preEle)){ + return; + }; + var ele=this.direction=='pre'? this.getNext() : this.getPrevious(); + if(ele && !dataset(ele,'preloaded')){ + return ele; + }else{ + return this.getPreloadEle(); + }; + }, + getNext:function(){ + this.nextNumber++; + this.direction='next'; + if(!this.nextEle)return; + return (this.nextEle = this.oriThis.getThumSpan(false,this.nextEle)); + }, + getPrevious:function(){ + this.preNumber++; + this.direction='pre'; + if(!this.preEle)return; + return (this.preEle = this.oriThis.getThumSpan(true,this.preEle)); + }, + abort:function(){ + this.aborted=true; + if(this.imgReady){ + this.imgReady.abort(); + }; + }, + }; + + + GalleryC.prototype.Scrollbar.prototype={//滚动条对象 + init:function(){ + var bar=this.scrollbar.bar; + this.shown=bar.offsetWidth!=0; + var self=this; + bar.addEventListener('mousedown',function(e){//点击滚动条区域,该干点什么! + e.preventDefault(); + var target=e.target; + var handle=self.scrollbar.handle; + var track=self.scrollbar.track; + switch(target){ + case handle:{//手柄;功能,拖动手柄来滚动窗口 + let pro=self.isHorizontal ? ['left','clientX'] : ['top','clientY']; + var oHOffset=parseFloat(handle.style[pro[0]]); + var oClient=e[pro[1]]; + + var moveHandler=function(e){ + self.scroll(oHOffset + e[pro[1]] - oClient,true); + }; + let upHandler=function(){ + document.removeEventListener('mousemove',moveHandler,true); + document.removeEventListener('mouseup',upHandler,true); + }; + document.addEventListener('mousemove',moveHandler,true); + document.addEventListener('mouseup',upHandler,true); + }break; + case track:{//轨道;功能,按住不放来连续滚动一个页面的距离 + let pro=self.isHorizontal ? ['left','offsetX','layerX','clientWidth','offsetWidth'] : ['top' , 'offsetY' ,'layerY','clientHeight','offsetHeight']; + var clickOffset=typeof e[pro[1]]=='undefined' ? e[pro[2]] : e[pro[1]]; + var handleOffset=parseFloat(handle.style[pro[0]]); + var handleSize=handle[pro[4]]; + var under= clickOffset > handleOffset ;//点击在滚动手柄的下方 + var containerSize=self.container[pro[3]]; + + var scroll=function(){ + self.scrollBy(under? (containerSize - 10) : (-containerSize + 10));//滚动一个页面距离少一点 + }; + scroll(); + + var checkStop=function(){//当手柄到达点击位置时停止 + var handleOffset=parseFloat(handle.style[pro[0]]); + if(clickOffset >= handleOffset && clickOffset <= (handleOffset + handleSize)){ + clearTimeout(scrollTimeout); + clearInterval(scrollInterval); + }; + }; + + + var scrollInterval; + var scrollTimeout=setTimeout(function(){ + scroll(); + scrollInterval=setInterval(function(){ + scroll(); + checkStop(); + },120); + checkStop(); + },300); + + + checkStop(); + + let upHandler=function(){ + clearTimeout(scrollTimeout); + clearInterval(scrollInterval); + document.removeEventListener('mouseup',upHandler,true); + }; + document.addEventListener('mouseup',upHandler,true); + }break; + }; + + },true); + }, + reset:function(){//判断滚动条该显示还是隐藏 + + var pro=this.isHorizontal ? ['scrollWidth','clientWidth','width'] : ['scrollHeight','clientHeight','height']; + + //如果内容大于容器的content区域 + + var scrollSize=this.container[pro[0]]; + var clientSize=this.container[pro[1]]; + var scrollMax=scrollSize - clientSize; + this.scrollMax=scrollMax; + if(scrollMax>0){ + this.show(); + var trackSize=this.scrollbar.track[pro[1]]; + this.trackSize=trackSize; + var handleSize=Math.floor((clientSize/scrollSize) * trackSize); + handleSize=Math.max(20,handleSize);//限制手柄的最小大小; + this.handleSize=handleSize; + this.one=(trackSize-handleSize) / scrollMax;//一个像素对应的滚动条长度 + this.scrollbar.handle.style[pro[2]]= handleSize + 'px'; + this.scroll(this.getScrolled()); + }else{ + this.hide(); + }; + }, + show:function(){ + if(this.shown)return; + this.shown=true; + this.scrollbar.bar.style.display='block'; + }, + hide:function(){ + if(!this.shown)return; + this.shown=false; + this.scrollbar.bar.style.display='none'; + }, + scrollBy:function(distance,handleDistance){ + return this.scroll(this.getScrolled() + (handleDistance? distance / this.one : distance)); + }, + scrollByPages:function(num){ + this.scroll(this.getScrolled() + (this.container[(this.isHorizontal ? 'clientWidth' : 'clientHeight')] - 10) * num); + }, + scroll:function(distance,handleDistance,transition){ + if(!this.shown)return; + + //滚动实际滚动条 + var _distance=distance; + _distance=handleDistance? distance / this.one : distance; + _distance=Math.max(0,_distance); + _distance=Math.min(_distance,this.scrollMax); + + + var pro=this.isHorizontal? ['left','scrollLeft'] : ['top','scrollTop']; + + + //滚动虚拟滚动条 + //根据比例转换为滚动条上应该滚动的距离。 + distance=handleDistance? distance : this.one * distance; + //处理非法值 + distance=Math.max(0,distance);//如果值小于0那么取0 + distance=Math.min(distance,this.trackSize - this.handleSize);//大于极限值,取极限值 + + var shs=this.scrollbar.handle.style; + var container=this.container; + if(transition){ + clearInterval(this.transitionInterval); + + var start=0; + var duration=10; + + var cStart=this.getScrolled(); + var cChange=_distance-cStart; + var sStart=parseFloat(shs[pro[0]]); + var sChange=distance-sStart; + + var transitionInterval=setInterval(function(){ + var cEnd=Tween.Cubic.easeInOut(start,cStart,cChange,duration); + var sEnd=Tween.Cubic.easeInOut(start,sStart,sChange,duration); + + container[pro[1]]=cEnd; + shs[pro[0]]=sEnd + 'px'; + + start++; + if(start>=duration){ + clearInterval(transitionInterval); + }; + },35); + + this.transitionInterval=transitionInterval; + + return; + }; + + var noScroll=shs[pro[0]].replace(/(\.\d*)?\s*px/,"")==Math.floor(distance); + shs[pro[0]]=distance + 'px'; + container[pro[1]]=_distance; + return noScroll; + }, + getScrolled:function(){ + return this.container[(this.isHorizontal ? 'scrollLeft' : 'scrollTop')]; + }, + }; + + + //放大镜 + function MagnifierC(img,data){ + this.img=img; + this.data=data; + this.init(); + }; + + MagnifierC.all=[]; + MagnifierC.styleZIndex=900000000;//全局z-index; + + MagnifierC.prototype={ + init:function(){ + MagnifierC.zoomRange=prefs.magnifier.wheelZoom.range.slice(0).sort((a, b)=>{return a - b}); + MagnifierC.zoomRangeR=MagnifierC.zoomRange.slice(0).reverse();//降序 + this.addStyle(); + MagnifierC.all.push(this); + var container=document.createElement('span'); + + container.className='pv-magnifier-container'; + getBody(document).appendChild(container); + + this.magnifier=container; + + var imgNaturalSize={ + h:this.img.naturalHeight, + w:this.img.naturalWidth, + }; + + this.imgNaturalSize=imgNaturalSize; + + var cs=container.style; + cs.zIndex=MagnifierC.styleZIndex++; + + + + var maxDia=Math.ceil(Math.sqrt(Math.pow(1/2*imgNaturalSize.w,2) + Math.pow(1/2*imgNaturalSize.h,2)) * 2); + this.maxDia=maxDia; + + var radius=prefs.magnifier.radius; + radius=Math.min(maxDia/2,radius); + this.radius=radius; + var diameter=radius * 2; + this.diameter=diameter; + + cs.width=diameter + 'px'; + cs.height=diameter + 'px'; + cs.borderRadius=radius+1 + 'px'; + cs.backgroundImage='url("'+ this.img.src +'")'; + cs.marginLeft= -radius +'px'; + cs.marginTop= -radius +'px'; + + var imgPos=getContentClientRect(this.data.img); + var wScrolled=getScrolled(); + var imgRange={//图片所在范围 + x:[imgPos.left + wScrolled.x , imgPos.right + wScrolled.x], + y:[imgPos.top + wScrolled.y, imgPos.bottom + wScrolled.y], + }; + var imgW=imgRange.x[1] - imgRange.x[0]; + var imgH=imgRange.y[1] - imgRange.y[0]; + //如果图片太小的话,进行范围扩大。 + var minSize=60; + if(imgW < minSize){ + imgRange.x[1] +=(minSize - imgW)/2; + imgRange.x[0] -=(minSize - imgW)/2; + imgW=minSize; + }; + if(imgH < minSize){ + imgRange.y[1] +=(minSize - imgH)/2; + imgRange.y[0] -=(minSize - imgH)/2; + imgH=minSize; + }; + this.imgSize={ + w:imgW, + h:imgH, + }; + this.imgRange=imgRange; + + this.setMouseRange(); + + + this.move({ + pageX:imgRange.x[0], + pageY:imgRange.y[0], + }); + + this._focus=this.focus.bind(this); + this._blur=this.blur.bind(this); + this._move=this.move.bind(this); + this._remove=this.remove.bind(this); + this._pause=this.pause.bind(this); + this._zoom=this.zoom.bind(this); + this._clickOut=this.clickOut.bind(this); + this._keydown=this.keydown.bind(this); + + if (prefs.magnifier.wheelZoom.enabled || prefs.magnifier.wheelZoom.scaleImage !== false) { + this.zoomLevel=1; + this.defaultDia=diameter; + addWheelEvent(container,this._zoom,false); + } + + container.addEventListener('mouseover',this._focus,false); + container.addEventListener('mouseout',this._blur,false); + container.addEventListener('dblclick',this._remove,false); + container.addEventListener('click',this._pause,false); + + + document.addEventListener('keydown',this._keydown, true); + document.addEventListener('mousemove',this._move,true); + document.addEventListener('mouseup',this._clickOut,true); + }, + addStyle:function(){ + if (MagnifierC.style) { + if (!MagnifierC.style.parentNode) { + MagnifierC.style = _GM_addStyle(MagnifierC.style.innerText); + } + return; + } + MagnifierC.style=_GM_addStyle('\ + .pv-magnifier-container{\ + position:absolute;\ + padding:0;\ + margin:0;\ + background-origin:border-box;\ + -moz-box-sizing:border-box;\ + box-sizing:border-box;\ + border:3px solid #CCCCCC;\ + background:rgba(40, 40, 40, 0.9) no-repeat;\ + }\ + .pv-magnifier-container_focus{\ + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.7);\ + }\ + .pv-magnifier-container_pause{\ + border-color:red;\ + }\ + '); + }, + clickOut:function(e){ + if(!this.magnifier.classList.contains("pv-magnifier-container_focus")){ + this.remove(e); + } + }, + focus:function(){ + this.magnifier.classList.add('pv-magnifier-container_focus'); + this.magnifier.style.zIndex=MagnifierC.styleZIndex++; + }, + blur:function(){ + this.magnifier.classList.remove('pv-magnifier-container_focus'); + }, + move:function(e){ + var mouseCoor={ + x:e.pageX, + y:e.pageY, + }; + var mouseRange=this.mouseRange; + var imgRange=this.imgRange; + + if( !(mouseCoor.x >= mouseRange.x[0] && mouseCoor.x <= mouseRange.x[1] && mouseCoor.y >= mouseRange.y[0] && mouseCoor.y <= mouseRange.y[1]))return;//如果不再鼠标范围 + if(mouseCoor.x > imgRange.x[1]){ + mouseCoor.x = imgRange.x[1]; + }else if(mouseCoor.x < imgRange.x[0]){ + mouseCoor.x = imgRange.x[0]; + }; + if(mouseCoor.y > imgRange.y[1]){ + mouseCoor.y = imgRange.y[1]; + }else if(mouseCoor.y < imgRange.y[0]){ + mouseCoor.y = imgRange.y[0]; + }; + + var ms=this.magnifier.style; + ms.top= mouseCoor.y + 'px'; + ms.left= mouseCoor.x + 'px'; + + var radius=this.radius; + var imgSize=this.imgSize; + var imgNaturalSize=this.imgNaturalSize; + var px=-((mouseCoor.x-imgRange.x[0])/imgSize.w * imgNaturalSize.w) + radius +'px'; + var py=-((mouseCoor.y-imgRange.y[0])/imgSize.h * imgNaturalSize.h) + radius +'px'; + ms.backgroundPosition=px + ' ' + py; + }, + getNextZoomLevel:function(){ + var level; + var self=this; + if(this.zoomOut){//缩小 + let found = MagnifierC.zoomRangeR.find(function(value){ + if(value < self.zoomLevel){ + level=value; + return true; + } + }) + if (!found) { + level = self.zoomLevel * 0.9; + } + }else{ + let found = MagnifierC.zoomRange.find(function(value){ + if(value > self.zoomLevel){ + level=value; + return true; + }; + }); + if (!found) { + level = self.zoomLevel * 1.1; + } + } + return level; + }, + zoom:function(e){ + if ((e.metaKey != !!prefs.magnifier.wheelZoom.meta) || + (e.altKey != !!prefs.magnifier.wheelZoom.alt) || + (e.ctrlKey != !!prefs.magnifier.wheelZoom.ctrl) || + (e.shiftKey != !!prefs.magnifier.wheelZoom.shift)) { + return; + } + if(e.deltaY===0)return;//非Y轴的滚动 + var ms=this.magnifier.style; + if(prefs.magnifier.wheelZoom.pauseFirst && !this.paused){ + if (prefs.magnifier.wheelZoom.scaleImage !== false) { + let curScale = ms.transform.match(/[\d\.]+/); + if (curScale) { + curScale = parseFloat(curScale[0]); + } else curScale = 1; + if (e.deltaY < 0) { + curScale += 0.1; + } else { + curScale -= 0.1; + } + if (curScale < 0.5) curScale = 0.5; + ms.transform = `scale(${curScale})`; + e.preventDefault(); + } + return; + } + if(!prefs.magnifier.wheelZoom.enabled)return; + e.preventDefault(); + if(e.deltaY < 0){//向上滚,放大; + if(this.diameter >= this.maxDia)return; + this.zoomOut=false; + }else{ + this.zoomOut=true; + }; + var level=this.getNextZoomLevel(); + if(!level)return; + + this.zoomLevel=level; + var diameter=this.defaultDia * level; + if(diameter > this.maxDia){ + diameter = this.maxDia; + }; + + var radius=diameter/2 + this.diameter=diameter; + var bRadius=this.radius; + this.radius=radius; + this.setMouseRange(); + ms.width=diameter+'px'; + ms.height=diameter+'px'; + ms.borderRadius=radius+1 + 'px'; + ms.marginLeft=-radius+'px'; + ms.marginTop=-radius+'px'; + var bBP=ms.backgroundPosition.split(' '); + ms.backgroundPosition=parseFloat(bBP[0]) + (radius - bRadius) + 'px' + ' ' + (parseFloat(bBP[1]) + ( radius - bRadius) + 'px'); + + }, + pause:function(){ + if(this.paused){ + this.magnifier.classList.remove('pv-magnifier-container_pause'); + document.addEventListener('mousemove',this._move,true); + }else{ + this.magnifier.classList.add('pv-magnifier-container_pause'); + document.removeEventListener('mousemove',this._move,true); + }; + this.paused=!this.paused; + }, + setMouseRange:function(){ + var imgRange=this.imgRange; + var radius=this.radius; + this.mouseRange={//鼠标活动范围 + x:[imgRange.x[0]-radius , imgRange.x[1] + radius], + y:[imgRange.y[0]-radius , imgRange.y[1] + radius], + }; + }, + keydown:function(e){ + if (e) { + e.preventDefault(); + e.stopPropagation(); + if (e.keyCode == 27) this.remove(); + } + }, + remove:function(e){ + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + if (this.magnifier.parentNode) { + this.magnifier.parentNode.removeChild(this.magnifier); + MagnifierC.all.splice(MagnifierC.all.indexOf(this),1); + } + document.removeEventListener('mousemove',this._move,true); + document.removeEventListener('mouseup',this._clickOut,true); + document.removeEventListener('keydown',this._keydown, true); + }, + }; + + //图片窗口 + function ImgWindowC(img, data, actual, initPos, preview){ + this.loaded = false; + this.img = img; + this.actual = !!actual; + this.src = data?data.src:img.src; + this.data = data || findPic(img); + this.initPos = initPos || false; + this.preview = !!preview; + this.isImg = this.img.nodeName.toUpperCase() == 'IMG'; + this.init(); + if (data && this.isImg) { + this.img.src = location.protocol == "https" ? data.src.replace(/^http:/,"https:") : data.src; + } + }; + + ImgWindowC.all=[];//所有的窗口对象 + ImgWindowC.overlayer=null; + + + ImgWindowC.prototype={ + init:function(){ + ImgWindowC.showing = true; + ImgWindowC.zoomRange=prefs.imgWindow.zoom.range.slice(0).sort((a, b)=>{return a - b}); + ImgWindowC.zoomRangeR=ImgWindowC.zoomRange.slice(0).reverse();//降序 + var self=this; + if(uniqueImgWin && !uniqueImgWin.removed){ + uniqueImgWin.remove(); + } + if (!this.preview) { + //图片是否已经被打开 + if(ImgWindowC.all.find(function(iwin){ + if(iwin.src==self.src){ + iwin.firstOpen(); + return true; + }; + }))return; + } + + this.addStyle(); + + var img=this.img; + img.className='pv-pic-window-pic pv-pic-ignored'; + img.style.cssText='\ + top:0px;\ + left:0px;\ + '; + + var imgNaturalSize={ + h:img.naturalHeight, + w:img.naturalWidth, + }; + this.imgNaturalSize=imgNaturalSize; + this.following=false; + + var container=document.createElement('span'); + container.style.cssText='\ + cursor:pointer;\ + top:0px;\ + left:0px;\ + opacity:0;\ + background:black url("'+prefs.icons.loading+'") center no-repeat;\ + '; + container.className='pv-pic-window-container'; + container.innerHTML=createHTML( + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '0'+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '0'+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + + '' + + '' + + //'' + + '' + + ''+ + ''+ + ''+ + ''+ + ''+prefs.icons.downloadSvgBtn+ + ''); + + let imgbox=container.firstChild; + imgbox.appendChild(img); + this.imgbox = imgbox; + + this.imgWindow=container; + + var toolMap={ + 'hand':container.querySelector('.pv-pic-window-tb-hand'), + 'rotate':container.querySelector('.pv-pic-window-tb-rotate'), + 'zoom':container.querySelector('.pv-pic-window-tb-zoom'), + 'fh':container.querySelector('.pv-pic-window-tb-flip-horizontal'), + 'fv':container.querySelector('.pv-pic-window-tb-flip-vertical'), + 'compare':container.querySelector('.pv-pic-window-tb-compare') + }; + this.toolMap=toolMap; + + var preButton=container.querySelector('.pv-pic-window-pre'); + preButton.addEventListener('click',function(e){ + e.preventDefault(); + e.stopPropagation(); + self.switchImage(false); + },false); + var nextButton=container.querySelector('.pv-pic-window-next'); + nextButton.addEventListener('click',function(e){ + e.preventDefault(); + e.stopPropagation(); + self.switchImage(true); + },false); + if (gallery && (gallery.shown || gallery.minimized)) { + preButton.style.display = "none"; + nextButton.style.display = "none"; + } + this.imgStateBox = container.querySelector('.pv-pic-search-state'); + this.imgState = container.querySelector('.pv-pic-search-state>span'); + this.preButton = container.querySelector('.pv-pic-window-pre'); + this.nextButton = container.querySelector('.pv-pic-window-next'); + let downloadIcon = container.querySelector('.pv-pic-search-state>svg'); + downloadIcon && downloadIcon.addEventListener('click', function(e) { + if (!self.data || !self.img) { + debug(self); + return; + } + e.preventDefault(); + e.stopPropagation(); + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + _GM_openInTab(self.img.src, {active:false}); + } else { + downloadImg(self.img.src, (self.data.img.title || self.data.img.alt), prefs.saveName); + } + }, true); + + //关闭 + var closeButton=container.querySelector('.pv-pic-window-close'); + closeButton.style.cssText='top: -22px;right: 0px;'; + this.closeButton=closeButton; + closeButton.addEventListener('click',function(e){ + self.remove(); + },false); + + var maxButton=container.querySelector('.pv-pic-window-max'); + maxButton.style.cssText='top: -22px;right: 46px;'; + this.maxButton=maxButton; + maxButton.addEventListener('click',async function(e){ + if(!gallery){ + gallery=new GalleryC(); + gallery.data=[]; + } + var allData=await gallery.getAllValidImgs(); + allData.target = self.data; + gallery.data=allData; + gallery.load(gallery.data); + self.remove(); + },false); + + //var searchButton=container.querySelector('.pv-pic-window-search'); + //searchButton.style.cssText='top: -22px;right: 50px;'; + //this.searchButton=searchButton; + var srcs, from; + img.onerror=function(e){ + //setSearchState(i18n("loadNextSimilar"),img.parentNode); + if(self.removed || !self.isImg)return; + console.info(img.src+" "+i18n("loadError")); + if(!self.data)return; + var src; + if(self.data.srcs) + src=self.data.srcs.shift(); + if(src)img.src=src; + else{ + if(img.src!=self.data.imgSrc) + img.src=self.data.imgSrc; + return; + if(from 1) { + for (let i = 0; i < this.data.all.length; i++) { + if (this.data.src.indexOf(this.data.all[i].replace(/^(video|audio):/, "")) !== -1) { + this.curIndex = i; + break; + } + } + } + img.onload = function(e) { + if (self.removed) return; + self.loaded = true; + container.style.background=''; + if (self.preview && img.naturalHeight == 1 && img.naturalWidth == 1) { + self.remove(); + return; + } + self.imgWindow.style.display = ""; + self.imgNaturalSize = { + h:img.naturalHeight, + w:img.naturalWidth, + }; + self.setToolBadge('zoom',self.zoomLevel); + if (self==uniqueImgWin && prefs.floatBar.globalkeys.previewFollowMouse) { + self.followPos(uniqueImgWinInitX, uniqueImgWinInitY); + } else { + if (!self.zoomed) { + if (!self.imgWindow.classList.contains("pv-pic-window-scroll")) { + self.zoomLevel=0; + if (img.naturalHeight && img.naturalHeight < 100) { + let zoomLevel = 100 / img.naturalHeight; + self.zoom(zoomLevel); + } else { + self.zoom(1); + } + } + if (self == uniqueImgWin) { + self.initMaxSize(); + } + if (prefs.imgWindow.fitToScreen) { + self.fitToScreen(); + } + if (self.initPos) { + self.imgWindow.style.left = self.initPos.left; + self.imgWindow.style.top = self.initPos.top; + } else self.center(true, true); + } + } + self.imgWindow.style.opacity = 1; + self.keepScreenInside(); + + var wSize = getWindowSize(); + wSize.h -= 16; + wSize.w -= 16; + self.isLongImg = self.imgNaturalSize.h >= wSize.h && self.imgNaturalSize.h / self.imgNaturalSize.w > 2.5; + } + if (imgNaturalSize.h && imgNaturalSize.w) { + container.style.background=''; + setSearchState(`${img.naturalWidth} x ${img.naturalHeight}`, self.imgState); + } + if (!this.isImg) { + if (/^video$/i.test(img.nodeName)) { + img.naturalHeight = img.videoHeight || 1080; + img.naturalWidth = img.videoWidth || 1920; + } else if (/^audio$/i.test(img.nodeName)) { + img.naturalHeight = 50; + img.naturalWidth = 350; + } + setTimeout(() => { + img.onload(); + }, 0); + } + /*searchButton.addEventListener('click',function(e){ + sortSearch(); + searchImgByImg(self.img.src, self.img.parentNode, function(srcs, index){ + from=index; + self.srcs=srcs; + self.img.src=srcs.shift(); + }); + },false);*/ + + /** + * 说明 + * 1、对原来的适应屏幕等功能会有影响,暂时禁用。 + * 2、分为 absolute 和默认的2种情况 + */ + if (this.data) { + var descriptionSpan = container.querySelector('.pv-pic-window-description'); + let desc = (this.data.description || ''); + if (Array.isArray(this.data.description) && Array.isArray(this.data.all)) { + let curIndex; + for (curIndex = 0; curIndex < this.data.all.length; curIndex++) { + if (this.data.src.indexOf(this.data.all[curIndex].replace(/^(video|audio):/, "")) !== -1) break; + } + desc = desc[curIndex] || desc[0] || ""; + } + descriptionSpan.textContent = desc.trim(); + this.imgStateBox.title = desc; + descriptionSpan.style.display = desc ? "inline" : "none"; + this.descriptionSpan = descriptionSpan; + } + + var toolbar=container.querySelector('.pv-pic-window-toolbar'); + toolbar.style.cssText='\ + top: 0px;\ + left: -42px;\ + '; + this.toolbar=toolbar; + + this.selectedToolClass='pv-pic-window-tb-tool-selected'; + + this.viewRange=container.querySelector('.pv-pic-window-range'); + + this.rotateIndicator=container.querySelector('.pv-pic-window-rotate-indicator'); + this.rotateIPointer=container.querySelector('.pv-pic-window-rotate-indicator-pointer'); + this.rotateOverlayer=container.querySelector('.pv-pic-window-rotate-overlayer'); + + + this.hKeyUp=true; + this.rKeyUp=true; + this.zKeyUp=true; + + this.spaceKeyUp=true; + this.ctrlKeyUp=true; + this.altKeyUp=true; + this.shiftKeyUp=true; + this.moving=false; + + //缩放工具的扩展菜单 + container.querySelector('.pv-pic-window-tb-tool-extend-menu-zoom').addEventListener('click',function(e){ + var target=e.target; + var text=target.title; + var value; + switch(text){ + case '1':{ + value=1; + }break; + case '+0.1':{ + value=self.zoomLevel + 0.1; + }break; + case '-0.1':{ + value=self.zoomLevel - 0.1; + }break; + }; + if(typeof value!='undefined'){ + self.zoom(value,{x:0,y:0}); + }; + },true); + + //旋转工具的扩展菜单 + container.querySelector('.pv-pic-window-tb-tool-extend-menu-rotate').addEventListener('click',function(e){ + var target=e.target; + var text=target.title; + var value; + function convert(deg){ + return deg * Math.PI/180; + }; + + switch(text){ + case '0':{ + value=0; + }break; + case '+90':{ + value=self.rotatedRadians + convert(90); + }break; + case '-90':{ + value=self.rotatedRadians - convert(90); + }break; + }; + + var PI=Math.PI; + if(typeof value!='undefined'){ + if(value>=2*PI){ + value-=2*PI; + }else if(value<0){ + value+=2*PI; + }; + self.rotate(value,true); + }; + },true); + + toolbar.addEventListener('mousedown',function(e){//鼠标按下选择工具 + self.toolbarEventHandler(e); + },false); + + toolbar.addEventListener('click',function(e){ + e.preventDefault(); + e.stopPropagation(); + },false); + + + toolbar.addEventListener('dblclick',function(e){//鼠标双击工具 + self.toolbarEventHandler(e); + },false); + + + //阻止浏览器对图片的默认控制行为 + img.addEventListener('mousedown',function(e){ + e.preventDefault(); + },false); + + let hideToolbarTimer = setTimeout(() => { + if (this !== uniqueImgWin) { + container.classList.add("hideToolbar"); + } + }, 800); + imgbox.addEventListener('mousemove',function(e){ + e.preventDefault(); + clearTimeout(hideToolbarTimer); + container.classList.remove("hideToolbar"); + hideToolbarTimer = setTimeout(() => { + container.classList.add("hideToolbar"); + }, 800); + },false); + imgbox.addEventListener('mouseleave',function(e){ + e.preventDefault(); + clearTimeout(hideToolbarTimer); + container.classList.remove("hideToolbar"); + },false); + + container.addEventListener('mousedown',function(e){//当按下的时,执行平移,缩放,旋转操作 + self.imgWindowEventHandler(e); + },false); + + container.addEventListener('touchstart',function(e){//当按下的时,执行平移,缩放,旋转操作 + self.imgWindowEventHandler(e); + },{ passive: true, capture: false }); + + container.addEventListener('click',function(e){//阻止opera ctrl+点击保存图片 + self.imgWindowEventHandler(e); + },false); + + if(prefs.imgWindow.zoom.mouseWheelZoom){//是否使用鼠标缩放 + addWheelEvent(container,function(e){//滚轮缩放 + if (e.target && (e.target.className == "pv-pic-window-next" || e.target.className == "pv-pic-window-pre")) { + e.preventDefault && e.preventDefault(); + return; + } + self.imgWindowEventHandler(e); + clearTimeout(hideToolbarTimer); + container.classList.remove("hideToolbar"); + hideToolbarTimer = setTimeout(() => { + container.classList.add("hideToolbar"); + }, 800); + },false); + }; + + + + //是否点击图片外部关闭 + if(prefs.imgWindow.overlayer.shown && prefs.imgWindow.close.clickOutside){ + var clickOutside=function(e){ + var target=e.target; + if(!container.contains(target)){ + self.remove(); + document.removeEventListener(prefs.imgWindow.close.clickOutside,clickOutside,true); + }; + }; + this.clickOutside=clickOutside; + document.addEventListener(prefs.imgWindow.close.clickOutside,clickOutside,true); + }; + + //是否双击图片本身关闭 + if(prefs.imgWindow.close.dblClickImgWindow){ + var dblClickImgWindow=function(e){ + var target=e.target; + if(target==container || target==img || target==self.imgState || target==self.rotateOverlayer){ + self.remove(); + e.stopPropagation(); + }; + e.preventDefault(); + }; + container.addEventListener('dblclick',dblClickImgWindow,true); + }; + + + if (!this.preview) { + ImgWindowC.all.push(this); + } + + this._blur=this.blur.bind(this); + this._focusedKeydown=this.focusedKeydown.bind(this); + this._focusedKeyup=this.focusedKeyup.bind(this); + + if(prefs.imgWindow.overlayer.shown){//是否显示覆盖层 + var overlayer=ImgWindowC.overlayer; + if(!overlayer){ + overlayer=document.createElement('span'); + ImgWindowC.overlayer=overlayer; + overlayer.className='pv-pic-window-overlayer'; + }; + overlayer.style.backgroundColor=prefs.imgWindow.overlayer.color; + getBody(document).appendChild(overlayer); + overlayer.style.display='block'; + }; + if(prefs.imgWindow.backgroundColor){ + this.imgWindow.style.backgroundColor=prefs.imgWindow.backgroundColor; + } + + if (gallery && gallery.shown) { + document.documentElement.appendChild(container); + } else { + getBody(document).appendChild(container); + } + + this.rotatedRadians=0;//已经旋转的角度 + this.zoomLevel=0; + this.zoom(1); + this.setToolBadge('zoom',1); + + this.firstOpen(); + this.imgWindow.style.opacity=1; + + //选中默认工具 + this.selectTool(prefs.imgWindow.defaultTool); + }, + geneCompareImg: function(src, percent) { + let parent = document.createElement("div"); + let compareImgCon = document.createElement("div"); + compareImgCon.className = "compareImgCon"; + let compareImg = document.createElement("img"); + let compareSlider = document.createElement("div"); + compareSlider.className = "compareSlider"; + compareSlider.title = "Right mouse to bottom, hold to top"; + compareImgCon.style.width = percent + "%"; + compareSlider.style.left = percent + "%"; + let compareSliderButton = document.createElement("button"); + let self = this; + let upTimer, initLeft; + let mouseMoveHandler = e => { + if (upTimer && Math.abs(initLeft - e.pageX) > 10) { + clearTimeout(upTimer); + upTimer = null; + } + if (compareSliderButton.style.display == "") { + document.removeEventListener("mousemove", mouseMoveHandler); + document.removeEventListener("touchmove", mouseMoveHandler); + return; + } + e = (e.changedTouches) ? e.changedTouches[0] : e; + let a = self.img.getBoundingClientRect(); + let x = e.pageX - a.left; + x = x - window.pageXOffset; + if (x < 0) x = 0; + if (x > a.width) x = a.width; + compareImgCon.style.width = x / a.width * 100 + "%"; + compareSlider.style.left = x / a.width * 100 + "%"; + }; + let beginSlide = e => { + compareSliderButton.style.display = "none"; + initLeft = e.pageX; + clearTimeout(upTimer); + upTimer = setTimeout(() => { + self.compareBox.appendChild(parent); + }, 1000); + document.addEventListener("mousemove", mouseMoveHandler); + document.addEventListener("touchmove", mouseMoveHandler); + e.preventDefault(); + e.stopPropagation(); + let mouseUpHandler = e => { + clearTimeout(upTimer); + compareSliderButton.style.display = ""; + document.removeEventListener("mousemove", mouseMoveHandler); + }; + document.addEventListener("mouseup", mouseUpHandler); + document.addEventListener("touchend", mouseUpHandler); + }; + compareSlider.addEventListener("mousedown", beginSlide); + compareSlider.addEventListener("contextmenu", e => { + self.compareBox.insertBefore(parent, self.compareBox.firstChild); + e.preventDefault(); + e.stopPropagation(); + }); + compareSlider.addEventListener("touchstart", beginSlide); + compareSlider.appendChild(compareSliderButton); + compareImg.src = src; + compareImgCon.appendChild(compareImg); + parent.appendChild(compareImgCon); + parent.appendChild(compareSlider); + return parent; + }, + compare: function(otherSrcs) { + if (!otherSrcs || otherSrcs.length === 0) return; + if (!this.compareBox) { + let compareBox = document.createElement("div"); + compareBox.className = "compareBox"; + this.compareBox = compareBox; + this.img.parentNode.appendChild(compareBox); + this.imgWindow.classList.add("compare"); + } + this.compareBox.innerHTML = createHTML(""); + let self = this, count = 0, compareImgConList = [], targetCompareImg; + otherSrcs.forEach(src => { + count++; + let percent = 100 - count * (100 / (otherSrcs.length + 1)); + let compareImg = self.geneCompareImg(src, percent); + self.compareBox.appendChild(compareImg); + compareImgConList.unshift(compareImg.children[0]); + }); + this.img.addEventListener("mousedown", e => { + targetCompareImg = null; + let percentX = e.offsetX / self.img.clientWidth * 100; + for (let i = 0; i < compareImgConList.length; i++) { + let compareImgCon = compareImgConList[i]; + let compareWidth = parseInt(compareImgCon.style.width); + if (percentX < compareWidth) { + targetCompareImg = compareImgCon; + targetCompareImg.style.opacity = 0; + break; + } + } + if (targetCompareImg == null) { + self.img.style.opacity = 0; + compareImgConList[compareImgConList.length - 1].style.overflow = "visible"; + } + }); + this.img.addEventListener("mouseup", e => { + if (targetCompareImg) { + targetCompareImg.style.opacity = ""; + } else { + self.img.style.opacity = ""; + compareImgConList[compareImgConList.length - 1].style.overflow = ""; + } + }); + this.preButton.style.display = "none"; + this.nextButton.style.display = "none"; + }, + switchImage:async function(fw){ + if (this.data && this.data.all && this.data.all.length > 1) { + let initPos = prefs.imgWindow.switchStoreLoc ? {left: this.imgWindow.style.left, top: this.imgWindow.style.top} : false; + this.remove(); + let imgData = this.data; + let curIndex; + for (curIndex = 0; curIndex < this.data.all.length; curIndex++) { + if (this.data.src.indexOf(this.data.all[curIndex].replace(/^(video|audio):/, "")) !== -1) break; + } + if (fw) { + curIndex++; + if (curIndex == this.data.all.length) curIndex = 0; + } else { + curIndex--; + if (curIndex == -1) curIndex = this.data.all.length - 1; + } + imgData.xhr = null; + imgData.src = this.data.all[curIndex]; + let openType = "actual"; + if (uniqueImgWin && uniqueImgWin == this) { + uniqueImgWin = null; + openType = "popup"; + } + new LoadingAnimC(imgData, openType, false, true, initPos); + return; + } + if (!gallery) { + gallery = new GalleryC(); + gallery.data = []; + } + if (gallery.shown || gallery.minimized) { + return; + } + var allData = await gallery.getAllValidImgs(false, true); + if (allData.length <= 1) return; + const validData = (data, src) => { + if (!data || !data.img || !data.img.parentNode) return false; + if (data.img.parentNode.classList.contains("pv-pic-window-container")) return false; + if (data.src == src) return false; + if (data.src && /^data:/.test(data.src) && data.src.length < 250) return false; + return true; + }; + if (this.data.img) { + for (let i = 0; i < allData.length; i++) { + let imgData = allData[i]; + if (imgData.img == this.data.img) { + if (fw) { + if (i != allData.length - 1) { + i++; + imgData = allData[i]; + while (!validData(imgData, this.data.src)) { + i++; + if (i == allData.length) return; + imgData = allData[i]; + } + if (imgData && imgData.img) { + let initPos = prefs.imgWindow.switchStoreLoc ? {left: this.imgWindow.style.left, top: this.imgWindow.style.top} : false; + this.remove(); + new LoadingAnimC(imgData, (this.actual ? "actual" : "current"), false, true, initPos); + } + } + } else { + if (i != 0) { + i--; + imgData = allData[i]; + while (!validData(imgData, this.data.src)) { + i--; + if (i == -1) return; + imgData = allData[i]; + } + if (imgData) { + let initPos = prefs.imgWindow.switchStoreLoc ? {left: this.imgWindow.style.left, top: this.imgWindow.style.top} : false; + this.remove(); + new LoadingAnimC(imgData, (this.actual ? "actual" : "current"), false, true, initPos); + } + } + } + return; + } + } + if (this.data.img.src) { + for (let i = 0; i < allData.length; i++) { + let imgData = allData[i]; + if (imgData.img && imgData.img.src == this.data.img.src) { + if (fw) { + if (i != allData.length - 1) { + i++; + imgData = allData[i]; + while (!validData(imgData, this.data.src)) { + i++; + if (i == allData.length) return; + imgData = allData[i]; + } + if (imgData && imgData.img) { + let initPos = prefs.imgWindow.switchStoreLoc ? {left: this.imgWindow.style.left, top: this.imgWindow.style.top} : false; + this.remove(); + new LoadingAnimC(imgData, (this.actual ? "actual" : "current"), false, true, initPos); + } + } + } else { + if (i != 0) { + i--; + imgData = allData[i]; + while (!validData(imgData, this.data.src)) { + i--; + if (i == -1) return; + imgData = allData[i]; + } + if (imgData) { + let initPos = prefs.imgWindow.switchStoreLoc ? {left: this.imgWindow.style.left, top: this.imgWindow.style.top} : false; + this.remove(); + new LoadingAnimC(imgData, (this.actual ? "actual" : "current"), false, true, initPos); + } + } + } + return; + } + } + } + } + }, + changeData:function(result){ + if(this.src != result.src){ + this.loaded = false; + this.data = result; + this.src = result.src; + this.img.src = location.protocol == "https"?result.src.replace(/^https?:/,""):result.src; + } + }, + addStyle:function(){ + if (ImgWindowC.style) { + if (!ImgWindowC.style.parentNode) { + ImgWindowC.style = _GM_addStyle(ImgWindowC.style.innerText); + } + return; + } + ImgWindowC.style=_GM_addStyle('\ + .pv-pic-window-container {\ + ' + (prefs.imgWindow.fixed ? 'position: fixed;' : 'position: absolute;') + '\ + background-image: initial;\ + padding: 0;\ + border: 3px solid rgb(255 255 255 / 50%);\ + border-radius: 1px;\ + line-height: 0;\ + text-align: left;\ + box-sizing: content-box;\ + -webkit-transition: opacity 0.2s ease-out;\ + transition: opacity 0.1s ease-out;\ + overscroll-behavior: none;\ + box-shadow: 0 0 6px 2px rgba(0,0,0,0.35);\ + box-sizing: content-box;\ + display: initial;\ + background: #00000080;\ + }\ + .pv-pic-window-container span {\ + background-image: initial;\ + float: initial;\ + }\ + .pv-pic-window-transition-all{\ + -webkit-transition: top 0.2s ease, left 0.2s ease;\ + transition: top 0.2s ease, left 0.2s ease;\ + }\ + .pv-pic-window-imgbox {\ + position: relative;\ + display: block;\ + overflow: hidden;\ + background-color: rgba(40, 40, 40, 0.65);\ + }\ + .pv-pic-window-container .compareBox {\ + position: absolute;\ + top: 0;\ + left: 0;\ + width: 100%;\ + height: 100%;\ + pointer-events: none;\ + }\ + .pv-pic-window-container .compareBox>div {\ + width: 100%;\ + height: 100%;\ + position: absolute;\ + }\ + .pv-pic-window-container .compareBox>div>.compareImgCon {\ + width: 100%;\ + max-width: 100%;\ + height: 100%;\ + overflow: hidden;\ + }\ + .pv-pic-window-container .compareBox>div>.compareImgCon>img {\ + width: auto;\ + height: 100%;\ + max-width: unset;\ + }\ + .pv-pic-window-container .compareBox>div>.compareSlider {\ + position: absolute;\ + top: 0;\ + bottom: 0;\ + width: 2px;\ + background-color: rgba(255,255,255,.5);\ + border-top: 0;\ + border-bottom: 0;\ + box-shadow: 0 0 2px rgba(0,0,0,.5);\ + overflow: visible;\ + z-index: 9;\ + }\ + .pv-pic-window-container .compareBox>div>.compareSlider>button {\ + position: absolute;\ + top: calc(50% - 40px);\ + left: -4px;\ + width: 10px;\ + background-color: #ddd;\ + border: 4px solid #fff;\ + height: 80px;\ + text-align: center;\ + padding: 0;\ + outline: 0;\ + cursor: ew-resize;\ + box-shadow: 0 0 2px rgba(0,0,0,.5);\ + pointer-events: all;\ + box-sizing: border-box;\ + background-image: none;\ + }\ + .pv-pic-window-container .compareBox>div>.compareSlider>button:hover {\ + background-color: #777;\ + }\ + .pv-pic-window-close,\ + .pv-pic-window-max,\ + .pv-pic-window-search,\ + .pv-pic-window-toolbar,\ + .pv-pic-window-tb-tool-extend-menu{\ + -webkit-transition: opacity 0.2s ease-in-out;\ + transition: opacity 0.2s ease-in-out;\ + box-sizing: content-box;\ + }\ + .pv-pic-window-toolbar {\ + position: absolute;\ + background-color: #535353;\ + padding: 0;\ + opacity: 0.5;\ + display: none;\ + cursor: default;\ + -o-user-select: none;\ + -webkit-user-select: none;\ + -moz-user-select: -moz-none;\ + user-select: none;\ + }\ + .pv-pic-window-container_focus:not(.preview)>.pv-pic-window-toolbar {\ + display: block;\ + }\ + .pv-pic-window-toolbar:hover,\ + .pv-pic-window-container>.pv-pic-window-toolbar.insert:hover{\ + opacity: 1;\ + }\ + span.pv-pic-window-close {\ + cursor: pointer;\ + position: absolute;\ + right: 0px;\ + top: -22px;\ + background: url("'+prefs.icons.close+'") no-repeat center bottom;\ + height: 17px;\ + width: 46px;\ + opacity: 0.5;\ + border:none;\ + padding:0;\ + padding-top:2px!important;\ + background-color:#1771FF;\ + display: none;\ + z-index: 2;\ + }\ + .pv-pic-window-container_focus:not(.preview)>.pv-pic-window-close {\ + display: block;\ + }\ + span.pv-pic-window-search {\ + cursor: pointer;\ + position: absolute;\ + right: 50px;\ + top: -22px;\ + background: url("'+prefs.icons.searchBtn+'") no-repeat center bottom;\ + height: 17px;\ + width: 46px;\ + opacity: 0.9;\ + border:none;\ + padding:0;\ + padding-top:2px;\ + background-color:#1771FF;\ + display: none;\ + }\ + span.pv-pic-window-max {\ + cursor: pointer;\ + position: absolute;\ + right: 46px;\ + top: -22px;\ + background: url("'+prefs.icons.maxBtn+'") no-repeat center bottom;\ + height: 17px;\ + width: 46px;\ + opacity: 0.5;\ + border:none;\ + border-right: 1px solid #868686;\ + padding:0;\ + padding-top:2px!important;\ + background-color:#1771FF;\ + display: none;\ + z-index: 2;\ + }\ + .pv-pic-window-container>.pv-pic-window-max:hover,\ + .pv-pic-window-container>.pv-pic-window-close:hover,\ + .pv-pic-window-container>.pv-pic-window-max.insert:hover,\ + .pv-pic-window-container>.pv-pic-window-close.insert:hover {\ + background-color:red;\ + opacity: 1!important;\ + }\ + .pv-pic-window-container_focus:not(.preview)>.pv-pic-window-max {\ + display: block;\ + }\ + .pv-pic-window-search:hover {\ + background-color:red;\ + opacity: 1;\ + }\ + .pv-pic-window-container_focus:not(.preview)>.pv-pic-window-search {\ + display: block;\ + }\ + .pv-pic-window-description {\ + display: none;\ + background: yellow;\ + margin: 0 -5px 0 15px!important;\ + padding: 3px!important;\ + color: black;\ + text-shadow: 0 0 1px #00000099;\ + }\ + .pv-pic-window-description::before {\ + display: inline-block;\ + content:"";\ + position: absolute;\ + border: 10px solid transparent;\ + margin-left: -22px;\ + top: -1px;\ + border-right-color: yellow;\ + }\ + span.pv-pic-window-pre,\ + span.pv-pic-window-next{\ + -webkit-transition: opacity 0.3s ease;\ + transition: opacity 0.3s ease;\ + position: absolute;\ + height: 100px;\ + top: calc(50% - 50px);\ + width: 36px;\ + background: #ffffff60 no-repeat center;\ + opacity: 0;\ + cursor: pointer;\ + pointer-events: none;\ + }\ + span.pv-pic-window-pre {\ + left: 0;\ + background-image: url("'+prefs.icons.arrowLeft+'");\ + }\ + span.pv-pic-window-next {\ + right: 0;\ + background-image: url("'+prefs.icons.arrowRight+'");\ + }\ + .compare>.pv-pic-search-state{\ + display: none;\ + }\ + .pv-pic-window-container_focus>.pv-pic-search-state,.preview>.pv-pic-search-state {\ + opacity:0.8;\ + }\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-window-imgbox{\ + cursor: none;\ + }\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-search-state,\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-window-toolbar,\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-window-max,\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-window-close,\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-window-pre,\ + .pv-pic-window-container_focus.hideToolbar>.pv-pic-window-next{\ + opacity:0!important;\ + }\ + .pv-pic-window-container_focus:not(.preview) .pv-pic-window-imgbox:hover~.pv-pic-window-pre,\ + .pv-pic-window-container_focus:not(.preview) .pv-pic-window-imgbox:hover~.pv-pic-window-next{\ + opacity:0.3;\ + pointer-events: all;\ + }\ + .pv-pic-window-container>.pv-pic-window-pre:hover,\ + .pv-pic-window-container>.pv-pic-window-next:hover{\ + opacity:1;\ + pointer-events: all;\ + }\ + .pv-pic-window-container_focus:hover>.pv-pic-search-state{\ + border-radius: 0 0 8px 0;\ + top: 0px;\ + pointer-events: all;\ + }\ + .pv-pic-window-container>.pv-pic-window-pre:hover~span.pv-pic-search-state,\ + .pv-pic-window-container>.pv-pic-window-next:hover~span.pv-pic-search-state{\ + opacity:0;\ + }\ + .pv-pic-window-container>span.pv-pic-search-state:hover{\ + overflow:visible;\ + background:none;\ + box-shadow:none;\ + opacity: 1;\ + height: 30px;\ + }\ + .pv-pic-window-container>span.pv-pic-search-state:hover>span{\ + opacity: 0;\ + }\ + .pv-pic-window-container>span.pv-pic-search-state:hover>svg{\ + display: block;\ + }\ + span.pv-pic-search-state {\ + top: -21px;\ + left: 0px;\ + display: block;\ + position: absolute;\ + z-index: 1;\ + color: #ffff00;\ + height: 18px;\ + line-height: 18px;\ + opacity:0;\ + transition: all 0.3s ease;\ + user-select: none;\ + -webkit-box-sizing: content-box;\ + box-sizing: content-box;\ + border-radius: 1px;\ + background: rgb(0 0 0 / 75%);\ + box-shadow: rgb(221, 221, 221) 0px 0px 1px inset;\ + max-width: 100%;\ + overflow: hidden;\ + font: 13px / 1.4em Roboto,arial,sans-serif,微软雅黑,"Noto Sans SC";\ + pointer-events: none;\ + }\ + .pv-pic-search-state>span {\ + pointer-events: none;\ + padding: 1px 5px!important;\ + white-space: nowrap;\ + overflow: hidden;\ + }\ + .pv-pic-search-state>span>b {\ + background: yellow;\ + color: black;\ + margin-right: -5px;\ + padding: 3px;\ + }\ + .pv-pic-search-state>span>strong {\ + background: inherit;\ + color: #ffffff;\ + }\ + span.pv-pic-search-state>.pv-icon {\ + width: 20px;\ + height: 20px;\ + vertical-align: middle;\ + fill: currentColor;\ + overflow: hidden;\ + position: absolute;\ + left: 1px;\ + top: 1px;\ + background: black;\ + border-radius: 15px;\ + padding: 5px;\ + color: white;\ + cursor: pointer;\ + display: none;\ + box-sizing: content-box;\ + transition: all 0.3s ease;\ + }\ + .pv-pic-search-state>.pv-icon:hover {\ + color: #ffff00;\ + }\ + .pv-pic-search-state>.pv-icon * {\ + pointer-events: none;\ + }\ + .pv-pic-window-scrollSign {\ + display: none;\ + width: 100px;\ + height: auto;\ + fill: black;\ + top: 10px;\ + right: 0px;\ + position: absolute;\ + opacity: 0;\ + -webkit-animation: scroll_sign_opacity 2s 3 ease-in-out;\ + animation: scroll_sign_opacity 2s 3 ease-in-out;\ + }\ + @-webkit-keyframes scroll_sign_opacity {\ + 0% { opacity: 0 }\ + 50% { opacity: 1 }\ + 100% { opacity: 0 }\ + }\ + @keyframes scroll_sign_opacity {\ + 0% { opacity: 0 }\ + 50% { opacity: 1 }\ + 100% { opacity: 0 }\ + }\ + .pv-pic-window-scroll {\ + max-height: calc(100vh - 2px);\ + max-width: 100vw;\ + }\ + .pv-pic-window-scroll>.pv-pic-window-imgbox {\ + max-height: calc(100vh - 2px);\ + max-width: 100vw;\ + overflow-y: scroll;\ + overflow-x: hidden;\ + overscroll-behavior: contain;\ + -ms-scroll-chaining: contain;\ + scrollbar-color: initial;\ + }\ + .pv-pic-window-scroll>.pv-pic-window-scrollSign {\ + display: block;\ + }\ + .pv-pic-window-scroll>.pv-pic-window-close,\ + .pv-pic-window-scroll>.pv-pic-window-max {\ + display: none;\ + }\ + .pv-pic-window-black>.pv-pic-window-imgbox>img {\ + background: black!important;\ + }\ + .pv-pic-window-white>.pv-pic-window-imgbox>img {\ + background: white!important;\ + }\ + .transition-transform{\ + transition: transform 0.3s ease;\ + }\ + .transition-all{\ + transition: all 0.3s ease;\ + }\ + .pv-pic-window-pic {\ + position: relative;\ + display:inline-block;\/*opera把图片设置display:block会出现渲染问题,会有残影,还会引发其他各种问题,吓尿*/\ + max-width:unset;\ + min-width:unset;\ + max-height:unset;\ + min-height:unset;\ + width:inherit;\ + height:inherit;\ + padding:0;\ + margin:0;\ + border:none;\ + vertical-align:middle;\ + }\ + .pv-pic-window-container.preview {\ + pointer-events: none;\ + }\ + .pv-pic-window-container_focus:not(.preview) .pv-pic-window-imgbox {\ + box-shadow: 0 0 6px black;\ + }\ + span.pv-pic-window-container_focus:not(.preview) .pv-pic-window-pic {\ + background: linear-gradient( 45deg, rgba(255, 255, 255, 0.4) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.4) 100% ), linear-gradient( 45deg, rgba(255, 255, 255, 0.4) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.4) 100% );\ + background-size: 20px 20px;\ + background-position: 0 0, 10px 10px;\ + }\ + span.pv-pic-window-tb-tool,\ + span.pv-pic-window-tb-command{\ + box-sizing:content-box;\ + -moz-box-sizing:content-box;\ + -webkit-box-sizing:content-box;\ + height: 24px;\ + width: 24px;\ + padding: 12px 8px 6px 6px!important;\ + margin:0;\ + display: block;\ + background: transparent no-repeat center;\ + cursor: pointer;\ + position: relative;\ + border: none;\ + border-left: 2px solid transparent;\ + border-bottom: 1px solid #868686;\ + background-origin: content-box;\ + }\ + .pv-pic-window-toolbar > span:last-child {\ + border-bottom: none;\ + }\ + .pv-pic-window-tb-tool:hover,\ + .pv-pic-window-tb-command:hover{\ + border-left: 2px solid red;\ + }\ + .pv-pic-window-tb-tool-selected{\ + box-shadow: inset 0 21px 0 rgba(255,255,255,0.3) ,inset 0 -21px 0 rgba(0,0,0,0.3);\ + border-left:2px solid #1771FF;\ + }\ + .pv-pic-window-toolbar span.pv-pic-window-tb-hand {\ + background-image: url("'+prefs.icons.hand+'")!important;\ + }\ + .pv-pic-window-toolbar span.pv-pic-window-tb-rotate {\ + background-image: url("'+prefs.icons.rotate+'")!important;\ + }\ + .pv-pic-window-toolbar span.pv-pic-window-tb-zoom {\ + background-image: url("'+prefs.icons.zoom+'")!important;\ + }\ + .pv-pic-window-toolbar span.pv-pic-window-tb-flip-horizontal {\ + background-image: url("'+prefs.icons.flipHorizontal+'")!important;\ + }\ + .pv-pic-window-toolbar span.pv-pic-window-tb-flip-vertical {\ + background-image: url("'+prefs.icons.flipVertical+'")!important;\ + }\ + .pv-pic-window-toolbar span.pv-pic-window-tb-compare {\ + background-image: url("'+prefs.icons.compare+'")!important;\ + }\ + .pv-pic-window-tb-tool-badge-container {\ + display: block;\ + position: relative;\ + }\ + span.pv-pic-window-tb-tool-badge {\ + position: absolute;\ + top: -3px;\ + right: 1px;\ + font-size: 10px;\ + line-height: 1.5;\ + padding: 0 3px;\ + background-color: #F93;\ + border-radius: 50px;\ + opacity: 0.5;\ + color: black;\ + }\ + span.pv-pic-window-tb-tool-extend-menu{\ + position:absolute;\ + top:0;\ + margin-left:-1px;\ + background-color:#535353;\ + display:none;\ + left:40px;\ + color:#C3C3C3;\ + font-size:12px;\ + text-shadow:0px -1px 0px black;\ + opacity:0.7;\ + }\ + .pv-pic-window-tb-tool-extend-menu:hover{\ + opacity:0.9;\ + }\ + .pv-pic-window-tb-tool-extend-menu-item{\ + display:block;\ + line-height:1.5;\ + text-align:center;\ + padding:10px!important;\ + cursor:pointer;\ + border: none;\ + border-right: 2px solid transparent;\ + border-bottom: 1px solid #868686;\ + font-size: 20px;\ + }\ + .pv-pic-window-tb-tool-extend-menu-item>svg{\ + pointer-events: none;\ + }\ + .pv-pic-window-tb-tool-extend-menu-item:last-child{\ + border-bottom: none;\ + }\ + .pv-pic-window-tb-tool-extend-menu-item:hover{\ + border-right:2px solid red;\ + }\ + .pv-pic-window-tb-tool-extend-menu-item:active{\ + padding:11px 9px 9px 11px;\ + }\ + .pv-pic-window-tb-tool-extend-menu-container:hover .pv-pic-window-tb-tool{\ + border-left:2px solid red;\ + }\ + .pv-pic-window-tb-tool-extend-menu-container:hover .pv-pic-window-tb-tool-extend-menu{\ + display:block;\ + }\ + .pv-pic-window-tb-tool-extend-menu-container::after{\ + content:"";\ + position:absolute;\ + right:1px;\ + bottom:2px;\ + width:0;\ + height:0;\ + padding:0;\ + margin:0;\ + border:3px solid #C3C3C3;\ + border-top-color:transparent;\ + border-left-color:transparent;\ + opacity:0.5;\ + }\ + .pv-pic-window-overlayer{\ + height:100%;\ + width:100%;\ + position:fixed;\ + top:0;\ + left:0;\ + z-index: '+prefs.imgWindow.zIndex+';\ + }\ + span.pv-pic-window-rotate-indicator{\ + display:none;\ + position:fixed;\ + width:250px;\ + height:250px;\ + padding:10px;\ + margin-top:-135px;\ + margin-left:-135px;\ + background:transparent url("'+ prefs.icons.rotateIndicatorBG +'") no-repeat center;\ + }\ + span.pv-pic-window-rotate-indicator-pointer{\ + display:block;\ + margin-left:auto;\ + margin-right:auto;\ + background:transparent url("'+ prefs.icons.rotateIndicatorPointer +'") no-repeat center;\ + width:60px;\ + height:240px;\ + position:relative;\ + top:5px;\ + transform:rotate(0.1deg);\ + }\ + .pv-pic-window-rotate-overlayer{/*当切换到旋转工具的时候显示这个覆盖层,然后旋转指示器显示在这个覆盖层的下面*/\ + position:absolute;\ + top:0;\ + bottom:0;\ + left:0;\ + right:0;\ + display:none;\ + background-color:transparent;\ + }\ + .pv-pic-window-range{\ + position:absolute;\ + border:none;\ + width:100px;\ + height:100px;\ + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.8);\ + display:none;\ + padding:0;\ + background-color:rgba(255, 0, 0, 0.150);\ + }\ + .pv-pic-window-container::-webkit-scrollbar, .pv-pic-window-container>.pv-pic-window-imgbox::-webkit-scrollbar { width: 0 !important }\ + .pv-pic-window-container, .pv-pic-window-container>.pv-pic-window-imgbox { -ms-overflow-style: none;overflow: -moz-scrollbars-none; }\ + '); + }, + + firstOpen:function(){ + ImgWindowC.selectedTool='hand'; + this.removed = false; + this.imgWindow.classList.remove("pv-pic-window-scroll"); + this.focus(); + var imgWindow=this.imgWindow; + var scrolled=prefs.imgWindow.fixed ? {x:0, y:0} : getScrolled(); + imgWindow.style.left=-5 + scrolled.x + 'px'; + imgWindow.style.top=-5 + scrolled.y + 'px'; + + //window的尺寸 + var wSize=getWindowSize(); + //空隙 + wSize.h -= 16; + wSize.w -= 16; + + var imgWindowCS=unsafeWindow.getComputedStyle(imgWindow); + var rectSize={ + h:parseFloat(imgWindowCS.height), + w:parseFloat(imgWindowCS.width), + }; + this.isLongImg=rectSize.h > wSize.h && rectSize.h/rectSize.w > 2.5; + if(prefs.imgWindow.suitLongImg && this.isLongImg){ + if(prefs.imgWindow.fitToScreen){ + this.fitToScreenWidth(); + } + this.center(rectSize.w <= wSize.w,false); + this.imgWindow.classList.add("pv-pic-window-scroll"); + }else if(prefs.imgWindow.fitToScreen){ + this.fitToScreen(); + this.center(true,true); + }else{ + this.center(rectSize.w <= wSize.w , rectSize.h <= wSize.h); + } + if (this.initPos) { + this.imgWindow.style.left = this.initPos.left; + this.imgWindow.style.top = this.initPos.top; + } + + this.keepScreenInside(); + }, + keepScreenInside:function(){//保持按钮在屏幕里面. + var imgWindow=this.imgWindow; + var imgWindowFullSize={ + h:imgWindow.offsetHeight, + w:imgWindow.offsetWidth, + }; + + var windowSize=getWindowSize(); + + function keepSI(obj,offsetDirection,defaultValue, out){ + var objRect=obj.getBoundingClientRect(); + var objStyle=obj.style; + var insert=false; + + while(offsetDirection.length){ + var oD=offsetDirection[0]; + var oDV=defaultValue[0]; + var outV=0; + if(out)outV=out.shift(); + offsetDirection.shift(); + defaultValue.shift(); + var oValue=parseFloat(objStyle[oD]); + var newValue; + switch(oD){ + case 'top': + newValue=oValue - objRect.top - outV; + if(objRect.top-outV<=0){ + newValue=Math.min(newValue,imgWindowFullSize.h); + }else{ + newValue=Math.max(newValue,oDV); + }; + break; + case 'right': + newValue=oValue + (objRect.right - windowSize.w) + outV; + if(objRect.right+outV >= windowSize.w){//屏幕外 + newValue=Math.min(newValue,imgWindowFullSize.w); + }else{ + newValue=Math.max(newValue,oDV); + }; + break; + case 'bottom': + newValue=oValue + (objRect.bottom - windowSize.h) + outV; + if(objRect.bottom+outV >= windowSize.h){//屏幕外 + newValue=Math.min(newValue,imgWindowFullSize.h); + }else{ + newValue=Math.max(newValue,oDV); + }; + break; + case 'left': + newValue=oValue - objRect.left - outV; + if(objRect.left-outV<=0){ + newValue=Math.min(newValue,imgWindowFullSize.w); + }else{ + newValue=Math.max(newValue,oDV); + } + break; + } + insert=insert||newValue!==oDV; + objStyle[oD]=newValue + 'px'; + } + insert ? obj.classList.add("insert") : obj.classList.remove("insert"); + } + + keepSI(this.closeButton,['top','right'],[-22,0]); + keepSI(this.maxButton,['top','right'],[-22,46],[0,46]); + //keepSI(this.searchButton,['top','right'],[-22,50]); + keepSI(this.toolbar,['top','left'],[0,-42]); + + // 保持注释在图片里面 + //keepSI(this.descriptionSpan,['bottom', 'left'],[-40, 10]); + }, + initMaxSize: function() { + let wSize = getWindowSize(); + let maxWidth = wSize.w - 50, maxHeight = wSize.h - 50, left, top; + if (prefs.floatBar.previewMaxSizeW && maxWidth > prefs.floatBar.previewMaxSizeW) { + maxWidth = prefs.floatBar.previewMaxSizeW; + } + if (prefs.floatBar.previewMaxSizeW && maxHeight > prefs.floatBar.previewMaxSizeH) { + maxHeight = prefs.floatBar.previewMaxSizeH; + } + if (this.zoomLevel === 1) { + this.zoomLevel = 0; + this.zoom(1); + } + + let imgWindowCS = unsafeWindow.getComputedStyle(this.imgWindow); + let rectSize = { + h: parseFloat(imgWindowCS.height), + w: parseFloat(imgWindowCS.width), + }; + + let size, containsScroll = this.imgWindow.classList.contains("pv-pic-window-scroll"); + + if (prefs.imgWindow.fitToScreenSmall || rectSize.w > maxWidth || rectSize.h > maxHeight) { + if (rectSize.w / rectSize.h > maxWidth / maxHeight) { + size = { + w: maxWidth, + h: maxWidth / (rectSize.w / rectSize.h), + }; + } else if (!containsScroll) { + size = { + h: maxHeight, + w: maxHeight * (rectSize.w / rectSize.h), + } + }; + + let cs = this.getRotatedImgCliSize(size); + let ns = this.imgNaturalSize; + if (cs && ns && cs.w && ns.w) { + this.zoom(cs.w / ns.w); + } + } + }, + followPos: function(posX, posY, imme) { + if (this.removed) return; + if (!prefs.floatBar.globalkeys.previewFollowMouse) return; + let imgWindow = this.imgWindow; + if (!imgWindow) return; + this.followPosX = posX; + this.followPosY = posY; + if (!imme && this.following) return; + let wSize = getWindowSize(); + + let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, (this.img.naturalWidth || 500)>>1, (this.img.naturalHeight || 500)>>1), padding2 = 50, left, top;//内外侧间距 + imgWindow.style.position = "fixed"; + let scrolled = {x: 0, y: 0}; + + this.initMaxSize(); + + if (imgWindow.offsetWidth / imgWindow.offsetHeight > wSize.w / wSize.h) { + //宽条,上下半屏 + if (posY > wSize.h / 2) { + //上 + top = posY - imgWindow.offsetHeight - padding1 + scrolled.y; + if (top < padding2>>1) top = padding2>>1; + } else { + //下 + top = posY + padding1 + scrolled.y; + if (top > wSize.h - imgWindow.offsetHeight - 1) top = wSize.h - imgWindow.offsetHeight - 1; + } + left = (wSize.w - imgWindow.offsetWidth) / 2; + let maxLeft = posX + padding1; + if (left > maxLeft) left = maxLeft; + else { + let minLeft = posX - imgWindow.offsetWidth - padding1; + if (left < minLeft) left = minLeft; + } + left = left + scrolled.x; + } else { + //窄条,左右半屏 + if (posX > wSize.w / 2) { + //左 + left = posX - imgWindow.offsetWidth - padding1 + scrolled.x; + if (left < 1) left = 1; + } else { + //右 + left = posX + padding1 + scrolled.x; + if (left > wSize.w - imgWindow.offsetWidth - 1) left = wSize.w - imgWindow.offsetWidth - 1; + } + top = (wSize.h - imgWindow.offsetHeight) / 2; + let maxTop = posY + padding1; + if (top > maxTop) top = maxTop; + else { + let minTop = posY - imgWindow.offsetHeight - padding1; + if (top < minTop) top = minTop; + } + top = top + scrolled.y; + } + + clearTimeout(this.followPosTimer); + if (imme || !this.loaded) { + imgWindow.classList.remove("pv-pic-window-transition-all"); + this.following = false; + } else { + if (Math.abs(left - parseInt(imgWindow.style.left)) + Math.abs(top - parseInt(imgWindow.style.top)) > 50) { + this.following = true; + imgWindow.classList.add("pv-pic-window-transition-all"); + this.followPosTimer = setTimeout(() => { + this.following = false; + this.followPos(this.followPosX, this.followPosY); + imgWindow.classList.add("pv-pic-window-transition-all"); + }, 250); + } else { + imgWindow.classList.remove("pv-pic-window-transition-all"); + } + } + imgWindow.style.left = left + 'px'; + imgWindow.style.top = top + 'px'; + }, + fitToScreen:function(){ + let imgWindow = this.imgWindow; + if (!prefs.imgWindow.fitToScreen) return; + let wSize=getWindowSize(); + //空隙 + wSize.h -= 6; + wSize.w -= 6; + + let imgWindowCS = unsafeWindow.getComputedStyle(imgWindow); + let rectSize = { + h: parseFloat(imgWindowCS.height), + w: parseFloat(imgWindowCS.width), + }; + + let size = rectSize, containsScroll = imgWindow.classList.contains("pv-pic-window-scroll"); + if (prefs.imgWindow.fitToScreenSmall || rectSize.w - wSize.w > 0 || rectSize.h - wSize.h > 0) {//超出屏幕,那么缩小。 + if (rectSize.w / rectSize.h > wSize.w / wSize.h) { + size = { + w: wSize.w, + h: wSize.w / (rectSize.w / rectSize.h), + }; + } else if (!containsScroll) { + size = { + h: wSize.h, + w: wSize.h * (rectSize.w / rectSize.h), + } + }; + + let cs = this.getRotatedImgCliSize(size); + let ns = this.imgNaturalSize; + if (cs && ns && cs.w && ns.w) { + this.zoom(cs.w / ns.w); + } + }; + }, + fitToScreenWidth:function(){ + let imgWindow = this.imgWindow; + if (!prefs.imgWindow.fitToScreen) return; + let wSize=getWindowSize(); + wSize.h -= 6; + wSize.w -= 6; + + let imgWindowCS = unsafeWindow.getComputedStyle(imgWindow); + let rectSize = { + h: parseFloat(imgWindowCS.height), + w: parseFloat(imgWindowCS.width), + }; + + let size = rectSize, containsScroll = imgWindow.classList.contains("pv-pic-window-scroll"); + if (rectSize.w > wSize.w) { + if (rectSize.w / rectSize.h > wSize.w / wSize.h) { + size = { + w: wSize.w, + h: wSize.w / (rectSize.w / rectSize.h), + }; + } + + let cs = this.getRotatedImgCliSize(size); + let ns = this.imgNaturalSize; + if (cs && ns && cs.w && ns.w) { + this.zoom(cs.w / ns.w); + } + }; + }, + center:function(horizontal,vertical){ + if(!horizontal && !vertical)return; + var imgWindow=this.imgWindow; + if(!imgWindow)return; + var wSize=getWindowSize(); + var scrolled=prefs.imgWindow.fixed ? {x:0, y:0} : getScrolled(); + if(horizontal)imgWindow.style.left = (wSize.w - imgWindow.offsetWidth)/2 + scrolled.x +'px'; + if(vertical)imgWindow.style.top = (wSize.h - imgWindow.offsetHeight)/2 + scrolled.y +'px'; + }, + + + move:function(e){ + this.working=true; + var cursor=this.cursor; + this.changeCursor('handing'); + + var mouseCoor={ + x:e.pageX || e.touches[0].pageX, + y:e.pageY || e.touches[0].pageY, + }; + var imgWindow=this.imgWindow; + var imgWStyle=imgWindow.style; + var oriOffset={ + left:parseFloat(imgWStyle.left), + top:parseFloat(imgWStyle.top), + }; + var self=this; + var moveHandler=function(e){ + imgWStyle.left=oriOffset.left+ (e.pageX || e.touches[0].pageX)-mouseCoor.x +'px'; + imgWStyle.top=oriOffset.top + (e.pageY || e.touches[0].pageY)-mouseCoor.y +'px'; + self.keepScreenInside(); + self.moving=true; + e.preventDefault(); + e.stopPropagation(); + }; + var mouseupHandler=function(){ + e.preventDefault(); + self.changeCursor(cursor); + self.working=false; + if(self.tempHand && self.spaceKeyUp){//如果是临时切换到抓手工具,平移完成后返回上个工具 + self.tempHand=false; + self.changeCursor(self.selectedTool); + }; + document.removeEventListener('mousemove',moveHandler,true); + document.removeEventListener('mouseup',mouseupHandler,true); + document.removeEventListener('touchmove',moveHandler,true); + document.removeEventListener('touchend',mouseupHandler,true); + }; + document.addEventListener('mousemove',moveHandler,true); + document.addEventListener('mouseup',mouseupHandler,true); + document.addEventListener('touchmove',moveHandler,true); + document.addEventListener('touchend',mouseupHandler,true); + }, + rotate:function(origin,topLeft){ + + var img=this.img; + var imgWindow=this.imgWindow; + imgWindow.classList.remove("pv-pic-window-scroll"); + + var iTransform=img.style[support.cssTransform].replace(/rotate\([^)]*\)/i,''); + + var imgWindowCS=unsafeWindow.getComputedStyle(imgWindow); + var imgRectSize={ + h:parseFloat(imgWindowCS.height), + w:parseFloat(imgWindowCS.width), + }; + + var rectOffset={ + top:parseFloat(imgWindow.style.top), + left:parseFloat(imgWindow.style.left), + }; + + var imgSize={ + h:img.clientHeight, + w:img.clientWidth, + }; + + var imgOffset={ + top:parseFloat(img.parentNode.style.top), + left:parseFloat(img.parentNode.style.left), + }; + + var self=this; + var PI=Math.PI; + + var rotate = function (radians) { + if (self.rotatedRadians == radians) return; + if (self.working) { + img.parentNode.style[support.cssTransform] = ' rotate(' + radians + 'rad) ' + iTransform; + } else { + img.parentNode.classList.remove("transition-transform"); + img.parentNode.style[support.cssTransform] = ' rotate('+ radians +'rad) ' + iTransform; + setTimeout(()=>{ + img.parentNode.classList.add("transition-transform"); + },0); + } + self.rotateIPointer.style[support.cssTransform]='rotate('+ radians +'rad)';//旋转指示器 + + self.rotatedRadians=radians; + self.setToolBadge('rotate',radians/(PI/180)); + + var afterimgRectSize=self.getRotatedImgRectSize( radians, imgSize ); + imgWindow.style.width=afterimgRectSize.w +'px'; + imgWindow.style.height=afterimgRectSize.h + 'px'; + + if(!topLeft){ + self.setImgWindowOffset(rectOffset,imgRectSize,afterimgRectSize); + }; + + self.setImgOffset(imgOffset,imgRectSize,afterimgRectSize); + self.keepScreenInside(); + }; + + + if(typeof origin=='number'){ + rotate(origin); + return; + }; + + + this.working=true; + + var lastRotatedRadians=this.rotatedRadians; + this.shiftKeyUp=true; + var shiftRotateStep=prefs.imgWindow.shiftRotateStep / (180/Math.PI);//转成弧度 + + var moveHandler=function(e){ + self.rotateIndicator.style.display='block'; + var radians=lastRotatedRadians + Math.atan2( e.clientY - origin.y, e.clientX - origin.x ); + if(radians>=2*PI){ + radians-=2*PI; + }else if(radians<0){ + radians+=2*PI; + }; + + if(!self.shiftKeyUp){//如果按下了shift键,那么步进缩放 + radians -= radians % shiftRotateStep; + radians += shiftRotateStep; + }; + rotate(radians); + }; + + var mouseupHandler=function(){ + self.working=false; + self.rotateIndicator.style.display='none'; + document.removeEventListener('mousemove',moveHandler,true); + document.removeEventListener('mouseup',mouseupHandler,true); + self.img.parentNode.classList.add("transition-transform"); + }; + + self.img.parentNode.classList.remove("transition-transform"); + document.addEventListener('mousemove',moveHandler,true); + document.addEventListener('mouseup',mouseupHandler,true); + }, + convertToValidRadians:function(radians){ + //转成0-90的等价角度。 + var PI=Math.PI; + if(radians > PI){ + radians = 2*PI - radians; + }; + if(radians > 1/2*PI){ + radians = PI - radians; + }; + return radians; + }, + getRotatedImgRectSize:function( radians, imgSize ){//通过旋转后的角度和图片的大小,求虚拟矩形的大小 + imgSize= imgSize ? imgSize :{ + h:this.img.clientHeight, + w:this.img.clentWidth, + }; + + if(typeof radians==='undefined'){ + radians = this.rotatedRadians; + }; + + radians=this.convertToValidRadians(radians); + + return { + h:this.notExponential(imgSize.h* Math.cos(radians) + imgSize.w * Math.sin(radians)), + w:this.notExponential(imgSize.h* Math.sin(radians) + imgSize.w * Math.cos(radians)), + }; + }, + getRotatedImgCliSize:function(rectSize,radians){//通过虚拟矩形的大小和图片的旋转角度,求图片的大小 + + if(typeof radians==='undefined'){ + radians = this.rotatedRadians; + }; + + radians=this.convertToValidRadians(radians); + + if(radians==0){ + //radians=Math.PI/180 * 1/100; + return rectSize; + }; + + var h=(rectSize.h-rectSize.w * Math.tan(radians))/(Math.cos(radians)-Math.sin(radians)*Math.tan(radians)); + var w=(rectSize.h - h*Math.cos(radians))/Math.sin(radians); + return { + h:h, + w:w, + }; + + }, + setImgOffset:function(oriOffset,bImgSize,aImgSize){ + var imgStyle=this.img.parentNode.style; + + //避免出现指数形式的数字和单位相加,导致变成无效值 + var top=this.notExponential(oriOffset.top + (aImgSize.h-bImgSize.h)*1/2) + 'px'; + var left=this.notExponential(oriOffset.left + (aImgSize.w-bImgSize.w)*1/2) + 'px'; + imgStyle.top= top; + imgStyle.left= left; + }, + setImgWindowOffset:function(oriOffset,bImgWindowSize,aImgWidnowSize,ratio){ + ratio= ratio? ratio : {x:1/2,y:1/2}; + var imgWindowStyle=this.imgWindow.style; + var top=oriOffset.top - (aImgWidnowSize.h-bImgWindowSize.h)*ratio.y + 'px'; + var left=oriOffset.left - (aImgWidnowSize.w-bImgWindowSize.w)*ratio.x + 'px'; + imgWindowStyle.top= top; + imgWindowStyle.left= left; + }, + zoom:function(e,ratio){//e可能是undefined,可能是事件对象,可能是直接的缩放级别数字 + var imgWindow=this.imgWindow; + var imgWindowCS=unsafeWindow.getComputedStyle(imgWindow); + var imgRectSize={ + h:parseFloat(imgWindowCS.height), + w:parseFloat(imgWindowCS.width), + }; + + var rectOffset={ + top:parseFloat(imgWindow.style.top), + left:parseFloat(imgWindow.style.left), + }; + + var img=this.img; + var self=this; + + var zoom=function(level){//缩放到指定级别 + if(typeof level=='undefined' || level<0 || level==self.zoomLevel)return; + + var afterImgSize={ + h:self.imgNaturalSize.h * level || 10, + w:self.imgNaturalSize.w * level || 10, + }; + img.width=afterImgSize.w; + img.height=afterImgSize.h; + self.imgbox.style.width=afterImgSize.w + 'px'; + self.imgbox.style.height=afterImgSize.h + 'px'; + if (afterImgSize.w < 60) { + self.imgState.style.display = "none"; + } else { + self.imgState.style.display = ""; + } + if (afterImgSize.w < 220) { + self.maxButton.style.opacity = "0"; + self.closeButton.style.opacity = "0"; + } else { + self.maxButton.style.opacity = ""; + self.closeButton.style.opacity = ""; + } + if (afterImgSize.w < 100 || afterImgSize.h < 100) { + self.preButton.style.left = "-36px"; + self.nextButton.style.right = "-36px"; + } else { + self.preButton.style.left = "0px"; + self.nextButton.style.right = "0px"; + } + + var afterimgRectSize=self.getRotatedImgRectSize( self.rotatedRadians, afterImgSize ); + imgWindow.style.width=afterimgRectSize.w +'px'; + imgWindow.style.height=afterimgRectSize.h + 'px'; + self.setImgWindowOffset(rectOffset,imgRectSize,afterimgRectSize,ratio); + self.setImgOffset({top:0,left:0},afterImgSize,afterimgRectSize);//如果旋转了,调整偏移 + self.zoomLevel=level; + self.setToolBadge('zoom',level); + self.keepScreenInside(); + }; + + if(typeof e!='object'){ + ratio=ratio? ratio : { + x:1/2, + y:1/2, + }; + zoom(e); + return; + }; + + this.working=true; + + ratio=this.getZoomRatio({ + x:e.clientX, + y:e.clientY, + }); + + + var moved; + var lastPageX=e.pageX; + var currentLevel=this.zoomLevel; + var moveFired=0; + var moveHandler=function(e){ + moveFired++ + if(moveFired < 2){//有时候点击的时候不小心会触发一发move + return; + }; + moved=true; + var pageX=e.pageX; + var level; + if(pageX > lastPageX){//向右移,zoomin扩大 + self.changeCursor('zoom',false); + level=0.05; + }else{//向左移,zoomout缩小 + self.changeCursor('zoom',true); + level=-0.05; + }; + lastPageX=pageX; + currentLevel += level; + zoom(currentLevel); + }; + + var mouseupHandler=function(e){ + self.working=false; + document.removeEventListener('mousemove',moveHandler,true); + document.removeEventListener('mouseup',mouseupHandler,true); + + var level=self.getNextZoomLevel(); + + if(self.zoomOut && self.altKeyUp){ + self.zoomOut=false; + }; + + if(!moved){//如果没有平移缩放。 + zoom(level); + }; + + self.changeCursor('zoom',self.zoomOut); + + if(self.tempZoom && self.ctrlKeyUp && self.altKeyUp){ + self.tempZoom=false; + self.changeCursor(self.selectedTool); + }; + + }; + + document.addEventListener('mousemove',moveHandler,true); + document.addEventListener('mouseup',mouseupHandler,true); + }, + getNextZoomLevel:function(){ + var level; + var self=this; + if(this.zoomOut){//缩小 + let found = ImgWindowC.zoomRangeR.find(function(value){ + if(value < self.zoomLevel){ + level=value; + return true; + } + }); + if (!found) { + level = self.zoomLevel * 0.9; + } + }else{ + let found = ImgWindowC.zoomRange.find(function(value){ + if(value > self.zoomLevel){ + level=value; + return true; + }; + }); + if (!found) { + level = self.zoomLevel * 1.1; + } + } + return level; + }, + getZoomRatio:function(mouseCoor){ + var ibcRect=this.img.getBoundingClientRect(); + var ratio={ + x:(mouseCoor.x-ibcRect.left)/ibcRect.width, + y:(mouseCoor.y-ibcRect.top)/ibcRect.height, + }; + if(ratio.x<0){ + ratio.x=0 + }else if(ratio.x>1){ + ratio.x=1 + }; + if(ratio.y<0){ + ratio.y=0 + }else if(ratio.y>1){ + ratio.y=1 + }; + return ratio; + }, + aerialView:function(e){ + this.working=true; + //记住现在的缩放比例 + var cLevel=this.zoomLevel; + + var wSize=getWindowSize(); + wSize.h -= 16; + wSize.w -= 16; + + var imgWindow=this.imgWindow; + var imgWindowCS=unsafeWindow.getComputedStyle(imgWindow); + var rectSize={ + h:parseFloat(imgWindowCS.height), + w:parseFloat(imgWindowCS.width), + }; + var rectRatio=rectSize.h/rectSize.w; + var windowRatio=wSize.h/wSize.w; + + var size; + var rangeSize={}; + if(rectRatio > windowRatio){ + size={ + h:wSize.h, + w:wSize.h / rectRatio, + }; + rangeSize.h=Math.min(wSize.h * (size.h / rectSize.h), size.h); + rangeSize.w=Math.min(rangeSize.h / windowRatio , size.w); + }else{ + size={ + w:wSize.w, + h:wSize.w * rectRatio, + }; + rangeSize.w=Math.min(wSize.w * (size.w / rectSize.w), size.w); + rangeSize.h=Math.min(rangeSize.w * windowRatio , size.h); + }; + + + this.zoom(this.getRotatedImgCliSize(size).w/this.imgNaturalSize.w); + + this.center(true,true); + + this.keepScreenInside(); + + var viewRange=this.viewRange; + var vRS=viewRange.style; + vRS.display='block'; + vRS.height=rangeSize.h + 'px'; + vRS.width=rangeSize.w + 'px'; + vRS.top=0 + 'px'; + vRS.left=0 + 'px'; + + + + var viewRangeRect=viewRange.getBoundingClientRect(); + var scrolled=prefs.imgWindow.fixed ? {x:0, y:0} : getScrolled(); + var viewRangeCenterCoor={ + x:viewRangeRect.left + scrolled.x + 1/2 * rangeSize.w, + y:viewRangeRect.top + scrolled.y + 1/2 * rangeSize.h, + }; + + var self=this; + + var moveRange={ + x:[8,8+size.w-rangeSize.w], + y:[8,8+size.h-rangeSize.h] + }; + + + function setViewRangePosition(pageXY){ + var top=pageXY.y - viewRangeCenterCoor.y; + var left=pageXY.x - viewRangeCenterCoor.x; + if(top<=moveRange.y[0]){ + top=moveRange.y[0]; + }else if(top>=moveRange.y[1]){ + top=moveRange.y[1]; + }; + vRS.top= top + 'px'; + if(left<=moveRange.x[0]){ + left=moveRange.x[0]; + }else if(left>=moveRange.x[1]){ + left=moveRange.x[1]; + }; + vRS.left= left + 'px'; + }; + + setViewRangePosition({ + x:e.pageX, + y:e.pageY, + }); + + var moveHandler=function(e){ + setViewRangePosition({ + x:e.pageX, + y:e.pageY, + }); + }; + + var mouseupHandler=function(){ + self.working=false; + viewRange.style.display='none'; + self.zoom(cLevel); + var scrolled=prefs.imgWindow.fixed ? {x:0, y:0} : getScrolled(); + imgWindow.style.top= -13 - rectSize.h * ((parseFloat(vRS.top) - moveRange.y[0])/size.h) + scrolled.y +'px'; + imgWindow.style.left= -13 - rectSize.w * ((parseFloat(vRS.left) - moveRange.x[0])/size.w) + scrolled.x +'px'; + + //说明图片的高度没有屏幕高,居中 + //说明图片的宽度没有屏幕宽,居中 + self.center(rangeSize.w == size.w , rangeSize.h == size.h); + + self.keepScreenInside(); + + document.removeEventListener('mousemove',moveHandler,true); + document.removeEventListener('mouseup',mouseupHandler,true); + }; + document.addEventListener('mousemove',moveHandler,true); + document.addEventListener('mouseup',mouseupHandler,true); + }, + 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); + } + }break; + case 'rotate':{ + scale=1; + }break; + default:break; + } + content=typeof content=='string'? content : content.toFixed(scale); + this.toolMap[tool].nextElementSibling.textContent=content; + }, + notExponential:function(num){//不要转为指数形势 + if(num>0){ + if(num >= 999999999999999934463){ + return 999999999999999934463; + }else if(num <= 0.000001){ + return 0.000001; + }; + }else if(num < 0){ + if(num >= -0.000001){ + return -0.000001; + }else if(num <= -999999999999999934463){ + return -999999999999999934463 + }; + }; + + return num; + }, + + blur:function(e){ + if(!this.focused)return; + ImgWindowC.showing = false; + var imgWindow =this.imgWindow; + //点击imgWinodw的外部的时候失去焦点 + if(e!==true && imgWindow.contains(e.target))return; + imgWindow.classList.remove('pv-pic-window-container_focus'); + document.removeEventListener('mousedown',this._blur,true); + document.removeEventListener('keydown',this._focusedKeydown,false); + document.removeEventListener('keyup',this._focusedKeyup,true); + this.changeCursor('default'); + ImgWindowC.selectedTool=this.selectedTool; + this.imgWindow.style.zIndex= prefs.imgWindow.zIndex; + this.zIndex=prefs.imgWindow.zIndex; + this.focused=false; + }, + focus:function(){ + if(this.focused)return; + this.toolMap.compare.style.display = ImgWindowC.all.length > 1 ? "" : "none"; + ImgWindowC.showing = true; + this.imgWindow.classList.add('pv-pic-window-container_focus'); + this.imgWindow.style.zIndex=prefs.imgWindow.zIndex+1; + this.zIndex=prefs.imgWindow.zIndex+1; + document.addEventListener('keydown',this._focusedKeydown,false); + document.addEventListener('keyup',this._focusedKeyup,true); + document.addEventListener('mousedown',this._blur,true); + + //还原鼠标样式。 + this.changeCursor(this.selectedTool); + + if(prefs.imgWindow.syncSelectedTool && ImgWindowC.selectedTool){ + this.selectTool(ImgWindowC.selectedTool); + }; + + this.focused=true; + }, + focusedKeyup:function(e){ + var keyCode=e.keyCode; + var valid=[27,32,18,16,72,17,72,82,90,67,37,39]; + if(valid.indexOf(keyCode)==-1)return; + + if (window.getSelection().toString()) return; + e.preventDefault(); + + switch(keyCode){ + case 32:{//空格键,临时切换到移动 + this.spaceKeyUp=true; + if(!this.tempHand)return;//如果之前没有临时切换到抓手工具(当已经在工作的时候,按下空格不会临时切换到抓手工具) + if(!this.working){//松开按键的时候,没有在继续平移了。 + this.tempHand=false; + this.changeCursor(this.selectedTool); + }; + }break; + case 18:{//alt键盘切换缩小放大。 + this.altKeyUp=true; + if(!this.zoomOut)return; + if(!this.working){ + this.zoomOut=false; + this.changeCursor('zoom'); + if(this.tempZoom && this.ctrlKeyUp){ + this.tempZoom=false; + this.changeCursor(this.selectedTool); + }; + }; + }break; + case 16://shift键,旋转的时候按住shift键,步进缩放。 + this.shiftKeyUp=true; + break; + case 17:{//ctrl键 + clearTimeout(this.ctrlkeyDownTimer); + if(!this.justCKeyUp){//如果刚才没有松开c,规避划词软件的ctrl+c松开 + this.ctrlKeyUp=true; + if(!this.tempZoom)return;//如果没有切换到了缩放 + if(!this.working && this.altKeyUp){ + this.tempZoom=false; + this.changeCursor(this.selectedTool); + }; + }; + }break; + case 67:{//c键 + this.justCKeyUp=true; + var self=this; + clearTimeout(this.justCKeyUpTimer); + this.justCKeyUpTimer=setTimeout(function(){ + self.justCKeyUp=false; + _GM_setClipboard(self.src); + },100) + }break; + case 72://h键 + this.hKeyUp=true; + break; + case 82://r键 + this.rKeyUp=true; + break; + case 90://z键 + this.zKeyUp=true; + break; + case 39: + this.switchImage(true); + break; + case 37: + this.switchImage(false); + break; + case 27: + if (prefs.imgWindow.close.escKey) { + this.remove(); + e.stopPropagation(); + } + break; + default:break; + }; + + if([72,82,90].indexOf(keyCode)!=-1){ + if(!this.working && this.restoreBeforeTool){ + this.restoreBeforeTool=false; + this.selectTool(this.beforeTool); + }; + }; + }, + focusedKeydown:async function(e){ + var keyCode=e.keyCode; + if (!prefs.floatBar.keys.enable) return; + if (window.getSelection().toString()) return; + if (this.data && this.data.img && e.key.toLowerCase() == prefs.floatBar.keys.download) { + downloadImg(this.img.src, (this.data.img.title || this.data.img.alt), prefs.saveName); + e.preventDefault(); + e.stopPropagation(); + return; + } + if (e.key.toLowerCase() == prefs.floatBar.keys.gallery) { + if (!gallery) { + gallery = new GalleryC(); + gallery.data = []; + } + var allData = await gallery.getAllValidImgs(); + allData.target = this.data; + gallery.data = allData; + gallery.load(gallery.data); + this.remove(); + e.preventDefault(); + e.stopPropagation(); + return; + } + var valid=[32,82,72,90,18,16,17,27,67];//有效的按键 + if(valid.indexOf(keyCode)==-1) return; + + e.preventDefault(); + + if(this.working){//working的时候也可以接受按下shift键,以便旋转的时候可以任何时候按下 + if(keyCode==16){//shift键 + this.shiftKeyUp=false; + }; + return; + }; + switch(keyCode){ + case 82:{//r键,切换到旋转工具 + if(this.rKeyUp){ + this.rKeyUp=false; + this.beforeTool=this.selectedTool; + if (this.beforeTool != 'rotate') { + this.selectTool('rotate'); + } + var PI = Math.PI; + var value = this.rotatedRadians + (e.shiftKey ? -90 : 90) * PI / 180; + if (value >= 2 * PI) { + value -= 2 * PI; + } else if (value < 0) { + value += 2 * PI; + } + this.rotate(value,true); + }; + }break; + case 72:{//h键,切换到抓手工具 + if(this.hKeyUp){ + this.hKeyUp=false; + this.beforeTool=this.selectedTool; + this.selectTool('hand'); + }; + }break; + case 90:{//z键,切换到缩放工具 + if(this.zKeyUp){ + this.zKeyUp=false; + this.beforeTool=this.selectedTool; + this.selectTool('zoom'); + let level = e.shiftKey ? (this.zoomLevel - 0.5) : (this.zoomLevel + 0.5); + if (typeof level != 'undefined') { + this.zoom(level, { x: 0, y: 0}); + } + if (uniqueImgWin && uniqueImgWin == this) { + if (prefs.floatBar.globalkeys.previewFollowMouse) { + this.followPos(uniqueImgWinInitX, uniqueImgWinInitY, true); + } else { + this.center(true, true); + } + } + }; + }break; + case 32:{//空格键阻止,临时切换到抓手功能 + if(this.spaceKeyUp){ + this.spaceKeyUp=false; + if(this.selectedTool!='hand'){ + this.tempHand=true; + this.changeCursor('hand'); + }; + }; + }break; + case 18:{//alt键,在当前选择是缩放工具的时候,按下的时候切换到缩小功能 + if(this.altKeyUp){ + if((this.selectedTool!='zoom' && !this.tempZoom) || this.zoomOut)return; + this.zoomOut=true; + this.altKeyUp=false; + this.changeCursor('zoom',true); + }; + }break; + case 17:{//ctrl键临时切换到缩放工具 + if(this.ctrlKeyUp){ + var self=this; + this.ctrlkeyDownTimer=setTimeout(function(){//规避词典软件的ctrl+c,一瞬间切换到缩放的问题 + self.ctrlKeyUp=false; + if(self.selectedTool!='zoom'){ + self.tempZoom=true; + self.changeCursor('zoom'); + }; + },100); + }; + }break; + case 67:{//c键 + clearTimeout(this.ctrlkeyDownTimer); + }break; + case 27:{//ese关闭窗口 + }break; + default:break; + } + e.stopPropagation(); + return false; + }, + + toolbarEventHandler:function(e){ + e.preventDefault(); + e.stopPropagation(); + var target=e.target; + var toolMap=this.toolMap; + for(var i in toolMap){ + if(toolMap.hasOwnProperty(i) && toolMap[i]==target){ + switch(e.type){ + case 'mousedown':{ + this.selectTool(i); + }break; + case 'dblclick':{ + this.dblclickCommand(i); + }break; + default:break; + }; + break; + }; + }; + }, + imgWindowEventHandler:function(e){ + if (e.button == 0) { + e.stopPropagation(); + } + var selectedTool=this.selectedTool; + if(selectedTool == "hand" && prefs.imgWindow.suitLongImg && this.isLongImg){ + var inScroll=this.imgWindow.classList.contains("pv-pic-window-scroll"); + if(e.type == "wheel" && inScroll) + return; + if(e.type == "click" && !this.moving){ + var wSize=getWindowSize(); + var imgWindow=this.imgWindow; + var scrolled=prefs.imgWindow.fixed ? {x:0, y:0} : getScrolled(); + var origTop=parseFloat(imgWindow.style.top); + if(inScroll){ + imgWindow.style.top = parseFloat(imgWindow.style.top) - getScrolled(imgWindow.children[0]).y +'px'; + this.imgWindow.classList.remove("pv-pic-window-scroll"); + } else { + this.rotate(0,true); + this.imgWindow.classList.add("pv-pic-window-scroll"); + imgWindow.children[0].scrollTop = -parseInt(imgWindow.style.top); + } + //this.center(true , true); + if(!inScroll){ + imgWindow.style.top= (wSize.h - imgWindow.offsetHeight)/2 + scrolled.y +'px'; + var scrollTop=parseFloat(imgWindow.style.top)-origTop; + if(this.imgWindow.scrollTop)this.imgWindow.scrollTop = scrollTop; + else if(this.imgWindow.pageYOffset)this.imgWindow.pageYOffset = scrollTop; + else if(this.imgWindow.scrollY)this.imgWindow.scrollY = scrollTop; + } + this.keepScreenInside(); + } + } + switch(e.type){ + case 'click':{//阻止opera的图片保存 + if (selectedTool === "hand" && !this.moving) { + if (this.imgWindow.classList.contains("pv-pic-window-black")) { + this.imgWindow.classList.remove("pv-pic-window-black"); + this.imgWindow.classList.add("pv-pic-window-white"); + } else if (this.imgWindow.classList.contains("pv-pic-window-white")) { + this.imgWindow.classList.remove("pv-pic-window-white"); + } else { + this.imgWindow.classList.add("pv-pic-window-black"); + } + } + this.moving=false; + if(e.ctrlKey && e.target.nodeName.toUpperCase()=='IMG'){ + e.preventDefault(); + } + }break; + case 'mousedown': + case 'touchstart':{ + if(!this.focused){//如果没有focus,先focus + this.focus(); + this.moving=true; + this.keepScreenInside(); + }; + + var target=e.target; + if(e.button==2){//由于rotate时候的覆盖层问题,修复右键的图片菜单弹出 + if(target!=this.rotateOverlayer)return; + var self=this; + this.rotateOverlayer.style.display='none'; + var upHandler=function(){ + document.removeEventListener('mouseup',upHandler,true); + setTimeout(function(){ + self.rotateOverlayer.style.display='block'; + },10); + }; + document.addEventListener('mouseup',upHandler,true); + return; + }; + + if((e.button!=0 && e.type!="touchstart") || (target!=this.imgWindow && target!=this.img && target!=this.rotateOverlayer && target!=this.imgState))return; + e.preventDefault(); + if(this.tempHand){ + this.move(e); + }else if(this.tempZoom){ + this.zoom(e); + }else if(selectedTool=='hand'){ + this.restoreBeforeTool=!this.hKeyUp; + if(this.hKeyUp){ + this.move(e); + }else{//鸟瞰视图 + this.aerialView(e); + }; + }else if(selectedTool=='rotate'){ + var origin={//旋转原点 + x:e.clientX - 30,//稍微偏左一点。 + y:e.clientY , + }; + + var rIS=this.rotateIndicator.style; + //rIS.display='block'; + rIS.top=origin.y + 'px'; + rIS.left=origin.x + 'px'; + + this.restoreBeforeTool=!this.rKeyUp; + this.rotate(origin); + }else if(selectedTool=='zoom'){ + this.restoreBeforeTool=!this.zKeyUp; + this.zoom(e); + }; + }break; + case 'wheel':{ + if(!this.focused)return;//如果没有focus + if(e.deltaY===0)return;//非Y轴的滚动 + e.preventDefault(); + if(this.working)return; + var oriZoomOut=this.zoomOut; + this.zoomOut = !!(e.deltaY > 0); + + var ratio=this.getZoomRatio({ + x:e.clientX, + y:e.clientY, + }); + + var level=this.getNextZoomLevel(); + this.zoomed=true; + + this.zoom(level,ratio); + this.zoomOut=oriZoomOut; + }break; + default:break; + }; + }, + + dblclickCommand:function(tool){ + var done; + switch(tool){ + case 'hand':{//双击居中,并且适应屏幕 + this.zoom(1); + this.fitToScreen(); + this.center(true,true); + this.keepScreenInside(); + }break; + case 'rotate':{//双击还原旋转 + if(this.rotatedRadians==0)return; + done=true; + this.rotate(0,true); + }break; + case 'zoom':{//双击还原缩放 + if(this.zoomLevel==1)return; + done=true; + this.zoom(1,{x:0,y:0}); + }break; + default:break; + }; + + if((tool=='rotate' || tool=='zoom') && done){ + var scrolled=prefs.imgWindow.fixed ? {x:0, y:0} : getScrolled(); + var imgWindow=this.imgWindow; + var imgWinodowRect=imgWindow.getBoundingClientRect(); + var imgWindowStyle=imgWindow.style; + if(imgWinodowRect.left<40){ + imgWindowStyle.left=40 + scrolled.x + 'px'; + }; + if(imgWinodowRect.top<-5){ + imgWindowStyle.top=-5 + scrolled.y +'px'; + }; + this.keepScreenInside(); + }; + + }, + doFlipCommand:function(command){ + var map={ + fv:[/scaleY\([^)]*\)/i,' scaleY(-1) '], + fh:[/scaleX\([^)]*\)/i,' scaleX(-1) '], + }; + + var iTransform=this.img.parentNode.style[support.cssTransform]; + + var toolClassList=this.toolMap[command].classList; + + if(map[command][0].test(iTransform)){ + iTransform=iTransform.replace(map[command][0],''); + toolClassList.remove(this.selectedToolClass); + }else{ + iTransform += map[command][1]; + toolClassList.add(this.selectedToolClass); + }; + this.img.parentNode.style[support.cssTransform]=iTransform; + + }, + selectTool:function(tool){ + var command=['fv','fh']; + if(command.indexOf(tool)==-1){//工具选择 + if(this.selectedTool==tool){ + return; + } + let self=this; + if(tool=="compare"){ + var topmost=0, topmostWin; + ImgWindowC.all.forEach(function(iwin){ + if(iwin.zIndex >= topmost && iwin!=self){ + topmost=iwin.zIndex; + topmostWin=iwin; + }; + }); + if(topmostWin){ + this.toolMap.compare.style.display="none"; + this.compare([topmostWin.img.src]); + }; + return; + } + var selectedTool=this.selectedTool; + this.selectedTool=tool; + if(this.tempHand || this.tempZoom){//临时工具中。不变鼠标 + return; + }; + + this.rotateOverlayer.style.display=(tool=='rotate'? 'block' : 'none');//这个覆盖层是为了捕捉双击或者单击事件。 + + if(selectedTool){ + this.toolMap[selectedTool].classList.remove(this.selectedToolClass); + }; + this.toolMap[tool].classList.add(this.selectedToolClass); + this.changeCursor(tool); + }else{//命令 + this.doFlipCommand(tool); + }; + }, + changeCursor:function(tool,zoomOut){ + if(tool=='zoom'){ + tool+=zoomOut? '-out' : '-in'; + }; + if(this.cursor==tool)return; + this.cursor=tool; + + var cursor; + + switch(tool){ + case 'hand':{ + cursor=support.cssCursorValue.grab || 'pointer'; + }break; + case 'handing':{ + cursor=support.cssCursorValue.grabbing || 'pointer'; + }break; + case 'zoom-in':{ + cursor=support.cssCursorValue.zoomIn; + }break; + case 'zoom-out':{ + cursor=support.cssCursorValue.zoomOut; + }break; + case 'rotate':{ + cursor='progress'; + }break; + case 'default':{ + cursor=''; + }break; + }; + + if(typeof cursor!='undefined'){ + this.imgWindow.style.cursor=cursor; + }; + + }, + + remove:function(opacity){ + if(this.removed)return; + this.removed=true; + //this.imgWindow.classList.remove("pv-pic-window-transition-all"); + this.blur(true); + if(!opacity)this.imgWindow.style.opacity=0; + let self = this; + setTimeout(() => { + if (this.removed) { + if (self.isImg) self.img.src= prefs.icons.brokenImg_small;//如果在加载中取消,图片也取消读取。 + self.imgWindow.parentNode.removeChild(self.imgWindow); + } + },300); + + if (!this.preview) { + var index=ImgWindowC.all.indexOf(this); + ImgWindowC.all.splice(index,1); + } + var removeEvent=document.createEvent('CustomEvent'); + removeEvent.initCustomEvent('pv-removeImgWindow',false,false); + this.imgWindow.dispatchEvent(removeEvent); + + //focus next + if(ImgWindowC.all.length==0){ + if(ImgWindowC.overlayer){ + ImgWindowC.overlayer.style.display='none'; + }; + }else{ + var topmost=0, topmostWin; + ImgWindowC.all.forEach(function(iwin){ + if(iwin.zIndex >= topmost){ + topmost=iwin.zIndex; + topmostWin=iwin; + } + }); + if(topmostWin){ + topmostWin.focus(); + } + } + + }, + + }; + + addWheelEvent(getBody(document), e => { + if (uniqueImgWin && !uniqueImgWin.removed) { + if (uniqueImgWin.isLongImg) { + e.preventDefault(); + e.stopPropagation(); + uniqueImgWin.img.parentNode.scrollTop += e.deltaY; + } else if (uniqueImgWin.curIndex >= 0) { + e.preventDefault(); + e.stopPropagation(); + uniqueImgWin.switchImage(e.deltaY > 0); + } + } + }, true); + + var lastPopupLoading; + // 载入动画 + function LoadingAnimC(data, buttonType, waitImgLoad, openInTopWindow, initPos) { + if (LoadingAnimC.all.find(function(item, index, array) { + if (data.src == item.data.src || data.img == item.data.img) { + return true; + } + })) return false; + if (buttonType === "popup") { + if (lastPopupLoading) { + lastPopupLoading.cancel(); + } + lastPopupLoading = this; + } + this.args = arrayFn.slice.call(arguments, 0); + if (data.src != data.imgSrc && !data.srcs) { + data.srcs = [data.imgSrc]; + } + this.data = data;//data + this.buttonType = buttonType;//点击的按钮类型 + this.openInTopWindow = openInTopWindow;//是否在顶层窗口打开,如果在frame里面的话 + this.waitImgLoad = waitImgLoad;//是否等待完全读取后打开 + this.initPos = initPos || false; + this.init(); + } + + LoadingAnimC.all=[]; + + LoadingAnimC.prototype={ + init:function(){ + LoadingAnimC.all.push(this); + this.addStyle(); + var container=document.createElement('span'); + + container.className='pv-loading-container'; + this.loadingAnim=container; + + container.title=i18n("loading")+':' + this.data.src; + let retrySpan=document.createElement('span'); + retrySpan.className='pv-loading-button pv-loading-retry'; + retrySpan.title='Retry'; + container.appendChild(retrySpan); + let cancelSpan=document.createElement('span'); + cancelSpan.className='pv-loading-button pv-loading-cancle'; + cancelSpan.title='Cancel'; + container.appendChild(cancelSpan); + /*container.innerHTML= + ''+ + '';*/ + + if (this.buttonType == 'popup'){ + container.style.pointerEvents="none"; + } + getBody(document).appendChild(container); + + var self = this; + container.addEventListener('click',function(e){ + var tcl=e.target.classList; + if(tcl.contains('pv-loading-cancle')){ + self.cancel(); + }else if(tcl.contains('pv-loading-retry')){ + self.remove(); + new LoadingAnimC(self.args[0],self.args[1],self.args[2],self.args[3]); + }; + },true); + + this.setPosition(); + + if (!this.data.noActual && (this.buttonType == 'current' || this.buttonType == 'gallery')) { + this.loadImg(this.data.imgSrc); + } else { + if (!this.data.xhr) { + if(this.buttonType == 'search'){ + sortSearch(); + let from=0; + let searchFun=function(){ + searchImgByImg(self.data.imgSrc, null, function(srcs, index){ + let src=srcs.shift(); + if(index==3){ + self.loadImg(src, srcs); + }else{ + from=index+1; + self.loadImg(src, srcs, searchFun); + } + },function(e) { + self.error("网络错误"); + },function(e) { + self.error("没有找到原图"); + }, from); + }; + searchFun(); + }else{ + this.loadImg(this.data.src||this.data.imgSrc, this.data.srcs); + } + } else { + xhrLoad.load({ + url: this.data.src, + xhr: this.data.xhr, + cb: function(imgSrc, imgSrcs, caption) { + if (imgSrc) { + self.data.src = imgSrc; + if (imgSrcs && imgSrcs.length) { + imgSrcs = Array.from(new Set(imgSrcs)); + } + self.data.all = imgSrcs; + if (caption) self.data.description = caption; + self.loadImg(imgSrc, imgSrcs); + } else if (self.data.imgSrc) { + self.loadImg(self.data.imgSrc, imgSrcs); + } else { + self.error(); + } + }, + onerror: function() { + self.error(); + } + }); + } + } + }, + addStyle:function(){ + if (LoadingAnimC.style) { + if (!LoadingAnimC.style.parentNode) { + LoadingAnimC.style = _GM_addStyle(LoadingAnimC.style.innerText); + } + return; + } + LoadingAnimC.style=_GM_addStyle('\ + .pv-loading-container {\ + position: absolute;\ + z-index:999999997;\ + background: black url("'+prefs.icons.loading+'") center no-repeat;\ + background-origin: content-box;\ + border: none;\ + padding: 1px 30px 1px 2px;\ + margin: 0;\ + opacity: 0.5;\ + height: 24px;\ + min-width: 24px;\ + box-shadow: 2px 2px 0px #666;\ + -webkit-transition: opacity 0.15s ease-in-out;\ + transition: opacity 0.15s ease-in-out;\ + width: initial;\ + }\ + .pv-loading-container:hover {\ + opacity: 0.9;\ + }\ + .pv-loading-button {\ + cursor: pointer;\ + height: 24px;\ + width: 24px;\ + position: absolute;\ + right: 0;\ + top: 0;\ + opacity: 0.4;\ + background:transparent center no-repeat;\ + -webkit-transition: opacity 0.15s ease-in-out;\ + transition: opacity 0.15s ease-in-out;\ + }\ + .pv-loading-button:hover {\ + opacity: 1;\ + }\ + .pv-loading-cancle{\ + pointer-events: all;\ + background-image: url("'+prefs.icons.loadingCancle+'");\ + }\ + .pv-loading-retry{\ + display:none;\ + pointer-events: all;\ + background-image: url("'+prefs.icons.retry+'");\ + }\ + .pv-loading-container_error{\ + background-image:none;\ + }\ + .pv-loading-container_error::after{\ + content:"'+i18n("loadError")+'";\ + line-height: 24px;\ + color: red;\ + font-size: 14px;\ + display:inline;\ + }\ + .pv-loading-container_error .pv-loading-cancle{\ + display:none;\ + }\ + .pv-loading-container_error .pv-loading-retry{\ + display:block;\ + }\ + '); + }, + remove:function(){ + if(!this.removed){ + this.removed=true; + this.loadingAnim.parentNode.removeChild(this.loadingAnim); + LoadingAnimC.all.splice(LoadingAnimC.all.indexOf(this),1); + }; + }, + cancel:function(){ + this.imgReady && this.imgReady.abort(); + this.remove(); + }, + error:function(msg,img,e){ + if(msg)debug(msg); + this.loadingAnim.style.pointerEvents=""; + this.loadingAnim.classList.add('pv-loading-container_error'); + debug('Picviewer CE+ 载入大图错误:%o', this.data); + + var self=this; + setTimeout(function(){ + self.remove(); + },3000); + }, + setPosition:function(){ + var position=getContentClientRect(this.data.img); + var cs=this.loadingAnim.style; + var scrolled=getScrolled(); + cs.top=position.top + scrolled.y +1 + 'px'; + cs.left=position.left + scrolled.x +1 + 'px'; + cs.removeProperty('display'); + }, + + // 根据 imgSrc 载入图片,imgSrcs 为备用图片地址,imgSrc 加载失败备用 + loadImg: async function(imgSrc, imgSrcs, nextFun) { + var self = this; + + var mode = matchedRule.getMode(imgSrc); + var media; + let loaded = function() { + media.play(); + self.load(this || media); + media.removeEventListener('loadeddata', loaded); + } + if (this.buttonType === 'magnifier') { + media = document.createElement('img'); + media.src = (mode === "video" || mode === "audio") ? this.data.imgSrc : imgSrc; + mode = ""; + } else { + switch (mode) { + case "video": + media = createVideo(); + media.style.width = 0; + media.style.height = 0; + media.controls = true; + media.loop = true; + media.autoplay = true; + media.volume = matchedRule.mute ? 0 : 1; + imgSrc = imgSrc.replace(/^video:/, ""); + if (imgSrc.indexOf('.mkv') !== -1) media.type = 'video/mp4'; + else if (imgSrc.indexOf('.m3u8') !== -1) { + try { + loaded(); + await initVideojs(media, imgSrc); + imgSrc = ""; + } catch (error) { + console.error('Failed to load or initialize Video.js player:', error); + } + } + break; + case "audio": + media = document.createElement('audio'); + media.controls = true; + media.autoplay = true; + media.volume = matchedRule.mute ? 0 : 1; + imgSrc = imgSrc.replace(/^audio:/, ""); + break; + default: + media = document.createElement('img'); + break; + } + if (imgSrc) media.src = imgSrc; + curLoadingMedia = media; + } + + var opts = { + error: function(e) { + if (/^blob:/.test(imgSrc)) { + if (Array.isArray(imgSrcs)) { + var src = imgSrcs.shift(); + if (src) { + self.loadImg(src, imgSrcs, nextFun); + return; + } + } + } else if (mode == "image" || mode == "") { + _GM_xmlhttpRequest({ + method: 'GET', + url: media.src, + responseType: 'blob', + onload: function(response) { + const blobUrl = URL.createObjectURL(response.response); + self.loadImg(blobUrl, imgSrcs, nextFun); + const releaseBlob = () => URL.revokeObjectURL(blobUrl); + window.addEventListener('beforeunload', releaseBlob); + }, + onerror: function() { + if (Array.isArray(imgSrcs)) { + var src = imgSrcs.shift(); + if (src) { + self.loadImg(src, imgSrcs, nextFun); + return; + } + } else { + self.error('', this, e); + } + } + }); + return; + } else { + self.error('', this, e); + } + + if(nextFun) nextFun(); + else self.error('', this, e); + }, + }; + + opts[self.waitImgLoad ? 'load' : 'ready'] = function(e) { + self.load(this, e); + }; + + if (mode === 'video' || mode === 'audio') { + if (imgSrc) { + media.addEventListener('loadeddata', loaded); + media.load(); + } + } else { + self.imgReady = imgReady(media, opts); + } + }, + + load:async function(img,e){ + this.remove(); + this.img=img; + var buttonType=this.buttonType; + + if(buttonType=='gallery'){ + if(!gallery){ + gallery=new GalleryC(); + gallery.data=[]; + } + var allData=await gallery.getAllValidImgs(); + allData.target=this.data; + this.data=allData; + }; + + var self=this; + function openInTop(){ + var data=self.data; + + //删除不能发送的项。 + var delCantClone=function(obj){ + if(!obj)return; + delete obj.img; + delete obj.imgPA; + }; + + if(Array.isArray(data)){ + frameSentSuccessData=frameSentData; + frameSentData=cloneObject(data,true); + delCantClone(data.target); + data.forEach(function(obj){ + delCantClone(obj); + }); + }else{ + delCantClone(data); + }; + + window.postMessage({ + messageID:messageID, + src:img.src, + data:data, + command:'open', + buttonType:buttonType, + to:'top', + },'*'); + }; + + if(this.openInTopWindow && isFrame && topWindowValid!==false && buttonType!='magnifier'){ + if(topWindowValid){ + openInTop(); + }else{//先发消息问问顶层窗口是不是非frameset窗口 + window.postMessage({ + messageID:messageID, + command:'topWindowValid', + to:'top', + },'*'); + + document.addEventListener('pv-topWindowValid',function(e){ + topWindowValid=e.detail; + if(topWindowValid){//如果顶层窗口有效 + openInTop(); + }else{ + self.open(); + }; + },true); + }; + + }else{ + this.open(); + }; + + + }, + open:function(){ + switch(this.buttonType){ + case 'popup': + if(uniqueImgWin && uniqueImgWin.src != this.data.src && (!this.data.srcs || !this.data.srcs.includes(uniqueImgWin.src))){ + uniqueImgWin.remove(); + } + if(!uniqueImgWin || uniqueImgWin.removed){ + this.data.src=this.img.src; + uniqueImgWin = new ImgWindowC(this.img, this.data, null, null, true); + //uniqueImgWin.imgWindow.classList.add("pv-pic-window-transition-all"); + } + //uniqueImgWin.blur({target:this.data.img}); + if(!uniqueImgWin.loaded){ + if(prefs.waitImgLoad){ + uniqueImgWin.imgWindow.style.display = "none"; + uniqueImgWin.imgWindow.style.opacity = 0; + }else{ + if (prefs.floatBar.globalkeys.previewFollowMouse) { + uniqueImgWin.followPos(uniqueImgWinInitX, uniqueImgWinInitY); + } else { + uniqueImgWin.initMaxSize(); + uniqueImgWin.center(true,true); + if(centerInterval)clearInterval(centerInterval); + centerInterval=setInterval(function(){ + if(!uniqueImgWin || uniqueImgWin.removed || uniqueImgWin.loaded){ + clearInterval(centerInterval); + }else{ + uniqueImgWin.center(true,true); + } + },300); + } + } + } + uniqueImgWin.imgWindow.classList.add("preview"); + break; + case 'gallery': + if(!gallery){ + gallery=new GalleryC(); + }; + gallery.load(this.data,this.from); + gallery.changeMinView(); + break; + case 'magnifier': + new MagnifierC(this.img,this.data); + break; + case 'download': + downloadImg(this.data.src || this.data.imgSrc, (this.data.img.title || this.data.img.alt), prefs.saveName); + break; + case "copy": + _GM_setClipboard(this.data.src || this.data.imgSrc); + break; + case "open": + _GM_openInTab(this.data.src || this.data.imgSrc, {active:false}); + break; + case "copyImg": + copyData(this.data.src || this.data.imgSrc); + break; + case 'actual': + case 'search': + case 'current': + case 'original'://original 是为了兼容以前的规则 + if(this.data.src!=this.img.src)this.data.src=this.img.src; + new ImgWindowC(this.img, this.data, this.buttonType == 'actual', this.initPos || false); + break; + }; + }, + }; + + function copyData(url) { + if (typeof ClipboardItem != 'undefined') { + urlToBlob(url, (blob, ext) => { + if (blob) { + try { + navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob + }) + ]); + } catch (error) { + console.error(error); + } + } + }, true); + } else _GM_setClipboard(url); + } + + //工具栏 + function FloatBarC(){ + this.init(); + }; + + FloatBarC.prototype={ + init:function(){ + this.addStyle(); + var container=document.createElement('span'); + container.id='pv-float-bar-container'; + getBody(document).appendChild(container); + for(let i=0;i<5;i++){ + let spanChild=document.createElement('span'); + spanChild.className='pv-float-bar-button'; + container.appendChild(spanChild); + } + /*container.innerHTML= + ''+ + ''+ + ''+ + ''+ + '';*/ + + var buttons={ + }; + this.buttons=buttons; + this.children=container.children; + + arrayFn.forEach.call(this.children,function(child,index){ + var titleMap={ + actual:i18n("actualBtn").replace(/ ?\(A\)/,` (${prefs.floatBar.keys.actual.toUpperCase()})`), + search:i18n("searchBtn").replace(/ ?\(S\)/,` (${prefs.floatBar.keys.search.toUpperCase()})`), + gallery:i18n("galleryBtn").replace(/ ?\(G\)/,` (${prefs.floatBar.keys.gallery.toUpperCase()})`), + current:i18n("currentBtn").replace(/ ?\(C\)/,` (${prefs.floatBar.keys.current.toUpperCase()})`), + magnifier:i18n("magnifierBtn").replace(/ ?\(M\)/,` (${prefs.floatBar.keys.magnifier.toUpperCase()})`), + download:i18n("download")+` (${prefs.floatBar.keys.download.toUpperCase()})`, + }; + var buttonName=prefs.floatBar.butonOrder[index]; + if(!buttonName){ + child.style.display="none"; + return; + } + buttons[buttonName]=child; + child.title=titleMap[buttonName]; + child.classList.add('pv-float-bar-button-' + buttonName); + }); + + + this.floatBar=container; + + + var self=this; + container.addEventListener('click',function(e){ + var buttonType; + var target=e.target; + for(var type in buttons){ + if(!buttons.hasOwnProperty(type))return; + if(target==buttons[type]){ + buttonType=type; + break; + }; + }; + if(!buttonType)return; + + self.open(e,buttonType); + self.hide(); + + },true); + + + addCusMouseEvent('mouseleave',container,function(e){ + clearTimeout(self.hideTimer); + self.hideTimer=setTimeout(function(){ + self.hide(); + },prefs.floatBar.hideDelay); + }); + + addCusMouseEvent('mouseenter',container,function(e){ + clearTimeout(self.hideTimer); + clearTimeout(self.showTimer); + clearTimeout(self.globarOutTimer); + }); + + this._scrollHandler=this.scrollHandler.bind(this); + }, + addStyle:function(){ + if (FloatBarC.style) { + if (!FloatBarC.style.parentNode) { + FloatBarC.style = _GM_addStyle(FloatBarC.style.innerText); + } + return; + } + FloatBarC.style=_GM_addStyle('\ + #pv-float-bar-container {\ + position: absolute;\ + background-image: initial;\ + top: 0px;\ + left: 0px;\ + z-index:2147483640;\ + padding: 5px;\ + margin: 0;\ + border: none;\ + opacity: 0.35;\ + line-height: 0;\ + -webkit-transition: opacity 0.2s ease-in-out;\ + transition: opacity 0.2s ease-in-out;\ + display:none;\ + }\ + #pv-float-bar-container:hover {\ + opacity: 1;\ + }\ + #pv-float-bar-container .pv-float-bar-button {\ + vertical-align:middle;\ + cursor: pointer;\ + width: 18px;\ + height: 18px;\ + padding: 0;\ + margin:0;\ + border: none;\ + display: inline-block;\ + position: relative;\ + box-shadow: 1px 0 3px 0px rgba(0,0,0,0.9);\ + background: transparent center no-repeat;\ + background-size:100% 100%;\ + background-origin: content-box;\ + -webkit-transition: margin-right 0.15s ease-in-out , width 0.15s ease-in-out , height 0.15s ease-in-out ;\ + transition: margin-right 0.15s ease-in-out , width 0.15s ease-in-out , height 0.15s ease-in-out ;\ + }\ + #pv-float-bar-container .pv-float-bar-button:not(:last-child){\ + margin-right: -14px;\ + }\ + #pv-float-bar-container .pv-float-bar-button:first-child {\ + z-index: 4;\ + }\ + #pv-float-bar-container .pv-float-bar-button:nth-child(2) {\ + z-index: 3;\ + }\ + #pv-float-bar-container .pv-float-bar-button:nth-child(3) {\ + z-index: 2;\ + }\ + #pv-float-bar-container .pv-float-bar-button:last-child {\ + z-index: 1;\ + }\ + #pv-float-bar-container:hover > .pv-float-bar-button {\ + width: 24px;\ + height: 24px;\ + }\ + #pv-float-bar-container:hover > .pv-float-bar-button:not(:last-child) {\ + margin-right: 4px;\ + }\ + #pv-float-bar-container .pv-float-bar-button-actual {\ + background-image:url("'+ prefs.icons.actual +'")!important;\ + }\ + #pv-float-bar-container .pv-float-bar-button-search {\ + background-image:url("'+ prefs.icons.search +'")!important;\ + }\ + #pv-float-bar-container .pv-float-bar-button-gallery {\ + background-image:url("'+ prefs.icons.gallery +'")!important;\ + }\ + #pv-float-bar-container .pv-float-bar-button-current {\ + background-image:url("'+ prefs.icons.current +'")!important;\ + }\ + #pv-float-bar-container .pv-float-bar-button-magnifier {\ + background-image:url("'+ prefs.icons.magnifier +'")!important;\ + }\ + #pv-float-bar-container .pv-float-bar-button-download {\ + background-image:url("'+ prefs.icons.download +'")!important;\ + }\ + '); + }, + start:function(data){ + + if (data && data.type == "link") { + data.hide = true; + } + //读取中的图片,不显示浮动栏,调整读取图标的位置. + if(LoadingAnimC.all.find(function(item,index,array){ + if (data.src == item.data.src || data.img == item.data.img) { + return true; + } + })) return false; + + + //被放大镜盯上的图片,不要显示浮动栏. + if(MagnifierC.all.find(function(item,index,array){ + if(data.src==item.data.src){ + return true; + }; + }))return false; + + var self=this; + clearTimeout(this.hideTimer); + + var imgOutHandler=function(e){ + document.removeEventListener('mouseout',imgOutHandler,true); + clearTimeout(self.showTimer); + clearTimeout(self.hideTimer); + self.hideTimer=setTimeout(function(){ + self.hide(); + },prefs.floatBar.hideDelay); + }; + + clearTimeout(this.globarOutTimer); + this.globarOutTimer=setTimeout(function(){//稍微延时。错开由于css hover样式发生的out; + document.addEventListener('mouseout',imgOutHandler,true); + },150); + + clearTimeout(this.showTimer); + self.data=data; + if (data.hide) { + this.show(); + this.floatBar.style.display = 'none'; + this.floatBar.style.opacity = 0; + return false; + } + if(!this.shown || self.data.img!=data.img){ + this.floatBar.style.transition="unset"; + this.floatBar.style.opacity=0.01; + } + this.showTimer=setTimeout(function(){ + self.show(); + },prefs.floatBar.showDelay); + return true; + }, + setButton:function(){ + if(this.buttons['actual']){ + if(this.data.noActual){ + this.buttons['actual'].style.display='none'; + }else{ + this.buttons['actual'].style.removeProperty('display'); + } + } + if(this.buttons['magnifier']){ + if(this.data.type != "force" && this.data.img.nodeName.toUpperCase() == 'IMG'){ + this.buttons['magnifier'].style.removeProperty('display'); + }else{ + this.buttons['magnifier'].style.display='none'; + } + } + if (this.data.img.nodeName.toUpperCase() != 'IMG') { + //this.buttons['gallery'].style.display = 'none'; + //this.buttons['current'].style.display = 'none'; + } else { + //this.buttons['gallery'].style.removeProperty('display'); + //this.buttons['current'].style.removeProperty('display'); + } + }, + setPosition: function() { + //如果图片被删除了,或者隐藏了。 + if (this.data.img.offsetWidth == 0) { + return true; + } + var targetPosi = getContentClientRect(this.data.img); + var pa = this.data.img.parentNode, limited = false; + if (pa && pa.scrollHeight > 30 && pa.scrollWidth > 30) { + var paPosi=pa.getBoundingClientRect(); + if (paPosi.width > 30 && paPosi.height > 30) { + const style = unsafeWindow.getComputedStyle(this.data.img); + const matrix = new DOMMatrixReadOnly(style.transform); + let translateX = matrix.m41, translateY = matrix.m42, scaleX = matrix.m11, scaleY = matrix.m22; + if (translateY || this.data.img.offsetTop != 0 || (scaleY && scaleY !== 1)) { + if (paPosi.height < targetPosi.height - 3) { + limited = true; + targetPosi.top = paPosi.top; + } + } + if (translateX || this.data.img.offsetLeft != 0 || (scaleX && scaleX !== 1)) { + if (paPosi.width < targetPosi.width - 3) { + limited = true; + targetPosi.left = paPosi.left; + } + } + } + } + pa = this.data.img.offsetParent; + if (pa && !limited) { + paPosi=pa.getBoundingClientRect(); + if (paPosi.width > 30 && paPosi.height > 30) { + const style = unsafeWindow.getComputedStyle(this.data.img); + const matrix = new DOMMatrixReadOnly(style.transform); + let translateX = matrix.m41, translateY = matrix.m42, scaleX = matrix.m11, scaleY = matrix.m22; + if (translateY || this.data.img.offsetTop != 0 || (scaleY && scaleY !== 1)) { + if (paPosi.height < targetPosi.height - 3) { + targetPosi.top = paPosi.top; + } + } + if (translateX || this.data.img.offsetLeft != 0 || (scaleX && scaleX !== 1)) { + if (paPosi.width < targetPosi.width - 3) { + targetPosi.left = paPosi.left; + } + } + } + } + var windowSize=getWindowSize(); + var img=this.data.img; + + var floatBarPosi=prefs.floatBar.position.toLowerCase().split(/\s+/); + + var offsetX=prefs.floatBar.offset.x; + var offsetY=prefs.floatBar.offset.y; + + let body = getBody(document); + let bodyStyle = unsafeWindow.getComputedStyle(body); + let offsetParent, bodyPosi; + + if (bodyStyle.position === "static") { + offsetParent = document.documentElement; + } else { + offsetParent = body; + } + bodyPosi = offsetParent.getBoundingClientRect(); + + + var scrolled=getScrolled(offsetParent); + targetPosi.top = targetPosi.top - bodyPosi.top; + targetPosi.left = targetPosi.left - bodyPosi.left; + targetPosi.bottom = bodyPosi.bottom - targetPosi.bottom; + targetPosi.right = bodyPosi.right - targetPosi.right; + + var fbs = this.floatBar.style; + var setPosition = { + top:function() { + var top = targetPosi.top; + if (top + offsetY - scrolled.y < 10) { + top = scrolled.y; + offsetY = 0; + } else { + if (prefs.floatBar.stayOut) { + top = top + offsetY - 10 - prefs.floatBar.stayOutOffsetY; + } else { + top = top + offsetY; + } + if (targetPosi.height <= 50) top -= 10; + } + fbs.bottom = 'unset'; + fbs.top = top + 'px'; + }, + right:function() { + var right = targetPosi.right; + if (prefs.floatBar.stayOut) { + right = right - offsetX - prefs.floatBar.stayOutOffsetX; + } else { + right = right - offsetX; + } + if (targetPosi.width <= 50) right += 10; + fbs.left = 'unset'; + fbs.right = right + 'px'; + }, + bottom:function() { + var bottom = targetPosi.bottom; + if (prefs.floatBar.stayOut) { + bottom = bottom - offsetY - 40 - prefs.floatBar.stayOutOffsetY; + } else { + bottom = bottom - offsetY - 30; + } + if (targetPosi.height <= 50) bottom += 10; + fbs.top = 'unset'; + fbs.bottom = bottom + 'px'; + }, + left:function() { + var left = targetPosi.left; + if (left + offsetX - scrolled.x < 0) { + left = scrolled.x; + offsetX = 0; + } else { + if (prefs.floatBar.stayOut) { + left = left + offsetX - prefs.floatBar.stayOutOffsetX; + } else { + left = left + offsetX; + } + if (targetPosi.width <= 50) left -= 10; + } + fbs.right = 'unset'; + fbs.left = left + 'px'; + }, + center:function() { + var left = targetPosi.left + offsetX; + fbs.left = left + targetPosi.width / 2 + 'px'; + }, + hide:function(){ + var top=targetPosi.top; + var left=targetPosi.left; + if(prefs.floatBar.stayOut){ + top=top + offsetY - 10 - prefs.floatBar.stayOutOffsetY; + left=left + offsetX - prefs.floatBar.stayOutOffsetX; + }else{ + top=top + offsetY; + left=left + offsetX; + } + if(targetPosi.height<=50)top-=10; + fbs.top=top + 'px'; + if(targetPosi.width<=50)left-=10; + fbs.left=left + 'px'; + }, + }; + + setPosition[floatBarPosi[0]](); + if(floatBarPosi.length>1){ + setPosition[floatBarPosi[1]](); + } + }, + show:function(){ + if(this.setPosition())return; + this.shown=true; + this.addStyle(); + this.setButton(); + this.floatBar.style.transition=""; + this.floatBar.style.display='block'; + this.floatBar.style.opacity=""; + clearTimeout(this.hideTimer); + window.removeEventListener('scroll',this._scrollHandler,true); + window.addEventListener('scroll',this._scrollHandler,true); + }, + hide:function(){ + clearTimeout(this.showTimer); + this.floatBar.style.opacity=0.01; + this.shown=false; + this.floatBar.style.display='none'; + window.removeEventListener('scroll',this._scrollHandler,true); + }, + scrollHandler:function(){//更新坐标 + clearTimeout(this.scrollUpdateTimer); + var self=this; + this.scrollUpdateTimer=setTimeout(function(){ + self.setPosition(); + },100); + }, + open:async function(e,buttonType){ + if (!this.shown || !this.data || !this.data.imgSrc) return; + if (window.getSelection().toString()) return; + if (this.data.imgSrc.indexOf("blob:") === 0) { + let blobUrl = await getBase64FromBlobUrl(this.data.imgSrc); + if (blobUrl) { + let sameSrc = (this.data.src === this.data.imgSrc); + this.data.imgSrc = blobUrl; + this.data.srcs = [this.data.imgSrc]; + if (sameSrc) { + this.data.src = blobUrl; + } + } + } + if (buttonType === 'download' && !this.data.xhr) { + if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return; + downloadImg(this.data.src || this.data.imgSrc, (this.data.img.title || this.data.img.alt), prefs.saveName); + e.stopPropagation(); + e.preventDefault(); + return; + } else { + e.stopPropagation(); + e.preventDefault(); + let altKey = e.altKey; + if (e.type != "click" && prefs.floatBar.globalkeys.invertInitShow && prefs.floatBar.globalkeys.alt) { + altKey = false; + } + let additionEnable = prefs.floatBar.invertAdditionalFeature ? !altKey : altKey; + if (additionEnable) { + let src, feature = prefs.floatBar.additionalFeature; + if (buttonType == 'actual') { + if (this.data.xhr) { + buttonType = feature || "open"; + } else { + src = this.data.src || this.data.imgSrc; + } + } else if (buttonType == 'current') { + src = this.data.imgSrc; + } + if (src) { + switch(feature) { + case "copy": + _GM_setClipboard(src); + break; + case "open": + _GM_openInTab(src, {active:false}); + break; + case "copyImg": + copyData(src); + break; + } + return; + } + } + } + var waitImgLoad = e && e.ctrlKey ? !prefs.waitImgLoad : prefs.waitImgLoad; //按住ctrl取反向值 + var openInTopWindow = e && e.shiftKey ? !prefs.framesPicOpenInTopWindow : prefs.framesPicOpenInTopWindow; //按住shift取反向值 + if (!waitImgLoad && buttonType == 'magnifier' && !envir.chrome) { //非chrome的background-image需要全部载入后才能显示出来 + waitImgLoad = true; + }; + new LoadingAnimC(this.data, buttonType, waitImgLoad, openInTopWindow); + if (e.type == "click") { + floatBar.hide(); + } + }, + update:function(img,src){ + if(this.data.img==img && this.data.imgSrc!=src){ + this.data.src=src; + this.data.noActual=false; + this.data.type="rule"; + if(this.shown){ + this.setButton(); + } + } + } + }; + + /** + * 提取自 Mouseover Popup Image Viewer 脚本,用于 xhr 方式的获取 + */ + var xhrLoad = function() { + var _ = {}; + + var caches = {}; + var handleError; + var cacheNum = 0; + var xhr; + var useFetch = false; + + /** + * @param q 图片的选择器或函数 + * @param c 图片说明的选择器或函数 + */ + function parsePage(url, q, c, post, cb, headers, after) { + downloadPage(url, post, headers, async function(html) { + var iurl, iurls = [], cap, caps, doc = createDoc(html); + + if(typeof q == 'function') { + iurl = await q(html, doc, url + (post ? `#p{${post}}` : ""), xhr, GM_fetch); + if (iurl) { + if(iurl.url) { + cap = iurl.cap; + iurl = iurl.url; + } + if (Array.isArray(iurl)) { + iurl = iurl.map(u => after(u)); + iurls = iurl; + iurl = iurls[0]; + } else iurl = after(iurl); + } + } else { + var inodes = findNodes(q, doc); + inodes.forEach(function(node) { + iurls.push(after(findFile(node, url))); + }); + iurl = iurls[0]; + } + + if (c) { + if(typeof c == 'function') { + cap = await c(html, doc, url, xhr); + } else { + var cnodes = findNodes(c, doc); + cap = cnodes.length ? findCaption(cnodes[0]) : false; + } + if (Array.isArray(cap)) { + caps = cap; + cap = caps[0]; + } + } + + // 缓存 + if (iurl) { + let cacheData = { + iurl: iurl, + iurls: iurls, + cap: cap, + caps: caps + }; + caches[url + (post || "")] = cacheData; + if (cacheNum) { + storage.setListItem("xhrCache", url + (post || ""), cacheData, cacheNum); + } + } + + cb(iurl, iurls, cap, caps); + }); + } + + async function downloadPage(url, post, headers, cb) { + var opts = { + method: 'GET', + url: url, + onload: function(req) { + try { + if(req.status > 399) throw 'Server error: ' + req.status; + else cb(req.responseText, req.finalUrl || url); + } catch(ex) { + if (!useFetch && req.responseHeaders.indexOf("cloudflare") != -1) { + useFetch = true; + return downloadPage(url, post, headers, cb); + } + handleError(ex); + } + }, + onerror: handleError + }; + if (post) { + opts.method = 'POST'; + opts.data = post; + opts.headers = {'Content-Type':'application/x-www-form-urlencoded','Referer':url}; + } + if (headers) { + if (typeof headers == 'function') { + headers = await headers(url + (post ? `#p{${post}}` : ""), xhr, getCookie); + } + opts.headers = headers; + } + + if (useFetch) { + let fetchOption = {method: opts.method || 'GET', headers: opts.headers}; + if (opts.method && opts.method != 'GET') { + fetchOption.body = opts.data || ''; + } + fetch(opts.url, fetchOption).then(response => response.text()).then(data => {opts.onload({responseText: data})}).catch(e => opts.onerror(e)); + } else { + _GM_xmlhttpRequest(opts); + } + } + + function createDoc(text) { + var doc = document.implementation.createHTMLDocument('PicViewerCE'); + doc.documentElement.innerHTML = text; + return doc; + } + + function findNodes(q, doc) { + var nodes = [], + node; + if (!Array.isArray(q)) q = [q]; + for (var i = 0, len = q.length; i < len; i++) { + node = qs(q[i], doc); + if (node && node.length) { + [].forEach.call(node, n => { + nodes.push(n); + }); + } + } + return nodes; + } + + function findFile(n, url) { + pretreatment(n, true); + var path = (n.dataset && n.dataset.src) || n.src || n.href || (n.children && n.children[0] && n.children[0].src); + if (/^video$/i.test(n.nodeName)) { + path = "video:" + path; + } else if (/^audio$/i.test(n.nodeName)) { + path = "audio:" + path; + } else if (path.baseVal) path = path.baseVal; + return path ? path.trim() : false; + } + + function findCaption(n) { + return n.getAttribute('content') || n.getAttribute('title') || n.textContent; + } + + function qs(s, n) { + return n.querySelectorAll(s); + } + + _.load = function(opt) { + xhr = opt.xhr; + var info = caches[opt.url] || storage.getListItem("xhrCache", opt.url); + cacheNum = xhr.cacheNum || 0; + if (info) { + opt.cb(info.iurl, info.iurls, info.cap, info.caps); + return; + } + + handleError = opt.onerror || function() {}; + let postParams = opt.url.match(/#p{(.*)}$/); + if (!opt.post && postParams) { + opt.post = postParams[1]; + opt.url = opt.url.replace(/#p{.*/, ""); + } + + parsePage(opt.url, xhr.query || xhr.q, xhr.caption || xhr.c, opt.post, opt.cb, xhr.headers, xhr.after); + }; + + return _; + }(); + + + // ------------------- run ------------------------- + + function pretreatment(img, fetchImg) { + if (img.removeAttribute) img.removeAttribute("loading"); + if (img.nodeName.toUpperCase() != "IMG" || (!fetchImg && img.src && !img.srcset && !/^data/.test(img.src))) return; + if (img.src && !/(^data|loading|lazy)/.test(img.src)) return; + let src; + tprules.find(function(rule, index, array) { + try { + src = rule.call(img); + if (src) { + return true; + } + } catch(err) { + debug(err); + } + }); + if (src) { + img.src = src; + } + } + + function findPic(img){ + var imgPN=img; + var imgPA,imgPE=[]; + while(imgPN=imgPN.parentNode || imgPN.host){ + if(imgPN.nodeName.toUpperCase()=='A'){ + imgPA=imgPN; + break; + } + } + imgPN=img; + while(imgPN=imgPN.parentNode || imgPN.host){ + if(imgPN.nodeName.toUpperCase()=='BODY'){ + break; + }else{ + imgPE.push(imgPN); + } + } + + var iPASrc=imgPA? imgPA.href : ''; + //base64字符串过长导致正则匹配卡死浏览器 + var base64Img=/^data:/i.test(img.src); + var src, // 大图地址 + srcs, // 备用的大图地址 + type, // 类别 + noActual = false, //没有原图 + imgSrc = img.currentSrc||img.src||img.dataset.lazySrc, // img 节点的 src + xhr, + description; // 图片的注释 + var imgCStyle = unsafeWindow.getComputedStyle(img); + if (/^link$/i.test(img.nodeName)) { + imgCStyle = {height:64, width:64}; + } + if (!/IMG/i.test(img.nodeName) && imgCStyle && imgCStyle.backgroundImage && imgCStyle.backgroundImage != "none") { + let sh = imgCStyle.height, sw = imgCStyle.width; + if (!img.offsetWidth) sw = 10; + if (!img.offsetHeight) sh = 10; + if (imgCStyle.backgroundRepeatX == "repeat") { + sw = 10; + } + if (imgCStyle.backgroundRepeatY == "repeat") { + sh = 10; + } + imgCStyle = {height:sh, width:sw}; + } + var imgCS = { + h: parseFloat(imgCStyle.height) || img.height || img.offsetHeight, + w: parseFloat(imgCStyle.width) || img.width || img.offsetWidth, + }; + if (imgCS.h === 0 && imgCS.w === 0) { + for (let i = 0; i < imgPE.length; i++) { + if (imgPE[i].offsetHeight) { + imgCS = { + h: imgPE[i].offsetHeight, + w: imgPE[i].offsetWidth + }; + break; + } + } + } + var imgAS={//实际尺寸。 + h:img.naturalHeight > 1 ? img.naturalHeight : imgCS.h, + w:img.naturalWidth > 1 ? img.naturalWidth : imgCS.w, + }; + if(!src && matchedRule.rules.length>0){// 通过高级规则获取. + // 排除 + try{ + let newSrc=matchedRule.getImage(img,imgPA,imgPE); + if(newSrc && imgSrc!=newSrc) src=newSrc; + }catch(err){ + throwErrorInfo(err); + } + + if(src) { + if (Array.isArray(src)) { + srcs = src; + src = srcs.shift(); + } + + type = 'rule'; + xhr = matchedRule.xhr; + + if (matchedRule.lazyAttr) { // 由于采用了延迟加载技术,所以图片可能为 loading.gif + imgSrc = img.getAttribute(matchedRule.lazyAttr) || img.src; + } + + if (matchedRule.description) { + let desc = matchedRule.description, attr; + if (Array.isArray(desc) && desc.length === 2) { + attr = desc[1]; + desc = desc[0]; + } + var node = getElementMix(desc, img); + if (node) { + description = attr ? node.getAttribute(attr) : (node.getAttribute('title') || node.textContent); + } + } + } + } + + if(/^IMG$/i.test(img.nodeName) && !src && iPASrc){//链接可能是一张图片... + if(iPASrc!=img.src && imageReg.test(iPASrc)){ + src=iPASrc; + if (/[&\?]url\=/.test(src)) { + src = src.replace(/.*[&\?]url\=(.*?)(&.*|$)/, "$1"); + try { + src = decodeURIComponent(src); + } catch (e) {} + } else if (/[&\?]https?:/.test(src)) { + src = src.replace(/.*[&\?](https?:)/, "$1"); + try { + src = decodeURIComponent(src); + } catch (e) {} + } + } + if(src)type='tpRule'; + } + + if(!src && !base64Img){//遍历通配规则 + tprules.find(function(rule,index,array){ + try{ + src=rule.call(img,imgPA); + if(src){ + return true; + }; + }catch(err){ + throwErrorInfo(err); + }; + }); + if(src)type='tpRule'; + } + + if(!src || src==imgSrc){//本图片是否被缩放. + noActual=true; + if(!(imgAS.w==imgCS.w && imgAS.h==imgCS.h)){//如果不是两者完全相等,那么被缩放了. + src=imgSrc; + type='scale'; + if (imgAS.h < prefs.gallery.scaleSmallSize && imgAS.w < prefs.gallery.scaleSmallSize) { + type='scaleSmall'; + } + }else{ + src=imgSrc; + type='force'; + if (img.nodeName == "A") { + description = img.title || img.alt || img.innerText; + } + } + } + + if(!src)return; + + var ret = { + all: matchedRule.all, + src: src, // 得到的src + srcs: srcs, // 多个 src,失败了会尝试下一个 + type: type, // 通过哪种方式得到的 + imgSrc: imgSrc, // 处理的图片的src + iPASrc: iPASrc, // 图片的第一个父a元素的链接地址 + sizeH:imgAS.h, + sizeW:imgAS.w, + imgCS:imgCS, + imgAS:imgAS, + + noActual:noActual, + xhr: xhr, + description: description || img.title || img.alt || (img.parentNode && img.parentNode.title) || '', + + img: img, // 处理的图片 + imgPA: imgPA, // 图片的第一个父a元素 + }; + return ret; + } + + function getMatchedRule() { + return new MatchedRuleC(); + /*var rule = siteInfo.find(function(site, index, array) { + if (site.enabled != false && site.url && toRE(site.url).test(_URL)) { + return true; + } + }); + + return rule;*/ + } + + function MatchedRuleC(){ + this.init(); + } + + const videoExtensions = new Set(['3gpp', 'm4v', 'mkv', 'mp4', 'ogv', 'webm', 'm3u8']); + const audioExtensions = new Set(['flac', 'm4a', 'mp3', 'oga', 'ogg', 'opus', 'wav']); + + function isVideoLink(url) { + if (url.indexOf('.video') !== -1 || url.indexOf('video:') === 0) + return true; + + url = url.replace(/.gif(\?width=\d*&|\?)format=mp4/, '.mp4?').replace(/\/$/, ""); + if (url.lastIndexOf('?') > 0) + url = url.substring(0, url.lastIndexOf('?')); + const ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(); + + return videoExtensions.has(ext) + || url.indexOf('googlevideo.com/videoplayback') > 0 + || url.indexOf('v.redd.it') > 0; + } + + function isAudioLink(url) { + if (url.indexOf('.audio') !== -1 || url.indexOf('audio:') === 0) + return true; + url = url.replace(/\/$/, ""); + if (url.lastIndexOf('?') > 0) + url = url.substring(0, url.lastIndexOf('?')); + const ext = url.substring(url.lastIndexOf('.') + 1).toLowerCase(); + return audioExtensions.has(ext); + } + + MatchedRuleC.prototype={ + init:function(){ + if (prefs.customRules) { + if (prefs.customRules == `[ +/* + { + name: "Example, can be deleted safely", + url: /^https?:\\/\\/www\\.google\\.com\\/search\\?/, + getImage: function(a) {}, + src: /avatar/i, + r: /\\?.*$/i, + s: '' + } +*/]`) { + prefs.customRules = "[]"; + } + try { + var customRules; + if (prefs.customRules.indexOf("name:") !== -1) { + if (!isunsafe()) { + customRules = unsafeWindow.eval(createScript(prefs.customRules)); + } + } else { + customRules = JSON.parse(prefs.customRules); + } + if (Array.isArray(customRules)) { + customRules.forEach(rule => { + rule.custom = true; + let hasRule = false; + for (let s = 0; s < siteInfo.length; s++) { + if (siteInfo[s].name == rule.name) { + hasRule = true; + for (let si in rule) { + siteInfo[s][si] = rule[si]; + } + break; + } + } + if (!hasRule) siteInfo.unshift(rule); + }) + } + } catch(e) { + console.log("Wrong rule for Picviewer CE+"); + console.log(e); + } + } + + var self = this, r = 0, urlChecked = false; + self.rules=[]; + function searchByTime(){ + setTimeout(()=>{ + let end=r+20; + end=end>siteInfo.length?siteInfo.length:end; + for(;r { + let newSrc = self.replaceByRule(src, site, true); + if (Array.isArray(newSrc)) newSrc = newSrc[0]; + return newSrc && newSrc.length ? newSrc : src; + }; + let reMatch = typeof site.xhr.url === "string" && site.xhr.url.match(/^\/(.*)\/(\w*)$/); + if (reMatch) { + site.xhr.url = toRE(reMatch[1], reMatch[2]); + } else if (Array.isArray(site.xhr.url)) { + let urlRe = toRE(site.xhr.url[0], "i"); + let urlParam = site.xhr.url[1]; + site.xhr.url = (a, p) => { + if (!a || !a.href) return; + if (urlRe.test(a.href)) { + return a.href.replace(urlRe, urlParam);; + } + }; + } + } + if (site.url && !urlChecked) { + urlChecked = true; + if (site.css) { + var style = _GM_addStyle(site.css); + style.id = 'gm-picviewer-site-style'; + } + if (site.lazyAttr) { + self.lazyAttr = site.lazyAttr; + } + if (site.description) { + self.description = site.description; + } + if (site.clickToOpen) { + self.clickToOpen = site.clickToOpen; + } + if (site.ext) { + self.ext = site.ext; + } + if (site.gallery) { + let gallery = site.gallery; + self.gallery = () => { + if (typeof gallery === "string") { + return document.querySelectorAll(gallery); + } else { + return gallery(); + } + }; + } + if (site.video) { + let reMatch = typeof site.video === "string" && site.video.match(/^\/(.*)\/(\w*)$/); + if (reMatch) { + site.video = toRE(reMatch[1], reMatch[2]); + } + self.video = site.video; + } + if (site.audio) { + let reMatch = typeof site.audio === "string" && site.audio.match(/^\/(.*)\/(\w*)$/); + if (reMatch) { + site.audio = toRE(reMatch[1], reMatch[2]); + } + self.audio = site.audio; + } + if (site.getExtSrc) { + self.getExtSrc = site.getExtSrc; + } + if (site.mute) { + self.mute = true; + } + } + self.rules.push(site); + } + } + if(end { + if (a.custom && !b.custom) return -1; + if (!a.custom && b.custom) return 1; + if (a.url && !b.url) return -1; + if (!a.url && b.url) return 1; + return 0; + }); + } + },1); + } + setTimeout(() => { + if (unsafeWindow.pvcepRules && Array.isArray(unsafeWindow.pvcepRules)) { + unsafeWindow.pvcepRules.forEach(rule => { + rule.custom = true; + let hasRule = false; + for (let s = 0; s < siteInfo.length; s++) { + if (siteInfo[s].name == rule.name) { + hasRule = true; + for (let si in rule) { + siteInfo[s][si] = rule[si]; + } + break; + } + } + if (!hasRule) siteInfo.unshift(rule); + }) + } + searchByTime(); + }, 1); + }, + replace:function(str, r, s){ + var results=[],rt; + let reMatch = typeof r === "string" && r.match(/^\/(.*)\/(\w+)$/); + if (reMatch) { + r = toRE(reMatch[1], reMatch[2]); + } + if(Array.isArray(s)){ + s.forEach(_s=>{ + rt=str.replace(r, _s); + if(rt && rt!=str)results.push(rt); + }); + }else{ + rt=str.replace(r, s); + if(rt && rt!=str)return rt; + } + return results; + }, + replaceByRule: function(src, rule, check) { + if (check) { + if (!rule.r || /^data:/i.test(src)) return src; + } + let newSrc; + if (Array.isArray(rule.r)) {//r最多一层 + for (var j = 0; j < rule.r.length; j++) { + var _r = rule.r[j]; + if (_r) { + if (Array.isArray(rule.s)) {//s对上r最多两层 + var _s = rule.s[j]; + newSrc = this.replace(src, _r, _s); + } else { + newSrc = this.replace(src, _r, rule.s); + } + if (newSrc && newSrc.length && newSrc !== src) { + break; + } + } + } + } else { + newSrc = this.replace(src, rule.r, rule.s); + } + return newSrc && newSrc.length ? newSrc : src; + }, + getMode: function(src) { + if (!src || !src.length) return ""; + if (this.video && this.video.test(src)) { + return "video"; + } + if (this.audio && this.audio.test(src)) { + return "audio"; + } + if (isVideoLink(src)) { + return "video"; + } + if (isAudioLink(src)) { + return "audio"; + } + return ""; + }, + getXhrLink: function(a, p) { + var newSrc, rule; + this.xhr = null; + this.xhrLink = false; + for (var i = 0; i < this.rules.length; i++) { + rule = this.rules[i]; + if (rule.src) continue; + if (rule.xhr) { + if (rule.xhr.url) { + if (rule.xhr.url.test) { + if (a && rule.xhr.url.test(a.href)) { + newSrc = a.href; + } + } else if (typeof rule.xhr.url === 'string') { + try { + if (a && a.matches(rule.xhr.url)) { + newSrc = a.href; + } + } catch(e) { + debug(e); + } + } else { + newSrc = rule.xhr.url.call(a, a, p, rule.xhr); + } + if (newSrc) { + this.xhrLink = true; + this.xhr = rule.xhr; + return newSrc; + } + } else if (a) { + newSrc = a.href; + if (newSrc) { + this.xhrLink = true; + this.xhr = rule.xhr; + return newSrc; + } + } + } + } + }, + getImage: function(img, a, p, target) { + var newSrc, rule; + var base64Img = /^data:/i.test(img.src); + this.all = null; + this.xhr = null; + for (var i = 0; i < this.rules.length; i++) { + rule = this.rules[i]; + if (rule.src && !toRE(rule.src).test(img.src)) continue; + if (rule.exclude && toRE(rule.exclude).test(img.src)) continue; + if (rule.xhr) { + if (rule.xhr.url) { + if (rule.xhr.url.test) { + if (a && rule.xhr.url.test(a.href)) { + newSrc = a.href; + } + } else if (typeof rule.xhr.url === 'string') { + try { + if (a && a.matches(rule.xhr.url)) { + newSrc = a.href; + } + } catch(e) { + debug(e); + } + } else { + newSrc = rule.xhr.url.call(target || img, a, p, rule.xhr); + } + if (newSrc) { + this.xhr = rule.xhr; + return newSrc; + } else { + this.xhr = null; + } + } else if (a) { + newSrc = a.href; + } + } + if (base64Img && (!rule.url || !rule.getImage)) continue; + if (newSrc) { + this.xhr = rule.xhr; + return newSrc; + } + if (rule.getImage) { + newSrc = rule.getImage.call(target || img, a, p, rule); + if (newSrc && newSrc.all) { + this.all = newSrc.all; + newSrc = this.all[0]; + } + } else newSrc = null; + if (!base64Img && rule.r && img.src && !Array.isArray(newSrc)) { + if (!newSrc) newSrc = img.src || img.currentSrc; + newSrc = this.replaceByRule(newSrc, rule); + } + if (newSrc && newSrc.length > 0 && newSrc != (img.src || img.currentSrc)) { + debug(rule); + break; + } else newSrc = null; + } + if (newSrc && newSrc.length == 0) newSrc = null; + return newSrc; + } + }; + + var isFrame=window!=window.parent; + var topWindowValid; + var frameSentData; + var frameSentSuccessData; + function handleMessage(e){ + var data=e.data; + if( !data || !data.messageID || data.messageID != messageID )return; + var source=e.source,command,cusEvent; + if(typeof source=='undefined' || source!==window){ + if(!isFrame){ + command=data.command; + switch(command){ + case 'open':{ + if (data.buttonType === 'download') { + downloadImg(data.src, document.title, prefs.saveName); + return; + } + var img=document.createElement('img'); + img.src=data.src; + + imgReady(img,{ + ready:function(){ + LoadingAnimC.prototype.open.call({ + img:img, + data:data.data, + buttonType:data.buttonType, + from:data.from, + }); + }, + }); + }break; + case 'navigateToImg':{ + cusEvent=document.createEvent('CustomEvent'); + cusEvent.initCustomEvent('pv-navigateToImg',false,false,data.exist); + document.dispatchEvent(cusEvent); + }break; + case 'topWindowValid':{ + if(data.from) + window.postMessage({ + messageID:messageID, + command:'topWindowValid_frame', + valid:getBody(document).nodeName.toUpperCase()!='FRAMESET', + to:data.from, + },'*'); + }break; + }; + + }else{ + command=data.command; + switch(command){ + case 'navigateToImg':{ + + if(!frameSentData.unique){ + var unique=GalleryC.prototype.unique(frameSentData); + frameSentData=unique.data; + frameSentData.unique=true; + }; + var targetImg=frameSentData[data.index].img; + var exist=(document.documentElement.contains(targetImg) && unsafeWindow.getComputedStyle(targetImg).display!='none'); + + if(exist){ + if(gallery && gallery.shown){ + gallery.minimize(); + }; + setTimeout(function(){ + GalleryC.prototype.navigateToImg(targetImg); + flashEle(targetImg); + },0); + }; + if (data.from) { + window.postMessage({ + messageID:messageID, + command:'navigateToImg', + exist:exist, + to:data.from, + index:data.index + },'*'); + } + }break; + case 'sendFail':{ + frameSentData=frameSentSuccessData; + }break; + case 'topWindowValid_frame':{ + cusEvent=document.createEvent('CustomEvent'); + cusEvent.initCustomEvent('pv-topWindowValid',false,false,data.valid); + document.dispatchEvent(cusEvent); + }break; + }; + }; + + }; + } + + //页面脚本用来转发消息 + //原因chrome的contentscript无法访问非自己外的别的窗口。都会返回undefined,自然也无法向其他的窗口发送信息,这里用pagescript做个中间代理 + //通讯逻辑..A页面的contentscript发送到A页面的pagescript,pagescript转交给B页面的contentscript + var messageID='pv-0.5106795670312598'; + + var _isunsafe = null; + function isunsafe(){ + if (_isunsafe === null) { + try { + _isunsafe = unsafeWindow.eval(createScript("false")); + } catch (e) { + console.debug("unsafe"); + _isunsafe = true; + } + } + return _isunsafe; + } + function addPageScript() { + + if(isunsafe())return; + var pageScript=document.createElement('script'); + pageScript.id = 'picviewer-page-script'; + + var pageScriptText=function(messageID){ + var frameID=Math.random(); + var frames={ + top:window.top, + }; + + window.addEventListener('message',function(e){ + var data=e.data; + if( !data || !data.messageID || data.messageID != messageID )return;//通信ID认证 + var source=e.source; + if(source===window){//来自contentscript,发送出去,或者干嘛。 + if(data.to){ + data.from=frameID; + frames[data.to].postMessage(data,'*'); + }else{ + switch(data.command){ + case 'getIframeObject':{ + var frameWindow=frames[data.windowId]; + var iframes=document.getElementsByTagName('iframe'); + var iframe; + var targetIframe; + for(var i=iframes.length-1 ; i>=0 ; i--){ + iframe=iframes[i]; + if(iframe.contentWindow===frameWindow){ + targetIframe=iframe; + break; + }; + }; + var cusEvent=document.createEvent('CustomEvent'); + cusEvent.initCustomEvent('pv-getIframeObject',false,false,targetIframe); + document.dispatchEvent(cusEvent); + }break; + }; + }; + + }else{//来自别的窗口的,contentscript可以直接接收,这里保存下来自的窗口的引用 + frames[data.from]=source; + }; + },true) + }; + + pageScript.textContent=createScript('(' + pageScriptText.toString() + ')('+ JSON.stringify(messageID) +')'); + if(document.head)document.head.appendChild(pageScript); + } + + function clickToOpen(data){ + var preventDefault = matchedRule.clickToOpen.preventDefault; + var button = matchedRule.clickToOpen.button || 0; + var alt = !!matchedRule.clickToOpen.alt; + var ctrl = !!matchedRule.clickToOpen.ctrl; + var shift = !!matchedRule.clickToOpen.shift; + var meta = !!matchedRule.clickToOpen.meta; + + function mouseout(){ + document.removeEventListener('mouseout', mouseout, true); + document.removeEventListener(button == 2 ? 'contextmenu' : 'mousedown', click, true); + if(data.imgPA && preventDefault){ + data.imgPA.removeEventListener('click', clickA, true); + }; + }; + + function checkLimit(e){ + if(e.button!=button || + e.altKey!=alt || + e.ctrlKey!=ctrl || + e.shiftKey!=shift || + e.metaKey!=meta){ + return false; + } + return true; + } + + function click(e){ + if(!checkLimit(e))return; + FloatBarC.prototype.open.call({ + data:data, + },e,matchedRule.clickToOpen.type); + if(preventDefault){ + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + function clickA(e){//阻止a的默认行为 + if(!checkLimit(e))return; + e.preventDefault(); + }; + + document.addEventListener(button == 2 ? 'contextmenu' : 'mousedown', click, true); + + if(data.imgPA && preventDefault){ + data.imgPA.addEventListener('click', clickA, true); + }; + + setTimeout(function(){//稍微延时。错开由于css hover样式发生的out; + document.addEventListener('mouseout', mouseout, true); + },100); + + return function(){ + mouseout(); + }; + } + + var canclePreCTO,uniqueImgWin,centerInterval,globalFuncEnabled=false,isConfigOpen=false; + function checkGlobalKeydown(e){ + return(!((!e.ctrlKey && e.key !== 'Control' && prefs.floatBar.globalkeys.ctrl)|| + (!e.altKey && e.key !== 'Alt' && prefs.floatBar.globalkeys.alt)|| + (!e.shiftKey && e.key !== 'Shift' && prefs.floatBar.globalkeys.shift)|| + (!e.metaKey && e.key !== 'Meta' && prefs.floatBar.globalkeys.command)|| + (!prefs.floatBar.globalkeys.ctrl && !prefs.floatBar.globalkeys.alt && !prefs.floatBar.globalkeys.shift && !prefs.floatBar.globalkeys.command))); + } + + function checkPreview(e){ + if (isConfigOpen) return false; + let selStr; + try { + selStr = !selectionClientRect && selectionStr; + }catch(e){} + if (selStr && selStr != "\n") return false; + let keyActive=(prefs.floatBar.globalkeys.type == "hold" && checkGlobalKeydown(e)) || + (prefs.floatBar.globalkeys.type == "press" && globalFuncEnabled); + return prefs.floatBar.globalkeys.invertInitShow?!keyActive:keyActive; + } + + var untilMoveTimer, moveHandler, uniqueImgWinInitX, uniqueImgWinInitY; + function waitUntilMove(target, callback) { + if (moveHandler) document.removeEventListener('mousemove', moveHandler, true); + if (untilMoveTimer) clearTimeout(untilMoveTimer); + + moveHandler = e => { + uniqueImgWinInitX = e.clientX; + uniqueImgWinInitY = e.clientY; + if (target != e.target) { + let preRect = target.getBoundingClientRect(); + let nextRect = e.target.getBoundingClientRect(); + if (preRect && nextRect && (preRect.left > nextRect.left || preRect.right < nextRect.right || preRect.top > nextRect.top || preRect.bottom < nextRect.bottom)) { + document.removeEventListener('mousemove', moveHandler, true); + clearTimeout(untilMoveTimer); + } + } + } + document.addEventListener('mousemove', moveHandler, true); + untilMoveTimer = setTimeout(() => { + document.removeEventListener('mousemove', moveHandler, true); + callback(); + }, prefs.floatBar.showDelay || 0) + } + + function checkFloatBar(_target, type, canPreview, clientX, clientY, altKey) { + let target = _target; + if (!target || target.id == "pv-float-bar-container" || + (target.parentNode && (target.parentNode.id == "icons" || target.parentNode.className == "search-jumper-btn")) || + (target.className && + (/^pv\-/.test(target.className) || + target.className == "whx-a" || + target.className == "whx-a-node" || + target.className == "search-jumper-btn" || + target.classList.contains("pv-icon") || + target.classList.contains("ks-imagezoom-lens")))) { + return; + } + if (target.nodeName.toUpperCase() == "PICTURE"){ + target = target.querySelector("img"); + } + if (type == "mousemove") { + if ((uniqueImgWin && !uniqueImgWin.removed && !uniqueImgWin.previewed)) { + uniqueImgWin.followPos(clientX, clientY); + if (!canPreview) { + uniqueImgWin.remove(); + } + return; + } else if (target.nodeName.toUpperCase() != 'IMG' || !canPreview) { + return; + } + } + + // 扩展模式,检查前面一个是否为 img + if (target.nodeName.toUpperCase() != 'IMG' && matchedRule.rules.length > 0 && matchedRule.ext) { + var _type = typeof matchedRule.ext; + if (_type == 'string') { + switch (matchedRule.ext) { + case 'previous': + target = target.previousElementSibling || target; + break; + case 'next': + target = target.nextElementSibling || target; + break; + case 'previous-2': + target = (target.previousElementSibling && + target.previousElementSibling.previousElementSibling) || target; + break; + } + } else if (_type == 'function') { + try { + target = matchedRule.ext(target) || target; + } catch(ex) { + throwErrorInfo(ex); + } + + if (!target) return; + } + } + let bgReg = /.*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; + let bgRegLong = /^\s*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; + let result, targetBg, hasBg = node => { + if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document"){ + return false; + } + if (node.clientWidth <= prefs.floatBar.minSizeLimit.w || node.clientHeight <= prefs.floatBar.minSizeLimit.h) { + return false; + } + targetBg = ""; + let nodeStyle = unsafeWindow.getComputedStyle(node); + + let bg = nodeStyle.backgroundRepeatX != "repeat" && nodeStyle.backgroundRepeatY != "repeat" && nodeStyle.backgroundImage; + if (bg && bg !== "none") { + targetBg = nodeStyle.backgroundImage.match(bg.length > 500 ? bgRegLong : bgReg); + } + if (!targetBg) { + nodeStyle = unsafeWindow.getComputedStyle(node, "::before"); + bg = nodeStyle.backgroundRepeatX != "repeat" && nodeStyle.backgroundRepeatY != "repeat" && nodeStyle.backgroundImage; + if (bg && bg !== "none") { + targetBg = nodeStyle.backgroundImage.match(bg.length > 500 ? bgRegLong : bgReg); + } + } + if (!targetBg) { + nodeStyle = unsafeWindow.getComputedStyle(node, "::after"); + bg = nodeStyle.backgroundRepeatX != "repeat" && nodeStyle.backgroundRepeatY != "repeat" && nodeStyle.backgroundImage; + if (bg && bg !== "none") { + targetBg = nodeStyle.backgroundImage.match(bg.length > 500 ? bgRegLong : bgReg); + } + } + if (targetBg) { + targetBg = targetBg[1].replace(/\\"/g, '"'); + } + return targetBg; + }; + if (target.nodeName.toUpperCase() != 'IMG') { + let nsrc, imgPN, imgPA, imgPE = []; + try { + if (matchedRule.getExtSrc) { + nsrc = matchedRule.getExtSrc.call(target); + } + if (!nsrc && target.href) { + imgPN = target; + let i = 0; + while (imgPN = imgPN.parentNode || imgPN.host) { + if (i++ > 5 || imgPN.nodeName.toUpperCase() == 'BODY') { + break; + } else { + imgPE.push(imgPN); + } + } + nsrc = matchedRule.getXhrLink(target, imgPE); + } + } catch(ex) { + throwErrorInfo(ex); + } + if (nsrc) { + let all; + if (nsrc && nsrc.all) { + all = nsrc.all; + nsrc = all[0]; + } + let src = nsrc, imgSrc = prefs.floatBar.listenBg && hasBg(target) ? targetBg : nsrc; + if (Array.isArray(nsrc) && nsrc.length == 2) { + imgSrc = nsrc[0]; + src = nsrc[1]; + } + if (!matchedRule.xhrLink) { + imgPN = target; + imgPE = []; + do { + if (imgPN.nodeName.toUpperCase() == 'A') { + imgPA = imgPN; + break; + } + } while (imgPN = imgPN.parentNode || imgPN.host); + imgPN = target; + while (imgPN = imgPN.parentNode || imgPN.host) { + if (imgPN.nodeName.toUpperCase() == 'BODY') { + break; + } else { + imgPE.push(imgPN); + } + } + src = matchedRule.getImage(target, imgPA, imgPE) || src; + } + let noActual = src === imgSrc; + result = { + all: all || matchedRule.all, + src: src, + type: matchedRule.xhrLink && noActual ? "link" : "rule", + imgSrc: imgSrc, + noActual: noActual, + img: target, + xhr: matchedRule.xhr + }; + } + } + var checkUniqueImgWin = function() { + let invert = !canPreview && prefs.floatBar.globalkeys.invertInitShow && prefs.floatBar.globalkeys.type == "hold"; + if (canPreview || invert) { + let forceShow = (() => { + if (result.type != "link" && result.type != "rule" && result.src == result.imgSrc) { + if (result.imgAS.w < result.imgCS.w * 1.3 && result.imgAS.h < result.imgCS.h * 1.3) { + if (result.img && result.img.childElementCount) { + if (result.type == "force") return false; + if (prefs.floatBar.globalkeys.invertInitShow) return false; + } + var wSize = getWindowSize(); + if (prefs.floatBar.globalkeys.invertInitShow && result.imgAS.w <= wSize.w && result.imgAS.h <= wSize.h) return false; + } + } + return true; + })(); + if (forceShow) { + if (invert) return false; + } else { + if (!invert) return false; + } + uniqueImgWinInitX = clientX; + uniqueImgWinInitY = clientY; + if (uniqueImgWin && !uniqueImgWin.removed) { + if (uniqueImgWin.src == result.src) return true; + uniqueImgWin.remove(); + } + waitUntilMove(_target, () => { + new LoadingAnimC(result, 'popup', prefs.waitImgLoad, prefs.framesPicOpenInTopWindow); + }); + return true; + } else { + return false; + } + }; + if (!result) { + if (target.nodeName.toUpperCase() != 'IMG' && target.dataset.role == "img") { + let img = target.parentNode.querySelector('img'); + if (img) target = img; + } + if (target.nodeName.toUpperCase() == 'IMAGE') { + let src = target.href && target.href.baseVal; + if (src) { + result = { + src: src, + type: "rule", + imgSrc: src, + noActual: true, + img: target.parentNode + }; + } + } else if (target.nodeName.toUpperCase() != 'IMG') { + if (selectionClientRect && + clientX > selectionClientRect.left && + clientX < selectionClientRect.left + selectionClientRect.width && + clientY > selectionClientRect.top && + clientY < selectionClientRect.top + selectionClientRect.height) { + result = { + src: selectionStr, + type: "link", + imgSrc: selectionStr, + noActual:true, + img: target + }; + checkUniqueImgWin(); + + if (!floatBar) { + floatBar = new FloatBarC(); + } + floatBar.start(result); + + return; + } + let found = false; + if (target.nodeName.toUpperCase() == "AREA") target = target.parentNode; + var broEle, broImg; + if (target.nodeName.toUpperCase() != 'A' && target.parentNode && target.parentNode.style && !/flex|grid|table/.test(getComputedStyle(target.parentNode).display)) { + broEle = target.previousElementSibling; + while (broEle) { + if (broEle.offsetWidth && broEle.offsetWidth > target.offsetWidth>>1) { + if (broEle.nodeName == "IMG") broImg = broEle; + else if (broEle.nodeName == "PICTURE") broImg = broEle.querySelector("img"); + } + if (getComputedStyle(broEle).position !== "absolute") break; + broEle = broEle.previousElementSibling; + } + if (broEle == target) broEle = null; + else if (!broEle) { + broEle = target.nextElementSibling; + while (broEle) { + if (broEle.offsetWidth && broEle.offsetWidth > target.offsetWidth>>1) { + if (broEle.nodeName == "IMG") broImg = broEle; + else if (broEle.nodeName == "PICTURE") broImg = broEle.querySelector("img"); + } + if (getComputedStyle(broEle).position == "absolute") break; + broEle = broEle.nextElementSibling; + } + if (broEle == target) broEle = null; + } + } + if (target.children.length == 1 && !(target.textContent && target.textContent.trim()) && target.children[0].nodeName == "IMG") { + target = target.children[0]; + found = true; + } else if (prefs.floatBar.listenBg && hasBg(target)) { + let src = targetBg, nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + } else if (broImg) { + target = broImg; + found = true; + } else if (target.nodeName.toUpperCase() == 'CANVAS') { + let src = target.src || target.dataset.src; + if (src) { + let nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + } + } else if (prefs.floatBar.listenBg && broEle && hasBg(broEle)) { + let src = targetBg, nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + } else if (target.parentNode) { + let imgs; + if (target.nodeName == 'A') { + imgs = target.querySelectorAll('img'); + } + if (imgs && imgs.length == 1) { + target = imgs[0]; + found = true; + } else if (target.parentNode.nodeName.toUpperCase() == 'IMG') { + target = target.parentNode; + found = true; + } + } + if (!found) { + let checkEle = target; + while(checkEle && !(checkEle.textContent && checkEle.textContent.trim()) && checkEle.children.length === 1) { + checkEle = checkEle.children[0]; + if (checkEle.nodeName === "IMG") { + target = checkEle; + found = true; + break; + } else if (checkEle.nodeName === "PICTURE") { + target = checkEle.querySelector("img"); + found = true; + break; + } else if (prefs.floatBar.listenBg && hasBg(checkEle)) { + let src = targetBg, nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: checkEle + }; + found = true; + break; + } + } + } + if (!found && target.children && target.children[0] && target.children[0].nodeName.toUpperCase() == 'IMG') { + let img = target.children[0]; + while (img.nextElementSibling && img.nextElementSibling.nodeName.toUpperCase() == 'IMG') { + img = img.nextElementSibling; + } + let rect = img.getBoundingClientRect(); + + if (clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right) { + target = img; + found = true; + } + } + if (!found && document.elementsFromPoint) { + let elements = document.elementsFromPoint(clientX, clientY); + let checkLen = Math.min(elements.length, 10); + for (let i = 0; i < checkLen; i++) { + let ele = elements[i]; + if (!ele) continue; + if (/^img$/i.test(ele.nodeName)) { + target = ele; + result = null; + found = true; + break; + } else if (prefs.floatBar.listenBg && hasBg(ele)) { + target = ele; + let src = targetBg, nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + break; + } else if (target.nodeName.toUpperCase() != 'A' && ele.nodeName.toUpperCase() == 'CANVAS') { + let src = ele.src || ele.dataset.src; + if (src) { + target = ele; + let nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + break; + } + } + } + } + if (!found && target.shadowRoot) { + let imgs = target.shadowRoot.querySelectorAll('img'); + if (imgs.length === 1) target = imgs[0]; + } + if (!found && prefs.floatBar.listenBg && hasBg(target.parentNode)) { + target = target.parentNode; + let src = targetBg, nsrc = src, noActual = true, type = "scale"; + result = { + src: nsrc, + type: type, + imgSrc: src, + noActual:noActual, + img: target + }; + found = true; + } + if (result && !/^data:/i.test(result.src)) { + if (matchedRule.rules.length > 0 && target.nodeName.toUpperCase() != 'IMG') { + let src = result.src, img = {src: src}, type, imgSrc = src; + try { + let imgPN=target; + let imgPA,imgPE=[]; + while(imgPN=imgPN.parentNode || imgPN.host){ + if(imgPN.nodeName.toUpperCase()=='A'){ + imgPA=imgPN; + break; + } + } + imgPN=target; + while(imgPN=imgPN.parentNode || imgPN.host){ + if(imgPN.nodeName.toUpperCase()=='BODY'){ + break; + }else{ + imgPE.push(imgPN); + } + } + let newSrc = matchedRule.getImage(img, imgPA, imgPE, target); + if (newSrc && imgSrc != newSrc) { + let srcs, description; + src = newSrc; + if (Array.isArray(src)) { + srcs = src; + src = srcs.shift(); + } + type = 'rule'; + + if (matchedRule.description) { + let desc = matchedRule.description, attr; + if (Array.isArray(desc) && desc.length === 2) { + attr = desc[1]; + desc = desc[0]; + } + var node = getElementMix(desc, img); + if (node) { + description = attr ? node.getAttribute(attr) : (node.getAttribute('title') || node.textContent); + } + } + result.all = matchedRule.all; + result.src = src; + result.type = type; + result.noActual = false; + result.xhr = matchedRule.xhr; + result.description = description || ''; + } + } catch(err) {} + if (result.type != "rule") { + tprules.find(function(rule, index, array) { + try { + src = rule.call(img); + if (src) { + return true; + }; + } catch(err) { + } + }); + if (src && src != imgSrc) { + result.src = src; + result.type = "tpRule"; + result.noActual = false; + } + } + } + } + } + } + + if (!result && target.nodeName.toUpperCase() != 'IMG') { + let i = 0; + while(target) { + if (i++ > 5) { + target = null; + break; + } + if ((target.nodeName == 'A' || target.nodeName == 'a') && imageReg.test(target.href)) { + break; + } + target = target.parentNode; + } + if (target) { + let src = target.href; + if (/[&\?]url\=/.test(src)) { + src = src.replace(/.*[&\?]url\=(.*?)(&.*|$)/, "$1"); + try { + src = decodeURIComponent(src); + } catch (e) {} + } else if (/[&\?]https?:/.test(src)) { + src = src.replace(/.*[&\?](https?:)/, "$1"); + try { + src = decodeURIComponent(src); + } catch (e) {} + } + result = { + src: src, + type: "link", + imgSrc: src, + noActual:true, + img: target, + description: target.title || target.innerText + }; + checkUniqueImgWin(); + + if (!floatBar) { + floatBar = new FloatBarC(); + } + floatBar.start(result); + } + return; + } + + let sizeHide = false; + if (!result) { + pretreatment(target) + result = findPic(target); + if (!result) return; + } + + if (result) { + if (!result.imgAS && !result.imgCS) { + let sizeInfo = { + w: result.img.offsetWidth || result.img.scrollWidth || target.offsetWidth || target.scrollWidth, + h: result.img.offsetHeight || result.img.scrollHeight || target.offsetHeight || target.scrollHeight + } + result.imgAS = sizeInfo; + result.imgCS = sizeInfo; + } + if (prefs.floatBar.showWithRules && (result.type == "rule" || result.type == "tpRule")) { + } else if (!(result.imgAS.w == result.imgCS.w && result.imgAS.h == result.imgCS.h)) {//如果不是两者完全相等,那么被缩放了. + if (prefs.floatBar.sizeLimitOr) { + if (result.imgCS.h <= prefs.floatBar.minSizeLimit.h && result.imgCS.w <= prefs.floatBar.minSizeLimit.w) {//最小限定判断. + sizeHide = true; + } + }else{ + if (result.imgCS.h <= prefs.floatBar.minSizeLimit.h || result.imgCS.w <= prefs.floatBar.minSizeLimit.w) {//最小限定判断. + sizeHide = true; + } + } + } else { + if (prefs.floatBar.sizeLimitOr) { + if (result.imgCS.w <= prefs.floatBar.forceShow.size.w && result.imgCS.h <= prefs.floatBar.forceShow.size.h) { + sizeHide = true; + } + } else { + if (result.imgCS.w <= prefs.floatBar.forceShow.size.w || result.imgCS.h <= prefs.floatBar.forceShow.size.h) { + sizeHide = true; + } + } + } + debug(result); + if (!result.noActual) { + if (!result.srcs) { + result.srcs = [result.imgSrc]; + } else { + if (result.imgSrc && result.srcs.join(" ").indexOf(result.imgSrc) == -1) { + result.srcs.push(result.imgSrc); + } + } + } + if (!floatBar) { + floatBar = new FloatBarC(); + } + if (result.type == 'rule' && matchedRule.clickToOpen && matchedRule.clickToOpen.enabled) { + if (canclePreCTO) {//取消上次的,防止一次点击打开多张图片 + canclePreCTO(); + } + canclePreCTO = clickToOpen(result); + } + + let hide = sizeHide || (prefs.floatBar.position == "hide" ? true : altKey); + result.hide = hide; + let canShow = floatBar.start(result); + if (!checkUniqueImgWin() && canShow) { + if (floatBar.floatBar.style.opacity == 0) { + floatBar.floatBar.style.opacity = ""; + } + floatBar.floatBar.style.display = "initial"; + } + } + } + + var checkFloatBarTimer, initMouse = false; + function globalMouseoverHandler(e) { + if (galleryMode) return;//库模式全屏中...... + if (e.target == ImgWindowC.overlayer) return; + let canPreview = checkPreview(e); + if (e.type == "mousemove") { + if (!initMouse) { + initMouse = true; + return; + } + if ((uniqueImgWin && !uniqueImgWin.removed && !uniqueImgWin.previewed)) { + if (canPreview || prefs.floatBar.globalkeys.invertInitShow) { + uniqueImgWinInitX = e.clientX; + uniqueImgWinInitY = e.clientY; + uniqueImgWin.followPos(uniqueImgWinInitX, uniqueImgWinInitY); + } else { + uniqueImgWin.remove(); + } + return; + } else { + if (!canPreview) return; + let target = e.target; + if (target.nodeName == "PICTURE"){ + target = target.lastElementChild || target; + } + if (target.nodeName != 'IMG') return; + } + } + if (!initMouse) return; + clearTimeout(checkFloatBarTimer); + checkFloatBarTimer = setTimeout(function() { + if (!e || !e.target || !e.target.parentNode) return; + if (gallery && gallery.shown) return; + checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey); + }, 50); + } + + var selectionClientRect, selectionStr, selectionChanging = false; + document.addEventListener('selectionchange', e => { + if (selectionChanging) return; + selectionChanging = true; + setTimeout(() => { + selectionChanging = false; + const selection = window.getSelection(); + selectionStr = selection.toString(); + if (selectionStr && selectionStr.length < 500) selectionStr = selectionStr.trim().replace(/^t?t?p?s?:/, "https:"); + else selectionStr = ''; + if (selectionStr && imageReg.test(selectionStr)) { + const range = selection.getRangeAt(0); + selectionClientRect = range.getBoundingClientRect(); + } else { + selectionClientRect = null; + } + }, 300); + }); + + document.addEventListener('visibilitychange', e => { + initMouse = false; + }); + + var curLoadingMedia; + document.addEventListener('securitypolicyviolation', (e) => { + if (!e.violatedDirective.includes('media-src')) { + return; + } + if (curLoadingMedia && curLoadingMedia.src == e.blockedURI) { + _GM_xmlhttpRequest({ + method: 'GET', + url: curLoadingMedia.src, + responseType: 'blob', + onload: function(response) { + const blobUrl = URL.createObjectURL(response.response); + + curLoadingMedia.src = blobUrl; + curLoadingMedia.load(); + curLoadingMedia.play().catch(err => console.warn('[CSP Fixer] Autoplay after fix was blocked.', err)); + + const releaseBlob = () => URL.revokeObjectURL(blobUrl); + curLoadingMedia.addEventListener('ended', releaseBlob); + window.addEventListener('beforeunload', releaseBlob); + } + }); + } + }); + function createVideo() { + let media = document.createElement('video'); + media.addEventListener('error', e => { + if (/^blob:/.test(media.src)) return; + _GM_xmlhttpRequest({ + method: 'GET', + url: media.src, + responseType: 'blob', + onload: function(response) { + const blobUrl = URL.createObjectURL(response.response); + + media.src = blobUrl; + media.load(); + media.play().catch(err => console.warn('[CSP Fixer] Autoplay after fix was blocked.', err)); + + const releaseBlob = () => URL.revokeObjectURL(blobUrl); + media.addEventListener('ended', releaseBlob); + window.addEventListener('beforeunload', releaseBlob); + curLoadingMedia = null; + } + }); + }); + return media; + } + + async function input(sel, v) { + await new Promise((resolve) => { + let checkInv = setInterval(() => { + let input = document.querySelector(sel); + if (input) { + input.focus(); + input.scrollIntoView(); + let lastValue = input.value; + if (input.nodeName == "INPUT") { + var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set; + nativeInputValueSetter.call(input, v); + } else { + var nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; + nativeTextareaValueSetter.call(input, v); + } + let event = new Event('focus', { bubbles: true }); + input.dispatchEvent(event); + event = new Event('input', { bubbles: true }); + let tracker = input._valueTracker; + if (tracker) { + tracker.setValue(lastValue); + } + input.dispatchEvent(event); + event = new Event('change', { bubbles: true }); + input.dispatchEvent(event); + clearInterval(checkInv); + resolve(); + } + }, 100); + }); + } + + function emuClick(btn){ + if(!PointerEvent)return btn.click(); + let eventParam={ + isTrusted: true, + altKey: false, + azimuthAngle: 0, + bubbles: true, + button: 0, + buttons: 0, + clientX: 1, + clientY: 1, + cancelBubble: false, + cancelable: true, + composed: true, + ctrlKey: false, + defaultPrevented: false, + detail: 1, + eventPhase: 2, + fromElement: null, + height: 1, + isPrimary: false, + metaKey: false, + pointerId: 1, + pointerType: "mouse", + pressure: 0, + relatedTarget: null, + returnValue: true, + shiftKey: false, + toElement: null, + twist: 0, + which: 1 + }; + btn.focus(); + var mouseclick = new PointerEvent("mouseover",eventParam); + btn.dispatchEvent(mouseclick); + mouseclick = new PointerEvent("pointerover",eventParam); + btn.dispatchEvent(mouseclick); + mouseclick = new PointerEvent("mousedown",eventParam); + btn.dispatchEvent(mouseclick); + mouseclick = new PointerEvent("pointerdown",eventParam); + btn.dispatchEvent(mouseclick); + mouseclick = new PointerEvent("mouseup",eventParam); + btn.dispatchEvent(mouseclick); + mouseclick = new PointerEvent("pointerup",eventParam); + btn.dispatchEvent(mouseclick); + btn.click(); + } + + async function clickEle(sel, failAction) { + await new Promise((resolve) => { + let checkInv = setInterval(() => { + let ele = document.querySelector(sel); + if (ele) { + clearInterval(checkInv); + emuClick(ele); + resolve(); + }else if(failAction) { + failAction(); + } + }, 100); + }); + } + + async function sleep(time) { + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, time); + }) + } + + function isKeyDownEffectiveTarget(target) { + var localName = (target.shadowRoot ? (target.shadowRoot.activeElement || target) : target).localName; + + // 确保光标不是定位在文字输入框或选择框 + if (localName == 'textarea' || localName == 'input' || localName == 'select'){ + return false; + } + + // 视频播放器 + if (localName == 'object' || localName == 'embed'){ + return false; + } + + // 百度贴吧回复输入的问题 + if (target.getAttribute('contenteditable') == 'true'){ + return false; + } + + return true; + } + + async function openGallery(){ + if(!gallery){ + gallery=new GalleryC(); + gallery.data=[]; + } + var allData=await gallery.getAllValidImgs(); + if(allData.length<1)return; + gallery.data=allData; + gallery.load(gallery.data); + return gallery; + } + + function getActiveElement(root) { + const activeEl = root.activeElement; + if (!activeEl) { + return null; + } + if (activeEl.shadowRoot) { + return getActiveElement(activeEl.shadowRoot); + } else { + return activeEl; + } + } + + function inputActive(doc) { + let activeEl = getActiveElement(doc); + if (activeEl && + ((/INPUT|TEXTAREA/i.test(activeEl.nodeName) && + activeEl.getAttribute("aria-readonly") != "true" + ) || + activeEl.contentEditable == 'true' + ) + ) { + return true; + } else { + while (activeEl && activeEl.nodeName) { + if (activeEl.contentEditable == 'true') return true; + if (activeEl.nodeName.toUpperCase() == 'BODY') { + break; + } + activeEl = activeEl.parentNode; + } + } + return false; + } + + function keydown(event) { + + //if (ImgWindowC.showing) return; + if (gallery && gallery.shown) return; + if (inputActive(document)) { + return; + } + var key = event.key; + if(checkGlobalKeydown(event)){ + if(prefs.floatBar.keys.enable && key==prefs.floatBar.keys.gallery){ + openGallery(); + event.stopPropagation(); + event.preventDefault(); + globalFuncEnabled = !globalFuncEnabled; + return true; + }else if((!gallery || (!gallery.shown && !gallery.minimized)) && prefs.floatBar.globalkeys.type == "press"){ + globalFuncEnabled = !globalFuncEnabled; + return true; + } + } + if (!prefs.floatBar.keys.enable){ + return false; + } + + if (event) { + if (event.ctrlKey || event.metaKey) return false; + if (window.getSelection().toString()) return false; + } + if (floatBar && isKeyDownEffectiveTarget(event.target)) { + Object.keys(prefs.floatBar.keys).some(function(action) { + if (action == 'enable' || action == 'search') return; + if (key == prefs.floatBar.keys[action]) { + floatBar.open(event, action); + return true; + } + }); + } + } + + function keyup(event) { + let isFuncKey = !event.isTrusted || event.key == 'Alt' || event.key == 'Control' || event.key == 'Meta'; + if(isFuncKey && (prefs.floatBar.globalkeys.type == "hold" || !checkPreview(event)) && (uniqueImgWin && !uniqueImgWin.removed)){ + clearTimeout(checkFloatBarTimer); + if(prefs.floatBar.globalkeys.closeAfterPreview){ + if (uniqueImgWin) { + uniqueImgWin.remove(); + } + }else{ + uniqueImgWin.focus(); + uniqueImgWin.imgWindow.classList.remove("pv-pic-window-transition-all"); + uniqueImgWin.imgWindow.classList.remove("preview"); + uniqueImgWin.previewed=true; + uniqueImgWin = null; + } + } + } + + function createEleFromJson(json) { + let collection = document.createDocumentFragment(); + json.forEach(data => { + if (/^script/i.test(data.node)) return; + let ele = document.createElement(data.node); + if (data.text) { + ele.innerText = data.text; + } + if (data.attr) { + Object.keys(data.attr).forEach(key => { + if (/^on/i.test(key)) return; + ele.setAttribute(key, data.attr[key]); + }); + } + if (data.children) { + let children = createEleFromJson(data.children); + ele.appendChild(children); + } + collection.appendChild(ele); + }); + return collection; + } + + window.addEventListener('message', handleMessage, true); + + addPageScript(); + + document.addEventListener('keyup', keyup, false); + document.addEventListener('mouseenter', globalMouseoverHandler, true); + document.addEventListener('mousemove', globalMouseoverHandler, true); + + document.addEventListener('mouseout',e=>{ + if (e.relatedTarget == ImgWindowC.overlayer) return; + if(uniqueImgWin && !uniqueImgWin.removed){ + if(checkPreview(e)){ + let showArea=uniqueImgWin.data.img.getBoundingClientRect(); + if(e.clientX < showArea.left + 20 || + e.clientX > showArea.right - 20 || + e.clientY < showArea.top + 20 || + e.clientY > showArea.bottom - 20){ + uniqueImgWin && uniqueImgWin.remove(); + } + } + } + }, true); + var editSitesFunc={ + "Lunapic": (src, initOpen) => { + _GM_openInTab('https://www.lunapic.com/editor/index.php?action=url&url=' + src, {active:true}); + }, + "Pixlr easy": async (src, initOpen) => { + if(initOpen){ + storage.setItem("editUrl", src); + _GM_openInTab('https://pixlr.com/x/', {active:true}); + }else{ + storage.setItem("editUrl", ""); + if(/^https:\/\/pixlr\.com\//.test(location.href)){ + await sleep(1000); + await clickEle('#splash-file-menu'); + await clickEle('#splash-file-url'); + await input('#image-url', src); + await clickEle('.dialog>.buttons>a.button.positive'); + } + } + }, + "Pixlr advanced": async (src, initOpen) => { + if(initOpen){ + storage.setItem("editUrl", src); + _GM_openInTab('https://pixlr.com/e/', {active:true}); + }else{ + storage.setItem("editUrl", ""); + if(/^https:\/\/pixlr\.com\//.test(location.href)){ + await sleep(1000); + await clickEle('#splash-file-menu'); + await clickEle('#splash-file-url'); + await input('#image-url', src); + await clickEle('.dialog>.buttons>a.button.positive'); + } + } + }, + "Photopea": async (src, initOpen) => { + if(initOpen){ + storage.setItem("editUrl", src); + _GM_openInTab('https://www.photopea.com/', {active:true}); + }else{ + storage.setItem("editUrl", ""); + if(/^https:\/\/www\.photopea\.com\//.test(location.href)){ + await sleep(1000); + await clickEle('.topbar>span>button'); + await clickEle('.cmanager>.contextpanel>div:nth-child(4)'); + await clickEle('.cmanager>div:last-child>div:nth-child(3)'); + await input('span.fitem.tinput>input', src); + await clickEle('.form>button'); + } + } + } + }; + var editSitesName={}; + for(let key in editSitesFunc){ + editSitesName[key]=key; + } + var newsInited = false, newsNode = null; + + initLang(); + var customLangOption={ + 'auto': i18n("defaultLang") + }; + for(let key in langList){ + customLangOption[key]=langList[key]; + } + GM_config.init({ + id: 'pv-prefs', + title: GM_config.create('a', { + href: 'https://greasyfork.org/scripts/24204-picviewer-ce', + target: '_blank', + textContent: 'Picviewer CE+ '+i18n("config"), + title: i18n("openHomePage") + }), + isTabs: true, + skin: 'tab', + frameStyle: { + minWidth: "350px", + width: ((visualLength((i18n("floatBar") + i18n("magnifier") + i18n("gallery") + i18n("imgWindow") + i18n("others")),"14px","arial,tahoma,myriad pro,sans-serif") + 250) || 480) + 'px', + zIndex:'2147483648', + margin: '1px', + border: '2px solid rgb(0, 0, 0)' + }, + css: [ + "#pv-prefs input[type='text'] { width: 50px; } ", + "#pv-prefs input[type='number'] { width: 50px; } ", + "#pv-prefs .inline .config_var { margin-left: 6px; }", + "#pv-prefs label.size { width: 205px; }", + "#pv-prefs span.sep-x { margin-left: 0px !important; }", + "#pv-prefs label.sep-x { margin-right: 5px; }", + "#pv-prefs label.floatBar-key { margin-left: 20px; width: 100px; }", + "#pv-prefs input.color { width: 120px; }", + "#pv-prefs input.order { width: 250px; }", + "#pv-prefs .config_header>a { border-bottom: solid 2px; }", + "#pv-prefs a:hover { color: #9f9f9f; }", + "#pv-prefs a { color: black; }", + "#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; }", + ].join('\n'), + fields: { + // 浮动工具栏 + 'floatBar.position': { + label: i18n("position"), + title: i18n("positionTips"), + type: 'select', + options: { + 'top left': i18n("topLeft"), + 'top right': i18n("topRight"), + 'bottom right': i18n("bottomRight"), + 'bottom left': i18n("bottomLeft"), + 'top center': i18n("topCenter"), + 'bottom center': i18n("bottomCenter"), + 'hide': i18n("hide") + }, + "default": prefs.floatBar.position, + section: [i18n("floatBar")], + }, + 'floatBar.stayOut': { + label: i18n("stayOut"), + type: 'checkbox', + "default": prefs.floatBar.stayOut, + line: 'start' + }, + 'floatBar.stayOutOffsetX': { + label: 'X:', + type: 'int', + "default": prefs.floatBar.stayOutOffsetX + }, + 'floatBar.stayOutOffsetY': { + label: 'Y:', + type: 'int', + "default": prefs.floatBar.stayOutOffsetY, + after: ' '+i18n("px"), + line: 'end' + }, + 'floatBar.showDelay': { + label: i18n("showDelay"), + type: 'int', + "default": prefs.floatBar.showDelay, + after: ' '+i18n("ms"), + }, + 'floatBar.hideDelay': { + label: i18n("hideDelay"), + type: 'int', + className: 'hideDelay', + "default": prefs.floatBar.hideDelay, + after: ' '+i18n("ms") + }, + 'floatBar.forceShow.size.w': { + label: i18n("forceShow"), + type: 'int', + className: 'size', + "default": prefs.floatBar.forceShow.size.w, + title: i18n("forceShowTip"), + line: 'start', + }, + 'floatBar.forceShow.size.h': { + label: ' x ', + type: 'int', + className: 'sep-x', + after: ' '+i18n("px"), + "default": prefs.floatBar.forceShow.size.h, + line: 'end', + }, + 'floatBar.minSizeLimit.w': { + label: i18n("minSizeLimit"), + type: 'int', + className: 'size', + "default": prefs.floatBar.minSizeLimit.w, + title: i18n("minSizeLimitTip"), + line: 'start', + }, + 'floatBar.minSizeLimit.h': { + label: ' x ', + type: 'int', + className: 'sep-x', + after: ' '+i18n("px"), + "default": prefs.floatBar.minSizeLimit.h, + line: 'end', + }, + 'floatBar.sizeLimitOr': { + label: i18n("sizeLimitOr"), + type: "checkbox", + "default": false + }, + 'floatBar.showWithRules': { + label: i18n("showWithRules"), + type: "checkbox", + "default": prefs.floatBar.showWithRules, + title: i18n("showWithRulesTip"), + }, + 'floatBar.butonOrder': { + label: i18n("butonOrder"), + type: 'text', + className: 'order', + title: 'actual,current,gallery,magnifier,download', + "default": prefs.floatBar.butonOrder.join(', '), + }, + 'floatBar.additionalFeature': { + label: i18n("additionalFeature"), + type: 'select', + options: { + 'copy': i18n("copy"), + 'copyImg': i18n("copyImg"), + 'open': i18n("openInNewTab") + }, + "default": prefs.floatBar.additionalFeature || 'open' + }, + 'floatBar.invertAdditionalFeature': { + label: i18n("invertAdditionalFeature"), + type: 'checkbox', + "default": prefs.floatBar.invertAdditionalFeature + }, + 'floatBar.listenBg': { + label: i18n("listenBg"), + type: 'checkbox', + "default": prefs.floatBar.listenBg, + title: i18n("listenBgTip") + }, + 'floatBar.globalkeys.ctrl': { + label: i18n("globalkeys"), + type: 'checkbox', + after: "CTRL +", + "default": true, + line: 'start' + }, + 'floatBar.globalkeys.alt': { + after: "ALT +", + type: 'checkbox', + className: 'sep-x', + "default": false, + }, + 'floatBar.globalkeys.shift': { + after: "SHIFT +", + type: 'checkbox', + className: 'sep-x', + "default": false, + }, + 'floatBar.globalkeys.command': { + after: "META", + type: 'checkbox', + className: 'sep-x', + "default": false, + line: 'end', + }, + 'floatBar.globalkeys.type': { + label: i18n("globalkeysType"), + type: 'select', + options: { + 'press': i18n("globalkeysPress"), + 'hold': i18n("globalkeysHold") + }, + "default": prefs.floatBar.globalkeys.type + }, + 'floatBar.globalkeys.invertInitShow': { + label: i18n("initShow"), + type: 'checkbox', + "default": prefs.floatBar.globalkeys.invertInitShow + }, + 'floatBar.globalkeys.closeAfterPreview': { + label: i18n("closeAfterPreview"), + type: 'checkbox', + "default": prefs.floatBar.globalkeys.closeAfterPreview + }, + 'floatBar.previewMaxSizeW': { + label: i18n("previewMaxSize"), + type: 'int', + className: 'size', + "default": prefs.previewMaxSizeW || 0, + title: i18n("previewMaxSizeTip"), + line: 'start', + }, + 'floatBar.previewMaxSizeH': { + label: ' x ', + type: 'int', + className: 'sep-x', + after: ' '+i18n("px"), + "default": prefs.previewMaxSizeH || 0, + line: 'end', + }, + 'floatBar.globalkeys.previewFollowMouse': { + label: i18n("previewFollowMouse"), + type: 'checkbox', + "default": prefs.floatBar.globalkeys.previewFollowMouse + }, + // 按键 + 'floatBar.keys.enable': { + label: i18n("keysEnable"), + type: 'checkbox', + "default": prefs.floatBar.keys.enable + }, + 'floatBar.keys.actual': { + label: i18n("keysActual"), + type: 'text', + className: 'floatBar-key', + "default": prefs.floatBar.keys.actual, + title: i18n("keysActualTip") + }, + /*'floatBar.keys.search': { + label: i18n("keysSearch"), + type: 'text', + className: 'floatBar-key', + "default": prefs.floatBar.keys.search, + title: i18n("keysSearchTip") + },*/ + 'floatBar.keys.current': { + label: i18n("keysCurrent"), + type: 'text', + className: 'floatBar-key', + "default": prefs.floatBar.keys.current, + title: i18n("keysCurrentTip") + }, + 'floatBar.keys.magnifier': { + label: i18n("keysMagnifier"), + type: 'text', + className: 'floatBar-key', + "default": prefs.floatBar.keys.magnifier, + title: i18n("keysMagnifierTip") + }, + 'floatBar.keys.gallery': { + label: i18n("keysGallery"), + type: 'text', + className: 'floatBar-key', + "default": prefs.floatBar.keys.gallery, + title: i18n("keysGalleryTip") + }, + 'floatBar.keys.download': { + label: i18n("download"), + type: 'text', + className: 'floatBar-key', + "default": prefs.floatBar.keys.download + }, + 'floatBar.disableKeySites': { + label: i18n("disableKeySites"), + type: 'textarea', + "default": prefs.floatBar.disableKeySites + }, + + // 放大镜 + 'magnifier.radius': { + label: i18n("magnifierRadius"), + type: 'int', + "default": prefs.magnifier.radius, + section: [i18n("magnifier")], + after: ' '+i18n("px") + }, + 'magnifier.wheelZoom.enabled': { + label: i18n("magnifierWheelZoomEnabled"), + type: 'checkbox', + "default": prefs.magnifier.wheelZoom.enabled, + }, + 'magnifier.wheelZoom.scaleImage': { + label: i18n("magnifierScaleImage"), + type: 'checkbox', + "default": prefs.magnifier.wheelZoom.scaleImage !== false, + }, + 'magnifier.wheelZoom.ctrl': { + label: '', + type: 'checkbox', + after: "CTRL +", + "default": false, + line: 'start' + }, + 'magnifier.wheelZoom.alt': { + after: "ALT +", + type: 'checkbox', + className: 'sep-x', + "default": false, + }, + 'magnifier.wheelZoom.shift': { + after: "SHIFT +", + type: 'checkbox', + className: 'sep-x', + "default": false, + }, + 'magnifier.wheelZoom.meta': { + after: "META", + type: 'checkbox', + className: 'sep-x', + "default": false, + line: 'end', + }, + 'magnifier.wheelZoom.range': { + label: i18n("magnifierWheelZoomRange"), + type: 'textarea', + "default": prefs.magnifier.wheelZoom.range.join(', '), + }, + + // 图库 + 'gallery.defaultSizeLimit.w': { + label: i18n("defaultSizeLimit"), + type: 'int', + className: 'size', + section: [i18n("gallery")], + "default": prefs.gallery.defaultSizeLimit.w, + line: 'start', + }, + 'gallery.defaultSizeLimit.h': { + label: ' x ', + type: 'int', + className: 'sep-x', + after: ' '+i18n("px"), + "default": prefs.gallery.defaultSizeLimit.h, + line: 'end', + }, + 'gallery.fitToScreen': { + label: i18n("galleryFitToScreen"), + type: 'checkbox', + "default": prefs.gallery.fitToScreen, + title: i18n("galleryFitToScreenTip"), + line: 'start', + }, + 'gallery.fitToScreenSmall': { + label: i18n("galleryFitToScreenSmall"), + type: 'checkbox', + "default": prefs.gallery.fitToScreenSmall, + line: 'end', + }, + 'gallery.scrollEndToChange': { + label: i18n("galleryScrollEndToChange"), + type: 'checkbox', + "default": prefs.gallery.scrollEndToChange, + title: i18n("galleryScrollEndToChangeTip") + }, + 'gallery.backgroundColor': { + label: i18n("backgroundColor"), + type: 'text', + className: 'color', + "default": prefs.gallery.backgroundColor || 'rgba(20,20,20,0.75)', + line: 'end' + }, + 'gallery.exportType': { + label: i18n("galleryExportType"), + type: 'select', + options: { + 'grid': i18n("grid"), + 'gridBig': i18n("gridBig"), + 'list': i18n("list") + }, + "default": prefs.gallery.exportType, + }, + 'gallery.loadMore': { + label: i18n("galleryAutoLoad"), + type: 'checkbox', + "default": prefs.gallery.loadMore + }, + 'gallery.loadAll': { + label: i18n("galleryLoadAll"), + type: 'checkbox', + "default": prefs.gallery.loadAll, + title: i18n("galleryLoadAllTip") + }, + 'gallery.viewmoreEndless': { + label: i18n("viewmoreEndless"), + type: 'checkbox', + "default": prefs.gallery.viewmoreEndless + }, + 'gallery.downloadWithZip': { + label: i18n("galleryDownloadWithZip"), + type: 'checkbox', + "default": prefs.gallery.downloadWithZip + }, + 'gallery.downloadGap': { + label: i18n("galleryDownloadGap"), + type: 'int', + "default": prefs.gallery.downloadGap, + after: ' ms', + }, + 'gallery.formatConversion': { + label: i18n("formatConversion"), + type: 'textarea', + title: 'webp>png\nx-icon>png', + "default": prefs.gallery.formatConversion || '' + }, + 'gallery.aria2Host': { + label: i18n("aria2Host"), + type: 'text', + className: 'order', + "default": prefs.gallery.aria2Host || 'http://localhost:6800', + after: '/jsonrpc' + }, + 'gallery.aria2Token': { + label: i18n("aria2Token"), + type: 'text', + "default": prefs.gallery.aria2Token || '' + }, + 'gallery.scaleSmallSize': { + label: i18n("galleryScaleSmallSize1"), + type: 'int', + "default": prefs.gallery.scaleSmallSize, + after: i18n("galleryScaleSmallSize2") + }, + 'gallery.showSmallSize':{ + label: i18n("galleryShowSmallSize"), + type: 'checkbox', + "default": prefs.gallery.showSmallSize + }, + 'gallery.transition': { + label: i18n("galleryTransition"), + type: 'checkbox', + "default": prefs.gallery.transition + }, + 'gallery.disableArrow': { + label: i18n("galleryDisableArrow"), + type: 'checkbox', + "default": prefs.gallery.disableArrow + }, + 'gallery.sidebarPosition': { + label: i18n("gallerySidebarPosition"), + type: 'select', + options: { + 'bottom': i18n("bottom"), + 'right': i18n("right"), + 'left': i18n("left"), + 'top': i18n("top") + }, + "default": prefs.gallery.sidebarPosition, + line: 'start', + }, + 'gallery.sidebarSize': { + label: i18n("gallerySidebarSize"), + type: 'int', + "default": prefs.gallery.sidebarSize, + title: i18n("gallerySidebarSizeTip"), + after: ' '+i18n("px"), + line: 'end', + }, + 'gallery.max': { + label: i18n("galleryMax1"), + type: 'number', + "default": prefs.gallery.max, + after: i18n("galleryMax2") + }, + 'gallery.autoZoom': { + label: i18n("galleryAutoZoom"), + type: 'checkbox', + "default": prefs.gallery.autoZoom, + title: i18n("galleryAutoZoomTip") + }, + 'gallery.descriptionLength': { + label: i18n("galleryDescriptionLength1"), + type: 'int', + "default": prefs.gallery.descriptionLength, + after: i18n("galleryDescriptionLength2") + }, + 'gallery.autoOpenViewmore': { + label: i18n("autoOpenViewmore"), + type: 'checkbox', + "default": prefs.gallery.autoOpenViewmore + }, + 'gallery.autoOpenSites': { + label: i18n("galleryAutoOpenSites"), + type: 'textarea', + "default": prefs.gallery.autoOpenSites + }, + 'gallery.searchData': { + label: i18n("gallerySearchData"), + type: 'textarea', + "default": prefs.gallery.searchData + }, + 'gallery.editSite': { + label: i18n("galleryEditSite"), + type: 'select', + options: editSitesName, + "default": prefs.gallery.editSite, + }, + + // 图片窗口 + 'imgWindow.fitToScreen': { + label: i18n("imgWindowFitToScreen"), + type: 'checkbox', + "default": prefs.imgWindow.fitToScreen, + section: [i18n("imgWindow")], + title: i18n("imgWindowFitToScreenTip"), + }, + 'imgWindow.fitToScreenSmall': { + label: i18n("imgWindowFitToScreenWhenSmall"), + type: 'checkbox', + "default": prefs.imgWindow.fitToScreenSmall + }, + 'imgWindow.suitLongImg': { + label: i18n("suitLongImg"), + type: 'checkbox', + "default": prefs.imgWindow.suitLongImg + }, + 'imgWindow.switchStoreLoc': { + label: i18n("switchStoreLoc"), + type: 'checkbox', + "default": prefs.imgWindow.switchStoreLoc + }, + 'imgWindow.defaultTool': { + label: i18n("imgWindowDefaultTool"), + type: 'select', + options: { + 'hand': i18n("hand"), + 'rotate': i18n("rotate"), + 'zoom': i18n("zoom"), + }, + "default": prefs.imgWindow.defaultTool, + }, + 'imgWindow.close.escKey': { + label: i18n("imgWindowEscKey"), + type: 'checkbox', + "default": prefs.imgWindow.close.escKey, + line: 'start', + }, + 'imgWindow.close.dblClickImgWindow': { + label: i18n("imgWindowDblClickImgWindow"), + type: 'checkbox', + "default": prefs.imgWindow.close.dblClickImgWindow, + }, + 'imgWindow.close.clickOutside': { + label: i18n("imgWindowClickOutside"), + type: 'select', + options: { + '': i18n("none"), + 'click': i18n("click"), + 'dblclick': i18n("dblclick"), + }, + "default": prefs.imgWindow.close.clickOutside, + title: i18n("imgWindowClickOutsideTip"), + line: 'end', + }, + 'imgWindow.backgroundColor': { + label: i18n("backgroundColor"), + type: 'text', + className: 'color', + "default": prefs.imgWindow.backgroundColor, + line: 'end' + }, + 'imgWindow.overlayer.shown': { + label: i18n("imgWindowOverlayerShown"), + type: 'checkbox', + "default": prefs.imgWindow.overlayer.shown, + line: 'start', + }, + 'imgWindow.overlayer.color': { + label: i18n("imgWindowOverlayerColor"), + type: 'text', + className: 'color', + "default": prefs.imgWindow.overlayer.color, + line: 'end' + }, + 'imgWindow.shiftRotateStep': { + label: i18n("imgWindowShiftRotateStep1"), + type: 'int', + "default": prefs.imgWindow.shiftRotateStep, + after: i18n("imgWindowShiftRotateStep2") + }, + 'imgWindow.zoom.mouseWheelZoom': { + label: i18n("imgWindowMouseWheelZoom"), + type: 'checkbox', + "default": prefs.imgWindow.zoom.mouseWheelZoom, + }, + 'imgWindow.zoom.range': { + label: i18n("imgWindowZoomRange"), + type: 'textarea', + "default": prefs.imgWindow.zoom.range.join(', '), + title: i18n("imgWindowZoomRangeTip"), + attr: { + "spellcheck": "false" + } + }, + 'imgWindow.fixed': { + label: i18n("positionFixed"), + "default": prefs.imgWindow.fixed, + type: 'checkbox', + }, + 'imgWindow.zIndex': { + label: "z-Index", + "default": prefs.imgWindow.zIndex, + type: 'int', + }, + + // 其它 + 'waitImgLoad': { + label: i18n("waitImgLoad"), + type: 'checkbox', + "default": prefs.waitImgLoad, + section: [i18n("others")], + title: i18n("waitImgLoadTip") + }, + 'customLang': { + label: i18n("customLang"), + type: 'select', + options: customLangOption, + "default": prefs.customLang, + line: 'end', + }, + 'saveName': { + label: i18n("saveName"), + type: 'select', + options: { + 0: i18n("default"), + 1: i18n("textFirst"), + 2: i18n("onlyUrl"), + 3: i18n("urlAndText") + }, + "default": (prefs.saveName || 0), + title: i18n("saveNameTip"), + }, + 'saveNameAddTitle': { + label: i18n("saveNameAddTitle"), + type: 'checkbox', + "default": !!prefs.saveNameAddTitle + }, + 'debug': { + label: i18n("debug"), + type: 'checkbox', + "default": prefs.debug + }, + 'customRules': { + label: GM_config.create('a', { + href: 'https://github.com/hoothin/UserScripts/tree/master/Picviewer%20CE%2B#-custom-rules-example', + target: '_blank', + textContent: i18n("customRules") + }), + type: 'textarea', + "default": prefs.customRules + } + /*'firstEngine': { + label: i18n("firstEngine"), + type: 'select', + options: { + "Tineye":"Tineye", + "Google":"Google", + "Baidu":"Baidu" + }, + "default": prefs.firstEngine, + },*/ + }, + events: { + open: async function(doc, win, frame) { + isConfigOpen = true; + let saveBtn = doc.querySelector("#"+this.id+"_saveBtn"); + let closeBtn = doc.querySelector("#"+this.id+"_closeBtn"); + let resetLink = doc.querySelector("#"+this.id+"_resetLink"); + let customInput = doc.querySelector("#"+this.id+"_field_customRules"); + customInput.style.height = "188px"; + customInput.setAttribute("spellcheck", "false"); + saveBtn.textContent = i18n("saveBtn"); + saveBtn.title = i18n("saveBtnTips"); + saveBtn.addEventListener('click', e => { + if (customInput.value) { + if (customInput.value.trim().indexOf("[") != 0) { + e.stopPropagation(); + e.preventDefault(); + alert("The rules must be enclosed in square brackets ([])."); + return; + } + try { + var customRules; + if (customInput.value.indexOf("name:") !== -1) { + if (!isunsafe()) { + unsafeWindow.eval(createScript(customInput.value)); + } + } else { + customInput.value = JSON.stringify(JSON.parse(customInput.value), null, 4); + } + } catch(err) { + e.stopPropagation(); + e.preventDefault(); + alert("Wrong rule:" + err.toString()); + } + } + }, true); + closeBtn.textContent=i18n("closeBtn"); + closeBtn.title=i18n("closeBtnTips"); + resetLink.textContent=i18n("resetLink"); + resetLink.title=i18n("resetLinkTips"); + let searchData=doc.getElementById(this.id+"_field_gallery.searchData"); + if(searchData && searchData.value==""){ + searchData.value=defaultSearchData; + } + let about = doc.getElementById(this.id + "_section_4"); + if (about) { + if (!newsNode) { + newsNode = document.createElement("div"); + let newsEles = createEleFromJson([ + { + node: "div", + text: "Made with ❤️ by @", + attr: { + style: "width: calc(100% - 8px); text-align: center;" + }, + children: [ + { + node: "a", + text: "Hoothin", + attr: { + "href": "mailto:rixixi@gmail.com" + } + }, + { + node: "br" + }, + { + node: "a", + text: "Star Me on 🐱Github", + attr: { + href: "https://github.com/hoothin/UserScripts", + target: "_blank" + } + }, + { + node: "br" + }, + { + node: "span", + text: "Join our " + }, + { + node: "a", + text: "Discord", + attr: { + href: "https://discord.com/invite/keqypXC6wD", + target: "_blank" + } + } + ] + }, + { + node: "img", + attr: { + src: "https://s2.loli.net/2023/02/06/afTMxeASm48z5vE.jpg", + style: "width: 100%; margin-top: 5px; max-height: 180px; display: none;", + onload: "this.style.display=''" + } + }, + { + node: "div", + attr: { + style: "width: 100%; display: flex; align-items: center; justify-content: center; margin-top: 5px; font-size: 14px;" + }, + children: [ + { + node: "img", + attr: { + src: "https://ko-fi.com/favicon-32x32.png", + style: "margin-right: 5px; height: 25px; display: none;", + onload: "this.style.display=''" + } + }, + { + node: "a", + text: "Ko-fi", + attr: { + href: "https://ko-fi.com/hoothin", + style: "margin-right: 10px;", + target: "_blank" + } + }, + { + node: "img", + attr: { + src: "https://static.afdiancdn.com/favicon.ico", + style: "margin-right: 5px; height: 20px; display: none;", + onload: "this.style.display=''" + } + }, + { + node: "a", + text: "爱发电", + attr: { + href: "https://afdian.com/@hoothin", + target: "_blank" + } + } + ] + } + ]); + newsNode.style.padding = "5px"; + newsNode.appendChild(newsEles); + about.appendChild(newsNode); + } + if (!newsInited) { + let news = await GM_fetch(`https://hoothin.com/scripts/pvcep/${lang}`).then(response => response.json()).catch(e => {}); + newsInited = true; + if (!news) return; + let newsEles = createEleFromJson(news); + if (newsEles && newsEles.childElementCount) { + if (newsNode) about.removeChild(newsNode); + newsNode = document.createElement("div"); + newsNode.appendChild(newsEles); + } + } + about.appendChild(newsNode); + } + }, + save: function() { + loadPrefs(); + storage.setItem("customLang", prefs.customLang); + }, + close: function() { + isConfigOpen = false; + } + } + }); + + + + loadPrefs(); + + var hideIcon=storage.getListItem("hideIcon", location.hostname) || false; + var hideIconStyle=document.createElement('style'); + hideIconStyle.textContent=`#pv-float-bar-container{display:none!important}`; + if(hideIcon){ + document.head.appendChild(hideIconStyle); + } + _GM_registerMenuCommand(i18n("openConfig"), openPrefs); + _GM_registerMenuCommand(i18n("openGallery"), openGallery); + _GM_registerMenuCommand(i18n("hideIcon") + (hideIcon ? "☑️" : ""), () => { + hideIcon=!hideIcon; + storage.setListItem("hideIcon", location.hostname, hideIcon); + if(hideIcon){ + document.head.appendChild(hideIconStyle); + }else{ + document.head.removeChild(hideIconStyle); + } + }); + _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}); + }); + + function initKeyInputs() { + if (!GM_config.frame || !GM_config.frame.contentDocument) return; + let keyInputs = GM_config.frame.contentDocument.querySelectorAll('input.floatBar-key'); + [].forEach.call(keyInputs, input => { + input.setAttribute('readOnly', 'readonly'); + input.addEventListener("keydown", e => { + if (e.key === 'Escape' || e.key === 'Backspace') input.value = ''; + else input.value = e.key; + e.stopPropagation(); + e.preventDefault(); + }); + }); + } + + matchedRule = getMatchedRule(); + let editUrl=storage.getItem("editUrl"); + if(editUrl){ + let editFunc=editSitesFunc[prefs.gallery.editSite]; + if(!editFunc)editFunc=editSitesFunc[Object.keys(editSitesFunc)[0]]; + if(editFunc){ + if (document.readyState == 'complete'){ + editFunc(editUrl); + }else{ + let readystatechangeHandler = e => { + if (document.readyState == 'complete') { + editFunc(editUrl); + document.removeEventListener('readystatechange', readystatechangeHandler); + } + }; + document.addEventListener('readystatechange', readystatechangeHandler); + } + } + } + + 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+/") { + openPrefs(); + } else if (location.hostname == "hoothin.github.io" && location.pathname == "/UserScripts/Picviewer%20CE+/gallery.html") { + let gallery = new GalleryC(); + gallery.data = []; + gallery.lockGallery = true; + var allData = await gallery.getAllValidImgs(); + gallery.data = allData; + gallery.load(gallery.data); + let searchParams = new URLSearchParams(location.search); + let viewMore = searchParams.get("mode"); + let imgs = searchParams.get("imgs"); + if (imgs) { + gallery.addImageUrls(imgs); + } + if (viewMore == "1") { + gallery.maximizeSidebar(); + } + let imgCon = gallery.eleMaps['img-content'], waitingDouble = false, openImageTimer; + imgCon.addEventListener('click', e => { + if (e.target == imgCon) { + clearTimeout(openImageTimer); + if (waitingDouble) { + waitingDouble = false; + gallery.openImages({altKey: true}); + return; + } + waitingDouble = true; + openImageTimer = setTimeout(() => { + waitingDouble = false; + gallery.openImages(e); + }, 300); + } + }, true); + } else if (prefs.gallery.autoOpenSites) { + let sitesArr=prefs.gallery.autoOpenSites.split("\n"); + for(let s=0;s { + if (!ruleImportUrlReg.test(location.href)) return; + if (/pre|code/i.test(e.target.nodeName)) { + let content = e.target.innerText.trim(); + if (/"name":/.test(content) && /"(r|xhr)":/.test(content)) { + try { + localStorage.setItem('picviewerCE.config.curTab', 4); + let webRule = JSON.parse(content.replace(/^\/\/.*/g, "")); + let customRules; + let fieldsCustomRules = GM_config.fields.customRules; + if (prefs.customRules.indexOf('"name":') !== -1) { + customRules = JSON.parse(prefs.customRules); + } else { + customRules = []; + } + customRules.push(webRule); + fieldsCustomRules.value = JSON.stringify(customRules, null, 4); + openPrefs(); + } catch (e) { + alert(e.toString()); + } + } + } + }, true); + } + + function openPrefs() { + let fieldsSearchData = GM_config.fields["gallery.searchData"]; + if (fieldsSearchData && fieldsSearchData.value) { + fieldsSearchData.value = fieldsSearchData.value.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&").replace(/>/g, ">").replace(/").replace(/</g, "<").replace(/&/g, "&").replace(/>/g, ">").replace(/{ + if (GM_config.frame && GM_config.frame.contentDocument.body.innerHTML === "") { + _GM_openInTab("https://hoothin.github.io/UserScripts/Picviewer%20CE+/", {active:true}); + return; + } + if (GM_config.frame && GM_config.frame.style && GM_config.frame.style.display == "none") { + GM_config.frame.src=""; + } + initKeyInputs(); + }, 1000); + } + + function loadPrefs() { + // 根据 GM_config 的 key 载入设置到 prefs + Object.keys(GM_config.fields).forEach(function(keyStr) { + var keys = keyStr.split('.'); + var lastKey = keys.pop(); + + var lastPref = prefs; + var curKey = keys.shift(); + while(curKey){ + lastPref = lastPref[curKey]; + curKey = keys.shift(); + } + + var value = GM_config.get(keyStr); + if (typeof value != 'undefined') { + // 特殊的 + if (keyStr == 'magnifier.wheelZoom.range' || keyStr == 'imgWindow.zoom.range') { + lastPref[lastKey] = value.split(/[,,]\s*/).map(function(s) { return parseFloat(s)}); + } else if(keyStr == 'floatBar.butonOrder') { + lastPref[lastKey] = value.trim().split(/\s*[,,]\s*/); + } else { + lastPref[lastKey] = value; + } + } + }); + try { + if (localStorage && localStorage.setItem) { + if (!storage.getItem('inited')) { + localStorage.setItem('picviewerCE.config.curTab', 4); + storage.setItem('inited', true); + } + } + } catch(e) {} + if (typeof prefs.gallery.formatConversion == 'undefined') { + prefs.gallery.formatConversion = "webp>png"; + } + prefs.gallery.formatConversion.split("\n").forEach(str => { + let pair = str.split(">"); + if (pair.length !== 2) return; + formatDict.set(pair[0].trim(), pair[1].trim()); + }); + + debug = prefs.debug ? console.log.bind(console) : function() {}; + } + + }; + + function drawTobase64(img){ + canvas.width = img.naturalWidth || img.width; + canvas.height = img.naturalHeight || img.height; + let ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0); + return canvas.toDataURL("image/png"); + } + + function init2(){ + init(topObject,window,document,arrayFn,envir,storage,unsafeWindow); + }; + + + //大致检测运行环境 + var envir={ + ie:typeof document.documentMode == 'number', + firefox:typeof XPCNativeWrapper == 'function', + opera:!!window.opera, + chrome:!!window.chrome, + }; + + //ie的话,不支持 < ie9的版本 + if(envir.ie && document.documentMode < 9){ + return; + }; + + + var arrayFn=(function(){ + var arrayProto=Array.prototype; + return { + find:arrayProto.find, + slice:arrayProto.slice, + forEach:arrayProto.forEach, + some:arrayProto.some, + every:arrayProto.every, + map:arrayProto.map, + filter:arrayProto.filter, + indexOf:arrayProto.indexOf, + lastIndexOf:arrayProto.lastIndexOf, + }; + + })(); + + var storage={ + supportGM: typeof GM_getValue=='function' && typeof GM_getValue('a','b')!='undefined',//chrome的gm函数式空函数 + mxAppStorage:(function(){//傲游扩展储存接口 + try{ + return window.external.mxGetRuntime().storage; + }catch(e){ + }; + })(), + operaUJSStorage:(function(){//opera userjs全局存储接口 + try{ + return window.opera.scriptStorage; + }catch(e){ + }; + })(), + setItem:function(key,value){ + if (this.supportGM) { + GM_setValue(key, value); + if (value === "" && typeof GM_deleteValue != 'undefined') { + GM_deleteValue(key); + } + } else if (this.operaUJSStorage) { + this.operaUJSStorage.setItem(key, value); + } else if (this.mxAppStorage) { + this.mxAppStorage.setConfig(key, value); + } else if (window.localStorage) { + window.localStorage.setItem(key, value); + } + }, + getItem:function(key){ + var value; + if(this.supportGM){ + value=GM_getValue(key); + }else if(this.operaUJSStorage){ + value=this.operaUJSStorage.getItem(key); + }else if(this.mxAppStorage){ + value=this.mxAppStorage.getConfig(key); + }else if(window.localStorage){ + value=window.localStorage.getItem(key); + }; + return value; + }, + getListItem: function(list, key) { + var value; + var listData = this.getItem(list); + if (listData) { + for(var i = 0; i < listData.length; i++) { + var data = listData[i]; + if (data.k == key) { + value = data.v; + break; + } + } + } + return value; + }, + setListItem: function(list, key, value, limitNum = 50) { + var listData = this.getItem(list); + if (!listData || !listData.filter) listData = []; + listData = listData.filter(data => data && data.k != key); + if (value) { + listData.unshift({k: key, v: value}); + if (listData.length > limitNum) listData.pop(); + } + this.setItem(list, listData); + } + }; + + function getUrl(url, callback, onError){ + _GM_xmlhttpRequest({ + method: 'GET', + url: url.trim(), + onload: callback, + onerror: onError + }); + } + + function setSearchState(words, imgState){ + if (imgState) imgState.innerHTML=createHTML(words); + } + + var searchSort=["Tineye","Google","Baidu"]; + function sortSearch(){ + for(var i=0;i2)break; + srcs.push(imgData.objURL); + } + setSearchState(i18n("findOverBeginLoad",["百度",srcs.length]),imgCon); + callBackFun(srcs); + }else{ + searchNext(); + return; + } + }, onError); + }; + var searchGoogle=function(){ + setSearchState(i18n("beginSearchImg","Google"),imgCon); + getUrl("https://www.google.com/searchbyimage?safe=off&image_url="+encodeURIComponent(imgSrc), function(d){ + let googleHtml=document.implementation.createHTMLDocument(''); + googleHtml.documentElement.innerHTML = d.responseText; + let sizeUrl=googleHtml.querySelector("div.card-section>div>div>span.gl>a"); + if(sizeUrl){ + getUrl("https://www.google.com"+sizeUrl.getAttribute("href"), function(d){ + googleHtml.documentElement.innerHTML = d.responseText; + let imgs=googleHtml.querySelectorAll("div.rg_meta"); + if(imgs.length==0){searchNext();return;} + srcs=[]; + for(var i=0;i2)break; + let jsonData=JSON.parse(imgs[i].innerHTML); + srcs.push(jsonData.ou); + } + setSearchState(i18n("findOverBeginLoad",["Google",srcs.length]),imgCon); + callBackFun(srcs); + }, onError); + }else{ + searchNext(); + } + }, onError); + }; + var searchTineye=function(){ + setSearchState(i18n("beginSearchImg","Tineye"),imgCon); + getUrl("https://www.tineye.com/search?url="+encodeURIComponent(imgSrc)+"&sort=size", function(d){ + let tineyeHtml=document.implementation.createHTMLDocument(''); + tineyeHtml.documentElement.innerHTML = d.responseText; + let searchImg=tineyeHtml.querySelectorAll(".match-details>div.match:first-of-type>p.image-link:first-of-type>a"); + if(searchImg.length>0){ + srcs=[]; + for(var i=0;i2)break; + srcs.push(searchImg[i].href); + } + setSearchState(i18n("findOverBeginLoad",["Tineye",srcs.length]),imgCon); + callBackFun(srcs); + }else{ + searchNext(); + } + }, onError); + }; + var searchNext=function(){ + searchFrom++; + if(searchFrom<=searchSort.length)switchSearch(); + else{ + if(noneResult)noneResult(); + setSearchState(i18n("findNoPic"),imgCon); + setTimeout(function(){ + setSearchState("",imgCon); + },2000); + } + }; + var callBackFun=function(srcs){ + callBack(srcs, searchFrom); + }; + if(!searchFrom)searchFrom=1; + var switchSearch=function(){ + switch(searchSort[searchFrom-1]){ + case "Baidu": + searchBaidu(); + break; + case "Google": + searchGoogle(); + break; + case "Tineye": + searchTineye(); + break; + default: + searchTineye(); + break; + } + }; + switchSearch(); + } + + if (window.top != window.self) { + if (window.self.innerWidth === 0 && window.self.innerHeight === 0) { + if (document.readyState !== "complete") { + window.addEventListener('load', e => { + setTimeout(() => { + if (window.self.innerWidth > 250 && window.self.innerHeight > 250) { + init2(); + } + }, 500); + }); + } + } else { + init2(); + } + } else { + init2(); + } + +})(this,window,document,(typeof unsafeWindow=='undefined'? window : unsafeWindow)); \ No newline at end of file From 016b93b6decfaf20e60e6d119375d76a35b7dca8 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:19:54 +0900 Subject: [PATCH 059/252] Update jekyll-gh-pages.yml --- .github/workflows/jekyll-gh-pages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml index 00b864d6d2c..5ed5cada8c8 100644 --- a/.github/workflows/jekyll-gh-pages.yml +++ b/.github/workflows/jekyll-gh-pages.yml @@ -8,6 +8,7 @@ on: paths-ignore: - 'Pagetual/items_all.json' - 'Pagetual/pagetualRules.json' + - '.github/*' workflow_run: workflows: ["Update Version on File Change"] types: From feb52f2a007fa8abd6253e679efceab51e860d4d Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:21:06 +0900 Subject: [PATCH 060/252] Update jekyll-gh-pages.yml --- .github/workflows/jekyll-gh-pages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml index 5ed5cada8c8..9bdd4e3eec5 100644 --- a/.github/workflows/jekyll-gh-pages.yml +++ b/.github/workflows/jekyll-gh-pages.yml @@ -9,6 +9,7 @@ on: - 'Pagetual/items_all.json' - 'Pagetual/pagetualRules.json' - '.github/*' + - '.github/*/*' workflow_run: workflows: ["Update Version on File Change"] types: From 30d7d930ec73fc1562f010d8ecfecdab45ffa9fd Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:23:02 +0900 Subject: [PATCH 061/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index 88c92b44c76..5d4053480f2 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -20,9 +20,9 @@ jobs: run: | TIMESTAMP=$(date +%s) - sed -e "s#// @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ - -e "s#// @require https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ - -e "s#// @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ + sed -e "s#https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" - name: Commit and push dist.user.js From 25d4a9eb2bdfd03bfe73fcea5396597c0706d50e Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 7 Sep 2025 01:23:38 +0000 Subject: [PATCH 062/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- 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 77b2308bb08..259a54a3d0d 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js -// @require https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js -// @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757208217 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757208217 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757208217 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* From f1ed00d9436aa6fb3add1c385f98a551425c262c Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:29:38 +0900 Subject: [PATCH 063/252] Update README.md --- Picviewer CE+/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index 9a99e07cd7a..3c60cc5ac91 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -19,9 +19,9 @@ Recommended similar plugins: [Imagus](https://chromewebstore.google.com/detail/imagus/immpkjjlgappgfkkfieppnmlhakdmaab) - [Hover-zoom+](https://chromewebstore.google.com/detail/hover-zoom+/pccckmaobkjjboncdfnnofkonhgpceea) - [Mouseover Popup Image Viewer](https://greasyfork.org/scripts/394820) -## 🚀 Quick Start +## Quick Start -Install from [Github](https://hoothin.github.io/UserScripts/Picviewer%20CE+/Picviewer%20CE+.user.js) or [Greasefork](https://greasyfork.org/scripts/24204-picviewer-ce). +Install from [Github📥](https://hoothin.github.io/UserScripts/Picviewer%20CE+/Picviewer%20CE+.user.js) or [Greasefork📥](https://greasyfork.org/scripts/24204-picviewer-ce). Hover your mouse over any image and click the icons on the float bar. @@ -29,7 +29,7 @@ Press `CTRL + G` to quickly enter the gallery. Hold `CTRL` to view a larger pict There are additional settings available in the "Picviewer CE+ config" for further customization. Currently, reviewing these settings is the best way to learn about the script's capabilities. Try exploring more functions on your own! -## 🤝 Contribution & Support +## Contribution & Support If you are glad to assist with the translation, please ✍️[edit this file](https://github.com/hoothin/UserScripts/edit/master/Picviewer%20CE%2B/pvcep_lang.js#L1). It will be beneficial for individuals who speak the same language as you do. Thank you for your help. @@ -47,7 +47,7 @@ If you are glad to assist with the translation, please ✍️[edit this file](ht *Need more rules for peculiar sites? feel free to pull requests or open issues.* -## ✨ PDF Addon +## PDF Addon Picviewer CE+ [PDF Addon](https://greasyfork.org/scripts/498445-picviewer-ce-pdf-addon). After installing this addon, when the `Compress to ZIP` feature is enabled, a PDF file will be generated instead of a ZIP file during the packaging process.
Make a PDF e-book with this addon @@ -62,7 +62,7 @@ Picviewer CE+ [PDF Addon](https://greasyfork.org/scripts/498445-picviewer-ce-pdf This way, you'll get a beautifully created PDF e-book.
-## 🔧 Custom [Rules Example](pvcep_rules.js): +## Custom [Rules Example](pvcep_rules.js): **💝 Buy me a coffee with [Ko-fi](https://ko-fi.com/hoothin) or [愛發電](https://afdian.com/a/hoothin) to keep my scripts always up to date.** From a4a6a8d276da7792557a332827784e58c968ba98 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:30:45 +0900 Subject: [PATCH 064/252] Update README.md --- Picviewer CE+/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index 3c60cc5ac91..2ff6f1da30d 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -21,7 +21,7 @@ Recommended similar plugins: [Imagus](https://chromewebstore.google.com/detail/i ## Quick Start -Install from [Github📥](https://hoothin.github.io/UserScripts/Picviewer%20CE+/Picviewer%20CE+.user.js) or [Greasefork📥](https://greasyfork.org/scripts/24204-picviewer-ce). +Install from [Github📥](https://hoothin.github.io/UserScripts/Picviewer%20CE+/dist.user.js) or [Greasefork📥](https://greasyfork.org/scripts/24204-picviewer-ce). Hover your mouse over any image and click the icons on the float bar. From 23014da270a40e1b170a3240b27a3b4c1cf7bc73 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:36:35 +0900 Subject: [PATCH 065/252] clean --- 91wii/91wii.user.js | 50 --------------------------------------------- 91wii/README.md | 1 - 2 files changed, 51 deletions(-) delete mode 100644 91wii/91wii.user.js delete mode 100644 91wii/README.md diff --git a/91wii/91wii.user.js b/91wii/91wii.user.js deleted file mode 100644 index b94c7d4fd79..00000000000 --- a/91wii/91wii.user.js +++ /dev/null @@ -1,50 +0,0 @@ -// ==UserScript== -// @name 91wii -// @namespace hoothin -// @version 0.6 -// @description 91wii签到 -// @author hoothin -// @match https://*.91wii.com/* -// @match https://*.91tvg.com/* -// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== -// @run-at document-idle -// @grant unsafeWindow -// ==/UserScript== - -(function() { - 'use strict'; - setTimeout(()=>{ - var formula = document.querySelector("b"),dcsignin = document.querySelector("#dcsignin_tips"); - if(formula && /^[\d\s\+-=?]+$/.test(formula.innerText.trim())){ - setTimeout(function(){ - document.querySelector("input[type=text]").value=eval(formula.innerText.match(/[\d\s\+-]+/)[0]); - document.querySelector("input[type=submit]").click(); - },10); - }else if(dcsignin){ - if(dcsignin.style.background.indexOf("signin_yes") != -1)return; - unsafeWindow.showWindow('sign', 'plugin.php?id=dc_signin:sign'); - var checkTimes=0; - let signInterval=setInterval(()=>{ - var signWin=document.querySelector("#fwin_sign"); - if(signWin){ - signWin.style.display="none"; - } - var dialogWin=document.querySelector("#fwin_dialog"); - if(dialogWin){ - dialogWin.style.display="none"; - } - let emotIndex=parseInt(Math.random() * 10) + 1; - if(document.querySelector("#emot_" + emotIndex)){ - clearInterval(signInterval); - document.querySelector("#emot_" + emotIndex).click(); - //document.querySelector('#content').value = '君子有四时,朝以听政,昼以访问,夕以修令,夜以安身。'; - document.querySelector("button[name=signpn]").click(); - unsafeWindow.hideWindow('sign'); - }else if(checkTimes++>15){ - clearInterval(signInterval); - unsafeWindow.hideWindow('sign'); - } - }, 100); - } - },2000); -})(); \ No newline at end of file diff --git a/91wii/README.md b/91wii/README.md deleted file mode 100644 index d7cb01b5315..00000000000 --- a/91wii/README.md +++ /dev/null @@ -1 +0,0 @@ -# 91wii 自动作答数学题,自动签到 From 4cdec0726ce7eef7c4bbccbb1189dc98c362bf7c Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:47:44 +0900 Subject: [PATCH 066/252] Update README.md --- Picviewer CE+/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index 2ff6f1da30d..113d217db68 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -206,7 +206,9 @@ Feel free to share your own custom rules on Greasy Fork! > *A blank gallery page designed for viewing local or online pictures, showcasing every image you have imported.* -You can drag and drop folders or videos/audios/images into this gallery to get an electronic slideshow to view them. +You can drag and drop **folders** or videos/audios/images into this gallery to get an electronic slideshow to view them. + +Click with `Ctrl key` can import folder too. Include `mode=`*`1`* to open gallery in view-more mode.
Add `imgs=`*`http://xxx/xxx.jpg`* to import images. ` ` to split multi-image, `[01-09]` to generate nine urls form 01 to 09
From ca65ddca001cdb23b03213fbf6aa8b2432311ad4 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 10:49:12 +0900 Subject: [PATCH 067/252] Update README.md --- RandomSexyPic/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RandomSexyPic/README.md b/RandomSexyPic/README.md index da0cf674153..74032e9065c 100644 --- a/RandomSexyPic/README.md +++ b/RandomSexyPic/README.md @@ -3,7 +3,7 @@ Random sexy pictures Browsing assistance for [Lolicon sexy pictures Api](https://api.lolicon.app/setu/v2?r18=1&num=5) --- -Can be used to preview pictures with any other JSON api.
Just open the api and click the "Parse current api" under command menu, and click again & cancel to disable.
+Can be used to preview pictures with **any other JSON api**.
Just open the api and click the "Parse current api" under command menu, and click again & cancel to disable.
For example: https://wall.alphacoders.com/api2.0/get.php?method=highest_rated&page=1&info_level=2&page=2 ![case1](case1.jpg)![case2](case2.jpg)![case3](case3.jpg)![case4](case4.jpg)![case5](case5.jpg) From 2c55c3737db786f3fe628e82590467bd7ca23983 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 18:16:51 +0900 Subject: [PATCH 068/252] Update README.md --- DownloadAllContent/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DownloadAllContent/README.md b/DownloadAllContent/README.md index 5ac83b0837a..74e08fc3948 100644 --- a/DownloadAllContent/README.md +++ b/DownloadAllContent/README.md @@ -9,6 +9,8 @@ [![img](https://img.shields.io/github/stars/hoothin/UserScripts?style=social)](https://github.com/hoothin/UserScripts#StarMe) ⭐[Star Me](https://github.com/hoothin/UserScripts#StarMe) +安裝: [Github📥](https://hoothin.github.io/UserScripts/DownloadAllContent/DownloadAllContent.user.js) / [Greasefork📥](https://greasyfork.org/scripts/25068). + --- # 操作說明 From 8a674a203192910678c5c38248375baa58d308b1 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 18:42:55 +0900 Subject: [PATCH 069/252] Update README.md --- Picviewer CE+/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index 113d217db68..bcf279a3eb4 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -144,10 +144,12 @@ Feel free to share your own custom rules on Greasy Fork!

Advance rule wizard

There are two types of rules available: + + JSON (simple mode) These rules are written in JSON format and can be imported online through [Discussions](https://github.com/hoothin/UserScripts/discussions) or [Reddit](https://www.reddit.com/r/PicviewerCE). They won't limited by websites that have a strict Content Security Policy that disallows unsafe-eval. + + JSON params - name @@ -189,9 +191,11 @@ Feel free to share your own custom rules on Greasy Fork! `"xhr": { "url": ".showcase__link", "query": "img[fetchpriority]" }` Fetch the link above the image that matches ".showcase__link" and query the "img[fetchpriority]" on the inner page from the link. + + JS (full mode) These rules are written in JavaScript object format. If you are not using a standalone userscript, they may be limited by websites that have a strict Content Security Policy that disallows unsafe-eval. + + JS params - all mentioned above and the function type instead of string type - getImage From 6dba7997e4d8725780e618aac834a233e2de16f1 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 18:48:09 +0900 Subject: [PATCH 070/252] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4a12fb47c4..cd32fd58fbb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

⚙️Greasemonkey scripts

+

⚙️Greasemonkey scripts

license From 90b0937b198d59010e2e0ed843a68d7ead5e694a Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 18:54:00 +0900 Subject: [PATCH 071/252] Update README.md --- Picviewer CE+/README.md | 132 +++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 62 deletions(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index bcf279a3eb4..313e4896d75 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -140,69 +140,77 @@ Feel free to share your own custom rules on Greasy Fork! })(); ``` -

-

Advance rule wizard

- - There are two types of rules available: - - + JSON (simple mode) - - These rules are written in JSON format and can be imported online through [Discussions](https://github.com/hoothin/UserScripts/discussions) or [Reddit](https://www.reddit.com/r/PicviewerCE). - They won't limited by websites that have a strict Content Security Policy that disallows unsafe-eval. - - + JSON params - - name - - `"name": "rule name"` - - Name of the rule - - url - - `"url": "^https://google\\.com"` - - Regular expression used to match the site URL. - - src - - `"src": "^https://image\\.xx\\.com"` - - Regular expression used to match the image src - - r - - `"r": "/(.*)\\d+/i"` or `"r": "thumb"` - - Simple string or regular expression used to replace the image src from - - s - - `"s": "$1"` - - Replace the image src to - - ext - - `"ext": "previous"` - - Capture nearby image element when the mouse hovers over a non-image element. - - lazyAttr - - `"lazyAttr": "data-lazy"` - - Lazy loaded original image URL attribute name - - xhr - - `"xhr": { "url": ".showcase__link", "query": "img[fetchpriority]" }` - - Fetch the link above the image that matches ".showcase__link" and query the "img[fetchpriority]" on the inner page from the link. - - + JS (full mode) - - These rules are written in JavaScript object format. If you are not using a standalone userscript, they may be limited by websites that have a strict Content Security Policy that disallows unsafe-eval. - - + JS params - - all mentioned above and the function type instead of string type - - getImage - - getExtSrc - - Learn from https://github.com/hoothin/UserScripts/blob/master/Picviewer%20CE%2B/pvcep_rules.js +
+

Advance rule wizard

+

There are two types of rules available:

+
    +
  • +

    JSON (simple mode)

    +

    These rules are written in JSON format and can be imported online through Discussions or Reddit. +They won't limited by websites that have a strict Content Security Policy that disallows unsafe-eval.

    +
      +
    • JSON params +
        +
      • +

        name

        +

        "name": "rule name"

        +

        Name of the rule

        +
      • +
      • +

        url

        +

        "url": "^https://google\\.com"

        +

        Regular expression used to match the site URL.

        +
      • +
      • +

        src

        +

        "src": "^https://image\\.xx\\.com"

        +

        Regular expression used to match the image src

        +
      • +
      • +

        r

        +

        "r": "/(.*)\\d+/i" or "r": "thumb"

        +

        Simple string or regular expression used to replace the image src from

        +
      • +
      • +

        s

        +

        "s": "$1"

        +

        Replace the image src to

        +
      • +
      • +

        ext

        +

        "ext": "previous"

        +

        Capture nearby image element when the mouse hovers over a non-image element.

        +
      • +
      • +

        lazyAttr

        +

        "lazyAttr": "data-lazy"

        +

        Lazy loaded original image URL attribute name

        +
      • +
      • +

        xhr

        +

        "xhr": { "url": ".showcase__link", "query": "img[fetchpriority]" }

        +

        Fetch the link above the image that matches ".showcase__link" and query the "img[fetchpriority]" on the inner page from the link.

        +
      • +
      +
    • +
    +
  • +
  • +

    JS (full mode)

    +

    These rules are written in JavaScript object format. If you are not using a standalone userscript, they may be limited by websites that have a strict Content Security Policy that disallows unsafe-eval.

    +
      +
    • JS params +
        +
      • all mentioned above and the function type instead of string type
      • +
      • getImage
      • +
      • getExtSrc
      • +
      +
    • +
    +

    Learn from https://github.com/hoothin/UserScripts/blob/master/Picviewer%20CE%2B/pvcep_rules.js

    +
  • +
## Blank Gallery Page From d54398c1e53ec865267e2fe9e3a5be6d723c19af Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 18:57:16 +0900 Subject: [PATCH 072/252] Update README.md --- Picviewer CE+/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index 313e4896d75..c15333f29cc 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -142,7 +142,7 @@ Feel free to share your own custom rules on Greasy Fork!
-

Advance rule wizard

+Advance rule wizard

There are two types of rules available:

  • From 87beff8bf775654b03f799553e9933e53e886470 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 18:59:56 +0900 Subject: [PATCH 073/252] Update README.md --- Picviewer CE+/README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index c15333f29cc..b3dd17cd2dd 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -49,17 +49,18 @@ If you are glad to assist with the translation, please ✍️[edit this file](ht ## PDF Addon Picviewer CE+ [PDF Addon](https://greasyfork.org/scripts/498445-picviewer-ce-pdf-addon). After installing this addon, when the `Compress to ZIP` feature is enabled, a PDF file will be generated instead of a ZIP file during the packaging process. -
    + +
    Make a PDF e-book with this addon - - For example, if there is a website with images from `xxx.com/1.jpg` to `xxx.com/99.jpg`, you can use this addon to generate a beautiful PDF e-book as follows: -1. Open the gallery by pressing Ctrl + g -2. In the `Command` menu, find and click `Add image` -3. Input `xxx.com/[1-99].jpg` -4. Right-click in the thumbnail frame below to ignore any unwanted images -5. Click `Download all shown` in the `Command` menu - -This way, you'll get a beautifully created PDF e-book. +

    For example, if there is a website with images from xxx.com/1.jpg to xxx.com/99.jpg, you can use this addon to generate a beautiful PDF e-book as follows:

    +
      +
    1. Open the gallery by pressing Ctrl + g
    2. +
    3. In the Command menu, find and click Add image
    4. +
    5. Input xxx.com/[1-99].jpg
    6. +
    7. Right-click in the thumbnail frame below to ignore any unwanted images
    8. +
    9. Click Download all shown in the Command menu
    10. +
    +

    This way, you'll get a beautifully created PDF e-book.

    ## Custom [Rules Example](pvcep_rules.js): From b4c7d82d0d2c7b512cb495c7cfb609d5bdaeefb8 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 19:02:51 +0900 Subject: [PATCH 074/252] Update README.md --- Picviewer CE+/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Picviewer CE+/README.md b/Picviewer CE+/README.md index b3dd17cd2dd..392a5418786 100644 --- a/Picviewer CE+/README.md +++ b/Picviewer CE+/README.md @@ -48,7 +48,7 @@ If you are glad to assist with the translation, please ✍️[edit this file](ht *Need more rules for peculiar sites? feel free to pull requests or open issues.* ## PDF Addon -Picviewer CE+ [PDF Addon](https://greasyfork.org/scripts/498445-picviewer-ce-pdf-addon). After installing this addon, when the `Compress to ZIP` feature is enabled, a PDF file will be generated instead of a ZIP file during the packaging process. +Picviewer CE+ [PDF Addon](https://hoothin.github.io/UserScripts/Picviewer%20CE+/pvcep_pdf_addon.user.js). After installing this addon, when the `Compress to ZIP` feature is enabled, a PDF file will be generated instead of a ZIP file during the packaging process.
    Make a PDF e-book with this addon From b42eb23d7dc775b1a073c4972f35eb870368902a Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 19:04:24 +0900 Subject: [PATCH 075/252] Update README.md --- DownloadAllContent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DownloadAllContent/README.md b/DownloadAllContent/README.md index 74e08fc3948..46df4e75dbd 100644 --- a/DownloadAllContent/README.md +++ b/DownloadAllContent/README.md @@ -25,7 +25,7 @@ ![donate](https://s2.loli.net/2023/02/06/afTMxeASm48z5vE.jpg) -[怠惰小説下載器 ZIP 擴充](https://greasyfork.org/scripts/476943) 下載時將各章節分別存為 TXT,並打包成 ZIP 檔 +[怠惰小説下載器 ZIP 擴充](https://hoothin.github.io/UserScripts/DownloadAllContent/DownloadAllContentSavaAsZIP.user.js) 下載時將各章節分別存為 TXT,並打包成 ZIP 檔 [圖片驗證碼辨識](https://github.com/hoothin/ImgCodeCheck) 開啟`保留內文圖片的網址`後配合 ZIP 擴充可自動轉換圖片文字,詳閱[愛發電](https://afdian.com/p/c7fc3abc8e8411ee9b1852540025c377) From 56d4b8909dae7eefb0a4f8d6b583cceb57473e58 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 7 Sep 2025 20:26:16 +0900 Subject: [PATCH 076/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 77b2308bb08..5d2e3b12a01 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -26218,7 +26218,7 @@ ImgOps | https://imgops.com/#b#`; GM_config.init({ id: 'pv-prefs', title: GM_config.create('a', { - href: 'https://greasyfork.org/scripts/24204-picviewer-ce', + href: 'https://hoothin.github.io/UserScripts/Picviewer%20CE%2B', target: '_blank', textContent: 'Picviewer CE+ '+i18n("config"), title: i18n("openHomePage") @@ -26940,7 +26940,8 @@ ImgOps | https://imgops.com/#b#`; node: "a", text: "Hoothin", attr: { - "href": "mailto:rixixi@gmail.com" + href: "https://www.hoothin.com/", + target: "_blank" } }, { From 8ef58be22c03cf351153194e9ce1c19e096bbd45 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 7 Sep 2025 11:26:31 +0000 Subject: [PATCH 077/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 259a54a3d0d..be91b33a8f1 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757208217 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757208217 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757208217 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757244391 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757244391 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757244391 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -26218,7 +26218,7 @@ ImgOps | https://imgops.com/#b#`; GM_config.init({ id: 'pv-prefs', title: GM_config.create('a', { - href: 'https://greasyfork.org/scripts/24204-picviewer-ce', + href: 'https://hoothin.github.io/UserScripts/Picviewer%20CE%2B', target: '_blank', textContent: 'Picviewer CE+ '+i18n("config"), title: i18n("openHomePage") @@ -26940,7 +26940,8 @@ ImgOps | https://imgops.com/#b#`; node: "a", text: "Hoothin", attr: { - "href": "mailto:rixixi@gmail.com" + href: "https://www.hoothin.com/", + target: "_blank" } }, { From 08f740e9c21c08b36c46fc61bfe58e364ad2f4ce Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 10 Sep 2025 21:04:41 +0900 Subject: [PATCH 078/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 39 +++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 5d2e3b12a01..462415137cd 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.9.5.2 +// @version 2025.9.10.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 @@ -12218,7 +12218,33 @@ ImgOps | https://imgops.com/#b#`; img.src = dataurl; } function urlToBlobWithFetch(urlString, cb){ - fetch(urlString).then(response => response.blob()).then(blob => cb(blob)).catch(error => { + fetch(urlString).then(response => response.blob()).then(blob => { + let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); + if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext === "none") ext = "webp"; + let conversion = formatDict.get(ext); + if (canvas && conversion) { + var self = this; + var a = new FileReader(); + a.readAsDataURL(blob); + a.onload = function (e) { + dataURLToCanvas(e.target.result, canvas => { + canvas.toBlob(nblob => { + if (!nblob) { + cb(blob); + } else { + cb(nblob, conversion || "png"); + } + }, "image/" + (conversion || "png")); + }); + }; + a.onerror = function (e){ + cb(blob); + } + } else { + cb(blob); + } + }).catch(error => { cb(null); }); } @@ -15756,9 +15782,10 @@ ImgOps | https://imgops.com/#b#`; var len = saveParams.length; function downloadOne(imgSrc, imgName, over){ let crosHandler = imgSrc => { - urlToBlob(imgSrc, blob=>{ + urlToBlob(imgSrc, (blob, ext)=>{ if (blob && blob.size>58) { let fileName = imgName.replace(/\//g, ""); + if (ext) fileName = fileName.replace(/\.\w+$/, "") + "." + ext; zip.file(fileName, blob); } else console.debug("error: "+imgSrc); downloaded++; @@ -15776,13 +15803,15 @@ ImgOps | https://imgops.com/#b#`; }); } if(canvas && (/^data:/.test(imgSrc) || imgSrc.split("/")[2] == document.domain)){ - urlToBlobWithFetch(imgSrc, blob=>{ + urlToBlobWithFetch(imgSrc, (blob, ext)=>{ if(!blob){ crosHandler(imgSrc); return; } self.showTips("Downloading "+(downloaded+1)+"/"+len, 1000000); - zip.file(imgName.replace(/^data:.*/, "img").replace(/\//g,""), blob); + let fileName = imgName.replace(/^data:.*/, "img").replace(/\//g,""); + if (ext) fileName = fileName.replace(/\.\w+$/, "") + "." + ext; + zip.file(fileName, blob); downloaded++; over && over(); if(downloaded == len){ From 1d601ee9601a8310912c9f07a21f84a331bf1192 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Wed, 10 Sep 2025 12:04:57 +0000 Subject: [PATCH 079/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 45 +++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index be91b33a8f1..62aa1bb71e6 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.9.5.2 +// @version 2025.9.10.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 @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757244391 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757244391 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757244391 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757505897 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757505897 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757505897 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -12218,7 +12218,33 @@ ImgOps | https://imgops.com/#b#`; img.src = dataurl; } function urlToBlobWithFetch(urlString, cb){ - fetch(urlString).then(response => response.blob()).then(blob => cb(blob)).catch(error => { + fetch(urlString).then(response => response.blob()).then(blob => { + let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); + if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext === "none") ext = "webp"; + let conversion = formatDict.get(ext); + if (canvas && conversion) { + var self = this; + var a = new FileReader(); + a.readAsDataURL(blob); + a.onload = function (e) { + dataURLToCanvas(e.target.result, canvas => { + canvas.toBlob(nblob => { + if (!nblob) { + cb(blob); + } else { + cb(nblob, conversion || "png"); + } + }, "image/" + (conversion || "png")); + }); + }; + a.onerror = function (e){ + cb(blob); + } + } else { + cb(blob); + } + }).catch(error => { cb(null); }); } @@ -15756,9 +15782,10 @@ ImgOps | https://imgops.com/#b#`; var len = saveParams.length; function downloadOne(imgSrc, imgName, over){ let crosHandler = imgSrc => { - urlToBlob(imgSrc, blob=>{ + urlToBlob(imgSrc, (blob, ext)=>{ if (blob && blob.size>58) { let fileName = imgName.replace(/\//g, ""); + if (ext) fileName = fileName.replace(/\.\w+$/, "") + "." + ext; zip.file(fileName, blob); } else console.debug("error: "+imgSrc); downloaded++; @@ -15776,13 +15803,15 @@ ImgOps | https://imgops.com/#b#`; }); } if(canvas && (/^data:/.test(imgSrc) || imgSrc.split("/")[2] == document.domain)){ - urlToBlobWithFetch(imgSrc, blob=>{ + urlToBlobWithFetch(imgSrc, (blob, ext)=>{ if(!blob){ crosHandler(imgSrc); return; } self.showTips("Downloading "+(downloaded+1)+"/"+len, 1000000); - zip.file(imgName.replace(/^data:.*/, "img").replace(/\//g,""), blob); + let fileName = imgName.replace(/^data:.*/, "img").replace(/\//g,""); + if (ext) fileName = fileName.replace(/\.\w+$/, "") + "." + ext; + zip.file(fileName, blob); downloaded++; over && over(); if(downloaded == len){ From 3056f84814c03717b769d6f4c9cc1b52d674258d Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 11 Sep 2025 22:35:14 +0900 Subject: [PATCH 080/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 462415137cd..7b6ae024385 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.9.10.1 +// @version 2025.9.11.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 @@ -23439,6 +23439,7 @@ ImgOps | https://imgops.com/#b#`; },3000); }, setPosition:function(){ + if (!this.data.img) return; var position=getContentClientRect(this.data.img); var cs=this.loadingAnim.style; var scrolled=getScrolled(); @@ -23572,7 +23573,7 @@ ImgOps | https://imgops.com/#b#`; }; var self=this; - function openInTop(){ + async function openInTop(){ var data=self.data; //删除不能发送的项。 @@ -23593,6 +23594,15 @@ ImgOps | https://imgops.com/#b#`; delCantClone(data); }; + if(!gallery){ + gallery=new GalleryC(); + gallery.data=[]; + } + let allData=(await gallery.getAllValidImgs()).map(item => item.src); + allData = allData.filter((item, index) => { + return allData.indexOf(item) === index; + }); + data.all = allData; window.postMessage({ messageID:messageID, src:img.src, From cbc827dbc624403417fc954e5631abfb74fdb2c0 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 11 Sep 2025 13:35:25 +0000 Subject: [PATCH 081/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 62aa1bb71e6..d664e6cbef1 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.9.10.1 +// @version 2025.9.11.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 @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757505897 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757505897 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757505897 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757597725 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757597725 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757597725 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -23439,6 +23439,7 @@ ImgOps | https://imgops.com/#b#`; },3000); }, setPosition:function(){ + if (!this.data.img) return; var position=getContentClientRect(this.data.img); var cs=this.loadingAnim.style; var scrolled=getScrolled(); @@ -23572,7 +23573,7 @@ ImgOps | https://imgops.com/#b#`; }; var self=this; - function openInTop(){ + async function openInTop(){ var data=self.data; //删除不能发送的项。 @@ -23593,6 +23594,15 @@ ImgOps | https://imgops.com/#b#`; delCantClone(data); }; + if(!gallery){ + gallery=new GalleryC(); + gallery.data=[]; + } + let allData=(await gallery.getAllValidImgs()).map(item => item.src); + allData = allData.filter((item, index) => { + return allData.indexOf(item) === index; + }); + data.all = allData; window.postMessage({ messageID:messageID, src:img.src, From b3eda9bc4438228b9937f0b23eea958035d2df2e Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 14 Sep 2025 22:57:03 +0900 Subject: [PATCH 082/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 54 +++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index 5d4053480f2..db9b72a7711 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -16,19 +16,59 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Replace require paths, add timestamp, and create dist file + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Terser + run: npm install terser -g + + - name: Build and Minify User Script run: | + set -e + TIMESTAMP=$(date +%s) + SOURCE_FILE="Picviewer CE+/Picviewer CE+.user.js" + DIST_FILE="Picviewer CE+/dist.user.js" + + TEMP_DIR=$(mktemp -d) + TEMP_PROCESSED_FILE="$TEMP_DIR/processed.user.js" + HEADER_FILE="$TEMP_DIR/header.txt" + BODY_FILE="$TEMP_DIR/body.js" + MINIFIED_BODY_FILE="$TEMP_DIR/body.min.js" + + echo "Step 1: Replacing paths and adding timestamp..." + sed -e "s#https://update.greasyfork.org/scripts/6158/[0-9]\+/GM_config%20CN.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/438080/[0-9]\+/pvcep_rules.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/440698/[0-9]\+/pvcep_lang.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ + "$SOURCE_FILE" > "$TEMP_PROCESSED_FILE" + echo "Path replacement complete." - sed -e "s#https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ - "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" + echo "Step 2: Separating UserScript header and body..." + sed -n '/\/\/ ==UserScript==/,/\/\/ ==\/UserScript==/p' "$TEMP_PROCESSED_FILE" > "$HEADER_FILE" + sed '1,/\/\/ ==\/UserScript==/d' "$TEMP_PROCESSED_FILE" > "$BODY_FILE" + echo "Header and body separated." + + echo "Step 3: Minifying the script body with Terser..." + terser "$BODY_FILE" --compress --mangle -o "$MINIFIED_BODY_FILE" + echo "Minification complete." + + echo "Step 4: Combining header and minified body..." + cat "$HEADER_FILE" > "$DIST_FILE" + echo "" >> "$DIST_FILE" + cat "$MINIFIED_BODY_FILE" >> "$DIST_FILE" + echo "Final dist file created at $DIST_FILE." - name: Commit and push dist.user.js run: | git config --local user.email "rixixi@gmail.com" git config --local user.name "hoothin-update" git add "Picviewer CE+/dist.user.js" - git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" - git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} \ No newline at end of file + if git diff --staged --quiet; then + echo "No changes to commit." + else + git commit -m "chore(Picviewer CE+): Auto-generate and minify dist.user.js" + git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} + fi + From 113d601eefac095ebfab0b6718b7660ac1d4e879 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 17 Sep 2025 20:14:06 +0900 Subject: [PATCH 083/252] 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 7b6ae024385..c62ff2313ee 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -21626,8 +21626,8 @@ ImgOps | https://imgops.com/#b#`; min-width:unset;\ max-height:unset;\ min-height:unset;\ - width:inherit;\ - height:inherit;\ + width:inherit!important;\ + height:inherit!important;\ padding:0;\ margin:0;\ border:none;\ From b76ffcb7072ddf65602db09b8566dffe9c3223bc Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 17 Sep 2025 20:19:34 +0900 Subject: [PATCH 084/252] Revert "Update build-dist.yml" This reverts commit b3eda9bc4438228b9937f0b23eea958035d2df2e. --- .github/workflows/build-dist.yml | 54 +++++--------------------------- 1 file changed, 7 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index db9b72a7711..5d4053480f2 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -16,59 +16,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install Terser - run: npm install terser -g - - - name: Build and Minify User Script + - name: Replace require paths, add timestamp, and create dist file run: | - set -e - TIMESTAMP=$(date +%s) - SOURCE_FILE="Picviewer CE+/Picviewer CE+.user.js" - DIST_FILE="Picviewer CE+/dist.user.js" - - TEMP_DIR=$(mktemp -d) - TEMP_PROCESSED_FILE="$TEMP_DIR/processed.user.js" - HEADER_FILE="$TEMP_DIR/header.txt" - BODY_FILE="$TEMP_DIR/body.js" - MINIFIED_BODY_FILE="$TEMP_DIR/body.min.js" - - echo "Step 1: Replacing paths and adding timestamp..." - sed -e "s#https://update.greasyfork.org/scripts/6158/[0-9]\+/GM_config%20CN.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/438080/[0-9]\+/pvcep_rules.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/440698/[0-9]\+/pvcep_lang.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ - "$SOURCE_FILE" > "$TEMP_PROCESSED_FILE" - echo "Path replacement complete." - echo "Step 2: Separating UserScript header and body..." - sed -n '/\/\/ ==UserScript==/,/\/\/ ==\/UserScript==/p' "$TEMP_PROCESSED_FILE" > "$HEADER_FILE" - sed '1,/\/\/ ==\/UserScript==/d' "$TEMP_PROCESSED_FILE" > "$BODY_FILE" - echo "Header and body separated." - - echo "Step 3: Minifying the script body with Terser..." - terser "$BODY_FILE" --compress --mangle -o "$MINIFIED_BODY_FILE" - echo "Minification complete." - - echo "Step 4: Combining header and minified body..." - cat "$HEADER_FILE" > "$DIST_FILE" - echo "" >> "$DIST_FILE" - cat "$MINIFIED_BODY_FILE" >> "$DIST_FILE" - echo "Final dist file created at $DIST_FILE." + sed -e "s#https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ + "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" - name: Commit and push dist.user.js run: | git config --local user.email "rixixi@gmail.com" git config --local user.name "hoothin-update" git add "Picviewer CE+/dist.user.js" - if git diff --staged --quiet; then - echo "No changes to commit." - else - git commit -m "chore(Picviewer CE+): Auto-generate and minify dist.user.js" - git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} - fi - + git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" + git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} \ No newline at end of file From 2b9d4708ac79073702856ca7e8393558da4c855a Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 18:15:47 +0900 Subject: [PATCH 085/252] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index 1681c198b16..61a294a9005 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1043,14 +1043,13 @@ var siteInfo = [ const re = /^https?:\/\/(?:www\.)?(?:(realbooru\.com|rule34\.xxx))\/index\.php\?page=post&s=view&id=(\d+).*/; const m = a.href.match(re); if (m) { - return m[1] == "rule34.xxx" ? `https://api.${m[1]}/index.php?page=dapi&s=post&q=index&id=${m[2]}&json=1` : `https://${m[1]}/index.php?page=dapi&s=post&q=index&id=${m[2]}&json=1`; + return a.href; } }, query: function(html, doc, url) { try { - const o = JSON.parse(html); - let url = o[0]; - return url.file_url || `https://${location.hostname}/images/${url.directory}/${url.image}`; + const o = doc.querySelector(".link-list>ul>li>a[href^=http]"); + return o && o.href; } catch { } } } From 14dbc3bce91adcc434ac40d2b51e3b14a4bb5b98 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 18:16:43 +0900 Subject: [PATCH 086/252] 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 c62ff2313ee..2f7610ddf7b 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.9.11.1 +// @version 2025.9.21.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/1655629/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 7b7b1611367661c9b198e09b0a8c39245c3143fc Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 21 Sep 2025 09:16:54 +0000 Subject: [PATCH 087/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index d664e6cbef1..5bf9939bc48 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.9.11.1 +// @version 2025.9.21.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 @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1757597725 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1757597725 -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1757597725 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758446213 +// @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758446213 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -21626,8 +21626,8 @@ ImgOps | https://imgops.com/#b#`; min-width:unset;\ max-height:unset;\ min-height:unset;\ - width:inherit;\ - height:inherit;\ + width:inherit!important;\ + height:inherit!important;\ padding:0;\ margin:0;\ border:none;\ From 00e21d9cc7496a2902c0d55a76b3f53cbc1a408f Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 19:32:04 +0900 Subject: [PATCH 088/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 2f7610ddf7b..2a4470fb7cb 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.9.21.1 +// @version 2025.9.21.2 // @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 @@ -15837,10 +15837,14 @@ ImgOps | https://imgops.com/#b#`; }, prefs.gallery.downloadGap); }; let threadNum = 10; + if (prefs.gallery.downloadGap > 500) { + threadNum = 1; + } else if (prefs.gallery.downloadGap > 100) { + threadNum = 5; + } let downIntv = setInterval(() => { - if (threadNum-- === 0) { + if (--threadNum === 0) { clearInterval(downIntv); - return; } let saveParam = saveParams && saveParams.shift(); if (!saveParam) clearInterval(downIntv); @@ -19847,6 +19851,9 @@ ImgOps | https://imgops.com/#b#`; GalleryC.prototype.Preload.prototype.container=div; }; this.max=prefs.gallery.max; + if (prefs.gallery.downloadGap > 500) { + this.max = 0; + } this.nextNumber=0; this.nextEle=this.ele; this.preNumber=0; From ae6c200cded606160611f51d668f3e2437c8710c Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 21 Sep 2025 10:32:16 +0000 Subject: [PATCH 089/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 5bf9939bc48..7924c3b8bb0 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.9.21.1 +// @version 2025.9.21.2 // @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 @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758446213 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758450736 // @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758446213 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758450736 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -15837,10 +15837,14 @@ ImgOps | https://imgops.com/#b#`; }, prefs.gallery.downloadGap); }; let threadNum = 10; + if (prefs.gallery.downloadGap > 500) { + threadNum = 1; + } else if (prefs.gallery.downloadGap > 100) { + threadNum = 5; + } let downIntv = setInterval(() => { - if (threadNum-- === 0) { + if (--threadNum === 0) { clearInterval(downIntv); - return; } let saveParam = saveParams && saveParams.shift(); if (!saveParam) clearInterval(downIntv); @@ -19847,6 +19851,9 @@ ImgOps | https://imgops.com/#b#`; GalleryC.prototype.Preload.prototype.container=div; }; this.max=prefs.gallery.max; + if (prefs.gallery.downloadGap > 500) { + this.max = 0; + } this.nextNumber=0; this.nextEle=this.ele; this.preNumber=0; From 3607242c9b7e8093c8b4ec526a01ad706ccb0b7d Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 19:40:48 +0900 Subject: [PATCH 090/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 2a4470fb7cb..88248a374cc 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12928,18 +12928,19 @@ ImgOps | https://imgops.com/#b#`; unsafeWindow.URL.createObjectURL = createObjectURLProxy; } - function downloadImg(url, name, type, errCb) { + function downloadImg(url, name, type, over) { urlToBlob(url, (blob, ext) => { if(blob){ try { saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); + over && over(); } catch(e) { _GM_download(url, name, type); - if (errCb) errCb(); + over && over(); } }else{ _GM_download(url, name, type); - if (errCb) errCb(); + over && over(); } }); } @@ -15857,9 +15858,14 @@ ImgOps | https://imgops.com/#b#`; } return; } - - let download5Times=function(){ - for(let i=0;i<5;i++){ + let threadNum = 10; + if (prefs.gallery.downloadGap > 500) { + threadNum = 1; + } else if (prefs.gallery.downloadGap > 100) { + threadNum = 5; + } + let downloadMulTimes=function(){ + for(let i=0;i0){ setTimeout(()=>{ - download5Times(); - },1000); + downloadMulTimes(); + },prefs.gallery.downloadGap); } }; - download5Times(); + downloadMulTimes(); }, changeMinView:function(){ var sizeInputH=this.sizeInputH; From 935bf368d1f3f474f1cde518abdd0e6fa046c693 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 20:18:05 +0900 Subject: [PATCH 091/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 44 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 88248a374cc..7b111a6a560 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -12220,7 +12220,7 @@ ImgOps | https://imgops.com/#b#`; function urlToBlobWithFetch(urlString, cb){ fetch(urlString).then(response => response.blob()).then(blob => { let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); - if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext && ext.indexOf("text/html") === 0 && (blob.size || 0) < 1000) return cb(null, ''); if (ext === "none") ext = "webp"; let conversion = formatDict.get(ext); if (canvas && conversion) { @@ -12416,7 +12416,10 @@ ImgOps | https://imgops.com/#b#`; let blob = d.response; if (!blob.type) return urlToBlob(url, cb, forcePng, tryTimes); let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); - if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext && ext.indexOf("text/html") === 0 && (blob.size || 0) < 100000) { + urlToBlobWithFetch(url, cb); + return; + } if (ext === "none") ext = "webp"; let conversion = formatDict.get(ext); if (canvas && (conversion || forcePng)) { @@ -12929,8 +12932,13 @@ ImgOps | https://imgops.com/#b#`; } function downloadImg(url, name, type, over) { - urlToBlob(url, (blob, ext) => { - if(blob){ + if(canvas && (/^data:/.test(url) || url.split("/")[2] == document.domain)){ + urlToBlobWithFetch(url, (blob, ext)=>{ + if(!blob){ + _GM_download(url, name, type); + over && over(); + return; + } try { saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); over && over(); @@ -12938,11 +12946,23 @@ ImgOps | https://imgops.com/#b#`; _GM_download(url, name, type); over && over(); } - }else{ - _GM_download(url, name, type); - over && over(); - } - }); + }); + } else { + urlToBlob(url, (blob, ext) => { + if(blob){ + try { + saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); + over && over(); + } catch(e) { + _GM_download(url, name, type); + over && over(); + } + }else{ + _GM_download(url, name, type); + over && over(); + } + }); + } } function getBlob(url) { @@ -14676,7 +14696,7 @@ ImgOps | https://imgops.com/#b#`; let xhr = dataset(node, 'xhr') !== 'stop' && self.getPropBySpanMark(node, "xhr"); if (xhr) { - self.showTips("Sending request..."); + self.showTips("Sending request...", 3000); await new Promise(resolve => { setTimeout(() => { let xhrError = function() { @@ -15506,6 +15526,7 @@ ImgOps | https://imgops.com/#b#`; saveIndex++; if (conItem.dataset.xhr) { + self.showTips("Sending request...", 3000); await new Promise((resolve) => { let getxhroverHandler = e => { conItem.removeEventListener('getxhrover', getxhroverHandler); @@ -15865,6 +15886,7 @@ ImgOps | https://imgops.com/#b#`; threadNum = 5; } let downloadMulTimes=function(){ + self.showTips("Downloading...", 3000); for(let i=0;i Date: Sun, 21 Sep 2025 11:18:20 +0000 Subject: [PATCH 092/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 70 ++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 7924c3b8bb0..85fb6dad1b1 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758450736 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758453500 // @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758450736 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758453500 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -12220,7 +12220,7 @@ ImgOps | https://imgops.com/#b#`; function urlToBlobWithFetch(urlString, cb){ fetch(urlString).then(response => response.blob()).then(blob => { let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); - if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext && ext.indexOf("text/html") === 0 && (blob.size || 0) < 1000) return cb(null, ''); if (ext === "none") ext = "webp"; let conversion = formatDict.get(ext); if (canvas && conversion) { @@ -12416,7 +12416,10 @@ ImgOps | https://imgops.com/#b#`; let blob = d.response; if (!blob.type) return urlToBlob(url, cb, forcePng, tryTimes); let ext = blob.type.replace(/.*image\/([\w\-]+).*/, "$1"); - if (ext === "text/html" && (blob.size || 0) < 1000) return cb(null, ''); + if (ext && ext.indexOf("text/html") === 0 && (blob.size || 0) < 100000) { + urlToBlobWithFetch(url, cb); + return; + } if (ext === "none") ext = "webp"; let conversion = formatDict.get(ext); if (canvas && (conversion || forcePng)) { @@ -12928,20 +12931,38 @@ ImgOps | https://imgops.com/#b#`; unsafeWindow.URL.createObjectURL = createObjectURLProxy; } - function downloadImg(url, name, type, errCb) { - urlToBlob(url, (blob, ext) => { - if(blob){ + function downloadImg(url, name, type, over) { + if(canvas && (/^data:/.test(url) || url.split("/")[2] == document.domain)){ + urlToBlobWithFetch(url, (blob, ext)=>{ + if(!blob){ + _GM_download(url, name, type); + over && over(); + return; + } try { saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); + over && over(); } catch(e) { _GM_download(url, name, type); - if (errCb) errCb(); + over && over(); } - }else{ - _GM_download(url, name, type); - if (errCb) errCb(); - } - }); + }); + } else { + urlToBlob(url, (blob, ext) => { + if(blob){ + try { + saveAs(blob, (prefs.saveNameAddTitle ? document.title.replace(/[\*\/:<>\?\\\|]/g, "") + " - " : "") + getRightSaveName(url, name, type, ext)); + over && over(); + } catch(e) { + _GM_download(url, name, type); + over && over(); + } + }else{ + _GM_download(url, name, type); + over && over(); + } + }); + } } function getBlob(url) { @@ -14675,7 +14696,7 @@ ImgOps | https://imgops.com/#b#`; let xhr = dataset(node, 'xhr') !== 'stop' && self.getPropBySpanMark(node, "xhr"); if (xhr) { - self.showTips("Sending request..."); + self.showTips("Sending request...", 3000); await new Promise(resolve => { setTimeout(() => { let xhrError = function() { @@ -15505,6 +15526,7 @@ ImgOps | https://imgops.com/#b#`; saveIndex++; if (conItem.dataset.xhr) { + self.showTips("Sending request...", 3000); await new Promise((resolve) => { let getxhroverHandler = e => { conItem.removeEventListener('getxhrover', getxhroverHandler); @@ -15857,9 +15879,15 @@ ImgOps | https://imgops.com/#b#`; } return; } - - let download5Times=function(){ - for(let i=0;i<5;i++){ + let threadNum = 10; + if (prefs.gallery.downloadGap > 500) { + threadNum = 1; + } else if (prefs.gallery.downloadGap > 100) { + threadNum = 5; + } + let downloadMulTimes=function(){ + self.showTips("Downloading...", 3000); + for(let i=0;i0){ setTimeout(()=>{ - download5Times(); - },1000); + downloadMulTimes(); + },prefs.gallery.downloadGap); } }; - download5Times(); + downloadMulTimes(); }, changeMinView:function(){ var sizeInputH=this.sizeInputH; @@ -16349,7 +16377,7 @@ ImgOps | https://imgops.com/#b#`; }; let xhr = dataset(node, 'xhr') !== 'stop' && self.getPropBySpanMark(node, "xhr"); if (xhr) { - self.showTips("Sending request..."); + self.showTips("Sending request...", 3000); let imgSrc = await getXhr(); if (imgSrc) { loadImg(); From 58f321512dcc4d31aff752e91ffda5754487257b Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 20:27:17 +0900 Subject: [PATCH 093/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 7b111a6a560..3237d1e04e5 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -16254,6 +16254,10 @@ ImgOps | https://imgops.com/#b#`; imgSpan.addEventListener('getxhr', getXhrHandler); } imgSpan.addEventListener("click", async function(e) { + if (maximizeContainer.classList.contains("checked")) { + imgSpan.querySelector("input").click(); + return; + } e.preventDefault(); self.selectViewmore(imgSpan, curNode.dataset.src); let loadError = e => { From a6775bab3c0001af3d3bd59ac1007389a33f8c88 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 21 Sep 2025 11:27:28 +0000 Subject: [PATCH 094/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 85fb6dad1b1..1608d33bb9c 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758453500 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758454047 // @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758453500 +// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758454047 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* @@ -16254,6 +16254,10 @@ ImgOps | https://imgops.com/#b#`; imgSpan.addEventListener('getxhr', getXhrHandler); } imgSpan.addEventListener("click", async function(e) { + if (maximizeContainer.classList.contains("checked")) { + imgSpan.querySelector("input").click(); + return; + } e.preventDefault(); self.selectViewmore(imgSpan, curNode.dataset.src); let loadError = e => { From d384b18d6c6ed482c22ef5ccec2fc07c2cd12988 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 21:05:22 +0900 Subject: [PATCH 095/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index 5d4053480f2..c2c383a628e 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -20,9 +20,9 @@ jobs: run: | TIMESTAMP=$(date +%s) - sed -e "s#https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ + sed -e "s#https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ + -e "s#https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" - name: Commit and push dist.user.js @@ -31,4 +31,4 @@ jobs: git config --local user.name "hoothin-update" git add "Picviewer CE+/dist.user.js" git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" - git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} \ No newline at end of file + git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} From b1614d46c21709eb3e6bad691f923ff4e1758330 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 21 Sep 2025 12:07:12 +0000 Subject: [PATCH 096/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- 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 1608d33bb9c..264424cb9ce 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758454047 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758456432 // @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js -// @require http://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758454047 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758456432 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* From bdad502664683a8ae26bdcb73bd181a13e602e85 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 21:11:47 +0900 Subject: [PATCH 097/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index c2c383a628e..f91248a171e 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -18,11 +18,9 @@ jobs: - name: Replace require paths, add timestamp, and create dist file run: | - TIMESTAMP=$(date +%s) - - sed -e "s#https://update.greasyfork.org/scripts/6158/23710/GM_config%20CN.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/438080/1655629/pvcep_rules.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=$TIMESTAMP#" \ - -e "s#https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=$TIMESTAMP#" \ + sed -e "s#https://update.greasyfork.org/scripts/6158/([0-9]+)/GM_config%20CN.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=\1#" \ + -e "s#https://update.greasyfork.org/scripts/438080/([0-9]+)/pvcep_rules.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=\1#" \ + -e "s#https://update.greasyfork.org/scripts/440698/([0-9]+)/pvcep_lang.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=\1#" \ "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" - name: Commit and push dist.user.js From adb12606ab1b4c5726d978fc5e465882a5704198 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 21:16:34 +0900 Subject: [PATCH 098/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index f91248a171e..4ad8cabf542 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -18,10 +18,11 @@ jobs: - name: Replace require paths, add timestamp, and create dist file run: | - sed -e "s#https://update.greasyfork.org/scripts/6158/([0-9]+)/GM_config%20CN.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=\1#" \ - -e "s#https://update.greasyfork.org/scripts/438080/([0-9]+)/pvcep_rules.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=\1#" \ - -e "s#https://update.greasyfork.org/scripts/440698/([0-9]+)/pvcep_lang.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=\1#" \ - "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" + sed -E \ + -e "s#https://update.greasyfork.org/scripts/6158/([0-9]+)/GM_config%20CN.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=\1#g" \ + -e "s#https://update.greasyfork.org/scripts/438080/([0-9]+)/pvcep_rules.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=\1#g" \ + -e "s#https://update.greasyfork.org/scripts/440698/([0-9]+)/pvcep_lang.js#https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=\1#g" \ + "Picviewer CE+/Picviewer CE+.user.js" > "Picviewer CE+/dist.user.js" - name: Commit and push dist.user.js run: | From e5914d4019de15cf0d31ed73ecff8398bf0645aa Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 21 Sep 2025 12:17:05 +0000 Subject: [PATCH 099/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- 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 264424cb9ce..fdcab2bebcb 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -45,9 +45,9 @@ // @grant GM.registerMenuCommand // @grant GM.notification // @grant unsafeWindow -// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/GM_config%20CN.js?v=1758456432 -// @require https://update.greasyfork.org/scripts/438080/1664500/pvcep_rules.js -// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1758456432 +// @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=1664500 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1653424 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* // @exclude http*://maps.google.com*/* From 724c844ef0e4eec007a57b2a73ecebfea5599fbd Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 21 Sep 2025 23:47:32 +0900 Subject: [PATCH 100/252] 1.9.37.124 --- Pagetual/README.md | 2 +- Pagetual/pagetual.user.js | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index d10465d01b4..c4fcad3d2c6 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.123](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.124](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 f9c24858c4f..2b03ef4a593 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.123 +// @version 1.9.37.124 // @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 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -5568,7 +5568,7 @@ posEle = posEle.previousElementSibling || posEle.parentNode; } if (posEle && posEle.lastElementChild) { - if (posEle.scrollHeight === 0) posEle = posEle.lastElementChild; + if (posEle.scrollHeight === 0 && posEle.lastElementChild.offsetParent) posEle = posEle.lastElementChild; else if (posEle.lastElementChild.offsetTop > 500) { posEle = posEle.lastElementChild; } @@ -7618,7 +7618,7 @@ text-shadow: rgb(255 255 255) 0px 0px 10px; } #pagetual-sideController #pagetual-sideController-pagenum { - font-size: 15px!important; + font-size: 12px!important; line-height: 30px; text-align: center; position: absolute; @@ -9950,6 +9950,12 @@ configCon.appendChild(blacklistBtn); configCon.appendChild(blacklistInput); let saveBtn = document.createElement("button"); + langSelect.onchange = e => { + saveBtn.click(); + setTimeout(() => { + location.reload(); + }, 500); + }; saveBtn.innerHTML = i18n("save"); saveBtn.id = "saveBtn"; configCon.appendChild(saveBtn); @@ -10328,7 +10334,9 @@ }, 100); } _GM_registerMenuCommand(i18n("configure"), () => { - _GM_openInTab(rulesData.configPage || configPage[0], {active: true}); + if (window.top == window.self) { + _GM_openInTab(rulesData.configPage || configPage[0], {active: true}); + } }); if (rulesData.blacklist && rulesData.blacklist.length > 0) { let href = location.href.slice(0, 500); @@ -10411,7 +10419,7 @@ rulesData.updateNotification = true; } if (typeof(rulesData.initRun) == "undefined") { - rulesData.initRun = true; + rulesData.initRun = false; } if (typeof(rulesData.preload) == "undefined") { rulesData.preload = false; @@ -10669,6 +10677,11 @@ if (!ruleParser.curSiteRule.smart || !xhrFailed) { xhrFailed = true; return callback(false); + } else { + if (!ruleParser.curSiteRule.sleep) { + ruleParser.curSiteRule.sleep = 1000; + return callback(false); + } } } if (inCors && (!pageElement || pageElement.length == 0) && ruleParser.curSiteRule.smart) { @@ -11800,7 +11813,7 @@ example = compareNodeName(example, ["tr", "tbody"]) ? example : example.nextElementSibling || example; if (compareNodeName(example, ["tbody"])) example = example.querySelector("tr"); let nextTr = example; - while (nextTr && nextTr.children.length == 0) nextTr = nextTr.nextElementSibling; + while (nextTr && (nextTr.children.length == 0 || (nextTr.children.length == 1 && !nextTr.children[0].offsetParent))) nextTr = nextTr.nextElementSibling; if (nextTr) example = nextTr; let tdNum = 0; if (exampleStyle.display == "table-row") { From 05b982fb608976fd4edd95a4c0d2ff3352ec84df Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 22 Sep 2025 17:27:23 +0900 Subject: [PATCH 101/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 60 +++++++++++++++++------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 3237d1e04e5..43ee1eda989 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.9.21.2 +// @version 2025.9.22.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 @@ -18193,7 +18193,7 @@ ImgOps | https://imgops.com/#b#`; total.push(node); } } else if (/^a$/i.test(node.nodeName)) { - if (imageReg.test(node.href)) { + if (node.href && node.href.length < 300 && imageReg.test(node.href)) { let src = node.href; if (/[&\?]url\=/.test(src)) { src = src.replace(/.*[&\?]url\=(.*?)(&.*|$)/, "$1"); @@ -18413,30 +18413,30 @@ ImgOps | https://imgops.com/#b#`; } scrollTarget = scrollTarget || document.documentElement; var self = this; - setTimeout(() => { - self.isScrollToEndAndReloading = false; - var des = document.documentElement.style; - des.overflow = ''; - document.head.appendChild(self.hideScrollStyle); - let scrollIntv = setInterval(function() { - let scrollTop = scrollTarget.scrollTop; - scrollTarget.scrollTop += 500; - if (scrollTop === scrollTarget.scrollTop) { - clearInterval(scrollIntv); - setTimeout(() => { - des.overflow = 'hidden'; - document.head.removeChild(self.hideScrollStyle); - }, 0); - clearTimeout(self.reloadTimeout); - self.reloadTimeout = setTimeout(function() { - self.reloadNew(); - self.loadThumb(); - }, 1000); - } else { + var des = document.documentElement.style; + des.overflow = ''; + document.head.appendChild(self.hideScrollStyle); + let scrollIntv = setInterval(function() { + let scrollTop = scrollTarget.scrollTop; + scrollTarget.scrollTop += 800; + if (scrollTop === scrollTarget.scrollTop) { + clearInterval(scrollIntv); + self.isScrollToEndAndReloading = false; + setTimeout(() => { + des.overflow = 'hidden'; + document.head.removeChild(self.hideScrollStyle); + }, 0); + if (self.lastScrollTop == scrollTop) return; + clearTimeout(self.reloadTimeout); + self.reloadTimeout = setTimeout(function() { self.reloadNew(); - } - }, 1); - }, 300); + self.loadThumb(); + self.lastScrollTop = scrollTop; + }, 300); + } else { + self.reloadNew(); + } + }, 150); }, exportImages: function () {// 导出所有图片到新窗口 var nodes = this.eleMaps['sidebar-thumbnails-container'].querySelectorAll('.pv-gallery-sidebar-thumb-container[data-src]:not(.ignore)'),i; @@ -19483,6 +19483,16 @@ ImgOps | https://imgops.com/#b#`; .pv-gallery-maximize-container.checked span>.pv-top-banner{\ opacity: 0.6;\ }\ + .pv-gallery-maximize-container+p>input{\ + width:min-content;\ + border: 1px solid transparent;\ + border-bottom-color: #a3a3a3;\ + }\ + .pv-gallery-maximize-container+p>input:hover{\ + border-color: white;\ + background: #333;\ + color: white;\ + }\ .pv-gallery-maximize-container+p>input.compareBtn{\ display: none;\ }\ From a60962cde0d8372bfcc92ff73364e39f159f007d Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Mon, 22 Sep 2025 08:27:34 +0000 Subject: [PATCH 102/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 60 ++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index fdcab2bebcb..6575a5bbcaf 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.9.21.2 +// @version 2025.9.22.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 @@ -18193,7 +18193,7 @@ ImgOps | https://imgops.com/#b#`; total.push(node); } } else if (/^a$/i.test(node.nodeName)) { - if (imageReg.test(node.href)) { + if (node.href && node.href.length < 300 && imageReg.test(node.href)) { let src = node.href; if (/[&\?]url\=/.test(src)) { src = src.replace(/.*[&\?]url\=(.*?)(&.*|$)/, "$1"); @@ -18413,30 +18413,30 @@ ImgOps | https://imgops.com/#b#`; } scrollTarget = scrollTarget || document.documentElement; var self = this; - setTimeout(() => { - self.isScrollToEndAndReloading = false; - var des = document.documentElement.style; - des.overflow = ''; - document.head.appendChild(self.hideScrollStyle); - let scrollIntv = setInterval(function() { - let scrollTop = scrollTarget.scrollTop; - scrollTarget.scrollTop += 500; - if (scrollTop === scrollTarget.scrollTop) { - clearInterval(scrollIntv); - setTimeout(() => { - des.overflow = 'hidden'; - document.head.removeChild(self.hideScrollStyle); - }, 0); - clearTimeout(self.reloadTimeout); - self.reloadTimeout = setTimeout(function() { - self.reloadNew(); - self.loadThumb(); - }, 1000); - } else { + var des = document.documentElement.style; + des.overflow = ''; + document.head.appendChild(self.hideScrollStyle); + let scrollIntv = setInterval(function() { + let scrollTop = scrollTarget.scrollTop; + scrollTarget.scrollTop += 800; + if (scrollTop === scrollTarget.scrollTop) { + clearInterval(scrollIntv); + self.isScrollToEndAndReloading = false; + setTimeout(() => { + des.overflow = 'hidden'; + document.head.removeChild(self.hideScrollStyle); + }, 0); + if (self.lastScrollTop == scrollTop) return; + clearTimeout(self.reloadTimeout); + self.reloadTimeout = setTimeout(function() { self.reloadNew(); - } - }, 1); - }, 300); + self.loadThumb(); + self.lastScrollTop = scrollTop; + }, 300); + } else { + self.reloadNew(); + } + }, 150); }, exportImages: function () {// 导出所有图片到新窗口 var nodes = this.eleMaps['sidebar-thumbnails-container'].querySelectorAll('.pv-gallery-sidebar-thumb-container[data-src]:not(.ignore)'),i; @@ -19483,6 +19483,16 @@ ImgOps | https://imgops.com/#b#`; .pv-gallery-maximize-container.checked span>.pv-top-banner{\ opacity: 0.6;\ }\ + .pv-gallery-maximize-container+p>input{\ + width:min-content;\ + border: 1px solid transparent;\ + border-bottom-color: #a3a3a3;\ + }\ + .pv-gallery-maximize-container+p>input:hover{\ + border-color: white;\ + background: #333;\ + color: white;\ + }\ .pv-gallery-maximize-container+p>input.compareBtn{\ display: none;\ }\ From bb90de1412c61738ef876e3eb0ba5a19437ea0c8 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 24 Sep 2025 16:55:13 +0900 Subject: [PATCH 103/252] Update flashViewer.user.js --- FlashViewer-HTML5 Video/flashViewer.user.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/FlashViewer-HTML5 Video/flashViewer.user.js b/FlashViewer-HTML5 Video/flashViewer.user.js index f6d953c7643..86a8f8e6620 100644 --- a/FlashViewer-HTML5 Video/flashViewer.user.js +++ b/FlashViewer-HTML5 Video/flashViewer.user.js @@ -2,9 +2,9 @@ // @name flashViewer // @author NLF & Hoothin // @description 围观Flash,增加 HTML5 视频速度与亮度调整 -// @version 1.2.1.8 +// @version 1.2.1.9 // @created 2013-12-27 -// @lastUpdated 2024-8-10 +// @lastUpdated 2025-9-24 // @grant none // @run-at document-start // @namespace http://userscripts.org/users/NLF @@ -1555,6 +1555,7 @@ minHeight: 150, scale: function (e) { + if (this.pinned || this.maximized) return; if (e.deltaY > 0) { this.zoomLevel += -0.1; if (this.zoomLevel > 3) this.zoomLevel = 1; @@ -1635,7 +1636,7 @@ var vNodeName = video.nodeName; // 如果是这些元素,那么pin的时候直接用fixed方式(这些元素随便调整position不会引发重载) - var fixedPin = /^(?:IFRAME|VIDEO|AUDIO)$/.test(vNodeName); + var fixedPin = /^(?:IFRAME|VIDEO|AUDIO|CANVAS)$/.test(vNodeName); this.fixedPin = fixedPin; video.fvPopVideo = true;// 标记弹出中。 @@ -3843,7 +3844,7 @@ } // 可弹出元素 - const availableNode = /^(?:OBJECT|EMBED|VIDEO|AUDIO|IFRAME)$/i; + const availableNode = /^(?:OBJECT|EMBED|VIDEO|AUDIO|IFRAME|CANVAS)$/i; if (!availableNode.test(tNName)) { target = null; if (document.elementsFromPoint) { From 464b3876a353a536ed0b048f0fe0064dd4af45a9 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 24 Sep 2025 17:01:20 +0900 Subject: [PATCH 104/252] Update flashViewer.user.js --- FlashViewer-HTML5 Video/flashViewer.user.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FlashViewer-HTML5 Video/flashViewer.user.js b/FlashViewer-HTML5 Video/flashViewer.user.js index 86a8f8e6620..e3f508f5e90 100644 --- a/FlashViewer-HTML5 Video/flashViewer.user.js +++ b/FlashViewer-HTML5 Video/flashViewer.user.js @@ -1642,7 +1642,9 @@ video.fvPopVideo = true;// 标记弹出中。 this.zoomLevel = 1; video.addEventListener("wheel", this.scale.bind(this)); - video.addEventListener("mousedown", this.videoMouseDown.bind(this), true); + if (vNodeName === "VIDEO") { + video.addEventListener("mousedown", this.videoMouseDown.bind(this), true); + } // 很多网站加载flash为了兼容现代浏览器和ie,经常使用 object classid嵌套object或者embed的格式 var vPEIsObject; From 5e390a40d74e87d594b7da67d0c0f5550bbea25d Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 24 Sep 2025 17:04:32 +0900 Subject: [PATCH 105/252] Update README.md --- FlashViewer-HTML5 Video/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlashViewer-HTML5 Video/README.md b/FlashViewer-HTML5 Video/README.md index f066f20599a..75131a452b8 100644 --- a/FlashViewer-HTML5 Video/README.md +++ b/FlashViewer-HTML5 Video/README.md @@ -1,4 +1,4 @@ -# [FlashViewer](https://github.com/hoothin/UserScripts/raw/master/FlashViewer-HTML5%20Video/flashViewer.user.js) +# [FlashViewer](https://hoothin.github.io/UserScripts/FlashViewer-HTML5%20Video/flashViewer.user.js) HTML5 視頻增強腳本 原作者 NLF From 77499da0414709254b705b89cb46908a9871ee2e Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 24 Sep 2025 21:18:04 +0900 Subject: [PATCH 106/252] Update README.md --- FlashViewer-HTML5 Video/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlashViewer-HTML5 Video/README.md b/FlashViewer-HTML5 Video/README.md index 75131a452b8..94771d645c5 100644 --- a/FlashViewer-HTML5 Video/README.md +++ b/FlashViewer-HTML5 Video/README.md @@ -1,4 +1,4 @@ -# [FlashViewer](https://hoothin.github.io/UserScripts/FlashViewer-HTML5%20Video/flashViewer.user.js) +# [FlashViewer⬇️](https://hoothin.github.io/UserScripts/FlashViewer-HTML5%20Video/flashViewer.user.js) HTML5 視頻增強腳本 原作者 NLF From 4735f1b09aa0fe59846df49d3642e600342d623c Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 25 Sep 2025 22:50:00 +0900 Subject: [PATCH 107/252] 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 61a294a9005..fa5d76083e6 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -1073,7 +1073,7 @@ var siteInfo = [ } return target; }, - r: [/\/w\/\d+\/(h\/\d+\/)?(q\/\d+\/)?/i, /.*\.xhscdn\.com.*\/(\w+)(!.*|$)/i], + r: [/\/w\/\d+\/(h\/\d+\/)?(q\/\d+\/)?/i, /.*\.xhscdn\.com.*\d\/(\w+)(!.*|$)/i], s: ["/w/1080/", "https://sns-img-bd.xhscdn.com/$1"] }, { From bd4e0606f90960514e96caa72c7df1e578f5d94f Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 25 Sep 2025 22:50:59 +0900 Subject: [PATCH 108/252] 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 43ee1eda989..fec4f2ded3d 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.9.22.1 +// @version 2025.9.25.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/1664500/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1666736/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 5e4a9c1982b1f7777f76a61ba6406e614c64666f Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 25 Sep 2025 13:51:12 +0000 Subject: [PATCH 109/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- 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 6575a5bbcaf..b32f1490479 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.9.22.1 +// @version 2025.9.25.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=1664500 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1666736 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1653424 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* From 5da90838ebc61fd327ee7b06835cd7cec1e3cfbe Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 26 Sep 2025 15:45:35 +0900 Subject: [PATCH 110/252] 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 fec4f2ded3d..b2144a1a6c9 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.9.25.1 +// @version 2025.9.26.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 @@ -22031,7 +22031,7 @@ ImgOps | https://imgops.com/#b#`; if (!imme && this.following) return; let wSize = getWindowSize(); - let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, (this.img.naturalWidth || 500)>>1, (this.img.naturalHeight || 500)>>1), padding2 = 50, left, top;//内外侧间距 + let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 100)>>2), padding2 = 50, left, top;//内外侧间距 imgWindow.style.position = "fixed"; let scrolled = {x: 0, y: 0}; From 1ca7e08fb05054e6689171927daeae25bb236834 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Fri, 26 Sep 2025 06:45:57 +0000 Subject: [PATCH 111/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- 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 b32f1490479..314ff647062 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.9.25.1 +// @version 2025.9.26.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 @@ -22031,7 +22031,7 @@ ImgOps | https://imgops.com/#b#`; if (!imme && this.following) return; let wSize = getWindowSize(); - let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, (this.img.naturalWidth || 500)>>1, (this.img.naturalHeight || 500)>>1), padding2 = 50, left, top;//内外侧间距 + let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 100)>>2), padding2 = 50, left, top;//内外侧间距 imgWindow.style.position = "fixed"; let scrolled = {x: 0, y: 0}; From 98e9e98aec23832e3735119dab33f24b8740fb24 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 27 Sep 2025 09:24:34 +0900 Subject: [PATCH 112/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index b2144a1a6c9..2807b7e71f9 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.9.26.1 +// @version 2025.9.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 @@ -22031,7 +22031,7 @@ ImgOps | https://imgops.com/#b#`; if (!imme && this.following) return; let wSize = getWindowSize(); - let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 100)>>2), padding2 = 50, left, top;//内外侧间距 + let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 50)>>1), padding2 = 50, left, top;//内外侧间距 imgWindow.style.position = "fixed"; let scrolled = {x: 0, y: 0}; @@ -25584,6 +25584,7 @@ ImgOps | https://imgops.com/#b#`; let checkEle = target; while(checkEle && !(checkEle.textContent && checkEle.textContent.trim()) && checkEle.children.length === 1) { checkEle = checkEle.children[0]; + if (target.clientWidth && target.clientWidth > checkEle.clientWidth * 1.2) break; if (checkEle.nodeName === "IMG") { target = checkEle; found = true; @@ -26229,6 +26230,7 @@ ImgOps | https://imgops.com/#b#`; if (e.relatedTarget == ImgWindowC.overlayer) return; if(uniqueImgWin && !uniqueImgWin.removed){ if(checkPreview(e)){ + if (!uniqueImgWin.data.img) return uniqueImgWin && uniqueImgWin.remove(); let showArea=uniqueImgWin.data.img.getBoundingClientRect(); if(e.clientX < showArea.left + 20 || e.clientX > showArea.right - 20 || From 8d46a9eb565e53652c0a62f1b966019d49985f5d Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sat, 27 Sep 2025 00:24:45 +0000 Subject: [PATCH 113/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 314ff647062..29cef4f8836 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.9.26.1 +// @version 2025.9.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 @@ -22031,7 +22031,7 @@ ImgOps | https://imgops.com/#b#`; if (!imme && this.following) return; let wSize = getWindowSize(); - let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 100)>>2), padding2 = 50, left, top;//内外侧间距 + let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 50)>>1), padding2 = 50, left, top;//内外侧间距 imgWindow.style.position = "fixed"; let scrolled = {x: 0, y: 0}; @@ -25584,6 +25584,7 @@ ImgOps | https://imgops.com/#b#`; let checkEle = target; while(checkEle && !(checkEle.textContent && checkEle.textContent.trim()) && checkEle.children.length === 1) { checkEle = checkEle.children[0]; + if (target.clientWidth && target.clientWidth > checkEle.clientWidth * 1.2) break; if (checkEle.nodeName === "IMG") { target = checkEle; found = true; @@ -26229,6 +26230,7 @@ ImgOps | https://imgops.com/#b#`; if (e.relatedTarget == ImgWindowC.overlayer) return; if(uniqueImgWin && !uniqueImgWin.removed){ if(checkPreview(e)){ + if (!uniqueImgWin.data.img) return uniqueImgWin && uniqueImgWin.remove(); let showArea=uniqueImgWin.data.img.getBoundingClientRect(); if(e.clientX < showArea.left + 20 || e.clientX > showArea.right - 20 || From 604b7b76b954c63d7c24dceeba0276e4590a90d4 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 27 Sep 2025 10:01:46 +0900 Subject: [PATCH 114/252] 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 2807b7e71f9..7d55dbb9832 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -27303,6 +27303,7 @@ ImgOps | https://imgops.com/#b#`; } function openPrefs() { + if (window.top != window.self) return; let fieldsSearchData = GM_config.fields["gallery.searchData"]; if (fieldsSearchData && fieldsSearchData.value) { fieldsSearchData.value = fieldsSearchData.value.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&").replace(/>/g, ">").replace(/ Date: Sat, 27 Sep 2025 01:01:58 +0000 Subject: [PATCH 115/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- 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 29cef4f8836..fddce2949fe 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -27303,6 +27303,7 @@ ImgOps | https://imgops.com/#b#`; } function openPrefs() { + if (window.top != window.self) return; let fieldsSearchData = GM_config.fields["gallery.searchData"]; if (fieldsSearchData && fieldsSearchData.value) { fieldsSearchData.value = fieldsSearchData.value.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&").replace(/>/g, ">").replace(/ Date: Sat, 27 Sep 2025 21:01:43 +0900 Subject: [PATCH 116/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 7d55dbb9832..e806eaf5b7b 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -22031,7 +22031,9 @@ ImgOps | https://imgops.com/#b#`; if (!imme && this.following) return; let wSize = getWindowSize(); - let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 50)>>1), padding2 = 50, left, top;//内外侧间距 + let paddingW = Math.min(250, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, 50)>>1); + let paddingH = Math.min(250, wSize.h>>2, Math.max(this.data.img && this.data.img.clientHeight, 50)>>1); + let padding2 = 50, left, top;//内外侧间距 imgWindow.style.position = "fixed"; let scrolled = {x: 0, y: 0}; @@ -22041,18 +22043,18 @@ ImgOps | https://imgops.com/#b#`; //宽条,上下半屏 if (posY > wSize.h / 2) { //上 - top = posY - imgWindow.offsetHeight - padding1 + scrolled.y; + top = posY - imgWindow.offsetHeight - paddingH + scrolled.y; if (top < padding2>>1) top = padding2>>1; } else { //下 - top = posY + padding1 + scrolled.y; + top = posY + paddingH + scrolled.y; if (top > wSize.h - imgWindow.offsetHeight - 1) top = wSize.h - imgWindow.offsetHeight - 1; } left = (wSize.w - imgWindow.offsetWidth) / 2; - let maxLeft = posX + padding1; + let maxLeft = posX + paddingW; if (left > maxLeft) left = maxLeft; else { - let minLeft = posX - imgWindow.offsetWidth - padding1; + let minLeft = posX - imgWindow.offsetWidth - paddingW; if (left < minLeft) left = minLeft; } left = left + scrolled.x; @@ -22060,18 +22062,18 @@ ImgOps | https://imgops.com/#b#`; //窄条,左右半屏 if (posX > wSize.w / 2) { //左 - left = posX - imgWindow.offsetWidth - padding1 + scrolled.x; + left = posX - imgWindow.offsetWidth - paddingW + scrolled.x; if (left < 1) left = 1; } else { //右 - left = posX + padding1 + scrolled.x; + left = posX + paddingW + scrolled.x; if (left > wSize.w - imgWindow.offsetWidth - 1) left = wSize.w - imgWindow.offsetWidth - 1; } top = (wSize.h - imgWindow.offsetHeight) / 2; - let maxTop = posY + padding1; + let maxTop = posY + paddingH; if (top > maxTop) top = maxTop; else { - let minTop = posY - imgWindow.offsetHeight - padding1; + let minTop = posY - imgWindow.offsetHeight - paddingH; if (top < minTop) top = minTop; } top = top + scrolled.y; From 0f0ca61da0b98f04070f04a8103a83a87927460e Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sat, 27 Sep 2025 12:25:15 +0000 Subject: [PATCH 117/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index fddce2949fe..c31d2231f6e 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -22031,7 +22031,9 @@ ImgOps | https://imgops.com/#b#`; if (!imme && this.following) return; let wSize = getWindowSize(); - let padding1 = Math.min(250, wSize.h>>2, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, this.data.img && this.data.img.clientHeight, 50)>>1), padding2 = 50, left, top;//内外侧间距 + let paddingW = Math.min(250, wSize.w>>2, Math.max(this.data.img && this.data.img.clientWidth, 50)>>1); + let paddingH = Math.min(250, wSize.h>>2, Math.max(this.data.img && this.data.img.clientHeight, 50)>>1); + let padding2 = 50, left, top;//内外侧间距 imgWindow.style.position = "fixed"; let scrolled = {x: 0, y: 0}; @@ -22041,18 +22043,18 @@ ImgOps | https://imgops.com/#b#`; //宽条,上下半屏 if (posY > wSize.h / 2) { //上 - top = posY - imgWindow.offsetHeight - padding1 + scrolled.y; + top = posY - imgWindow.offsetHeight - paddingH + scrolled.y; if (top < padding2>>1) top = padding2>>1; } else { //下 - top = posY + padding1 + scrolled.y; + top = posY + paddingH + scrolled.y; if (top > wSize.h - imgWindow.offsetHeight - 1) top = wSize.h - imgWindow.offsetHeight - 1; } left = (wSize.w - imgWindow.offsetWidth) / 2; - let maxLeft = posX + padding1; + let maxLeft = posX + paddingW; if (left > maxLeft) left = maxLeft; else { - let minLeft = posX - imgWindow.offsetWidth - padding1; + let minLeft = posX - imgWindow.offsetWidth - paddingW; if (left < minLeft) left = minLeft; } left = left + scrolled.x; @@ -22060,18 +22062,18 @@ ImgOps | https://imgops.com/#b#`; //窄条,左右半屏 if (posX > wSize.w / 2) { //左 - left = posX - imgWindow.offsetWidth - padding1 + scrolled.x; + left = posX - imgWindow.offsetWidth - paddingW + scrolled.x; if (left < 1) left = 1; } else { //右 - left = posX + padding1 + scrolled.x; + left = posX + paddingW + scrolled.x; if (left > wSize.w - imgWindow.offsetWidth - 1) left = wSize.w - imgWindow.offsetWidth - 1; } top = (wSize.h - imgWindow.offsetHeight) / 2; - let maxTop = posY + padding1; + let maxTop = posY + paddingH; if (top > maxTop) top = maxTop; else { - let minTop = posY - imgWindow.offsetHeight - padding1; + let minTop = posY - imgWindow.offsetHeight - paddingH; if (top < minTop) top = minTop; } top = top + scrolled.y; From ebc155ffbca3dd67399947ab05bea21bee95b3f5 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 28 Sep 2025 12:53:33 +0900 Subject: [PATCH 118/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index e806eaf5b7b..2e3f1b24cb7 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.9.27.1 +// @version 2025.9.27.2 // @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 @@ -25861,11 +25861,17 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false; + var checkFloatBarTimer, initMouse = false, lastEvent; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; let canPreview = checkPreview(e); + if (e.type == "keydown") { + if (!lastEvent) return; + e = lastEvent; + } else { + lastEvent = e; + } if (e.type == "mousemove") { if (!initMouse) { initMouse = true; @@ -26140,6 +26146,7 @@ ImgOps | https://imgops.com/#b#`; return false; } + let keypressing = false; function keydown(event) { //if (ImgWindowC.showing) return; @@ -26149,6 +26156,10 @@ ImgOps | https://imgops.com/#b#`; } var key = event.key; if(checkGlobalKeydown(event)){ + if (!keypressing) { + globalMouseoverHandler(event); + keypressing = true; + } if(prefs.floatBar.keys.enable && key==prefs.floatBar.keys.gallery){ openGallery(); event.stopPropagation(); @@ -26165,7 +26176,9 @@ ImgOps | https://imgops.com/#b#`; } if (event) { - if (event.ctrlKey || event.metaKey) return false; + if (event.ctrlKey || event.metaKey) { + return false; + } if (window.getSelection().toString()) return false; } if (floatBar && isKeyDownEffectiveTarget(event.target)) { @@ -26180,6 +26193,7 @@ ImgOps | https://imgops.com/#b#`; } function keyup(event) { + keypressing = false; let isFuncKey = !event.isTrusted || event.key == 'Alt' || event.key == 'Control' || event.key == 'Meta'; if(isFuncKey && (prefs.floatBar.globalkeys.type == "hold" || !checkPreview(event)) && (uniqueImgWin && !uniqueImgWin.removed)){ clearTimeout(checkFloatBarTimer); From b577523d8e4f5c1b9db137f360719e95a220b61f Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 28 Sep 2025 03:53:54 +0000 Subject: [PATCH 119/252] chore(Picviewer CE+): Auto-generate dist.user.js with timestamp --- Picviewer CE+/dist.user.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index c31d2231f6e..1a58b0f785d 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.9.27.1 +// @version 2025.9.27.2 // @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 @@ -25861,11 +25861,17 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false; + var checkFloatBarTimer, initMouse = false, lastEvent; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; let canPreview = checkPreview(e); + if (e.type == "keydown") { + if (!lastEvent) return; + e = lastEvent; + } else { + lastEvent = e; + } if (e.type == "mousemove") { if (!initMouse) { initMouse = true; @@ -26140,6 +26146,7 @@ ImgOps | https://imgops.com/#b#`; return false; } + let keypressing = false; function keydown(event) { //if (ImgWindowC.showing) return; @@ -26149,6 +26156,10 @@ ImgOps | https://imgops.com/#b#`; } var key = event.key; if(checkGlobalKeydown(event)){ + if (!keypressing) { + globalMouseoverHandler(event); + keypressing = true; + } if(prefs.floatBar.keys.enable && key==prefs.floatBar.keys.gallery){ openGallery(); event.stopPropagation(); @@ -26165,7 +26176,9 @@ ImgOps | https://imgops.com/#b#`; } if (event) { - if (event.ctrlKey || event.metaKey) return false; + if (event.ctrlKey || event.metaKey) { + return false; + } if (window.getSelection().toString()) return false; } if (floatBar && isKeyDownEffectiveTarget(event.target)) { @@ -26180,6 +26193,7 @@ ImgOps | https://imgops.com/#b#`; } function keyup(event) { + keypressing = false; let isFuncKey = !event.isTrusted || event.key == 'Alt' || event.key == 'Control' || event.key == 'Meta'; if(isFuncKey && (prefs.floatBar.globalkeys.type == "hold" || !checkPreview(event)) && (uniqueImgWin && !uniqueImgWin.removed)){ clearTimeout(checkFloatBarTimer); From e11141b969e2253941ff7dad007ce34ee87ce862 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 29 Sep 2025 11:43:34 +0900 Subject: [PATCH 120/252] Update build-dist.yml --- .github/workflows/build-dist.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dist.yml b/.github/workflows/build-dist.yml index 4ad8cabf542..ef4f9880fb6 100644 --- a/.github/workflows/build-dist.yml +++ b/.github/workflows/build-dist.yml @@ -29,5 +29,5 @@ jobs: git config --local user.email "rixixi@gmail.com" git config --local user.name "hoothin-update" git add "Picviewer CE+/dist.user.js" - git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js with timestamp" + git commit -m "chore(Picviewer CE+): Auto-generate dist.user.js" git push https://${{ secrets.ACTION_SECRET }}@github.com/${{ github.repository }} From 2904f7df6b3c248e19dbecb6ed0517410a9ff520 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 30 Sep 2025 16:39:52 +0900 Subject: [PATCH 121/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 2b03ef4a593..cde5111054d 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -6823,11 +6823,15 @@ }); self.preloadImageHandler(); } + self.fetchFailed = 0; } catch(e) { debug(e); return; } + }).catch(error => { + self.fetchFailed = (self.fetchFailed || 0) + 1; + if (self.fetchFailed > 1) self.curSiteRule.preload = 0; }); } @@ -7286,7 +7290,7 @@ } if (loadingDiv.previousElementSibling) { let preStyle = _unsafeWindow.getComputedStyle(loadingDiv.previousElementSibling); - loadingDiv.style.order = preStyle.order; + if (preStyle.order && preStyle.order !== '0') loadingDiv.style.order = preStyle.order; } } //this.setPageTop(lastScrollTop); From 245a405972b6151a62a37cf7ee6c0f2e981e9ac6 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 30 Sep 2025 18:56:56 +0900 Subject: [PATCH 122/252] Update pvcep_rules.js --- Picviewer CE+/pvcep_rules.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Picviewer CE+/pvcep_rules.js b/Picviewer CE+/pvcep_rules.js index fa5d76083e6..34f9148c807 100644 --- a/Picviewer CE+/pvcep_rules.js +++ b/Picviewer CE+/pvcep_rules.js @@ -2031,5 +2031,11 @@ var siteInfo = [ url: "/^https://(www\\.)?img(?:inn|sed)\\.com/p/[\\w-]/", query: '.swiper-slide,.downloads a' } + }, + { + name: "zlib", + src: /^https:\/\/[^\/]+\.cdn\-zlib\./i, + r: /covers\d+/i, + s: 'covers4096' } ]; From 456bb139b9896691bb4dea53a5e5cf2974340b42 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 30 Sep 2025 19:01:15 +0900 Subject: [PATCH 123/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 180 +++++++++++----------------- 1 file changed, 69 insertions(+), 111 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 2e3f1b24cb7..890624b1b63 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.9.27.2 +// @version 2025.9.30.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/1666736/pvcep_rules.js +// @require https://update.greasyfork.org/scripts/438080/1669339/pvcep_rules.js // @require https://update.greasyfork.org/scripts/440698/1653424/pvcep_lang.js // @match *://*/* // @exclude http://www.toodledo.com/tasks/* @@ -12675,7 +12675,7 @@ ImgOps | https://imgops.com/#b#`; } ]; - const imageReg = /^\s*(https?|ftp):\/\/.*?\/[^\.]*\.(avi|avif|avifs|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|jif|jfi|a?png|svgz?|webp|xbm|dib|divx|3gpp|m3u|m4v|mkv|mp4|mpe?g|ogv|webm|flv|flac|m4a|m4b|mpa|mp3|aac|cda|oga|ogg|opus|wma|wav)(&|\?|#|\/?$|\s)/i; + const imageReg = /^\s*(https?|ftp):\/\/.*?\/[^\.::]*\.(avi|avif|avifs|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|jif|jfi|a?png|svgz?|webp|xbm|dib|divx|3gpp|m3u|m4v|mkv|mp4|mpe?g|ogv|webm|flv|flac|m4a|m4b|mpa|mp3|aac|cda|oga|ogg|opus|wma|wav)(&|\?|#|\/?$|\s)/i; const ruleImportHost = ["greasyfork.org", "github.com", "reddit.com"]; const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/24204(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Picviewer%20CE%2B|issues|discussions)|\.reddit\.com\/r\/PicviewerCE/i; @@ -13451,6 +13451,7 @@ ImgOps | https://imgops.com/#b#`; + var errorList = {}, errorBlobList = {}; var imgReady=function(img,opts){ if(/NodeList|HTMLCollection/.test(Object.prototype.toString.call(img)) || Array.isArray(img)){ @@ -13596,6 +13597,42 @@ ImgOps | https://imgops.com/#b#`; function errorHandler(e){ if(aborted)return; + + if (!errorBlobList[img.src] && !/^blob:/.test(img.src)) { + errorList[img.src]=true; + _GM_xmlhttpRequest({ + method: 'GET', + url: img.src, + responseType: 'blob', + onload: function(response) { + const blobUrl = URL.createObjectURL(response.response); + const releaseBlob = () => URL.revokeObjectURL(blobUrl); + window.addEventListener('beforeunload', releaseBlob); + + let orgSrc = img.src; + img.src = blobUrl; + 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); + }, + onerror: function() { + if (aborted) return; + errorBlobList[orgSrc] = true; + go('error',e); + } + }); + return; + } + go('error',e); }; @@ -13616,6 +13653,7 @@ ImgOps | https://imgops.com/#b#`; }); },0); }else{//这不是图片.opera会识别错误. + errorList[img.src]=true; setTimeout(function(){ if(aborted)return; go('error',{ @@ -13646,6 +13684,12 @@ ImgOps | https://imgops.com/#b#`; iRInterval=setInterval(checkReady,66); }; }; + if (errorList[img.src]) { + go('error',{ + type:'error', + target:img, + }); + } return ret; }; @@ -14947,7 +14991,7 @@ ImgOps | https://imgops.com/#b#`; self.img.style.display = ""; } }); - }, + } }); } else { let target = self.img; @@ -16345,36 +16389,8 @@ ImgOps | https://imgops.com/#b#`; popupImgWin(this); }, error:function(e){ - if (/^blob:/.test(imgSrc)) { - self.showTips(""); - loadError(); - } else if (mode == "image" || mode == "") { - _GM_xmlhttpRequest({ - method: 'GET', - url: imgSrc, - responseType: 'blob', - onload: function(response) { - if (response.response && response.response.type == "text/html") { - self.showTips(""); - loadError(); - return; - } - const blobUrl = URL.createObjectURL(response.response); - self.showTips(""); - let img = document.createElement("img"); - popupImgWin(img); - const releaseBlob = () => URL.revokeObjectURL(blobUrl); - window.addEventListener('beforeunload', releaseBlob); - }, - onerror: function() { - self.showTips(""); - loadError(); - } - }); - } else { - self.showTips(""); - loadError(); - } + self.showTips(""); + loadError(); } }); } @@ -16807,42 +16823,9 @@ ImgOps | https://imgops.com/#b#`; if(e.type=='error'){ if (loadingIndicator && loadingIndicator.style) loadingIndicator.style.display=''; - if (/^blob:/.test(src)) { - self.errorSpan=ele; - if(preImgR)preImgR.abort(); - self.loadImg(img, ele,true); - } else { - _GM_xmlhttpRequest({ - method: 'GET', - url: src, - responseType: 'blob', - onload: function(response) { - if (response.response && response.response.type == "text/html") { - self.errorSpan=ele; - if(preImgR)preImgR.abort(); - self.loadImg(img, ele, true); - return; - } - const blobUrl = URL.createObjectURL(response.response); - self.slideShow.run('loadEnd'); - let img = document.createElement("img"); - img.src = blobUrl; - imgReady(blobUrl, { - ready:function(){ - self.loadImg(img, ele, false); - self.slideShow.run('loadEnd'); - } - }); - const releaseBlob = () => URL.revokeObjectURL(blobUrl); - window.addEventListener('beforeunload', releaseBlob); - }, - onerror: function() { - self.errorSpan=ele; - if(preImgR)preImgR.abort(); - self.loadImg(img, ele, true); - } - }); - } + self.errorSpan=ele; + if(preImgR)preImgR.abort(); + self.loadImg(img, ele,true); }; self.slideShow.run('loadEnd'); @@ -23553,44 +23536,16 @@ ImgOps | https://imgops.com/#b#`; var opts = { error: function(e) { - if (/^blob:/.test(imgSrc)) { - if (Array.isArray(imgSrcs)) { - var src = imgSrcs.shift(); - if (src) { - self.loadImg(src, imgSrcs, nextFun); - return; - } + if (Array.isArray(imgSrcs)) { + var src = imgSrcs.shift(); + if (src) { + self.loadImg(src, imgSrcs, nextFun); + return; } - } else if (mode == "image" || mode == "") { - _GM_xmlhttpRequest({ - method: 'GET', - url: media.src, - responseType: 'blob', - onload: function(response) { - const blobUrl = URL.createObjectURL(response.response); - self.loadImg(blobUrl, imgSrcs, nextFun); - const releaseBlob = () => URL.revokeObjectURL(blobUrl); - window.addEventListener('beforeunload', releaseBlob); - }, - onerror: function() { - if (Array.isArray(imgSrcs)) { - var src = imgSrcs.shift(); - if (src) { - self.loadImg(src, imgSrcs, nextFun); - return; - } - } else { - self.error('', this, e); - } - } - }); - return; } else { - self.error('', this, e); + if(nextFun) nextFun(); + else self.error('', this, e); } - - if(nextFun) nextFun(); - else self.error('', this, e); }, }; @@ -25278,7 +25233,7 @@ ImgOps | https://imgops.com/#b#`; }, prefs.floatBar.showDelay || 0) } - function checkFloatBar(_target, type, canPreview, clientX, clientY, altKey) { + function checkFloatBar(_target, type, canPreview, clientX, clientY, altKey, composedTarget) { let target = _target; if (!target || target.id == "pv-float-bar-container" || (target.parentNode && (target.parentNode.id == "icons" || target.parentNode.className == "search-jumper-btn")) || @@ -25335,7 +25290,7 @@ ImgOps | https://imgops.com/#b#`; let bgReg = /.*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let bgRegLong = /^\s*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let result, targetBg, hasBg = node => { - if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document"){ + if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document" || node.nodeType != 1){ return false; } if (node.clientWidth <= prefs.floatBar.minSizeLimit.w || node.clientHeight <= prefs.floatBar.minSizeLimit.h) { @@ -25503,17 +25458,18 @@ ImgOps | https://imgops.com/#b#`; return; } + if (composedTarget) target = composedTarget; let found = false; if (target.nodeName.toUpperCase() == "AREA") target = target.parentNode; var broEle, broImg; - if (target.nodeName.toUpperCase() != 'A' && target.parentNode && target.parentNode.style && !/flex|grid|table/.test(getComputedStyle(target.parentNode).display)) { + if (target.parentNode && target.parentNode.style && !/flex|grid|table/.test(getComputedStyle(target.parentNode).display)) { broEle = target.previousElementSibling; while (broEle) { if (broEle.offsetWidth && broEle.offsetWidth > target.offsetWidth>>1) { if (broEle.nodeName == "IMG") broImg = broEle; else if (broEle.nodeName == "PICTURE") broImg = broEle.querySelector("img"); } - if (getComputedStyle(broEle).position !== "absolute") break; + if (broEle.offsetWidth && broEle.offsetHeight && getComputedStyle(broEle).position !== "absolute") break; broEle = broEle.previousElementSibling; } if (broEle == target) broEle = null; @@ -25861,7 +25817,7 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false, lastEvent; + var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; @@ -25871,6 +25827,8 @@ ImgOps | https://imgops.com/#b#`; e = lastEvent; } else { lastEvent = e; + let path = e && e.composedPath && e.composedPath(); + composedTarget = path && path[0]; } if (e.type == "mousemove") { if (!initMouse) { @@ -25900,7 +25858,7 @@ ImgOps | https://imgops.com/#b#`; checkFloatBarTimer = setTimeout(function() { if (!e || !e.target || !e.target.parentNode) return; if (gallery && gallery.shown) return; - checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey); + checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey, composedTarget); }, 50); } From c84e38f7034fc20862fd7e35940c181d72c7250c Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 30 Sep 2025 10:01:26 +0000 Subject: [PATCH 124/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 180 ++++++++++++++----------------------- 1 file changed, 69 insertions(+), 111 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 1a58b0f785d..a4262be7589 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.9.27.2 +// @version 2025.9.30.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=1666736 +// @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_rules.js?v=1669339 // @require https://hoothin.github.io/UserScripts/Picviewer%20CE%2B/pvcep_lang.js?v=1653424 // @match *://*/* // @exclude http://www.toodledo.com/tasks/* @@ -12675,7 +12675,7 @@ ImgOps | https://imgops.com/#b#`; } ]; - const imageReg = /^\s*(https?|ftp):\/\/.*?\/[^\.]*\.(avi|avif|avifs|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|jif|jfi|a?png|svgz?|webp|xbm|dib|divx|3gpp|m3u|m4v|mkv|mp4|mpe?g|ogv|webm|flv|flac|m4a|m4b|mpa|mp3|aac|cda|oga|ogg|opus|wma|wav)(&|\?|#|\/?$|\s)/i; + const imageReg = /^\s*(https?|ftp):\/\/.*?\/[^\.::]*\.(avi|avif|avifs|bmp|gif|gifv|ico|jfif|jpe|jpeg|jpg|jif|jfi|a?png|svgz?|webp|xbm|dib|divx|3gpp|m3u|m4v|mkv|mp4|mpe?g|ogv|webm|flv|flac|m4a|m4b|mpa|mp3|aac|cda|oga|ogg|opus|wma|wav)(&|\?|#|\/?$|\s)/i; const ruleImportHost = ["greasyfork.org", "github.com", "reddit.com"]; const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/24204(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Picviewer%20CE%2B|issues|discussions)|\.reddit\.com\/r\/PicviewerCE/i; @@ -13451,6 +13451,7 @@ ImgOps | https://imgops.com/#b#`; + var errorList = {}, errorBlobList = {}; var imgReady=function(img,opts){ if(/NodeList|HTMLCollection/.test(Object.prototype.toString.call(img)) || Array.isArray(img)){ @@ -13596,6 +13597,42 @@ ImgOps | https://imgops.com/#b#`; function errorHandler(e){ if(aborted)return; + + if (!errorBlobList[img.src] && !/^blob:/.test(img.src)) { + errorList[img.src]=true; + _GM_xmlhttpRequest({ + method: 'GET', + url: img.src, + responseType: 'blob', + onload: function(response) { + const blobUrl = URL.createObjectURL(response.response); + const releaseBlob = () => URL.revokeObjectURL(blobUrl); + window.addEventListener('beforeunload', releaseBlob); + + let orgSrc = img.src; + img.src = blobUrl; + 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); + }, + onerror: function() { + if (aborted) return; + errorBlobList[orgSrc] = true; + go('error',e); + } + }); + return; + } + go('error',e); }; @@ -13616,6 +13653,7 @@ ImgOps | https://imgops.com/#b#`; }); },0); }else{//这不是图片.opera会识别错误. + errorList[img.src]=true; setTimeout(function(){ if(aborted)return; go('error',{ @@ -13646,6 +13684,12 @@ ImgOps | https://imgops.com/#b#`; iRInterval=setInterval(checkReady,66); }; }; + if (errorList[img.src]) { + go('error',{ + type:'error', + target:img, + }); + } return ret; }; @@ -14947,7 +14991,7 @@ ImgOps | https://imgops.com/#b#`; self.img.style.display = ""; } }); - }, + } }); } else { let target = self.img; @@ -16345,36 +16389,8 @@ ImgOps | https://imgops.com/#b#`; popupImgWin(this); }, error:function(e){ - if (/^blob:/.test(imgSrc)) { - self.showTips(""); - loadError(); - } else if (mode == "image" || mode == "") { - _GM_xmlhttpRequest({ - method: 'GET', - url: imgSrc, - responseType: 'blob', - onload: function(response) { - if (response.response && response.response.type == "text/html") { - self.showTips(""); - loadError(); - return; - } - const blobUrl = URL.createObjectURL(response.response); - self.showTips(""); - let img = document.createElement("img"); - popupImgWin(img); - const releaseBlob = () => URL.revokeObjectURL(blobUrl); - window.addEventListener('beforeunload', releaseBlob); - }, - onerror: function() { - self.showTips(""); - loadError(); - } - }); - } else { - self.showTips(""); - loadError(); - } + self.showTips(""); + loadError(); } }); } @@ -16807,42 +16823,9 @@ ImgOps | https://imgops.com/#b#`; if(e.type=='error'){ if (loadingIndicator && loadingIndicator.style) loadingIndicator.style.display=''; - if (/^blob:/.test(src)) { - self.errorSpan=ele; - if(preImgR)preImgR.abort(); - self.loadImg(img, ele,true); - } else { - _GM_xmlhttpRequest({ - method: 'GET', - url: src, - responseType: 'blob', - onload: function(response) { - if (response.response && response.response.type == "text/html") { - self.errorSpan=ele; - if(preImgR)preImgR.abort(); - self.loadImg(img, ele, true); - return; - } - const blobUrl = URL.createObjectURL(response.response); - self.slideShow.run('loadEnd'); - let img = document.createElement("img"); - img.src = blobUrl; - imgReady(blobUrl, { - ready:function(){ - self.loadImg(img, ele, false); - self.slideShow.run('loadEnd'); - } - }); - const releaseBlob = () => URL.revokeObjectURL(blobUrl); - window.addEventListener('beforeunload', releaseBlob); - }, - onerror: function() { - self.errorSpan=ele; - if(preImgR)preImgR.abort(); - self.loadImg(img, ele, true); - } - }); - } + self.errorSpan=ele; + if(preImgR)preImgR.abort(); + self.loadImg(img, ele,true); }; self.slideShow.run('loadEnd'); @@ -23553,44 +23536,16 @@ ImgOps | https://imgops.com/#b#`; var opts = { error: function(e) { - if (/^blob:/.test(imgSrc)) { - if (Array.isArray(imgSrcs)) { - var src = imgSrcs.shift(); - if (src) { - self.loadImg(src, imgSrcs, nextFun); - return; - } + if (Array.isArray(imgSrcs)) { + var src = imgSrcs.shift(); + if (src) { + self.loadImg(src, imgSrcs, nextFun); + return; } - } else if (mode == "image" || mode == "") { - _GM_xmlhttpRequest({ - method: 'GET', - url: media.src, - responseType: 'blob', - onload: function(response) { - const blobUrl = URL.createObjectURL(response.response); - self.loadImg(blobUrl, imgSrcs, nextFun); - const releaseBlob = () => URL.revokeObjectURL(blobUrl); - window.addEventListener('beforeunload', releaseBlob); - }, - onerror: function() { - if (Array.isArray(imgSrcs)) { - var src = imgSrcs.shift(); - if (src) { - self.loadImg(src, imgSrcs, nextFun); - return; - } - } else { - self.error('', this, e); - } - } - }); - return; } else { - self.error('', this, e); + if(nextFun) nextFun(); + else self.error('', this, e); } - - if(nextFun) nextFun(); - else self.error('', this, e); }, }; @@ -25278,7 +25233,7 @@ ImgOps | https://imgops.com/#b#`; }, prefs.floatBar.showDelay || 0) } - function checkFloatBar(_target, type, canPreview, clientX, clientY, altKey) { + function checkFloatBar(_target, type, canPreview, clientX, clientY, altKey, composedTarget) { let target = _target; if (!target || target.id == "pv-float-bar-container" || (target.parentNode && (target.parentNode.id == "icons" || target.parentNode.className == "search-jumper-btn")) || @@ -25335,7 +25290,7 @@ ImgOps | https://imgops.com/#b#`; let bgReg = /.*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let bgRegLong = /^\s*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let result, targetBg, hasBg = node => { - if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document"){ + if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document" || node.nodeType != 1){ return false; } if (node.clientWidth <= prefs.floatBar.minSizeLimit.w || node.clientHeight <= prefs.floatBar.minSizeLimit.h) { @@ -25503,17 +25458,18 @@ ImgOps | https://imgops.com/#b#`; return; } + if (composedTarget) target = composedTarget; let found = false; if (target.nodeName.toUpperCase() == "AREA") target = target.parentNode; var broEle, broImg; - if (target.nodeName.toUpperCase() != 'A' && target.parentNode && target.parentNode.style && !/flex|grid|table/.test(getComputedStyle(target.parentNode).display)) { + if (target.parentNode && target.parentNode.style && !/flex|grid|table/.test(getComputedStyle(target.parentNode).display)) { broEle = target.previousElementSibling; while (broEle) { if (broEle.offsetWidth && broEle.offsetWidth > target.offsetWidth>>1) { if (broEle.nodeName == "IMG") broImg = broEle; else if (broEle.nodeName == "PICTURE") broImg = broEle.querySelector("img"); } - if (getComputedStyle(broEle).position !== "absolute") break; + if (broEle.offsetWidth && broEle.offsetHeight && getComputedStyle(broEle).position !== "absolute") break; broEle = broEle.previousElementSibling; } if (broEle == target) broEle = null; @@ -25861,7 +25817,7 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false, lastEvent; + var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; @@ -25871,6 +25827,8 @@ ImgOps | https://imgops.com/#b#`; e = lastEvent; } else { lastEvent = e; + let path = e && e.composedPath && e.composedPath(); + composedTarget = path && path[0]; } if (e.type == "mousemove") { if (!initMouse) { @@ -25900,7 +25858,7 @@ ImgOps | https://imgops.com/#b#`; checkFloatBarTimer = setTimeout(function() { if (!e || !e.target || !e.target.parentNode) return; if (gallery && gallery.shown) return; - checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey); + checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey, composedTarget); }, 50); } From e383523626d9c3c161b27504bb82aca0adbd2916 Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 30 Sep 2025 19:06:30 +0900 Subject: [PATCH 125/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 890624b1b63..24f25ceb7e3 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -25822,14 +25822,6 @@ ImgOps | https://imgops.com/#b#`; if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; let canPreview = checkPreview(e); - if (e.type == "keydown") { - if (!lastEvent) return; - e = lastEvent; - } else { - lastEvent = e; - let path = e && e.composedPath && e.composedPath(); - composedTarget = path && path[0]; - } if (e.type == "mousemove") { if (!initMouse) { initMouse = true; @@ -25854,6 +25846,14 @@ ImgOps | https://imgops.com/#b#`; } } if (!initMouse) return; + if (e.type == "keydown") { + if (!lastEvent) return; + e = lastEvent; + } else { + lastEvent = e; + let path = e && e.composedPath && e.composedPath(); + composedTarget = path && path[0]; + } clearTimeout(checkFloatBarTimer); checkFloatBarTimer = setTimeout(function() { if (!e || !e.target || !e.target.parentNode) return; From f897d2afa68b53b77e73429cdccaa5c4b15c21f1 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 30 Sep 2025 10:08:09 +0000 Subject: [PATCH 126/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index a4262be7589..2a544b5075e 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -25822,14 +25822,6 @@ ImgOps | https://imgops.com/#b#`; if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; let canPreview = checkPreview(e); - if (e.type == "keydown") { - if (!lastEvent) return; - e = lastEvent; - } else { - lastEvent = e; - let path = e && e.composedPath && e.composedPath(); - composedTarget = path && path[0]; - } if (e.type == "mousemove") { if (!initMouse) { initMouse = true; @@ -25854,6 +25846,14 @@ ImgOps | https://imgops.com/#b#`; } } if (!initMouse) return; + if (e.type == "keydown") { + if (!lastEvent) return; + e = lastEvent; + } else { + lastEvent = e; + let path = e && e.composedPath && e.composedPath(); + composedTarget = path && path[0]; + } clearTimeout(checkFloatBarTimer); checkFloatBarTimer = setTimeout(function() { if (!e || !e.target || !e.target.parentNode) return; From e25b2aff7d9a5c1e7b0d0966134e41f9ad7ab166 Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 2 Oct 2025 20:19:35 +0900 Subject: [PATCH 127/252] 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 24f25ceb7e3..afbc0130109 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -25290,7 +25290,7 @@ ImgOps | https://imgops.com/#b#`; let bgReg = /.*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let bgRegLong = /^\s*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let result, targetBg, hasBg = node => { - if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document" || node.nodeType != 1){ + if(/^(html|body|#document)$/i.test(node.nodeName) || node.nodeType != 1){ return false; } if (node.clientWidth <= prefs.floatBar.minSizeLimit.w || node.clientHeight <= prefs.floatBar.minSizeLimit.h) { From 9758a22b7ea072d4ede79f6f5a58da7b0f142380 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 2 Oct 2025 11:19:48 +0000 Subject: [PATCH 128/252] 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 2a544b5075e..7117fbffb8a 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -25290,7 +25290,7 @@ ImgOps | https://imgops.com/#b#`; let bgReg = /.*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let bgRegLong = /^\s*url\(\s*["']?([^ad\s'"#].+?)["']?\s*\)([^'"]|$)/i; let result, targetBg, hasBg = node => { - if(node.nodeName.toUpperCase() == "HTML" || node.nodeName == "#document" || node.nodeType != 1){ + if(/^(html|body|#document)$/i.test(node.nodeName) || node.nodeType != 1){ return false; } if (node.clientWidth <= prefs.floatBar.minSizeLimit.w || node.clientHeight <= prefs.floatBar.minSizeLimit.h) { From 33311300077ee7a938906ab2c8d935e8a53fd38c Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 5 Oct 2025 18:52:29 +0900 Subject: [PATCH 129/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index afbc0130109..d1d432dea14 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.9.30.1 +// @version 2025.10.5.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 @@ -24038,10 +24038,20 @@ ImgOps | https://imgops.com/#b#`; if (bodyStyle.position === "static") { offsetParent = document.documentElement; + bodyPosi = body.getBoundingClientRect(); + let docPosy = offsetParent.getBoundingClientRect(); + if (bodyPosi.top == docPosy.top && bodyPosi.left == docPosy.left && bodyPosi.bottom == docPosy.bottom && bodyPosi.right == docPosy.right) { + bodyPosi = { + top: -offsetParent.scrollTop, + left: -offsetParent.scrollLeft, + bottom: offsetParent.scrollHeight - offsetParent.scrollTop, + right: offsetParent.scrollWidth - offsetParent.scrollLeft + }; + } } else { offsetParent = body; + bodyPosi = offsetParent.getBoundingClientRect(); } - bodyPosi = offsetParent.getBoundingClientRect(); var scrolled=getScrolled(offsetParent); @@ -24145,6 +24155,7 @@ ImgOps | https://imgops.com/#b#`; window.addEventListener('scroll',this._scrollHandler,true); }, hide:function(){ + lastEvent = null; clearTimeout(this.showTimer); this.floatBar.style.opacity=0.01; this.shown=false; @@ -26204,7 +26215,7 @@ ImgOps | https://imgops.com/#b#`; if (e.relatedTarget == ImgWindowC.overlayer) return; if(uniqueImgWin && !uniqueImgWin.removed){ if(checkPreview(e)){ - if (!uniqueImgWin.data.img) return uniqueImgWin && uniqueImgWin.remove(); + if (!uniqueImgWin.data.img || !uniqueImgWin.data.img.parentNode) return uniqueImgWin && uniqueImgWin.remove(); let showArea=uniqueImgWin.data.img.getBoundingClientRect(); if(e.clientX < showArea.left + 20 || e.clientX > showArea.right - 20 || From d506c2a3771b68e806ba2b35a527f3f386725aa1 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 5 Oct 2025 19:29:05 +0900 Subject: [PATCH 130/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index cde5111054d..55d0456001a 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -5798,9 +5798,9 @@ if (e.style.display === "none" || e.getAttribute("aria-disabled") === "true") { return false; } - if (/\bbanner|slick|slide|carousel|gallery/i.test(e.id)) return false; + if (/\bbanner|slick|slide|carousel/i.test(e.id)) return false; if (e.className) { - if (!/page/i.test(e.className) && /\bbanner|slick|slide|carousel|gallery|disabled\s*$/i.test(e.className)) { + if (!/page/i.test(e.className) && /\bbanner|slick|slide|carousel|disabled\s*$/i.test(e.className)) { return false; } else if (e.classList) { if (e.classList.contains('disabled') || e.classList.contains('active')) { @@ -5809,7 +5809,7 @@ } } let ariaLabel = e.getAttribute("aria-label"); - if (ariaLabel && /\bbanner|slick|slide|carousel|gallery/i.test(ariaLabel)) return false; + if (ariaLabel && /\bbanner|slick|slide|carousel/i.test(ariaLabel)) return false; return true; }; if (!ele) return false; @@ -5919,7 +5919,8 @@ ".btn_next:not([disabled])", ".btn-next:not([disabled])", '//button[contains(@class, "Page")][text()="Next"]', - '//button[contains(@class, "page")][text()="next"]' + '//button[contains(@class, "page")][text()="next"]', + '//form/button[@type="submit"][text()="Next"]' ]; let next = await this.querySelectorList(body, selectorList, doc.defaultView); if (!next) { From 8216506dceff5242b871819e37f791df4fc5f35c Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 5 Oct 2025 10:29:28 +0000 Subject: [PATCH 131/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 7117fbffb8a..f55c6b1d18f 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.9.30.1 +// @version 2025.10.5.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 @@ -24038,10 +24038,20 @@ ImgOps | https://imgops.com/#b#`; if (bodyStyle.position === "static") { offsetParent = document.documentElement; + bodyPosi = body.getBoundingClientRect(); + let docPosy = offsetParent.getBoundingClientRect(); + if (bodyPosi.top == docPosy.top && bodyPosi.left == docPosy.left && bodyPosi.bottom == docPosy.bottom && bodyPosi.right == docPosy.right) { + bodyPosi = { + top: -offsetParent.scrollTop, + left: -offsetParent.scrollLeft, + bottom: offsetParent.scrollHeight - offsetParent.scrollTop, + right: offsetParent.scrollWidth - offsetParent.scrollLeft + }; + } } else { offsetParent = body; + bodyPosi = offsetParent.getBoundingClientRect(); } - bodyPosi = offsetParent.getBoundingClientRect(); var scrolled=getScrolled(offsetParent); @@ -24145,6 +24155,7 @@ ImgOps | https://imgops.com/#b#`; window.addEventListener('scroll',this._scrollHandler,true); }, hide:function(){ + lastEvent = null; clearTimeout(this.showTimer); this.floatBar.style.opacity=0.01; this.shown=false; @@ -26204,7 +26215,7 @@ ImgOps | https://imgops.com/#b#`; if (e.relatedTarget == ImgWindowC.overlayer) return; if(uniqueImgWin && !uniqueImgWin.removed){ if(checkPreview(e)){ - if (!uniqueImgWin.data.img) return uniqueImgWin && uniqueImgWin.remove(); + if (!uniqueImgWin.data.img || !uniqueImgWin.data.img.parentNode) return uniqueImgWin && uniqueImgWin.remove(); let showArea=uniqueImgWin.data.img.getBoundingClientRect(); if(e.clientX < showArea.left + 20 || e.clientX > showArea.right - 20 || From 3932b4bf214267899e0e15f207d17c8fc4c62091 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 5 Oct 2025 19:30:16 +0900 Subject: [PATCH 132/252] Update pagetualRules.json --- Pagetual/pagetualRules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/pagetualRules.json b/Pagetual/pagetualRules.json index 0bef18b9b67..9a03e0b6e17 100644 --- a/Pagetual/pagetualRules.json +++ b/Pagetual/pagetualRules.json @@ -3061,7 +3061,7 @@ "name": "美人图 - 分類", "author": "skofkyo", "example": "https://meirentu.cc/group/xiuren.html", - "url": "^https?://meirentu\\.\\w+/", + "url": "^https?://meirentu\\.\\w+/group", "nextLink": "//a[text()='下页'] | //a[@class='current']/following-sibling::a[1]", "stopSign": [ "ul.update_area_lists img" From 4605fdb88e262c09c490ef1f540bbc133b20b425 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 5 Oct 2025 10:30:47 +0000 Subject: [PATCH 133/252] Update version of Pagetual rules to 101 --- Pagetual/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/version b/Pagetual/version index 29d6383b52c..398050c62c8 100644 --- a/Pagetual/version +++ b/Pagetual/version @@ -1 +1 @@ -100 +101 From 7ca84de4be5035185354781e8b540906cb9a9a8c Mon Sep 17 00:00:00 2001 From: hoothin Date: Tue, 7 Oct 2025 15:12:28 +0900 Subject: [PATCH 134/252] 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 d1d432dea14..19d52d686a2 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.5.1 +// @version 2025.10.7.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 @@ -25272,6 +25272,9 @@ ImgOps | https://imgops.com/#b#`; } } + if (target.nodeName.toUpperCase() == 'IMG' && !target.naturalHeight) { + target = target.parentNode; + } // 扩展模式,检查前面一个是否为 img if (target.nodeName.toUpperCase() != 'IMG' && matchedRule.rules.length > 0 && matchedRule.ext) { var _type = typeof matchedRule.ext; @@ -25469,7 +25472,7 @@ ImgOps | https://imgops.com/#b#`; return; } - if (composedTarget) target = composedTarget; + if (composedTarget && composedTarget.naturalHeight) target = composedTarget; let found = false; if (target.nodeName.toUpperCase() == "AREA") target = target.parentNode; var broEle, broImg; @@ -25497,7 +25500,7 @@ ImgOps | https://imgops.com/#b#`; if (broEle == target) broEle = null; } } - if (target.children.length == 1 && !(target.textContent && target.textContent.trim()) && target.children[0].nodeName == "IMG") { + if (target.children.length == 1 && !(target.textContent && target.textContent.trim()) && target.children[0].naturalHeight) { target = target.children[0]; found = true; } else if (prefs.floatBar.listenBg && hasBg(target)) { From c71d961ffc1210c9b749db4dba916c857e7584c7 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Tue, 7 Oct 2025 06:12:38 +0000 Subject: [PATCH 135/252] 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 f55c6b1d18f..451246496d7 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.5.1 +// @version 2025.10.7.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 @@ -25272,6 +25272,9 @@ ImgOps | https://imgops.com/#b#`; } } + if (target.nodeName.toUpperCase() == 'IMG' && !target.naturalHeight) { + target = target.parentNode; + } // 扩展模式,检查前面一个是否为 img if (target.nodeName.toUpperCase() != 'IMG' && matchedRule.rules.length > 0 && matchedRule.ext) { var _type = typeof matchedRule.ext; @@ -25469,7 +25472,7 @@ ImgOps | https://imgops.com/#b#`; return; } - if (composedTarget) target = composedTarget; + if (composedTarget && composedTarget.naturalHeight) target = composedTarget; let found = false; if (target.nodeName.toUpperCase() == "AREA") target = target.parentNode; var broEle, broImg; @@ -25497,7 +25500,7 @@ ImgOps | https://imgops.com/#b#`; if (broEle == target) broEle = null; } } - if (target.children.length == 1 && !(target.textContent && target.textContent.trim()) && target.children[0].nodeName == "IMG") { + if (target.children.length == 1 && !(target.textContent && target.textContent.trim()) && target.children[0].naturalHeight) { target = target.children[0]; found = true; } else if (prefs.floatBar.listenBg && hasBg(target)) { From 0f60c6bc6d1676aef7a0c4ead34318997b148e1b Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 9 Oct 2025 17:55:17 +0900 Subject: [PATCH 136/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 19d52d686a2..df509f9e9cf 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -25831,7 +25831,7 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget; + var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget, checking = false; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; @@ -25851,6 +25851,13 @@ ImgOps | https://imgops.com/#b#`; } return; } else { + if (checking) { + setTimeout(() => { + checking = false; + }, 50); + return; + } + checking = true; if (!canPreview) return; let target = e.target; if (target.nodeName == "PICTURE"){ @@ -25865,8 +25872,15 @@ ImgOps | https://imgops.com/#b#`; e = lastEvent; } else { lastEvent = e; - let path = e && e.composedPath && e.composedPath(); - composedTarget = path && path[0]; + if (checking) { + setTimeout(() => { + checking = false; + }, 50); + } else { + checking = true; + let path = e && e.composedPath && e.composedPath(); + composedTarget = path && path[0]; + } } clearTimeout(checkFloatBarTimer); checkFloatBarTimer = setTimeout(function() { From fee0f1f85139fd60374c55fa5e16f0108f3760f8 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Fri, 10 Oct 2025 05:31:56 +0000 Subject: [PATCH 137/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 451246496d7..8664664b8ef 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -25831,7 +25831,7 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget; + var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget, checking = false; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; @@ -25851,6 +25851,13 @@ ImgOps | https://imgops.com/#b#`; } return; } else { + if (checking) { + setTimeout(() => { + checking = false; + }, 50); + return; + } + checking = true; if (!canPreview) return; let target = e.target; if (target.nodeName == "PICTURE"){ @@ -25865,8 +25872,15 @@ ImgOps | https://imgops.com/#b#`; e = lastEvent; } else { lastEvent = e; - let path = e && e.composedPath && e.composedPath(); - composedTarget = path && path[0]; + if (checking) { + setTimeout(() => { + checking = false; + }, 50); + } else { + checking = true; + let path = e && e.composedPath && e.composedPath(); + composedTarget = path && path[0]; + } } clearTimeout(checkFloatBarTimer); checkFloatBarTimer = setTimeout(function() { From 2af014b3c874e4179a4a2b717a807a3c139561a0 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 10 Oct 2025 20:10:02 +0900 Subject: [PATCH 138/252] Update pagetual.user.js --- Pagetual/pagetual.user.js | 105 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 55d0456001a..19a800cae18 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.124 +// @version 1.9.37.125 // @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 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁 @@ -4560,9 +4560,103 @@ return segs.length ? '/' + segs.join('/') : null; } - const escapeHTMLPolicy = (_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.createPolicy) ? _unsafeWindow.trustedTypes.createPolicy('pagetual_default', { - createHTML: (string, sink) => string - }) : null; + function parseTrustedTypes(cspString) { + const policies = new Set(); + 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); + } + + async function getAvailablePolicyNamesOptimized() { + if (_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.getPolicyNames) { + const existingNames = _unsafeWindow.trustedTypes.getPolicyNames(); + if (existingNames.length > 0) { + return new Set(existingNames); + } + } + + 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); + } + } + + return new Promise((resolve) => { + GM_xmlhttpRequest({ + method: "HEAD", + 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()); + } + }, + onerror: function(error) { + resolve(new Set()); + } + }); + }); + } + + function isTrustedTypesEnforced() { + try { + document.createElement('div').innerHTML = ''; + return false; + } catch (e) { + return true; + } + } + + async function createPolicy() { + if (_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced()) { + const allowedNames = await getAvailablePolicyNamesOptimized(); + + if (allowedNames.size === 0) { + escapeHTMLPolicy = _unsafeWindow.trustedTypes.createPolicy('pagetual_default', { + createHTML: (string, sink) => string + }); + return; + } + + for (const name of allowedNames) { + if (name === '*') continue; + try { + escapeHTMLPolicy = _unsafeWindow.trustedTypes.createPolicy(name, { + createHTML: (string, sink) => string + }); + break; + } catch (e) { + try { + escapeHTMLPolicy = _unsafeWindow.trustedTypes.policies.get(name); + if (escapeHTMLPolicy) { + break; + } + } catch (e2) { + console.warn(`create '${name}' failed`); + } + } + } + } + } + + let escapeHTMLPolicy = null; function createHTML(html) { return escapeHTMLPolicy ? escapeHTMLPolicy.createHTML(html) : html; @@ -10290,7 +10384,8 @@ } let pageReady = false; - function initRules(callback) { + async function initRules(callback) { + await createPolicy(); charset = (document.characterSet || document.charset || document.inputEncoding); let equiv = document.querySelector('[http-equiv="Content-Type"]'); if (equiv && equiv.content) { From a33bdb63ea341236a294bca4f5e7ab7cfc9fd725 Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 10 Oct 2025 20:17:48 +0900 Subject: [PATCH 139/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 109 ++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index df509f9e9cf..4015b405bae 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.7.1 +// @version 2025.10.10.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 @@ -12455,15 +12455,109 @@ ImgOps | https://imgops.com/#b#`; }); } var prefs; - var escapeHTMLPolicy; - if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy) { - escapeHTMLPolicy=unsafeWindow.trustedTypes.createPolicy('pvcep_default', { - createHTML: (string, sink) => string, - createScriptURL: string => string, - createScript: string => string + + function parseTrustedTypes(cspString) { + const policies = new Set(); + 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); + } + + async function getAvailablePolicyNamesOptimized() { + if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.getPolicyNames) { + const existingNames = unsafeWindow.trustedTypes.getPolicyNames(); + if (existingNames.length > 0) { + return new Set(existingNames); + } + } + + 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); + } + } + + return new Promise((resolve) => { + GM_xmlhttpRequest({ + method: "HEAD", + 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()); + } + }, + onerror: function(error) { + resolve(new Set()); + } + }); }); } + function isTrustedTypesEnforced() { + try { + document.createElement('div').innerHTML = ''; + return false; + } catch (e) { + return true; + } + } + + async function createPolicy() { + if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced()) { + const allowedNames = await getAvailablePolicyNamesOptimized(); + + if (allowedNames.size === 0) { + escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy('pvcep_default', { + 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) { + try { + escapeHTMLPolicy = unsafeWindow.trustedTypes.policies.get(name); + if (escapeHTMLPolicy) { + break; + } + } catch (e2) { + console.warn(`create '${name}' failed`); + } + } + } + } + } + + let escapeHTMLPolicy = null; + function getBody(doc){ return doc.body || doc.querySelector('body') || doc; } @@ -12477,6 +12571,7 @@ ImgOps | https://imgops.com/#b#`; return escapeHTMLPolicy?escapeHTMLPolicy.createScript(html):html; } async function init(topObject,window,document,arrayFn,envir,storage,unsafeWindow){ + await createPolicy(); // 默认设置,请到设置界面修改 prefs={ floatBar:{//浮动工具栏相关设置. From e1a9c1ab0d1811aafec7ce0e53cfcbd1801ffcad Mon Sep 17 00:00:00 2001 From: hoothin Date: Fri, 10 Oct 2025 20:18:29 +0900 Subject: [PATCH 140/252] Update README.md --- Pagetual/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/README.md b/Pagetual/README.md index c4fcad3d2c6..b41b3d04643 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.124](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.125](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 !* From f061593a6b38640426f591921426305c6024f389 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Fri, 10 Oct 2025 11:18:53 +0000 Subject: [PATCH 141/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 109 ++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 8664664b8ef..c4ce7f0cda0 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.7.1 +// @version 2025.10.10.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 @@ -12455,15 +12455,109 @@ ImgOps | https://imgops.com/#b#`; }); } var prefs; - var escapeHTMLPolicy; - if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy) { - escapeHTMLPolicy=unsafeWindow.trustedTypes.createPolicy('pvcep_default', { - createHTML: (string, sink) => string, - createScriptURL: string => string, - createScript: string => string + + function parseTrustedTypes(cspString) { + const policies = new Set(); + 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); + } + + async function getAvailablePolicyNamesOptimized() { + if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.getPolicyNames) { + const existingNames = unsafeWindow.trustedTypes.getPolicyNames(); + if (existingNames.length > 0) { + return new Set(existingNames); + } + } + + 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); + } + } + + return new Promise((resolve) => { + GM_xmlhttpRequest({ + method: "HEAD", + 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()); + } + }, + onerror: function(error) { + resolve(new Set()); + } + }); }); } + function isTrustedTypesEnforced() { + try { + document.createElement('div').innerHTML = ''; + return false; + } catch (e) { + return true; + } + } + + async function createPolicy() { + if (unsafeWindow.trustedTypes && unsafeWindow.trustedTypes.createPolicy && isTrustedTypesEnforced()) { + const allowedNames = await getAvailablePolicyNamesOptimized(); + + if (allowedNames.size === 0) { + escapeHTMLPolicy = unsafeWindow.trustedTypes.createPolicy('pvcep_default', { + 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) { + try { + escapeHTMLPolicy = unsafeWindow.trustedTypes.policies.get(name); + if (escapeHTMLPolicy) { + break; + } + } catch (e2) { + console.warn(`create '${name}' failed`); + } + } + } + } + } + + let escapeHTMLPolicy = null; + function getBody(doc){ return doc.body || doc.querySelector('body') || doc; } @@ -12477,6 +12571,7 @@ ImgOps | https://imgops.com/#b#`; return escapeHTMLPolicy?escapeHTMLPolicy.createScript(html):html; } async function init(topObject,window,document,arrayFn,envir,storage,unsafeWindow){ + await createPolicy(); // 默认设置,请到设置界面修改 prefs={ floatBar:{//浮动工具栏相关设置. From 4143b2f34eab9448dde54f4283489c41a639499b Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 12 Oct 2025 21:24:31 +0900 Subject: [PATCH 142/252] Update pagetualRules.json --- Pagetual/pagetualRules.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Pagetual/pagetualRules.json b/Pagetual/pagetualRules.json index 9a03e0b6e17..c8332cd09d8 100644 --- a/Pagetual/pagetualRules.json +++ b/Pagetual/pagetualRules.json @@ -5519,6 +5519,14 @@ "action": 0, "include": "[rel='next']" }, +{ + "name": "github repos list", + "url": "^https?://github\\.com/[^/]+\\?tab=repositories", + "pageElement": "//ul[@data-filterable-for='your-repos-filter']", + "nextLink": "//a[@rel='next']", + "action": 0, + "css": ".starring-container+.hide-lg{display:none}" +}, { "name": "Actions · github", "action": 0, From 9c7dca8e32418161177a842ec8d005a0c8e7eeb0 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Sun, 12 Oct 2025 12:24:46 +0000 Subject: [PATCH 143/252] Update version of Pagetual rules to 102 --- Pagetual/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pagetual/version b/Pagetual/version index 398050c62c8..257e563266b 100644 --- a/Pagetual/version +++ b/Pagetual/version @@ -1 +1 @@ -101 +102 From edba8c1b5f4e8a7f85a745640c8484ccabe390dc Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 16 Oct 2025 13:25:23 +0900 Subject: [PATCH 144/252] Update Switch Traditional Chinese and Simplified Chinese.user.js --- ...nal Chinese and Simplified Chinese.user.js | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 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 e379235e701..c4009b12925 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.9 +// @version 1.2.7.10 // @description 任意轉換網頁中的簡體中文與正體中文(默認簡體→正體),顯示漢字對應漢語拼音,自訂任意替換文本 // @description:zh-CN 任意转换网页中的简体中文与繁体中文(默认繁体→简体),显示汉字对应汉语拼音,自定义任意替换文本 // @description:ja ウェブページ上の簡体字中国語と繁体字中国語を自由に変換し、中国語の文字にひらがなを表示し、任意の文字を置き換えることができます。 @@ -1713,12 +1713,16 @@ storage.setItem('sc2tcCombConfig', sc2tcCombConfig); } catch (e) { console.log(e); + alert("自訂簡繁用語轉換格式錯誤:" + e); + return; } try { illiteracyConfig = customIlliteracyInput.value ? JSON.parse(customIlliteracyInput.value) : ""; storage.setItem('illiteracyConfig', illiteracyConfig); } catch (e) { console.log(e); + alert("通用字詞轉換格式錯誤:" + e); + return; } storage.setItem('sc2tcCombTree', ""); storage.setItem('tc2scCombTree', ""); @@ -1888,32 +1892,38 @@ function makeCombTree(key, value) { let curTree=sc2tcCombTree; for(let i=0;i Date: Thu, 16 Oct 2025 20:28:19 +0900 Subject: [PATCH 145/252] update --- Pagetual/pagetual.user.js | 10 +--- Picviewer CE+/Picviewer CE+.user.js | 75 +++++++++++++++-------------- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js index 19a800cae18..c644b963b10 100644 --- a/Pagetual/pagetual.user.js +++ b/Pagetual/pagetual.user.js @@ -4643,14 +4643,8 @@ }); break; } catch (e) { - try { - escapeHTMLPolicy = _unsafeWindow.trustedTypes.policies.get(name); - if (escapeHTMLPolicy) { - break; - } - } catch (e2) { - console.warn(`create '${name}' failed`); - } + console.warn(`create '${name}' failed`); + return; } } } diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 4015b405bae..92a3f8522cc 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.10.1 +// @version 2025.10.16.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 @@ -12543,14 +12543,8 @@ ImgOps | https://imgops.com/#b#`; }); break; } catch (e) { - try { - escapeHTMLPolicy = unsafeWindow.trustedTypes.policies.get(name); - if (escapeHTMLPolicy) { - break; - } - } catch (e2) { - console.warn(`create '${name}' failed`); - } + console.warn(`create '${name}' failed`); + return; } } } @@ -22227,12 +22221,10 @@ ImgOps | https://imgops.com/#b#`; let size = rectSize, containsScroll = imgWindow.classList.contains("pv-pic-window-scroll"); if (rectSize.w > wSize.w) { - if (rectSize.w / rectSize.h > wSize.w / wSize.h) { - size = { - w: wSize.w, - h: wSize.w / (rectSize.w / rectSize.h), - }; - } + size = { + w: wSize.w, + h: wSize.w / (rectSize.w / rectSize.h), + }; let cs = this.getRotatedImgCliSize(size); let ns = this.imgNaturalSize; @@ -24250,7 +24242,7 @@ ImgOps | https://imgops.com/#b#`; window.addEventListener('scroll',this._scrollHandler,true); }, hide:function(){ - lastEvent = null; + target = null; clearTimeout(this.showTimer); this.floatBar.style.opacity=0.01; this.shown=false; @@ -25674,16 +25666,18 @@ ImgOps | https://imgops.com/#b#`; } } } - if (!found && target.children && target.children[0] && target.children[0].nodeName.toUpperCase() == 'IMG') { - let img = target.children[0]; - while (img.nextElementSibling && img.nextElementSibling.nodeName.toUpperCase() == 'IMG') { - img = img.nextElementSibling; - } - let rect = img.getBoundingClientRect(); + if (!found && target.children && target.children.length) { + let img = target.querySelector("img"); + if (img) { + while (img.nextElementSibling && img.nextElementSibling.nodeName.toUpperCase() == 'IMG') { + img = img.nextElementSibling; + } + let rect = img.getBoundingClientRect(); - if (clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right) { - target = img; - found = true; + if (clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right) { + target = img; + found = true; + } } } if (!found && document.elementsFromPoint) { @@ -25926,7 +25920,7 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget, checking = false; + var checkFloatBarTimer, initMouse = false, composedTarget, checking = false, target, type, clientX, clientY, altKey; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; @@ -25963,26 +25957,33 @@ ImgOps | https://imgops.com/#b#`; } if (!initMouse) return; if (e.type == "keydown") { - if (!lastEvent) return; - e = lastEvent; + if (!target) return; } else { - lastEvent = e; - if (checking) { - setTimeout(() => { - checking = false; - }, 50); - } else { - checking = true; + target = e.target; + type = e.type; + clientX = e.clientX; + clientY = e.clientY; + altKey = e.altKey; + if (e.type !== "mousemove") { let path = e && e.composedPath && e.composedPath(); composedTarget = path && path[0]; } } clearTimeout(checkFloatBarTimer); checkFloatBarTimer = setTimeout(function() { - if (!e || !e.target || !e.target.parentNode) return; + if (!target || !target.parentNode) return; if (gallery && gallery.shown) return; - checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey, composedTarget); + checkFloatBar(target, type, canPreview, clientX, clientY, altKey, composedTarget); }, 50); + if (e.target.shadowRoot) { + if (!e.target.shadowRoot.initListener) { + e.target.shadowRoot.initListener = true; + e.target.shadowRoot.addEventListener('mouseenter', (e) => { + globalMouseoverHandler(e); + }, true); + e.target.shadowRoot.addEventListener('mousemove', globalMouseoverHandler, true); + } + } } var selectionClientRect, selectionStr, selectionChanging = false; From 68710e448a1771eb0d9ef0788eaf2d94a0784d06 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 16 Oct 2025 11:28:30 +0000 Subject: [PATCH 146/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 75 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index c4ce7f0cda0..6a142a772c1 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.10.1 +// @version 2025.10.16.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 @@ -12543,14 +12543,8 @@ ImgOps | https://imgops.com/#b#`; }); break; } catch (e) { - try { - escapeHTMLPolicy = unsafeWindow.trustedTypes.policies.get(name); - if (escapeHTMLPolicy) { - break; - } - } catch (e2) { - console.warn(`create '${name}' failed`); - } + console.warn(`create '${name}' failed`); + return; } } } @@ -22227,12 +22221,10 @@ ImgOps | https://imgops.com/#b#`; let size = rectSize, containsScroll = imgWindow.classList.contains("pv-pic-window-scroll"); if (rectSize.w > wSize.w) { - if (rectSize.w / rectSize.h > wSize.w / wSize.h) { - size = { - w: wSize.w, - h: wSize.w / (rectSize.w / rectSize.h), - }; - } + size = { + w: wSize.w, + h: wSize.w / (rectSize.w / rectSize.h), + }; let cs = this.getRotatedImgCliSize(size); let ns = this.imgNaturalSize; @@ -24250,7 +24242,7 @@ ImgOps | https://imgops.com/#b#`; window.addEventListener('scroll',this._scrollHandler,true); }, hide:function(){ - lastEvent = null; + target = null; clearTimeout(this.showTimer); this.floatBar.style.opacity=0.01; this.shown=false; @@ -25674,16 +25666,18 @@ ImgOps | https://imgops.com/#b#`; } } } - if (!found && target.children && target.children[0] && target.children[0].nodeName.toUpperCase() == 'IMG') { - let img = target.children[0]; - while (img.nextElementSibling && img.nextElementSibling.nodeName.toUpperCase() == 'IMG') { - img = img.nextElementSibling; - } - let rect = img.getBoundingClientRect(); + if (!found && target.children && target.children.length) { + let img = target.querySelector("img"); + if (img) { + while (img.nextElementSibling && img.nextElementSibling.nodeName.toUpperCase() == 'IMG') { + img = img.nextElementSibling; + } + let rect = img.getBoundingClientRect(); - if (clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right) { - target = img; - found = true; + if (clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right) { + target = img; + found = true; + } } } if (!found && document.elementsFromPoint) { @@ -25926,7 +25920,7 @@ ImgOps | https://imgops.com/#b#`; } } - var checkFloatBarTimer, initMouse = false, lastEvent, composedTarget, checking = false; + var checkFloatBarTimer, initMouse = false, composedTarget, checking = false, target, type, clientX, clientY, altKey; function globalMouseoverHandler(e) { if (galleryMode) return;//库模式全屏中...... if (e.target == ImgWindowC.overlayer) return; @@ -25963,26 +25957,33 @@ ImgOps | https://imgops.com/#b#`; } if (!initMouse) return; if (e.type == "keydown") { - if (!lastEvent) return; - e = lastEvent; + if (!target) return; } else { - lastEvent = e; - if (checking) { - setTimeout(() => { - checking = false; - }, 50); - } else { - checking = true; + target = e.target; + type = e.type; + clientX = e.clientX; + clientY = e.clientY; + altKey = e.altKey; + if (e.type !== "mousemove") { let path = e && e.composedPath && e.composedPath(); composedTarget = path && path[0]; } } clearTimeout(checkFloatBarTimer); checkFloatBarTimer = setTimeout(function() { - if (!e || !e.target || !e.target.parentNode) return; + if (!target || !target.parentNode) return; if (gallery && gallery.shown) return; - checkFloatBar(e.target, e.type, canPreview, e.clientX, e.clientY, e.altKey, composedTarget); + checkFloatBar(target, type, canPreview, clientX, clientY, altKey, composedTarget); }, 50); + if (e.target.shadowRoot) { + if (!e.target.shadowRoot.initListener) { + e.target.shadowRoot.initListener = true; + e.target.shadowRoot.addEventListener('mouseenter', (e) => { + globalMouseoverHandler(e); + }, true); + e.target.shadowRoot.addEventListener('mousemove', globalMouseoverHandler, true); + } + } } var selectionClientRect, selectionStr, selectionChanging = false; From 179a45dcdd92afc215a98334b08ab7323379c08b Mon Sep 17 00:00:00 2001 From: hoothin Date: Thu, 16 Oct 2025 21:58:55 +0900 Subject: [PATCH 147/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index 92a3f8522cc..d5d71a9dc12 100644 --- a/Picviewer CE+/Picviewer CE+.user.js +++ b/Picviewer CE+/Picviewer CE+.user.js @@ -23539,6 +23539,7 @@ ImgOps | https://imgops.com/#b#`; }, remove:function(){ if(!this.removed){ + lastPopupLoading = null; this.removed=true; this.loadingAnim.parentNode.removeChild(this.loadingAnim); LoadingAnimC.all.splice(LoadingAnimC.all.indexOf(this),1); @@ -23651,6 +23652,7 @@ ImgOps | https://imgops.com/#b#`; }, load:async function(img,e){ + lastPopupLoading = null; this.remove(); this.img=img; var buttonType=this.buttonType; From 85c80405624b4a68dae6b4e157a7435306b6ff3f Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Thu, 16 Oct 2025 13:34:17 +0000 Subject: [PATCH 148/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 6a142a772c1..2a12f799800 100644 --- a/Picviewer CE+/dist.user.js +++ b/Picviewer CE+/dist.user.js @@ -23539,6 +23539,7 @@ ImgOps | https://imgops.com/#b#`; }, remove:function(){ if(!this.removed){ + lastPopupLoading = null; this.removed=true; this.loadingAnim.parentNode.removeChild(this.loadingAnim); LoadingAnimC.all.splice(LoadingAnimC.all.indexOf(this),1); @@ -23651,6 +23652,7 @@ ImgOps | https://imgops.com/#b#`; }, load:async function(img,e){ + lastPopupLoading = null; this.remove(); this.img=img; var buttonType=this.buttonType; From e74c019e77c44b0702b830b8bcbc8904014f8f5b Mon Sep 17 00:00:00 2001 From: hoothin Date: Sat, 18 Oct 2025 18:55:42 +0900 Subject: [PATCH 149/252] sbbd --- BingBgForBaidu/BingBgForBaidu.user.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/BingBgForBaidu/BingBgForBaidu.user.js b/BingBgForBaidu/BingBgForBaidu.user.js index c33755d652d..663d6b4c93a 100644 --- a/BingBgForBaidu/BingBgForBaidu.user.js +++ b/BingBgForBaidu/BingBgForBaidu.user.js @@ -2,7 +2,7 @@ // @name 百Bing图 // @name:en BingBgForBaidu // @namespace hoothin -// @version 2.3.42 +// @version 2.3.43 // @description 给百度首页换上Bing的背景图,并添加背景图链接与日历组件 // @description:en Just change the background image of baidu.com to bing.com // @author hoothin @@ -99,7 +99,7 @@ iframeDoc.scrollTop(125); iframeDoc.scrollLeft(145); iframe.width=618; - iframe.height=615; + iframe.height=630; }; riliLink.onmouseleave=function(){ clearTimeout(t); @@ -123,17 +123,14 @@ riliLink.title=title; } }; - var skinContainer=document.querySelector(".s-skin-container"); - if(!skinContainer){ - skinContainer=document.getElementsByTagName("body")[0]; - GM_addStyle(".s-news-rank-content{max-height: 180px; width: 99%; overflow-y: auto; overflow-x: hidden;}.s-top-right .ai-entry-right-nologin,.s-top-right .operate-wrapper-nologin{right:362px;}.hot-refresh{padding-bottom:7px;}.hot-title>div,.hot-refresh{border-radius: 3px 3px 0 0}.s-hotsearch-content{position: absolute; background-color: #f0f8ff95; border-radius: 0 0 5px 5px;padding-right: 2px;}.s_ipt{margin:0!important;}.s_ipt_wr{border-radius: 10px 4px 4px 10px;border-radius: 10px 0 0 10px;background: #fff!important;}#qrcodeCon{display:none}body{position:fixed;_position:absolute;top:0;left:0;height:100%;width:100%;min-width:1000px;z-index:-10;background-position:center 0;background-repeat:no-repeat;background-size:cover;-webkit-background-size:cover;-o-background-size:cover;zoom:1;}"); - var inputsu=document.querySelector("input#su"); - var clickHandler=e=>{ - if(skinContainer)skinContainer.style.backgroundImage=""; - else inputsu.removeEventListener("click",clickHandler); - }; - inputsu.addEventListener("click",clickHandler); - } + var skinContainer=document.getElementsByTagName("body")[0]; + GM_addStyle(".s-news-rank-content{max-height: 180px; width: 99%; overflow-y: auto; overflow-x: hidden;}.s-top-right .ai-entry-right-nologin,.s-top-right .operate-wrapper-nologin{right:362px;}.hot-refresh{padding-bottom:7px;}.hot-title>div,.hot-refresh{border-radius: 3px 3px 0 0}.s-hotsearch-title>a,.s-hotsearch-title>a>div{padding: 5px;background-color: #f0f8ff95;border-radius: 5px;}.s-hotsearch-content{position: absolute; background-color: #f0f8ff95; border-radius: 5px;padding: 5px;}.s_ipt{margin:0!important;}.s_ipt_wr{border-radius: 10px 4px 4px 10px;border-radius: 10px 0 0 10px;background: #fff!important;}#qrcodeCon{display:none}body{position:fixed;_position:absolute;top:0;left:0;height:100%;width:100%;min-width:1000px;z-index:-10;background-position:center 0;background-repeat:no-repeat;background-size:cover;-webkit-background-size:cover;-o-background-size:cover;zoom:1;}"); + var inputsu=document.querySelector("input#su"); + var clickHandler=e=>{ + if(skinContainer)skinContainer.style.backgroundImage=""; + else inputsu.removeEventListener("click",clickHandler); + }; + inputsu.addEventListener("click",clickHandler); var bingImgObj=GM_getValue("bingImgObj"); if(bingImgObj){ skinContainer.style.backgroundImage = "url(\""+bingImgObj.base64+"\")"; From f112393bf1b4e99b97b3d031c477a864e2815529 Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 19 Oct 2025 21:12:57 +0900 Subject: [PATCH 150/252] fkbd --- BingBgForBaidu/BingBgForBaidu.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BingBgForBaidu/BingBgForBaidu.user.js b/BingBgForBaidu/BingBgForBaidu.user.js index 663d6b4c93a..6d53105a5c0 100644 --- a/BingBgForBaidu/BingBgForBaidu.user.js +++ b/BingBgForBaidu/BingBgForBaidu.user.js @@ -2,7 +2,7 @@ // @name 百Bing图 // @name:en BingBgForBaidu // @namespace hoothin -// @version 2.3.43 +// @version 2.3.44 // @description 给百度首页换上Bing的背景图,并添加背景图链接与日历组件 // @description:en Just change the background image of baidu.com to bing.com // @author hoothin @@ -31,7 +31,7 @@ if(!head.classList.contains("s-skin-hasbg")){ head.classList.add("s-skin-hasbg"); head.classList.add("s-opacity-50"); - GM_addStyle(".s-opacity-50 .s-opacity-border1-top{border-top-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-bottom{border-bottom-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-left{border-left-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-right{border-right-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border2-top{border-top-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-bottom{border-bottom-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-left{border-left-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-right{border-right-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border3-top{border-top-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-bottom{border-bottom-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-left{border-left-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-right{border-right-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border4-top{border-top-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-bottom{border-bottom-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-left{border-left-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-right{border-right-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border5-top{border-top-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-bottom{border-bottom-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-left{border-left-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-right{border-right-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border6-top{border-top-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-bottom{border-bottom-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-left{border-left-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-right{border-right-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border7-top{border-top-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-bottom{border-bottom-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-left{border-left-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-right{border-right-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-blank1{border-color: rgba(206,206,206,0.5) !important;}.s-opacity-50 .s-opacity-blank2{border-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-blank3{border-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-blank4{border-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-blank5{border-color: rgba(216,216,216,0.5) !important;}.s-opacity-50 .s-opacity-blank6{border-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-blank7{border-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-blank8{border-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-color: rgba(227,227,227,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-left-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(227,227,227,0.5)),to(rgba(227,227,227,0.5))) !important;background-image: -moz-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FE3E3E3,endColorstr=#7FE3E3E3) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-bottom-color: rgba(212,212,212,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-background1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(243,243,243,0.5)),to(rgba(243,243,243,0.5))) !important;background-image: -moz-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF3F3F3,endColorstr=#7FF3F3F3) !important;}.s-opacity-50 .s-opacity-background2{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(248,248,248,0.5)),to(rgba(248,248,248,0.5))) !important;background-image: -moz-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF8F8F8,endColorstr=#7FF8F8F8) !important;}.s-opacity-50 .s-opacity-white-background{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-menu1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.4)),to(rgba(0,0,0,0.4))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#66000000,endColorstr=#66000000) !important;}.s-opacity-50 .s-opacity-foreground{opacity: 0.5;filter: alpha(opacity=50);}.s-opacity-50 .s-opacity-max{opacity: 0.4;filter: alpha(opacity=40);}.s-opacity-50 .s-opacity-bottommoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,0.45))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00000000,endColorstr=#72000000) !important;}.s-opacity-50 .s-opacity-topmoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.45)),to(rgba(0,0,0,0))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#72000000,endColorstr=#00000000) !important;}"); + GM_addStyle("[tpl='ai-tools-panel'],.fixed-skin-wrapper{display: none!important;}.s-opacity-50 .s-opacity-border1-top{border-top-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-bottom{border-bottom-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-left{border-left-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-right{border-right-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border2-top{border-top-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-bottom{border-bottom-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-left{border-left-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-right{border-right-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border3-top{border-top-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-bottom{border-bottom-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-left{border-left-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-right{border-right-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border4-top{border-top-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-bottom{border-bottom-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-left{border-left-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-right{border-right-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border5-top{border-top-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-bottom{border-bottom-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-left{border-left-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-right{border-right-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border6-top{border-top-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-bottom{border-bottom-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-left{border-left-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-right{border-right-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border7-top{border-top-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-bottom{border-bottom-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-left{border-left-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-right{border-right-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-blank1{border-color: rgba(206,206,206,0.5) !important;}.s-opacity-50 .s-opacity-blank2{border-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-blank3{border-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-blank4{border-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-blank5{border-color: rgba(216,216,216,0.5) !important;}.s-opacity-50 .s-opacity-blank6{border-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-blank7{border-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-blank8{border-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-color: rgba(227,227,227,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-left-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(227,227,227,0.5)),to(rgba(227,227,227,0.5))) !important;background-image: -moz-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FE3E3E3,endColorstr=#7FE3E3E3) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-bottom-color: rgba(212,212,212,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-background1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(243,243,243,0.5)),to(rgba(243,243,243,0.5))) !important;background-image: -moz-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF3F3F3,endColorstr=#7FF3F3F3) !important;}.s-opacity-50 .s-opacity-background2{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(248,248,248,0.5)),to(rgba(248,248,248,0.5))) !important;background-image: -moz-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF8F8F8,endColorstr=#7FF8F8F8) !important;}.s-opacity-50 .s-opacity-white-background{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-menu1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.4)),to(rgba(0,0,0,0.4))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#66000000,endColorstr=#66000000) !important;}.s-opacity-50 .s-opacity-foreground{opacity: 0.5;filter: alpha(opacity=50);}.s-opacity-50 .s-opacity-max{opacity: 0.4;filter: alpha(opacity=40);}.s-opacity-50 .s-opacity-bottommoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,0.45))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00000000,endColorstr=#72000000) !important;}.s-opacity-50 .s-opacity-topmoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.45)),to(rgba(0,0,0,0))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#72000000,endColorstr=#00000000) !important;}"); } var bingBgLink=document.createElement("a"); bingBgLink.innerHTML="Bing图"; From 19be024fa622e158a7a5e4cd75d670ac26a5a34e Mon Sep 17 00:00:00 2001 From: hoothin Date: Sun, 19 Oct 2025 21:17:44 +0900 Subject: [PATCH 151/252] Update BingBgForBaidu.user.js --- BingBgForBaidu/BingBgForBaidu.user.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/BingBgForBaidu/BingBgForBaidu.user.js b/BingBgForBaidu/BingBgForBaidu.user.js index 6d53105a5c0..43bb7714ff9 100644 --- a/BingBgForBaidu/BingBgForBaidu.user.js +++ b/BingBgForBaidu/BingBgForBaidu.user.js @@ -153,7 +153,7 @@ logo.style.display="initial"; } } - var input=document.querySelector("#kw"); + var input=document.querySelector("#kw,#chat-textarea"); var headWrapper=document.querySelector("#head_wrapper"); let inputHandler = e => { setTimeout(() => { @@ -165,6 +165,11 @@ }, 0); }; input.addEventListener('input', inputHandler); + var submit=document.querySelector("#chat-submit-button"); + submit.addEventListener('click', e => { + if(skinContainer)skinContainer.style.backgroundImage=""; + skinContainer=null; + }); GM_xmlhttpRequest({ method: 'GET', url: "https://global.bing.com/HPImageArchive.aspx?format=js&idx=0&pid=hp&video=1&n=1", From f9701f9c17603a4186a8f05b407430bc058fe1d2 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 20 Oct 2025 09:16:23 +0900 Subject: [PATCH 152/252] Update BingBgForBaidu.user.js --- BingBgForBaidu/BingBgForBaidu.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BingBgForBaidu/BingBgForBaidu.user.js b/BingBgForBaidu/BingBgForBaidu.user.js index 43bb7714ff9..6c86b1050dd 100644 --- a/BingBgForBaidu/BingBgForBaidu.user.js +++ b/BingBgForBaidu/BingBgForBaidu.user.js @@ -31,7 +31,7 @@ if(!head.classList.contains("s-skin-hasbg")){ head.classList.add("s-skin-hasbg"); head.classList.add("s-opacity-50"); - GM_addStyle("[tpl='ai-tools-panel'],.fixed-skin-wrapper{display: none!important;}.s-opacity-50 .s-opacity-border1-top{border-top-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-bottom{border-bottom-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-left{border-left-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-right{border-right-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border2-top{border-top-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-bottom{border-bottom-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-left{border-left-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-right{border-right-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border3-top{border-top-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-bottom{border-bottom-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-left{border-left-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-right{border-right-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border4-top{border-top-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-bottom{border-bottom-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-left{border-left-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-right{border-right-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border5-top{border-top-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-bottom{border-bottom-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-left{border-left-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-right{border-right-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border6-top{border-top-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-bottom{border-bottom-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-left{border-left-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-right{border-right-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border7-top{border-top-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-bottom{border-bottom-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-left{border-left-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-right{border-right-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-blank1{border-color: rgba(206,206,206,0.5) !important;}.s-opacity-50 .s-opacity-blank2{border-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-blank3{border-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-blank4{border-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-blank5{border-color: rgba(216,216,216,0.5) !important;}.s-opacity-50 .s-opacity-blank6{border-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-blank7{border-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-blank8{border-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-color: rgba(227,227,227,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-left-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(227,227,227,0.5)),to(rgba(227,227,227,0.5))) !important;background-image: -moz-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FE3E3E3,endColorstr=#7FE3E3E3) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-bottom-color: rgba(212,212,212,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-background1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(243,243,243,0.5)),to(rgba(243,243,243,0.5))) !important;background-image: -moz-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF3F3F3,endColorstr=#7FF3F3F3) !important;}.s-opacity-50 .s-opacity-background2{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(248,248,248,0.5)),to(rgba(248,248,248,0.5))) !important;background-image: -moz-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF8F8F8,endColorstr=#7FF8F8F8) !important;}.s-opacity-50 .s-opacity-white-background{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-menu1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.4)),to(rgba(0,0,0,0.4))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#66000000,endColorstr=#66000000) !important;}.s-opacity-50 .s-opacity-foreground{opacity: 0.5;filter: alpha(opacity=50);}.s-opacity-50 .s-opacity-max{opacity: 0.4;filter: alpha(opacity=40);}.s-opacity-50 .s-opacity-bottommoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,0.45))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00000000,endColorstr=#72000000) !important;}.s-opacity-50 .s-opacity-topmoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.45)),to(rgba(0,0,0,0))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#72000000,endColorstr=#00000000) !important;}"); + GM_addStyle("[tpl='ai-tools-panel'],.fixed-skin-wrapper{display: none!important;}#bottom_layer{position:absolute!important;}.s-opacity-50 .s-opacity-border1-top{border-top-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-bottom{border-bottom-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-left{border-left-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border1-right{border-right-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-border2-top{border-top-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-bottom{border-bottom-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-left{border-left-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border2-right{border-right-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-border3-top{border-top-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-bottom{border-bottom-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-left{border-left-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border3-right{border-right-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-border4-top{border-top-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-bottom{border-bottom-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-left{border-left-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border4-right{border-right-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-border5-top{border-top-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-bottom{border-bottom-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-left{border-left-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border5-right{border-right-color: rgba(243,243,243,0.5) !important;}.s-opacity-50 .s-opacity-border6-top{border-top-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-bottom{border-bottom-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-left{border-left-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border6-right{border-right-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-border7-top{border-top-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-bottom{border-bottom-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-left{border-left-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-border7-right{border-right-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-blank1{border-color: rgba(206,206,206,0.5) !important;}.s-opacity-50 .s-opacity-blank2{border-color: rgba(233,233,233,0.5) !important;}.s-opacity-50 .s-opacity-blank3{border-color: rgba(240,240,240,0.5) !important;}.s-opacity-50 .s-opacity-blank4{border-color: rgba(218,218,218,0.5) !important;}.s-opacity-50 .s-opacity-blank5{border-color: rgba(216,216,216,0.5) !important;}.s-opacity-50 .s-opacity-blank6{border-color: rgba(238,238,238,0.5) !important;}.s-opacity-50 .s-opacity-blank7{border-color: rgba(234,234,234,0.5) !important;}.s-opacity-50 .s-opacity-blank8{border-color: rgba(226,226,226,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-color: rgba(227,227,227,0.5) !important;}.s-opacity-50 .s-opacity-scroll{border-left-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(227,227,227,0.5)),to(rgba(227,227,227,0.5))) !important;background-image: -moz-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;background-image: linear-gradient(rgba(227,227,227,0.5) 0%,rgba(227,227,227,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FE3E3E3,endColorstr=#7FE3E3E3) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-color: rgba(225,225,225,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{border-bottom-color: rgba(212,212,212,0.5) !important;}.s-opacity-50 .s-opacity-scroll-slider{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-background1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(243,243,243,0.5)),to(rgba(243,243,243,0.5))) !important;background-image: -moz-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;background-image: linear-gradient(rgba(243,243,243,0.5) 0%,rgba(243,243,243,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF3F3F3,endColorstr=#7FF3F3F3) !important;}.s-opacity-50 .s-opacity-background2{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(248,248,248,0.5)),to(rgba(248,248,248,0.5))) !important;background-image: -moz-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;background-image: linear-gradient(rgba(248,248,248,0.5) 0%,rgba(248,248,248,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FF8F8F8,endColorstr=#7FF8F8F8) !important;}.s-opacity-50 .s-opacity-white-background{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(255,255,255,0.5)),to(rgba(255,255,255,0.5))) !important;background-image: -moz-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -ms-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: -o-linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;background-image: linear-gradient(rgba(255,255,255,0.5) 0%,rgba(255,255,255,0.5) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#7FFFFFFF,endColorstr=#7FFFFFFF) !important;}.s-opacity-50 .s-opacity-menu1{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.4)),to(rgba(0,0,0,0.4))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.4) 0%,rgba(0,0,0,0.4) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#66000000,endColorstr=#66000000) !important;}.s-opacity-50 .s-opacity-foreground{opacity: 0.5;filter: alpha(opacity=50);}.s-opacity-50 .s-opacity-max{opacity: 0.4;filter: alpha(opacity=40);}.s-opacity-50 .s-opacity-bottommoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,0.45))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.45) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00000000,endColorstr=#72000000) !important;}.s-opacity-50 .s-opacity-topmoremenu{background: none !important;background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0.45)),to(rgba(0,0,0,0))) !important;background-image: -moz-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -ms-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: -o-linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;background-image: linear-gradient(rgba(0,0,0,0.45) 0%,rgba(0,0,0,0) 100%) !important;filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#72000000,endColorstr=#00000000) !important;}"); } var bingBgLink=document.createElement("a"); bingBgLink.innerHTML="Bing图"; From d613c68876c0a5d7384fd18b8d3dac9683671013 Mon Sep 17 00:00:00 2001 From: hoothin Date: Wed, 22 Oct 2025 09:42:05 +0900 Subject: [PATCH 153/252] Update Picviewer CE+.user.js --- Picviewer CE+/Picviewer CE+.user.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/Picviewer CE+.user.js b/Picviewer CE+/Picviewer CE+.user.js index d5d71a9dc12..3b841f72517 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.16.1 +// @version 2025.10.22.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 @@ -22813,7 +22813,7 @@ ImgOps | https://imgops.com/#b#`; }, focusedKeyup:function(e){ var keyCode=e.keyCode; - var valid=[27,32,18,16,72,17,72,82,90,67,37,39]; + var valid=[27,32,18,16,72,17,72,82,90,67,37,38,39,40]; if(valid.indexOf(keyCode)==-1)return; if (window.getSelection().toString()) return; @@ -22872,12 +22872,12 @@ ImgOps | https://imgops.com/#b#`; case 90://z键 this.zKeyUp=true; break; - case 39: - this.switchImage(true); - break; case 37: this.switchImage(false); break; + case 39: + this.switchImage(true); + break; case 27: if (prefs.imgWindow.close.escKey) { this.remove(); @@ -22918,7 +22918,7 @@ ImgOps | https://imgops.com/#b#`; e.stopPropagation(); return; } - var valid=[32,82,72,90,18,16,17,27,67];//有效的按键 + var valid=[32,82,72,90,18,16,17,27,67,38,40];//有效的按键 if(valid.indexOf(keyCode)==-1) return; e.preventDefault(); @@ -23006,6 +23006,12 @@ ImgOps | https://imgops.com/#b#`; }break; case 27:{//ese关闭窗口 }break; + case 38:{//上 + this.imgbox.scrollTop -= 200; + }break; + case 40:{//下 + this.imgbox.scrollTop += 200; + }break; default:break; } e.stopPropagation(); From 78dfe1a5f9b62c0d19b48ca924dcb038f33469d6 Mon Sep 17 00:00:00 2001 From: hoothin-update Date: Wed, 22 Oct 2025 00:42:24 +0000 Subject: [PATCH 154/252] chore(Picviewer CE+): Auto-generate dist.user.js --- Picviewer CE+/dist.user.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Picviewer CE+/dist.user.js b/Picviewer CE+/dist.user.js index 2a12f799800..e25371e7230 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.16.1 +// @version 2025.10.22.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 @@ -22813,7 +22813,7 @@ ImgOps | https://imgops.com/#b#`; }, focusedKeyup:function(e){ var keyCode=e.keyCode; - var valid=[27,32,18,16,72,17,72,82,90,67,37,39]; + var valid=[27,32,18,16,72,17,72,82,90,67,37,38,39,40]; if(valid.indexOf(keyCode)==-1)return; if (window.getSelection().toString()) return; @@ -22872,12 +22872,12 @@ ImgOps | https://imgops.com/#b#`; case 90://z键 this.zKeyUp=true; break; - case 39: - this.switchImage(true); - break; case 37: this.switchImage(false); break; + case 39: + this.switchImage(true); + break; case 27: if (prefs.imgWindow.close.escKey) { this.remove(); @@ -22918,7 +22918,7 @@ ImgOps | https://imgops.com/#b#`; e.stopPropagation(); return; } - var valid=[32,82,72,90,18,16,17,27,67];//有效的按键 + var valid=[32,82,72,90,18,16,17,27,67,38,40];//有效的按键 if(valid.indexOf(keyCode)==-1) return; e.preventDefault(); @@ -23006,6 +23006,12 @@ ImgOps | https://imgops.com/#b#`; }break; case 27:{//ese关闭窗口 }break; + case 38:{//上 + this.imgbox.scrollTop -= 200; + }break; + case 40:{//下 + this.imgbox.scrollTop += 200; + }break; default:break; } e.stopPropagation(); From c55d0703088768187a3c859e14dcbf32d47f55c9 Mon Sep 17 00:00:00 2001 From: hoothin Date: Mon, 27 Oct 2025 21:53:59 +0900 Subject: [PATCH 155/252] 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 156/252] 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 157/252] 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 158/252] 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 159/252] 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 160/252] 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 161/252] 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 162/252] 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 163/252] 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 164/252] 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 165/252] 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 166/252] 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 167/252] 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 168/252] 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 169/252] 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 170/252] 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 171/252] 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 172/252] 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 173/252] 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 174/252] 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 175/252] 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 176/252] 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 177/252] 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 178/252] 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 179/252] 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 180/252] 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 181/252] 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 182/252] 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 183/252] 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 184/252] 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 185/252] 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 186/252] 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 187/252] =?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 188/252] 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 189/252] 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 190/252] 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 191/252] 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 192/252] 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 193/252] 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 194/252] 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 195/252] 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 196/252] 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 197/252] 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 198/252] 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 199/252] 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 200/252] 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 201/252] 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 202/252] 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 203/252] 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 204/252] 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 205/252] 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 206/252] 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 207/252] 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 208/252] 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 209/252] 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 210/252] 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 211/252] 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 212/252] 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 213/252] 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 214/252] 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 215/252] 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 216/252] 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 217/252] 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('