能力有限,目前主题开发只是在大佬们开发的主题上折腾,加上一些自己想要的功能(Memos,时间轴以及随手拍等)。
自定义模板
一般来说,新增功能页面通过新增自定义模板的方式来进行会比较方便,最好就是有功能相近的页面进行参考。例如我的Memos页面就是搬了瞬间的前端代码,修改了获取渲染数据的逻辑代码,来达成展示Memos数据的目标。
说是模板,但无非也是一个HTML页面,实现非模板的功能页面当然也可以。
目前 Halo 支持为 文章、独立页面和分类归档 设置自定义模板。
新增自定义模板配置
修改主题文件目录下的 theme.yaml
文件,定义新模板,方式如下:
在 theme.yaml
的 spec
节点下添加如下配置:
customTemplates:
{type}:
- name: {name}
description: {description}
screenshot: {screenshot}
file: {file}.html
字段说明:
type
:模板类型,目前支持post
page
category
。name
:模板名称description
:模板描述screenshot
:模板预览图file
:模板文件名,需要在/templates/
目录下创建
示例:
customTemplates:
page:
- name: 留言板
description: 留言板文章
screenshot:
file: page_leaving.html
- name: Memos
description: Memos
screenshot:
file: memos_display.html
配置加载后,就能在Halo后台新建相应类型的内容时,在设置处,选择自定义模板
。此时打开内容则会打开自定义模板配置时的html
文件。具体的功能便可以以此 html 文件为起点进行设计开发。
setting.yaml
定义主题配置里的配置项。在spec.forms
节点下添加新配置定义
spec:
forms:
- group: basic
label: 基本设置
formSchema:
- $formkit: select
name:
if:
label:
value: 'user'
help: ''
group
:配置分组,即配置的导航栏菜单选项label
:group对于显示的名称formkit
:配置方式select
:下拉选择框radio
:复选框text
:文本输入number
:数值输入attachment
:文件路径选择group
:一组配置,含多个子配置
name
:配置变量名if
:判断条件,满足条件表单结构才会显示label
:配置展示名value
:默认值help
:配置提示信息options
:选择项配置,配置方式为select或radio时需要给出选择项children
:子配置,当配置方式为group时需要添加子配置default
: 默认值validation
:验证规则required
:表示字段是必填项,不能为空。minLength:length
:要求字段值的长度至少为 lengthmaxLength:length
:要求字段值的长度最大为 lengthnumeric
:验证字段值是否为数字matches:fieldName
:验证当前字段的值是否和另一个name为 fieldName 的字段的值相匹配alpha
:只允许字母alpha_num
:只允许字母和数url
:验证是否为有效的 URL。min:value
:数值字段的最小值验证。max:value
:数值字段的最大值验证。pattern:regex
:使用正则表达式进行验证。
validationMessage
:验证提示信息
Tip:theme.yaml 以及 setting.yaml 是持久化存储在数据库中的,不会在修改之后主动更新,需要重新加载主题才能生效
模板变量
以单页为例,在单页模板开发中,可以用到变量singlePage
,singlePage是SinglePageVo类型,类型定义如下:(来源Halo文档)
{
"metadata": {
"name": "string", // 唯一标识
"creationTimestamp": "2022-11-20T14:29:44.601Z", // 创建时间
......
},
"spec": {
"title": "string", // 标题
"cover": "string", // 封面图
......
"stats": {
"visit": 0, // 访问数量
"upvote": 0, // 点赞数量
"comment": 0 // 评论数量
},
"owner": "#ContributorVo", // 创建者
"content": "#ContentVo" // 内容
}
在进行单页的模板开发时就可以用到这个变量。我所用到的场景是时间轴/里程碑页面的数据传递。通过编辑页面内容,在页面内容里保存数据,而页面代码通过${singlePage.content.content}
获取到内容字符串,再进行后续的数据处理。相当于把页面的数据存储在了编辑器的内容里。
变量类型具体信息参考Halo官方文档
元数据
元数据,应该是指在每个内容的设置页面里的配置数据。
元素据的定义在主题根目录下的annotation-setting.yaml
文件。
代码中使用参考:
<!--是否开启侧边栏-->
<th:block th:if="${#annotations.getOrDefault(singlePage, 'enable_aside', 'true') == 'true'}">
<th:block th:replace="~{modules/common/aside :: aside}" />
</th:block>
<!--end-->
htmlType
主题的多数html文件头部都有着类似代码:
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
th:replace="~{modules/layout :: html(title = ${theme.config.journals.journals_title ?: '我的动态'}+'-'+${site.title},htmlType = journals,header = null,leftSidebar = true,content = ~{::content}, head = null, footer = null)}"
>
其中指定了htmlType
,不同的htmlType,代码所加载的内容会不同,例如引入的js文件,链接的css文件等。
以tail.html
为例,多数html文件最后都含有这句代码
<th:block th:replace="~{modules/macro/tail :: tail}" />
在templates/modules/macro
下找到tail.html
文件,此文件用于引入js文件,同时会根据htmlType的不同,引入不同的文件。
Finder API
为了满足在任意位置获取数据的需求,Halo官方提供了 Finder API
例如,需要获取指定单页的数据,可以使用singlePageFinder.getByName(pageName) ,API将返回所指定页面的SinglePageVo singlePage
,就是上面模板变量的例子。
如此就可以在任何地方获取到想要的数据。
其中参数pageName
为单页的唯一标识,其他类型内容的API也是一样,即上面例子中的metadata.name
。较为直接的获取单一内容唯一标识的方法是,点开内容的编辑页面,点开后的URL,接于name=
后的是唯一标识。
RESTful API
Halo 也提供了 RESTful 风格的 API。
Finder API有一定的使用限制,它是Halo自定义的方法,并不能在JS代码中灵活使用,需要使用注入之类的方法进行传递数据。
<script th:inline="javascript"> // 服务器端注入数据
const Config = {
Host: /*[[${theme.config.memos.memos_host}]]*/ '',
Height: /*[[${theme.config.memos.memos_block_height}]]*/ 300,
isShow: /*[[${theme.config.memos.enable_memos_link}]]*/ true
};
</script>
以singlePageFinder.getByName(pageName) 为例,如果参数pageName是在JS代码执行过程中动态获得的,想通过getByName获取数据就不是那么容易了,此时就需要Halo提供的RESTful API。
同样的例子,Halo提供了SinglePage的API/apis/api.content.halo.run/v1alpha1/singlepages/{name}
来获取单页的数据。
async function getSinglePageDataByName(name) {
try {
const url = `/apis/api.content.halo.run/v1alpha1/singlepages/${name}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
......
} catch (error) {
console.error('Error fetching post:', error);
return null;
}
}
其他API参考 Halo API 文档
wl
05 / 07用上了!谢!
From Nginx Proxy Manager 登录出错 Bad gateway