您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a simpler mobile-friendly tag-search + thread-search popup button.
// ==UserScript== // @name SB/SV/QQ Simple Filter & Search // @description Adds a simpler mobile-friendly tag-search + thread-search popup button. // @version 1.10 // @author C89sd // @namespace https://gf.qytechs.cn/users/1376767 // @match https://*.spacebattles.com/* // @match https://*.sufficientvelocity.com/* // @match https://*.questionablequesting.com/* // @noframes // ==/UserScript== 'use strict'; let FOCUS = false; let site = location.hostname.split('.').slice(-2, -1)[0]; if (location.pathname.includes('/threads/')) return; // if (!location.pathname.includes('/forums/')) return; const isSearchPage = location.pathname.match(/\/search\/\d+\//) && location.search; const FORUM_NODES = { 'spacebattles': { creative: [18], quests: [240] }, 'sufficientvelocity': { creative: [2], quests: [29] }, 'questionablequesting': { creative: [19, 29], quests: [20, 12] }, 'alternatehistory': { creative: [], quests: [] } }[site]; // --- Initial State from URL Parameters --- let initialSimpleOrder = 'watchers'; let initialSimpleDirection = 'desc'; let initialMinWordCount = 0; let initialMaxWordCount = 0; let initialWithoutSynonyms = false; let initialTags = []; let initialActiveTab = 'simple'; // Default to simple filter for URL params let initialThreadQuery = ''; let initialThreadSearchFirstPostOnly = false; let initialThreadMinReplies = 0; let initialThreadSortBy = 'date'; let initialThreadForums = ['creative', 'quests']; // Default to checked function parseAndSetInitialState() { const params = new URLSearchParams(location.search); // If on a thread search result page, parse thread search params if (isSearchPage) { initialActiveTab = 'thread'; if (params.has('keywords') || params.has('q')) initialThreadQuery = params.get('keywords') || params.get('q'); if (params.get('c[content]') === 'thread') initialThreadSearchFirstPostOnly = true; if (params.has('c[tags]')) { initialTags = params.get('c[tags]').split(',').map(t => t.trim()).filter(t => t); } if (params.has('c[word_count][lower]')) initialMinWordCount = parseInt(params.get('c[word_count][lower]'), 10) || 0; if (params.has('c[word_count][upper]')) initialMaxWordCount = parseInt(params.get('c[word_count][upper]'), 10) || 0; if (params.has('c[min_reply_count]')) initialThreadMinReplies = parseInt(params.get('c[min_reply_count]'), 10) || 0; if (params.has('order') || params.has('o')) initialThreadSortBy = params.get('order') || params.get('o'); // Parse c[nodes] to determine which forums were searched const urlNodeKeys = Array.from(params.keys()).filter(key => key.startsWith('c[nodes][')); if (urlNodeKeys.length > 0) { const urlNodes = new Set(urlNodeKeys.map(key => parseInt(params.get(key), 10))); const selectedForums = []; // For each forum category, check if ALL its required nodes are in the URL params for (const forumCategory in FORUM_NODES) { const requiredNodes = FORUM_NODES[forumCategory]; if (requiredNodes.length > 0 && requiredNodes.every(nodeId => urlNodes.has(nodeId))) { selectedForums.push(forumCategory); } } initialThreadForums = selectedForums; } } else { // It's a simple filter URL or a regular forum page if (params.has('order')) { initialSimpleOrder = params.get('order'); } if (params.has('direction')) { initialSimpleDirection = params.get('direction'); } if (params.has('min_word_count')) { initialMinWordCount = parseInt(params.get('min_word_count'), 10); } if (params.has('max_word_count')) { initialMaxWordCount = parseInt(params.get('max_word_count'), 10); } if (params.has('withoutSynonym') && params.get('withoutSynonym') === '1') { initialWithoutSynonyms = true; } // Parse tags[] for simple filter const tagKeys = Array.from(params.keys()).filter(key => key.startsWith('tags[') && key.endsWith(']')); tagKeys.forEach(tagKey => { initialTags.push(params.get(tagKey)); }); if (tagKeys.length > 0 || params.has('min_word_count') || params.has('max_word_count') || params.has('withoutSynonym')) { initialActiveTab = 'simple'; // Ensure simple tab is active if any simple filter params are present } } } // Call this function immediately to set initial state from URL parseAndSetInitialState(); // --- 1. SCRIPT INITIALIZATION --- const targetContainer = document.querySelector('.block-outer-opposite .buttonGroup') || document.querySelector('.p-breadcrumbs'); // on search pages, .p-breadcrumbs puts it at the top if (!targetContainer) return; const xfToken = document.querySelector('input[name="_xfToken"]')?.value; if (!xfToken) console.warn('QQ Search Script: Could not find _xfToken. Tag search will not work.'); // --- 2. STATE MANAGEMENT --- let searchPanel = null; let selectedTags = new Set(); let debounceTimer; let activeTab = initialActiveTab; // Set initial active tab based on URL parameters const TAG_SEARCH_DEBOUNCE_DELAY = 500; const TOP_BACKGROUND_COLOR = window.getComputedStyle(document.querySelector('#top')).backgroundColor; // --- 3. CSS STYLES --- const styles = ` .menu { margin: 0; } .qq-search-panel { position: absolute; display: none; z-index: 1200; top: 0; width: 90vw; max-width: 480px; left: 50%; transform: translateX(-50%); } .qq-search-panel.is-active { display: block; } /* Tab navigation */ .search-tabs { display: flex; position: relative; } .search-tab { padding: 8px 12px; cursor: pointer; border: 1px solid transparent; border-bottom: none; user-select: none; } .search-tab.is-active { background-color: var(--xf-contentBg); border-color: var(--input-border-light); border-bottom-color: var(--xf-contentBg); position: relative; top: 1px; border-top-left-radius: 3px; border-top-right-radius: 3px; } .qq-search-panel .close-button { position: absolute; right: 0; top: 0; height: 100%; display: flex; align-items: center; padding: 0 15px; font-size: 24px; cursor: pointer; color: #808080; /* A neutral gray color */ background-color: transparent; border: none; box-sizing: border-box; user-select: none; /* Make the 'X' non-selectable */ } .qq-search-panel .close-button:hover { color: #007bff; /* A common blue for hover effect */ } /* removed padding so items sit flush with the top */ /* Hide/Show elements based on active tab */ /* FIX: Increased selector specificity to ensure elements are hidden by default, fixing the visibility bug. */ .menu-row[data-tab-exclusive] { display: none; } .qq-search-panel.is-simple-tab .menu-row[data-tab-exclusive="simple"], .qq-search-panel.is-thread-tab .menu-row[data-tab-exclusive="thread"] { display: flex; /* Use flex for alignment */ } /* Form row layout fixes for vertical alignment */ .flex-form-row { display: flex; align-items: center; } .flex-form-row > label { /* FIX: Set a fixed basis for all labels to align them into a neat column. */ flex-basis: 70px; /* 25% smaller than 95px (95 * 0.75 = 71.25) */ flex-shrink: 0; margin-right: 12px; white-space: nowrap; text-align: right; } /* generic vertical row layout */ .menu-row { border-top: 1px solid var(--input-border-light); display: flex; flex-direction: column; padding-top: 6px; } .menu-row:first-child { border-top: none; } .menu-row.no-border { border-top: none; padding-top: 0; } .menu-row > label { margin-bottom: 6px; text-align: left; } .menu-row > .split-2 { display: flex; gap: 5px; } /* inputs / selects share the space evenly, single element fills all */ .split-2 > .input, .split-2 > .input--number { flex: 1 1 50% !important; min-width: 0; width: 100% !important; max-width: none !important; } /* Checkbox row layout */ .checkbox-row { display: flex; flex-wrap: wrap; gap: 15px; align-items: center; } .checkbox-row label, .selected-tag { user-select: none; } /* Tag search styling */ .tag-search-container { display: flex; flex-wrap: wrap; align-items: center; padding: 2px; } /* Thread search field styling */ .thread-search-container { display: flex; align-items: center; width: 100%; } /* Fix overflow for simple sort selectors */ .flex-form-row .inputGroup .input { flex: 1 1 50%; min-width: 0; } .selected-tag { display: inline-flex; align-items: center; border: 1px solid var(--input-border-heavy, #505050); margin: 2px; padding: 0px 0px 0px 6px; border-radius: 5px; font-size: 0.9em; } .selected-tag-remove { padding: 0 6px 0 4px; cursor: pointer; font-weight: bold; } #tag-search-input { flex-grow: 1; border: none; background: transparent; padding: 4px; min-width: 120px; } #tag-search-input:focus { outline: none; } .tag-suggestions-wrapper { position: relative; } /* Suggestion box */ #tag-suggestions { position: absolute; width: 100%; top: calc(100% - 1px); /* Offset below the input container */ left: 0; z-index: 1201; /* On top of the panel (z-index 1200) */ } #tag-suggestions:not(:empty)::before { content: ''; display: block; margin: 0; border-top: none; /* Remove separator */ } .suggestion-list { max-height: 153px; overflow-y: auto; border: 1px solid var(--input-border-light); border-radius: 3px; /* background-color will be set dynamically */ } .suggestion-item .contentRow-main { padding: 8px 12px; } .suggestion-item:hover, .suggestion-item.is-highlighted { background-color: var(--xf-contentHighlightBg); } /* Disable number input spinners */ input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } input[type=number] { -moz-appearance: textfield; } label:has(input[type="checkbox"]) { user-select: none; } #sf-menu-form { border: 1px solid var(--input-border-light); border-top: none; } `; const styleSheet = document.createElement("style"); styleSheet.innerText = styles; document.head.appendChild(styleSheet); // --- 4. CREATE THE TRIGGER BUTTON --- const triggerButton = document.createElement('a'); triggerButton.className = 'button'; // button--link triggerButton.innerHTML = `<span class="button-text">Simple Filter</span>`; triggerButton.href = "#"; targetContainer.appendChild(triggerButton); if (isSearchPage) { triggerButton.textContent = 'Thread Search'; } // --- 5. PANEL CREATION AND LOGIC --- function createSearchPanel() { if (searchPanel) return; const panel = document.createElement('div'); panel.className = 'menu menu--wide qq-search-panel is-simple-tab'; panel.innerHTML = ` <div class="menu-content"> <div id="sf-search-tabs" class="search-tabs"> <div class="search-tab is-active" data-tab="simple">Simple Filter</div> <div class="search-tab" data-tab="thread">Thread Search</div> <div class="close-button">×</div> </div> <div id="sf-menu-form" class="menu-form"> <!-- THREAD SEARCH EXCLUSIVE --> <div class="menu-row" data-tab-exclusive="thread"> <label for="thread-search-query">Search title:</label> <div> <input type="text" class="input" id="thread-search-query" placeholder="Search title and tags..." autocomplete="off"> </div> </div> <div class="menu-row no-border" data-tab-exclusive="thread"> <label><input type="checkbox" id="search-first-post" /> Search first post</label> </div> <!-- SHARED: Tags --> <div class="menu-row"> <label>Tags:</label> <!-- Use a simple, non-flex wrapper instead of .split-2 --> <div class="tag-suggestions-wrapper"> <div class="input tag-search-container"> <input type="text" id="tag-search-input" placeholder="Search tags..." autocomplete="off"> </div> <!-- This will now appear below the div above, as intended --> <div id="tag-suggestions"></div> </div> </div> <div class="menu-row no-border"> <label><input type="checkbox" id="without-synonyms" /> Without synonyms</label> </div> <!-- SHARED: Word Count --> <div class="menu-row"> <label>Word count:</label> <div class="split-2"> <input type="text" class="input input--number" value="0" name="min_word_count" placeholder="Min"> <input type="text" class="input input--number" value="0" name="max_word_count" placeholder="Max"> </div> </div> <!-- SIMPLE SEARCH EXCLUSIVE --> <div class="menu-row" data-tab-exclusive="simple"> <label>Sort by:</label> <div class="split-2"> <select name="simple_order" class="input"> <option value="last_threadmark">Last threadmark</option> <!-- <option value="last_post_date" selected="selected">Last message</option> --> <option value="post_date">First message</option> <!-- <option value="title">Title</option> --> <option value="reply_count">Replies</option> <option value="view_count">Views</option> <option value="first_post_reaction_score">First message reaction score</option> <option value="word_count">Word count</option> <option value="watchers" selected>Watchers</option> </select> <select name="simple_direction" class="input"> <option value="desc" selected>Descending</option> <option value="asc">Ascending</option> </select> </div> </div> <!-- THREAD SEARCH EXCLUSIVE --> <div class="menu-row" data-tab-exclusive="thread"> <label for="min-replies">Min replies:</label> <div> <input type="text" class="input input--number" value="0" id="min-replies"> </div> </div> <div class="menu-row" data-tab-exclusive="thread"> <label>Forums:</label> <div id="ts_forum_choice" class="checkbox-row"> <label><input type="checkbox" name="forum_choice" value="creative" checked> Creative Writing</label> <label><input type="checkbox" name="forum_choice" value="quests"> Quests</label> </div> </div> <div class="menu-row" data-tab-exclusive="thread"> <label>Sort by:</label> <div> <select id="ts_thread_order" name="thread_order" class="input"> <option value="relevance">Relevance</option> <option value="date" selected>Date</option> <option value="last_update">Most recent</option> <option value="replies">Most replies</option> <option value="word_count">Words</option> </select> </div> </div> <!-- Filter Button --> <div class="menu-footer"> <span class="menu-footer-controls"> <button type="submit" class="button--primary button" id="advanced-search-filter-btn"> <span class="button-text">Filter</span> </button> </span> </div> </div> </div> `; document.body.appendChild(panel); searchPanel = panel; if (isSearchPage) { // Remove the first tab "Simple Filter" const simpleTab = searchPanel.querySelector('.search-tab[data-tab="simple"]'); if (simpleTab) { simpleTab.remove(); } } addPanelEventListeners(); loadInitialFilterState(); // Load query parameters into the filter } function loadInitialFilterState() { // --- Load Shared State --- searchPanel.querySelector(`input[name="min_word_count"]`).value = initialMinWordCount || 0; searchPanel.querySelector(`input[name="max_word_count"]`).value = initialMaxWordCount || 0; searchPanel.querySelector(`#without-synonyms`).checked = initialWithoutSynonyms; if (initialTags.length > 0) { initialTags.forEach(tagText => { selectTag({ id: tagText, text: tagText }); }); } // --- Load Simple Filter State --- searchPanel.querySelector(`select[name="simple_order"]`).value = initialSimpleOrder; searchPanel.querySelector(`select[name="simple_direction"]`).value = initialSimpleDirection; // --- Load Thread Search State --- searchPanel.querySelector('#thread-search-query').value = initialThreadQuery; searchPanel.querySelector('#search-first-post').checked = initialThreadSearchFirstPostOnly; searchPanel.querySelector('#min-replies').value = initialThreadMinReplies || 0; searchPanel.querySelector('#ts_thread_order').value = initialThreadSortBy; searchPanel.querySelectorAll('#ts_forum_choice input[name="forum_choice"]').forEach(cb => { cb.checked = initialThreadForums.includes(cb.value); }); // --- Set Initial Active Tab --- const tabToActivate = initialActiveTab === 'thread' ? 'thread' : 'simple'; const tabElement = searchPanel.querySelector(`#sf-search-tabs .search-tab[data-tab="${tabToActivate}"]`); if (tabElement) { handleTabClick({ currentTarget: tabElement }); } } function addPanelEventListeners() { searchPanel.querySelectorAll('#sf-search-tabs .search-tab').forEach(tab => tab.addEventListener('click', handleTabClick)); searchPanel.querySelector('.close-button').addEventListener('click', hidePanel); const tagInput = searchPanel.querySelector('#tag-search-input'); tagInput.addEventListener('input', handleTagInput); tagInput.addEventListener('focus', () => { const query = tagInput.value.trim(); const suggestionsContainer = searchPanel.querySelector('#tag-suggestions'); if (query.length >= 2 && suggestionsContainer.children.length === 0) { handleTagInput({ target: tagInput }); } }); tagInput.addEventListener('keydown', handleFilterKeydown); // Must be before tag so it doesnt press filter once empty tagInput.addEventListener('keydown', handleTagInputKeydown); // Existing listener for suggestions searchPanel.querySelectorAll('input[type="number"]').forEach(input => { input.addEventListener('focus', e => e.target.select()); input.addEventListener('keydown', handleFilterKeydown); }); searchPanel.querySelector('#thread-search-query')?.addEventListener('keydown', handleFilterKeydown); searchPanel.querySelector('#advanced-search-filter-btn').addEventListener('click', handleFilter); searchPanel.addEventListener('click', (e) => { const suggestions = searchPanel.querySelector('#tag-suggestions'); if (suggestions && !suggestions.contains(e.target) && e.target.id !== 'tag-search-input' && !searchPanel.querySelector('.close-button').contains(e.target)) { suggestions.innerHTML = ''; clearTimeout(debounceTimer); // Clear any pending tag search } }); } function handleTabClick(event) { const clickedTab = event.currentTarget; activeTab = clickedTab.dataset.tab; searchPanel.querySelectorAll('#sf-search-tabs .search-tab').forEach(t => t.classList.remove('is-active')); clickedTab.classList.add('is-active'); searchPanel.classList.toggle('is-simple-tab', activeTab === 'simple'); searchPanel.classList.toggle('is-thread-tab', activeTab === 'thread'); // Autofocus based on active tab if (activeTab === 'simple') { // searchPanel.querySelector('#tag-search-input')?.focus(); // thread autofocus } else if (activeTab === 'thread') { // searchPanel.querySelector('#thread-search-query')?.focus(); } } function togglePanel(event) { event.preventDefault(); event.stopPropagation(); document.querySelector('.menu.menu--wide.menu--right.is-active')?.remove(); // close filter panel if (!searchPanel) createSearchPanel(); searchPanel.classList.contains('is-active') ? hidePanel() : showPanel(); } function showPanel() { const rect = triggerButton.getBoundingClientRect(); // const panelTop = window.scrollY + rect.top -8; const panelTop = window.scrollY + rect.bottom + 0; // window.scrollTo(0, panelTop); searchPanel.style.top = panelTop + 'px'; searchPanel.classList.add('is-active'); document.addEventListener('click', closeOnClickOutside, true); FOCUS && searchPanel.querySelector('#tag-search-input')?.focus(); // Autofocus when panel opens } function hidePanel() { if (!searchPanel) return; searchPanel.classList.remove('is-active'); searchPanel.querySelector('#tag-suggestions').innerHTML = ''; document.removeEventListener('click', closeOnClickOutside, true); } function closeOnClickOutside(event) { if (searchPanel && !searchPanel.contains(event.target) && !triggerButton.contains(event.target)) { event.preventDefault(); // Prevent default action (e.g., link navigation) event.stopPropagation(); // Stop propagation to prevent accidental clicks on underlying elements hidePanel(); } } // --- 6. TAG SEARCH FUNCTIONALITY --- function handleTagInput(event) { clearTimeout(debounceTimer); const query = event.target.value.trim(); const suggestionsContainer = searchPanel.querySelector('#tag-suggestions'); if (query.length < 2) { suggestionsContainer.innerHTML = ''; return; } debounceTimer = setTimeout(() => { if (!xfToken) return console.error("Cannot fetch tags: _xfToken is missing."); const params = new URLSearchParams({ 'q': query, '_xfRequestUri': location.pathname, '_xfWithData': '1', '_xfToken': xfToken, '_xfResponseType': 'json' }); fetch(`/misc/tag-auto-complete?${params.toString()}`) .then(response => response.ok ? response.json() : Promise.reject(response)) .then(data => { if (data.results) displaySuggestions(data.results); }) .catch(error => console.error('Error fetching tags:', error)); }, TAG_SEARCH_DEBOUNCE_DELAY); } function handleTagInputKeydown(event) { if (event.key === 'Enter') { const suggestionItem = searchPanel.querySelector('.suggestion-item'); if (suggestionItem) { suggestionItem.click(); event.preventDefault(); // Prevent default form submission // Unfocus the input after selection searchPanel.querySelector('#tag-search-input').blur(); } } } function handleFilterKeydown(event) { if (event.key === 'Enter') { const inputElement = event.target; // For text inputs, only trigger if empty if (inputElement.type === 'text' && inputElement.value.trim() !== '') { return; } // For number inputs or empty text inputs, trigger the filter searchPanel.querySelector('#advanced-search-filter-btn').click(); event.preventDefault(); } } function displaySuggestions(suggestions) { const suggestionsContainer = searchPanel.querySelector('#tag-suggestions'); if (suggestions.length === 0) { suggestionsContainer.innerHTML = ''; return; } const suggestionList = document.createElement('div'); suggestionList.className = 'suggestion-list'; suggestionList.style.backgroundColor = TOP_BACKGROUND_COLOR; // Dynamically set background color suggestions.forEach(suggestion => { if (!selectedTags.has(suggestion.id)) { const item = document.createElement('div'); item.className = 'suggestion-item contentRow'; item.innerHTML = `<div class="contentRow-main">${suggestion.text}</div>`; item.addEventListener('click', () => selectTag(suggestion)); suggestionList.appendChild(item); } }); suggestionsContainer.innerHTML = ''; suggestionsContainer.appendChild(suggestionList); } function selectTag(tag) { selectedTags.add(tag.id); const tagContainer = searchPanel.querySelector('.tag-search-container'); const tagInput = searchPanel.querySelector('#tag-search-input'); const pill = document.createElement('span'); pill.className = 'selected-tag'; pill.dataset.tagId = tag.id; pill.innerHTML = `${tag.text}<span class="selected-tag-remove" title="Remove tag">×</span>`; pill.querySelector('.selected-tag-remove').addEventListener('click', () => { selectedTags.delete(tag.id); pill.remove(); tagInput.focus(); }); tagContainer.insertBefore(pill, tagInput); tagInput.value = ''; searchPanel.querySelector('#tag-suggestions').innerHTML = ''; tagInput.blur(); // tagInput.focus(); // focus back in after selecting tag } // --- 7. FINAL FILTER ACTION --- function handleFilter(event) { event.preventDefault(); hidePanel(); const sharedData = { tags: Array.from(selectedTags), word_count_min: parseInt(searchPanel.querySelector('input[name="min_word_count"]').value, 10) || 0, word_count_max: parseInt(searchPanel.querySelector('input[name="max_word_count"]').value, 10) || 0, without_synonyms: searchPanel.querySelector('#without-synonyms').checked, }; if (activeTab === 'simple') { const simpleSearchData = { ...sharedData, sort_by: searchPanel.querySelector('select[name="simple_order"]').value, sort_direction: searchPanel.querySelector('select[name="simple_direction"]').value, }; let simpleSearchURL = location.origin + location.pathname; const params = new URLSearchParams(); params.append('order', simpleSearchData.sort_by); params.append('direction', simpleSearchData.sort_direction); simpleSearchData.tags.forEach((tag, index) => { params.append(`tags[${index}]`, tag); }); if (simpleSearchData.without_synonyms) { params.append('withoutSynonym', '1'); } if (simpleSearchData.word_count_min > 0) { params.append('min_word_count', simpleSearchData.word_count_min); } if (simpleSearchData.word_count_max > 0) { params.append('max_word_count', simpleSearchData.word_count_max); } // params.append('nodes[0]', -1); const queryString = params.toString(); if (queryString) { simpleSearchURL += `?${queryString}`; } // console.log("--- Simple Search Data ---", simpleSearchData); // console.log("Constructed Simple Search URL:", simpleSearchURL); location.href = simpleSearchURL; } else if (activeTab === 'thread') { const threadSearchData = { ...sharedData, query: searchPanel.querySelector('#thread-search-query').value, minimum_replies: parseInt(searchPanel.querySelector('#min-replies').value, 10) || 0, sort_by: searchPanel.querySelector('#ts_thread_order').value, search_first_post_only: searchPanel.querySelector('#search-first-post').checked, forums: Array.from(searchPanel.querySelectorAll('#ts_forum_choice [name="forum_choice"]:checked')).map(cb => cb.value), }; // Note: Inspect the /search/search request multipart payload const searchForm = document.createElement('form'); searchForm.method = 'POST'; searchForm.action = '/search/search/'; searchForm.style.display = 'none'; const addInput = (name, value) => { const input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = value; searchForm.appendChild(input); }; addInput('_xfToken', xfToken); addInput('keywords', threadSearchData.query); if (threadSearchData.search_first_post_only) { addInput('c[content]', 'thread'); } else { addInput('c[title_only]', '1'); } addInput('c[threadmark_only]', '1'); addInput('c[users]', ''); addInput('c[newer_than]', ''); addInput('c[older_than]', ''); addInput('c[tags]', threadSearchData.tags.join(', ')); addInput('c[excludeTags]', ''); if (threadSearchData.word_count_min > 0) addInput('c[word_count][lower]', threadSearchData.word_count_min); if (threadSearchData.word_count_max > 0) addInput('c[word_count][upper]', threadSearchData.word_count_max); if (threadSearchData.minimum_replies > 0) addInput('c[min_reply_count]', threadSearchData.minimum_replies); addInput('c[child_nodes]', '1'); addInput('order', threadSearchData.sort_by); addInput('grouped', '1'); addInput('search_type', 'post'); addInput('_xfRequestUri', '/search/?type=post'); addInput('_xfWithData', '1'); if (threadSearchData.forums.length > 0) { threadSearchData.forums.forEach(forumChoice => { let nodesToAppend = []; if (forumChoice === 'creative') nodesToAppend = FORUM_NODES.creative; if (forumChoice === 'quests') nodesToAppend = FORUM_NODES.quests; nodesToAppend.forEach(nodeId => { addInput('c[nodes][]', nodeId); }); }); } document.body.appendChild(searchForm); searchForm.submit(); } } // --- 8. ATTACH THE MAIN TRIGGER --- triggerButton.addEventListener('click', togglePanel);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址