From 74615bf62a63a19028f51e02cb3ccc5876c4c4f3 Mon Sep 17 00:00:00 2001 From: ImXiangYu <2670833123@qq.com> Date: Sat, 14 Mar 2026 10:14:11 +0800 Subject: [PATCH 1/6] feat: Adapt to dark mode --- better-github-nav.user.js | 385 +++++++++++++++++++++++++++++++++----- package-lock.json | 4 +- package.json | 2 +- src/config.js | 16 +- src/constants.js | 9 + src/main.js | 3 + src/navigation.js | 64 ++++++- src/settings-panel.js | 26 ++- src/styles.js | 229 +++++++++++++++++++---- src/theme.js | 75 ++++++++ 10 files changed, 712 insertions(+), 101 deletions(-) create mode 100644 src/theme.js diff --git a/better-github-nav.user.js b/better-github-nav.user.js index 6d498a2..4c410a3 100644 --- a/better-github-nav.user.js +++ b/better-github-nav.user.js @@ -2,7 +2,7 @@ // @name Better GitHub Navigation // @name:zh-CN 更好的 GitHub 导航栏 // @namespace https://github.com/ImXiangYu/better-github-nav -// @version 0.1.45 +// @version 0.1.49 // @description Bring Dashboard, Trending, Explore, Collections, and Stars closer on desktop and narrow screens, and keep your most-used repositories pinned where they are easiest to reach. // @description:zh-CN 在桌面端和窄屏场景下,把 Dashboard、Trending、Explore、Collections、Stars 放到更顺手的位置,并把你最常用的仓库固定在最容易到达的地方。 // @author Ayubass @@ -16,15 +16,18 @@ (() => { // src/constants.js - var SCRIPT_VERSION = "0.1.45"; + var SCRIPT_VERSION = "0.1.49"; var CUSTOM_BUTTON_CLASS = "custom-gh-nav-btn"; var CUSTOM_BUTTON_ACTIVE_CLASS = "custom-gh-nav-btn-active"; var CUSTOM_BUTTON_COMPACT_CLASS = "custom-gh-nav-btn-compact"; var QUICK_LINK_MARK_ATTR = "data-better-gh-nav-quick-link"; + var QUICK_LINK_HOST_MARK_ATTR = "data-better-gh-nav-quick-link-host"; var RESPONSIVE_TOGGLE_MARK_ATTR = "data-better-gh-nav-overflow-toggle"; var CONFIG_STORAGE_KEY = "better-gh-nav-config-v1"; var TOP_REPOSITORIES_PIN_STORAGE_KEY = "better-gh-nav-top-repositories-pins-v1"; var UI_LANG_STORAGE_KEY = "better-gh-nav-ui-lang-v1"; + var THEME_ATTR = "data-better-github-nav-theme"; + var THEME_SOURCE_ATTR = "data-better-github-nav-theme-source"; var SETTINGS_OVERLAY_ID = "custom-gh-nav-settings-overlay"; var SETTINGS_PANEL_ID = "custom-gh-nav-settings-panel"; var SETTINGS_MESSAGE_ID = "custom-gh-nav-settings-message"; @@ -50,6 +53,9 @@ menuLangZh: "Better GitHub Nav: 界面语言 -> 中文", menuLangEn: "Better GitHub Nav: 界面语言 -> English", menuLangAuto: "Better GitHub Nav: 界面语言 -> 自动(跟随页面)", + menuThemeLight: "Better GitHub Nav: 主题 -> 亮色", + menuThemeDark: "Better GitHub Nav: 主题 -> 暗色", + menuThemeAuto: "Better GitHub Nav: 主题 -> 自动(跟随 GitHub)", resetConfirm: "确认重置快捷链接配置为默认值吗?", panelTitle: "Better GitHub Nav 设置", panelDesc: "勾选决定显示项,拖动整行(或右侧手柄)调整显示顺序。", @@ -72,6 +78,9 @@ menuLangZh: "Better GitHub Nav: UI Language -> 中文", menuLangEn: "Better GitHub Nav: UI Language -> English", menuLangAuto: "Better GitHub Nav: UI Language -> Auto (Follow Page)", + menuThemeLight: "Better GitHub Nav: Theme -> Light", + menuThemeDark: "Better GitHub Nav: Theme -> Dark", + menuThemeAuto: "Better GitHub Nav: Theme -> Auto (Follow GitHub)", resetConfirm: "Reset quick-link config to defaults?", panelTitle: "Better GitHub Nav Settings", panelDesc: "Select visible links and drag the row (or handle) to reorder.", @@ -91,6 +100,9 @@ }; // src/config.js + function sanitizeThemePreference(themePreference) { + return themePreference === "light" || themePreference === "dark" ? themePreference : "auto"; + } function sanitizeKeys(keys) { const validSet = new Set(DEFAULT_LINK_KEYS); const seen = /* @__PURE__ */ new Set(); @@ -111,9 +123,11 @@ ...orderKeysRaw, ...DEFAULT_LINK_KEYS.filter((key) => !orderSet.has(key)) ]; + const themePreference = sanitizeThemePreference(rawConfig?.themePreference); return { enabledKeys: enabledKeys.length ? enabledKeys : DEFAULT_LINK_KEYS.slice(), - orderKeys: orderKeys.length ? orderKeys : DEFAULT_LINK_KEYS.slice() + orderKeys: orderKeys.length ? orderKeys : DEFAULT_LINK_KEYS.slice(), + themePreference }; } function loadConfig() { @@ -128,6 +142,13 @@ function saveConfig(config) { localStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(sanitizeConfig(config))); } + function updateConfig(partialConfig) { + const currentConfig = loadConfig(); + saveConfig({ + ...currentConfig, + ...partialConfig + }); + } function getConfiguredLinks(username) { const config = loadConfig(); const presetMap = new Map( @@ -179,6 +200,136 @@ const style = document.createElement("style"); style.id = "custom-gh-nav-style"; style.textContent = ` + :root { + --bgn-color-scheme: light; + --bgn-fg-default: #1f2328; + --bgn-fg-muted: #656d76; + --bgn-fg-on-emphasis: #ffffff; + --bgn-border-default: #d1d9e0; + --bgn-border-muted: #d8dee4; + --bgn-surface-default: #ffffff; + --bgn-surface-subtle: #f6f8fa; + --bgn-surface-hover: rgba(177, 186, 196, 0.12); + --bgn-surface-active: rgba(177, 186, 196, 0.18); + --bgn-accent-fg: #0969da; + --bgn-accent-subtle: rgba(9, 105, 218, 0.08); + --bgn-btn-bg: #f6f8fa; + --bgn-btn-hover-bg: #f3f4f6; + --bgn-btn-primary-bg: #1f883d; + --bgn-btn-primary-hover-bg: #1a7f37; + --bgn-btn-primary-text: #ffffff; + --bgn-attention-fg: #9a6700; + --bgn-tooltip-bg: #1f2328; + --bgn-tooltip-kbd-bg: rgba(110, 118, 129, 0.4); + --bgn-overlay-backdrop: rgba(0, 0, 0, 0.45); + --bgn-shadow-medium: 0 8px 24px rgba(0, 0, 0, 0.2); + --bgn-shadow-large: 0 16px 32px rgba(0, 0, 0, 0.16); + --bgn-shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.25); + } + :root[${THEME_SOURCE_ATTR}="auto"][${THEME_ATTR}="light"] { + --bgn-color-scheme: light; + --bgn-fg-default: var(--color-fg-default, #1f2328); + --bgn-fg-muted: var(--color-fg-muted, #656d76); + --bgn-fg-on-emphasis: var(--color-fg-on-emphasis, #ffffff); + --bgn-border-default: var(--color-border-default, #d1d9e0); + --bgn-border-muted: var(--color-border-muted, #d8dee4); + --bgn-surface-default: var(--color-canvas-default, #ffffff); + --bgn-surface-subtle: var(--color-canvas-subtle, #f6f8fa); + --bgn-surface-hover: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); + --bgn-surface-active: var(--color-neutral-muted, rgba(177, 186, 196, 0.18)); + --bgn-accent-fg: var(--color-accent-fg, #0969da); + --bgn-accent-subtle: var(--color-accent-subtle, rgba(9, 105, 218, 0.08)); + --bgn-btn-bg: var(--color-btn-bg, #f6f8fa); + --bgn-btn-hover-bg: var(--color-btn-hover-bg, #f3f4f6); + --bgn-btn-primary-bg: var(--color-btn-primary-bg, #1f883d); + --bgn-btn-primary-hover-bg: var(--color-btn-primary-hover-bg, #1a7f37); + --bgn-btn-primary-text: var(--color-btn-primary-text, #ffffff); + --bgn-attention-fg: var(--color-attention-fg, #9a6700); + --bgn-tooltip-bg: var(--color-neutral-emphasis-plus, #1f2328); + --bgn-tooltip-kbd-bg: rgba(110, 118, 129, 0.4); + --bgn-overlay-backdrop: rgba(0, 0, 0, 0.45); + --bgn-shadow-medium: var(--color-shadow-medium, 0 8px 24px rgba(0, 0, 0, 0.2)); + --bgn-shadow-large: var(--color-shadow-large, 0 16px 32px rgba(0, 0, 0, 0.16)); + --bgn-shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.25); + } + :root[${THEME_SOURCE_ATTR}="auto"][${THEME_ATTR}="dark"] { + --bgn-color-scheme: dark; + --bgn-fg-default: var(--color-fg-default, #e6edf3); + --bgn-fg-muted: var(--color-fg-muted, #8b949e); + --bgn-fg-on-emphasis: var(--color-fg-on-emphasis, #ffffff); + --bgn-border-default: var(--color-border-default, #30363d); + --bgn-border-muted: var(--color-border-muted, #30363d); + --bgn-surface-default: var(--color-canvas-default, #0d1117); + --bgn-surface-subtle: var(--color-canvas-subtle, #161b22); + --bgn-surface-hover: var(--color-neutral-muted, rgba(110, 118, 129, 0.22)); + --bgn-surface-active: var(--color-neutral-muted, rgba(110, 118, 129, 0.32)); + --bgn-accent-fg: var(--color-accent-fg, #58a6ff); + --bgn-accent-subtle: var(--color-accent-subtle, rgba(56, 139, 253, 0.18)); + --bgn-btn-bg: var(--color-btn-bg, #212830); + --bgn-btn-hover-bg: var(--color-btn-hover-bg, #30363d); + --bgn-btn-primary-bg: var(--color-btn-primary-bg, #238636); + --bgn-btn-primary-hover-bg: var(--color-btn-primary-hover-bg, #2ea043); + --bgn-btn-primary-text: var(--color-btn-primary-text, #ffffff); + --bgn-attention-fg: var(--color-attention-fg, #d29922); + --bgn-tooltip-bg: var(--color-neutral-emphasis-plus, #21262d); + --bgn-tooltip-kbd-bg: rgba(139, 148, 158, 0.35); + --bgn-overlay-backdrop: rgba(1, 4, 9, 0.72); + --bgn-shadow-medium: var(--color-shadow-medium, 0 8px 24px rgba(1, 4, 9, 0.45)); + --bgn-shadow-large: var(--color-shadow-large, 0 16px 32px rgba(1, 4, 9, 0.5)); + --bgn-shadow-panel: 0 18px 42px rgba(1, 4, 9, 0.6); + } + :root[${THEME_SOURCE_ATTR}="custom"][${THEME_ATTR}="light"] { + --bgn-color-scheme: light; + --bgn-fg-default: #1f2328; + --bgn-fg-muted: #656d76; + --bgn-fg-on-emphasis: #ffffff; + --bgn-border-default: #d1d9e0; + --bgn-border-muted: #d8dee4; + --bgn-surface-default: #ffffff; + --bgn-surface-subtle: #f6f8fa; + --bgn-surface-hover: rgba(177, 186, 196, 0.12); + --bgn-surface-active: rgba(177, 186, 196, 0.18); + --bgn-accent-fg: #0969da; + --bgn-accent-subtle: rgba(9, 105, 218, 0.08); + --bgn-btn-bg: #f6f8fa; + --bgn-btn-hover-bg: #f3f4f6; + --bgn-btn-primary-bg: #1f883d; + --bgn-btn-primary-hover-bg: #1a7f37; + --bgn-btn-primary-text: #ffffff; + --bgn-attention-fg: #9a6700; + --bgn-tooltip-bg: #1f2328; + --bgn-tooltip-kbd-bg: rgba(110, 118, 129, 0.4); + --bgn-overlay-backdrop: rgba(0, 0, 0, 0.45); + --bgn-shadow-medium: 0 8px 24px rgba(0, 0, 0, 0.2); + --bgn-shadow-large: 0 16px 32px rgba(0, 0, 0, 0.16); + --bgn-shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.25); + } + :root[${THEME_SOURCE_ATTR}="custom"][${THEME_ATTR}="dark"] { + --bgn-color-scheme: dark; + --bgn-fg-default: #e6edf3; + --bgn-fg-muted: #8b949e; + --bgn-fg-on-emphasis: #ffffff; + --bgn-border-default: #30363d; + --bgn-border-muted: #30363d; + --bgn-surface-default: #0d1117; + --bgn-surface-subtle: #161b22; + --bgn-surface-hover: rgba(110, 118, 129, 0.22); + --bgn-surface-active: rgba(110, 118, 129, 0.32); + --bgn-accent-fg: #58a6ff; + --bgn-accent-subtle: rgba(56, 139, 253, 0.18); + --bgn-btn-bg: #212830; + --bgn-btn-hover-bg: #30363d; + --bgn-btn-primary-bg: #238636; + --bgn-btn-primary-hover-bg: #2ea043; + --bgn-btn-primary-text: #ffffff; + --bgn-attention-fg: #d29922; + --bgn-tooltip-bg: #21262d; + --bgn-tooltip-kbd-bg: rgba(139, 148, 158, 0.35); + --bgn-overlay-backdrop: rgba(1, 4, 9, 0.72); + --bgn-shadow-medium: 0 8px 24px rgba(1, 4, 9, 0.45); + --bgn-shadow-large: 0 16px 32px rgba(1, 4, 9, 0.5); + --bgn-shadow-panel: 0 18px 42px rgba(1, 4, 9, 0.6); + } a.${CUSTOM_BUTTON_CLASS} { border-radius: 6px; padding-inline: 8px; @@ -195,6 +346,15 @@ a.${CUSTOM_BUTTON_CLASS} * { cursor: pointer; } + header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::before, + header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::after, + header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::before, + header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::after, + header a[${QUICK_LINK_MARK_ATTR}="1"]::before, + header a[${QUICK_LINK_MARK_ATTR}="1"]::after { + content: none !important; + display: none !important; + } a.${CUSTOM_BUTTON_CLASS}:hover { background-color: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); text-decoration: none; @@ -217,10 +377,11 @@ min-width: 28px; min-height: 28px; padding: 0; - border: none; + border: 1px solid var(--bgn-border-default); border-radius: 6px; - background: transparent; - color: var(--color-fg-default, #1f2328); + background: var(--bgn-surface-subtle); + color: var(--bgn-fg-default); + color-scheme: var(--bgn-color-scheme); font: inherit; font-weight: 600; line-height: 1; @@ -228,10 +389,10 @@ } .custom-gh-nav-overflow-toggle:hover, .custom-gh-nav-overflow-toggle[aria-expanded="true"] { - background-color: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); + background-color: var(--bgn-surface-hover); } .custom-gh-nav-overflow-toggle:focus-visible { - outline: 2px solid var(--color-accent-fg, #0969da); + outline: 2px solid var(--bgn-accent-fg); outline-offset: 1px; } .custom-gh-nav-overflow-toggle-icon { @@ -252,10 +413,12 @@ min-width: 220px; max-width: min(280px, calc(100vw - 16px)); padding: 6px; - border: 1px solid var(--color-border-default, #d1d9e0); + border: 1px solid var(--bgn-border-default); border-radius: 12px; - background: var(--color-canvas-default, #fff); - box-shadow: var(--color-shadow-large, 0 16px 32px rgba(0, 0, 0, 0.16)); + background: var(--bgn-surface-default); + color: var(--bgn-fg-default); + color-scheme: var(--bgn-color-scheme); + box-shadow: var(--bgn-shadow-large); box-sizing: border-box; } .custom-gh-nav-overflow-menu[hidden] { @@ -269,18 +432,18 @@ min-height: 32px; padding: 6px 10px; border-radius: 8px; - color: var(--color-fg-default, #1f2328); + color: var(--bgn-fg-default); font-size: 13px; font-weight: 600; text-decoration: none; } .custom-gh-nav-overflow-link:hover { - background: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); + background: var(--bgn-surface-hover); text-decoration: none; } .custom-gh-nav-overflow-link[aria-current="page"] { - color: var(--color-accent-fg, #0969da); - background: var(--color-accent-subtle, rgba(9, 105, 218, 0.08)); + color: var(--bgn-accent-fg); + background: var(--bgn-accent-subtle); } .custom-gh-nav-overflow-link-text { min-width: 0; @@ -294,8 +457,8 @@ padding: 2px 6px; border: none !important; border-radius: 999px; - background: var(--color-neutral-muted, rgba(177, 186, 196, 0.18)) !important; - color: var(--color-fg-muted, #656d76); + background: var(--bgn-surface-active) !important; + color: var(--bgn-fg-muted); box-shadow: none !important; font: inherit; font-size: 11px; @@ -309,8 +472,8 @@ align-items: center; gap: 8px; max-width: min(320px, calc(100vw - 16px)); - background: var(--color-neutral-emphasis-plus, #1f2328); - color: var(--color-fg-on-emphasis, #ffffff); + background: var(--bgn-tooltip-bg); + color: var(--bgn-fg-on-emphasis); border-radius: 6px; padding: 4px 8px; font-size: 12px; @@ -319,8 +482,8 @@ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; pointer-events: none; box-sizing: border-box; - box-shadow: var(--color-shadow-medium, 0 8px 24px rgba(0,0,0,0.2)); - border: 1px solid var(--color-border-default, transparent); + box-shadow: var(--bgn-shadow-medium); + border: 1px solid var(--bgn-border-default); text-decoration: none; } .custom-gh-nav-tooltip[hidden] { @@ -357,7 +520,7 @@ vertical-align: middle; padding: 0 4px; border-radius: 4px; - background: rgba(110, 118, 129, 0.4); + background: var(--bgn-tooltip-kbd-bg); color: #ffffff; font-size: 11px; font-weight: 400; @@ -373,7 +536,7 @@ position: fixed; inset: 0; z-index: 2147483647; - background: rgba(0, 0, 0, 0.45); + background: var(--bgn-overlay-backdrop); display: flex; align-items: center; justify-content: center; @@ -384,11 +547,12 @@ width: min(560px, 100%); max-height: min(80vh, 720px); overflow: auto; - background: var(--color-canvas-default, #fff); - color: var(--color-fg-default, #1f2328); - border: 1px solid var(--color-border-default, #d1d9e0); + background: var(--bgn-surface-default); + color: var(--bgn-fg-default); + border: 1px solid var(--bgn-border-default); border-radius: 10px; - box-shadow: 0 16px 40px rgba(0, 0, 0, 0.25); + color-scheme: var(--bgn-color-scheme); + box-shadow: var(--bgn-shadow-panel); padding: 16px; box-sizing: border-box; } @@ -399,7 +563,7 @@ } .custom-gh-nav-settings-desc { margin: 0 0 12px; - color: var(--color-fg-muted, #656d76); + color: var(--bgn-fg-muted); font-size: 13px; } .custom-gh-nav-settings-list { @@ -412,10 +576,10 @@ align-items: center; justify-content: space-between; gap: 12px; - border: 1px solid var(--color-border-muted, #d8dee4); + border: 1px solid var(--bgn-border-muted); border-radius: 8px; padding: 8px 10px; - background: var(--color-canvas-subtle, #f6f8fa); + background: var(--bgn-surface-subtle); cursor: grab; } .custom-gh-nav-settings-row:active { @@ -437,9 +601,9 @@ gap: 6px; } .custom-gh-nav-settings-drag-handle { - border: 1px solid var(--color-border-default, #d1d9e0); - background: var(--color-btn-bg, #f6f8fa); - color: var(--color-fg-muted, #656d76); + border: 1px solid var(--bgn-border-default); + background: var(--bgn-btn-bg); + color: var(--bgn-fg-muted); border-radius: 6px; width: 32px; height: 26px; @@ -455,32 +619,32 @@ opacity: 0.55; } .custom-gh-nav-settings-row-drag-over { - border-color: var(--color-accent-fg, #0969da); - background: var(--color-accent-subtle, #ddf4ff); + border-color: var(--bgn-accent-fg); + background: var(--bgn-accent-subtle); } .custom-gh-nav-settings-btn { - border: 1px solid var(--color-border-default, #d1d9e0); - background: var(--color-btn-bg, #f6f8fa); - color: var(--color-fg-default, #1f2328); + border: 1px solid var(--bgn-border-default); + background: var(--bgn-btn-bg); + color: var(--bgn-fg-default); border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; } .custom-gh-nav-settings-btn:hover { - background: var(--color-btn-hover-bg, #f3f4f6); + background: var(--bgn-btn-hover-bg); } .custom-gh-nav-settings-btn:disabled { opacity: 0.45; cursor: not-allowed; } .custom-gh-nav-settings-btn-primary { - background: var(--color-btn-primary-bg, #1f883d); - border-color: var(--color-btn-primary-bg, #1f883d); - color: var(--color-btn-primary-text, #fff); + background: var(--bgn-btn-primary-bg); + border-color: var(--bgn-btn-primary-bg); + color: var(--bgn-btn-primary-text); } .custom-gh-nav-settings-btn-primary:hover { - background: var(--color-btn-primary-hover-bg, #1a7f37); + background: var(--bgn-btn-primary-hover-bg); } .custom-gh-nav-settings-footer { margin-top: 12px; @@ -491,7 +655,7 @@ .custom-gh-nav-settings-message { min-height: 20px; margin-top: 8px; - color: var(--color-attention-fg, #9a6700); + color: var(--bgn-attention-fg); font-size: 12px; } .custom-gh-top-repos-row { @@ -664,11 +828,55 @@ } function insertNodeAfter(parent, node, referenceNode) { if (!parent || !node || !referenceNode || referenceNode.parentNode !== parent) return; - const nextSibling = referenceNode.nextSibling; - if (node.parentNode === parent && node.previousSibling === referenceNode) return; + let insertionReference = referenceNode; + while (insertionReference.nextSibling && insertionReference.nextSibling.nodeType === Node.TEXT_NODE) { + const textContent = insertionReference.nextSibling.textContent || ""; + if (textContent.trim()) break; + insertionReference = insertionReference.nextSibling; + } + if (isBreadcrumbSeparatorNode(insertionReference.nextSibling)) { + insertionReference = insertionReference.nextSibling; + } + const nextSibling = insertionReference.nextSibling; + if (node.parentNode === parent && node.previousSibling === insertionReference) return; if (nextSibling === node) return; parent.insertBefore(node, nextSibling); } + function isBreadcrumbSeparatorNode(node) { + if (!node) return false; + if (node.nodeType === Node.TEXT_NODE) { + return (node.textContent || "").trim() === "/"; + } + if (node.nodeType !== Node.ELEMENT_NODE) return false; + const text = (node.textContent || "").replace(/\s+/g, ""); + if (text === "/") return true; + const className = typeof node.className === "string" ? node.className : ""; + return /breadcrumb|separator|divider/i.test(className) && text === "/"; + } + function stripBreadcrumbSeparatorsFromHost(hostNode) { + if (!hostNode || hostNode.nodeType !== Node.ELEMENT_NODE) return; + const anchor = hostNode.tagName.toLowerCase() === "a" ? hostNode : hostNode.querySelector("a"); + Array.from(hostNode.childNodes).forEach((child) => { + if (child === anchor) return; + if (child.nodeType === Node.TEXT_NODE && isBreadcrumbSeparatorNode(child)) { + child.remove(); + } + }); + Array.from(hostNode.querySelectorAll("*")).forEach((node) => { + if (anchor && anchor.contains(node)) return; + if (isBreadcrumbSeparatorNode(node)) { + node.remove(); + } + }); + } + function setQuickLinkHostMark(hostNode, enabled) { + if (!hostNode || hostNode.nodeType !== Node.ELEMENT_NODE) return; + if (enabled) { + hostNode.setAttribute(QUICK_LINK_HOST_MARK_ATTR, "1"); + } else { + hostNode.removeAttribute(QUICK_LINK_HOST_MARK_ATTR); + } + } function createOverflowChevronIcon() { const ns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(ns, "svg"); @@ -1256,10 +1464,12 @@ const renderedQuickAnchors = []; const renderedQuickItems = []; if (isOnPresetPage && anchorTag && primaryLink) { + setQuickLinkHostMark(insertAnchorNode, true); anchorTag.id = primaryLink.id; anchorTag.setAttribute(QUICK_LINK_MARK_ATTR, "1"); anchorTag.href = primaryLink.href; setLinkText(anchorTag, primaryLink.text); + stripBreadcrumbSeparatorsFromHost(insertAnchorNode); applyLinkShortcut(anchorTag, primaryLink); renderedQuickAnchors.push(anchorTag); setActiveStyle(anchorTag, isCurrentPage(primaryLink.path), shouldUseCompactButtons); @@ -1271,6 +1481,7 @@ if (anchorTag) { anchorTag.removeAttribute(QUICK_LINK_MARK_ATTR); } + setQuickLinkHostMark(insertAnchorNode, false); if (anchorTag && wasQuickAnchor) { anchorTag.removeAttribute("data-hotkey"); anchorTag.removeAttribute("aria-keyshortcuts"); @@ -1284,14 +1495,16 @@ linksToRender.forEach((linkInfo) => { const newNode = cloneTemplateNode.cloneNode(true); const aTag = ensureAnchor(newNode, isTemplateLiParent); + setQuickLinkHostMark(newNode, true); aTag.id = linkInfo.id; aTag.setAttribute(QUICK_LINK_MARK_ATTR, "1"); aTag.href = linkInfo.href; setLinkText(aTag, linkInfo.text); + stripBreadcrumbSeparatorsFromHost(newNode); applyLinkShortcut(aTag, linkInfo); renderedQuickAnchors.push(aTag); setActiveStyle(aTag, isCurrentPage(linkInfo.path), shouldUseCompactButtons); - insertAfterNode.parentNode.insertBefore(newNode, insertAfterNode.nextSibling); + insertNodeAfter(insertAfterNode.parentNode, newNode, insertAfterNode); insertAfterNode = newNode; renderedQuickItems.push({ anchor: aTag, @@ -1308,6 +1521,63 @@ } } + // src/theme.js + var themeSyncBound = false; + var systemThemeQuery = null; + function detectGitHubTheme() { + const root = document.documentElement; + const colorMode = String(root.getAttribute("data-color-mode") || "").toLowerCase(); + if (colorMode === "light" || colorMode === "dark") { + return colorMode; + } + const rootStyle = getComputedStyle(root); + const colorScheme = String(rootStyle.colorScheme || "").toLowerCase(); + if (colorScheme.includes("dark")) return "dark"; + if (colorScheme.includes("light")) return "light"; + if (!systemThemeQuery && typeof window.matchMedia === "function") { + systemThemeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + } + return systemThemeQuery?.matches ? "dark" : "light"; + } + function resolveThemePreference(preference = loadConfig().themePreference) { + const safePreference = sanitizeThemePreference(preference); + return safePreference === "auto" ? detectGitHubTheme() : safePreference; + } + function syncThemePreference() { + const preference = sanitizeThemePreference(loadConfig().themePreference); + const appliedTheme = resolveThemePreference(preference); + document.documentElement.setAttribute(THEME_ATTR, appliedTheme); + document.documentElement.setAttribute(THEME_SOURCE_ATTR, preference === "auto" ? "auto" : "custom"); + return appliedTheme; + } + function setThemePreference(preference) { + updateConfig({ themePreference: preference }); + return syncThemePreference(); + } + function bindThemePreferenceSync() { + if (themeSyncBound) return; + themeSyncBound = true; + syncThemePreference(); + if (!systemThemeQuery && typeof window.matchMedia === "function") { + systemThemeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + } + if (systemThemeQuery?.addEventListener) { + systemThemeQuery.addEventListener("change", syncThemePreference); + } else if (systemThemeQuery?.addListener) { + systemThemeQuery.addListener(syncThemePreference); + } + const observer2 = new MutationObserver((mutations) => { + const shouldSync = mutations.some((mutation) => mutation.type === "attributes" && (mutation.attributeName === "data-color-mode" || mutation.attributeName === "data-dark-theme" || mutation.attributeName === "data-light-theme")); + if (shouldSync) { + syncThemePreference(); + } + }); + observer2.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-color-mode", "data-dark-theme", "data-light-theme"] + }); + } + // src/settings-panel.js var settingsEscHandler = null; function closeConfigPanel() { @@ -1463,7 +1733,7 @@ message.textContent = t("atLeastOneLink"); return; } - saveConfig({ + updateConfig({ enabledKeys, orderKeys: state.order.slice() }); @@ -1494,7 +1764,10 @@ GM_registerMenuCommand(t("menuResetSettings"), () => { const shouldReset = confirm(t("resetConfirm")); if (!shouldReset) return; - localStorage.removeItem(CONFIG_STORAGE_KEY); + updateConfig({ + enabledKeys: DEFAULT_LINK_KEYS, + orderKeys: DEFAULT_LINK_KEYS + }); closeConfigPanel(); location.reload(); }); @@ -1513,6 +1786,18 @@ closeConfigPanel(); location.reload(); }); + GM_registerMenuCommand(t("menuThemeLight"), () => { + setThemePreference("light"); + closeConfigPanel(); + }); + GM_registerMenuCommand(t("menuThemeDark"), () => { + setThemePreference("dark"); + closeConfigPanel(); + }); + GM_registerMenuCommand(t("menuThemeAuto"), () => { + setThemePreference("auto"); + closeConfigPanel(); + }); } // src/top-repositories.js @@ -1856,6 +2141,7 @@ // src/main.js var renderQueued = false; function applyEnhancements() { + syncThemePreference(); ensureStyles(); addCustomButtons(); enhanceTopRepositories(); @@ -1871,6 +2157,7 @@ console.info(`[Better GitHub Navigation] loaded v${SCRIPT_VERSION}`); window.__betterGithubNavVersion = SCRIPT_VERSION; window.__openBetterGithubNavSettings = openConfigPanel; + bindThemePreferenceSync(); registerConfigMenu(); scheduleEnhancements(); document.addEventListener("turbo:load", scheduleEnhancements); diff --git a/package-lock.json b/package-lock.json index 760921f..2453b6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "better-github-nav", - "version": "0.1.45", + "version": "0.1.49", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "better-github-nav", - "version": "0.1.45", + "version": "0.1.49", "license": "MIT", "devDependencies": { "esbuild": "^0.27.3" diff --git a/package.json b/package.json index 4753421..76ccace 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "better-github-nav", - "version": "0.1.45", + "version": "0.1.49", "description": "Bring Dashboard, Trending, Explore, Collections, and Stars closer on desktop and narrow screens, and keep your most-used repositories pinned where they are easiest to reach.", "private": true, "scripts": { diff --git a/src/config.js b/src/config.js index 34ad9b8..5d77257 100644 --- a/src/config.js +++ b/src/config.js @@ -4,6 +4,10 @@ import { PRESET_LINKS } from './constants.js'; +export function sanitizeThemePreference(themePreference) { + return themePreference === 'light' || themePreference === 'dark' ? themePreference : 'auto'; +} + export function sanitizeKeys(keys) { const validSet = new Set(DEFAULT_LINK_KEYS); const seen = new Set(); @@ -25,9 +29,11 @@ export function sanitizeConfig(rawConfig) { ...orderKeysRaw, ...DEFAULT_LINK_KEYS.filter(key => !orderSet.has(key)) ]; + const themePreference = sanitizeThemePreference(rawConfig?.themePreference); return { enabledKeys: enabledKeys.length ? enabledKeys : DEFAULT_LINK_KEYS.slice(), - orderKeys: orderKeys.length ? orderKeys : DEFAULT_LINK_KEYS.slice() + orderKeys: orderKeys.length ? orderKeys : DEFAULT_LINK_KEYS.slice(), + themePreference }; } @@ -45,6 +51,14 @@ export function saveConfig(config) { localStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify(sanitizeConfig(config))); } +export function updateConfig(partialConfig) { + const currentConfig = loadConfig(); + saveConfig({ + ...currentConfig, + ...partialConfig + }); +} + export function getConfiguredLinks(username) { const config = loadConfig(); const presetMap = new Map( diff --git a/src/constants.js b/src/constants.js index 73f098e..8ac87dc 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,10 +4,13 @@ export const CUSTOM_BUTTON_CLASS = 'custom-gh-nav-btn'; export const CUSTOM_BUTTON_ACTIVE_CLASS = 'custom-gh-nav-btn-active'; export const CUSTOM_BUTTON_COMPACT_CLASS = 'custom-gh-nav-btn-compact'; export const QUICK_LINK_MARK_ATTR = 'data-better-gh-nav-quick-link'; +export const QUICK_LINK_HOST_MARK_ATTR = 'data-better-gh-nav-quick-link-host'; export const RESPONSIVE_TOGGLE_MARK_ATTR = 'data-better-gh-nav-overflow-toggle'; export const CONFIG_STORAGE_KEY = 'better-gh-nav-config-v1'; export const TOP_REPOSITORIES_PIN_STORAGE_KEY = 'better-gh-nav-top-repositories-pins-v1'; export const UI_LANG_STORAGE_KEY = 'better-gh-nav-ui-lang-v1'; +export const THEME_ATTR = 'data-better-github-nav-theme'; +export const THEME_SOURCE_ATTR = 'data-better-github-nav-theme-source'; export const SETTINGS_OVERLAY_ID = 'custom-gh-nav-settings-overlay'; export const SETTINGS_PANEL_ID = 'custom-gh-nav-settings-panel'; export const SETTINGS_MESSAGE_ID = 'custom-gh-nav-settings-message'; @@ -36,6 +39,9 @@ export const I18N = { menuLangZh: 'Better GitHub Nav: 界面语言 -> 中文', menuLangEn: 'Better GitHub Nav: 界面语言 -> English', menuLangAuto: 'Better GitHub Nav: 界面语言 -> 自动(跟随页面)', + menuThemeLight: 'Better GitHub Nav: 主题 -> 亮色', + menuThemeDark: 'Better GitHub Nav: 主题 -> 暗色', + menuThemeAuto: 'Better GitHub Nav: 主题 -> 自动(跟随 GitHub)', resetConfirm: '确认重置快捷链接配置为默认值吗?', panelTitle: 'Better GitHub Nav 设置', panelDesc: '勾选决定显示项,拖动整行(或右侧手柄)调整显示顺序。', @@ -58,6 +64,9 @@ export const I18N = { menuLangZh: 'Better GitHub Nav: UI Language -> 中文', menuLangEn: 'Better GitHub Nav: UI Language -> English', menuLangAuto: 'Better GitHub Nav: UI Language -> Auto (Follow Page)', + menuThemeLight: 'Better GitHub Nav: Theme -> Light', + menuThemeDark: 'Better GitHub Nav: Theme -> Dark', + menuThemeAuto: 'Better GitHub Nav: Theme -> Auto (Follow GitHub)', resetConfirm: 'Reset quick-link config to defaults?', panelTitle: 'Better GitHub Nav Settings', panelDesc: 'Select visible links and drag the row (or handle) to reorder.', diff --git a/src/main.js b/src/main.js index 2fbda96..4870724 100644 --- a/src/main.js +++ b/src/main.js @@ -13,10 +13,12 @@ import { isTopRepositoriesToggleTarget, needsTopRepositoriesEnhancement } from './top-repositories.js'; +import { bindThemePreferenceSync, syncThemePreference } from './theme.js'; let renderQueued = false; function applyEnhancements() { + syncThemePreference(); ensureStyles(); addCustomButtons(); enhanceTopRepositories(); @@ -35,6 +37,7 @@ function scheduleEnhancements() { console.info(`[Better GitHub Navigation] loaded v${SCRIPT_VERSION}`); window.__betterGithubNavVersion = SCRIPT_VERSION; window.__openBetterGithubNavSettings = openConfigPanel; +bindThemePreferenceSync(); registerConfigMenu(); scheduleEnhancements(); diff --git a/src/navigation.js b/src/navigation.js index ab7a568..71a03f2 100644 --- a/src/navigation.js +++ b/src/navigation.js @@ -1,6 +1,7 @@ import { PRESET_LINKS, PRESET_LINK_SHORTCUTS, + QUICK_LINK_HOST_MARK_ATTR, QUICK_LINK_MARK_ATTR, RESPONSIVE_TOGGLE_MARK_ATTR } from './constants.js'; @@ -133,12 +134,64 @@ function cleanupQuickLinksForContainer(renderParent, keepNode) { function insertNodeAfter(parent, node, referenceNode) { if (!parent || !node || !referenceNode || referenceNode.parentNode !== parent) return; - const nextSibling = referenceNode.nextSibling; - if (node.parentNode === parent && node.previousSibling === referenceNode) return; + let insertionReference = referenceNode; + while (insertionReference.nextSibling && insertionReference.nextSibling.nodeType === Node.TEXT_NODE) { + const textContent = insertionReference.nextSibling.textContent || ''; + if (textContent.trim()) break; + insertionReference = insertionReference.nextSibling; + } + if (isBreadcrumbSeparatorNode(insertionReference.nextSibling)) { + insertionReference = insertionReference.nextSibling; + } + + const nextSibling = insertionReference.nextSibling; + if (node.parentNode === parent && node.previousSibling === insertionReference) return; if (nextSibling === node) return; parent.insertBefore(node, nextSibling); } +function isBreadcrumbSeparatorNode(node) { + if (!node) return false; + if (node.nodeType === Node.TEXT_NODE) { + return (node.textContent || '').trim() === '/'; + } + if (node.nodeType !== Node.ELEMENT_NODE) return false; + + const text = (node.textContent || '').replace(/\s+/g, ''); + if (text === '/') return true; + + const className = typeof node.className === 'string' ? node.className : ''; + return /breadcrumb|separator|divider/i.test(className) && text === '/'; +} + +function stripBreadcrumbSeparatorsFromHost(hostNode) { + if (!hostNode || hostNode.nodeType !== Node.ELEMENT_NODE) return; + + const anchor = hostNode.tagName.toLowerCase() === 'a' ? hostNode : hostNode.querySelector('a'); + Array.from(hostNode.childNodes).forEach(child => { + if (child === anchor) return; + if (child.nodeType === Node.TEXT_NODE && isBreadcrumbSeparatorNode(child)) { + child.remove(); + } + }); + + Array.from(hostNode.querySelectorAll('*')).forEach(node => { + if (anchor && anchor.contains(node)) return; + if (isBreadcrumbSeparatorNode(node)) { + node.remove(); + } + }); +} + +function setQuickLinkHostMark(hostNode, enabled) { + if (!hostNode || hostNode.nodeType !== Node.ELEMENT_NODE) return; + if (enabled) { + hostNode.setAttribute(QUICK_LINK_HOST_MARK_ATTR, '1'); + } else { + hostNode.removeAttribute(QUICK_LINK_HOST_MARK_ATTR); + } +} + function createOverflowChevronIcon() { const ns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(ns, 'svg'); @@ -885,10 +938,12 @@ export function addCustomButtons() { if (isOnPresetPage && anchorTag && primaryLink) { // 预设页面:首个按钮替换为当前配置顺序中的第一个 + setQuickLinkHostMark(insertAnchorNode, true); anchorTag.id = primaryLink.id; anchorTag.setAttribute(QUICK_LINK_MARK_ATTR, '1'); anchorTag.href = primaryLink.href; setLinkText(anchorTag, primaryLink.text); + stripBreadcrumbSeparatorsFromHost(insertAnchorNode); applyLinkShortcut(anchorTag, primaryLink); renderedQuickAnchors.push(anchorTag); setActiveStyle(anchorTag, isCurrentPage(primaryLink.path), shouldUseCompactButtons); @@ -904,6 +959,7 @@ export function addCustomButtons() { if (anchorTag) { anchorTag.removeAttribute(QUICK_LINK_MARK_ATTR); } + setQuickLinkHostMark(insertAnchorNode, false); if (anchorTag && wasQuickAnchor) { anchorTag.removeAttribute('data-hotkey'); anchorTag.removeAttribute('aria-keyshortcuts'); @@ -922,17 +978,19 @@ export function addCustomButtons() { const newNode = cloneTemplateNode.cloneNode(true); const aTag = ensureAnchor(newNode, isTemplateLiParent); + setQuickLinkHostMark(newNode, true); aTag.id = linkInfo.id; aTag.setAttribute(QUICK_LINK_MARK_ATTR, '1'); aTag.href = linkInfo.href; setLinkText(aTag, linkInfo.text); + stripBreadcrumbSeparatorsFromHost(newNode); applyLinkShortcut(aTag, linkInfo); renderedQuickAnchors.push(aTag); setActiveStyle(aTag, isCurrentPage(linkInfo.path), shouldUseCompactButtons); // 将新按钮插入到锚点之后,并更新锚点 - insertAfterNode.parentNode.insertBefore(newNode, insertAfterNode.nextSibling); + insertNodeAfter(insertAfterNode.parentNode, newNode, insertAfterNode); insertAfterNode = newNode; renderedQuickItems.push({ anchor: aTag, diff --git a/src/settings-panel.js b/src/settings-panel.js index e4e928e..8a01e3f 100644 --- a/src/settings-panel.js +++ b/src/settings-panel.js @@ -1,13 +1,13 @@ import { - CONFIG_STORAGE_KEY, DEFAULT_LINK_KEYS, SETTINGS_MESSAGE_ID, SETTINGS_OVERLAY_ID, SETTINGS_PANEL_ID } from './constants.js'; -import { getDisplayNameByKey, loadConfig, sanitizeConfig, saveConfig } from './config.js'; +import { getDisplayNameByKey, loadConfig, sanitizeConfig, updateConfig } from './config.js'; import { t, setUiLangPreference } from './i18n.js'; import { ensureStyles } from './styles.js'; +import { setThemePreference } from './theme.js'; let settingsEscHandler = null; @@ -193,7 +193,7 @@ export function openConfigPanel() { message.textContent = t('atLeastOneLink'); return; } - saveConfig({ + updateConfig({ enabledKeys, orderKeys: state.order.slice() }); @@ -231,7 +231,10 @@ export function registerConfigMenu() { GM_registerMenuCommand(t('menuResetSettings'), () => { const shouldReset = confirm(t('resetConfirm')); if (!shouldReset) return; - localStorage.removeItem(CONFIG_STORAGE_KEY); + updateConfig({ + enabledKeys: DEFAULT_LINK_KEYS, + orderKeys: DEFAULT_LINK_KEYS + }); closeConfigPanel(); location.reload(); }); @@ -253,4 +256,19 @@ export function registerConfigMenu() { closeConfigPanel(); location.reload(); }); + + GM_registerMenuCommand(t('menuThemeLight'), () => { + setThemePreference('light'); + closeConfigPanel(); + }); + + GM_registerMenuCommand(t('menuThemeDark'), () => { + setThemePreference('dark'); + closeConfigPanel(); + }); + + GM_registerMenuCommand(t('menuThemeAuto'), () => { + setThemePreference('auto'); + closeConfigPanel(); + }); } diff --git a/src/styles.js b/src/styles.js index dc278f8..f8e4621 100644 --- a/src/styles.js +++ b/src/styles.js @@ -2,6 +2,10 @@ import { CUSTOM_BUTTON_ACTIVE_CLASS, CUSTOM_BUTTON_CLASS, CUSTOM_BUTTON_COMPACT_CLASS, + QUICK_LINK_HOST_MARK_ATTR, + QUICK_LINK_MARK_ATTR, + THEME_ATTR, + THEME_SOURCE_ATTR, SETTINGS_OVERLAY_ID, SETTINGS_PANEL_ID } from './constants.js'; @@ -11,6 +15,136 @@ export function ensureStyles() { const style = document.createElement('style'); style.id = 'custom-gh-nav-style'; style.textContent = ` + :root { + --bgn-color-scheme: light; + --bgn-fg-default: #1f2328; + --bgn-fg-muted: #656d76; + --bgn-fg-on-emphasis: #ffffff; + --bgn-border-default: #d1d9e0; + --bgn-border-muted: #d8dee4; + --bgn-surface-default: #ffffff; + --bgn-surface-subtle: #f6f8fa; + --bgn-surface-hover: rgba(177, 186, 196, 0.12); + --bgn-surface-active: rgba(177, 186, 196, 0.18); + --bgn-accent-fg: #0969da; + --bgn-accent-subtle: rgba(9, 105, 218, 0.08); + --bgn-btn-bg: #f6f8fa; + --bgn-btn-hover-bg: #f3f4f6; + --bgn-btn-primary-bg: #1f883d; + --bgn-btn-primary-hover-bg: #1a7f37; + --bgn-btn-primary-text: #ffffff; + --bgn-attention-fg: #9a6700; + --bgn-tooltip-bg: #1f2328; + --bgn-tooltip-kbd-bg: rgba(110, 118, 129, 0.4); + --bgn-overlay-backdrop: rgba(0, 0, 0, 0.45); + --bgn-shadow-medium: 0 8px 24px rgba(0, 0, 0, 0.2); + --bgn-shadow-large: 0 16px 32px rgba(0, 0, 0, 0.16); + --bgn-shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.25); + } + :root[${THEME_SOURCE_ATTR}="auto"][${THEME_ATTR}="light"] { + --bgn-color-scheme: light; + --bgn-fg-default: var(--color-fg-default, #1f2328); + --bgn-fg-muted: var(--color-fg-muted, #656d76); + --bgn-fg-on-emphasis: var(--color-fg-on-emphasis, #ffffff); + --bgn-border-default: var(--color-border-default, #d1d9e0); + --bgn-border-muted: var(--color-border-muted, #d8dee4); + --bgn-surface-default: var(--color-canvas-default, #ffffff); + --bgn-surface-subtle: var(--color-canvas-subtle, #f6f8fa); + --bgn-surface-hover: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); + --bgn-surface-active: var(--color-neutral-muted, rgba(177, 186, 196, 0.18)); + --bgn-accent-fg: var(--color-accent-fg, #0969da); + --bgn-accent-subtle: var(--color-accent-subtle, rgba(9, 105, 218, 0.08)); + --bgn-btn-bg: var(--color-btn-bg, #f6f8fa); + --bgn-btn-hover-bg: var(--color-btn-hover-bg, #f3f4f6); + --bgn-btn-primary-bg: var(--color-btn-primary-bg, #1f883d); + --bgn-btn-primary-hover-bg: var(--color-btn-primary-hover-bg, #1a7f37); + --bgn-btn-primary-text: var(--color-btn-primary-text, #ffffff); + --bgn-attention-fg: var(--color-attention-fg, #9a6700); + --bgn-tooltip-bg: var(--color-neutral-emphasis-plus, #1f2328); + --bgn-tooltip-kbd-bg: rgba(110, 118, 129, 0.4); + --bgn-overlay-backdrop: rgba(0, 0, 0, 0.45); + --bgn-shadow-medium: var(--color-shadow-medium, 0 8px 24px rgba(0, 0, 0, 0.2)); + --bgn-shadow-large: var(--color-shadow-large, 0 16px 32px rgba(0, 0, 0, 0.16)); + --bgn-shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.25); + } + :root[${THEME_SOURCE_ATTR}="auto"][${THEME_ATTR}="dark"] { + --bgn-color-scheme: dark; + --bgn-fg-default: var(--color-fg-default, #e6edf3); + --bgn-fg-muted: var(--color-fg-muted, #8b949e); + --bgn-fg-on-emphasis: var(--color-fg-on-emphasis, #ffffff); + --bgn-border-default: var(--color-border-default, #30363d); + --bgn-border-muted: var(--color-border-muted, #30363d); + --bgn-surface-default: var(--color-canvas-default, #0d1117); + --bgn-surface-subtle: var(--color-canvas-subtle, #161b22); + --bgn-surface-hover: var(--color-neutral-muted, rgba(110, 118, 129, 0.22)); + --bgn-surface-active: var(--color-neutral-muted, rgba(110, 118, 129, 0.32)); + --bgn-accent-fg: var(--color-accent-fg, #58a6ff); + --bgn-accent-subtle: var(--color-accent-subtle, rgba(56, 139, 253, 0.18)); + --bgn-btn-bg: var(--color-btn-bg, #212830); + --bgn-btn-hover-bg: var(--color-btn-hover-bg, #30363d); + --bgn-btn-primary-bg: var(--color-btn-primary-bg, #238636); + --bgn-btn-primary-hover-bg: var(--color-btn-primary-hover-bg, #2ea043); + --bgn-btn-primary-text: var(--color-btn-primary-text, #ffffff); + --bgn-attention-fg: var(--color-attention-fg, #d29922); + --bgn-tooltip-bg: var(--color-neutral-emphasis-plus, #21262d); + --bgn-tooltip-kbd-bg: rgba(139, 148, 158, 0.35); + --bgn-overlay-backdrop: rgba(1, 4, 9, 0.72); + --bgn-shadow-medium: var(--color-shadow-medium, 0 8px 24px rgba(1, 4, 9, 0.45)); + --bgn-shadow-large: var(--color-shadow-large, 0 16px 32px rgba(1, 4, 9, 0.5)); + --bgn-shadow-panel: 0 18px 42px rgba(1, 4, 9, 0.6); + } + :root[${THEME_SOURCE_ATTR}="custom"][${THEME_ATTR}="light"] { + --bgn-color-scheme: light; + --bgn-fg-default: #1f2328; + --bgn-fg-muted: #656d76; + --bgn-fg-on-emphasis: #ffffff; + --bgn-border-default: #d1d9e0; + --bgn-border-muted: #d8dee4; + --bgn-surface-default: #ffffff; + --bgn-surface-subtle: #f6f8fa; + --bgn-surface-hover: rgba(177, 186, 196, 0.12); + --bgn-surface-active: rgba(177, 186, 196, 0.18); + --bgn-accent-fg: #0969da; + --bgn-accent-subtle: rgba(9, 105, 218, 0.08); + --bgn-btn-bg: #f6f8fa; + --bgn-btn-hover-bg: #f3f4f6; + --bgn-btn-primary-bg: #1f883d; + --bgn-btn-primary-hover-bg: #1a7f37; + --bgn-btn-primary-text: #ffffff; + --bgn-attention-fg: #9a6700; + --bgn-tooltip-bg: #1f2328; + --bgn-tooltip-kbd-bg: rgba(110, 118, 129, 0.4); + --bgn-overlay-backdrop: rgba(0, 0, 0, 0.45); + --bgn-shadow-medium: 0 8px 24px rgba(0, 0, 0, 0.2); + --bgn-shadow-large: 0 16px 32px rgba(0, 0, 0, 0.16); + --bgn-shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.25); + } + :root[${THEME_SOURCE_ATTR}="custom"][${THEME_ATTR}="dark"] { + --bgn-color-scheme: dark; + --bgn-fg-default: #e6edf3; + --bgn-fg-muted: #8b949e; + --bgn-fg-on-emphasis: #ffffff; + --bgn-border-default: #30363d; + --bgn-border-muted: #30363d; + --bgn-surface-default: #0d1117; + --bgn-surface-subtle: #161b22; + --bgn-surface-hover: rgba(110, 118, 129, 0.22); + --bgn-surface-active: rgba(110, 118, 129, 0.32); + --bgn-accent-fg: #58a6ff; + --bgn-accent-subtle: rgba(56, 139, 253, 0.18); + --bgn-btn-bg: #212830; + --bgn-btn-hover-bg: #30363d; + --bgn-btn-primary-bg: #238636; + --bgn-btn-primary-hover-bg: #2ea043; + --bgn-btn-primary-text: #ffffff; + --bgn-attention-fg: #d29922; + --bgn-tooltip-bg: #21262d; + --bgn-tooltip-kbd-bg: rgba(139, 148, 158, 0.35); + --bgn-overlay-backdrop: rgba(1, 4, 9, 0.72); + --bgn-shadow-medium: 0 8px 24px rgba(1, 4, 9, 0.45); + --bgn-shadow-large: 0 16px 32px rgba(1, 4, 9, 0.5); + --bgn-shadow-panel: 0 18px 42px rgba(1, 4, 9, 0.6); + } a.${CUSTOM_BUTTON_CLASS} { border-radius: 6px; padding-inline: 8px; @@ -27,6 +161,15 @@ export function ensureStyles() { a.${CUSTOM_BUTTON_CLASS} * { cursor: pointer; } + header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::before, + header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::after, + header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::before, + header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::after, + header a[${QUICK_LINK_MARK_ATTR}="1"]::before, + header a[${QUICK_LINK_MARK_ATTR}="1"]::after { + content: none !important; + display: none !important; + } a.${CUSTOM_BUTTON_CLASS}:hover { background-color: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); text-decoration: none; @@ -49,10 +192,11 @@ export function ensureStyles() { min-width: 28px; min-height: 28px; padding: 0; - border: none; + border: 1px solid var(--bgn-border-default); border-radius: 6px; - background: transparent; - color: var(--color-fg-default, #1f2328); + background: var(--bgn-surface-subtle); + color: var(--bgn-fg-default); + color-scheme: var(--bgn-color-scheme); font: inherit; font-weight: 600; line-height: 1; @@ -60,10 +204,10 @@ export function ensureStyles() { } .custom-gh-nav-overflow-toggle:hover, .custom-gh-nav-overflow-toggle[aria-expanded="true"] { - background-color: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); + background-color: var(--bgn-surface-hover); } .custom-gh-nav-overflow-toggle:focus-visible { - outline: 2px solid var(--color-accent-fg, #0969da); + outline: 2px solid var(--bgn-accent-fg); outline-offset: 1px; } .custom-gh-nav-overflow-toggle-icon { @@ -84,10 +228,12 @@ export function ensureStyles() { min-width: 220px; max-width: min(280px, calc(100vw - 16px)); padding: 6px; - border: 1px solid var(--color-border-default, #d1d9e0); + border: 1px solid var(--bgn-border-default); border-radius: 12px; - background: var(--color-canvas-default, #fff); - box-shadow: var(--color-shadow-large, 0 16px 32px rgba(0, 0, 0, 0.16)); + background: var(--bgn-surface-default); + color: var(--bgn-fg-default); + color-scheme: var(--bgn-color-scheme); + box-shadow: var(--bgn-shadow-large); box-sizing: border-box; } .custom-gh-nav-overflow-menu[hidden] { @@ -101,18 +247,18 @@ export function ensureStyles() { min-height: 32px; padding: 6px 10px; border-radius: 8px; - color: var(--color-fg-default, #1f2328); + color: var(--bgn-fg-default); font-size: 13px; font-weight: 600; text-decoration: none; } .custom-gh-nav-overflow-link:hover { - background: var(--color-neutral-muted, rgba(177, 186, 196, 0.12)); + background: var(--bgn-surface-hover); text-decoration: none; } .custom-gh-nav-overflow-link[aria-current="page"] { - color: var(--color-accent-fg, #0969da); - background: var(--color-accent-subtle, rgba(9, 105, 218, 0.08)); + color: var(--bgn-accent-fg); + background: var(--bgn-accent-subtle); } .custom-gh-nav-overflow-link-text { min-width: 0; @@ -126,8 +272,8 @@ export function ensureStyles() { padding: 2px 6px; border: none !important; border-radius: 999px; - background: var(--color-neutral-muted, rgba(177, 186, 196, 0.18)) !important; - color: var(--color-fg-muted, #656d76); + background: var(--bgn-surface-active) !important; + color: var(--bgn-fg-muted); box-shadow: none !important; font: inherit; font-size: 11px; @@ -141,8 +287,8 @@ export function ensureStyles() { align-items: center; gap: 8px; max-width: min(320px, calc(100vw - 16px)); - background: var(--color-neutral-emphasis-plus, #1f2328); - color: var(--color-fg-on-emphasis, #ffffff); + background: var(--bgn-tooltip-bg); + color: var(--bgn-fg-on-emphasis); border-radius: 6px; padding: 4px 8px; font-size: 12px; @@ -151,8 +297,8 @@ export function ensureStyles() { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; pointer-events: none; box-sizing: border-box; - box-shadow: var(--color-shadow-medium, 0 8px 24px rgba(0,0,0,0.2)); - border: 1px solid var(--color-border-default, transparent); + box-shadow: var(--bgn-shadow-medium); + border: 1px solid var(--bgn-border-default); text-decoration: none; } .custom-gh-nav-tooltip[hidden] { @@ -189,7 +335,7 @@ export function ensureStyles() { vertical-align: middle; padding: 0 4px; border-radius: 4px; - background: rgba(110, 118, 129, 0.4); + background: var(--bgn-tooltip-kbd-bg); color: #ffffff; font-size: 11px; font-weight: 400; @@ -205,7 +351,7 @@ export function ensureStyles() { position: fixed; inset: 0; z-index: 2147483647; - background: rgba(0, 0, 0, 0.45); + background: var(--bgn-overlay-backdrop); display: flex; align-items: center; justify-content: center; @@ -216,11 +362,12 @@ export function ensureStyles() { width: min(560px, 100%); max-height: min(80vh, 720px); overflow: auto; - background: var(--color-canvas-default, #fff); - color: var(--color-fg-default, #1f2328); - border: 1px solid var(--color-border-default, #d1d9e0); + background: var(--bgn-surface-default); + color: var(--bgn-fg-default); + border: 1px solid var(--bgn-border-default); border-radius: 10px; - box-shadow: 0 16px 40px rgba(0, 0, 0, 0.25); + color-scheme: var(--bgn-color-scheme); + box-shadow: var(--bgn-shadow-panel); padding: 16px; box-sizing: border-box; } @@ -231,7 +378,7 @@ export function ensureStyles() { } .custom-gh-nav-settings-desc { margin: 0 0 12px; - color: var(--color-fg-muted, #656d76); + color: var(--bgn-fg-muted); font-size: 13px; } .custom-gh-nav-settings-list { @@ -244,10 +391,10 @@ export function ensureStyles() { align-items: center; justify-content: space-between; gap: 12px; - border: 1px solid var(--color-border-muted, #d8dee4); + border: 1px solid var(--bgn-border-muted); border-radius: 8px; padding: 8px 10px; - background: var(--color-canvas-subtle, #f6f8fa); + background: var(--bgn-surface-subtle); cursor: grab; } .custom-gh-nav-settings-row:active { @@ -269,9 +416,9 @@ export function ensureStyles() { gap: 6px; } .custom-gh-nav-settings-drag-handle { - border: 1px solid var(--color-border-default, #d1d9e0); - background: var(--color-btn-bg, #f6f8fa); - color: var(--color-fg-muted, #656d76); + border: 1px solid var(--bgn-border-default); + background: var(--bgn-btn-bg); + color: var(--bgn-fg-muted); border-radius: 6px; width: 32px; height: 26px; @@ -287,32 +434,32 @@ export function ensureStyles() { opacity: 0.55; } .custom-gh-nav-settings-row-drag-over { - border-color: var(--color-accent-fg, #0969da); - background: var(--color-accent-subtle, #ddf4ff); + border-color: var(--bgn-accent-fg); + background: var(--bgn-accent-subtle); } .custom-gh-nav-settings-btn { - border: 1px solid var(--color-border-default, #d1d9e0); - background: var(--color-btn-bg, #f6f8fa); - color: var(--color-fg-default, #1f2328); + border: 1px solid var(--bgn-border-default); + background: var(--bgn-btn-bg); + color: var(--bgn-fg-default); border-radius: 6px; padding: 4px 10px; font-size: 12px; cursor: pointer; } .custom-gh-nav-settings-btn:hover { - background: var(--color-btn-hover-bg, #f3f4f6); + background: var(--bgn-btn-hover-bg); } .custom-gh-nav-settings-btn:disabled { opacity: 0.45; cursor: not-allowed; } .custom-gh-nav-settings-btn-primary { - background: var(--color-btn-primary-bg, #1f883d); - border-color: var(--color-btn-primary-bg, #1f883d); - color: var(--color-btn-primary-text, #fff); + background: var(--bgn-btn-primary-bg); + border-color: var(--bgn-btn-primary-bg); + color: var(--bgn-btn-primary-text); } .custom-gh-nav-settings-btn-primary:hover { - background: var(--color-btn-primary-hover-bg, #1a7f37); + background: var(--bgn-btn-primary-hover-bg); } .custom-gh-nav-settings-footer { margin-top: 12px; @@ -323,7 +470,7 @@ export function ensureStyles() { .custom-gh-nav-settings-message { min-height: 20px; margin-top: 8px; - color: var(--color-attention-fg, #9a6700); + color: var(--bgn-attention-fg); font-size: 12px; } .custom-gh-top-repos-row { diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 0000000..8352a12 --- /dev/null +++ b/src/theme.js @@ -0,0 +1,75 @@ +import { THEME_ATTR, THEME_SOURCE_ATTR } from './constants.js'; +import { loadConfig, sanitizeThemePreference, updateConfig } from './config.js'; + +let themeSyncBound = false; +let systemThemeQuery = null; + +function detectGitHubTheme() { + const root = document.documentElement; + const colorMode = String(root.getAttribute('data-color-mode') || '').toLowerCase(); + if (colorMode === 'light' || colorMode === 'dark') { + return colorMode; + } + + const rootStyle = getComputedStyle(root); + const colorScheme = String(rootStyle.colorScheme || '').toLowerCase(); + if (colorScheme.includes('dark')) return 'dark'; + if (colorScheme.includes('light')) return 'light'; + + if (!systemThemeQuery && typeof window.matchMedia === 'function') { + systemThemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); + } + return systemThemeQuery?.matches ? 'dark' : 'light'; +} + +export function resolveThemePreference(preference = loadConfig().themePreference) { + const safePreference = sanitizeThemePreference(preference); + return safePreference === 'auto' ? detectGitHubTheme() : safePreference; +} + +export function syncThemePreference() { + const preference = sanitizeThemePreference(loadConfig().themePreference); + const appliedTheme = resolveThemePreference(preference); + document.documentElement.setAttribute(THEME_ATTR, appliedTheme); + document.documentElement.setAttribute(THEME_SOURCE_ATTR, preference === 'auto' ? 'auto' : 'custom'); + return appliedTheme; +} + +export function setThemePreference(preference) { + updateConfig({ themePreference: preference }); + return syncThemePreference(); +} + +export function bindThemePreferenceSync() { + if (themeSyncBound) return; + themeSyncBound = true; + + syncThemePreference(); + + if (!systemThemeQuery && typeof window.matchMedia === 'function') { + systemThemeQuery = window.matchMedia('(prefers-color-scheme: dark)'); + } + if (systemThemeQuery?.addEventListener) { + systemThemeQuery.addEventListener('change', syncThemePreference); + } else if (systemThemeQuery?.addListener) { + systemThemeQuery.addListener(syncThemePreference); + } + + const observer = new MutationObserver(mutations => { + const shouldSync = mutations.some(mutation => ( + mutation.type === 'attributes' + && ( + mutation.attributeName === 'data-color-mode' + || mutation.attributeName === 'data-dark-theme' + || mutation.attributeName === 'data-light-theme' + ) + )); + if (shouldSync) { + syncThemePreference(); + } + }); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-color-mode', 'data-dark-theme', 'data-light-theme'] + }); +} From 573ad374bac20a6c4cd11c7308e1ac47e1b0aaf8 Mon Sep 17 00:00:00 2001 From: ImXiangYu <2670833123@qq.com> Date: Sat, 14 Mar 2026 10:17:59 +0800 Subject: [PATCH 2/6] fix: fixed the delimiter issue --- better-github-nav.user.js | 41 +++++++++++++++++++++++++++++---------- package-lock.json | 4 ++-- package.json | 2 +- src/constants.js | 1 + src/navigation.js | 31 +++++++++++++++++++++++++++-- src/styles.js | 10 ++++------ 6 files changed, 68 insertions(+), 21 deletions(-) diff --git a/better-github-nav.user.js b/better-github-nav.user.js index 4c410a3..e1eaabd 100644 --- a/better-github-nav.user.js +++ b/better-github-nav.user.js @@ -2,7 +2,7 @@ // @name Better GitHub Navigation // @name:zh-CN 更好的 GitHub 导航栏 // @namespace https://github.com/ImXiangYu/better-github-nav -// @version 0.1.49 +// @version 0.1.50 // @description Bring Dashboard, Trending, Explore, Collections, and Stars closer on desktop and narrow screens, and keep your most-used repositories pinned where they are easiest to reach. // @description:zh-CN 在桌面端和窄屏场景下,把 Dashboard、Trending、Explore、Collections、Stars 放到更顺手的位置,并把你最常用的仓库固定在最容易到达的地方。 // @author Ayubass @@ -16,12 +16,13 @@ (() => { // src/constants.js - var SCRIPT_VERSION = "0.1.49"; + var SCRIPT_VERSION = "0.1.50"; var CUSTOM_BUTTON_CLASS = "custom-gh-nav-btn"; var CUSTOM_BUTTON_ACTIVE_CLASS = "custom-gh-nav-btn-active"; var CUSTOM_BUTTON_COMPACT_CLASS = "custom-gh-nav-btn-compact"; var QUICK_LINK_MARK_ATTR = "data-better-gh-nav-quick-link"; var QUICK_LINK_HOST_MARK_ATTR = "data-better-gh-nav-quick-link-host"; + var QUICK_LINK_LAST_MARK_ATTR = "data-better-gh-nav-quick-link-last"; var RESPONSIVE_TOGGLE_MARK_ATTR = "data-better-gh-nav-overflow-toggle"; var CONFIG_STORAGE_KEY = "better-gh-nav-config-v1"; var TOP_REPOSITORIES_PIN_STORAGE_KEY = "better-gh-nav-top-repositories-pins-v1"; @@ -346,12 +347,9 @@ a.${CUSTOM_BUTTON_CLASS} * { cursor: pointer; } - header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::before, - header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::after, - header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::before, - header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::after, - header a[${QUICK_LINK_MARK_ATTR}="1"]::before, - header a[${QUICK_LINK_MARK_ATTR}="1"]::after { + header [${QUICK_LINK_LAST_MARK_ATTR}="1"]::after, + header [${QUICK_LINK_LAST_MARK_ATTR}="1"] > a::after, + header a[${QUICK_LINK_MARK_ATTR}="1"][${QUICK_LINK_LAST_MARK_ATTR}="1"]::after { content: none !important; display: none !important; } @@ -877,6 +875,21 @@ hostNode.removeAttribute(QUICK_LINK_HOST_MARK_ATTR); } } + function setQuickLinkLastMark(hostNode, enabled) { + if (!hostNode || hostNode.nodeType !== Node.ELEMENT_NODE) return; + const anchor = hostNode.tagName.toLowerCase() === "a" ? hostNode : hostNode.querySelector("a"); + if (enabled) { + hostNode.setAttribute(QUICK_LINK_LAST_MARK_ATTR, "1"); + if (anchor && anchor.getAttribute(QUICK_LINK_MARK_ATTR) === "1") { + anchor.setAttribute(QUICK_LINK_LAST_MARK_ATTR, "1"); + } + return; + } + hostNode.removeAttribute(QUICK_LINK_LAST_MARK_ATTR); + if (anchor) { + anchor.removeAttribute(QUICK_LINK_LAST_MARK_ATTR); + } + } function createOverflowChevronIcon() { const ns = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(ns, "svg"); @@ -1463,15 +1476,16 @@ const hasShortcutActive = navPresetLinks.some((link) => isCurrentPage(link.path)); const renderedQuickAnchors = []; const renderedQuickItems = []; + const quickHostNodes = []; if (isOnPresetPage && anchorTag && primaryLink) { setQuickLinkHostMark(insertAnchorNode, true); anchorTag.id = primaryLink.id; anchorTag.setAttribute(QUICK_LINK_MARK_ATTR, "1"); anchorTag.href = primaryLink.href; setLinkText(anchorTag, primaryLink.text); - stripBreadcrumbSeparatorsFromHost(insertAnchorNode); applyLinkShortcut(anchorTag, primaryLink); renderedQuickAnchors.push(anchorTag); + quickHostNodes.push(insertAnchorNode); setActiveStyle(anchorTag, isCurrentPage(primaryLink.path), shouldUseCompactButtons); } else { const wasQuickAnchor = Boolean(anchorTag) && (anchorTag.id && anchorTag.id.startsWith("custom-gh-btn-") || anchorTag.getAttribute(QUICK_LINK_MARK_ATTR) === "1"); @@ -1482,6 +1496,7 @@ anchorTag.removeAttribute(QUICK_LINK_MARK_ATTR); } setQuickLinkHostMark(insertAnchorNode, false); + setQuickLinkLastMark(insertAnchorNode, false); if (anchorTag && wasQuickAnchor) { anchorTag.removeAttribute("data-hotkey"); anchorTag.removeAttribute("aria-keyshortcuts"); @@ -1500,9 +1515,9 @@ aTag.setAttribute(QUICK_LINK_MARK_ATTR, "1"); aTag.href = linkInfo.href; setLinkText(aTag, linkInfo.text); - stripBreadcrumbSeparatorsFromHost(newNode); applyLinkShortcut(aTag, linkInfo); renderedQuickAnchors.push(aTag); + quickHostNodes.push(newNode); setActiveStyle(aTag, isCurrentPage(linkInfo.path), shouldUseCompactButtons); insertNodeAfter(insertAfterNode.parentNode, newNode, insertAfterNode); insertAfterNode = newNode; @@ -1512,6 +1527,12 @@ linkInfo }); }); + quickHostNodes.forEach((node) => setQuickLinkLastMark(node, false)); + const lastQuickHostNode = quickHostNodes[quickHostNodes.length - 1]; + if (lastQuickHostNode) { + setQuickLinkLastMark(lastQuickHostNode, true); + stripBreadcrumbSeparatorsFromHost(lastQuickHostNode); + } setupResponsiveQuickLinks({ inlineItems: renderedQuickItems, referenceNode: insertAnchorNode, diff --git a/package-lock.json b/package-lock.json index 2453b6b..e3c7676 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "better-github-nav", - "version": "0.1.49", + "version": "0.1.50", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "better-github-nav", - "version": "0.1.49", + "version": "0.1.50", "license": "MIT", "devDependencies": { "esbuild": "^0.27.3" diff --git a/package.json b/package.json index 76ccace..9055b25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "better-github-nav", - "version": "0.1.49", + "version": "0.1.50", "description": "Bring Dashboard, Trending, Explore, Collections, and Stars closer on desktop and narrow screens, and keep your most-used repositories pinned where they are easiest to reach.", "private": true, "scripts": { diff --git a/src/constants.js b/src/constants.js index 8ac87dc..7e8d8b1 100644 --- a/src/constants.js +++ b/src/constants.js @@ -5,6 +5,7 @@ export const CUSTOM_BUTTON_ACTIVE_CLASS = 'custom-gh-nav-btn-active'; export const CUSTOM_BUTTON_COMPACT_CLASS = 'custom-gh-nav-btn-compact'; export const QUICK_LINK_MARK_ATTR = 'data-better-gh-nav-quick-link'; export const QUICK_LINK_HOST_MARK_ATTR = 'data-better-gh-nav-quick-link-host'; +export const QUICK_LINK_LAST_MARK_ATTR = 'data-better-gh-nav-quick-link-last'; export const RESPONSIVE_TOGGLE_MARK_ATTR = 'data-better-gh-nav-overflow-toggle'; export const CONFIG_STORAGE_KEY = 'better-gh-nav-config-v1'; export const TOP_REPOSITORIES_PIN_STORAGE_KEY = 'better-gh-nav-top-repositories-pins-v1'; diff --git a/src/navigation.js b/src/navigation.js index 71a03f2..d528cb3 100644 --- a/src/navigation.js +++ b/src/navigation.js @@ -2,6 +2,7 @@ import { PRESET_LINKS, PRESET_LINK_SHORTCUTS, QUICK_LINK_HOST_MARK_ATTR, + QUICK_LINK_LAST_MARK_ATTR, QUICK_LINK_MARK_ATTR, RESPONSIVE_TOGGLE_MARK_ATTR } from './constants.js'; @@ -192,6 +193,23 @@ function setQuickLinkHostMark(hostNode, enabled) { } } +function setQuickLinkLastMark(hostNode, enabled) { + if (!hostNode || hostNode.nodeType !== Node.ELEMENT_NODE) return; + const anchor = hostNode.tagName.toLowerCase() === 'a' ? hostNode : hostNode.querySelector('a'); + if (enabled) { + hostNode.setAttribute(QUICK_LINK_LAST_MARK_ATTR, '1'); + if (anchor && anchor.getAttribute(QUICK_LINK_MARK_ATTR) === '1') { + anchor.setAttribute(QUICK_LINK_LAST_MARK_ATTR, '1'); + } + return; + } + + hostNode.removeAttribute(QUICK_LINK_LAST_MARK_ATTR); + if (anchor) { + anchor.removeAttribute(QUICK_LINK_LAST_MARK_ATTR); + } +} + function createOverflowChevronIcon() { const ns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(ns, 'svg'); @@ -935,6 +953,7 @@ export function addCustomButtons() { const hasShortcutActive = navPresetLinks.some(link => isCurrentPage(link.path)); const renderedQuickAnchors = []; const renderedQuickItems = []; + const quickHostNodes = []; if (isOnPresetPage && anchorTag && primaryLink) { // 预设页面:首个按钮替换为当前配置顺序中的第一个 @@ -943,9 +962,9 @@ export function addCustomButtons() { anchorTag.setAttribute(QUICK_LINK_MARK_ATTR, '1'); anchorTag.href = primaryLink.href; setLinkText(anchorTag, primaryLink.text); - stripBreadcrumbSeparatorsFromHost(insertAnchorNode); applyLinkShortcut(anchorTag, primaryLink); renderedQuickAnchors.push(anchorTag); + quickHostNodes.push(insertAnchorNode); setActiveStyle(anchorTag, isCurrentPage(primaryLink.path), shouldUseCompactButtons); } else { // 其他页面:保留原生当前按钮,仅做高亮 @@ -960,6 +979,7 @@ export function addCustomButtons() { anchorTag.removeAttribute(QUICK_LINK_MARK_ATTR); } setQuickLinkHostMark(insertAnchorNode, false); + setQuickLinkLastMark(insertAnchorNode, false); if (anchorTag && wasQuickAnchor) { anchorTag.removeAttribute('data-hotkey'); anchorTag.removeAttribute('aria-keyshortcuts'); @@ -983,9 +1003,9 @@ export function addCustomButtons() { aTag.setAttribute(QUICK_LINK_MARK_ATTR, '1'); aTag.href = linkInfo.href; setLinkText(aTag, linkInfo.text); - stripBreadcrumbSeparatorsFromHost(newNode); applyLinkShortcut(aTag, linkInfo); renderedQuickAnchors.push(aTag); + quickHostNodes.push(newNode); setActiveStyle(aTag, isCurrentPage(linkInfo.path), shouldUseCompactButtons); @@ -999,6 +1019,13 @@ export function addCustomButtons() { }); }); + quickHostNodes.forEach(node => setQuickLinkLastMark(node, false)); + const lastQuickHostNode = quickHostNodes[quickHostNodes.length - 1]; + if (lastQuickHostNode) { + setQuickLinkLastMark(lastQuickHostNode, true); + stripBreadcrumbSeparatorsFromHost(lastQuickHostNode); + } + setupResponsiveQuickLinks({ inlineItems: renderedQuickItems, referenceNode: insertAnchorNode, diff --git a/src/styles.js b/src/styles.js index f8e4621..bae873d 100644 --- a/src/styles.js +++ b/src/styles.js @@ -3,6 +3,7 @@ import { CUSTOM_BUTTON_CLASS, CUSTOM_BUTTON_COMPACT_CLASS, QUICK_LINK_HOST_MARK_ATTR, + QUICK_LINK_LAST_MARK_ATTR, QUICK_LINK_MARK_ATTR, THEME_ATTR, THEME_SOURCE_ATTR, @@ -161,12 +162,9 @@ export function ensureStyles() { a.${CUSTOM_BUTTON_CLASS} * { cursor: pointer; } - header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::before, - header [${QUICK_LINK_HOST_MARK_ATTR}="1"]::after, - header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::before, - header [${QUICK_LINK_HOST_MARK_ATTR}="1"] > a::after, - header a[${QUICK_LINK_MARK_ATTR}="1"]::before, - header a[${QUICK_LINK_MARK_ATTR}="1"]::after { + header [${QUICK_LINK_LAST_MARK_ATTR}="1"]::after, + header [${QUICK_LINK_LAST_MARK_ATTR}="1"] > a::after, + header a[${QUICK_LINK_MARK_ATTR}="1"][${QUICK_LINK_LAST_MARK_ATTR}="1"]::after { content: none !important; display: none !important; } From e252eefb93206536da12bcc25ae74252b6402f56 Mon Sep 17 00:00:00 2001 From: ImXiangYu <2670833123@qq.com> Date: Sat, 14 Mar 2026 10:29:29 +0800 Subject: [PATCH 3/6] chore: update description and readme --- README.md | 53 ++++++++++++++++++----------------- better-github-nav.user.js | 4 +-- scripts/userscript-header.txt | 4 +-- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ecb707c..13cddc6 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,32 @@ # Better GitHub Navigation +

