feat: 1.0.3 引入 Dark Reader 通用暗色引擎

This commit is contained in:
2026-08-08 17:21:22 +08:00
parent b36ad30d7a
commit 202ab537d1
29 changed files with 19555 additions and 207 deletions
+8
View File
@@ -10,8 +10,16 @@ build/
*.iml
.DS_Store
node_modules/
.pnpm-store/
dist/
# Playwright 测试工具 — 只跟踪脚本和配置,忽略输出/截图/缓存
workplace/*
!workplace/*.py
!workplace/*.yaml
# 暗色扫描输出与会话
scripts/output/
scripts/.browser-profile/
# Dark Reader 源码下载目录(构建依赖已 vendor 到 ui/vendor/darkreader
docs/darkreader/
+18 -2
View File
@@ -54,6 +54,7 @@ halo-dark-mode-plugin/
├── 前端 (Vue 3 / TypeScript) ← 核心实现
│ ├── index.ts ← definePlugin() 入口,注册路由+组件
│ ├── darkreader-engine.ts ← Dark Reader 通用暗色引擎
│ ├── injector.ts ← ThemeToggle 侧边栏注入器(MutationObserver 方案)
│ ├── composables/
│ │ ├── useDarkMode.ts ← 单例状态管理(light/dark/auto + localStorage 持久化)
@@ -80,18 +81,23 @@ halo-dark-mode-plugin/
### 主题切换机制
- **触发方式**`document.documentElement` 上设置/移除 `data-halo-theme="dark"` 属性
- **主引擎**Dark Reader(本地 vendored 于 `ui/vendor/darkreader`API 包由
`npm run api` 构建),监听 `useDarkMode().isDark`,深色时 `enable()`,浅色时
`disable()`,可自动分析 Halo 核心与第三方插件的动态 DOM
- **CSS 变量体系**:所有颜色通过 `--halo-*` 前缀的 CSS 自定义属性控制,一个语义变量对应一个视觉属性
- **颜色空间**:全部使用 OKLCH(感知均匀,暗色模式天然适配)
- **暗色配色策略**:用亮度层次区分背景(越"高"的层越亮),中性色含微量蓝色调,强调色略降饱和
- **CSS 覆盖定位**:现有 `overrides/` 仅作为 Dark Reader 的兼容层与兜底,
不再逐页新增手工转换规则;新页面残留统一交给 Dark Reader 处理
### ⚠️ 最关键的教训:Halo 2.25 用 UnoCSS,不是 Tailwind
Halo 2.25 的 Console 实际使用 **UnoCSS**hash 类如 `uno-*`)加 **BEM 语义类**。**不要写 `.bg-white``.text-gray-900``.v-card` 这类 Tailwind/Vuetify 原子选择器**——它们在真实 DOM 中不存在,覆盖会静默失效。
Halo 2.25 的 Console 实际使用 **UnoCSS**hash 类如 `uno-*`)加 **BEM 语义aliyun "curl -sI --max-time 5 http://localhost/ 2>&1 |**。**不要写 `.bg-white``.text-gray-900``.v-card` 这类 Tailwind/Vuetify 原子选择器**——它们在真实 DOM 中不存在,覆盖会静默失效。
真实 DOM 里的容器类名(已被 `halo-core.css` 覆盖):
| 语义 | 真实类名 |
|------|---------|
| ------------------- | --------------------------------------------------------------------------------------------- |
| 页面顶栏 | `.page-header` / `.page-header__title-text` |
| 列表卡片容器 | `.card-wrapper` |
| 文章/用户列表项标题 | `.entity-field-title` / `.entity-field-title-body` |
@@ -107,6 +113,7 @@ Halo 2.25 的 Console 实际使用 **UnoCSS**hash 类如 `uno-*`)加 **BEM
### 覆盖策略(重写后)
`halo-core.css` 是主要覆盖文件,按语义类精准覆盖。它包含三类规则:
1. **BEM 语义类**(如 `.card-wrapper`)— 直接 `[data-halo-theme="dark"] .card-wrapper { background-color: var(--halo-bg-card) }`
2. **通用 UnoCSS 工具类**(如 `.bg-gray-50``.hover:text-gray-600`)— 批量覆盖文字/背景/边框
3. **根背景兜底**`html, body { background-color: var(--halo-bg-body) !important }`body 不设暗色会在溢出时露白)
@@ -134,6 +141,15 @@ Halo 扩展点系统**没有侧边栏插槽**。`injector.ts` 用 `MutationObser
后端极简 — `DarkModePlugin extends BasePlugin` 仅含 `start()`/`stop()` 生命周期钩子。所有核心逻辑在前端。插件不依赖后端 Setting API。
## 打包约定(必须)
每次执行 `./gradlew build` 打 JAR 前,**必须先递增版本标签**:
1. 修改 `gradle.properties` 中的 `version`(如 `1.0.2``1.0.3`
2. 同步更新 `src/main/java/run/halo/darkmode/DarkModePlugin.java``@since`
3. 构建完成后核对 `build/libs/plugin-dark-mode-<新版本>.jar` 存在,且 JAR 内的
`ui/style.css``ui/main.js` 包含本次改动(用关键字检查,例如新增类名)
4. 向用户交付时明确给出新 JAR 路径和版本号,避免线上仍加载旧 bundle 的混淆
## 验证工作流(必须)
对 CSS 覆盖的任何改动,都要在**真实 Halo 环境**验证,不能只靠 `vite build` 通过:
+3
View File
@@ -10,6 +10,8 @@ Halo 2.25 暗色模式插件 — 为 Halo 后台管理面板提供深色/浅色
-**瞬间切换**CSS 变量瞬时生效,无可见闪烁
- 🧩 **侧边栏注入**:切换按钮自动出现在侧边栏底部(UserProfileBanner 上方)
- ⚙️ **设置页面**:提供详细的模式选择界面(菜单 → 偏好设置 → 深色模式)
- 🔍 **Dark Reader 引擎**:内置成熟的开源暗色转换引擎,自动分析页面 CSS 与 DOM,
第三方插件页面的黑字/白底也能自动转换,不再依赖逐页手工覆盖
- 🎨 **OKLCH 色彩空间**:感知均匀,暗色模式天然适配,WCAG AA 对比度保证
- 📦 **零后端依赖**:纯前端实现,不需要后端 API
-**覆盖已验证**:仪表盘、内容管理(文章/页面/评论)、链接、订阅、瞬间、用户、主题、设置、编辑器、模态框等页面已针对 Halo 2.25 真实 DOM 逐一验证
@@ -69,6 +71,7 @@ pnpm test:unit # 单元测试
└── ui/
└── src/
├── index.ts # definePlugin 入口
├── darkreader-engine.ts # Dark Reader 通用暗色引擎
├── injector.ts # ThemeToggle 侧边栏注入器
├── composables/
│ ├── useDarkMode.ts # 核心状态管理(模块级单例)
+1 -1
View File
@@ -1,3 +1,3 @@
version=1.0.1
version=1.0.3
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
org.gradle.java.home=D:/settings/Language/Java/jdk-25.0.1
+60
View File
@@ -0,0 +1,60 @@
# 暗色模式回归扫描工具
用 Playwright 驱动 Edge 对 Halo 后台做全路由深浅色残留扫描,验证每次 CSS 改动。
## 依赖
- Python 3.13 环境(本机:`D:\settings\settings\uv\my_uv_env`
- Playwright(已安装):`pip install playwright``uv pip install playwright`
- 本机 EdgePlaywright 通过 `channel="msedge"` 复用,无需下载浏览器内核)
## 首次使用:登录
```powershell
D:\settings\settings\uv\my_uv_env\Scripts\python.exe scripts\login-wait.py
```
会弹出 Edge 窗口,登录 `https://blog.liuhangyv.top/console` 后脚本自动退出,
会话保存在 `scripts/.browser-profile/`(已被 gitignore,不会入库)。
已有登录态想复用,可显式指定 profile:
```powershell
D:\settings\settings\uv\my_uv_env\Scripts\python.exe scripts\scan-dark.py --profile workplace\pw-profile
```
## 常用命令
```powershell
# 全路由深色扫描,注入本地构建产物做预部署验证,存在残留时返回非 0
D:\settings\settings\uv\my_uv_env\Scripts\python.exe scripts\scan-dark.py --local-css ui\build\dist\style.css --assert-zero
# 只扫指定页面
D:\settings\settings\uv\my_uv_env\Scripts\python.exe scripts\scan-dark.py --pages /console/posts,/console/users
# 浅色模式回归抽查
D:\settings\settings\uv\my_uv_env\Scripts\python.exe scripts\scan-dark.py --mode light --pages /console/dashboard,/console/posts
```
## 输出
- `scripts/output/scan-results.json`:按路由分组的结果
- `scripts/output/shots/*.png`:每页截图
- 均已被 gitignore,不入库。
## 切换行为验证
```powershell
D:\settings\settings\uv\my_uv_env\Scripts\python.exe scripts\verify-toggle.py
```
部署新版插件后运行,会检查侧边栏按钮切换、`data-halo-theme` 属性、
localStorage 持久化以及 Monaco 日志查看器主题是否同步。
## 注意事项
- 扫描会忽略 `img/canvas/video/svg/iframe`,避免把文章图片、编辑器画布等内容性
元素误判为界面残留。
- 路由从 Vue Router 动态发现,Halo 或插件升级新增页面后无需维护清单。
- `--local-css``add_style_tag` 追加本地样式(不替换线上 bundle),可安全验证
CSS 修复;JS 改动(如 Monaco 主题同步)需要重新构建并部署后再跑 `verify-toggle.py`
+53
View File
@@ -0,0 +1,53 @@
"""打开带持久化配置的 Edge 窗口,等待用户在 Halo 后台完成登录。
登录成功后会话保存在 profile 目录,供 scan-dark.py 复用。
"""
import argparse
import pathlib
import sys
import time
from playwright.sync_api import sync_playwright
DEFAULT_PROFILE = pathlib.Path(__file__).parent / ".browser-profile"
LOGIN_URL = "https://blog.liuhangyv.top/console/login"
TIMEOUT_S = 280
def main() -> int:
parser = argparse.ArgumentParser(description="登录 Halo 后台并保存会话")
parser.add_argument("--profile", default=str(DEFAULT_PROFILE))
args = parser.parse_args()
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
args.profile,
channel="msedge",
headless=False,
viewport={"width": 1600, "height": 950},
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto(LOGIN_URL)
print("浏览器窗口已打开,请在其中登录 Halo 后台...", flush=True)
deadline = time.time() + TIMEOUT_S
while time.time() < deadline:
try:
url = page.url
except Exception:
print("检测到窗口被关闭", flush=True)
return 2
if "/console" in url and "/login" not in url:
time.sleep(3)
print(f"检测到登录成功: {url}", flush=True)
ctx.close()
return 0
time.sleep(2)
print("等待超时,未检测到登录", flush=True)
ctx.close()
return 1
if __name__ == "__main__":
sys.exit(main())
+220
View File
@@ -0,0 +1,220 @@
"""Halo 后台暗色模式残留扫描器(正式回归工具)。
用法示例:
python scripts/scan-dark.py --local-css ui/build/dist/style.css --assert-zero
python scripts/scan-dark.py --mode light --pages /console/posts,/console/users
python scripts/scan-dark.py --profile workplace/pw-profile
"""
import argparse
import json
import pathlib
import re
import sys
import time
from playwright.sync_api import sync_playwright
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
BASE = "https://blog.liuhangyv.top"
DEFAULT_PROFILE = pathlib.Path(__file__).parent / ".browser-profile"
DEFAULT_OUTPUT = pathlib.Path(__file__).parent / "output"
SCAN_JS = r"""
() => {
const EXCLUDED_TAGS = new Set(['IMG', 'CANVAS', 'VIDEO', 'SVG', 'IFRAME']);
const results = [];
const seen = new Set();
const isVisible = (el) => {
const cs = getComputedStyle(el);
return cs.display !== 'none' && cs.visibility !== 'hidden' && +cs.opacity > 0.05;
};
const shortPath = (el) => {
const parts = [];
let cur = el;
for (let i = 0; i < 5 && cur && cur !== document.body; i++) {
let p = cur.tagName.toLowerCase();
if (cur.id) p += '#' + cur.id;
else if (typeof cur.className === 'string' && cur.className.trim()) {
p += '.' + cur.className.trim().split(/\s+/).slice(0, 2).join('.');
}
parts.unshift(p);
cur = cur.parentElement;
}
return parts.join(' > ');
};
document.querySelectorAll('body *').forEach(el => {
if (EXCLUDED_TAGS.has(el.tagName)) return;
if (!isVisible(el)) return;
const r = el.getBoundingClientRect();
if (r.width < 50 || r.height < 20) return;
const cs = getComputedStyle(el);
const issues = [];
const bg = cs.backgroundColor.match(/rgba?\(([\d.]+),\s*([\d.]+),\s*([\d.]+)(?:,\s*([\d.]+))?\)/);
if (bg && bg[4] !== '0' && +bg[1] > 235 && +bg[2] > 235 && +bg[3] > 235) {
issues.push('light-bg ' + cs.backgroundColor);
}
const hasText = [...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim());
const c = cs.color.match(/rgba?\(([\d.]+),\s*([\d.]+),\s*([\d.]+)(?:,\s*([\d.]+))?\)/);
if (hasText && c && (+c[4] ?? 1) > 0.05 && (+c[1] + +c[2] + +c[3]) / 3 < 95) {
issues.push('dark-text ' + cs.color);
}
if (!issues.length) return;
const cls = (typeof el.className === 'string' ? el.className : '').trim().replace(/\s+/g, ' ').slice(0, 150);
const key = el.tagName + '|' + cls + '|' + issues.join(',');
if (seen.has(key)) return;
seen.add(key);
results.push({
tag: el.tagName.toLowerCase(),
cls,
path: shortPath(el),
issues,
size: Math.round(r.width) + 'x' + Math.round(r.height),
text: (el.textContent || '').trim().slice(0, 40),
});
});
return results;
}
"""
def slug(route: str) -> str:
return re.sub(r"[^a-z0-9]+", "-", route.lower()).strip("-") or "root"
def main() -> int:
parser = argparse.ArgumentParser(description="Halo 后台暗色模式残留扫描")
parser.add_argument("--base", default=BASE)
parser.add_argument("--profile", default=str(DEFAULT_PROFILE))
parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT))
parser.add_argument("--mode", choices=["dark", "light"], default="dark")
parser.add_argument("--pages", default="", help="逗号分隔的路由子集,默认扫描全部")
parser.add_argument("--local-css", default="", help="注入本地构建的 style.css 做预部署验证")
parser.add_argument("--assert-zero", action="store_true", help="存在残留时以退出码 1 结束")
args = parser.parse_args()
output = pathlib.Path(args.output_dir)
shots = output / "shots"
shots.mkdir(parents=True, exist_ok=True)
if args.mode == "dark":
init_js = """
try { localStorage.setItem('halo-dark-mode-theme', 'dark'); } catch (e) {}
document.documentElement.setAttribute('data-halo-theme', 'dark');
"""
else:
init_js = """
try { localStorage.setItem('halo-dark-mode-theme', 'light'); } catch (e) {}
document.documentElement.removeAttribute('data-halo-theme');
"""
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
args.profile,
channel="msedge",
headless=True,
viewport={"width": 1600, "height": 950},
)
ctx.add_init_script(init_js)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto(args.base + "/console/dashboard", wait_until="domcontentloaded")
try:
page.wait_for_load_state("networkidle", timeout=10000)
except Exception:
pass
try:
page.wait_for_selector("#app > *, .sidebar, .main-content", timeout=20000)
except Exception:
pass
time.sleep(2)
if "/login" in page.url:
print("SESSION_EXPIRED 登录态失效,请先运行 scripts/login-wait.py 登录")
ctx.close()
return 3
routes = []
try:
routes = page.evaluate(
"""() => {
const app = document.querySelector('#app').__vue_app__;
const router = app.config.globalProperties.$router;
return router.getRoutes().map(r => r.path);
}"""
)
except Exception:
routes = page.evaluate(
"""() => [...new Set([...document.querySelectorAll('a[href]')]
.map(a => a.getAttribute('href'))
.filter(h => h && h.startsWith('/console')))]"""
)
routes = sorted(
{
r if r.startswith("/console") else "/console" + r
for r in routes
if r.startswith("/") and ":" not in r and r not in ("/", "/console")
}
)
editor = [r for r in routes if "editor" in r]
routes = [r for r in routes if "editor" not in r] + editor[:1]
if args.pages:
wanted = [r.strip() for r in args.pages.split(",") if r.strip()]
routes = [r for r in wanted if r.startswith("/console")]
print(f"模式={args.mode} 待扫描路由数={len(routes)}", flush=True)
all_results = {}
for route in routes:
try:
last_error = None
for attempt in range(3):
try:
page.goto(args.base + route, wait_until="domcontentloaded")
last_error = None
break
except Exception as e:
last_error = e
print(f"{route}: 第 {attempt + 1} 次导航失败,稍后重试", flush=True)
time.sleep(8)
if last_error:
raise last_error
try:
page.wait_for_load_state("networkidle", timeout=8000)
except Exception:
pass
# 限流时 Halo bundle 可能加载较慢,确保应用渲染完成再扫描
page.wait_for_selector("#app > *, .sidebar, .main-content", timeout=20000)
if args.local_css:
page.add_style_tag(path=args.local_css)
time.sleep(2)
if args.mode == "dark":
page.evaluate("document.documentElement.setAttribute('data-halo-theme','dark')")
else:
page.evaluate("document.documentElement.removeAttribute('data-halo-theme')")
time.sleep(0.3)
items = page.evaluate(SCAN_JS)
all_results[route] = items
page.screenshot(path=str(shots / (slug(route) + ".png")))
print(f"{route}: {len(items)} 处疑似残留", flush=True)
except Exception as e:
all_results[route] = {"error": str(e)[:200]}
print(f"{route}: 扫描失败 {e}", flush=True)
out_json = output / "scan-results.json"
out_json.write_text(json.dumps(all_results, ensure_ascii=False, indent=2), encoding="utf-8")
ctx.close()
total = sum(
len(v) for v in all_results.values() if isinstance(v, list)
)
dirty = sum(1 for v in all_results.values() if isinstance(v, list) and v)
print(f"DONE 残留条目={total} 残留路由={dirty} 结果文件={out_json}", flush=True)
if args.assert_zero and dirty:
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+76
View File
@@ -0,0 +1,76 @@
"""验证侧边栏主题切换:点击后检查 data-halo-theme、localStorage 与 Monaco 主题同步。
部署新版插件后运行可获得完整结果;预部署阶段 Monaco 断言会提示跳过。
"""
import argparse
import pathlib
import sys
import time
from playwright.sync_api import sync_playwright
DEFAULT_PROFILE = pathlib.Path(__file__).parent / ".browser-profile"
BASE = "https://blog.liuhangyv.top"
def main() -> int:
parser = argparse.ArgumentParser(description="验证主题切换运行时行为")
parser.add_argument("--base", default=BASE)
parser.add_argument("--profile", default=str(DEFAULT_PROFILE))
args = parser.parse_args()
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
args.profile, channel="msedge", headless=True,
viewport={"width": 1600, "height": 950},
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto(args.base + "/console/dashboard", wait_until="domcontentloaded")
try:
page.wait_for_load_state("networkidle", timeout=10000)
except Exception:
pass
time.sleep(3)
if "/login" in page.url:
print("SESSION_EXPIRED 请先运行 scripts/login-wait.py")
ctx.close()
return 3
toggle = page.locator(".theme-toggle")
toggle.wait_for(state="visible", timeout=15000)
before = page.evaluate("() => ({ attr: document.documentElement.getAttribute('data-halo-theme'), stored: localStorage.getItem('halo-dark-mode-theme') })")
toggle.click()
time.sleep(0.5)
after = page.evaluate("() => ({ attr: document.documentElement.getAttribute('data-halo-theme'), stored: localStorage.getItem('halo-dark-mode-theme') })")
toggle.click()
time.sleep(0.5)
restored = page.evaluate("() => ({ attr: document.documentElement.getAttribute('data-halo-theme'), stored: localStorage.getItem('halo-dark-mode-theme') })")
attr_flipped = before["attr"] != after["attr"] and before["attr"] == restored["attr"]
stored_flipped = before["stored"] != after["stored"] and before["stored"] == restored["stored"]
print(f"初始: {before}")
print(f"切换: {after}")
print(f"还原: {restored}")
print(f"属性翻转: {'PASS' if attr_flipped else 'FAIL'} 存储翻转: {'PASS' if stored_flipped else 'FAIL'}")
page.goto(args.base + "/console/log-viewer", wait_until="domcontentloaded")
try:
page.wait_for_load_state("networkidle", timeout=6000)
except Exception:
pass
time.sleep(2)
has_monaco = page.locator(".monaco-editor").count() > 0
if has_monaco:
dark_attr = page.evaluate("document.documentElement.getAttribute('data-halo-theme')") == "dark"
theme_cls = page.evaluate("() => { const el = document.querySelector('.monaco-editor'); return el ? el.className : '' }")
monaco_dark = "vs-dark" in theme_cls
print(f"Monaco 实例: 存在 | 属性dark={dark_attr} | 主题类含vs-dark={monaco_dark} | class={theme_cls[:60]}")
print(f"Monaco 主题同步: {'PASS' if (dark_attr and monaco_dark) or (not dark_attr and not monaco_dark) else 'FAIL(可能部署的还是旧版 JS'}")
else:
print("Monaco 实例: 未检测到,跳过主题断言(日志页面可能未加载或路由不同)")
ctx.close()
return 0 if attr_flipped and stored_flipped else 1
if __name__ == "__main__":
sys.exit(main())
@@ -10,7 +10,7 @@ import run.halo.app.plugin.PluginContext;
* <p>Only one main class extending {@link BasePlugin} is allowed per plugin.</p>
*
* @author LHY
* @since 1.0.1
* @since 1.0.3
*/
@Component
public class DarkModePlugin extends BasePlugin {
+5
View File
@@ -5,6 +5,11 @@ plugins {
group 'run.halo.darkmode.ui'
node {
// 使用系统安装的 Node.js,避免 Gradle 下载的本地 Node 目录缺失时失败
download = false
}
tasks.register('pnpmBuild', PnpmTask) {
group = 'build'
description = 'Build the UI project using pnpm'
+3 -1
View File
@@ -23,10 +23,10 @@
"@halo-dev/ui-shared": "^2.25.1",
"axios": "^1.13.5",
"canvas-confetti": "^1.9.3",
"darkreader": "file:./vendor/darkreader",
"vue": "^3.5.28"
},
"devDependencies": {
"vite": "^8.0.16",
"@halo-dev/ui-plugin-bundler-kit": "^2.25.1",
"@iconify-json/ri": "^1.2.10",
"@tsconfig/node20": "^20.1.6",
@@ -41,6 +41,7 @@
"eslint": "^9.29.0",
"eslint-plugin-oxlint": "^0.16.12",
"eslint-plugin-vue": "~10.0.1",
"jiti": "^2.7.0",
"jsdom": "^26.1.0",
"npm-run-all2": "^7.0.2",
"oxlint": "^0.16.12",
@@ -48,6 +49,7 @@
"sass": "^1.89.2",
"typescript": "~5.8.3",
"unplugin-icons": "^23.0.1",
"vite": "^8.0.16",
"vitest": "^4.1.0",
"vue-tsc": "^3.3.3"
},
+98 -78
View File
@@ -13,23 +13,26 @@ importers:
version: 2.25.2(axios@1.19.0)
'@halo-dev/components':
specifier: ^2.25.1
version: 2.25.2(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))
version: 2.25.2(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))
'@halo-dev/ui-shared':
specifier: ^2.25.1
version: 2.25.2(@tiptap/pm@3.29.2)(axios@1.19.0)(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))
version: 2.25.2(@tiptap/pm@3.29.2)(axios@1.19.0)(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))
axios:
specifier: ^1.13.5
version: 1.19.0
canvas-confetti:
specifier: ^1.9.3
version: 1.9.4
darkreader:
specifier: file:./vendor/darkreader
version: file:vendor/darkreader
vue:
specifier: ^3.5.28
version: 3.5.41(typescript@5.8.3)
devDependencies:
'@halo-dev/ui-plugin-bundler-kit':
specifier: ^2.25.1
version: 2.25.2(@rsbuild/core@2.1.10)(@rsbuild/plugin-vue@2.0.1(@rsbuild/core@2.1.10)(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(vue@3.5.41(typescript@5.8.3)))(@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(axios@1.19.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))
version: 2.25.2(@rsbuild/core@2.1.10)(@rsbuild/plugin-vue@2.0.1(@rsbuild/core@2.1.10)(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(vue@3.5.41(typescript@5.8.3)))(@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(axios@1.19.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))
'@iconify-json/ri':
specifier: ^1.2.10
version: 1.2.10
@@ -47,13 +50,13 @@ importers:
version: 24.13.3
'@vitest/eslint-plugin':
specifier: ^1.2.7
version: 1.6.26(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)))
version: 1.6.26(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)))
'@vue/eslint-config-prettier':
specifier: ^10.2.0
version: 10.2.0(eslint@9.39.5)(prettier@3.9.6)
version: 10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6)
'@vue/eslint-config-typescript':
specifier: ^14.5.1
version: 14.9.0(eslint-plugin-vue@10.0.1(eslint@9.39.5)(vue-eslint-parser@10.4.1(eslint@9.39.5)))(eslint@9.39.5)(typescript@5.8.3)
version: 14.9.0(eslint-plugin-vue@10.0.1(eslint@9.39.5(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0))))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
'@vue/test-utils':
specifier: ^2.4.6
version: 2.4.11(@vue/compiler-dom@3.5.41)(@vue/server-renderer@3.5.41)(vue@3.5.41(typescript@5.8.3))
@@ -62,13 +65,16 @@ importers:
version: 0.7.0(typescript@5.8.3)(vue@3.5.41(typescript@5.8.3))
eslint:
specifier: ^9.29.0
version: 9.39.5
version: 9.39.5(jiti@2.7.0)
eslint-plugin-oxlint:
specifier: ^0.16.12
version: 0.16.12
eslint-plugin-vue:
specifier: ~10.0.1
version: 10.0.1(eslint@9.39.5)(vue-eslint-parser@10.4.1(eslint@9.39.5))
version: 10.0.1(eslint@9.39.5(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0)))
jiti:
specifier: ^2.7.0
version: 2.7.0
jsdom:
specifier: ^26.1.0
version: 26.1.0
@@ -92,10 +98,10 @@ importers:
version: 23.0.1(@vue/compiler-sfc@3.5.41)
vite:
specifier: ^8.0.16
version: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
version: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
vitest:
specifier: ^4.1.0
version: 4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))
version: 4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))
vue-tsc:
specifier: ^3.3.3
version: 3.3.9(typescript@5.8.3)
@@ -1079,6 +1085,9 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
darkreader@file:vendor/darkreader:
resolution: {directory: vendor/darkreader, type: directory}
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
@@ -1454,6 +1463,10 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jiti@2.7.0:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
js-beautify@1.15.4:
resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==}
engines: {node: '>=14'}
@@ -2427,9 +2440,9 @@ snapshots:
tslib: 2.8.1
optional: true
'@eslint-community/eslint-utils@4.10.1(eslint@9.39.5)':
'@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))':
dependencies:
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -2488,33 +2501,33 @@ snapshots:
axios: 1.19.0
qs: 6.15.3
'@halo-dev/components@2.25.2(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))':
'@halo-dev/components@2.25.2(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))':
dependencies:
floating-vue: 5.2.2(vue@3.5.41(typescript@5.8.3))
vue: 3.5.41(typescript@5.8.3)
vue-router: 5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))
vue-router: 5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))
transitivePeerDependencies:
- '@nuxt/kit'
'@halo-dev/ui-plugin-bundler-kit@2.25.2(@rsbuild/core@2.1.10)(@rsbuild/plugin-vue@2.0.1(@rsbuild/core@2.1.10)(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(vue@3.5.41(typescript@5.8.3)))(@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(axios@1.19.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))':
'@halo-dev/ui-plugin-bundler-kit@2.25.2(@rsbuild/core@2.1.10)(@rsbuild/plugin-vue@2.0.1(@rsbuild/core@2.1.10)(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(vue@3.5.41(typescript@5.8.3)))(@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(axios@1.19.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))':
dependencies:
'@halo-dev/api-client': 2.25.2(axios@1.19.0)
'@rsbuild/core': 2.1.10
'@rsbuild/plugin-vue': 2.0.1(@rsbuild/core@2.1.10)(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(vue@3.5.41(typescript@5.8.3))
'@vitejs/plugin-vue': 6.0.8(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))
'@vitejs/plugin-vue': 6.0.8(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))
js-yaml: 4.3.1
semver: 7.8.5
vite: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
vite: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
transitivePeerDependencies:
- axios
'@halo-dev/ui-shared@2.25.2(@tiptap/pm@3.29.2)(axios@1.19.0)(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))':
'@halo-dev/ui-shared@2.25.2(@tiptap/pm@3.29.2)(axios@1.19.0)(vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)))(vue@3.5.41(typescript@5.8.3))':
dependencies:
'@halo-dev/api-client': 2.25.2(axios@1.19.0)
'@tiptap/core': 3.29.2(@tiptap/pm@3.29.2)
mitt: 3.0.1
vue: 3.5.41(typescript@5.8.3)
vue-router: 5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))
vue-router: 5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))
transitivePeerDependencies:
- '@tiptap/pm'
- axios
@@ -2870,15 +2883,15 @@ snapshots:
'@types/tough-cookie@4.0.5': {}
'@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3)':
'@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/parser': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
'@typescript-eslint/scope-manager': 8.66.0
'@typescript-eslint/type-utils': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/type-utils': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.66.0
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
ignore: 7.0.6
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@5.8.3)
@@ -2886,14 +2899,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.8.3)':
'@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.66.0
'@typescript-eslint/types': 8.66.0
'@typescript-eslint/typescript-estree': 8.66.0(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.66.0
debug: 4.4.3
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -2916,13 +2929,13 @@ snapshots:
dependencies:
typescript: 5.8.3
'@typescript-eslint/type-utils@8.66.0(eslint@9.39.5)(typescript@5.8.3)':
'@typescript-eslint/type-utils@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/types': 8.66.0
'@typescript-eslint/typescript-estree': 8.66.0(typescript@5.8.3)
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
debug: 4.4.3
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
ts-api-utils: 2.5.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
@@ -2945,13 +2958,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.66.0(eslint@9.39.5)(typescript@5.8.3)':
'@typescript-eslint/utils@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)':
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5)
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
'@typescript-eslint/scope-manager': 8.66.0
'@typescript-eslint/types': 8.66.0
'@typescript-eslint/typescript-estree': 8.66.0(typescript@5.8.3)
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -2961,21 +2974,21 @@ snapshots:
'@typescript-eslint/types': 8.66.0
eslint-visitor-keys: 5.0.1
'@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))':
'@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3))':
dependencies:
'@rolldown/pluginutils': 1.0.1
vite: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
vite: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
vue: 3.5.41(typescript@5.8.3)
'@vitest/eslint-plugin@1.6.26(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)))':
'@vitest/eslint-plugin@1.6.26(@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)(vitest@4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)))':
dependencies:
'@typescript-eslint/scope-manager': 8.66.0
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
eslint: 9.39.5
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
eslint: 9.39.5(jiti@2.7.0)
optionalDependencies:
'@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
typescript: 5.8.3
vitest: 4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))
vitest: 4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))
transitivePeerDependencies:
- supports-color
@@ -2988,13 +3001,13 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.1
'@vitest/mocker@4.1.10(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))':
'@vitest/mocker@4.1.10(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))':
dependencies:
'@vitest/spy': 4.1.10
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
vite: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
'@vitest/pretty-format@4.1.10':
dependencies:
@@ -3085,23 +3098,23 @@ snapshots:
'@vue/devtools-shared@8.2.1': {}
'@vue/eslint-config-prettier@10.2.0(eslint@9.39.5)(prettier@3.9.6)':
'@vue/eslint-config-prettier@10.2.0(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6)':
dependencies:
eslint: 9.39.5
eslint-config-prettier: 10.1.8(eslint@9.39.5)
eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6)
eslint: 9.39.5(jiti@2.7.0)
eslint-config-prettier: 10.1.8(eslint@9.39.5(jiti@2.7.0))
eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6)
prettier: 3.9.6
transitivePeerDependencies:
- '@types/eslint'
'@vue/eslint-config-typescript@14.9.0(eslint-plugin-vue@10.0.1(eslint@9.39.5)(vue-eslint-parser@10.4.1(eslint@9.39.5)))(eslint@9.39.5)(typescript@5.8.3)':
'@vue/eslint-config-typescript@14.9.0(eslint-plugin-vue@10.0.1(eslint@9.39.5(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0))))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)':
dependencies:
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
eslint: 9.39.5
eslint-plugin-vue: 10.0.1(eslint@9.39.5)(vue-eslint-parser@10.4.1(eslint@9.39.5))
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
eslint: 9.39.5(jiti@2.7.0)
eslint-plugin-vue: 10.0.1(eslint@9.39.5(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0)))
fast-glob: 3.3.3
typescript-eslint: 8.66.0(eslint@9.39.5)(typescript@5.8.3)
vue-eslint-parser: 10.4.1(eslint@9.39.5)
typescript-eslint: 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
vue-eslint-parser: 10.4.1(eslint@9.39.5(jiti@2.7.0))
optionalDependencies:
typescript: 5.8.3
transitivePeerDependencies:
@@ -3307,6 +3320,8 @@ snapshots:
csstype@3.2.3: {}
darkreader@file:vendor/darkreader: {}
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
@@ -3366,32 +3381,32 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-prettier@10.1.8(eslint@9.39.5):
eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)):
dependencies:
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
eslint-plugin-oxlint@0.16.12:
dependencies:
jsonc-parser: 3.3.1
eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6):
eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0))(prettier@3.9.6):
dependencies:
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
prettier: 3.9.6
prettier-linter-helpers: 1.0.1
synckit: 0.11.13
optionalDependencies:
eslint-config-prettier: 10.1.8(eslint@9.39.5)
eslint-config-prettier: 10.1.8(eslint@9.39.5(jiti@2.7.0))
eslint-plugin-vue@10.0.1(eslint@9.39.5)(vue-eslint-parser@10.4.1(eslint@9.39.5)):
eslint-plugin-vue@10.0.1(eslint@9.39.5(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0))):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5)
eslint: 9.39.5
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
eslint: 9.39.5(jiti@2.7.0)
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.1.4
semver: 7.8.5
vue-eslint-parser: 10.4.1(eslint@9.39.5)
vue-eslint-parser: 10.4.1(eslint@9.39.5(jiti@2.7.0))
xml-name-validator: 4.0.0
eslint-scope@8.4.0:
@@ -3412,9 +3427,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@9.39.5:
eslint@9.39.5(jiti@2.7.0):
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5)
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.21.2
'@eslint/config-helpers': 0.4.2
@@ -3448,6 +3463,8 @@ snapshots:
minimatch: 3.1.5
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
jiti: 2.7.0
transitivePeerDependencies:
- supports-color
@@ -3676,6 +3693,8 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jiti@2.7.0: {}
js-beautify@1.15.4:
dependencies:
config-chain: 1.1.13
@@ -4259,13 +4278,13 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript-eslint@8.66.0(eslint@9.39.5)(typescript@5.8.3):
typescript-eslint@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.8.3))(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/parser': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
'@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
'@typescript-eslint/parser': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
'@typescript-eslint/typescript-estree': 8.66.0(typescript@5.8.3)
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5)(typescript@5.8.3)
eslint: 9.39.5
'@typescript-eslint/utils': 8.66.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.8.3)
eslint: 9.39.5(jiti@2.7.0)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
@@ -4298,7 +4317,7 @@ snapshots:
picomatch: 4.0.5
webpack-virtual-modules: 0.6.2
unplugin@3.3.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)):
unplugin@3.3.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)):
dependencies:
'@jridgewell/remapping': 2.3.5
picomatch: 4.0.5
@@ -4306,7 +4325,7 @@ snapshots:
optionalDependencies:
'@rspack/core': 2.1.8(@swc/helpers@0.5.23)
rolldown: 1.2.3
vite: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
vite: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
uri-js@4.4.1:
dependencies:
@@ -4314,7 +4333,7 @@ snapshots:
util-deprecate@1.0.2: {}
vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0):
vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.5
@@ -4324,13 +4343,14 @@ snapshots:
optionalDependencies:
'@types/node': 24.13.3
fsevents: 2.3.3
jiti: 2.7.0
sass: 1.102.0
yaml: 2.9.0
vitest@4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)):
vitest@4.1.10(@types/node@24.13.3)(jsdom@26.1.0)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))
'@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))
'@vitest/pretty-format': 4.1.10
'@vitest/runner': 4.1.10
'@vitest/snapshot': 4.1.10
@@ -4347,7 +4367,7 @@ snapshots:
tinyexec: 1.3.0
tinyglobby: 0.2.17
tinyrainbow: 3.1.1
vite: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
vite: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 24.13.3
@@ -4359,10 +4379,10 @@ snapshots:
vue-component-type-helpers@3.3.9: {}
vue-eslint-parser@10.4.1(eslint@9.39.5):
vue-eslint-parser@10.4.1(eslint@9.39.5(jiti@2.7.0)):
dependencies:
debug: 4.4.3
eslint: 9.39.5
eslint: 9.39.5(jiti@2.7.0)
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
espree: 11.2.0
@@ -4375,7 +4395,7 @@ snapshots:
dependencies:
vue: 3.5.41(typescript@5.8.3)
vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)):
vue-router@5.2.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(@vue/compiler-sfc@3.5.41)(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))(vue@3.5.41(typescript@5.8.3)):
dependencies:
'@babel/generator': 8.0.0
'@vue-macros/common': 3.1.4(vue@3.5.41(typescript@5.8.3))
@@ -4392,13 +4412,13 @@ snapshots:
picomatch: 4.0.5
scule: 1.3.0
tinyglobby: 0.2.17
unplugin: 3.3.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0))
unplugin: 3.3.0(@rspack/core@2.1.8(@swc/helpers@0.5.23))(rolldown@1.2.3)(vite@8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0))
unplugin-utils: 0.3.2
vue: 3.5.41(typescript@5.8.3)
yaml: 2.9.0
optionalDependencies:
'@vue/compiler-sfc': 3.5.41
vite: 8.2.0(@types/node@24.13.3)(sass@1.102.0)(yaml@2.9.0)
vite: 8.2.0(@types/node@24.13.3)(jiti@2.7.0)(sass@1.102.0)(yaml@2.9.0)
transitivePeerDependencies:
- '@farmfe/core'
- '@rspack/core'
+48
View File
@@ -0,0 +1,48 @@
import { disable, enable } from 'darkreader'
import { watch } from 'vue'
import { useDarkMode } from './composables/useDarkMode'
/**
* Dark Reader 引擎参数。
* brightness/contrast 保持默认观感,背景色与现有 Halo 暗色变量接近,
* 让 Dark Reader 补全第三方插件页面时不会显得突兀。
*/
const DARK_READER_THEME = {
brightness: 100,
contrast: 90,
grayscale: 0,
sepia: 0,
darkSchemeBackgroundColor: '#181b20',
darkSchemeTextColor: '#e8e6e3',
scrollbarColor: '#3a3f4a',
selectionColor: '#2f6f7a',
styleSystemControls: true,
} as const
let initialized = false
/**
* 初始化 Dark Reader 通用暗色引擎。
* 监听 useDarkMode 的 isDark 状态,深色时启用,浅色时关闭。
*/
export function initDarkReaderEngine(): void {
if (initialized) return
initialized = true
const { isDark } = useDarkMode()
watch(
isDark,
(dark) => {
try {
if (dark) {
enable(DARK_READER_THEME)
} else {
disable()
}
} catch (error) {
console.error('[dark-mode] Dark Reader 引擎异常', error)
}
},
{ immediate: true },
)
}
+5 -1
View File
@@ -2,10 +2,14 @@ import { definePlugin } from '@halo-dev/ui-shared'
import { IconPalette } from '@halo-dev/components'
import { markRaw } from 'vue'
import './styles/index.css'
import { initDarkReaderEngine } from './darkreader-engine'
import { injectThemeToggle } from './injector'
import { initMonacoThemeSync } from './monaco-theme'
// 在插件加载后将切换器注入到侧边栏
// 在插件加载后将切换器注入到侧边栏,并让 Monaco 跟随主题
injectThemeToggle()
initMonacoThemeSync()
initDarkReaderEngine()
export default definePlugin({
components: {},
+43
View File
@@ -0,0 +1,43 @@
/**
* 让 Monaco Editor(日志查看器)跟随 Halo 暗色模式。
* 优先调用 Monaco 官方主题 APICSS 兜底见 styles/overrides/editor.css。
*/
type MonacoLike = {
editor?: {
setTheme: (name: string) => void
}
}
const DARK_THEME = 'vs-dark'
const LIGHT_THEME = 'vs'
function getMonaco(): MonacoLike | undefined {
return (window as unknown as { monaco?: MonacoLike }).monaco
}
function applyMonacoTheme(): void {
const monaco = getMonaco()
if (!monaco?.editor?.setTheme) return
const isDark = document.documentElement.getAttribute('data-halo-theme') === 'dark'
monaco.editor.setTheme(isDark ? DARK_THEME : LIGHT_THEME)
}
/** 初始化 Monaco 主题同步:立即应用,并监听属性变化与实例懒加载。 */
export function initMonacoThemeSync(): void {
applyMonacoTheme()
const attributeObserver = new MutationObserver(() => applyMonacoTheme())
attributeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-halo-theme'],
})
// 日志页面可能懒加载 Monaco,实例渲染后再补一次
const instanceObserver = new MutationObserver(() => {
if (document.querySelector('.monaco-editor')) {
applyMonacoTheme()
}
})
instanceObserver.observe(document.body, { childList: true, subtree: true })
}
+1
View File
@@ -6,6 +6,7 @@
@import './variables.css';
@import './overrides/halo-core.css';
@import './overrides/plugin-pages.css';
@import './overrides/uno-fallback.css';
@import './overrides/utilities.css';
@import './overrides/layout.css';
@import './overrides/components.css';
+15 -87
View File
@@ -1,73 +1,21 @@
/* ============================================================
Halo Dark Mode — @halo-dev/components 组件库覆盖
覆盖 Halo 组件库中的 VCard, VModal, VDropdown, VTag 等
Halo Dark Mode — Halo 组件库覆盖(已清理)
已删除 Halo 2.25 中不存在的 Vuetify 类(.v-card 等),
真实 DOM 类覆盖请优先维护 halo-core.css。
============================================================ */
[data-halo-theme="dark"] {
/* ===== 卡片 VCard ===== */
.v-card,
[class*="v-card"] {
background-color: var(--halo-bg-card);
border-color: var(--halo-border-base);
color: var(--halo-text-primary);
}
/* ===== 模态框 VModal ===== */
.v-modal,
.modal-container,
[class*="modal"] {
background-color: var(--halo-bg-card);
color: var(--halo-text-primary);
}
/* ===== 下拉菜单 VDropdown ===== */
.v-dropdown,
.dropdown-menu,
[class*="dropdown"] {
background-color: var(--halo-bg-dropdown);
border-color: var(--halo-border-base);
color: var(--halo-text-primary);
}
.v-dropdown-item:hover,
.dropdown-item:hover {
background-color: var(--halo-bg-hover);
}
/* ===== 提示框 VTooltip ===== */
.v-tooltip,
[class*="tooltip"] {
background-color: var(--halo-bg-tooltip);
color: var(--halo-text-inverse);
}
/* ===== 标签/徽章 VTag, VBadge ===== */
.v-tag,
[class*="tag"],
.v-badge,
[class*="badge"] {
background-color: var(--halo-tag-bg);
color: var(--halo-tag-text);
}
/* ===== 按钮 ===== */
.btn-default,
.btn-secondary,
button:not([class*="btn-primary"]):not([class*="btn-danger"]) {
background-color: var(--halo-bg-card);
color: var(--halo-text-primary);
border-color: var(--halo-border-base);
}
.btn-default:hover,
.btn-secondary:hover {
background-color: var(--halo-bg-hover);
/* ===== 模态框(具体类 + !important,防被核心 scoped 样式覆盖) ===== */
.modal-content,
.modal-header,
.modal-body,
.modal-footer {
background-color: var(--halo-bg-card) !important;
color: var(--halo-text-primary) !important;
}
/* ===== 表格 VTable ===== */
table,
.v-table,
[class*="table"] {
table {
background-color: var(--halo-bg-card);
color: var(--halo-text-primary);
}
@@ -94,49 +42,29 @@
}
/* ===== 分页 ===== */
.pagination,
.v-pagination {
.pagination {
color: var(--halo-text-secondary);
}
.pagination .active,
.v-pagination .active {
.pagination .active {
background-color: var(--halo-accent-primary);
color: var(--halo-accent-primary-text);
}
/* ===== 面包屑 ===== */
.breadcrumb,
.v-breadcrumb {
.breadcrumb {
color: var(--halo-text-secondary);
}
.breadcrumb a,
.v-breadcrumb a {
.breadcrumb a {
color: var(--halo-text-link);
}
/* ===== Toast / 通知 ===== */
.v-toast,
.toast-notification,
[class*="toast"] {
background-color: var(--halo-bg-card);
color: var(--halo-text-primary);
border-color: var(--halo-border-base);
}
/* ===== 步骤条 ===== */
.v-steps,
[class*="steps"] {
color: var(--halo-text-secondary);
}
/* ===== 开关 VSwitch ===== */
.v-switch-track {
background-color: var(--halo-bg-disabled);
}
.v-switch-track[aria-checked="true"] {
background-color: var(--halo-accent-primary);
}
}
+29
View File
@@ -232,3 +232,32 @@
background-color: oklch(30% 0.04 160 / 50%);
}
}
/* ===== Monaco Editor(日志查看器,JS 主题优先,此处仅兜底) ===== */
html[data-halo-theme="dark"] .monaco-editor.vs,
html[data-halo-theme="dark"] .monaco-editor.vs .margin,
html[data-halo-theme="dark"] .monaco-editor.vs .lines-content,
html[data-halo-theme="dark"] .monaco-editor.vs .monaco-scrollable-element {
background-color: var(--halo-bg-input) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .monaco-editor.vs .line-numbers {
color: var(--halo-text-tertiary) !important;
}
html[data-halo-theme="dark"] .monaco-editor.vs .cursor {
border-color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .monaco-editor.vs .selected-text,
html[data-halo-theme="dark"] .monaco-editor.vs .view-overlays .selected-text {
background-color: oklch(30% 0.04 160 / 50%) !important;
}
html[data-halo-theme="dark"] .monaco-editor.vs .find-widget,
html[data-halo-theme="dark"] .monaco-editor.vs .monaco-hover {
background-color: var(--halo-bg-dropdown) !important;
color: var(--halo-text-primary) !important;
}
/* ===== 编辑器链接(覆盖通用链接色规则) ===== */
html[data-halo-theme="dark"] .ProseMirror a,
html[data-halo-theme="dark"] .tiptap a {
color: var(--halo-text-link) !important;
}
+13
View File
@@ -87,3 +87,16 @@
border-color: var(--halo-border-base);
}
}
/* ===== 强表单覆盖(scoped 哈希类特异性更高,需 html 前缀 + !important ===== */
html[data-halo-theme="dark"] input:not([type="checkbox"]):not([type="radio"]):not([type="color"]),
html[data-halo-theme="dark"] textarea,
html[data-halo-theme="dark"] select {
background-color: var(--halo-bg-input) !important;
color: var(--halo-text-primary) !important;
border-color: var(--halo-border-input) !important;
}
html[data-halo-theme="dark"] input:not([type="checkbox"]):not([type="radio"]):not([type="color"])::placeholder,
html[data-halo-theme="dark"] textarea::placeholder,
html[data-halo-theme="dark"] select::placeholder {
color: var(--halo-text-tertiary) !important;
}
+54 -18
View File
@@ -1,11 +1,14 @@
/* ===== 根元素背景(html 自身携带属性,需独立选择器) ===== */
html[data-halo-theme="dark"] {
background-color: var(--halo-bg-body) !important;
}
/* ============================================================
Halo Dark Mode — Halo 核心 BEM 类覆盖
基于 Halo 2.25 真实 DOM 扫描(demo.halocms.site)得出的选择器
============================================================ */
[data-halo-theme="dark"] {
/* ===== 页面根背景(body 溢出时兜底) ===== */
html,
/* ===== 页面根背景(body 溢出时兜底html 见文件顶部独立规则 ===== */
body {
background-color: var(--halo-bg-body) !important;
color: var(--halo-text-primary);
@@ -100,14 +103,6 @@
.entity-field-description {
color: var(--halo-text-secondary);
}
.entity-field-title a,
.entity-field-title-body a {
color: var(--halo-text-primary);
}
.entity-field-title a:hover,
.entity-field-title-body a:hover {
color: var(--halo-text-link);
}
/* ===== 分页 ===== */
.pagination {
@@ -131,8 +126,7 @@
/* ===== 标签/徽章(BEM ===== */
.tag-wrapper,
.tag-default,
.tag-pill,
[class*="tag-"] {
.tag-pill {
background-color: var(--halo-bg-hover);
color: var(--halo-text-secondary);
}
@@ -206,10 +200,10 @@
border-color: var(--halo-border-light);
}
.description-item__label {
color: var(--halo-text-secondary);
color: var(--halo-text-secondary) !important;
}
.description-item__content {
color: var(--halo-text-primary);
color: var(--halo-text-primary) !important;
}
/* ===== 已选指示器 ===== */
@@ -265,8 +259,50 @@
border-color: var(--halo-border-base);
}
/* ===== 通用链接 ===== */
a:not([class]) {
color: var(--halo-text-link);
}
}
/* ===== 侧边栏激活菜单(Halo 核心 .menu-item-title.active ===== */
html[data-halo-theme="dark"] .menu-item-title.active,
html[data-halo-theme="dark"] .menu-item-title.active:hover {
background-color: var(--halo-menu-item-active) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .menu-item-title.active::after {
background: var(--halo-accent-primary) !important;
}
/* ===== 空状态标题 ===== */
html[data-halo-theme="dark"] .empty-wrapper .empty-title {
color: var(--halo-text-secondary) !important;
}
/* ===== 提示条(Alert ===== */
html[data-halo-theme="dark"] .alert-wrapper {
background-color: var(--halo-bg-card) !important;
border-color: var(--halo-border-base) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .alert-wrapper .alert-title,
html[data-halo-theme="dark"] .alert-wrapper .alert-description,
html[data-halo-theme="dark"] .alert-wrapper .alert-icon {
color: var(--halo-text-secondary) !important;
}
html[data-halo-theme="dark"] .alert-wrapper.alert-warning {
border-color: oklch(40% 0.12 85) !important;
}
html[data-halo-theme="dark"] .alert-wrapper.alert-error {
border-color: oklch(40% 0.17 25) !important;
}
html[data-halo-theme="dark"] .alert-wrapper.alert-success {
border-color: oklch(40% 0.14 150) !important;
}
/* ===== 实体列表标题链接(覆盖通用链接色,hover 变链接色) ===== */
html[data-halo-theme="dark"] .entity-field-title a,
html[data-halo-theme="dark"] .entity-field-title-body a {
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .entity-field-title a:hover,
html[data-halo-theme="dark"] .entity-field-title-body a:hover {
color: var(--halo-text-link) !important;
}
+1 -2
View File
@@ -55,8 +55,7 @@
}
/* ===== 页面标题 ===== */
.page-title,
.v-card-title {
.page-title {
color: var(--halo-text-primary);
}
}
+134
View File
@@ -247,3 +247,137 @@
color: var(--halo-text-primary) !important;
}
}
/* ===== AstraHub / 心愿便签(共用 ah-* UI ===== */
html[data-halo-theme="dark"] .ah-card,
html[data-halo-theme="dark"] .ah-topbar,
html[data-halo-theme="dark"] .ah-topbar-search,
html[data-halo-theme="dark"] .ah-float-nav {
background-color: var(--halo-bg-card) !important;
border-color: var(--halo-border-base) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .ah-topbar-search {
background-color: var(--halo-bg-input) !important;
}
html[data-halo-theme="dark"] .ah-topbar-brand,
html[data-halo-theme="dark"] .planet-hero-title {
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .ah-topbar-search input {
background-color: transparent !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .planet-links-more {
background-color: var(--halo-bg-card) !important;
color: var(--halo-text-secondary) !important;
}
/* ===== RSS 订阅(links/rss ===== */
html[data-halo-theme="dark"] .subscription-panel,
html[data-halo-theme="dark"] .feed-main,
html[data-halo-theme="dark"] .feed-toolbar,
html[data-halo-theme="dark"] .feed-stream,
html[data-halo-theme="dark"] .feed-stream__footer {
background-color: var(--halo-bg-card) !important;
border-color: var(--halo-border-base) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .feed-status-tabs {
background-color: var(--halo-bg-hover) !important;
color: var(--halo-text-secondary) !important;
}
html[data-halo-theme="dark"] .feed-brief__title,
html[data-halo-theme="dark"] .feed-stream p,
html[data-halo-theme="dark"] .feed-stream a,
html[data-halo-theme="dark"] .subscription-panel a,
html[data-halo-theme="dark"] .subscription-panel span {
color: var(--halo-text-primary) !important;
}
/* ===== 私密文章插件 ===== */
html[data-halo-theme="dark"] .focus-card,
html[data-halo-theme="dark"] .overview-card,
html[data-halo-theme="dark"] .list-card,
html[data-halo-theme="dark"] .empty-state {
background-color: var(--halo-bg-card) !important;
border-color: var(--halo-border-base) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .overview-card h2,
html[data-halo-theme="dark"] .page-shell h2,
html[data-halo-theme="dark"] .overview-stats dd,
html[data-halo-theme="dark"] .overview-stats dt {
color: var(--halo-text-primary) !important;
}
/* ===== AI 评论自动处理插件 ===== */
html[data-halo-theme="dark"] .settings-container,
html[data-halo-theme="dark"] .setting-panel,
html[data-halo-theme="dark"] .tabs-wrap,
html[data-halo-theme="dark"] .sidebar-card,
html[data-halo-theme="dark"] .list-empty,
html[data-halo-theme="dark"] .reply-card {
background-color: var(--halo-bg-card) !important;
border-color: var(--halo-border-base) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .form-row,
html[data-halo-theme="dark"] .list-col,
html[data-halo-theme="dark"] .panel-header,
html[data-halo-theme="dark"] .card-footer {
background-color: var(--halo-bg-hover) !important;
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .section-header__text h3,
html[data-halo-theme="dark"] .sidebar-header h4,
html[data-halo-theme="dark"] .panel-header h3,
html[data-halo-theme="dark"] .reply-card h3 {
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .wake-word-tag {
background-color: var(--halo-bg-hover) !important;
color: var(--halo-text-secondary) !important;
}
/* ===== 数据工坊 / 代码注入器等工具页语义容器 ===== */
html[data-halo-theme="dark"] .data-studio-card-body,
html[data-halo-theme="dark"] .injector-view-card-body,
html[data-halo-theme="dark"] .injector-editor-container {
background-color: var(--halo-bg-card) !important;
border-color: var(--halo-border-base) !important;
color: var(--halo-text-primary) !important;
}
/* ===== 示例插件页 ===== */
html[data-halo-theme="dark"] main section#plugin-starter {
background-color: var(--halo-bg-body) !important;
color: var(--halo-text-primary) !important;
}
/* ===== 友链卡片徽章 ===== */
html[data-halo-theme="dark"] .link-badge {
background-color: var(--halo-bg-hover) !important;
color: var(--halo-text-primary) !important;
border-color: var(--halo-border-base) !important;
}
/* ===== 日程日历插件(time-column 布局) ===== */
html[data-halo-theme="dark"] .time-column__header,
html[data-halo-theme="dark"] .time-column__body,
html[data-halo-theme="dark"] [class*="time-column"] {
background-color: var(--halo-bg-card) !important;
color: var(--halo-text-primary) !important;
border-color: var(--halo-border-base) !important;
}
html[data-halo-theme="dark"] .entry-card-header__title {
color: var(--halo-text-primary) !important;
}
/* ===== 存储工具箱补充类 ===== */
html[data-halo-theme="dark"] .stat-value,
html[data-halo-theme="dark"] .stat-card .card-title,
html[data-halo-theme="dark"] .panel-card .card-title {
color: var(--halo-text-primary) !important;
}
html[data-halo-theme="dark"] .bar-track {
background-color: var(--halo-bg-disabled) !important;
}
+34
View File
@@ -0,0 +1,34 @@
/* ============================================================
Halo Dark Mode — UnoCSS 哈希类兜底
Halo 2.25 中 UnoCSS 同时生成 uno-* / i-* 哈希类,
这些类随构建变化,只能按结构定位兜底,不写死哈希值。
注意:本文件规则刻意不加 !important,便于后续语义规则覆盖。
============================================================ */
/* 文字兜底:只修正文本元素,豁免表单控件 */
html[data-halo-theme="dark"] main [class*="uno-"]:not(input):not(select):not(textarea) {
color: var(--halo-text-primary);
}
/* 块级容器背景兜底:豁免交互元素与表单控件 */
html[data-halo-theme="dark"] main [class*="uno-"]:not(button):not(a):not(input):not(select):not(textarea):not(label) {
background-color: var(--halo-bg-card);
border-color: var(--halo-border-base);
}
/* 页面级容器使用内容区背景 */
html[data-halo-theme="dark"] main > [class*="uno-"] {
background-color: var(--halo-bg-content);
}
/* 卡片头/卡片体内的 uno-* 区块使用 hover 级背景 */
html[data-halo-theme="dark"] .card-header [class*="uno-"],
html[data-halo-theme="dark"] .card-body [class*="uno-"] {
background-color: var(--halo-bg-hover);
border-color: var(--halo-border-light);
}
/* 旧版 i-* 哈希:只做文字兜底,避免给图标类设置背景 */
html[data-halo-theme="dark"] main [class*="i-"]:not(input):not(select):not(textarea) {
color: var(--halo-text-primary);
}
+1 -4
View File
@@ -62,10 +62,7 @@
}
/* ===== 链接 ===== */
a:not([class*="text-"]),
.hover\:text-gray-900:hover,
.hover\:text-gray-800:hover,
.hover\:text-gray-700:hover {
html[data-halo-theme="dark"] a:not([class*="text-"]) {
color: var(--halo-text-link);
}
+23
View File
@@ -0,0 +1,23 @@
MIT License
Copyright (c) 2026 Dark Reader Ltd.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+9366
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+173
View File
@@ -0,0 +1,173 @@
declare namespace DarkReader {
/**
* Enables dark mode for current web page.
* @param theme Theme options.
* @param fixes Fixes for the generated theme.
*/
function enable(theme: Partial<Theme>, fixes?: DynamicThemeFix): void;
/**
* Disables dark mode for current web page.
*/
function disable(): void;
/**
* Enables dark mode when system color scheme is dark.
* @param theme Theme options.
* @param fixes Fixes for the generated theme.
*/
function auto(theme: Partial<Theme> | false, fixes?: DynamicThemeFix): void;
/**
* Stops watching for system color scheme.
* @param isEnabled Boolean `false` value.
*/
function auto(isEnabled: false): void;
/**
* Returns if darkreader is enabled.
*/
function isEnabled(): boolean;
/**
* Sets a function for making CORS requests.
* @param fetch A fetch function.
*/
function setFetchMethod(fetch: (url: string) => Promise<Response>): void;
/**
* Returns the generated CSS by Dark Reader as a string.
*/
function exportGeneratedCSS(): Promise<string>;
/**
* Theme options.
*/
interface Theme {
/**
* 1 - dark mode, 0 - dimmed mode.
* Default 1.
*/
mode: 0 | 1;
/**
* Brightness (0 - 100+).
* Default 100.
*/
brightness: number;
/**
* Contrast (0 - 100+).
* Default 100.
*/
contrast: number;
/**
* Grayscale (0 - 100).
* Default 0.
*/
grayscale: number;
/**
* Sepia (0 - 100).
* Default 0.
*/
sepia: number;
/**
* Specifies if custom font should be used.
* Default false.
*/
useFont: boolean;
/**
* Font family to use.
*/
fontFamily: string;
/**
* Makes text look bolder (0 - 1px).
* Default 0.
*/
textStroke: number;
/**
* Background color to use for dark mode.
* Default #181a1b
*/
darkSchemeBackgroundColor: string;
/**
* Text color to use for dark mode.
* Default #e8e6e3
*/
darkSchemeTextColor: string;
/**
* Background color to use for light mode.
* Default #dcdad7
*/
lightSchemeBackgroundColor: string;
/**
* Text color to use for light mode.
* Default #181a1b
*/
lightSchemeTextColor: string;
/**
* Scrollbar color
* Default auto
*/
scrollbarColor: string;
/**
* Selection color
* Default auto
*/
selectionColor: string;
/**
* Specifies if it has to style system controls
* Default true
*/
styleSystemControls: boolean;
}
/**
* Contains fixes for the generated theme.
*/
interface DynamicThemeFix {
/**
* List of CSS selectors that should be inverted.
* Usually icons that are contained in sprites.
*/
invert: string[];
/**
* Additional CSS.
* ${color} template should be used to apply theme options to a color.
* Example:
* ```
* body {
* background-color: ${white} !important;
* background-image: none !important;
* }
* ```
*/
css: string;
/**
* List of CSS selectors where it's inline style should not be analyzed
* Mostly used for color pickers
*/
ignoreInlineStyle: string[];
/**
* List of CSS selectors where it's image should not be analyzed
* Mostly used for wrongly inverted background-images
*/
ignoreImageAnalysis: string[];
/**
* A toggle to disable the proxying of `document.styleSheets`.
* This is a API-Exclusive option, as it can break legitimate websites,
* who are using the Dark Reader API.
*/
disableStyleSheetsProxy: boolean;
/**
* List of stylesheet URL patterns to ignore.
* Stylesheets matching these patterns will not be processed by Dark Reader.
* - Simple string: matches if URL contains the string (e.g., "crisp")
* - Prefix with ^: matches if URL starts with pattern (e.g., "^https://client.crisp")
* - Suffix with $: matches if URL ends with pattern (e.g., ".css$")
*/
ignoreCSSUrl: string[];
}
}
declare module 'darkreader' {
export = DarkReader;
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "darkreader",
"version": "4.9.129",
"description": "Dark mode for every website",
"main": "darkreader.js",
"module": "darkreader.mjs",
"types": "index.d.ts",
"license": "MIT"
}