博主水平有限,本文含有大量 AIGC,请根据自己需求使用。

添加赛博摇一摇立牌

图例:可爱的稀音小姐(画师:菌菌)

最简单的方法就是直接更换原有角色的图片(个人建议使用1:1比例的图片)

这里需要引用自己修改的js文件,最简单的方法就是将教程中提到的 sakana.min.js 文件存到博客资源文件夹中进行修改,然后直接引用

修改示例:

source/sakana_rimrose.js
1
2
3
4
var N = {
    chisato: {
        // 原图那串base64代码太长了就不在这儿放了
        image: "https://rimrose.top/img/Scene_1000px_1.png", // 自定义图片
_config.solitude.yml
1
2
3
4
5
body:
- <script
  async
  onload="initSakanaWidget()"
  src="https://rimrose.top/sakana_rimrose.js"></script> # 这里是自定义的js文件

修改链接卡片样式

1
2
3
{% link [标题] [副标题] [链接] (自定义Tips) (自定义图标/图片) %}

(后边的两项留空即为默认配置)
1
2
{% link GitHub 软件源代码托管服务平台 https://github.com/ 全球最大的同性交友平台 'fab fa-github' %}
{% link Photoshop '由 Adobe 开发和发行的图像处理软件' https://www.adobe.com/products/photoshop.html ' ' '/img/logos/photoshop.png' %}

将 hexo-solitude-tag 插件里原有的 LinkTag 部分修改为以下内容

node_modules/hexo-solitude-tag/dist/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function linkTag([title, subtitle, link, customTips, customIcon]) {
  _link = true;
  const isLocal = link.startsWith('/');
  const tipsContent = customTips || (isLocal ? '站内链接' : '引用站外链接');

  // 根据 customIcon 判断使用字体图标还是背景图片
  const iconHTML = customIcon
          ? (customIcon.startsWith('fa')
                  ? `<i class="solitude ${customIcon}"></i>`
                  : `<div class="tag-link-left" style="background-image: url('${customIcon}');"></div>`)  // 使用图片作为背景
          : '<i class="solitude fas fa-link"></i>';

  const bottom = `
    <div class="tag-link-tips">${tipsContent}</div>
    <div class="tag-link-bottom">
        <div class="tag-link-left">
          ${iconHTML}
        </div>
        <div class="tag-link-right">
            <div class="tag-link-title">${title}</div>
            <div class="tag-link-sitename">${subtitle}</div>
        </div>
        <i class="solitude fas fa-chevron-right"></i>
    </div>`;

  return `<div class="link-wrapper">${(0, hexo_util_1.htmlTag)('a', {
    class: 'tag-link',
    href: link,
    target: isLocal ? '_self' : '_blank'
  }, bottom, false)}</div>`;
}

node_modules/hexo-solitude-tag/dist/css/link.styl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
.article-container
  a.tag-link
    background var(--efu-secondbg)
    border-radius 8px !important
    display flex
    border var(--style-border)
    flex-direction column
    padding 0.5rem 1rem !important
    border-width 1px
    margin 1rem 0
    margin-left: auto
    margin-right: auto

    &:hover
      background var(--efu-main)
      border var(--style-border-hover) !important

    i
      transition .3s
      margin-left auto
      opacity .6

    .tag-link-bottom
      display flex
      margin-top 0.5rem
      align-items center
      justify-content space-around

    .tag-link-left
      width: 60px
      min-width: 60px
      height: 60px
      background-size: cover
      background-position: center
      border-radius 8px
      background-color var(--efu-card-bg)
      display: flex
      align-items: center
      justify-content: center

      i
        padding 0
        margin auto
        font-size 24px
        color var(--efu-fontcolor)

    .tag-link-right
      margin-left 1rem
      max-width calc(100% - 76px - 1rem)

      .tag-link-title
        font-weight: bold
        color: var(--efu-fontcolor) /* 保持原样,标题颜色 */

      .tag-link-sitename
        color: var(--efu-gray-darker) /* 副标题颜色调整 */
        opacity: 0.8 /* 副标题亮度稍暗 */

    /* 新增:调整 tag-link-tips 样式,使副标题的亮度稍微暗一点 */
    .tag-link-tips
      border-bottom var(--style-border)
      padding-bottom 4px
      font-size 12px
      color var(--efu-gray) /* 修改颜色为稍暗的灰色 */
      font-weight 400
      opacity: 0.7 /* 使提示文字亮度稍暗 */
      transition .3s

@media (max-width: 768px)
  .article-container a.tag-link
    width: 100%
    max-width: none

添加游戏页面

说是魔改,其实也只是使用 hexo-better-steam 插件来获取自己的 steam 游戏库数据,从而为游戏页面提供游戏(其实还有一个原因是不知道为什么我用 hexo-better-steam 插件无法正确生成页面,而且懒得改了)

  • hexo-better-steam 插件地址:
  • 在博客项目根目录运行命令安装插件:
    1
    npm install hexo-better-steam --save

在站点配置文件 _config.yml 里添加以下配置:

_config.yml
1
2
3
4
5
6
7
8
9
steam:
  enable: true
  steamId: '******'
  apiKey: '******'
  path: # 默认为 steamgames/index.html
  title: Steam 游戏库
  quote: 'No game no life'
  tab: all
  length: 1000

  • steamId 的获取方法:

打开 steam 点开个人资料页面即可获取自己的 steamid:
划线部分的数字就是 steamId

  • apiKey的 获取方法:

配置完成后在终端输入 hexo steam -u 即可更新 steam 库,然后会在根目录的 source/_data/ 路径下生成一个 json 文件

不过前边添加游戏页面的时候使用的是 _data/games.yml 里的数据,所以我写了个 python 脚本来根据生成的 json 文件里的数据修改 _data/games.yml

steam_json_to_yml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import json
import yaml
import requests
from concurrent.futures import ThreadPoolExecutor

# 定义 JSON 文件和 YAML 文件的路径
JSON_FILE = "source/_data/steam/yoursteamid.json"  # 输入的 JSON 文件路径
YAML_FILE = "source/_data/games.yml"  # 输出的 YAML 文件路径

# 获取游戏的简介(short_description)
def get_game_description(appid):
    url = f"https://store.steampowered.com/api/appdetails?appids={appid}&l=zh-cn"

    # 模拟浏览器的请求头,强制设置为中文
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
    }

    try:
        response = requests.get(url, headers=headers, timeout=10)  # 设置超时为10秒
        if response.status_code == 200:
            data = response.json()
            # 检查请求是否成功并包含中文数据
            if data[str(appid)]['success']:
                game_data = data[str(appid)]['data']
                # 如果返回的简介为空,则返回默认值
                return game_data.get('short_description', '没有简介')
    except requests.exceptions.RequestException as e:
        print(f"请求游戏 {appid} 时发生错误: {e}")

    return '无法获取游戏简介'

# 将 JSON 文件转换为 YAML 文件
def json_to_yaml(json_file, yaml_file):
    with open(json_file, 'r', encoding='utf-8') as jf:
        # 加载 JSON 数据
        data = json.load(jf)

    # 根据 playtime_forever 字段对游戏数据排序(从大到小)
    data.sort(key=lambda x: x.get("playtime_forever", 0), reverse=True)

    # 初始化 YAML 数据
    yaml_data = []

    # 分类名称(示例中可以按时间等不同维度来分类,这里我们直接用 '好游戏')
    category_name = "我的 Steam 游戏"
    category_desc = "全部游戏"

    # 构建分类数据
    category_item = {
        "name": category_name,  # 分类名称
        "desc": category_desc,  # 分类描述
        "list": []  # 游戏列表
    }

    # 使用 ThreadPoolExecutor 并行处理多个请求
    with ThreadPoolExecutor(max_workers=5) as executor:
        # 提交获取游戏简介的任务
        futures = {game['appid']: executor.submit(get_game_description, game['appid']) for game in data}

        # 遍历排序后的 JSON 中的每个游戏单元
        for game in data:
            # 获取游戏简介(short_description)
            description = futures[game['appid']].result()

            # 构造游戏条目
            yaml_game = {
                "name": game["name"],  # 游戏名称
                "playtime": round(game["playtime_forever"]/60, 1),  # 游戏时长
                "spec": "",  # 副标题(默认)
                "desc": description,  # 游戏描述(从 Steam API 获取)
                "link": f"https://store.steampowered.com/app/{game['appid']}/",  # 游戏链接
                "image": f"https://cdn.cloudflare.steamstatic.com/steam/apps/{game['appid']}/header.jpg"  # 游戏封面
            }
            # 将游戏条目添加到分类的游戏列表中
            category_item["list"].append(yaml_game)

    # 将分类数据添加到 YAML 数据中
    yaml_data.append(category_item)

    # 写入到 YAML 文件
    with open(yaml_file, 'w', encoding='utf-8') as yf:
        yaml.dump(yaml_data, yf, allow_unicode=True, sort_keys=False)

# 执行转换
if __name__ == "__main__":
    json_to_yaml(JSON_FILE, YAML_FILE)
    print(f"YAML 文件已生成:{YAML_FILE}")

这个脚本会将游戏按照总游戏时长降序排列,并且由于我用 playtime_forever 字段来代替原本的 score,所以还需要修改 games.pug 里的内容,修改后的完整代码如下:

games.pug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
include ../widgets/page/banner
#games
    if site.data.games
        each cls in site.data.games
            .game-group
                h2.game-group-title= cls.name
                .game-group-desc= cls.desc
                .game-group-content
                    each item in cls.list
                        .game-item
                            .game-item-cover
                                img.game-item-image(src=item.image, alt=item.name)
                            .game-item-info
                                .game-item-name(onclick='utils.copy("' + item.name + '")')= item.name
                                if item.playtime
                                    .game-item-score= "时长: " + item.playtime + " 小时"
                                .game-item-spec= item.spec
                                .game-item-desc= item.desc
                                if item.link
                                    .game-item-toolbar
                                        a.game-item-link(href=item.link, target="_blank") 详情
                                        a.bber-reply(onclick=`sco.toTalk('${item.name}\\n\\n${item.spec}\\n\\n${item.desc}')`)
                                            i.solitude.fa-solid.fa-comment(style="font-size: 1rem;")

添加追番页面

安装 hexo-bilibili-bangumi 插件

1
npm install hexo-bilibili-bangumi --save

写入插件配置

将下面的配置写入站点配置文件 _config.yml 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
bangumi: # 追番设置
  enable: true
  source: bili
  bgmInfoSource: 'bgmv0'
  path:
  vmid:
  title: '追番列表'
  quote: '生命不息,追番不止!'
  show: 1
  lazyload: true
  srcValue: '__image__'
  lazyloadAttrName: 'data-src=__image__'
  loading:
  showMyComment: false
  pagination: false
  metaColor:
  color:
  webp:
  progress:
  progressBar:
  extraOrder:
  order: latest
  proxy:
    host: '代理host'
    port: '代理端口'
  extra_options:
    key: value
  coverMirror:
cinema: # 追剧设置
  enable: true
  path:
  vmid:
  title: '追剧列表'
  quote: '生命不息,追剧不止!'
  show: 1
  lazyload: true
  srcValue: '__image__'
  lazyloadAttrName: 'data-src=__image__'
  loading:
  metaColor:
  color:
  webp:
  progress:
  progressBar:
  extraOrder:
  order:
  extra_options:
    key: value
  coverMirror:
game: # 游戏设置,仅支持source: bgmv0
  enable: true
  path:
  source: bgmv0
  vmid:
  title: '游戏列表'
  quote: '生命不息,游戏不止!'
  show: 1
  lazyload: true
  srcValue: '__image__'
  lazyloadAttrName: 'data-src=__image__'
  loading:
  metaColor:
  color:
  webp:
  progress:
  progressBar:
  extraOrder:
  order:
  extra_options:
    key: value
  coverMirror:

请按需修改各项配置参数,必须修改的有 source, vmid, lazyload,其中 vmid 的获取方式请参考 README,其他配置也可以在这里查看

在 hexo generate 或 hexo deploy 之前使用 hexo bangumi -u 命令更新追番数据

一切就绪之后使用 hexo generate 命令即可生成追番页面,默认路径为 bangumis/index.html

在主题配置文件里添加追番页面到导航栏,例如:

_config.solitude.yml
1
2
3
4
nav:
  menu:
     我的:
       追番列表: /bangumis/ || fas fa-tv

由于我觉得 hexo-bilibili-bangumi 插件的样式不够好看,然后我去看了一下b站网页端的番剧页面的样式,我觉得还挺不错的:

它做了一个番剧卡片悬停展开的样式:

卡片展开样式

我觉得这个样式挺好看,于是就照着写了一个样式:

魔改后的样子

创建页面的方法和添加游戏页面一样,不再赘述;具体代码如下:

themes/solitude/layout/includes/page/animes.pug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
include ../widgets/page/banner
#animes
if site.data.animes
    .toggle-button-container
        button.toggle-button(onclick="showCardSet(1)") 想看
        button.toggle-button.active(onclick="showCardSet(2)") 在看
        button.toggle-button(onclick="showCardSet(3)") 已看
    each cls, idx in site.data.animes
        .card-set(id='cardSet' + (idx + 1), class=(idx === 1 ? 'active' : ''))
            - let rowCount = Math.ceil(cls.list.length / 5);
            each rowIdx in Array.from({ length: rowCount }, (_, i) => i)
                .card-row
                    - let startIdx = rowIdx * 5;
                    - let endIdx = startIdx + 5;
                    each item, i in cls.list.slice(startIdx, endIdx)
                        .card-container(
                            onclick=(item.link ? 'window.open("' + item.link + '", "_blank")' : null)
                            onmouseover=`this.querySelector('.card').style.backgroundImage = 'url("${item.bg}")'`
                            onmouseout=`this.querySelector('.card').style.backgroundImage = 'url("${item.image}")'`
                        )
                            .card(style="background-image: url("+ item.image + ")")
                            //.card-text= item.score
                            .card-description
                                .description-line= item.name
                                //.description-line= item.desc

script.
    function showCardSet(setNumber) {
        const cardSets = document.querySelectorAll('.card-set');
        const buttons = document.querySelectorAll('.toggle-button');

        cardSets.forEach(cardSet => cardSet.classList.remove('active'));
        buttons.forEach(button => button.classList.remove('active'));

        const selectedCardSet = document.getElementById(`cardSet${setNumber}`);
        const selectedButton = document.querySelector(`.toggle-button:nth-child(${setNumber})`);

        if (selectedCardSet) {
            selectedCardSet.classList.add('active');
        }
        if (selectedButton) {
            selectedButton.classList.add('active');
        }
    }
themes/solitude/source/css/_page/animes.styl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
.toggle-button-container
  display: flex
  justify-content: center
  gap: 10px /* 按钮之间的间距 */
  margin-bottom: 20px /* 按钮与卡片集之间的间距 */

.toggle-button
  display: inline-block
  padding: 0.5rem 1rem
  margin: 0.5rem
  border: var(--style-border)
  border-radius: 8px
  background-color: var(--efu-card-btn-bg)
  color: var(--efu-fontcolor)
  text-decoration: none
  cursor: pointer
  transition: all 0.3s

.toggle-button:hover
  border: var(--style-border-hover)
  background-color: var(--efu-theme-op)

.toggle-button.active
  background-color: var(--efu-theme)
  border: var(--style-border-hover)
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2)
  color: var(--efu-white)
  transform: translateY(1px)

.card-set
  display: none /* 默认隐藏所有卡片集 */

.card-set.active
  display: block /* 显示激活的卡片集 */

.card-row
  display: flex
  justify-content: center
  gap: 20px
  margin-bottom: 20px

.card-container
  display: flex
  flex-direction: column /* 垂直布局,包含卡片和描述 */
  align-items: flex-start
  width: 200px /* 初始宽度 */
  height: auto /* 自适应内容高度 */
  overflow: hidden
  position: relative
  transition: 0.3s ease-in-out
  text-align: left
  cursor: pointer
  border-radius: 10px

.card-container:hover
  width: 533px
  box-shadow: var(--efu-shadow-border)

.card
  width: 100%
  height: 300px /* 固定高度 */
  background-size: cover
  background-position: center
  border-radius: 10px
  transition: background-image 0.3s ease-in-out
  position: absolute
  top: 0
  left: 0
  transform-origin: center

.card-container:first-child:hover .card
  transform-origin: left center

.card-container:last-child:hover .card
  transform-origin: right center

.card-text
  position: relative
  bottom: -240px /* 调整底部位置 */
  right: -140px /* 右侧间距 */
  padding: 5px /* 内边距 */
  text-align: center /* 文字居中 */
  color: #FFFFFF
  font-size: 1.8em
  font-weight: bold
  font-family: "PingFang SC"
  text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.8) /* 文字阴影 */
  z-index: 1 /* 确保文字在图片之上 */

.card-container:hover .card-text
  opacity: 0 /* 悬停时文字消失 */
  pointer-events: none /* 悬停时文字块不可选中 */

.card-description
  display: block
  margin-top: 320px /* 确保描述与卡片下方对齐 */
  text-align: left
  padding-left: 0

.description-line
  font-size: 1.2em
  color: var(--efu-fontcolor)
  white-space: nowrap /* 防止文字换行 */
  overflow: hidden /* 隐藏溢出的文字 */
  text-overflow: ellipsis /* 显示省略号 */
  max-width: 200px /* 初始最大宽度 */
  transition: max-width 0.3s ease-in-out /* 添加过渡效果 */

.card-container:hover .description-line
  max-width: 533px /* 悬停时的最大宽度 */

当然,由于 hexo-bilibili-bangumi 插件获取的数据是存在 source/_data/bangumis.json 里边的,所以还需要将 bangumis.json 里边的数据转换到 animes.yml 里

所以我也搞了个用来转换的脚本:

bangumis_json_to_yml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import json
import yaml
from pathlib import Path

# 定义文件路径
json_file_path = Path('source/_data/bangumis.json')
yml_file_path = Path('source/_data/animes.yml')

# 读取 JSON 文件
with open(json_file_path, 'r', encoding='utf-8') as json_file:
    data = json.load(json_file)

# 如果 YAML 文件已存在,先读取现有数据
if yml_file_path.exists():
    with open(yml_file_path, 'r', encoding='utf-8') as yml_file:
        existing_data = yaml.safe_load(yml_file)
else:
    existing_data = []

# 准备要写入 YAML 的数据
yaml_data = []

# 定义分类信息
categories = {
    'wantWatch': {'name': '想看', 'desc': '想看的动漫'},
    'watching': {'name': '在看', 'desc': '正在看的动漫'},
    'watched': {'name': '看过', 'desc': '看过的动漫'}
}

# 将现有数据转为字典格式,方便查找和保留字段
existing_data_dict = {
    category['name']: {anime['name']: anime for anime in category.get('list', [])}
    for category in existing_data
}

# 处理每个分类
for category_key, category_info in categories.items():
    if category_key in data:
        category_data = {
            'name': category_info['name'],
            'desc': category_info['desc'],
            'list': []
        }

        # 查找现有分类数据
        existing_category = existing_data_dict.get(category_info['name'], {})

        for item in data[category_key]:
            # 获取已有的条目(如果存在)
            existing_item = existing_category.get(item.get('title'), {})

            # 构建新条目,保留已有字段
            anime_item = {
                'name': item.get('title'),
                'score': f"{item.get('score')}" if item.get('score') else None,
                'desc': item.get('myComment') or " ",
                'link': f"https://bgm.tv/subject/{item.get('id')}" if item.get('id') else None,
                'image': item.get('cover'),
                'bg': existing_item.get('bg', "https://images.rimrose.site/images/DEFAULT_ANIME_BACKGROUND.jpg"),  # 保留已有 bg 值或设置默认值
            }

            # 移除空值
            anime_item = {k: v for k, v in anime_item.items() if v is not None}

            category_data['list'].append(anime_item)

        yaml_data.append(category_data)

# 写入 YAML 文件
with open(yml_file_path, 'w', encoding='utf-8') as yml_file:
    yaml.dump(yaml_data, yml_file, allow_unicode=True, sort_keys=False)

print(f"Generated {yml_file_path} successfully.")

程序会保留自定义的背景图,如果没有设置背景图,则会使用默认的背景图,默认的背景图在第59行,请按需修改。

修改代码高亮引擎

使用 highlightjs 或 prismjs 时无法正确渲染某些语言,比如 pug 或者 Assembly,单单是这一点就让我很头疼

而且在看到璜珀使用 shiki 渲染代码时使用了很炫酷的 diff 效果,所以干脆就照着改了一下

基础修改请参考:

需要注意的是 scripts/helper/stylus.js 的第10行,如果原本没有 shiki 的话需要加上

scripts/helper/stylus.jsline 10
9
10
11
12
13
// highlight
const {syntax_highlighter: syntaxHighlighter, highlight, prismjs, shiki} = hexo.config 
let {enable: highlightEnable, line_number: highlightLineNumber} = highlight
let {enable: prismjsEnable, line_number: prismjsLineNumber} = prismjs
let {enable: shikiEnable, line_number: shikiLineNumber} = shiki

看完了基础修改之后,我发现无法正确渲染 diff 样式,所以在评论里问了璜珀,他告诉我需要在 shiki 的配置里开启 transformerNotationDiff 转换器并手动添加 Diff 样式

首先需要在 _config.yml 中添加如下配置(这部分的详细说明在Hexo-Highlight-Shiki 的 README里写有):

_config.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
shiki:
  theme:
    light: 'github-light'
    dark: 'github-dark'
  line_number: true
  transformers:
    - name: transformerNotationHighlight
      options:
        classActiveLine: "marked"
        classActivePre: "has-highlighted"
    - name: transformerNotationDiff
      options: 
        classLineAdd: "diff add"
        classLineRemove: "diff remove"
        classActivePre: "has-diff"
    - name: transformerNotationFocus
      options:
        classActiveLine: "focused"
        classActivePre: "has-focused"

你应该也注意到了我这里写的 transformerNotationFocus 这个转换器,它和 transformerNotationDiff 的用法都可以在Shiki - 常用转换器里找到,但是 Focus 的样式也是需要自己再写的

这里给出我在璜珀写的 Diff 样式的基础上添加了对 Focus 的支持的完整代码:

source/css/_highlight/shiki/diff.styl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
$highlight-line-diff-add-bg = #b6ffe4
$highlight-line-diff-add-bg-dark = #173b2e
$highlight-line-diff-add-symbol = #3dd68c
$highlight-line-diff-remove-bg = #edd5d5
$highlight-line-diff-remove-bg-dark = #421b1b
$highlight-line-diff-remove-symbol = #dd6464

figure.highlight
  table::-webkit-scrollbar
    color var(--efu-secondbg)
    height 6px
    background var(--efu-hl-bg)
    border-radius 6px
    display initial

  pre.has-diff
    padding-left 1rem

    .diff
      position relative

      &::before
        position absolute
        left -0.7rem
        content ""
        color transparent

      &.add
        background-color $highlight-line-diff-add-bg

        &::before
          content "+"
          color $highlight-line-diff-add-symbol

      &.remove
        background-color $highlight-line-diff-remove-bg

        &::before
          content "-"
          color $highlight-line-diff-remove-symbol

  pre.has-focused
    .line
      filter: blur(3px)
      transition: filter 0.2s ease-in-out
      position relative

    .focused
      filter: none
      z-index 2

    &:hover .line
      filter: none

  [data-theme=dark] &
    td.code
      pre
        background-color transparent !important
      span
        color var(--shiki-dark) !important
        font-style var(--shiki-dark-font-style) !important
        font-weight var(--shiki-dark-font-weight) !important
        text-decoration var(--shiki-dark-text-decoration) !important

    pre.has-diff
      .diff
        &.add
          background-color $highlight-line-diff-add-bg-dark

        &.remove
          background-color $highlight-line-diff-remove-bg-dark

By the way,也许需要注意一下转换器代码的写法,应该是当前代码对应的注释写法,比如在 python 中写 # [!code ++] 的话,在 C++ 里就应该写成 // [!code ++]