Greasy Fork is available in English.
Boost 列表折叠 + 回复字数提示
// ==UserScript==
// @name Linux.do Comment Helper
// @namespace https://linux.do/
// @version 1.2
// @author lying
// @description Boost 列表折叠 + 回复字数提示
// @license MIT
// @homepage https://linux.do/
// @match https://linux.do/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const BOOST_LINE_HEIGHT = 38;
const MODES = { firstLine: '显示第一行', hidden: '完全折叠' };
let boostMode = GM_getValue('boostMode', 'firstLine');
function registerMenuCommands() {
Object.entries(MODES).forEach(([key, label]) => {
const current = key === boostMode ? ' ✓' : '';
GM_registerMenuCommand(`Boost: ${label}${current}`, () => {
boostMode = key;
GM_setValue('boostMode', key);
applyBoostMode();
location.reload();
});
});
}
function applyBoostMode() {
document.documentElement.classList.toggle('boost-hidden', boostMode === 'hidden');
}
GM_addStyle(`
.discourse-boosts__list {
max-height: ${BOOST_LINE_HEIGHT}px;
overflow: hidden;
}
.discourse-boosts__list.boost-no-collapse,
.discourse-boosts__list.boost-expanded {
max-height: none;
overflow: visible;
}
html.boost-hidden .discourse-boosts__list {
max-height: 0;
overflow: hidden;
}
html.boost-hidden .discourse-boosts__list.boost-expanded {
max-height: none;
overflow: visible;
}
.boost-toggle-btn {
background: none;
border: none;
cursor: pointer;
font-size: 12px;
color: var(--primary-medium, #888);
padding: 4px 8px;
border-radius: 4px;
transition: color 0.2s, background-color 0.2s;
}
.boost-toggle-btn:hover {
background-color: var(--primary-very-low, #f5f5f5);
color: var(--primary, #333);
}
.char-counter {
display: inline-flex;
align-items: center;
margin-inline-start: 12px;
padding: 0.3em 0.6em;
border-radius: 999px;
border: 1px solid var(--primary-low, #e0e0e0);
background: var(--secondary, #fff);
color: var(--primary-medium, #888);
font-size: 0.8rem;
line-height: 1;
}
`);
applyBoostMode();
// ===== Boost 折叠 =====
function ensureToggleButton(post) {
const list = post.querySelector('.discourse-boosts__list');
if (!list || list.classList.contains('boost-no-collapse')) return;
const bubbleCount = list.querySelectorAll('.discourse-boosts__bubble').length;
if (bubbleCount === 0) return;
if (boostMode === 'firstLine' && list.scrollHeight <= BOOST_LINE_HEIGHT) {
list.classList.add('boost-no-collapse');
return;
}
const actions = post.querySelector('nav.post-controls .actions');
if (!actions || actions.querySelector('.boost-toggle-btn')) return;
const expanded = list.classList.contains('boost-expanded');
const btn = document.createElement('button');
btn.className = 'boost-toggle-btn';
btn.textContent = expanded ? '▼ Boost' : `▶ Boost (${bubbleCount})`;
btn.addEventListener('click', () => {
const isExpanded = list.classList.toggle('boost-expanded');
btn.textContent = isExpanded ? '▼ Boost' : `▶ Boost (${bubbleCount})`;
});
actions.insertBefore(btn, actions.firstChild);
}
function scanBoosts() {
const posts = document.querySelectorAll('.topic-post:has(.discourse-boosts__list)');
posts.forEach(ensureToggleButton);
}
// ===== 字数提示 =====
function syncCharCounter() {
const host = document.querySelector('#reply-control.open .save-or-cancel');
if (!host) {
document.querySelector('.char-counter')?.remove();
return;
}
let counter = host.querySelector('.char-counter');
if (!counter) {
counter = document.createElement('span');
counter.className = 'char-counter';
host.appendChild(counter);
}
const textarea = document.querySelector('#reply-control textarea.d-editor-input');
const text = (textarea?.value ?? '').replace(/\u200B/g, '');
const count = Array.from(text.trim()).length;
counter.textContent = `${count} 字`;
}
// ===== 调度 =====
let boostRafId = null;
let counterRafId = null;
function scheduleBoostScan() {
if (boostRafId) return;
boostRafId = requestAnimationFrame(() => {
boostRafId = null;
scanBoosts();
});
}
function scheduleCounterSync() {
if (counterRafId) return;
counterRafId = requestAnimationFrame(() => {
counterRafId = null;
syncCharCounter();
});
}
function init() {
registerMenuCommands();
scheduleBoostScan();
scheduleCounterSync();
document.addEventListener('input', (e) => {
if (e.target.matches?.('#reply-control textarea.d-editor-input')) {
scheduleCounterSync();
}
}, true);
const observer = new MutationObserver(scheduleBoostScan);
const root = document.querySelector('.post-stream')
|| document.getElementById('main-outlet')
|| document.body;
observer.observe(root, { childList: true, subtree: true });
const rc = document.getElementById('reply-control');
if (rc) {
new MutationObserver(scheduleCounterSync)
.observe(rc, { attributes: true, attributeFilter: ['class'] });
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();