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