- SignalDesk2小时前
Original Summary
Discourse 在用户资料页开启了实验性的侧边导航,横向 tab 被替换为侧栏,移动端则把面板进放进页面顶部的汉堡菜单。新导航模式在移动端不够直观,操作步骤变多,体验极差。 Discourse Meta – 24 Sep 26 Experimental user sidebar navigation News and Events Announcements We use contextual navigation in various parts of Discourse now (admin, documentation categories, chat, AI conversations)… one place with a lot of navigation we haven’t applied this yet is user profiles. This is an area where we have quite a few... 阅读时间: 1 分钟 🕑 赞: 28 ❤ 这个脚本可以恢复用户资料页原本的 横向 tab 导航 ,也可以通过油猴 GM 菜单切换为 侧栏模式 。 // ==UserScript== // @name Discourse User Nav Restore // @name:zh-CN Discourse 用户资料页导航恢复 // @namespace discourse-user-nav-restore // @version 0.1.0 // @description Restore horizontal tab navigation on Discourse user profile pages (override the experimental sidebar_user_navigation from discourse/discourse#44050), or force the sidebar mode - switchable from the userscript menu. // @description:zh-CN 恢复 Discourse 用户资料页原本的横向 tab 导航(客户端覆盖 PR #44050 引入的实验性 sidebar_user_navigation 设置),也可强制侧栏模式;在脚本菜单中切换。 // @author Cyan // @match https://linux.do/* // @run-at document-start // @noframes // @license MIT // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant unsafeWindow // ==/UserScript== (function () { "use strict"; const SETTING_KEY = "sidebar_user_navigation"; const PRELOADED_ID = "data-preloaded"; const OVERRIDE_KEYS = ["upcomingChanges", "themeSiteSettingOverrides"]; const MODE_KEY = "nav_mode"; const LOG = "[Discourse User Nav Restore]"; const SERVICE_NAMES = [ "service:site-settings", "service:siteSettings", "service:site-settings-service", "site-settings:main", ]; let sawDiscourse = false; let menuId = null; const pageWindow = () => typeof unsafeWindow !== "undefined" ? unsafeWindow : window; function getMode() { return GM_getValue(MODE_KEY, "tab") === "sidebar" ? "sidebar" : "tab"; } function safeParse(text) { try { return JSON.parse(text); } catch { return null; } } /** 纯函数:改写预加载 map(raw 是外层 JSON.parse 的结果,各 value 为 JSON 字符串)。 返回是否有改动。 */ function patchPreloadedData(raw, desired) { let changed = false; if (raw && typeof raw.siteSettings === "string") { const settings = safeParse(raw.siteSettings); if (settings && typeof settings === "object") { if (settings[SETTING_KEY] !== desired) { settings[SETTING_KEY] = desired; raw.siteSettings = JSON.stringify(settings); changed = true; } } } // 这两个块的同名值会在 site-settings 服务初始化时覆盖 siteSettings,直接删除 for (const key of OVERRIDE_KEYS) { if (typeof raw?.[key] !== "string") continue; const obj = safeParse(raw[key]); if (obj && typeof obj === "object" && SETTING_KEY in obj) { delete obj[SETTING_KEY]; raw[key] = JSON.stringify(obj); changed = true; } } return changed; } function waitFor(check, timeoutMs, intervalMs = 10) { return new Promise((resolve) => { const deadline = Date.now() + timeoutMs; const tick = () => { let value = null; try { value = check(); } catch { value = null; } if (value) return resolve(value); if (Date.now() >= deadline) return resolve(null); setTimeout(tick, intervalMs); }; tick(); }); } /** 在应用 JS 执行前改写预加载数据。返回 "applied" | "noop" | "failed"。 */ async function applyPreBootPatch(desired) { const el = await waitFor( () => document.getElementById(PRELOADED_ID), 15000, 8 ); if (!el || !el.textContent) return "failed"; sawDiscourse = true; try { const raw = JSON.parse(el.textContent); if (!raw || typeof raw !== "object") return "failed"; const settings = safeParse(raw.siteSettings); const overridden = OVERRIDE_KEYS.some((key) => { const obj = safeParse(raw[key]); return obj && typeof obj === "object" && SETTING_KEY in obj; }); // 站点本来就没开、目标又是关闭 -> 不碰页面 if ( desired === false && !overridden && !(settings && settings[SETTING_KEY] === true) ) { return "noop"; } if (patchPreloadedData(raw, desired)) { el.textContent = JSON.stringify(raw); console.debug(LOG, "pre-boot patch applied:", SETTING_KEY, "->", desired); return "applied"; } return "failed"; } catch (err) { console.warn(LOG, "pre-boot patch failed:", err); return "failed"; } } /** 通过应用容器直接修改 site-settings 服务;成功返回 true。 / function setPageSetting(desired) { const w = pageWindow(); const container = w.Discourse && w.Discourse.__container__; if (!container) return false; for (const name of SERVICE_NAMES) { let svc; try { svc = container.lookup(name); } catch { continue; } // available_locales 是服务初始化时必然存在的客户端设置,用来确认身份 if (!svc || !("available_locales" in svc)) continue; try { if (svc[SETTING_KEY] !== desired) svc[SETTING_KEY] = desired; return svc[SETTING_KEY] === desired; } catch { try { svc.set(SETTING_KEY, desired); return true; } catch { continue; } } } return false; } async function applyPostBootFix(desired) { const found = await waitFor(() => { const w = pageWindow(); return w.Discourse && w.Discourse.__container__ ? true : null; }, 30000, 25); if (!found) return false; sawDiscourse = true; const ok = setPageSetting(desired); if (ok) { console.debug(LOG, "post-boot fix applied:", SETTING_KEY, "->", desired); } else { console.warn(LOG, "site-settings service not found; fallback unavailable"); } return ok; } function registerMenu() { if (typeof GM_registerMenuCommand !== "function") return; if (menuId !== null) { if (typeof GM_unregisterMenuCommand !== "function") return; try { GM_unregisterMenuCommand(menuId); } catch { / ignore */ } menuId = null; } const mode = getMode(); menuId = GM_registerMenuCommand( mode === "tab" ? "Discourse 用户导航: 横向 Tab(点击切换为 Sidebar 侧栏)" : "Discourse 用户导航: Sidebar 侧栏(点击切换为横向 Tab)", () => { const next = getMode() === "tab" ? "sidebar" : "tab"; GM_setValue(MODE_KEY, next); registerMenu(); // 优先实时翻转(Ember 响应式,免刷新);失败时刷新走启动前补丁路径; // 非 Discourse 页面只保存偏好,下次访问生效。 if (!setPageSetting(next === "sidebar")) { if (sawDiscourse) location.reload(); } console.debug(LOG, "mode ->", next); } ); } async function main() { registerMenu(); const desired = getMode() === "sidebar"; const out
- 情报分类:技术学习与提效
- 分类依据:内容涉及技术、AI、软件工具或工程实践
- 信息来源:服务器 / LINUX DO - 最新话题
- 发布时间:2026/9/27 08:55:10
- 暂无回复