[分享] 强迫症的底层重构:Linux.do 全方位净化与智能导航

脚本简介 这是一个专为 Linux.do 设计的用户脚本,旨在提供干净、极简、功能增强的浏览体验。它通过隐藏干扰元素、折叠置顶话题以及添加便捷导航面板,让你专注于内容阅读,而不被广告、横幅和公告打扰。 主要功能 界面净化 自动隐藏论坛顶部的搜索横幅。 移除全局公告栏和欢迎横幅。 折叠置顶话题,减少列...
[分享] 强迫症的底层重构:Linux.do 全方位净化与智能导航
[分享] 强迫症的底层重构:Linux.do 全方位净化与智能导航

脚本简介

这是一个专为 Linux.do 设计的用户脚本,旨在提供干净、极简、功能增强的浏览体验。它通过隐藏干扰元素、折叠置顶话题以及添加便捷导航面板,让你专注于内容阅读,而不被广告、横幅和公告打扰。

主要功能

  1. 界面净化

    • 自动隐藏论坛顶部的搜索横幅。

    • 移除全局公告栏和欢迎横幅。

    • 折叠置顶话题,减少列表占用空间,同时可一键展开查看。

  2. 智能置顶话题管理

    • 显示折叠/展开置顶话题的按钮,并记录用户偏好。

    • 支持动态刷新,自动适应新加载的置顶话题。

    • 可通过按钮快速切换显示状态。

  3. iOS 风格导航面板

    • 固定在页面右下角的毛玻璃浮动面板。

    • 面板包含:

      • 回到顶部:滚动到页面顶部并记录当前位置。

      • 返回原位 / 页尾:快速回到之前滚动的位置或页面底部。

    • 支持浅色/深色模式自适应,具备悬停、点击动画效果。

  4. 眼睛保护平滑滚动

    • 滚动过程中自动添加遮罩,减少闪烁和突变。

    • 提供平滑过渡动画,保护视觉体验。

  5. CloudFlare Challenge 自动跳转

    • 当访问受 CloudFlare 保护的页面时,检测错误提示并自动重定向至 Challenge 页面。

    • 提供菜单命令手动触发跳转,确保论坛访问不中断。

  6. 实时监控与自动刷新

    • 使用 MutationObserver 实时监控页面变化。

    • 当页面元素变动或新内容加载时,自动重新渲染导航面板和置顶话题。

使用方法

  1. 安装用户脚本管理器(如 Tampermonkey、Violentmonkey)。

  2. 新建脚本,将代码粘贴进去。

  3. 保存并访问 https://linux.do/ 即可生效。

  4. 可通过右键扩展菜单或脚本菜单手动触发 CloudFlare Challenge 跳转。
    // ==UserScript==
    // @name Linux.do - 论坛全方位净化导航
    // @namespace https://linux.do/
    // @version 1.0
    // @description 隐藏搜索横幅 + 移除全局公告栏 + 折叠置顶话题
    // @author
    // @match https://linux.do/*
    // @grant GM_addStyle
    // @grant GM_registerMenuCommand
    // @run-at document-start
    // @license MIT
    // ==/UserScript==

