Suppress default previews, persistent “You” highlight dropdown, and Q‑key Quick‑Reply for holotower.org.
当前为
// ==UserScript==
// @name Holotower Custom Fixes
// @namespace http://holotower.org/
// @version 1.0
// @author /hlgg/
// @license MIT
// @description Suppress default previews, persistent “You” highlight dropdown, and Q‑key Quick‑Reply for holotower.org.
// @icon https://boards.holotower.org/favicon.gif
// @match *://boards.holotower.org/*
// @match *://holotower.org/*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// --------------------------------------------------------------------------
// 1) Hide the site's built-in hover previews
// We only want our custom iq-preview-* popups to show.
// --------------------------------------------------------------------------
GM_addStyle(`
/* Any .qp element that does NOT have an id starting with "iq-preview-" will be hidden */
div.qp:not([id^="iq-preview-"]) {
display: none !important;
}
`);
// --------------------------------------------------------------------------
// 2) Prepare a <style> tag to hold our dynamic "You" highlighting rules
// We'll update its contents when the user picks a new border style.
// --------------------------------------------------------------------------
const youStyleEl = document.createElement('style');
youStyleEl.id = 'you-custom-style';
document.head.appendChild(youStyleEl);
// Key under which we store the user's chosen border style in localStorage
const STORAGE_KEY = 'youBorderStyle';
const DEFAULT_STYLE = 'dashed';
/**
* Writes CSS into our <style> tag to highlight posts:
* - Blue left-border for your own replies (.you)
* - Red left-border for replies quoting you (.quoting-you)
* @param {string} style - the CSS border-style (e.g., solid, dashed)
*/
function updateYouBorder(style) {
youStyleEl.textContent = `
/* Own posts: add blue border */
div.post.reply.you:has(span.own_post) {
border-left: 5px ${style} #00b8e6 !important;
}
/* Replies quoting you: add red border */
div.post.reply.quoting-you {
border-left: 5px ${style} #ff3d3d !important;
}
/* Re-add "(You)" text after the quote link */
div.post.reply.quoting-you a:has(+ small)::after {
content: " (You)" !important;
}
/* Hide any original small "(You)" or embed placeholders */
div.post.reply div.body a + small,
div.post.reply div.body span.embed-container {
display: none !important;
}
`;
// Save the user's choice so it persists across reloads
try {
localStorage.setItem(STORAGE_KEY, style);
} catch (e) {
// localStorage may not be available; ignore errors
}
}
// --------------------------------------------------------------------------
// 3) Create and insert a dropdown UI for the user to select their border style
// --------------------------------------------------------------------------
const container = document.createElement('div');
container.style.cssText = 'float:right; margin-bottom:10px';
container.innerHTML = `
Border style:
<select id="youBorderSelector">
<option value="solid">solid</option>
<option value="dashed">dashed</option>
<option value="dotted">dotted</option>
<option value="double">double</option>
<option value="groove">groove</option>
<option value="ridge">ridge</option>
</select>
`;
// Insert our dropdown before the existing #style-select element (site's theme selector)
const styleSelect = document.getElementById('style-select');
if (styleSelect && styleSelect.parentNode) {
styleSelect.parentNode.insertBefore(container, styleSelect);
}
// Initialize dropdown value from localStorage (or default)
const dropdown = document.getElementById('youBorderSelector');
const saved = localStorage.getItem(STORAGE_KEY);
const initial = saved && dropdown.querySelector(`option[value="${saved}"]`) ? saved : DEFAULT_STYLE;
dropdown.value = initial;
updateYouBorder(initial);
// Update the border style whenever the user picks a new option
dropdown.addEventListener('change', e => updateYouBorder(e.target.value));
// --------------------------------------------------------------------------
// 4) JavaScript marking of posts with "you" and "quoting-you" classes
// .you => posts authored by you
// .quoting-you => replies that quote "(You)"
// --------------------------------------------------------------------------
/**
* Scans the given root (default: whole document) and adds classes:
* - "you" to posts containing <span class="own_post">
* - "quoting-you" to posts containing a <small> with text "(You)"
*/
function markYou(root = document.body) {
// Mark own posts
root.querySelectorAll('div.post.reply span.own_post')
.forEach(el => el.closest('div.post.reply')?.classList.add('you'));
// Mark replies quoting you
root.querySelectorAll('div.post.reply .body small')
.forEach(sm => {
if (sm.textContent.trim() === '(You)') {
sm.closest('div.post.reply')?.classList.add('quoting-you');
}
});
}
// Initial pass on page load
markYou();
// Observe for new or inlined replies to apply marking
new MutationObserver(muts => {
muts.forEach(m => {
m.addedNodes.forEach(n => {
if (n.nodeType === 1) markYou(n);
});
});
}).observe(document.body, { childList: true, subtree: true });
// --------------------------------------------------------------------------
// 5) Q-key Quick‑Reply toggle and auto-focus on citeReply links
// --------------------------------------------------------------------------
/**
* Toggles the Quick‑Reply panel when the user presses 'q' (unless typing in a form).
*/
function onKey(e) {
if (e.key.toLowerCase() !== 'q' || e.ctrlKey || e.altKey || e.metaKey) return;
// If the user is typing into an input or textarea, do nothing
const active = document.activeElement;
const nm = active?.getAttribute('name');
if (active?.tagName === 'TEXTAREA' || (active?.tagName === 'INPUT' && ['name','email','subject','embed'].includes(nm))) {
return;
}
const form = document.getElementById('quick-reply');
if (form && form.style.display !== 'none') {
// Close Quick‑Reply if it's open
form.querySelector('.close-btn')?.click();
} else {
// Otherwise open it and focus the textarea
document.querySelector('.quick-reply-btn')?.click();
setTimeout(() => {
document.querySelector('#quick-reply textarea[name="body"]')?.focus();
}, 50);
}
e.preventDefault();
}
// Capture keydown at the documentElement level to override site handlers
document.documentElement.addEventListener('keydown', onKey, true);
// When clicking on a post_no link (citeReply), auto-focus the textarea
document.body.addEventListener('click', e => {
const link = e.target.closest('a.post_no');
if (link && /^citeReply\(\d+\)$/.test(link.getAttribute('onclick') || '')) {
setTimeout(() => {
const ta = document.querySelector('#quick-reply textarea[name="body"]');
if (ta) {
ta.focus();
ta.setSelectionRange(ta.value.length, ta.value.length);
}
}, 50);
}
});
})();