From a8fc1cde8a75b691b9e6ef7f36907e0b1dcb7fbf Mon Sep 17 00:00:00 2001 From: "lzyme.dev@gmail.com" Date: Sun, 30 Aug 2026 12:57:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=A3=81=E5=8A=9B=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E4=B8=8E=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/jhs-enhance.user.js | 539 ++++++++++++++++++++++++++++-- src/core/constants.js | 192 +++++++++++ src/plugins/detail-page-button.js | 219 +++++++++++- src/plugins/highlight-magnet.js | 128 +++++-- 4 files changed, 1011 insertions(+), 67 deletions(-) diff --git a/dist/jhs-enhance.user.js b/dist/jhs-enhance.user.js index dad5e4f..310f55b 100644 --- a/dist/jhs-enhance.user.js +++ b/dist/jhs-enhance.user.js @@ -631,6 +631,198 @@ background: rgba(59, 130, 246, 0.9); box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4); } + + .magnet-4k-highlight { + background-color: #fff5f5 !important; + } + .magnet-4k-highlight .name { + color: #f40 !important; + } + + .msf-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.3); + z-index: 999997; + } + .msf-panel { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #fff; + border-radius: 10px; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2); + z-index: 999998; + width: 380px; + display: flex; + flex-direction: column; + } + .msf-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border-bottom: 1px solid #e5e7eb; + background: #f9fafb; + border-radius: 10px 10px 0 0; + } + .msf-title { + font-size: 15px; + font-weight: 600; + color: #1e293b; + } + .msf-close { + font-size: 22px; + color: #9ca3af; + cursor: pointer; + padding: 0 6px; + line-height: 1; + transition: color 0.2s; + } + .msf-close:hover { + color: #ef4444; + } + .msf-body { + padding: 18px; + display: flex; + flex-direction: column; + gap: 16px; + } + .msf-section { + display: flex; + flex-direction: column; + gap: 8px; + } + .msf-section-title { + font-size: 13px; + font-weight: 600; + color: #6b7280; + } + .msf-sort-list { + display: flex; + flex-direction: column; + gap: 6px; + } + .msf-sort-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 6px; + cursor: default; + transition: all 0.2s; + user-select: none; + } + .msf-sort-item:hover { + border-color: #cbd5e1; + background: #f1f5f9; + } + .msf-sort-item.msf-dragging { + opacity: 0.5; + border-color: #3b82f6; + background: #eff6ff; + } + .msf-sort-item.msf-drag-over { + border-color: #3b82f6; + background: #eff6ff; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); + } + .msf-drag-handle { + color: #cbd5e1; + font-size: 16px; + cursor: grab; + line-height: 1; + letter-spacing: -2px; + transition: color 0.2s; + } + .msf-drag-handle:active { + cursor: grabbing; + } + .msf-sort-item:hover .msf-drag-handle { + color: #94a3b8; + } + .msf-sort-label { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #374151; + cursor: pointer; + flex: 1; + } + .msf-sort-checkbox { + margin: 0; + cursor: pointer; + width: 16px; + height: 16px; + } + .msf-sort-hint { + font-size: 12px; + color: #9ca3af; + margin-left: 4px; + } + .msf-filter-list { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + .msf-filter-label { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 14px; + color: #374151; + cursor: pointer; + transition: all 0.2s; + user-select: none; + } + .msf-filter-label:hover { + border-color: #cbd5e1; + background: #f1f5f9; + } + .msf-filter-checkbox { + margin: 0; + cursor: pointer; + width: 16px; + height: 16px; + } + .msf-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 14px 18px; + border-top: 1px solid #e5e7eb; + } + .msf-btn { + padding: 8px 22px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + } + .msf-btn-cancel { + background: #f3f4f6; + color: #374151; + } + .msf-btn-cancel:hover { + background: #e5e7eb; + } + .msf-btn-confirm { + background: #3b82f6; + color: #fff; + } + .msf-btn-confirm:hover { + background: #2563eb; + } `; const subtitleModalCss = ` @@ -8075,26 +8267,112 @@ ${value}\r getName() { return "HighlightMagnetPlugin"; } - doFilterMagnet() { - this.handleDb(); - this.handleBus(); + + parseDateFromAttr(dateStr) { + if (!dateStr || dateStr.length !== 8) { + const d2 = new Date(dateStr); + return isNaN(d2.getTime()) ? 0 : d2.getTime(); + } + const y = parseInt(dateStr.substring(0, 4), 10); + const m = parseInt(dateStr.substring(4, 6), 10) - 1; + const d = parseInt(dateStr.substring(6, 8), 10); + const date = new Date(y, m, d); + return isNaN(date.getTime()) ? 0 : date.getTime(); } - handleDb() { + + getRowInfo(row) { + const $row = $(row); + const nameText = $row.find(".name").text().toLowerCase(); + const tags = []; + $row.find(".tags .tag").each((_, tag) => tags.push($(tag).text().toLowerCase())); + const sizeMB = parseInt($row.attr("data-size"), 10) || 0; + const files = parseInt($row.attr("data-files"), 10) || 0; + const dateStr = $row.attr("data-date") || ""; + return { + row, + $row, + nameText, + date: this.parseDateFromAttr(dateStr), + dateStr, + size: sizeMB * 1024 * 1024, + files, + tags, + is4K: nameText.includes("4k") || nameText.includes("[4k]") || tags.includes("4k"), + isHD: nameText.includes("高清") || tags.includes("高清"), + isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi") || tags.includes("字幕"), + isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc") || tags.includes("无码") || tags.includes("無碼") + }; + } + + async applySortAndFilter(settings) { if (!isJavDb$1) return; - let magnetNameList = $("#magnets-content .name"); - if (0 === magnetNameList.length) return; - const HIGH_QUALITY_KEYWORDS = ["4k", "-c", "-u", "-uc"]; - let hasHighQuality = false; - magnetNameList.each(((_, el) => { - const $el = $(el), text = $el.text().toLowerCase(), isHighQuality = HIGH_QUALITY_KEYWORDS.some(((keyword) => text.includes(keyword))); - $el.parent().parent().parent().addClass("magnet-row"); - text.includes("4k") && $el.css("color", "#f40"); - if (isHighQuality) { - hasHighQuality = true; - $el.parent().parent().parent().addClass("high-quality"); - } - })); - hasHighQuality ? $("#magnets-content .magnet-row").not(".high-quality").hide() : $("#enable-magnets-filter").addClass("do-hide"); + const $container = $("#magnets-content"); + const $rows = $container.find(".item[data-rank]"); + if (0 === $rows.length) return; + const rowInfos = $rows.toArray().map((row) => this.getRowInfo(row)); + const {filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder} = settings; + const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored; + let visibleInfos = rowInfos; + if (anyFilterEnabled) { + visibleInfos = rowInfos.filter((info) => { + if (filterHD && info.isHD) return true; + if (filter4K && info.is4K) return true; + if (filterSubtitle && info.isSubtitle) return true; + if (filterUncensored && info.isUncensored) return true; + return false; + }); + } + if (sortOrder && sortOrder.length > 0) { + visibleInfos.sort((a, b) => { + for (const key of sortOrder) { + let cmp = 0; + switch (key) { + case "date": + cmp = b.date - a.date; + break; + case "size": + cmp = b.size - a.size; + break; + case "files": + cmp = a.files - b.files; + break; + } + if (0 !== cmp) return cmp; + } + return 0; + }); + } + const container = document.getElementById("magnets-content"); + if (!container) return; + const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info)); + const reviewsContainer = document.getElementById("reviewsContainer"); + const insertRef = reviewsContainer && reviewsContainer.parentNode === container ? reviewsContainer.previousElementSibling || reviewsContainer : null; + const fragment = document.createDocumentFragment(); + visibleInfos.forEach((info) => fragment.appendChild(info.row)); + hiddenInfos.forEach((info) => fragment.appendChild(info.row)); + if (insertRef) { + container.insertBefore(fragment, insertRef); + } else { + container.appendChild(fragment); + } + visibleInfos.forEach((info) => { + if (info.is4K) { + info.$row.addClass("magnet-4k-highlight"); + } + }); + hiddenInfos.forEach((info) => $(info.row).hide()); + } + + showAll() { + if (isJavDb$1) { + $("#magnets-content .item[data-rank]").toArray().forEach(((el) => { + $(el).show(); + $(el).removeClass("magnet-4k-highlight"); + })); + } + if (isJavBus$1) { + $("#magnet-table tr").toArray().forEach(((el) => $(el).show())); + } } handleBus() { isJavBus$1 && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => { @@ -8114,12 +8392,6 @@ ${value}\r })) : $("#enable-magnets-filter").addClass("do-hide"); })); } - showAll() { - if (isJavDb$1) { - $("#magnets-content .item").toArray().forEach(((el) => $(el).show())); - } - isJavBus$1 && $("#magnet-table tr").toArray().forEach(((el) => $(el).show())); - } } class FoldCategoryPlugin extends BasePlugin { getName() { @@ -12896,7 +13168,7 @@ ${err.stack}` : ""); } async createMenuBtn() { const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum, - buttonsHtml = '\n
\n
\n \n 🚫 屏蔽\n \n \n ⭐ 收藏\n \n \n 📥️ 已下载\n \n \n 🔍 已观看\n \n
\n \n
\n \n 关闭磁力过滤\n \n \n 磁力搜索\n \n \n 字幕搜索\n \n \n
\n
\n '; + buttonsHtml = '\n
\n
\n \n 🚫 屏蔽\n \n \n ⭐ 收藏\n \n \n 📥️ 已下载\n \n \n 🔍 已观看\n \n
\n \n
\n \n 排序与过滤\n \n \n 磁力搜索\n \n \n 字幕搜索\n \n \n
\n
\n '; isJavDb$1 && $(".tabs").after(buttonsHtml); isJavBus$1 && $("#mag-submit-show").before(buttonsHtml); $("#favoriteBtn").on("click", (() => this.favoriteOne())); @@ -12916,19 +13188,38 @@ ${err.stack}` : ""); } }); })); - const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", YES); - $("#magnets-span").text(enableMagnetsFilter === YES ? "关闭磁力过滤" : "开启磁力过滤"); - enableMagnetsFilter === YES && highlightMagnetPlugin.doFilterMagnet(); + const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), + enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", NO); + if (enableMagnetsFilter === YES) { + const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); + let settings; + try { + settings = JSON.parse(savedSettings); + } catch (e) { + settings = {}; + } + const sortEnabled = settings.sortEnabled || {}; + const fullSortOrder = settings.sortOrder && settings.sortOrder.length === 3 ? settings.sortOrder : ["date", "size", "files"]; + const effectiveSortOrder = fullSortOrder.filter((key) => sortEnabled[key]); + highlightMagnetPlugin.applySortAndFilter({ + filterHD: settings.filterHD || false, + filter4K: settings.filter4K || false, + filterSubtitle: settings.filterSubtitle || false, + filterUncensored: settings.filterUncensored || false, + sortOrder: effectiveSortOrder + }); + $("#magnets-span").text("取消磁力过滤"); + } else { + $("#magnets-span").text("排序与过滤"); + } $("#enable-magnets-filter").on("click", ((event) => { - let $span = $("#magnets-span"); - if ("关闭磁力过滤" === $span.text()) { + const $span = $("#magnets-span"); + if ("取消磁力过滤" === $span.text()) { highlightMagnetPlugin.showAll(); - $span.text("开启磁力过滤"); + $span.text("排序与过滤"); storageManager.saveSettingItem("enableMagnetsFilter", NO); } else { - highlightMagnetPlugin.doFilterMagnet(); - $span.text("关闭磁力过滤"); - storageManager.saveSettingItem("enableMagnetsFilter", YES); + this.openMagnetSortFilterPanel(highlightMagnetPlugin); } })); $("#subtitleSearchBtn").on("click", (() => { @@ -12937,6 +13228,186 @@ ${err.stack}` : ""); window.JavPackSubtitle.preload115Matches(carNum2); this.showStatus(carNum2).then(); } + + async openMagnetSortFilterPanel(highlightMagnetPlugin) { + const existingPanel = document.getElementById("magnet-sort-filter-panel"); + if (existingPanel) { + existingPanel.remove(); + return; + } + const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); + let settings; + try { + settings = JSON.parse(savedSettings); + } catch (e) { + settings = {}; + } + const sortEnabled = settings.sortEnabled || {date: false, size: false, files: false}; + const sortOrder = settings.sortOrder && settings.sortOrder.length === 3 ? settings.sortOrder : ["date", "size", "files"]; + const filterHD = settings.filterHD || false; + const filter4K = settings.filter4K || false; + const filterSubtitle = settings.filterSubtitle || false; + const filterUncensored = settings.filterUncensored || false; + const SORT_LABELS = {date: "发布日期", size: "文件大小", files: "文件数量"}; + const SORT_HINTS = {date: "从新到旧", size: "从大到小", files: "从少到多"}; + const FILTER_ITEMS = [ + {key: "HD", label: "高清", checked: filterHD}, + {key: "4K", label: "4K", checked: filter4K}, + {key: "Subtitle", label: "字幕", checked: filterSubtitle}, + {key: "Uncensored", label: "无码", checked: filterUncensored} + ]; + const currentSortOrder = [...sortOrder]; + const currentSortEnabled = {...sortEnabled}; + const currentFilters = { + HD: filterHD, + "4K": filter4K, + Subtitle: filterSubtitle, + Uncensored: filterUncensored + }; + const buildSortItemsHTML = () => { + return currentSortOrder.map((key) => { + const checked = currentSortEnabled[key] ? "checked" : ""; + return `
+ ⋮⋮ + +
`; + }).join(""); + }; + const buildFilterItemsHTML = () => { + return FILTER_ITEMS.map((item) => { + const checked = currentFilters[item.key] ? "checked" : ""; + return ``; + }).join(""); + }; + const panelHTML = ` +
+
+
+ 磁力排序与过滤 + × +
+
+
+
排序优先级(拖动调整顺序,勾选启用)
+
+ ${buildSortItemsHTML()} +
+
+
+
过滤条件(可多选,并集关系)
+
+ ${buildFilterItemsHTML()} +
+
+
+ +
+ `; + const wrapper = document.createElement("div"); + wrapper.innerHTML = panelHTML; + document.body.appendChild(wrapper); + const panel = document.getElementById("magnet-sort-filter-panel"); + const overlay = document.getElementById("msf-overlay"); + const closePanel = () => { + panel.remove(); + overlay.remove(); + }; + overlay.addEventListener("click", closePanel); + document.getElementById("msf-close-btn").addEventListener("click", closePanel); + document.getElementById("msf-cancel-btn").addEventListener("click", closePanel); + const rebuildSortList = () => { + const sortList = document.getElementById("msf-sort-list"); + sortList.innerHTML = buildSortItemsHTML(); + bindSortEvents(); + }; + const bindSortEvents = () => { + const sortItems = document.querySelectorAll("#msf-sort-list .msf-sort-item"); + sortItems.forEach((item) => { + item.addEventListener("dragstart", (e) => { + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("text/plain", item.dataset.sortKey); + item.classList.add("msf-dragging"); + }); + item.addEventListener("dragend", () => { + item.classList.remove("msf-dragging"); + document.querySelectorAll("#msf-sort-list .msf-sort-item").forEach((el) => el.classList.remove("msf-drag-over")); + }); + item.addEventListener("dragover", (e) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + item.classList.add("msf-drag-over"); + }); + item.addEventListener("dragleave", () => { + item.classList.remove("msf-drag-over"); + }); + item.addEventListener("drop", (e) => { + e.preventDefault(); + item.classList.remove("msf-drag-over"); + const fromKey = e.dataTransfer.getData("text/plain"); + const toKey = item.dataset.sortKey; + if (fromKey === toKey) return; + const fromIdx = currentSortOrder.indexOf(fromKey); + const toIdx = currentSortOrder.indexOf(toKey); + currentSortOrder.splice(fromIdx, 1); + currentSortOrder.splice(toIdx, 0, fromKey); + rebuildSortList(); + }); + const checkbox = item.querySelector(".msf-sort-checkbox"); + checkbox.addEventListener("change", () => { + currentSortEnabled[item.dataset.sortKey] = checkbox.checked; + }); + }); + }; + bindSortEvents(); + document.querySelectorAll(".msf-filter-checkbox").forEach((cb) => { + cb.addEventListener("change", () => { + currentFilters[cb.dataset.filterKey] = cb.checked; + }); + }); + document.getElementById("msf-confirm-btn").addEventListener("click", async () => { + try { + const enabledSortOrder = currentSortOrder.filter((key) => currentSortEnabled[key]); + const newSettings = { + sortOrder: currentSortOrder, + sortEnabled: currentSortEnabled, + filterHD: currentFilters.HD, + filter4K: currentFilters["4K"], + filterSubtitle: currentFilters.Subtitle, + filterUncensored: currentFilters.Uncensored + }; + await storageManager.saveSettingItem("magnetSortAndFilter", JSON.stringify(newSettings)); + const hasAnyFilter = currentFilters.HD || currentFilters["4K"] || currentFilters.Subtitle || currentFilters.Uncensored; + const hasAnySort = enabledSortOrder.length > 0; + if (hasAnyFilter || hasAnySort) { + await highlightMagnetPlugin.applySortAndFilter({ + filterHD: currentFilters.HD, + filter4K: currentFilters["4K"], + filterSubtitle: currentFilters.Subtitle, + filterUncensored: currentFilters.Uncensored, + sortOrder: enabledSortOrder + }); + $("#magnets-span").text("取消磁力过滤"); + await storageManager.saveSettingItem("enableMagnetsFilter", YES); + } else { + highlightMagnetPlugin.showAll(); + $("#magnets-span").text("排序与过滤"); + await storageManager.saveSettingItem("enableMagnetsFilter", NO); + } + } catch (e) { + console.error("[磁力排序] 应用失败:", e); + } + closePanel(); + }); + } async showStatus(carNum2) { const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span"), hotKeyDisplay = (hotKey) => hotKey ? `(${hotKey})` : ""; $filterBtn.text(`🚫 屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); diff --git a/src/core/constants.js b/src/core/constants.js index 1b1d27b..6fe8ace 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -569,6 +569,198 @@ const mainCss = ` background: rgba(59, 130, 246, 0.9); box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4); } + + .magnet-4k-highlight { + background-color: #fff5f5 !important; + } + .magnet-4k-highlight .name { + color: #f40 !important; + } + + .msf-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.3); + z-index: 999997; + } + .msf-panel { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #fff; + border-radius: 10px; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2); + z-index: 999998; + width: 380px; + display: flex; + flex-direction: column; + } + .msf-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + border-bottom: 1px solid #e5e7eb; + background: #f9fafb; + border-radius: 10px 10px 0 0; + } + .msf-title { + font-size: 15px; + font-weight: 600; + color: #1e293b; + } + .msf-close { + font-size: 22px; + color: #9ca3af; + cursor: pointer; + padding: 0 6px; + line-height: 1; + transition: color 0.2s; + } + .msf-close:hover { + color: #ef4444; + } + .msf-body { + padding: 18px; + display: flex; + flex-direction: column; + gap: 16px; + } + .msf-section { + display: flex; + flex-direction: column; + gap: 8px; + } + .msf-section-title { + font-size: 13px; + font-weight: 600; + color: #6b7280; + } + .msf-sort-list { + display: flex; + flex-direction: column; + gap: 6px; + } + .msf-sort-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 6px; + cursor: default; + transition: all 0.2s; + user-select: none; + } + .msf-sort-item:hover { + border-color: #cbd5e1; + background: #f1f5f9; + } + .msf-sort-item.msf-dragging { + opacity: 0.5; + border-color: #3b82f6; + background: #eff6ff; + } + .msf-sort-item.msf-drag-over { + border-color: #3b82f6; + background: #eff6ff; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); + } + .msf-drag-handle { + color: #cbd5e1; + font-size: 16px; + cursor: grab; + line-height: 1; + letter-spacing: -2px; + transition: color 0.2s; + } + .msf-drag-handle:active { + cursor: grabbing; + } + .msf-sort-item:hover .msf-drag-handle { + color: #94a3b8; + } + .msf-sort-label { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #374151; + cursor: pointer; + flex: 1; + } + .msf-sort-checkbox { + margin: 0; + cursor: pointer; + width: 16px; + height: 16px; + } + .msf-sort-hint { + font-size: 12px; + color: #9ca3af; + margin-left: 4px; + } + .msf-filter-list { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + .msf-filter-label { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 14px; + color: #374151; + cursor: pointer; + transition: all 0.2s; + user-select: none; + } + .msf-filter-label:hover { + border-color: #cbd5e1; + background: #f1f5f9; + } + .msf-filter-checkbox { + margin: 0; + cursor: pointer; + width: 16px; + height: 16px; + } + .msf-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 14px 18px; + border-top: 1px solid #e5e7eb; + } + .msf-btn { + padding: 8px 22px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + } + .msf-btn-cancel { + background: #f3f4f6; + color: #374151; + } + .msf-btn-cancel:hover { + background: #e5e7eb; + } + .msf-btn-confirm { + background: #3b82f6; + color: #fff; + } + .msf-btn-confirm:hover { + background: #2563eb; + } `; diff --git a/src/plugins/detail-page-button.js b/src/plugins/detail-page-button.js index a0c1b56..c79b492 100644 --- a/src/plugins/detail-page-button.js +++ b/src/plugins/detail-page-button.js @@ -23,7 +23,7 @@ class DetailPageButtonPlugin extends BasePlugin { } async createMenuBtn() { const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum, - buttonsHtml = '\n
\n
\n \n 🚫 屏蔽\n \n \n ⭐ 收藏\n \n \n 📥️ 已下载\n \n \n 🔍 已观看\n \n
\n \n
\n \n 关闭磁力过滤\n \n \n 磁力搜索\n \n \n 字幕搜索\n \n \n
\n
\n '; + buttonsHtml = '\n
\n
\n \n 🚫 屏蔽\n \n \n ⭐ 收藏\n \n \n 📥️ 已下载\n \n \n 🔍 已观看\n \n
\n \n
\n \n 排序与过滤\n \n \n 磁力搜索\n \n \n 字幕搜索\n \n \n
\n
\n '; isJavDb && $(".tabs").after(buttonsHtml); isJavBus && $("#mag-submit-show").before(buttonsHtml); $("#favoriteBtn").on("click", (() => this.favoriteOne())); @@ -43,19 +43,38 @@ class DetailPageButtonPlugin extends BasePlugin { } }); })); - const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", YES); - $("#magnets-span").text(enableMagnetsFilter === YES ? "关闭磁力过滤" : "开启磁力过滤"); - enableMagnetsFilter === YES && highlightMagnetPlugin.doFilterMagnet(); + const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), + enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", NO); + if (enableMagnetsFilter === YES) { + const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); + let settings; + try { + settings = JSON.parse(savedSettings); + } catch (e) { + settings = {}; + } + const sortEnabled = settings.sortEnabled || {}; + const fullSortOrder = (settings.sortOrder && settings.sortOrder.length === 3) ? settings.sortOrder : ["date", "size", "files"]; + const effectiveSortOrder = fullSortOrder.filter(key => sortEnabled[key]); + highlightMagnetPlugin.applySortAndFilter({ + filterHD: settings.filterHD || !1, + filter4K: settings.filter4K || !1, + filterSubtitle: settings.filterSubtitle || !1, + filterUncensored: settings.filterUncensored || !1, + sortOrder: effectiveSortOrder, + }); + $("#magnets-span").text("取消磁力过滤"); + } else { + $("#magnets-span").text("排序与过滤"); + } $("#enable-magnets-filter").on("click", (event => { - let $span = $("#magnets-span"); - if ("关闭磁力过滤" === $span.text()) { + const $span = $("#magnets-span"); + if ("取消磁力过滤" === $span.text()) { highlightMagnetPlugin.showAll(); - $span.text("开启磁力过滤"); + $span.text("排序与过滤"); storageManager.saveSettingItem("enableMagnetsFilter", NO); } else { - highlightMagnetPlugin.doFilterMagnet(); - $span.text("关闭磁力过滤"); - storageManager.saveSettingItem("enableMagnetsFilter", YES); + this.openMagnetSortFilterPanel(highlightMagnetPlugin); } })); $("#subtitleSearchBtn").on("click", (() => { @@ -66,6 +85,186 @@ class DetailPageButtonPlugin extends BasePlugin { // $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2))); this.showStatus(carNum2).then(); } + + async openMagnetSortFilterPanel(highlightMagnetPlugin) { + const existingPanel = document.getElementById("magnet-sort-filter-panel"); + if (existingPanel) { + existingPanel.remove(); + return; + } + const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); + let settings; + try { + settings = JSON.parse(savedSettings); + } catch (e) { + settings = {}; + } + const sortEnabled = settings.sortEnabled || {date: !1, size: !1, files: !1}; + const sortOrder = (settings.sortOrder && settings.sortOrder.length === 3) ? settings.sortOrder : ["date", "size", "files"]; + const filterHD = settings.filterHD || !1; + const filter4K = settings.filter4K || !1; + const filterSubtitle = settings.filterSubtitle || !1; + const filterUncensored = settings.filterUncensored || !1; + const SORT_LABELS = {date: "发布日期", size: "文件大小", files: "文件数量"}; + const SORT_HINTS = {date: "从新到旧", size: "从大到小", files: "从少到多"}; + const FILTER_ITEMS = [ + {key: "HD", label: "高清", checked: filterHD}, + {key: "4K", label: "4K", checked: filter4K}, + {key: "Subtitle", label: "字幕", checked: filterSubtitle}, + {key: "Uncensored", label: "无码", checked: filterUncensored}, + ]; + const currentSortOrder = [...sortOrder]; + const currentSortEnabled = {...sortEnabled}; + const currentFilters = { + HD: filterHD, + "4K": filter4K, + Subtitle: filterSubtitle, + Uncensored: filterUncensored, + }; + const buildSortItemsHTML = () => { + return currentSortOrder.map(key => { + const checked = currentSortEnabled[key] ? "checked" : ""; + return `
+ ⋮⋮ + +
`; + }).join(""); + }; + const buildFilterItemsHTML = () => { + return FILTER_ITEMS.map(item => { + const checked = currentFilters[item.key] ? "checked" : ""; + return ``; + }).join(""); + }; + const panelHTML = ` +
+
+
+ 磁力排序与过滤 + × +
+
+
+
排序优先级(拖动调整顺序,勾选启用)
+
+ ${buildSortItemsHTML()} +
+
+
+
过滤条件(可多选,并集关系)
+
+ ${buildFilterItemsHTML()} +
+
+
+ +
+ `; + const wrapper = document.createElement("div"); + wrapper.innerHTML = panelHTML; + document.body.appendChild(wrapper); + const panel = document.getElementById("magnet-sort-filter-panel"); + const overlay = document.getElementById("msf-overlay"); + const closePanel = () => { + panel.remove(); + overlay.remove(); + }; + overlay.addEventListener("click", closePanel); + document.getElementById("msf-close-btn").addEventListener("click", closePanel); + document.getElementById("msf-cancel-btn").addEventListener("click", closePanel); + const rebuildSortList = () => { + const sortList = document.getElementById("msf-sort-list"); + sortList.innerHTML = buildSortItemsHTML(); + bindSortEvents(); + }; + const bindSortEvents = () => { + const sortItems = document.querySelectorAll("#msf-sort-list .msf-sort-item"); + sortItems.forEach(item => { + item.addEventListener("dragstart", (e) => { + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("text/plain", item.dataset.sortKey); + item.classList.add("msf-dragging"); + }); + item.addEventListener("dragend", () => { + item.classList.remove("msf-dragging"); + document.querySelectorAll("#msf-sort-list .msf-sort-item").forEach(el => el.classList.remove("msf-drag-over")); + }); + item.addEventListener("dragover", (e) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + item.classList.add("msf-drag-over"); + }); + item.addEventListener("dragleave", () => { + item.classList.remove("msf-drag-over"); + }); + item.addEventListener("drop", (e) => { + e.preventDefault(); + item.classList.remove("msf-drag-over"); + const fromKey = e.dataTransfer.getData("text/plain"); + const toKey = item.dataset.sortKey; + if (fromKey === toKey) return; + const fromIdx = currentSortOrder.indexOf(fromKey); + const toIdx = currentSortOrder.indexOf(toKey); + currentSortOrder.splice(fromIdx, 1); + currentSortOrder.splice(toIdx, 0, fromKey); + rebuildSortList(); + }); + const checkbox = item.querySelector(".msf-sort-checkbox"); + checkbox.addEventListener("change", () => { + currentSortEnabled[item.dataset.sortKey] = checkbox.checked; + }); + }); + }; + bindSortEvents(); + document.querySelectorAll(".msf-filter-checkbox").forEach(cb => { + cb.addEventListener("change", () => { + currentFilters[cb.dataset.filterKey] = cb.checked; + }); + }); + document.getElementById("msf-confirm-btn").addEventListener("click", async () => { + try { + const enabledSortOrder = currentSortOrder.filter(key => currentSortEnabled[key]); + const newSettings = { + sortOrder: currentSortOrder, + sortEnabled: currentSortEnabled, + filterHD: currentFilters.HD, + filter4K: currentFilters["4K"], + filterSubtitle: currentFilters.Subtitle, + filterUncensored: currentFilters.Uncensored, + }; + await storageManager.saveSettingItem("magnetSortAndFilter", JSON.stringify(newSettings)); + const hasAnyFilter = currentFilters.HD || currentFilters["4K"] || currentFilters.Subtitle || currentFilters.Uncensored; + const hasAnySort = enabledSortOrder.length > 0; + if (hasAnyFilter || hasAnySort) { + await highlightMagnetPlugin.applySortAndFilter({ + filterHD: currentFilters.HD, + filter4K: currentFilters["4K"], + filterSubtitle: currentFilters.Subtitle, + filterUncensored: currentFilters.Uncensored, + sortOrder: enabledSortOrder, + }); + $("#magnets-span").text("取消磁力过滤"); + await storageManager.saveSettingItem("enableMagnetsFilter", YES); + } else { + highlightMagnetPlugin.showAll(); + $("#magnets-span").text("排序与过滤"); + await storageManager.saveSettingItem("enableMagnetsFilter", NO); + } + } catch (e) { + console.error("[磁力排序] 应用失败:", e); + } + closePanel(); + }); + } async showStatus(carNum2) { const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span"), hotKeyDisplay = hotKey => hotKey ? `(${hotKey})` : ""; $filterBtn.text(`🚫 屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); diff --git a/src/plugins/highlight-magnet.js b/src/plugins/highlight-magnet.js index a532ef6..1290bc9 100644 --- a/src/plugins/highlight-magnet.js +++ b/src/plugins/highlight-magnet.js @@ -5,26 +5,114 @@ class HighlightMagnetPlugin extends BasePlugin { getName() { return "HighlightMagnetPlugin"; } - doFilterMagnet() { - this.handleDb(); - this.handleBus(); + + parseDateFromAttr(dateStr) { + if (!dateStr || dateStr.length !== 8) { + const d = new Date(dateStr); + return isNaN(d.getTime()) ? 0 : d.getTime(); + } + const y = parseInt(dateStr.substring(0, 4), 10); + const m = parseInt(dateStr.substring(4, 6), 10) - 1; + const d = parseInt(dateStr.substring(6, 8), 10); + const date = new Date(y, m, d); + return isNaN(date.getTime()) ? 0 : date.getTime(); } - handleDb() { + + getRowInfo(row) { + const $row = $(row); + const nameText = $row.find('.name').text().toLowerCase(); + const tags = []; + $row.find('.tags .tag').each((_, tag) => tags.push($(tag).text().toLowerCase())); + const sizeMB = parseInt($row.attr('data-size'), 10) || 0; + const files = parseInt($row.attr('data-files'), 10) || 0; + const dateStr = $row.attr('data-date') || ''; + return { + row: row, + $row: $row, + nameText: nameText, + date: this.parseDateFromAttr(dateStr), + dateStr: dateStr, + size: sizeMB * 1024 * 1024, + files: files, + tags: tags, + is4K: nameText.includes('4k') || nameText.includes('[4k]') || tags.includes('4k'), + isHD: nameText.includes('高清') || tags.includes('高清'), + isSubtitle: nameText.includes('字幕') || nameText.includes('-c') || nameText.includes('.chs') || nameText.includes('.cht') || nameText.includes('.chi') || tags.includes('字幕'), + isUncensored: nameText.includes('无码') || nameText.includes('無碼') || nameText.includes('-u') || nameText.includes('-uc') || tags.includes('无码') || tags.includes('無碼'), + }; + } + + async applySortAndFilter(settings) { if (!isJavDb) return; - let magnetNameList = $("#magnets-content .name"); - if (0 === magnetNameList.length) return; - const HIGH_QUALITY_KEYWORDS = [ "4k", "-c", "-u", "-uc" ]; - let hasHighQuality = !1; - magnetNameList.each(((_, el) => { - const $el = $(el), text = $el.text().toLowerCase(), isHighQuality = HIGH_QUALITY_KEYWORDS.some((keyword => text.includes(keyword))); - $el.parent().parent().parent().addClass("magnet-row"); - text.includes("4k") && $el.css("color", "#f40"); - if (isHighQuality) { - hasHighQuality = !0; - $el.parent().parent().parent().addClass("high-quality"); + const $container = $("#magnets-content"); + const $rows = $container.find(".item[data-rank]"); + if (0 === $rows.length) return; + const rowInfos = $rows.toArray().map(row => this.getRowInfo(row)); + const {filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder} = settings; + const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored; + let visibleInfos = rowInfos; + if (anyFilterEnabled) { + visibleInfos = rowInfos.filter(info => { + if (filterHD && info.isHD) return !0; + if (filter4K && info.is4K) return !0; + if (filterSubtitle && info.isSubtitle) return !0; + if (filterUncensored && info.isUncensored) return !0; + return !1; + }); + } + if (sortOrder && sortOrder.length > 0) { + visibleInfos.sort((a, b) => { + for (const key of sortOrder) { + let cmp = 0; + switch (key) { + case 'date': + cmp = b.date - a.date; + break; + case 'size': + cmp = b.size - a.size; + break; + case 'files': + cmp = a.files - b.files; + break; + } + if (0 !== cmp) return cmp; + } + return 0; + }); + } + const container = document.getElementById("magnets-content"); + if (!container) return; + const hiddenInfos = rowInfos.filter(info => !visibleInfos.includes(info)); + const reviewsContainer = document.getElementById("reviewsContainer"); + const insertRef = reviewsContainer && reviewsContainer.parentNode === container + ? (reviewsContainer.previousElementSibling || reviewsContainer) + : null; + const fragment = document.createDocumentFragment(); + visibleInfos.forEach(info => fragment.appendChild(info.row)); + hiddenInfos.forEach(info => fragment.appendChild(info.row)); + if (insertRef) { + container.insertBefore(fragment, insertRef); + } else { + container.appendChild(fragment); + } + visibleInfos.forEach(info => { + if (info.is4K) { + info.$row.addClass('magnet-4k-highlight'); } - })); - hasHighQuality ? $("#magnets-content .magnet-row").not(".high-quality").hide() : $("#enable-magnets-filter").addClass("do-hide"); + }); + hiddenInfos.forEach(info => $(info.row).hide()); + } + + showAll() { + if (isJavDb) { + $("#magnets-content .item[data-rank]").toArray().forEach((el => { + $(el).show(); + $(el).removeClass('magnet-4k-highlight'); + })); + } + if (isJavBus) { + $("#magnet-table tr").toArray().forEach((el => $(el).show())); + } } handleBus() { isJavBus && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => { @@ -44,12 +132,6 @@ class HighlightMagnetPlugin extends BasePlugin { })) : $("#enable-magnets-filter").addClass("do-hide"); })); } - showAll() { - if (isJavDb) { - $("#magnets-content .item").toArray().forEach((el => $(el).show())); - } - isJavBus && $("#magnet-table tr").toArray().forEach((el => $(el).show())); - } }