+ 中文说明 + English +

+ + + ## 中文说明 **Better GitHub Navigation** 是一款给 GitHub 补效率的 Tampermonkey(油猴)脚本。 -如果你经常在 GitHub 里来回切 `Dashboard`、`Explore`、`Trending`、`Stars`,或者总要去首页左侧翻那几个最常开的仓库,这个脚本就是拿来解决这些问题的。它会把常用入口放到更顺手的位置,让你少找一步、少点一下、少绕一圈;在窄屏、平板和移动端场景下,也能保持头部清爽,不会因为快捷入口太多而显得拥挤。 +支持将 DashBoard、Explore、Trending、Collections、Stars 这些常用入口放入导航栏中一键直达。支持自定义是否显示,自定义顺序。支持深色模式、窄屏场景。同时加入了 Github Top repositories 显示仓库置顶功能,常用仓库一键置顶,方便查看。 ### 🚀 核心功能 -- **常用页面一键直达**:把 `Dashboard`、`Explore`、`Trending`、`Collections`、`Stars` 这些高频入口补到顶部导航,少绕路。 -- **快捷入口按你习惯来**:想显示哪些、放什么顺序,都可以自己调整,导航栏终于能配合你的工作流。 -- **窄屏和移动端也顺手**:窗口变窄时,常用入口依然好找好点,小屏浏览 GitHub 也不会被拥挤的头部打断节奏。 -- **常用仓库固定在手边**:GitHub 首页左侧 `Top repositories` 支持一键置顶,把你真正常开的仓库留在最前面。 -- **展开后也继续可用**:点开 `Show more` 之后,新显示出来的仓库也一样可以直接置顶。 -- **熟悉的 GitHub 感还在**:脚本只帮你把常用内容提到更近的位置,不会把整套使用习惯打乱。 -- **跨页浏览依然顺滑**:在 GitHub 里切页面时,增强入口会持续生效。 -- **双语界面更省心**:支持中文和英文,自动跟随页面语言,也能手动切换。 +- **常用页面一键直达**:把 `Dashboard`、`Explore`、`Trending`、`Collections`、`Stars` 这些常用入口补到顶部导航。 +- **快捷入口自定义显示**:想显示哪些、放什么顺序,都可以自己调整,导航栏终于能配合你的工作流。 +- **窄屏和移动端适配**:窗口变窄时,常用入口依然好找好点,小屏浏览 GitHub 也不会被拥挤的头部打断节奏。 +- **常用仓库固定**:GitHub 首页左侧 `Top repositories` 支持一键置顶,把你真正常开的仓库留在最前面。 +- **跨页浏览**:在 GitHub 里切页面时,增强入口会持续生效。 +- **双语界面**:支持中文和英文,自动跟随页面语言。 +- **深色模式适配**:深色场景下依然适用。 ### 🛠️ 如何使用 1. 安装脚本后,在 GitHub 页面点击油猴扩展图标。 2. 在脚本菜单中选择 **"Better GitHub Nav: 打开设置面板"**。 3. 勾选你想保留的快捷入口,并拖动顺序,整理出最适合自己的导航栏。 4. 回到 GitHub 首页,点击 `Top repositories` 每个仓库后方的置顶按钮,把最常用的仓库固定到前面。 -5. 需要查看更多仓库时,展开 `Show more`,新增显示的仓库也能继续置顶。 -6. 之后常用页面和常用仓库都会更靠近你,日常切换会明显更顺。 ### 📦 安装地址 请前往 GreasyFork 安装最新版本: @@ -33,29 +37,28 @@ GitHub 首页增强, GitHub 导航增强, Top repositories 置顶, 常用仓库 --- + + ## English Description -**Better GitHub Navigation** is a Tampermonkey userscript built to make everyday GitHub navigation faster. +**Better GitHub Navigation** is a Tampermonkey userscript built to make GitHub more efficient. -If you keep jumping between `Dashboard`, `Explore`, `Trending`, `Stars`, and a handful of repositories you open all the time, this script brings those places closer. It adds better shortcuts where you already look, keeps your favorite repos easy to reach from the home page, and still feels tidy when you are browsing GitHub on narrower screens. +It allows commonly used sections such as Dashboard, Explore, Trending, Collections, and Stars to be added to the navigation bar for one-click access. Users can customize visibility and order. It also supports dark mode and narrow-screen layouts. Additionally, it includes a “GitHub Top Repositories” feature for pinning repositories, making frequently used repositories easy to access. ### 🚀 Key Features -- **Jump to the pages you use most**: Add quick access to `Dashboard`, `Explore`, `Trending`, `Collections`, and `Stars` right in the header. -- **Shape the header around your workflow**: Keep only the shortcuts you want and arrange them in the order that makes sense for you. -- **Comfortable on narrow screens too**: Your shortcuts stay easy to reach without making the header feel crowded on tablets, split-screen windows, or mobile browsing setups. -- **Pin the repos you actually use**: The home-page `Top repositories` list gets one-click pinning, so your real favorites stay at the top. -- **Still works after `Show more`**: Expand the list and newly revealed repositories can be pinned too. -- **Feels like GitHub, just more convenient**: The script helps you surface what matters without making GitHub feel unfamiliar. -- **Stays with you while browsing**: The shortcuts keep working as you move around GitHub. -- **Comfortable in both Chinese and English**: The UI follows the page language and can also be switched manually. +- **One-click access to the pages you use most**: Add `Dashboard`, `Explore`, `Trending`, `Collections`, and `Stars` directly to the top navigation. +- **Customize which shortcuts appear**: Decide what to show and in what order, so the header finally matches your workflow. +- **Works well on narrow screens and mobile layouts**: Even when the window gets smaller, your shortcuts stay easy to find and click. +- **Pin your most-used repositories**: On the GitHub home page, `Top repositories` gets one-click pinning so your real daily repos stay at the front. +- **Keeps working while you move across GitHub**: The enhanced entry points stay available as you switch pages. +- **Bilingual UI**: Supports both Chinese and English, and follows the page language automatically. +- **Dark mode ready**: Still clear and comfortable to use in dark mode. ### 🛠️ How to Use 1. After installation, click the Tampermonkey icon on any GitHub page. 2. Select **"Better GitHub Nav: Open Settings Panel"** from the script menu. -3. Keep the shortcuts you want, drag them into the order you like, and shape the header around your workflow. -4. On the GitHub home page, use the pin button next to any repo in `Top repositories` to keep it at the top. -5. If you open `Show more`, the newly revealed repositories can be pinned as well. -6. From then on, the pages and repos you use most stay much closer. +3. Keep the shortcuts you want, then drag them into the order that fits you best. +4. Go back to the GitHub home page and click the pin button next to each repo in `Top repositories` to keep your most-used repos at the front. ### 📦 Installation Install the latest version via GreasyFork: diff --git a/better-github-nav.user.js b/better-github-nav.user.js index e1eaabd..2845866 100644 --- a/better-github-nav.user.js +++ b/better-github-nav.user.js @@ -3,8 +3,8 @@ // @name:zh-CN 更好的 GitHub 导航栏 // @namespace https://github.com/ImXiangYu/better-github-nav // @version 0.1.50 -// @description Bring Dashboard, Trending, Explore, Collections, and Stars closer on desktop and narrow screens, and keep your most-used repositories pinned where they are easiest to reach. -// @description:zh-CN 在桌面端和窄屏场景下,把 Dashboard、Trending、Explore、Collections、Stars 放到更顺手的位置,并把你最常用的仓库固定在最容易到达的地方。 +// @description Supports adding commonly used sections such as Dashboard, Explore, Trending, Collections, and Stars to the navigation bar for one-click access. Allows customization of visibility and order. Supports dark mode and narrow-screen layouts. Also includes a “GitHub Top Repositories” feature for pinning repositories, making frequently used repositories easy to access. +// @description:zh-CN 支持将 DashBoard、Explore、Trending、Collections、Stars 等常用入口放入导航栏中一键直达。支持自定义是否显示,自定义顺序。支持深色模式、窄屏场景。同时加入了 Github Top repositories 显示仓库置顶功能,常用仓库一键置顶,方便查看。 // @author Ayubass // @license MIT // @match https://github.com/* diff --git a/scripts/userscript-header.txt b/scripts/userscript-header.txt index f42f8bc..1d7b0f5 100644 --- a/scripts/userscript-header.txt +++ b/scripts/userscript-header.txt @@ -3,8 +3,8 @@ // @name:zh-CN 更好的 GitHub 导航栏 // @namespace https://github.com/ImXiangYu/better-github-nav // @version __VERSION__ -// @description Bring Dashboard, Trending, Explore, Collections, and Stars closer on desktop and narrow screens, and keep your most-used repositories pinned where they are easiest to reach. -// @description:zh-CN 在桌面端和窄屏场景下,把 Dashboard、Trending、Explore、Collections、Stars 放到更顺手的位置,并把你最常用的仓库固定在最容易到达的地方。 +// @description Supports adding commonly used sections such as Dashboard, Explore, Trending, Collections, and Stars to the navigation bar for one-click access. Allows customization of visibility and order. Supports dark mode and narrow-screen layouts. Also includes a “GitHub Top Repositories” feature for pinning repositories, making frequently used repositories easy to access. +// @description:zh-CN 支持将 DashBoard、Explore、Trending、Collections、Stars 等常用入口放入导航栏中一键直达。支持自定义是否显示,自定义顺序。支持深色模式、窄屏场景。同时加入了 Github Top repositories 显示仓库置顶功能,常用仓库一键置顶,方便查看。 // @author Ayubass // @license MIT // @match https://github.com/* From c061bc69a08bdf5044346c67d45d5923bc048125 Mon Sep 17 00:00:00 2001 From: ImXiangYu <2670833123@qq.com> Date: Sat, 14 Mar 2026 10:32:35 +0800 Subject: [PATCH 4/6] chore: update README --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 13cddc6..70954f8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Better GitHub Navigation -

