您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
16/04/2025, 18:06:52
当前为
// ==UserScript== // @name Fullchan X // @namespace Violentmonkey Scripts // @match https://8chan.moe/*/res/*.html* // @grant none // @version 1.0 // @author vfyxe // @description 16/04/2025, 18:06:52 // ==/UserScript== if (!document.querySelector('.divPosts')) return; class fullChanX extends HTMLElement { constructor() { super(); this.enableNestedQuotes = true; } init() { this.threadParent = document.querySelector('#divThreads'); this.thread = this.threadParent.querySelector('.divPosts'); this.posts = [...this.thread.querySelectorAll('.postCell')]; this.postOrder = 'default'; this.postOrderSelect = this.querySelector('#thread-sort'); this.yousContainer = this.querySelector('#my-yous'); this.updateYous(); this.observers(); } observers () { this.postOrderSelect.addEventListener('change', (event) => { this.postOrder = event.target.value; this.assignPostOrder(); }); const observerCallback = (mutationsList, observer) => { for (const mutation of mutationsList) { if (mutation.type === 'childList') { this.posts = [...this.thread.querySelectorAll('.postCell')]; if (this.postOrder !== 'default') this.assignPostOrder(); this.updateYous(); } } }; const threadObserver = new MutationObserver(observerCallback); threadObserver.observe(this.thread, { childList: true, subtree: false }); if (this.enableNestedQuotes) { this.thread.addEventListener('click', event => { const nestQuote = event.target.closest('.quoteLink'); if (nestQuote) { event.preventDefault(); this.nestQuote(nestQuote); } }); } } assignPostOrder () { const postOrderReplies = (post) => { const replyCount = post.querySelectorAll('.panelBacklinks a').length; post.style.order = 100 - replyCount; } const postOrderCatbox = (post) => { const postContent = post.querySelector('.divMessage').textContent; const matches = postContent.match(/catbox\.moe/g); const catboxCount = matches ? matches.length : 0; post.style.order = 100 - catboxCount; } if (this.postOrder === 'default') { this.thread.style.display = 'block'; return; } this.thread.style.display = 'flex'; if (this.postOrder === 'replies') { this.posts.forEach(post => postOrderReplies(post)); } else if (this.postOrder === 'catbox') { this.posts.forEach(post => postOrderCatbox(post)); } } updateYous () { const yous = this.posts.filter(post => post.querySelector('.quoteLink.you')); const yousLinks = yous.map(you => { const youLink = document.createElement('a'); youLink.textContent = '>>' + you.id; youLink.href = '#' + you.id; return youLink; }) this.yousContainer.innerHTML = ''; yousLinks.forEach(you => this.yousContainer.appendChild(you)); } nestQuote(quoteLink) { const parentPostMessage = quoteLink.closest('.divMessage'); const quoteId = quoteLink.href.split('#')[1]; const quotePost = document.getElementById(quoteId); if (!quotePost) return; const quotePostContent = quotePost.querySelector('.innerOP') || quotePost.querySelector('.innerPost'); if (!quotePostContent) return; const existing = parentPostMessage.querySelector(`.nestedPost[data-quote-id="${quoteId}"]`); if (existing) { existing.remove(); return; } const wrapper = document.createElement('div'); wrapper.classList.add('nestedPost'); wrapper.setAttribute('data-quote-id', quoteId); const clone = quotePostContent.cloneNode(true); clone.style.whiteSpace = "unset"; wrapper.appendChild(clone); parentPostMessage.appendChild(wrapper); } }; window.customElements.define('fullchan-x', fullChanX); // Create fullchan-x elemnt const fcx = document.createElement('fullchan-x'); fcx.innerHTML = ` <div class="fcx__controls"> <select id="thread-sort"> <option value="default">Default</option> <option value="replies">Replies</option> <option value="catbox">Catbox</option> </select> <div class="fcx__my-yous"> <p class="my-yous__label">My (You)s</p> <div class="my-yous__yous" id="my-yous"></div> </div> </div> `; document.body.appendChild(fcx); fcx.init(); // Styles const style = document.createElement('style'); style.innerHTML = ` fullchan-x { display: block; position: fixed; top: 2.5rem; right: 2rem; padding: 10px; background: var(--contrast-color); border: 1px solid var(--navbar-text-color); color: var(--link-color); font-size: 14px; opacity: 0.5; } fullchan-x:hover { opacity: 1; } .divPosts { flex-direction: column; } .fcx__controls { display: flex; flex-direction: column; gap: 2px; } #thread-sort { padding: 0.4rem 0.6rem; background: white !important; border: none !important; border-radius: 0.2rem; transition: all ease 150ms; cursor: pointer; } .my-yous__yous { display: none; flex-direction: column; } .my-yous__label { padding: 0.4rem 0.6rem; background: white !important; border: none !important; border-radius: 0.2rem; transition: all ease 150ms; cursor: pointer; } .fcx__my-yous:hover .my-yous__yous { display: flex; } .innerPost:has(.quoteLink.you) { border-left: solid red 6px; } // --- Nested quotes ---- // I don't know why it needs this, weird CSS inheritance on cloned element .nestedPost {} .divMessage .nestedPost { display: block; white-space: normal!important; overflow-wrap: anywhere; margin-top: 0.5em; border: 1px solid var(--navbar-text-color); } `; document.head.appendChild(style); // Asuka and Eris (fantasy Asuka) are best girls