GM_addStyle(`
:root {
–ios-panel-width: 44px;
–ios-panel-radius: 24px;
–ios-btn-width: 36px;
–ios-btn-height: 36px;
–ios-blur: blur(35px) saturate(220%);
–ios-border: 1px solid rgba(255, 255, 255, 0.45);
}

#main-outlet > div[class*=“welcome-banner”],
#main-outlet > div.–location-above-topic-content,
div.custom-search-banner-wrap.welcome-banner__wrap,
div[data-component=“welcome-banner”],
div.global-notice {
display: none !important;
}

.topic-list-item.pinned, tr.pinned, .latest-topic-list-item.pinned {
background-color: var(–secondary, #ffffff) !important;
}
.dark-mode .topic-list-item.pinned, .dark-mode tr.pinned, .dark-mode .latest-topic-list-item.pinned {
background-color: var(–secondary, #111111) !important;
}
.topic-list-body tr.pinned, .topic-list tbody tr.pinned, table.topic-list tbody tr.pinned {
display: none !important;
}
html.ld-pinned-expanded .topic-list-body tr.pinned,
html.ld-pinned-expanded .topic-list tbody tr.pinned,
html.ld-pinned-expanded table.topic-list tbody tr.pinned {
display: table-row !important;
}
tr.linux-do-pinned-toggle-row td {
padding: 12px; border-bottom: 1px solid var(–primary-low, #e9ecef); background: var(–secondary, #fff);
}
.linux-do-pinned-toggle-button {
padding: 0; border: 0; background: transparent; color: var(–tertiary, #0ea5e9); font-size: 14px; font-weight: 600; cursor: pointer;
}
.linux-do-pinned-toggle-button:hover { text-decoration: underline; }

#ld-nav-panel {
position: fixed; right: 20px; bottom: 140px; z-index: 99999; width: var(–ios-panel-width); padding: 8px 0;
display: flex; flex-direction: column; align-items: center; gap: 8px;
border: var(–ios-border); border-radius: var(–ios-panel-radius); background: rgba(255, 255, 255, 0.42);
-webkit-backdrop-filter: var(–ios-blur); backdrop-filter: var(–ios-blur);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.08);
}
#ld-nav-panel.ld-nav-hidden {
display: none !important;
}

.ld-nav-btn {
width: var(–ios-btn-width); height: var(–ios-btn-height); border: none; border-radius: 50%; background: transparent; cursor: pointer;
display: flex; align-items: center; justify-content: center; color: rgba(0, 0, 0, 0.7);
transition: background 0.1s ease, color 0.1s ease;
}
.ld-nav-btn:hover { color: #000000; background: rgba(0, 0, 0, 0.06); }
.ld-nav-btn:active { background: rgba(0, 0, 0, 0.12); }

.dark-mode #ld-nav-panel { background: rgba(28, 28, 30, 0.45); border: 1px solid rgba(255, 255, 255, 0.15); box-shadow: 0 12px 36px rgba(0, 0, 0, 0.4); }
.dark-mode .ld-nav-btn { color: rgba(255, 255, 255, 0.8); }
.dark-mode .ld-nav-btn:hover { color: #ffffff; background: rgba(255, 255, 255, 0.12); }
.dark-mode .ld-nav-btn:active { background: rgba(255, 255, 255, 0.18); }

html.ld-scroll-locked { overflow: hidden !important; }
#ld-eye-protection-mask {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 999999; opacity: 0; pointer-events: none;
transform: translate3d(0, 0, 0); background: radial-gradient(circle at center, rgba(255, 255, 255, 0.35) 0%, rgba(232, 241, 255, 0.65) 100%);
-webkit-backdrop-filter: blur(0px) saturate(100%); backdrop-filter: blur(0px) saturate(100%);
will-change: opacity, backdrop-filter, -webkit-backdrop-filter;
transition: opacity 0.2s ease, backdrop-filter 0.2s ease, -webkit-backdrop-filter 0.2s ease;
}
.dark-mode #ld-eye-protection-mask { background: radial-gradient(circle at center, rgba(14, 14, 17, 0.6) 0%, rgba(24, 25, 33, 0.8) 100%); }
#ld-eye-protection-mask.mask-entering { opacity: 1; pointer-events: auto; -webkit-backdrop-filter: blur(55px) saturate(240%); backdrop-filter: blur(55px) saturate(240%); }
#ld-eye-protection-mask.mask-leaving { opacity: 0; pointer-events: none; -webkit-backdrop-filter: blur(0px) saturate(100%); backdrop-filter: blur(0px) saturate(100%); transition: opacity 0.22s ease, backdrop-filter 0.22s ease, -webkit-backdrop-filter 0.22s ease; }
html.ld-scroll-locked .d-header, html.ld-scroll-locked .d-header * { transition: none !important; animation: none !important; opacity: 0 !important; visibility: hidden !important; pointer-events: none !important; }
`);

