Halo 主题分析

theme.yaml

Halo主题存放与工作目录的 themes 目录下,在该目录下新建一个文件夹,例如 theme-foo。当前一个最小可被系统加载的主题必须在主题根目录下包含 theme.yaml 配置文件

apiVersion: theme.halo.run/v1alpha1  
kind: Theme
metadata:
  name: theme-Joe3  # 主题的唯一标识
spec:
  displayName: Theme Joe3  # 显示名称
  author:
    name: Jiewenhuang  # 作者名称
    website: "https://halo.run"  # 作者网站
  description: Halo2.0主题 Joe3  # 	主题描述
  logo:   "https://wmimg.com/i/70/2023/08/64d3c41d5bde2.webp"  # 主题 Logo
  website: "https://www.jiewen.run" # 主题网站
  repo: "https://github.com/jiewenhuang/halo-theme-joe3.0" # 主题托管地址
  settingName: "theme-Joe-setting"  # 设置表单定义的名称,需要同时创建对应的 settings.yaml 文件
  configMapName: "theme-Joe-configMap"  # 设置持久化配置的 ConfigMap 名称
  version: "1.2.2"  # 主题版本
  require: ">=2.11.0"  # 	所需 Halo 的运行版本
  customTemplates:  # 自定义模板配置
    page:  # 独立页面
      - name: 留言板
        description: 留言板文章
        screenshot:
        file: page_leaving.html
      - name: 友链
        description: 友链模板
        screenshot:
        file: page_links.html

setting.yaml

主题设置定义文件,配置主题的设置项表单。

需要在theme.yaml中配置spec.settingNamespec.configMapName

在安装或者初始化主题的时候会自动识别并在 Console 端的主题设置中生成表单

settingNameconfigMapName 必须同时配置,且可以自定义名称,但是 settingName 必须和 Setting 的 metadata.name 一致。

Setting 资源的 metadata.name 必须和 theme.yaml 中的 spec.settingName 一致。

apiVersion: v1alpha1
kind: Setting
metadata:
  name: theme-Joe-setting
  • 基本框架

spec:
  forms:
    - group: basic
      label: 基本设置
      formSchema:

forms下对应着就是一个小组,每个group就是一页设置。

FormSchema

1. 下拉框制作

formSchema:
        - $formkit: select   
          name: theme_mode
          label: 主题模式
          value: "user"
          help: "设置博客的主题模式(用户/自动/浅色/暗黑),默认为用户模式,仅在用户模式下页面才有主题切换按钮,自动模式下根据时间自动切换"
          options:
            - value: user
              label: 用户模式
            - value: auto
              label: 自动模式
            - value: light
              label: 浅色模式
            - value: dark
              label: 暗黑模式

- $formkit: select 表示用下拉框

help 底部小字的提示

options 对应着下拉框的内容。

2. 单选按钮制作

- $formkit: radio
          name: comment_option
          id: comment_option
          value: default
          label: 评论系统
          help: "选择使用的评论系统"
          options:
            - value: default
              label: 默认
            - value: waline
              label: Waline

3. 单选按钮选中Waline触发事件

- $formkit: group
          name: waline
          if: "$get(comment_option).value === 'waline'"
          label: Waline 设置
          id: waline
          children:
            - $formkit: text
              name: waline_serverURL
              label: Waline 服务端地址
              value: ""
              help: "Waline 服务端地址,如 https://waline-server.herokuapp.com 不要加结尾反斜杠"
            - $formkit: text
              name: waline_css
              label: Waline CSS地址
              value: "https://unpkg.com/@waline/client@v2/dist/waline.css"
              help: Waline 的主样式地址
            - $formkit: text
              name: waline_js_comment
              label: 用于评论的 JS 地址
              value: "https://unpkg.com/@waline/client@v2/dist/waline.mjs"
              help: Waline 的评论 JS 地址
            - $formkit: text
              name: waline_js_leaving
              label: 功能 JS
              value: "https://cdn.jsdelivr.net/npm/@waline/client/dist/waline.mjs"
              help: 用于加载留言板和最新评论的 JS 地址
            - $formkit: text
              name: waline_js_list
              label: 列表 JS
              value: "https://unpkg.com/@waline/client@v2/dist/comment.mjs"
              help: 首页加载显示评论数的 JS 地址

4. 文本

- $formkit: text
          name: mode_color_light
          label: 主题色(浅色)
          value: "#fb6c28"
          help: "浅色主题色色值,默认#fb6c28"

5. 附件

   - $formkit: attachment
          name: background_dark_mode
          label: 背景图(暗黑模式)
          help: "设置暗黑模式下的背景图(建议webp格式),为空则只显示默认背景色"
          value: ""

静态资源

目前主题的静态资源统一托管在 /templates/assets/ 目录下

模板标签的引用

  • 前面加 th

  • @{/assets/xxx} 其实就是 /templates/assets/xxx

<link rel="stylesheet" th:href="@{/assets/dist/style.css}" />
<script th:src="@{/assets/dist/main.iife.js}"></script>
<img th:src="@{/assets/images/logo.png}" />

