diff --git a/README.md b/README.md
index ecb707c..fc77425 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,38 @@
# Better GitHub Navigation
+[中文说明](#readme-zh) | [English](#readme-en)
+
+
+
## 中文说明
**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 +43,37 @@ 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 6d498a2..2845866 100644
--- a/better-github-nav.user.js
+++ b/better-github-nav.user.js
@@ -2,9 +2,9 @@
// @name Better GitHub Navigation
// @name:zh-CN 更好的 GitHub 导航栏
// @namespace https://github.com/ImXiangYu/better-github-nav
-// @version 0.1.45
-// @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 放到更顺手的位置,并把你最常用的仓库固定在最容易到达的地方。
+// @version 0.1.50
+// @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/*
@@ -16,15 +16,19 @@
(() => {
// src/constants.js
- var SCRIPT_VERSION = "0.1.45";
+ 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";
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 +54,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 +79,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 +101,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 +124,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 +143,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 +201,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 +347,12 @@
a.${CUSTOM_BUTTON_CLASS} * {
cursor: pointer;
}
+ 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;
+ }
a.${CUSTOM_BUTTON_CLASS}:hover {
background-color: var(--color-neutral-muted, rgba(177, 186, 196, 0.12));
text-decoration: none;
@@ -217,10 +375,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 +387,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 +411,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 +430,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 +455,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 +470,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 +480,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 +518,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 +534,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 +545,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 +561,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 +574,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 +599,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 +617,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 +653,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 +826,70 @@
}
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 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");
@@ -1255,13 +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);
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");
@@ -1271,6 +1495,8 @@
if (anchorTag) {
anchorTag.removeAttribute(QUICK_LINK_MARK_ATTR);
}
+ setQuickLinkHostMark(insertAnchorNode, false);
+ setQuickLinkLastMark(insertAnchorNode, false);
if (anchorTag && wasQuickAnchor) {
anchorTag.removeAttribute("data-hotkey");
anchorTag.removeAttribute("aria-keyshortcuts");
@@ -1284,14 +1510,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);
applyLinkShortcut(aTag, linkInfo);
renderedQuickAnchors.push(aTag);
+ quickHostNodes.push(newNode);
setActiveStyle(aTag, isCurrentPage(linkInfo.path), shouldUseCompactButtons);
- insertAfterNode.parentNode.insertBefore(newNode, insertAfterNode.nextSibling);
+ insertNodeAfter(insertAfterNode.parentNode, newNode, insertAfterNode);
insertAfterNode = newNode;
renderedQuickItems.push({
anchor: aTag,
@@ -1299,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,
@@ -1308,6 +1542,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 +1754,7 @@
message.textContent = t("atLeastOneLink");
return;
}
- saveConfig({
+ updateConfig({
enabledKeys,
orderKeys: state.order.slice()
});
@@ -1494,7 +1785,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 +1807,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 +2162,7 @@
// src/main.js
var renderQueued = false;
function applyEnhancements() {
+ syncThemePreference();
ensureStyles();
addCustomButtons();
enhanceTopRepositories();
@@ -1871,6 +2178,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..e3c7676 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "better-github-nav",
- "version": "0.1.45",
+ "version": "0.1.50",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "better-github-nav",
- "version": "0.1.45",
+ "version": "0.1.50",
"license": "MIT",
"devDependencies": {
"esbuild": "^0.27.3"
diff --git a/package.json b/package.json
index 4753421..9055b25 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "better-github-nav",
- "version": "0.1.45",
+ "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/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/*
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..7e8d8b1 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -4,10 +4,14 @@ 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 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';
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 +40,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 +65,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..d528cb3 100644
--- a/src/navigation.js
+++ b/src/navigation.js
@@ -1,6 +1,8 @@
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';
@@ -133,12 +135,81 @@ 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 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');
@@ -882,15 +953,18 @@ export function addCustomButtons() {
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);
applyLinkShortcut(anchorTag, primaryLink);
renderedQuickAnchors.push(anchorTag);
+ quickHostNodes.push(insertAnchorNode);
setActiveStyle(anchorTag, isCurrentPage(primaryLink.path), shouldUseCompactButtons);
} else {
// 其他页面:保留原生当前按钮,仅做高亮
@@ -904,6 +978,8 @@ export function addCustomButtons() {
if (anchorTag) {
anchorTag.removeAttribute(QUICK_LINK_MARK_ATTR);
}
+ setQuickLinkHostMark(insertAnchorNode, false);
+ setQuickLinkLastMark(insertAnchorNode, false);
if (anchorTag && wasQuickAnchor) {
anchorTag.removeAttribute('data-hotkey');
anchorTag.removeAttribute('aria-keyshortcuts');
@@ -922,17 +998,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);
applyLinkShortcut(aTag, linkInfo);
renderedQuickAnchors.push(aTag);
+ quickHostNodes.push(newNode);
setActiveStyle(aTag, isCurrentPage(linkInfo.path), shouldUseCompactButtons);
// 将新按钮插入到锚点之后,并更新锚点
- insertAfterNode.parentNode.insertBefore(newNode, insertAfterNode.nextSibling);
+ insertNodeAfter(insertAfterNode.parentNode, newNode, insertAfterNode);
insertAfterNode = newNode;
renderedQuickItems.push({
anchor: aTag,
@@ -941,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/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..bae873d 100644
--- a/src/styles.js
+++ b/src/styles.js
@@ -2,6 +2,11 @@ import {
CUSTOM_BUTTON_ACTIVE_CLASS,
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,
SETTINGS_OVERLAY_ID,
SETTINGS_PANEL_ID
} from './constants.js';
@@ -11,6 +16,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 +162,12 @@ export function ensureStyles() {
a.${CUSTOM_BUTTON_CLASS} * {
cursor: pointer;
}
+ 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;
+ }
a.${CUSTOM_BUTTON_CLASS}:hover {
background-color: var(--color-neutral-muted, rgba(177, 186, 196, 0.12));
text-decoration: none;
@@ -49,10 +190,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 +202,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 +226,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 +245,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 +270,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 +285,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 +295,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 +333,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 +349,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 +360,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 +376,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 +389,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 +414,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 +432,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 +468,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']
+ });
+}