From 7fab0c60390240e5060bbf51a2c530bba141f113 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Thu, 2 Apr 2026 23:12:27 +0800 Subject: [PATCH] Add halo-theme-dev skill & theme templates Add a new .agents/skills/halo-theme-dev skill providing Halo theme development resources: two theme templates (theme-vite and theme-minimal) with theme.yaml/settings, Vite config, package.json, CSS and JS assets, and a full set of Thymeleaf template files/partials. Include comprehensive reference docs (structure & config, finder APIs, global variables, annotations, static resources, packaging, official plugins, thymeleaf tips) and an agents/openai.yaml interface. Also update project files: modify package.json, update tsconfig.json, add skills-lock.json, rename vite-env.d.ts and remove obsolete src/main.ts and src/styles/main.css while populating src/ with the new template files. --- .agents/skills/halo-theme-dev/SKILL.md | 91 ++++++ .../skills/halo-theme-dev/agents/openai.yaml | 4 + .../assets/theme-minimal/settings.yaml | 14 + .../assets/theme-minimal/theme.yaml | 20 ++ .../assets/theme-vite/package.json | 14 + .../assets/theme-vite/settings.yaml | 14 + .../assets/theme-vite/src/archives.html | 32 ++ .../assets/theme-vite/src/categories.html | 22 ++ .../assets/theme-vite/src/category.html | 14 + .../assets/theme-vite/src/css/main.css | 167 ++++++++++ .../assets/theme-vite/src/index.html | 12 + .../assets/theme-vite/src/js/index.ts | 1 + .../assets/theme-vite/src/js/main.ts | 1 + .../assets/theme-vite/src/js/post.ts | 1 + .../assets/theme-vite/src/page.html | 20 ++ .../theme-vite/src/partials/layout.html | 42 +++ .../theme-vite/src/partials/pagination.html | 5 + .../theme-vite/src/partials/post-card.html | 18 ++ .../assets/theme-vite/src/post.html | 56 ++++ .../assets/theme-vite/src/tag.html | 13 + .../assets/theme-vite/src/tags.html | 22 ++ .../assets/theme-vite/theme.yaml | 20 ++ .../assets/theme-vite/vite.config.ts | 6 + .../halo-theme-dev/references/annotations.md | 111 +++++++ .../halo-theme-dev/references/finder-apis.md | 53 ++++ .../references/global-variables.md | 140 +++++++++ .../references/official-plugins.md | 61 ++++ .../halo-theme-dev/references/packaging.md | 39 +++ .../references/static-resources.md | 69 +++++ .../references/structure-and-config.md | 253 +++++++++++++++ .../references/template-tags.md | 71 +++++ .../halo-theme-dev/references/templates.md | 51 +++ .../references/thymeleaf-tips.md | 82 +++++ .../halo-theme-dev/references/vite-plugin.md | 291 ++++++++++++++++++ package.json | 3 +- skills-lock.json | 10 + src/archives.html | 32 ++ src/categories.html | 22 ++ src/category.html | 14 + src/css/main.css | 167 ++++++++++ src/index.html | 12 + src/js/index.ts | 1 + src/js/main.ts | 1 + src/js/post.ts | 1 + src/main.ts | 5 - src/page.html | 20 ++ src/partials/layout.html | 42 +++ src/partials/pagination.html | 5 + src/partials/post-card.html | 18 ++ src/post.html | 56 ++++ src/styles/main.css | 7 - src/tag.html | 13 + src/tags.html | 18 ++ tsconfig.json | 4 +- src/vite-env.d.ts => vite-env.d.ts | 0 vite.config.ts | 4 +- 56 files changed, 2268 insertions(+), 17 deletions(-) create mode 100644 .agents/skills/halo-theme-dev/SKILL.md create mode 100644 .agents/skills/halo-theme-dev/agents/openai.yaml create mode 100644 .agents/skills/halo-theme-dev/assets/theme-minimal/settings.yaml create mode 100644 .agents/skills/halo-theme-dev/assets/theme-minimal/theme.yaml create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/package.json create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/settings.yaml create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/archives.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/categories.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/category.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/css/main.css create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/index.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/js/index.ts create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/js/main.ts create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/js/post.ts create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/page.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/partials/layout.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/partials/pagination.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/partials/post-card.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/post.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/tag.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/src/tags.html create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/theme.yaml create mode 100644 .agents/skills/halo-theme-dev/assets/theme-vite/vite.config.ts create mode 100644 .agents/skills/halo-theme-dev/references/annotations.md create mode 100644 .agents/skills/halo-theme-dev/references/finder-apis.md create mode 100644 .agents/skills/halo-theme-dev/references/global-variables.md create mode 100644 .agents/skills/halo-theme-dev/references/official-plugins.md create mode 100644 .agents/skills/halo-theme-dev/references/packaging.md create mode 100644 .agents/skills/halo-theme-dev/references/static-resources.md create mode 100644 .agents/skills/halo-theme-dev/references/structure-and-config.md create mode 100644 .agents/skills/halo-theme-dev/references/template-tags.md create mode 100644 .agents/skills/halo-theme-dev/references/templates.md create mode 100644 .agents/skills/halo-theme-dev/references/thymeleaf-tips.md create mode 100644 .agents/skills/halo-theme-dev/references/vite-plugin.md create mode 100644 skills-lock.json create mode 100644 src/archives.html create mode 100644 src/categories.html create mode 100644 src/category.html create mode 100644 src/css/main.css create mode 100644 src/index.html create mode 100644 src/js/index.ts create mode 100644 src/js/main.ts create mode 100644 src/js/post.ts delete mode 100644 src/main.ts create mode 100644 src/page.html create mode 100644 src/partials/layout.html create mode 100644 src/partials/pagination.html create mode 100644 src/partials/post-card.html create mode 100644 src/post.html delete mode 100644 src/styles/main.css create mode 100644 src/tag.html create mode 100644 src/tags.html rename src/vite-env.d.ts => vite-env.d.ts (100%) diff --git a/.agents/skills/halo-theme-dev/SKILL.md b/.agents/skills/halo-theme-dev/SKILL.md new file mode 100644 index 0000000..7249927 --- /dev/null +++ b/.agents/skills/halo-theme-dev/SKILL.md @@ -0,0 +1,91 @@ +--- +name: halo-theme-dev +description: > + Use when creating or modifying a Halo CMS theme: writing Thymeleaf templates, + configuring theme.yaml or settings.yaml, calling Finder APIs, using + vite-plugin-halo-theme, defining theme settings forms, referencing static assets, + implementing halo:comment or halo:footer extension points, or defining model + annotation fields (AnnotationSetting). +--- + +# Halo Theme Development + +Halo is built on **Spring Boot + Spring WebFlux + Thymeleaf**. Themes use Thymeleaf templates for frontend page rendering. + +> **Important**: Halo's APIs, VO field names, and template variables evolve across versions. **Do not rely on training data for specific field names, method signatures, or type structures.** When writing code that accesses template variables or calls Finder API methods, always fetch the relevant online doc from the References section below first. + +## Thymeleaf Quick Reference + +Full docs: https://raw.githubusercontent.com/thymeleaf/thymeleaf-docs/refs/heads/master/docs/tutorials/3.1/usingthymeleaf.md + +Core syntax cheatsheet: + +```html + +

