开发新的主题
主题开发
创建主题时,需要在 package.json
中指定 name
字段、在 config.yml
中指定 id
字段和 language
字段,否则合法性检查会失败。
示例主题可以在此处找到:mogland-dev/mog-theme-tiny-ejs (opens in a new tab)
- 按照主题目录结构创建主题目录。
- 开始开发主题,其中需要有 EJS 的基础语法知识,主题渲染中提供了一些变量,可以在 EJS 中使用,具体变量见 变量。
- 在
config.yml
中指定主题的配置,具体配置见 主题配置。 - 开发完成后,将主题上传至 GitHub,然后标注
mog-theme
标签,等待 awesome-mog (opens in a new tab) 更新完毕即可在主题市场中搜索到。
正常情况下最多在 3 小时后,你的项目将能出现在 Awesome List 和 Mog 主题市场中
主题目录结构
- packages.json: 主题的配置文件,类似于 npm 的
packages.json
,用于描述主题的基本信息,如:主题名字、版本、作者、描述等 - config.yaml: 主题的配置文件,用于描述主题的配置项,如:头像源、评论系统等 - #主题配置
- i18n.yaml: 主题的国际化文件,用于描述主题的国际化信息 - #i18n-方案
- assets: 主题的静态资源文件,如:css、js、图片等 - #主题静态资源
- plugins: 主题的插件文件,用于注入方法到主题中,如:moment.js 等方法类库 - #主题模板扩展
- index.ejs: 主页
- post.ejs: 文章页
- page.ejs: 页面页
- archives.ejs: 归档页,分类 & 标签页
- friends.ejs: 友链页
- 404.ejs: 404页
- page-*.ejs: 自定义页面,如:关于页 等,
*
为自定义的页面路径 - preview.png: 主题预览图
主题配置
我们考虑使用 YAML 定义主题配置
id: "theme.tiny.wibus-wee"
language: zh # 语言, 要与 i18n.yaml 中设置的语言一致
configs:
- name: "头像源"
key: "avatar_source"
# key is optional, if not set, it will be the same as name
# But I recommend you to set it if your name contains special characters or use Chinese
type: "select"
data:
- name: "Gravatar"
# key: "Gravatar"
value: "https://cn.gravatar.com/avatar/"
- name: "国内源"
key: "China"
value: "https://cdn.v2ex.com/gravatar/"
value: "Gravatar"
- id: 主题的唯一标识,格式建议为
theme.{name}.{author}
- language: 主题的语言,要与 i18n.yaml 中设置的语言匹配
- key: 配置储存名(取值的时候使用此名)
key 是可选的,如果不去设置就与 name 是一样的。但是当 name 中涉及到一些特殊符号或者涉及中文字符的时候,强制需要填入 key
- type: 配置组件
- data: 传入配置组件的值(内部的定义与上方相似)
- value: 默认值,会传入配置组件,可选
配置组件
所有的 key 一般情况下除特殊要求,皆为可选字段,如果不设置 key 就与 name 一样。但是当 name 中涉及到一些特殊符号或者涉及中文字符的时候,强制需要填入 key。
-
input: 输入框
type: "input" value: "https://cn.gravatar.com/avatar/"
传入的 data 为一个对象,对象中包含 value 一个必须的字段,key 为可选字段,如果不设置 key 就与 name 一样。
-
switch: 开关
type: "switch" value: true
传入的 data 为一个对象,对象中包含 value 一个必须的字段,要求 value 为布尔值。
-
color: 颜色选择器
type: "color" value: "#000000"
传入的 data 为一个对象,对象中包含 value 一个必须的字段,要求 value 为颜色值。
-
radio: 单选框
type: "radio" data: - name: "Gravatar" value: "https://cn.gravatar.com/avatar/" - name: "国内源" key: "China" value: "https://cdn.v2ex.com/gravatar/" value: "Gravatar"
传入的 data 为一个数组,数组中的每一项都是一个对象,对象中包含 name, value 两个必须的字段。key 为可选字段,如果不设置 key 就与 name 一样。
value 为传入的值,value 会传入配置组件,需要与 data 中的 key 对应。
-
checkbox: 多选框
type: "checkbox" data: - name: "Gravatar" value: "https://cn.gravatar.com/avatar/" - name: "国内源" key: "China" value: "https://cdn.v2ex.com/gravatar/" value: "Gravatar"
传入的 data 为一个数组,数组中的每一项都是一个对象,对象中包含 name, value 两个必须的字段。key 为可选字段,如果不设置 key 就与 name 一样。
value 为传入的值,value 会传入配置组件,需要与 data 中的 key 对应。
-
textarea: 多行文本框
type: "textarea" value: "https://cn.gravatar.com/avatar/"
传入的 data 为一个对象,对象中包含 value 一个必须的字段,key 为可选字段,如果不设置 key 就与 name 一样。
-
select: 下拉选择框
type: "select" data: - name: "Gravatar" value: "https://cn.gravatar.com/avatar/" - name: "国内源" key: "China" value: "https://cdn.v2ex.com/gravatar/" value: "Gravatar"
传入的 data 为一个数组,数组中的每一项都是一个对象,对象中包含 name, value 两个必须的字段。key 为可选字段,如果不设置 key 就与 name 一样。
value 为传入的值,value 会传入配置组件,需要与 data 中的 key 对应。
变量
全局变量
{
site: any, // 见 「网站变量」
page: any, // 见 「页面变量」
config: ConfigInterface, // 见 「ConfigInterface」- https://github.com/mogland/core/blob/main/libs/config/src/config.interface.ts#L14-L30
theme: any, // 主题配置文件
path: string, // 当前页面的路径(不含根路径)
url: any, // 见 「URL变量」
}
网站变量
{
posts: PostModel[], // 文章列表
pages: PageModel[], // 分页列表
categories: CategoryModel[], // 分类列表
tags: { // 标签列表
count: number, // 标签数量
name: string, // 标签名
}[],
}
页面变量
页面(page
): PageModel
(opens in a new tab) - 不会输出 password
等隐私字段
文章 (post
): PostModel
(opens in a new tab) - 不会输出 password
等隐私字段
首页(index
)
{
docs: PostModel[], // 文章列表
totalDocs: number, // 文章总数
limit: number, // 每页文章数
totalPages: number, // 总页数
page: number, // 当前页
hasPrevPage: boolean, // 是否有上一页
hasNextPage: boolean, // 是否有下一页
prevPage: number | null, // 上一页页码
nextPage: number | null, // 下一页页码
}
分类 (category
) :CategoryModel
(opens in a new tab)
data: {
[key in keyof CategoryModel]: CategoryModel[key]; // 继承自 CategoryModel 的字段
children: PostModel[], // 文章列表
},
isTag: false, // 是否是标签页
isCategory: true, // 是否是分类或者标签页
标签 (tag
)
data: {
name: string, // 标签名
children: PostModel[], // 文章列表
},
isTag: true, // 是否是标签页
isCategory: true, // 是否是分类或者标签页
归档 (archives
)
data: {
children: PostModel[], // 文章列表
},
isCategory: false, // 是否是分类或者标签页
友链 (friends
) :FriendsModel
URL变量
{
url: string, // 完整的链接
path: string, // 路径
query: object, // 查询参数
params: object, // 路径参数
origin: string, // 域名: protocol + host
host: string, // 域名: host
protocol: string, // 协议
}
i18n 方案
我们可以使用内置函数 _i(<key>)
,这个函数可以根据传入的 key 来获取对应的 value。
定义 i18n key
我们使用 yaml 文件来定义 i18n,在主题根目录创建 i18n.yaml
,并定义 key。
en: # 语言
hello: 'Hello' # key
world: 'World'
zh:
hello: '你好'
world: '世界'
按照上面的定义,我们可以通过 _i('hello')
来获取对应的 value。当无法找到对应的 key 时,会返回 key 本身。
<%- _i('hello') %> <%- _i('world') %>
<!-- zh:你好 世界 -->
<!-- en:Hello World -->
主题模板扩展
如果你想在主题模板中使用函数输出某些东西,你可以通过「扩展」来实现这个功能。
在 plugins
文件夹下创建一个 js 文件,然后在该文件中使用 module.exports
导出一个对象,对象中包含一个 name
属性,用于定义插件名称,以及其他函数,用于在主题模板中调用。
定义主题模板扩展插件
我们将会遍历 plugins
文件夹下的所有文件,将其作为主题模板扩展插件,将其挂载到主题模板中。
比如说,我们创建一个 time.js
文件,用于输出当前时间。
// plugins/time.js
module.exports = {
name: 'time',
time: () => {
return new Date().toLocaleString()
},
}
在主题模板中,我们可以通过 time()
来调用 time.js
中定义的 time
函数。
<%- time() %>
在函数内,你依然可以使用 变量 中定义的变量。
例子:在头部输出 OpenGrraph 标签
module.exports = {
name: 'opengraph', // 插件名称
opengraph: function () {
const siteName = config.seo.title; // 网站名称
const siteDescription = config.seo.description; // 网站描述
const isArticle = page.title ? true : false; // 是否是文章页(有 title)
const title = isArticle ? page.title : siteName; // 标题
const summary = isArticle ?
page.summary ? page.summary : page.text.substring(0, 100)
: siteDescription; // 描述,文章页取文章描述,首页取网站描述
const image = page.image ? page?.image[0].url : config.user.avatar; // 图片,文章页取文章图片,首页取网站头像
const _url = url.url; // 当前页面链接
const type = isArticle ? 'article' : 'website'; // 类型,文章页为 article,首页为 website
return ` // 返回 OpenGrraph 标签
<meta property="og:title" content="${title}" />
<meta property="og:type" content="${type}" />
<meta property="og:url" content="${_url}" />
<meta property="og:image" content="${image}" />
<meta property="og:site_name" content="${siteName}" />
<meta property="og:description" content="${summary}" />
`
},
}
在主题模板中,我们可以通过 opengraph()
来调用 opengraph.js
中定义的 opengraph
函数。
<%- opengraph() %>
主题静态资源
如果你想在主题中使用静态资源,你可以将静态资源放在 assets
文件夹下,然后在主题模板中请求 /raw/assets/*
来获取静态资源。
<link rel="stylesheet" href="/raw/assets/css/style.css">
需要注意的是,以下文件可以直接访问,无需通过 /raw/*
来访问:
assets/favicon.*
assets/robots.txt
./favicon.*
./robots.txt
<link rel="icon" href="/raw/assets/favicon.ico">
<!-- 可以变成 -->
<link rel="icon" href="/favicon.ico">
也就是说,如果你想要获取 ejs 等源文件,你可以通过 /raw/*
来获取:
主题评论组件
以 mog-comments-wc (opens in a new tab) 为例,我们可以使用它在主题中实现评论功能。
本地引入
- 从 Release (opens in a new tab) 页面下载最新的
mog-comments-wc.js
文件,放到assets/js
文件夹下。 - 在主题模板中引入
mog-comments-wc.js
文件。
<script src="/raw/js/mog-comments-wc.js"></script>
- 在主题模板合适的地方中添加评论组件。
<mog-comments api="<%= config.serverUrl %>" pid="<%= page.id %>"></mog-comments>
CDN 引入
- 在主题模板中引入
mog-comments-wc.js
文件。
<script src="https://cdn.jsdelivr.net/gh/mogland/mog-comments-wc/dist/mog-comments-wc.js"></script>
- 在主题模板合适的地方中添加评论组件。
<mog-comments api="<%= config.serverUrl %>" pid="<%= page.id %>"></mog-comments>