commit 59972a282c660aa55a2b1e48fd23d0d732b513b2 Author: User Date: Thu Sep 25 09:28:03 2025 +0800 初始化提交 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c8115c9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 2 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 120 + +# documentation, utils +[*.{md,mdx,diff}] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..77f64d6 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# 开发模式下编译的主题 (开发模式仅编译单个主题) +DEV_THEME=dark +# 把编译后的主题上传到服务器的服务器名称, 通过 SCP 上传 +SSH_SERVER=localhost +# 上传到服务器的用户名称, 不支持密码, 请确保有 SSH 免密登录权限 +SSH_USER=root +# 上传到服务器的主题路径, 请使用绝对路径 +GITEA_THEME_PATH=/data/gitea/public/assets/css/ \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ec649e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +.github/* linguist-vendored +.vscode/* linguist-vendored +screenshots/* linguist-vendored +scripts/* linguist-vendored +eslint.config.js linguist-vendored \ No newline at end of file diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md new file mode 100644 index 0000000..a611f54 --- /dev/null +++ b/.github/CHANGELOG.md @@ -0,0 +1,21 @@ +### 🎉 + +### ✨ Feature + +#### CSS 变量 + +### 🌈 Style + +##### 更符合 GitHub 风格 + +### 🐞 Fix + +## 📃 English + +#### CSS Variable + +##### More GitHub-like style + +```text +在 English 下方补充上面 Feature 以下部分的内容的中译英​ +``` diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fb5dfc4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: ["https://afdian.com/a/luting"] diff --git a/.github/ISSUE_TEMPLATE/1-style-bug-report.yml b/.github/ISSUE_TEMPLATE/1-style-bug-report.yml new file mode 100644 index 0000000..3f8491a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-style-bug-report.yml @@ -0,0 +1,76 @@ +name: 样式错误报告 ( Style bug report ) +description: 安装主题后出现颜色/位置错误等 ( Color/position errors occur after installing the theme ) +labels: ["style bug"] +body: + - type: markdown + attributes: + value: | + 1. 请使用最新的主题, 并使用 Ctrl+F5 刷新页面查看 Bug 是否消失 ( Please use the latest theme and refresh the page with Ctrl+F5 to see if the Bug has disappeared ) + 2. 请查看项目根目录下的 CHANGELOG.md 文件, 查看是否有修复该 Bug 的相关描述 ( Please check the CHANGELOG.md file in the project root directory for descriptions of fixes related to this Bug ) + 3. 如果有相关描述, 可以查看 README.md 自己编译开发版本是否修复了该 Bug ( If there are related descriptions, you can check if the Bug has been fixed by compiling the development version yourself in the README.md ) + 4. 请查看 Gitea 默认主题下是否有同样的错误, 确认非主题问题 ( Please check if there is the same error in the default Gitea theme, confirming that it is not a theme problem ) + - type: input + id: theme-version + attributes: + label: 主题版本 ( Theme Version ) + description: + 主题的版本, 如果是开发版本可以查看 CSS 文件中 `--theme-version` 的值, 或者查看项目根目录下的 `package.json` 文件 + ( The version of the theme, if it is a development version, you can check the value of `--theme-version` in the + CSS file, or check the `package.json` file in the project root directory ) + placeholder: ex. 1.24.5 + validations: + required: true + - type: input + id: gitea-version + attributes: + label: Gitea 版本 ( Gitea Version ) + description: 应用主题的 Gitea 版本 ( What version of Gitea are you using? ) + placeholder: ex. 1.24.5 + validations: + required: true + - type: textarea + id: actual-behavior + attributes: + label: 实际表现 ( Actual Behavior ) + description: 请描述实际的错误表现 ( Please describe the actual error presentation ) + placeholder: 页面底部的颜色错误 ( The color of the bottom of the page is incorrect ) + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: 期望表现 ( Expected Behavior ) + description: 请描述期望的正确表现 ( Please describe the expected correct presentation ) + placeholder: 页面底部的颜色应该是白色 ( The color of the bottom of the page should be white ) + validations: + required: true + - type: textarea + id: theme-screenshot + attributes: + label: 应用主题后的截图 ( Screenshot of the theme applied ) + description: 请上传应用主题后的截图 ( Please upload a screenshot of the theme applied ) + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: 复现步骤 ( Steps to reproduce ) + description: 请描述如何复现错误 ( Please describe how to reproduce the error ) + placeholder: 1. 安装主题 2. 刷新页面 3. 查看错误 ( 1. Install the theme 2. Refresh the page 3. View the error ) + validations: + required: false + - type: textarea + id: bug-description + attributes: + label: 错误描述 ( Bug Description ) + description: 请描述错误的具体表现 ( Please describe the specific presentation of the error ) + placeholder: 页面底部的颜色错误 ( The color of the bottom of the page is incorrect ) + validations: + required: false + - type: textarea + id: gitea-screenshot + attributes: + label: Gitea 默认主题下的截图 ( Screenshot of the default Gitea theme ) + description: 请上传 Gitea 默认主题下的截图 ( Please upload a screenshot of the default Gitea theme ) + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yml b/.github/ISSUE_TEMPLATE/2-feature-request.yml new file mode 100644 index 0000000..e65d74d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature-request.yml @@ -0,0 +1,32 @@ +name: 功能请求 ( Feature request ) +description: 提出新的功能建议 ( Suggest new features ) +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + 1. 不接受非 Gitea 和 GitHub 的样式需求 ( We do not accept style requests that are not Gitea or GitHub ) + 2. 颜色需求最好自己实现 ( Color requests are best implemented by yourself ) + - type: input + id: page + attributes: + label: 页面 ( Page ) + description: 功能涉及的页面 ( The page the feature involves ) + placeholder: 首页 ( Home page ) + validations: + required: false + - type: textarea + id: feature-description + attributes: + label: 功能描述 ( Feature Description ) + description: 请描述你想要的功能 ( Please describe the feature you want ) + placeholder: 菜单样式修改 ( Menu style modification ) + validations: + required: true + - type: textarea + id: screenshot + attributes: + label: 截图 ( Screenshot ) + description: 预期的主题效果 ( Expected theme effect ) + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/3-core-bug-report.yml b/.github/ISSUE_TEMPLATE/3-core-bug-report.yml new file mode 100644 index 0000000..2cc6b7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-core-bug-report.yml @@ -0,0 +1,44 @@ +name: 框架错误报告 ( Core bug report ) +description: 报告框架的错误 ( Report errors in the theme framework ) +labels: ["core bug"] +body: + - type: textarea + id: actual-behavior + attributes: + label: 实际表现 ( Actual Behavior ) + description: 请描述实际的错误表现 ( Please describe the actual error presentation ) + placeholder: 未生成自动颜色主题 ( No automatic color theme generated ) + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: 期望表现 ( Expected Behavior ) + description: 请描述期望的正确表现 ( Please describe the expected correct presentation ) + placeholder: 生成自动颜色主题 ( Generate automatic color theme ) + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: 复现步骤 ( Steps to reproduce ) + description: 请描述如何复现错误 ( Please describe how to reproduce the error ) + placeholder: + 1. 新建颜色主题 2. 编译主题 3. 查看错误 ( 1. Create a color theme 2. Compile the theme 3. View the error ) + validations: + required: false + - type: textarea + id: bug-description + attributes: + label: 错误描述 ( Bug Description ) + description: 请描述错误的具体表现 ( Please describe the specific presentation of the error ) + placeholder: 未生成自动颜色主题 ( No automatic color theme generated ) + validations: + required: false + - type: textarea + id: screenshot + attributes: + label: 截图 ( Screenshot ) + description: 请上传错误的截图 ( Please upload the screenshot of the error ) + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/4-doc-report.yml b/.github/ISSUE_TEMPLATE/4-doc-report.yml new file mode 100644 index 0000000..ccf21f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-doc-report.yml @@ -0,0 +1,12 @@ +name: 文档报告 ( Document report ) +description: 改进或补充文档 ( Improve or supplement the document ) +labels: ["documentation"] +body: + - type: textarea + id: doc-description + attributes: + label: 文档描述 ( Document Description ) + description: 请描述文档的具体内容 ( Please describe the specific content of the document ) + placeholder: 文档内容有误 ( The document content is incorrect ) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9994712 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +name: release + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build theme + run: | + npm install + npm run build + - name: Create release + run: | + tar -zcf dist/theme-github-base.tar.gz --remove-files \ + dist/theme-github-auto.css dist/theme-github-light.css dist/theme-github-dark.css dist/theme-github-soft-dark.css + + tar -zcf dist/theme-github-colorblindness-colorblind.tar.gz --remove-files \ + dist/theme-github-colorblind-auto.css dist/theme-github-colorblind-light.css dist/theme-github-colorblind-dark.css + + tar -zcf dist/theme-github-colorblindness-tritanopia.tar.gz --remove-files \ + dist/theme-github-tritanopia-auto.css dist/theme-github-tritanopia-light.css dist/theme-github-tritanopia-dark.css + + tar -zcf dist/theme-github-extra-pink.tar.gz --remove-files \ + dist/theme-github-pink-auto.css dist/theme-github-pink-light.css dist/theme-github-pink-dark.css dist/theme-github-pink-soft-dark.css + + TAG="v$(npm run -s version)" + gh release create "$TAG" dist/* --notes-file CHANGELOG.md --draft -t $TAG + env: + GH_TOKEN: ${{ github.token }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57794ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +dist +node_modules +package-lock.json +.env +.VSCodeCounter \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4b879ac --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "usernamehw.errorlens", + "mikestead.dotenv", + "styled-components.vscode-styled-components" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ecf0b1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +### 🌈 Style + +- 优化顶部和二级导航栏的样式和颜色 + +##### 更符合 GitHub 风格 + +- 同步页脚样式 +- 优化创建标签菜单的菜单项的样式 + +### 🐞 Fix + +- 修复 Wiki 页面搜索项目没有正确隐藏的问题 #15 +- 修复后台账户管理排序菜单项的宽度问题 +- 修复合并提交中的主色调按钮组阴影问题 +- 修复仓库页面下二级导航栏下划线过粗的问题 +- 修复仪表板切换用户按钮菜单下无创建组织按钮时的菜单圆角问题 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f5f7021 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,171 @@ +# 贡献指南 + +## 目录结构 + +| 目录 | 说明 | +| ----------------- | ---------------------------- | +| src | 主题生成框架与辅助工具的包 | +| src/core | 主题生成框架 | +| src/functions | 主题辅助工具 | +| src/types | 主题的颜色变量定义 | +| styles | 主题样式 | +| styles/components | 具体页面的元素的样式 | +| styles/public | 基础元素或跨页面的元素的样式 | +| themes | 颜色主题 | + +## 贡献说明 + +不推荐主题样式贡献, 因为 Gitea 主题最终是由单个 CSS 文件提供, 所以会有先后顺序覆盖, 样式影响广泛等问题. + +开发者每个人的思路不一样, 审核很难看出这些问题, 会极大增加维护难度. + +如果确定理解了 Gitea 的样式布局和我的思路, 请先提交 Issue 确认工作量和预期效果, 然后再开发提交 PR. + +如果认为有更好的思路, 欢迎提交 Issue. + +> [!IMPORTANT] +> +> 提交 PR 时, 请执行 `npm run commit` + +## 开发环境 + +主题仅依赖于 Node.js 环境, 请确保你的环境中已经安装了 Node.js, 推荐使用 Node.js 20 或以上版本. + +请使用 VSCode 开发, 并安装仓库中推荐的插件. + +如果不喜欢多余的插件, 请务必安装 `vscode-styled-components` 插件, 此插件用于渲染和检查 TypeScript 中的 CSS 模板字符串. + +推荐使用 VSCode 1.102.0 版本以上开发, 此版本以上提供 TypeScript 代码中 16 进制颜色的支持. + +## 开发流程 + +### 安装依赖 + +```bash +npm install +``` + +### 设置环境变量 + +在项目根目录下创建 `.env` 文件, 变量参考 `.env.example` 文件. + +环境变量用于发送编译后的主题到服务器上, 快速预览主题. + +### 编译主题 + +编译开发中的主题 + +```bash +npm run dev +``` + +编译所有主题 + +```bash +npm run build +``` + +格式化项目中的代码 + +```bash +npm run format +``` + +用于 PR, 检查并编译项目中的所有代码并进行格式化 + +```bash +npm run commit +``` + +## 开发规范 + +`src`, `styles`, `themes` 为项目的主目录, 主目录下的第一个目录为模块. + +主目录或主目录下模块互相引用时, 请使用绝对路径, 例如 `import { defineTheme } from "src"` + +模块下的文件互相引用时, 请使用相对路径, 例如 `import { defineTheme } from "./theme"` + +## 颜色主题贡献 + +颜色主题名称格式: `主题名称-dark.css.ts` 或 `主题名称-light.css.ts`, 分别表示深色和亮色主题. + +如果主题有深色和亮色模式, 会自动生成自动颜色主题, 不需要手动添加. + +项目接受自定义主题并会附加到发布的版本中, 但项目所有者不参与维护和审核. + +请在颜色主题文件头部附加自己的作者信息, 方便 Issue 提问者找到你 `@`. + +推荐使用 `import { defineTheme, type ThemeColor } from "src"` 导入主题生成框架, 声明主题颜色, 然后使用 `defineTheme` +函数生成主题所有 CSS 变量, defineTheme 中设置了一些经过计算得到的 Gitea 变量可以减少工作量, 具体请查看函数说明和定义. + +颜色计算函数可以从 `src/functions` 导入, 例如 `import { scaleColorLight } from "src/functions"`, 或者使用 `polished` 库. + +例: `themes/主题名称-dark.css.ts` + +```ts +/** + * @author 你的名字 + * @description 主题描述 + */ +import { defineTheme, type ThemeColor } from "src"; +export const 主题名称DarkColors: ThemeColor = { + ... +} +export default defineTheme(主题名称DarkColors); +// 使用其他主题颜色作为基础 +import dark from "themes/dark"; +import { darkColors } from "themes/dark"; +export const 主题名称DarkColors: ThemeColor = darkColors; +export default defineTheme({ + ...dark, + ... +}); +``` + +如果不需要自定义代码高亮色, 则不传递 chroma 参数, 框架会自动根据主题的暗色或亮色生成代码高亮色. + +如果需要完全自定义每个 Gitea 变量, 请导入 `import type { Theme } from "src"` + +例: `themes/主题名称-dark.css.ts` + +```ts +/** + * @author 你的名字 + * @description 主题描述 + */ +import type { Theme, Chroma, Primary, Secondary, ... } from "src"; +export const primary: Primary = ...; +export const secondary: Secondary =...; +export const chroma: Chroma =...; +... +export default theme: Theme = { + primary, + secondary, + chroma, + ... +}; +``` + +完成主题颜色开发后, 请在某个仓库的代码文件列表页, 打开 Code 菜单选择 Tea Cli 进行截图, 并放入 `screenshots` +目录下, 截图名与主题名相同. (推荐克隆本仓库, 避免泄露个人隐私) + +然后将截图信息添加到 `README.md` 文件中, 可以在折叠部分中添加自己的说明. + +主题颜色复用案例可以参考本主题的色盲主题. + +## 主题样式贡献 + +主题样式使用 TypeScript 的 css 模板字符串开发, 该模板字符串会经过 sass 预处理器处理, 支持 SCSS 语法并且主题只接受 SCSS 嵌套语法, 请不要使用 CSS 语法, 如果一定要用请说明原因. + +请尽量不要使用 SCSS 的函数, `vscode-styled-components` 插件会报错, 请使用 TypeScript 相关库处理, 比如主题自带的 +`polished` 库. + +推荐需要使用复杂处理时, 提取逻辑到 `src/functions` 目录下的函数中, 然后在 `src/styles` 目录下的样式文件中使用. + +主题样式中使用到的所有颜色必须使用颜色变量, 颜色变量导入 `import { themeVars } from "src/types/vars"` + +涉及到主题自己的颜色变量 `${themeVars.github.xxx}`, 在使用时请将使用的文件和变量添加到对应变量的注释中 +`src/types/color/github` + +小型圆角(6px)请使用全局圆角变量, 圆角变量导入 `import { otherThemeVars } from "src/types/vars"` +`${otherThemeVars.border.radius}` diff --git a/IFLOW.md b/IFLOW.md new file mode 100644 index 0000000..dfcf837 --- /dev/null +++ b/IFLOW.md @@ -0,0 +1,82 @@ +# Gitea GitHub Theme 项目概述 + +这个项目为 Gitea 提供了一套 GitHub 风格的主题。它包含多种预设颜色方案,如基础的亮色、暗色和柔和暗色主题,以及为色盲用户设计的主题和粉色主题。主题通过 CSS 实现,并利用 Vite 和 Vanilla Extract 等工具进行构建。 + +## 项目结构 + +- `src/`: 核心源代码,包括主题定义、颜色处理和类型定义。 +- `styles/`: 全局样式定义,覆盖 Gitea 的 UI 组件。 +- `themes/`: 各种预定义的主题颜色配置。 +- `dist/`: 构建后的 CSS 主题文件输出目录。 +- `screenshots/`: 各种主题的预览截图。 + +## 技术栈 + +- **TypeScript**: 用于类型安全的 JavaScript 开发。 +- **Vite**: 构建工具,提供快速的开发和构建体验。 +- **Vanilla Extract**: 用于在 JavaScript/TypeScript 中编写 CSS 样式。 +- **Lightning CSS**: 用于 CSS 转换和优化。 +- **Linaria/WYW-in-JS**: 用于在 JavaScript 中编写 CSS。 + +## 构建和运行 + +### 安装依赖 + +```bash +npm install +``` + +### 开发构建 + +构建开发版本(仅构建 `DEV_THEME` 环境变量指定的主题,或默认的 `dark` 主题): + +```bash +npm run dev +``` + +### 生产构建 + +构建所有主题: + +```bash +npm run build +``` + +构建过程会生成 CSS 文件到 `dist/` 目录。 + +### 代码检查和格式化 + +- 检查代码风格: + + ```bash + npm run lint + ``` + +- 格式化代码: + + ```bash + npm run format + ``` + +- 一键执行检查、格式化和构建: + + ```bash + npm run commit + ``` + +## 主题开发 + +主题定义在 `themes/` 目录下,每个主题是一个 `.css.ts` 文件。这些文件导出一个包含颜色和其他样式变量的对象,该对象通过 `defineTheme` 函数处理后生成最终的 CSS。 + +核心主题逻辑位于 `src/core/` 目录中,`theme.ts` 文件负责创建全局 CSS 主题,`vite.ts` 文件包含 Vite 插件,用于处理主题构建过程。 + +## 自定义 CSS 变量 + +用户可以通过在生成的 CSS 文件中添加自定义 CSS 变量来覆盖部分样式。支持的变量包括菜单宽度、列表列数等。 + +## 安装到 Gitea + +1. 构建项目以生成 CSS 文件。 +2. 将 `dist/` 目录下的 CSS 文件复制到 Gitea 服务器的 `gitea/public/assets/css` 目录。 +3. 修改 Gitea 配置文件 `gitea/conf/app.ini`,在 `[ui]` 部分的 `THEMES` 列表中添加主题名称(文件名去掉 `theme-` 前缀)。 +4. 重启 Gitea 服务。 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..287d580 --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +

+ 中文 | + English +

+ +# Gitea GitHub Theme + +> [!TIP] +> +> 推荐搭配 Catppuccin 文件图标浏览器插件一起使用更佳 +> [web-file-explorer-icons](https://github.com/catppuccin/web-file-explorer-icons) + +## 版本号说明 + +主题版本号与 Gitea 版本号保持一致 + +Gitea 版本号格式: `1.大版本号.小版本号` + +Gitea 理论上小版本号变更不会修改前端布局, 所以主题的小版本号适用于所有 Gitea 大版本号相同的 Gitea 版本. + +比如: 主题版本 `1.24.5` 适用于 Gitea 版本 `>=1.24.0` `<1.25.0` + +仅维护项目发布中的最新的 Gitea 版本, 其他旧版本主题不接受 Issue 和 PR. + +> 开发阶段的主题版本号格式: `1.大版本号.小版本号.时间戳` + +## 安装 + +1. 在发布页下载最新的 CSS 主题文件放入 `gitea/public/assets/css` 目录下 +2. 修改 `gitea/conf/app.ini`,并将 CSS 文件名去掉 `theme-` 的名称附加到 `[ui]` 下的 `THEMES` 末尾 +3. 重启 Gitea +4. 在设置中查看主题 + +> [!IMPORTANT] +> +> 自动颜色主题需要亮色和暗色的主题文件 + +例: 主题文件名为 `theme-github-dark.css`,则添加 `github-dark` 到 `THEMES` 末尾 + +`gitea/conf/app.ini` 例: + +```ini +[ui] +THEMES = gitea-auto, gitea-light, gitea-dark, github-auto, github-light, github-dark, github-soft-dark +``` + +详细请查看 Gitea 文档 +[Gitea docs](https://docs.gitea.com/next/administration/customizing-gitea#customizing-the-look-of-gitea) + +## 截图 + +![Dashboard](screenshots/dashboard.png) + +### 基本主题 + +```ini +THEMES = github-auto, github-light, github-dark, github-soft-dark +``` + +
+Base +

theme-github-light.css

+ +

theme-github-dark.css

+ +

theme-github-soft-dark.css

+ +
+ +### 色盲主题 ( Beta ) + +```ini +THEMES = github-colorblind-auto, github-colorblind-light, github-colorblind-dark +THEMES = github-tritanopia-auto, github-tritanopia-light, github-tritanopia-dark +``` + +
+Colorblind & Tritanopia (红绿色盲 & 蓝色盲) +

theme-github-colorblind-light.css & theme-github-tritanopia-light.css

+ +

theme-github-colorblind-dark.css & theme-github-tritanopia-dark.css

+ +
+ +### 粉色主题 + +```ini +THEMES = github-pink-auto, github-pink-light, github-pink-dark, github-pink-soft-dark +``` + +
+Pink +

theme-github-pink-light.css

+ +

theme-github-pink-dark.css

+ +

theme-github-pink-soft-dark.css

+ +
+ +## 自定义 CSS 变量 + +可以根据自己的偏好自定义主题的一部分样式 + +### 使用方法 + +在主题的 CSS 文件的头部或尾部添加以下代码 + +```css +:root { + --custom-clone-menu-width: 150px; + ... +} +``` + +> [!IMPORTANT] +> +> 请确保在 `:root` 选择器中添加自定义变量,否则无法生效 +> +> 变量之间用 `;` 分隔 +> +> 建议自定义变量放在单独的文件中, 通过 shell 命令等方式追加到主题文件中 + +### CSS 变量 + +| 变量名 | 描述 | 默认 | Github | 推荐 | 最小 | 最大 | +| :-------------------------------- | :-------------------------- | :---- | :----- | :---- | :---- | :---- | +| --custom-branch-menu-width | 分支菜单的宽度 | 320px | 320px | 320px | Gitea | 640px | +| --custom-clone-menu-width | 克隆按钮的菜单宽度 | Gitea | 332px | 200px | 150px | 400px | +| --custom-user-menu-width | 用户菜单的宽度 | 192px | 256px | | Gitea | 320px | +| --custom-explore-repolist-columns | 探索页面的仓库列表列数 | 2 | 2 | 2 | | | +| --custom-explore-userlist-columns | 探索页面的用户/组织列表列数 | 3 | 1 | 2/3 | | | +| --custom-user-repolist-columns | 用户页面的仓库列表列数 | 2 | 2 | 1/2 | | | +| --custom-org-repolist-columns | 组织页面的仓库列表列数 | 1 | 1 | 1/2 | | | +| --custom-org-userlist-columns | 组织页面的用户列表列数 | 2 | 1 | 1/2 | | | + +## 使用开发中的主题 + +也许你会想使用开发中的主题, 而不是发布的主题 + +请确保你已经安装了 Node.js 环境, 推荐使用 Node.js 20 或以上版本 + +```bash +git clone https://github.com/lutinglt/gitea-github-theme.git +cd gitea-github-theme +npm install +npm run build +``` + +编译完成后, 会在 `dist` 目录下生成主题文件, 你可以将主题文件放入 `gitea/public/assets/css` 目录下, 然后在 +`gitea/conf/app.ini` 中添加主题名称到 `THEMES` 末尾 + +## 贡献 + +请查看 [CONTRIBUTING](CONTRIBUTING.md) diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..8bc1f5f --- /dev/null +++ b/README_EN.md @@ -0,0 +1,159 @@ +

+ 中文 | + English +

+ +# Gitea GitHub Theme + +> [!TIP] +> +> Recommend using Catppuccin file icon browser plugin together for better performance. +> [web-file-explorer-icons](https://github.com/catppuccin/web-file-explorer-icons) + +## Version Number Explanation + +The theme version number is kept consistent with the Gitea version number + +Gitea version number format: `1.major.minor` + +Theoretically, minor version changes in Gitea do not modify the frontend layout, so the minor version of the theme is +applicable to all Gitea versions with the same major version number. + +For example: Theme version `1.24.5` is applicable to Gitea versions `>=1.24.0` `<1.25.0` + +Only the latest released Gitea version is maintained. Issues and PRs for other older theme versions will not be +accepted. + +> Development stage theme version number format: `1.major.minor.timestamp` + +## Installation + +1. Download the latest CSS theme file from the release page and place it in the `gitea/public/assets/css` directory +2. Modify `gitea/conf/app.ini` and append the CSS filename without the `theme-` prefix to the end of `THEMES` under the + `[ui]` section +3. Restart Gitea +4. Check the theme in the settings + +> [!IMPORTANT] +> +> Automatic color theme requires both light and dark theme files. + +Example: If the theme filename is `theme-github-dark.css`, add `github-dark` to the end of `THEMES` + +Example `gitea/conf/app.ini`: + +```ini +[ui] +THEMES = gitea-auto, gitea-light, gitea-dark, github-auto, github-light, github-dark, github-soft-dark +``` + +For details, please refer to the Gitea documentation +[Gitea docs](https://docs.gitea.com/next/administration/customizing-gitea#customizing-the-look-of-gitea) + +## Screenshots + +![Dashboard](screenshots/en/dashboard-en.png) + +### Basic Themes + +```ini +THEMES = github-auto, github-light, github-dark, github-soft-dark +``` + +
+Base +

theme-github-light.css

+ +

theme-github-dark.css

+ +

theme-github-soft-dark.css

+ +
+ +### Colorblind Themes (Beta) + +```ini +THEMES = github-colorblind-auto, github-colorblind-light, github-colorblind-dark +THEMES = github-tritanopia-auto, github-tritanopia-light, github-tritanopia-dark +``` + +
+Colorblind & Tritanopia +

theme-github-colorblind-light.css & theme-github-tritanopia-light.css

+ +

theme-github-colorblind-dark.css & theme-github-tritanopia-dark.css

+ +
+ +### Pink Themes + +```ini +THEMES = github-pink-auto, github-pink-light, github-pink-dark, github-pink-soft-dark +``` + +
+Pink +

theme-github-pink-light.css

+ +

theme-github-pink-dark.css

+ +

theme-github-pink-soft-dark.css

