为了更好的使用体验,现将本站对solitude主题的全部魔改文件公示于此,希望能给一些想魔改但无从下手的小白提供一些帮助

2025.02.12 更新说明:本文修改内容适用于 solitude 3.0.11 版本

评论链接安全跳转

路径: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

文章统计

原教程与更多魔改内容:

  1. 创建页面

    hexo new page charts
  2. 配置文件引入部分引入:

    inject:
      head:
        - <script src="https://npm.elemecdn.com/echarts@4.9.0/dist/echarts.min.js"></script>
  3. 新建文件路径:solitude/scripts/helper/charts.js

    const 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(/&amp;#/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>`
    }
  4. 在页面中显示

    <!-- 文章发布时间统计图 -->
    <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/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