您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View posts in tree view
// ==UserScript== // @name CC98 Tools - TreeView // @version 0.0.1 // @description View posts in tree view // @icon https://www.cc98.org/static/98icon.ico // @author ml98 // @namespace https://www.cc98.org/user/name/ml98 // @license MIT // @match https://www.cc98.org/* // @match http://www-cc98-org-s.webvpn.zju.edu.cn:8001/* // @match https://www-cc98-org-s.webvpn.zju.edu.cn:8001/* // @run-at document-start // @grant none // ==/UserScript== const _all_posts = []; const resolve = (url, data) => { if (/\/Topic\/\d+\/post/.test(url)) { const topic_id = url.match(/\/Topic\/(\d+)/)[1]; (_all_posts[topic_id] ??= []).push(...data); const map = new Map(_all_posts[topic_id].map(i => [i.id, i])); _all_posts[topic_id] = [...map.values()]; data = _all_posts[topic_id].sort((i,j)=>i.id-j.id); } return data; }; const json = Response.prototype.json; Response.prototype.json = function () { return json.call(this).then((data) => resolve(this.url, data)); }; const style = document.createElement('style'); document.head.append(style); waitForElement(".center > .center", function(e) { const m = location.href.match(/\/topic\/(\d+)(?:\/(\d+)(?:#(\d+))?)?/); if(!m) { return; } const topic_id = m[1]; const floor = ((m[2] || 1) - 1) * 10 + (m[3] || 1) % 10; const posts = _all_posts[topic_id]; const children = {}, parent = {}, level = {}, order = {}; posts.forEach(i => { parent[i.id] = i.parentId }); posts.forEach(i => { const p = parent[i.parentId] >= 0 ? i.parentId : 0; (children[p] ??= []).push(i.id); }); let index = 0; (function traverse(id, depth) { level[id] = depth; order[id] = index++; children[id]?.forEach(id => traverse(id, depth+1)); })(0, -1); style.textContent = ` .center > .center { ${posts.map(i => ` --indent-${i.id}: calc(${level[i.id]} * var(--indent)); --order-${i.id}: ${order[i.id]}; `).join('')} --indent: 4em; } .center > .center > .reply article > div[style^="background-color: rgb(245, 250, 255)"]:first-child { /* display: none; */ }`; setTimeout(() => { [...e.children].find(i => React(i.querySelector('.reply-content')).props.floor >= floor) ?.scrollIntoView({block: 'nearest', behavior: 'smooth'}); }, 1000); }); waitForElement(".center > .center > .reply", function(e) { const id = React(e.querySelector('.reply-content')).props.postId; e.style = ` margin-left: var(--indent-${id}, 0); width: calc(100% - var(--indent-${id}, 0)) !important; order: var(--order-${id}); `; }); /* helper functions */ function React(dom) { const key = Object.keys(dom).find((key) => key.startsWith("__reactInternalInstance$")); const instance = dom[key]; return ( instance?._debugOwner?.stateNode || instance?.return?.stateNode || instance?._currentElement?._owner?._instance || null ); } function waitForElement(selector, callback, startNode = document) { const uid = "_" + Math.random().toString().slice(2); selector = `:is(${selector}):not([${uid}])`; const observer = new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType == 1) { if (node.matches(selector)) { node.setAttribute(uid, ""); callback(node); } node.querySelectorAll(selector).forEach(child => { child.setAttribute(uid, ""); callback(child); }); } } } }); observer.observe(startNode, { attributes: true, childList: true, subtree: true, }); }