Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ public class MomentRouter {

使用 `TemplateNameResolver` 来解析模板名称,如果主题提供了对应的模板,那么就使用主题提供的模板,否则使用插件提供的模板,如果直接返回模板名称,那么只会使用主题提供的模板,如果主题没有提供对应的模板,那么会抛出异常。

## 复用当前主题页面布局

从 Halo 2.26.0 开始,如果插件提供的前台页面希望复用当前主题的页头、页脚和整体页面外壳,可以在插件模板中调用 `layout :: html(...)`:

```html title="src/main/resources/templates/moment.html"
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{layout :: html(head = ~{::head}, content = ~{::content})}"
>
<th:block th:fragment="head">
<title>瞬间 - [[${site.title}]]</title>
</th:block>
<th:block th:fragment="content">
<section>
<!-- 插件页面正文 -->
</section>
</th:block>
</html>
```

`layout` 是 Halo 保留的集成模板名。当当前主题提供符合契约的 `templates/layout.html` 时,插件页面会使用主题布局;否则会使用 Halo 内置的 fallback 布局。完整契约可参考[页面布局契约](../../../theme/page-layout.md)。

## 模板片段

如果你的默认模板不止一个,你可能需要通过模板片段来抽取一些公共的部分,例如,你的插件提供了一个 `moment.html` 模板,你可能需要抽取一些公共的部分,例如头部、尾部等,你可以这样做:
Expand Down
6 changes: 6 additions & 0 deletions docs/developer-guide/theme/api-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ title: API 变更日志
description: 记录每一个版本的主题 API 变更记录,方便开发者适配
---

## 2.26.0

### 主题目录结构 > 新增页面布局契约

在 2.26.0 中,主题可以通过 `templates/layout.html` 提供 `html(head, content)` 片段,让插件前台页面复用当前主题的页面外壳。Halo 会在主题安装、更新或重载后检查这个模板,并通过 `Theme.status.pageLayout` 暴露 `SUPPORTED`、`MISSING` 或 `INVALID` 状态;未适配或校验异常时,使用布局契约的插件页面会回退到 Halo 内置布局。详细文档可查阅:[页面布局契约](../../developer-guide/theme/page-layout.md)。

## 2.25.0

### 表单定义 > `select` 选项支持图标和描述
Expand Down
6 changes: 4 additions & 2 deletions docs/developer-guide/theme/code-snippets.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: 本文档介绍了常用的代码片段,以便于开发者快速

## 布局模板

通常情况下,我们需要一个公共模板来定义页面的布局。
通常情况下,我们需要一个公共模板来定义页面的布局。从 Halo 2.26.0 开始,如果主题在 `templates/layout.html` 中声明 `html(head, content)` 片段,这个模板也会被识别为[页面布局契约](./page-layout.md),用于让插件前台页面复用当前主题的页面外壳。

```html title="templates/layout.html"
<!DOCTYPE html>
Expand Down Expand Up @@ -33,7 +33,7 @@ description: 本文档介绍了常用的代码片段,以便于开发者快速
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{modules/layout :: html(head = null,content = ~{::content})}"
th:replace="~{layout :: html(head = null, content = ~{::content})}"
>
<th:block th:fragment="content">
<!-- 文章列表 -->
Expand All @@ -45,3 +45,5 @@ description: 本文档介绍了常用的代码片段,以便于开发者快速
</th:block>
</html>
```

如果只是主题内部私有布局,也可以放在 `templates/modules/layout.html` 等其他路径中,再通过 `modules/layout :: ...` 引用。
87 changes: 87 additions & 0 deletions docs/developer-guide/theme/page-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: 页面布局契约
description: 了解如何通过主题提供页面布局,让插件前台页面复用当前主题的页面外壳。
---

从 Halo 2.26.0 开始,主题可以通过 `templates/layout.html` 提供一个标准页面布局契约。插件提供的前台页面调用这个契约后,可以复用当前主题的页头、页脚、样式和响应式布局;如果当前主题没有适配,Halo 会使用内置的 fallback 布局保证页面可以继续渲染。

这个能力是增量适配项,不会影响主题安装、升级、启用,也不会替代主题内部自用的普通模板片段。

## 适配主题布局

主题需要在 `templates/layout.html` 中声明 `html(head, content)` 片段:

```html title="templates/layout.html"
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" th:lang="${#locale.toLanguageTag}" th:fragment="html (head, content)">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="${site.title}"></title>
<link rel="stylesheet" th:href="@{/assets/dist/style.css}" />
<th:block th:if="${head != null}">
<th:block th:replace="${head}" />
</th:block>
</head>
<body>
<header>
<!-- 主题页头 -->
</header>
<main>
<th:block th:replace="${content}" />
</main>
<footer>
<!-- 主题页脚 -->
</footer>
<halo:footer />
</body>
</html>
```

其中:

- `head`:调用方提供的头部片段,通常用于插入页面标题、meta 信息或当前页面需要的资源。
- `content`:调用方提供的正文片段,应被插入到主题页面主体中。

主题可以自行决定外层容器、页头、页脚、暗黑模式、响应式布局等实现细节。建议保留 `<halo:footer />`,以便 Halo 和插件继续向页面底部注入必要内容。

如果你的主题已经使用 `templates/layout.html` 作为内部公共模板,只要它声明了 `html(head, content)` 片段,就可以同时作为页面布局契约使用。如果这只是主题内部私有模板,建议改用 `templates/modules/layout.html` 等其他路径,避免被识别为插件页面集成布局。

## 插件页面调用布局

插件模板可以通过 `layout :: html(...)` 调用当前主题的页面布局:

```html title="src/main/resources/templates/moment.html"
<!DOCTYPE html>
<html
xmlns:th="https://www.thymeleaf.org"
th:replace="~{layout :: html(head = ~{::head}, content = ~{::content})}"
>
<th:block th:fragment="head">
<title>瞬间 - [[${site.title}]]</title>
</th:block>
<th:block th:fragment="content">
<section>
<!-- 插件页面正文 -->
</section>
</th:block>
</html>
```

`layout` 是 Halo 为插件前台页面保留的集成模板名。这个特殊解析只对插件自身提供的模板生效,例如通过 `plugin:<plugin-name>:moment` 解析出的模板。即使插件包内存在 `templates/layout.html`,它也不会用于满足主题的页面布局契约。

插件内部私有布局应继续使用其他模板名,并通过 `plugin:<plugin-name>:` 前缀显式引用。详细方式可参考[在插件中提供主题模板](../plugin/api-reference/server/template-for-theme.md)。

## 兼容状态

主题安装、更新或重载后,Halo 会静态检查 `templates/layout.html`,并在 `Theme.status.pageLayout` 中记录兼容状态:

- `SUPPORTED`:主题提供了符合 `html(head, content)` 契约的布局。
- `MISSING`:主题未提供 `templates/layout.html`,使用布局契约的插件页面会使用 Halo 的 fallback 布局。
- `INVALID`:主题提供了 `templates/layout.html`,但片段签名不符合当前契约。

缺失或异常不会让主题进入失败状态。Console 会在主题详情和主题列表中展示页面布局状态,帮助用户和主题开发者判断是否需要适配。

## 版本演进

当前 v1 契约只包含 `head` 和 `content` 两个片段参数。主题开发者不应假设调用方一定提供更多片段;插件开发者也不应依赖主题私有变量来渲染核心内容。后续如果需要新增插槽,Halo 会优先考虑新的片段名或新的契约版本,避免破坏已适配的主题。
8 changes: 5 additions & 3 deletions docs/developer-guide/theme/structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ my-theme
│ │ │ └── style.css
│ │ └── js/
│ │ └── main.js
│ ├── layout.html
│ ├── index.html
│ ├── post.html
│ ├── page.html
Expand All @@ -30,6 +31,7 @@ my-theme

1. `/templates/` - 主题模板目录,存放主题模板文件,所有模板都需要放在这个目录。关于模板的详细说明,请查阅 [模板编写](./template-variables.md)。
2. `/templates/assets/` - 主题静态资源目录,存放主题的静态资源文件,目前静态资源文件只能放在这个目录,引用方式请查阅 [静态资源](./static-resources)。
3. `/screenshot.png` - 可选的主题预览图文件,支持 `screenshot.png`、`screenshot.jpeg`、`screenshot.jpg` 和 `screenshot.webp`。Halo 会按此顺序识别第一个可读文件,用于 Console 主题预览,并通过 `Theme.status.screenshot` 暴露访问地址。
4. `/theme.yaml` - 主题配置文件,配置主题的基本信息,如主题名称、版本、作者等。详细文档请查阅 [配置文件](./config)。
5. `/settings.yaml` - 主题设置定义文件,配置主题的设置项表单。详细文档请查阅 [设置选项](./settings)。
3. `/templates/layout.html` - 可选的页面布局契约模板,从 Halo 2.26.0 开始可用于让插件前台页面复用当前主题的页面外壳。详细文档请查阅 [页面布局契约](./page-layout.md)。
4. `/screenshot.png` - 可选的主题预览图文件,支持 `screenshot.png`、`screenshot.jpeg`、`screenshot.jpg` 和 `screenshot.webp`。Halo 会按此顺序识别第一个可读文件,用于 Console 主题预览,并通过 `Theme.status.screenshot` 暴露访问地址。
5. `/theme.yaml` - 主题配置文件,配置主题的基本信息,如主题名称、版本、作者等。详细文档请查阅 [配置文件](./config)。
6. `/settings.yaml` - 主题设置定义文件,配置主题的设置项表单。详细文档请查阅 [设置选项](./settings)。
1 change: 1 addition & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ module.exports = {
"developer-guide/theme/prepare",
"developer-guide/theme/config",
"developer-guide/theme/structure",
"developer-guide/theme/page-layout",
"developer-guide/theme/static-resources",
"developer-guide/theme/settings",
"developer-guide/theme/annotations",
Expand Down
Loading