- 中文说明 - English -

+[中文说明](#readme-zh) | [English](#readme-en) From f45cc40227ffd5f866f9342c799a48f66ae9e9dd Mon Sep 17 00:00:00 2001 From: ImXiangYu <2670833123@qq.com> Date: Sat, 14 Mar 2026 11:26:21 +0800 Subject: [PATCH 5/6] chore: upload some images --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 70954f8..739ff23 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,21 @@ ### 🚀 核心功能 - **常用页面一键直达**:把 `Dashboard`、`Explore`、`Trending`、`Collections`、`Stars` 这些常用入口补到顶部导航。 + ![image-20260314111704936](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111704936.png) - **快捷入口自定义显示**:想显示哪些、放什么顺序,都可以自己调整,导航栏终于能配合你的工作流。 + ![image-20260314111810164](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111810164.png) - **窄屏和移动端适配**:窗口变窄时,常用入口依然好找好点,小屏浏览 GitHub 也不会被拥挤的头部打断节奏。 + ![image-20260314111848110](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111848110.png) - **常用仓库固定**:GitHub 首页左侧 `Top repositories` 支持一键置顶,把你真正常开的仓库留在最前面。 + ![image-20260314111928748](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111928748.png) - **跨页浏览**:在 GitHub 里切页面时,增强入口会持续生效。 + ![image-20260314111954077](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111954077.png) - **双语界面**:支持中文和英文,自动跟随页面语言。 + ![image-20260314112134041](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112134041.png) + ![image-20260314112154596](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112154596.png) - **深色模式适配**:深色场景下依然适用。 + ![image-20260314112223000](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112223000.png) + ![image-20260314112245868](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112245868.png) ### 🛠️ 如何使用 1. 安装脚本后,在 GitHub 页面点击油猴扩展图标。 @@ -44,12 +53,21 @@ It allows commonly used sections such as Dashboard, Explore, Trending, Collectio ### 🚀 Key Features - **One-click access to the pages you use most**: Add `Dashboard`, `Explore`, `Trending`, `Collections`, and `Stars` directly to the top navigation. + ![image-20260314111704936](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111704936.png) - **Customize which shortcuts appear**: Decide what to show and in what order, so the header finally matches your workflow. + ![image-20260314111810164](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111810164.png) - **Works well on narrow screens and mobile layouts**: Even when the window gets smaller, your shortcuts stay easy to find and click. + ![image-20260314111848110](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111848110.png) - **Pin your most-used repositories**: On the GitHub home page, `Top repositories` gets one-click pinning so your real daily repos stay at the front. + ![image-20260314111928748](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111928748.png) - **Keeps working while you move across GitHub**: The enhanced entry points stay available as you switch pages. + ![image-20260314111954077](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111954077.png) - **Bilingual UI**: Supports both Chinese and English, and follows the page language automatically. + ![image-20260314112134041](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112134041.png) + ![image-20260314112154596](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112154596.png) - **Dark mode ready**: Still clear and comfortable to use in dark mode. + ![image-20260314112223000](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112223000.png) + ![image-20260314112245868](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112245868.png) ### 🛠️ How to Use 1. After installation, click the Tampermonkey icon on any GitHub page. From d4dc80acf900acdf3ccf0e677697d27a411e2f5d Mon Sep 17 00:00:00 2001 From: ImXiangYu <2670833123@qq.com> Date: Sat, 14 Mar 2026 11:29:02 +0800 Subject: [PATCH 6/6] chore: update README --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 739ff23..fc77425 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,18 @@ ### 🚀 核心功能 - **常用页面一键直达**:把 `Dashboard`、`Explore`、`Trending`、`Collections`、`Stars` 这些常用入口补到顶部导航。 ![image-20260314111704936](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111704936.png) -- **快捷入口自定义显示**:想显示哪些、放什么顺序,都可以自己调整,导航栏终于能配合你的工作流。 +- **快捷入口自定义显示**:想显示哪些、放什么顺序,都可以自己调整,导航栏终于能配合你的工作流。
![image-20260314111810164](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111810164.png) -- **窄屏和移动端适配**:窗口变窄时,常用入口依然好找好点,小屏浏览 GitHub 也不会被拥挤的头部打断节奏。 +- **窄屏和移动端适配**:窗口变窄时,常用入口依然好找好点,小屏浏览 GitHub 也不会被拥挤的头部打断节奏。
![image-20260314111848110](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111848110.png) -- **常用仓库固定**:GitHub 首页左侧 `Top repositories` 支持一键置顶,把你真正常开的仓库留在最前面。 +- **常用仓库固定**:GitHub 首页左侧 `Top repositories` 支持一键置顶,把你真正常开的仓库留在最前面。
![image-20260314111928748](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111928748.png) -- **跨页浏览**:在 GitHub 里切页面时,增强入口会持续生效。 +- **跨页浏览**:在 GitHub 里切页面时,增强入口会持续生效。
![image-20260314111954077](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111954077.png) -- **双语界面**:支持中文和英文,自动跟随页面语言。 +- **双语界面**:支持中文和英文,自动跟随页面语言。
![image-20260314112134041](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112134041.png) ![image-20260314112154596](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112154596.png) -- **深色模式适配**:深色场景下依然适用。 +- **深色模式适配**:深色场景下依然适用。
![image-20260314112223000](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112223000.png) ![image-20260314112245868](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112245868.png) @@ -52,20 +52,20 @@ GitHub 首页增强, GitHub 导航增强, Top repositories 置顶, 常用仓库 It allows commonly used sections such as Dashboard, Explore, Trending, Collections, and Stars to be added to the navigation bar for one-click access. Users can customize visibility and order. It also supports dark mode and narrow-screen layouts. Additionally, it includes a “GitHub Top Repositories” feature for pinning repositories, making frequently used repositories easy to access. ### 🚀 Key Features -- **One-click access to the pages you use most**: Add `Dashboard`, `Explore`, `Trending`, `Collections`, and `Stars` directly to the top navigation. +- **One-click access to the pages you use most**: Add `Dashboard`, `Explore`, `Trending`, `Collections`, and `Stars` directly to the top navigation.
![image-20260314111704936](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111704936.png) -- **Customize which shortcuts appear**: Decide what to show and in what order, so the header finally matches your workflow. +- **Customize which shortcuts appear**: Decide what to show and in what order, so the header finally matches your workflow.
![image-20260314111810164](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111810164.png) -- **Works well on narrow screens and mobile layouts**: Even when the window gets smaller, your shortcuts stay easy to find and click. +- **Works well on narrow screens and mobile layouts**: Even when the window gets smaller, your shortcuts stay easy to find and click.
![image-20260314111848110](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111848110.png) -- **Pin your most-used repositories**: On the GitHub home page, `Top repositories` gets one-click pinning so your real daily repos stay at the front. +- **Pin your most-used repositories**: On the GitHub home page, `Top repositories` gets one-click pinning so your real daily repos stay at the front.
![image-20260314111928748](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111928748.png) -- **Keeps working while you move across GitHub**: The enhanced entry points stay available as you switch pages. +- **Keeps working while you move across GitHub**: The enhanced entry points stay available as you switch pages.
![image-20260314111954077](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314111954077.png) -- **Bilingual UI**: Supports both Chinese and English, and follows the page language automatically. +- **Bilingual UI**: Supports both Chinese and English, and follows the page language automatically.
![image-20260314112134041](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112134041.png) ![image-20260314112154596](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112154596.png) -- **Dark mode ready**: Still clear and comfortable to use in dark mode. +- **Dark mode ready**: Still clear and comfortable to use in dark mode.
![image-20260314112223000](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112223000.png) ![image-20260314112245868](https://cdn.jsdelivr.net/gh/ImXiangYu/image-hosting@main/image/image-20260314112245868.png)