# Kite 使用与开发参考 [返回 README](../README.zh-CN.md) ## 后台 `kite run` 会在 `/admin/` 打开后台。它是一个编译进二进制的 React 应用,没有东西 要装,也没有东西需要和服务端对版本。在空文件夹里运行时,它会先打开一个建站页面, 填好站点名称、你的名字、地址和语言就在这里建好站点,问的就是 `kite init` 在终端里 问的那些。你的名字就是站点的作者。 | | | |---|---| | **仪表盘** | 已发布多少、还有多少草稿、有哪些没提交,以及按月的发布趋势 | | **内容** | 文章和页面,通过索引而不是文件系统来过滤和搜索 | | **编辑器** | 可视化编辑,读写的都是 Markdown,一键切到源码;实时预览、front matter 表单、分类标签、slug、字数,图片直接拖进 bundle,“附件”里列出 bundle 的文件和正文用到了哪些,可以复制链接、原地替换或删除;草稿边写边自动保存,还没保存的内容先留在浏览器里 | | **分类法** | 标签和分类在全部内容里的真实分布 | | **主题** | 项目里的全部主题,启用前都能在整站上预览;上传 zip 安装主题;在实时预览旁边调整当前主题的设置 | | **应用中心** | 索引里的主题和插件,在这里搜索、安装和更新;安装前列出它会从哪些网站加载东西、注入几段代码、运行哪些钩子;更新会覆盖手动修改、或者插件要做更多事时,要先勾选同意 | | **部署** | 把网站导出成 zip,上传到任何地方;或者查看推送到 GitHub Pages 进行到了哪一步 | | **设置** | 站点的标题、描述、地址、语言、关键词、时区、搜索引擎收录和全站附加代码;后台自己的语言和配色;你的账户:名称、头像、密码和登录会话 | 加载之后又在磁盘上变过的内容,会被拒绝写入而不是覆盖,后台会明说这件事。后台 界面支持 English 和简体中文,按浏览器语言选择。 在后台添加的照片,保存之前会去掉拍摄位置,因为存下的文件都会发布:JPEG、PNG、WebP 和 AVIF 里 EXIF 的 GPS 信息和 XMP 里标明位置的部分,还有一些相机藏在 JPEG 里的预览图 上的同样信息。照片怎么转正、用什么拍的、什么时候拍的都保留,像素不动;去掉了位置时, 后台会提示。用 git 添加的文件原样发布。 标签和分类按站点列出它们的方式计数:Go、go 和 GO 是同一个标签,名称取写得最多的 那种,卡片上会注明它还有哪些写法。给标签改名,会把新名称写进每篇带着它的文章,不管 原来怎么写,所以把 Go 改名为 Go 就能统一写法;删除时每种写法都会去掉。在编辑器里 换一种写法输入已有的标签,会直接选中它,而不是新建一个。 后台新建的文章先放进默认分类,标题下面就看得到,写的时候可以换掉或者去掉。默认分类是 `kite.yaml` 里的 `content.defaultCategory`,也可以在后台的设置 → 站点里改:不写时按站点 语言叫「未分类」,英文站点是 Uncategorized;写成 `""` 就不预填。它只是写进新文章 front matter 的一个普通分类,已有的文章不受影响,没写分类的文章也不会被算进去。 ### 登录 在 localhost 上,没有设置密码的项目是敞开的 —— 这台机器上没有别人需要挡。换成 任何别的地址,后台就必须有账号;没有账号却要放到别人能访问的地址上时,服务器 不会敞着启动。 它会以**安装模式**启动:除了安装页,什么都不会应答 —— 内容不会、草稿不会、设置不会, 连站点自己叫什么都不会,所以一个没装好的服务器什么也交不出去。 ```bash kite serve --admin --write --addr 0.0.0.0:1717 ``` ``` Finish installing this site in a browser: http://localhost:1717/admin/setup ``` 安装页本身是开放的,这是刻意的:装完之前根本没有账号,也就没有任何东西可以拿来 校验请求,最先打开表单的那个浏览器就是拿到账号的人。要么尽快装完,要么在它可达 之前就先给它一个账号,完全跳过安装: ```bash kite auth set-password # 输入两次,不回显 ``` `kite auth status` 会说明当前项目是否需要密码,`kite auth remove` 则把账号去掉。 这些也可以在后台的 **设置 → 账户** 里完成。在 localhost 上敞开的后台可以在那里 设置密码,设置后立刻生效,不用重启。已经有密码的后台,确认当前密码后可以修改 用户名和密码,其他浏览器随之退出登录,当前浏览器保持登录;也可以什么都不改,只让 其他浏览器退出;在 localhost 上还能把密码移除。来自环境变量的账号,要在设置环境 变量的地方修改。 同一个页面里还有你的名称、邮箱和头像。名称就是站点的作者 `site.author`,主题会把它 显示在你发布的内容旁边,所以它写在 `kite.yaml` 里,和其他设置一样要发布;后台也用它 称呼你。邮箱和头像和账号放在一起,存在 `.kite/secrets/profile.json` 和 `.kite/secrets/avatar`,永远不会发布。 账号以 argon2id 哈希的形式存放在 `.kite/secrets/account.json`,永远不会被提交, 也必须在部署时保留下来,账号才会跟着留下来。容器可以改用环境变量提供账号: `KITE_ADMIN_USER` 配 `KITE_ADMIN_PASSWORD`,或者用 `KITE_ADMIN_PASSWORD_HASH` 以免明文密码出现在进程列表里;环境变量优先于文件。 ### API 后台做的每一件事,都走 `/api/v1` 下的同一套 HTTP API,而这套 API 由二进制自己 描述: ```bash kite openapi > openapi.json ``` `--admin` 提供这套 API,`--write` 允许它改动项目;不加 `--write` 时同一套 API 是只读的,后台会在站点名旁标出“只读”,只提供浏览和预览。后台的类型化客户端由这份描述生成,并在 CI 里校验,所以它编译时依赖的 类型不可能描述一个服务端并不提供的 API。 ## 主题 Kite 自带一套主题,编译进二进制:为个人写作准备的安静衬线排版,深浅两色,不主动 引入任何 Web 字体 —— 除非你自己指定,否则页面不向第三方请求任何东西。 其他主题放在 `themes/` 下,每套一个目录,`kite.yaml` 里的 `theme.name` 用目录名 选择它;`default` 永远指内置主题。后台会列出全部主题,说明哪些无法使用以及原因;也可以 上传 zip 压缩包安装主题,`theme.yaml` 放在最外层或压缩包里唯一的文件夹中。安装时按站点 加载主题的标准检查,同名主题只有在你确认后才会被替换。启用之前可以先在整站上试用:预览 用这套主题和正在编辑的设置绘制,页面里的链接都留在预览中,保存之前什么都不会写入。切换 写下的 `kite.yaml` 和主题目录,在设置页里像内容一样发布。 主题在 `theme.yaml` 里声明自己的设置项,后台把它们渲染成表单 —— 一个选项是一处 声明,而不是一个文档问题。主题和站点的模板都放在 `layouts/` 下,同名相对路径以 站点的为准,所以替换单个模板不需要 fork 整套主题。 ```yaml settings: - key: look type: section # 表单里的一个分组标题;其中的字段仍存在同一层 label: Look preview: home # 编辑这个分区时后台预览哪个页面 fields: - key: accent type: color label: Accent color default: "#7d5c3c" options: # 颜色字段的 options 是推荐色,不是限制 - {value: "#7d5c3c", label: Umber} - {key: favicon, type: image, label: Site icon} - key: nav type: repeat # 由若干项组成的列表,每项有下面这些字段 label: Extra links fields: - {key: label, type: string, label: Label} - {key: url, type: url, label: Address} ``` 字段类型有 `string`、`text`、`number`、`boolean`、`color`、`select`、`multiselect`、 `image`、`url`、`date`、`code`、`group`、`repeat` 和 `section`。后台把主题的分区列在 表单旁边,分区的 `preview` 指定编辑它时预览哪个页面:`home`(首页)、`post`(最新的 文章)、`page`(一个独立页面)或 `posts`(文章列表)。设置值存在 `kite.yaml` 的 `theme.settings` 下,恢复成默认值的设置会从中删除。模板按字段声明的类型读取每个值, 写作 `.Site.ThemeSettings.accent`,读不成该类型的值就用默认值;`repeat` 还能读取每行 一条 `名称 | /路径/` 的文本,所以主题把文本设置改成列表时,已经填好的站点不会丢内容。 主题在后台里的说明文字由主题 `i18n/` 目录下的语言包翻译,每种语言一个文件,放在 `theme` 键下;语言包里没有的部分按 `theme.yaml` 的原文显示: ```yaml # i18n/zh-CN.yaml theme: title: 纸 settings: accent: {label: 强调色, options: {"#7d5c3c": 赭石}} nav: label: 额外链接 fields: {url: {label: 地址}} layouts: links: {label: 友链} ``` 同一套语言包里 `theme` 以外的键,是主题页面上的词,模板用 `T` 读:`{{ T "read_more" }}`。 词取自站点语言对应的语言包,站点自己的 `i18n/<语言>.yaml` 盖过主题的,所以站点不用替换模板 就能改主题的用词;这门语言里没有的词用英文的,都没有就是键本身。词里可以放模板给的值, 带数量时按复数规则选形式: ```yaml # i18n/en.yaml posts: one: "{{ .Count }} post" other: "{{ .Count }} posts" of: "{{ .Count }} of {{ .Total }}" ``` `{{ T "posts" 8 }}` 是 `8 posts`,`{{ T "of" (dict "Count" 8 "Total" 13) }}` 是 `8 of 13`。词是文字,落在哪里就在哪里转义,所以能放进属性里。`i18n.Has "key"` 说明有没有 这个词,`{{ i18n.Words "copy" "copied" }}` 把几个词作为一个 JSON 对象交给脚本。内置主题 带英文和中文的词;用其他语言写的站点,加一个自己的语言包就能翻译它。 主题目录里放 `screenshot.png`、`.jpg` 或 `.webp` 作为截图,也可以在 `theme.yaml` 里用 `screenshot:` 指定其他文件。 页面里的代码按类名而不是颜色高亮,主题的样式表可以为浅色和深色各带一套配色。一个代码块写出来是 `
`,`data-lang` 是作者标注的语言,主题可以用它给代码块加上标题。
主题还可以提供让作者按页面选用的模板,比如友链页,在 `theme.yaml` 里声明:
```yaml
layouts:
- name: links
label: Links
description: 把一组链接排成卡片。
types: [page] # 不写则所有类型都可以选
```
内容在 front matter 里用 `layout: links` 选用,和 Hugo 的写法一样;也可以在
编辑器的“模板”下拉框里选,它列出当前主题为这类内容提供的模板,预览随之切换。页面随后用
`layouts/page/links.html` 渲染,没有的话用 `layouts/links.html`。主题不能声明
没有模板文件的布局;页面选了当前主题没有的模板时,退回它所属类型的默认模板。
站点的菜单写在 `kite.yaml` 里,不属于哪一个主题,换了主题还在。站内地址从站点根写起,
发布时放在站点的路径下:
```yaml
menus:
main:
- name: 归档
url: /posts/
- name: 关于
url: /about/
- name: 别处 # 只用来归拢下级链接的一项
children:
- {name: 代码, url: "https://github.com/someone"}
```
主题在 `theme.yaml` 里声明它画哪些菜单、每个画几层,后台的“菜单”页据此列出要填的菜单,
页面和文章可以按标题搜索添加:
```yaml
menus:
- name: main
label: Header
description: 每一页顶部的链接。
depth: 1 # 2 表示链接可以展开下级菜单
```
模板用 `{{ range .Site.Menus.main }}` 画它:每个链接有 `.Name`、已经带上站点路径的
`.URL`、`.Children`,以及 `.Params`,即站点给它的其他东西,比如图标。站点没写的菜单是空的。
内置主题在页头画 `main`,站点写这个菜单之前,显示它自己的链接。
主题也可以在命令行里管理。命令检查主题的方式和后台一样,写下的改动也一样,之后照常发布:
```bash
kite theme list # 站点能用的主题,* 标出正在用的
kite theme add vane # 按名字从索引安装;vane@1.0.0 安装指定版本
kite theme add vane-0.2.0.zip # 发布附带的压缩包,或一个目录;--replace 替换同名的
kite theme use vane # 写 theme.name
kite theme remove paper # 正在用的不能删
kite theme new paper # 生成一个起步的主题目录
```
主题可以对照它所依据的契约检查:
```bash
kite theme verify ./themes/paper
```
它用这套主题构建一个用到每种页面的小站点,再向服务器请求构建写出的每个文件,包括
RSS 和 sitemap,逐字节比较。通过检查的主题,发布出去的就是 `kite run` 预览时看到的;
没通过的,会指出每个文件第一处不同的行。不给目录时,检查当前项目在用的主题,在项目之外则检查内置主题。
它还会列出页面会让读者的浏览器从哪些其他网站加载东西:脚本、样式表、字体、图片和框架,站长安装主题前会看到这些;
`--json` 把结果全部交给脚本。
主题发布成一个 zip 压缩包,后台和 `kite theme add` 都装这种包。`kite theme pack` 从主题目录打出它:`theme.yaml`、
`layouts`、`static`、`assets`、`i18n`、截图,以及许可和说明文件,放在一个以主题命名的文件夹里,写到
`dist/<名字>-<版本>.zip`。仓库里的其他东西,比如示例站点或构建工具,都不会进去;同样的文件总是打出同样的字节,
写出之前还会按安装时的标准检查一遍。
主题所依据的契约 `apiVersion: kite/v1` 已经冻结:模板能调用的东西,每个方法和函数
连同签名,列在 [theme-system.md](design/theme-system.md) 第 6、7 节,以后只增,不改名、
不删除、不改签名。用到后来新增的东西的主题,用 `requires` 写明它需要的 Kite 版本。
这个小站点发布在一个路径下,就像 GitHub Pages 的项目站点那样,所以从域名根开始写的链接,
比如 `/rss.xml`,也会被报告出来。模板链接到 Kite 自己的页面用 `url.For "home"`、
`url.For "list" "post"`、`url.For "taxonomy" "tags"` 或 `url.For "term" "tags" "Go"`,
链接到站点的其他路径用 `url.Rel "rss.xml"`,两者都会带上这段路径。
写法不同但地址相同的词条是同一个词条:Go、go 和 GO 都是 `/tags/go/`,Web Dev 和
web-dev 都是 `/tags/web-dev/`。它的页面列出带着其中任何一种写法的文章,名称取写得最多
的那种;各种写法一样多时,按字符顺序取第一个,Go 排在 go 前面。分类页的 `.Terms` 只把
它算一次,文章自己的 `.Terms` 按这篇文章的写法显示。只由短横线、斜杠或空格组成的词条
没有自己的页面。
以 bundle 保存的页面,它的文件在 `.Resources` 里,按 bundle 里的名字取:
`.Resources.Get "cover.jpg"`、`.Resources.Match "images/*"` 或 `.Resources.ByType "image"`。
其中的图片可以做成另一张:更小、裁过或者换格式,主题就这样给列表配小封面,或者给图片写
`srcset`:
```html
{{ with .Resources.Get "river.jpg" }}
{{ $small := img.Fit "800x800" . }}
{{ $card := . | img.Fill "600x400" | img.Format "webp" | img.Quality 80 }}
{{ end }}
```
`img.Resize "800x"` 缩放到这个尺寸,缺的一边按比例;`img.Fit` 只缩小,放进这个框;
`img.Fill "600x400 top"` 先按比例裁、再缩放到正好这个尺寸,锚点指定保留哪一部分;
`img.Crop` 只裁不缩;`img.Format` 写成 `webp`、`jpeg`、`png` 或 `gif`;`img.Quality` 是 WebP
或 JPEG 的质量,不写就是 75。照片先按 EXIF 转正,做出的图不带任何 EXIF,也就不会说出拍摄
地点。JPEG、PNG、GIF 和 WebP 都能读写,用的是 Kite 自己的代码,在每台机器上做出同样的字节,
所以在笔记本上和在 CI 里构建的站点发布的是同样的文件。WebP 是有损压缩,保留透明;写成 JPEG
时透明的部分铺成白色。每张图只做一次:模板第一次问它的地址或尺寸时才做,发布在
源文件旁边,名字是 `river_.jpg`,并保存在 `.kite/cache/images/`,之后的构建和
`kite serve` 直接用。
正文里的图片,站点或主题有 `layouts/_markup/render-image.html` 时由它来画,和 Hugo 一样;
手机拍的照片就这样缩小后再发布:
```html
{{- with .Page.Resources.Get .Destination -}}
{{- with img.Fit "1600x1600" . -}}
{{- end -}}
{{- else -}}
{{- end -}}
```
`.Destination` 是正文里写的图片地址,写的是 bundle 里的文件时,正好是 `.Resources.Get` 要的名字;
`.Src` 是没有这个模板时页面引用它的地址,正文从站点根开始写的,前面带上站点的路径。
`.Text` 是图片的替代文字,`.Title` 是标题。
文章的封面是 front matter 里的 `cover`,模板从 `.Params.cover` 读到的就是作者写的原文;
`.Images` 按出现顺序列出正文里的图片,也是原文。列表里的页面同样带着这两样。主题解析它们的方式,
和浏览器解析正文里的图片一样:完整地址原样用,从站点根开始写的用 `url.Rel`,其余的相对于页面地址。
没写封面时,主题可以改用正文第一张图;写了 `cover: false` 就不显示封面,编辑器里的「不用封面」写的就是它。
主题可以按列表的种类规定分页,用在设计需要的地方:首页不是一页页翻的文章列表,或者归档页
要列出所有文章。
```yaml
pagination:
home: 0 # 全部显示在一页
list: 0
term: 20 # 每页 20 条
```
没写到的种类按站点的 `build.pageSize` 分页,站点的 `build.pagination` 可以替换其中任何
一种。`.Paginator` 描述的是页面最终的分页;全部显示在一页的列表是第 1 页、共 1 页,
`PageSize` 是条目的数目。
## 短代码
短代码按名字调用一个模板,把 Markdown 没有写法的东西放进页面,比如视频、相册或提示框。
语法和 Hugo 相同,所以从 Hugo 迁过来的内容,只要有了对应的模板就照常可用:
```markdown
{{< figure src="river.jpg" caption="上游" >}}
{{< note title="注意" >}}
一对标签中间的 **Markdown** 也会渲染。
{{< /note >}}
按 {{< kbd Enter >}} 继续。
```
`{{% %}}` 的读法与此相同。单独占一行的标签是一个块,不会被包进段落,一对这样的标签
包住的是中间的块;写在一行文字当中的标签是这行里的一个词,一对这样的标签包住的是中间的
文字。参数按顺序给,如 `{{< kbd Enter >}}`,或者按名字给,如
`{{< figure src="river.jpg" >}}`,不能混用。带引号的值是文字;不带引号的值读得出
`true`、`false` 或数字时,就是那个值。代码里的标签原样显示;`{{* figure */>}}`
在任何地方都显示成它注释掉的那个标签,写介绍短代码的文章时就这样写。
模板是 `layouts/_shortcodes/<名字>.html`,放在站点或主题里,站点的优先;名字里可以
带目录,`docs/note` 对应 `layouts/_shortcodes/docs/note.html`。模板拿到的是这一次调用:
```html
```
- `.Get` 按位置(`.Get 0`)或按名字(`.Get "src"`)取一个参数,调用里没有给时什么也
不返回。`.Params` 是全部参数,`.IsNamedParams` 说明参数是按哪种方式给的。
- `.Inner` 是一对标签包住的内容,按 Markdown 渲染好;`.RawInner` 是它的原文,给把它
当成别的东西来读的短代码用,比如图表。
- `.Parent` 是这次调用所在的外层短代码,`.Ordinal` 是它在外层里排第几个,从 0 数起。
- `.Page` 是正文里写了这次调用的页面,`.Site` 是站点。页面的正文还在绘制当中,所以
`.Page.Content` 是空的。模板能调用页面模板能调用的所有 partial。
只有模板显示出来的内容才算数:模板没有用到的 `.Inner`,其中的字不计入页面的字数、
正文和摘要,所以留空的 `layouts/_shortcodes/private.html` 能让它包住的内容不出现在
网站上。没有人定义的短代码会让构建停下,并指出所在的文件和行,而不是把标签原样印出来;
写作时后台的预览也会同样提示。可视化编辑器无法保留短代码,所以用到短代码的内容会以
Markdown 源码打开。
## 插件
插件为网站加上主题之外的功能:评论、统计、搜索、公式。插件放在 `plugins/` 下,每个
一个目录,列进 `kite.yaml` 的 `plugins.enabled` 后才会运行,列表的顺序就是运行顺序。
后台的「插件」页可以上传 zip 安装插件、开关、修改设置、删除;开启之前会说明插件往页面里
加什么、它的代码会从哪些网站加载内容。命令行也能做同样的事:
```bash
kite plugin add search # 按名字从索引安装;也可以是压缩包或目录
kite plugin enable search
kite plugin list
kite plugin disable search
kite plugin remove search
```
官方插件和默认主题以外的主题一样,各自放在独立的仓库里:
| 插件 | 作用 |
|---|---|
| [analytics](https://github.com/kite-plus/plugin-analytics) | 用百度统计、Google Analytics、Umami 或 Plausible 统计访问量 |
| [comments](https://github.com/kite-plus/plugin-comments) | 在文章下放评论区,支持 Giscus、Waline 和 Twikoo |
| [math](https://github.com/kite-plus/plugin-math) | 用 KaTeX 排版 TeX 公式,把 mermaid 代码块画成图表 |
| [search](https://github.com/kite-plus/plugin-search) | 在读者的浏览器里搜索,索引在构建时生成 |
### 编写插件
```bash
kite plugin new greet # 生成一个起步目录
kite plugin verify greet # 按站点加载插件的标准检查;--json 交给脚本
kite plugin pack greet # 打出发布用的 zip,在 greet/dist/ 下
```
插件是一个带 `plugin.yaml` 的目录。`assets/` 里的文件随网站发布到 `plugins//`
下;`i18n/` 里的语言包放在 `plugin` 键下,翻译后台对插件的描述,和主题的语言包一样。
```yaml
id: greet # 与目录名相同
name: Greet
version: 0.1.0
apiVersion: kite/plugin/v1
requires: ">=0.1.0 <2.0.0" # 适用的 Kite 版本
description: A line under every post.
hosts: [cdn.example.com] # 插件自带脚本会从哪些网站加载内容
inject:
- at: head # 放在 之前;body 放在