edbf78f236
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试 - Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面 - 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等) - 设计文档和调查文档 - Halo 插件/主题开发 Agent Skills Co-Authored-By: Claude <noreply@anthropic.com>
272 lines
12 KiB
Markdown
272 lines
12 KiB
Markdown
# Directory Structure & Configuration
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
my-theme/
|
|
├── templates/
|
|
│ ├── assets/ # Static assets (CSS/JS/images etc.) — must be placed here
|
|
│ │ ├── css/
|
|
│ │ │ └── style.css
|
|
│ │ └── js/
|
|
│ │ └── main.js
|
|
│ ├── index.html # Home page
|
|
│ ├── post.html # Post detail
|
|
│ ├── page.html # Single page detail
|
|
│ ├── archives.html # Post archives
|
|
│ ├── tags.html # Tag listing
|
|
│ ├── tag.html # Tag archive
|
|
│ ├── categories.html # Category listing
|
|
│ └── category.html # Category archive
|
|
├── screenshot.png # Optional Console preview image (Halo 2.25+)
|
|
├── theme.yaml # Theme configuration (required)
|
|
└── settings.yaml # Theme settings form definition (optional)
|
|
```
|
|
|
|
> **Important**: The theme folder name must match the `metadata.name` field in `theme.yaml`; otherwise some assets may fail to load.
|
|
|
|
Halo 2.25+ recognizes the first readable root preview image in this order:
|
|
`screenshot.png`, `screenshot.jpeg`, `screenshot.jpg`, `screenshot.webp`. The
|
|
resolved URL is exposed as `Theme.status.screenshot`.
|
|
|
|
## theme.yaml
|
|
|
|
The theme root directory must contain `theme.yaml`. Minimal runnable config:
|
|
|
|
```yaml
|
|
apiVersion: theme.halo.run/v1alpha1
|
|
kind: Theme
|
|
metadata:
|
|
name: theme-foo # must match the theme folder name
|
|
spec:
|
|
displayName: My Theme
|
|
version: 1.0.0
|
|
requires: ">=2.0.0"
|
|
```
|
|
|
|
Full field example:
|
|
|
|
```yaml
|
|
apiVersion: theme.halo.run/v1alpha1
|
|
kind: Theme
|
|
metadata:
|
|
name: theme-foo
|
|
spec:
|
|
displayName: My Theme
|
|
author:
|
|
name: Author Name
|
|
website: https://example.com
|
|
description: An example theme
|
|
logo: https://example.com/logo.png
|
|
homepage: https://github.com/example/theme-foo
|
|
repo: https://github.com/example/theme-foo.git
|
|
issues: https://github.com/example/theme-foo/issues
|
|
settingName: "theme-foo-setting" # must match metadata.name in settings.yaml
|
|
configMapName: "theme-foo-configMap"
|
|
customTemplates: # optional
|
|
post:
|
|
- name: Documentation
|
|
description: Article in documentation format
|
|
screenshot:
|
|
file: post_documentation.html
|
|
category:
|
|
- name: Knowledge Base
|
|
description: Knowledge base category
|
|
screenshot:
|
|
file: category_knowledge.html
|
|
page:
|
|
- name: About
|
|
description: About page
|
|
screenshot:
|
|
file: page_about.html
|
|
version: 1.0.0
|
|
requires: ">=2.0.0"
|
|
license:
|
|
- name: "GPL-3.0"
|
|
url: "https://github.com/example/theme-foo/blob/main/LICENSE"
|
|
```
|
|
|
|
### Key Fields
|
|
|
|
| Field | Description | Required |
|
|
| ---------------------- | ------------------------------------------------------------------- | ---------------------------------------- |
|
|
| `metadata.name` | Unique theme identifier — **must match the folder name** | Yes |
|
|
| `spec.displayName` | Display name | Yes |
|
|
| `spec.version` | Theme version | Yes |
|
|
| `spec.requires` | Minimum required Halo version | Yes |
|
|
| `spec.settingName` | Setting resource name — must match `metadata.name` in settings.yaml | No |
|
|
| `spec.configMapName` | ConfigMap name for persisting settings | No (configure together with settingName) |
|
|
| `spec.customTemplates` | Custom template configuration | No |
|
|
|
|
> After modifying `theme.yaml`, click "Reload Theme Configuration" on the theme page in Console for changes to take effect.
|
|
|
|
## settings.yaml
|
|
|
|
Defines a form that is auto-rendered on the theme settings page in Console, using [FormKit](https://github.com/formkit/formkit).
|
|
|
|
```yaml
|
|
apiVersion: v1alpha1
|
|
kind: Setting
|
|
metadata:
|
|
name: theme-foo-setting # must match spec.settingName in theme.yaml
|
|
spec:
|
|
forms:
|
|
- group: style # group name (accessed in templates as theme.config.style.xxx)
|
|
label: Style
|
|
formSchema:
|
|
- $formkit: radio
|
|
name: color_scheme # field name (accessed as theme.config.style.color_scheme)
|
|
label: Default color scheme
|
|
value: system
|
|
options:
|
|
- label: Follow system
|
|
value: system
|
|
- label: Dark
|
|
value: dark
|
|
- label: Light
|
|
value: light
|
|
- $formkit: color
|
|
name: background_color
|
|
label: Background color
|
|
value: "#f2f2f2"
|
|
- group: layout
|
|
label: Layout
|
|
formSchema:
|
|
- $formkit: radio
|
|
name: nav
|
|
label: Navigation layout
|
|
value: "single"
|
|
options:
|
|
- label: Single column
|
|
value: "single"
|
|
- label: Double column
|
|
value: "double"
|
|
```
|
|
|
|
### Using Settings Values in Templates
|
|
|
|
Pattern: `theme.config.[group].[name]`
|
|
|
|
```html
|
|
<!-- Use color_scheme from the style group -->
|
|
<body th:class="${theme.config.style.color_scheme}">
|
|
<ul th:if="${theme.config.layout.nav == 'single'}">
|
|
Single-column nav
|
|
</ul>
|
|
<div th:if="${theme.config.layout.nav == 'double'}">Double-column nav</div>
|
|
</body>
|
|
```
|
|
|
|
### Native FormKit Input Types
|
|
|
|
Commonly used native input components in theme settings (see links for full docs):
|
|
|
|
| `$formkit` value | Purpose | Docs |
|
|
| ---------------- | --------------------------------- | -------------------------------------------- |
|
|
| `text` | Single-line text | https://formkit.com/inputs/text.md |
|
|
| `textarea` | Multi-line text | https://formkit.com/inputs/textarea.md |
|
|
| `number` | Number input | https://formkit.com/inputs/number.md |
|
|
| `password` | Password input | https://formkit.com/inputs/password.md |
|
|
| `radio` | Radio buttons (options list) | https://formkit.com/inputs/radio.md |
|
|
| `checkbox` | Checkbox (single or multi-select) | https://formkit.com/inputs/checkbox.md |
|
|
| `color` | Color picker (returns hex value) | https://formkit.com/inputs/color.md |
|
|
| `range` | Slider range | https://formkit.com/inputs/range.md |
|
|
| `date` | Date picker | https://formkit.com/inputs/date.md |
|
|
| `datetime-local` | Date-time picker | https://formkit.com/inputs/datetime-local.md |
|
|
| `button` | Button | https://formkit.com/inputs/button.md |
|
|
| `email` | Email input | https://formkit.com/inputs/email.md |
|
|
| `group` | Group (for grouping fields) | https://formkit.com/inputs/group.md |
|
|
| `url` | URL input | https://formkit.com/inputs/url.md |
|
|
|
|
FormKit Inputs overview: https://formkit.com/inputs
|
|
|
|
FormKit Schema (conditional rendering, loops, expressions, advanced usage): https://formkit.com/essentials/schema.md
|
|
|
|
> Note: FormKit Pro input components are not supported.
|
|
|
|
### Halo Extended Input Types
|
|
|
|
Additional input components provided by Halo on top of FormKit:
|
|
|
|
| `$formkit` value | Purpose |
|
|
| ------------------ | ------------------------------------------------------------------- |
|
|
| `select` | Enhanced dropdown with multi-select, search, and remote data source |
|
|
| `switch` | Toggle switch (boolean or custom on/off values) |
|
|
| `toggle` | Visual toggle supporting image/color/text options |
|
|
| `attachment` | Attachment picker (preview, direct upload, select from library) |
|
|
| `attachmentInput` | Attachment picker (library popup only) |
|
|
| `code` | Code editor (supports yaml/html/js/css/json) |
|
|
| `array` | Object array (add/remove/reorder — recommended over repeater) |
|
|
| `list` | Primitive array (strings, numbers, etc.) |
|
|
| `categorySelect` | Post category selector (returns `metadata.name`) |
|
|
| `categoryCheckbox` | Post category checkbox (returns array of `metadata.name`) |
|
|
| `tagSelect` | Post tag selector (returns `metadata.name`) |
|
|
| `tagCheckbox` | Post tag checkbox (returns array of `metadata.name`) |
|
|
| `postSelect` | Post selector |
|
|
| `singlePageSelect` | Single page selector |
|
|
| `menuSelect` | Menu selector (supports multi-select) |
|
|
| `menuCheckbox` | Menu checkbox |
|
|
| `menuRadio` | Menu radio |
|
|
| `iconify` | Icon picker (Iconify-based, supports svg/dataurl/url/name formats) |
|
|
| `secret` | Secret resource selector |
|
|
| `verificationForm` | Remote verification form group |
|
|
|
|
Full parameter reference: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/form-schema.md
|
|
|
|
Version-sensitive FormKit notes:
|
|
|
|
- Halo 2.25+: `select` option objects support `icon` and `description`; remote
|
|
select data can map those fields via `requestOption.iconField` and
|
|
`requestOption.descriptionField`.
|
|
- Halo 2.25+: `secret` supports `descriptionPreset`.
|
|
- Halo 2.23+: `iconify` supports optional `sizing`.
|
|
- Halo 2.22.8+: `toggle` is available.
|
|
- Halo 2.22.2+: `switch` is available.
|
|
- Halo 2.22+: prefer `array` over `repeater`; the newer `attachment` supports
|
|
preview/direct upload/library selection, while the older library-only picker
|
|
is `attachmentInput`.
|
|
|
|
### FormKit Schema Gotchas
|
|
|
|
**1. Inside `array` / `list` children, use `$value` — not `$get()`**
|
|
|
|
Within the `children` of an `array`, `list`, or `repeater`, access the current item's data via `$value`, not `$get(id).value`. `$get()` can only reference standalone named input nodes by `id`; it cannot reach the current item in a nested context.
|
|
|
|
```yaml
|
|
# ✅ Correct: use $value.[name] to reference a sibling field
|
|
- $formkit: array
|
|
name: socials
|
|
children:
|
|
- $formkit: text
|
|
name: platform
|
|
label: Platform
|
|
- $formkit: text
|
|
name: url
|
|
label: URL
|
|
if: "$value.platform !== ''" # references sibling field "platform"
|
|
|
|
# ❌ Wrong: $get(platform).value does not work in a nested context
|
|
- $formkit: text
|
|
name: url
|
|
if: "$get(platform).value !== ''"
|
|
```
|
|
|
|
**2. Nodes using `if` must also declare a `key`**
|
|
|
|
Any `$formkit`/`$el`/`$cmp` node with an `if` attribute must declare a unique `key`. Without it, Vue reuses DOM nodes when the condition toggles, causing stale form values or rendering glitches.
|
|
|
|
```yaml
|
|
# ✅ Correct: add key whenever if is present
|
|
- $formkit: text
|
|
key: url-field
|
|
name: url
|
|
label: URL
|
|
if: "$value.show_link === true"
|
|
|
|
# ❌ Wrong: missing key may cause value leakage on toggle
|
|
- $formkit: text
|
|
name: url
|
|
label: URL
|
|
if: "$value.show_link === true"
|
|
```
|