Greasy Fork is available in English.
sb.sb 深度视觉、阅读与列表增强:完整主题系统、布局、过滤、图片预览、公开资料卡与阅读辅助。
// ==UserScript==
// @name SB.SB-Enhance-Script
// @namespace https://sb.sb/
// @version 2.4.24
// @description sb.sb 深度视觉、阅读与列表增强:完整主题系统、布局、过滤、图片预览、公开资料卡与阅读辅助。
// @author com.com.ee
// @match https://sb.sb/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
if (/^\/pages\/top(?:\/|$)/.test(window.location.pathname)) {
return;
}
var STORAGE_KEY = 'sb-sb-enhance-settings-v2';
var SETTINGS_VERSION = 8;
var CHECKIN_STORAGE_PREFIX = 'sb-sb-enhance-checkin-v1-';
var SAVE_DELAY = 180;
var PROFILE_CACHE_TTL = 10 * 60 * 1000;
var RANGE_DEFINITIONS = [
{ key: 'maxWidth', label: '页面最大宽度', min: 960, max: 2160, step: 20, unit: 'px' },
{ key: 'headerWidth', label: '顶部栏内容宽度', min: 720, max: 2160, step: 20, unit: 'px' },
{ key: 'headerHeight', label: '顶部栏高度', min: 48, max: 92, step: 2, unit: 'px' },
{ key: 'fontSize', label: '正文基准字号', min: 13, max: 21, step: 1, unit: 'px' },
{ key: 'radius', label: '全局圆角', min: 4, max: 18, step: 1, unit: 'px' },
{ key: 'sidebarWidth', label: '侧栏宽度', min: 220, max: 410, step: 10, unit: 'px' },
{ key: 'shellPadding', label: '内容内边距', min: 10, max: 52, step: 2, unit: 'px' },
{ key: 'columnGap', label: '正文与侧栏间距', min: 12, max: 56, step: 2, unit: 'px' },
{ key: 'avatarSize', label: '帖子列表头像大小', min: 20, max: 40, step: 1, unit: 'px' }
];
/*
* 每个主题都同时定义背景、面板、边线、语义色和交互色。
* 暗色主题的 hover 仅在相邻层级之间提升明度,不使用纯白反转。
*/
var THEMES = {
original: { label: '跟随原站主题', vars: null },
obsidian: {
label: '曜石夜色',
vars: {
'--bg': '#0b0d12', '--panel': '#141821', '--panel-alt': '#1a202b', '--panel-hover': '#202735',
'--line': '#2c3442', '--line-soft': '#202834', '--line-strong': '#414c60',
'--text': '#e8ebf1', '--text-strong': '#f8f9fb', '--text-muted': '#b5bfce', '--text-subtle': '#8d98a9', '--text-disabled': '#687384',
'--brand': '#aabaff', '--brand-hover': '#c7d1ff', '--brand-soft': '#212d55', '--brand-border': '#465886',
'--success': '#52d6a0', '--success-soft': '#123426', '--success-soft-text': '#8eecc4',
'--danger': '#ff9299', '--danger-soft': '#432127', '--danger-soft-text': '#ffb8bd',
'--warning': '#ffd166', '--warning-soft': '#443719', '--warning-soft-text': '#ffe19a',
'--info': '#aabaff', '--info-soft': '#212d55', '--info-soft-text': '#c7d1ff',
'--mark': '#f5c451', '--mark-soft': '#3e3217', '--mark-soft-text': '#ffe09a', '--mark-text': '#fff9e8',
'--action': '#e8ebf1', '--action-hover': '#ffffff', '--action-text': '#12151c', '--action-border': '#e8ebf1',
'--inverse': '#e8ebf1', '--inverse-text': '#12151c', '--inverse-border': '#f6f7fa',
'--focus-ring': '#aabaff', '--selection': '#3c4d7a', '--backdrop': 'rgb(3 6 12 / 0.78)',
'--shadow-base': '0 2px 7px rgb(0 0 0 / 0.32)', '--shadow-medium': '0 20px 58px -14px rgb(0 0 0 / 0.70)'
}
},
graphite: {
label: '石墨中性',
vars: {
'--bg': '#121313', '--panel': '#1a1b1b', '--panel-alt': '#202121', '--panel-hover': '#292b2b',
'--line': '#363838', '--line-soft': '#292b2b', '--line-strong': '#505353',
'--text': '#eceeed', '--text-strong': '#ffffff', '--text-muted': '#c0c4c2', '--text-subtle': '#929795', '--text-disabled': '#6f7472',
'--brand': '#9dd6c8', '--brand-hover': '#c1f1e5', '--brand-soft': '#1e3b35', '--brand-border': '#47756b',
'--success': '#85d7ad', '--success-soft': '#173426', '--success-soft-text': '#a8e8c5',
'--danger': '#f09a9a', '--danger-soft': '#422525', '--danger-soft-text': '#ffc0c0',
'--warning': '#e7c275', '--warning-soft': '#40341e', '--warning-soft-text': '#f7d898',
'--info': '#a9c3e8', '--info-soft': '#253449', '--info-soft-text': '#c6daf7',
'--mark': '#e8c66e', '--mark-soft': '#40351f', '--mark-soft-text': '#f5dfa1', '--mark-text': '#fff8e5',
'--action': '#eceeed', '--action-hover': '#ffffff', '--action-text': '#171818', '--action-border': '#eceeed',
'--inverse': '#eceeed', '--inverse-text': '#171818', '--inverse-border': '#ffffff',
'--focus-ring': '#9dd6c8', '--selection': '#36584f', '--backdrop': 'rgb(0 0 0 / 0.76)',
'--shadow-base': '0 2px 7px rgb(0 0 0 / 0.28)', '--shadow-medium': '0 20px 58px -14px rgb(0 0 0 / 0.66)'
}
},
paper: {
label: '雾白纸感',
vars: {
'--bg': '#f4f5f7', '--panel': '#ffffff', '--panel-alt': '#fafbfc', '--panel-hover': '#edf1f6',
'--line': '#d9dde5', '--line-soft': '#eaedf2', '--line-strong': '#c3cad6',
'--text': '#242832', '--text-strong': '#11141b', '--text-muted': '#596273', '--text-subtle': '#737d8e', '--text-disabled': '#9aa2ae',
'--brand': '#4965aa', '--brand-hover': '#314e96', '--brand-soft': '#e8edf9', '--brand-border': '#b8c6e8',
'--success': '#138c5a', '--success-soft': '#e4f5ec', '--success-soft-text': '#08643e',
'--danger': '#c5424d', '--danger-soft': '#fce8ea', '--danger-soft-text': '#8d2029',
'--warning': '#9b6d00', '--warning-soft': '#fff3cf', '--warning-soft-text': '#6e4d00',
'--info': '#4965aa', '--info-soft': '#e8edf9', '--info-soft-text': '#314e96',
'--mark': '#e6b935', '--mark-soft': '#fff3c8', '--mark-soft-text': '#745b17', '--mark-text': '#2b2617',
'--action': '#232934', '--action-hover': '#121720', '--action-text': '#ffffff', '--action-border': '#232934',
'--inverse': '#232934', '--inverse-text': '#ffffff', '--inverse-border': '#323a48',
'--focus-ring': '#4965aa', '--selection': '#cbd7f2', '--backdrop': 'rgb(16 23 38 / 0.48)',
'--shadow-base': '0 2px 7px rgb(32 43 67 / 0.08)', '--shadow-medium': '0 20px 58px -14px rgb(32 43 67 / 0.22)'
}
}
};
var THEME_VARIABLES = Object.keys(THEMES.obsidian.vars);
var DEFAULTS = {
version: SETTINGS_VERSION,
maxWidth: 1240,
headerWidth: 1240,
headerHeight: 62,
fontSize: 15,
radius: 10,
sidebarWidth: 270,
shellPadding: 20,
columnGap: 24,
avatarSize: 40,
theme: 'original',
density: 'comfortable',
newWindowForTopics: false,
stickyForumList: false,
avatarProfileCard: false,
blockNativeProfileCard: true,
blockNativeReplyTree: false,
imageLightbox: false,
readingProgress: false,
codeCopyButtons: false,
autoCheckin: false,
titleFilters: [],
userFilters: []
};
var panel = null;
var toggleButton = null;
var statusElement = null;
var saveTimer = 0;
var filterTimer = 0;
var refreshTimer = 0;
var refreshFrame = 0;
var progressFrame = 0;
var lastProgressValue = '';
var enhancementsRunning = false;
var pendingEnhancementRoots = [];
var pageObserver = null;
var lightbox = null;
var lightboxImage = null;
var lightboxEventsBound = false;
var searchModal = null;
var searchModalForm = null;
var searchModalInput = null;
var profileCard = null;
var profileHideTimer = 0;
var nativeProfileCardBlockerBound = false;
var profileCache = Object.create(null);
var progressBar = null;
var progressBound = false;
var checkinPending = false;
var settings = loadSettings();
var BASE_CSS = [
':root { scrollbar-gutter: stable; }',
'a, a:hover, a:focus-visible, a:active { text-decoration: none !important; }',
':root {',
' --sbe-max-width: 1240px; --sbe-header-width: 1240px; --sbe-header-height: 62px; --sbe-font-size: 15px; --sbe-radius: 10px;',
' --sbe-sidebar-width: 270px; --sbe-shell-padding: 20px; --sbe-column-gap: 24px; --sbe-avatar-size: 24px;',
'}',
'@media (min-width: 721px) {',
' :root { --font-size-sm: calc(var(--sbe-font-size) - 2px) !important; --font-size-md: var(--sbe-font-size) !important; --font-size-lg: calc(var(--sbe-font-size) + 1px) !important; }',
' body { font-size: var(--sbe-font-size) !important; }',
' .top .bar { width: min(var(--sbe-header-width), calc(100% - 44px)) !important; max-width: none !important; height: var(--sbe-header-height) !important; min-height: var(--sbe-header-height) !important; box-sizing: border-box; }',
' main.wrap, .footer { width: min(var(--sbe-max-width), calc(100% - 44px)) !important; max-width: none !important; }',
' main.wrap { padding-top: 5px !important; padding-bottom: 30px !important; }',
' .home-shell { padding: var(--sbe-shell-padding) !important; }',
' .forum-layout { display: grid !important; grid-template-columns: minmax(0, 1fr) minmax(220px, var(--sbe-sidebar-width)) !important; grid-template-areas: "main sidebar" !important; align-items: stretch !important; gap: var(--sbe-column-gap) !important; }',
' .forum-layout > .forum-main { grid-area: main !important; min-width: 0 !important; width: auto !important; }',
' .forum-layout > .sidebar { grid-area: sidebar !important; align-self: stretch !important; width: auto !important; min-width: 0 !important; }',
' .forum-layout > .sidebar > .sbe-forum-card { position: sticky !important; top: calc(var(--sbe-header-height) + 14px); z-index: 2; max-height: calc(100vh - var(--sbe-header-height) - 28px); overflow-y: auto; overscroll-behavior: contain; }',
' .top .bar { display: flex !important; align-items: center !important; gap: 10px !important; }',
' .top .brand { flex: 0 0 auto; white-space: nowrap; }',
' .top .forum-nav { display: flex !important; min-width: 0; min-height: 30px; flex: 1 1 auto; align-items: center; justify-content: flex-start; gap: 1px; overflow-x: auto; scrollbar-width: none; }',
' .top .forum-nav::-webkit-scrollbar { display: none; }',
' .top .forum-link { display: inline-flex !important; width: auto; height: 32px !important; min-height: 32px !important; box-sizing: border-box; align-items: center !important; justify-content: center !important; padding: 0 9px !important; border: 1px solid transparent !important; border-radius: 6px; font-size: 13px; line-height: 1 !important; text-align: center; vertical-align: middle; white-space: nowrap; }',
' .top .forum-link.active, .top .forum-link[aria-current="page"] { border-color: var(--brand-border) !important; }',
' .top .search-form { display: flex !important; width: clamp(190px, 18vw, 260px); min-width: 0; align-items: center; margin-left: 4px; padding: 2px 3px 2px 7px; border: 1px solid var(--line) !important; border-radius: 8px; background: var(--panel-alt); }',
' .top .search-field { width: 50px !important; min-width: 50px; padding: 4px 1px !important; border: 0 !important; border-radius: 0 !important; background: transparent !important; color: var(--text-subtle) !important; font-size: 12px; }',
' .top .search-input { min-width: 0; flex: 1; padding: 5px 6px !important; border: 0 !important; border-radius: 0 !important; background: transparent !important; font-size: 13px; }',
' .top .search-btn { display: grid !important; width: 28px; height: 28px; flex: 0 0 28px; place-items: center; padding: 0 !important; border: 0 !important; border-radius: 6px !important; background: transparent !important; color: var(--text-muted); }',
' .top .search-btn:hover, .top .search-btn:focus-visible { background: var(--brand-soft) !important; color: var(--brand-hover); outline: none; }',
' .top .script-toggle > button, .top .theme-toggle { display: grid !important; width: 30px; height: 30px; place-items: center; padding: 0 !important; border-radius: 6px !important; }',
'}',
'@media (min-width: 1080px) { .top .forum-nav { justify-content: center; } }',
'@media (max-width: 720px) {',
' .sbe-layout-toggle { right: 16px !important; bottom: 16px !important; }',
' .sbe-layout-panel { right: 12px !important; bottom: 68px !important; width: min(392px, calc(100vw - 24px)) !important; max-height: min(78vh, 720px) !important; }',
' .sbe-forum-card { display: none !important; }',
'}',
':root[data-sbe-density="compact"] .post-item { min-height: 55px !important; padding-top: 8px !important; padding-bottom: 8px !important; }',
':root[data-sbe-density="compact"] .post-content { line-height: 1.64 !important; }',
':root[data-sbe-density="relaxed"] .post-item { min-height: 72px !important; padding-top: 15px !important; padding-bottom: 15px !important; }',
':root[data-sbe-density="relaxed"] .post-content { line-height: 1.86 !important; }',
':root[data-sbe-theme] body { background: var(--bg) !important; color: var(--text) !important; }',
':root[data-sbe-theme] .top { background: var(--panel) !important; border-bottom-color: var(--line) !important; }',
':root[data-sbe-theme] .card, :root[data-sbe-theme] .sidebar-card, :root[data-sbe-theme] .topic-post-list.topic-opening, :root[data-sbe-theme] .topic-replies > .topic-post-list, :root[data-sbe-theme] .bio-card { background: var(--panel) !important; border-color: var(--line) !important; }',
':root[data-sbe-theme] .post-list, :root[data-sbe-theme] .main-panel { border-color: var(--line) !important; }',
':root[data-sbe-theme] .post-item { border-color: var(--line-soft) !important; color: var(--text) !important; }',
'@media (hover: hover) { :root[data-sbe-theme] .post-item:not(.topic-pinned):hover { background: var(--panel-hover) !important; } }',
':root[data-sbe-theme] .post-item.topic-pinned { background: var(--mark-soft) !important; }',
':root[data-sbe-theme] .post-title, :root[data-sbe-theme] .post-topic-title, :root[data-sbe-theme] .post-content h1, :root[data-sbe-theme] .post-content h2, :root[data-sbe-theme] .post-content h3 { color: var(--text-strong) !important; }',
':root[data-sbe-theme] .post-title:hover, :root[data-sbe-theme] .post-title:focus-visible, :root[data-sbe-theme] .sidebar-topic-title:hover { color: var(--brand) !important; }',
':root[data-sbe-theme] .post-content, :root[data-sbe-theme] .post-content p, :root[data-sbe-theme] .reply-light { color: var(--text) !important; }',
':root[data-sbe-theme] .post-content a, :root[data-sbe-theme] .post-meta a:hover, :root[data-sbe-theme] .post-head a:hover, :root[data-sbe-theme] .footer a:hover { color: var(--brand-hover) !important; }',
':root[data-sbe-theme] .post-meta, :root[data-sbe-theme] .post-reply-no, :root[data-sbe-theme] .user-rank, :root[data-sbe-theme] .footer { color: var(--text-subtle) !important; }',
':root[data-sbe-theme] .post-forum-badge, :root[data-sbe-theme] .topic-badge { background: var(--panel-alt) !important; border-color: var(--line) !important; color: var(--text-muted) !important; }',
':root[data-sbe-theme] .topic-badge.pinned { background: var(--mark-soft) !important; border-color: var(--mark) !important; color: var(--mark-soft-text) !important; }',
':root[data-sbe-theme] .tab-bar .tab { color: var(--text-muted) !important; }',
':root[data-sbe-theme] .tab-bar .tab.active, :root[data-sbe-theme] .tab-bar .tab[aria-current="page"] { background: var(--brand) !important; border-color: var(--brand) !important; color: var(--inverse-text) !important; }',
':root[data-sbe-theme] .tab-bar .tab:not(.active):hover { background: var(--brand-soft) !important; color: var(--brand-hover) !important; }',
':root[data-sbe-theme] .search-input, :root[data-sbe-theme] .search-field, :root[data-sbe-theme] input, :root[data-sbe-theme] select, :root[data-sbe-theme] textarea { background: var(--panel) !important; border-color: var(--line-strong) !important; color: var(--text) !important; }',
':root[data-sbe-theme] input::placeholder, :root[data-sbe-theme] textarea::placeholder { color: var(--text-subtle) !important; opacity: 1 !important; }',
':root[data-sbe-theme] .post-content table { border-color: var(--line) !important; }',
':root[data-sbe-theme] .post-content th { background: var(--panel-alt) !important; color: var(--text-strong) !important; }',
':root[data-sbe-theme] .post-content td, :root[data-sbe-theme] .post-content th { border-color: var(--line) !important; }',
':root[data-sbe-theme] .post-content blockquote { border-left-color: var(--brand) !important; background: var(--brand-soft) !important; color: var(--text-muted) !important; }',
':root[data-sbe-theme] .post-content pre { background: var(--panel-alt) !important; border-color: var(--line) !important; }',
':root[data-sbe-theme] .post-content code:not(pre code) { background: var(--panel-alt) !important; color: var(--brand-hover) !important; }',
':root[data-sbe-theme] .pagination a, :root[data-sbe-theme] .profile-toolbar, :root[data-sbe-theme] .quick-card { border-color: var(--line) !important; }',
':root[data-sbe-theme] .pagination a:hover, :root[data-sbe-theme] .pagination a[aria-current="page"] { background: var(--brand-soft) !important; border-color: var(--brand-border) !important; color: var(--brand-hover) !important; }',
':root[data-sbe-theme] ::selection { background: var(--selection) !important; color: var(--text-strong) !important; }',
':root[data-sbe-theme] a:focus-visible, :root[data-sbe-theme] button:focus-visible, :root[data-sbe-theme] input:focus-visible, :root[data-sbe-theme] select:focus-visible, :root[data-sbe-theme] textarea:focus-visible { outline: 2px solid var(--focus-ring) !important; outline-offset: 2px !important; }',
':root[data-sbe-theme] .modal-panel, :root[data-sbe-theme] .toast { background: var(--panel) !important; border-color: var(--line) !important; color: var(--text) !important; }',
':root[data-sbe-theme] .to-top { background: var(--panel) !important; border-color: var(--line-strong) !important; color: var(--text) !important; }',
'.sbe-forum-card { position: sticky; top: calc(var(--sbe-header-height) + 14px); z-index: 2; order: 99; margin: 14px 0 0 !important; padding: 14px; border: 1px solid var(--line); border-radius: var(--sbe-radius); background: var(--panel); box-shadow: var(--shadow-base); color: var(--text); }',
'.forum-layout > .sidebar > .sbe-forum-card { margin-top: 14px !important; }',
'.sbe-forum-card h2 { margin: 0 0 11px; color: var(--text-strong); font-size: 13px; letter-spacing: .01em; }',
'.sbe-forum-list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }',
'.sbe-forum-list a { display: flex; min-width: 0; min-height: 27px; align-items: center; justify-content: center; overflow: hidden; padding: 0 4px; border: 1px solid var(--line-soft); border-radius: var(--r-pill); background: var(--panel-alt); color: var(--text-muted); font-size: 12px; line-height: 1; text-align: center; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; transition: background-color .15s ease, border-color .15s ease, color .15s ease; }',
'.sbe-forum-list a:hover, .sbe-forum-list a:focus-visible { border-color: var(--brand-border); background: var(--brand-soft); color: var(--brand-hover); outline: none; }',
'.post-list .post-item > .post-avatar { width: var(--sbe-avatar-size) !important; height: var(--sbe-avatar-size) !important; flex: 0 0 var(--sbe-avatar-size) !important; }',
'.post-list .post-item > .post-avatar .avatar-img { display: block; width: 100% !important; height: 100% !important; object-fit: cover; }',
'.post-list .post-item > .post-body { min-width: 0 !important; flex: 1 1 auto !important; }',
'.user-stats { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; gap: 0 !important; }',
'.user-stat { align-items: center; text-align: center; }',
'.user-stat-label, .user-stat-value { width: 100%; text-align: center; }',
'.user-stat-value { font-size: 13px !important; font-weight: 400 !important; line-height: 1.35; }',
'.user-links { align-items: center; }',
'.user-links a { display: flex !important; min-width: 0; align-items: center !important; justify-content: center; gap: 6px !important; line-height: 20px; }',
'.user-links a > svg, .user-links a > .meta-icon { width: 14px; height: 14px; flex: 0 0 14px; }',
'.quick-card .quick-forum-links { display: flex !important; flex-wrap: wrap !important; justify-content: flex-start; gap: 6px !important; margin: 12px 0 0 !important; padding: 0 !important; list-style: none !important; }',
'.quick-card .quick-forum-links li { box-sizing: border-box; flex: 0 0 calc((100% - 18px) / 4); margin: 0 !important; }',
'.quick-card .quick-forum-links a { display: inline-flex !important; width: 100%; min-height: 27px; box-sizing: border-box; align-items: center; justify-content: center; padding: 0 6px; border: 1px solid var(--line-soft); border-radius: var(--r-pill); background: var(--panel-alt); color: var(--text-muted); font-size: 12px; line-height: 1; white-space: nowrap; }',
'.quick-card .quick-forum-links a:hover, .quick-card .quick-forum-links a:focus-visible { border-color: var(--brand-border); background: var(--brand-soft); color: var(--brand-hover); outline: none; }',
'/* 站点将菜单链接收进 .nav-user-links 后,双列规则需作用于该容器;其余原样式保持不变。 */',
'.top .nav-user { position: relative !important; flex: 0 0 auto !important; }',
'.top .nav-user-menu:not([hidden]) { position: absolute !important; top: calc(100% + 8px) !important; right: 0 !important; left: auto !important; display: block !important; min-width: 0 !important; width: min(168px, calc(100vw - 20px)) !important; box-sizing: border-box !important; transform: none !important; }',
'.top .nav-user-menu .nav-user-links { display: grid !important; grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: 1px 2px !important; }',
'.top .nav-user-menu .nav-user-item { display: flex; min-width: 0; min-height: 32px; align-items: center; justify-content: center; padding: 6px 8px; font-size: 13px; overflow: hidden; text-align: center; text-overflow: ellipsis; }',
'.top .search-form { display: contents !important; width: auto !important; min-width: 0 !important; flex: 0 0 auto !important; margin-left: 4px !important; padding: 0 !important; border: 0 !important; background: transparent !important; }',
'.top .search-form .search-input { display: none !important; }',
'.top .search-form .search-btn { display: grid !important; width: 30px !important; height: 30px !important; flex: 0 0 30px !important; place-items: center; padding: 0 !important; border: 1px solid var(--line) !important; border-radius: 6px !important; background: var(--panel) !important; color: var(--text-muted); }',
'.top .search-form .search-btn:hover, .top .search-form .search-btn:focus-visible { border-color: var(--brand-border) !important; background: var(--brand-soft) !important; color: var(--brand-hover) !important; outline: none; }',
'#sbe-search-modal { position: fixed; z-index: 2147483646; inset: 0; display: grid; place-items: start center; padding: min(18vh, 140px) 16px 16px; background: var(--backdrop); }',
'#sbe-search-modal[hidden] { display: none !important; }',
'#sbe-search-modal-dialog { width: min(520px, 100%); padding: 14px; border: 1px solid var(--line); border-radius: var(--sbe-radius); background: var(--panel); box-shadow: var(--shadow-medium); animation: sbe-search-dialog-in .16s ease-out; }',
'#sbe-search-modal-dialog form { display: flex; align-items: center; gap: 8px; }',
'#sbe-search-modal-dialog .sbe-search-input { min-width: 0; flex: 1 1 auto; height: 38px; box-sizing: border-box; padding: 0 11px; border: 1px solid var(--line-strong); border-radius: 7px; background: var(--panel-alt); color: var(--text); font: inherit; }',
'#sbe-search-modal-dialog .sbe-search-submit { display: inline-flex; width: 64px; min-width: 64px; height: 38px; flex: 0 0 64px; box-sizing: border-box; align-items: center; justify-content: center; padding: 0; border: 1px solid var(--brand-border); border-radius: 7px; background: var(--brand-soft); color: var(--brand-hover); font: inherit; line-height: 1; white-space: nowrap; cursor: pointer; }',
'#sbe-search-modal-dialog .sbe-search-submit:hover, #sbe-search-modal-dialog .sbe-search-submit:focus-visible { background: var(--brand); color: var(--inverse-text); outline: none; }',
'#sbe-search-modal-dialog .sbe-search-close { display: block; margin: 10px auto 0; padding: 0; border: 0; background: transparent; color: var(--text-subtle); font: 12px/1.4 var(--font-sans, system-ui); cursor: pointer; }',
'#sbe-search-modal-dialog .sbe-search-close:hover, #sbe-search-modal-dialog .sbe-search-close:focus-visible { color: var(--brand-hover); outline: none; }',
'.topic-replies.sbe-native-reply-tree-blocked .tree-toggle, .topic-replies.sbe-native-reply-tree-blocked .tree-status { display: none !important; }',
'.post-head .post-author + .verify-badge { margin-left: 4px !important; vertical-align: middle; }',
'.user-card .sbe-user-name-verify-row { display: flex !important; min-width: 0; align-items: center; gap: 4px; line-height: 1.2; }',
'.user-card .sbe-user-name-verify-row .user-name { display: inline-block !important; min-width: 0; }',
'.user-card .sbe-user-name-verify-row .user-verify { display: inline-flex !important; flex: 0 0 auto; margin: 0 !important; }',
'.user-card .sbe-user-name-verify-row .verify-badge { margin: 0 !important; white-space: nowrap; }',
'@keyframes sbe-search-dialog-in { from { opacity: 0; transform: translateY(-8px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } }',
'.stats-card .stats-wrap { padding: 12px 14px 11px; }',
'.stats-card .stats-title { margin-bottom: 6px; }',
'.stats-card .stats-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); justify-content: normal; gap: 2px 0; margin: 0 0 10px; padding: 0 6px; }',
'.stats-card .stats-grid > div:nth-child(1) { text-align: left; }',
'.stats-card .stats-grid > div:nth-child(2) { text-align: center; }',
'.stats-card .stats-grid > div:nth-child(3) { text-align: right; }',
'.stats-card .stats-grid dd { font-size: 13px; font-weight: 400; line-height: 1.35; }',
'.stats-card .new-users-title { margin: 0 0 8px; padding-top: 10px; border-top: 1px solid var(--line-soft); }',
'.stats-card .new-users { gap: 8px 4px; }',
'.stats-card .nu-item { position: relative; gap: 0; }',
'.stats-card .nu-avatar-circle { width: var(--sbe-avatar-size) !important; height: var(--sbe-avatar-size) !important; }',
'.stats-card .nu-avatar-circle .avatar-img { display: block; width: 100%; height: 100%; object-fit: cover; }',
'.stats-card .nu-name { display: none !important; }',
'.sbe-image-target, .sbe-image-target img { cursor: zoom-in !important; }',
'#sbe-image-lightbox { position: fixed; z-index: 2147483644; inset: 0; display: grid; place-items: center; padding: 16px; background: var(--backdrop); cursor: zoom-out; }',
'#sbe-image-lightbox[hidden] { display: none !important; }',
'#sbe-image-lightbox img { display: block; max-width: 96vw; max-height: 92vh; border: 0; border-radius: 0; box-shadow: none; cursor: zoom-out; object-fit: contain; }',
'#sbe-profile-card { position: fixed; z-index: 2147483645; width: 276px; padding: 13px; border: 1px solid var(--line); border-radius: var(--sbe-radius); background: var(--panel); box-shadow: var(--shadow-medium); color: var(--text); font-size: 13px; }',
'#sbe-profile-card[hidden] { display: none !important; }',
'.sbe-profile-head { min-width: 0; }',
'.sbe-profile-name-row { display: flex; min-width: 0; align-items: baseline; gap: 10px; }',
'.sbe-profile-name { overflow: hidden; min-width: 0; color: var(--text-strong); font-size: 14px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }',
'.sbe-profile-uid { margin-left: auto; color: var(--text-subtle); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }',
'.sbe-profile-name { text-decoration: none !important; }',
'.sbe-profile-name:hover, .sbe-profile-name:focus-visible { color: var(--brand-hover); text-decoration: none !important; outline: none; }',
'.sbe-profile-rank { margin-top: 3px; color: var(--text-subtle); font-size: 12px; }',
'.sbe-profile-stats { display: flex; align-items: center; gap: 8px; margin: 12px 0 0; }',
'.sbe-profile-stat { display: flex; min-width: 0; flex: 1 1 0; align-items: baseline; justify-content: space-between; gap: 8px; padding: 8px 9px; border-radius: max(5px, calc(var(--sbe-radius) - 4px)); background: var(--panel-alt); }',
'.sbe-profile-stat dt { overflow: hidden; color: var(--text-subtle); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }',
'.sbe-profile-stat dd { margin: 0; color: var(--text-strong); font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }',
'.sbe-code-copy { position: absolute; top: 8px; right: 8px; z-index: 1; padding: 4px 8px; border: 1px solid var(--line-strong); border-radius: 6px; background: var(--panel); color: var(--text-muted); font: 12px/1.2 var(--font-sans, system-ui); cursor: pointer; opacity: .82; }',
'.sbe-code-copy:hover, .sbe-code-copy:focus-visible { border-color: var(--brand-border); background: var(--brand-soft); color: var(--brand-hover); opacity: 1; outline: none; }',
'.post-content pre[data-sbe-copyable] { position: relative; }',
'#sbe-reading-progress { position: fixed; z-index: 2147483643; top: 0; left: 0; width: 100%; height: 3px; background: transparent; pointer-events: none; }',
'#sbe-reading-progress::before { display: block; width: 100%; height: 100%; background: var(--brand); box-shadow: 0 0 12px var(--brand); content: ""; transform: scaleX(var(--sbe-progress, 0)); transform-origin: left center; }',
'#sbe-reading-progress[hidden] { display: none !important; }',
'.sbe-layout-toggle { position: fixed; z-index: 2147483640; right: 24px; bottom: 24px; display: grid; width: 44px; height: 44px; place-items: center; padding: 0; border: 1px solid var(--brand-border); border-radius: 50% !important; background: var(--panel); box-shadow: var(--shadow-medium); color: var(--brand); cursor: pointer; transition: transform .15s ease, background-color .15s ease; }',
'.sbe-layout-toggle:hover { background: var(--brand-soft); transform: translateY(-2px); }',
'.sbe-layout-toggle svg { width: 20px; height: 20px; }',
'.sbe-layout-panel { position: fixed; z-index: 2147483641; right: 24px; bottom: 80px; width: 380px; max-height: min(78vh, 760px); overflow: auto; border: 1px solid var(--line); border-radius: calc(var(--sbe-radius) + 2px); background: var(--panel); box-shadow: var(--shadow-medium); color: var(--text); }',
'.sbe-layout-panel[hidden] { display: none !important; }',
'.sbe-panel-head { position: sticky; top: 0; z-index: 1; display: grid; min-height: 52px; grid-template-columns: minmax(0, 1fr) auto 30px; align-items: center; gap: 10px; padding: 10px 11px 10px 15px; border-bottom: 1px solid var(--line-soft); background: var(--panel); }',
'.sbe-panel-title { margin: 0; min-width: 0; color: var(--text-strong); font-size: 15px; white-space: nowrap; }',
'.sbe-panel-credit { justify-self: end; margin: 0; overflow: hidden; color: var(--text-subtle); font-size: 11px; text-align: right; text-overflow: ellipsis; white-space: nowrap; }',
'.sbe-panel-credit a { color: inherit; text-decoration: none; }',
'.sbe-panel-credit a:hover, .sbe-panel-credit a:focus-visible { color: var(--brand-hover); text-decoration: none; outline: none; }',
'.sbe-icon-button, .sbe-button { border: 1px solid var(--line-strong); border-radius: max(5px, calc(var(--sbe-radius) - 3px)) !important; background: transparent; color: var(--text); cursor: pointer; }',
'.sbe-icon-button { display: grid; width: 30px; height: 30px; flex: 0 0 30px; place-items: center; padding: 0; font-size: 21px; line-height: 1; }',
'.sbe-panel-body { padding: 14px 15px 16px; }',
'.sbe-section { margin: 0 0 14px; padding: 0 0 14px; border-bottom: 1px solid var(--line-soft); }',
'.sbe-section-title { margin: 0 0 10px; color: var(--text-strong); font-size: 13px; }',
'.sbe-range, .sbe-field, .sbe-check-line { display: grid; gap: 6px; margin: 9px 0; }',
'.sbe-range-head, .sbe-check-line { display: flex; align-items: center; justify-content: space-between; gap: 10px; }',
'.sbe-range-head span:first-child, .sbe-field > span { color: var(--text-muted); font-size: 12px; }',
'.sbe-range output, .sbe-color-value { color: var(--text-subtle); font-size: 12px; font-variant-numeric: tabular-nums; }',
'.sbe-range input[type="range"] { width: 100%; accent-color: var(--brand); }',
'.sbe-check-line { justify-content: flex-start; color: var(--text); font-size: 13px; }',
'.sbe-check-line input { margin: 0; accent-color: var(--brand); }',
'.sbe-input, .sbe-select, .sbe-textarea { width: 100%; box-sizing: border-box; padding: 8px 9px; border: 1px solid var(--line-strong); border-radius: max(5px, calc(var(--sbe-radius) - 3px)) !important; background: var(--panel-alt); color: var(--text); font: inherit; font-size: 13px; }',
'.sbe-textarea { min-height: 64px; resize: vertical; line-height: 1.45; }',
'.sbe-color-line { display: flex; align-items: center; gap: 8px; }',
'.sbe-color-input { width: 34px; height: 28px; padding: 2px; border: 1px solid var(--line-strong); border-radius: 5px !important; background: transparent; }',
'.sbe-note { margin: 8px 0 0; color: var(--text-subtle); font-size: 11px; line-height: 1.55; }',
'.sbe-actions { display: flex; justify-content: flex-end; gap: 8px; }',
'.sbe-button { padding: 8px 11px; font-size: 12px; }',
'.sbe-button:hover { border-color: var(--brand-border); background: var(--brand-soft); color: var(--brand-hover); }',
'.sbe-button-primary { border-color: var(--brand); background: var(--brand); color: var(--inverse-text); }',
'.sbe-button-primary:hover { border-color: var(--brand-hover); background: var(--brand-hover); color: var(--inverse-text); }',
'.sbe-status { min-height: 16px; margin: 10px 0 0; color: var(--text-subtle); font-size: 11px; text-align: right; }',
'@media (prefers-reduced-motion: reduce) { .sbe-layout-toggle, .sbe-forum-list a { transition: none !important; } }'
].join('\n');
addStyle(buildStartupCss(settings) + '\n' + BASE_CSS);
applySettings({ deferDynamic: true });
/* 在页面解析阶段即开始监听;分类页局部更新时可在同一绘制帧补齐增强,避免 100ms 后的二次跳动。 */
observePage();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize, { once: true });
} else {
initialize();
}
if (typeof GM_registerMenuCommand === 'function') {
GM_registerMenuCommand('打开 SB.SB 增强设置', function () {
ensureInterface();
setPanelOpen(true);
});
}
function initialize() {
ensureInterface();
applyDynamicEnhancements();
}
function addStyle(cssText) {
if (typeof GM_addStyle === 'function') {
GM_addStyle(cssText);
return;
}
var style = document.createElement('style');
style.id = 'sb-sb-enhance-style';
style.textContent = cssText;
(document.head || document.documentElement).appendChild(style);
}
function buildStartupCss(currentSettings) {
var start = ':root:not([data-sbe-ready]){';
start += '--sbe-max-width:' + currentSettings.maxWidth + 'px!important;';
start += '--sbe-header-width:' + currentSettings.headerWidth + 'px!important;';
start += '--sbe-header-height:' + currentSettings.headerHeight + 'px!important;';
start += '--sbe-font-size:' + currentSettings.fontSize + 'px!important;';
start += '--sbe-radius:' + currentSettings.radius + 'px!important;';
start += '--sbe-sidebar-width:' + currentSettings.sidebarWidth + 'px!important;';
start += '--sbe-shell-padding:' + currentSettings.shellPadding + 'px!important;';
start += '--sbe-column-gap:' + currentSettings.columnGap + 'px!important;';
start += '--sbe-avatar-size:' + currentSettings.avatarSize + 'px!important;';
start += '}';
return start;
}
function normalizeSettings(value) {
var source = value && typeof value === 'object' ? value : {};
var result = Object.assign({}, DEFAULTS, source);
delete result['infinite' + 'HomeScroll'];
if (Number(source.version || 0) < 7 && Number(source.avatarSize) === 24) {
result.avatarSize = DEFAULTS.avatarSize;
}
RANGE_DEFINITIONS.forEach(function (definition) {
var number = Number(result[definition.key]);
result[definition.key] = Number.isFinite(number) ? Math.max(definition.min, Math.min(definition.max, number)) : DEFAULTS[definition.key];
});
if (!Object.prototype.hasOwnProperty.call(THEMES, result.theme)) {
result.theme = DEFAULTS.theme;
}
if (['compact', 'comfortable', 'relaxed'].indexOf(result.density) < 0) {
result.density = DEFAULTS.density;
}
['newWindowForTopics', 'stickyForumList', 'avatarProfileCard', 'blockNativeProfileCard', 'blockNativeReplyTree', 'imageLightbox', 'readingProgress', 'codeCopyButtons', 'autoCheckin'].forEach(function (key) {
result[key] = result[key] === true || result[key] === 'true';
});
result.titleFilters = normalizeFilterList(result.titleFilters);
result.userFilters = normalizeFilterList(result.userFilters);
result.version = SETTINGS_VERSION;
return result;
}
function normalizeFilterList(value) {
var list = Array.isArray(value) ? value : [];
return list.map(function (item) { return String(item || '').trim().replace(/\s+/g, ' '); }).filter(Boolean).slice(0, 10).map(function (item) { return item.slice(0, 64); });
}
function loadSettings() {
try {
var stored = typeof GM_getValue === 'function' ? GM_getValue(STORAGE_KEY, null) : JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');
return normalizeSettings(stored);
} catch (error) {
return normalizeSettings(null);
}
}
function persistSettings() {
try {
if (typeof GM_setValue === 'function') {
GM_setValue(STORAGE_KEY, settings);
} else {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
}
showStatus('设置已保存');
} catch (error) {
showStatus('设置已应用,但保存失败');
}
}
function scheduleSave() {
window.clearTimeout(saveTimer);
saveTimer = window.setTimeout(persistSettings, SAVE_DELAY);
}
function setRootVariable(name, value) {
if (document.documentElement) {
document.documentElement.style.setProperty(name, value, 'important');
}
}
function clearThemeVariables() {
if (!document.documentElement) {
return;
}
THEME_VARIABLES.forEach(function (name) { document.documentElement.style.removeProperty(name); });
}
function applySettings(options) {
var configuration = options || {};
if (!document.documentElement) {
return;
}
setRootVariable('--sbe-max-width', settings.maxWidth + 'px');
setRootVariable('--sbe-header-width', settings.headerWidth + 'px');
setRootVariable('--sbe-header-height', settings.headerHeight + 'px');
setRootVariable('--sbe-font-size', settings.fontSize + 'px');
setRootVariable('--sbe-radius', settings.radius + 'px');
setRootVariable('--sbe-sidebar-width', settings.sidebarWidth + 'px');
setRootVariable('--sbe-shell-padding', settings.shellPadding + 'px');
setRootVariable('--sbe-column-gap', settings.columnGap + 'px');
setRootVariable('--sbe-avatar-size', settings.avatarSize + 'px');
setRootVariable('--r', settings.radius + 'px');
setRootVariable('--r-sm', Math.max(4, settings.radius - 3) + 'px');
setRootVariable('--r-xs', Math.max(3, settings.radius - 5) + 'px');
document.documentElement.setAttribute('data-sbe-density', settings.density);
syncNativeProfileCardBlocker();
applyTheme();
document.documentElement.setAttribute('data-sbe-ready', '');
if (!configuration.deferDynamic) {
applyDynamicEnhancements();
}
}
function applyTheme() {
var theme = THEMES[settings.theme] || THEMES.original;
clearThemeVariables();
document.documentElement.removeAttribute('data-sbe-theme');
if (!theme.vars) {
return;
}
Object.keys(theme.vars).forEach(function (name) { setRootVariable(name, theme.vars[name]); });
document.documentElement.setAttribute('data-sbe-theme', settings.theme);
}
function scheduleDynamicRefresh(root) {
/* 分类切换时只处理新节点,并在本轮微任务完成,避免固定延迟和二次跳动。 */
if (root && root.nodeType === 1) {
var covered = pendingEnhancementRoots.some(function (item) { return item === root || item.contains(root); });
if (!covered) {
pendingEnhancementRoots = pendingEnhancementRoots.filter(function (item) { return !root.contains(item); });
pendingEnhancementRoots.push(root);
}
}
if (refreshFrame) { return; }
refreshFrame = 1;
var flush = function () {
refreshFrame = 0;
var roots = pendingEnhancementRoots.splice(0);
applyDynamicEnhancements(roots.length ? roots : [document]);
};
if (typeof window.queueMicrotask === 'function') {
window.queueMicrotask(flush);
} else {
Promise.resolve().then(flush);
}
}
function observePage() {
if (pageObserver || typeof MutationObserver !== 'function' || !document.documentElement) {
return;
}
pageObserver = new MutationObserver(function (mutations) {
if (enhancementsRunning) { return; }
mutations.forEach(function (mutation) {
if (mutation.type !== 'childList' || !mutation.addedNodes.length) { return; }
var target = mutation.target && mutation.target.nodeType === 1 ? mutation.target : mutation.target.parentElement;
if (target && target.closest && target.closest('#sbe-layout-panel, #sbe-image-lightbox, #sbe-profile-card, .sbe-forum-card')) { return; }
Array.prototype.forEach.call(mutation.addedNodes, function (node) {
if (node.nodeType === 1) { scheduleDynamicRefresh(node); }
});
});
});
pageObserver.observe(document.documentElement, { childList: true, subtree: true });
}
function queryWithin(roots, selector) {
var result = [];
var seen = new Set();
(roots && roots.length ? roots : [document]).forEach(function (root) {
if (!root || !root.querySelectorAll) { return; }
if (root.nodeType === 1 && root.matches(selector) && !seen.has(root)) {
seen.add(root);
result.push(root);
}
root.querySelectorAll(selector).forEach(function (node) {
if (!seen.has(node)) {
seen.add(node);
result.push(node);
}
});
});
return result;
}
function applyDynamicEnhancements(roots) {
if (enhancementsRunning) { return; }
var scopes = roots && roots.length ? roots : [document];
enhancementsRunning = true;
/* 执行增强时暂停观察,杜绝脚本自己的 DOM 写入形成反馈循环。 */
if (pageObserver) { pageObserver.disconnect(); }
try {
ensureForumShortcutCard();
applyTopicNewWindow(scopes);
removeLegacyTopicViews(scopes);
removeLegacyTopicPostUids(scopes);
applyFilters(scopes);
syncLatestUserAvatars(scopes);
alignVerificationBadges(scopes);
syncNativeReplyTreeBlocker(scopes);
bindSearchModal();
updateImageLightboxTargets(scopes);
bindProfileCardTargets(scopes);
maybeRunAutoCheckin();
scheduleReadingProgressUpdate();
updateCodeCopyButtons(scopes);
} finally {
enhancementsRunning = false;
if (pageObserver && document.documentElement) {
pageObserver.observe(document.documentElement, { childList: true, subtree: true });
}
}
}
function getCheckinStorageKey() {
var now = new Date();
return CHECKIN_STORAGE_PREFIX + now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0');
}
function hasAutoCheckinRunToday() {
var key = getCheckinStorageKey();
try {
return typeof GM_getValue === 'function' ? GM_getValue(key, false) === true : localStorage.getItem(key) === '1';
} catch (error) {
return false;
}
}
function markAutoCheckinRunToday() {
var key = getCheckinStorageKey();
try {
if (typeof GM_setValue === 'function') {
GM_setValue(key, true);
} else {
localStorage.setItem(key, '1');
}
} catch (error) {
/* 存储不可用时仍避免当前页面内重复触发。 */
}
}
function requestSigninPage(method, csrfToken) {
var options = { method: method, credentials: 'same-origin' };
if (method === 'POST') {
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' };
options.body = '_csrf=' + encodeURIComponent(csrfToken || '');
}
return fetch('/signin/', options).then(function (response) { return response.text(); });
}
function parseSigninState(html) {
var parsed = new DOMParser().parseFromString(String(html || ''), 'text/html');
var text = ((parsed.body || {}).textContent || '').replace(/\s+/g, ' ').trim();
var button = parsed.querySelector('.signin-hero-action .btn-post, form[action*="/signin" i] button[type="submit"], form[action*="/signin" i] input[type="submit"]');
var label = button ? (button.textContent || button.value || '').replace(/\s+/g, ' ').trim() : '';
var csrf = ((parsed.querySelector('input[name="_csrf"]') || {}).value || '').trim();
var signed = /今日已签到/.test(label) || Boolean(parsed.querySelector('.signin-cal-day.done.today'));
var loginRequired = Boolean(parsed.querySelector('input[name="username"], input[type="password"]')) && /登录/.test(text) && !/每日签到/.test(text);
return { signed: signed, loginRequired: loginRequired, csrf: csrf, canSubmit: Boolean(button && /签到/.test(label) && !/已签到/.test(label)), label: label };
}
function maybeRunAutoCheckin() {
if (!settings.autoCheckin || checkinPending || hasAutoCheckinRunToday()) {
return;
}
checkinPending = true;
requestSigninPage('GET').then(function (html) {
var state = parseSigninState(html);
if (state.loginRequired) {
throw new Error('未登录');
}
if (state.signed) {
markAutoCheckinRunToday();
showStatus('今日已签到');
return null;
}
if (!state.canSubmit || !state.csrf) {
throw new Error('未找到可用签到入口');
}
return requestSigninPage('POST', state.csrf).then(function (resultHtml) {
var result = parseSigninState(resultHtml);
if (!result.loginRequired && result.signed) {
markAutoCheckinRunToday();
showStatus('已完成自动签到');
return;
}
throw new Error('自动签到未确认成功');
});
}).catch(function () {
/* 登录失效、站点结构变化或网络波动时保持静默,避免打扰正常浏览。 */
}).finally(function () {
checkinPending = false;
});
}
function ensureForumShortcutCard() {
var existing = document.querySelector('.sbe-forum-card');
var sidebar = document.querySelector('.forum-layout > .sidebar');
if (!settings.stickyForumList || !sidebar || window.matchMedia('(max-width: 720px)').matches) {
if (existing) { existing.remove(); }
return;
}
if (existing && existing.parentElement === sidebar) {
if (sidebar.lastElementChild !== existing) {
sidebar.appendChild(existing);
}
return;
}
if (existing) { existing.remove(); }
var sources = Array.from(document.querySelectorAll('.forum-more-panel a[href^="/go/"]'));
if (!sources.length) {
sources = Array.from(document.querySelectorAll('.forum-nav a[href^="/go/"]'));
}
if (!sources.length) {
return;
}
var card = document.createElement('section');
card.className = 'sbe-forum-card';
card.setAttribute('aria-label', '版块快捷导航');
var title = document.createElement('h2');
title.textContent = '版块快捷导航';
var list = document.createElement('nav');
list.className = 'sbe-forum-list';
sources.forEach(function (source) {
var link = document.createElement('a');
link.href = source.href;
link.textContent = source.textContent.trim();
if (source.classList.contains('active') || source.getAttribute('aria-current') === 'page') {
link.setAttribute('aria-current', 'page');
}
list.appendChild(link);
});
card.appendChild(title);
card.appendChild(list);
/* 让快捷导航保持在原生侧栏卡片之后,避免抢占访客/用户信息的位置。 */
sidebar.appendChild(card);
}
function removeLegacyTopicPostUids(roots) {
queryWithin(roots, '.sbe-topic-post-uid').forEach(function (element) {
element.remove();
});
}
function removeLegacyTopicViews(roots) {
queryWithin(roots, '.sbe-topic-views').forEach(function (element) {
element.remove();
});
}
function applyTopicNewWindow(roots) {
queryWithin(roots, '.post-item .post-title[href^="/t/"], .sidebar-topic-title[href^="/t/"]').forEach(function (link) {
if (settings.newWindowForTopics) {
if (!link.hasAttribute('data-sbe-original-target')) {
link.setAttribute('data-sbe-original-target', link.getAttribute('target') || '__none__');
link.setAttribute('data-sbe-original-rel', link.getAttribute('rel') || '__none__');
}
link.target = '_blank';
link.rel = 'noopener noreferrer';
} else if (link.hasAttribute('data-sbe-original-target')) {
restoreAttribute(link, 'target', link.getAttribute('data-sbe-original-target'));
restoreAttribute(link, 'rel', link.getAttribute('data-sbe-original-rel'));
link.removeAttribute('data-sbe-original-target');
link.removeAttribute('data-sbe-original-rel');
}
});
}
function restoreAttribute(element, attribute, value) {
if (value === '__none__') {
element.removeAttribute(attribute);
} else {
element.setAttribute(attribute, value);
}
}
function applyFilters(roots) {
var titles = settings.titleFilters.map(function (item) { return item.toLowerCase(); });
var users = settings.userFilters.map(function (item) { return item.toLowerCase(); });
queryWithin(roots, '.post-item').forEach(function (item) {
var title = ((item.querySelector('.post-title') || {}).textContent || '').trim().toLowerCase();
var author = ((item.querySelector('.post-meta a[href^="/u/"]') || {}).textContent || '').trim().toLowerCase();
var hidden = titles.some(function (itemTitle) { return title.indexOf(itemTitle) !== -1; }) || users.some(function (username) { return username === author; });
if (hidden) {
item.style.setProperty('display', 'none', 'important');
item.setAttribute('data-sbe-filtered', '1');
} else if (item.hasAttribute('data-sbe-filtered')) {
item.style.removeProperty('display');
item.removeAttribute('data-sbe-filtered');
}
});
}
function syncLatestUserAvatars(roots) {
queryWithin(roots, '.stats-card .nu-item').forEach(function (item) {
var name = ((item.querySelector('.nu-name') || {}).textContent || '').trim();
if (!name) {
var avatar = item.querySelector('.avatar-img');
name = avatar ? (avatar.getAttribute('alt') || '').trim() : '';
}
if (name) {
item.setAttribute('title', name);
item.setAttribute('aria-label', name);
}
});
}
function alignVerificationBadges(roots) {
queryWithin(roots, '.post-head .post-author').forEach(function (author) {
var head = author.closest('.post-head');
var badge = head ? head.querySelector('.verify-badge') : null;
if (badge && badge.previousElementSibling !== author) {
author.insertAdjacentElement('afterend', badge);
}
});
queryWithin(roots, '.user-card .user-header-info').forEach(function (header) {
var name = header.querySelector('.user-name');
var verification = header.querySelector('.user-verify');
var host = name && name.parentElement;
if (!name || !verification || !host) {
return;
}
var row = host.querySelector(':scope > .sbe-user-name-verify-row');
if (!row) {
row = document.createElement('div');
row.className = 'sbe-user-name-verify-row';
host.insertBefore(row, name);
}
if (name.parentElement !== row) {
row.appendChild(name);
}
if (verification.parentElement !== row) {
row.appendChild(verification);
}
});
}
function syncNativeReplyTreeBlocker(roots) {
queryWithin(roots, '.topic-replies[data-reply-tree="1"]').forEach(function (container) {
if (!settings.blockNativeReplyTree) {
container.classList.remove('sbe-native-reply-tree-blocked');
return;
}
container.classList.add('sbe-native-reply-tree-blocked');
var flatButton = container.querySelector('[data-tree-mode="flat"]');
if (flatButton && !flatButton.classList.contains('on')) {
flatButton.click();
}
});
}
function ensureSearchModal(sourceForm) {
if (searchModal || !document.body) {
return;
}
searchModal = document.createElement('div');
searchModal.id = 'sbe-search-modal';
searchModal.hidden = true;
searchModal.setAttribute('role', 'dialog');
searchModal.setAttribute('aria-modal', 'true');
searchModal.setAttribute('aria-label', '站内搜索');
var dialog = document.createElement('div');
dialog.id = 'sbe-search-modal-dialog';
searchModalForm = document.createElement('form');
searchModalForm.method = sourceForm.getAttribute('method') || 'post';
searchModalForm.action = sourceForm.getAttribute('action') || '/search/';
searchModalForm.setAttribute('data-no-ajax', '1');
var sourceCsrf = sourceForm.querySelector('input[name="_csrf"]');
if (sourceCsrf) {
var csrf = document.createElement('input');
csrf.type = 'hidden';
csrf.name = '_csrf';
csrf.value = sourceCsrf.value || '';
searchModalForm.appendChild(csrf);
}
searchModalInput = document.createElement('input');
searchModalInput.className = 'sbe-search-input';
searchModalInput.type = 'search';
searchModalInput.name = 'q';
searchModalInput.placeholder = '搜索关键词';
searchModalInput.minLength = 2;
searchModalInput.required = true;
var submit = document.createElement('button');
submit.className = 'sbe-search-submit';
submit.type = 'submit';
submit.textContent = '搜索';
searchModalForm.appendChild(searchModalInput);
searchModalForm.appendChild(submit);
var close = document.createElement('button');
close.className = 'sbe-search-close';
close.type = 'button';
// close.textContent = '取消';
close.addEventListener('click', closeSearchModal);
dialog.appendChild(searchModalForm);
dialog.appendChild(close);
searchModal.appendChild(dialog);
searchModal.addEventListener('click', function (event) {
if (event.target === searchModal) {
closeSearchModal();
}
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && searchModal && !searchModal.hidden) {
closeSearchModal();
}
});
document.body.appendChild(searchModal);
}
function openSearchModal(sourceForm, value) {
ensureSearchModal(sourceForm);
if (!searchModal || !searchModalInput) {
return;
}
searchModalInput.value = value || '';
searchModal.hidden = false;
window.setTimeout(function () { searchModalInput.focus(); }, 0);
}
function closeSearchModal() {
if (searchModal) {
searchModal.hidden = true;
}
}
function bindSearchModal() {
var form = document.querySelector('.top .search-form');
if (!form || form.getAttribute('data-sbe-search-modal-bound') === '1') {
return;
}
var input = form.querySelector('.search-input[type="search"]');
var button = form.querySelector('.search-btn[type="submit"]');
if (!input || !button) {
return;
}
form.setAttribute('data-sbe-search-modal-bound', '1');
var open = function (event) {
event.preventDefault();
openSearchModal(form, input.value);
};
button.addEventListener('click', open);
form.addEventListener('submit', open);
}
function updateImageLightboxTargets(roots) {
queryWithin(roots, '.post-content a.post-image-link').forEach(function (link) {
var image = link.querySelector('img');
if (settings.imageLightbox && image && (image.currentSrc || image.src)) {
link.classList.add('sbe-image-target');
link.setAttribute('data-sbe-lightbox', '1');
link.setAttribute('aria-label', '打开图片预览:' + (image.alt || '图片'));
} else {
link.classList.remove('sbe-image-target');
link.removeAttribute('data-sbe-lightbox');
link.removeAttribute('aria-label');
}
});
ensureImageLightboxEvents();
if (!settings.imageLightbox) {
closeImageLightbox();
}
}
function ensureImageLightboxEvents() {
if (lightboxEventsBound) {
return;
}
lightboxEventsBound = true;
document.addEventListener('click', function (event) {
var link = event.target && event.target.closest ? event.target.closest('a.sbe-image-target[data-sbe-lightbox]') : null;
if (!link || !settings.imageLightbox) {
return;
}
var image = link.querySelector('img');
if (!image) {
return;
}
event.preventDefault();
openImageLightbox(link.href || image.currentSrc || image.src, image.alt || '图片预览');
}, true);
}
function ensureImageLightbox() {
if (lightbox || !document.body) {
return;
}
lightbox = document.createElement('div');
lightbox.id = 'sbe-image-lightbox';
lightbox.hidden = true;
lightbox.setAttribute('role', 'dialog');
lightbox.setAttribute('aria-modal', 'true');
lightbox.setAttribute('aria-label', '图片预览');
lightboxImage = document.createElement('img');
lightbox.appendChild(lightboxImage);
lightbox.addEventListener('click', function (event) {
if (event.target === lightbox || event.target === lightboxImage) {
closeImageLightbox();
}
});
document.body.appendChild(lightbox);
}
function openImageLightbox(source, altText) {
if (!settings.imageLightbox || !source) {
return;
}
ensureImageLightbox();
if (!lightbox || !lightboxImage) {
return;
}
lightboxImage.src = source;
lightboxImage.alt = altText || '';
lightbox.hidden = false;
}
function closeImageLightbox() {
if (!lightbox || lightbox.hidden) {
return;
}
lightbox.hidden = true;
}
function getUserId(href) {
var match = String(href || '').match(/\/u\/(\d+)(?:[/?#]|$)/);
return match ? match[1] : '';
}
function syncNativeProfileCardBlocker() {
if (nativeProfileCardBlockerBound || !document || !document.addEventListener) {
return;
}
nativeProfileCardBlockerBound = true;
['pointerover', 'pointerenter', 'mouseover', 'mouseenter'].forEach(function (eventName) {
document.addEventListener(eventName, blockNativeProfileCard, true);
});
}
function blockNativeProfileCard(event) {
if (!settings.avatarProfileCard || !settings.blockNativeProfileCard || !isTopicPage()) {
return;
}
var source = event.target;
if (!source || !source.closest) {
return;
}
var target = source.closest('.post-item a[href^="/u/"], .topic-post-list .post-head a[href^="/u/"], .post-item .post-avatar img');
if (!target) {
return;
}
var profileHref = target.getAttribute('href');
var username = target.textContent.trim();
if (target.matches('img')) {
var item = target.closest('.post-item');
var author = item ? item.querySelector('.post-meta a[href^="/u/"]') : null;
if (!author) {
return;
}
profileHref = author.getAttribute('href');
username = author.textContent.trim();
}
var uid = getUserId(profileHref);
if (!uid) {
return;
}
/* 在捕获阶段阻止站点为同一目标注册的悬停监听器;点击与链接导航不受影响。 */
event.stopImmediatePropagation();
if (event.type === 'mouseenter') {
target.setAttribute('data-sbe-profile-url', profileHref);
target.setAttribute('data-sbe-profile-name', username || ('UID ' + uid));
showProfileCard(target, uid);
}
}
function bindProfileCardTargets(roots) {
if (!settings.avatarProfileCard) {
hideProfileCard();
return;
}
queryWithin(roots, '.post-item a[href^="/u/"], .topic-post-list .post-head a[href^="/u/"]').forEach(function (anchor) {
bindProfileCardTarget(anchor, anchor.getAttribute('href'), anchor.textContent.trim());
});
queryWithin(roots, '.post-item .post-avatar img').forEach(function (avatar) {
var item = avatar.closest('.post-item');
var author = item ? item.querySelector('.post-meta a[href^="/u/"]') : null;
if (author) {
bindProfileCardTarget(avatar, author.getAttribute('href'), author.textContent.trim());
}
});
}
function bindProfileCardTarget(target, profileHref, username) {
var uid = getUserId(profileHref);
if (!target || !uid || target.getAttribute('data-sbe-profile-bound') === '1') {
return;
}
target.setAttribute('data-sbe-profile-bound', '1');
target.setAttribute('data-sbe-profile-url', profileHref);
target.setAttribute('data-sbe-profile-name', username || ('UID ' + uid));
target.addEventListener('mouseenter', function () { showProfileCard(target, uid); });
target.addEventListener('mouseleave', scheduleHideProfileCard);
if (target.matches('a')) {
target.addEventListener('focus', function () { showProfileCard(target, uid); });
target.addEventListener('blur', hideProfileCard);
}
}
function getPublicProfile(target, uid) {
var cached = profileCache[uid];
if (cached && cached.data && cached.expiresAt > Date.now()) {
return Promise.resolve(cached.data);
}
if (cached && cached.promise) {
return cached.promise;
}
var fallbackName = target.getAttribute('data-sbe-profile-name') || target.textContent.trim() || ('UID ' + uid);
var href = target.getAttribute('data-sbe-profile-url') || target.getAttribute('href');
var url = new URL(href, window.location.origin).href;
var request = fetch(url, { credentials: 'same-origin' }).then(function (response) {
if (!response.ok) { throw new Error('资料请求失败'); }
return response.text();
}).then(function (html) {
var data = parsePublicProfile(html, uid, fallbackName);
profileCache[uid] = { data: data, expiresAt: Date.now() + PROFILE_CACHE_TTL };
return data;
}).catch(function () {
var fallback = { uid: uid, username: fallbackName, rank: '暂未读取', avatarUrl: '', stats: {} };
profileCache[uid] = { data: fallback, expiresAt: Date.now() + 60 * 1000 };
return fallback;
});
profileCache[uid] = { promise: request };
return request;
}
function parsePublicProfile(html, uid, fallbackName) {
var parsed = new DOMParser().parseFromString(String(html || ''), 'text/html');
var name = parsed.querySelector('.user-name');
var rank = parsed.querySelector('.user-rank');
var avatar = parsed.querySelector('.user-avatar-big img, .user-header img.avatar-img');
var stats = {};
parsed.querySelectorAll('.user-stat').forEach(function (stat) {
var label = ((stat.querySelector('.user-stat-label') || {}).textContent || '').trim();
var value = ((stat.querySelector('.user-stat-value') || {}).textContent || '').trim();
/* 站点已将“积分”更名为“烧饼”;旧页面或缓存资料仍兼容归一到新键名。 */
if (label === '积分') { label = '烧饼'; }
if (label && value) { stats[label] = value; }
});
return {
uid: uid,
username: name ? name.textContent.trim() : fallbackName,
rank: rank ? rank.textContent.replace(/\s+/g, ' ').trim() : '公开资料',
avatarUrl: avatar ? avatar.currentSrc || avatar.src : '',
stats: stats
};
}
function ensureProfileCard() {
if (profileCard || !document.body) {
return;
}
profileCard = document.createElement('aside');
profileCard.id = 'sbe-profile-card';
profileCard.hidden = true;
profileCard.setAttribute('role', 'status');
profileCard.setAttribute('aria-live', 'polite');
profileCard.addEventListener('mouseenter', function () { window.clearTimeout(profileHideTimer); });
profileCard.addEventListener('mouseleave', scheduleHideProfileCard);
document.body.appendChild(profileCard);
}
function renderProfileCard(data, loading) {
if (!profileCard) {
return;
}
profileCard.textContent = '';
var head = document.createElement('div');
head.className = 'sbe-profile-head';
var identity = document.createElement('div');
var nameRow = document.createElement('div');
nameRow.className = 'sbe-profile-name-row';
var name = document.createElement('a');
name.className = 'sbe-profile-name';
name.href = '/u/' + encodeURIComponent(String(data.uid)) + '/';
name.textContent = data.username;
var uid = document.createElement('div');
uid.className = 'sbe-profile-uid';
uid.textContent = 'UID ' + data.uid;
var rank = document.createElement('div');
rank.className = 'sbe-profile-rank';
rank.textContent = loading ? '读取公开资料中…' : data.rank;
nameRow.appendChild(name);
nameRow.appendChild(uid);
identity.appendChild(nameRow);
identity.appendChild(rank);
head.appendChild(identity);
profileCard.appendChild(head);
var grid = document.createElement('dl');
grid.className = 'sbe-profile-stats';
['烧饼', '成长值'].forEach(function (label) {
var item = document.createElement('div');
item.className = 'sbe-profile-stat';
var title = document.createElement('dt');
title.textContent = label;
var value = document.createElement('dd');
value.textContent = loading ? '读取中' : (data.stats[label] || '未公开');
item.appendChild(title);
item.appendChild(value);
grid.appendChild(item);
});
profileCard.appendChild(grid);
}
function positionProfileCard(target) {
if (!profileCard || !target) {
return;
}
var rect = target.getBoundingClientRect();
var gap = 10;
var maxLeft = Math.max(12, window.innerWidth - profileCard.offsetWidth - 12);
var maxTop = Math.max(12, window.innerHeight - profileCard.offsetHeight - 12);
var rightSide = rect.right + gap;
var left = rightSide + profileCard.offsetWidth <= window.innerWidth - 12 ? rightSide : rect.left - profileCard.offsetWidth - gap;
profileCard.style.left = Math.round(Math.max(12, Math.min(maxLeft, left))) + 'px';
profileCard.style.top = Math.round(Math.max(12, Math.min(maxTop, rect.top))) + 'px';
}
function showProfileCard(target, uid) {
if (!settings.avatarProfileCard || !target) {
return;
}
window.clearTimeout(profileHideTimer);
ensureProfileCard();
if (!profileCard) { return; }
var initial = { uid: uid, username: target.getAttribute('data-sbe-profile-name') || ('UID ' + uid), rank: '', avatarUrl: '', stats: {} };
profileCard.dataset.uid = uid;
renderProfileCard(initial, true);
profileCard.hidden = false;
positionProfileCard(target);
getPublicProfile(target, uid).then(function (data) {
if (!profileCard || profileCard.hidden || profileCard.dataset.uid !== uid) { return; }
renderProfileCard(data, false);
positionProfileCard(target);
});
}
function scheduleHideProfileCard() {
window.clearTimeout(profileHideTimer);
profileHideTimer = window.setTimeout(hideProfileCard, 120);
}
function hideProfileCard() {
window.clearTimeout(profileHideTimer);
if (profileCard) { profileCard.hidden = true; }
}
function isTopicPage() {
return /^\/t\/\d+/.test(window.location.pathname) && Boolean(document.querySelector('.topic-post-list, .topic-replies'));
}
function ensureReadingProgress() {
if (progressBar || !document.body) {
return;
}
progressBar = document.createElement('div');
progressBar.id = 'sbe-reading-progress';
progressBar.hidden = true;
progressBar.setAttribute('aria-hidden', 'true');
document.body.appendChild(progressBar);
if (!progressBound) {
progressBound = true;
window.addEventListener('scroll', scheduleReadingProgressUpdate, { passive: true });
window.addEventListener('resize', scheduleReadingProgressUpdate, { passive: true });
}
}
function scheduleReadingProgressUpdate() {
if (progressFrame) { return; }
var run = function () {
progressFrame = 0;
updateReadingProgressNow();
};
if (typeof window.requestAnimationFrame === 'function') {
progressFrame = window.requestAnimationFrame(run);
} else {
progressFrame = window.setTimeout(run, 16);
}
}
function updateReadingProgressNow() {
ensureReadingProgress();
if (!progressBar) { return; }
var enabled = settings.readingProgress && isTopicPage();
if (progressBar.hidden === enabled) { progressBar.hidden = !enabled; }
if (!enabled) {
lastProgressValue = '';
return;
}
var scrollable = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
var value = Math.max(0, Math.min(1, window.scrollY / scrollable)).toFixed(4);
if (value !== lastProgressValue) {
lastProgressValue = value;
progressBar.style.setProperty('--sbe-progress', value);
}
}
function updateCodeCopyButtons(roots) {
queryWithin(roots, '.post-content pre').forEach(function (block) {
var existing = block.querySelector(':scope > .sbe-code-copy');
if (!settings.codeCopyButtons) {
if (existing) { existing.remove(); }
block.removeAttribute('data-sbe-copyable');
return;
}
block.setAttribute('data-sbe-copyable', '1');
if (existing) { return; }
var button = document.createElement('button');
button.type = 'button';
button.className = 'sbe-code-copy';
button.textContent = '复制';
button.setAttribute('aria-label', '复制代码');
button.addEventListener('click', function () {
copyText(block.innerText || block.textContent || '').then(function () {
button.textContent = '已复制';
window.setTimeout(function () { button.textContent = '复制'; }, 1200);
}).catch(function () {
button.textContent = '失败';
window.setTimeout(function () { button.textContent = '复制'; }, 1200);
});
});
block.appendChild(button);
});
}
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
return new Promise(function (resolve, reject) {
try {
var input = document.createElement('textarea');
input.value = text;
input.style.position = 'fixed';
input.style.opacity = '0';
document.body.appendChild(input);
input.select();
var copied = document.execCommand('copy');
input.remove();
copied ? resolve() : reject(new Error('复制失败'));
} catch (error) {
reject(error);
}
});
}
function ensureInterface() {
if (panel && document.body && document.body.contains(panel)) {
return;
}
if (!document.body) {
return;
}
toggleButton = document.createElement('button');
toggleButton.className = 'sbe-layout-toggle';
toggleButton.type = 'button';
toggleButton.title = 'SB.SB 增强设置';
toggleButton.setAttribute('aria-label', '打开 SB.SB 增强设置');
toggleButton.setAttribute('aria-controls', 'sbe-layout-panel');
toggleButton.setAttribute('aria-expanded', 'false');
toggleButton.innerHTML = '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h2M10 17h10M14 4v6M7 14v6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>';
panel = document.createElement('section');
panel.id = 'sbe-layout-panel';
panel.className = 'sbe-layout-panel';
panel.hidden = true;
panel.setAttribute('role', 'dialog');
panel.setAttribute('aria-modal', 'false');
panel.setAttribute('aria-label', 'SB.SB 增强设置');
panel.innerHTML = [
'<div class="sbe-panel-head"><h2 class="sbe-panel-title">SB.SB 增强设置</h2><p class="sbe-panel-credit">Design by <a href="https://com.com.ee" target="_blank" rel="noopener noreferrer external">COM</a></p><button class="sbe-icon-button" type="button" data-sbe-close aria-label="关闭设置">×</button></div>',
'<div class="sbe-panel-body">',
'<section class="sbe-section"><h3 class="sbe-section-title">布局参数</h3><div data-sbe-ranges></div><label class="sbe-field"><span>列表密度</span><select class="sbe-select" data-sbe-density><option value="compact">紧凑</option><option value="comfortable">舒适</option><option value="relaxed">宽松</option></select></label></section>',
'<section class="sbe-section"><h3 class="sbe-section-title">主题与配色</h3><label class="sbe-field"><span>配色方案</span><select class="sbe-select" data-sbe-theme></select></label><p class="sbe-note">深色主题的悬停状态仅使用相邻层级的面板色和品牌柔光,不会出现白底反转或白色闪烁。</p></section>',
'<section class="sbe-section"><h3 class="sbe-section-title">阅读与列表</h3>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-new-window><span>主题链接在新窗口打开</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-sticky-forums><span>侧栏常驻版块快捷导航</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-avatar-card><span>头像与用户名悬停资料卡</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-block-native-card><span>屏蔽网站自带的悬停资料卡</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-block-reply-tree><span>屏蔽网站回复树</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-lightbox><span>帖子图片灯箱</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-reading-progress><span>主题页阅读进度条</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-code-copy><span>代码块一键复制</span></label>',
'<label class="sbe-check-line"><input type="checkbox" data-sbe-auto-checkin><span>发现站内原生签到入口时,每日自动签到一次</span></label>',
'<p class="sbe-note">仅在已登录页面检测到原生“签到”控件时才会触发;脚本不构造、猜测或转发签到接口。</p>',
'</section>',
'<section class="sbe-section"><h3 class="sbe-section-title">内容过滤</h3>',
'<label class="sbe-field"><span>过滤标题关键字(每行一个,最多 10 项)</span><textarea class="sbe-textarea" data-sbe-title-filters rows="3" placeholder="输入要隐藏的标题关键字"></textarea></label>',
'<label class="sbe-field"><span>过滤用户名(每行一个,最多 10 项)</span><textarea class="sbe-textarea" data-sbe-user-filters rows="3" placeholder="输入要隐藏的发帖用户名"></textarea></label>',
'<p class="sbe-note">本站原生支持图片、附件和截图粘贴上传。本脚本不会使用第三方图床,也不会将图片或附件外传。</p>',
'</section>',
'<div class="sbe-actions"><button class="sbe-button" type="button" data-sbe-reset>恢复默认</button><button class="sbe-button sbe-button-primary" type="button" data-sbe-done>完成</button></div>',
'<p class="sbe-status" data-sbe-status aria-live="polite"></p></div>'
].join('');
document.body.appendChild(toggleButton);
document.body.appendChild(panel);
statusElement = panel.querySelector('[data-sbe-status]');
buildRangeControls();
buildThemeControls();
bindInterfaceEvents();
syncInterface();
}
function buildRangeControls() {
var container = panel.querySelector('[data-sbe-ranges]');
if (!container) { return; }
container.textContent = '';
RANGE_DEFINITIONS.forEach(function (definition) {
var row = document.createElement('label');
row.className = 'sbe-range';
var head = document.createElement('span');
head.className = 'sbe-range-head';
var label = document.createElement('span');
label.textContent = definition.label;
var output = document.createElement('output');
output.setAttribute('data-sbe-range-output', definition.key);
head.appendChild(label);
head.appendChild(output);
var input = document.createElement('input');
input.type = 'range';
input.min = String(definition.min);
input.max = String(definition.max);
input.step = String(definition.step);
input.setAttribute('data-sbe-range', definition.key);
row.appendChild(head);
row.appendChild(input);
container.appendChild(row);
});
}
function buildThemeControls() {
var select = panel.querySelector('[data-sbe-theme]');
if (!select) { return; }
select.textContent = '';
Object.keys(THEMES).forEach(function (key) {
var option = document.createElement('option');
option.value = key;
option.textContent = THEMES[key].label;
select.appendChild(option);
});
}
function bindInterfaceEvents() {
toggleButton.addEventListener('click', function () { setPanelOpen(panel.hidden); });
panel.addEventListener('click', function (event) {
if (event.target.closest('[data-sbe-close], [data-sbe-done]')) {
setPanelOpen(false);
return;
}
if (event.target.closest('[data-sbe-reset]')) {
settings = normalizeSettings(null);
applySettings();
syncInterface();
persistSettings();
}
});
panel.addEventListener('input', function (event) {
var target = event.target;
var rangeKey = target.getAttribute('data-sbe-range');
if (rangeKey) {
settings[rangeKey] = Number(target.value);
/* 拖动滑块只更新 CSS 变量,不触发列表、图片、资料卡等全页扫描。 */
applySettings({ deferDynamic: true });
syncRangeOutput(rangeKey);
scheduleSave();
return;
}
if (target.matches('[data-sbe-title-filters], [data-sbe-user-filters]')) {
window.clearTimeout(filterTimer);
filterTimer = window.setTimeout(function () {
settings.titleFilters = normalizeFilterList(panel.querySelector('[data-sbe-title-filters]').value.split(/\n/));
settings.userFilters = normalizeFilterList(panel.querySelector('[data-sbe-user-filters]').value.split(/\n/));
applyFilters();
scheduleSave();
}, SAVE_DELAY);
}
});
panel.addEventListener('change', function (event) {
var target = event.target;
var boolMap = [
['[data-sbe-new-window]', 'newWindowForTopics'], ['[data-sbe-sticky-forums]', 'stickyForumList'], ['[data-sbe-avatar-card]', 'avatarProfileCard'],
['[data-sbe-block-native-card]', 'blockNativeProfileCard'], ['[data-sbe-block-reply-tree]', 'blockNativeReplyTree'], ['[data-sbe-lightbox]', 'imageLightbox'], ['[data-sbe-reading-progress]', 'readingProgress'], ['[data-sbe-code-copy]', 'codeCopyButtons'], ['[data-sbe-auto-checkin]', 'autoCheckin']
];
for (var index = 0; index < boolMap.length; index += 1) {
if (target.matches(boolMap[index][0])) {
settings[boolMap[index][1]] = target.checked;
applySettings();
scheduleSave();
return;
}
}
if (target.matches('[data-sbe-theme]')) {
settings.theme = target.value;
applySettings({ deferDynamic: true });
scheduleSave();
return;
}
if (target.matches('[data-sbe-density]')) {
settings.density = target.value;
applySettings({ deferDynamic: true });
scheduleSave();
}
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && panel && !panel.hidden) {
setPanelOpen(false);
}
});
}
function syncInterface() {
if (!panel) { return; }
RANGE_DEFINITIONS.forEach(function (definition) {
var input = panel.querySelector('[data-sbe-range="' + definition.key + '"]');
if (input) { input.value = String(settings[definition.key]); }
syncRangeOutput(definition.key);
});
var setChecked = function (selector, value) {
var element = panel.querySelector(selector);
if (element) { element.checked = value; }
};
panel.querySelector('[data-sbe-theme]').value = settings.theme;
panel.querySelector('[data-sbe-density]').value = settings.density;
setChecked('[data-sbe-new-window]', settings.newWindowForTopics);
setChecked('[data-sbe-sticky-forums]', settings.stickyForumList);
setChecked('[data-sbe-avatar-card]', settings.avatarProfileCard);
setChecked('[data-sbe-block-native-card]', settings.blockNativeProfileCard);
setChecked('[data-sbe-block-reply-tree]', settings.blockNativeReplyTree);
setChecked('[data-sbe-lightbox]', settings.imageLightbox);
setChecked('[data-sbe-reading-progress]', settings.readingProgress);
setChecked('[data-sbe-code-copy]', settings.codeCopyButtons);
setChecked('[data-sbe-auto-checkin]', settings.autoCheckin);
panel.querySelector('[data-sbe-title-filters]').value = settings.titleFilters.join('\n');
panel.querySelector('[data-sbe-user-filters]').value = settings.userFilters.join('\n');
}
function syncRangeOutput(key) {
if (!panel) { return; }
var definition = RANGE_DEFINITIONS.find(function (item) { return item.key === key; });
var output = panel.querySelector('[data-sbe-range-output="' + key + '"]');
if (definition && output) { output.textContent = settings[key] + definition.unit; }
}
function setPanelOpen(open) {
ensureInterface();
if (!panel || !toggleButton) { return; }
panel.hidden = !open;
toggleButton.setAttribute('aria-expanded', String(Boolean(open)));
if (open) { syncInterface(); }
}
function showStatus(message) {
if (!statusElement) { return; }
statusElement.textContent = message;
window.setTimeout(function () {
if (statusElement && statusElement.textContent === message) { statusElement.textContent = ''; }
}, 1800);
}
})();