对Hexo-Solitude主题的一些修改
博主水平有限,本文含有大量 AIGC,请根据自己需求使用。
添加赛博摇一摇立牌
基础修改请参考:
最简单的方法就是直接更换原有角色的图片(个人建议使用1:1比例的图片)
这里需要引用自己修改的js文件,最简单的方法就是将教程中提到的 sakana.min.js 文件存到博客资源文件夹中进行修改,然后直接引用
修改示例:
1 | var N = { chisato: { // 原图那串base64代码太长了就不在这儿放了 image: "https://rimrose.top/img/Scene_1000px_1.png", // 自定义图片 |
1 | body: - <script async onload="initSakanaWidget()" src="https://rimrose.top/sakana_rimrose.js"></script> # 这里是自定义的js文件 |
修改链接卡片样式
1 | {% link [标题] [副标题] [链接] (自定义Tips) (自定义图标/图片) %} (后边的两项留空即为默认配置) |
1 | {% 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 部分修改为以下内容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
31function 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>`;
}
1 | .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 里添加以下配置:1
2
3
4
5
6
7
8
9steam:
enable: true
steamId: '******'
apiKey: '******'
path: # 默认为 steamgames/index.html
title: Steam 游戏库
quote: 'No game no life'
tab: all
length: 1000
- steamId 的获取方法:
打开 steam 点开个人资料页面即可获取自己的 steamid:
- apiKey的 获取方法:
配置完成后在终端输入 hexo steam -u
即可更新 steam 库,然后会在根目录的 source/_data/ 路径下生成一个 json 文件
不过前边添加游戏页面的时候使用的是 _data/games.yml 里的数据,所以我写了个 python 脚本来根据生成的 json 文件里的数据修改 _data/games.yml
1 | 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 里的内容,修改后的完整代码如下:
1 | 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 | 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
在主题配置文件里添加追番页面到导航栏,例如:
1 | nav: menu: 我的: 追番列表: /bangumis/ || fas fa-tv |
由于我觉得 hexo-bilibili-bangumi 插件的样式不够好看,然后我去看了一下b站网页端的番剧页面的样式,我觉得还挺不错的:
它做了一个番剧卡片悬停展开的样式:
我觉得这个样式挺好看,于是就照着写了一个样式:
创建页面的方法和添加游戏页面一样,不再赘述;具体代码如下:
1 | 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'); } } |
1 | .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 里
所以我也搞了个用来转换的脚本:
1 | 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 的话需要加上
9 | // 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里写有):
1 | 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 的支持的完整代码:
1 | $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 ++]