
关于本站|Solitude主题魔改内容
为了更好的使用体验,现将本站对solitude主题的全部魔改文件公示于此,希望能给一些想魔改但无从下手的小白提供一些帮助
2025.05.04 更新说明:本文修改内容适用于 solitude 3.0.19 版本
评论链接安全跳转
路径:
solitude/layout/includes/widgets/third-party/comments/twikoo.pug
为实现评论区链接安全跳转提醒,将该文件onCommentLoaded
之后一行进行修改,为保证缩进一致,此处贴出全部文件(使用前请先安装hexo安全跳转插件)
- const { envId, region, option ,accessToken } = theme.twikoo
- const { lazyload, count, use,commentBarrage } = theme.comment
script().
(() => {
const getCount = () => {
const ele = document.querySelectorAll('.twikoo-count')
if (!ele) return
twikoo.getCommentsCount({
envId: '!{envId}',
region: '!{region}',
urls: [window.location.pathname],
includeReply: false
}).then(res => {
ele.forEach(item => item.textContent = res[0].count)
}).catch(err => {
console.error(err)
})
}
const init = () => {
twikoo.init(Object.assign({
el: '#twikoo-wrap',
envId: '!{envId}',
region: '!{region}',
path: window.location.pathname,
onCommentLoaded: () => {
GLOBAL_CONFIG.lightbox && utils.lightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
const processLinks = () => {
const container = document.querySelector('#twikoo .tk-comments-container');
if (!container) return;
const links = container.querySelectorAll('a');
links.forEach((link) => {
const href = link.getAttribute('href');
if (!href) return;
if (!href.startsWith(window.location.origin) && !link.hasAttribute('data-fancybox')) {
const encodedHref = btoa(href);
const newHref = `/go.html?i=${encodedHref}`;
link.setAttribute('href', newHref);
link.setAttribute('rel', 'external nofollow noopener noreferrer');
link.setAttribute('target', '_blank');
}
});
};
processLinks();
}
}, !{JSON.stringify(option)}))
!{count ? ' && getCount()' : ''}
sco.owoBig({
body: '.OwO-body',
item: '.OwO-items li'
})
!{commentBarrage} && barrageTwikoo()
}
const loadTwikoo = () => {
if (typeof twikoo === 'object') setTimeout(init,0)
else utils.getScript('!{url_for(theme.cdn.twikoo)}').then(init)
}
if ('!{use[0]}' === 'Twikoo' || !{lazyload}) {
if (!{lazyload}) utils.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
else loadTwikoo()
} else {
window.loadTwoComment = loadTwikoo
}
})()
if commentBarrage
script.
async function barrageTwikoo() {
await fetch("!{envId}", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
event: "COMMENT_GET",
accessToken: "!{accessToken}",
url: window.location.pathname
})
}).then(async res => {
if (!res.ok) throw new Error("HTTP error! status: " + res.status)
const data = await res.json();
const init = () => {
initializeCommentBarrage((data.data).map(item => Object.assign({
content: item.comment,
nick: item.nick,
mailMd5: item.mailMd5,
id: item.id
})))
}
if (typeof initializeCommentBarrage === "undefined") await utils.getScript('!{url_for(theme.cdn.commentBarrage)}').then(init)
else init()
}).catch(error => console.error("An error occurred while fetching comments: ", error))
}
修改的文件内容中对本站有自行修改过的独立适配,若您在安装安全跳转插件后使用默认配置,请将本文件中第39行内容替换为
const newHref = `/go.html?u=${encodedHref}`;
加载动画
颜色与闪烁速度
路径:
solitude/source/css/_layout/fullpage.styl
.loading-bg
部分针对加载动画颜色的修改(增加透明度)
background var(--loading-bg)
.loading-img
部分针对加载动画头像闪烁速度的修改
animation-duration 0.5s
背景颜色(透明度)
路径:
solitude/source/css/_mode/index.styl
针对日夜双模式加载动画的颜色(透明度)的修改
[data-theme=dark]
--loading-bg #000000dd
[data-theme=light]
--loading-bg #ffffffdd
文章统计
原教程与更多魔改内容:
创建页面
hexo new page charts
配置文件引入部分引入:
inject: head: - <script src="https://npm.elemecdn.com/echarts@4.9.0/dist/echarts.min.js"></script>
新建文件路径:
solitude/scripts/helper/charts.jsconst cheerio = require('cheerio') const moment = require('moment') hexo.extend.filter.register('after_render:html', function (locals) { const $ = cheerio.load(locals) const post = $('#posts-chart') const tag = $('#tags-chart') const category = $('#categories-chart') const htmlEncode = false if (post.length > 0 || tag.length > 0 || category.length > 0) { if (post.length > 0 && $('#postsChart').length === 0) { if (post.attr('data-encode') === 'true') htmlEncode = true post.after(postsChart(post.attr('data-start'))) } if (tag.length > 0 && $('#tagsChart').length === 0) { if (tag.attr('data-encode') === 'true') htmlEncode = true tag.after(tagsChart(tag.attr('data-length'))) } if (category.length > 0 && $('#categoriesChart').length === 0) { if (category.attr('data-encode') === 'true') htmlEncode = true category.after(categoriesChart(category.attr('data-parent'))) } if (htmlEncode) { return $.root().html().replace(/&#/g, '&#') } else { return $.root().html() } } else { return locals } }, 15) function postsChart (startMonth) { const startDate = moment(startMonth || '2020-01') const endDate = moment() const monthMap = new Map() const dayTime = 3600 * 24 * 1000 for (let time = startDate; time <= endDate; time += dayTime) { const month = moment(time).format('YYYY-MM') if (!monthMap.has(month)) { monthMap.set(month, 0) } } hexo.locals.get('posts').forEach(function (post) { const month = post.date.format('YYYY-MM') if (monthMap.has(month)) { monthMap.set(month, monthMap.get(month) + 1) } }) const monthArr = JSON.stringify([...monthMap.keys()]) const monthValueArr = JSON.stringify([...monthMap.values()]) return ` <script id="postsChart"> var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)' var postsChart = echarts.init(document.getElementById('posts-chart'), 'light'); var postsOption = { title: { text: '文章发布统计图', x: 'center', textStyle: { color: color } }, tooltip: { trigger: 'axis' }, xAxis: { name: '日期', type: 'category', boundaryGap: false, nameTextStyle: { color: color }, axisTick: { show: false }, axisLabel: { show: true, color: color }, axisLine: { show: true, lineStyle: { color: color } }, data: ${monthArr} }, yAxis: { name: '文章篇数', type: 'value', nameTextStyle: { color: color }, splitLine: { show: false }, axisTick: { show: false }, axisLabel: { show: true, color: color }, axisLine: { show: true, lineStyle: { color: color } } }, series: [{ name: '文章篇数', type: 'line', smooth: true, lineStyle: { width: 0 }, showSymbol: false, itemStyle: { opacity: 1, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(128, 255, 165)' }, { offset: 1, color: 'rgba(1, 191, 236)' }]) }, areaStyle: { opacity: 1, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(128, 255, 165)' }, { offset: 1, color: 'rgba(1, 191, 236)' }]) }, data: ${monthValueArr}, markLine: { data: [{ name: '平均值', type: 'average', label: { color: color } }] } }] }; postsChart.setOption(postsOption); window.addEventListener('resize', () => { postsChart.resize(); }); postsChart.on('click', 'series', (event) => { if (event.componentType === 'series') window.location.href = '/archives/' + event.name.replace('-', '/'); }); </script>` } function tagsChart (len) { const tagArr = [] hexo.locals.get('tags').map(function (tag) { tagArr.push({ name: tag.name, value: tag.length, path: tag.path }) }) tagArr.sort((a, b) => { return b.value - a.value }) const dataLength = Math.min(tagArr.length, len) || tagArr.length const tagNameArr = [] for (let i = 0; i < dataLength; i++) { tagNameArr.push(tagArr[i].name) } const tagNameArrJson = JSON.stringify(tagNameArr) const tagArrJson = JSON.stringify(tagArr) return ` <script id="tagsChart"> var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)' var tagsChart = echarts.init(document.getElementById('tags-chart'), 'light'); var tagsOption = { title: { text: 'Top ${dataLength} 标签统计图', x: 'center', textStyle: { color: color } }, tooltip: {}, xAxis: { name: '标签', type: 'category', nameTextStyle: { color: color }, axisTick: { show: false }, axisLabel: { show: true, color: color, interval: 0 }, axisLine: { show: true, lineStyle: { color: color } }, data: ${tagNameArrJson} }, yAxis: { name: '文章篇数', type: 'value', splitLine: { show: false }, nameTextStyle: { color: color }, axisTick: { show: false }, axisLabel: { show: true, color: color }, axisLine: { show: true, lineStyle: { color: color } } }, series: [{ name: '文章篇数', type: 'bar', data: ${tagArrJson}, itemStyle: { borderRadius: [5, 5, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(128, 255, 165)' }, { offset: 1, color: 'rgba(1, 191, 236)' }]) }, emphasis: { itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(128, 255, 195)' }, { offset: 1, color: 'rgba(1, 211, 255)' }]) } }, markLine: { data: [{ name: '平均值', type: 'average', label: { color: color } }] } }] }; tagsChart.setOption(tagsOption); window.addEventListener('resize', () => { tagsChart.resize(); }); tagsChart.on('click', 'series', (event) => { if(event.data.path) window.location.href = '/' + event.data.path; }); </script>` } function categoriesChart (dataParent) { const categoryArr = [] let categoryParentFlag = false hexo.locals.get('categories').map(function (category) { if (category.parent) categoryParentFlag = true categoryArr.push({ name: category.name, value: category.length, path: category.path, id: category._id, parentId: category.parent || '0' }) }) categoryParentFlag = categoryParentFlag && dataParent === 'true' categoryArr.sort((a, b) => { return b.value - a.value }) function translateListToTree (data, parent) { let tree = [] let temp data.forEach((item, index) => { if (data[index].parentId == parent) { let obj = data[index]; temp = translateListToTree(data, data[index].id); if (temp.length > 0) { obj.children = temp } if (tree.indexOf()) tree.push(obj) } }) return tree } const categoryNameJson = JSON.stringify(categoryArr.map(function (category) { return category.name })) const categoryArrJson = JSON.stringify(categoryArr) const categoryArrParentJson = JSON.stringify(translateListToTree(categoryArr, '0')) return ` <script id="categoriesChart"> var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)' var categoriesChart = echarts.init(document.getElementById('categories-chart'), 'light'); var categoryParentFlag = ${categoryParentFlag} var categoriesOption = { title: { text: '文章分类统计图', x: 'center', textStyle: { color: color } }, legend: { top: 'bottom', data: ${categoryNameJson}, textStyle: { color: color } }, tooltip: { trigger: 'item' }, series: [] }; categoriesOption.series.push( categoryParentFlag ? { nodeClick :false, name: '文章篇数', type: 'sunburst', radius: ['15%', '90%'], center: ['50%', '55%'], sort: 'desc', data: ${categoryArrParentJson}, itemStyle: { borderColor: '#fff', borderWidth: 2, emphasis: { focus: 'ancestor', shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(255, 255, 255, 0.5)' } } } : { name: '文章篇数', type: 'pie', radius: [30, 80], roseType: 'area', label: { color: color, formatter: '{b} : {c} ({d}%)' }, data: ${categoryArrJson}, itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(255, 255, 255, 0.5)' } } } ) categoriesChart.setOption(categoriesOption); window.addEventListener('resize', () => { categoriesChart.resize(); }); categoriesChart.on('click', 'series', (event) => { if(event.data.path) window.location.href = '/' + event.data.path; }); </script>` }
在页面中显示
<!-- 文章发布时间统计图 --> <div id="posts-chart" data-start="2021-01" style="border-radius: 8px; height: 300px; padding: 10px;"></div> <!-- 文章标签统计图 --> <div id="tags-chart" data-length="10" style="border-radius: 8px; height: 300px; padding: 10px;"></div> <!-- 文章分类统计图 --> <div id="categories-chart" data-parent="true" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
标题宽度修改
为h1增加上下宽度、为h2增加下宽度
路径:
solitude/source/css/_layout/article-container.styl
\\ 193行起
h1
font-size 1.5rem
line-height 1.3
padding-top 1rem
padding-bottom 1rem
h2
font-size 1.3rem
line-height 1.3
border-top 1px dashed var(--efu-theme-op)
padding-top 1rem
padding-bottom 1rem
移动端文章页边距调整
路径:
solitude/source/css/_layout/article-container.styl
\\第6行
padding 1rem .5rem
路径:
solitude/source/css/_global/index.styl
\\409行
padding 0 .5rem
即刻说说支持换行与加粗
路径:
solitude/layout/includes/page/brevity.pug文件,修改第19行
!= item.content
路径:
solitude/layout/includes/widgets/home/bbTimeList.pug,修改第7行
| #{item.content.replace(/<[^>]*>/g, '')}
后续撰写使用<br>
进行换行,使用<b>加粗文字</b>
实现文字加粗标重
文章本地AI调整
本部分内容调整鸣谢:清羽飞扬
另外感谢ChatGPT(?
具体实现的效果可参考本站
js调整
路径:
solitude/source/js/post_ai.js
class AIPostRenderer {
static ANIMATION_DELAY_MS = 40; // 每个字符之间的延迟(单位:ms)
static AI_EXPLANATION_SELECTOR = ".ai-explanation"; // 解释容器选择器
static AI_TAG_SELECTOR = ".ai-tag"; // 标签选择器
constructor() {
this.startTextAnimation = this.startTextAnimation.bind(this);
this.animationFrame = null;
this.isDeleting = false; // 是否处于删除阶段
}
init() {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", this.initialize.bind(this));
} else {
this.initialize();
}
}
initialize() {
this.cacheElements();
if (this.validateContent()) {
this.renderAIContent();
}
}
cacheElements() {
this.refs = new WeakMap();
this.refs.set(document, {
explanationElement: document.querySelector(AIPostRenderer.AI_EXPLANATION_SELECTOR),
tagElement: document.querySelector(AIPostRenderer.AI_TAG_SELECTOR)
});
const { explanationElement, tagElement } = this.refs.get(document) || {};
this.explanationElement = explanationElement;
this.tagElement = tagElement;
}
validateContent() {
return (
this.explanationElement &&
this.tagElement &&
this.aiContent.length &&
!this.isAnimating
);
}
renderAIContent() {
this.prepareAnimation();
// 延迟3秒后,开始真正执行删除和打字动画
setTimeout(() => {
this.explanationElement.style.display = "block"; // 显示解释容器
this.explanationElement.classList.add("fast-blink"); // 开始快闪
this.animationFrame = requestAnimationFrame(() =>
this.startTextAnimation(0)
);
}, 3000);
}
prepareAnimation() {
this.isAnimating = true;
this.isDeleting = true; // 初始化设为删除模式
this.tagElement.classList.add("loadingAI");
// 注意这里不加 fast-blink,保持3000ms期间慢闪
}
startTextAnimation(index) {
if (this.isDeleting) {
// 删除阶段
const currentText = this.explanationElement.textContent;
if (currentText.length > 0) {
this.explanationElement.textContent = currentText.slice(0, -1); // 删除最后一个字
setTimeout(() => {
this.animationFrame = requestAnimationFrame(() =>
this.startTextAnimation(index)
);
}, AIPostRenderer.ANIMATION_DELAY_MS);
} else {
this.isDeleting = false; // 删除完成,开始打字
this.startTextAnimation(0);
}
} else {
// 打字阶段
if (index >= this.aiContent.length) {
this.completeAnimation(); // 打字完成
} else {
this.explanationElement.textContent += this.aiContent[index]; // 添加下一个字符
setTimeout(() => {
this.animationFrame = requestAnimationFrame(() =>
this.startTextAnimation(index + 1)
);
}, AIPostRenderer.ANIMATION_DELAY_MS);
}
}
}
completeAnimation() {
cancelAnimationFrame(this.animationFrame);
this.isAnimating = false;
this.tagElement.classList.remove("loadingAI");
this.explanationElement.classList.remove("fast-blink"); // 打字完成后恢复慢闪
const event = new CustomEvent("aiRenderComplete", {
detail: { element: this.explanationElement }
});
document.dispatchEvent(event);
}
get aiContent() {
return PAGE_CONFIG?.ai_text || "";
}
}
// 单例模式,避免重复初始化
const aiPostRenderer = (() => {
let instance;
return () => {
if (!instance) {
instance = new AIPostRenderer();
instance.init();
}
return instance;
};
})();
// 初始化
const ai = aiPostRenderer();
css调整
路径:
solitude/source/css/_post/index.styl
if hexo-config('comment.commentBarrage')
@import 'commentBarrage'
@import "copyright"
@import "meta"
@import "relatedPost"
@import "tools"
@import "pagination"
if hexo-config('google_adsense.enable')
@import "ads.styl"
if hexo-config('post.ai.enable')
.post-ai
background var(--efu-secondbg)
border-radius 12px
padding 12px
line-height 1.3
border var(--style-border-always)
margin-top 16px
min-height 101.22px
box-shadow var(--efu-shadow-border)
+maxWidth768()
margin-top 0
.ai-title
display flex
color var(--efu-lighttext)
border-radius 8px
align-items center
user-select none
.ai-title-left
display flex
align-items center
color var(--efu-lighttext)
i.ai-title-icon
width 24px
height 24px
display flex
background var(--efu-lighttext)
color var(--efu-card-bg)
font-size 14px
border-radius 20px
justify-content center
align-items center
.ai-title-text
font-weight 700
margin-left 8px
line-height 1
font-size 14px
.ai-tag
font-size 12px
background-color var(--efu-lighttext)
box-shadow var(--efu-shadow-main)
color var(--efu-card-bg)
font-weight 700
border-radius 12px
margin-left auto
line-height 12px
padding 6px 8px
display flex
align-items center
justify-content center
transition .3s
&.loadingAI
animation-duration 2s
animation-name AILoading
animation-iteration-count infinite
animation-direction alternate
.ai-explanation
margin-top 12px
overflow hidden
padding 8px 12px
background var(--efu-card-bg)
border-radius 8px
border var(--style-border-always)
font-size 15px
line-height 1.4
display none
text-align left
&:after
content ''
display inline-block
width 10px
height 3px
margin-left 2px
background var(--efu-lighttext)
vertical-align bottom
animation blink-underline 1s ease-in-out infinite
transition all .3s
position relative
bottom 3px
@keyframes blink-underline
0%, 100%
opacity 1
50%
opacity 0
.blinking-cursor
background-color var(--efu-lighttext)
width 14px
height 14px
border-radius 16px
display inline-block
vertical-align middle
animation blinking-cursor 2s infinite
margin-left 4px
margin-bottom 3px
transform scale(.6)
.char
display inline-block
opacity 0
animation chat-float .5s ease forwards;
@keyframes chat-float
0%
opacity 0
transform translateY(20px)
100%
opacity 1
transform translateY(0)
@keyframes blink-underline-fast
0%, 100%
opacity 1
50%
opacity 0
.ai-explanation.fast-blink:after
animation blink-underline-fast 0.4s ease-in-out infinite
#post
border var(--style-border)
border-radius var(--radius)
box-shadow var(--efu-shadow-border)
background var(--efu-card-bg)
添加加载延迟提示
修改pug文件
路径:
solitude/layout/includes/widgets/post/ai.pug
\\ 修改第7行
.ai-explanation(style="display: block;")=theme.post.ai.loading
修改配置文件
post:
# 其他配置内容
ai:
# 其他配置内容
loading: 拼命加载中··· # 添加延迟加载期间的提示文字
图标修改
路径:
solitude/layout/includes/widgets/home/bbTimeList.pug
第2行
i.bber-logo.solitude.far.fa-newspaper.fa-bounce(onclick=`pjax.loadUrl('${url_for(theme.brevity.page)}')`)
路径:
solitude/layout/includes/widgets/nav/right.pug
第9行
i.solitude.iconfont.icon-dice-line
取消《开往》按钮移动端自动消失
路径:
solitude/source/css/_layout/header.styl
\\ 删除272-274行
#travellings_button
+maxWidth768()
opacity 0
- 感谢您的赞赏