From 4bc66db15a1c5e30efff8d6a477b17c40d7e2064 Mon Sep 17 00:00:00 2001 From: Darkings Date: Mon, 22 Jun 2026 17:16:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=201.0.2=20=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E8=B4=A8=E9=87=8F=E4=B8=8E=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 13 +- .github/workflows/release.yml | 2 + .gitignore | 1 + AGENTS.md | 81 ++++ CHANGELOG.md | 11 + README.md | 160 +++++-- README.zh.md | 77 +-- dist/theme.css | 448 +++++++++++++++--- manifest.json | 2 +- package-lock.json | 4 +- package.json | 14 +- scripts/audit-css.js | 50 +- scripts/check-contrast.js | 52 +- scripts/create-qa-content.js | 6 +- scripts/generate-icon-theme.js | 15 +- scripts/qa-paths.js | 22 + scripts/sync-vault-theme.js | 12 +- scripts/verify-active-visual.js | 6 +- scripts/verify-generated.js | 74 +++ scripts/verify-icons.js | 6 + scripts/verify-pip-editor.js | 5 +- scripts/verify-qa.js | 106 +++-- scripts/verify-style-polish.js | 241 ++++++++++ scripts/verify-versions.js | 78 +++ src/scss/_active-visual-overrides.scss | 34 +- src/scss/_base.scss | 26 +- src/scss/components/_editor.scss | 339 ++++++++++++- .../components/_file-icons.generated.scss | 15 +- src/scss/components/_file-icons.scss | 39 +- src/scss/components/_modals.scss | 6 +- src/scss/components/_tabs.scss | 4 + src/scss/plugins/_canvas.scss | 2 +- src/scss/plugins/_style-settings.scss | 35 +- test/audit-css.test.js | 18 + test/check-contrast.test.js | 12 + test/qa-paths.test.js | 16 + test/verify-generated.test.js | 16 + test/verify-qa.test.js | 33 ++ test/verify-versions.test.js | 26 + theme.css | 448 +++++++++++++++--- versions.json | 3 +- 41 files changed, 2158 insertions(+), 400 deletions(-) create mode 100644 AGENTS.md create mode 100644 scripts/qa-paths.js create mode 100644 scripts/verify-generated.js create mode 100644 scripts/verify-style-polish.js create mode 100644 scripts/verify-versions.js create mode 100644 test/audit-css.test.js create mode 100644 test/check-contrast.test.js create mode 100644 test/qa-paths.test.js create mode 100644 test/verify-generated.test.js create mode 100644 test/verify-qa.test.js create mode 100644 test/verify-versions.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e24c8c..e329ae9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,14 +19,5 @@ jobs: - run: npm ci - - name: Stylelint - run: npm run lint:css - - - name: Build - run: npm run build - - - name: CSS Audit - run: npm run audit:css - - - name: Contrast Check - run: npm run check:contrast + - name: Verify + run: npm run verify diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94cdea9..bd8d574 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ on: jobs: release: runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true permissions: contents: write steps: diff --git a/.gitignore b/.gitignore index c87495c..6e646ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ docs/ ERRORS.md +tmp/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..aa0ba8d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,81 @@ +# AGENTS.md + +本文件为 Codex 在此仓库中工作时提供指导。 + +## 项目规范 + +- 本项目所有文档、注释、提交信息均使用中文书写(专业术语如 SCSS、CSS、Vite、Obsidian 等除外)。 +- 长期项目文档、研究文档、路线图、QA 文档与问题汇总优先维护在 Obsidian 项目目录: + +```text +C:\Users\Jie\iCloudDrive\iCloud~md~obsidian\SecondBrain\project\Monokai Syntax +``` + +- 当前仓库仅保留开发、构建、协作、发布与源码旁注所必需的文档。 +- 将仓库文档迁移到上述 Obsidian 项目目录前,需先确认该文档不会被构建脚本、发布流程、协作规则或源码注释直接依赖。 +- 已迁移到 Obsidian 项目目录的文档,不应在仓库中继续维护重复副本;如仓库流程需要引用,应在仓库文档中写明 Obsidian 目录中的目标位置。 +- `docs/` 目录仅作为临时整理区使用。稳定的说明文档、QA 清单、路线图和研究报告应迁移到 Obsidian 项目目录。 + +## 构建命令 + +``` +npm run dev # 监听模式(SCSS 变更时重新构建 theme.css) +npm run build # 生产构建 → dist/theme.css +npm run lint:css # Stylelint 检查 src/scss/**/*.scss +npm run check:contrast # WCAG 对比度审计 +npm run audit:css # CSS 质量审计 +npm run verify:icons # 验证图标字体覆盖 +npm run verify:graph # 验证图谱 CSS 变量 +npm run generate:icons # 从图标字体重新生成 file-icons.generated.scss +npm run release:pack # 完整发布前检查(verify + lint + build + audit) +npm run build:vault # 构建并同步到默认 Obsidian vault(可追加自定义路径) +npm run build:vault -- D:/你的/Vault # 构建并同步到指定 Vault +``` + +`generate:icons` 脚本通过 `prebuild`/`prelint:css` 钩子在 `build` 和 `lint:css` 之前自动运行。 + +## 架构 + +这是一个 **Obsidian 主题**,将 VS Code Monokai Pro 调色板映射到 Obsidian 约 400 个 CSS 自定义属性上。构建工具链使用 **Vite**,配有一个自定义 `obsidianThemeBundle` 插件,将 `license.css` + 编译后的 SCSS + Style Settings YAML 配置拼接为单个 `dist/theme.css`。 + +### SCSS 模块层级(全部位于 `src/scss/`) + +| 文件 | 职责 | +|---|---| +| `_variables.scss` | 设计 token:40+ 颜色原语(深色 + 浅色)、字体栈、间距、圆角 | +| `_mixins.scss` | `focus-ring`、`compact-density`、`reduced-motion` 等 mixin | +| `_base.scss` | 将 SCSS 变量映射到 Obsidian CSS 自定义属性(`:root`、`.theme-dark`、`.theme-light`),同时包含全局 UI 规则(导航、元数据、搜索、标签) | +| `components/_editor.scss` | Markdown 渲染与 CodeMirror 6 语法高亮(`.cm-s-obsidian` token 颜色) | +| `components/_ribbon.scss` | 左侧边栏 Ribbon(类似 VS Code 活动栏) | +| `components/_tabs.scss` | 工作区标签页头部,激活态强调下划线 | +| `components/_modals.scss` | 命令面板、提示框、建议弹窗 | +| `components/_file-icons.scss` | 自定义图标字体(`monokai-pro-icons`),用于文件树,支持单色切换 | +| `plugins/_graph.scss` | 关系图谱:节点/连线颜色、面板控件、聚焦环动画 | +| `plugins/_canvas.scss` | Canvas 卡片颜色映射到 Monokai 六色光谱 | +| `plugins/_style-settings.scss` | 动态开关:5 种滤镜主题(Machine/Octagon/Ristretto/Spectrum)、紧凑模式、单色图标、通过 `body` 类实现用户可覆盖的强调色/链接色/代码色 | + +入口点为 `src/main.js`,仅导入 `src/scss/index.scss`。该文件按顺序转发 `variables → mixins → base → components → plugins`。 + +### Style Settings 配置(`src/css/style-settings/`) + +这些 `*.css.md` 文件包含由 `obsidian-style-settings` 社区插件解析的 YAML 元数据块。Vite 插件按字母顺序排列并拼接到 `theme.css` 末尾。文件:`00-overview`、`10-palette`、`20-density`、`30-icons`、`40-typography`、`50-accents`。 + +### 关键 Obsidian CSS 变量 + +主题通过设置 Obsidian 文档化的 CSS 自定义属性来工作。最重要的分组: +- `--background-primary/secondary` — 主编辑区 vs 侧边栏背景 +- `--text-normal/muted/faint` — 文本层级 +- `--text-accent`、`--interactive-accent` — 行动号召洋红色 +- `--h1-color` 至 `--h6-color` — 标题颜色级联(洋红 → 橙 → 黄 → 绿 → 青 → 紫) +- `--link-color`、`--link-external-color` — 青色链接 +- `--blockquote-border-color` — 绿色边框 +- `--code-*` — 代码语法 token 颜色 +- `--nav-item-*`、`--ribbon-background` — 导航 chrome +- 自定义 `--monokai-*` 变量 — 滤镜和用户覆盖的内部桥接变量 + +## 约束 + +- **禁止外部资源的 `@import`** — Obsidian 审核流程拒绝在运行时获取远程字体/资源的主题。所有资源必须内联(图标字体以 Base64 编码在 `_file-icons.generated.scss` 中)。 +- **禁止 `!important`** — Obsidian 的 CSS 级联依赖特异性;`!important` 会破坏用户 CSS 片段。 +- **禁止 ID 选择器** — 由 Stylelint 强制执行;只允许使用 class。 +- 主题目标为 Obsidian 1.0.0+(manifest.json 中的 `minAppVersion`),该版本引入了 CSS 变量系统。 diff --git a/CHANGELOG.md b/CHANGELOG.md index 500ab61..51854c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # 更新日志 / Changelog +## 1.0.2(2026-06-22) + +- 系统优化阅读模式与 Live Preview 的视觉一致性,专项验证扩展到 30 项。 +- 修正文件树同层级文件夹与 Markdown 文件对齐问题。 +- 调整根层文件夹折叠图标尺寸与线宽,降低视觉重量。 +- 优化 H1-H6 标题下边距,按层级从大到小递减。 +- 更新深色模式代码 token 色彩,使其更贴近 Monokai Pro。 +- 补齐段落、表格源码、Callout、块引用、代码块、附件嵌入、标签、链接、脚注与扩展任务状态的阅读/编辑一致性规则。 +- 增加版本一致性、生成文件一致性、样式 polish、Vault QA 等发布前验证。 +- 更新发布版 README,补充安装、验证与 GitHub Release 流程。 + ## 1.0.1(2026-05-15) - 完善文档树文档图标:README、TODO、CHANGELOG、API、PROMPTS、CURSOR、CLAUDE、AGENTS 与 `Bug_*.md` diff --git a/README.md b/README.md index d969f1c..dabbc0e 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,152 @@ # Monokai Syntax -[中文](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/README.zh.md) | [Development](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/DEVELOPMENT.md) | [GitHub](https://github.com/Darkings/Obsidian-MonokaiSyntax) - -Monokai Syntax is an Obsidian community theme inspired by the Monokai Pro visual language, tuned for writing, code notes, navigation, graph view, and long-running knowledge bases. +Monokai Syntax 是一个面向 Obsidian 的 Monokai Pro 风格主题,重点优化写作、代码笔记、文件树导航、阅读模式、Live Preview、关系图谱和 Canvas 的长期使用体验。 ![Monokai Syntax](./MonokaiSyntax.png) -## Highlights +## 版本 -- Monokai-inspired dark and light palettes for Obsidian. -- Markdown reading view and Live Preview styling. -- CodeMirror 6 syntax colors mapped to the Monokai spectrum. -- File explorer, ribbon, tabs, modals, graph view, and canvas polish. -- Custom file icons with optional monochrome mode. -- Style Settings support for palette filters, density, icons, typography, and accents. -- No remote runtime assets in the final theme CSS. +当前版本:`1.0.2` -## 1.0.1 Update +最低 Obsidian 版本:`1.0.0` -- Expanded document icons for README, TODO, CHANGELOG, API, PROMPTS, CURSOR, CLAUDE, AGENTS, and `Bug_*.md` notes. -- Reworked callout icons with glyphs from `icons/icons.woff`, with larger icons and Monokai-style semantic backgrounds and left borders. -- Updated the file explorer so second-level and deeper folder collapse icons use `folder-dimmed2`, while root-level folders keep Obsidian's native collapse icon. -- Added a Style Settings toggle to hide file tree icons. -- Improved compact mode density for the file explorer. +## 主要特性 -## Installation +- Monokai Pro 风格深色与浅色调色板。 +- CodeMirror 6 语法高亮映射到 Monokai 六色光谱。 +- 阅读模式与 Live Preview 使用共享语义变量收敛视觉差异。 +- 文件树图标覆盖常见文档、配置、代码与项目文件。 +- 文件树同层级文件夹与 Markdown 文件对齐,根层折叠图标使用轻量尺寸。 +- Callout、块引用、任务列表、表格、代码块、链接、标签、脚注、图片与附件嵌入统一样式层级。 +- 关系图谱、Canvas、Ribbon、标签页、弹窗和搜索界面适配主题色。 +- 支持 Style Settings:调色板滤镜、紧凑模式、图标模式、字体、强调色、链接色与代码色。 +- 构建产物不加载远程运行时资源,图标字体以内联 woff 形式打包。 -### Community Theme +## 1.0.2 更新重点 -Monokai Syntax is available from Obsidian's community theme store. +- 系统收敛阅读模式与 Live Preview 的视觉差异,专项验证覆盖 30 项样式一致性检查。 +- 优化 H1-H6 标题下边距,按标题层级递减。 +- 修正文件树同层级文件夹与 Markdown 文件的对齐问题。 +- 调整根层文件夹折叠图标尺寸与线宽,避免视觉过重。 +- 更新代码 token 颜色,使深色模式更贴近 Monokai Pro。 +- 增强代码块、表格源码、块引用、Callout、标签、链接、脚注、附件嵌入和扩展任务状态的阅读/编辑一致性。 +- 增加发布前验证:版本一致性、生成文件一致性、样式 polish、Vault QA 和 CSS 审计。 -Open Obsidian, go to `Settings` -> `Appearance` -> `Themes` -> `Manage`, search for `Monokai Syntax`, then install and enable it. +## 安装 -### Manual Installation +### 手动安装 -Create this folder in your Obsidian vault: +在你的 Obsidian vault 中创建目录: ```text -Your Vault/.obsidian/themes/Monokai Syntax/ +你的 Vault/.obsidian/themes/Monokai Syntax/ ``` -Copy these files into it: +复制以下文件到该目录: ```text theme.css manifest.json ``` -Then enable `Monokai Syntax` from Obsidian's Appearance settings. +然后在 Obsidian 中进入 `设置` -> `外观` -> `主题`,选择并启用 `Monokai Syntax`。 + +### 从 GitHub Release 安装 + +发布 GitHub Release 后,下载对应版本中的: + +```text +theme.css +manifest.json +``` + +放入你的 vault 主题目录即可。 + +### 提交 Obsidian 社区主题 + +社区主题上架需要向 `obsidianmd/obsidian-releases` 提交主题信息 PR,并等待审核通过。审核通过后,用户才能在 Obsidian 的社区主题商店中搜索安装。 ## Style Settings -For the full configuration experience, install the community plugin `Style Settings`. +建议安装 Obsidian 社区插件 `Style Settings`,以获得完整配置体验。 -Available controls include: +当前支持: -- Palette filter: Classic, Machine, Octagon, Ristretto, Spectrum. -- Compact density. -- Colorful or monochrome file icons. -- Font and typography options. -- Accent, link, and code color overrides. +- 调色板滤镜:Classic、Machine、Octagon、Ristretto、Spectrum。 +- 紧凑模式。 +- 彩色或单色文件树图标。 +- 文件树图标开关。 +- 字体与排版选项。 +- 强调色、链接色与代码色覆盖。 -## Roadmap +## 本地开发 -- Expand file icon coverage. -- Refine Markdown reading view and Live Preview details. -- Improve Style Settings coverage. -- Polish graph view, canvas, and plugin compatibility. -- Track Obsidian UI changes and keep the published community theme up to date. +安装依赖: -## Development +```powershell +npm install +``` -Build, lint, audit, and vault sync instructions live in [DEVELOPMENT.md](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/DEVELOPMENT.md). +构建主题: -## License +```powershell +npm run build +``` -MIT. See [LICENSE.md](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/LICENSE.md). +完整验证: + +```powershell +npm run verify +``` + +构建并同步到默认 vault: + +```powershell +npm run build:vault +``` + +发布前检查: + +```powershell +npm run release:pack +``` + +## 发布流程 + +1. 确认 `package.json`、`manifest.json`、`versions.json` 版本一致。 +2. 运行 `npm run release:pack`。 +3. 确认根目录存在发布产物: + +```text +theme.css +manifest.json +``` + +4. 创建并推送版本标签,例如: + +```powershell +git tag v1.0.2 +git push origin v1.0.2 +``` + +5. GitHub Actions 会在标签推送后运行发布检查并创建 Release,附件包含 `theme.css` 与 `manifest.json`。 + +## 验证覆盖 + +当前发布前验证包含: + +- 版本一致性检查。 +- 图标字体生成与覆盖验证。 +- 阅读模式与 Live Preview 样式一致性验证。 +- Active 视觉覆盖验证。 +- 关系图谱变量验证。 +- Node 测试。 +- Stylelint。 +- WCAG 对比度检查。 +- 构建产物一致性检查。 +- CSS 审计:禁止远程资源、`!important` 与 ID 选择器。 +- Vault 侧 QA 验证。 + +## 许可证 + +MIT。详见 [LICENSE.md](./LICENSE.md)。 diff --git a/README.zh.md b/README.zh.md index 96a1c44..f96ce9b 100644 --- a/README.zh.md +++ b/README.zh.md @@ -1,78 +1,19 @@ -# Monokai Syntax +# Monokai Syntax 中文说明 -[English](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/README.md) | [开发说明](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/DEVELOPMENT.zh.md) | [GitHub](https://github.com/Darkings/Obsidian-MonokaiSyntax) +中文说明已合并到主文档:[README.md](./README.md)。 -Monokai Syntax 是一个受 Monokai Pro 视觉语言启发的 Obsidian 社区主题,面向写作、代码笔记、知识库导航、关系图谱与长期知识管理场景优化。 +常用发布前命令: -![Monokai Syntax](./MonokaiSyntax.png) - -## 亮点 - -- 面向 Obsidian 的 Monokai 风格深色与浅色调色板。 -- Markdown 阅读视图与 Live Preview 样式优化。 -- CodeMirror 6 语法高亮映射到 Monokai 六色光谱。 -- 文件树、Ribbon、标签页、弹窗、关系图谱与 Canvas 细节打磨。 -- 自定义文件图标,支持彩色与单色模式。 -- 支持 Style Settings 调整调色板滤镜、密度、图标、字体与强调色。 -- 最终主题 CSS 不加载远程运行时资源。 - -## 1.0.1 更新 - -- 扩展文档图标覆盖:README、TODO、CHANGELOG、API、PROMPTS、CURSOR、CLAUDE、AGENTS 与 `Bug_*.md`。 -- Callout 图标改用 `icons/icons.woff` 内置 glyph,并放大图标尺寸,补齐 Monokai 风格语义背景色与左边框。 -- 文件树第二层及以后文件夹折叠图标改用 `folder-dimmed2`,根目录第一层保留 Obsidian 原生折叠图标。 -- Style Settings 新增关闭文档树图标开关。 -- 优化文件树紧凑模式的字号、行高与图标间距。 - -## 安装 - -### 社区主题 - -Monokai Syntax 已发布到 Obsidian 社区主题商店。 - -打开 Obsidian,进入 `设置` -> `外观` -> `主题` -> `管理`,搜索 `Monokai Syntax`,然后安装并启用。 - -### 手动安装 - -在你的 Obsidian vault 中创建目录: - -```text -你的 Vault/.obsidian/themes/Monokai Syntax/ +```powershell +npm run verify +npm run build:vault +npm run qa:verify +npm run release:pack ``` -复制以下文件到该目录: +发布产物: ```text theme.css manifest.json ``` - -然后在 Obsidian 的外观设置中启用 `Monokai Syntax`。 - -## Style Settings - -建议安装 Obsidian 社区插件 `Style Settings`,以获得完整配置体验。 - -当前支持: - -- 调色板滤镜:Classic、Machine、Octagon、Ristretto、Spectrum。 -- 紧凑密度。 -- 彩色或单色文件图标。 -- 字体与排版选项。 -- 强调色、链接色与代码色覆盖。 - -## 未来规划 - -- 扩展文件图标覆盖。 -- 继续打磨 Markdown 阅读视图与 Live Preview 细节。 -- 增强 Style Settings 覆盖范围。 -- 优化关系图谱、Canvas 与插件兼容性。 -- 跟进 Obsidian UI 变化,持续维护已发布的社区主题版本。 - -## 开发 - -构建、检查、审计与 Vault 同步说明见 [DEVELOPMENT.zh.md](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/DEVELOPMENT.zh.md)。 - -## 许可证 - -MIT。详见 [LICENSE.md](https://github.com/Darkings/Obsidian-MonokaiSyntax/blob/master/LICENSE.md)。 diff --git a/dist/theme.css b/dist/theme.css index 9111590..70c59a7 100644 --- a/dist/theme.css +++ b/dist/theme.css @@ -60,16 +60,16 @@ --list-marker-color: #ae81ff; --code-normal: #f8f8f2; --code-background: #1e1f1c; - --code-comment: #8f908a; - --code-function: #a6e22e; - --code-important: #f92672; - --code-keyword: #f92672; - --code-operator: #ff6188; - --code-property: #66d9ef; + --code-comment: #72736f; + --code-function: #a9dc76; + --code-important: #ff6188; + --code-keyword: #ff6188; + --code-operator: #fc9867; + --code-property: #78dce8; --code-punctuation: #c1c1bd; - --code-string: #e6db74; - --code-tag: #f92672; - --code-value: #ae81ff; + --code-string: #ffd866; + --code-tag: #ff6188; + --code-value: #ab9df2; --ribbon-background: #1e1f1c; --nav-item-background-hover: #3e3d32; --nav-item-background-active: #414339; @@ -253,10 +253,10 @@ input[type=checkbox]:checked { } .tag { - color: var(--link-color); - background-color: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: 999px; + color: var(--monokai-tag-color, var(--link-color)); + background-color: var(--monokai-tag-background, var(--background-secondary)); + border: 1px solid var(--monokai-tag-border-color, var(--background-modifier-border)); + border-radius: var(--monokai-tag-radius, var(--radius-s)); } .tag:hover { @@ -270,12 +270,48 @@ input[type=checkbox]:checked { --bold-weight: 900; --italic-color: var(--text-normal); --heading-spacing: 1rem; + --monokai-heading-gap-h1: 1.25rem; + --monokai-heading-gap-h2: 1.1rem; + --monokai-heading-gap-h3: 0.95rem; + --monokai-heading-gap-h4: 0.8rem; + --monokai-heading-gap-h5: 0.7rem; + --monokai-heading-gap-h6: 0.6rem; --monokai-heading-weight: 600; --monokai-inline-code-color: #a6e22e; --monokai-inline-code-background: var(--code-background); + --monokai-inline-code-padding: 0.1em 0.3em; + --monokai-formatting-marker-color: var(--text-faint); + --monokai-strikethrough-color: var(--text-muted); + --monokai-highlight-radius: var(--radius-s); + --monokai-highlight-padding-inline: 0.12em; + --monokai-hr-color: var(--background-modifier-border); + --monokai-hr-thickness: 1px; + --monokai-hr-spacing: 1.25rem; + --monokai-footnote-color: var(--link-color); + --monokai-footnote-marker-color: var(--text-faint); + --monokai-footnote-font-size: 0.85em; + --monokai-media-radius: var(--radius-m); + --monokai-media-border-color: var(--background-modifier-border); + --monokai-media-background: var(--background-secondary); + --monokai-embed-padding: 0.75rem; + --monokai-tag-color: var(--link-color); + --monokai-tag-background: var(--background-secondary); + --monokai-tag-border-color: var(--background-modifier-border); + --monokai-tag-radius: var(--radius-s); + --monokai-link-decoration-color: var(--link-color); + --monokai-link-decoration-thickness: 1px; + --monokai-paragraph-line-height: var(--line-height-normal); + --monokai-paragraph-spacing: 0.75rem; + --monokai-table-source-background: var(--background-secondary); + --monokai-table-source-font-family: var(--font-monospace-theme); + --monokai-table-source-separator-color: var(--text-faint); + --monokai-codeblock-border-color: var(--background-modifier-border); + --monokai-codeblock-radius: var(--radius-m); + --monokai-codeblock-padding: 1rem; + --monokai-codeblock-padding-inline: 1rem; --callout-blend-mode: normal; --callout-icon-size: 1.25em; - --p-spacing: 0.75rem; + --p-spacing: var(--monokai-paragraph-spacing); -webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; } @@ -313,7 +349,13 @@ body.theme-dark { --monokai-inline-code-color: #a6e22e; --monokai-inline-code-background: var(--code-background); --monokai-table-border: rgb(248 248 242 / 18%); + --monokai-table-header-background: rgb(248 248 242 / 6%); --monokai-task-done-color: rgb(248 248 242 / 68%); + --monokai-task-forward-color: var(--text-accent); + --monokai-task-cancelled-color: var(--text-faint); + --monokai-task-question-color: var(--link-color); + --monokai-modal-shadow: 0 18px 50px rgb(0 0 0 / 35%); + --monokai-selection-ring: rgb(249 38 114 / 28%); } body.theme-light { @@ -349,7 +391,13 @@ body.theme-light { --monokai-inline-code-color: #269d69; --monokai-inline-code-background: var(--code-background); --monokai-table-border: rgb(61 61 61 / 18%); + --monokai-table-header-background: rgb(61 61 61 / 6%); --monokai-task-done-color: rgb(61 61 61 / 66%); + --monokai-task-forward-color: var(--text-accent); + --monokai-task-cancelled-color: var(--text-faint); + --monokai-task-question-color: var(--link-color); + --monokai-modal-shadow: 0 16px 36px rgb(61 61 61 / 16%); + --monokai-selection-ring: rgb(200 63 103 / 22%); } .markdown-source-view.mod-cm6 .inline-title { @@ -358,10 +406,38 @@ body.theme-light { .markdown-rendered h1, .markdown-rendered h2, -.markdown-rendered h3 { +.markdown-rendered h3, +.markdown-rendered h4, +.markdown-rendered h5, +.markdown-rendered h6 { font-weight: var(--monokai-heading-weight); letter-spacing: 0; - margin-block: calc(var(--heading-spacing) * 1.5) var(--spacing-4); + margin-block-start: calc(var(--heading-spacing) * 1.5); +} +.markdown-rendered h1 { + margin-block-end: var(--monokai-heading-gap-h1); +} +.markdown-rendered h2 { + margin-block-end: var(--monokai-heading-gap-h2); +} +.markdown-rendered h3 { + margin-block-end: var(--monokai-heading-gap-h3); +} +.markdown-rendered h4 { + color: var(--h4-color); + margin-block-end: var(--monokai-heading-gap-h4); +} +.markdown-rendered h5 { + color: var(--h5-color); + margin-block-end: var(--monokai-heading-gap-h5); +} +.markdown-rendered h6 { + color: var(--h6-color); + margin-block-end: var(--monokai-heading-gap-h6); +} +.markdown-rendered p { + line-height: var(--monokai-paragraph-line-height); + margin-block: 0 var(--monokai-paragraph-spacing); } .markdown-rendered strong { color: var(--bold-color); @@ -370,11 +446,16 @@ body.theme-light { .markdown-rendered em { color: var(--italic-color); } +.markdown-rendered del, +.markdown-rendered s { + color: var(--monokai-strikethrough-color); + text-decoration-color: var(--monokai-strikethrough-color); +} .markdown-rendered mark { color: var(--monokai-highlight-text); background-color: var(--text-highlight-bg); - border-radius: var(--radius-s); - padding-inline: 0.12em; + border-radius: var(--monokai-highlight-radius); + padding-inline: var(--monokai-highlight-padding-inline); } .markdown-rendered ul > li::marker, .markdown-rendered ol > li::marker { @@ -386,6 +467,16 @@ body.theme-light { .markdown-rendered .task-list-item.is-checked > p { color: var(--monokai-task-done-color); } +.markdown-rendered .task-list-item[data-task=">"] { + color: var(--monokai-task-forward-color); +} +.markdown-rendered .task-list-item[data-task="-"] { + color: var(--monokai-task-cancelled-color); + text-decoration-line: line-through; +} +.markdown-rendered .task-list-item[data-task="?"] { + color: var(--monokai-task-question-color); +} .markdown-rendered table { border: 1px solid var(--monokai-table-border); } @@ -393,20 +484,25 @@ body.theme-light { .markdown-rendered td { border: 1px solid var(--monokai-table-border); } +.markdown-rendered th { + color: var(--text-normal); + background-color: var(--monokai-table-header-background); + font-weight: 700; +} .markdown-rendered code { color: var(--monokai-inline-code-color); background-color: var(--monokai-inline-code-background); border: 0; border-radius: var(--radius-s); box-shadow: none; - padding: 0.1em 0.3em; + padding: var(--monokai-inline-code-padding); } .markdown-rendered pre { background-color: var(--code-background); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); + border: 1px solid var(--monokai-codeblock-border-color); + border-radius: var(--monokai-codeblock-radius); font-family: var(--font-monospace-theme); - padding: 1rem; + padding: var(--monokai-codeblock-padding); } .markdown-rendered pre code { background-color: transparent; @@ -414,7 +510,91 @@ body.theme-light { box-shadow: none; padding: 0; } +.markdown-rendered hr { + border: 0; + border-top: var(--monokai-hr-thickness) solid var(--monokai-hr-color); + margin-block: var(--monokai-hr-spacing); +} +.markdown-rendered .footnote-ref, +.markdown-rendered .footnote-link { + color: var(--monokai-footnote-color); + font-size: var(--monokai-footnote-font-size); +} +.markdown-rendered .footnote-backref { + color: var(--monokai-footnote-marker-color); +} +.markdown-rendered img, +.markdown-rendered .image-embed { + max-width: 100%; + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); +} +.markdown-rendered .file-embed, +.markdown-rendered .pdf-embed, +.markdown-rendered .audio-embed, +.markdown-rendered .video-embed, +.markdown-rendered .internal-embed { + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + padding: var(--monokai-embed-padding); +} +.markdown-source-view.mod-cm6 { + /* stylelint-disable selector-class-pattern -- Obsidian/CodeMirror 内部类名。 */ +} +.markdown-source-view.mod-cm6 .HyperMD-header-1 { + padding-block-end: var(--monokai-heading-gap-h1); +} +.markdown-source-view.mod-cm6 .HyperMD-header-2 { + padding-block-end: var(--monokai-heading-gap-h2); +} +.markdown-source-view.mod-cm6 .HyperMD-header-3 { + padding-block-end: var(--monokai-heading-gap-h3); +} +.markdown-source-view.mod-cm6 .HyperMD-header-4 { + padding-block-end: var(--monokai-heading-gap-h4); +} +.markdown-source-view.mod-cm6 .HyperMD-header-5 { + padding-block-end: var(--monokai-heading-gap-h5); +} +.markdown-source-view.mod-cm6 .HyperMD-header-6 { + padding-block-end: var(--monokai-heading-gap-h6); +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task=x], +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task=X] { + color: var(--monokai-task-done-color); +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task=">"] { + color: var(--monokai-task-forward-color); +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task="-"] { + color: var(--monokai-task-cancelled-color); + text-decoration-line: line-through; +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task="?"] { + color: var(--monokai-task-question-color); +} +.markdown-source-view.mod-cm6 .HyperMD-codeblock { + background-color: var(--code-background); + font-family: var(--font-monospace-theme); + padding-inline: var(--monokai-codeblock-padding-inline); +} +.markdown-source-view.mod-cm6 .HyperMD-hr { + color: var(--monokai-formatting-marker-color); + padding-block: calc(var(--monokai-hr-spacing) / 2); +} +.markdown-source-view.mod-cm6 .HyperMD-paragraph { + padding-block-end: calc(var(--monokai-paragraph-spacing) / 3); +} +.markdown-source-view.mod-cm6 .HyperMD-table-row { + font-family: var(--monokai-table-source-font-family); + background-color: var(--monokai-table-source-background); +} +.markdown-source-view.mod-cm6 { + /* stylelint-enable selector-class-pattern */ +} .markdown-source-view.mod-cm6 .cm-header-1 { color: var(--h1-color); font-weight: var(--monokai-heading-weight); @@ -427,6 +607,18 @@ body.theme-light { color: var(--h3-color); font-weight: var(--monokai-heading-weight); } +.markdown-source-view.mod-cm6 .cm-header-4 { + color: var(--h4-color); + font-weight: var(--monokai-heading-weight); +} +.markdown-source-view.mod-cm6 .cm-header-5 { + color: var(--h5-color); + font-weight: var(--monokai-heading-weight); +} +.markdown-source-view.mod-cm6 .cm-header-6 { + color: var(--h6-color); + font-weight: var(--monokai-heading-weight); +} .markdown-source-view.mod-cm6 .cm-strong { color: var(--bold-color); font-weight: var(--bold-weight); @@ -434,16 +626,83 @@ body.theme-light { .markdown-source-view.mod-cm6 .cm-em { color: var(--italic-color); } +.markdown-source-view.mod-cm6 .cm-strikethrough { + color: var(--monokai-strikethrough-color); + text-decoration-color: var(--monokai-strikethrough-color); +} .markdown-source-view.mod-cm6 .cm-link, .markdown-source-view.mod-cm6 .cm-url { color: var(--link-color); } .markdown-source-view.mod-cm6 .cm-formatting-link { - color: var(--text-faint); + color: var(--monokai-formatting-marker-color); +} +.markdown-source-view.mod-cm6 .cm-hmd-table-sep, +.markdown-source-view.mod-cm6 .cm-hmd-table-align, +.markdown-source-view.mod-cm6 .cm-formatting-table { + color: var(--monokai-table-source-separator-color); +} +.markdown-source-view.mod-cm6 .cm-formatting-code-block, +.markdown-source-view.mod-cm6 .cm-hmd-codeblock-begin, +.markdown-source-view.mod-cm6 .cm-hmd-codeblock-end { + color: var(--monokai-formatting-marker-color); +} +.markdown-source-view.mod-cm6 .cm-table { + color: var(--text-normal); +} +.markdown-source-view.mod-cm6 .cm-formatting-list { + color: var(--list-marker-color); +} +.markdown-source-view.mod-cm6 .cm-formatting-strong, +.markdown-source-view.mod-cm6 .cm-formatting-em, +.markdown-source-view.mod-cm6 .cm-formatting-strikethrough, +.markdown-source-view.mod-cm6 .cm-formatting-code, +.markdown-source-view.mod-cm6 .cm-formatting-highlight { + color: var(--monokai-formatting-marker-color); } .markdown-source-view.mod-cm6 .cm-highlight { color: var(--monokai-highlight-text); background-color: var(--text-highlight-bg); + border-radius: var(--monokai-highlight-radius); + padding-inline: var(--monokai-highlight-padding-inline); +} +.markdown-source-view.mod-cm6 .cm-hr { + color: var(--monokai-hr-color); +} +.markdown-source-view.mod-cm6 .cm-footref { + color: var(--monokai-footnote-color); + font-size: var(--monokai-footnote-font-size); +} +.markdown-source-view.mod-cm6 .cm-formatting-footref { + color: var(--monokai-footnote-marker-color); +} +.markdown-source-view.mod-cm6 .image-embed, +.markdown-source-view.mod-cm6 .cm-embed-block { + max-width: 100%; + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); +} +.markdown-source-view.mod-cm6 .file-embed, +.markdown-source-view.mod-cm6 .pdf-embed, +.markdown-source-view.mod-cm6 .audio-embed, +.markdown-source-view.mod-cm6 .video-embed, +.markdown-source-view.mod-cm6 .internal-embed, +.markdown-source-view.mod-cm6 .cm-embed-block { + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + padding: var(--monokai-embed-padding); +} +.markdown-source-view.mod-cm6 .cm-hashtag { + color: var(--monokai-tag-color); + background-color: var(--monokai-tag-background); + border: 1px solid var(--monokai-tag-border-color); + border-radius: var(--monokai-tag-radius); + padding-inline: 0.25em; +} +.markdown-source-view.mod-cm6 .cm-line { + line-height: var(--monokai-paragraph-line-height); } .markdown-source-view.mod-cm6 .cm-inline-code { color: var(--monokai-inline-code-color); @@ -451,7 +710,7 @@ body.theme-light { border: 0; border-radius: var(--radius-s); box-shadow: none; - padding: 0.05em 0.25em; + padding: var(--monokai-inline-code-padding); } .cm-s-obsidian span.cm-comment { @@ -480,8 +739,12 @@ body.theme-light { a, .markdown-rendered a, -.cm-hmd-internal-link { +.cm-hmd-internal-link, +.cm-link, +.cm-url { color: var(--link-color); + text-decoration-color: var(--monokai-link-decoration-color); + text-decoration-thickness: var(--monokai-link-decoration-thickness); } a:hover, @@ -490,6 +753,14 @@ a:hover, color: var(--link-color-hover); } +a:focus-visible, +.markdown-rendered a:focus-visible, +.cm-hmd-internal-link:focus-visible { + outline: 2px solid var(--interactive-accent); + outline-offset: 2px; + border-radius: var(--radius-s); +} + /* stylelint-disable */ /* * 此文件由 `npm run generate:icons` 自动生成。 @@ -503,19 +774,24 @@ a:hover, font-weight: 400; } .nav-file-title::before { - display: inline-block; - flex: 0 0 1rem; - width: 1rem; - margin-inline-end: 0.35rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--monokai-file-tree-slot-size); + min-width: var(--monokai-file-tree-slot-size); + height: var(--monokai-file-tree-glyph-size); + margin-inline-end: calc(-1 * var(--monokai-file-tree-slot-size)); + transform: translateX(calc(-1 * (var(--monokai-file-tree-slot-size) + var(--monokai-file-tree-icon-gap)))); --monokai-file-icon-color: #ffd866; color: var(--monokai-file-icon-color, var(--icon-color)); content: "\e658"; font-family: "monokai-pro-icons"; - font-size: 1rem; + font-size: var(--monokai-file-tree-glyph-size); font-style: normal; font-variant: normal; font-weight: 400; line-height: 1; + pointer-events: none; speak: never; text-align: center; text-rendering: auto; @@ -884,14 +1160,40 @@ a:hover, content: "\e8e8"; } -.nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator, -.nav-folder-title[data-path*="/"] .collapse-icon, -.nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator, -.nav-folder[data-path*="/"] > .nav-folder-title .collapse-icon, -.nav-folder-children .nav-folder-children .nav-folder-title .nav-folder-collapse-indicator, -.nav-folder-children .nav-folder-children .nav-folder-title .collapse-icon { - width: 1rem; +.nav-file-title, +.nav-folder-title { + --monokai-file-tree-slot-size: 1rem; + --monokai-file-tree-icon-gap: 0.35rem; + --monokai-file-tree-glyph-size: 0.8125rem; + --monokai-file-tree-toggle-size: 9px; + --monokai-file-tree-toggle-stroke-width: 1.75; + overflow: visible; + position: relative; } + +.nav-folder-title .nav-folder-collapse-indicator, +.nav-folder-title .collapse-icon { + flex: 0 0 var(--monokai-file-tree-slot-size); + width: var(--monokai-file-tree-slot-size); + min-width: var(--monokai-file-tree-slot-size); + margin-inline-end: var(--monokai-file-tree-icon-gap); +} + +.nav-folder-title .nav-folder-collapse-indicator svg, +.nav-folder-title .collapse-icon svg { + width: var(--monokai-file-tree-toggle-size); + height: var(--monokai-file-tree-toggle-size); + font-size: var(--monokai-file-tree-toggle-size); + stroke-width: var(--monokai-file-tree-toggle-stroke-width); +} + +.nav-folder-title .nav-folder-collapse-indicator, +.nav-folder-title .collapse-icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + .nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator::before, .nav-folder-title[data-path*="/"] .collapse-icon::before, .nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator::before, @@ -902,7 +1204,7 @@ a:hover, display: inline-block; color: var(--icon-color); font-family: monokai-pro-icons, monospace; - font-size: 1rem; + font-size: var(--monokai-file-tree-glyph-size); font-style: normal; font-weight: 400; line-height: 1; @@ -985,6 +1287,7 @@ body.is-mobile .workspace-ribbon { color: var(--text-muted); background-color: transparent; border-radius: var(--radius-s) var(--radius-s) 0 0; + transition: color 140ms ease, background-color 140ms ease, box-shadow 140ms ease; } .workspace-tab-header:hover { @@ -1011,7 +1314,7 @@ body.is-mobile .workspace-ribbon { background-color: var(--background-secondary); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-m); - box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35); + box-shadow: var(--monokai-modal-shadow); } .modal-title, @@ -1027,6 +1330,8 @@ body.is-mobile .workspace-ribbon { background-color: var(--background-primary); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-s); + padding: 0.45rem 0.6rem; + transition: border-color 140ms ease, box-shadow 140ms ease; } .prompt-input:focus, @@ -1237,7 +1542,7 @@ body.is-mobile .workspace-ribbon { .canvas-node.is-focused, .canvas-node.is-selected { border-color: var(--interactive-accent); - box-shadow: 0 0 0 2px rgba(249, 38, 114, 0.28); + box-shadow: 0 0 0 2px var(--monokai-selection-ring); } .canvas-node-label { @@ -1306,12 +1611,18 @@ body.monokai-syntax-compact { --nav-item-size: 22px; --ribbon-item-size: 32px; --input-height: 28px; + --callout-padding: 0.9rem; + --callout-content-padding: var(--spacing-3) 0 0 0; } body.monokai-syntax-compact .nav-files-container { font-size: var(--monokai-nav-font-size); } body.monokai-syntax-compact .nav-file-title, body.monokai-syntax-compact .nav-folder-title { + --monokai-file-tree-slot-size: 13px; + --monokai-file-tree-icon-gap: 4px; + --monokai-file-tree-glyph-size: 12px; + --monokai-file-tree-toggle-size: 8px; min-height: var(--nav-item-size); line-height: var(--nav-item-size); padding: 0 6px; @@ -1321,24 +1632,6 @@ body.monokai-syntax-compact .nav-folder-title-content { font-size: var(--monokai-nav-font-size); line-height: var(--nav-item-size); } -body.monokai-syntax-compact .nav-file-title::before { - width: 13px; - flex: 0 0 13px; - margin-inline-end: 4px; - font-size: 13px; -} -body.monokai-syntax-compact .nav-folder-collapse-indicator { - width: 14px; - margin-inline-end: 1px; -} -body.monokai-syntax-compact .nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator::before, -body.monokai-syntax-compact .nav-folder-title[data-path*="/"] .collapse-icon::before, -body.monokai-syntax-compact .nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator::before, -body.monokai-syntax-compact .nav-folder[data-path*="/"] > .nav-folder-title .collapse-icon::before, -body.monokai-syntax-compact .nav-folder-children .nav-folder-children .nav-folder-title .nav-folder-collapse-indicator::before, -body.monokai-syntax-compact .nav-folder-children .nav-folder-children .nav-folder-title .collapse-icon::before { - font-size: 13px; -} body.monokai-syntax-compact .side-dock-ribbon-action { height: var(--ribbon-item-size); padding: 4px; @@ -1346,6 +1639,10 @@ body.monokai-syntax-compact .side-dock-ribbon-action { body.monokai-syntax-compact .markdown-rendered pre { padding: 0.75rem; } +body.monokai-syntax-compact .markdown-rendered th, +body.monokai-syntax-compact .markdown-rendered td { + padding: 0.35rem 0.5rem; +} body.monokai-syntax-monochrome-icons { --icon-color: var(--text-muted); @@ -1360,6 +1657,7 @@ body { --link-color: var(--monokai-link-color, var(--monokai-default-link)); --link-external-color: var(--monokai-link-color, var(--monokai-default-link)); --blockquote-border-color: var(--monokai-success-color, var(--monokai-default-success)); + --monokai-blockquote-border: var(--monokai-success-color, var(--monokai-default-success)); --checkbox-color-checked: var(--monokai-success-color, var(--monokai-default-success)); --text-highlight-bg: var(--monokai-warning-color, var(--monokai-default-warning)); --code-background: var(--monokai-code-background, var(--monokai-default-code-background)); @@ -1373,6 +1671,10 @@ body:is(.theme-dark, .theme-light) { --callout-content-padding: var(--spacing-4) 0 0 0; --callout-border-width: 0; --callout-radius: var(--radius-s); + --monokai-blockquote-content-gap: 1.25rem; + --monokai-blockquote-nested-gap: 0.75rem; + --monokai-callout-title-gap: var(--spacing-2); + --monokai-callout-source-marker-color: var(--text-faint); } /* ===== 块引用(阅读 + 编辑统一) ===== */ @@ -1382,7 +1684,11 @@ body .markdown-rendered blockquote { border-inline-start: 2px solid var(--monokai-blockquote-border); border-radius: 0 var(--radius-s) var(--radius-s) 0; color: var(--text-normal); - padding: 0.75rem 0.75rem 0.75rem 1.25rem; + padding: 0.75rem 0.75rem 0.75rem var(--monokai-blockquote-content-gap); +} + +body .markdown-rendered blockquote blockquote { + margin-block: var(--monokai-blockquote-nested-gap); } body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote, @@ -1392,14 +1698,25 @@ body .markdown-source-view.mod-cm6 .HyperMD-quote { border-radius: 0 var(--radius-s) var(--radius-s) 0; } +body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote + .cm-line.HyperMD-quote, +body .markdown-source-view.mod-cm6 .HyperMD-quote + .HyperMD-quote { + border-start-start-radius: 0; + border-end-start-radius: 0; +} + body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote .cm-quote, body .markdown-source-view.mod-cm6 .HyperMD-quote .cm-quote { color: var(--text-normal); border-inline-start: 0; - margin-left: 0.6rem; + margin-left: var(--monokai-blockquote-content-gap); padding-inline-start: 0; } +body .markdown-source-view.mod-cm6 .cm-quote-2, +body .markdown-source-view.mod-cm6 .cm-quote-3 { + color: var(--text-normal); +} + body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote .cm-formatting-quote, body .markdown-source-view.mod-cm6 .HyperMD-quote .cm-formatting-quote { display: none; @@ -1430,7 +1747,7 @@ body .markdown-source-view.mod-cm6 .callout-title, body .markdown-source-view.mod-cm6 .cm-callout-title { color: var(--callout-border, var(--monokai-callout-note-border)); font-weight: 700; - gap: var(--spacing-2); + gap: var(--monokai-callout-title-gap); padding-inline-start: 0; } @@ -1445,6 +1762,15 @@ body .markdown-source-view.mod-cm6 .cm-callout-content { padding-inline-start: 0; } +body .markdown-source-view.mod-cm6 .cm-callout-content { + color: var(--text-normal); +} + +body .markdown-source-view.mod-cm6 .cm-formatting-callout, +body .markdown-source-view.mod-cm6 .cm-callout .cm-formatting { + color: var(--monokai-callout-source-marker-color); +} + /* ===== Callout 语义色分组映射 ===== */ body .markdown-preview-view.markdown-rendered .callout[data-callout=note], body .markdown-rendered .callout[data-callout=note], diff --git a/manifest.json b/manifest.json index 86299a1..1b17ee0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "Monokai Syntax", - "version": "1.0.1", + "version": "1.0.2", "minAppVersion": "1.0.0", "author": "lat3ncy" } diff --git a/package-lock.json b/package-lock.json index ddcd102..73748ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "monokai-syntax", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "monokai-syntax", - "version": "1.0.1", + "version": "1.0.2", "license": "MIT", "devDependencies": { "sass": "^1.99.0", diff --git a/package.json b/package.json index e4d0403..bc3315c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ - { +{ "name": "monokai-syntax", - "version": "1.0.1", + "version": "1.0.2", "description": "面向 Obsidian 的 Monokai Syntax 主题。", "author": "lat3ncy", "license": "MIT", @@ -12,19 +12,27 @@ "dev": "vite build --watch", "generate:icons": "node scripts/generate-icon-theme.js", "build": "vite build", + "test": "node --test", "lint:css": "stylelint \"src/scss/**/*.scss\"", "check:contrast": "node scripts/check-contrast.js", "audit:css": "node scripts/audit-css.js", + "verify": "npm run verify:versions && npm run verify:pip-editor && npm run verify:icons && npm run verify:graph && npm run verify:active-visual && npm run verify:style-polish && npm test && npm run lint:css && npm run check:contrast && npm run build && npm run verify:generated && npm run audit:css", + "verify:versions": "node scripts/verify-versions.js", + "verify:generated": "node scripts/verify-generated.js", "verify:pip-editor": "node scripts/verify-pip-editor.js", "verify:icons": "node scripts/verify-icons.js", "verify:graph": "node scripts/verify-graph.js", "verify:active-visual": "node scripts/verify-active-visual.js", + "verify:style-polish": "node scripts/verify-style-polish.js", "qa:create": "node scripts/create-qa-content.js", "qa:verify": "node scripts/verify-qa.js", "build:dev": "vite build --mode development", "sync:vault": "node scripts/sync-vault-theme.js", "build:vault": "npm run build && node scripts/sync-vault-theme.js", - "release:pack": "npm run verify:pip-editor && npm run verify:icons && npm run verify:graph && npm run verify:active-visual && npm run lint:css && npm run check:contrast && npm run build && npm run audit:css" + "release:pack": "npm run verify" + }, + "engines": { + "node": ">=22" }, "devDependencies": { "sass": "^1.99.0", diff --git a/scripts/audit-css.js b/scripts/audit-css.js index 1663edb..1ace589 100644 --- a/scripts/audit-css.js +++ b/scripts/audit-css.js @@ -1,31 +1,41 @@ import { readFileSync } from "node:fs"; import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; -const themeCssPath = resolve(import.meta.dirname, "../theme.css"); -const themeCss = readFileSync(themeCssPath, "utf8"); +export function auditCssContent(themeCss) { + const urlMatches = [...themeCss.matchAll(/url\((["']?)(.*?)\1\)/gi)].map((match) => match[2]); + const forbiddenRuntimeUrls = urlMatches.filter( + (url) => !url.startsWith("data:font/woff;base64,"), + ); + const checks = [ + ["外部 @import", /@import/i.test(themeCss)], + ["远程 URL", /https?:\/\//i.test(themeCss)], + ["非本地内联字体 url() 资源", forbiddenRuntimeUrls.length > 0], + ["!important 声明", /!important/i.test(themeCss)], + ["ID 选择器", /(^|[{};])\s*#[A-Za-z][\w-]*/m.test(themeCss)], + ]; + const failures = checks.filter(([, matched]) => matched).map(([label]) => label); -const urlMatches = [...themeCss.matchAll(/url\((["']?)(.*?)\1\)/gi)].map((match) => match[2]); -const forbiddenRuntimeUrls = urlMatches.filter( - (url) => !url.startsWith("data:font/woff;base64,"), -); + return { + passed: failures.length === 0, + failures, + checks, + }; +} -const checks = [ - ["外部 @import", /@import/i.test(themeCss)], - ["远程 URL", /https?:\/\//i.test(themeCss)], - ["非本地内联字体 url() 资源", forbiddenRuntimeUrls.length > 0], - ["!important 声明", /!important/i.test(themeCss)], -]; +function runCli() { + const themeCssPath = resolve(import.meta.dirname, "../theme.css"); + const result = auditCssContent(readFileSync(themeCssPath, "utf8")); -let hasFailure = false; + for (const [label, matched] of result.checks) { + console.log(`${label}: ${matched ? "发现" : "未发现"}`); + } -for (const [label, matched] of checks) { - console.log(`${label}: ${matched ? "发现" : "未发现"}`); - - if (matched) { - hasFailure = true; + if (!result.passed) { + process.exitCode = 1; } } -if (hasFailure) { - process.exitCode = 1; +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + runCli(); } diff --git a/scripts/check-contrast.js b/scripts/check-contrast.js index 3a430ee..39ee06a 100644 --- a/scripts/check-contrast.js +++ b/scripts/check-contrast.js @@ -1,4 +1,6 @@ -const pairs = [ +import { pathToFileURL } from "node:url"; + +export const pairs = [ ["深色正文", "#f8f8f2", "#272822", 4.5], ["深色链接", "#66d9ef", "#272822", 4.5], ["深色图标", "#c1c1bd", "#1e1f1c", 3], @@ -9,7 +11,7 @@ const pairs = [ ["浅色高亮文本", "#3d3d3d", "#f7e7b3", 4.5], ]; -function toRgb(hex) { +export function toRgb(hex) { const value = hex.replace("#", ""); return [ Number.parseInt(value.slice(0, 2), 16), @@ -18,19 +20,19 @@ function toRgb(hex) { ]; } -function channel(value) { +export function channel(value) { const normalized = value / 255; return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4; } -function luminance(hex) { +export function luminance(hex) { const [red, green, blue] = toRgb(hex).map(channel); return 0.2126 * red + 0.7152 * green + 0.0722 * blue; } -function contrast(foreground, background) { +export function contrast(foreground, background) { const foregroundLuminance = luminance(foreground); const backgroundLuminance = luminance(background); const lighter = Math.max(foregroundLuminance, backgroundLuminance); @@ -38,19 +40,39 @@ function contrast(foreground, background) { return (lighter + 0.05) / (darker + 0.05); } -let hasFailure = false; +export function checkContrastPairs(contrastPairs = pairs) { + return contrastPairs.map(([label, foreground, background, minimum]) => { + const ratio = contrast(foreground, background); -for (const [label, foreground, background, minimum] of pairs) { - const ratio = contrast(foreground, background); - const passed = ratio >= minimum; - const formattedRatio = ratio.toFixed(2); - console.log(`${label}: ${formattedRatio}:1,要求 >= ${minimum}:1`); + return { + label, + foreground, + background, + minimum, + ratio, + passed: ratio >= minimum, + }; + }); +} - if (!passed) { - hasFailure = true; +function runCli() { + const results = checkContrastPairs(); + let hasFailure = false; + + for (const result of results) { + const formattedRatio = result.ratio.toFixed(2); + console.log(`${result.label}: ${formattedRatio}:1,要求 >= ${result.minimum}:1`); + + if (!result.passed) { + hasFailure = true; + } + } + + if (hasFailure) { + process.exitCode = 1; } } -if (hasFailure) { - process.exitCode = 1; +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + runCli(); } diff --git a/scripts/create-qa-content.js b/scripts/create-qa-content.js index ef61818..5b58ec2 100644 --- a/scripts/create-qa-content.js +++ b/scripts/create-qa-content.js @@ -1,9 +1,9 @@ import { mkdirSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; -const qaDir = resolve( - "C:/Users/Jie/iCloudDrive/iCloud~md~obsidian/SecondBrain/project/Monokai Syntax/QA", -); +import { resolveQaPaths, resolveVaultRoot } from "./qa-paths.js"; + +const { qaDir } = resolveQaPaths(resolveVaultRoot()); mkdirSync(qaDir, { recursive: true }); diff --git a/scripts/generate-icon-theme.js b/scripts/generate-icon-theme.js index eda3710..ffc30a2 100644 --- a/scripts/generate-icon-theme.js +++ b/scripts/generate-icon-theme.js @@ -171,19 +171,24 @@ const lines = [ "}", "", ".nav-file-title::before {", - ' display: inline-block;', - ' flex: 0 0 1rem;', - ' width: 1rem;', - ' margin-inline-end: 0.35rem;', + " display: inline-flex;", + " align-items: center;", + " justify-content: center;", + " width: var(--monokai-file-tree-slot-size);", + " min-width: var(--monokai-file-tree-slot-size);", + " height: var(--monokai-file-tree-glyph-size);", + " margin-inline-end: calc(-1 * var(--monokai-file-tree-slot-size));", + " transform: translateX(calc(-1 * (var(--monokai-file-tree-slot-size) + var(--monokai-file-tree-icon-gap))));", ` --monokai-file-icon-color: ${defaultFileIcon.fontColor};`, ' color: var(--monokai-file-icon-color, var(--icon-color));', ` content: "${cssContent(defaultFileIcon.fontCharacter)}";`, ' font-family: "monokai-pro-icons";', - " font-size: 1rem;", + " font-size: var(--monokai-file-tree-glyph-size);", " font-style: normal;", " font-variant: normal;", " font-weight: 400;", " line-height: 1;", + " pointer-events: none;", " speak: never;", " text-align: center;", " text-rendering: auto;", diff --git a/scripts/qa-paths.js b/scripts/qa-paths.js new file mode 100644 index 0000000..b044ec5 --- /dev/null +++ b/scripts/qa-paths.js @@ -0,0 +1,22 @@ +import { resolve } from "node:path"; + +export const DEFAULT_VAULT = "C:/Users/Jie/iCloudDrive/iCloud~md~obsidian/SecondBrain"; + +export function resolveVaultRoot({ argv = process.argv.slice(2), env = process.env } = {}) { + return argv[0] ?? env.OBSIDIAN_VAULT ?? DEFAULT_VAULT; +} + +export function resolveQaPaths(vaultRoot) { + const vaultDir = resolve(vaultRoot); + const qaDir = resolve(vaultDir, "project/Monokai Syntax/QA"); + const themeDir = resolve(vaultDir, ".obsidian/themes/Monokai Syntax"); + + return { + vaultDir, + qaDir, + themeDir, + appearancePath: resolve(vaultDir, ".obsidian/appearance.json"), + themeCssPath: resolve(themeDir, "theme.css"), + manifestPath: resolve(themeDir, "manifest.json"), + }; +} diff --git a/scripts/sync-vault-theme.js b/scripts/sync-vault-theme.js index ba88c80..f3c7e65 100644 --- a/scripts/sync-vault-theme.js +++ b/scripts/sync-vault-theme.js @@ -1,16 +1,10 @@ import { copyFileSync, mkdirSync } from "node:fs"; import { resolve } from "node:path"; +import { resolveQaPaths, resolveVaultRoot } from "./qa-paths.js"; + const rootDir = resolve(import.meta.dirname, ".."); -const vaultRoot = process.argv[2] ?? process.env.OBSIDIAN_VAULT; - -if (!vaultRoot) { - console.error("请设置 OBSIDIAN_VAULT 环境变量或通过命令行参数指定 Vault 路径"); - console.error("示例:npm run sync:vault -- D:/你的/Obsidian/Vault"); - process.exit(1); -} - -const vaultThemeDir = resolve(vaultRoot, ".obsidian/themes/Monokai Syntax"); +const { themeDir: vaultThemeDir } = resolveQaPaths(resolveVaultRoot()); mkdirSync(vaultThemeDir, { recursive: true }); diff --git a/scripts/verify-active-visual.js b/scripts/verify-active-visual.js index 54972ac..bc915ac 100644 --- a/scripts/verify-active-visual.js +++ b/scripts/verify-active-visual.js @@ -24,7 +24,11 @@ const packageJson = JSON.parse(files.packageJson); const checks = [ [ "Active 视觉验证脚本已接入 release:pack", - packageJson.scripts?.["release:pack"]?.includes("npm run verify:active-visual"), + packageJson.scripts?.verify?.includes("npm run verify:active-visual") + && ( + packageJson.scripts?.["release:pack"]?.includes("npm run verify:active-visual") + || packageJson.scripts?.["release:pack"]?.includes("npm run verify") + ), ], [ "Active 视觉覆盖层位于插件样式之后", diff --git a/scripts/verify-generated.js b/scripts/verify-generated.js new file mode 100644 index 0000000..8973da5 --- /dev/null +++ b/scripts/verify-generated.js @@ -0,0 +1,74 @@ +import { createHash } from "node:crypto"; +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +function sha256(content) { + return createHash("sha256").update(content).digest("hex"); +} + +export function compareThemeCss({ rootThemeCss, distThemeCss }) { + const rootHash = sha256(rootThemeCss); + const distHash = sha256(distThemeCss); + + return { + passed: rootHash === distHash, + rootHash, + distHash, + }; +} + +export function verifyGeneratedOutputs(rootDir = resolve(import.meta.dirname, "..")) { + const paths = { + rootTheme: resolve(rootDir, "theme.css"), + distTheme: resolve(rootDir, "dist/theme.css"), + generatedIcons: resolve(rootDir, "src/scss/components/_file-icons.generated.scss"), + }; + const checks = []; + + function add(label, passed) { + checks.push({ label, passed }); + } + + add("根目录 theme.css 存在", existsSync(paths.rootTheme)); + add("dist/theme.css 存在", existsSync(paths.distTheme)); + add("图标生成 SCSS 存在", existsSync(paths.generatedIcons)); + + if (existsSync(paths.rootTheme) && existsSync(paths.distTheme)) { + const comparison = compareThemeCss({ + rootThemeCss: readFileSync(paths.rootTheme, "utf8"), + distThemeCss: readFileSync(paths.distTheme, "utf8"), + }); + add("根目录 theme.css 与 dist/theme.css 内容一致", comparison.passed); + } + + if (existsSync(paths.generatedIcons)) { + const generatedIcons = readFileSync(paths.generatedIcons, "utf8"); + add("图标 SCSS 包含内联字体", /data:font\/woff;base64,/.test(generatedIcons)); + add("图标 SCSS 包含 AGENTS.md 专属规则", /\[data-path="AGENTS\.md"\]::before/.test(generatedIcons)); + } + + const failures = checks.filter((check) => !check.passed).map((check) => check.label); + + return { + passed: failures.length === 0, + failures, + checks, + }; +} + +function runCli() { + const result = verifyGeneratedOutputs(); + + for (const check of result.checks) { + console.log(`${check.label}: ${check.passed ? "通过" : "失败"}`); + } + + if (!result.passed) { + process.exitCode = 1; + } +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + runCli(); +} diff --git a/scripts/verify-icons.js b/scripts/verify-icons.js index 0c8adc1..6524fef 100644 --- a/scripts/verify-icons.js +++ b/scripts/verify-icons.js @@ -31,6 +31,7 @@ const checks = [ ["图标 content 使用 CSS Unicode 转义", /content: "[\\][0-9a-f]{4}";/i.test(files.generated)], ["图标 content 未被双重转义", !/content: "[\\][\\][0-9a-f]{4}";/i.test(files.generated)], ["默认文件图标规则", /\.nav-file-title::before/.test(files.generated)], + ["文件图标以零净宽度进入 gutter 且不推开文件名", /\.nav-file-title::before[\s\S]*?display:\s*inline-flex;[\s\S]*?width:\s*var\(--monokai-file-tree-slot-size\);[\s\S]*?margin-inline-end:\s*calc\(-1 \* var\(--monokai-file-tree-slot-size\)\);[\s\S]*?transform:\s*translateX\(calc\(-1 \* \(var\(--monokai-file-tree-slot-size\) \+ var\(--monokai-file-tree-icon-gap\)\)\)\);[\s\S]*?font-size:\s*var\(--monokai-file-tree-glyph-size\);/.test(files.generated) && !/\.nav-file-title::before[\s\S]*?position:\s*absolute;/.test(files.generated)], ["Markdown 扩展名规则", /\[data-path\$="\.md"\]::before/.test(files.generated)], ["package.json 文件名规则", /\[data-path\$="\/package\.json"\]::before/.test(files.generated)], ["README.md/readme.md 文件名规则", /\[data-path\$="\/README\.md"\]::before/.test(files.generated) && /\[data-path\$="\/readme\.md"\]::before/.test(files.generated)], @@ -50,6 +51,11 @@ const checks = [ ["所有默认 Callout 类型都有背景与边框映射", ["note", "abstract", "info", "todo", "tip", "question", "warning", "failure", "bug", "success", "example", "quote"].every((name) => new RegExp(`--monokai-callout-${name}-bg`).test(files.activeVisual) || new RegExp(`--monokai-callout-${name}-bg`).test(readFileSync(resolve(rootDir, "src/scss/components/_editor.scss"), "utf8")))], ["Callout 图标尺寸放大", /--callout-icon-size:\s*1\.25em/.test(readFileSync(resolve(rootDir, "src/scss/components/_editor.scss"), "utf8"))], ["文件树扩展名颜色多样", /--monokai-file-icon-color:\s*#ff6188/.test(files.generated) && /--monokai-file-icon-color:\s*#fc9867/.test(files.generated) && /--monokai-file-icon-color:\s*#ffd866/.test(files.generated) && /--monokai-file-icon-color:\s*#a9dc76/.test(files.generated) && /--monokai-file-icon-color:\s*#78dce8/.test(files.generated) && /--monokai-file-icon-color:\s*#ab9df2/.test(files.generated)], + ["文件夹内容不额外生成第二个图标", !/\.nav-folder-title-content::before/.test(files.wrapper) && !/\.nav-folder-title::before/.test(files.wrapper)], + ["文件行不通过移动标题盒修正对齐", !/\.nav-file-title\s*\{[\s\S]*?margin-inline-start:/.test(files.wrapper) && !/\.nav-file-title-content\s*\{[\s\S]*?margin-inline-start:/.test(files.wrapper)], + ["文件树拆分布局槽与真实图标尺寸", /--monokai-file-tree-slot-size:\s*1rem/.test(files.wrapper) && /--monokai-file-tree-glyph-size:\s*0\.8125rem/.test(files.wrapper) && /--monokai-file-tree-toggle-size:\s*9px/.test(files.wrapper) && /--monokai-file-tree-toggle-stroke-width:\s*1\.75/.test(files.wrapper) && /\.nav-folder-title\s+\.nav-folder-collapse-indicator,\s*[\r\n]+\.nav-folder-title\s+\.collapse-icon[\s\S]*?flex:\s*0 0 var\(--monokai-file-tree-slot-size\);[\s\S]*?width:\s*var\(--monokai-file-tree-slot-size\);[\s\S]*?margin-inline-end:\s*var\(--monokai-file-tree-icon-gap\);/.test(files.wrapper)], + ["根层文件夹折叠 SVG 使用独立轻量尺寸与线宽", /\.nav-folder-title\s+\.nav-folder-collapse-indicator svg,\s*[\r\n]+\.nav-folder-title\s+\.collapse-icon svg[\s\S]*?width:\s*var\(--monokai-file-tree-toggle-size\);[\s\S]*?height:\s*var\(--monokai-file-tree-toggle-size\);[\s\S]*?stroke-width:\s*var\(--monokai-file-tree-toggle-stroke-width\);/.test(files.wrapper)], + ["紧凑模式仍共享同一图标槽变量", /body\.monokai-syntax-compact[\s\S]*?\.nav-file-title,\s*[\r\n]+\s*\.nav-folder-title[\s\S]*?--monokai-file-tree-slot-size:\s*13px;[\s\S]*?--monokai-file-tree-icon-gap:\s*4px;[\s\S]*?--monokai-file-tree-glyph-size:\s*12px;[\s\S]*?--monokai-file-tree-toggle-size:\s*8px;/.test(readFileSync(resolve(rootDir, "src/scss/plugins/_style-settings.scss"), "utf8"))], ["保留 Obsidian 默认文件夹折叠指示器", /\.nav-folder-collapse-indicator/.test(readFileSync(resolve(rootDir, "src/scss/_base.scss"), "utf8"))], ["单色图标模式规则", /body\.monokai-syntax-monochrome-icons/.test(files.wrapper)], ["保留彩色图标模式", /--monokai-file-icon-color/.test(files.generated)], diff --git a/scripts/verify-pip-editor.js b/scripts/verify-pip-editor.js index 128b89d..45069c3 100644 --- a/scripts/verify-pip-editor.js +++ b/scripts/verify-pip-editor.js @@ -8,20 +8,21 @@ const activeVisualScss = readFileSync(activeVisualPath, "utf8"); const checks = [ ["行内代码使用强调背景变量", /--monokai-inline-code-background:/], + ["行内代码编辑与阅读模式使用同一 padding 变量", /--monokai-inline-code-padding:\s*0\.1em 0\.3em;[\s\S]*\.markdown-rendered[\s\S]*?code[\s\S]*?padding:\s*var\(--monokai-inline-code-padding\);[\s\S]*\.cm-inline-code[\s\S]*?padding:\s*var\(--monokai-inline-code-padding\);/], ["行内代码不使用着色边框变量", !/--monokai-inline-code-border:/.test(editorScss)], ["阅读视图行内代码不使用边框阴影", /\.markdown-rendered[\s\S]*?code[\s\S]*?box-shadow:\s*none;/], ["阅读视图块引用背景保持透明", /\.markdown-rendered[\s\S]*?blockquote[\s\S]*?background-color:\s*transparent;/], ["Obsidian 块引用原生变量同步透明背景", /--blockquote-background-color:\s*transparent;/], ["Obsidian 块引用原生变量同步细边框", /--blockquote-border-thickness:\s*2px;/], ["阅读视图块引用使用左边框", /\.markdown-rendered[\s\S]*?blockquote[\s\S]*?border-inline-start:\s*2px solid var\(--monokai-blockquote-border\);/], - ["阅读视图块引用增加左侧呼吸感", /\.markdown-rendered[\s\S]*?blockquote[\s\S]*?padding:\s*#\{\$spacing-3\} #\{\$spacing-3\} #\{\$spacing-3\} 1\.25rem;/], + ["阅读视图块引用增加左侧呼吸感", /\.markdown-rendered[\s\S]*?blockquote[\s\S]*?padding:\s*#\{\$spacing-3\} #\{\$spacing-3\} #\{\$spacing-3\} var\(--monokai-blockquote-content-gap\);/], ["深色块引用边框使用 Monokai 黄色", /body\.theme-dark[\s\S]*?--monokai-blockquote-border:\s*#\{\$color-dark-blockquote-border\};/], ["浅色块引用边框使用 Monokai 黄色", /body\.theme-light[\s\S]*?--monokai-blockquote-border:\s*#\{\$color-light-blockquote-border\};/], ["Live Preview 块引用取消默认格式符号显示", /\.cm-formatting-quote[\s\S]*?display:\s*none;/], ["Live Preview 块引用标记不重复绘制边框", /\.cm-quote[\s\S]*?border-inline-start:\s*0;/], ["Live Preview 块引用背景保持透明", /\.HyperMD-quote[\s\S]*?background-color:\s*transparent;/], ["Live Preview 块引用使用左边框", /\.HyperMD-quote[\s\S]*?border-inline-start:\s*2px solid var\(--monokai-blockquote-border\);/], - ["Live Preview 块引用文本自身保留间距", /\.HyperMD-quote[\s\S]*?\.cm-quote[\s\S]*?margin-left:\s*0\.6rem;/], + ["Live Preview 块引用文本自身保留间距", /\.HyperMD-quote[\s\S]*?\.cm-quote[\s\S]*?margin-left:\s*var\(--monokai-blockquote-content-gap\);/], ["浅色模式 H1-H3 字重为 700", /body\.theme-light[\s\S]*?--monokai-heading-weight:\s*700;/], ["深色模式 H1-H3 字重为 600", /body\.theme-dark[\s\S]*?--monokai-heading-weight:\s*600;/], ["标题样式使用 PIP 字重变量", /font-weight:\s*var\(--monokai-heading-weight\)/], diff --git a/scripts/verify-qa.js b/scripts/verify-qa.js index a1da14b..d1f37d7 100644 --- a/scripts/verify-qa.js +++ b/scripts/verify-qa.js @@ -1,18 +1,8 @@ import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; -const rootDir = resolve(import.meta.dirname, ".."); -const vaultDir = resolve("C:/Users/Jie/iCloudDrive/iCloud~md~obsidian/SecondBrain"); -const qaDir = resolve(vaultDir, "project/Monokai Syntax/QA"); -const themeDir = resolve(vaultDir, ".obsidian/themes/Monokai Syntax"); -const appearancePath = resolve(vaultDir, ".obsidian/appearance.json"); -const themeCssPath = resolve(themeDir, "theme.css"); -const checks = []; - -function addCheck(label, passed) { - checks.push([label, passed]); - console.log(`${label}: ${passed ? "通过" : "失败"}`); -} +import { resolveQaPaths, resolveVaultRoot } from "./qa-paths.js"; const qaFiles = [ "Monokai Syntax QA 总览.md", @@ -22,20 +12,9 @@ const qaFiles = [ "Monokai Syntax QA.canvas", ]; -for (const fileName of qaFiles) { - addCheck(`QA 文件存在:${fileName}`, existsSync(resolve(qaDir, fileName))); -} - -addCheck("Vault 主题 CSS 存在", existsSync(themeCssPath)); -addCheck("Vault manifest 存在", existsSync(resolve(themeDir, "manifest.json"))); - -const appearance = JSON.parse(readFileSync(appearancePath, "utf8")); -addCheck("Obsidian 已选择 Monokai Syntax 主题", appearance.cssTheme === "Monokai Syntax"); - -const themeCss = readFileSync(themeCssPath, "utf8"); const requiredPatterns = [ - ["深色模式变量", /\.theme-dark\{/], - ["浅色模式变量", /\.theme-light\{/], + ["深色模式变量", /\.theme-dark\s*\{/], + ["浅色模式变量", /\.theme-light\s*\{/], ["编辑器样式", /\.markdown-rendered/], ["关系图谱样式", /\.graph-view/], ["Canvas 样式", /\.canvas-wrapper/], @@ -51,16 +30,73 @@ const requiredPatterns = [ ["强调色变量", /monokai-accent-color/], ]; -for (const [label, pattern] of requiredPatterns) { - addCheck(label, pattern.test(themeCss)); +function readJsonResult(path, label) { + if (!existsSync(path)) { + return { ok: false, error: `${label} 不存在` }; + } + + try { + return { ok: true, value: JSON.parse(readFileSync(path, "utf8")) }; + } catch { + return { ok: false, error: `${label} 不是有效 JSON` }; + } } -const canvas = JSON.parse(readFileSync(resolve(qaDir, "Monokai Syntax QA.canvas"), "utf8")); -addCheck("Canvas 包含节点", Array.isArray(canvas.nodes) && canvas.nodes.length >= 3); -addCheck("Canvas 包含边线", Array.isArray(canvas.edges) && canvas.edges.length >= 2); - -const failed = checks.filter(([, passed]) => !passed); - -if (failed.length > 0) { - process.exitCode = 1; +function addCheck(checks, label, passed) { + checks.push({ label, passed }); +} + +export function verifyQaVault(vaultRoot) { + const paths = resolveQaPaths(vaultRoot); + const checks = []; + + for (const fileName of qaFiles) { + addCheck(checks, `QA 文件存在:${fileName}`, existsSync(resolve(paths.qaDir, fileName))); + } + + addCheck(checks, "Vault 主题 CSS 存在", existsSync(paths.themeCssPath)); + addCheck(checks, "Vault manifest 存在", existsSync(paths.manifestPath)); + + const appearance = readJsonResult(paths.appearancePath, "appearance.json"); + addCheck(checks, appearance.error ?? "appearance.json 可读取", appearance.ok); + addCheck( + checks, + "Obsidian 已选择 Monokai Syntax 主题", + appearance.ok && appearance.value.cssTheme === "Monokai Syntax", + ); + + const themeCss = existsSync(paths.themeCssPath) ? readFileSync(paths.themeCssPath, "utf8") : ""; + for (const [label, pattern] of requiredPatterns) { + addCheck(checks, label, pattern.test(themeCss)); + } + + const canvas = readJsonResult(resolve(paths.qaDir, "Monokai Syntax QA.canvas"), "Monokai Syntax QA.canvas"); + addCheck(checks, canvas.error ?? "Canvas JSON 可读取", canvas.ok); + addCheck(checks, "Canvas 包含节点", canvas.ok && Array.isArray(canvas.value.nodes) && canvas.value.nodes.length >= 3); + addCheck(checks, "Canvas 包含边线", canvas.ok && Array.isArray(canvas.value.edges) && canvas.value.edges.length >= 2); + + const failures = checks.filter((check) => !check.passed).map((check) => check.label); + + return { + passed: failures.length === 0, + failures, + checks, + paths, + }; +} + +function runCli() { + const result = verifyQaVault(resolveVaultRoot()); + + for (const check of result.checks) { + console.log(`${check.label}: ${check.passed ? "通过" : "失败"}`); + } + + if (!result.passed) { + process.exitCode = 1; + } +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + runCli(); } diff --git a/scripts/verify-style-polish.js b/scripts/verify-style-polish.js new file mode 100644 index 0000000..45bba79 --- /dev/null +++ b/scripts/verify-style-polish.js @@ -0,0 +1,241 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const rootDir = resolve(import.meta.dirname, ".."); +const files = { + editor: readFileSync(resolve(rootDir, "src/scss/components/_editor.scss"), "utf8"), + activeVisual: readFileSync(resolve(rootDir, "src/scss/_active-visual-overrides.scss"), "utf8"), + base: readFileSync(resolve(rootDir, "src/scss/_base.scss"), "utf8"), + modals: readFileSync(resolve(rootDir, "src/scss/components/_modals.scss"), "utf8"), + tabs: readFileSync(resolve(rootDir, "src/scss/components/_tabs.scss"), "utf8"), + canvas: readFileSync(resolve(rootDir, "src/scss/plugins/_canvas.scss"), "utf8"), + styleSettings: readFileSync(resolve(rootDir, "src/scss/plugins/_style-settings.scss"), "utf8"), +}; + +const checks = [ + [ + "H4-H6 在阅读模式与编辑模式都有颜色和字重覆盖", + /\.markdown-rendered[\s\S]*?h4,[\s\S]*?h5,[\s\S]*?h6[\s\S]*?font-weight:\s*var\(--monokai-heading-weight\);/.test(files.editor) + && /\.cm-header-4[\s\S]*?color:\s*var\(--h4-color\);[\s\S]*?font-weight:\s*var\(--monokai-heading-weight\);/.test(files.editor) + && /\.cm-header-5[\s\S]*?color:\s*var\(--h5-color\);[\s\S]*?font-weight:\s*var\(--monokai-heading-weight\);/.test(files.editor) + && /\.cm-header-6[\s\S]*?color:\s*var\(--h6-color\);[\s\S]*?font-weight:\s*var\(--monokai-heading-weight\);/.test(files.editor), + ], + [ + "H1-H6 下边距按层级递减并同步编辑模式", + /--monokai-heading-gap-h1:\s*1\.25rem;[\s\S]*?--monokai-heading-gap-h2:\s*1\.1rem;[\s\S]*?--monokai-heading-gap-h3:\s*0\.95rem;[\s\S]*?--monokai-heading-gap-h4:\s*0\.8rem;[\s\S]*?--monokai-heading-gap-h5:\s*0\.7rem;[\s\S]*?--monokai-heading-gap-h6:\s*0\.6rem;/.test(files.editor) + && /\.markdown-rendered[\s\S]*?h1\s*\{[\s\S]*?margin-block-end:\s*var\(--monokai-heading-gap-h1\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?h6\s*\{[\s\S]*?margin-block-end:\s*var\(--monokai-heading-gap-h6\);/.test(files.editor) + && /\.HyperMD-header-1[\s\S]*?padding-block-end:\s*var\(--monokai-heading-gap-h1\);/.test(files.editor) + && /\.HyperMD-header-6[\s\S]*?padding-block-end:\s*var\(--monokai-heading-gap-h6\);/.test(files.editor), + ], + [ + "块引用阅读模式与编辑模式共享内容间距变量", + /--monokai-blockquote-content-gap:\s*1\.25rem;/.test(files.activeVisual) + && /blockquote[\s\S]*?padding:\s*#\{\$spacing-3\} #\{\$spacing-3\} #\{\$spacing-3\} var\(--monokai-blockquote-content-gap\);/.test(files.activeVisual) + && /\.cm-quote[\s\S]*?margin-left:\s*var\(--monokai-blockquote-content-gap\);/.test(files.activeVisual), + ], + [ + "Style Settings 成功色同步影响块引用边框", + /--monokai-blockquote-border:\s*var\(--monokai-success-color, var\(--monokai-default-success\)\);/.test(files.styleSettings), + ], + [ + "Modal 阴影使用深浅模式变量", + /body\.theme-dark[\s\S]*?--monokai-modal-shadow:\s*0 18px 50px rgb\(0 0 0 \/ 35%\);/.test(files.editor) + && /body\.theme-light[\s\S]*?--monokai-modal-shadow:\s*0 16px 36px rgb\(61 61 61 \/ 16%\);/.test(files.editor) + && /box-shadow:\s*var\(--monokai-modal-shadow\);/.test(files.modals), + ], + [ + "Canvas 选中光环跟随强调色变量", + /--monokai-selection-ring:\s*rgb\(249 38 114 \/ 28%\);/.test(files.editor) + && /--monokai-selection-ring:\s*rgb\(200 63 103 \/ 22%\);/.test(files.editor) + && /\.canvas-node\.is-focused[\s\S]*?box-shadow:\s*0 0 0 2px var\(--monokai-selection-ring\);/.test(files.canvas), + ], + [ + "紧凑模式同步压缩 Callout 与表格", + /body\.monokai-syntax-compact[\s\S]*?--callout-padding:\s*0\.9rem;/.test(files.styleSettings) + && /body\.monokai-syntax-compact[\s\S]*?--callout-content-padding:\s*var\(--spacing-3\) 0 0 0;/.test(files.styleSettings) + && /body\.monokai-syntax-compact[\s\S]*?\.markdown-rendered th,[\s\S]*?\.markdown-rendered td[\s\S]*?padding:\s*0\.35rem 0\.5rem;/.test(files.styleSettings), + ], + [ + "标签圆角使用主题半径系统", + /\.tag\s*\{[\s\S]*?border-radius:\s*var\(--monokai-tag-radius, var\(--radius-s\)\);/.test(files.base) + && !/border-radius:\s*999px;/.test(files.base), + ], + [ + "链接具有键盘焦点可见状态", + /a:focus-visible,[\s\S]*?\.markdown-rendered a:focus-visible,[\s\S]*?\.cm-hmd-internal-link:focus-visible[\s\S]*?outline:\s*2px solid var\(--interactive-accent\);/.test(files.editor) + && /outline-offset:\s*2px;/.test(files.editor), + ], + [ + "标签页和 Modal 输入具备克制动效与舒适内边距", + /\.workspace-tab-header\s*\{[\s\S]*?transition:[\s\S]*?color 140ms ease,[\s\S]*?background-color 140ms ease,[\s\S]*?box-shadow 140ms ease;/.test(files.tabs) + && /\.prompt-input,[\s\S]*?\.modal textarea\s*\{[\s\S]*?padding:\s*0\.45rem 0\.6rem;[\s\S]*?transition:[\s\S]*?border-color 140ms ease,[\s\S]*?box-shadow 140ms ease;/.test(files.modals), + ], + [ + "表格表头具备独立背景与文字层级", + /--monokai-table-header-background:\s*rgb\(248 248 242 \/ 6%\);/.test(files.editor) + && /--monokai-table-header-background:\s*rgb\(61 61 61 \/ 6%\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?th\s*\{(?=[\s\S]*?color:\s*var\(--text-normal\);)(?=[\s\S]*?background-color:\s*var\(--monokai-table-header-background\);)[\s\S]*?\}/.test(files.editor), + ], + [ + "深色代码 token 使用 Monokai Pro palette", + /\.theme-dark[\s\S]*?--code-keyword:\s*#\{\$color-dark-red-soft\};/.test(files.base) + && /\.theme-dark[\s\S]*?--code-operator:\s*#\{\$color-dark-orange-soft\};/.test(files.base) + && /\.theme-dark[\s\S]*?--code-string:\s*#\{\$color-dark-yellow-soft\};/.test(files.base) + && /\.theme-dark[\s\S]*?--code-function:\s*#\{\$color-dark-green-soft\};/.test(files.base) + && /\.theme-dark[\s\S]*?--code-property:\s*#\{\$color-dark-cyan-soft\};/.test(files.base) + && /\.theme-dark[\s\S]*?--code-value:\s*#\{\$color-dark-purple-soft\};/.test(files.base), + ], + [ + "列表符号阅读模式与编辑模式共享颜色变量", + /\.markdown-rendered[\s\S]*?ul > li::marker,[\s\S]*?ol > li::marker[\s\S]*?color:\s*var\(--list-marker-color\);/.test(files.editor) + && /\.cm-formatting-list[\s\S]*?color:\s*var\(--list-marker-color\);/.test(files.editor), + ], + [ + "已完成任务阅读模式与编辑模式共享弱化文本变量", + /\.markdown-rendered[\s\S]*?\.task-list-item\.is-checked[\s\S]*?color:\s*var\(--monokai-task-done-color\);/.test(files.editor) + && /\.HyperMD-task-line\[data-task="x"\][\s\S]*?color:\s*var\(--monokai-task-done-color\);/.test(files.editor) + && /\.HyperMD-task-line\[data-task="X"\][\s\S]*?color:\s*var\(--monokai-task-done-color\);/.test(files.editor), + ], + [ + "编辑模式代码块使用阅读模式代码块背景和等宽字体", + /\.markdown-rendered[\s\S]*?pre\s*\{[\s\S]*?background-color:\s*var\(--code-background\);[\s\S]*?font-family:\s*var\(--font-monospace-theme\);/.test(files.editor) + && /\.HyperMD-codeblock[\s\S]*?background-color:\s*var\(--code-background\);[\s\S]*?font-family:\s*var\(--font-monospace-theme\);/.test(files.editor), + ], + [ + "删除线阅读模式与编辑模式共享弱化颜色", + /--monokai-strikethrough-color:\s*var\(--text-muted\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?del,[\s\S]*?s\s*\{[\s\S]*?color:\s*var\(--monokai-strikethrough-color\);[\s\S]*?text-decoration-color:\s*var\(--monokai-strikethrough-color\);/.test(files.editor) + && /\.cm-strikethrough[\s\S]*?color:\s*var\(--monokai-strikethrough-color\);[\s\S]*?text-decoration-color:\s*var\(--monokai-strikethrough-color\);/.test(files.editor), + ], + [ + "编辑模式 Markdown 格式符号统一弱化", + /--monokai-formatting-marker-color:\s*var\(--text-faint\);/.test(files.editor) + && /\.cm-formatting-strong,[\s\S]*?\.cm-formatting-em,[\s\S]*?\.cm-formatting-strikethrough,[\s\S]*?\.cm-formatting-code,[\s\S]*?\.cm-formatting-highlight[\s\S]*?color:\s*var\(--monokai-formatting-marker-color\);/.test(files.editor), + ], + [ + "高亮阅读模式与编辑模式共享圆角和横向内边距", + /--monokai-highlight-radius:\s*var\(--radius-s\);/.test(files.editor) + && /--monokai-highlight-padding-inline:\s*0\.12em;/.test(files.editor) + && /\.markdown-rendered[\s\S]*?mark\s*\{[\s\S]*?border-radius:\s*var\(--monokai-highlight-radius\);[\s\S]*?padding-inline:\s*var\(--monokai-highlight-padding-inline\);/.test(files.editor) + && /\.cm-highlight[\s\S]*?border-radius:\s*var\(--monokai-highlight-radius\);[\s\S]*?padding-inline:\s*var\(--monokai-highlight-padding-inline\);/.test(files.editor), + ], + [ + "水平分割线阅读模式与编辑模式共享颜色和间距变量", + /--monokai-hr-color:\s*var\(--background-modifier-border\);/.test(files.editor) + && /--monokai-hr-thickness:\s*1px;/.test(files.editor) + && /--monokai-hr-spacing:\s*#\{\$spacing-5\};/.test(files.editor) + && /\.markdown-rendered[\s\S]*?hr\s*\{[\s\S]*?border-top:\s*var\(--monokai-hr-thickness\) solid var\(--monokai-hr-color\);[\s\S]*?margin-block:\s*var\(--monokai-hr-spacing\);/.test(files.editor) + && /\.HyperMD-hr[\s\S]*?color:\s*var\(--monokai-formatting-marker-color\);[\s\S]*?padding-block:\s*calc\(var\(--monokai-hr-spacing\) \/ 2\);/.test(files.editor) + && /\.cm-hr[\s\S]*?color:\s*var\(--monokai-hr-color\);/.test(files.editor), + ], + [ + "脚注引用阅读模式与编辑模式共享链接层级", + /--monokai-footnote-color:\s*var\(--link-color\);/.test(files.editor) + && /--monokai-footnote-marker-color:\s*var\(--text-faint\);/.test(files.editor) + && /--monokai-footnote-font-size:\s*0\.85em;/.test(files.editor) + && /\.markdown-rendered[\s\S]*?\.footnote-ref,[\s\S]*?\.footnote-link[\s\S]*?color:\s*var\(--monokai-footnote-color\);[\s\S]*?font-size:\s*var\(--monokai-footnote-font-size\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?\.footnote-backref[\s\S]*?color:\s*var\(--monokai-footnote-marker-color\);/.test(files.editor) + && /\.cm-footref[\s\S]*?color:\s*var\(--monokai-footnote-color\);[\s\S]*?font-size:\s*var\(--monokai-footnote-font-size\);/.test(files.editor) + && /\.cm-formatting-footref[\s\S]*?color:\s*var\(--monokai-footnote-marker-color\);/.test(files.editor), + ], + [ + "图片与编辑模式嵌入共享边框圆角背景", + /--monokai-media-radius:\s*var\(--radius-m\);/.test(files.editor) + && /--monokai-media-border-color:\s*var\(--background-modifier-border\);/.test(files.editor) + && /--monokai-media-background:\s*var\(--background-secondary\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?img,[\s\S]*?\.image-embed\s*\{[\s\S]*?background-color:\s*var\(--monokai-media-background\);[\s\S]*?border:\s*1px solid var\(--monokai-media-border-color\);[\s\S]*?border-radius:\s*var\(--monokai-media-radius\);/.test(files.editor) + && /\.markdown-source-view\.mod-cm6[\s\S]*?\.image-embed,[\s\S]*?\.cm-embed-block\s*\{[\s\S]*?background-color:\s*var\(--monokai-media-background\);[\s\S]*?border:\s*1px solid var\(--monokai-media-border-color\);[\s\S]*?border-radius:\s*var\(--monokai-media-radius\);/.test(files.editor), + ], + [ + "标签阅读模式与编辑模式共享胶囊样式变量", + /--monokai-tag-color:\s*var\(--link-color\);/.test(files.editor) + && /--monokai-tag-background:\s*var\(--background-secondary\);/.test(files.editor) + && /--monokai-tag-border-color:\s*var\(--background-modifier-border\);/.test(files.editor) + && /--monokai-tag-radius:\s*var\(--radius-s\);/.test(files.editor) + && /\.tag\s*\{[\s\S]*?color:\s*var\(--monokai-tag-color, var\(--link-color\)\);[\s\S]*?background-color:\s*var\(--monokai-tag-background, var\(--background-secondary\)\);[\s\S]*?border:\s*1px solid var\(--monokai-tag-border-color, var\(--background-modifier-border\)\);[\s\S]*?border-radius:\s*var\(--monokai-tag-radius, var\(--radius-s\)\);/.test(files.base) + && /\.cm-hashtag[\s\S]*?color:\s*var\(--monokai-tag-color\);[\s\S]*?background-color:\s*var\(--monokai-tag-background\);[\s\S]*?border:\s*1px solid var\(--monokai-tag-border-color\);[\s\S]*?border-radius:\s*var\(--monokai-tag-radius\);/.test(files.editor), + ], + [ + "链接阅读模式与编辑模式共享装饰和格式符号变量", + /--monokai-link-decoration-color:\s*var\(--link-color\);/.test(files.editor) + && /--monokai-link-decoration-thickness:\s*1px;/.test(files.editor) + && /a,[\s\S]*?\.markdown-rendered a,[\s\S]*?\.cm-hmd-internal-link,[\s\S]*?\.cm-link,[\s\S]*?\.cm-url[\s\S]*?color:\s*var\(--link-color\);[\s\S]*?text-decoration-color:\s*var\(--monokai-link-decoration-color\);[\s\S]*?text-decoration-thickness:\s*var\(--monokai-link-decoration-thickness\);/.test(files.editor) + && /\.cm-formatting-link[\s\S]*?color:\s*var\(--monokai-formatting-marker-color\);/.test(files.editor), + ], + [ + "段落阅读模式与编辑模式共享行高和段落节奏变量", + /--monokai-paragraph-line-height:\s*var\(--line-height-normal\);/.test(files.editor) + && /--monokai-paragraph-spacing:\s*#\{\$spacing-3\};/.test(files.editor) + && /--p-spacing:\s*var\(--monokai-paragraph-spacing\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?p\s*\{[\s\S]*?line-height:\s*var\(--monokai-paragraph-line-height\);[\s\S]*?margin-block:\s*0 var\(--monokai-paragraph-spacing\);/.test(files.editor) + && /\.markdown-source-view\.mod-cm6[\s\S]*?\.cm-line\s*\{[\s\S]*?line-height:\s*var\(--monokai-paragraph-line-height\);/.test(files.editor) + && /\.HyperMD-paragraph[\s\S]*?padding-block-end:\s*calc\(var\(--monokai-paragraph-spacing\) \/ 3\);/.test(files.editor), + ], + [ + "Markdown 表格源码在编辑模式具备等宽和分隔符层级", + /--monokai-table-source-background:\s*var\(--background-secondary\);/.test(files.editor) + && /--monokai-table-source-font-family:\s*var\(--font-monospace-theme\);/.test(files.editor) + && /--monokai-table-source-separator-color:\s*var\(--text-faint\);/.test(files.editor) + && /\.HyperMD-table-row[\s\S]*?font-family:\s*var\(--monokai-table-source-font-family\);[\s\S]*?background-color:\s*var\(--monokai-table-source-background\);/.test(files.editor) + && /\.cm-hmd-table-sep,[\s\S]*?\.cm-hmd-table-align,[\s\S]*?\.cm-formatting-table[\s\S]*?color:\s*var\(--monokai-table-source-separator-color\);/.test(files.editor) + && /\.cm-table[\s\S]*?color:\s*var\(--text-normal\);/.test(files.editor), + ], + [ + "Callout 编辑态标题和源码标记贴近阅读模式层级", + /--monokai-callout-title-gap:\s*var\(--spacing-2\);/.test(files.activeVisual) + && /--monokai-callout-source-marker-color:\s*var\(--text-faint\);/.test(files.activeVisual) + && /\.callout-title,[\s\S]*?\.cm-callout-title[\s\S]*?gap:\s*var\(--monokai-callout-title-gap\);/.test(files.activeVisual) + && /body \.markdown-source-view\.mod-cm6 \.cm-callout-content[\s\S]*?color:\s*var\(--text-normal\);/.test(files.activeVisual) + && /body \.markdown-source-view\.mod-cm6 \.cm-formatting-callout,[\s\S]*?body \.markdown-source-view\.mod-cm6 \.cm-callout \.cm-formatting[\s\S]*?color:\s*var\(--monokai-callout-source-marker-color\);/.test(files.activeVisual), + ], + [ + "块引用多行和嵌套层级在阅读模式与编辑模式收敛", + /--monokai-blockquote-nested-gap:\s*#\{\$spacing-3\};/.test(files.activeVisual) + && /body \.markdown-rendered blockquote blockquote[\s\S]*?margin-block:\s*var\(--monokai-blockquote-nested-gap\);/.test(files.activeVisual) + && /body \.markdown-source-view\.mod-cm6 \.cm-line\.HyperMD-quote \+ \.cm-line\.HyperMD-quote,[\s\S]*?body \.markdown-source-view\.mod-cm6 \.HyperMD-quote \+ \.HyperMD-quote[\s\S]*?border-start-start-radius:\s*0;[\s\S]*?border-end-start-radius:\s*0;/.test(files.activeVisual) + && /\.cm-quote-2,[\s\S]*?\.cm-quote-3[\s\S]*?color:\s*var\(--text-normal\);/.test(files.activeVisual), + ], + [ + "代码块阅读模式与编辑模式共享边框圆角并弱化 fence token", + /--monokai-codeblock-border-color:\s*var\(--background-modifier-border\);/.test(files.editor) + && /--monokai-codeblock-radius:\s*var\(--radius-m\);/.test(files.editor) + && /--monokai-codeblock-padding:\s*#\{\$spacing-4\};/.test(files.editor) + && /--monokai-codeblock-padding-inline:\s*#\{\$spacing-4\};/.test(files.editor) + && /\.markdown-rendered[\s\S]*?pre\s*\{[\s\S]*?border:\s*1px solid var\(--monokai-codeblock-border-color\);[\s\S]*?border-radius:\s*var\(--monokai-codeblock-radius\);[\s\S]*?padding:\s*var\(--monokai-codeblock-padding\);/.test(files.editor) + && /\.HyperMD-codeblock[\s\S]*?background-color:\s*var\(--code-background\);[\s\S]*?font-family:\s*var\(--font-monospace-theme\);[\s\S]*?padding-inline:\s*var\(--monokai-codeblock-padding-inline\);/.test(files.editor) + && /\.cm-formatting-code-block,[\s\S]*?\.cm-hmd-codeblock-begin,[\s\S]*?\.cm-hmd-codeblock-end[\s\S]*?color:\s*var\(--monokai-formatting-marker-color\);/.test(files.editor), + ], + [ + "附件与内部嵌入扩展共享媒体容器变量", + /--monokai-embed-padding:\s*#\{\$spacing-3\};/.test(files.editor) + && /\.markdown-rendered[\s\S]*?\.file-embed,[\s\S]*?\.pdf-embed,[\s\S]*?\.audio-embed,[\s\S]*?\.video-embed,[\s\S]*?\.internal-embed\s*\{[\s\S]*?background-color:\s*var\(--monokai-media-background\);[\s\S]*?border:\s*1px solid var\(--monokai-media-border-color\);[\s\S]*?border-radius:\s*var\(--monokai-media-radius\);[\s\S]*?padding:\s*var\(--monokai-embed-padding\);/.test(files.editor) + && /\.markdown-source-view\.mod-cm6[\s\S]*?\.file-embed,[\s\S]*?\.pdf-embed,[\s\S]*?\.audio-embed,[\s\S]*?\.video-embed,[\s\S]*?\.internal-embed,[\s\S]*?\.cm-embed-block\s*\{[\s\S]*?background-color:\s*var\(--monokai-media-background\);[\s\S]*?border:\s*1px solid var\(--monokai-media-border-color\);[\s\S]*?border-radius:\s*var\(--monokai-media-radius\);[\s\S]*?padding:\s*var\(--monokai-embed-padding\);/.test(files.editor), + ], + [ + "扩展任务状态阅读模式与编辑模式共享语义颜色", + /--monokai-task-forward-color:\s*var\(--text-accent\);/.test(files.editor) + && /--monokai-task-cancelled-color:\s*var\(--text-faint\);/.test(files.editor) + && /--monokai-task-question-color:\s*var\(--link-color\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?\.task-list-item\[data-task=">"\][\s\S]*?color:\s*var\(--monokai-task-forward-color\);/.test(files.editor) + && /\.markdown-rendered[\s\S]*?\.task-list-item\[data-task="-"\][\s\S]*?color:\s*var\(--monokai-task-cancelled-color\);[\s\S]*?text-decoration-line:\s*line-through;/.test(files.editor) + && /\.markdown-rendered[\s\S]*?\.task-list-item\[data-task="\?"\][\s\S]*?color:\s*var\(--monokai-task-question-color\);/.test(files.editor) + && /\.HyperMD-task-line\[data-task=">"\][\s\S]*?color:\s*var\(--monokai-task-forward-color\);/.test(files.editor) + && /\.HyperMD-task-line\[data-task="-"\][\s\S]*?color:\s*var\(--monokai-task-cancelled-color\);[\s\S]*?text-decoration-line:\s*line-through;/.test(files.editor) + && /\.HyperMD-task-line\[data-task="\?"\][\s\S]*?color:\s*var\(--monokai-task-question-color\);/.test(files.editor), + ], +]; + +let hasFailure = false; + +for (const [label, passed] of checks) { + console.log(`${label}: ${passed ? "通过" : "失败"}`); + + if (!passed) { + hasFailure = true; + } +} + +if (hasFailure) { + process.exitCode = 1; +} diff --git a/scripts/verify-versions.js b/scripts/verify-versions.js new file mode 100644 index 0000000..33410c3 --- /dev/null +++ b/scripts/verify-versions.js @@ -0,0 +1,78 @@ +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +export function readJsonFile(path) { + return JSON.parse(readFileSync(path, "utf8")); +} + +export function normalizeReleaseTag(tag) { + return tag?.replace(/^refs\/tags\//, "").replace(/^v/, ""); +} + +export function checkVersionConsistency({ + packageJson, + manifestJson, + versionsJson, + releaseTag = process.env.GITHUB_REF_NAME, + requireReleaseTag = Boolean(process.env.GITHUB_ACTIONS && process.env.GITHUB_REF_TYPE === "tag"), +}) { + const failures = []; + const packageVersion = packageJson.version; + const manifestVersion = manifestJson.version; + const minAppVersion = manifestJson.minAppVersion; + const tagVersion = normalizeReleaseTag(releaseTag); + + if (!packageVersion) { + failures.push("package.json 缺少 version 字段"); + } + + if (manifestVersion !== packageVersion) { + failures.push(`manifest.json 版本 ${manifestVersion ?? "缺失"} 与 package.json 版本 ${packageVersion ?? "缺失"} 不一致`); + } + + if (!versionsJson[packageVersion]) { + failures.push(`versions.json 缺少当前版本 ${packageVersion} 的映射`); + } + + if (versionsJson[packageVersion] !== minAppVersion) { + failures.push(`versions.json 中 ${packageVersion} 的最低 Obsidian 版本应为 ${minAppVersion}`); + } + + if (requireReleaseTag && !tagVersion) { + failures.push("发布环境缺少版本标签"); + } + + if (tagVersion && tagVersion !== packageVersion) { + failures.push(`发布标签 ${releaseTag} 与 package.json 版本 ${packageVersion} 不一致`); + } + + return { + passed: failures.length === 0, + failures, + }; +} + +function runCli() { + const rootDir = resolve(import.meta.dirname, ".."); + const result = checkVersionConsistency({ + packageJson: readJsonFile(resolve(rootDir, "package.json")), + manifestJson: readJsonFile(resolve(rootDir, "manifest.json")), + versionsJson: readJsonFile(resolve(rootDir, "versions.json")), + }); + + if (result.passed) { + console.log("版本一致性检查: 通过"); + return; + } + + for (const failure of result.failures) { + console.error(`版本一致性检查: ${failure}`); + } + + process.exitCode = 1; +} + +if (existsSync(process.argv[1] ?? "") && import.meta.url === pathToFileURL(process.argv[1]).href) { + runCli(); +} diff --git a/src/scss/_active-visual-overrides.scss b/src/scss/_active-visual-overrides.scss index d9d1ee5..5353421 100644 --- a/src/scss/_active-visual-overrides.scss +++ b/src/scss/_active-visual-overrides.scss @@ -9,6 +9,10 @@ body:is(.theme-dark, .theme-light) { --callout-content-padding: var(--spacing-4) 0 0 0; --callout-border-width: 0; --callout-radius: var(--radius-s); + --monokai-blockquote-content-gap: 1.25rem; + --monokai-blockquote-nested-gap: #{$spacing-3}; + --monokai-callout-title-gap: var(--spacing-2); + --monokai-callout-source-marker-color: var(--text-faint); } /* ===== 块引用(阅读 + 编辑统一) ===== */ @@ -19,7 +23,11 @@ body .markdown-rendered blockquote { border-inline-start: 2px solid var(--monokai-blockquote-border); border-radius: 0 var(--radius-s) var(--radius-s) 0; color: var(--text-normal); - padding: #{$spacing-3} #{$spacing-3} #{$spacing-3} 1.25rem; + padding: #{$spacing-3} #{$spacing-3} #{$spacing-3} var(--monokai-blockquote-content-gap); +} + +body .markdown-rendered blockquote blockquote { + margin-block: var(--monokai-blockquote-nested-gap); } body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote, @@ -29,14 +37,25 @@ body .markdown-source-view.mod-cm6 .HyperMD-quote { border-radius: 0 var(--radius-s) var(--radius-s) 0; } +body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote + .cm-line.HyperMD-quote, +body .markdown-source-view.mod-cm6 .HyperMD-quote + .HyperMD-quote { + border-start-start-radius: 0; + border-end-start-radius: 0; +} + body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote .cm-quote, body .markdown-source-view.mod-cm6 .HyperMD-quote .cm-quote { color: var(--text-normal); border-inline-start: 0; - margin-left: 0.6rem; + margin-left: var(--monokai-blockquote-content-gap); padding-inline-start: 0; } +body .markdown-source-view.mod-cm6 .cm-quote-2, +body .markdown-source-view.mod-cm6 .cm-quote-3 { + color: var(--text-normal); +} + body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote .cm-formatting-quote, body .markdown-source-view.mod-cm6 .HyperMD-quote .cm-formatting-quote { display: none; @@ -68,7 +87,7 @@ body .markdown-source-view.mod-cm6 .callout-title, body .markdown-source-view.mod-cm6 .cm-callout-title { color: var(--callout-border, var(--monokai-callout-note-border)); font-weight: 700; - gap: var(--spacing-2); + gap: var(--monokai-callout-title-gap); padding-inline-start: 0; } @@ -83,6 +102,15 @@ body .markdown-source-view.mod-cm6 .cm-callout-content { padding-inline-start: 0; } +body .markdown-source-view.mod-cm6 .cm-callout-content { + color: var(--text-normal); +} + +body .markdown-source-view.mod-cm6 .cm-formatting-callout, +body .markdown-source-view.mod-cm6 .cm-callout .cm-formatting { + color: var(--monokai-callout-source-marker-color); +} + /* ===== Callout 语义色分组映射 ===== */ body .markdown-preview-view.markdown-rendered .callout[data-callout="note"], diff --git a/src/scss/_base.scss b/src/scss/_base.scss index b71e8e1..b1043ad 100644 --- a/src/scss/_base.scss +++ b/src/scss/_base.scss @@ -55,16 +55,16 @@ --list-marker-color: #{$color-dark-purple}; --code-normal: #{$color-dark-text-normal}; --code-background: #{$color-dark-background-secondary}; - --code-comment: #{$color-dark-text-faint}; - --code-function: #{$color-dark-green}; - --code-important: #{$color-dark-magenta}; - --code-keyword: #{$color-dark-magenta}; - --code-operator: #{$color-dark-red-soft}; - --code-property: #{$color-dark-cyan}; + --code-comment: #72736f; + --code-function: #{$color-dark-green-soft}; + --code-important: #{$color-dark-red-soft}; + --code-keyword: #{$color-dark-red-soft}; + --code-operator: #{$color-dark-orange-soft}; + --code-property: #{$color-dark-cyan-soft}; --code-punctuation: #{$color-dark-text-muted}; - --code-string: #{$color-dark-yellow}; - --code-tag: #{$color-dark-magenta}; - --code-value: #{$color-dark-purple}; + --code-string: #{$color-dark-yellow-soft}; + --code-tag: #{$color-dark-red-soft}; + --code-value: #{$color-dark-purple-soft}; --ribbon-background: #{$color-dark-background-secondary}; --nav-item-background-hover: #{$color-dark-hover}; --nav-item-background-active: #{$color-dark-active}; @@ -248,10 +248,10 @@ input[type="checkbox"]:checked { } .tag { - color: var(--link-color); - background-color: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: 999px; + color: var(--monokai-tag-color, var(--link-color)); + background-color: var(--monokai-tag-background, var(--background-secondary)); + border: 1px solid var(--monokai-tag-border-color, var(--background-modifier-border)); + border-radius: var(--monokai-tag-radius, var(--radius-s)); } .tag:hover { diff --git a/src/scss/components/_editor.scss b/src/scss/components/_editor.scss index a559ff5..65b60da 100644 --- a/src/scss/components/_editor.scss +++ b/src/scss/components/_editor.scss @@ -6,12 +6,48 @@ --bold-weight: 900; --italic-color: var(--text-normal); --heading-spacing: #{$spacing-4}; + --monokai-heading-gap-h1: 1.25rem; + --monokai-heading-gap-h2: 1.1rem; + --monokai-heading-gap-h3: 0.95rem; + --monokai-heading-gap-h4: 0.8rem; + --monokai-heading-gap-h5: 0.7rem; + --monokai-heading-gap-h6: 0.6rem; --monokai-heading-weight: 600; --monokai-inline-code-color: #a6e22e; --monokai-inline-code-background: var(--code-background); + --monokai-inline-code-padding: 0.1em 0.3em; + --monokai-formatting-marker-color: var(--text-faint); + --monokai-strikethrough-color: var(--text-muted); + --monokai-highlight-radius: var(--radius-s); + --monokai-highlight-padding-inline: 0.12em; + --monokai-hr-color: var(--background-modifier-border); + --monokai-hr-thickness: 1px; + --monokai-hr-spacing: #{$spacing-5}; + --monokai-footnote-color: var(--link-color); + --monokai-footnote-marker-color: var(--text-faint); + --monokai-footnote-font-size: 0.85em; + --monokai-media-radius: var(--radius-m); + --monokai-media-border-color: var(--background-modifier-border); + --monokai-media-background: var(--background-secondary); + --monokai-embed-padding: #{$spacing-3}; + --monokai-tag-color: var(--link-color); + --monokai-tag-background: var(--background-secondary); + --monokai-tag-border-color: var(--background-modifier-border); + --monokai-tag-radius: var(--radius-s); + --monokai-link-decoration-color: var(--link-color); + --monokai-link-decoration-thickness: 1px; + --monokai-paragraph-line-height: var(--line-height-normal); + --monokai-paragraph-spacing: #{$spacing-3}; + --monokai-table-source-background: var(--background-secondary); + --monokai-table-source-font-family: var(--font-monospace-theme); + --monokai-table-source-separator-color: var(--text-faint); + --monokai-codeblock-border-color: var(--background-modifier-border); + --monokai-codeblock-radius: var(--radius-m); + --monokai-codeblock-padding: #{$spacing-4}; + --monokai-codeblock-padding-inline: #{$spacing-4}; --callout-blend-mode: normal; --callout-icon-size: 1.25em; - --p-spacing: #{$spacing-3}; + --p-spacing: var(--monokai-paragraph-spacing); -webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; @@ -50,7 +86,13 @@ body.theme-dark { --monokai-inline-code-color: #a6e22e; --monokai-inline-code-background: var(--code-background); --monokai-table-border: rgb(248 248 242 / 18%); + --monokai-table-header-background: rgb(248 248 242 / 6%); --monokai-task-done-color: rgb(248 248 242 / 68%); + --monokai-task-forward-color: var(--text-accent); + --monokai-task-cancelled-color: var(--text-faint); + --monokai-task-question-color: var(--link-color); + --monokai-modal-shadow: 0 18px 50px rgb(0 0 0 / 35%); + --monokai-selection-ring: rgb(249 38 114 / 28%); } body.theme-light { @@ -86,7 +128,13 @@ body.theme-light { --monokai-inline-code-color: #269d69; --monokai-inline-code-background: var(--code-background); --monokai-table-border: rgb(61 61 61 / 18%); + --monokai-table-header-background: rgb(61 61 61 / 6%); --monokai-task-done-color: rgb(61 61 61 / 66%); + --monokai-task-forward-color: var(--text-accent); + --monokai-task-cancelled-color: var(--text-faint); + --monokai-task-question-color: var(--link-color); + --monokai-modal-shadow: 0 16px 36px rgb(61 61 61 / 16%); + --monokai-selection-ring: rgb(200 63 103 / 22%); } .markdown-source-view.mod-cm6 .inline-title { @@ -96,10 +144,45 @@ body.theme-light { .markdown-rendered { h1, h2, - h3 { + h3, + h4, + h5, + h6 { font-weight: var(--monokai-heading-weight); letter-spacing: 0; - margin-block: calc(var(--heading-spacing) * 1.5) var(--spacing-4); + margin-block-start: calc(var(--heading-spacing) * 1.5); + } + + h1 { + margin-block-end: var(--monokai-heading-gap-h1); + } + + h2 { + margin-block-end: var(--monokai-heading-gap-h2); + } + + h3 { + margin-block-end: var(--monokai-heading-gap-h3); + } + + h4 { + color: var(--h4-color); + margin-block-end: var(--monokai-heading-gap-h4); + } + + h5 { + color: var(--h5-color); + margin-block-end: var(--monokai-heading-gap-h5); + } + + h6 { + color: var(--h6-color); + margin-block-end: var(--monokai-heading-gap-h6); + } + + p { + line-height: var(--monokai-paragraph-line-height); + margin-block: 0 var(--monokai-paragraph-spacing); } strong { @@ -111,11 +194,17 @@ body.theme-light { color: var(--italic-color); } + del, + s { + color: var(--monokai-strikethrough-color); + text-decoration-color: var(--monokai-strikethrough-color); + } + mark { color: var(--monokai-highlight-text); background-color: var(--text-highlight-bg); - border-radius: var(--radius-s); - padding-inline: 0.12em; + border-radius: var(--monokai-highlight-radius); + padding-inline: var(--monokai-highlight-padding-inline); } ul > li::marker, @@ -131,6 +220,19 @@ body.theme-light { color: var(--monokai-task-done-color); } + .task-list-item[data-task=">"] { + color: var(--monokai-task-forward-color); + } + + .task-list-item[data-task="-"] { + color: var(--monokai-task-cancelled-color); + text-decoration-line: line-through; + } + + .task-list-item[data-task="?"] { + color: var(--monokai-task-question-color); + } + table { border: 1px solid var(--monokai-table-border); } @@ -140,21 +242,27 @@ body.theme-light { border: 1px solid var(--monokai-table-border); } + th { + color: var(--text-normal); + background-color: var(--monokai-table-header-background); + font-weight: 700; + } + code { color: var(--monokai-inline-code-color); background-color: var(--monokai-inline-code-background); border: 0; border-radius: var(--radius-s); box-shadow: none; - padding: 0.1em 0.3em; + padding: var(--monokai-inline-code-padding); } pre { background-color: var(--code-background); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); + border: 1px solid var(--monokai-codeblock-border-color); + border-radius: var(--monokai-codeblock-radius); font-family: var(--font-monospace-theme); - padding: #{$spacing-4}; + padding: var(--monokai-codeblock-padding); code { background-color: transparent; @@ -163,9 +271,109 @@ body.theme-light { padding: 0; } } + + hr { + border: 0; + border-top: var(--monokai-hr-thickness) solid var(--monokai-hr-color); + margin-block: var(--monokai-hr-spacing); + } + + .footnote-ref, + .footnote-link { + color: var(--monokai-footnote-color); + font-size: var(--monokai-footnote-font-size); + } + + .footnote-backref { + color: var(--monokai-footnote-marker-color); + } + + img, + .image-embed { + max-width: 100%; + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + } + + .file-embed, + .pdf-embed, + .audio-embed, + .video-embed, + .internal-embed { + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + padding: var(--monokai-embed-padding); + } } .markdown-source-view.mod-cm6 { + /* stylelint-disable selector-class-pattern -- Obsidian/CodeMirror 内部类名。 */ + .HyperMD-header-1 { + padding-block-end: var(--monokai-heading-gap-h1); + } + + .HyperMD-header-2 { + padding-block-end: var(--monokai-heading-gap-h2); + } + + .HyperMD-header-3 { + padding-block-end: var(--monokai-heading-gap-h3); + } + + .HyperMD-header-4 { + padding-block-end: var(--monokai-heading-gap-h4); + } + + .HyperMD-header-5 { + padding-block-end: var(--monokai-heading-gap-h5); + } + + .HyperMD-header-6 { + padding-block-end: var(--monokai-heading-gap-h6); + } + + .HyperMD-task-line[data-task="x"], + .HyperMD-task-line[data-task="X"] { + color: var(--monokai-task-done-color); + } + + .HyperMD-task-line[data-task=">"] { + color: var(--monokai-task-forward-color); + } + + .HyperMD-task-line[data-task="-"] { + color: var(--monokai-task-cancelled-color); + text-decoration-line: line-through; + } + + .HyperMD-task-line[data-task="?"] { + color: var(--monokai-task-question-color); + } + + .HyperMD-codeblock { + background-color: var(--code-background); + font-family: var(--font-monospace-theme); + padding-inline: var(--monokai-codeblock-padding-inline); + } + + .HyperMD-hr { + color: var(--monokai-formatting-marker-color); + padding-block: calc(var(--monokai-hr-spacing) / 2); + } + + .HyperMD-paragraph { + padding-block-end: calc(var(--monokai-paragraph-spacing) / 3); + } + + .HyperMD-table-row { + font-family: var(--monokai-table-source-font-family); + background-color: var(--monokai-table-source-background); + } + + /* stylelint-enable selector-class-pattern */ + .cm-header-1 { color: var(--h1-color); font-weight: var(--monokai-heading-weight); @@ -181,6 +389,21 @@ body.theme-light { font-weight: var(--monokai-heading-weight); } + .cm-header-4 { + color: var(--h4-color); + font-weight: var(--monokai-heading-weight); + } + + .cm-header-5 { + color: var(--h5-color); + font-weight: var(--monokai-heading-weight); + } + + .cm-header-6 { + color: var(--h6-color); + font-weight: var(--monokai-heading-weight); + } + .cm-strong { color: var(--bold-color); font-weight: var(--bold-weight); @@ -190,18 +413,98 @@ body.theme-light { color: var(--italic-color); } + .cm-strikethrough { + color: var(--monokai-strikethrough-color); + text-decoration-color: var(--monokai-strikethrough-color); + } + .cm-link, .cm-url { color: var(--link-color); } .cm-formatting-link { - color: var(--text-faint); + color: var(--monokai-formatting-marker-color); + } + + .cm-hmd-table-sep, + .cm-hmd-table-align, + .cm-formatting-table { + color: var(--monokai-table-source-separator-color); + } + + .cm-formatting-code-block, + .cm-hmd-codeblock-begin, + .cm-hmd-codeblock-end { + color: var(--monokai-formatting-marker-color); + } + + .cm-table { + color: var(--text-normal); + } + + .cm-formatting-list { + color: var(--list-marker-color); + } + + .cm-formatting-strong, + .cm-formatting-em, + .cm-formatting-strikethrough, + .cm-formatting-code, + .cm-formatting-highlight { + color: var(--monokai-formatting-marker-color); } .cm-highlight { color: var(--monokai-highlight-text); background-color: var(--text-highlight-bg); + border-radius: var(--monokai-highlight-radius); + padding-inline: var(--monokai-highlight-padding-inline); + } + + .cm-hr { + color: var(--monokai-hr-color); + } + + .cm-footref { + color: var(--monokai-footnote-color); + font-size: var(--monokai-footnote-font-size); + } + + .cm-formatting-footref { + color: var(--monokai-footnote-marker-color); + } + + .image-embed, + .cm-embed-block { + max-width: 100%; + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + } + + .file-embed, + .pdf-embed, + .audio-embed, + .video-embed, + .internal-embed, + .cm-embed-block { + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + padding: var(--monokai-embed-padding); + } + + .cm-hashtag { + color: var(--monokai-tag-color); + background-color: var(--monokai-tag-background); + border: 1px solid var(--monokai-tag-border-color); + border-radius: var(--monokai-tag-radius); + padding-inline: 0.25em; + } + + .cm-line { + line-height: var(--monokai-paragraph-line-height); } .cm-inline-code { @@ -210,7 +513,7 @@ body.theme-light { border: 0; border-radius: var(--radius-s); box-shadow: none; - padding: 0.05em 0.25em; + padding: var(--monokai-inline-code-padding); } } @@ -248,8 +551,12 @@ body.theme-light { a, .markdown-rendered a, -.cm-hmd-internal-link { +.cm-hmd-internal-link, +.cm-link, +.cm-url { color: var(--link-color); + text-decoration-color: var(--monokai-link-decoration-color); + text-decoration-thickness: var(--monokai-link-decoration-thickness); } a:hover, @@ -257,3 +564,11 @@ a:hover, .cm-hmd-internal-link:hover { color: var(--link-color-hover); } + +a:focus-visible, +.markdown-rendered a:focus-visible, +.cm-hmd-internal-link:focus-visible { + outline: 2px solid var(--interactive-accent); + outline-offset: 2px; + border-radius: var(--radius-s); +} diff --git a/src/scss/components/_file-icons.generated.scss b/src/scss/components/_file-icons.generated.scss index 5e369d4..ee2f15c 100644 --- a/src/scss/components/_file-icons.generated.scss +++ b/src/scss/components/_file-icons.generated.scss @@ -13,19 +13,24 @@ } .nav-file-title::before { - display: inline-block; - flex: 0 0 1rem; - width: 1rem; - margin-inline-end: 0.35rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--monokai-file-tree-slot-size); + min-width: var(--monokai-file-tree-slot-size); + height: var(--monokai-file-tree-glyph-size); + margin-inline-end: calc(-1 * var(--monokai-file-tree-slot-size)); + transform: translateX(calc(-1 * (var(--monokai-file-tree-slot-size) + var(--monokai-file-tree-icon-gap)))); --monokai-file-icon-color: #ffd866; color: var(--monokai-file-icon-color, var(--icon-color)); content: "\e658"; font-family: "monokai-pro-icons"; - font-size: 1rem; + font-size: var(--monokai-file-tree-glyph-size); font-style: normal; font-variant: normal; font-weight: 400; line-height: 1; + pointer-events: none; speak: never; text-align: center; text-rendering: auto; diff --git a/src/scss/components/_file-icons.scss b/src/scss/components/_file-icons.scss index 4c4b074..163f356 100644 --- a/src/scss/components/_file-icons.scss +++ b/src/scss/components/_file-icons.scss @@ -1,19 +1,52 @@ @forward "file-icons.generated"; +.nav-file-title, +.nav-folder-title { + --monokai-file-tree-slot-size: 1rem; + --monokai-file-tree-icon-gap: 0.35rem; + --monokai-file-tree-glyph-size: 0.8125rem; + --monokai-file-tree-toggle-size: 9px; + --monokai-file-tree-toggle-stroke-width: 1.75; + + overflow: visible; + position: relative; +} + +.nav-folder-title .nav-folder-collapse-indicator, +.nav-folder-title .collapse-icon { + flex: 0 0 var(--monokai-file-tree-slot-size); + width: var(--monokai-file-tree-slot-size); + min-width: var(--monokai-file-tree-slot-size); + margin-inline-end: var(--monokai-file-tree-icon-gap); +} + +.nav-folder-title .nav-folder-collapse-indicator svg, +.nav-folder-title .collapse-icon svg { + width: var(--monokai-file-tree-toggle-size); + height: var(--monokai-file-tree-toggle-size); + font-size: var(--monokai-file-tree-toggle-size); + stroke-width: var(--monokai-file-tree-toggle-stroke-width); +} + +.nav-folder-title .nav-folder-collapse-indicator, +.nav-folder-title .collapse-icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + .nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator, .nav-folder-title[data-path*="/"] .collapse-icon, .nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator, .nav-folder[data-path*="/"] > .nav-folder-title .collapse-icon, .nav-folder-children .nav-folder-children .nav-folder-title .nav-folder-collapse-indicator, .nav-folder-children .nav-folder-children .nav-folder-title .collapse-icon { - width: 1rem; - &::before { content: "\e64d"; display: inline-block; color: var(--icon-color); font-family: monokai-pro-icons, monospace; - font-size: 1rem; + font-size: var(--monokai-file-tree-glyph-size); font-style: normal; font-weight: 400; line-height: 1; diff --git a/src/scss/components/_modals.scss b/src/scss/components/_modals.scss index c1c18ac..69083cf 100644 --- a/src/scss/components/_modals.scss +++ b/src/scss/components/_modals.scss @@ -7,7 +7,7 @@ background-color: var(--background-secondary); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-m); - box-shadow: 0 18px 50px rgb(0 0 0 / 35%); + box-shadow: var(--monokai-modal-shadow); } .modal-title, @@ -23,6 +23,10 @@ background-color: var(--background-primary); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-s); + padding: 0.45rem 0.6rem; + transition: + border-color 140ms ease, + box-shadow 140ms ease; } .prompt-input:focus, diff --git a/src/scss/components/_tabs.scss b/src/scss/components/_tabs.scss index 0d08afb..b0546a8 100644 --- a/src/scss/components/_tabs.scss +++ b/src/scss/components/_tabs.scss @@ -9,6 +9,10 @@ color: var(--text-muted); background-color: transparent; border-radius: var(--radius-s) var(--radius-s) 0 0; + transition: + color 140ms ease, + background-color 140ms ease, + box-shadow 140ms ease; } .workspace-tab-header:hover { diff --git a/src/scss/plugins/_canvas.scss b/src/scss/plugins/_canvas.scss index d9697d0..46bc14c 100644 --- a/src/scss/plugins/_canvas.scss +++ b/src/scss/plugins/_canvas.scss @@ -25,7 +25,7 @@ .canvas-node.is-focused, .canvas-node.is-selected { border-color: var(--interactive-accent); - box-shadow: 0 0 0 2px rgb(249 38 114 / 28%); + box-shadow: 0 0 0 2px var(--monokai-selection-ring); } .canvas-node-label { diff --git a/src/scss/plugins/_style-settings.scss b/src/scss/plugins/_style-settings.scss index 64c0fe6..ef4ae6e 100644 --- a/src/scss/plugins/_style-settings.scss +++ b/src/scss/plugins/_style-settings.scss @@ -42,12 +42,20 @@ body.monokai-syntax-filter-spectrum { body.monokai-syntax-compact { @include compact-density; + --callout-padding: 0.9rem; + --callout-content-padding: var(--spacing-3) 0 0 0; + .nav-files-container { font-size: var(--monokai-nav-font-size); } .nav-file-title, .nav-folder-title { + --monokai-file-tree-slot-size: 13px; + --monokai-file-tree-icon-gap: 4px; + --monokai-file-tree-glyph-size: 12px; + --monokai-file-tree-toggle-size: 8px; + min-height: var(--nav-item-size); line-height: var(--nav-item-size); padding: 0 6px; @@ -59,27 +67,6 @@ body.monokai-syntax-compact { line-height: var(--nav-item-size); } - .nav-file-title::before { - width: 13px; - flex: 0 0 13px; - margin-inline-end: 4px; - font-size: 13px; - } - - .nav-folder-collapse-indicator { - width: 14px; - margin-inline-end: 1px; - } - - .nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator::before, - .nav-folder-title[data-path*="/"] .collapse-icon::before, - .nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator::before, - .nav-folder[data-path*="/"] > .nav-folder-title .collapse-icon::before, - .nav-folder-children .nav-folder-children .nav-folder-title .nav-folder-collapse-indicator::before, - .nav-folder-children .nav-folder-children .nav-folder-title .collapse-icon::before { - font-size: 13px; - } - .side-dock-ribbon-action { height: var(--ribbon-item-size); padding: 4px; @@ -88,6 +75,11 @@ body.monokai-syntax-compact { .markdown-rendered pre { padding: 0.75rem; } + + .markdown-rendered th, + .markdown-rendered td { + padding: 0.35rem 0.5rem; + } } body.monokai-syntax-monochrome-icons { @@ -103,6 +95,7 @@ body { --link-color: var(--monokai-link-color, var(--monokai-default-link)); --link-external-color: var(--monokai-link-color, var(--monokai-default-link)); --blockquote-border-color: var(--monokai-success-color, var(--monokai-default-success)); + --monokai-blockquote-border: var(--monokai-success-color, var(--monokai-default-success)); --checkbox-color-checked: var(--monokai-success-color, var(--monokai-default-success)); --text-highlight-bg: var(--monokai-warning-color, var(--monokai-default-warning)); --code-background: var(--monokai-code-background, var(--monokai-default-code-background)); diff --git a/test/audit-css.test.js b/test/audit-css.test.js new file mode 100644 index 0000000..8f32bdf --- /dev/null +++ b/test/audit-css.test.js @@ -0,0 +1,18 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { auditCssContent } from "../scripts/audit-css.js"; + +test("CSS 审计允许本地内联 woff 字体", () => { + const result = auditCssContent("@font-face { src: url(data:font/woff;base64,AAAA); }"); + + assert.equal(result.passed, true); +}); + +test("CSS 审计报告远程资源和 ID 选择器", () => { + const result = auditCssContent("#app { background: url(https://example.com/a.png); }"); + + assert.equal(result.passed, false); + assert.match(result.failures.join("\n"), /远程 URL/); + assert.match(result.failures.join("\n"), /ID 选择器/); +}); diff --git a/test/check-contrast.test.js b/test/check-contrast.test.js new file mode 100644 index 0000000..e2a1c63 --- /dev/null +++ b/test/check-contrast.test.js @@ -0,0 +1,12 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { contrast, toRgb } from "../scripts/check-contrast.js"; + +test("十六进制颜色转换为 RGB", () => { + assert.deepEqual(toRgb("#66d9ef"), [102, 217, 239]); +}); + +test("对比度计算符合 WCAG 公式", () => { + assert.equal(contrast("#000000", "#ffffff").toFixed(2), "21.00"); +}); diff --git a/test/qa-paths.test.js b/test/qa-paths.test.js new file mode 100644 index 0000000..e5c0247 --- /dev/null +++ b/test/qa-paths.test.js @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { DEFAULT_VAULT, resolveVaultRoot } from "../scripts/qa-paths.js"; + +test("Vault 路径优先使用命令行参数", () => { + assert.equal(resolveVaultRoot({ argv: ["D:/Vault"], env: { OBSIDIAN_VAULT: "E:/Vault" } }), "D:/Vault"); +}); + +test("Vault 路径其次使用 OBSIDIAN_VAULT", () => { + assert.equal(resolveVaultRoot({ argv: [], env: { OBSIDIAN_VAULT: "E:/Vault" } }), "E:/Vault"); +}); + +test("Vault 路径没有输入时回退到本机默认值", () => { + assert.equal(resolveVaultRoot({ argv: [], env: {} }), DEFAULT_VAULT); +}); diff --git a/test/verify-generated.test.js b/test/verify-generated.test.js new file mode 100644 index 0000000..f517292 --- /dev/null +++ b/test/verify-generated.test.js @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { compareThemeCss } from "../scripts/verify-generated.js"; + +test("主题 CSS 内容一致时哈希相同", () => { + const result = compareThemeCss({ rootThemeCss: "body{}", distThemeCss: "body{}" }); + + assert.equal(result.passed, true); +}); + +test("主题 CSS 内容不一致时报告失败", () => { + const result = compareThemeCss({ rootThemeCss: "body{}", distThemeCss: ".theme-dark{}" }); + + assert.equal(result.passed, false); +}); diff --git a/test/verify-qa.test.js b/test/verify-qa.test.js new file mode 100644 index 0000000..5807dd9 --- /dev/null +++ b/test/verify-qa.test.js @@ -0,0 +1,33 @@ +import assert from "node:assert/strict"; +import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { resolve } from "node:path"; +import test from "node:test"; + +import { verifyQaVault } from "../scripts/verify-qa.js"; + +test("QA 验证将缺失文件记录为失败项", () => { + const vaultDir = mkdtempSync(resolve(tmpdir(), "monokai-qa-")); + mkdirSync(resolve(vaultDir, ".obsidian/themes/Monokai Syntax"), { recursive: true }); + writeFileSync(resolve(vaultDir, ".obsidian/appearance.json"), "{}"); + writeFileSync(resolve(vaultDir, ".obsidian/themes/Monokai Syntax/theme.css"), ".theme-dark{}"); + writeFileSync(resolve(vaultDir, ".obsidian/themes/Monokai Syntax/manifest.json"), "{}"); + + const result = verifyQaVault(vaultDir); + + assert.equal(result.passed, false); + assert.match(result.checks.map((check) => check.label).join("\n"), /QA 文件存在/); +}); + +test("QA 验证对无效 JSON 给出失败项而不抛出异常", () => { + const vaultDir = mkdtempSync(resolve(tmpdir(), "monokai-qa-")); + mkdirSync(resolve(vaultDir, ".obsidian/themes/Monokai Syntax"), { recursive: true }); + writeFileSync(resolve(vaultDir, ".obsidian/appearance.json"), "{"); + writeFileSync(resolve(vaultDir, ".obsidian/themes/Monokai Syntax/theme.css"), ""); + writeFileSync(resolve(vaultDir, ".obsidian/themes/Monokai Syntax/manifest.json"), "{}"); + + const result = verifyQaVault(vaultDir); + + assert.equal(result.passed, false); + assert.match(result.failures.join("\n"), /appearance\.json 不是有效 JSON/); +}); diff --git a/test/verify-versions.test.js b/test/verify-versions.test.js new file mode 100644 index 0000000..009fd94 --- /dev/null +++ b/test/verify-versions.test.js @@ -0,0 +1,26 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { checkVersionConsistency } from "../scripts/verify-versions.js"; + +test("版本检查要求 package、manifest、versions 保持一致", () => { + const result = checkVersionConsistency({ + packageJson: { version: "1.2.3" }, + manifestJson: { version: "1.2.3", minAppVersion: "1.0.0" }, + versionsJson: { "1.2.3": "1.0.0" }, + }); + + assert.equal(result.passed, true); + assert.deepEqual(result.failures, []); +}); + +test("版本检查报告不一致的中文错误", () => { + const result = checkVersionConsistency({ + packageJson: { version: "1.2.3" }, + manifestJson: { version: "1.2.4", minAppVersion: "1.0.0" }, + versionsJson: { "1.2.3": "1.0.0" }, + }); + + assert.equal(result.passed, false); + assert.match(result.failures.join("\n"), /manifest\.json 版本/); +}); diff --git a/theme.css b/theme.css index 9111590..70c59a7 100644 --- a/theme.css +++ b/theme.css @@ -60,16 +60,16 @@ --list-marker-color: #ae81ff; --code-normal: #f8f8f2; --code-background: #1e1f1c; - --code-comment: #8f908a; - --code-function: #a6e22e; - --code-important: #f92672; - --code-keyword: #f92672; - --code-operator: #ff6188; - --code-property: #66d9ef; + --code-comment: #72736f; + --code-function: #a9dc76; + --code-important: #ff6188; + --code-keyword: #ff6188; + --code-operator: #fc9867; + --code-property: #78dce8; --code-punctuation: #c1c1bd; - --code-string: #e6db74; - --code-tag: #f92672; - --code-value: #ae81ff; + --code-string: #ffd866; + --code-tag: #ff6188; + --code-value: #ab9df2; --ribbon-background: #1e1f1c; --nav-item-background-hover: #3e3d32; --nav-item-background-active: #414339; @@ -253,10 +253,10 @@ input[type=checkbox]:checked { } .tag { - color: var(--link-color); - background-color: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: 999px; + color: var(--monokai-tag-color, var(--link-color)); + background-color: var(--monokai-tag-background, var(--background-secondary)); + border: 1px solid var(--monokai-tag-border-color, var(--background-modifier-border)); + border-radius: var(--monokai-tag-radius, var(--radius-s)); } .tag:hover { @@ -270,12 +270,48 @@ input[type=checkbox]:checked { --bold-weight: 900; --italic-color: var(--text-normal); --heading-spacing: 1rem; + --monokai-heading-gap-h1: 1.25rem; + --monokai-heading-gap-h2: 1.1rem; + --monokai-heading-gap-h3: 0.95rem; + --monokai-heading-gap-h4: 0.8rem; + --monokai-heading-gap-h5: 0.7rem; + --monokai-heading-gap-h6: 0.6rem; --monokai-heading-weight: 600; --monokai-inline-code-color: #a6e22e; --monokai-inline-code-background: var(--code-background); + --monokai-inline-code-padding: 0.1em 0.3em; + --monokai-formatting-marker-color: var(--text-faint); + --monokai-strikethrough-color: var(--text-muted); + --monokai-highlight-radius: var(--radius-s); + --monokai-highlight-padding-inline: 0.12em; + --monokai-hr-color: var(--background-modifier-border); + --monokai-hr-thickness: 1px; + --monokai-hr-spacing: 1.25rem; + --monokai-footnote-color: var(--link-color); + --monokai-footnote-marker-color: var(--text-faint); + --monokai-footnote-font-size: 0.85em; + --monokai-media-radius: var(--radius-m); + --monokai-media-border-color: var(--background-modifier-border); + --monokai-media-background: var(--background-secondary); + --monokai-embed-padding: 0.75rem; + --monokai-tag-color: var(--link-color); + --monokai-tag-background: var(--background-secondary); + --monokai-tag-border-color: var(--background-modifier-border); + --monokai-tag-radius: var(--radius-s); + --monokai-link-decoration-color: var(--link-color); + --monokai-link-decoration-thickness: 1px; + --monokai-paragraph-line-height: var(--line-height-normal); + --monokai-paragraph-spacing: 0.75rem; + --monokai-table-source-background: var(--background-secondary); + --monokai-table-source-font-family: var(--font-monospace-theme); + --monokai-table-source-separator-color: var(--text-faint); + --monokai-codeblock-border-color: var(--background-modifier-border); + --monokai-codeblock-radius: var(--radius-m); + --monokai-codeblock-padding: 1rem; + --monokai-codeblock-padding-inline: 1rem; --callout-blend-mode: normal; --callout-icon-size: 1.25em; - --p-spacing: 0.75rem; + --p-spacing: var(--monokai-paragraph-spacing); -webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; } @@ -313,7 +349,13 @@ body.theme-dark { --monokai-inline-code-color: #a6e22e; --monokai-inline-code-background: var(--code-background); --monokai-table-border: rgb(248 248 242 / 18%); + --monokai-table-header-background: rgb(248 248 242 / 6%); --monokai-task-done-color: rgb(248 248 242 / 68%); + --monokai-task-forward-color: var(--text-accent); + --monokai-task-cancelled-color: var(--text-faint); + --monokai-task-question-color: var(--link-color); + --monokai-modal-shadow: 0 18px 50px rgb(0 0 0 / 35%); + --monokai-selection-ring: rgb(249 38 114 / 28%); } body.theme-light { @@ -349,7 +391,13 @@ body.theme-light { --monokai-inline-code-color: #269d69; --monokai-inline-code-background: var(--code-background); --monokai-table-border: rgb(61 61 61 / 18%); + --monokai-table-header-background: rgb(61 61 61 / 6%); --monokai-task-done-color: rgb(61 61 61 / 66%); + --monokai-task-forward-color: var(--text-accent); + --monokai-task-cancelled-color: var(--text-faint); + --monokai-task-question-color: var(--link-color); + --monokai-modal-shadow: 0 16px 36px rgb(61 61 61 / 16%); + --monokai-selection-ring: rgb(200 63 103 / 22%); } .markdown-source-view.mod-cm6 .inline-title { @@ -358,10 +406,38 @@ body.theme-light { .markdown-rendered h1, .markdown-rendered h2, -.markdown-rendered h3 { +.markdown-rendered h3, +.markdown-rendered h4, +.markdown-rendered h5, +.markdown-rendered h6 { font-weight: var(--monokai-heading-weight); letter-spacing: 0; - margin-block: calc(var(--heading-spacing) * 1.5) var(--spacing-4); + margin-block-start: calc(var(--heading-spacing) * 1.5); +} +.markdown-rendered h1 { + margin-block-end: var(--monokai-heading-gap-h1); +} +.markdown-rendered h2 { + margin-block-end: var(--monokai-heading-gap-h2); +} +.markdown-rendered h3 { + margin-block-end: var(--monokai-heading-gap-h3); +} +.markdown-rendered h4 { + color: var(--h4-color); + margin-block-end: var(--monokai-heading-gap-h4); +} +.markdown-rendered h5 { + color: var(--h5-color); + margin-block-end: var(--monokai-heading-gap-h5); +} +.markdown-rendered h6 { + color: var(--h6-color); + margin-block-end: var(--monokai-heading-gap-h6); +} +.markdown-rendered p { + line-height: var(--monokai-paragraph-line-height); + margin-block: 0 var(--monokai-paragraph-spacing); } .markdown-rendered strong { color: var(--bold-color); @@ -370,11 +446,16 @@ body.theme-light { .markdown-rendered em { color: var(--italic-color); } +.markdown-rendered del, +.markdown-rendered s { + color: var(--monokai-strikethrough-color); + text-decoration-color: var(--monokai-strikethrough-color); +} .markdown-rendered mark { color: var(--monokai-highlight-text); background-color: var(--text-highlight-bg); - border-radius: var(--radius-s); - padding-inline: 0.12em; + border-radius: var(--monokai-highlight-radius); + padding-inline: var(--monokai-highlight-padding-inline); } .markdown-rendered ul > li::marker, .markdown-rendered ol > li::marker { @@ -386,6 +467,16 @@ body.theme-light { .markdown-rendered .task-list-item.is-checked > p { color: var(--monokai-task-done-color); } +.markdown-rendered .task-list-item[data-task=">"] { + color: var(--monokai-task-forward-color); +} +.markdown-rendered .task-list-item[data-task="-"] { + color: var(--monokai-task-cancelled-color); + text-decoration-line: line-through; +} +.markdown-rendered .task-list-item[data-task="?"] { + color: var(--monokai-task-question-color); +} .markdown-rendered table { border: 1px solid var(--monokai-table-border); } @@ -393,20 +484,25 @@ body.theme-light { .markdown-rendered td { border: 1px solid var(--monokai-table-border); } +.markdown-rendered th { + color: var(--text-normal); + background-color: var(--monokai-table-header-background); + font-weight: 700; +} .markdown-rendered code { color: var(--monokai-inline-code-color); background-color: var(--monokai-inline-code-background); border: 0; border-radius: var(--radius-s); box-shadow: none; - padding: 0.1em 0.3em; + padding: var(--monokai-inline-code-padding); } .markdown-rendered pre { background-color: var(--code-background); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); + border: 1px solid var(--monokai-codeblock-border-color); + border-radius: var(--monokai-codeblock-radius); font-family: var(--font-monospace-theme); - padding: 1rem; + padding: var(--monokai-codeblock-padding); } .markdown-rendered pre code { background-color: transparent; @@ -414,7 +510,91 @@ body.theme-light { box-shadow: none; padding: 0; } +.markdown-rendered hr { + border: 0; + border-top: var(--monokai-hr-thickness) solid var(--monokai-hr-color); + margin-block: var(--monokai-hr-spacing); +} +.markdown-rendered .footnote-ref, +.markdown-rendered .footnote-link { + color: var(--monokai-footnote-color); + font-size: var(--monokai-footnote-font-size); +} +.markdown-rendered .footnote-backref { + color: var(--monokai-footnote-marker-color); +} +.markdown-rendered img, +.markdown-rendered .image-embed { + max-width: 100%; + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); +} +.markdown-rendered .file-embed, +.markdown-rendered .pdf-embed, +.markdown-rendered .audio-embed, +.markdown-rendered .video-embed, +.markdown-rendered .internal-embed { + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + padding: var(--monokai-embed-padding); +} +.markdown-source-view.mod-cm6 { + /* stylelint-disable selector-class-pattern -- Obsidian/CodeMirror 内部类名。 */ +} +.markdown-source-view.mod-cm6 .HyperMD-header-1 { + padding-block-end: var(--monokai-heading-gap-h1); +} +.markdown-source-view.mod-cm6 .HyperMD-header-2 { + padding-block-end: var(--monokai-heading-gap-h2); +} +.markdown-source-view.mod-cm6 .HyperMD-header-3 { + padding-block-end: var(--monokai-heading-gap-h3); +} +.markdown-source-view.mod-cm6 .HyperMD-header-4 { + padding-block-end: var(--monokai-heading-gap-h4); +} +.markdown-source-view.mod-cm6 .HyperMD-header-5 { + padding-block-end: var(--monokai-heading-gap-h5); +} +.markdown-source-view.mod-cm6 .HyperMD-header-6 { + padding-block-end: var(--monokai-heading-gap-h6); +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task=x], +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task=X] { + color: var(--monokai-task-done-color); +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task=">"] { + color: var(--monokai-task-forward-color); +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task="-"] { + color: var(--monokai-task-cancelled-color); + text-decoration-line: line-through; +} +.markdown-source-view.mod-cm6 .HyperMD-task-line[data-task="?"] { + color: var(--monokai-task-question-color); +} +.markdown-source-view.mod-cm6 .HyperMD-codeblock { + background-color: var(--code-background); + font-family: var(--font-monospace-theme); + padding-inline: var(--monokai-codeblock-padding-inline); +} +.markdown-source-view.mod-cm6 .HyperMD-hr { + color: var(--monokai-formatting-marker-color); + padding-block: calc(var(--monokai-hr-spacing) / 2); +} +.markdown-source-view.mod-cm6 .HyperMD-paragraph { + padding-block-end: calc(var(--monokai-paragraph-spacing) / 3); +} +.markdown-source-view.mod-cm6 .HyperMD-table-row { + font-family: var(--monokai-table-source-font-family); + background-color: var(--monokai-table-source-background); +} +.markdown-source-view.mod-cm6 { + /* stylelint-enable selector-class-pattern */ +} .markdown-source-view.mod-cm6 .cm-header-1 { color: var(--h1-color); font-weight: var(--monokai-heading-weight); @@ -427,6 +607,18 @@ body.theme-light { color: var(--h3-color); font-weight: var(--monokai-heading-weight); } +.markdown-source-view.mod-cm6 .cm-header-4 { + color: var(--h4-color); + font-weight: var(--monokai-heading-weight); +} +.markdown-source-view.mod-cm6 .cm-header-5 { + color: var(--h5-color); + font-weight: var(--monokai-heading-weight); +} +.markdown-source-view.mod-cm6 .cm-header-6 { + color: var(--h6-color); + font-weight: var(--monokai-heading-weight); +} .markdown-source-view.mod-cm6 .cm-strong { color: var(--bold-color); font-weight: var(--bold-weight); @@ -434,16 +626,83 @@ body.theme-light { .markdown-source-view.mod-cm6 .cm-em { color: var(--italic-color); } +.markdown-source-view.mod-cm6 .cm-strikethrough { + color: var(--monokai-strikethrough-color); + text-decoration-color: var(--monokai-strikethrough-color); +} .markdown-source-view.mod-cm6 .cm-link, .markdown-source-view.mod-cm6 .cm-url { color: var(--link-color); } .markdown-source-view.mod-cm6 .cm-formatting-link { - color: var(--text-faint); + color: var(--monokai-formatting-marker-color); +} +.markdown-source-view.mod-cm6 .cm-hmd-table-sep, +.markdown-source-view.mod-cm6 .cm-hmd-table-align, +.markdown-source-view.mod-cm6 .cm-formatting-table { + color: var(--monokai-table-source-separator-color); +} +.markdown-source-view.mod-cm6 .cm-formatting-code-block, +.markdown-source-view.mod-cm6 .cm-hmd-codeblock-begin, +.markdown-source-view.mod-cm6 .cm-hmd-codeblock-end { + color: var(--monokai-formatting-marker-color); +} +.markdown-source-view.mod-cm6 .cm-table { + color: var(--text-normal); +} +.markdown-source-view.mod-cm6 .cm-formatting-list { + color: var(--list-marker-color); +} +.markdown-source-view.mod-cm6 .cm-formatting-strong, +.markdown-source-view.mod-cm6 .cm-formatting-em, +.markdown-source-view.mod-cm6 .cm-formatting-strikethrough, +.markdown-source-view.mod-cm6 .cm-formatting-code, +.markdown-source-view.mod-cm6 .cm-formatting-highlight { + color: var(--monokai-formatting-marker-color); } .markdown-source-view.mod-cm6 .cm-highlight { color: var(--monokai-highlight-text); background-color: var(--text-highlight-bg); + border-radius: var(--monokai-highlight-radius); + padding-inline: var(--monokai-highlight-padding-inline); +} +.markdown-source-view.mod-cm6 .cm-hr { + color: var(--monokai-hr-color); +} +.markdown-source-view.mod-cm6 .cm-footref { + color: var(--monokai-footnote-color); + font-size: var(--monokai-footnote-font-size); +} +.markdown-source-view.mod-cm6 .cm-formatting-footref { + color: var(--monokai-footnote-marker-color); +} +.markdown-source-view.mod-cm6 .image-embed, +.markdown-source-view.mod-cm6 .cm-embed-block { + max-width: 100%; + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); +} +.markdown-source-view.mod-cm6 .file-embed, +.markdown-source-view.mod-cm6 .pdf-embed, +.markdown-source-view.mod-cm6 .audio-embed, +.markdown-source-view.mod-cm6 .video-embed, +.markdown-source-view.mod-cm6 .internal-embed, +.markdown-source-view.mod-cm6 .cm-embed-block { + background-color: var(--monokai-media-background); + border: 1px solid var(--monokai-media-border-color); + border-radius: var(--monokai-media-radius); + padding: var(--monokai-embed-padding); +} +.markdown-source-view.mod-cm6 .cm-hashtag { + color: var(--monokai-tag-color); + background-color: var(--monokai-tag-background); + border: 1px solid var(--monokai-tag-border-color); + border-radius: var(--monokai-tag-radius); + padding-inline: 0.25em; +} +.markdown-source-view.mod-cm6 .cm-line { + line-height: var(--monokai-paragraph-line-height); } .markdown-source-view.mod-cm6 .cm-inline-code { color: var(--monokai-inline-code-color); @@ -451,7 +710,7 @@ body.theme-light { border: 0; border-radius: var(--radius-s); box-shadow: none; - padding: 0.05em 0.25em; + padding: var(--monokai-inline-code-padding); } .cm-s-obsidian span.cm-comment { @@ -480,8 +739,12 @@ body.theme-light { a, .markdown-rendered a, -.cm-hmd-internal-link { +.cm-hmd-internal-link, +.cm-link, +.cm-url { color: var(--link-color); + text-decoration-color: var(--monokai-link-decoration-color); + text-decoration-thickness: var(--monokai-link-decoration-thickness); } a:hover, @@ -490,6 +753,14 @@ a:hover, color: var(--link-color-hover); } +a:focus-visible, +.markdown-rendered a:focus-visible, +.cm-hmd-internal-link:focus-visible { + outline: 2px solid var(--interactive-accent); + outline-offset: 2px; + border-radius: var(--radius-s); +} + /* stylelint-disable */ /* * 此文件由 `npm run generate:icons` 自动生成。 @@ -503,19 +774,24 @@ a:hover, font-weight: 400; } .nav-file-title::before { - display: inline-block; - flex: 0 0 1rem; - width: 1rem; - margin-inline-end: 0.35rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--monokai-file-tree-slot-size); + min-width: var(--monokai-file-tree-slot-size); + height: var(--monokai-file-tree-glyph-size); + margin-inline-end: calc(-1 * var(--monokai-file-tree-slot-size)); + transform: translateX(calc(-1 * (var(--monokai-file-tree-slot-size) + var(--monokai-file-tree-icon-gap)))); --monokai-file-icon-color: #ffd866; color: var(--monokai-file-icon-color, var(--icon-color)); content: "\e658"; font-family: "monokai-pro-icons"; - font-size: 1rem; + font-size: var(--monokai-file-tree-glyph-size); font-style: normal; font-variant: normal; font-weight: 400; line-height: 1; + pointer-events: none; speak: never; text-align: center; text-rendering: auto; @@ -884,14 +1160,40 @@ a:hover, content: "\e8e8"; } -.nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator, -.nav-folder-title[data-path*="/"] .collapse-icon, -.nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator, -.nav-folder[data-path*="/"] > .nav-folder-title .collapse-icon, -.nav-folder-children .nav-folder-children .nav-folder-title .nav-folder-collapse-indicator, -.nav-folder-children .nav-folder-children .nav-folder-title .collapse-icon { - width: 1rem; +.nav-file-title, +.nav-folder-title { + --monokai-file-tree-slot-size: 1rem; + --monokai-file-tree-icon-gap: 0.35rem; + --monokai-file-tree-glyph-size: 0.8125rem; + --monokai-file-tree-toggle-size: 9px; + --monokai-file-tree-toggle-stroke-width: 1.75; + overflow: visible; + position: relative; } + +.nav-folder-title .nav-folder-collapse-indicator, +.nav-folder-title .collapse-icon { + flex: 0 0 var(--monokai-file-tree-slot-size); + width: var(--monokai-file-tree-slot-size); + min-width: var(--monokai-file-tree-slot-size); + margin-inline-end: var(--monokai-file-tree-icon-gap); +} + +.nav-folder-title .nav-folder-collapse-indicator svg, +.nav-folder-title .collapse-icon svg { + width: var(--monokai-file-tree-toggle-size); + height: var(--monokai-file-tree-toggle-size); + font-size: var(--monokai-file-tree-toggle-size); + stroke-width: var(--monokai-file-tree-toggle-stroke-width); +} + +.nav-folder-title .nav-folder-collapse-indicator, +.nav-folder-title .collapse-icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + .nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator::before, .nav-folder-title[data-path*="/"] .collapse-icon::before, .nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator::before, @@ -902,7 +1204,7 @@ a:hover, display: inline-block; color: var(--icon-color); font-family: monokai-pro-icons, monospace; - font-size: 1rem; + font-size: var(--monokai-file-tree-glyph-size); font-style: normal; font-weight: 400; line-height: 1; @@ -985,6 +1287,7 @@ body.is-mobile .workspace-ribbon { color: var(--text-muted); background-color: transparent; border-radius: var(--radius-s) var(--radius-s) 0 0; + transition: color 140ms ease, background-color 140ms ease, box-shadow 140ms ease; } .workspace-tab-header:hover { @@ -1011,7 +1314,7 @@ body.is-mobile .workspace-ribbon { background-color: var(--background-secondary); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-m); - box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35); + box-shadow: var(--monokai-modal-shadow); } .modal-title, @@ -1027,6 +1330,8 @@ body.is-mobile .workspace-ribbon { background-color: var(--background-primary); border: 1px solid var(--background-modifier-border); border-radius: var(--radius-s); + padding: 0.45rem 0.6rem; + transition: border-color 140ms ease, box-shadow 140ms ease; } .prompt-input:focus, @@ -1237,7 +1542,7 @@ body.is-mobile .workspace-ribbon { .canvas-node.is-focused, .canvas-node.is-selected { border-color: var(--interactive-accent); - box-shadow: 0 0 0 2px rgba(249, 38, 114, 0.28); + box-shadow: 0 0 0 2px var(--monokai-selection-ring); } .canvas-node-label { @@ -1306,12 +1611,18 @@ body.monokai-syntax-compact { --nav-item-size: 22px; --ribbon-item-size: 32px; --input-height: 28px; + --callout-padding: 0.9rem; + --callout-content-padding: var(--spacing-3) 0 0 0; } body.monokai-syntax-compact .nav-files-container { font-size: var(--monokai-nav-font-size); } body.monokai-syntax-compact .nav-file-title, body.monokai-syntax-compact .nav-folder-title { + --monokai-file-tree-slot-size: 13px; + --monokai-file-tree-icon-gap: 4px; + --monokai-file-tree-glyph-size: 12px; + --monokai-file-tree-toggle-size: 8px; min-height: var(--nav-item-size); line-height: var(--nav-item-size); padding: 0 6px; @@ -1321,24 +1632,6 @@ body.monokai-syntax-compact .nav-folder-title-content { font-size: var(--monokai-nav-font-size); line-height: var(--nav-item-size); } -body.monokai-syntax-compact .nav-file-title::before { - width: 13px; - flex: 0 0 13px; - margin-inline-end: 4px; - font-size: 13px; -} -body.monokai-syntax-compact .nav-folder-collapse-indicator { - width: 14px; - margin-inline-end: 1px; -} -body.monokai-syntax-compact .nav-folder-title[data-path*="/"] .nav-folder-collapse-indicator::before, -body.monokai-syntax-compact .nav-folder-title[data-path*="/"] .collapse-icon::before, -body.monokai-syntax-compact .nav-folder[data-path*="/"] > .nav-folder-title .nav-folder-collapse-indicator::before, -body.monokai-syntax-compact .nav-folder[data-path*="/"] > .nav-folder-title .collapse-icon::before, -body.monokai-syntax-compact .nav-folder-children .nav-folder-children .nav-folder-title .nav-folder-collapse-indicator::before, -body.monokai-syntax-compact .nav-folder-children .nav-folder-children .nav-folder-title .collapse-icon::before { - font-size: 13px; -} body.monokai-syntax-compact .side-dock-ribbon-action { height: var(--ribbon-item-size); padding: 4px; @@ -1346,6 +1639,10 @@ body.monokai-syntax-compact .side-dock-ribbon-action { body.monokai-syntax-compact .markdown-rendered pre { padding: 0.75rem; } +body.monokai-syntax-compact .markdown-rendered th, +body.monokai-syntax-compact .markdown-rendered td { + padding: 0.35rem 0.5rem; +} body.monokai-syntax-monochrome-icons { --icon-color: var(--text-muted); @@ -1360,6 +1657,7 @@ body { --link-color: var(--monokai-link-color, var(--monokai-default-link)); --link-external-color: var(--monokai-link-color, var(--monokai-default-link)); --blockquote-border-color: var(--monokai-success-color, var(--monokai-default-success)); + --monokai-blockquote-border: var(--monokai-success-color, var(--monokai-default-success)); --checkbox-color-checked: var(--monokai-success-color, var(--monokai-default-success)); --text-highlight-bg: var(--monokai-warning-color, var(--monokai-default-warning)); --code-background: var(--monokai-code-background, var(--monokai-default-code-background)); @@ -1373,6 +1671,10 @@ body:is(.theme-dark, .theme-light) { --callout-content-padding: var(--spacing-4) 0 0 0; --callout-border-width: 0; --callout-radius: var(--radius-s); + --monokai-blockquote-content-gap: 1.25rem; + --monokai-blockquote-nested-gap: 0.75rem; + --monokai-callout-title-gap: var(--spacing-2); + --monokai-callout-source-marker-color: var(--text-faint); } /* ===== 块引用(阅读 + 编辑统一) ===== */ @@ -1382,7 +1684,11 @@ body .markdown-rendered blockquote { border-inline-start: 2px solid var(--monokai-blockquote-border); border-radius: 0 var(--radius-s) var(--radius-s) 0; color: var(--text-normal); - padding: 0.75rem 0.75rem 0.75rem 1.25rem; + padding: 0.75rem 0.75rem 0.75rem var(--monokai-blockquote-content-gap); +} + +body .markdown-rendered blockquote blockquote { + margin-block: var(--monokai-blockquote-nested-gap); } body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote, @@ -1392,14 +1698,25 @@ body .markdown-source-view.mod-cm6 .HyperMD-quote { border-radius: 0 var(--radius-s) var(--radius-s) 0; } +body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote + .cm-line.HyperMD-quote, +body .markdown-source-view.mod-cm6 .HyperMD-quote + .HyperMD-quote { + border-start-start-radius: 0; + border-end-start-radius: 0; +} + body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote .cm-quote, body .markdown-source-view.mod-cm6 .HyperMD-quote .cm-quote { color: var(--text-normal); border-inline-start: 0; - margin-left: 0.6rem; + margin-left: var(--monokai-blockquote-content-gap); padding-inline-start: 0; } +body .markdown-source-view.mod-cm6 .cm-quote-2, +body .markdown-source-view.mod-cm6 .cm-quote-3 { + color: var(--text-normal); +} + body .markdown-source-view.mod-cm6 .cm-line.HyperMD-quote .cm-formatting-quote, body .markdown-source-view.mod-cm6 .HyperMD-quote .cm-formatting-quote { display: none; @@ -1430,7 +1747,7 @@ body .markdown-source-view.mod-cm6 .callout-title, body .markdown-source-view.mod-cm6 .cm-callout-title { color: var(--callout-border, var(--monokai-callout-note-border)); font-weight: 700; - gap: var(--spacing-2); + gap: var(--monokai-callout-title-gap); padding-inline-start: 0; } @@ -1445,6 +1762,15 @@ body .markdown-source-view.mod-cm6 .cm-callout-content { padding-inline-start: 0; } +body .markdown-source-view.mod-cm6 .cm-callout-content { + color: var(--text-normal); +} + +body .markdown-source-view.mod-cm6 .cm-formatting-callout, +body .markdown-source-view.mod-cm6 .cm-callout .cm-formatting { + color: var(--monokai-callout-source-marker-color); +} + /* ===== Callout 语义色分组映射 ===== */ body .markdown-preview-view.markdown-rendered .callout[data-callout=note], body .markdown-rendered .callout[data-callout=note], diff --git a/versions.json b/versions.json index 85df038..82c50d2 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { "1.0.0": "1.0.0", - "1.0.1": "1.0.0" + "1.0.1": "1.0.0", + "1.0.2": "1.0.0" }