+ + +
+ + +Post link + + + +
  • + + +
    Next page
    +
    Last page
    + + +
    ...
    + + +
    + + + + + + + + +``` + +## Development Workflow + +1. Create a theme folder under `themes/` in the Halo working directory (must match `metadata.name` in `theme.yaml`) +2. Write `theme.yaml` (required) and `settings.yaml` (optional) +3. Create template files under `templates/` +4. Install and activate the theme in Console → Theme Management +5. Visit the frontend to verify + +Disable Thymeleaf caching during development: set env var `SPRING_THYMELEAF_CACHE=false` (Docker), or `spring.thymeleaf.cache: false` in config (source mode). + +## Starter Templates + +The `assets/` directory provides two ready-to-use theme templates: + +- **`assets/theme-minimal/`** — Zero-build-tool minimal theme with all 8 template files; ideal for quick prototyping or simple themes +- **`assets/theme-vite/`** — Vite project template with `vite-plugin-halo-theme` (**recommended for new themes**); includes partial layout reuse and CSS toolchain + +Usage: copy the directory into `themes/` in your Halo working directory, ensure the folder name matches `metadata.name` in `theme.yaml`, then install and activate in Console. + +## References Index + +| File | Content | When to read | +| ---- | ------- | ------------ | +| [references/structure-and-config.md](references/structure-and-config.md) | Directory structure, theme.yaml fields, settings.yaml form definition | Creating a theme, configuring theme.yaml/settings.yaml | +| [references/vite-plugin.md](references/vite-plugin.md) | vite-plugin-halo-theme integration guide, include/slot template syntax, TailwindCSS integration | Setting up a Vite-based theme (recommended) | +| [references/templates.md](references/templates.md) | Template route mapping, available variables per template | Writing template files | +| [references/global-variables.md](references/global-variables.md) | Global variables (site, theme, theme.config) and type definitions | Accessing site info or theme setting values | +| [references/finder-apis.md](references/finder-apis.md) | All Finder APIs (postFinder, categoryFinder, tagFinder, menuFinder, singlePageFinder, etc.) | Querying data from any template | +| [references/static-resources.md](references/static-resources.md) | Static asset reference methods (`@{}`, `#theme.assets()`) | Referencing CSS/JS/images in plain HTML themes | +| [references/template-tags.md](references/template-tags.md) | Custom tags (halo:comment extension point, halo:footer injection) | Integrating comment plugins, injecting footer code | +| [references/official-plugins.md](references/official-plugins.md) | Official plugin integration: pluginFinder.available(), search widget, dark mode color scheme adaptation | Adding search, adapting dark mode for plugin UI | +| [references/annotations.md](references/annotations.md) | AnnotationSetting for model custom fields, `#annotations` utility for reading metadata in templates | Adding custom fields to menu items/posts/categories and using them in templates | +| [references/packaging.md](references/packaging.md) | Packaging a theme as a ZIP using `@halo-dev/theme-package-cli` | Preparing a theme for release or upload | +| [references/thymeleaf-tips.md](references/thymeleaf-tips.md) | Halo-specific Thymeleaf best practices: literal substitutions, safe navigation, meta tag rules, permalink syntax | Writing any template file | diff --git a/.agents/skills/halo-theme-dev/agents/openai.yaml b/.agents/skills/halo-theme-dev/agents/openai.yaml new file mode 100644 index 0000000..c18da1f --- /dev/null +++ b/.agents/skills/halo-theme-dev/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Halo Theme Development" + short_description: "Build Halo CMS themes with Thymeleaf" + default_prompt: "Help me create a Halo theme" diff --git a/.agents/skills/halo-theme-dev/assets/theme-minimal/settings.yaml b/.agents/skills/halo-theme-dev/assets/theme-minimal/settings.yaml new file mode 100644 index 0000000..d5d68d5 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-minimal/settings.yaml @@ -0,0 +1,14 @@ +apiVersion: v1alpha1 +kind: Setting +metadata: + name: theme-minimal-setting +spec: + forms: + - group: basic + label: Basic Settings + formSchema: + - $formkit: text + name: custom_footer + label: Custom footer text + value: "" + placeholder: "e.g. Copyright © 2024 My Site" diff --git a/.agents/skills/halo-theme-dev/assets/theme-minimal/theme.yaml b/.agents/skills/halo-theme-dev/assets/theme-minimal/theme.yaml new file mode 100644 index 0000000..07ab9ed --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-minimal/theme.yaml @@ -0,0 +1,20 @@ +apiVersion: theme.halo.run/v1alpha1 +kind: Theme +metadata: + name: theme-minimal +spec: + displayName: Minimal Theme + author: + name: Your Name + website: https://example.com + description: A minimal Halo theme starter + logo: "" + homepage: "" + repo: "" + issues: "" + settingName: "theme-minimal-setting" + configMapName: "theme-minimal-configmap" + version: 1.0.0 + requires: ">=2.0.0" + license: + - name: "MIT" diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/package.json b/.agents/skills/halo-theme-dev/assets/theme-vite/package.json new file mode 100644 index 0000000..aa15993 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/package.json @@ -0,0 +1,14 @@ +{ + "type": "module", + "scripts": { + "dev": "vite build --watch", + "build": "vite build && theme-package", + "build-only": "vite build" + }, + "devDependencies": { + "@halo-dev/theme-package-cli": "^1.0.1", + "@halo-dev/vite-plugin-halo-theme": "latest", + "vite": "^8.0.0" + }, + "packageManager": "pnpm@10.33.0" +} diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/settings.yaml b/.agents/skills/halo-theme-dev/assets/theme-vite/settings.yaml new file mode 100644 index 0000000..73161a3 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/settings.yaml @@ -0,0 +1,14 @@ +apiVersion: v1alpha1 +kind: Setting +metadata: + name: theme-vite-setting +spec: + forms: + - group: basic + label: Basic Settings + formSchema: + - $formkit: text + name: custom_footer + label: Custom footer text + value: "" + placeholder: "e.g. Copyright © 2024 My Site" diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/archives.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/archives.html new file mode 100644 index 0000000..1e6abc3 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/archives.html @@ -0,0 +1,32 @@ + + + +

    Archives

    + + +
    +

    + +

    +
      +
    • +

      + +

      + +
    • +
    +
    +
    +
    + + +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/categories.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/categories.html new file mode 100644 index 0000000..1dfe11f --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/categories.html @@ -0,0 +1,22 @@ + + + +

    Categories

    + + + +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/category.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/category.html new file mode 100644 index 0000000..c2483a1 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/category.html @@ -0,0 +1,14 @@ + + + +

    +

    + + + + +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/css/main.css b/.agents/skills/halo-theme-dev/assets/theme-vite/src/css/main.css new file mode 100644 index 0000000..ae1e7bd --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/css/main.css @@ -0,0 +1,167 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + line-height: 1.6; + color: #333; +} + +a { + color: #0070f3; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.container { + max-width: 800px; + margin: 0 auto; + padding: 0 1rem; +} + +.site-header { + border-bottom: 1px solid #eee; + padding: 1rem 0; +} + +.site-header .container { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.5rem; +} + +.site-title { + font-size: 1.25rem; + font-weight: 700; + color: #333; +} + +.site-nav ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + gap: 1rem; +} + +.site-main { + padding: 2rem 0; +} + +.post-list { + list-style: none; + margin: 0; + padding: 0; +} + +.post-item { + padding: 1.5rem 0; + border-bottom: 1px solid #eee; +} + +.post-item:last-child { + border-bottom: none; +} + +.post-title { + margin: 0 0 0.25rem; + font-size: 1.25rem; +} + +.post-meta { + color: #666; + font-size: 0.875rem; + margin: 0 0 0.5rem; +} + +.post-excerpt { + color: #555; + margin: 0; +} + +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + padding: 2rem 0; +} + +.article-header { + margin-bottom: 2rem; +} + +.article-title { + margin: 0 0 0.5rem; + font-size: 2rem; +} + +.article-meta { + color: #666; + font-size: 0.875rem; +} + +.article-content { + max-width: 100%; + line-height: 1.8; +} + +.article-content img { + max-width: 100%; +} + +.article-tags, +.article-categories { + margin-top: 1.5rem; +} + +.tag, +.category { + display: inline-block; + padding: 0.125rem 0.5rem; + background: #f0f0f0; + border-radius: 4px; + font-size: 0.875rem; + margin: 0.25rem; +} + +.post-nav { + display: flex; + justify-content: space-between; + padding: 2rem 0; + border-top: 1px solid #eee; + margin-top: 2rem; +} + +.site-footer { + border-top: 1px solid #eee; + padding: 1.5rem 0; + text-align: center; + color: #666; + font-size: 0.875rem; +} + +.archive-year { + margin-top: 2rem; +} + +.tag-list, +.category-list { + list-style: none; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/index.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/index.html new file mode 100644 index 0000000..c9800d9 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/index.html @@ -0,0 +1,12 @@ + + + + + + + diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/index.ts b/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/index.ts new file mode 100644 index 0000000..dd2e03d --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/index.ts @@ -0,0 +1 @@ +console.log("Hello, Halo Theme!"); diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/main.ts b/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/main.ts new file mode 100644 index 0000000..0f20a91 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/main.ts @@ -0,0 +1 @@ +import "../css/main.css"; diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/post.ts b/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/post.ts new file mode 100644 index 0000000..d9387c7 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/js/post.ts @@ -0,0 +1 @@ +console.log("Hello, Halo Theme Post!"); diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/page.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/page.html new file mode 100644 index 0000000..0b7f9be --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/page.html @@ -0,0 +1,20 @@ + + + +
    +
    +

    +
    +
    +
    + +
    + +
    +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/layout.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/layout.html new file mode 100644 index 0000000..998e5de --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/layout.html @@ -0,0 +1,42 @@ + + + + + + + Site Title + + + + + + +
    +
    + +
    +
    + + + + diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/pagination.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/pagination.html new file mode 100644 index 0000000..29972c1 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/pagination.html @@ -0,0 +1,5 @@ + diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/post-card.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/post-card.html new file mode 100644 index 0000000..47dd2e1 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/partials/post-card.html @@ -0,0 +1,18 @@ +
  • +

    + +

    + +

    +
  • diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/post.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/post.html new file mode 100644 index 0000000..939b435 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/post.html @@ -0,0 +1,56 @@ + + + +
    +
    +

    + +
    + +
    + + + + +
    + +
    + +
    +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/tag.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/tag.html new file mode 100644 index 0000000..567d9c0 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/tag.html @@ -0,0 +1,13 @@ + + + +

    + + + + +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/src/tags.html b/.agents/skills/halo-theme-dev/assets/theme-vite/src/tags.html new file mode 100644 index 0000000..75751f7 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/src/tags.html @@ -0,0 +1,22 @@ + + + +

    Tags

    + + + +
    diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/theme.yaml b/.agents/skills/halo-theme-dev/assets/theme-vite/theme.yaml new file mode 100644 index 0000000..b2792a0 --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/theme.yaml @@ -0,0 +1,20 @@ +apiVersion: theme.halo.run/v1alpha1 +kind: Theme +metadata: + name: theme-vite +spec: + displayName: Vite Theme + author: + name: Your Name + website: https://example.com + description: A Halo theme powered by vite-plugin-halo-theme + logo: "" + homepage: "" + repo: "" + issues: "" + settingName: "theme-vite-setting" + configMapName: "theme-vite-configmap" + version: 1.0.0 + requires: ">=2.0.0" + license: + - name: "MIT" diff --git a/.agents/skills/halo-theme-dev/assets/theme-vite/vite.config.ts b/.agents/skills/halo-theme-dev/assets/theme-vite/vite.config.ts new file mode 100644 index 0000000..489733f --- /dev/null +++ b/.agents/skills/halo-theme-dev/assets/theme-vite/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import { haloThemePlugin } from "@halo-dev/vite-plugin-halo-theme"; + +export default defineConfig({ + plugins: [haloThemePlugin()], +}); diff --git a/.agents/skills/halo-theme-dev/references/annotations.md b/.agents/skills/halo-theme-dev/references/annotations.md new file mode 100644 index 0000000..261c84f --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/annotations.md @@ -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 +
    +
  • + + +
  • +
    +``` + +### `#annotations.getOrDefault(object, key, defaultValue)` — Get a value with fallback + +```html + +``` + +### `#annotations.contains(object, key)` — Check if a key exists + +```html + +``` + +## 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 diff --git a/.agents/skills/halo-theme-dev/references/finder-apis.md b/.agents/skills/halo-theme-dev/references/finder-apis.md new file mode 100644 index 0000000..bf7b2a6 --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/finder-apis.md @@ -0,0 +1,53 @@ +# Finder API + +Finder APIs query data from **any template location** regardless of the current route — ideal for sidebars, footers, and other global data needs. + +## Available Finders + +| Finder | Purpose | +| ------ | ------- | +| `postFinder` | Post list / detail / prev-next / archives | +| `categoryFinder` | Category list / tree structure / breadcrumbs | +| `tagFinder` | Tag list / detail | +| `menuFinder` | Menus and menu items | +| `singlePageFinder` | Single page list / detail | +| `commentFinder` | Comments and replies | +| `contributorFinder` | Contributors | +| `siteStatsFinder` | Site statistics | +| `themeFinder` | Theme information | +| `pluginFinder` | Plugin information | + +## Key Usage Pattern + +Use `th:with` to bind the result in the current scope: + +```html +
    + +
    +``` + +## Common Notes + +- `postFinder.list({...})` is the recommended unified query method (all parameters are optional); it supersedes the deprecated `list(page, size)`, `listByCategory(...)`, etc. +- `metadata.name` is the unique resource identifier — it is not the display name (`displayName`/`title`). +- Pair `settings.yaml` `categorySelect`/`tagSelect` inputs with Finder queries so users can configure query parameters in Console instead of hard-coding them in templates. + +## Online Docs + +> **Do not rely on training data for Finder API method signatures — Halo evolves across versions and your training data may be outdated or incomplete. Always fetch the relevant doc before writing code that calls a specific Finder method.** + +- postFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/post.md +- categoryFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/category.md +- tagFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/tag.md +- menuFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/menu.md +- singlePageFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/single-page.md +- commentFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/comment.md +- contributorFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/contributor.md +- siteStatsFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/site-stats.md +- themeFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/finder-apis/theme.md +- pluginFinder: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/plugin/api-reference/server/finder-for-theme.md diff --git a/.agents/skills/halo-theme-dev/references/global-variables.md b/.agents/skills/halo-theme-dev/references/global-variables.md new file mode 100644 index 0000000..155700f --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/global-variables.md @@ -0,0 +1,140 @@ +# Global Variables + +The following variables are available in all templates without any additional declaration. + +--- + +## `site` — Site information + +Source: Console → System Settings. + +```json +{ + "title": "Site Title", + "subtitle": "Site Subtitle", + "url": "https://example.com", + "logo": "https://example.com/logo.png", + "favicon": "https://example.com/favicon.ico", + "allowRegistration": false, + "post": { + "postPageSize": 10, + "archivePageSize": 10, + "categoryPageSize": 10, + "tagPageSize": 10 + }, + "seo": { + "blockSpiders": false, + "keywords": "keywords", + "description": "Site description" + }, + "comment": { + "enable": true, + "systemUserOnly": false, + "requireReviewForNew": false + }, + "routes": { + "categoriesUri": "/categories", + "tagsUri": "/tags", + "archivesUri": "/archives" + } +} +``` + +**Common examples**: + +```html + +Logo + +``` + +--- + +## `theme` — Current theme info + +```json +{ + "metadata": { + "name": "theme-foo", + "creationTimestamp": "..." + }, + "spec": { + "displayName": "My Theme", + "version": "1.0.0", + "author": { "name": "Author", "website": "https://example.com" }, + "description": "Theme description", + "logo": "https://example.com/logo.png", + "homepage": "https://github.com/example/theme-foo", + "settingName": "theme-foo-setting", + "configMapName": "theme-foo-configMap" + }, + "config": { + "style": { "color_scheme": "system" }, + "layout": { "nav": "single" } + } +} +``` + +**Common examples**: + +```html + + + + + + +``` + +--- + +## `theme.config` — Theme settings values + +Access pattern: `theme.config.[group].[name]` + +- `group`: value of `spec.forms[].group` in `settings.yaml` +- `name`: value of `spec.forms[].formSchema[].name` + +**Example** (based on the settings.yaml in [structure-and-config.md](structure-and-config.md)): + +```html + + + + +``` + +--- + +## `#theme.assets()` — Static asset path utility + +Returns the full path to a static asset for use in non-attribute contexts (e.g. inside JavaScript). + +> Note: the path passed to this function does **not** need an `/assets/` prefix. + +```html + +``` + +--- + +## Online Docs + +> **If you need the exact structure of `site`, `theme`, or `theme.config`, fetch the doc below — do not guess field names from training data.** + +https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/global-variables.md + +--- + +## `haloCommentEnabled` — Comment component status + +Boolean. Evaluates both "is a comment plugin installed" and "are comments enabled for this page". Use together with the `halo:comment` custom tag: + +```html +
    + +
    +``` diff --git a/.agents/skills/halo-theme-dev/references/official-plugins.md b/.agents/skills/halo-theme-dev/references/official-plugins.md new file mode 100644 index 0000000..389ae6d --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/official-plugins.md @@ -0,0 +1,61 @@ +# Official Plugin Integration + +Halo's official plugins can extend the frontend UI. Themes should adapt to these plugins rather than re-implementing the same functionality. + +## Checking Plugin Availability + +Use `pluginFinder.available(pluginName)` to conditionally render plugin-dependent UI. This prevents broken UI when the plugin is not installed. + +```html + +``` + +Always guard plugin-dependent elements with `th:if="${pluginFinder.available('...')}"`. + +## Search Widget (PluginSearchWidget) + +The official search plugin provides a ready-made search UI. Themes do not need to build their own search — just add a trigger button: + +```html + +``` + +## Dark Mode Adaptation + +Official plugins that provide UI components (search widget, comment component, etc.) support a shared color scheme system. Themes that implement dark mode should apply the appropriate class or `data-color-scheme` attribute to `` or `` so plugin UI matches the theme's color scheme automatically. + +### Method 1: CSS class on `` or `` + +| Class | Effect | +| ----- | ------ | +| `color-scheme-auto` | Follows system dark/light preference | +| `color-scheme-dark` or `dark` | Force dark mode | +| `color-scheme-light` or `light` | Force light mode | + +```html + + + + + +``` + +### Method 2: `data-color-scheme` attribute on `` or `` + +| Value | Effect | +| ----- | ------ | +| `auto` | Follows system preference | +| `dark` | Force dark mode | +| `light` | Force light mode | + +```html + +``` + +> Both methods achieve the same result. Dark mode switching is typically handled by frontend JavaScript (toggling the class/attribute at runtime). This applies to all official plugins that render UI (search widget, comment component, etc.). diff --git a/.agents/skills/halo-theme-dev/references/packaging.md b/.agents/skills/halo-theme-dev/references/packaging.md new file mode 100644 index 0000000..b8f7ccb --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/packaging.md @@ -0,0 +1,39 @@ +# Theme Packaging + +Use [`@halo-dev/theme-package-cli`](https://github.com/halo-dev/theme-package-cli) to package a theme into a ZIP file for uploading to Halo Console or distributing to others. + +## Installation + +```bash +npm install -g @halo-dev/theme-package-cli +``` + +Or run without installing via `npx`: + +```bash +npx @halo-dev/theme-package-cli +``` + +## Usage + +Run in the theme root directory (the one containing `theme.yaml`): + +```bash +# Package only essential files (recommended) +theme-package + +# Package all files (excluding node_modules, dist, .git, etc.) +theme-package --all +``` + +## Default Package Contents (without `--all`) + +| Included | Description | +| -------- | ----------- | +| `templates/` | Templates and static assets | +| `*.yaml` / `*.yml` | Config files: `theme.yaml`, `settings.yaml`, etc. | +| `i18n/` | Internationalization files (if present) | +| `README.md` | Documentation file (if present) | +| `LICENSE` | License file (if present) | + +> For Vite-based themes, run `npm run build` first to generate the `templates/` output, then run the packaging command. diff --git a/.agents/skills/halo-theme-dev/references/static-resources.md b/.agents/skills/halo-theme-dev/references/static-resources.md new file mode 100644 index 0000000..a91107b --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/static-resources.md @@ -0,0 +1,69 @@ +# Static Assets & Build Tooling + +## Static Asset Directory + +All theme static assets **must** be placed under `templates/assets/`: + +``` +templates/ +└── assets/ + ├── css/ + │ └── style.css + ├── js/ + │ └── main.js + └── images/ + └── logo.png +``` + +--- + +## Referencing Static Assets in Templates + +### Method 1: HTML tag attributes (recommended) + +Use Thymeleaf's `@{}` expression: + +```html + + + + + + + +Logo +``` + +The path `@{/assets/dist/style.css}` maps to `templates/assets/dist/style.css` and renders as `/themes/theme-foo/assets/dist/style.css`. + +**With version query string (strongly recommended — prevents caching)**: + +```html + + +``` + +### Method 2: `#theme.assets()` utility + +Use when you need the asset path in a non-attribute context (e.g. a JavaScript variable or inline CSS). + +> Note: do **not** include `/assets/` prefix in the path argument. + +```html + +``` + +--- + +## Vite Integration + +For themes using a modern frontend stack (TypeScript, Sass, PostCSS, TailwindCSS, etc.) or needing HTML template reuse (`include`/`slot`), the official Vite plugin is strongly recommended: + +**`vite-plugin-halo-theme`**: https://github.com/halo-sigs/vite-plugin-halo-theme + +See [vite-plugin.md](vite-plugin.md) for the full integration guide, template syntax, and configuration examples. diff --git a/.agents/skills/halo-theme-dev/references/structure-and-config.md b/.agents/skills/halo-theme-dev/references/structure-and-config.md new file mode 100644 index 0000000..62dfe9b --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/structure-and-config.md @@ -0,0 +1,253 @@ +# 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 +├── 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. + +## 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 + + + +
    Double-column nav
    + +``` + +### 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 + +### 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" +``` diff --git a/.agents/skills/halo-theme-dev/references/template-tags.md b/.agents/skills/halo-theme-dev/references/template-tags.md new file mode 100644 index 0000000..e314b5c --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/template-tags.md @@ -0,0 +1,71 @@ +# Custom Template Tags + +Halo provides proprietary custom tags for code injection and extension points. + +--- + +## `` — Comment extension point + +### Description + +Extension point tag for the comment component. When a plugin (such as the official comment plugin) implements this extension point, it renders the comment component at this tag's location. + +### Usage + +```html title="templates/post.html" + +
    + +
    +``` + +```html title="templates/page.html" +
    + +
    +``` + +### Attributes + +| Attribute | Description | +| --------- | ----------- | +| `group` | Resource group | +| `kind` | Resource type | +| `name` | Unique resource identifier (`metadata.name`) | + +### Supported Resource Types + +| Resource | `group` | `kind` | +| -------- | ------- | ------ | +| Post | `content.halo.run` | `Post` | +| Single page | `content.halo.run` | `SinglePage` | + +--- + +## `` — Footer code injection + +### Description + +Renders the "Footer Code" configured in Console → System Settings → Code Injection at this tag's location. All themes should include this tag in the footer to ensure full Halo functionality (e.g. analytics scripts injected by plugins). + +### Usage + +```html title="templates/index.html (bottom of any template)" + + + +
    + +

    © 2024 My Site

    + + + +
    + +``` + +> For complete Halo functionality, include this tag in every template that contains a `` element. diff --git a/.agents/skills/halo-theme-dev/references/templates.md b/.agents/skills/halo-theme-dev/references/templates.md new file mode 100644 index 0000000..c9c7d4e --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/templates.md @@ -0,0 +1,51 @@ +# Template Route Mapping & Template Variables + +## Template Route Mapping + +| Template file | URL path | Main variables | `_templateId` | +| ------------- | -------- | -------------- | ------------- | +| `templates/index.html` | `/` | `posts` | `index` | +| `templates/post.html` | `/archives/:slug` | `post` | `post` | +| `templates/page.html` | `/:slug` | `singlePage` | `page` | +| `templates/archives.html` | `/archives[/:year[/:month]]` | `archives` | `archives` | +| `templates/tags.html` | `/tags` | `tags` | `tags` | +| `templates/tag.html` | `/tags/:slug` | `tag`, `posts` | `tag` | +| `templates/categories.html` | `/categories` | `categories` | `categories` | +| `templates/category.html` | `/categories/:slug` | `category`, `posts` | `category` | +| `templates/author.html` | `/authors/:slug` | `author`, `posts` | `author` | + +> Route prefixes (`/archives`, `/tags`, `/categories`) can be customized by users in Console system settings. + +## Custom Templates + +Register additional rendering templates for posts, single pages, or category archives via `spec.customTemplates` in `theme.yaml`. Supported types: `post`, `page`, `category`. + +```yaml +spec: + customTemplates: + post: + - name: Documentation + file: post_documentation.html # create under templates/ +``` + +> After modifying theme.yaml, click "Reload Theme Configuration" on the theme page in Console. + +## Key Notes + +- Use `th:utext` (unescaped) to render post/page body content — never `th:text`. +- List variables (`posts`, `archives`, etc.) are `UrlContextListResult`; use `.hasPrevious()`/`.hasNext()`/`.prevUrl`/`.nextUrl` for pagination. +- `post.content.content` is only available automatically in `post.html`; in other templates fetch it separately via `postFinder.content(postName)`. + +## Online Docs + +> **Halo's VO types (field names, nested structures) change across versions. Do not guess field names from training data. Always fetch the doc for the relevant template before accessing specific fields on template variables.** + +- index: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/index_.md +- post: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/post.md +- page: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/page.md +- archives: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/archives.md +- tag: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/tag.md +- tags: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/tags.md +- category: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/category.md +- categories: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/categories.md +- author: https://raw.githubusercontent.com/halo-dev/docs/refs/heads/main/docs/developer-guide/theme/template-variables/author.md diff --git a/.agents/skills/halo-theme-dev/references/thymeleaf-tips.md b/.agents/skills/halo-theme-dev/references/thymeleaf-tips.md new file mode 100644 index 0000000..5325cd8 --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/thymeleaf-tips.md @@ -0,0 +1,82 @@ +# Thymeleaf Best Practices for Halo Themes + +**1. Prefer literal substitutions over string concatenation** + +```html + + + + + +``` + +**2. Use safe navigation `?.` to avoid NullPointerException** + +```html + + +``` + +**3. Use Elvis operator `?:` for default values** + +```html +

    +``` + +**4. Use `th:block` to group without adding extra DOM elements** + +```html + +

    +
      ...
    +
    +``` + +**5. Use `th:classappend` for conditional classes** + +```html + +... + + +... +``` + +**6. Use `#lists.isEmpty()` and `#strings.isEmpty()` for null-safe checks** + +```html +
    + +
    +``` + +**7. Do not manually add meta tags — Halo injects them automatically** + +Only `` needs to be in the theme. Halo automatically injects at runtime: +- `<meta name="description">` and `<meta name="keywords">` +- Open Graph tags (`og:title`, `og:description`, `og:image`, etc.) +- Twitter Card tags and canonical URL + +```html +<!-- ✅ correct --> +<head> + <title th:text="${site.title}">Site Title + + + + + Site Title + + + +``` + +**8. Use `@{${url}}` for dynamic permalink URLs** + +```html + +... + + +... +``` diff --git a/.agents/skills/halo-theme-dev/references/vite-plugin.md b/.agents/skills/halo-theme-dev/references/vite-plugin.md new file mode 100644 index 0000000..ac22c90 --- /dev/null +++ b/.agents/skills/halo-theme-dev/references/vite-plugin.md @@ -0,0 +1,291 @@ +# Vite Integration (Recommended) + +> **Strongly recommended** for building theme projects. Compared to plain Thymeleaf, this approach provides a modern frontend development experience (TypeScript, Sass, PostCSS, TailwindCSS) plus lightweight HTML component reuse (`include`/`slot`) — significantly reducing the verbosity of Thymeleaf's native fragment system. + +--- + +## vite-plugin-halo-theme + +- GitHub: https://github.com/halo-sigs/vite-plugin-halo-theme +- NPM: `@halo-dev/vite-plugin-halo-theme` + +### Key Features + +1. **HTML template reuse**: supports ``, `` (default and named), and `` syntax, preprocessed at Vite build time — fully independent of Thymeleaf. +2. **Automatic multi-page entry scanning**: automatically discovers all `.html` files under `src/` (except `src/partials/`) as Vite build entries, with output going to `templates/`. +3. **Static asset handling**: CSS/JS in `src/` is bundled by Vite into `templates/assets/`; files in `public/` are copied directly to `templates/assets/` without processing. + +--- + +## Directory Convention + +``` +my-theme/ +├── src/ +│ ├── css/ +│ │ └── main.css # CSS source +│ ├── js/ +│ │ └── main.ts # TypeScript source +│ ├── partials/ # Not treated as page entries; referenced via include +│ │ ├── layout.html # Shared layout +│ │ └── post-card.html # Reusable post card component +│ ├── index.html # Home page entry → templates/index.html +│ ├── post.html # → templates/post.html +│ ├── page.html +│ ├── archives.html +│ ├── tags.html +│ ├── tag.html +│ ├── categories.html +│ └── category.html +├── public/ # Copied directly to templates/assets/ (no Vite processing) +├── templates/ # Build output directory (add to .gitignore) +├── vite.config.ts +├── package.json +├── .gitignore +├── theme.yaml +└── settings.yaml +``` + +--- + +## Quick Setup + +### package.json + +```json +{ + "type": "module", + "scripts": { + "dev": "vite build --watch", + "build": "vite build" + }, + "devDependencies": { + "@halo-dev/vite-plugin-halo-theme": "latest", + "vite": "^6.0.0" + } +} +``` + +### vite.config.ts + +```ts +import { defineConfig } from "vite"; +import { haloThemePlugin } from "@halo-dev/vite-plugin-halo-theme"; + +export default defineConfig({ + plugins: [haloThemePlugin()], +}); +``` + +### .gitignore + +```gitignore +node_modules/ +templates/ +!templates/.gitkeep +``` + +--- + +## Template Syntax (build-time only — independent of Thymeleaf) + +> **Important**: `include`/`slot` syntax is processed at **Vite build time**; Thymeleaf syntax is processed at **server runtime**. The two systems are fully isolated and can be mixed freely. + +### `` — Import a partial + +```html + +
    Page content
    +
    +``` + +### Named Slots + +Declare slots in a partial (`src/partials/layout.html`): + +```html + + + Default title + + + + + +``` + +Use in a page: + +```html + + +
    +
    +``` + +### Include Path Resolution + +| Syntax | Resolves to | +| ------ | ----------- | +| `foo.html` | `src/partials/foo.html` (preferred) | +| `partials/foo.html` | `src/partials/foo.html` | +| `./foo.html` | Relative to current file | +| `/foo.html` | Relative to `src/` root | + +### Static Asset Paths + +**All HTML files — both `src/*.html` and `src/partials/*.html` — resolve static asset references relative to `src/`**, regardless of the file's actual location. + +```html + + + + + + +``` + +Always write asset paths as if the file is in `src/`, even when it is inside `src/partials/`. + +--- + +## Full Example: Shared Layout + +### `src/partials/layout.html` + +```html + + + + + + + Site title + + + + +
    + + +
    + +
    + +
    + +
    + +
    + + +``` + +### `src/index.html` + +```html + + + +
    +
      +
    • + + +

      +
    • +
    + +
    +
    +``` + +### `src/post.html` + +```html + + + + + +``` + +--- + +## TailwindCSS Integration + +```bash +pnpm add -D tailwindcss @tailwindcss/vite +``` + +```ts +// vite.config.ts +import tailwindcss from "@tailwindcss/vite"; +import { haloThemePlugin } from "@halo-dev/vite-plugin-halo-theme"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [tailwindcss(), haloThemePlugin()], +}); +``` + +```css +/* src/css/main.css */ +@import "tailwindcss"; +``` + +--- + +## Ready-to-use Templates + +The skill's `assets/` directory provides two ready-to-use templates: + +- `assets/theme-minimal/` — Zero-build-tool minimal theme; good for quick prototyping or plain HTML/CSS themes +- `assets/theme-vite/` — Full Vite project with vite-plugin-halo-theme; recommended as the starting point for new themes + +Usage: copy the directory into `themes/` in your Halo working directory, ensure the folder name matches `metadata.name` in `theme.yaml`, then install and activate in Console. diff --git a/package.json b/package.json index 3244574..c49fde8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "build-only": "tsc && vp build", "dev": "vp build --watch", "check": "vp check --fix", - "prepare": "vp config" + "prepare": "vp config", + "skills:update": "npx skills update" }, "devDependencies": { "@halo-dev/theme-package-cli": "^1.0.1", diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..35b5794 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "skills": { + "halo-theme-dev": { + "source": "halo-dev/dev-skills", + "sourceType": "github", + "computedHash": "f6b0a799298f6f729366fdad175662fa7386e615839ad4af807285954969a9f4" + } + } +} diff --git a/src/archives.html b/src/archives.html new file mode 100644 index 0000000..1e6abc3 --- /dev/null +++ b/src/archives.html @@ -0,0 +1,32 @@ + + + +

    Archives

    + + +
    +

    + +

    +
      +
    • +

      + +

      + +
    • +
    +
    +
    +
    + + +
    diff --git a/src/categories.html b/src/categories.html new file mode 100644 index 0000000..1dfe11f --- /dev/null +++ b/src/categories.html @@ -0,0 +1,22 @@ + + + +

    Categories

    +
      +
    • + +
    • +
    + + +
    diff --git a/src/category.html b/src/category.html new file mode 100644 index 0000000..c2483a1 --- /dev/null +++ b/src/category.html @@ -0,0 +1,14 @@ + + + +

    +

    + +
      + +
    + + +
    diff --git a/src/css/main.css b/src/css/main.css new file mode 100644 index 0000000..ae1e7bd --- /dev/null +++ b/src/css/main.css @@ -0,0 +1,167 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: + system-ui, + -apple-system, + sans-serif; + line-height: 1.6; + color: #333; +} + +a { + color: #0070f3; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.container { + max-width: 800px; + margin: 0 auto; + padding: 0 1rem; +} + +.site-header { + border-bottom: 1px solid #eee; + padding: 1rem 0; +} + +.site-header .container { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.5rem; +} + +.site-title { + font-size: 1.25rem; + font-weight: 700; + color: #333; +} + +.site-nav ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + gap: 1rem; +} + +.site-main { + padding: 2rem 0; +} + +.post-list { + list-style: none; + margin: 0; + padding: 0; +} + +.post-item { + padding: 1.5rem 0; + border-bottom: 1px solid #eee; +} + +.post-item:last-child { + border-bottom: none; +} + +.post-title { + margin: 0 0 0.25rem; + font-size: 1.25rem; +} + +.post-meta { + color: #666; + font-size: 0.875rem; + margin: 0 0 0.5rem; +} + +.post-excerpt { + color: #555; + margin: 0; +} + +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + padding: 2rem 0; +} + +.article-header { + margin-bottom: 2rem; +} + +.article-title { + margin: 0 0 0.5rem; + font-size: 2rem; +} + +.article-meta { + color: #666; + font-size: 0.875rem; +} + +.article-content { + max-width: 100%; + line-height: 1.8; +} + +.article-content img { + max-width: 100%; +} + +.article-tags, +.article-categories { + margin-top: 1.5rem; +} + +.tag, +.category { + display: inline-block; + padding: 0.125rem 0.5rem; + background: #f0f0f0; + border-radius: 4px; + font-size: 0.875rem; + margin: 0.25rem; +} + +.post-nav { + display: flex; + justify-content: space-between; + padding: 2rem 0; + border-top: 1px solid #eee; + margin-top: 2rem; +} + +.site-footer { + border-top: 1px solid #eee; + padding: 1.5rem 0; + text-align: center; + color: #666; + font-size: 0.875rem; +} + +.archive-year { + margin-top: 2rem; +} + +.tag-list, +.category-list { + list-style: none; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..c9800d9 --- /dev/null +++ b/src/index.html @@ -0,0 +1,12 @@ + + + +
      + +
    + + +
    diff --git a/src/js/index.ts b/src/js/index.ts new file mode 100644 index 0000000..dd2e03d --- /dev/null +++ b/src/js/index.ts @@ -0,0 +1 @@ +console.log("Hello, Halo Theme!"); diff --git a/src/js/main.ts b/src/js/main.ts new file mode 100644 index 0000000..0f20a91 --- /dev/null +++ b/src/js/main.ts @@ -0,0 +1 @@ +import "../css/main.css"; diff --git a/src/js/post.ts b/src/js/post.ts new file mode 100644 index 0000000..d9387c7 --- /dev/null +++ b/src/js/post.ts @@ -0,0 +1 @@ +console.log("Hello, Halo Theme Post!"); diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index c053455..0000000 --- a/src/main.ts +++ /dev/null @@ -1,5 +0,0 @@ -import "./styles/main.css"; - -export function count(x: number, y: number) { - return x + y; -} diff --git a/src/page.html b/src/page.html new file mode 100644 index 0000000..0b7f9be --- /dev/null +++ b/src/page.html @@ -0,0 +1,20 @@ + + + +
    +
    +

    +
    +
    +
    + +
    + +
    +
    diff --git a/src/partials/layout.html b/src/partials/layout.html new file mode 100644 index 0000000..998e5de --- /dev/null +++ b/src/partials/layout.html @@ -0,0 +1,42 @@ + + + + + + + Site Title + + + + + + +
    +
    + +
    +
    + +
    +
    +

    + +
    +
    + + diff --git a/src/partials/pagination.html b/src/partials/pagination.html new file mode 100644 index 0000000..29972c1 --- /dev/null +++ b/src/partials/pagination.html @@ -0,0 +1,5 @@ + diff --git a/src/partials/post-card.html b/src/partials/post-card.html new file mode 100644 index 0000000..47dd2e1 --- /dev/null +++ b/src/partials/post-card.html @@ -0,0 +1,18 @@ +
  • +

    + +

    + +

    +
  • diff --git a/src/post.html b/src/post.html new file mode 100644 index 0000000..939b435 --- /dev/null +++ b/src/post.html @@ -0,0 +1,56 @@ + + + +
    +
    +

    + +
    + +
    + + + + +
    + +
    + +
    +
    diff --git a/src/styles/main.css b/src/styles/main.css deleted file mode 100644 index 0d051d0..0000000 --- a/src/styles/main.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - background: #f2f2f2; -} - -ul { - list-style: decimal; -} diff --git a/src/tag.html b/src/tag.html new file mode 100644 index 0000000..567d9c0 --- /dev/null +++ b/src/tag.html @@ -0,0 +1,13 @@ + + + +

    + +
      + +
    + + +
    diff --git a/src/tags.html b/src/tags.html new file mode 100644 index 0000000..2f41d5d --- /dev/null +++ b/src/tags.html @@ -0,0 +1,18 @@ + + + +

    Tags

    +
      +
    • + +
    • +
    + + +
    diff --git a/tsconfig.json b/tsconfig.json index fbd0225..0ce5a50 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "useDefineForClassFields": true, "module": "ESNext", "lib": ["ESNext", "DOM"], - "moduleResolution": "Node", + "moduleResolution": "bundler", "strict": true, "sourceMap": true, "resolveJsonModule": true, @@ -16,5 +16,5 @@ "noImplicitReturns": true, "skipLibCheck": true }, - "include": ["src"] + "include": ["src", "./vite-env.d.ts"] } diff --git a/src/vite-env.d.ts b/vite-env.d.ts similarity index 100% rename from src/vite-env.d.ts rename to vite-env.d.ts diff --git a/vite.config.ts b/vite.config.ts index 2a28c9b..7b1b362 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "vite-plus"; export default defineConfig({ plugins: [haloThemePlugin()], - lint: { options: { typeAware: true, typeCheck: true } }, + lint: { options: { typeAware: true, typeCheck: true }, ignorePatterns: [".agents"] }, fmt: { printWidth: 100, tabWidth: 2, @@ -13,7 +13,7 @@ export default defineConfig({ insertFinalNewline: true, sortImports: {}, sortTailwindcss: {}, - ignorePatterns: [], + ignorePatterns: [".agents"], }, staged: { "*": ["vp check"],