feat: 初始化 Halo 暗色模式插件

- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试
- Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面
- 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等)
- 设计文档和调查文档
- Halo 插件/主题开发 Agent Skills

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-06 21:03:00 +08:00
commit edbf78f236
115 changed files with 14745 additions and 0 deletions
@@ -0,0 +1,111 @@
# Model Metadata (Annotations)
Themes can extend built-in Halo models with custom fields via `AnnotationSetting` resources (e.g. adding an icon to menu items, or a download URL to posts), then read those values in templates via the `#annotations` utility.
## Defining a Metadata Form (AnnotationSetting)
Create a file (any name) in the theme root, e.g. `annotation-setting.yaml`, and declare an `AnnotationSetting` resource:
```yaml
apiVersion: v1alpha1
kind: AnnotationSetting
metadata:
name: theme-foo-menuitem-abc123 # recommended: add theme prefix + random suffix to avoid conflicts
spec:
targetRef:
group: ""
kind: MenuItem
formSchema:
- $formkit: text
name: icon
label: Menu icon class
value: ""
```
Multiple models can be declared in the same file separated by `---`:
```yaml
apiVersion: v1alpha1
kind: AnnotationSetting
metadata:
name: theme-foo-post-abc123
spec:
targetRef:
group: content.halo.run
kind: Post
formSchema:
- $formkit: text
name: download_url
label: Download URL
value: ""
---
apiVersion: v1alpha1
kind: AnnotationSetting
metadata:
name: theme-foo-menuitem-abc123
spec:
targetRef:
group: ""
kind: MenuItem
formSchema:
- $formkit: text
name: icon
label: Icon
value: ""
```
### Supported Models
| Model | `group` | `kind` |
| ------------- | ------------------ | ------------ |
| Post | `content.halo.run` | `Post` |
| Single page | `content.halo.run` | `SinglePage` |
| Post category | `content.halo.run` | `Category` |
| Post tag | `content.halo.run` | `Tag` |
| Menu item | `""` | `MenuItem` |
| User | `""` | `User` |
### Notes
- All values in `metadata.annotations` are **strings**, so form values must also be strings.
- Do not use components with non-string output such as `number`, `group`, or `repeater`.
- For `checkbox`, explicitly set `on-value` / `off-value` to string values (e.g. `"true"` / `"false"`).
- Use a theme-name prefix plus a random suffix for `metadata.name` to avoid conflicts with other themes/plugins, e.g. `theme-earth-post-wanfs5`.
## Reading Metadata in Templates
Halo provides a `#annotations` utility object in Thymeleaf with three methods:
### `#annotations.get(object, key)` — Get a value
```html
<div th:with="menu = ${menuFinder.getPrimary()}">
<li th:each="item : ${menu.menuItems}">
<i th:class="${#annotations.get(item, 'icon')}"></i>
<a th:href="${item.status.href}" th:text="${item.status.displayName}"></a>
</li>
</div>
```
### `#annotations.getOrDefault(object, key, defaultValue)` — Get a value with fallback
```html
<i th:class="${#annotations.getOrDefault(menuItem, 'icon', 'fa fa-link')}"></i>
```
### `#annotations.contains(object, key)` — Check if a key exists
```html
<i
th:if="${#annotations.contains(menuItem, 'icon')}"
th:class="${#annotations.get(menuItem, 'icon')}"
></i>
```
## Online Docs
> **`AnnotationSetting` spec and supported models may change across Halo versions. Fetch the relevant doc if unsure about supported `group`/`kind` combinations or form schema constraints.**
- Using metadata in templates: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/annotations.md
- Defining annotation forms: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/annotations-form.md