API引用

  • 调用 #theme.assets() 的时候,资源地址不需要添加 /assets/

  • <script th:inline="javascript">用于在HTML中插入JavaScript代码片段,并支持Thymeleaf的内联表达式处理。这样可以在服务器端渲染页面时生成动态的JavaScript代码。

  • 使用 [[...]]${...}来引用Java表达式或者变量值

<script th:inline="javascript">

loadScript('[(${#theme.assets("/dist/main.iife.js")})]');

// loadScript('/themes/my-theme/assets/dist/main.iife.js');

function loadScript(url) {
    return new Promise(function (resolve, reject) {
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
    });
}
</script>

Page.html

<!doctype html>

HTML文档声明,位于HTML文档的开头,用于告知浏览器文档遵循的HTML规范版本

<html 
lang="en" 
xmlns:th="http://www.thymeleaf.org"
th:replace="~{modules/layout :: html(title = ${site.title},htmlType = sheet,header = null,leftSidebar = true,content = ~{::content}, head = null, footer = null)}"
>

  • html标签:HTML文档的根元素,所有其他HTML元素都必须嵌套在这个标签内。

  • xmlns:th="http://www.thymeleaf.org"

    • xmlns:使用什么命名空间。用来解决元素和属性名称冲突

    • 设置:th="http://www.thymeleaf.org"就是想要该文档的所有与thymeleaf模板指令下的标签属性合法化

  • th:replace:这是一个thymeleaf模板下的一个属性,当你使用 th:replace 时,Thymeleaf 会查找指定的模板片段,并用该片段的内容完全替换掉带有 th:replace 属性的元素。这意味着原标签及其内部的所有内容都会被删除,只保留新的内容。

    • ~{modules/layout :: html(...)}:引用了布局模块(layout)中的html片段,并传入了一系列参数

    • title: 设置页面标题,值来源于变量site.title

    • htmlType: 设置HTML类型为sheet,这通常是自定义的属性,用于在布局模板中根据不同需求切换不同类型的页面结构

    • content = ~{::content}表示引用当前模板文件内的一个名为 content 的片段。通常由 th:fragment 指令定义一个片段,然后将内容传递给layout

  • th:replace 指令的作用是将当前标签及其内容完全替换为指定模板片段的结果

也就是将下面的整个数据(标签、内容)传递给Layout.html下的content

<th:block th:fragment="content">
  ...
</th:block>

主体内容 navbar

<th:block th:replace="~{modules/macro/navbar :: navbar}" />

主体内容

<div class="joe_main">
  ...
</div>
<h1 class="joe_detail__title"
     th:classappend="${theme.config.post.enable_title_shadow ? 'txt-shadow':''}">[[${singlePage.spec.title}]]</h1>
  • enable_title_shadow:标题阴影【是否为文章页标题应用文字阴影】

  • [[${singlePage.spec.title}]] :文章标题

主体内容 评论

主体内容 aside

 <th:block th:if="${theme.config.aside.enable_sheet_aside}">
    <th:block th:replace="~{modules/common/aside :: aside}" />
  </th:block>
  • enable_sheet_aside:自定义页侧边栏 【自定义页面右侧是否展示侧边栏,默认关闭。开启后,所有自定义页面都会展示侧边栏(若部分页面不想展示,可以配置对应页面的元数据 enable_aside 为 false )】

主体部分 action

<th:block th:replace="~{modules/common/actions :: actions}" />

<th:block th:replace="~{modules/common/footer :: footer}" />

主体部分 tail

<th:block th:replace="~{modules/macro/tail :: tail}" />

Layout.html

<!DOCTYPE html>
<html
  lang="en"
  xmlns:th="https://www.thymeleaf.org"
  th:fragment="html (head,content)"
>
  • th:fragment="html (head,content)" 是一个片段定义。这个片段被定义为 html,并带有两个参数(head和content)见(page.html)

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=2"
    />
    <title th:text="${site.title}"></title>
    <link rel="stylesheet" th:href="@{/assets/css/style.css}" />
    <script th:src="@{/assets/js/main.js}"></script>
    <th:block th:if="${head != null}">
      <th:block th:replace="${head}" />
    </th:block>
</head>

  • <meta charset="UTF-8"> 来指定网页使用的字符编码为UTF-8

  • <meta http-equiv="X-UA-Compatible" content="IE=edge"> 声明让IE浏览器使用最新可用的渲染引擎来显示网页内容

  • <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2"> 设置了视口属性,使网页能响应不同设备屏幕尺寸的变化,初始缩放比例为1,最大缩放比例为2

  • <th:block th:if="${head != null}"> 如何head不为空,需要将<th:block th:replace="${head}" /> 替换为 传过来的 head 信息。(page.html传过来的head为null)

<body th:style="'background-color: '+${theme.config.style.background_color ?: '#f2f2f2'}">
    <section>
      <th:block th:replace="${content}" />
    </section>
</body>
  • <section> 是 HTML5 中的一个语义化标签,用于划分文档中的不同部分或区域

assets/js/main.js

console.log("Hi from main.js");

assets/css/style.css