(() => {
‘use strict’;

var CF_CONFIG = {
ERROR_TEXTS: [‘403 error’, ‘该回应是很久以前创建的’, ‘reaction was created too long ago’, ‘我们无法加载该话题’, ‘You are not allowed to react’],
DIALOG_SELECTOR: ‘.dialog-body’,
CHALLENGE_PATH: ‘/challenge’,
MENU_TEXT: ‘手动触发 Challenge 跳转’,
NOT_FOUND_REDIRECT_GUARD_KEY: ‘linux_do_auto_challenge_nf_guard’
};

if (globalThis.location.pathname.startsWith(CF_CONFIG.CHALLENGE_PATH)) {
const isNotFoundPage = () => Boolean(document.querySelector(‘.page-not-found’));
const getRedirectParamUrl = () => {
try {
const sp = new URLSearchParams(globalThis.location.search);
const raw = sp.get(‘redirect’);
if (!raw) return void 0;
const url = new URL(raw, globalThis.location.origin);
return url.origin === globalThis.location.origin ? url.href : void 0;
} catch (e) { return void 0; }
};
const redirectFromNotFoundPage = () => {
const fallback = ‘’.concat(globalThis.location.origin, ‘/’);
const target = getRedirectParamUrl() || fallback;
const now = Date.now();
let guardTs = 0;
try { const raw = sessionStorage.getItem(CF_CONFIG.NOT_FOUND_REDIRECT_GUARD_KEY); guardTs = raw ? Number(raw) : 0; } catch (e) {}
if (guardTs && now - guardTs < 5e3) return;
try { sessionStorage.setItem(CF_CONFIG.NOT_FOUND_REDIRECT_GUARD_KEY, String(now)); } catch (e) {}
if (target === globalThis.location.href) { globalThis.location.replace(fallback); } else { globalThis.location.replace(target); }
};
const runChallengeGuard = () => { if (isNotFoundPage()) redirectFromNotFoundPage(); };
if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, runChallengeGuard); } else { runChallengeGuard(); }
return;
}

const SVG_ICONS = {
top: ``,
bottom: ``
};

const STORAGE_KEY = ‘linux-do-collapse-pinned-topics’;
const TOGGLE_ROW_CLASS = ‘linux-do-pinned-toggle-row’;
const TOGGLE_BUTTON_CLASS = ‘linux-do-pinned-toggle-button’;

let engineTimer = null, globalObserver = null;
let lastRecordedY = null;

function syncExpandedClass() {
document.documentElement.classList.toggle(‘ld-pinned-expanded’, window.localStorage.getItem(STORAGE_KEY) === ‘expanded’);
}
syncExpandedClass();

function redirectToChallenge() {
try { globalThis.location.href = ‘’.concat(CF_CONFIG.CHALLENGE_PATH, ‘?redirect=’).concat(encodeURIComponent(globalThis.location.href)); } catch (error) {}
}

function checkAndRedirect() {
try {
const dialogElement = document.querySelector(CF_CONFIG.DIALOG_SELECTOR);
if (!dialogElement) return false;
const text = dialogElement.textContent || ‘’;
if (CF_CONFIG.ERROR_TEXTS.some((errorText) => text.includes(errorText))) {
if (globalObserver) globalObserver.disconnect();
redirectToChallenge();
return true;
}
} catch (error) {}
return false;
}

function smoothScrollWithMask(targetY) {
if (!document.body || targetY === null || targetY === undefined) return;
document.documentElement.classList.add(‘ld-scroll-locked’);
let mask = document.getElementById(‘ld-eye-protection-mask’);
if (!mask) {
mask = document.createElement(‘div’);
mask.id = ‘ld-eye-protection-mask’;
document.body.append(mask);
}
mask.classList.remove(‘mask-leaving’);
mask.classList.add(‘mask-entering’);

if (mask.safetyTimer) clearTimeout(mask.safetyTimer);
const forceCleanUp = () => {
  document.documentElement.classList.remove('ld-scroll-locked');
  if (mask && mask.parentNode) mask.remove();
};

setTimeout(() => {
  window.requestAnimationFrame(() => {
    window.scrollTo({ top: targetY, behavior: 'auto' }); 
    window.requestAnimationFrame(() => {
      window.requestAnimationFrame(() => {
        mask.classList.remove('mask-entering');
        mask.classList.add('mask-leaving');
        document.documentElement.classList.remove('ld-scroll-locked');
        const onTransitionEnd = (e) => {
          if (e.propertyName === 'opacity' && mask.classList.contains('mask-leaving')) {
            forceCleanUp();
            mask.removeEventListener('transitionend', onTransitionEnd);
          }
        };
        mask.addEventListener('transitionend', onTransitionEnd);
        mask.safetyTimer = setTimeout(forceCleanUp, 350); 
      });
    });
  });
}, 240); 

}

