您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Wraps CivitAI versions into multiple rows, adds a search bar, and allows for sorting alphabetically.
当前为
// ==UserScript== // @name Civitai Model Versions Wraparound + Search + Sort // @version 0.2.1 // @description Wraps CivitAI versions into multiple rows, adds a search bar, and allows for sorting alphabetically. // @author redtvpe // @match https://civitai.com/models/* // @grant none // @namespace https://gf.qytechs.cn/users/1418032 // ==/UserScript== (function () { 'use strict'; // --- 1) SELECT THE VERSION CONTAINER --- const scrollAreaSelector = '.mantine-ScrollArea-viewport .mantine-Group-root'; let scrollArea = null; // --- 2) PARSE __NEXT_DATA__ FOR MODEL VERSIONS & BUILD A DATE DICTIONARY --- let dateDict = {}; try { const nextData = JSON.parse(document.getElementById("__NEXT_DATA__").innerText); // Look in the queries array for the one that contains "model","getById" const modelQuery = nextData.props.pageProps.trpcState.json.queries .filter(x => x.queryHash.includes('"model","getById"'))[0]; const modelVersions = modelQuery.state.data.modelVersions; // Build the dictionary: { versionName (lowercase) -> Date } dateDict = modelVersions.reduce((acc, v) => { acc[v.name.trim().toLowerCase()] = new Date(v.publishedAt); return acc; }, {}); } catch (err) { console.warn("[Civitai] Could not parse modelVersions from __NEXT_DATA__:", err); } // --- 3) SAVE ORIGINAL ORDER --- let originalOrder = []; // --- 4) CREATE / UPDATE THE CONTROL PANEL --- function injectControls(container) { // Prevent duplicate insertion if (document.getElementById('civitaiVersionControls')) return; const controlPanel = document.createElement('div'); controlPanel.id = 'civitaiVersionControls'; controlPanel.style.marginBottom = '10px'; controlPanel.style.display = 'flex'; controlPanel.style.flexWrap = 'wrap'; controlPanel.style.alignItems = 'center'; controlPanel.style.gap = '10px'; // Count label for visible versions const countLabel = document.createElement('span'); countLabel.style.fontWeight = 'bold'; updateCountLabel(countLabel, container); // Search input const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.placeholder = 'Search versions...'; searchInput.style.padding = '4px'; // Clear button for search const clearBtn = document.createElement('button'); clearBtn.textContent = 'Clear'; clearBtn.style.padding = '4px 8px'; clearBtn.addEventListener('click', () => { searchInput.value = ''; searchInput.dispatchEvent(new Event('input')); }); // "Sort by:" label const sortLabel = document.createElement('span'); sortLabel.textContent = 'Sort by:'; // Sort dropdown const sortSelect = document.createElement('select'); sortSelect.style.padding = '4px'; const options = [ { value: 'default', text: 'Default' }, { value: 'date-desc', text: 'Date (Newest First)' }, { value: 'date-asc', text: 'Date (Oldest First)' }, { value: 'alpha-asc', text: 'Alphabetical (A–Z)' }, { value: 'alpha-desc', text: 'Alphabetical (Z–A)' }, ]; options.forEach(opt => { const optionEl = document.createElement('option'); optionEl.value = opt.value; optionEl.textContent = opt.text; sortSelect.appendChild(optionEl); }); // Append controls controlPanel.appendChild(countLabel); controlPanel.appendChild(searchInput); controlPanel.appendChild(clearBtn); controlPanel.appendChild(sortLabel); controlPanel.appendChild(sortSelect); // Insert control panel before the container container.parentNode.insertBefore(controlPanel, container); // --- Event: Search --- searchInput.addEventListener('input', () => { const query = searchInput.value.toLowerCase(); const items = [...container.children]; items.forEach(item => { const text = item.textContent.toLowerCase(); item.style.display = text.includes(query) ? '' : 'none'; }); updateCountLabel(countLabel, container); }); // --- Event: Sort --- sortSelect.addEventListener('change', () => { applySorting(container, sortSelect.value); // Re-run search filter to maintain hidden items searchInput.dispatchEvent(new Event('input')); }); } // Helper: Update version count label based on visible buttons function updateCountLabel(labelEl, container) { const items = [...container.children]; const visibleCount = items.filter(item => item.style.display !== 'none').length; labelEl.textContent = `Total Versions: ${visibleCount}`; } // --- 5) SORTING LOGIC --- function applySorting(container, mode) { if (!container) return; const items = [...container.children]; if (mode === 'default') { // Revert to original order originalOrder.forEach(node => container.appendChild(node)); return; } items.sort((a, b) => { const aText = a.textContent.trim().toLowerCase(); const bText = b.textContent.trim().toLowerCase(); if (mode === 'date-desc' || mode === 'date-asc') { const aDate = dateDict[aText] || new Date(0); const bDate = dateDict[bText] || new Date(0); const diff = bDate - aDate; return mode === 'date-desc' ? diff : -diff; } else if (mode === 'alpha-asc') { if (aText < bText) return -1; if (aText > bText) return 1; return 0; } else if (mode === 'alpha-desc') { if (aText > bText) return -1; if (aText < bText) return 1; return 0; } return 0; }); items.forEach(item => container.appendChild(item)); } // --- 6) MAIN LAYOUT ADJUSTMENT FUNCTION --- function adjustLayout() { scrollArea = document.querySelector(scrollAreaSelector); if (!scrollArea) return; // Wrap versions into multiple rows scrollArea.style.display = 'flex'; scrollArea.style.flexWrap = 'wrap'; scrollArea.style.gap = '8px'; scrollArea.style.overflowX = 'visible'; // Save original order on first run if (originalOrder.length === 0 && scrollArea.children.length > 0) { originalOrder = [...scrollArea.children]; } injectControls(scrollArea); } // --- 7) SETUP MUTATION OBSERVER --- const observer = new MutationObserver(mutations => { for (const mutation of mutations) { if (mutation.addedNodes.length > 0) { adjustLayout(); } } }); const body = document.querySelector('body'); if (body) { observer.observe(body, { childList: true, subtree: true }); } // --- 8) INITIAL RUN --- adjustLayout(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址