body {
  font-family: "Open Sans", sans-serif;
  font-size: 14px;
  color: #333;
  line-height: 1.5;
  margin: 0;
  padding: 0;
}
  • 当渲染文本时,优先尝试使用名为 "Open Sans" 的字体

  • 如果用户的计算机或设备上没有安装 "Open Sans" 字体,则自动选用任何可用的无衬线字体(sans-serif

modules/macro/navbar

  • 顶部导航栏

    • logo

    • 自选导航栏

  • 搜索按钮

  • 侧边栏

    • 栏目

    • 文章目录

<div class="joe_header__above"
  th:classappend="|${theme.config.theme.enable_show_in_up ? 'topInDown':''} ${theme.config.navbar.enable_fixed_header ? 'fixed':''} ${theme.config.navbar.enable_fixed_header and theme.config.navbar.enable_glass_blur ? 'glass':''}|">

  • th:classappend 是Thymeleaf的一个特性,它允许我们在运行时根据表达式的值向元素的class属性追加类名

  • enable_show_in_up : 开启模块缓入效果(全局),开启后,当页面载入时,页面中的模块会有缓入动画

  • enable_fixed_header : 为true时,当页面滚动时,导航条会始终固定在可视区域顶部

  • enable_glass_blur :毛玻璃效果。是否开启导航条毛玻璃效果(默认关闭以节省性能,仅在“导航条吸顶”开启时生效)

<div class="joe_container joe_header_container"
    th:classappend="${theme.config.navbar.enable_full_header ? 'full':''}">

  • enable_full_header:100%宽度,导航条宽度是否100%,默认和内容区域宽度一致

<th:block th:if="${theme.config.navbar.show_logo}"
                th:with="logoLink = ${theme.config.navbar.logo_link == '#'}?'javascript:;':${theme.config.navbar.logo_link}">
      <a th:title="${site.title}" class="joe_header__above-logo" th:href="${logoLink == ''?site.url:logoLink}">
        <img th:style="'border-radius:'+${theme.config.navbar.logo_radius} ?:'4px'" th:src="${site.logo}"  th:alt="${site.title}">
      </a>
</th:block>

  • show_logo:展示博客LOGO 【导航条是否展示博客LOGO】

  • th:with 定义临时变量 logoLink

  • logo_link:LOGO跳转链接 【点击LOGO时跳转的链接,不填默认跳转博客主页(不想跳转请填入#)】

  • logo_radius: LOGO圆角值 【导航栏博客LOGO的圆角值,像素或百分比,默认4px】

modules/common/aside

  • 博主信息:modules/widgets/asideWidget

  • 公告

  • 图片

  • 音乐播放器

  • 最新文章

  • 人生倒计时

  • 最新评论

  • 标签云

  • 侧边栏广告

modules/widgets/asideWidget

<th:block th:fragment="enable_blogger">
    <th:block th:replace="~{modules/common/blogger :: blogger}" />
</th:block>

modules/common/blogger

  • author_bg:博主栏背景图

  • avatar_frame:头像框

  • avatar_widget:头像挂件

  • 博主等级

  • motto:个人独白

  • enable_day_words:开启每日一句

  • 搭配方案:分类数+标签数+文章数 等

  • enable_weather : 展示天气信息

  • weather_key:天气插件appkey

  • socials: 社交信息

  • enable_strips:是否展示彩带动画

modules/common/actions

  • enable_mobile_toc:移动端文章TOC目录 【是否启用移动端的TOC目录】

  • 模式切换:黑夜模式和白天模式

  • enable_back2top:开启返回顶部 【是否开启返回顶部功能】

modules/macro/tail

引入脚本

  • lazysizes.min.js:是一个轻量级的、高性能的延迟加载(lazy loading)JavaScript库,可以轻松实现图片及其他资源的延迟加载功能

  • wow.min.js:主要用于在网页中实现平滑滚动动画效果。当用户滚动页面时,Wow.js能够检测到元素进入可视区域,并按照预先设定的动画效果(如淡入、滑动、旋转等)触发相应的CSS3动画。这个库常与Animate.css等CSS动画库一起使用,以实现丰富的动画效果

  • qmsg.js:是一个可能与即时消息推送服务相关的JavaScript库

  • marked.min.js:将Markdown格式的文本转换为HTML

  • utils.min.js

  • swiper-bundle.min.js

  • tocbot.min.js

  • vue.min.js

  • jquery.fancybox.min.js

  • APlayer.min.css

  • meting.min.js

  • clipboard.min.js

  • favico.min.js

  • jquery.qrcode.min.js

相册页

<th:block th:if="${htmlType == 'photos'}">
        <script th:src="${source_link+'/assets/lib/justifiedGallery/justifiedGallery.min.js'}"></script>
        <script th:src="${source_link+'/assets/lib/masonry/masonry.pkgd.min.js'}"></script>
        <script th:src="${source_link+'/assets/lib/masonry/isotope.pkgd.min.js'}"></script>
        <script th:src="${source_link+'/assets/lib/masonry/imagesloaded.pkgd.min.js'}"></script>
</th:block>