在原有 Shoka 特殊语法的基础上,整理适合写教程、项目介绍和图文记录的 Markdown 扩展,并补充本站已有的交互式 3D 留声机标签,以及本次新增的行内批注、参数卡片和阅读增强。这篇文章既是使用手册,也是一张可以直接操作的展示页:每一节先给出源码,再展示实际效果。
原有语法仍然可以照常使用,包括提示块、折叠块、选项卡、公式、Mermaid、行号与高亮代码块等。原有手册见 Step.4 主题特殊功能(转载)。本文的新增语法和查看器不会替换原有写法或音乐语法。
#一、扩展速览
| 扩展 | 最简写法 | 适合的内容 |
|---|---|---|
| 文件树 | :::file-tree | 文件夹层级、项目结构、文件增删说明 |
| 代码树 | :::code-tree | 一个示例包含多个代码文件时,切换查看 |
| 步骤流 | :::steps | 安装、配置、排错等需要按顺序操作的教程 |
| 图片画廊网格 | :::grid | 截图、作品集、照片;点击查看完整原图 |
| GitHub 仓库卡片 | ::github{repo="作者/仓库"} | 分享开源项目 |
| 交互式 3D 留声机 | gramophone 成对标签 | 歌单唱片架、音乐播放、歌词与模型互动 |
| 行内批注 | [+术语] 与 [+术语]: 说明 | 不打断阅读的术语解释和补充说明 |
| 参数卡片 | :::field 参数名 | 配置项、接口字段、类型、必填与默认值 |
| 增强代码块 | 三个反引号后填写 js title="app.js" | 文件标题、增删标记、局部折叠、复制和下载 |
| 代码组 | ::: code-group labels=[npm,pnpm] | 同一操作的不同命令,切换选项卡阅读 |
#使用前需要设置什么?
除留声机外,表中的语法直接写在文章正文中即可,不需要为每篇文章单独打开开关,也不需要安装 Astro 或 Svelte。留声机还需要在该文章顶部的 Front Matter 中加入 gramophone: true ,具体见第七节。批注、参数卡片、流程图查看器与阅读设置见第九节。
- 多行容器以
:::结束;GitHub 卡片只有一行,不写结束标记。 - 参数写在开头的花括号中,含空格的值使用英文引号。
- 示例中的四个反引号仅用于在教程里展示三个反引号的写法;真正使用时,复制示例框里面的内容即可。
- 修改语法插件后,如果本地预览一直开着,需要重启一次
hexo s;正常写文章不需要反复重启。
#二、文件树:清楚展示目录与文件改动
#2.1 用缩进列表写文件树
- 表示一项,前面多缩进两个空格,表示它属于上一层目录。
:::file-tree{title="博客示例目录"} | |
- source | |
- _posts | |
- **hello.md** # 重点关注的文章 | |
- images/ # 点击展开这个目录 | |
- cover.webp | |
- themes | |
- shoka | |
- ++ markdown-extensions.styl # 新增样式 | |
- -- legacy.css # 准备移除的旧文件 | |
- package.json # 依赖清单 | |
::: |
实际效果: 可以点击 images 左边的箭头展开目录。
source
_posts
- hello.md重点关注的文章
images点击展开这个目录
- cover.webp
themes
shoka
- markdown-extensions.styl新增新增样式
- legacy.css删除准备移除的旧文件
- package.json依赖清单
#2.2 名称后面这些符号是什么意思?
| 写法 | 显示效果 |
|---|---|
- src 下方继续缩进 | 识别为目录,默认展开 |
- src/ 下方继续缩进 | 识别为目录,默认折叠 |
- empty/ ,没有子项 | 显示为空目录 |
- ++ new.js | 新增标记 |
- -- old.js | 删除标记和删除线 |
- **important.js** | 强调文件名 |
- main.js # 入口文件 | 文件名旁边显示说明 |
注意,说明的写法是 “空格 + # + 空格”。 C# 这样的文件名不会被误当成说明。这里的新增、删除标记只是文档展示,不会真的修改或删除电脑里的文件。
#2.3 直接粘贴终端的树形结构
已经有 tree 一类命令生成的目录文本,也可以直接使用 file-tree 代码块。
```file-tree title="构建后的文件" icon="simple" | |
public | |
├── css/ | |
│ ├── app.css | |
│ └── markdown-extensions.css | |
├── js | |
│ ├── app.js | |
│ └── markdown-extensions.js | |
└── index.html | |
``` |
public
css
- app.css
- markdown-extensions.css
js
- app.js
- markdown-extensions.js
- index.html
支持 ├── 、 └── 、 │ 和 Windows 风格的 +--- 、 \--- 、 | 。 icon="simple" 使用统一颜色的图标;省略该参数或写 icon="colored" ,会按文件类型使用不同颜色。
#三、代码树:像小型编辑器一样切换文件
#3.1 手动写入多个文件
把多个代码块放进 :::code-tree ,每个代码块通过 title="路径/文件名" 声明它代表的文件。
:::code-tree{title="一个主题按钮" height="360px" entry="src/main.js"} | |
```html title="index.html" | |
<button class="hello">点我试试</button> | |
``` | |
```js title="src/main.js" | |
const button = document.querySelector('.hello'); | |
button.addEventListener('click', () => { | |
button.textContent = '你好,Shoka!'; | |
}); | |
``` | |
```css title="src/button.css" | |
.hello { | |
padding: 10px 18px; | |
border: 0; | |
border-radius: 12px; | |
color: white; | |
background: linear-gradient(135deg, #ab66ff, #819ee8); | |
} | |
``` | |
::: |
实际效果: 点击左侧文件切换代码,右上角可以复制当前文件或放大阅读。手机上文件导航会放到代码上方。
<button class="hello">点我试试</button> |
const button = document.querySelector('.hello'); | |
button.addEventListener('click', () => { | |
button.textContent = '你好,Shoka!'; | |
}); |
.hello { | |
padding: 10px 18px; | |
border: 0; | |
border-radius: 12px; | |
color: white; | |
background: linear-gradient(135deg, #ab66ff, #819ee8); | |
} |
代码树只是展示代码,不会执行示例里的 JavaScript,也不会把 HTML 变成真正的网页按钮。普通代码块的原有语法没有变化, title="路径" 的文件导航含义只在代码树内部使用。
#3.2 代码树参数与默认文件
| 参数 | 用途 | 默认值 |
|---|---|---|
title | 代码树顶部名称 | 示例工程 |
height | 桌面阅读区域高度,支持 px 、 rem 、 em 、 vh | 420px |
entry | 第一次打开时选择哪个文件,推荐填写完整相对路径 | 第一个文件 |
icon | colored 或 simple | colored |
也可以在某个代码块的首行末尾加上 :active :
:::code-tree{title="选择默认文件"} | |
```json title="package.json" | |
{ "name": "demo" } | |
``` | |
```js title="main.js" :active | |
console.log('默认先显示这个文件'); | |
``` | |
::: |
{ "name": "demo" } |
console.log('默认先显示这个文件'); |
同时写了 entry 和 :active 时,以 entry 为准。文件名应保持唯一;没有匹配到时回到第一个文件。
#3.3 从专门准备的本地示例目录导入
不想在文章里重复粘贴多份代码,可以把准备公开的示例放进项目根目录的 code-examples/ ,然后用一行指令导入。
my-blog
code-examples
markdown-demo
- main.js
- message.js
styles
- button.css
source
- _posts
写法:
@[code-tree title="公开的本地示例" entry="main.js" height="320px"](/code-examples/markdown-demo) |
实际效果: 下方三个文件在构建时读取并写进静态 HTML,不需要访客额外下载这些源文件。
// 这是可以公开展示的示例,不包含网站真实配置。 | |
import { greeting } from './message.js'; | |
const button = document.querySelector('.hello'); | |
button?.addEventListener('click', () => { | |
button.textContent = greeting('Shoka'); | |
}); |
export function greeting(name) { | |
return `你好,${name}!`; | |
} |
.hello { | |
padding: 10px 18px; | |
border: 0; | |
border-radius: 12px; | |
color: white; | |
background: linear-gradient(135deg, #ab66ff, #819ee8); | |
} |
等价的单行指令写法:
::code-tree{dir="/code-examples/markdown-demo" title="公开的本地示例" entry="main.js"} |
这里的 /code-examples/ 是项目目录约定,不是网站公开 URL,也不是 Windows 磁盘根目录。为避免泄露隐私,不允许直接导入 themes/shoka 、 _config.yml 或项目外目录。请先把需要公开的、已经脱敏的示例放进 code-examples/ 。
会跳过隐藏文件、符号链接、常见敏感文件名和不支持的文件类型。上限为 40 个文件、单文件 64 KB、合计 512 KB。即使文件名普通,也要自己确认内容里没有密码、密钥等信息。
更新被导入的示例后,如果文章仍显示旧版本,可以执行 hexo clean ,再执行 hexo g 重新生成。 code-examples/ 本身不会被当成网页目录发布,只有文章指令选中的内容会进入页面。
#四、Markdown 步骤流
#4.1 把有序列表变成操作流程
:::steps 中放一个有序列表,每个最外层列表项就是一步。属于同一步的段落、代码或子列表,需要和该步骤的正文对齐缩进。
:::steps[发布一篇博客] | |
1. **创建文章** | |
在项目目录执行: | |
```bash | |
hexo new "我的新文章" | |
``` | |
2. **编写并预览** | |
打开新文章,修改标题并填写正文。 | |
- 检查图片与链接。 | |
- 检查手机上的阅读效果。 | |
3. **生成静态文件** | |
```bash | |
hexo g | |
``` | |
::: |
创建文章
在项目目录执行:
hexo new "我的新文章"编写并预览
打开新文章,修改标题并填写正文。
- 检查图片与链接。
- 检查手机上的阅读效果。
生成静态文件
hexo g
#4.2 标题和起始编号
标题可以写成 :::steps[标题] ,也可以写成 :::steps{title="标题"} 。 start 用于接着上一段流程继续编号。
:::steps{title="继续检查" start="4"} | |
1. **确认生成结果**:查看页面是否正常。 | |
2. **发布更新**:按本站原有方式发布静态文件。 | |
::: |
- 确认生成结果:查看页面是否正常。
- 发布更新:按本站原有方式发布静态文件。
步骤流中只放一个有序列表。普通介绍段落放在容器外;结构不符合要求时会保留为普通 Markdown,不会把正文丢掉。
#五、图片画廊网格与 Fancybox
#5.1 最简单的画廊
把普通 Markdown 图片放到 :::grid 里,每张图片单独成段,中间留一个空行。
:::grid | |
 | |
 | |
 | |
::: |
默认桌面三列,卡片比例 16/10 。点击任意一张,可在 Fancybox 中查看完整原图,并在这一个画廊内部切换。灯箱会按原图比例淡入,不会先把裁剪后的卡片拉伸再恢复比例; aspect 和 fit 只影响页面中的缩略图。
#5.2 参数说明
| 参数 | 可用值 | 默认值 |
|---|---|---|
columns | 整数 1 到 6 | 3 |
aspect | 正数比例,例如 16/9 、 3/4 、 1/1 | 16/10 |
fit | cover 或 contain | cover |
cover:居中裁剪并填满卡片,网格更整齐;原图不受影响。contain:完整显示图片,比例不同时会保留背景留白,适合不能裁剪的截图。- 图片的
alt是无障碍说明,也是默认图注;有可选标题时,标题优先作为图注。 - 小于 768px 时最多两列,小于 480px 时一列,
columns="1"始终保持一列。 - 不足一行的图片靠左排列,不会被强行拉宽。
#5.3 两列方形卡片
:::grid{columns="2" aspect="1/1" fit="cover"} | |
 | |
 | |
::: |
#5.4 保留完整原图,不做裁剪
:::grid{columns="2" aspect="16/9" fit="contain"} | |
 | |
 | |
::: |
本页多处重复使用同一张示例图,是为了比较不同的裁剪参数。每个画廊独立分组,不会混进上面或下面的画廊。图片仍使用现有的延迟加载与 Fancybox;不会引入另一套灯箱组件。原有普通图片和 .gallery 语法照常使用。
#六、GitHub 仓库卡片
#6.1 最简写法
repo 填 “所有者/仓库名”,不要粘贴完整网址。
::github{repo="LyraVoid/Shirone"} |
查看项目源码、文档与最新动态
卡片接近可视范围时,才读取 GitHub 的公开仓库信息,补充简介、Stars、Forks、语言和许可证。不需要填写 Token,也不要把私人 Token 写进文章。
#6.2 添加静态说明,或者完全不请求接口
网络不稳定时,可以提供一段静态说明。卡片在请求失败时仍然可阅读、可跳转,不会无限重试或一直显示加载动画。
::github{repo="mrdoob/three.js" description="用于在浏览器中创建三维场景的 JavaScript 库,也是本文留声机使用的图形基础。" fetch="false"} |
用于在浏览器中创建三维场景的 JavaScript 库,也是本文留声机使用的图形基础。
fetch="false" 表示只显示静态卡片,不访问 GitHub API。启用动态信息时,同一仓库在本次浏览中复用缓存约 30 分钟;离开页面会取消未完成的请求。Stars 等数值是公开接口数据,不保证即时更新。
#七、交互式 3D 留声机:在 Markdown 中放一台黑胶播放器
留声机是本站已有的 Hexo 自定义标签,不是 Shirone 提供的组件,也不需要把大段 HTML 粘进文章。这里只演示如何使用;模型与交互的改造过程见 Shoka 主题:使用 Three.js 实现交互式 3D 留声机。
#7.1 最小可用示例
分两步:先在文章顶部两条 --- 之间加入 gramophone: true ,再把成对标签和 YAML 歌单列表放进正文。下面是一个可以复制的新文章示例:
--- | |
title: 我的 3D 留声机 | |
gramophone: true | |
--- | |
听听音乐吧! | |
{% gramophone %} | |
- title: 示例唱片 A | |
list: | |
- https://music.163.com/playlist?id=3778678 | |
- title: 示例唱片 B | |
list: | |
- https://music.163.com/playlist?id=19723756 | |
{% endgramophone %} |
如果文章已经有标题、日期和标签,只把 gramophone: true 加进原来的 Front Matter,不要再新增第二份 Front Matter。
#7.2 实际效果:挑一张唱片
下方使用两张示例唱片,地址指向网易云的公开榜单,不使用博主的私人歌单。不会自动播放,请自行点击唱片或播放按钮;曲目是否能播放仍取决于音源接口、网络及歌曲版权限制。
可以依次试试这些操作:
- 点击 “挑选唱片” 或模型后方的唱片架,展开歌单唱片;选择一张,观察换唱片的动画。
- 点击 “播放室” 或模型正面的屏幕,打开音乐面板;在 “当前歌单曲目” 中选歌,也可以按歌名或歌手搜索。
- 使用播放、暂停、停止、上一首、下一首、进度和音量控制;面板内可以选择单曲循环、列表播放或随机播放。
- 播放有歌词的歌曲时查看歌词,也可以展开歌词阅读;没有歌词的歌曲不会凭空生成歌词。
- 手机浏览时,先点 “操作模型” 再拖动模型;不操作时关闭它,便于继续上下阅读文章。点击 “复位” 可以恢复视角。
#7.3 如何换成自己的歌单?
| 配置项 | 写在哪里 | 作用 |
|---|---|---|
gramophone: true | 文章顶部 Front Matter | 允许这篇文章加载 Three.js 和留声机资源 |
title | 标签内部,每个歌单组的第一行 | 唱片架和播放室中显示的歌单名称 |
list | 和同组的 title 对齐 | 填写这个歌单组的音乐链接列表 |
| 歌单地址 | list 下方的缩进列表 | 替换为播放器支持的公开歌单链接 |
增加第三张唱片时,只需在结束标签前再加入一组 title 和 list 。每一组对应唱片架上的一张唱片,而不是每首歌对应一张唱片。
- title: 我的第三张唱片 | |
list: | |
- https://music.163.com/playlist?id=替换为你的公开歌单ID |
YAML 缩进请使用空格,不要使用 Tab。 list 和同组的 title 对齐,音乐链接再多缩进两格。示例中的 “替换为你的公开歌单 ID” 需要改成真实 ID,不能原样用于播放。
当前留声机使用固定的画布与播放器标识,因此同一页面只放一组完整的留声机标签;多个歌单放在这一组标签内部,不要复制多台留声机到同一页。不要在公开文章中填写私密歌单链接、登录凭据或接口密钥。
#7.4 为什么只有占位文字,没有模型或音乐?
- 完全没有模型:检查文章顶部是否开启
gramophone: true,以及开始与结束标签是否成对。 - 有模型但没有曲目:检查 YAML 缩进、歌单是否公开,以及音乐接口是否可用。
- 有曲目但不能播放:尝试其他歌曲,检查音源连接和版权限制;这不一定是 Markdown 语法错误。
- 修改标签插件后仍是旧效果:重启本地预览,再重新生成并刷新页面。
#八、常见问题与排查
#原来的提示框、折叠框会被改变吗?
不会。本次只增加特定名字的新容器与指令, :::primary 、 +++info 、 ; 选项卡等原有语法继续由原来的渲染逻辑处理。下面仍是原有提示块:
这是原来的 :::success ,它和本文新增的步骤流、文件树可以同时使用。
#嵌套容器如何避免提前结束?
需要在一个容器内部写另一个容器时,外层多写一个冒号,例如外层使用 ::::primary ,内层使用 :::steps 。代码树内部则正常使用三个反引号,不是冒号。
#代码树按钮没出现,或者样式没有变化?
- 确认文章中的结束标记完整。
- 重启本地预览并强制刷新浏览器。
- 部署时同时发布新生成的
css/markdown-extensions.css和js/markdown-extensions.js,不要只更新文章 HTML。 - 如果使用自己的静态资源 CDN,也要同步这些新文件。
即使 JavaScript 不可用,代码树仍会显示全部文件内容,不会只剩空白;文件树折叠和步骤流不依赖额外 JavaScript。
#普通页面会多加载这些资源吗?
只有实际使用新语法的文章才会请求扩展样式。代码切换和动态仓库卡片才需要额外的小脚本;文件树和步骤流在构建时已经生成。普通页面不会因此加载 Astro、Svelte 或新的前端框架。
本页为了展示真实留声机,额外开启了 gramophone: true ,会加载对应的 Three.js、留声机脚本、样式和歌单。其他没有开启留声机的文章不会因此加载这些资源;只想写文件树或步骤流时,不需要复制这个开关。
#九、阅读增强:批注、参数卡片与流程图查看
这组扩展参考 Shirone 的阅读设计,沿用本站的 Shoka 主题色。原来的脚注、提示块、代码块和标签页语法不会改变。
#9.1 行内批注
在需要解释的地方写 [+批注名] ,然后在文章中用同名定义补充说明。点击批注会在文字附近展开解释;脚本不可用时,会跳到文末的 “文中批注”,正文仍然可读。
这篇教程采用渐进增强[+渐进增强]。 | |
[+渐进增强]: 先保证正文与链接可用,再添加弹窗、搜索等交互;脚本加载失败也不应丢失内容。 |
实际效果:这篇教程采用渐进增强渐进增强+。同一个批注可以重复引用渐进增强+。
#9.2 参数说明卡片
field-group 是可选的外层分组;每张卡片使用 field 参数名称 。 @type 写类型、 @required 标记必填、 @optional 标记可选、 @default 写默认值。元信息后空一行,再写说明正文。
::::field-group | |
:::field title | |
@type string | |
@required | |
文章的标题,填写一段简洁清楚的文字。 | |
::: | |
:::field pageSize | |
@type number | |
@optional | |
@default 12 | |
每次展开的记录数量。这里只是**示例参数**,不是博客全局配置。 | |
::: | |
:::: |
titlestring必填文章的标题,填写一段简洁清楚的文字。
pageSizenumber可选12每次展开的记录数量。这里只是示例参数,不是博客全局配置。
也支持把参数写在开头,例如 :::field title {type="string" required} 。避免在教程里粘贴自己网站的完整私有配置,只展示相关参数。
#9.3 流程图放大与全屏
仍然使用原来的 Mermaid 代码块,不需要学习另一套流程图语法,也不必为新文章手工添加 mermaid: true 。
```mermaid | |
flowchart LR | |
A[书写 Markdown] --> B[生成静态页面] | |
B --> C[按实际内容加载增强] | |
C --> D[开始阅读] | |
``` |
flowchart LR A[书写 Markdown] --> B[生成静态页面] B --> C[按实际内容加载增强] C --> D[开始阅读]
- 图表右上角可放大、缩小、适应窗口和全屏查看。
- 放大后可以拖动。普通滚轮继续滚动文章,不会被图表抢走;
Ctrl/⌘加滚轮用于缩放。 - 焦点位于图表时,可用
+、-缩放,方向键平移,0复位。 - 关闭全屏窗口会回到打开前的位置,不会跳到页面顶部。
#9.4 文章阅读工具在哪里?
文章正文上方提供 “目录”“本文阅读”“分享海报”。手机上还有底部阅读工具栏,支持目录、评论与阅读进度;关闭评论的文章不会显示直达评论按钮。
“本文阅读” 与全站的 “显示与阅读设置” 已分开。全站设置仍负责减弱动效、背景纹理、简洁背景与首页布局;本文阅读只处理文章正文,初始不改变原来的排版。
可以直接在本页尝试:
- 点击 本文阅读,选择舒适阅读,或展开正文排版,调整字号、行高、段落间距和宽度。“仅本文” 只改变这篇文章;“作为文章默认” 用于没有单独设置的文章,不影响首页、分类等页面。
- 选择 专注阅读,暂时收起侧栏、推荐与封面装饰;点击页面上的 “退出专注阅读” 恢复。退出时保持当前读到的位置。
- 选择 教程跟练,第四节的真实步骤会出现 “标记完成”;面板中的复选框与正文同步,刷新后仍记得已经完成的步骤。
- 在 “教程辅助” 中选择一个代码文件,点击 固定到伴读窗。代码留在右侧,你可以继续滚动正文;支持复制、切换和跳转原代码,不会执行代码。
- 展开 本文速览,按章节、图片、代码、图表或参数定位,之后点击 “返回跳转前”。手机定位后会自动收起抽屉。
- “阅读位置” 会保存进度,并提供书签。下次打开文章时,由你点击 “继续阅读”,不会自动跳走。开启段落聚焦后可以点击正文,也可以使用
Alt + ↑ / ↓切换段落。 - 在正文中选中一句话,再到 私人摘录 中写下想法并保存。支持定位、修改想法和确认删除,不会变成公开评论。
这些记录只保存在当前浏览器,不发送到 Waline,也不会跨设备同步。清理网站数据或关闭无痕窗口可能丢失记录,请勿把它作为重要笔记的唯一备份。没有代码或步骤的文章不会显示相应空工具;完整阅读面板只在打开时加载。
分享海报仍在点击后才生成;图片读取失败会使用后备图,不影响文章阅读或复制文章链接。
旧文章默认在最后更新超过 180 天时显示时效提示。主题只需要维护与本功能相关的配置:
reading: | |
enable: true | |
notice: true | |
notice_days: 180 | |
related: true | |
related_count: 4 |
这段位于 themes/shoka/_config.yml 。如果单篇不需要提示,在该篇文章顶部添加:
reading: | |
notice: false |
如果需要关闭该篇的阅读工具、提示和推荐,可用 reading: false 。文章下方及侧栏的推荐根据共同标签和分类生成,不查询数据库。
#9.5 新内容页怎么维护?
- 个人随笔:使用独立的 Waline / Neon 动态表。在页面点击「发布 / 管理动态」,用 Waline 管理员账号登录即可发布、编辑、保存草稿和回收动态。首次需部署后端并初始化动态表,之后无需
hexo d。动态支持普通 Markdown,不执行需要 Hexo 构建的主题扩展。静态 YAML 示例保留作备用,但在数据库模式下不展示,也不会自动导入数据库。 - 个人图集:自动读取文章封面图池,每 24 张分组;保持原图地址,不修改 CDN 或代理前缀。追加相册写入
source/_data/albums.yml,首次添加时用列表替换末尾的[];可以单独填写thumbnail,不填则使用原图,不会自动生成一张不存在的缩略图。 - 工具箱:编辑
source/_data/resources.yml,填写资源名、链接、分类和简介。卡片不请求远程图标或统计接口。
工具箱中,每个 - title: 段落就是一张卡片。例如在文件末尾追加以下内容,重新生成和部署后就会出现;相同 group 自动放进同一筛选分类。修改 url 就能更换链接,添加 draft: true 可临时隐藏。
- title: "GitHub Docs" | |
url: https://docs.github.com/zh | |
group: "开发文档" | |
description: "Git 与代码协作功能的官方说明。" |
显示与阅读设置的悬浮按钮位于工具栏最上方;滚动顶部时隐藏,向下浏览后出现。选项使用统一尺寸的主题色卡片,日夜模式下均可使用。
数据文件都附有可复制的字段说明。修改后重新生成站点;部署时同时同步 public 下的 HTML、CSS 和 JS。详细维护说明见项目里的 docs/reading-experience.md 。
#9.6 增强代码块:标记、局部折叠与代码组
旧的 mark:2 、 first_line:10 、 command:("$":1) 和带来源链接的标题仍然有效。以下都是可选参数,不需要安装额外框架,也不会执行代码。文章和写作室使用同一个渲染器。
#9.6.1 标题栏、窗口圆点与参考链接
普通代码块的标题栏左侧依次显示 红黄绿圆点、代码类型、文件名,右侧是阅读工具按钮。文件名和来源链接过长时会省略显示,悬停可查看完整文字;手机上空间不足时按钮自动换行,不会盖住左侧内容。没有填写标题和类型的 raw 代码块仍然保留窗口圆点。
下面是带参考链接的实际效果,沿用原 Shoka 的 [language] [title] [url] [link text] 写法:
console.log("你好,Shoka!"); |
```js src/greeting.js https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/console 参考文档 | |
console.log("你好,Shoka!"); | |
``` |
不需要参考链接时,也可以使用 title="文件名" 。下面三种框样式均可直接使用:
const greeting = "编辑器风格"; | |
console.log(greeting); |
npm install three |
简洁框不显示窗口圆点。 |
```js title="app.js" frame="editor" | |
const greeting = "编辑器风格"; | |
console.log(greeting); | |
``` | |
```bash title="install.sh" frame="terminal" | |
npm install three | |
``` | |
```text title="note.txt" frame="none" | |
简洁框不显示窗口圆点。 | |
``` |
frame 不填时自动判断:Shell、PowerShell、ANSI 日志或含命令提示符的代码使用终端框,其余使用编辑器框。 frame="none" 只去掉窗口装饰,不会关闭复制、换行等工具。
没有标题与类型、只保留窗口圆点:
这段文字没有语言高亮,也没有文件名。 | |
仍可换行、复制、下载和全屏阅读。 |
```raw | |
这段文字没有语言高亮,也没有文件名。 | |
仍可换行、复制、下载和全屏阅读。 | |
``` |
#9.6.2 行与文字的增删标记
const enabled = false; | |
const enabled = true;开启功能 | |
console.log(enabled); |
上面的示例把第一行标为删除、第二行标为新增,并给文字 enabled 添加标记。颜色代表增删语义,不会随自定义主题色变成同一种颜色。写法如下:
```js title="config.js" frame="editor" del={1} ins={2} mark="enabled" label="2:开启功能" | |
const enabled = false; | |
const enabled = true; | |
console.log(enabled); | |
``` |
| 参数 | 写法示例 | 作用 |
|---|---|---|
title | title="config.js" | 文件名,也是下载文件的默认名称 |
frame | frame="editor" | editor 编辑器、 terminal 终端、 none 简洁框;不填自动判断 |
| 行标记 | mark={2,4-6} 、 ins={2} 、 del={3} | 中性高亮、新增、删除;使用显示出来的行号 |
| 文字标记 | mark="enabled" 、 ins="新的值" | 按原文匹配文字,不是正则表达式;可重复填写 |
| 行标签 | label="2:开启功能" | 在这一行下方显示简短说明;可重复填写 |
| 局部折叠 | collapse={2-4,8-10} | 默认收起这些行,点击可展开;不影响复制和下载 |
| 自动换行 | wrap=true | 窄屏自动换行;默认保留续行缩进 |
| 续行缩进 | preserveIndent=false | 关闭续行缩进对齐 |
| 行号 | lineNumbers=false | 默认隐藏行号,仍可在工具栏打开 |
| 起始行号 | startLine=10 | 等同于旧的 first_line:10 |
行范围可用逗号分开,例如 {2,4-6} 。行标记使用显示行号,设置 startLine=10 后,要标记第二行应填写 ins={11} ,不是 ins={2} 。
const theme = "default"; | |
const theme = "lavender";使用新的主题色 | |
const enabled = true; | |
console.log(theme, enabled);记录当前设置 |
```js title="settings.js" startLine=10 del={10} ins={11} mark={12-13} label="11:使用新的主题色" label="13:记录当前设置" | |
const theme = "default"; | |
const theme = "lavender"; | |
const enabled = true; | |
console.log(theme, enabled); | |
``` |
** 只标记一部分文字:** 带引号的 mark 、 ins 、 del 按原文匹配,不会运行正则表达式;同类参数可以重复填写。
const oldGreeting = "旧的问候"; | |
const greeting = "新的问候"; | |
console.log(greeting); |
```js title="message.js" del="旧的问候" ins="新的问候" mark="console" mark="greeting" | |
const oldGreeting = "旧的问候"; | |
const greeting = "新的问候"; | |
console.log(greeting); | |
``` |
这些标记只是展示说明,不会修改代码逻辑。标记同一段文字时尽量不要让多个参数重叠,以免难以判断要强调哪一种含义。
#9.6.3 局部折叠、长代码与长行换行
局部折叠与长行的实际效果:
function greet() { | |
const name = "Shoka"; | |
const message = "Hello, " + name; | |
console.log(message); | |
} | |
greet(); | |
console.log("这是一段很长的代码文字,用来展示手机上的自动换行效果;续行会保留缩进,长行不会挤出文章正文。也可以在工具栏中随时关闭换行。"); | |
```js title="greeting.js" collapse={2-4} wrap=true mark={5} | |
function greet() { | |
const name = "Shoka"; | |
const message = "Hello, " + name; | |
console.log(message); | |
} | |
greet(); | |
console.log("很长的文字会自动换行,并保留续行缩进。"); | |
``` |
局部折叠用 collapse={起始行-结束行} ,可同时填写多个范围,例如 collapse={2-4,8-10} 。折叠行没有被删除,复制与下载仍会包含它们。
超过 15 行的代码会另外出现 “展开完整代码” 按钮。下面故意放入一段较长的代码,可以同时试试整体展开与局部展开:
const chapters = []; | |
// 第一组 | |
chapters.push("起点"); | |
chapters.push("林间小路"); | |
chapters.push("午后的雨"); | |
chapters.push("窗边的光"); | |
// 第二组 | |
chapters.push("旧书店"); | |
chapters.push("纸上的风"); | |
chapters.push("散步"); | |
chapters.push("街角"); | |
// 第三组 | |
chapters.push("傍晚"); | |
chapters.push("一封信"); | |
chapters.push("归途"); | |
// 即使上面的行被折叠,复制时也不会丢失。 | |
const summary = chapters.join(" → "); | |
console.log(summary); | |
console.log("阅读结束,继续探索。"); | |
这段示例的完整写法是:
```js title="long-example.js" collapse={4-6,13-15} mark={18} wrap=true | |
const chapters = []; | |
// 第一组 | |
chapters.push("起点"); | |
chapters.push("林间小路"); | |
chapters.push("午后的雨"); | |
chapters.push("窗边的光"); | |
// 第二组 | |
chapters.push("旧书店"); | |
chapters.push("纸上的风"); | |
chapters.push("散步"); | |
chapters.push("街角"); | |
// 第三组 | |
chapters.push("傍晚"); | |
chapters.push("一封信"); | |
chapters.push("归途"); | |
// 即使上面的行被折叠,复制时也不会丢失。 | |
const summary = chapters.join(" → "); | |
console.log(summary); | |
console.log("阅读结束,继续探索。"); | |
``` |
若想从一开始隐藏行号,加入 lineNumbers=false ;不需要续行缩进时加入 preserveIndent=false :
const explanation = "这一行足够长,可以在较窄的文章正文中换行;换行后的文字从代码列左侧开始,而不是继续对齐原来的两个空格。"; | |
console.log(explanation); |
```js title="wrapping.js" wrap=true preserveIndent=false lineNumbers=false | |
const explanation = "这一行足够长,可以在较窄的文章正文中换行;换行后的文字从代码列左侧开始,而不是继续对齐原来的两个空格。"; | |
console.log(explanation); | |
``` |
#9.6.4 代码组选项卡
多种安装命令放进一个代码组:
npm install three |
pnpm add three |
yarn add three |
::: code-group labels=[npm,pnpm,yarn] | |
```bash title="install.sh" frame="terminal" | |
npm install three | |
``` | |
```bash title="install.sh" frame="terminal" | |
pnpm add three | |
``` | |
```bash title="install.sh" frame="terminal" | |
yarn add three | |
``` | |
::: |
代码组可以点击切换,也支持方向键、 Home 、 End ;手机上选项卡过多时可横向滚动。复制和下载只处理当前代码文件,不会把其他选项卡混进来。
代码组适合放 npm / pnpm / yarn 等平行示例;第三节的代码树适合显示目录层级和多个关联文件。两者可以分别使用,不会互相替换,也不要把代码组容器嵌进代码树。
#9.6.5 代码差异与真实补丁
展示代码差异时可以用 diff lang="js" ,每行开头的 + / - 会变成边栏标记。复制时不包含这些装饰符:
const theme = "default"; | |
const theme = "lavender"; | |
console.log(theme); |
```diff lang="js" title="theme.js" | |
-const theme = "default"; | |
+const theme = "lavender"; | |
console.log(theme); | |
``` |
如果内容是带 --- 、 +++ 、 @@ 的完整补丁,则保留原来的补丁文字,包括增删符号。下面的示例可点击复制确认:
--- a/theme.js | |
+++ b/theme.js | |
@@ -1,2 +1,2 @@ | |
-const theme = "default"; | |
+const theme = "lavender"; | |
console.log(theme); |
```diff title="theme.patch" | |
--- a/theme.js | |
+++ b/theme.js | |
@@ -1,2 +1,2 @@ | |
-const theme = "default"; | |
+const theme = "lavender"; | |
console.log(theme); | |
``` |
#9.6.6 保留旧语法:起始行号、行高亮与命令提示符
已有文章不必逐篇改成新参数。下面仍使用原有的 first_line 、 mark 和 command ,标题栏右侧也能正常显示参考链接:
npm install three | ||
npm install --save-dev typescript |
```bash commands.sh https://docs.npmjs.com/cli/commands/npm-install npm文档 first_line:10 mark:11 command:("$":10||"#":11) | |
npm install three | |
npm install --save-dev typescript | |
``` |
command 是代码列左侧的提示符装饰,不会被复制或下载到代码文件中。多个提示符用 || 分开; first_line:10 与 startLine=10 二选一即可。
#9.6.7 ANSI 彩色终端日志
命令日志使用 ansi 语言可解析真实 ANSI 控制码,支持标准色、256 色和 RGB 真彩色。下面的 JavaScript 可以生成三种颜色的日志:
console.log("\u001b[32m构建成功\u001b[0m"); | |
console.log("\u001b[38;5;214m请检查配置\u001b[0m"); | |
console.log("\u001b[38;2;128;96;192m下一步:本地预览\u001b[0m"); |
将包含真实控制字符的日志写进下面这样的围栏中:
```ansi title="build.log" frame="terminal" | |
在这里粘贴带有真实 ANSI 控制字符的日志。 | |
``` |
包含真实 ANSI 控制字符的显示效果:
构建成功 | |
请检查配置 | |
下一步:本地预览 |
注意:直接在 Markdown 中输入字面文字 \033[32m 或 \u001b[32m 并不会产生彩色效果;它们只有在程序字符串里被解释后才成为控制字符。复制和下载彩色日志时输出纯文本,不会混入颜色控制码;光标移动、清屏和终端链接控制码也不会被当成网页操作执行。
#9.6.8 阅读工具与使用边界
工具栏提供 换行、行号、复制、下载、标记跳转、全屏。复制后会显示 “已复制完整代码”,下载直接保存当前展示的代码文字;折叠区域也包括在内。点击行号可以定位并更新链接锚点;“标记跳转” 按区域逐个定位,隐藏的区域会自动展开。禁用复制的文章同时不提供下载按钮。
| 操作 | 可以在本节怎样试用 |
|---|---|
| 自动换行 | 在 wrapping.js 示例中切换,长行不会撑宽整个页面 |
| 显示 / 隐藏行号 | 初始隐藏行号的示例也可以随时重新显示 |
| 复制完整代码 | 在局部折叠和长代码示例中复制,确认内容完整,不含行标签和命令提示符 |
| 下载当前文件 | 文件名使用 title 或旧式标题中的文件名;代码组只下载当前选项卡 |
| 跳到下一处标记 | 仅有标记时显示此按钮,依次定位各个标记区域,并展开被折叠的行 |
| 定位某一行 | 点击行号,地址中出现代码行锚点,可在同一篇文章中返回该行 |
| 全屏阅读 | 点击右侧全屏图标,按 Esc 或再次点击退出,返回原页面位置 |
| 展开完整代码 | 代码超过 15 行时显示在下方,可与局部折叠一起使用 |
普通代码块默认是五个工具按钮;出现行或文字标记时,再增加 “跳到下一处标记”。红黄绿圆点仅作窗口装饰,不是关闭或缩小按钮。
原有 info 围栏仍是说明文字样式,不会变成带工具栏的代码窗口;Mermaid 仍使用自己的流程图查看器;代码树保留自己的文件切换、复制与全屏入口。文章的 nocopy: true 只隐藏复制和下载入口,不是防止源码被读取的安全措施。
本节新增参数也可在文章写作室中使用,预览沿用文章样式与代码阅读交互;代码是展示内容,不会在浏览器中自动执行。
#十、参考项目与致谢
下面标明各个公开项目的名称、GitHub 地址和在本文中的作用。仓库卡片本身就是第六节语法的实际示例;这里开启动态信息,卡片接近可视区域时会补充 Stars、Forks、主要语言和许可证,同一仓库会复用缓存。网络不可用或接口限流时仍保留项目说明和链接;语言、许可证只展示接口实际提供的信息。
#Shirone:扩展语法与阅读设计参考
项目:LyraVoid / Shirone。文件树、代码树、步骤流、图片网格与 GitHub 卡片的写法和行为参考这个项目;行内批注、参数卡片及阅读增强也借鉴了它的教程表达与阅读设计,再适配当前博客的 Markdown-it、Shoka 主题色、PJAX 与 Fancybox。不是直接加载它的 Astro / Svelte 组件。
本文 Markdown 扩展、教程表达与阅读体验的参考;实际实现已适配 Hexo / Shoka。
对应文档源码如下,链接固定到参考时的版本:
- 文件树与代码树文档源码
- 步骤流文档源码
- 图片网格文档源码
- 仓库卡片用法
对应 MIT 许可说明随项目保存在 docs/licenses/Shirone-MIT.txt 。
#Shoka:原主题与已有功能的基础
增强代码阅读参考 Firefly 代码示例 与 Expressive Code 文档。这里使用原生 JavaScript 与现有 Prism 实现,并非加载它们的 Astro 组件或运行时。
增强代码框、文字标记与代码阅读交互的设计参考。
项目:amehime / hexo-theme-shoka。原有主题风格、提示块、音乐标签及播放器是本次扩展适配的基础;本站的留声机是在现有音乐功能上增加三维交互,并非上游主题原生提供的模型。
原 Shoka 主题,提供本文沿用的主题风格、原有 Markdown 扩展及音乐播放器基础。
#Three.js:留声机的三维渲染基础
项目:mrdoob / three.js。留声机的模型、材质、灯光、相机与交互场景使用 Three.js;页面中的具体模型和音乐联动由本站组件实现。
JavaScript 三维图形库,用于留声机的模型、光照、相机和场景渲染。
#AudioPlayer:留声机音频可视化的视觉参考
项目:Hocoa / AudioPlayer。原留声机设计过程中参考过这个项目的音乐可视化效果;这里只注明视觉参考来源,不表示本页直接嵌入了它的播放器。
留声机原设计的音乐可视化视觉参考,本页不直接加载该项目的播放器。
- 渐进增强先保证正文与链接可用,再添加弹窗、搜索等交互;脚本加载失败也不应丢失内容。它不是要求所有功能都依赖 JavaScript。