+ +
+ +## Custom CSS Variables + +You can customize parts of the theme style according to your preferences + +### Usage Method + +Add the following code at the beginning or end of the theme's CSS file + +```css +:root { + --custom-clone-menu-width: 150px; + ... +} +``` + +> [!IMPORTANT] +> +> Please ensure to add custom variables in the `:root` selector, otherwise they will not take effect +> +> Variables are separated by `;` +> +> It is recommended to place custom variables in a separate file and append them to the theme file using shell commands +> or other methods + +### CSS Variables + +| Variable Name | Description | Default | Github | Recommend | Min | Max | +| :-------------------------------- | :------------------------------------------------------- | :------ | :----- | :-------- | :---- | :---- | +| --custom-branch-menu-width | Branch menu width | 320px | 320px | 320px | Gitea | 640px | +| --custom-clone-menu-width | Clone button menu width | Gitea | 332px | 200px | 150px | 400px | +| --custom-user-menu-width | User menu width | 192px | 200px | | Gitea | 320px | +| --custom-explore-repolist-columns | Number of repository list columns on explore page | 2 | 2 | 2 | | | +| --custom-explore-userlist-columns | Number of user/organization list columns on explore page | 3 | 1 | 2/3 | | | +| --custom-user-repolist-columns | Number of repository list columns on user page | 2 | 2 | 1/2 | | | +| --custom-org-repolist-columns | Number of repository list columns on organization page | 1 | 1 | 1/2 | | | +| --custom-org-userlist-columns | Number of user list columns on organization page | 2 | 1 | 1/2 | | | + +## Using Development Version of the Theme + +You might want to use the development version of the theme instead of the released version + +Please ensure you have Node.js environment installed, Node.js 20 or above is recommended + +```bash +git clone https://github.com/lutinglt/gitea-github-theme.git +cd gitea-github-theme +npm install +npm run build +``` + +After compilation, theme files will be generated in the `dist` directory. You can place the theme files into the +`gitea/public/assets/css` directory, then add the theme name to the end of `THEMES` in `gitea/conf/app.ini` + +## Contribution + +Please refer to [CONTRIBUTING](CONTRIBUTING.md) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..11d3e3c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,15 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + } +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..75b3b58 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "gitea-github-theme", + "version": "1.24.7", + "type": "module", + "scripts": { + "dev": "vite build --mode dev", + "build": "tsc -b && vite build", + "lint": "eslint .", + "format": "prettier --write .", + "commit": "npm run lint && npm run format && npm run build", + "version": "node scripts/version.cjs" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.27.1", + "@eslint/js": "^9.29.0", + "@linaria/core": "^6.3.0", + "@types/node": "^24.0.3", + "@vanilla-extract/css": "^1.17.4", + "@vanilla-extract/vite-plugin": "^5.0.6", + "@wyw-in-js/babel-preset": "^0.7.0", + "@wyw-in-js/vite": "^0.7.0", + "dotenv": "^17.0.0", + "eslint": "^9.29.0", + "globals": "^16.2.0", + "lightningcss": "^1.30.1", + "polished": "^4.3.1", + "prettier": "3.5.3", + "prettier-plugin-organize-imports": "^4.1.0", + "sass-embedded": "^1.89.2", + "typescript": "^5.8.3", + "typescript-eslint": "^8.34.1", + "typescript-plugin-css-modules": "^5.1.0", + "typescript-styled-plugin": "^0.18.3", + "vite": "^6.3.5" + }, + "prettier": { + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": true, + "arrowParens": "avoid", + "proseWrap": "always", + "htmlWhitespaceSensitivity": "css", + "endOfLine": "lf", + "plugins": [ + "prettier-plugin-organize-imports" + ], + "organizeImportsSkipDestructiveCodeActions": false + } +} diff --git a/screenshots/colorblind-dark.png b/screenshots/colorblind-dark.png new file mode 100644 index 0000000..4d9c303 Binary files /dev/null and b/screenshots/colorblind-dark.png differ diff --git a/screenshots/colorblind-light.png b/screenshots/colorblind-light.png new file mode 100644 index 0000000..7386189 Binary files /dev/null and b/screenshots/colorblind-light.png differ diff --git a/screenshots/dark.png b/screenshots/dark.png new file mode 100644 index 0000000..f78c368 Binary files /dev/null and b/screenshots/dark.png differ diff --git a/screenshots/dashboard.png b/screenshots/dashboard.png new file mode 100644 index 0000000..dbd85e5 Binary files /dev/null and b/screenshots/dashboard.png differ diff --git a/screenshots/en/colorblind-dark.png b/screenshots/en/colorblind-dark.png new file mode 100644 index 0000000..be9449e Binary files /dev/null and b/screenshots/en/colorblind-dark.png differ diff --git a/screenshots/en/colorblind-light.png b/screenshots/en/colorblind-light.png new file mode 100644 index 0000000..dda270b Binary files /dev/null and b/screenshots/en/colorblind-light.png differ diff --git a/screenshots/en/dark.png b/screenshots/en/dark.png new file mode 100644 index 0000000..1b7d45f Binary files /dev/null and b/screenshots/en/dark.png differ diff --git a/screenshots/en/dashboard-en.png b/screenshots/en/dashboard-en.png new file mode 100644 index 0000000..4a8f7c7 Binary files /dev/null and b/screenshots/en/dashboard-en.png differ diff --git a/screenshots/en/light.png b/screenshots/en/light.png new file mode 100644 index 0000000..a9a3123 Binary files /dev/null and b/screenshots/en/light.png differ diff --git a/screenshots/en/soft-dark.png b/screenshots/en/soft-dark.png new file mode 100644 index 0000000..a972f91 Binary files /dev/null and b/screenshots/en/soft-dark.png differ diff --git a/screenshots/light.png b/screenshots/light.png new file mode 100644 index 0000000..ed4b6c5 Binary files /dev/null and b/screenshots/light.png differ diff --git a/screenshots/pink/en/pink-dark-en.png b/screenshots/pink/en/pink-dark-en.png new file mode 100644 index 0000000..3d9be0b Binary files /dev/null and b/screenshots/pink/en/pink-dark-en.png differ diff --git a/screenshots/pink/en/pink-light-en.png b/screenshots/pink/en/pink-light-en.png new file mode 100644 index 0000000..372c0b5 Binary files /dev/null and b/screenshots/pink/en/pink-light-en.png differ diff --git a/screenshots/pink/en/pink-soft-dark-en.png b/screenshots/pink/en/pink-soft-dark-en.png new file mode 100644 index 0000000..a498111 Binary files /dev/null and b/screenshots/pink/en/pink-soft-dark-en.png differ diff --git a/screenshots/pink/pink-dark.png b/screenshots/pink/pink-dark.png new file mode 100644 index 0000000..d8a0363 Binary files /dev/null and b/screenshots/pink/pink-dark.png differ diff --git a/screenshots/pink/pink-light.png b/screenshots/pink/pink-light.png new file mode 100644 index 0000000..b8d002d Binary files /dev/null and b/screenshots/pink/pink-light.png differ diff --git a/screenshots/pink/pink-soft-dark.png b/screenshots/pink/pink-soft-dark.png new file mode 100644 index 0000000..5494cac Binary files /dev/null and b/screenshots/pink/pink-soft-dark.png differ diff --git a/screenshots/soft-dark.png b/screenshots/soft-dark.png new file mode 100644 index 0000000..3e0a561 Binary files /dev/null and b/screenshots/soft-dark.png differ diff --git a/scripts/version.cjs b/scripts/version.cjs new file mode 100644 index 0000000..9af3719 --- /dev/null +++ b/scripts/version.cjs @@ -0,0 +1,7 @@ +const fs = require("fs"); +const path = require("path"); + +const pkgPath = path.join(__dirname, "..", "package.json"); +const pkg = JSON.parse(fs.readFileSync(pkgPath)); + +console.log(pkg.version); diff --git a/src/core/chroma.ts b/src/core/chroma.ts new file mode 100644 index 0000000..d00412e --- /dev/null +++ b/src/core/chroma.ts @@ -0,0 +1,64 @@ +import { prettylights2Chroma, type prettylightsColor } from "./prettylights"; + +export const lightPrettylights: prettylightsColor = { + syntax: { + brackethighlighter: { angle: "#59636e", unmatched: "#82071e" }, + carriage: { return: { bg: "#cf222e", text: "#f6f8fa" } }, + comment: "#59636e", + constant: "#0550ae", + constantOtherReferenceLink: "#0a3069", + entity: "#6639ba", + entityTag: "#0550ae", + invalid: { illegal: { bg: "#82071e", text: "#f6f8fa" } }, + keyword: "#cf222e", + markup: { + bold: "#1f2328", + changed: { bg: "#ffd8b5", text: "#953800" }, + deleted: { bg: "#ffebe9", text: "#82071e" }, + heading: "#0550ae", + ignored: { bg: "#0550ae", text: "#d1d9e0" }, + inserted: { bg: "#dafbe1", text: "#116329" }, + italic: "#1f2328", + list: "#3b2300", + }, + metaDiffRange: "#8250df", + storageModifierImport: "#1f2328", + string: "#0a3069", + stringRegexp: "#116329", + sublimelinterGutterMark: "#818b98", + variable: "#953800", + }, +}; + +export const darkPrettylights: prettylightsColor = { + syntax: { + brackethighlighter: { angle: "#9198a1", unmatched: "#f85149" }, + carriage: { return: { bg: "#b62324", text: "#f0f6fc" } }, + comment: "#9198a1", + constant: "#79c0ff", + constantOtherReferenceLink: "#a5d6ff", + entity: "#d2a8ff", + entityTag: "#7ee787", + invalid: { illegal: { bg: "#8e1519", text: "#f0f6fc" } }, + keyword: "#ff7b72", + markup: { + bold: "#f0f6fc", + changed: { bg: "#5a1e02", text: "#ffdfb6" }, + deleted: { bg: "#67060c", text: "#ffdcd7" }, + heading: "#1f6feb", + ignored: { bg: "#1158c7", text: "#f0f6fc" }, + inserted: { bg: "#033a16", text: "#aff5b4" }, + italic: "#f0f6fc", + list: "#f2cc60", + }, + metaDiffRange: "#d2a8ff", + storageModifierImport: "#f0f6fc", + string: "#a5d6ff", + stringRegexp: "#7ee787", + sublimelinterGutterMark: "#3d444d", + variable: "#ffa657", + }, +}; + +export const defaultLightChroma = prettylights2Chroma(lightPrettylights); +export const defaultDarkChroma = prettylights2Chroma(darkPrettylights); diff --git a/src/core/color.ts b/src/core/color.ts new file mode 100644 index 0000000..3e0f597 --- /dev/null +++ b/src/core/color.ts @@ -0,0 +1,389 @@ +import { rgba, saturate } from "polished"; +import { scaleColorLight } from "src/functions"; +import type { Ansi, Chroma, Console, Diff, Github, Message, Named, Other, Primary, Secondary } from "src/types"; +import { themeVars } from "src/types/vars"; +import { defaultDarkChroma, defaultLightChroma } from "./chroma"; +import type { Theme } from "./theme"; + +export type ThemeColor = { + /** 用于标识当前是否为暗色主题: `true` 暗色 `false` 亮色 */ + isDarkTheme: boolean; + /** 主色调(强调色) */ + primary: string; + /** 主色调的对比色, 一般用于 `color` 属性, primary 用于 `background-color` */ + primaryContrast: string; + /** 副色调(边框色) */ + secondary: string; + /** 基础颜色 */ + base: { + /** 红色 */ + red: string; + /** 橙色 */ + orange: string; + /** 黄色 */ + yellow: string; + /** 黄绿色/橄榄色 */ + olive: string; + /** 绿色 */ + green: string; + /** 蓝绿色/青色(偏绿) */ + teal: string; + /** 蓝绿色/青色(偏蓝) */ + cyan: string; + /** 蓝色 */ + blue: string; + /** 蓝紫色/紫罗兰色 */ + violet: string; + /** 紫色 */ + purple: string; + /** 粉红色 */ + pink: string; + /** 棕色 */ + brown: string; + /** 黑色 */ + black: string; + /** 灰色 */ + grey: string; + /** 金色 */ + gold: string; + /** 白色 */ + white: string; + }; + /** Action 日志 */ + console: Console; + /** 提交代码对比 */ + diff: Diff; + /** 其他 */ + other: Other; + /** 仅适用于本主题的全局变量, 取自 Github */ + github: Github; +}; + +/** 定义颜色, 用于生成颜色主题 + * @example + * 文件名: "dark.css.ts" + * import type { Console, Diff, Other, Github } from "src/types"; + * import { defineTheme, themeVars } from "src"; + * + * const console: Console = { + * fg: { + * self: "#f0f6fc", // self 表示本身等于 --color-console-fg: #f0f6fc, 所有键名为 self 的都将被忽略 + * subtle: themeVars.color.body, // 引用别的CSS变量等于 --color-console-fg-subtle: var(--color-body) + * num1: "rgb(125, 133, 144)", // 由于纯数字无法在 TS 中使用点调用, 采用 num 前缀等于 --color-console-fg-1: rgb(125, 133, 144) + * }, + * ... + * } + * ... + * export default defineTheme({ + * isDarkTheme: true, + * primary: "#0969da", + * ... + * console, + * diff, + * other, + * github, + * }) + */ +export function defineTheme(themeColor: ThemeColor, chroma?: Chroma): Theme { + const brightDir = themeColor.isDarkTheme ? -1 : 1; + + const primary: Primary = { + self: themeColor.primary, + contrast: themeColor.primaryContrast, + dark: { + num1: scaleColorLight(themeColor.primary, -12 * brightDir), + num2: scaleColorLight(themeColor.primary, -24 * brightDir), + num3: scaleColorLight(themeColor.primary, -36 * brightDir), + num4: scaleColorLight(themeColor.primary, -48 * brightDir), + num5: scaleColorLight(themeColor.primary, -60 * brightDir), + num6: scaleColorLight(themeColor.primary, -72 * brightDir), + num7: scaleColorLight(themeColor.primary, -84 * brightDir), + }, + light: { + num1: scaleColorLight(themeColor.primary, 12 * brightDir), + num2: scaleColorLight(themeColor.primary, 24 * brightDir), + num3: scaleColorLight(themeColor.primary, 36 * brightDir), + num4: scaleColorLight(themeColor.primary, 48 * brightDir), + num5: scaleColorLight(themeColor.primary, 60 * brightDir), + num6: scaleColorLight(themeColor.primary, 72 * brightDir), + num7: scaleColorLight(themeColor.primary, 84 * brightDir), + }, + alpha: { + num10: rgba(themeColor.primary, 0.1), + num20: rgba(themeColor.primary, 0.2), + num30: rgba(themeColor.primary, 0.3), + num40: rgba(themeColor.primary, 0.4), + num50: rgba(themeColor.primary, 0.5), + num60: rgba(themeColor.primary, 0.6), + num70: rgba(themeColor.primary, 0.7), + num80: rgba(themeColor.primary, 0.8), + num90: rgba(themeColor.primary, 0.9), + }, + hover: themeColor.isDarkTheme ? themeVars.color.primary.light.num1 : themeVars.color.primary.dark.num1, + active: themeColor.isDarkTheme ? themeVars.color.primary.light.num2 : themeVars.color.primary.dark.num2, + }; + + const secondary: Secondary = { + self: themeColor.secondary, + dark: { + num1: scaleColorLight(themeColor.secondary, -6 * brightDir), + num2: scaleColorLight(themeColor.secondary, -12 * brightDir), + num3: scaleColorLight(themeColor.secondary, -18 * brightDir), + num4: scaleColorLight(themeColor.secondary, -24 * brightDir), + num5: scaleColorLight(themeColor.secondary, -30 * brightDir), + num6: scaleColorLight(themeColor.secondary, -36 * brightDir), + num7: scaleColorLight(themeColor.secondary, -42 * brightDir), + num8: scaleColorLight(themeColor.secondary, -48 * brightDir), + num9: scaleColorLight(themeColor.secondary, -54 * brightDir), + num10: scaleColorLight(themeColor.secondary, -60 * brightDir), + num11: scaleColorLight(themeColor.secondary, -66 * brightDir), + num12: scaleColorLight(themeColor.secondary, -72 * brightDir), + num13: scaleColorLight(themeColor.secondary, -80 * brightDir), + }, + light: { + num1: scaleColorLight(themeColor.secondary, 18 * brightDir), + num2: scaleColorLight(themeColor.secondary, 36 * brightDir), + num3: scaleColorLight(themeColor.secondary, 54 * brightDir), + num4: scaleColorLight(themeColor.secondary, 72 * brightDir), + }, + alpha: { + num10: rgba(themeColor.secondary, 0.1), + num20: rgba(themeColor.secondary, 0.2), + num30: rgba(themeColor.secondary, 0.3), + num40: rgba(themeColor.secondary, 0.4), + num50: rgba(themeColor.secondary, 0.5), + num60: rgba(themeColor.secondary, 0.6), + num70: rgba(themeColor.secondary, 0.7), + num80: rgba(themeColor.secondary, 0.8), + num90: rgba(themeColor.secondary, 0.9), + }, + button: themeVars.color.secondary.dark.num4, + hover: themeColor.isDarkTheme ? themeVars.color.secondary.dark.num3 : themeVars.color.secondary.dark.num5, + active: themeColor.isDarkTheme ? themeVars.color.secondary.dark.num2 : themeVars.color.secondary.dark.num6, + }; + + const named: Named = { + red: { + self: themeColor.base.red, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.red, 15) + : scaleColorLight(themeColor.base.red, 25), + dark: { + num1: scaleColorLight(themeColor.base.red, -10), + num2: scaleColorLight(themeColor.base.red, -20), + }, + badge: { + self: themeColor.base.red, + bg: rgba(themeColor.base.red, 0.1), + hover: { + bg: rgba(themeColor.base.red, 0.3), + }, + }, + }, + orange: { + self: themeColor.base.orange, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.orange, 15) + : scaleColorLight(themeColor.base.orange, 25), + dark: { + num1: scaleColorLight(themeColor.base.orange, -10), + num2: scaleColorLight(themeColor.base.orange, -20), + }, + badge: { + self: themeColor.base.orange, + bg: rgba(themeColor.base.orange, 0.1), + hover: { + bg: rgba(themeColor.base.orange, 0.3), + }, + }, + }, + yellow: { + self: themeColor.base.yellow, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.yellow, 15) + : scaleColorLight(themeColor.base.yellow, 25), + dark: { + num1: scaleColorLight(themeColor.base.yellow, -10), + num2: scaleColorLight(themeColor.base.yellow, -20), + }, + badge: { + self: themeColor.base.yellow, + bg: rgba(themeColor.base.yellow, 0.1), + hover: { + bg: rgba(themeColor.base.yellow, 0.3), + }, + }, + }, + olive: { + self: themeColor.base.olive, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.olive, 15) + : scaleColorLight(themeColor.base.olive, 25), + dark: { + num1: scaleColorLight(themeColor.base.olive, -10), + num2: scaleColorLight(themeColor.base.olive, -20), + }, + }, + green: { + self: themeColor.base.green, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.green, 15) + : scaleColorLight(themeColor.base.green, 25), + dark: { + num1: scaleColorLight(themeColor.base.green, -10), + num2: scaleColorLight(themeColor.base.green, -20), + }, + badge: { + self: themeColor.base.green, + bg: rgba(themeColor.base.green, 0.1), + hover: { + bg: rgba(themeColor.base.green, 0.3), + }, + }, + }, + teal: { + self: themeColor.base.teal, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.teal, 15) + : scaleColorLight(themeColor.base.teal, 25), + dark: { + num1: scaleColorLight(themeColor.base.teal, -10), + num2: scaleColorLight(themeColor.base.teal, -20), + }, + }, + blue: { + self: themeColor.base.blue, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.blue, 15) + : scaleColorLight(themeColor.base.blue, 25), + dark: { + num1: scaleColorLight(themeColor.base.blue, -10), + num2: scaleColorLight(themeColor.base.blue, -20), + }, + }, + violet: { + self: themeColor.base.violet, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.violet, 15) + : scaleColorLight(themeColor.base.violet, 25), + dark: { + num1: scaleColorLight(themeColor.base.violet, -10), + num2: scaleColorLight(themeColor.base.violet, -20), + }, + }, + purple: { + self: themeColor.base.purple, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.purple, 15) + : scaleColorLight(themeColor.base.purple, 25), + dark: { + num1: scaleColorLight(themeColor.base.purple, -10), + num2: scaleColorLight(themeColor.base.purple, -20), + }, + }, + pink: { + self: themeColor.base.pink, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.pink, 15) + : scaleColorLight(themeColor.base.pink, 25), + dark: { + num1: scaleColorLight(themeColor.base.pink, -10), + num2: scaleColorLight(themeColor.base.pink, -20), + }, + }, + brown: { + self: themeColor.base.brown, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.brown, 15) + : scaleColorLight(themeColor.base.brown, 25), + dark: { + num1: scaleColorLight(themeColor.base.brown, -10), + num2: scaleColorLight(themeColor.base.brown, -20), + }, + }, + black: { + self: themeColor.base.black, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.black, 15) + : scaleColorLight(themeColor.base.black, 25), + dark: { + num1: scaleColorLight(themeColor.base.black, -10), + num2: scaleColorLight(themeColor.base.black, -20), + }, + }, + grey: { + self: themeColor.base.grey, + light: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.grey, 15) + : scaleColorLight(themeColor.base.grey, 25), + }, + gold: themeColor.base.gold, + white: themeColor.base.white, + }; + + const message: Message = { + error: { + bg: { + self: rgba(themeColor.base.red, 0.1), + active: rgba(themeColor.base.red, 0.5), + hover: rgba(themeColor.base.red, 0.3), + }, + border: rgba(themeColor.base.red, 0.4), + text: saturate(0.2, themeColor.base.red), // 饱和度提高 + }, + success: { + bg: rgba(themeColor.base.green, 0.1), + border: rgba(themeColor.base.green, 0.4), + text: saturate(0.2, themeColor.base.green), + }, + warning: { + bg: rgba(themeColor.base.yellow, 0.1), + border: rgba(themeColor.base.yellow, 0.4), + text: saturate(0.2, themeColor.base.yellow), + }, + info: { + bg: rgba(themeColor.base.blue, 0.1), + border: rgba(themeColor.base.blue, 0.4), + text: saturate(0.2, themeColor.base.blue), + }, + }; + + const ansi: Ansi = { + black: themeVars.color.black.self, + red: themeVars.color.red.self, + green: themeVars.color.green.self, + yellow: themeVars.color.yellow.self, + blue: themeVars.color.blue.self, + magenta: themeVars.color.pink.self, + cyan: themeColor.base.cyan, + white: themeVars.color.console.fg.subtle, + bright: { + black: themeVars.color.black.light, + red: themeVars.color.red.light, + green: themeVars.color.green.light, + yellow: themeVars.color.yellow.light, + blue: themeVars.color.blue.light, + magenta: themeVars.color.pink.light, + cyan: themeColor.isDarkTheme + ? scaleColorLight(themeColor.base.cyan, 10) + : scaleColorLight(themeColor.base.cyan, 25), + white: themeVars.color.console.fg.self, + }, + }; + + return { + isDarkTheme: themeColor.isDarkTheme.toString(), + chroma: chroma || (themeColor.isDarkTheme ? defaultDarkChroma : defaultLightChroma), + color: { + primary, + secondary, + ...named, + ansi, + console: themeColor.console, + diff: themeColor.diff, + ...message, + ...themeColor.other, + }, + github: themeColor.github, + }; +} diff --git a/src/core/display.ts b/src/core/display.ts new file mode 100644 index 0000000..b4358ef --- /dev/null +++ b/src/core/display.ts @@ -0,0 +1,85 @@ +import { saturate } from "polished"; +import { scaleColorLight } from "src/functions"; +import { type GithubColor } from "./github"; + +export type DisplayColor = { + num0: string; + num1: string; + num2: string; + num3: string; + num4: string; + num5: string; + num6: string; + num7: string; + num8: string; + num9: string; +}; + +export function display2GithubColor( + displayColor: DisplayColor, + baseGithubColor: GithubColor, + soft?: boolean +): GithubColor { + return { + ...baseGithubColor, + diffBlob: { + ...baseGithubColor.diffBlob, + hunkNum: { bgColor: { rest: soft ? displayColor.num2 : displayColor.num1 } }, + }, + fgColor: { + ...baseGithubColor.fgColor, + accent: soft ? displayColor.num7 : displayColor.num6, + }, + bgColor: { + ...baseGithubColor.bgColor, + accent: { + emphasis: soft ? saturate(-0.1, scaleColorLight(displayColor.num5, -2)) : displayColor.num5, + muted: soft ? displayColor.num1 : displayColor.num0, + }, + }, + borderColor: { + ...baseGithubColor.borderColor, + accent: { + emphasis: soft ? displayColor.num6 : displayColor.num5, + }, + }, + button: { + ...baseGithubColor.button, + primary: { + ...baseGithubColor.button.primary, + fgColor: { + ...baseGithubColor.button.primary.fgColor, + accent: soft ? displayColor.num7 : displayColor.num6, + }, + bgColor: { + ...baseGithubColor.button.primary.bgColor, + rest: soft ? saturate(-0.1, scaleColorLight(displayColor.num5, -2)) : displayColor.num5, + hover: soft ? saturate(-0.1, scaleColorLight(displayColor.num5, 3)) : scaleColorLight(displayColor.num5, 5), + }, + }, + star: { + iconColor: soft + ? scaleColorLight(displayColor.num6, -2) + : saturate(0.1, scaleColorLight(displayColor.num6, -2)), + }, + }, + underlineNav: { + borderColor: { + active: soft ? scaleColorLight(saturate(0.1, displayColor.num6), -5) : saturate(0.2, displayColor.num6), + }, + }, + contribution: { + ...baseGithubColor.contribution, + default: { + ...baseGithubColor.contribution.default, + bgColor: { + num0: baseGithubColor.contribution.default.bgColor.num0, + num1: soft ? displayColor.num2 : displayColor.num1, + num2: soft ? displayColor.num3 : displayColor.num2, + num3: soft ? displayColor.num5 : displayColor.num4, + num4: soft ? displayColor.num7 : displayColor.num6, + }, + }, + }, + }; +} diff --git a/src/core/github.ts b/src/core/github.ts new file mode 100644 index 0000000..a144b15 --- /dev/null +++ b/src/core/github.ts @@ -0,0 +1,370 @@ +import { saturate } from "polished"; +import type { Console, Diff, Other } from "src"; +import { scaleColorLight } from "src/functions"; +import type { Github } from "src/types"; +import { themeVars } from "src/types/vars"; +import { type ThemeColor } from "./color"; + +export type GithubColor = { + isDarkTheme: boolean; + display: { + blue: { fgColor: string }; + brown: { fgColor: string }; + cyan: { fgColor: string }; + indigo: { fgColor: string }; + lemon: { fgColor: string }; + olive: { fgColor: string }; + teal: { fgColor: string }; + }; + diffBlob: { + addtionNum: { bgColor: string }; + addtionWord: { bgColor: string }; + deletionNum: { bgColor: string }; + deletionWord: { bgColor: string }; + hunkNum: { bgColor: { rest: string } }; + }; + fgColor: { + accent: string; + attention: string; + danger: string; + default: string; + disabled: string; + done: string; + neutral: string; + severe: string; + sponsors: string; + success: string; + black: string; + white: string; + muted: string; + onEmphasis: string; + }; + bgColor: { + accent: { emphasis: string; muted: string }; + attention: { muted: string }; + emphasis: string; + success: { emphasis: string; muted: string }; + danger: { muted: string }; + done: { emphasis: string }; + default: string; + inset: string; + muted: string; + neutral: { muted: string }; + }; + borderColor: { + accent: { emphasis: string }; + attention: { emphasis: string }; + default: string; + success: { emphasis: string }; + done: { emphasis: string }; + muted: string; + translucent: string; + }; + button: { + primary: { fgColor: { accent: string; rest: string }; bgColor: { rest: string; hover: string } }; + danger: { fgColor: { rest: string; hover: string }; bgColor: { hover: string } }; + star: { iconColor: string }; + }; + control: { + bgColor: { active: string; hover: string; rest: string }; + transparent: { bgColor: { active: string; hover: string; selected: string } }; + }; + shadow: { floating: { small: string }; resting: { small: string } }; + overlay: { backdrop: { bgColor: string } }; + underlineNav: { borderColor: { active: string } }; + contribution: { + default: { + bgColor: { num0: string; num1: string; num2: string; num3: string; num4: string }; + borderColor: { num0: string }; + }; + }; +}; + +export function github2ThemeColor(githubColor: GithubColor): ThemeColor { + const console: Console = { + fg: { + self: githubColor.fgColor.default, + subtle: githubColor.fgColor.muted, + }, + bg: githubColor.bgColor.inset, + border: githubColor.borderColor.muted, + activeBg: githubColor.control.bgColor.active, + hoverBg: githubColor.control.transparent.bgColor.hover, + menu: { + bg: githubColor.bgColor.inset, + border: githubColor.borderColor.muted, + }, + }; + + const diff: Diff = { + added: { + linenum: { + bg: githubColor.diffBlob.addtionNum.bgColor, + }, + row: { + bg: githubColor.bgColor.success.muted, + border: githubColor.bgColor.success.muted, + }, + word: { + bg: githubColor.diffBlob.addtionWord.bgColor, + }, + }, + removed: { + linenum: { + bg: githubColor.diffBlob.deletionNum.bgColor, + }, + row: { + bg: githubColor.bgColor.danger.muted, + border: githubColor.bgColor.danger.muted, + }, + word: { + bg: githubColor.diffBlob.deletionWord.bgColor, + }, + }, + moved: { + row: { + bg: githubColor.bgColor.attention.muted, + border: githubColor.bgColor.attention.muted, + }, + }, + inactive: githubColor.bgColor.muted, + }; + + const other: Other = { + body: githubColor.bgColor.default, + box: { + header: githubColor.bgColor.muted, + body: { + self: themeVars.color.body, + highlight: githubColor.bgColor.accent.muted, + }, + }, + text: { + self: githubColor.fgColor.default, + light: { + self: githubColor.fgColor.default, + num1: githubColor.fgColor.muted, + num2: githubColor.fgColor.muted, + num3: githubColor.fgColor.muted, + }, + dark: githubColor.fgColor.default, + }, + footer: githubColor.bgColor.inset, + timeline: githubColor.borderColor.muted, + input: { + text: themeVars.color.text.self, + background: githubColor.bgColor.muted, + toggleBackgound: themeVars.color.body, + border: { + self: themeVars.color.light.border, + hover: themeVars.color.light.border, + }, + }, + light: { + self: themeVars.color.body, + border: githubColor.borderColor.default, + }, + hover: { + self: githubColor.control.bgColor.hover, + opaque: themeVars.color.box.header, + }, + active: githubColor.control.transparent.bgColor.selected, + menu: githubColor.bgColor.inset, + card: themeVars.color.body, + markup: { + tableRow: githubColor.bgColor.muted, + code: { + block: githubColor.bgColor.muted, + inline: githubColor.bgColor.neutral.muted, + }, + }, + button: githubColor.control.bgColor.rest, + codeBg: "unset", + shadow: { + self: githubColor.shadow.floating.small, + opaque: themeVars.color.shadow.self, + }, + secondaryBg: "unset", + expandButton: githubColor.diffBlob.hunkNum.bgColor.rest, + placeholderText: themeVars.color.text.light.num3, + editorLineHighlight: themeVars.color.primary.light.num5, + projectColumnBg: githubColor.bgColor.inset, + caret: themeVars.color.text.dark, + reaction: { + bg: "initial", + hoverBg: githubColor.bgColor.accent.muted, + activeBg: githubColor.bgColor.accent.muted, + }, + tooltip: { + text: githubColor.fgColor.onEmphasis, + bg: githubColor.bgColor.emphasis, + }, + nav: { + bg: githubColor.bgColor.muted, + hoverBg: githubColor.control.transparent.bgColor.hover, + text: themeVars.color.text.self, + }, + secondaryNavBg: themeVars.color.body, + label: { + text: themeVars.color.text.self, + bg: githubColor.bgColor.neutral.muted, + hoverBg: githubColor.control.transparent.bgColor.active, + activeBg: themeVars.color.label.hoverBg, + }, + accent: themeVars.color.primary.light.num1, + smallAccent: themeVars.color.primary.light.num5, + highlight: { + fg: githubColor.fgColor.attention, + bg: githubColor.bgColor.attention.muted, + }, + overlayBackdrop: githubColor.overlay.backdrop.bgColor, + }; + + const github: Github = { + fgColor: { + accent: githubColor.fgColor.accent, + success: githubColor.fgColor.success, + done: githubColor.fgColor.done, + }, + bgColor: { + accent: { + emphasis: githubColor.bgColor.accent.emphasis, + muted: githubColor.bgColor.accent.muted, + }, + success: { + emphasis: githubColor.bgColor.success.emphasis, + }, + done: { + emphasis: githubColor.bgColor.done.emphasis, + }, + }, + borderColor: { + accent: { + emphasis: githubColor.borderColor.accent.emphasis, + }, + attention: { + emphasis: githubColor.borderColor.attention.emphasis, + }, + success: { + emphasis: githubColor.borderColor.success.emphasis, + }, + done: { + emphasis: githubColor.borderColor.done.emphasis, + }, + }, + button: { + default: { + bgColor: { + active: githubColor.control.bgColor.active, + }, + }, + primary: { + fgColor: { + accent: saturate( + 0.1, + scaleColorLight(githubColor.button.primary.fgColor.accent, githubColor.isDarkTheme ? 10 : -10) + ), + rest: githubColor.button.primary.fgColor.rest, + }, + bgColor: { + rest: githubColor.button.primary.bgColor.rest, + hover: githubColor.button.primary.bgColor.hover, + }, + borderColor: { + rest: githubColor.borderColor.translucent, + hover: githubColor.borderColor.translucent, + }, + }, + danger: { + fgColor: { + rest: githubColor.button.danger.fgColor.rest, + hover: githubColor.button.danger.fgColor.hover, + }, + bgColor: { + rest: githubColor.control.bgColor.rest, + hover: githubColor.button.danger.bgColor.hover, + }, + borderColor: { + hover: githubColor.borderColor.translucent, + }, + }, + star: { + iconColor: githubColor.button.star.iconColor, + }, + }, + control: { + bgColor: { + rest: githubColor.control.bgColor.rest, + }, + transparent: { + bgColor: { + hover: githubColor.control.transparent.bgColor.hover, + }, + }, + }, + shadow: { + floating: { + small: `0px 0px 0px 1px ${themeVars.color.light.border}, 0px 6px 12px -3px ${themeVars.color.shadow.self}, 0px 6px 18px 0px ${themeVars.color.shadow.self};`, + }, + resting: { + small: `0px 1px 1px 0px ${githubColor.shadow.resting.small}, 0px 1px 3px 0px ${githubColor.shadow.resting.small};`, + }, + }, + underlineNav: { + borderColor: { + active: githubColor.underlineNav.borderColor.active, + }, + }, + contribution: { + default: { + bgColor: { + num0: githubColor.contribution.default.bgColor.num0, + num1: githubColor.contribution.default.bgColor.num1, + num2: githubColor.contribution.default.bgColor.num2, + num3: githubColor.contribution.default.bgColor.num3, + num4: githubColor.contribution.default.bgColor.num4, + num5: saturate( + 0.2, + scaleColorLight(githubColor.contribution.default.bgColor.num4, githubColor.isDarkTheme ? 58 : -58) + ), + }, + borderColor: { + num0: githubColor.contribution.default.borderColor.num0, + num1: themeVars.github.contribution.default.borderColor.num0, + num2: themeVars.github.contribution.default.borderColor.num0, + num3: themeVars.github.contribution.default.borderColor.num0, + num4: themeVars.github.contribution.default.borderColor.num0, + num5: themeVars.github.contribution.default.borderColor.num0, + }, + }, + }, + }; + return { + isDarkTheme: githubColor.isDarkTheme, + primary: githubColor.fgColor.accent, + primaryContrast: githubColor.fgColor.default, + secondary: githubColor.borderColor.default, + base: { + red: githubColor.fgColor.danger, + orange: githubColor.fgColor.severe, + yellow: githubColor.fgColor.attention, + olive: githubColor.display.olive.fgColor, + green: githubColor.fgColor.success, + cyan: githubColor.display.cyan.fgColor, + teal: githubColor.display.teal.fgColor, + blue: githubColor.display.blue.fgColor, + violet: githubColor.display.indigo.fgColor, + purple: githubColor.fgColor.done, + pink: githubColor.fgColor.sponsors, + brown: githubColor.display.brown.fgColor, + black: githubColor.fgColor.black, + grey: githubColor.fgColor.neutral, + gold: githubColor.display.lemon.fgColor, + white: githubColor.fgColor.white, + }, + console, + diff, + other, + github, + }; +} diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..796bf88 --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1 @@ +export { createTheme } from "./theme"; diff --git a/src/core/prettylights.ts b/src/core/prettylights.ts new file mode 100644 index 0000000..1f5c749 --- /dev/null +++ b/src/core/prettylights.ts @@ -0,0 +1,127 @@ +import type { Chroma } from "src/types"; + +export type prettylightsColor = { + syntax: { + brackethighlighter: { angle: string; unmatched: string }; + carriage: { return: { bg: string; text: string } }; + comment: string; + constant: string; + constantOtherReferenceLink: string; + entity: string; + entityTag: string; + invalid: { illegal: { bg: string; text: string } }; + keyword: string; + markup: { + bold: string; + changed: { bg: string; text: string }; + deleted: { bg: string; text: string }; + heading: string; + ignored: { bg: string; text: string }; + inserted: { bg: string; text: string }; + italic: string; + list: string; + }; + metaDiffRange: string; + storageModifierImport: string; + string: string; + stringRegexp: string; + sublimelinterGutterMark: string; + variable: string; + }; +}; + +export function prettylights2Chroma(prettylights: prettylightsColor): Chroma { + return { + textWhiteSpace: prettylights.syntax.brackethighlighter.unmatched, + err: prettylights.syntax.brackethighlighter.unmatched, + keyword: { + self: prettylights.syntax.keyword, + constant: prettylights.syntax.constant, + declaration: prettylights.syntax.keyword, + namespace: prettylights.syntax.keyword, + pseudo: prettylights.syntax.constant, + reserved: prettylights.syntax.keyword, + type: prettylights.syntax.markup.bold, + }, + name: { + self: prettylights.syntax.markup.bold, + attribute: prettylights.syntax.entityTag, + builtin: prettylights.syntax.entity, + builtinPseudo: prettylights.syntax.markup.bold, + class: prettylights.syntax.variable, + constant: prettylights.syntax.variable, + decorator: prettylights.syntax.entity, + entity: prettylights.syntax.variable, + exception: prettylights.syntax.variable, + function: prettylights.syntax.entity, + functionMagic: prettylights.syntax.entity, + label: prettylights.syntax.constant, + other: prettylights.syntax.markup.bold, + namespace: prettylights.syntax.markup.bold, + property: prettylights.syntax.constant, + tag: prettylights.syntax.entityTag, + variable: prettylights.syntax.constant, + variableClass: prettylights.syntax.constant, + variableGlobal: prettylights.syntax.constant, + variableInstance: prettylights.syntax.constant, + variableMagic: prettylights.syntax.markup.bold, + }, + literal: { + self: prettylights.syntax.string, + date: prettylights.syntax.constant, + }, + string: { + self: prettylights.syntax.string, + affix: prettylights.syntax.string, + backtick: prettylights.syntax.constant, + char: prettylights.syntax.string, + delimiter: prettylights.syntax.string, + doc: prettylights.syntax.comment, + double: prettylights.syntax.string, + escape: prettylights.syntax.string, + heredoc: prettylights.syntax.string, + interpol: prettylights.syntax.string, + other: prettylights.syntax.string, + regex: prettylights.syntax.stringRegexp, + single: prettylights.syntax.string, + symbol: prettylights.syntax.string, + }, + number: { + self: prettylights.syntax.constant, + bin: prettylights.syntax.constant, + float: prettylights.syntax.constant, + hex: prettylights.syntax.constant, + integer: prettylights.syntax.constant, + integerLong: prettylights.syntax.constant, + oct: prettylights.syntax.constant, + }, + operator: { + self: prettylights.syntax.constant, + word: prettylights.syntax.constant, + }, + punctuation: prettylights.syntax.markup.bold, + comment: { + self: prettylights.syntax.comment, + hashbang: prettylights.syntax.comment, + multiline: prettylights.syntax.comment, + preproc: prettylights.syntax.constant, + preprocFile: prettylights.syntax.constant, + single: prettylights.syntax.comment, + special: prettylights.syntax.comment, + }, + generic: { + self: prettylights.syntax.markup.bold, + deleted: prettylights.syntax.markup.deleted.text, + emph: prettylights.syntax.markup.italic, + error: prettylights.syntax.invalid.illegal.text, + heading: prettylights.syntax.markup.heading, + inserted: prettylights.syntax.markup.inserted.text, + output: prettylights.syntax.markup.bold, + prompt: prettylights.syntax.markup.bold, + strong: prettylights.syntax.markup.bold, + subheading: prettylights.syntax.markup.heading, + traceback: prettylights.syntax.invalid.illegal.text, + underline: prettylights.syntax.markup.italic, + }, + }; +} diff --git a/src/core/theme.ts b/src/core/theme.ts new file mode 100644 index 0000000..4e8641c --- /dev/null +++ b/src/core/theme.ts @@ -0,0 +1,67 @@ +import { createGlobalTheme, globalStyle } from "@vanilla-extract/css"; +import fs from "node:fs"; +import path from "node:path"; +import { otherThemeVars, themeInfoVars, themeVars } from "src/types/vars"; +import type { MapLeafNodes, WithOptionalLayer } from "./types"; + +export type Theme = WithOptionalLayer>; + +export const overlayAppearDown = "overlay-appear-down"; +export const animationDown = `200ms cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running ${overlayAppearDown}`; +export const overlayAppearUp = "overlay-appear-up"; +export const animationUp = `200ms cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running ${overlayAppearUp}`; + +const emoji = ` +.emoji[aria-label="check mark"], +.emoji[aria-label="currency exchange"], +.emoji[aria-label="TOP arrow"], +.emoji[aria-label="END arrow"], +.emoji[aria-label="ON! arrow"], +.emoji[aria-label="SOON arrow"], +.emoji[aria-label="heavy dollar sign"], +.emoji[aria-label="copyright"], +.emoji[aria-label="registered"], +.emoji[aria-label="trade mark"], +.emoji[aria-label="multiply"], +.emoji[aria-label="plus"], +.emoji[aria-label="minus"], +.emoji[aria-label="divide"], +.emoji[aria-label="curly loop"], +.emoji[aria-label="double curly loop"], +.emoji[aria-label="wavy dash"], +.emoji[aria-label="paw prints"], +.emoji[aria-label="musical note"], +.emoji[aria-label="musical notes"] +`; + +// 版本号: 版本号.YYMMDD +const now = new Date(); +const year = now.getFullYear().toString().slice(-2); +const month = (now.getMonth() + 1).toString().padStart(2, "0"); +const day = now.getDate().toString().padStart(2, "0"); + +const pkgPath = path.join(__dirname, "../..", "package.json"); +const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); +const version = `"${pkg.version}.${year}${month}${day}"`; + +export function createTheme(theme: Theme): void { + const isDarkTheme: boolean = JSON.parse(theme.isDarkTheme); + createGlobalTheme(":root", themeInfoVars, { version }); + createGlobalTheme(":root", themeVars, theme); + createGlobalTheme(":root", otherThemeVars, { + border: { radius: "6px" }, + color: { + git: "#f05133", + light: { + mimicEnabled: isDarkTheme + ? "rgba(0, 0, 0, calc(40 / 255 * 222 / 255 / var(--opacity-disabled)))" + : "rgba(0, 0, 0, calc(6 / 255 * 222 / 255 / var(--opacity-disabled)))", + }, + }, + }); + globalStyle(":root", { + accentColor: themeVars.color.accent, + colorScheme: isDarkTheme ? "dark" : "light", + }); + if (isDarkTheme) globalStyle(emoji, { filter: "invert(100%) hue-rotate(180deg)" }); +} diff --git a/src/core/types.ts b/src/core/types.ts new file mode 100644 index 0000000..116709a --- /dev/null +++ b/src/core/types.ts @@ -0,0 +1,12 @@ +type Primitive = string | boolean | number | null | undefined; +type Tokens = { [key: string]: string | Tokens }; + +export type CSSVarFunction = `var(--${string})`; +export type WithOptionalLayer = T & { "@layer"?: string }; +export type MapLeafNodes = { + [Prop in keyof Obj]: Obj[Prop] extends Primitive + ? LeafType + : Obj[Prop] extends Record + ? MapLeafNodes + : never; +}; diff --git a/src/core/vite.ts b/src/core/vite.ts new file mode 100644 index 0000000..55337ca --- /dev/null +++ b/src/core/vite.ts @@ -0,0 +1,139 @@ +import { execSync } from "node:child_process"; +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import type { Plugin } from "vite"; + +const suffix = ".css.ts"; + +/** + * 生成主题输入 + * @param outDir 输出目录与 vite 配置中的 outDir 一致, 用于生成临时目录 + * @param themeDir 颜色主题目录 + * @param devTheme 开发模式下的主题, 仅打包该主题 + * @param mode 模式, 开发模式为 dev `vite build --mode dev` + * @returns vite.rollupOptions.input 的配置 + */ +export function themeInput(outDir: string, themeDir: string, mode: string): { [key: string]: string } { + const hash = crypto.randomBytes(6).toString("hex"); + const tmpDir = `${outDir}/tmp-${hash}`; // 输出目录下的临时目录 + fs.mkdirSync(tmpDir, { recursive: true }); + + const input: { [key: string]: string } = {}; + const themeEntries = fs.readdirSync(themeDir, { withFileTypes: true }); + const devTheme = process.env.DEV_THEME || "dark"; // 开发模式仅打包单个颜色主题 + + for (const entry of themeEntries) { + // 目录下所有的 css.ts 文件都作为主题入口 + if (entry.isFile() && entry.name.endsWith(suffix)) { + const fileName = entry.name.replace(suffix, ""); + // 开发模式只打包 devTheme 主题 + if (mode === "dev" && fileName !== devTheme) continue; + // 创建颜色主题的 css.ts 文件, vanilla-extract 需要这个文件后缀名并生成 css + const tmpCssTs = path.join(tmpDir, `${fileName}${suffix}`); + const createImport = `import { createTheme } from "src/core";`; + const themeImport = `import theme from "themes/${fileName}";`; + const createFn = `createTheme(theme);`; + fs.writeFileSync(tmpCssTs, `${createImport}\n${themeImport}\n${createFn}`); + // 生成主题入口的 .ts 文件, 合并样式和颜色主题 + const tmpInputTs = path.join(tmpDir, `${fileName}.ts`); + const stylesImport = `import "styles";`; + const cssImport = `import "./${fileName}${suffix}";`; + fs.writeFileSync(tmpInputTs, `${stylesImport}\n${cssImport}`); + + input[fileName] = tmpInputTs; + } + } + if (mode === "dev") { + console.log("[themeInput] devTheme:", devTheme); + } + return input; +} + +function giteaThemeMetaInfo(nameGroup: string[]): string { + const displayName = nameGroup.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" "); + return `gitea-theme-meta-info{--theme-display-name:"GitHub ${displayName}";}`; // 不要省略分号, 也不要追加任何变量, 否则 Gitea 不识别 +} + +const prefix = "theme-github-"; + +/** + * 生成主题文件 + * @important vite.rollupOptions.output 配置 `assetFileNames: "[name].[ext]"` + */ +export function themePlugin(): Plugin { + return { + name: "themePlugin", + generateBundle(this, _, bundle) { + let styles = ""; + for (const [key, value] of Object.entries(bundle)) { + if (value.type === "chunk") { + delete bundle[key]; // 删除 chunk + } else { + // 样式文件是通过入口导入的, 没有原始文件名 + if (value.originalFileNames.length === 0) { + // 收集所有的样式文件(逻辑上只有一个) + // vite 会在尾部添加注释, 后续会合并到颜色主题, 此处需要删除注释 + styles += value.source.toString().replace("/*$vite$:1*/", ""); + delete bundle[key]; + } + } + } + // 生成所有的主题文件 + for (const [key, value] of Object.entries(bundle)) { + // 仅为了类型检查, 逻辑上输出中全是 asset 类型 + if (value.type === "asset") { + const name = `${prefix}${key}`; + const fileName = `${prefix}${value.fileName}`; + const originalFileName = value.originalFileNames.pop(); + const type = value.type; + // 合并样式文件和主题信息 + const meta = giteaThemeMetaInfo(key.split(".")[0].split("-")); + const source = `${meta}${value.source.toString()}${styles}`; + // 添加主题到输出 + this.emitFile({ name, fileName, source, type, originalFileName }); + // 自动颜色主题 + const isDark = key.endsWith("dark.css"); + const darkName = key.replace("light", "dark"); + const lightName = darkName.replace("dark", "light"); + const findTheme = isDark ? lightName : darkName; + if (findTheme in bundle) { + const autoName = `${prefix}${darkName.replace("dark", "auto")}`; + const lightContent = `@import "./${prefix}${lightName}" (prefers-color-scheme: light);`; + const darkContent = `@import "./${prefix}${darkName}" (prefers-color-scheme: dark);`; + const nameGroup = key.split(".")[0].split("-").slice(0, -1); + nameGroup.push("auto"); + const metaInfo = giteaThemeMetaInfo(nameGroup); + this.emitFile({ + name: autoName, + fileName: autoName, + type: "asset", + source: `${lightContent}\n${darkContent}\n${metaInfo}`, + }); + } + // 删除原始的样式文件, 自动颜色主题因为删除, 永远不会生成两次 + delete bundle[key]; + } + } + }, + writeBundle() { + // 上传到服务器 + const server = process.env.SSH_SERVER; + const user = process.env.SSH_USER || "root"; + const theme_path = process.env.GITEA_THEME_PATH; + if (server && theme_path) { + const cmd = `scp dist/${prefix}*.css ${user}@${server}:${theme_path}`; + console.log("[themePlugin] exec:", cmd); + try { + execSync(cmd, { stdio: "inherit" }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + // continue regardless of error + } + } else { + console.log("[themePlugin] no SSH_SERVER or GITEA_THEME_PATH, skip upload"); + } + console.log("[themePlugin] exec end."); + }, + }; +} diff --git a/src/functions/index.ts b/src/functions/index.ts new file mode 100644 index 0000000..c703d21 --- /dev/null +++ b/src/functions/index.ts @@ -0,0 +1,2 @@ +export { scaleColorLight } from "./scss"; +export { fallbackVar } from "./var"; diff --git a/src/functions/scss.ts b/src/functions/scss.ts new file mode 100644 index 0000000..1501751 --- /dev/null +++ b/src/functions/scss.ts @@ -0,0 +1,27 @@ +import { hsl, parseToHsl } from "polished"; + +/** + * 改变颜色的亮度, 等同于 sass 中的 `color.scale` 函数 + * @param color 颜色值 + * @param lightnessScale 亮度变化比例,负数表示变暗,正数表示变亮 + * @returns 新的颜色值 + * @example + * const newColor = scaleColorLight("#ff0000", 20); // 变亮 + * const newColor = scaleColorLight("#ff0000", -20); // 变暗 + * 等同于 sass `@use "sass:color"`; + * color: color.scale(#ff0000, $lightness: 20%) + * color: color.scale(#ff0000, $lightness: -20%) + */ +export function scaleColorLight(color: string, lightness: number) { + const hslColor = parseToHsl(color); + let newLightness; + + if (lightness < 0) { + newLightness = hslColor.lightness * (1 + lightness / 100); // 变暗 + } else { + newLightness = hslColor.lightness + (1 - hslColor.lightness) * (lightness / 100); // 变亮 + } + + newLightness = Math.min(1, Math.max(0, newLightness)); // 确保亮度值在 0 到 1 之间 + return hsl(hslColor.hue, hslColor.saturation, newLightness); +} diff --git a/src/functions/var.ts b/src/functions/var.ts new file mode 100644 index 0000000..546a560 --- /dev/null +++ b/src/functions/var.ts @@ -0,0 +1,13 @@ +import type { CSSVarFunction } from "src/core/types"; + +type CSSFallbackVar = `var(--${string}, ${string})`; +/** + * 设置 CSS 变量的回退值 + * @param cssvar `var(--${string})` + * @param fallback + * @returns `var(--${string}, fallback)` + */ +export function fallbackVar(cssvar: CSSVarFunction, fallback: string): CSSFallbackVar { + const var_name = cssvar.replace("var(--", "").replace(")", ""); + return `var(--${var_name}, ${fallback})`; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..da28aef --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +export { defaultDarkChroma, defaultLightChroma } from "./core/chroma"; +export { defineTheme, type ThemeColor } from "./core/color"; +export type { Theme } from "./core/theme"; +export type { Ansi, Chroma, Console, Diff, Github, Message, Named, Other, Primary, Secondary } from "./types"; +export { otherThemeVars, themeVars } from "./types/vars"; diff --git a/src/types/color/chroma.ts b/src/types/color/chroma.ts new file mode 100644 index 0000000..03760c0 --- /dev/null +++ b/src/types/color/chroma.ts @@ -0,0 +1,255 @@ +// 注释来自 AI +export const chroma = { + textWhiteSpace: "text-white-space", + err: null, + keyword: { + /** 所有关键字 + * @example class function var if else return + */ + self: null, + /** 常量关键字 + * @example true false null + */ + constant: null, + /** 声明关键字 + * @example var let const + */ + declaration: null, + /** 命名空间关键字 + * @example package import export + */ + namespace: null, + /** 伪关键字 + * @example this super __init__ + */ + pseudo: null, + /** 保留关键字 + * @example yield await goto + */ + reserved: null, + /** 类型关键字 + * @example int float string bool + */ + type: null, + }, + name: { + /** 通用标识符 */ + self: null, + /** 属性名 + * @example obj.foo HTML/XML 属性名 id="foo" + */ + attribute: null, + /** 内置函数/对象 + * @example Math.PI Math.max + */ + builtin: null, + /** 内置伪标识符 + * @example this super self + */ + builtinPseudo: null, + /** 类名 */ + class: null, + /** 常量名 */ + constant: null, + /** 装饰器 */ + decorator: null, + /** 实体名 + * @example HTML实体名 < > & + */ + entity: null, + /** 异常类名 */ + exception: null, + /** 函数名 */ + function: null, + /** 魔术方法名 + * @example __init__ __str__ + */ + functionMagic: null, + /** 对象属性 */ + property: null, + /** 标签名 + * @example 跳转标签 + */ + label: null, + /** 命名空间 */ + namespace: null, + /** 其他未归类的标识符 */ + other: null, + /** 标签名 + * @example 跳转标签 + */ + tag: null, + /** 变量名 */ + variable: null, + /** 类变量 */ + variableClass: null, + /** 全局变量 */ + variableGlobal: null, + /** 实例变量 */ + variableInstance: null, + /** 魔术变量 + * @example __name__ __doc__ + */ + variableMagic: null, + }, + literal: { + /** 通用字面量 */ + self: null, + /** 日期字面量 + * @example SQL 日期 + */ + date: null, + }, + string: { + /** 通用字符串 */ + self: null, + /** 字符串前缀/后缀 + * @example f"..." 的 f + */ + affix: null, + /** 反引号字符串 + * @example `string` + */ + backtick: null, + /** 字符字面量 + * @example 'a' + */ + char: null, + /** 字符串分隔符 + * @example 引号自身 + */ + delimiter: null, + /** 文档字符串 + * @example """docstring""" + */ + doc: null, + /** 双引号字符串 + * @example "string" + */ + double: null, + /** 转义字符 + * @example \n \t + */ + escape: null, + /** 定界字符串 + * @example <> + */ + heredoc: null, + /** 插值字符串 + * @example ${name} + */ + interpol: null, + /** 其他类型字符串 */ + other: null, + /** 正则表达式字面量 + * @example /^abc/ + */ + regex: null, + /** 单引号字符串 + * @example 'string' + */ + single: null, + /** 符号字符串 + * @example ruby 的 :symbol + */ + symbol: null, + }, + number: { + /** 通用数字 */ + self: null, + /** 二进制数字 + * @example 0b1010 + */ + bin: null, + /** 浮点数 + * @example 1.23 + */ + float: null, + /** 十六进制数字 + * @example 0x123 + */ + hex: null, + /** 普通整数 + * @example 123 + */ + integer: null, + /** 长整数 + * @example 123L + */ + integerLong: null, + /** 八进制数字 + * @example 0o123 + */ + oct: null, + }, + operator: { + /** 运算符 + * @example + - * / = + */ + self: null, + /** 单词运算符 + * @example and or not in is + */ + word: null, + }, + /** 标点符号 + * @example , . : ; ( ) [ ] { } + */ + punctuation: null, + comment: { + /** 通用注释 */ + self: null, + /** Hashbang 注释 + * @example #!/bin/bash + */ + hashbang: null, + /** 多行注释 */ + multiline: null, + /** 预处理器注释 + * @example #include + */ + preproc: null, + /** 预处理器文件注释 + * @example 如 python 的编码声明 # -*- coding: utf-8 -*- + */ + preprocFile: null, + /** 单行注释 */ + single: null, + /** 特殊注释 + * @example JavaDoc 的 `@param` + */ + special: null, + }, + generic: { + /** 通用文本容器 */ + self: null, + /** 被删除的内容 */ + deleted: null, + /** 强调文本 (斜体) */ + emph: null, + /** 错误信息 */ + error: null, + /** 标题 + * @example Markdown 标题 # + */ + heading: null, + /** 新增内容 */ + inserted: null, + /** 程序输出文本 */ + output: null, + /** 交互式提示符 + * @example shell 的 $ + */ + prompt: null, + /** 强调文本 (粗体) */ + strong: null, + /** 子标题 + * @example Markdown 子标题 ## + */ + subheading: null, + /** 堆栈跟踪信息 */ + traceback: null, + /** 下划线文本 */ + underline: null, + }, +}; diff --git a/src/types/color/console.ts b/src/types/color/console.ts new file mode 100644 index 0000000..16bcc09 --- /dev/null +++ b/src/types/color/console.ts @@ -0,0 +1,49 @@ +const ansiColor = { + /** 黑色 */ + black: null, + /** 红色 */ + red: null, + /** 绿色 */ + green: null, + /** 黄色 */ + yellow: null, + /** 蓝色 */ + blue: null, + /** 品红 */ + magenta: null, + /** 青色 */ + cyan: null, + /** 白色 */ + white: null, +}; + +export const ansi = { + /** 亮色 */ + bright: ansiColor, + ...ansiColor, +}; + +export const console = { + /** Action 页面日志部分字体颜色 */ + fg: { + /** 亮色用于标题或步骤标题激活时 */ + self: null, + /** 暗色用于副标题或步骤标题 */ + subtle: null, + }, + /** Action 页面日志部分背景色 */ + bg: null, + /** Action 页面日志部分边框色 */ + border: null, + /** Action 页面日志部分步骤标题激活颜色 */ + activeBg: "color-console-active-bg", + /** Action 页面日志部分步骤标题悬停颜色 */ + hoverBg: "color-console-hover-bg", + /** Action 页面日志部分设置菜单颜色 */ + menu: { + /** 菜单背景色 */ + bg: null, + /** 菜单边框色 */ + border: null, + }, +}; diff --git a/src/types/color/diff.ts b/src/types/color/diff.ts new file mode 100644 index 0000000..6966c68 --- /dev/null +++ b/src/types/color/diff.ts @@ -0,0 +1,30 @@ +const row = { + bg: null, + border: null, +}; + +const line = { + /** 行号 */ + linenum: { + bg: null, + }, + /** 代码行 */ + row: row, + /** 代码 */ + word: { + bg: null, + }, +}; + +export const diff = { + /** 添加 */ + added: line, + /** 移动 */ + moved: { + row: row, + }, + /** 删除 */ + removed: line, + /** 对比空白部分背景色 */ + inactive: null, +}; diff --git a/src/types/color/github.ts b/src/types/color/github.ts new file mode 100644 index 0000000..305ebc7 --- /dev/null +++ b/src/types/color/github.ts @@ -0,0 +1,253 @@ +export const github = { + /** 用于 color 属性的颜色 */ + fgColor: { + /** 强调色 + * @actions `actionViewRight` 右侧日志标题颜色 + * @issue `prBranch` 分支名称文本颜色 + * @repo `repoTopic` 仓库主题标签文本颜色 + * @actions `actions` 分支标签按钮文本颜色 + * @actions `actionViewHeader` 分支标签按钮文本颜色 + */ + accent: null, + /** 成功的文本颜色 + * @issue `button` 重新开启按钮文本颜色 + * @label `label` 绿色标签的文本颜色 + */ + success: null, + /** 完成的文本颜色 + * @issue `button` 关闭工单按钮文本颜色 + * @svg `issueClosed` 工单已关闭图标颜色 + */ + done: null, + }, + /** 用于 background 属性的颜色 */ + bgColor: { + accent: { + /** 强调色 + * @diff 折叠/展开按钮的悬停颜色 + * @release `releaseTagMenu` 顶部栏左侧按钮激活背景色 + * @navbar `navbarRight` 头像管理员标识背景颜色 + * @dropdown `dropdown` emoji 的悬停背景色 + * @menu `paginationMenu` 分页菜单的激活背景色 + */ + emphasis: null, + /** 暗淡的背景颜色 + * @issue `prBranch` 分支名称背景颜色 + * @repo `repoTopic` 仓库主题标签背景颜色 + * @actions `actions` 分支标签按钮背景颜色 + * @actions `actionViewHeader` 分支标签按钮背景颜色 + * @notification `notification` 通知列表悬停时的背景颜色 + */ + muted: null, + }, + success: { + /** 成功的背景颜色 + * @issue `babel` 重新开启图标背景颜色 + * @issue `prMerge` 合并提交的图标背景色 + */ + emphasis: null, + }, + done: { + /** 完成的背景颜色 + * @issue `babel` 工单已关闭图标背景颜色 + */ + emphasis: null, + }, + }, + borderColor: { + accent: { + /** 强调色 + * @input `input` 输入框被选中时的边框颜色 + * @clone `clone` 克隆地址框被选中时的边框颜色 + * @issue `comment` 评论框被选中时的边框颜色 + * @actions `actionViewLeft` 左侧子作业激活伪元素颜色 + * @menu `verticalMenu` 垂直菜单项激活时左侧的伪元素颜色 + * @dropdown `selectionDropdown` 选择输入框的内部边框颜色 + * @notification `notification` 通知列表悬停时的左边框颜色 + */ + emphasis: null, + }, + attention: { + /** 注意的边框颜色 + * @label `label` 黄色/橙色标签的边框色 + */ + emphasis: null, + }, + success: { + /** 成功的边框颜色 + * @label `label` 绿色标签的边框色 + */ + emphasis: null, + }, + done: { + /** 完成的边框颜色 + * @label `label` 红色/紫色标签的边框色 + */ + emphasis: null, + }, + }, + button: { + /** 普通按钮 */ + default: { + bgColor: { + /** 静止色 + * @button `baseButton` 默认按钮激活颜色 + */ + active: null, + }, + }, + /** 主色调按钮 */ + primary: { + fgColor: { + /** 静止色 + * @button `primaryStyle` `primaryHoverStyle` 按钮文本颜色 + * @setting `tinyHoverStyle` 按钮的悬停文本颜色 + */ + rest: null, + /** 强调色 (Github 没有此颜色, 为本主题自定义, 需自行设置) + * @setting `tinyStyle` 按钮的文本颜色 + */ + accent: null, + }, + bgColor: { + /** 静止色 + * @button `primaryStyle` 按钮颜色 + */ + rest: null, + /** 悬停色 + * @button `primaryHoverStyle` 按钮悬停颜色 + * @setting `tinyHoverStyle` 按钮的悬停背景颜色 + */ + hover: null, + }, + borderColor: { + /** 静止色 + * @button `primaryStyle` 按钮边框颜色 + */ + rest: null, + /** 悬停色 + * @button `primaryHoverStyle` 按钮悬停边框颜色 + * @setting `tinyHoverStyle` 按钮的悬停边框颜色 + */ + hover: null, + }, + }, + danger: { + fgColor: { + /** 静止色 + * @button `redButton` 红色按钮文本颜色 + */ + rest: null, + /** 悬停色 + * @button `redButton` 红色按钮悬停文本颜色 + */ + hover: null, + }, + bgColor: { + /** 静止色 + * @button `redButton` 红色按钮颜色 + */ + rest: null, + /** 悬停色 + * @button `redButton` 红色按钮悬停颜色 + */ + hover: null, + }, + borderColor: { + /** 悬停色 + * @button `redButton` 红色按钮悬停边框颜色 + */ + hover: null, + }, + }, + star: { + /** 已标星的星星颜色 */ + iconColor: null, + }, + }, + control: { + bgColor: { + /** 背景色 + * @issue `timeline` 时间线标签背景色 + */ + rest: null, + }, + transparent: { + bgColor: { + /** 悬停色 + * @clone `clone` 克隆按钮下按钮组和面板操作列表的悬停背景颜色 + * @input `inputAction` 输入框动作按钮的悬停背景颜色 + * @dropdown `dropdown` 下拉框子项的悬停背景颜色 + * @menu `verticalMenu` 垂直菜单项的悬停背景颜色 + * @menu `menu` 菜单项的悬停背景颜色 + * @menu `secondaryMenu` 二级菜单按钮的悬停背景颜色 + * @repo `repoHeader` 仓库标题的悬停背景颜色 + * @commit `commit` 提交信息的 Action 按钮的悬停背景颜色 + * @filelist `repoFiles` README 栏的按钮的悬停背景颜色 + * @issue `issueSidebar` 操作按钮的悬停背景颜色 + * @issue `issueList` 头部菜单左侧开启关闭菜单的悬停背景颜色 + * @dashboard `dashboard` 仓库列表项目的悬停背景颜色 + * @notification `notification` 通知列表的按钮悬停背景颜色 + */ + hover: null, + }, + }, + }, + shadow: { + floating: { + /** 悬浮阴影 + * @tippy `tippyBox` 悬浮框阴影 + * @dropdown `dropdown` 下拉框阴影 + * @dashboard `dashboard` 仓库/组织切换按钮和列表边框和阴影 + * @heatmap `heatmap` 热力图阴影 + * @heatmap `activity` 动态活动阴影 + */ + small: null, + }, + resting: { + /** 静止阴影 + * @button `primaryStyle` 主色调按钮阴影 + * @button `redButton` 红色按钮悬浮阴影 + * @setting `button` 红色按钮阴影 + */ + small: null, + }, + }, + underlineNav: { + borderColor: { + /** 下划线导航栏的边框颜色 + * @clone `clone` 按钮组的按钮下划线颜色 + * @menu `secondaryMenu` 二级菜单按钮的下划线颜色 + * @filelist `repoFiles` README 栏的左边按钮下划线颜色 + */ + active: null, + }, + }, + /** 热力图 */ + contribution: { + default: { + /** 热力图方块的颜色 */ + bgColor: { + num0: null, + num1: null, + num2: null, + num3: null, + num4: null, + /** github 无此颜色需自行计算 */ + num5: null, + }, + /** 热力图方块的内边框颜色 */ + borderColor: { + num0: null, + num1: null, + num2: null, + num3: null, + num4: null, + /** github 无此颜色需自行计算 + * @example 目前均取 num0 的值 + */ + num5: null, + }, + }, + }, +}; diff --git a/src/types/color/index.ts b/src/types/color/index.ts new file mode 100644 index 0000000..430f6df --- /dev/null +++ b/src/types/color/index.ts @@ -0,0 +1,8 @@ +export { chroma } from "./chroma"; +export { ansi, console } from "./console"; +export { diff } from "./diff"; +export { github } from "./github"; +export { primary, secondary } from "./main"; +export { message } from "./message"; +export { named } from "./named"; +export { other, otherAuto } from "./other"; diff --git a/src/types/color/main.ts b/src/types/color/main.ts new file mode 100644 index 0000000..5bb40c1 --- /dev/null +++ b/src/types/color/main.ts @@ -0,0 +1,25 @@ +const num = { num1: null, num2: null, num3: null, num4: null, num5: null, num6: null, num7: null }; + +const alpha = { + num10: null, + num20: null, + num30: null, + num40: null, + num50: null, + num60: null, + num70: null, + num80: null, + num90: null, +}; + +export const primary = { self: null, contrast: null, dark: num, light: num, alpha: alpha, hover: null, active: null }; + +export const secondary = { + self: null, + dark: { num8: null, num9: null, num10: null, num11: null, num12: null, num13: null, ...num }, + light: { num1: null, num2: null, num3: null, num4: null }, + alpha: alpha, + button: null, + hover: null, + active: null, +}; diff --git a/src/types/color/message.ts b/src/types/color/message.ts new file mode 100644 index 0000000..9d4b502 --- /dev/null +++ b/src/types/color/message.ts @@ -0,0 +1,8 @@ +const msg = { bg: null, border: null, text: null }; + +const error = { ...msg, bg: { self: null, active: null, hover: null } }; +const success = { ...msg }; +const warning = { ...msg }; +const info = { ...msg }; + +export const message = { error, success, warning, info }; diff --git a/src/types/color/named.ts b/src/types/color/named.ts new file mode 100644 index 0000000..dac81b7 --- /dev/null +++ b/src/types/color/named.ts @@ -0,0 +1,37 @@ +const baseColor = { self: null, light: null, dark: { num1: null, num2: null } }; + +const commitColor = { + /** 提交哈希值颜色 */ + badge: { + /** 边框色 */ + self: null, + /** 背景色 */ + bg: null, + /** 悬停时背景色 */ + hover: { + bg: null, + }, + }, +}; + +export const named = { + /** 红色/提交警告签名颜色 */ + red: { ...commitColor, ...baseColor }, + /** 橙色/提交未匹配签名颜色 */ + orange: { ...commitColor, ...baseColor }, + /** 黄色/提交未信任签名颜色 */ + yellow: { ...commitColor, ...baseColor }, + olive: baseColor, + /** 绿色/提交信任签名颜色 */ + green: { ...commitColor, ...baseColor }, + teal: baseColor, + blue: baseColor, + violet: baseColor, + purple: baseColor, + pink: baseColor, + brown: baseColor, + black: baseColor, + grey: { self: null, light: null }, + gold: null, + white: null, +}; diff --git a/src/types/color/other.ts b/src/types/color/other.ts new file mode 100644 index 0000000..62d8f67 --- /dev/null +++ b/src/types/color/other.ts @@ -0,0 +1,154 @@ +export const otherAuto = { + /** 未知 */ + git: null, + light: { + /** 不知道什么用, gitea css 中没有使用这个属性的 */ + mimicEnabled: "color-light-mimic-enabled", + }, +}; + +export const other = { + /** 主要背景色 */ + body: null, + /** 页面底部状态栏背景色 */ + footer: null, + /** Issue 等页面时间线的线颜色 */ + timeline: null, + /** 一些盒子颜色, 比如仓库文件列表 */ + box: { + /** 仓库文件列表最后一次提交, 头行背景色 */ + header: null, + body: { + /** 仓库文件列表背景色 */ + self: null, + /** diff 按钮行行色 */ + highlight: null, + }, + }, + /** 文本 */ + text: { + /** 主文本/主标题颜色 */ + self: null, + light: { + /** 普通basic按钮的文本颜色 */ + self: null, + /** 仓库文件列表的commit信息和时间文本 */ + num1: null, + /** 副标题颜色 */ + num2: null, + /** git 提交图里的提交时间文本 */ + num3: null, + }, + /** 弹窗标题色/一些激活的标题色 */ + dark: null, + }, + /** 输入框 */ + input: { + /** 选中时的文字颜色 */ + text: null, + background: null, + /** 找不到, 不知道啥玩意, 似乎是和复选框有关的东西 */ + toggleBackgound: "color-input-toggle-background", + border: { + self: null, + hover: null, + }, + }, + light: { + /** 多行下交替行的强调色, 例提交历史 */ + self: null, + /** 基础按钮/标签的边框色 */ + border: null, + }, + hover: { + /** 按钮悬停背景色 */ + self: null, + /** 仓库文件列表悬停背景色 */ + opaque: null, + }, + /** 设置页面菜单项当前项的背景色 */ + active: null, + /** 菜单背景色 */ + menu: null, + /** 卡片背景色, 但是找不到元素, 可能是个人 README */ + card: null, + /** Markdown 颜色 */ + markup: { + /** 隔行背景色 */ + tableRow: "color-markup-table-row", + code: { + /** 代码块背景色 */ + block: null, + /** 代码行背景色 */ + inline: null, + }, + }, + /** 普通按钮的背景色 (basic 非 primary) */ + button: null, + /** 代码页面背景色 */ + codeBg: "color-code-bg", + /** 弹窗阴影 */ + shadow: { + self: null, + /** css 没有使用 */ + opaque: null, + }, + /** 弹窗按钮行的背景色 */ + secondaryBg: "color-secondary-bg", + /** 代码差异对比折叠行按钮背景色 */ + expandButton: "color-expand-button", + /** 不知道 */ + placeholderText: "color-placeholder-text", + /** 不知道, css 没有 */ + editorLineHighlight: "color-editor-line-highlight", + /** 仓库项目页面列的背景色 */ + projectColumnBg: "color-project-column-bg", + /** caret-color 属性 */ + caret: null, + /** Issue 表情按钮 */ + reaction: { + /** css 里没用 */ + bg: null, + /** 悬停时颜色 */ + hoverBg: "color-reaction-hover-bg", + /** 点击后颜色 */ + activeBg: "color-reaction-active-bg", + }, + /** 鼠标悬浮时的提示文本, 比如提交的具体时间, 任务状态等 */ + tooltip: { + text: null, + bg: null, + }, + /** 顶部导航栏(用户导航栏) */ + nav: { + /** 背景色 */ + bg: null, + /** 悬停时背景色 */ + hoverBg: "color-nav-hover-bg", + /** color 颜色 */ + text: null, + }, + /** 顶部二级导航栏背景色(仓库导航栏等) */ + secondaryNavBg: "color-secondary-nav-bg", + /** 普通标签 */ + label: { + text: null, + bg: null, + hoverBg: "color-label-hover-bg", + /** css 没用 */ + activeBg: "color-label-active-bg", + }, + /** 不知道. 似乎和最后一次 review 相关的边框色 */ + accent: null, + /** 不知道. 似乎和最后一次 review 相关的背景色 */ + smallAccent: "color-small-accent", + /** 不知道啥玩意, 跟文件预览内容行颜色有关系 */ + highlight: { + /** 在行号前追加的伪元素颜色 */ + fg: null, + /** 背景色 */ + bg: null, + }, + /** 叠加背景色, 比如弹窗时遮蔽页面其他部分的背景色 */ + overlayBackdrop: "color-overlay-backdrop", +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..6f43f00 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,23 @@ +import type { MapLeafNodes } from "src/core/types"; +import * as color from "./color"; + +/** 代码高亮色 */ +export type Chroma = MapLeafNodes; +/** 主色调(强调色) */ +export type Primary = MapLeafNodes; +/** 副色调(边框色) */ +export type Secondary = MapLeafNodes; +/** 基础颜色 */ +export type Named = MapLeafNodes; +/** 提示消息 */ +export type Message = MapLeafNodes; +/** Actions 日志 ANSI 颜色 */ +export type Ansi = MapLeafNodes; +/** Actions 颜色 */ +export type Console = MapLeafNodes; +/** 代码差异对比颜色 */ +export type Diff = MapLeafNodes; +/** 其他颜色 */ +export type Other = MapLeafNodes; +/** 仅限本主题的 Github 颜色 */ +export type Github = MapLeafNodes; diff --git a/src/types/vars.ts b/src/types/vars.ts new file mode 100644 index 0000000..3110b49 --- /dev/null +++ b/src/types/vars.ts @@ -0,0 +1,53 @@ +import { createGlobalThemeContract } from "@vanilla-extract/css"; +import * as color from "./color"; + +function varMapper(prefix: string | null = null) { + return (value: string | null, path: string[]) => { + if (value === null) { + path = path.filter(item => item !== "self"); + path = path.map(item => item.replace(/^num/, "")); + value = path.join("-"); + } + if (prefix) value = `${prefix}-${value}`; + return value; + }; +} + +const vars = { + isDarkTheme: "is-dark-theme", + chroma: color.chroma, + color: { + ...color.other, + ...color.message, + ...color.named, + primary: color.primary, + secondary: color.secondary, + /** Actions 日志 ANSI 颜色 */ + ansi: color.ansi, + console: color.console, + diff: color.diff, + }, + github: color.github, +}; + +const otherVars = { border: { radius: null }, color: { ...color.otherAuto } }; + +const customVars = { + branchMenuWidth: "branch-menu-width", + cloneMenuWidth: "clone-menu-width", + userMenuWidth: "user-menu-width", + explore: { repolistColumns: "explore-repolist-columns", userlistColumns: "explore-userlist-columns" }, + userRepolistColumns: "user-repolist-columns", + org: { repolistColumns: "org-repolist-columns", userlistColumns: "org-userlist-columns" }, +}; + +const themeInfo = { + version: null, +}; + +export const themeVars = createGlobalThemeContract(vars, varMapper()); +export const otherThemeVars = createGlobalThemeContract(otherVars, varMapper()); +export const customThemeVars = createGlobalThemeContract(customVars, varMapper("custom")); +export const themeInfoVars = createGlobalThemeContract(themeInfo, varMapper("theme")); + +export { css } from "@linaria/core"; diff --git a/styles/components/actions.ts b/styles/components/actions.ts new file mode 100644 index 0000000..bea1619 --- /dev/null +++ b/styles/components/actions.ts @@ -0,0 +1,267 @@ +import { animationDown } from "src/core/theme"; +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 仓库 Actions 页面 +export const actions = css` + .page-content.repository.actions .ui.grid { + .four.wide { + border-right: 1px solid ${themeVars.color.light.border}; + min-height: calc(-104px + 100vh); + + &:before { + content: "Actions"; + display: block; + font-size: 20px; + font-weight: 600; + margin-top: 6px; + margin-bottom: 24px; + } + } + + .twelve.wide { + &:before { + content: "Workflow Runs"; + display: block; + font-size: 20px; + margin-top: 6px; + margin-bottom: 24px; + margin-left: 2px; + } + // 工作流列表标题栏菜单 + .ui.secondary.menu { + background-color: ${themeVars.color.box.header}; + border: 1px solid ${themeVars.color.light.border}; + border-bottom: 0; + border-top-left-radius: ${otherThemeVars.border.radius}; + border-top-right-radius: ${otherThemeVars.border.radius}; + padding: 16px; + margin-bottom: 0; + > .item { + color: ${themeVars.color.text.light.num1}; + } + } + + .ui.info.message { + border-radius: 0; + border-left-color: ${themeVars.color.light.border}; + border-right-color: ${themeVars.color.light.border}; + margin: 0; + + ~ .run-list { + border-top: 0; + } + } + + .run-list { + border: 1px solid ${themeVars.color.light.border}; + border-bottom-left-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + + // 分支标签按钮 + .run-list-ref { + background-color: ${themeVars.github.bgColor.accent.muted}; + color: ${themeVars.github.fgColor.accent}; + font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace); + font-weight: 400; + &:hover { + background-color: ${themeVars.github.bgColor.accent.muted}; + color: ${themeVars.github.fgColor.accent}; + text-decoration-line: underline !important; + } + } + // 标签右侧任务信息 + .run-list-item-right { + color: ${themeVars.color.text.light.num1}; + } + + .flex-item { + padding: 16px; + + .flex-item-leading { + align-self: flex-start; + margin-top: 2px; + } + + .flex-item-main { + gap: 0.5rem; + } + + .flex-item-trailing { + justify-content: space-between; + + > .ui.label { + border-radius: ${otherThemeVars.border.radius}; + margin-left: 32px; + } + } + } + } + } + } +`; + +// 避免手机下左侧工作流项空白过长 +export const actionsMedia = css` + @media (max-width: 767.98px) { + .page-content.repository.actions .ui.grid .four.wide { + min-height: auto; + } + } +`; + +// 工作流禁用标签 +export const label = css` + .ui.vertical.menu .item > .ui.red.label { + color: ${themeVars.color.error.text}; + border: 1px solid ${themeVars.color.error.border}; + background: ${themeVars.color.error.bg.self}; + margin-top: 0.5px; + } +`; + +// 手动工作流下拉列表 +export const runWorkflow = css` + #runWorkflowDispatchForm { + // 分支选择按钮 + .ui.dropdown.button.branch-selector-dropdown .svg.octicon-git-branch { + margin-right: 6px; + } + } +`; + +// 工作流详情页标题 +export const actionViewHeader = css` + .action-view-header { + .action-commit-summary { + // 分支标签按钮 + .ui.label { + background-color: ${themeVars.github.bgColor.accent.muted}; + color: ${themeVars.github.fgColor.accent}; + font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace); + font-weight: 400; + > a { + opacity: 1; + } + } + } + } +`; + +// 工作流左侧作业列表 +export const actionViewLeft = css` + .action-view-left { + margin-right: 28px; + border-top: 1px solid ${themeVars.color.console.border}; + + &:before { + content: "Jobs"; + color: ${themeVars.color.console.fg.subtle}; + font-size: 12px; + font-weight: 600; + position: relative; + display: inline-block; + margin-top: 22px; + top: -8px; + left: 15px; + } + + .job-group-section { + padding-right: 8px; + padding-bottom: 8px; + border-bottom: 1px solid ${themeVars.color.console.border}; + + .job-brief-item { + border-radius: ${otherThemeVars.border.radius} !important; + padding: 8px; + position: relative; + margin-left: 0.5rem; + + &.selected { + &:hover { + background-color: ${themeVars.color.console.hoverBg}; + } + + &:after { + overflow: visible; + background: ${themeVars.github.borderColor.accent.emphasis}; + border-radius: ${otherThemeVars.border.radius}; + content: ""; + height: 24px; + left: calc(0.5rem * -1); + position: absolute; + top: calc(50% - 12px); + width: 4px; + } + } + } + } + } +`; + +// 工作流右侧作业步骤日志详情 +export const actionViewRight = css` + .action-view-right { + /* 提前加载高度和滚动条 */ + min-height: calc(100vh - 245px); + + .job-info-header { + padding: 16px 12px 16px 24px; + height: 80px; + + .job-info-header-title { + color: ${themeVars.github.fgColor.accent}; + } + + .job-info-header-detail { + margin-top: 8px; + } + } + + .job-step-container { + // 步骤标题 + .job-step-summary { + color: ${themeVars.color.console.fg.subtle}; + padding: 8px 10px; + + &.selected { + // 滚动时固定在顶部的高度与 job-info-header 高度相同 + top: 80px; + } + + &.step-expandable:hover { + color: ${themeVars.color.console.fg.subtle}; + } + + .tw-mr-2:not(.svg) svg.svg { + margin: 1.5px 6px 0px 2px; + } + /* 绿色步骤状态改为白色 */ + svg.svg.text.green { + color: ${themeVars.color.console.fg.subtle} !important; + } + + &.selected { + /* 不被 hover 效果影响 */ + color: ${themeVars.color.console.fg.self} !important; + background-color: ${themeVars.color.console.activeBg} !important; + + svg.svg.text.green { + color: ${themeVars.color.console.fg.self} !important; + } + } + } + // 步骤日志 + .job-step-logs { + animation: ${animationDown}; + /* 日志字体颜色白色 */ + .job-log-line { + color: ${themeVars.color.console.fg.self}; + /* 被 hover 时覆盖 ANSI 颜色 */ + .log-msg:hover * { + color: ${themeVars.color.console.fg.self} !important; + } + } + } + } + } +`; diff --git a/styles/components/chroma.ts b/styles/components/chroma.ts new file mode 100644 index 0000000..d895980 --- /dev/null +++ b/styles/components/chroma.ts @@ -0,0 +1,286 @@ +import { css, themeVars } from "src/types/vars"; + +// https://github.com/go-gitea/gitea/blob/main/web_src/css/chroma/base.css +export const chromaBase = css` + .chroma { + // LineTableTD + .lntd { + vertical-align: top; + padding: 0; + margin: 0; + border: 0; + } + // LineTable + .lntable { + border-spacing: 0; + padding: 0; + margin: 0; + border: 0; + width: auto; + overflow: auto; + display: block; + } + // LineHighlight + .hl { + display: block; + width: 100%; + } + // LineNumbersTable + .lnt, + // LineNumbers + .ln { + margin-right: 0.4em; + padding: 0 0.4em; + } + // GenericStrong + .gs { + font-weight: var(--font-weight-semibold); + } + // GenericUnderline + .gl { + text-decoration: underline; + } + } +`; + +// https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 +export const chromaCode = css` + .chroma { + // TextWhiteSpace + .w { + color: ${themeVars.chroma.textWhiteSpace}; + } + // Error + .err { + color: ${themeVars.chroma.err}; + } + // Keyword + .k { + color: ${themeVars.chroma.keyword.self}; + } + .kc { + color: ${themeVars.chroma.keyword.constant}; + } + .kd { + color: ${themeVars.chroma.keyword.declaration}; + } + .kn { + color: ${themeVars.chroma.keyword.namespace}; + } + .kp { + color: ${themeVars.chroma.keyword.pseudo}; + } + .kr { + color: ${themeVars.chroma.keyword.reserved}; + } + .kt { + color: ${themeVars.chroma.keyword.type}; + } + // Name + .n { + color: ${themeVars.chroma.name.self}; + } + .na { + color: ${themeVars.chroma.name.attribute}; + } + .nb { + color: ${themeVars.chroma.name.builtin}; + } + .bp { + color: ${themeVars.chroma.name.builtinPseudo}; + } + .nc { + color: ${themeVars.chroma.name.class}; + } + .no { + color: ${themeVars.chroma.name.constant}; + } + .nd { + color: ${themeVars.chroma.name.decorator}; + } + .ni { + color: ${themeVars.chroma.name.entity}; + } + .ne { + color: ${themeVars.chroma.name.exception}; + } + .nf { + color: ${themeVars.chroma.name.function}; + } + .fm { + color: ${themeVars.chroma.name.functionMagic}; + } + .py { + color: ${themeVars.chroma.name.property}; + } + .nl { + color: ${themeVars.chroma.name.label}; + } + .nn { + color: ${themeVars.chroma.name.namespace}; + } + .nx { + color: ${themeVars.chroma.name.other}; + } + .nt { + color: ${themeVars.chroma.name.tag}; + } + .nv { + color: ${themeVars.chroma.name.variable}; + } + .vc { + color: ${themeVars.chroma.name.variableClass}; + } + .vg { + color: ${themeVars.chroma.name.variableGlobal}; + } + .vi { + color: ${themeVars.chroma.name.variableInstance}; + } + .vm { + color: ${themeVars.chroma.name.variableMagic}; + } + // Literal + .l { + color: ${themeVars.chroma.literal.self}; + } + .ld { + color: ${themeVars.chroma.literal.date}; + } + // String + .s { + color: ${themeVars.chroma.string.self}; + } + .sa { + color: ${themeVars.chroma.string.affix}; + } + .sb { + color: ${themeVars.chroma.string.backtick}; + } + .sc { + color: ${themeVars.chroma.string.char}; + } + .dl { + color: ${themeVars.chroma.string.delimiter}; + } + .sd { + color: ${themeVars.chroma.string.doc}; + } + .s2 { + color: ${themeVars.chroma.string.double}; + } + .se { + color: ${themeVars.chroma.string.escape}; + } + .sh { + color: ${themeVars.chroma.string.heredoc}; + } + .si { + color: ${themeVars.chroma.string.interpol}; + } + .sx { + color: ${themeVars.chroma.string.other}; + } + .sr { + color: ${themeVars.chroma.string.regex}; + } + .s1 { + color: ${themeVars.chroma.string.single}; + } + .ss { + color: ${themeVars.chroma.string.symbol}; + } + // Number + .m { + color: ${themeVars.chroma.number.self}; + } + .mb { + color: ${themeVars.chroma.number.bin}; + } + .mf { + color: ${themeVars.chroma.number.float}; + } + .mh { + color: ${themeVars.chroma.number.hex}; + } + .mi { + color: ${themeVars.chroma.number.integer}; + } + .il { + color: ${themeVars.chroma.number.integerLong}; + } + .mo { + color: ${themeVars.chroma.number.oct}; + } + // Operator + .o { + color: ${themeVars.chroma.operator.self}; + } + .ow { + color: ${themeVars.chroma.operator.word}; + } + // Punctuation + .p { + color: ${themeVars.chroma.punctuation}; + } + // Comment + .c { + color: ${themeVars.chroma.comment.self}; + } + .ch { + color: ${themeVars.chroma.comment.hashbang}; + } + .cm { + color: ${themeVars.chroma.comment.multiline}; + } + .cp { + color: ${themeVars.chroma.comment.preproc}; + } + .cpf { + color: ${themeVars.chroma.comment.preprocFile}; + } + .c1 { + color: ${themeVars.chroma.comment.single}; + } + .cs { + color: ${themeVars.chroma.comment.special}; + } + // Generic + .g { + color: ${themeVars.chroma.generic.self}; + } + .gd { + color: ${themeVars.chroma.generic.deleted}; + } + .ge { + color: ${themeVars.chroma.generic.emph}; + } + .gr { + color: ${themeVars.chroma.generic.error}; + } + .gh { + color: ${themeVars.chroma.generic.heading}; + } + .gi { + color: ${themeVars.chroma.generic.inserted}; + } + .go { + color: ${themeVars.chroma.generic.output}; + } + .gp { + color: ${themeVars.chroma.generic.prompt}; + } + .gs { + color: ${themeVars.chroma.generic.strong}; + } + .gu { + color: ${themeVars.chroma.generic.subheading}; + } + .gt { + color: ${themeVars.chroma.generic.traceback}; + } + .gu { + color: ${themeVars.chroma.generic.underline}; + } + } +`; diff --git a/styles/components/clone.ts b/styles/components/clone.ts new file mode 100644 index 0000000..41147a7 --- /dev/null +++ b/styles/components/clone.ts @@ -0,0 +1,110 @@ +import { css, customThemeVars, otherThemeVars, themeVars } from "src/types/vars"; + +// 克隆按钮的弹窗 +export const clone = css` + .tippy-box { + .clone-panel-popup { + //标题和克隆地址 + > .clone-panel-field { + margin-left: 16px; + margin-right: 16px; + //标题 + &:first-of-type { + font-weight: 600; + margin-top: 16px; + } + } + // 按钮组 + .clone-panel-tab { + display: flex; + gap: 8px; + margin-left: 16px; + // 按钮 + .item { + color: ${themeVars.color.text.self}; + font-weight: 600; + padding: 6px 8px; + margin: 8px 0; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + border-radius: ${otherThemeVars.border.radius}; + } + &.active:after { + content: ""; + display: block; + position: absolute; + bottom: -8px; + left: 0; + width: 100%; + height: 2px; + background: ${themeVars.github.underlineNav.borderColor.active}; + border-radius: ${otherThemeVars.border.radius}; + } + } + .item.active { + border-bottom: 0; + position: relative; + } + } + // 克隆地址 + .ui.input.action { + > input { + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace); + min-width: 150px; + max-width: 400px; + width: ${customThemeVars.cloneMenuWidth}; + &:hover { + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + } + &:focus, + &:focus-visible { + border-color: ${themeVars.github.borderColor.accent.emphasis}; + } + } + .ui.ui.ui.button { + background-color: unset; + border: unset; + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.light.num1}; + padding: 0; + width: 32px; + height: 32px; + margin-left: 5px; + &:hover { + background-color: ${themeVars.github.control.transparent.bgColor.hover}; + } + svg { + width: 16px; + height: 16px; + } + } + } + // 面板操作列表之间的分割线 + .divider { + margin: 0; + } + // 面板操作列表 + .clone-panel-list { + margin: 8px; + .item { + border-radius: ${otherThemeVars.border.radius}; + padding: 6px 8px; + line-height: 1.5; + margin: 0; + svg { + color: ${themeVars.color.text.light.num1}; + margin-right: 0.25rem; + } + } + > .item:hover { + color: ${themeVars.color.text.self}; + text-decoration: none; + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + } + } +`; diff --git a/styles/components/commit.ts b/styles/components/commit.ts new file mode 100644 index 0000000..9d00fb1 --- /dev/null +++ b/styles/components/commit.ts @@ -0,0 +1,77 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 提交列表 +export const commit = css` + .page-content.repository { + // 提交列表 (选择器保证同等优先级覆盖了 gitea 原生的样式) + #commits-table.ui.basic.striped.table tbody.commit-list { + // 作者 + .author { + // 作者名称 + .author-wrapper { + color: ${themeVars.color.text.light.num1}; + } + } + // SHA 标签 + .sha { + a.ui.label.commit-id-short { + padding: 2px 8px; + height: 28px; + margin-top: 0.375rem; + margin-bottom: 0.375rem; + margin-left: -8px; + } + } + // 提交信息 + .message { + // tag 标签 + a.ui.basic.primary.label { + border-radius: 25px; + border-width: 1.5px; + padding: 5px 8px !important; + } + } + // 提交信息右侧 + .tw-text-right { + // 时间标签 + relative-time, + // 复制 SHA 按钮 + .btn.copy-commit-id, + // 查看提交路径按钮 + .btn.view-commit-path { + color: ${themeVars.color.text.light.num1}; + } + } + tr { + // 整行悬停色 + &:hover { + background-color: ${themeVars.color.hover.opaque}; + } + // 偶数行悬停色 + &:nth-child(2n):hover { + background-color: ${themeVars.color.hover.opaque} !important; + } + // 尾行圆角 + &:last-child { + td:first-child { + border-bottom-left-radius: ${otherThemeVars.border.radius}; + } + td:last-child { + border-bottom-right-radius: ${otherThemeVars.border.radius}; + } + } + } + } + } +`; + +export const commitStatus = css` + .flex-text-inline[data-global-init="initCommitStatuses"] { + padding: 6px; + margin-top: 2px; + border-radius: ${otherThemeVars.border.radius}; + &:hover { + background-color: ${themeVars.github.control.transparent.bgColor.hover}; + } + } +`; diff --git a/styles/components/dashboard.ts b/styles/components/dashboard.ts new file mode 100644 index 0000000..6251179 --- /dev/null +++ b/styles/components/dashboard.ts @@ -0,0 +1,144 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +export const dashboard = css` + // 首页仪表板, 避免选中管理员后台的维护管理面板 + .page-content.dashboard.feeds { + // 仓库列表的仓库/组织切换按钮 + .ui.two.item.menu { + background: ${themeVars.color.hover.self}; + border: 0; + border-radius: 12px; + margin-bottom: 8px; + > .item { + background: unset; + border-radius: 12px; + padding: 6px 12px !important; + &.active { + background: ${themeVars.color.menu}; + box-shadow: ${themeVars.github.shadow.floating.small}; + font-weight: 600; + } + &::before { + display: none; + } + &:not(.active) { + border-radius: ${otherThemeVars.border.radius}; + margin: 6px !important; + width: calc(50% - 12px); + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + } + } + // 仓库/组织列表标题 + .ui.top.attached.header { + border: 0; + font-size: 20px; + font-weight: 400; + background-color: unset !important; + margin-bottom: 0.25rem; + } + // 仓库/组织列表 + .ui.attached.segment { + background-color: ${themeVars.color.menu}; + border: unset !important; + box-shadow: ${themeVars.github.shadow.floating.small}; + &.repos-search { + border-top-left-radius: 12px; + border-top-right-radius: 12px; + } + &.table { + &:last-child { + border-bottom-left-radius: 12px !important; + border-bottom-right-radius: 12px !important; + } + ul { + padding: 8px; + li { + border-radius: ${otherThemeVars.border.radius}; + padding: 6px 8px !important; + &:not(:last-child) { + border-bottom: 0; + } + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + a.muted:hover { + color: inherit; + text-decoration-line: none; + } + } + } + } + } + // 组织列表 + .ui.tab.dashboard-orgs .ui.attached.segment.table { + border-top-left-radius: 12px; + border-top-right-radius: 12px; + } + } +`; + +// 导航栏的工单/PR/里程碑仪表板 +export const dashboardIssues = css` + .page-content.dashboard.issues { + .list-header { + background-color: ${themeVars.color.box.header}; + border: 1px solid ${themeVars.color.light.border}; + border-bottom: 0; + border-top-left-radius: ${otherThemeVars.border.radius}; + border-top-right-radius: ${otherThemeVars.border.radius}; + height: 54px; + padding: 16px 8px; + .list-header-toggle { + align-items: center; + border: 0; + > .item { + background: unset !important; + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.light.num1}; + padding: 0px 8px; + height: 30px; + &:before { + display: none; + } + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + &.active { + color: ${themeVars.color.text.self}; + font-weight: 700; + } + } + } + .list-header-filters { + > .item { + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.light.num1}; + padding: 0px 12px; + height: 32px; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + } + } + } +`; + +// 避免手机/平板下菜单错位 +export const issueListMobile = css` + @media (max-width: 767.98px) { + .page-content.dashboard.issues .list-header { + height: auto; + } + } +`; + +// 修复仪表板下二级面板选择菜单组织的标签间隔 +export const fixOrgLabel = css` + .dashboard .secondary-nav .org-visibility .label { + margin-right: 0; + } +`; diff --git a/styles/components/diff.ts b/styles/components/diff.ts new file mode 100644 index 0000000..bbd2865 --- /dev/null +++ b/styles/components/diff.ts @@ -0,0 +1,72 @@ +import { css, themeVars } from "src/types/vars"; + +export const diff = css` + /* 折叠行多余的颜色 */ + .tag-code { + background-color: unset; + /* 折叠行文本 */ + .code-inner { + color: ${themeVars.color.text.light.num1}; + } + } + /* 增加/删除行多余的颜色 */ + .code-diff-unified { + .del-code, + .add-code { + background: unset; + border-color: unset; + } + } + /* 增加/删除相关代码背景色圆角 */ + .added-code, + .removed-code { + border-radius: 0.1875rem; + color: ${themeVars.color.text.self}; + /* 覆盖掉 chroma 的颜色 */ + * { + color: ${themeVars.color.text.self} !important; + } + } + /* 展开/收缩按钮 */ + .code-expander-button { + color: ${themeVars.color.text.light.num1}; + height: 28px !important; + &:hover { + background: ${themeVars.github.bgColor.accent.emphasis}; + color: ${themeVars.color.white}; + } + } + /* 行号居中 */ + .lines-num { + text-align: center !important; + } + // 差异对比文件盒子 + .diff-file-box { + // 差异对比文件头 + .diff-file-header { + // 文件名 + .diff-file-name { + font-weight: 400; + .fold-file.btn svg { + min-width: 16px; + min-height: 16px; + height: 16px; + width: 16px; + } + .diff-stats-bar { + height: 8px; + } + > div, + .file-link { + font-size: 12px; + } + } + // 操作按钮 + .diff-file-header-actions { + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + font-weight: 400; + } + } + } +`; diff --git a/styles/components/editor.ts b/styles/components/editor.ts new file mode 100644 index 0000000..45412ab --- /dev/null +++ b/styles/components/editor.ts @@ -0,0 +1,14 @@ +import { css, themeVars } from "src/types/vars"; + +export const monaco = css` + .monaco-editor { + --vscode-editor-background: ${themeVars.color.body} !important; + --vscode-editorGutter-background: ${themeVars.color.body} !important; + // 滚动时固定在顶部的行 + .sticky-widget { + background: ${themeVars.color.body} !important; + box-shadow: 0 1px 0 ${themeVars.color.secondary.self} !important; + width: 100% !important; + } + } +`; diff --git a/styles/components/explore.ts b/styles/components/explore.ts new file mode 100644 index 0000000..9fb0350 --- /dev/null +++ b/styles/components/explore.ts @@ -0,0 +1,159 @@ +import { fallbackVar } from "src/functions"; +import { css, customThemeVars, otherThemeVars, themeVars } from "src/types/vars"; + +const userRepoVar = fallbackVar(customThemeVars.userRepolistColumns, "2"); +const exploreRepoVar = fallbackVar(customThemeVars.explore.repolistColumns, "2"); +const orgRepoVar = fallbackVar(customThemeVars.org.repolistColumns, "1"); + +// 仓库列表 +export const repoList = css` + // 组织 + .page-content.organization.profile > .ui.container > .ui.stackable > .ui.eleven, + // 用户 + .page-content.user.profile > .ui.container > .ui.stackable > .ui.twelve, + // 探索 + .page-content.explore.repositories > .ui.container { + // 排除用户的公开活动页 + > .flex-list:not(#activity-feed) { + display: grid; + > .flex-item { + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + padding: 16px; + // 仓库头像 + > .flex-item-leading { + img, + svg { + color: ${themeVars.color.text.light.num1}; + } + } + // 仓库信息 + > .flex-item-main { + // 仓库标题 + > .flex-item-header { + // 仓库名称 + > .flex-item-title { + gap: 8px; + // 仓库中间的间隔线 + &:not(a) { + color: ${themeVars.color.text.light.num1}; + } + } + // 仓库语言, 星标 + > .flex-item-trailing { + color: ${themeVars.color.text.light.num1}; + gap: 16px; + font-size: 12px; + > .flex-text-inline .color-icon { + width: 12px; + height: 12px; + margin-right: 0 !important; + } + } + } + // 描述和更新时间 + > .flex-item-body { + margin-top: 8px; + // 更新时间 + &:last-child { + font-size: 12px; + } + } + // 主题标签 + > .label-list { + margin-top: 8px; + } + } + } + } + } + // 仓库列表列数 + // 组织 + .page-content.organization.profile > .ui.container > .ui.stackable > .ui.eleven > .flex-list { + grid-template-columns: repeat(${orgRepoVar}, 1fr); + gap: min(${orgRepoVar} * 8px, 16px); + } + // 用户 + // 排除用户的公开活动页 + .page-content.user.profile > .ui.container > .ui.stackable > .ui.twelve > .flex-list:not(#activity-feed) { + grid-template-columns: repeat(${userRepoVar}, 1fr); + gap: min(${userRepoVar} * 8px, 16px); + } + // 探索 + .page-content.explore.repositories > .ui.container > .flex-list { + grid-template-columns: repeat(${exploreRepoVar}, 1fr); + gap: min(${exploreRepoVar} * 8px, 16px); + } +`; + +const exploreUserVar = fallbackVar(customThemeVars.explore.userlistColumns, "3"); +const orgUserVar = fallbackVar(customThemeVars.org.userlistColumns, "2"); + +// 用户列表 +export const userList = css` + // 组织 + .page-content.organization.members > .ui.container, + // 探索的用户和组织 + .page-content.explore.users > .ui.container { + > .flex-list { + display: grid; + > .flex-item { + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + padding: 16px; + > .flex-item-main { + // 用户名称 + > .flex-item-title { + gap: 8px; + margin-bottom: 8px; + // 用户标签 + > .label { + font-size: 12px; + } + } + // 用户描述 + > .flex-item-body { + font-size: 12px; + svg { + width: 12px; + min-width: 12px; + } + } + } + } + } + } + // 用户列表列数 + // 组织 + .page-content.organization.members > .ui.container > .flex-list { + grid-template-columns: repeat(${orgUserVar}, 1fr); + gap: min(${orgUserVar} * 8px, 16px); + } + // 探索的用户和组织 + .page-content.explore.users > .ui.container > .flex-list { + grid-template-columns: repeat(${exploreUserVar}, 1fr); + gap: min(${exploreUserVar} * 8px, 16px); + } +`; + +// 手机下的仓库和用户列表 +export const mobileList = css` + @media (max-width: 767.98px) { + // 组织的仓库列表 + .page-content.organization.profile > .ui.container > .ui.stackable > .ui.eleven, + // 用户的仓库列表 + .page-content.user.profile > .ui.container > .ui.stackable > .ui.twelve, + // 探索的仓库列表 + .page-content.explore.repositories > .ui.container, + // 组织的成员列表 + .page-content.organization.members >.ui.container, + // 探索的用户和组织列表 + .page-content.explore.users >.ui.container { + // 排除用户的公开活动页 + > .flex-list:not(#activity-feed) { + grid-template-columns: 1fr; + gap: 8px; + } + } + } +`; diff --git a/styles/components/filelist.ts b/styles/components/filelist.ts new file mode 100644 index 0000000..0f405f3 --- /dev/null +++ b/styles/components/filelist.ts @@ -0,0 +1,467 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 文件列表页面下的分支按钮 +export const branchButton = css` + .page-content.repository.file.list { + .ui.dropdown.branch-selector-dropdown > .menu > .menu { + // 显示默认分支的标签 + .ui.label { + background-color: ${themeVars.color.menu}; + border: 1px solid ${themeVars.color.light.border}; + margin-top: 1px; + margin-left: auto; + margin-right: 20px; // gitea 有 RSS 留出足够的空间 + } + } + } +`; + +// 仓库同步派生 +export const syncFork = css` + .page-content.repository.file.list { + .repo-home-filelist > .ui.message { + background: ${themeVars.color.box.header}; + padding: 8px 8px 8px 16px; + margin: 16px 0px; + .ui.button { + min-width: 96px; + } + } + } +`; + +// 仓库文件列表 +export const repoFiles = css` + // 文件列表和提交列表的按钮组 + .repo-button-row { + margin: 16px 0; + } + .repository.file.list { + #repo-files-table { + margin: 16px 0; + // 头部最后一次提交 + .repo-file-line { + padding-right: 16px; + // 父目录 + &.parent-link { + gap: 0.5rem; + padding-left: 16px; + svg { + margin-right: 2px; + } + } + // 最后一次提交 + &.repo-file-last-commit { + min-height: 3.725rem; + .latest-commit { + gap: 8px; + .commit-summary { + color: ${themeVars.color.text.light.num1}; + } + img.tw-align-middle { + margin-top: -1px; + margin-left: 1px; + } + // 作者 + .author-wrapper { + &:hover { + color: ${themeVars.color.text.self}; + } + } + // 提交哈希值 + .ui.label { + display: none; + } + } + relative-time { + color: ${themeVars.color.text.light.num1}; + } + } + } + // 文件列表 + .repo-file-item { + .repo-file-cell { + height: 40px; + &.name { + display: flex; + align-items: center; + gap: 0.5rem; + padding-left: 16px; + } + &.age { + padding-right: 16px; + } + } + } + } + #readme { + .file-header { + background: ${themeVars.color.body}; + min-height: 48px; + padding: 0px 8px !important; + svg { + color: ${themeVars.color.text.light.num1}; + } + .file-header-left { + padding: 8px !important; + // 伪元素宽度等于按钮宽度而不是父元素宽度 + position: relative; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + border-radius: ${otherThemeVars.border.radius}; + } + &:after { + content: ""; + background: ${themeVars.github.underlineNav.borderColor.active}; + border-radius: ${otherThemeVars.border.radius}; + bottom: -7px; + left: 0; + height: 2px; + position: absolute; + width: 100%; + } + a.muted:hover { + color: inherit; + text-decoration-line: none; + } + } + .file-header-right:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + border-radius: ${otherThemeVars.border.radius}; + } + } + } + } +`; + +// 手机下隐藏提交信息 +export const repoFilesMobile = css` + @media (max-width: 767.98px) { + #repo-files-table { + grid-template-columns: 1fr auto; + .repo-file-line { + grid-column: 1 / span 2; + } + .repo-file-cell { + &.name { + max-width: none; + } + &.message { + display: none; + } + } + } + } +`; + +// 仓库打开文件时的视图 +export const repoFileView = css` + // 隐藏主内容的下内容和页脚, 避免滚动文件树时滚动条遮挡 + body > .full.height:has(.repo-view-file-tree-container) { + padding-bottom: 0; + + .page-footer { + display: none; + } + } + .page-content.repository.file.list { + &:has(.repo-view-file-tree-container) { + // 取消下间隔优化观看体验 + .secondary-nav { + margin-bottom: 0 !important; + .ui.tabs.divider { + margin-bottom: 0; + } + } + } + > .ui.container.fluid { + max-width: calc(100% - calc(2 * 16px)); + } + .repo-view-container { + position: sticky; + top: 0; + // 左侧文件树 + .repo-view-file-tree-container { + height: calc(100vh - 64px); // 减去头部高度 + // 固定头部 + position: sticky; + top: 0; + &:after { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 1px; + height: calc(100% + 64px); // 头部高度 + background: ${themeVars.color.secondary.self}; + } + > .repo-button-row { + align-content: center; + background: ${themeVars.color.body}; + font-size: 16px; + height: 64px; + margin: 0; + // 固定头部, 早期父元素有多余的页脚和内容高度导致滚动时无法固定, 修复后也可保留此属性无需删除 + position: sticky; + top: 0; + &:after { + content: ""; + position: absolute; + top: 64px; + left: -16px; + width: calc(100% + 16px); + height: 1px; + background: ${themeVars.color.secondary.self}; + } + .ui.compact.icon.button { + border: 0; + } + } + .view-file-tree-items { + margin-right: 0; + padding: 12px 16px 8px 0; + } + } + // 右侧文件视图内容 + .repo-view-content { + padding-bottom: 40px; + .repo-button-row { + align-content: center; + background: ${themeVars.color.box.header}; + border: 1px solid ${themeVars.color.secondary.self}; + border-radius: ${otherThemeVars.border.radius}; + margin: 16px 0; + height: 46px; + min-height: 46px; + padding: 8px; + position: sticky; + top: 0; + z-index: 1; + .ui.button { + min-height: 32px; + } + // 打开文件树按钮 + .repo-view-file-tree-toggle-show { + background: initial; + border-color: #0000; + padding: 0; + min-width: 32px; + } + // 分支选择按钮 + .branch-dropdown-button { + padding: 0 12px; + } + // 路径 + .repo-path { + gap: 4px; + .section { + &:first-child, + &.active { + font-weight: 600; + } + } + .btn { + svg { + margin-left: 4px; + color: ${themeVars.color.text.light.num1}; + } + } + } + } + .non-diff-file-content { + // 避免分支菜单遮挡 + position: relative; + z-index: 0; + h4.file-header { + padding: 8px 12px !important; + position: sticky; + // 重叠边框线, 避免过粗 + top: 45px; + z-index: 1; + .file-header-left { + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + } + .file-header-right { + // 按钮组 + > .ui.buttons { + margin: 0 8px 0 0 !important; // 完全不知道为什么浏览器最终效果没生效, 只能 !important 了 + .ui.mini.button { + min-height: 28px; + height: 28px; + font-size: 12px; + padding: 0 8px; + } + } + // 右侧操作按钮 + > .btn-octicon { + display: flex; + align-items: center; + background: ${themeVars.color.button}; + border: 1px solid ${themeVars.color.light.border}; + height: 28px; + padding: 0 8px; + svg { + color: ${themeVars.color.text.light.num1}; + } + &:first-of-type { + border-top-left-radius: ${otherThemeVars.border.radius}; + border-bottom-left-radius: ${otherThemeVars.border.radius}; + } + &:last-of-type { + border-top-right-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + } + &:hover { + background: ${themeVars.color.hover.self}; + color: ${themeVars.color.text.light.num1}; + } + } + } + } + } + // 头部提交信息 + .ui.segment#repo-file-commit-box { + padding: 8px 12px; + margin-bottom: 16px !important; + min-height: 46px; + > .latest-commit { + gap: 8px; + .commit-summary { + color: ${themeVars.color.text.light.num1}; + } + } + // 右侧提交时间 + > .age { + display: flex; + align-items: center; + } + } + } + } + } +`; + +// 避免手机/平板下路径容器过长导致换行, 取消固定 +export const repoFileViewMobile = css` + @media (max-width: 1023.98px) { + .page-content.repository.file.list .repo-view-container .repo-view-content { + .repo-button-row { + height: auto; + position: static; + z-index: 0; + } + .non-diff-file-content { + h4.file-header { + top: 0; + } + } + } + } +`; + +// 仓库代码布局调整, 侧边栏宽度调整 +export const repoGrid = css` + .repo-grid-filelist-sidebar { + grid-template-columns: auto 312px; + gap: 24px; + } + + @media (max-width: 767.98px) { + .repo-grid-filelist-sidebar { + grid-template-columns: 100%; + } + } +`; + +// 仓库代码页面侧边栏上半部, 版本发布以上 +export const repoSidebarTop = css` + .page-content.repository.file.list { + .repo-home-sidebar-top { + a.muted:hover { + text-decoration: none; + } + .flex-item { + padding: 10px 0 0 0; + // 仓库描述本身 + .flex-item-title { + margin-top: 12px; + } + // 仓库描述内容 + .flex-item-body { + > .tw-flex:first-child { + margin-top: 21px !important; + } + .repo-description { + color: ${themeVars.color.text.self}; + } + #repo-topics { + margin: 10px 0px !important; + } + .flex-text-block { + font-size: 14px; + font-weight: 600; + margin-top: 10px; + &.muted { + margin-top: 2px; + font-weight: normal; + } + svg.svg { + margin-right: 4px; + &.octicon-database, + &.octicon-law { + margin-right: 6px; + } + } + } + } + } + } + } +`; + +// 仓库代码页面侧边栏下半部, 版本发布以下 +export const repoSidebarBottom = css` + .page-content.repository.file.list { + .repo-home-sidebar-bottom { + a.muted:hover { + text-decoration: none; + } + .flex-item { + padding: 16px 0; + .flex-item { + padding: 16px 0 0 0; + .flex-item-icon { + svg.svg.octicon-tag { + color: ${themeVars.color.green.self}; + margin-top: 2px; + } + } + .flex-item-header .flex-item-title { + font-size: 14px; + } + } + .flex-item-body { + font-size: 12px; + } + } + // 编程语言 + .language-stats { + height: 8px; + margin-bottom: 0px; + } + .language-stats-details .item { + font-size: 12px; + margin-right: 8px; + .color-icon { + height: 8px; + width: 8px; + margin-right: 8px; + } + .tw-font-semibold { + color: ${themeVars.color.text.self}; + margin-right: 2px; + } + } + } + } +`; diff --git a/styles/components/footer.ts b/styles/components/footer.ts new file mode 100644 index 0000000..1301b36 --- /dev/null +++ b/styles/components/footer.ts @@ -0,0 +1,43 @@ +import { css, themeVars } from "src/types/vars"; + +export const footer = css` + .page-footer { + background-color: ${themeVars.color.body}; + border-top: 0; + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + gap: 32px; + justify-content: center; + padding: 16px 0 40px 0; + > .left-links { + gap: 4px; + > a { + color: ${themeVars.color.text.light.num1}; + &:hover { + color: ${themeVars.color.primary.self}; + } + } + > strong { + font-weight: 400; + } + } + > .right-links { + gap: 16px; + > .ui.dropdown { + font-size: 12px; + &:hover { + color: ${themeVars.color.primary.self}; + } + } + > a { + border-left: 0; + color: ${themeVars.color.text.light.num1}; + padding-left: 0; + margin-left: 0; + &:hover { + color: ${themeVars.color.primary.self}; + } + } + } + } +`; diff --git a/styles/components/heatmap.ts b/styles/components/heatmap.ts new file mode 100644 index 0000000..750a3de --- /dev/null +++ b/styles/components/heatmap.ts @@ -0,0 +1,100 @@ +import { css, themeVars } from "src/types/vars"; + +export const heatmap = css` + #user-heatmap { + + .divider:not(.divider-text) { + border-color: #0000; + margin: 8px 0px; + } + .total-contributions { + left: 25px + 20px; + bottom: 0 + 12px; + } + .total-contributions, + .vch__legend-right { + color: ${themeVars.color.text.light.num1}; + } + .vch__container { + padding: 12px 20px; + box-shadow: ${themeVars.github.shadow.floating.small}; + border-radius: 12px; + // 覆盖热力图和图例的背景色 + .vch__day__square, + .vch__legend__wrapper rect { + // 圆角 + rx: 2.5px; + ry: 2.5px; + // hover 时的圆角 + border-radius: 0.75px; + // 宽度和高度可以用来控制间隔 + width: 9px; + height: 9px; + // 边框 + outline: 0.5px solid ${themeVars.github.contribution.default.borderColor.num0}; + // 边框向内偏移 + outline-offset: -0.5px; + + &[style="fill: var(--color-secondary-alpha-60);"] { + fill: ${themeVars.github.contribution.default.bgColor.num0} !important; + } + + &[style="fill: var(--color-primary-light-4);"] { + fill: ${themeVars.github.contribution.default.bgColor.num1} !important; + outline-color: ${themeVars.github.contribution.default.borderColor.num1}; + } + + &[style="fill: var(--color-primary-light-2);"] { + fill: ${themeVars.github.contribution.default.bgColor.num2} !important; + outline-color: ${themeVars.github.contribution.default.borderColor.num2}; + } + + &[style="fill: var(--color-primary);"] { + fill: ${themeVars.github.contribution.default.bgColor.num3} !important; + outline-color: ${themeVars.github.contribution.default.borderColor.num3}; + } + + &[style="fill: var(--color-primary-dark-2);"] { + fill: ${themeVars.github.contribution.default.bgColor.num4} !important; + outline-color: ${themeVars.github.contribution.default.borderColor.num4}; + } + + &[style="fill: var(--color-primary-dark-4);"] { + fill: ${themeVars.github.contribution.default.bgColor.num5} !important; + outline-color: ${themeVars.github.contribution.default.borderColor.num5}; + } + } + } + } +`; + +// 动态 +export const activity = css` + .flex-list#activity-feed { + border-radius: 12px; + box-shadow: ${themeVars.github.shadow.floating.small}; + > .flex-item { + gap: 12px; + padding: 12px 8px 16px 14px; + > .flex-item-main { + gap: 8px !important; + > div:not([class]) { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + relative-time { + color: ${themeVars.color.text.light.num1}; + } + } + // 动态的右侧 svg 图标 + .flex-item-trailing svg { + height: 20px; + width: 20px; + } + } + > .page.buttons { + border-top: 1px solid ${themeVars.color.secondary.self}; + padding: 12px 0px; + } + } +`; diff --git a/styles/components/index.ts b/styles/components/index.ts new file mode 100644 index 0000000..c14951d --- /dev/null +++ b/styles/components/index.ts @@ -0,0 +1,23 @@ +import "./actions"; +import "./chroma"; +import "./clone"; +import "./commit"; +import "./dashboard"; +import "./diff"; +import "./editor"; +import "./explore"; +import "./filelist"; +import "./footer"; +import "./heatmap"; +import "./issue"; +import "./milestones"; +import "./navbar"; +import "./newrepo"; +import "./notification"; +import "./org"; +import "./packages"; +import "./release"; +import "./repo"; +import "./setting"; +import "./signin"; +import "./user"; diff --git a/styles/components/issue.ts b/styles/components/issue.ts new file mode 100644 index 0000000..bce6b14 --- /dev/null +++ b/styles/components/issue.ts @@ -0,0 +1,657 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; +import { activeItemAfterStyle } from "styles/public/menu"; + +// 工单&PR 列表 +export const issueList = css` + // 仓库页面的里程碑列表菜单栏 + .page-content.repository.milestones, + .page-content.repository.milestone-issue-list, + .page-content.repository.issue-list { + // 头部筛选菜单栏 + .issue-list-toolbar { + align-items: center; + align-content: center; + background-color: ${themeVars.color.box.header}; + border: 1px solid ${themeVars.color.light.border}; + border-bottom: 0; + border-top-left-radius: ${otherThemeVars.border.radius}; + border-top-right-radius: ${otherThemeVars.border.radius}; + height: 48px; + padding: 8px; + margin-top: 16px; + .issue-list-toolbar-left { + // 复选框 + input { + margin: 0 8px !important; + } + > .ui.compact.menu { + align-items: center; + border: 0; + > .item { + background: unset !important; + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.light.num1}; + padding: 0px 8px; + height: 30px; + &:before { + display: none; + } + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + &.active { + color: ${themeVars.color.text.self}; + font-weight: 700; + } + } + } + } + .issue-list-toolbar-right > .ui.menu { + align-items: center; + > .item { + color: ${themeVars.color.text.light.num1}; + } + > .ui.button { + padding: 0 12px; + height: 32px; + } + } + } + } + // 里程碑详细页面的 Issue 列表 + .page-content.repository.milestone-issue-list, + // 顶部仪表板的 Issue 列表 + .page-content.dashboard.issues, + // 用户订阅的 Issue 列表 + .page-content.user.notification, + // 仓库 Issue 列表 + .page-content.repository.issue-list { + .flex-list#issue-list { + border: 1px solid ${themeVars.color.light.border}; + border-bottom-left-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + > .flex-item { + align-items: center; + padding: 0; + &:last-child { + border-bottom-left-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + } + &:hover { + background-color: ${themeVars.color.hover.opaque}; + } + > .flex-item-icon { + display: flex; + gap: 4px; + margin-left: 16px; + // 复选框 + input { + background: unset; + margin-top: 14px; + margin-right: 8px !important; + } + svg { + margin-top: 14px; + } + } + > .flex-item-main { + gap: 4px; + .flex-item-header { + padding-top: 8px; + } + .flex-item-body { + font-size: 12px; + padding-bottom: 8px; + } + } + > .flex-item-trailing { + margin-right: 32px; + } + } + } + } + // 里程碑列表 + // [TODO] 暂时排除项目的列表 + .page-content.repository.milestones:not(.projects) .milestone-list { + border: 1px solid ${themeVars.color.light.border}; + border-bottom-left-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + .milestone-card { + padding: 8px 16px 10px 16px; + .milestone-header { + h3 { + font-size: 16px; + font-weight: 500; + } + div span { + font-size: 14px; + font-weight: 600; + } + } + } + .milestone-toolbar { + font-size: 12px; + .group > a { + font-size: 13px; + } + } + } +`; + +// 避免手机/平板下菜单错位 +export const issueListMobile = css` + @media (max-width: 1023.98px) { + .page-content.repository.milestones, + .page-content.repository.milestone-issue-list, + .page-content.repository.issue-list { + .issue-list-toolbar { + height: auto; + } + } + } +`; + +// 置顶 Issue +export const issuePins = css` + #issue-pins { + gap: 12px; + margin-bottom: 16px; + .issue-card { + padding: 16px 12px; + .content { + .issue-card-title { + font-size: 16px; + font-weight: 600; + } + svg { + color: ${themeVars.color.text.light.num1}; + height: 100%; + margin-right: 3px; + } + .meta { + font-size: 12px; + padding-top: 4px; + } + } + .issue-card-bottom { + display: none; + } + } + } +`; + +export const button = css` + .issue-content-left .field.footer { + // 关闭工单按钮 + .ui.red.basic.button#status-button { + color: ${themeVars.github.fgColor.done}; + background-color: ${themeVars.color.button}; + border-color: ${themeVars.color.light.border}; + &:hover { + background-color: ${themeVars.color.hover.self}; + } + } + + // 重新开启按钮 + .ui.basic.primary.button#status-button { + color: ${themeVars.github.fgColor.success}; + } + } + // 工单&PR标题右侧按钮 + .repository.view.issue .issue-title-buttons > .ui.button { + padding: 0 12px; + height: 32px; + } +`; + +export const babel = css` + .issue-content-left { + .badge { + // 时间线打开状态标签 + &.tw-bg-green { + background-color: ${themeVars.github.bgColor.success.emphasis} !important; + } + // 时间线关闭状态标签 + &.tw-bg-red { + background-color: ${themeVars.github.bgColor.done.emphasis} !important; + } + // 时间线合并状态标签 + &.tw-bg-purple { + background-color: ${themeVars.github.bgColor.done.emphasis} !important; + } + } + } + // 工单&PR状态标签 + .ui.label.issue-state-label { + border-radius: 25px !important; + + &.green { + color: ${themeVars.color.white} !important; + background-color: ${themeVars.github.bgColor.success.emphasis} !important; + border-color: ${themeVars.github.bgColor.success.emphasis} !important; + } + + &.red { + color: ${themeVars.color.white} !important; + background-color: ${themeVars.github.bgColor.done.emphasis} !important; + border-color: ${themeVars.github.bgColor.done.emphasis} !important; + } + + &.purple { + color: ${themeVars.color.white} !important; + background-color: ${themeVars.github.bgColor.done.emphasis} !important; + border-color: ${themeVars.github.bgColor.done.emphasis} !important; + } + } +`; + +// PR 分支标签 +export const prBranch = css` + .repository.view.issue .pull-desc code, + #issue-list .flex-item-body .branches .branch { + color: ${themeVars.github.fgColor.accent}; + background-color: ${themeVars.github.bgColor.accent.muted}; + border-radius: ${otherThemeVars.border.radius}; + font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace); + font-size: 12px; + padding: 0 5px; + line-height: 20px; + } + + .repository.view.issue .pull-desc code { + padding-top: 3px; + padding-bottom: 3px; + a:hover { + text-decoration-line: none; + } + } +`; + +// 评论 +export const comment = css` + .comment .comment-container { + // 去除评论标题左侧指向头像的小箭头 + .comment-header, + &:target .comment-header { + &:before, + &:after { + display: none; + } + } + // 评论焦点样式 + &:target { + .comment-container { + border-color: ${themeVars.github.borderColor.accent.emphasis} !important; + box-shadow: 0 0 0 1px ${themeVars.color.primary.self} !important; + } + } + .comment-header { + padding: 4px 4px 4px 16px; + min-height: 38px; + } + .comment-header-right { + > .item, + > .label { + color: ${themeVars.color.text.light.num1}; + } + > .ui.label { + background-color: initial; + font-size: 12px; + height: 20px; + padding: 0 6px; + } + // 隐藏顶部菜单的表情按钮 + // 无法使用此样式, 评论无表情时底部的表情按钮元素不会渲染, 这是一个先有鸡还是先有蛋的问题 + // 很蛋疼, 希望 Gitea 早日使用 Github 的样式, 因为 Github 的更合理, 无论是操作的方便程度还是按钮的冗余度 + // .ui.dropdown.action.select-reaction { + // display: none; + // } + .context-dropdown { + a.context-menu { + display: flex; + align-items: center; + } + // 评论菜单的删除按钮 + .menu .item.delete-comment { + color: ${themeVars.color.red.self}; + &:hover { + background-color: ${themeVars.color.red.badge.bg} !important; + color: ${themeVars.color.red.light}; + } + } + } + } + // 表情菜单按钮头部+底部 + .ui.dropdown.action.select-reaction > a { + display: flex; + align-items: center; + justify-content: center; + background: ${themeVars.color.button}; + border-radius: 25px; + border: 1px solid ${themeVars.color.light.border}; + color: ${themeVars.color.text.light.num1}; + padding: 0px 8px !important; + height: 28px; + width: 28px; + } + // 底部表情栏 + .bottom-reactions { + .ui.ui.ui.label { + background-color: unset !important; + border-radius: 25px; + border-color: ${themeVars.color.light.border}; + &:hover { + background-color: ${themeVars.color.reaction.hoverBg} !important; + border-color: ${themeVars.color.light.border}; + } + .reaction { + font-size: 12px; + } + .reaction-count { + color: ${themeVars.color.text.light.self}; + font-weight: 500; + margin-left: 0; + } + } + // 显示表情菜单按钮 + .select-reaction { + padding: 0; + // 两个表情按钮看着怪怪的, 很难受 + // visibility: visible; + } + } + } +`; + +// 评论书写框 +export const commentForm = css` + .repository .comment.form .content .segment { + &::before, + &::after { + display: none; + } + } +`; + +export const dropdown = css` + .repository { + // Issue/PR 列表下的所有筛选菜单 + &.issue-list .ui.dropdown .menu, .ui.menu .ui.dropdown .menu, + // Issue/PR 详情下的右侧的上半部分选项菜单 + &.issue.view .issue-content-right .ui.dropdown .scrolling.menu { + .item:hover:after { + content: ""; + ${activeItemAfterStyle} + } + } + } +`; + +// PR 界面的 PR 操作评论 +export const prMerge = css` + .repository.view.issue .comment-list .timeline-item.pull-merge-box { + // 头像 + .timeline-avatar { + color: ${themeVars.color.white} !important; + border-radius: ${otherThemeVars.border.radius}; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + svg { + width: 24px; + height: 24px; + } + // 可以合并 + &.green { + background-color: ${themeVars.github.bgColor.success.emphasis}; + // 操作评论边框色 + + .content > .ui.attached.segment { + border-left-color: ${themeVars.github.bgColor.success.emphasis}; + border-right-color: ${themeVars.github.bgColor.success.emphasis}; + &:first-child { + border-top-color: ${themeVars.github.bgColor.success.emphasis}; + } + &:last-child { + border-bottom-color: ${themeVars.github.bgColor.success.emphasis}; + } + } + } + // 已合并 + &.purple { + background-color: ${themeVars.github.bgColor.done.emphasis}; + + .content > .ui.attached.segment { + border-left-color: ${themeVars.github.bgColor.done.emphasis}; + border-right-color: ${themeVars.github.bgColor.done.emphasis}; + &:first-child { + border-top-color: ${themeVars.github.bgColor.done.emphasis}; + } + &:last-child { + border-bottom-color: ${themeVars.github.bgColor.done.emphasis}; + } + } + } + } + // 检查状态 + .commit-status-panel { + .commit-status-header { + background: ${themeVars.color.body}; + padding: 16px; + font-size: 16px; + font-weight: 600; + .ui.right { + color: ${themeVars.color.text.light.num1}; + font-size: 14px; + font-weight: 400; + } + } + // 检查状态列表 + .commit-status-list { + background: ${themeVars.color.menu}; + .commit-status-item { + border-radius: ${otherThemeVars.border.radius}; + padding: 2px 8px; + margin: 0px 8px; + height: 37px; + &:first-child { + margin-top: 8px; + } + &:last-child { + margin-bottom: 8px; + } + &:hover { + background-color: ${themeVars.color.hover.opaque}; + } + } + } + } + // 合并信息和操作 + .merge-section { + color: ${themeVars.color.text.light.num1}; + padding: 16px; + display: grid; + gap: 8px; + &.no-header { + &::before, + &::after { + display: none; + } + } + } + } +`; + +// 时间线 +export const timeline = css` + .repository.view.issue { + .comment-list { + // 时间线本线 + .timeline::before { + // 不遮挡归档信息框, 归档信息框背景色有透明度时会漏出线 + height: calc(100% - 62px); + } + .timeline-item, + .timeline-item-group { + padding: 16px 0; + // 事件 + &.event { + // 修复覆盖后的位置问题 + padding-left: 15px; + .avatar { + width: 20px; + height: 20px; + } + .badge { + border: 2px solid ${themeVars.color.body}; + } + // 仅匹配只有 badge + .badge:not([class*=" "]) { + background-color: ${themeVars.github.control.bgColor.rest}; + svg { + color: ${themeVars.color.text.light.num1}; + } + } + } + // 提交 + &.commits-list { + // 每个提交之间的间隔 + .flex-text-block { + padding-top: 4px; + } + .badge svg { + color: ${themeVars.color.text.light.num1}; + } + // 仅覆盖左侧 commit 不覆盖右侧 SHA + a.muted { + font-size: 12px; + color: ${themeVars.color.text.light.num1}; + text-decoration-line: underline; + &:hover { + color: ${themeVars.color.primary.self}; + } + } + } + } + } + } +`; + +const sidebarPadding = { + padding: "4px 8px", +}; + +// 侧边栏 +export const issueSidebar = css` + // 工单&创建工单&PR页面侧边栏 + .page-content.repository.issue { + .issue-content { + gap: 24px; + .issue-content-right { + border: 0; + font-size: 12px; + padding: 0; + .ui.button { + font-size: 12px; + } + .ui.form, + a.fixed-text.muted, + span.text, + // 列表项为空时的文字 + span.item.empty-list, + p { + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + } + .ui.dropdown.select-branch, + .ui.form, + a.fixed-text.muted, + span.text, + .ui.watching > div, + .ui.depending > div, + .flex-text-block, + .ui.list, + .toggle-wip, + p { + ${sidebarPadding}; + } + // 允许维护者编辑 + > .ui.checkbox { + margin: 4px 8px; + strong { + font-weight: 400; + } + } + .issue-sidebar-combo { + .ui.dropdown > a.fixed-text.muted { + align-items: center; + border-radius: ${otherThemeVars.border.radius}; + text-decoration-line: none; + height: 28px; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + .ui.list { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + } + // 时间追踪 + > div:not([class]):not([id]) > .ui.dropdown.jump > a.fixed-text.muted { + align-items: center; + border-radius: ${otherThemeVars.border.radius}; + text-decoration-line: none; + height: 28px; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + // 选中日期颜色 + .ui.form .due-date { + color: ${themeVars.color.text.self}; + } + .divider { + margin: 12px 0 12px 8px; + width: calc(100% - 16px); + } + // 订阅按钮 + .ui.watching .ui.button { + padding: 0px 8px; + height: 28px; + svg { + margin: 0 !important; + } + } + // PIN 按钮 + .form-fetch-action.single-button-form .ui.button, + // 底部操作按钮 + .ui.show-modal.button { + border: 0; + background: unset; + font-weight: 400; + ${sidebarPadding}; + // 好像是浏览器 BUG, 最后不生效, 必须 !important + margin: 0 !important; + justify-content: left; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + .ui.show-modal.button[data-modal="#sidebar-delete-issue"] { + color: ${themeVars.color.red.self}; + svg { + color: ${themeVars.color.red.self}; + } + &:hover { + background-color: ${themeVars.color.red.badge.bg}; + color: ${themeVars.color.red.light}; + svg { + color: ${themeVars.color.red.light}; + } + } + } + } + } + } +`; diff --git a/styles/components/milestones.ts b/styles/components/milestones.ts new file mode 100644 index 0000000..c4ea603 --- /dev/null +++ b/styles/components/milestones.ts @@ -0,0 +1,59 @@ +import { css, themeVars } from "src/types/vars"; + +export const milestone = css` + // 里程碑头部 + .milestone-header { + gap: 16px; + // 进度条 + progress { + height: 5px; + width: 300px; + max-width: 80vw; + } + } + // 里程碑 Issue 列表的进度条 + .milestone-progress-big { + height: 8px; + } + // 里程碑 Issue 列表 + .page-content.repository.milestone-issue-list { + > .ui.container { + > .flex-text-block:first-child { + margin-bottom: 16px; + > h1 { + font-size: 32px; + font-weight: 600; + line-height: 48px; + word-break: keep-all; + } + + .tw-flex { + flex-direction: row !important; + align-items: center; + gap: 8px !important; + padding-top: 8px; + padding-bottom: 10px; + font-size: 14px; + color: ${themeVars.color.text.light.num1}; + strong { + color: ${themeVars.color.text.self}; + } + > .flex-text-block { + gap: 8px !important; + } + } + } + > .divider { + border-top-color: #0000; + } + } + } +`; + +// 避免手机/平板下菜单错位 +export const milestoneMobile = css` + @media (max-width: 767.98px) { + .page-content.repository.milestone-issue-list > .ui.container > .flex-text-block:first-child + .tw-flex { + flex-direction: column !important; + } + } +`; diff --git a/styles/components/navbar.ts b/styles/components/navbar.ts new file mode 100644 index 0000000..6209b17 --- /dev/null +++ b/styles/components/navbar.ts @@ -0,0 +1,173 @@ +import { fallbackVar } from "src/functions"; +import { css, customThemeVars, otherThemeVars, themeVars } from "src/types/vars"; + +export const navbarRight = css` + #navbar { + border-bottom: 0; + padding: 0px 16px; + min-height: 64px; + .navbar-left { + gap: 8px; + > .item { + padding: 4px 8px; + min-height: 20px; + &.active { + font-weight: 600; + } + &#navbar-logo { + // 与下方的用户切换头像对齐 + padding-left: 6px; + &:hover { + background: unset; + } + img { + height: 32px; + width: 32px; + } + } + } + } + // 进入用户页面后, 避免注册, 登录和首页等意外覆盖 + .navbar-right:has(.user-menu) { + gap: 8px; + // 右侧按钮, 但不包括头像 + > .item:not(:last-child) { + display: grid; + gap: 4px; + grid-auto-columns: max-content; + align-items: center; + align-content: center; + justify-content: center; + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + padding: unset; + height: 32px; + min-width: 32px; + min-height: 32px; + // 纠正内容保证居中 + .tw-relative { + height: 16px; + width: 16px; + } + .svg { + color: ${themeVars.color.text.light.num1}; + } + // 带下拉菜单的按钮 + &.ui.dropdown { + padding: 0 8px; + .text { + display: grid; + grid-auto-flow: column; + align-items: center; + > svg { + margin-right: 4px; + } + // 三角号纠正高度保持居中 + .not-mobile { + height: 16px; + } + } + &:hover { + background-color: ${themeVars.color.nav.hoverBg}; + } + } + } + .item.ui.dropdown { + // 头像菜单 + &:last-child { + padding-left: 2px; // 调整此选项需同步增减相同的标识的 left + padding-right: 16px; + .text { + // 不显示小箭头标识 + > .not-mobile { + display: none; + } + // 头像 + img { + border-radius: 25px; + height: 32px; + max-height: 32px; + } + } + } + // 用户头像的管理员标识 + .navbar-profile-admin { + background-color: ${themeVars.github.bgColor.accent.emphasis}; + border-radius: 25px; + border: 2px solid ${themeVars.color.nav.bg}; + color: ${themeVars.color.white}; + font-size: 9px; + font-weight: 600; + padding: 2px 5px; + top: -7px; + left: 21px; + } + } + // 通知和计时器的圆点 + a.item { + .notification_count, + .header-stopwatch-dot { + background-color: ${themeVars.github.bgColor.accent.emphasis}; + border-radius: 25px; + color: ${themeVars.color.white}; + font-size: 9px; + font-weight: 600; + top: -15px; + left: 11px; + } + } + } + // 用户菜单 + .navbar-right .user-menu { + width: ${fallbackVar(customThemeVars.userMenuWidth, "192px")}; + max-width: 320px; + > .header { + font-size: 14px; + font-weight: 400; + margin: 0; + padding: 16px 16px 8px 16px; + strong { + font-weight: 600; + } + } + > .divider { + margin: 8px; + width: calc(100% - 16px); + } + } + } + // 手机下的创建菜单按钮 + @media (max-width: 767.98px) { + #navbar .navbar-right:has(.user-menu) > .item:not(:last-child) { + display: none; + } + #navbar.navbar-menu-open .navbar-right:has(.user-menu) > .item:not(:last-child) { + display: grid; + } + } +`; + +// 二级导航栏 +export const secondaryNav = css` + .page-content > :first-child.secondary-nav { + margin-bottom: 16px; + // 仪表板界面的二级导航栏用户菜单 + > .ui.secondary.stackable.menu { + gap: 0px; + min-height: 48px; + > .item { + > .ui.dropdown > .text { + display: inline-flex; + align-items: center; + gap: 4px; + svg { + margin-right: 4px; + } + } + } + > .right.menu { + gap: 4px; + } + } + } +`; diff --git a/styles/components/newrepo.ts b/styles/components/newrepo.ts new file mode 100644 index 0000000..8ed02aa --- /dev/null +++ b/styles/components/newrepo.ts @@ -0,0 +1,14 @@ +import { css } from "src/types/vars"; +import { activeItemAfterStyle } from "styles/public/menu"; + +// 新建仓库页面下拉菜单 +export const newRepo = css` + .page-content.repository.new-repo { + .ui.dropdown .menu { + .item:hover:after { + content: ""; + ${activeItemAfterStyle} + } + } + } +`; diff --git a/styles/components/notification.ts b/styles/components/notification.ts new file mode 100644 index 0000000..918be74 --- /dev/null +++ b/styles/components/notification.ts @@ -0,0 +1,182 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 用户订阅/关注页面 +export const notification = css` + .page-content.user.notification { + > .ui.container { + > .ui.attached.segment { + border: 0; + padding: 0; + > .divider { + display: none; + } + // 订阅列表 + &:has(#issue-list) { + > .tw-flex { + align-items: center; + align-content: center; + background-color: ${themeVars.color.box.header}; + border: 1px solid ${themeVars.color.light.border}; + border-bottom: 0; + border-top-left-radius: ${otherThemeVars.border.radius}; + border-top-right-radius: ${otherThemeVars.border.radius}; + height: 52px; + padding: 8px; + // 左侧菜单 + > .tw-flex:first-child > .ui.compact.menu { + align-items: center; + border: 0; + > .item { + background: unset !important; + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.light.num1}; + padding: 0px 8px; + height: 30px; + &:before { + display: none; + } + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + &.active { + color: ${themeVars.color.text.self}; + font-weight: 700; + } + } + } + // 右侧菜单 + > .tw-flex:last-child > .ui.menu { + align-items: center; + > .item { + color: ${themeVars.color.text.light.num1}; + } + > .ui.button { + padding: 0 12px; + height: 32px; + } + } + } + } + // 关注列表 + > .flex-list:not([id]) { + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + > .flex-item { + padding: 16px; + > .flex-item-main { + gap: 4px; + > .flex-item-header { + > .flex-item-title { + gap: 12px; + } + > .flex-item-trailing { + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + font-weight: 400; + gap: 16px; + .color-icon { + width: 12px; + height: 12px; + margin-right: 0 !important; + } + } + } + > .flex-item-body:last-child { + font-size: 12px; + } + } + } + } + } + // 通知列表 + &:has(#notification_table) { + > .tw-flex:first-child { + background-color: ${themeVars.color.box.header}; + border: 1px solid ${themeVars.color.light.border}; + border-bottom: 0; + border-top-left-radius: ${otherThemeVars.border.radius}; + border-top-right-radius: ${otherThemeVars.border.radius}; + height: 52px; + padding: 8px; + margin-bottom: 0 !important; + // 左侧菜单 + > .ui.compact.menu { + align-items: center; + border: 0; + > .item { + background: unset !important; + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.light.num1}; + padding: 0px 8px; + height: 30px; + &:before { + display: none; + } + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + &.active { + color: ${themeVars.color.text.self}; + font-weight: 700; + } + .notifications-unread-count { + margin-left: 4px; + } + } + } + } + // 通知全部确认按钮 + .ui.ui.ui.ui.mini.button { + height: 32px; + } + } + // 通知列表 + #notification_table { + border-top-left-radius: 0; + border-top-right-radius: 0; + color: ${themeVars.color.text.light.num1}; + > .notifications-item { + border-top: 1px solid ${themeVars.color.light.border}; + padding: 8px !important; + &:first-child { + border-top: 0; + } + &:last-child { + border-bottom-left-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + &:hover { + border-bottom-left-radius: 0; + } + } + &:hover { + background: ${themeVars.github.bgColor.accent.muted}; + box-shadow: 2px 0 0 ${themeVars.github.borderColor.accent.emphasis} inset; + color: ${themeVars.color.text.self}; + } + > .notifications-link { + > .notifications-top-row { + font-size: 12px !important; + } + > .notifications-bottom-row { + font-size: 14px !important; + } + } + > .notifications-updated { + font-size: 12px; + } + > .notifications-buttons { + .interact-bg { + background: ${themeVars.github.bgColor.accent.muted} !important; + color: ${themeVars.color.text.light.num1}; + padding: 8px !important; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + color: ${themeVars.color.text.self}; + } + } + } + } + } + } + } +`; diff --git a/styles/components/org.ts b/styles/components/org.ts new file mode 100644 index 0000000..3e8293c --- /dev/null +++ b/styles/components/org.ts @@ -0,0 +1,21 @@ +import { css, themeVars } from "src/types/vars"; + +export const org = css` + .page-content.organization { + #org-info { + .ui.header { + // 组织页面的 RSS 订阅按钮 + .ui.label.button { + padding: 4px 16px; + .svg { + width: 20px; + min-width: 20px; + } + &:hover { + border-color: ${themeVars.color.light.border}; + } + } + } + } + } +`; diff --git a/styles/components/packages.ts b/styles/components/packages.ts new file mode 100644 index 0000000..48f3250 --- /dev/null +++ b/styles/components/packages.ts @@ -0,0 +1,124 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 组织/仓库的软件包列表(包含用户但未测试) +export const packagesList = css` + .page-content.packages { + // 这里必须要用 >, 否则会影响到软件包详细信息页的样式 + > .ui.container > div:not([class]) { + border: 1px solid ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + margin-top: 16px; + .flex-list { + border-top: 1px solid ${themeVars.color.light.border}; + &:first-child { + border-top: 0; + } + .flex-item { + padding: 16px; + .flex-item-main { + .flex-item-title { + gap: 8px; + > a { + min-height: 25px; + } + // 软件包类型的标签 + .ui.label { + gap: 4px; + padding: 3px 6px; + min-height: 24.5px; + background-color: unset; + border: 1px solid ${themeVars.color.light.border}; + color: ${themeVars.color.primary.self}; + } + } + .flex-item-body { + font-size: 12px; + a { + text-decoration: underline; + text-underline-offset: 3px; + } + } + } + } + } + } + } +`; + +// 软件包详细信息页 +export const packagesDetail = css` + .page-content.packages { + .issue-title-header > div { + color: ${themeVars.color.text.light.num1}; + } + .issue-content { + .issue-content-left { + .ui.top.attached.header { + font-size: 14px; + padding: 16px; + } + .ui.attached.segment { + color: ${themeVars.color.text.light.num1}; + padding: 16px; + .ui.table, + .ui.form .field > label { + color: ${themeVars.color.text.light.num1}; + } + .ui.form .field > label { + margin-bottom: 8px; + } + .markup { + color: ${themeVars.color.text.self}; + pre { + font-size: 12px; + font-weight: 400; + padding: 12px 16px; + } + } + + .ui.top.attached.header { + margin-top: 24px; + } + } + } + .issue-content-right { + border: 0; + padding: 0px 16px; + > strong { + font-size: 16px; + } + > .divider { + margin: 16px 0px; + } + // 详情 + > .ui.relaxed.list { + margin: 16px 0px; + .item { + color: ${themeVars.color.text.light.num1}; + svg { + color: ${themeVars.color.text.self}; + } + // 应该只选中版本中的 a 标签 + &.tw-flex { + justify-content: space-between; + > a { + border: 1px solid ${themeVars.color.light.border}; + border-radius: 25px; + font-size: 12px; + padding: 0px 6px; + min-height: 20px; + flex: none !important; + &:hover { + text-decoration: none; + } + } + // 不知道什么东西 + &::after { + display: none; + } + } + } + } + } + } + } +`; diff --git a/styles/components/release.ts b/styles/components/release.ts new file mode 100644 index 0000000..ceca3ce --- /dev/null +++ b/styles/components/release.ts @@ -0,0 +1,175 @@ +import { css, themeVars } from "src/types/vars"; + +// release 顶部栏左侧按钮组 +export const releaseTagMenu = css` + .page-content.repository { + &.releases, + &.tags { + h2.small.menu { + .item { + background-color: unset !important; + font-weight: 500; + &.active { + background: ${themeVars.github.bgColor.accent.emphasis} !important; + color: ${themeVars.color.white}; + } + } + } + } + } +`; + +// 顶部右侧按钮组 +export const rightButton = css` + .page-content.repository { + &.releases, + &.tags { + .ui.small.button { + background-color: ${themeVars.color.button}; + border-color: ${themeVars.color.light.border}; + box-shadow: none; + color: ${themeVars.color.text.light.self}; + padding: 5px 16px; + min-height: auto; + line-height: 20px; + &:hover { + background-color: ${themeVars.color.hover.self}; + } + } + } + } +`; + +// 标签页样式 +export const tags = css` + .page-content.repository.tags { + // 标签的选项取消下划线 + .tag-list-row { + .tag-list-row-title { + line-height: 1.5; + } + .download { + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + a.muted:hover { + text-decoration: none; + } + } + } + } +`; + +// 发布页样式 +export const releases = css` + .page-content.repository.releases { + > .ui.container > .divider { + margin: 16px 0; + } + ul#release-list { + gap: 32px; + margin: 32px 0 16px 0; + .release-entry { + // 左侧发布元信息 + .meta { + gap: 0.5rem; + padding-top: 24px; + padding-right: 40px; + text-align: left; + min-width: 0; + flex: 0.125; + a.muted { + color: ${themeVars.color.text.light.num1}; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-break: break-all; + svg { + margin-right: 8px !important; + } + svg.tw-mr-1.svg.octicon-tag { + margin-top: 1px; + } + } + // 分支选择按钮 + .ui.button.branch-dropdown-button { + min-height: 20px; + line-height: 20px; + padding: 3px 12px; + font-size: 12px; + } + } + // 右侧发布详细信息 + .segment.detail { + padding: 16px; + .svg { + color: ${themeVars.color.text.light.num1}; + } + // 标题 + .release-list-title { + font-size: 32px; + gap: 16px; + } + // 提交信息 + p.text.grey { + display: flex; + gap: 6px; + flex-wrap: wrap; + margin: 24px 0 0 0; + span { + word-break: break-word; + } + .time { + color: ${themeVars.color.text.self}; + } + } + // 发布内容 + .markup { + > *:first-child { + margin-top: 24px !important; + } + } + // 分割线 + .divider { + position: relative; + left: -16px; + width: calc(100% + 32px); + border-top-width: 1.5px; + margin: 24px 0 16px 0; + } + // 下载列表 + .download { + summary { + font-size: 18px; + font-weight: 600; + margin-top: 16px; + &::marker { + font-size: 14px; + } + } + .attachment-list { + margin-top: 16px; + .item { + align-items: center; + line-height: 17px; + padding: 8px 16px; + .flex-text-inline { + gap: 8px; + } + // 只选中左侧文件名称 + strong.flex-text-inline:hover { + text-decoration: underline !important; + } + .attachment-right-info { + color: ${themeVars.color.text.light.num1}; + .svg { + height: 28px; + } + } + } + } + } + } + } + } + } +`; diff --git a/styles/components/repo.ts b/styles/components/repo.ts new file mode 100644 index 0000000..26a3713 --- /dev/null +++ b/styles/components/repo.ts @@ -0,0 +1,92 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 仓库头信息 +export const repoHeader = css` + .page-content.repository .repo-header { + // 点星/关注/克隆/RSS 按钮 + .ui.compact.button { + padding: 3px 12px; + } + // 仓库名称 + .flex-item { + .flex-item-title { + // 间隔线颜色 + color: ${themeVars.color.text.light.num1}; + // 仓库名称 + a { + display: flex; + align-items: center; + color: ${themeVars.color.text.self}; + font-size: 16px; + text-decoration: none !important; + min-width: 3ch; + padding: 4px 6px; + border-radius: ${otherThemeVars.border.radius}; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + &.muted:not(.tw-font-normal) { + font-weight: 600; + } + } + } + // 默认的 hover 为 primary 颜色, 修正 + a:not(.label, .button):hover { + color: ${themeVars.color.text.self} !important; + } + } + } +`; + +// 顶部提交, 标签, 分支统计 +export const repoMenu = css` + .page-content.repository { + .repository-summary .repository-menu { + background-color: ${themeVars.color.box.header}; + .item { + color: ${themeVars.color.text.light.num1}; + height: 31px; // 文件列表下与右侧输入框对齐 + b { + color: ${themeVars.color.text.self}; + margin: 0 2px; + } + &.active { + background-color: ${themeVars.color.active}; + color: ${themeVars.color.text.self}; + font-weight: 500; + svg { + color: ${themeVars.color.text.light.num1}; + } + } + } + } + } +`; + +export const repoTopic = css` + // 理应只能覆盖探索/组织/用户下仓库的 topic 标签 + // 避免渲染到仓库的类型标签 + .flex-item-main > .label-list .ui.label, + // 仓库文件列表下的 topic 标签 + #repo-topics .ui.label.repo-topic { + border-radius: 25px; + font-size: 12px; + font-weight: 500; + padding: 5px 10px; + background-color: ${themeVars.github.bgColor.accent.muted}; + color: ${themeVars.github.fgColor.accent}; + &:hover { + background-color: ${themeVars.github.bgColor.accent.emphasis}; + color: ${themeVars.color.white}; + } + } +`; + +// 仓库动态页面关闭工单状态条颜色 +export const closedIssueTableCell = css` + .stats-table .table-cell.tw-bg-red[href="#closed-issues"] { + background-color: ${themeVars.color.purple.self} !important; + } +`; diff --git a/styles/components/setting.ts b/styles/components/setting.ts new file mode 100644 index 0000000..3d80b44 --- /dev/null +++ b/styles/components/setting.ts @@ -0,0 +1,85 @@ +import { css, themeVars } from "src/types/vars"; +import { primaryHoverStyle, primaryStyle } from "styles/public/button"; + +const tinyStyle = { + color: themeVars.github.button.primary.fgColor.accent, + backgroundColor: themeVars.color.button, + borderColor: themeVars.color.light.border, +}; + +const tinyHoverStyle = { + color: themeVars.github.button.primary.fgColor.rest, + backgroundColor: themeVars.github.button.primary.bgColor.hover, + borderColor: themeVars.github.button.primary.borderColor.hover, +}; + +// 设置界面下的按钮 +export const button = css` + // 不包含管理员的设置界面 + .user-main-content, + .repo-setting-content, + .user-setting-content, + .org-setting-content { + // 主色调按钮替换为普通按钮 + .ui.primary.button { + color: ${themeVars.color.text.light.self}; + background-color: ${themeVars.color.button}; + border-color: ${themeVars.color.light.border}; + box-shadow: none; + &:hover { + background-color: ${themeVars.color.hover.self}; + } + } + // 迷你按钮替换为自定义的主色调按钮 (例: SSH 验证按钮) + .ui.primary.button.tiny { + ${tinyStyle} + &:hover { + ${tinyHoverStyle} + } + } + } + // 右上角迷你按钮替换会主色调按钮 + .user-main-content, + .repo-setting-content, + .user-setting-content, + .org-setting-content, + .admin-setting-content { + .ui.attached.header > .ui.right { + .ui.primary.button.tiny { + ${primaryStyle} + padding: 3px 12px; + min-height: 20px; + line-height: 20px; + &:hover { + ${primaryHoverStyle} + } + } + } + } + // 管理员设置界面下的自定义主色调按钮 + .admin-setting-content { + .ui.primary.button { + ${tinyStyle} + padding: 5px 16px; + line-height: 22px; + &:hover { + ${tinyHoverStyle} + } + } + .ui.red.button { + box-shadow: ${themeVars.github.shadow.resting.small}; + padding: 5px 16px; + line-height: 22px; + } + } +`; + +export const label = css` + // Runner 标签 + .runner-container { + // 普通标签, runner 状态: 离线, runner 标签 + .ui.label { + border: 1px solid ${themeVars.color.light.border}; + } + } +`; diff --git a/styles/components/signin.ts b/styles/components/signin.ts new file mode 100644 index 0000000..a826da8 --- /dev/null +++ b/styles/components/signin.ts @@ -0,0 +1,62 @@ +import { css } from "src/types/vars"; + +// 注册/登录界面 +export const signIn = css` + .page-content.user.signin { + .ui.grid { + justify-content: center; + > .column { + width: 384px; + padding: 16px; + > .ui.container { + max-width: unset; + } + } + } + .ui.top.attached.header { + border: 0; + font-size: 20px; + font-weight: 600; + background-color: unset !important; + text-align: center; + padding: 16px; + } + + .ui.attached.segment { + border: 0; + padding: 16px 0 0 0; + .field:not(.inline) { + label { + font-size: 14px; + font-weight: 600; + } + input { + background: unset; + padding: 5px 12px; + height: 40px; + font-size: 16px; + } + } + .button { + height: 40px; + } + .divider.divider-text { + margin: 20px 0px; + } + #oauth2-login-navigator-inner { + gap: 8px; + .ui.button svg { + width: 18px; + } + } + } + .ui.top.attached.header.segment { + font-size: 14px; + font-weight: 400; + gap: 16px; + .signin-passkey { + font-weight: 500; + } + } + } +`; diff --git a/styles/components/user.ts b/styles/components/user.ts new file mode 100644 index 0000000..e56d60a --- /dev/null +++ b/styles/components/user.ts @@ -0,0 +1,62 @@ +import { css, themeVars } from "src/types/vars"; + +// 用户点星仓库列表 +export const stars = css` + .page-content.user.profile { + .stars { + > .flex-list { + > .flex-item { + padding: 24px 0; + &:first-child { + padding-top: 14px; + } + // 仓库头像 + > .flex-item-leading { + img, + svg { + color: ${themeVars.color.text.light.num1}; + } + } + // 仓库信息 + > .flex-item-main { + // 仓库标题 + > .flex-item-header { + // 仓库名称 + > .flex-item-title { + font-size: 20px; + gap: 8px; + // 仓库中间的间隔线 + &:not(a) { + color: ${themeVars.color.primary.self}; + } + } + // 仓库语言, 星标 + > .flex-item-trailing { + color: ${themeVars.color.text.light.num1}; + gap: 16px; + font-size: 12px; + > .flex-text-inline .color-icon { + width: 12px; + height: 12px; + margin-right: 0 !important; + } + } + } + // 描述和更新时间 + > .flex-item-body { + margin-top: 10px; + // 更新时间 + &:last-child { + font-size: 12px; + } + } + // 主题标签 + > .label-list { + margin-top: 10px; + } + } + } + } + } + } +`; diff --git a/styles/index.ts b/styles/index.ts new file mode 100644 index 0000000..b20025a --- /dev/null +++ b/styles/index.ts @@ -0,0 +1,8 @@ +// organize-imports-ignore +// tslint:disable:ordered-imports +// 组件导入有顺序, 禁止插件优化 + +// 全局样式 +import "./public"; +// 组件样式 +import "./components"; diff --git a/styles/public/animation.ts b/styles/public/animation.ts new file mode 100644 index 0000000..e0f96f9 --- /dev/null +++ b/styles/public/animation.ts @@ -0,0 +1,27 @@ +import { overlayAppearDown, overlayAppearUp } from "src/core/theme"; +import { css } from "src/types/vars"; + +export const keyframe = css` + // 向下出现动画 + @keyframes ${overlayAppearDown} { + 0% { + opacity: 0; + transform: translateY(-8px); + } + 100% { + opacity: 1; + transform: translateY(0); + } + } + // 向上出现动画 + @keyframes ${overlayAppearUp} { + 0% { + opacity: 0; + transform: translateY(8px); + } + 100% { + opacity: 1; + transform: translateY(0); + } + } +`; diff --git a/styles/public/attached.ts b/styles/public/attached.ts new file mode 100644 index 0000000..df3d000 --- /dev/null +++ b/styles/public/attached.ts @@ -0,0 +1,36 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +export const attached = css` + // 设置右面板的内容 + .user-main-content, + .repo-setting-content, + .user-setting-content, + .org-setting-content, + .admin-setting-content, + // 新建页面内容 + .page-content.repository.new-repo, + .page-content.repository.new.migrate, + .page-content.repository.new.fork, + .page-content.organization.new.org { + .ui.top.attached.header { + border: 0; + font-size: 20px; + font-weight: 400; + background-color: unset !important; + margin-bottom: 0.25rem; + } + + .ui.attached.segment { + background-color: unset; + border-radius: ${otherThemeVars.border.radius} !important; + } + + .ui.attached.segment:not(.error) { + border: 1px solid ${themeVars.color.light.border} !important; + } + + .ui.attached.segment.error { + border: 1px solid ${themeVars.color.error.border} !important; + } + } +`; diff --git a/styles/public/button.ts b/styles/public/button.ts new file mode 100644 index 0000000..9db1934 --- /dev/null +++ b/styles/public/button.ts @@ -0,0 +1,139 @@ +import { css, themeVars } from "src/types/vars"; + +export const primaryStyle = { + color: themeVars.github.button.primary.fgColor.rest, + backgroundColor: themeVars.github.button.primary.bgColor.rest, + borderColor: themeVars.github.button.primary.borderColor.rest, + boxShadow: themeVars.github.shadow.resting.small, +}; + +export const primaryHoverStyle = { + color: themeVars.github.button.primary.fgColor.rest, + backgroundColor: themeVars.github.button.primary.bgColor.hover, + borderColor: themeVars.github.button.primary.borderColor.hover, +}; + +// 普通按钮和主色调按钮 +export const baseButton = css` + .ui.button { + min-height: 30px; + font-weight: 500; + padding: 9px 16px; + &.ui { + gap: 8px; + } + } + .ui.button:not(.primary):not(.red) svg { + color: ${themeVars.color.text.light.num1}; + } + // 主色调按钮保持白色 + .ui.primary.buttons .button svg { + color: ${themeVars.color.white}; + } + .ui.primary { + &.button, + // 按钮组, PR 里的压缩合并按钮 + &.buttons .button { + ${primaryStyle} + &:hover { + ${primaryHoverStyle} + } + } + // 按钮组整体有阴影 + &.buttons { + box-shadow: ${themeVars.github.shadow.resting.small}; + .button { + // 按钮组里的按钮无阴影 + box-shadow: none; + } + } + } + // 主色调基本按钮和普通按钮一样 + // 作者的关注按钮 + .ui.basic.primary.button { + background-color: ${themeVars.color.button}; + color: ${themeVars.color.text.self}; + border-color: ${themeVars.color.light.border}; + box-shadow: none; + &:hover { + background-color: ${themeVars.color.hover.self}; + color: ${themeVars.color.text.self}; + border-color: ${themeVars.color.light.border}; + } + } + /* 普通按钮边框色不变 */ + .ui.basic.button, + /* 仓库点星等数字标签按钮边框色不变 */ + .ui.labeled.button > .label { + &:hover { + border-color: ${themeVars.color.light.border}; + } + } + + /* 普通按钮激活时背景色 */ + .ui.basic.buttons .button:active, + .ui.basic.button:active, + .ui.basic.buttons .active.button, + .ui.basic.active.button, + .ui.basic.buttons .active.button:hover, + .ui.basic.active.button:hover { + background-color: ${themeVars.github.button.default.bgColor.active}; + } +`; + +// 红色按钮 +export const redButton = css` + .ui.red.button, + .ui.basic.red.buttons .button, + .ui.basic.red.button { + color: ${themeVars.github.button.danger.fgColor.rest}; + background-color: ${themeVars.github.button.danger.bgColor.rest}; + /* 一些按钮边框色为红色, 比如危险操作区, 统一为暗色边框和 github 一致 */ + border-color: ${themeVars.color.light.border}; + + &:hover { + color: ${themeVars.github.button.danger.fgColor.hover}; + background-color: ${themeVars.github.button.danger.bgColor.hover}; + border-color: ${themeVars.github.button.danger.borderColor.hover}; + box-shadow: ${themeVars.github.shadow.resting.small}; + } + } +`; + +// 修复按钮高度 +export const fixButtonHeight = css` + // 修复一些主色调或者其他小按钮的高度避免过高 + .ui.small.buttons .button, + .ui.ui.ui.ui.small.button { + min-height: 26px; + height: 32px; + } + // 修复仓库页仓库操作按钮高度对齐和修正 + .repo-button-row .ui.button { + min-height: 32px; + } + // 修复因上面小按钮高度导致仓库星标克隆等按钮高度过高 + .repo-header { + .ui.ui.ui.ui.small.compact.button, + .ui.labeled.button > .label { + height: 28px; + min-height: 28px; + } + } + .ui.ui.ui.ui.small.button.compact .ui.tiny.buttons .button, + .ui.ui.ui.ui.tiny.button { + min-height: 20px; + } +`; + +export const fixButton = css` + // 修复关注&派生 hover 意外点亮右侧 label 左边框 + .ui.ui.ui.ui.small.button { + z-index: 0; + } + // 代码复制按钮 + .ui.button.code-copy { + padding: 4px 6px; + min-height: 28px; + } +`; diff --git a/styles/public/dropdown.ts b/styles/public/dropdown.ts new file mode 100644 index 0000000..17aca0b --- /dev/null +++ b/styles/public/dropdown.ts @@ -0,0 +1,272 @@ +import { animationDown, animationUp } from "src/core/theme"; +import { fallbackVar } from "src/functions"; +import { css, customThemeVars, otherThemeVars, themeVars } from "src/types/vars"; +import { activeItemAfterStyle } from "styles/public/menu"; + +export const dropdown = css` + .ui.dropdown, + .ui.menu .ui.dropdown { + .menu { + animation: ${animationDown}; + // 统一所有下拉菜单的样式 + background-color: ${themeVars.color.menu} !important; + border: unset !important; + border-radius: 12px !important; + box-shadow: ${themeVars.github.shadow.floating.small} !important; + // 忽略隐藏 + > .item:not(.tw-hidden) { + display: flex !important; + align-items: center; + padding: 6px 8px !important; + border-radius: ${otherThemeVars.border.radius} !important; + gap: 4px; + &:not(.emoji) { + margin: 0 8px; + } + &:not(.emoji):first-of-type { + margin-top: 8px; + } + // 不知道为什么提交差异对比页面操作中的 cherrypick 按钮无法被选中 + &.cherry-pick-button, + &:not(.emoji):last-of-type { + margin-bottom: 8px; + } + &:hover { + background-color: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + &.selected { + background-color: ${themeVars.color.active} !important; + &:hover { + background-color: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + } + &.active, + &.selected { + &:after { + content: ""; + ${activeItemAfterStyle}; + } + } + svg { + margin-top: 2px; + margin-right: 4px; + } + // 复选框对齐 + .ui.checkbox input[type="checkbox"] { + height: 100%; + } + // 修复 Wiki 页面下搜索框中搜索时, 显示隐藏的项目 + &.filtered { + display: none !important; + } + } + // 当筛选后, 让菜单提供边距, 因为无法确定保留的是菜单中哪个 item + // 不是所有菜单都提供边距方式, 原因为比如会导致分支菜单中的查看所有分支上间隔缺失, 这种方式单独为 Wiki 菜单做处理 + // 有筛选(.filtered)且有筛选结果(.selected)时提供菜单边距, 没结果时提供会导致多余的菜单边框线 + &:has(> .item.filtered):has(> .item.selected) { + padding: 8px 0px; + > .item { + &:first-of-type { + margin-top: 0; + } + &:last-of-type { + margin-bottom: 0; + } + } + } + > .divider { + margin: 8px 0; + } + &:after { + display: none !important; + } + } + } + // 向下弹出的下拉菜单向下偏移 + .ui.dropdown:not(.upward), + .ui.menu .ui.dropdown:not(.upward) { + > .menu { + margin-top: 4px !important; + } + } + // 向上弹出的下拉菜单向上偏移 + .ui.dropdown.upward, + .ui.menu .ui.dropdown.upward { + > .menu { + animation: ${animationUp}; + margin-bottom: 4px !important; + } + } + // 修复 wiki 的页面下拉菜单圆角 + .ui.floating.dropdown > .menu { + border-radius: 12px !important; + } + // 修复嵌套菜单的圆角问题, wiki 页面和组织页面的用户下拉菜单 + .ui.dropdown .menu { + .scrolling.menu { + border-radius: 0 0 12px 12px !important; + } + // 修复仪表板切换用户按钮菜单下无创建组织按钮时的菜单圆角 + &.context-user-switch { + .scrolling.menu:last-child { + border-radius: 0 0 12px 12px !important; + } + } + } + // 修复下拉菜单元素溢出问题 + // 用户菜单 + .user-menu>.item, + // Issue/PR 菜单 + .ui.menu .ui.dropdown.item .menu .item { + width: calc(100% - 16px); // 减去上方 item 的 margin 左右边距 + } + // 去掉下拉菜单的边框线 + // 设置里的下拉菜单 + .ui.selection.dropdown .menu > .item { + border: unset; + } + // 修复一些菜单下的菜单元素溢出问题 + // 目前主要为分支菜单 + .ui.dropdown .menu .menu { + border-radius: 12px !important; + } + // 修复按钮阴影被覆盖缺少边框线的问题 + // 仓库动态页面的右侧按钮, 比如时间周期 + .ui.floating.dropdown .menu, + // 仪表板切换控制用户按钮 + .ui.dropdown .menu.context-user-switch .scrolling.menu { + box-shadow: ${themeVars.github.shadow.floating.small} !important; + } +`; + +// 选择框的下拉菜单看起来像普通按钮 +export const selectionDropdown = css` + // 排除可以选择的输入搜索框和创建仓库的拥有者 + .ui.selection.dropdown:not(.search):not(.ellipsis-text-items), + .ui.selection.dropdown.active:not(.search):not(.ellipsis-text-items) { + background-color: ${themeVars.color.button}; + border-color: ${themeVars.color.light.border}; + border-radius: ${otherThemeVars.border.radius}; + padding: 8px 16px; + min-height: 32px; + &:focus { + background: ${themeVars.color.button}; + border-color: ${themeVars.color.light.border}; + } + &:hover { + background: ${themeVars.color.hover.self}; + border-color: ${themeVars.color.light.border}; + } + // 悬停提供伪元素 + .item:hover:after { + content: ""; + ${activeItemAfterStyle}; + } + } + // 修复顶部导航栏工单管理/请求合并页面搜索框旁的选择下拉框按钮内容过窄 + .list-header-search .ui.action.input > .dropdown.small { + padding: 8px 40px 8px 16px; + } + // 发布版本页面的分支按钮, 覆盖 Gitea 的样式, 避免按钮元素高度不一致 + .repository.new.release .target .selection.dropdown { + padding-top: 8px; + padding-bottom: 8px; + } + // 这个按钮项目前只在创建仓库的拥有者 + // 不实现伪元素, 因为 .item 设置溢出的元素会被截断 + .ui.selection.dropdown.ellipsis-text-items, + .ui.selection.dropdown.activeellipsis-text-items { + &:focus { + background: ${themeVars.color.button}; + border-color: ${themeVars.color.light.border}; + } + &:hover { + background: ${themeVars.color.hover.self}; + border-color: ${themeVars.color.light.border}; + } + } + // 选择输入框效果和输入框 focus 效果一样 + .ui.selection.dropdown.active.search { + background: ${themeVars.color.body}; + // 向内部添加一个 1px 的边框 + box-shadow: inset 0 0 0 1px ${themeVars.github.borderColor.accent.emphasis}; + outline: none; + } + // 覆盖选择输入框向上弹出时的 hover 效果, 原阴影会覆盖加厚的边框线 + .ui.selection.dropdown.active.search.upward:hover { + box-shadow: inset 0 0 0 1px ${themeVars.github.borderColor.accent.emphasis}; + } + // 由于之前的排除导致样式优先级变高, 小按钮去除圆角 + .ui.action.input > .dropdown.small:not(:first-child) { + border-radius: 0; + } + // 排除一些小按钮, 例如软件包类型, 通常相邻有元素 + .ui.selection.dropdown.active:not(.small) { + border-bottom-left-radius: ${otherThemeVars.border.radius} !important; + border-bottom-right-radius: ${otherThemeVars.border.radius} !important; + } + // 修复因为上面的排除导致的圆角问题 + // 具体工单页面下依赖菜单选择框 + #new-dependency-drop-list.ui.selection.dropdown.active, + // 提交图分支选择框 + #git-graph-container #flow-select-refs-dropdown { + border-bottom-right-radius: 0 !important; + } + // 修复选择框的下拉菜单向上显示时的样式问题 + .ui.upward.selection.dropdown.visible:not(.small), + .ui.active.upward.selection.dropdown:not(.small) { + border-top-left-radius: ${otherThemeVars.border.radius} !important; + border-top-right-radius: ${otherThemeVars.border.radius} !important; + } + // 工单标签菜单中的标签换行和颜色 + // 标签页面的标签选择框 + .page-content.repository.labels .ui.selection.dropdown.active, + // 创建仓库页面的标签选择框 + .ui.search.selection.dropdown { + > .menu > .item { + flex-wrap: wrap; + > i { + color: ${themeVars.color.text.light.num1}; + } + } + } +`; + +export const fixSelectionDropdown = css` + // 具体工单页面下依赖菜单选择框 + #new-dependency-drop-list.ui.selection.dropdown { + // 高度对齐问题, 应该与 input 框高度一致 + min-height: 32px; + } +`; + +// 分支菜单 +export const branchDropdown = css` + .ui.dropdown.branch-selector-dropdown > .menu { + width: ${fallbackVar(customThemeVars.branchMenuWidth, "320px")}; + max-width: 640px; + > .menu > .item { + animation: ${animationDown}; + } + } +`; + +// 包含表情的下拉菜单 +export const emojiDropdown = css` + .ui.dropdown.action.select-reaction.active .menu:has(.emoji) { + display: flex !important; + flex-direction: row; + gap: 4px; + padding: 4px; + min-width: auto; + > .item.emoji { + font-size: 14px; + min-height: 32px; + height: 32px; + margin: 0px; + &:hover { + background-color: ${themeVars.github.bgColor.accent.emphasis} !important; + } + } + } +`; diff --git a/styles/public/index.ts b/styles/public/index.ts new file mode 100644 index 0000000..a3866db --- /dev/null +++ b/styles/public/index.ts @@ -0,0 +1,16 @@ +// organize-imports-ignore +// tslint:disable:ordered-imports +// 组件导入有顺序, 禁止插件优化 +import "./radius"; // 圆角, 此样式为基础样式, 确保在其他样式之前导入 +import "./animation"; // 动画效果 +import "./transition"; // 过渡效果 +import "./text"; // 文本或 SVG 的基本颜色 +import "./button"; // 按钮 +import "./dropdown"; // 下拉框 +import "./input"; // 输入框 +import "./label"; // 标签 +import "./menu"; // 菜单 +import "./modal"; // 弹窗 +import "./tippy"; // 提示框 +import "./attached"; // 附加样式 +import "./other"; // 其他样式 diff --git a/styles/public/input.ts b/styles/public/input.ts new file mode 100644 index 0000000..8ebce4b --- /dev/null +++ b/styles/public/input.ts @@ -0,0 +1,42 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +export const input = css` + input, + textarea, + .ui.input input, + // 排除可以选择的输入搜索框 + .ui.form input:not([type]):not(.search), + .ui.form select, + .ui.form textarea, + .ui.form input[type="date"], + .ui.form input[type="datetime-local"], + .ui.form input[type="email"], + .ui.form input[type="file"], + .ui.form input[type="number"], + .ui.form input[type="password"], + .ui.form input[type="search"], + .ui.form input[type="tel"], + .ui.form input[type="text"], + .ui.form input[type="time"], + .ui.form input[type="url"] { + padding: 8px 12px; + &:focus, + &:focus-visible { + background: ${themeVars.color.body} !important; + border-radius: ${otherThemeVars.border.radius}; + border-color: ${themeVars.github.borderColor.accent.emphasis}; + // 向内部添加一个 1px 的边框 + box-shadow: inset 0 0 0 1px ${themeVars.github.borderColor.accent.emphasis}; + outline: none; + } + } + + .ui.input { + height: 32px; + } + // 由于输入框高度, 需要输入框在表单中垂直居中 + // 管理员页面仓库搜索表单 + .ui.form#repo-search-form { + align-items: center; + } +`; diff --git a/styles/public/label.ts b/styles/public/label.ts new file mode 100644 index 0000000..f128a7b --- /dev/null +++ b/styles/public/label.ts @@ -0,0 +1,190 @@ +import { css, themeVars } from "src/types/vars"; + +export const label = css` + /* 所有标签, 但不包括 a 标签 */ + /* a 标签比如仓库点星等按钮旁边的数字标签按钮,提交图中的 tag 标签 */ + div, + span { + &.ui.ui.ui { + &.label { + border-radius: 25px; + // 多个标签的组合标签的圆角修复 + &.scope-parent { + .scope-left { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .scope-middle { + border-radius: 0; + } + .scope-right { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + /* 主色调标签 */ + &.primary { + background-color: unset; + border: 1px solid ${themeVars.color.primary.self}; + color: ${themeVars.color.primary.self}; + } + /* 红色标签 */ + &.red { + background-color: unset; + border: 1px solid ${themeVars.github.borderColor.done.emphasis}; + color: ${themeVars.color.purple.self}; + } + /* 橙色标签 */ + &.orange { + background-color: unset; + border: 1px solid ${themeVars.github.borderColor.attention.emphasis}; + color: ${themeVars.color.yellow.self}; + } + /* 黄色标签 */ + &.yellow { + background-color: unset; + border: 1px solid ${themeVars.github.borderColor.attention.emphasis}; + color: ${themeVars.color.orange.self}; + } + /* 黄绿色标签 */ + &.olive { + background-color: unset; + border: 1px solid ${themeVars.color.olive.self}; + color: ${themeVars.color.olive.self}; + } + /* 绿色标签 */ + &.green { + background-color: unset; + border: 1px solid ${themeVars.github.borderColor.success.emphasis}; + color: ${themeVars.color.green.self}; + } + /* 紫色标签 */ + &.purple { + background-color: unset; + border: 1px solid ${themeVars.github.borderColor.done.emphasis}; + color: ${themeVars.color.purple.self}; + } + } + } + } + .ui.small.label { + font-size: 12px; + } + .ui.mini.label { + font-size: 10px; + } +`; + +// 提交中的 SHA 标签 +export const shaLabel = css` + a.ui.label.sha, + a.ui.label.commit-id-short { + border: unset; + background-color: unset; + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + font-weight: 500; + // 仪表盘页的提交 SHA 标签居中对齐 + margin-top: 2px; + &:hover { + background-color: ${themeVars.color.label.hoverBg}; + } + // 验证提交 SHA 标签 + &.commit-is-signed { + border: unset !important; + background-color: unset !important; + &:hover { + background-color: ${themeVars.color.label.hoverBg} !important; + } + span.ui.label.commit-is-signed { + padding: 3px 5px; + margin-left: 5px; + } + } + } + + // 验证提交附带的图标 + span.ui.label.commit-is-signed { + // 验证信任 + &.sign-trusted { + border: 1.5px solid ${themeVars.color.green.badge.self} !important; + color: ${themeVars.color.green.badge.self} !important; + &:hover { + background-color: ${themeVars.color.green.badge.hover.bg} !important; + } + } + // 验证未信任 + &.sign-untrusted { + border: 1.5px solid ${themeVars.color.yellow.badge.self} !important; + color: ${themeVars.color.yellow.badge.self} !important; + &:hover { + background-color: ${themeVars.color.yellow.badge.hover.bg} !important; + } + } + // 验证未匹配 + &.sign-unmatched { + border: 1.5px solid ${themeVars.color.orange.badge.self} !important; + color: ${themeVars.color.orange.badge.self} !important; + &:hover { + background-color: ${themeVars.color.orange.badge.hover.bg} !important; + } + } + // 验证警告 + &.sign-warning { + border: 1.5px solid ${themeVars.color.red.badge.self} !important; + color: ${themeVars.color.red.badge.self} !important; + &:hover { + background-color: ${themeVars.color.red.badge.hover.bg} !important; + } + } + } +`; + +// 任务状态标签, 目前仅在管理员页面 Runner 状态中看到 +export const taskStatusLabel = css` + .runner-container .ui.label.task-status- { + &success { + color: ${themeVars.color.success.text}; + border: 1px solid ${themeVars.color.success.border}; + background: ${themeVars.color.success.bg}; + } + + &failure { + color: ${themeVars.color.error.text}; + border: 1px solid ${themeVars.color.error.border}; + background: ${themeVars.color.error.bg.self}; + } + + &running, + &skipped { + color: ${themeVars.color.info.text}; + border: 1px solid ${themeVars.color.info.border}; + background: ${themeVars.color.info.bg}; + } + + &cancelled, + &blocked { + color: ${themeVars.color.warning.text}; + border: 1px solid ${themeVars.color.warning.border}; + background: ${themeVars.color.warning.bg}; + } + } +`; + +// 仓库标签 (私有/公开/内部) +export const repoLabel = css` + span, + // 用户切换面板的标签 + .org-visibility div { + &.ui.basic.label { + background-color: unset; + color: ${themeVars.color.text.light.num1}; + font-size: 12px; + font-weight: 500; + padding: 3px 6px; + } + } + .org-visibility span.ui.basic.label { + font-size: 14px; + } +`; diff --git a/styles/public/menu.ts b/styles/public/menu.ts new file mode 100644 index 0000000..e671265 --- /dev/null +++ b/styles/public/menu.ts @@ -0,0 +1,298 @@ +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +export const activeItemAfterStyle = { + backgroundColor: themeVars.github.borderColor.accent.emphasis, + borderRadius: otherThemeVars.border.radius, + height: "24px", + left: "calc(0.5rem * -1)", + position: "absolute", + top: "calc(50% - 12px)", + width: "4px", +}; + +export const verticalMenu = css` + // 垂直菜单, 用于左侧边栏 + // 设置页面中的菜单, Actions 工作流菜单, WorkflowRuns Job 菜单 + .ui.vertical.menu { + // 去除边框, 和背景色同色 + background: ${themeVars.color.body}; + border: 0; + // 设置页面的菜单头部 + .header.item { + color: ${themeVars.color.text.light.num1} !important; + font-size: 1.5rem; + font-weight: 700; + background: unset; + margin-bottom: 0.5rem; + } + // 菜单项被悬停时的背景色, 限制a标签, 避免为子菜单多余渲染 + a.item:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + // 菜单项 + .item, + .item > summary { + font-size: 1.1rem; + background: unset; + border-radius: ${otherThemeVars.border.radius}; + padding: 6px 8px; + } + // Actions 菜单的圆角覆盖 + > .item, + > .active.item { + &:first-child, + &:last-child { + border-radius: ${otherThemeVars.border.radius}; + } + } + // 去除菜单项的边框线 + .item:before { + background: unset; + } + // 激活的菜单项 + .active.item, + .active.item > summary { + font-weight: 600; + border-radius: ${otherThemeVars.border.radius}; + } + // 添加伪元素, 用于指示当前激活的菜单项 + .active.item:after { + content: ""; + ${activeItemAfterStyle}; + } + // 部分菜单项的子菜单 + details.item { + // 子菜单的标题 + summary:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + // 子菜单的选项 + .menu .item { + color: ${themeVars.color.text.self}; + } + // 子菜单的选项被激活 + &:has(.active.item) { + > summary { + font-weight: 600; + background: ${themeVars.color.active}; + // 收回状态,悬停色 + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + &:after { + content: ""; + ${activeItemAfterStyle}; + } + // 子菜单的选项 + .active.item { + background: ${themeVars.color.active}; + font-weight: 400; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + // 子菜单展开时 + &[open] { + > summary { + background: unset; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + &:after { + display: none; + } + } + } + } + } +`; + +export const menu = css` + .menu .item svg { + color: ${themeVars.color.text.light.num1}; + } + // 菜单默认悬浮色更改 + .ui.menu a.item, + .ui.secondary.pointing.menu a.item, + .ui.secondary.menu .dropdown.item { + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + // 一些水平小型菜单的颜色更改 + .small-menu-items .item { + background-color: ${themeVars.color.body} !important; + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + } + // 一些菜单的悬浮色更改 + .ui.segment .ui.tabular.menu, + .header-wrapper .ui.tabular.menu, + .ui.secondary.pointing.menu { + .item, + .active.item { + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + } +`; + +export const secondaryMenu = css` + // 二级菜单, 比如 Issue/PR/Actions 的筛选菜单 + .ui.secondary.menu { + .item { + padding: 0px 12px; + height: 32px; + font-weight: 500; + } + a.item:hover { + background: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + // 二级导航栏, 比如仓库的导航栏, 仓库列表的导航栏, 探索的类型导航栏 + .ui.secondary.pointing.menu { + .overflow-menu-items { + gap: 4px; + .item { + padding: 5px 8px !important; + margin-block-start: 0.5rem; + margin-block-end: 0.5rem; + margin-bottom: 0.5rem !important; + } + } + .item { + font-weight: 400; // 二级导航栏不需要加粗 + } + .active.item, + .dropdown.item, + .link.item, + a.item { + border-radius: ${otherThemeVars.border.radius}; + color: ${themeVars.color.text.self}; + svg { + margin-right: 8px; + } + } + .active.item { + // 取消激活时的下划线, 需要为透明, 保持间距 + border-color: #ffffff00; + // 模仿 github 的下划线 + span:after { + content: ""; + background: ${themeVars.github.underlineNav.borderColor.active}; + border-radius: ${otherThemeVars.border.radius}; + bottom: calc(50% - 1.8rem); + height: 2px; + position: absolute; + right: 50%; + transform: translate(50%, -50%); + width: 100%; + z-index: 1; + } + } + } + // 修复仓库页面下的二级导航栏的下划线因浏览器 BUG 导致显示的异常问题(导致下划线和分割线重叠变粗) + .page-content.repository .ui.secondary.pointing.menu { + border-bottom: none !important; + } +`; + +// 分页菜单 +export const paginationMenu = css` + .ui.borderless.pagination.menu { + align-items: center; + background-color: unset; + border: 0; + gap: 4px; + min-height: fit-content; + .item { + border-radius: ${otherThemeVars.border.radius}; + min-width: 32px; + min-height: 32px; + height: 32px; + justify-content: center; + // 避免一些 hover 效果调整内容 + padding: 5px 10px !important; + &.active { + background: ${themeVars.github.bgColor.accent.emphasis}; + color: ${themeVars.color.white}; + } + // 设置透明边框线避免 hover 时元素大小变化 + &:not(.active) { + border: 1px solid #ffffff00; + &:hover { + background: unset; + border-color: ${themeVars.color.secondary.self}; + } + } + &.navigation { + &:not(.disabled) { + span, + svg { + color: ${themeVars.color.primary.self}; + } + } + // 对齐文字 + svg { + margin-top: 2px; + } + } + } + } +`; + +// 单行双选项菜单 +export const smallCompactMenu = css` + // 订阅/关注切换菜单(应只选中订阅/关注页面, 不能选中通知页面) + .page-content.user.notification > .ui.container:has(.flex-list), + // 里程碑/标签切换菜单(里程碑页) + .page-content.repository.milestones .list-header, + // 里程碑/标签切换菜单(新建里程碑页) + .page-content.repository.new.milestone .issue-navbar, + // 里程碑/标签切换菜单(标签页) + .page-content.repository.labels .issue-navbar { + .ui.compact.small.menu.small-menu-items { + background: ${themeVars.color.hover.self} !important; + border: 0; + font-size: 14px; + gap: 8px; + height: 32px; + min-height: 32px !important; + > .item { + background: unset !important; + border: 1px solid ${themeVars.color.hover.self}; + border-radius: ${otherThemeVars.border.radius}; + padding: 6px 12px !important; + &.active { + background: ${themeVars.color.menu} !important; + border-color: ${themeVars.color.light.border}; + font-weight: 600; + } + &::before { + display: none; + } + &:not(.active) { + top: 4px; + padding: 4px 12px !important; + height: calc(100% - 8px); + position: relative; + // 该方案只适用于 2 个 item 的情况 + // left / right 数值为 gap 数值的一半 + &:first-child { + left: 4px; + } + &:last-child { + right: 4px; + } + &:hover { + background: ${themeVars.github.control.transparent.bgColor.hover} !important; + } + } + } + } + } +`; diff --git a/styles/public/modal.ts b/styles/public/modal.ts new file mode 100644 index 0000000..385285e --- /dev/null +++ b/styles/public/modal.ts @@ -0,0 +1,29 @@ +import { animationDown } from "src/core/theme"; +import { css, themeVars } from "src/types/vars"; + +export const modal = css` + .ui.modal { + animation: ${animationDown}; + border: 1.5px solid ${themeVars.color.light.border}; + + > .header { + background-color: ${themeVars.color.menu}; + border-bottom: 1.5px solid ${themeVars.color.light.border}; + } + + > .content, + form > .content { + background-color: ${themeVars.color.menu}; + } + + > .actions, + .content + .actions, + .content + form > .actions { + background-color: ${themeVars.color.menu}; + border-top: 1.5px solid ${themeVars.color.light.border}; + } + .actions > .ui.button { + padding: 8px 12px; + } + } +`; diff --git a/styles/public/other.ts b/styles/public/other.ts new file mode 100644 index 0000000..449c0cb --- /dev/null +++ b/styles/public/other.ts @@ -0,0 +1,16 @@ +import { css, themeVars } from "src/types/vars"; + +// 一些列表头, 比如工单的搜索标签里程碑栏 +export const listHeader = css` + .list-header { + align-items: center; + align-content: center; + } +`; + +// 已标星的图标 +export const star = css` + .octicon-star-fill { + color: ${themeVars.github.button.star.iconColor} !important; + } +`; diff --git a/styles/public/radius.ts b/styles/public/radius.ts new file mode 100644 index 0000000..b740ebe --- /dev/null +++ b/styles/public/radius.ts @@ -0,0 +1,294 @@ +import { css, otherThemeVars } from "src/types/vars"; + +// 全部圆角替换 +export const radius = css` + .ui.form textarea, + .ui.form input:not([type]), + .ui.form input[type="date"], + .ui.form input[type="datetime-local"], + .ui.form input[type="email"], + .ui.form input[type="number"], + .ui.form input[type="password"], + .ui.form input[type="search"], + .ui.form input[type="tel"], + .ui.form input[type="time"], + .ui.form input[type="text"], + .ui.form input[type="file"], + .ui.form input[type="url"] { + border-radius: ${otherThemeVars.border.radius}; + &:focus { + border-radius: ${otherThemeVars.border.radius}; + } + } + .ui.form select { + border-radius: ${otherThemeVars.border.radius}; + } + + .ui.input { + textarea, + input { + border-radius: ${otherThemeVars.border.radius}; + } + } + + .ui { + &.menu, + &.modal, + &.label, + &.table, + &.segment, + &.segments, + &.grid.segment, + &.selection.dropdown, + &.horizontal.segments, + &.active.empty.selection.dropdown { + border-radius: ${otherThemeVars.border.radius}; + } + } + + .ui.dropdown, + .ui.inline.dropdown, + .ui.pointing.upward.dropdown, + .ui.top.pointing.upward.dropdown { + .menu { + border-radius: ${otherThemeVars.border.radius}; + } + } + + .ui.search > .results, + .ui.search.selection .prompt, + .ui.pointing.dropdown > .menu, + .ui.loading.form.segments:before, + .ui.secondary.menu .dropdown.item > .menu, + .ui.segments:not(.horizontal) > .segment:has(~ .tw-hidden) { + border-radius: ${otherThemeVars.border.radius}; + } +`; + +// 全部圆角替换(原CSS带!important) +export const radiusImportant = css` + .ui.dropdown .menu .menu, + .ui.dropdown .menu .left.menu, + .ui.dropdown .menu .right.menu, + .ui.dropdown > .left.menu .menu, + .ui.floating.dropdown .menu, + .ui.floating.dropdown > .menu, + .ui.dropdown .right.menu > .menu, + .ui.secondary.vertical.menu > .item { + border-radius: ${otherThemeVars.border.radius} !important; + } + // 目前为仓库列表/探索的二级菜单, 改版需要替换为全圆角 + .ui.secondary.tabular.menu .item { + border-radius: ${otherThemeVars.border.radius} !important; + &.active, + &.active:hover, + &:hover { + border-radius: ${otherThemeVars.border.radius} !important; + } + } +`; + +// 上半部分圆角替换 +export const radiusTop = css` + .ui.vertical.menu > .item, + .ui.vertical.menu > .active.item, + .ui.segments:not(.horizontal) > .segment { + &:first-child { + border-radius: ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} 0 0; + } + } + + .ui.top.attached.header, + .ui.upward.dropdown > .menu, + .ui.upward.dropdown.button:not(.pointing):not(.floating).active, + .ui[class*="top attached"].menu, + .ui[class*="top attached"].segment, + .ui[class*="top attached"].segment:last-child, + .ui.search > .results > :first-child, + .ui.modal > i.icon:first-child + *, + .ui.modal > .dimmer:first-child + i.icon + *, + .ui.modal > .dimmer:first-child + *:not(.icon), + .ui.modal > :first-child:not(.icon):not(.dimmer) { + border-radius: ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} 0 0; + } +`; + +// 上半部分圆角替换(原CSS带!important) +export const radiusTopImportant = css` + .ui.tabular.menu .item, + .ui.simple.upward.dropdown { + &.active, + &.active:hover, + &:hover { + border-radius: ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} 0 0 !important; + } + } +`; + +// 下半部分圆角替换 +export const radiusBottom = css` + .ui.attached.segment, + .ui.vertical.menu > .item, + .ui.vertical.menu > .active.item, + .ui.segments:not(.horizontal) > .segment { + &:last-child { + border-radius: 0 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius}; + } + } + + .ui.modal, + .ui.search > .results { + > :last-child { + border-radius: 0 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius}; + } + } + + .ui.selection.dropdown, + .ui.menu .dropdown.item { + .menu { + border-radius: 0 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius}; + } + } + + .ui.bottom.attached.header, + .ui[class*="bottom attached"].table, + .ui.segment[class*="bottom attached"], + .ui.attached.segment:has(+ .ui.modal), + .ui.attached.segment:has(+ .page.buttons), + .ui.attached.segment:has(+ .ui[class*="top attached"].header), + .ui.segment:has(+ .ui.segment:not(.attached)) { + border-radius: 0 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius}; + } +`; + +// 下半部分圆角替换(原CSS带!important) +export const radiusBottomImportant = css` + .ui.upward.selection.dropdown.visible, + .ui.active.upward.selection.dropdown { + border-radius: 0 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} !important; + } +`; + +// 左半部分圆角替换 +export const radiusLeft = css` + .ui.menu > .item, + .ui.action.input > .button, + .ui.action.input > .dropdown, + .ui.horizontal.segments > .segment { + &:first-child { + border-radius: ${otherThemeVars.border.radius} 0 0 ${otherThemeVars.border.radius}; + } + } + + .ui[class*="left icon"].input > i.icon, + .ui.action.input > .buttons:first-child > .button { + border-radius: ${otherThemeVars.border.radius} 0 0 ${otherThemeVars.border.radius}; + } +`; + +// 右半部分圆角替换 +export const radiusRight = css` + .ui.compact.menu .item, + .ui.compact.menu:not(.secondary) .item, + .ui.pagination.menu .item, + .ui.action.input > .button, + .ui.action.input > .dropdown, + .ui.horizontal.segments > .segment { + &:last-child { + border-radius: 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} 0; + } + } + + .ui.icon.input > i.icon, + .ui.buttons .unescape-button, + .ui.action.input > .buttons:last-child > .button { + border-radius: 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} 0; + } +`; + +// 左上圆角替换 +export const radiusTopLeft = css` + .ui.top.attached.menu > .item:first-child, + .ui.table > thead > tr:first-child > th:first-child { + border-top-left-radius: ${otherThemeVars.border.radius}; + } +`; + +// 右上圆角替换 +export const radiusTopRight = css` + .ui.table > thead > tr:first-child > th:last-child, + .ui.category.search > .results .category:first-child .name + .result { + border-top-right-radius: ${otherThemeVars.border.radius}; + } +`; + +// 左下圆角替换 +export const radiusBottomLeft = css` + .ui.table > tfoot > tr:first-child { + > th, + > td { + &:first-child { + border-bottom-left-radius: ${otherThemeVars.border.radius}; + } + } + } +`; + +// 右下圆角替换 +export const radiusBottomRight = css` + .ui.table > tfoot > tr:first-child { + > th, + > td { + &:last-child { + border-bottom-right-radius: ${otherThemeVars.border.radius}; + } + } + } + + .ui.category.search > .results .category:last-child .result:last-child { + border-bottom-right-radius: ${otherThemeVars.border.radius}; + } +`; + +// only-child 顺序最后生效 +export const onlyChild = css` + .ui.vertical.menu > .active.item:only-child, + .ui.segments:not(.horizontal) > .segment:only-child { + border-radius: ${otherThemeVars.border.radius}; + } + + .ui.table > thead > tr:first-child > th:only-child { + border-radius: ${otherThemeVars.border.radius} ${otherThemeVars.border.radius} 0 0; + } + + .ui.table > tfoot > tr:first-child { + > th, + > td { + &:only-child { + border-radius: 0 0 ${otherThemeVars.border.radius} ${otherThemeVars.border.radius}; + } + } + } +`; + +// 修复一些情况下圆角边框线被覆盖的问题 +export const fixRadius = css` + // 评论列表标题 + .repository.view.issue .comment-list .comment > .content > div:first-child { + border-top-left-radius: ${otherThemeVars.border.radius}; + border-top-right-radius: ${otherThemeVars.border.radius}; + } + // 评论列表内容 + .repository.view.issue .comment-list .comment > .content > div:last-child { + border-bottom-left-radius: ${otherThemeVars.border.radius}; + border-bottom-right-radius: ${otherThemeVars.border.radius}; + } +`; + +// 修复一些根本无法理解的生效顺序 +export const fixWhyRadius = css` + // Issue 菜单第一个按钮 + .ui.secondary.menu .item { + border-radius: ${otherThemeVars.border.radius}; + } +`; diff --git a/styles/public/text.ts b/styles/public/text.ts new file mode 100644 index 0000000..546bbb0 --- /dev/null +++ b/styles/public/text.ts @@ -0,0 +1,19 @@ +import { css, themeVars } from "src/types/vars"; + +export const red = css` + .text.red .svg, + .text.red.svg { + // 关闭工单按钮设置为紫色 + &.octicon-issue-closed { + color: ${themeVars.github.fgColor.done} !important; + } + } +`; + +export const grey = css` + // 默认颜色是 --color-text-light, 主题下此颜色是亮白色, 修改为灰色 + // release 页面下一些描述信息的文本颜色 + .text.grey { + color: ${themeVars.color.text.light.num1} !important; + } +`; diff --git a/styles/public/tippy.ts b/styles/public/tippy.ts new file mode 100644 index 0000000..0ce2409 --- /dev/null +++ b/styles/public/tippy.ts @@ -0,0 +1,50 @@ +import { animationDown } from "src/core/theme"; +import { css, otherThemeVars, themeVars } from "src/types/vars"; + +// 一些界面内的提示框, 比如克隆按钮, PR信息, Runner信息 等 +export const tippyBox = css` + .tippy-box { + margin-top: -3px; + border-radius: ${otherThemeVars.border.radius}; + overflow: hidden; + animation: ${animationDown}; + // 克隆菜单和PR提示框为 default + &[data-theme="default"], + // 带标题的提示框 (Runner信息) + &[data-theme="box-with-header"] { + border: unset; + box-shadow: ${themeVars.github.shadow.floating.small}; + } + &[data-theme="default"] { + border-radius: 12px; + } + &[data-theme="box-with-header"] { + .tippy-content { + background-color: ${themeVars.color.menu}; + .ui.attached.header { + background-color: ${themeVars.color.body}; + } + } + } + // 差异对比中文件路径行右侧的三个点菜单 + &[data-theme="menu"] { + .tippy-content { + padding: 8px; + .item { + border-radius: ${otherThemeVars.border.radius}; + &:hover { + background-color: ${themeVars.github.control.transparent.bgColor.hover}; + } + } + } + } + // 专门用于提示信息的提示框, 比如提交的具体时间, 任务状态等 + &[data-theme="tooltip"] { + .tippy-content { + font-size: 12px; + font-weight: 400; + padding: 4px 8px; + } + } + } +`; diff --git a/styles/public/transition.ts b/styles/public/transition.ts new file mode 100644 index 0000000..19d4654 --- /dev/null +++ b/styles/public/transition.ts @@ -0,0 +1,61 @@ +import { css } from "src/types/vars"; + +// 简单的渐变过渡 +export const transition = css` + // 差异对比的代码折叠按钮 + .code-expander-button, + // 仓库 README 头部的按钮 + .file-header-left, + .file-header-right, + // 仪表板仓库列表 + .ui.attached.segment.table ul li, + // Issue 列表 + .issue-list-toolbar .item, + .flex-list#issue-list > .flex-item, + // 分页菜单 + .ui.borderless.pagination.menu .item, + // 迁移的元素 + #navbar .item, + .header-wrapper .ui.tabular.menu .item, + .job-step-summary, + .job-step-logs, + .job-brief-item, + .repo-file-cell, + .tippy-box .flex-items-block .item, + .clone-panel-tab .item, + .ui.form select, + .ui.label, + .ui.modal, + .ui.checkbox label:before, + .ui.checkbox input:checked ~ label:before, + .ui.checkbox input:not([type="radio"]):indeterminate ~ label:before, + .ui.selection.dropdown, + .ui.selection.active.dropdown, + .ui.selection.active.dropdown:hover, + .ui.selection.active.dropdown .menu, + .ui.selection.active.dropdown:hover .menu, + .ui.vertical.menu .header.item, + .ui.secondary.menu .item { + transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); + } + // Gitea 表单元素原始过渡覆盖 + input, + textarea, + tr, + .ui.input textarea, + .ui.form textarea, + .ui.form input:not([type]), + .ui.form input[type="date"], + .ui.form input[type="datetime-local"], + .ui.form input[type="email"], + .ui.form input[type="number"], + .ui.form input[type="password"], + .ui.form input[type="search"], + .ui.form input[type="tel"], + .ui.form input[type="time"], + .ui.form input[type="text"], + .ui.form input[type="file"], + .ui.form input[type="url"] { + transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); + } +`; diff --git a/themes/colorblind-dark.css.ts b/themes/colorblind-dark.css.ts new file mode 100644 index 0000000..be94d6d --- /dev/null +++ b/themes/colorblind-dark.css.ts @@ -0,0 +1,82 @@ +/** + * @author lutinglt + */ + +import { defineTheme, themeVars, type Chroma } from "src"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; +import { prettylights2Chroma, type prettylightsColor } from "src/core/prettylights"; +import { darkGithubColors } from "themes/dark"; + +export const colorblindDarkGithubColors: GithubColor = { + isDarkTheme: true, + display: darkGithubColors.display, + diffBlob: { + addtionNum: { bgColor: "#58a6ff4d" }, + addtionWord: { bgColor: "#388bfd66" }, + deletionNum: { bgColor: "#db6d284d" }, + deletionWord: { bgColor: "#db6d2866" }, + hunkNum: { bgColor: { rest: "#2f3742" } }, + }, + fgColor: { + ...darkGithubColors.fgColor, + danger: "#f0883e", + success: "#58a6ff", + }, + bgColor: { + ...darkGithubColors.bgColor, + danger: { muted: "#db6d281a" }, + success: { emphasis: "#1f6feb", muted: "#388bfd33" }, + }, + borderColor: { + ...darkGithubColors.borderColor, + success: { emphasis: "#1f6feb" }, + }, + button: { + primary: { + fgColor: { accent: "#58a6ff", rest: "#ffffff" }, + bgColor: { rest: themeVars.github.bgColor.success.emphasis, hover: "#2a7aef" }, + }, + danger: { fgColor: { rest: "#f0883e", hover: "#ffffff" }, bgColor: { hover: "#9b4215" } }, + star: darkGithubColors.button.star, + }, + control: darkGithubColors.control, + shadow: darkGithubColors.shadow, + overlay: darkGithubColors.overlay, + underlineNav: darkGithubColors.underlineNav, + contribution: darkGithubColors.contribution, +}; + +export const colorblindDarkPrettylights: prettylightsColor = { + syntax: { + brackethighlighter: { angle: "#9198a1", unmatched: "#db6d28" }, + carriage: { return: { bg: "#9b4215", text: "#f0f6fc" } }, + comment: "#9198a1", + constant: "#79c0ff", + constantOtherReferenceLink: "#a5d6ff", + entity: "#d2a8ff", + entityTag: "#a5d6ff", + invalid: { illegal: { bg: "#762d0a", text: "#f0f6fc" } }, + keyword: "#f0883e", + markup: { + bold: "#f0f6fc", + changed: { bg: "#5a1e02", text: "#ffdfb6" }, + deleted: { bg: "#5a1e02", text: "#ffdfb6" }, + heading: "#1f6feb", + ignored: { bg: "#1158c7", text: "#f0f6fc" }, + inserted: { bg: "#0c2d6b", text: "#cae8ff" }, + italic: "#f0f6fc", + list: "#f2cc60", + }, + metaDiffRange: "#d2a8ff", + storageModifierImport: "#f0f6fc", + string: "#a5d6ff", + stringRegexp: "#a5d6ff", + sublimelinterGutterMark: "#3d444d", + variable: "#ffa657", + }, +}; + +export const colorblindDarkColors = github2ThemeColor(colorblindDarkGithubColors); +export const colorblindDarkChroma: Chroma = prettylights2Chroma(colorblindDarkPrettylights); + +export default defineTheme(colorblindDarkColors, colorblindDarkChroma); diff --git a/themes/colorblind-light.css.ts b/themes/colorblind-light.css.ts new file mode 100644 index 0000000..c35c4f0 --- /dev/null +++ b/themes/colorblind-light.css.ts @@ -0,0 +1,82 @@ +/** + * @author lutinglt + */ + +import { defineTheme, themeVars, type Chroma } from "src"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; +import { prettylights2Chroma, type prettylightsColor } from "src/core/prettylights"; +import { lightGithubColors } from "themes/light"; + +export const colorblindLightGithubColors: GithubColor = { + isDarkTheme: false, + display: lightGithubColors.display, + diffBlob: { + addtionNum: { bgColor: "#b6e3ff" }, + addtionWord: { bgColor: "#b6e3ff" }, + deletionNum: { bgColor: "#ffd8b5" }, + deletionWord: { bgColor: "#ffd8b5" }, + hunkNum: { bgColor: { rest: "#e6eaef" } }, + }, + fgColor: { + ...lightGithubColors.fgColor, + danger: "#bc4c00", + success: "#0969da", + }, + bgColor: { + ...lightGithubColors.bgColor, + danger: { muted: "#fff1e5" }, + success: { emphasis: "#0969da", muted: "#ddf4ff" }, + }, + borderColor: { + ...lightGithubColors.borderColor, + success: { emphasis: "#0969da" }, + }, + button: { + primary: { + fgColor: { accent: "#0969da", rest: "#ffffff" }, + bgColor: { rest: themeVars.github.bgColor.success.emphasis, hover: "#0864d1" }, + }, + danger: { fgColor: { rest: "#bc4c00", hover: "#ffffff" }, bgColor: { hover: "#bc4c00" } }, + star: lightGithubColors.button.star, + }, + control: lightGithubColors.control, + shadow: lightGithubColors.shadow, + overlay: lightGithubColors.overlay, + underlineNav: lightGithubColors.underlineNav, + contribution: lightGithubColors.contribution, +}; + +export const colorblindLightPrettylights: prettylightsColor = { + syntax: { + brackethighlighter: { angle: "#59636e", unmatched: "#762c00" }, + carriage: { return: { bg: "#bc4c00", text: "#f6f8fa" } }, + comment: "#59636e", + constant: "#0550ae", + constantOtherReferenceLink: "#0a3069", + entity: "#6639ba", + entityTag: "#0550ae", + invalid: { illegal: { bg: "#762c00", text: "#f6f8fa" } }, + keyword: "#bc4c00", + markup: { + bold: "#1f2328", + changed: { bg: "#ffd8b5", text: "#953800" }, + deleted: { bg: "#fff1e5", text: "#762c00" }, + heading: "#0550ae", + ignored: { bg: "#0550ae", text: "#d1d9e0" }, + inserted: { bg: "#ddf4ff", text: "#0550ae" }, + italic: "#1f2328", + list: "#3b2300", + }, + metaDiffRange: "#8250df", + storageModifierImport: "#1f2328", + string: "#0a3069", + stringRegexp: "#0550ae", + sublimelinterGutterMark: "#818b98", + variable: "#953800", + }, +}; + +export const colorblindLightColors = github2ThemeColor(colorblindLightGithubColors); +export const colorblindLightChroma: Chroma = prettylights2Chroma(colorblindLightPrettylights); + +export default defineTheme(colorblindLightColors, colorblindLightChroma); diff --git a/themes/dark.css.ts b/themes/dark.css.ts new file mode 100644 index 0000000..9dcf261 --- /dev/null +++ b/themes/dark.css.ts @@ -0,0 +1,88 @@ +/** + * @author lutinglt + */ + +import { defineTheme, themeVars } from "src"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; + +export const darkGithubColors: GithubColor = { + isDarkTheme: true, + display: { + blue: { fgColor: "#4493f8" }, + brown: { fgColor: "#b69a6d" }, + cyan: { fgColor: "#07ace4" }, + indigo: { fgColor: "#9899ec" }, + lemon: { fgColor: "#ba9b12" }, + olive: { fgColor: "#a2a626" }, + teal: { fgColor: "#1cb0ab" }, + }, + diffBlob: { + addtionNum: { bgColor: "#3fb9504d" }, + addtionWord: { bgColor: "#2ea04366" }, + deletionNum: { bgColor: "#f851494d" }, + deletionWord: { bgColor: "#f8514966" }, + hunkNum: { bgColor: { rest: "#0c2d6b" } }, + }, + fgColor: { + accent: "#4493f8", + attention: "#d29922", + danger: "#f85149", + default: "#f0f6fc", + disabled: "#656c76", + done: "#ab7df8", + muted: "#9198a1", + neutral: "#9198a1", + severe: "#db6d28", + sponsors: "#db61a2", + success: "#3fb950", + black: "#010409", + white: "#ffffff", + onEmphasis: "#ffffff", + }, + bgColor: { + accent: { emphasis: "#1f6feb", muted: "#388bfd1a" }, + attention: { muted: "#bb800926" }, + danger: { muted: "#f851491a" }, + default: "#0d1117", + done: { emphasis: "#8957e5" }, + emphasis: "#3d444d", + muted: "#151b23", + neutral: { muted: "#656c7633" }, + success: { emphasis: "#238636", muted: "#2ea04326" }, + inset: "#010409", + }, + borderColor: { + accent: { emphasis: "#1f6feb" }, + attention: { emphasis: "#9e6a03" }, + default: "#3d444d", + done: { emphasis: "#8957e5" }, + success: { emphasis: "#238636" }, + muted: "#3d444db3", + translucent: "#ffffff26", + }, + button: { + primary: { + fgColor: { accent: "#3fb950", rest: "#ffffff" }, + bgColor: { rest: themeVars.github.bgColor.success.emphasis, hover: "#29903b" }, + }, + danger: { fgColor: { rest: "#fa5e55", hover: "#ffffff" }, bgColor: { hover: "#b62324" } }, + star: { iconColor: "#e3b341" }, + }, + control: { + bgColor: { active: "#2a313c", hover: "#262c36", rest: "#212830" }, + transparent: { bgColor: { active: "#656c7640", hover: "#656c7633", selected: "#656c761a" } }, + }, + shadow: { floating: { small: "#01040966" }, resting: { small: "#01040999" } }, + overlay: { backdrop: { bgColor: "#21283066" } }, + underlineNav: { borderColor: { active: "#f78166" } }, + contribution: { + default: { + bgColor: { num0: "#151b23", num1: "#033a16", num2: "#196c2e", num3: "#2ea043", num4: "#56d364" }, + borderColor: { num0: "#0104090d" }, + }, + }, +}; + +export const darkColors = github2ThemeColor(darkGithubColors); + +export default defineTheme(darkColors); diff --git a/themes/light.css.ts b/themes/light.css.ts new file mode 100644 index 0000000..7f41310 --- /dev/null +++ b/themes/light.css.ts @@ -0,0 +1,88 @@ +/** + * @author lutinglt + */ + +import { defineTheme, themeVars } from "src"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; + +export const lightGithubColors: GithubColor = { + isDarkTheme: false, + display: { + blue: { fgColor: "#0969da" }, + brown: { fgColor: "#755f43" }, + cyan: { fgColor: "#006a80" }, + indigo: { fgColor: "#494edf" }, + lemon: { fgColor: "#786002" }, + olive: { fgColor: "#56682c" }, + teal: { fgColor: "#106e75" }, + }, + diffBlob: { + addtionNum: { bgColor: "#aceebb" }, + addtionWord: { bgColor: "#aceebb" }, + deletionNum: { bgColor: "#ffcecb" }, + deletionWord: { bgColor: "#ffcecb" }, + hunkNum: { bgColor: { rest: "#b6e3ff" } }, + }, + fgColor: { + accent: "#0969da", + attention: "#9a6700", + danger: "#d1242f", + default: "#1f2328", + disabled: "#818b98", + done: "#8250df", + muted: "#59636e", + neutral: "#59636e", + severe: "#bc4c00", + sponsors: "#bf3989", + success: "#1a7f37", + black: "#1f2328", + white: "#ffffff", + onEmphasis: "#ffffff", + }, + bgColor: { + accent: { emphasis: "#0969da", muted: "#ddf4ff" }, + attention: { muted: "#fff8c5" }, + danger: { muted: "#ffebe9" }, + default: "#ffffff", + done: { emphasis: "#8250df" }, + emphasis: "#25292e", + muted: "#f6f8fa", + neutral: { muted: "#818b981f" }, + success: { emphasis: "#1f883d", muted: "#dafbe1" }, + inset: "#f6f8fa", + }, + borderColor: { + accent: { emphasis: "#0969da" }, + attention: { emphasis: "#9a6700" }, + default: "#d1d9e0", + done: { emphasis: "#8250df" }, + success: { emphasis: "#1a7f37" }, + muted: "#d1d9e0b3", + translucent: "#1f232826", + }, + button: { + primary: { + fgColor: { accent: "#1a7f37", rest: "#ffffff" }, + bgColor: { rest: themeVars.github.bgColor.success.emphasis, hover: "#1c8139" }, + }, + danger: { fgColor: { rest: "#d1242f", hover: "#ffffff" }, bgColor: { hover: "#cf222e" } }, + star: { iconColor: "#eac54f" }, + }, + control: { + bgColor: { active: "#e6eaef", hover: "#eff2f5", rest: "#f6f8fa" }, + transparent: { bgColor: { active: "#818b9826", hover: "#818b981a", selected: "#818b9826" } }, + }, + shadow: { floating: { small: "#25292e0a" }, resting: { small: "#1f23280f" } }, + overlay: { backdrop: { bgColor: "#c8d1da66" } }, + underlineNav: { borderColor: { active: "#fd8c73" } }, + contribution: { + default: { + bgColor: { num0: "#eff2f5", num1: "#aceebb", num2: "#4ac26b", num3: "#2da44e", num4: "#116329" }, + borderColor: { num0: "#1f23280d" }, + }, + }, +}; + +export const lightColors = github2ThemeColor(lightGithubColors); + +export default defineTheme(lightColors); diff --git a/themes/pink-dark.css.ts b/themes/pink-dark.css.ts new file mode 100644 index 0000000..18abc51 --- /dev/null +++ b/themes/pink-dark.css.ts @@ -0,0 +1,26 @@ +/** + * @author lutinglt + */ + +import { defineTheme } from "src"; +import { display2GithubColor, type DisplayColor } from "src/core/display"; +import { github2ThemeColor } from "src/core/github"; +import { darkGithubColors } from "themes/dark"; + +export const pinkDarkDisplayColors: DisplayColor = { + num0: "#2d1524", + num1: "#451c35", + num2: "#65244a", + num3: "#842a5d", + num4: "#ac2f74", + num5: "#d34591", + num6: "#e57bb2", + num7: "#ec8dbd", + num8: "#f4a9cd", + num9: "#f9bed9", +}; + +export const pinkDarkGithubColors = display2GithubColor(pinkDarkDisplayColors, darkGithubColors); +export const pinkDarkColors = github2ThemeColor(pinkDarkGithubColors); + +export default defineTheme(pinkDarkColors); diff --git a/themes/pink-light.css.ts b/themes/pink-light.css.ts new file mode 100644 index 0000000..58671f5 --- /dev/null +++ b/themes/pink-light.css.ts @@ -0,0 +1,26 @@ +/** + * @author lutinglt + */ + +import { defineTheme } from "src"; +import { display2GithubColor, type DisplayColor } from "src/core/display"; +import { github2ThemeColor } from "src/core/github"; +import { lightGithubColors } from "themes/light"; + +export const pinkLightDisplayColors: DisplayColor = { + num0: "#ffe5f1", + num1: "#fdc9e2", + num2: "#f8a5cf", + num3: "#f184bc", + num4: "#e55da5", + num5: "#ce2c85", + num6: "#b12f79", + num7: "#8e2e66", + num8: "#6e2b53", + num9: "#4d233d", +}; + +export const pinkLightGithubColors = display2GithubColor(pinkLightDisplayColors, lightGithubColors); +export const pinkLightColors = github2ThemeColor(pinkLightGithubColors); + +export default defineTheme(pinkLightColors); diff --git a/themes/pink-soft-dark.css.ts b/themes/pink-soft-dark.css.ts new file mode 100644 index 0000000..c07c9a5 --- /dev/null +++ b/themes/pink-soft-dark.css.ts @@ -0,0 +1,14 @@ +/** + * @author lutinglt + */ + +import { defineTheme } from "src"; +import { display2GithubColor } from "src/core/display"; +import { github2ThemeColor } from "src/core/github"; +import { softDarkGithubColors } from "themes/soft-dark"; +import { pinkDarkDisplayColors } from "./pink-dark.css"; + +export const pinkSoftDarkGithubColors = display2GithubColor(pinkDarkDisplayColors, softDarkGithubColors, true); +export const pinkSoftDarkColors = github2ThemeColor(pinkSoftDarkGithubColors); + +export default defineTheme(pinkSoftDarkColors); diff --git a/themes/soft-dark.css.ts b/themes/soft-dark.css.ts new file mode 100644 index 0000000..1770a1f --- /dev/null +++ b/themes/soft-dark.css.ts @@ -0,0 +1,113 @@ +/** + * @author lutinglt + */ + +import { defineTheme, themeVars, type Chroma } from "src"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; +import { prettylights2Chroma, type prettylightsColor } from "src/core/prettylights"; +import { darkGithubColors } from "themes/dark"; + +export const softDarkGithubColors: GithubColor = { + isDarkTheme: true, + display: darkGithubColors.display, + diffBlob: { + addtionNum: { bgColor: "#57ab5a4d" }, + addtionWord: { bgColor: "#46954a66" }, + deletionNum: { bgColor: "#e5534b4d" }, + deletionWord: { bgColor: "#e5534b66" }, + hunkNum: { bgColor: { rest: "#143d79" } }, + }, + fgColor: { + accent: "#478be6", + attention: "#c69026", + danger: "#e5534b", + default: "#d1d7e0", + disabled: "#656c76", + done: "#986ee2", + muted: "#9198a1", + neutral: "#9198a1", + severe: "#cc6b2c", + sponsors: "#c96198", + success: "#57ab5a", + black: "#010409", + white: "#cdd9e5", + onEmphasis: "#ffffff", + }, + bgColor: { + accent: { emphasis: "#316dca", muted: "#4184e41a" }, + attention: { muted: "#ae7c1426" }, + danger: { muted: "#e5534b1a" }, + default: "#212830", + done: { emphasis: "#8256d0" }, + emphasis: "#3d444d", + muted: "#262c36", + neutral: { muted: "#656c7633" }, + success: { emphasis: "#347d39", muted: "#46954a26" }, + inset: "#151b23", + }, + borderColor: { + accent: { emphasis: "#316dca" }, + attention: { emphasis: "#966600" }, + default: "#3d444d", + done: { emphasis: "#8256d0" }, + success: { emphasis: "#347d39" }, + muted: "#3d444db3", + translucent: "#cdd9e526", + }, + button: { + primary: { + fgColor: { accent: "#57ab5a", rest: "#ffffff" }, + bgColor: { rest: themeVars.github.bgColor.success.emphasis, hover: "#3b8640" }, + }, + danger: { fgColor: { rest: "#ea5c53", hover: "#ffffff" }, bgColor: { hover: "#ad2e2c" } }, + star: { iconColor: "#daaa3f" }, + }, + control: { + bgColor: { active: "#3d444d", hover: "#2f3742", rest: "#2a313c" }, + transparent: { bgColor: { active: "#656c7633", hover: "#656c7626", selected: "#656c761a" } }, + }, + shadow: { floating: { small: "#01040966" }, resting: { small: "#01040999" } }, + overlay: { backdrop: { bgColor: "#262c3666" } }, + underlineNav: { borderColor: { active: "#ec775c" } }, + contribution: { + default: { + bgColor: { num0: "#2a313c", num1: "#1b4721", num2: "#2b6a30", num3: "#46954a", num4: "#6bc46d" }, + borderColor: { num0: "#0104090d" }, + }, + }, +}; + +export const softDarkPrettylights: prettylightsColor = { + syntax: { + brackethighlighter: { angle: "#9198a1", unmatched: "#e5534b" }, + carriage: { return: { bg: "#ad2e2c", text: "#f0f6fc" } }, + comment: "#9198a1", + constant: "#6cb6ff", + constantOtherReferenceLink: "#96d0ff", + entity: "#dcbdfb", + entityTag: "#8ddb8c", + invalid: { illegal: { bg: "#922323", text: "#f0f6fc" } }, + keyword: "#f47067", + markup: { + bold: "#f0f6fc", + changed: { bg: "#682d0f", text: "#ffddb0" }, + deleted: { bg: "#78191b", text: "#ffd8d3" }, + heading: "#316dca", + ignored: { bg: "#255ab2", text: "#f0f6fc" }, + inserted: { bg: "#1b4721", text: "#b4f1b4" }, + italic: "#f0f6fc", + list: "#eac55f", + }, + metaDiffRange: "#dcbdfb", + storageModifierImport: "#f0f6fc", + string: "#96d0ff", + stringRegexp: "#8ddb8c", + sublimelinterGutterMark: "#3d444d", + variable: "#f69d50", + }, +}; + +export const softDarkColors = github2ThemeColor(softDarkGithubColors); +export const softDarkChroma: Chroma = prettylights2Chroma(softDarkPrettylights); + +export default defineTheme(softDarkColors, softDarkChroma); diff --git a/themes/tritanopia-dark.css.ts b/themes/tritanopia-dark.css.ts new file mode 100644 index 0000000..a0cd9f5 --- /dev/null +++ b/themes/tritanopia-dark.css.ts @@ -0,0 +1,49 @@ +/** + * @author lutinglt + */ + +import { defineTheme, type Chroma } from "src"; +import { darkPrettylights } from "src/core/chroma"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; +import { prettylights2Chroma, type prettylightsColor } from "src/core/prettylights"; +import { colorblindDarkGithubColors, colorblindDarkPrettylights } from "themes/colorblind-dark"; +import { darkGithubColors } from "themes/dark"; + +export const tritanopiaDarkGithubColors: GithubColor = { + ...colorblindDarkGithubColors, + diffBlob: { + ...colorblindDarkGithubColors.diffBlob, + deletionNum: darkGithubColors.diffBlob.deletionNum, + deletionWord: darkGithubColors.diffBlob.deletionWord, + }, + fgColor: { + ...colorblindDarkGithubColors.fgColor, + danger: darkGithubColors.fgColor.danger, + }, + bgColor: { + ...colorblindDarkGithubColors.bgColor, + danger: darkGithubColors.bgColor.danger, + }, + button: { + ...colorblindDarkGithubColors.button, + danger: darkGithubColors.button.danger, + }, +}; + +export const tritanopiaDarkPrettylights: prettylightsColor = { + syntax: { + ...darkPrettylights.syntax, + markup: { + ...darkPrettylights.syntax.markup, + changed: { bg: "#67060c", text: "#ffdcd7" }, + inserted: colorblindDarkPrettylights.syntax.markup.inserted, + }, + stringRegexp: colorblindDarkPrettylights.syntax.stringRegexp, + variable: "#ffa198", + }, +}; + +export const tritanopiaDarkColors = github2ThemeColor(tritanopiaDarkGithubColors); +export const tritanopiaDarkChroma: Chroma = prettylights2Chroma(tritanopiaDarkPrettylights); + +export default defineTheme(tritanopiaDarkColors, tritanopiaDarkChroma); diff --git a/themes/tritanopia-light.css.ts b/themes/tritanopia-light.css.ts new file mode 100644 index 0000000..17bea73 --- /dev/null +++ b/themes/tritanopia-light.css.ts @@ -0,0 +1,49 @@ +/** + * @author lutinglt + */ + +import { defineTheme, type Chroma } from "src"; +import { lightPrettylights } from "src/core/chroma"; +import { github2ThemeColor, type GithubColor } from "src/core/github"; +import { prettylights2Chroma, type prettylightsColor } from "src/core/prettylights"; +import { colorblindLightGithubColors, colorblindLightPrettylights } from "themes/colorblind-light"; +import { lightGithubColors } from "themes/light"; + +export const tritanopiaLightGithubColors: GithubColor = { + ...colorblindLightGithubColors, + diffBlob: { + ...colorblindLightGithubColors.diffBlob, + deletionNum: lightGithubColors.diffBlob.deletionNum, + deletionWord: lightGithubColors.diffBlob.deletionWord, + }, + fgColor: { + ...colorblindLightGithubColors.fgColor, + danger: lightGithubColors.fgColor.danger, + }, + bgColor: { + ...colorblindLightGithubColors.bgColor, + danger: lightGithubColors.bgColor.danger, + }, + button: { + ...colorblindLightGithubColors.button, + danger: lightGithubColors.button.danger, + }, +}; + +export const tritanopiaLightPrettylights: prettylightsColor = { + syntax: { + ...lightPrettylights.syntax, + markup: { + ...lightPrettylights.syntax.markup, + changed: { bg: "#ffcecb", text: "#a40e26" }, + inserted: colorblindLightPrettylights.syntax.markup.inserted, + }, + stringRegexp: colorblindLightPrettylights.syntax.stringRegexp, + variable: "#a40e26", + }, +}; + +export const tritanopiaLightColors = github2ThemeColor(tritanopiaLightGithubColors); +export const tritanopiaLightChroma: Chroma = prettylights2Chroma(tritanopiaLightPrettylights); + +export default defineTheme(tritanopiaLightColors, tritanopiaLightChroma); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a6157b7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "plugins": [ + { + "name": "typescript-plugin-css-modules" + }, + { + "name": "typescript-styled-plugin", + "lint": { + "validProperties": "rx,ry" + } + } + ], + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "useDefineForClassFields": true, + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "src/*": ["src/*"], + "styles/*": ["styles/*"], + "themes/*": ["themes/*", "themes/*.css"] + } + }, + "include": ["src", "styles", "themes", "vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..855c280 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,61 @@ +import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; +import linaria from "@wyw-in-js/vite"; +import * as dotenv from "dotenv"; +import { Features } from "lightningcss"; +import { createRequire } from "node:module"; +import path from "node:path"; +import * as sass from "sass-embedded"; +import { defineConfig } from "vite"; +import { themeInput, themePlugin } from "./src/core/vite"; + +dotenv.config({ quiet: true }); + +const require = createRequire(import.meta.url); + +const outDir = "dist"; // 输出目录 +const themesDir = "themes"; // 颜色主题目录 + +export default defineConfig(({ mode }) => { + return { + resolve: { + alias: { + src: path.resolve(__dirname, "src"), + styles: path.resolve(__dirname, "styles"), + themes: path.resolve(__dirname, "themes"), + }, + extensions: [".js", ".ts", ".css.ts"], + }, + css: { + transformer: "lightningcss", + lightningcss: { + minify: true, + exclude: Features.LightDark, // 不生成 lightningcss 的主题变量 + }, + }, + plugins: [ + linaria({ + include: ["**/*.ts"], + babelOptions: { + presets: ["@babel/preset-typescript"], + }, + preprocessor: (_selector, cssText) => sass.compileString(cssText).css, // 默认为全局样式并使用 sass-embedded 预处理 css + tagResolver: (source, tag) => + // 识别从 src 导出的 css 标签,使用 @linaria/core/processors/css 处理 + source === "src/types/vars" && tag === "css" ? require.resolve("@linaria/core/processors/css") : null, + }), + vanillaExtractPlugin(), + themePlugin(), + ], + build: { + cssMinify: "lightningcss", + cssCodeSplit: true, + outDir: outDir, + rollupOptions: { + input: themeInput(outDir, themesDir, mode), + output: { + assetFileNames: "[name].[ext]", + }, + }, + }, + }; +});