function renderPinnedTopics() {
const tableBody = document.querySelector(‘#list-area tbody.topic-list-body, #list-area .topic-list tbody, table.topic-list tbody, tbody.topic-list-body’);
if (!tableBody) {
const activeRow = document.querySelector(`tr.${TOGGLE_ROW_CLASS}`);
if (activeRow) activeRow.remove();
return;
}
const pinnedRows = [];
for (const row of Array.from(tableBody.children)) {
if (row.tagName !== ‘TR’ || row.classList.contains(TOGGLE_ROW_CLASS)) continue;
if (!row.classList.contains(‘pinned’)) break;
pinnedRows.push(row);
}
if (pinnedRows.length === 0) {
const activeRow = document.querySelector(`tr.${TOGGLE_ROW_CLASS}`);
if (activeRow) activeRow.remove();
return;
}
const firstRow = tableBody.querySelector(‘tr:not(.linux-do-pinned-toggle-row)’);
const colCount = firstRow ? firstRow.cells.length : 5;

let toggleRow = tableBody.querySelector(\`:scope > tr.${TOGGLE_ROW_CLASS}\`);
if (!toggleRow) {
  toggleRow = document.createElement('tr');
  toggleRow.className = TOGGLE_ROW_CLASS;
  toggleRow.innerHTML = \`<td colspan="${colCount}"><button type="button" class="${TOGGLE_BUTTON_CLASS}"></button></td>\`;
  tableBody.insertBefore(toggleRow, pinnedRows\[0\]);
} else {
  const td = toggleRow.querySelector('td');
  if (td && td.getAttribute('colspan') !== String(colCount)) td.setAttribute('colspan', colCount);
}
const button = toggleRow.querySelector(\`button.${TOGGLE_BUTTON_CLASS}\`);
if (button) {
  button.textContent = \`${window.localStorage.getItem(STORAGE_KEY) !== 'expanded' ? '显示' : '折叠'}置顶话题 (${pinnedRows.length})\`;
}

}

function processNavPanel() {
if (!document.body) return;
let panel = document.getElementById(‘ld-nav-panel’);
if (!panel) {
panel = document.createElement(‘div’);
panel.id = ‘ld-nav-panel’;
panel.className = ‘ld-nav-hidden’;

  const buttons = \[
    { action: 'top',    title: '回到顶部(记录原坐标)', svg: SVG_ICONS.top },
    { action: 'bottom', title: '返回原位 / 未读分割线 / 去页尾', svg: SVG_ICONS.bottom }
  \];

  for (const b of buttons) {
    const btn = document.createElement('button');
    btn.className = 'ld-nav-btn';
    btn.setAttribute('data-action', b.action);
    btn.title = b.title;
    btn.setAttribute('aria-label', b.title);
    btn.innerHTML = b.svg; 
    panel.append(btn);
  }
  document.body.append(panel);
}
panel.classList.toggle('ld-nav-hidden', !document.querySelector('#topic-title, .topic-navigation, .posts-section'));

}

function handleBottomClick() {
if (lastRecordedY !== null) {
smoothScrollWithMask(lastRecordedY);
lastRecordedY = null;
} else {
const unreadBar = document.querySelector(‘.unread-highlighter, .topic-avatar.unread’);
if (unreadBar) {
smoothScrollWithMask(unreadBar.getBoundingClientRect().top + window.scrollY - 100);
} else {
smoothScrollWithMask(document.documentElement.scrollHeight);
}
}
}

function executeEngine() {
const notice = document.querySelector(‘div.global-notice’);
if (notice) notice.remove();
processNavPanel();
renderPinnedTopics();
}

document.addEventListener(‘click’, (e) => {
const btn = e.target.closest(‘.ld-nav-btn’);
if (btn) {
const action = btn.getAttribute(‘data-action’);
if (action === ‘top’) {
lastRecordedY = window.scrollY;
smoothScrollWithMask(0);
} else if (action === ‘bottom’) {
handleBottomClick();
}
return;
}

if (e.target.classList.contains(TOGGLE_BUTTON_CLASS)) {
  window.localStorage.setItem(STORAGE_KEY, window.localStorage.getItem(STORAGE_KEY) !== 'expanded' ? 'expanded' : 'collapsed');
  syncExpandedClass();
  executeEngine();
}

});

(() => {
try {
globalObserver = new MutationObserver((mutations) => {
let hasValidChange = false;
let hasNewNodes = false;

    for (const m of mutations) {
      if (m.target.id === 'ld-eye-protection-mask' || m.target.id === 'ld-nav-panel' || m.target.className === TOGGLE_ROW_CLASS) continue;
      hasValidChange = true;
      if (m.addedNodes.length > 0) hasNewNodes = true;
    }
    
    if (!hasValidChange) return;
    if (hasNewNodes && checkAndRedirect()) return;

    if (engineTimer) clearTimeout(engineTimer);
    engineTimer = setTimeout(executeEngine, 40);
  });
  globalObserver.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
} catch (error) {}

try { GM_registerMenuCommand(CF_CONFIG.MENU_TEXT, redirectToChallenge); } catch (error) {}

executeEngine();
checkAndRedirect();

})();
})();

1 个帖子 - 1 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文