主题魔改——Solitude主题
发表于:2025-03-25 | 分类: 折腾
字数统计: 16.1k | 阅读时长: 83分钟 | 阅读量:

前言

这几天有时间就把主题升级一下,顺便总结一下修改和添加内容。我很蠢,但是蠢人有蠢人的办法,一样能实现功能就好了。

更新记录

2025-03-30

添加 SWPP 开启 PWA

2025-03-29

适配 Waline 文章热度

2025-03-28

添加即刻短文图片的描述

2025-03-27

添加评论协议至评论区

2025-03-26

修改Tab标签生成逻辑

2025-03-25

首稿,solitude版本 v3.0.15

修改了什么

修改内容

1. 加载动画

切换页面时展现。

  1. 修改 themes/solitude/source/css/_layout/fullpage.styl 文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // ...
    .loading-bg
    display flex
    width 100%
    height 100%
    position fixed
    background var(--loading-bg) // 修改这一行
    // ...
    .loading-img
    width 100px
    height 100px
    margin auto
    animation-duration 0.5s // 修改这一行,复制完后请删除注释
    // ...
  2. 修改 themes/solitude/source/css/_mode/index.styl 文件:
    1
    2
    3
    4
    5
    6
    [data-theme=dark]
    --loading-bg #000000dd // 添加这一行
    // ...
    [data-theme=light]
    --loading-bg #ffffffdd // 添加这一行
    // ...
参考原帖
关于本站|Solitude主题魔改内容❖星港◎Star☆

2. Tab标签

点击按钮可以跳转页面。

  1. 修改 themes/solitude/scripts/tags/tabs.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
    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
    'use strict';

    function postTabs([name, active], content) {
    // 定义正则表达式,用于匹配选项卡块
    const tabBlock = /<!--\s*tab (.*?)(?:\s*\[(.*?)\])?\s*-->\n([\s\S]*?)<!--\s*endtab\s*-->/g;

    // 使用 matchAll 来获取所有匹配的选项卡
    const matches = [...content.matchAll(tabBlock)];

    // 将一个字符串转换为数字,设置默认选中的标签索引
    active = Number(active) || 0;

    // 生成选项卡项函数
    const generateTabItems = (matches, name, active, parentId) => {
    return matches.map((match, tabId) => {
    // 捕获选项卡标题和图标
    const [tabCaption = '', tabIcon = ''] = match[1].split('@');

    // 获取可选的链接,如果有的话
    const link = match[2] ? match[2].trim() : '';

    // 渲染内容,使用 Hexo 的 Markdown 渲染引擎
    const postContent = renderMarkdown(match[3]);

    // 生成唯一的 id,用于对应选项卡内容区域
    const tabHref = `${parentId}-${tabId}`;

    // 处理图标 HTML
    const iconHtml = tabIcon ? `<i class="${tabIcon.trim()} tab solitude"></i>` : '';

    // 判断是否为活动状态(选中状态)
    const isActive = active === tabId ? ' active' : '';

    // 根据是否存在链接来生成导航项
    return {
    nav: createNavItem(link, tabHref, iconHtml, tabCaption, isActive),
    // 选项卡的内容部分
    content: `<div class="tab-item-content${isActive}" id="${tabHref}">${postContent}</div>`
    };
    });
    };

    const renderMarkdown = (text) => {
    return hexo.render.renderSync({ text, engine: 'markdown' }).trim();
    };

    const createNavItem = (link, tabHref, iconHtml, tabCaption, isActive) => {
    const buttonContent = `${iconHtml}${tabCaption.trim() || `${name} ${tabHref}`}`;
    return link ? `
    <li class="tab${isActive}">
    <button onclick="location.href='${link}'" data-href="#${tabHref}" type="button">
    ${buttonContent}
    </button>
    </li>` : `
    <li class="tab${isActive}">
    <button type="button" data-href="#${tabHref}">
    ${buttonContent}
    </button>
    </li>`;
    };

    // 生成选项卡项
    const tabItems = generateTabItems(matches, name, active, name.toLowerCase().replace(/\s+/g, '-'));

    // 创建完整选项卡结构(包含导航和内容)
    const createTabStructure = (tabItems) => {
    const tabNav = `<ul class="nav-tabs">${tabItems.map(item => item.nav).join('')}</ul>`;
    const tabContent = `<div class="tab-contents">${tabItems.map(item => item.content).join('')}</div>`;
    return { tabNav, tabContent };
    };

    // 获取生成的选项卡导航和内容
    const { tabNav, tabContent } = createTabStructure(tabItems);

    // 返回最终的选项卡 HTML 结构
    return `<div class="tabs" id="${name.toLowerCase().replace(/\s+/g, '-')}">${tabNav}${tabContent}</div>`;
    }

    // 注册自定义标签,以便在 Hexo 中使用
    hexo.extend.tag.register('tabs', postTabs, { ends: true });
    hexo.extend.tag.register('subtabs', postTabs, { ends: true });
    hexo.extend.tag.register('subsubtabs', postTabs, { ends: true });

  2. source/custom/css/custom.css 最后添加(没有就新建):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /* tab标签的链接跳转 */
    .tab-to-top {
    position: relative;
    display: block;
    margin: 16px 0 0 auto;
    color: var(--efu-lighttext);
    }
    .tabs .nav-tabs .tab button {
    width: 100%;
    padding: 0 0.3rem;
    height: 100%;
    font-size: 14px;
    }
    .tabs .nav-tabs .tab.active {
    background: var(--light-grey);
    }
    .tabs .nav-tabs .tab.active i,
    .tabs .nav-tabs .tab.active button {
    color: var(--efu-lighttext);
    }
    .tabs .nav-tabs .tab i {
    font-size: 14px;
    margin-right: 0.3rem;
    }
  3. 用法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {% tabs 唯一名称, [index] %}

    <!-- tab [唯一Tab] [@icon] [链接] -->

    任何内容(也支持内联标签)。

    <!-- endtab -->

    {% endtabs %}

    {% tabs test, 1 %}

    <!-- tab test @fas fa-book [https://test.com] -->

    任何内容(也支持内联标签)。

    <!-- endtab -->

    {% endtabs %}
参考原帖
外挂标签使用伍十七

3. 微信公众号卡片

左边侧边栏,点击它可以跳转wechatOA。

  • 修改 themes/solitude/layout/includes/widgets/aside/asideFlipCard.pug 文件:
    1
    2
    3
    4
    5
    6
    7
    //- 添加这一行,位于第一行
    a(href="/rss/wechatOA/",data-pajx)
    .card-widget.card-platform
    #flip-wrapper
    #flip-content
    .face
    .back.face

4. 友链、留言页

改为点击按钮跳转页面。

  1. 修改 themes/solitude/languages/ 下所有文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # default.yml
    link:
    banner:
    toComment: 申请/修改友链 # 修改这一行
    # en.yml
    link:
    banner:
    toComment: Application/Modification link
    # zh-CN.yml
    link:
    banner:
    toComment: 申请/修改友链
    # zh-TW.yml
    link:
    banner:
    toComment: 申請/修改友鏈
  2. 修改 themes/solitude/layout/includes/widgets/page/links/banner.pug 文件:
    1
    2
    3
    4
    5
    6
    7
    //- ...
    if theme.comment.use
    //- 替换链接跳转
    a.banner-button(onclick="window.open('https://github.com/ymxblog/friends/',' _blank')")
    i.solitude.fas.fa-circle-chevron-right
    span.solitude.banner-button-text=_p('link.banner.toComment')
    //- ...
  3. 修改 themes/solitude/layout/includes/widgets/page/message/content.pug 文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .article-container
    != page.content
    if theme.envelope.enable
    .switch_message
    button.open(type='button' onclick="document.getElementById('barrage').classList.remove('hide')")
    span(aria-hidden='true')= __('message.open')
    button.close(type='button' onclick="document.getElementById('barrage').classList.add('hide')")
    span(aria-hidden='true')= __('message.close')
    //- 添加这两行,链接可改
    button(type='button' onclick="window.open('https://github.com/ymxblog/friends/',' _blank')")
    span(aria-hidden='true')= __('link.banner.toComment')
    #barrage

5. waline评论计数

无法获取waline的评论数据,返回undefined,翻了一下官方文档进行修改。

修改 themes/solitude/layout/includes/widgets/sidebar/waline.pug 如下:

1
2
3
4
5
6
7
8
9
10
.length-num#waline_allcount
i.solitude.fa-solid.fa-spinner.fa-spin

script(pjax).
(async () => {
await fetch('!{theme.waline.envId}/api/comment?type=count', {method: 'GET'}).then(async res => res.json())
.then(async data => {
document.querySelector('#waline_allcount').innerHTML = data.data
})
})()
原帖作者
API-Waline官网

6. 添加评论协议

评论区可看。

  1. 修改 themes/solitude/layout/includes/widgets/third-party/comments/comment.pug 如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //- ...

    if use.length > 1
    .comment-switch
    span.first=use[0]
    span#switch-btn
    span.second=use[1]

    a.plxycss(href="/comment/")
    i.solitude.fas.fa-file-lines
    span &nbsp;评论协议

    //- ...
  2. source/custom/css/custom.css 最后添加(没有就新建):
    1
    2
    3
    4
    5
    6
    /* 评论协议样式 */
    a.plxycss {
    float: right;
    font-weight: bold;
    font-size: 20px;
    }

7. 添加即刻短文图片的描述

即刻短言可看,点效果跳转

  1. 修改 themes/solitude/layout/includes/page/brevity.pug 如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //- ...

    if item.image
    .bber-content-img
    each img in item.image
    if typeof img === 'string'
    img(src=img, alt=item.content || "图片暂无描述")
    else
    img(src=img.url, alt=(img.alt || item.content || "图片暂无描述"))

    //- ...
  2. 用法两者皆可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    - content: 这次做的椰奶冻粉还不错,就是糖放多了,好吃(╯▽╰ )好香~~
    date: 2025-03-27 00:01:00
    location:
    image:
    - url: https://images.418121.xyz/file/blog/essay/2025/03/27/01.webp
    alt: 按着教程来的,事不过三,终于成功了
    - content: 我弟弟妹妹画的,很有天赋,像我小时候一样 b( ̄▽ ̄)d ~
    date: 2025-03-26 21:30:00
    location:
    image:
    - https://images.418121.xyz/file/blog/essay/2025/03/26/01.webp

8. 适配Waline文章热度

超过五条评论即可显示”多人互动“,点效果跳转。

  1. 修改 themes/solitude/layout/includes/widgets/third-party/hot/index.pug 如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - const { count } = theme.comment.hot_tip
    - const { use } = theme.comment

    if use
    case use[0]
    when 'Twikoo'
    include ./twikoo
    when 'Artalk'
    include ./artalk
    //- 添加下面两行
    when 'Waline'
    include ./waline
  2. themes/solitude/layout/includes/widgets/third-party/hot/ 下添加 waline.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
    45
    46
    47
    script.
    function updatePostsBasedOnComments() {
    const location = window.location.origin;
    const posts = Array.from(document.querySelectorAll('.recent-post-item[onclick] .post_cover a'))
    .map(item => item.href.replace(location, ''));
    const fetchCommentsCount = () => {
    const requests = posts.map(url => {
    return fetch(`#{theme.waline.envId}/api/comment?type=count&url=${encodeURIComponent(url)}`)
    .then(response => response.json())
    .then(data => ({
    url: url,
    count: data.errno === 0 ? data.data[0] : 0
    }));
    });
    Promise.all(requests)
    .then(handleCommentsResponse)
    .catch(error => console.error("Error fetching comments:", error));
    };
    const handleCommentsResponse = (response) => {
    response.forEach(({ url, count }) => {
    if (count > #{count}) {
    const postElement = document.querySelector(`.recent-post-item[onclick*="${url}"]`);
    if (postElement) updatePostElement(postElement);
    }
    });
    };
    const updatePostElement = (postElement) => {
    const infoTopTips = postElement.querySelector(".recent-post-info-top-tips");
    const originalSpan = infoTopTips?.querySelector(".original");
    const existingHotTip = infoTopTips?.querySelector(".hot-tip");
    if (!existingHotTip && originalSpan) {
    const hotTip = createHotTipElement();
    infoTopTips.insertBefore(hotTip, originalSpan);
    }
    };
    const createHotTipElement = () => {
    const hotTip = document.createElement("span");
    hotTip.classList.add("hot-tip");
    const icon = document.createElement("i");
    icon.classList.add("solitude", "fas", "fa-fire-flame-curved");
    hotTip.appendChild(icon);
    const commentCount = document.createTextNode("#{_p('hot-tip')}");
    hotTip.appendChild(commentCount);
    return hotTip;
    };
    fetchCommentsCount();
    }
  3. 确认功能是否开启
    1
    2
    3
    4
    5
    6
    7
    8
    # ...
    # Hot comment tips
    # 热评提示
    hot_tip:
    enable: true
    # Number of hot comments
    count: 5
    # ...

参考了本主题适配Twikoo的用法。

添加了什么

添加内容

1. Do you like me

右边侧边栏有显示你可以点来看看,手机不展示。

原帖作者
为你的网站添加 Do you like me 小组件I Am I

2. 恋爱墙

主页可以看,点效果即可跳转。

原帖作者
侧边栏yuang01

3. 弹幕留言板

类似于🍭Akilarの糖果屋信笺样式留言板,点效果即可跳转。

原帖作者
信笺样式留言板🍭Akilarの糖果屋

4. 添加建站时间置页脚

左边控件点评论即可到页脚。

  1. 新建 source/custom/js/jz.min.js 文件填入内容(可改):
    1
    2
    document.addEventListener("DOMContentLoaded",(function(){const startTime=new Date("2024-05-12T00:00:00Z");function padZero(num){return num<10?"0"+num:String(num)}function calculateElapsedTime(start){const elapsedMilliseconds=new Date-start,totalSeconds=Math.floor(elapsedMilliseconds/1e3);return{days:Math.floor(totalSeconds/86400),hours:Math.floor(totalSeconds%86400/3600),minutes:padZero(Math.floor(totalSeconds%3600/60)),seconds:padZero(totalSeconds%60)}}function updateDisplay(){const{days:days,hours:hours,minutes:minutes,seconds:seconds}=calculateElapsedTime(startTime),runtimeElement=document.getElementById("runtime");runtimeElement&&(runtimeElement.textContent=`花期:${days}${hours} 小时 ${minutes}${seconds} 秒`),requestAnimationFrame(updateDisplay)}updateDisplay()}));

  2. 修改主题配置文件(url可改):
    1
    2
    3
    4
    5
    # 页脚信息文字
    # 请不要删除主题信息,这是对作者的尊重
    links:
    - name: <span id="runtime"></span>
    url: /history/

5. 文章内插入地图

点击效果,即可跳转,如无显示请后退重新点一次。

  1. 安装插件
    1
    npm install hexo-tag-map --save
  2. 在想用的地方使用这外挂标签即可
    1
    2
    3
    4
    5
    插入一个混合地图的示例: {% map %}
    {% + 标签值 + 经度 + 纬度 + 文本 + 缩放等级 + 宽 + 高 + 默认图层 + %}
    {% map 120.101101,30.239119, 这里是西湖灵隐寺,据说求姻缘很灵验哦!, 15, 100%, 360px, 1 %}
    其它地图用法请参考原帖

原帖作者
使用Hexo-tag-map插件,给文章插入高德百度谷歌等5类9种地图八章
关于给你的 Hexo 文章插入交互式地图吧!kuole-o

6. 足迹图

点击效果,即可跳转。

  1. 下载zip并解压到 source/footmap/ 目录下
  2. 经过我的修改可以适配本主题,你想改可以自定义。
  3. 修改根目录 _config.yml 文件:
    1
    2
    3
    4
    5
    6
    7
    8
    # ...
    # Include / Exclude file(s)
    ## include:/exclude: options only apply to the 'source/' foldinger
    include:
    exclude:
    ignore:
    - source/footmap/* #添加这一句
    #...
  4. 打开 source/footmap/data/config.json 设置数据:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [
    {
    "latLng": [22.354887,110.946866], //为足迹的经纬度,可以通过 https://jingweidu.bmcx.com查询得到
    "name": "广东 · 茂名 · 信宜", //足迹地点的名称
    "articleUrl": "/posts/63e1fc9e.html", //文章地址
    "desc": "老家", //足迹地点的描述, \n 为换行符
    "photos":[
    "https://photo.tuchong.com/20342439/f/1276790136.jpg",
    "https://photo.tuchong.com/20342439/f/712590584.jpg",
    "https://photo.tuchong.com/20342439/f/888292716.jpg",
    "https://photo.tuchong.com/20342439/f/1184318812.jpg"
    ], //足迹地点的照片链接,为一组图片 url 数据
    "freq": 2 //足迹地点的到访次数,范围为 [1, 10]
    } // 写下一个的时候记得加逗号,最后一个不要加。
    ]
原帖作者
给自己做一个旅行地图吧(2.0升级版)扶苏

7. 订阅页面

点击效果,即可跳转。

  1. 新建 source/rss/index.md 文件,在文件内输入内容(按需修改):
    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
    ---
    title: 订阅本站
    date: 2024-11-16 23:10:28
    update: 2024-11-17 02:21:48
    desc: 推送全部文章推送简介
    container: true
    ---
    <center><h1>订阅本站</h1></center>
    <p style="text-align: right;">最新更新时间为:2024-11-27</p>
    <br>
    <div class="rss-plan-list"><a class="rss-plan-item rss-plan-wechat" href="./wechatOA/" title="公众号" target="_blank">
    <div class="rss-plan-description">推送精选文章<br>推送全文</div>
    <div class="rss-plan-info-group">
    <div class="rss-plan-title">公众号订阅</div>
    <div class="rss-plan-info">推荐的订阅方式</div><img class="rss-plan-icon no-lightbox entered loaded"
    src="https://images.418121.xyz/file/blog/page/wechat.webp" data-lazy-src="https://images.418121.xyz/file/blog/page/wechat.webp"
    data-ll-status="loaded">
    </div>
    </a><a class="rss-plan-item rss-plan-mail" title="邮箱订阅" href="https://github.com/yeminxi/Friend-Circle-Lite/issues/new?assignees=&labels=&projects=&template=%E9%82%AE%E7%AE%B1%E8%AE%A2%E9%98%85.md&title=%5B%E9%82%AE%E7%AE%B1%E8%AE%A2%E9%98%85%5D" target="_blank">
    <div class="rss-plan-description">推送全部文章<br>更新推送简介</div>
    <div class="rss-plan-info-group">
    <div class="rss-plan-title">邮件订阅</div>
    <div class="rss-plan-info">推荐的订阅方式</div><img class="rss-plan-icon no-lightbox entered loaded"
    src="https://images.418121.xyz/file/blog/page/mail.webp" data-lazy-src="https://images.418121.xyz/file/blog/page/mail.webp"
    data-ll-status="loaded">
    </div>
    </a><a class="rss-plan-item rss-plan-rss" href="/rss2.xml" title="rss" target="_blank">
    <div class="rss-plan-description">推送全部文章<br>推送简介</div>
    <div class="rss-plan-info-group">
    <div class="rss-plan-title">RSS</div>
    <div class="rss-plan-info">备用订阅方式</div><img class="rss-plan-icon no-lightbox entered loaded"
    src="https://images.418121.xyz/file/blog/page/rss.webp" data-lazy-src="https://images.418121.xyz/file/blog/page/rss.webp"
    data-ll-status="loaded">
    </div>
    </a></div>

    # 订阅本站
    首先感谢你对本站的文章产生一些兴趣,本站的主要内容风格为日常及教程分享、电脑骚操作,如果你对这方面内容感兴趣,欢迎关注。
    ## 全部文章订阅方式
    ### 1. 公众号订阅
    公众号订阅可以收到我觉得有价值,很重要,比较精彩的文章。这些文章将通过微信公众号发送。并不是所有博客文章都会发到公众号中。建议所有用户订阅。
    ### 2. RSS订阅(文章摘要)
    你可以使用第三方RSS客户端接收到博客的文章摘要通知。
    [https://blog.418121.xyz/atom.xml](https://blog.418121.xyz/atom.xml)
    [https://blog.418121.xyz/rss2.xml](https://blog.418121.xyz/rss2.xml)
    {% link ATOM,订阅链接,https://blog.418121.xyz/atom.xml %}
    {% link RSS,订阅链接,https://blog.418121.xyz/rss2.xml %}
    ### 3. Follow订阅
    你可以使用[Follow客户端](https://follow.is/)接收到博客的文章通知。
    {% link Follow,订阅链接,https://app.follow.is/share/users/yeminxi %}
    ### 4. 邮箱订阅
    * 如果你更喜欢通过邮箱接收更新,本站提供了邮箱订阅功能。此功能通过[Friend Circle Lite](https://github.com/willow-god/Friend-Circle-Lite)支持。只需简单的填写你的邮箱提交到GitHub Issue,即可开始接收本站的更新邮件,可以通过删除issue进行取消订阅!
    1. 访问 [GitHub Issue](https://github.com/yeminxi/Friend-Circle-Lite/issues/new?assignees=&labels=&projects=&template=%E9%82%AE%E7%AE%B1%E8%AE%A2%E9%98%85.md&title=%5B%E9%82%AE%E7%AE%B1%E8%AE%A2%E9%98%85%5D) 页面。
    2. 填写你的邮箱信息。
    3. 提交 Issue,即可完成订阅!
    * 期待与你的互动!如果你有任何问题或建议,欢迎随时联系我。
    ## 邮箱订阅功能提供鸣谢:
    {% link 清羽飞扬,柳影曳曳,清酒孤灯,扬笔撒墨,心境如霜,https://blog.liushen.fun/ %}
    ## 本站是如何维持的
    做公众号是为了保护文章版权,因为发布在个人博客站点对于版权保护能力比较弱,所以发在公众号并且标记为原创可以对文章版权有很好的保护。

  2. 新建 source/rss/wechatOA/index.md 文件,在文件内输入内容(按需修改):
    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
    112
    113
    114
    115
    116
    ---
    layout: false
    ---
    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>关注「叶泯希」微信公众号</title>
    <link rel="icon" type="image/x-icon" href="/favicon.ico">
    <meta name="apple-mobile-web-app-title" content="叶泯希">
    <link rel="bookmark" href="/favicon.ico">
    <meta name="description" content="关注叶泯希信公众号,获取更多最新内容。">
    <meta name="theme-color" content="#f6f7f8">
    <style>
    body {
    background: #f6f7f8;
    margin: 0;
    padding: 0
    }
    .wechatOA {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    height: var(--vh)
    }
    .wechat-card {
    background: #fff;
    max-width: 428px;
    max-height: 569px;
    border-radius: 16px;
    padding: 24px 36px 20px 36px;
    display: flex;
    flex-direction: column;
    margin: auto;
    align-items: center
    }
    .qrcode {
    width: 256px;
    height: 256px;
    display: flex
    }
    .tips {
    font-size: 14px;
    color: #000;
    opacity: .5;
    margin-top: 8px
    }
    .wechatOA-card {
    width: 100%;
    display: flex;
    margin-top: 16px;
    background: #f7f7f7;
    padding: 18px;
    border-radius: 16px;
    align-items: center
    }
    .wechatOA-head {
    width: 48px;
    height: 48px;
    background-image: url(/images/avatar.webp);
    border-radius: 68px;
    background-size: contain;
    margin-right: 16px
    }
    .wechatOA-name {
    font-weight: 700;
    color: #000;
    }
    .wechatOA-description {
    font-size: 12px;
    color: #000;
    opacity: .5
    }
    a.help {
    font-size: 14px;
    color: #000;
    opacity: .5;
    text-decoration: none;
    margin: 20px 0
    }
    a.help:visited {
    color: #000
    }
    </style>
    </head>
    <body>
    <div class="wechatOA">
    <div class="wechat-card">
    <img src="gzh.webp" class="qrcode" alt="">
    <div class="tips">扫描二维码关注公众号</div>
    <div class="wechatOA-card">
    <div class="wechatOA-head"></div>
    <div class="wechatOA-info">
    <div class="wechatOA-name">叶泯希</div>
    <div class="wechatOA-description">意义是自己赐予的</div>
    </div>
    </div>
    </div>
    <a class="help" href="https://kf.qq.com/touch/wxappfaq/1208117b2mai141113jaqAnU.html?platform=14" title="帮助" target="_blank">不会扫码?</a>
    </div>
    <script>
    const vh = 1 * window.innerHeight;
    document.documentElement.style.setProperty("--vh", `${vh}px`),
    window.addEventListener("resize", (()=>{
    let e = 1 * window.innerHeight;
    document.documentElement.style.setProperty("--vh", `${e}px`)
    }
    ))
    </script>
    </body>
    </html>

  3. source/custom/css/custom.css 最后添加(没有就新建):
    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
    /* 卡片订阅样式 */
    #post .tag_share + #follow {
    margin-top: 40px;
    }
    .rss-plan-list {
    display: flex;
    width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
    margin: 0 -4px;
    position: relative;
    justify-content: space-between;
    }
    .rss-plan-list a {
    border-bottom: none;
    border-radius: 12px;
    }
    .rss-plan-item:visited {
    color: var(--efu-white);
    }
    .rss-plan-item.rss-plan-wechat {
    background: #27c125;
    }
    .rss-plan-item.rss-plan-mail {
    background: var(--efu-blue);
    }
    .rss-plan-item.rss-plan-rss {
    background: var(--efu-orange);
    }
    .rss-plan-item {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    min-width: 240px;
    height: 240px;
    margin: 6px 0;
    overflow: hidden;
    text-decoration: none;
    width: calc(100% / 3 - 8px);
    filter: brightness(1);
    transition: .3s;
    border: var(--style-border);
    box-shadow: var(--efu-shadow-border);
    }
    .rss-plan-item:hover {
    filter: brightness(1.1)
    }
    @media screen and (max-width: 1024px) {
    .rss-plan-item {
    width:calc(100% / 2 - 4px)
    }
    .rss-plan-item:first-child {
    width: 100%
    }
    }
    @media screen and (max-width: 768px) {
    .rss-plan-item {
    width:100%
    }
    }

    /* 卡片大小 */
    .rss-plan-description {
    font-size: 16px;
    color: var(--efu-white);
    margin: 26px 0 0 30px;
    line-height: 20px;
    }
    .rss-plan-info-group {
    position: relative;
    margin: 0 0 26px 30px;
    color: var(--efu-white);
    }
    .rss-plan-title {
    font-size: 36px;
    font-weight: 700;
    width: fit-content;
    line-height: 1;
    }
    .rss-plan-info {
    width: fit-content;
    opacity: .6;
    }
    img.rss-plan-icon {
    position: absolute;
    bottom: -50px;
    right: -20px;
    height: 140px;
    -webkit-user-drag: none;
    user-select: none;
    -webkit-transition: all 1.2s cubic-bezier(.39,.575,.565,1) !important;
    -moz-transition: all 1.2s cubic-bezier(.39,.575,.565,1) !important;
    -o-transition: all 1.2s cubic-bezier(.39,.575,.565,1) !important;
    -ms-transition: all 1.2s cubic-bezier(.39,.575,.565,1) !important;
    transition: all 1.2s cubic-bezier(.39,.575,.565,1) !important;
    transform-origin: bottom right;
    filter: blur(8px);
    opacity: .6;
    }
    .rss-plan-item:hover img.rss-plan-icon {
    bottom: -40px;
    right: -10px;
    filter: blur(0);
    opacity: 1
    }
    .rss-plan-item:hover {
    text-decoration: none !important;
    }
参考原帖
如果你喜欢本站内容,欢迎订阅!Liushen
订阅本站与运营模式张洪Heo

8. 重构404页

点击效果,即可跳转。

  1. 新建 source/404.md 文件,在文件内输入以下内容:
    1
    2
    3
    4
    ---
    layout: false
    ---
    <script src="//cdn.dnpw.org/404/v1.min.js" maincolor="#000" jumptime="-1" jumptarget="/" tips="404" error="" charset="utf-8"></script>
  2. 修改主题配置文件
    1
    2
    3
    4
    5
    6
    # Page default settings
    # 页面默认设置
    page:
    # 404 page
    # 404 页面
    error: false
原帖作者
小代码,大公益域名公益

9. 自定义link标签

10. 安全跳转页面

  1. 安装
    1
    2
    npm install cheerio --save
    npm install hexo-safego --save
  2. Hexo 根目录 _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
    # hexo-safego安全跳转插件
    # see https://blog.liushen.fun/posts/1dfd1f41/
    hexo_safego:
    general: # 基本功能设置
    enable: true # 启用插件
    enable_base64_encode: true # 使用 Base64 编码
    enable_target_blank: true # 从新窗口打开跳转页面
    security: # 安全设置
    url_param_name: 'u' # URL 参数名
    html_file_name: 'go.html' # 重定向页面的文件名
    ignore_attrs: # 忽略处理的 HTML 结构
    - 'data-fancybox'
    scope: # 容器与页面设置
    apply_containers: # 应用的容器选择器
    - '#post'
    apply_pages: # 应用的页面路径
    - "/posts/"
    exclude_pages: # 排除的页面路径
    whitelist: # 域名白名单
    domain_whitelist: # 允许的白名单域名,通过字符串匹配实现
    - "yeminixi.github.io"
    - "418121.xyz"
    appearance: # 页面外观设置
    avatar: ./images/avatar.webp # 跳转页面头像路径
    title: "叶泯希" # 跳转页面标题
    subtitle: "安全中心提示你不要乱点,它与本站不存在法律裙带关系ヽ(≧□≦)ノ" # 跳转页面副标题
    darkmode: false # 是否启用深色模式
    countdowntime: -1 # 跳转页面倒计时秒数,如果设置为负数则为不自动跳转
    debug: # 调试设置
    enable: false # 启用调试模式
原帖作者
安全跳转页面·插件版LiuShen

11. Waline评论链接安全跳转

点击效果,即可跳转,下面有链接点点看。

  1. 在你 GithubForkWaline 仓库下修改 index.cjs 文件内容如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const Application = require('@waline/vercel');
    const LinkInterceptor = require('waline-link-interceptor'); // 添加这一行

    module.exports = Application({
    forbiddenWords: ['唱跳', 'rap篮球'], //词汇限制
    disallowIPList: ['8.8.8.8', '4.4.4.4'],//黑名单IP
    plugins: [
    LinkInterceptor({
    whiteList: [
    'yeminxi.github.io',
    '418121.xyz'
    ],
    // blackList: [],
    // interceptorTemplate: `hello __URL__ `, // 如果下面自定义了跳转地址,那么此处模板不生效
    redirectUrl: "https://blog.418121.xyz/go.html", // 填写中间页的具体 html 地址。
    encodeFunc: (url) =>{
    return "u="+Buffer.from(url).toString('base64'); // 填入一个外链 url 的处理函数
    }
    })
    ],
    async postSave(comment) {
    // do what ever you want after comment saved
    },
    });
  2. 修改 package.json 文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "name": "template",
    "version": "1.32.3",
    "private": true,
    "dependencies": {
    "@waline/vercel": "latest",
    "waline-link-interceptor": "^0.1.2" //添加这一句
    }
    }
原帖作者
Hexo 博客与 Waline 评论区实现外链跳转中间页wuanqin

12. 添加统计页面

点击效果,即可跳转。

  1. 新建 **themes/solitude/sscripts/helper/charts.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
    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
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    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>`
    }
  2. 修改主题配置文件
    1
    2
    3
    4
    5
    6
    7
    # Extend
    # 扩展
    extends:
    # Insert in head
    # 插入到 head
    head:
    - <script src="https://npm.elemecdn.com/echarts@4.9.0/dist/echarts.min.js"></script>
  3. 用法,在任意位置插入(记得开启 container: true ):
    1
    2
    3
    4
    5
    6
    <!-- 文章发布时间统计图 -->
    <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>
原帖作者
关于本站|Solitude主题魔改内容❖星港◎Star☆

13. 页面预加载

打开浏览器开发者模式切换到网络,然后找个本站页面把鼠标放上去,会进行预加载。

  • 修改主题配置文件
    1
    2
    3
    4
    5
    6
    7
    # Extend
    # 扩展
    extends:
    # Insert in head
    # 插入到 head
    head:
    - <script src="//instant.page/5.2.0" type="module"></script>
原帖作者
使您网站的页面即时在 1 分钟内并显着提高您的转化率Alexandre Dieulot

14. Vercel 加速

  • source/ 目录下新建一个 vercel.json 文件,在文件内输入以下内容:
    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
    {
    "headers": [
    {
    "source": "/sw.js",
    "headers": [
    {
    "key": "Cache-Control",
    "value": "public, max-age=0, must-revalidate"
    }
    ]
    },
    {
    "source": "(.*)",
    "headers": [
    {
    "key": "Cache-Control",
    "value": "public, s-maxage=86400, max-age=86400"
    }, {
    "key": "Vercel-CDN-Cache-Control",
    "value": "max-age=31536000"
    }
    ]
    }
    ]
    }
原帖作者
Vercel 加速,快,不止更快Mystic Stars

15. 友链状态检测

16. 朋友圈

点击效果,即可跳转,往下拖到最后。

  1. 这里只讲前端配置,完整教程请看参考。
  2. 动态获取json,适配 solitude主题 的方法,根目录下新建 links.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
    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
    const fs = require('fs');
    const https = require('https');

    const dataUrl = 'https://cdn.418121.xyz/links/active.json'; // 替换成你的数据来源的URL
    const outputFilePath = './source/flink_count.json'; // 替换成你想要保存json文件的路径

    function generateLinkCountJson(dataUrl, outputFilePath) {
    https.get(dataUrl, (response) => {
    let data = '';

    // 检查响应状态
    if (response.statusCode !== 200) {
    console.error(`请求失败,状态码: ${response.statusCode}`);
    return;
    }

    // 监听数据块
    response.on('data', (chunk) => {
    data += chunk;
    });

    // 完成请求
    response.on('end', () => {
    try {
    const jsonData = JSON.parse(data);

    if (!Array.isArray(jsonData.content)) {
    console.error("错误: JSON 文件中没有有效的 content 数组");
    return;
    }

    const ls = [];
    console.log(`链接列表条目数: ${jsonData.content.length}`);

    for (const item of jsonData.content) {
    console.log(`正在处理链接: ${item.title}`);
    ls.push({
    name: item.title,
    link: item.link,
    avatar: item.avatar || '',
    descr: item.descr || '无描述',
    topimg: item.topimg || null
    });
    }

    // 计数
    const length = ls.length;
    console.log(`总链接数: ${length}`);

    // 将结果写入到 JSON 文件中
    fs.writeFileSync(outputFilePath, JSON.stringify({ link_list: ls, length: length }, null, 2));
    console.log(`创建文件成功 flink_count.json at ${outputFilePath}`);

    } catch (error) {
    console.error('解析数据失败:', error);
    }
    });
    }).on('error', (error) => {
    console.error('请求错误:', error);
    });
    }

    // 调用函数生成 JSON 文件
    generateLinkCountJson(dataUrl, outputFilePath);
  3. 使用命令生成 flink_count.json
    1
    node links.js
  4. 我使用 Github Action 构建站点,在前面加上:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - name: 安装依赖
    if: steps.cache.outputs.cache-hit != 'true'
    run: |
    npm install gulp-cli -g #全局安装gulp
    npm install --save
    npm install yamljs --save //加入这一句

    - name: 更新友链朋友圈
    run: |
    node link.js
    node links.js //加入这一句
    node autolinks.js
  5. 在友链的页面 /pyq/index.md 添加以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ---
    title: 朋友圈
    date: 2024-11-17 06:08:33
    type: banner
    container: true
    ---
    <div id="friend-circle-lite-root"></div>
    <script>
    if (typeof UserConfig === 'undefined') {
    var UserConfig = {
    // 填写你的fc Lite地址
    private_api_url: 'https://fc.api.418121.xyz/',
    // 点击加载更多时,一次最多加载几篇文章,默认20
    page_turning_number: 20,
    // 头像加载失败时,默认头像地址
    error_img: 'https://api.vvhan.com/api/avatar/niche',
    }
    }
    </script>
    <link rel="stylesheet" href="https://fc.api.418121.xyz/main/fclite.css">
    <script src="https://fc.api.418121.xyz/main/fclite.js">//https://cdn.jsdelivr.net/gh/yeminxi/Friend-Circle-Lite/main/fclite.js
    </script>
原帖作者
Friend-Circle-Lite:轻量友链朋友圈LiuShen

17. 动态友链结合友链状态检测及朋友圈

  1. 动态获取json生成 links.yml 友链文件,适配 solitude主题 的方法,根目录下新建 autolinks.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
    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
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    const fs = require('fs');
    const https = require('https');
    const yaml = require('js-yaml');

    // 定义输入 URL 和输出文件名
    const inputUrls = [
    'https://cdn.418121.xyz/links/花架.json',
    'https://cdn.418121.xyz/links/花友.json',
    'https://cdn.418121.xyz/links/花族.json',
    'https://cdn.418121.xyz/links/花主.json',
    'https://cdn.418121.xyz/links/suspend.json',
    'https://cdn.418121.xyz/links/404.json',
    ];
    const outputFileName = './source/_data/links.yml';

    // 自定义名称和描述字典,可以根据 URL 或文件名进行变更
    const customNames = {
    '花架': '花架',
    '花友': '花友',
    '花族': '花族',
    '花主': '花主',
    'suspend': '花单',
    '404': '花失',
    };

    const descriptions = {
    '花架': '鸣谢提供框架及主题、技术支持',
    '花友': '造物主自玫瑰诞生起便策划了这场名为救赎的邂逅',
    '花族': '本站已加入的族织',
    '花主': '许是太久没有惊艳了,花也会忘了它是什么样子',
    'suspend': '单向友链,请添加本站链接后在GitHub提交issue留言',
    '404': '失效友链,请在GitHub提交issue修改后留言',
    };

    // 自定义类型字典
    const customTypes = {
    '花架': 'item',
    '花友': 'item',
    '花族': 'item',
    '花主': 'item',
    'suspend': 'discn',
    '404': 'discn',
    };

    // 用于存储所有分类的数组
    const allCategories = [];

    // 读取每个 JSON URL 并处理数据
    const readJsonUrls = async () => {
    for (let index = 0; index < inputUrls.length; index++) {
    const url = inputUrls[index];
    try {
    const jsonObj = await fetchJson(url);

    // 提取 URL 最后一部分作为分类名称(去掉扩展名)
    const className = url.split('/').pop().replace('.json', '');

    // 获取对应的描述、自定义名称和类型,如果不存在则使用默认
    const descr = descriptions[className] || `来自 ${className} 的链接`;
    const customName = customNames[className] || `默认名称 ${className}`;
    const customType = customTypes[className] || 'default'; // 默认类型

    // 将当前文件内容转换为 YAML 格式的数据结构
    let links = jsonObj.content.map(item => ({
    name: item.title,
    link: item.link,
    avatar: item.avatar,
    descr: item.descr,
    topimg: item.topimg || '',
    tag: item.tag || '',
    color: item.color || ''
    }));

    // 如果是第二个 URL,我们进行乱序处理
    if (index === 1) { // 注意这里的 index 从 0 开始,所以2号的位置是1
    links = shuffleArray(links); // 乱序
    }

    links.reverse(); // 逆序处理

    // 创建分类对象并推入分类数组
    allCategories.push({
    class_name: customName,
    descr: descr,
    type: customType,
    suffix: '',
    link_list: links
    });

    } catch (err) {
    console.error(`读取 URL ${url} 时出错:`, err.message);
    }
    }
    };

    // 随机打乱数组的函数
    const shuffleArray = (array) => {
    for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]]; // 交换元素
    }
    return array;
    };
    // 使用 https 模块获取 JSON 数据
    const fetchJson = (url) => {
    return new Promise((resolve, reject) => {
    https.get(url, (res) => {
    let data = '';

    // 收集数据
    res.on('data', chunk => {
    data += chunk;
    });

    // 请求结束后解析 JSON
    res.on('end', () => {
    try {
    resolve(JSON.parse(data));
    } catch (e) {
    reject(new Error('无法解析 JSON'));
    }
    });
    }).on('error', (err) => {
    reject(err);
    });
    });
    };

    // 生成 YAML 数据并写入文件
    const generateYaml = () => {
    const yamlData = {
    banner_suffix: '',
    links: allCategories
    };

    // 将生成的数据转换为 YAML 格式,强制使用双引号来防止折叠格式
    const generatedYaml = yaml.dump(yamlData, {
    noRefs: true,
    lineWidth: -1 // 设置行宽为 -1,避免自动折行
    });

    // 写入到 YAML 文件
    fs.writeFileSync(outputFileName, generatedYaml, 'utf8');
    console.log('YAML 文件已成功生成。');
    };

    // 执行读取和写入流程
    const processUrls = async () => {
    await readJsonUrls();
    generateYaml();
    };

    processUrls();

  2. 请参考原教程到5. 配置 config.yml,后接着下来我的步骤,或者直接Fork我的仓库进行修改。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 网络请求设置
    request:
    timeout: 10 # 超时设置
    ssl: false # ssl设置

    # 要抓取的 issues 配置
    issues:
    repo: ymxblog/friends # 仓库持有者/仓库名
    label:
    groups: ['active','checklist','suspend','404','花架','花友','花族','花主'] #标签有多少个就都写进去
    sort:
  3. 修改 /generator/main.py 文件:
    1
    outputdir = os.path.join(version, 'links')  # 第十行。输出文件结构变化时,更新输出路径版本
  4. 修改 .github/workflows/generator.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
    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
    name: Generator

    on:
    issues:
    watch:
    types: [started]

    jobs:
    build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v4
    - name: Set up Python
    uses: actions/setup-python@v5
    with:
    python-version: 3.x
    - name: Install requirements #安装requests
    run: |
    pip install -r requirements.txt
    - name: Update links #更新
    run: |
    python generator/main.py
    - name: Commit & Push
    uses: action-x/commit@v2.9
    with:
    github-token: ${{ secrets.PAT }}
    push-branch: 'output'
    force-push: 'true'
    commit-message: 'Generate Json'
    name: ${{ secrets.GITHUBUSERNAME }}
    email: ${{ secrets.GITHUBEMAIL }}
    - name: Checkout CDN repository
    run: |
    git clone https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.PAT }}@github.com/${{ secrets.GITHUBUSERNAME }}/CDN.git
    cd CDN

    # 确保没有未加入分支的内容
    git fetch origin

    # 如果需要删除完整的 links 目录,请使用 -r 参数
    git rm -r links/ # 删除整个 links 目录(如有必要)
    # 配置用户信息
    git config --global user.name "${{ secrets.GITHUBUSERNAME }}"
    git config --global user.email "${{ secrets.GITHUBEMAIL }}"
    git commit -m "Delete links directory"
    git push origin main
    - name: 延迟30s执行
    run: |
    echo "Starting delay..."
    sleep 30 # 延迟 30 秒
    echo "Delay completed."
    - name: Push changes to CDN repository
    run: |
    cd ./v2

    # 设置默认主分支为 'main'
    git config --global init.defaultBranch main

    if [ ! -d ".git" ]; then
    git init
    echo "Initialized new Git repository."
    fi

    # 配置用户信息
    git config --global user.name "${{ secrets.GITHUBUSERNAME }}"
    git config --global user.email "${{ secrets.GITHUBEMAIL }}"

    git remote add origin https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.PAT }}@github.com/${{ secrets.GITHUBUSERNAME }}/CDN.git

    # 更新本地分支以确保所有更改都已拉取
    git fetch origin

    # 合并最新的主分支
    git merge origin/main --allow-unrelated-histories || { echo "Merge conflict detected"; exit 1; }

    # 显示当前状态
    git status

    # 列出特定目录的内容
    ls -la ./links/

    # 添加文件并提交
    git add .

    if [ -n "$(git status --porcelain)" ]; then
    git commit -m "友链更新" || echo "No changes to commit"
    else
    echo "No changes to commit."
    fi

    # 推送到远程
    git push -u origin main || { echo "Push failed, please resolve the issues."; exit 1; }
    - name: 发送请求至本仓库的另外一工作流
    uses: peter-evans/repository-dispatch@v3
    with:
    token: ${{ secrets.GITHUB_TOKEN }} # 使用默认的 GITHUB_TOKEN
    repository: ${{ github.repository }} # 指向当前仓库
    event-type: linkschange
    client-payload: '{"branch": "output"}'
  5. 创建 .github/workflows/autolinks.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
    name: 更新网站友链
    on:
    repository_dispatch:
    types: [linkschange] # 本工作流由 change 事件触发

    jobs:
    build:
    runs-on: ubuntu-latest
    steps:
    # 步骤1:检出代码(根据需求可选)
    - name: Checkout Code
    uses: actions/checkout@v4
    with:
    ref: ${{ github.event.client_payload.branch || 'main' }}

    # 步骤2:执行本地任务
    - name: Run Tasks
    run: echo "成功触发,当前分支:${{ github.event.client_payload.branch }}"

    # 步骤3:触发账号B的私有仓库工作流(关键修改部分)
    - name: 触发更新
    uses: peter-evans/repository-dispatch@v3
    with:
    token: ${{ secrets.MY_SECRET_TOKEN }} # 必须使用账号B的PAT
    repository: yeminxi/hexo_code # 格式:owner/repo
    event-type: linkspush # 与目标仓库的事件类型严格匹配
    client-payload: '{"branch": "output"}' # 简化参数,移除冗余字段
  6. 我使用 Github Action 构建站点,在前面加上就可以实现友链检测,朋友圈,友链的三同步:
    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
    name: 自动部署
    # 当有改动推送到master分支时,启动Action
    on:
    repository_dispatch:
    types: [linkspush] # 本工作流由 change 事件触发
    push:
    branches:
    - solitude
    #2020年10月后github新建仓库默认分支改为main,注意更改
    release:
    types:
    - published

    jobs:

    deploy:
    permissions:
    actions: write
    checks: write
    contents: write
    runs-on: ubuntu-latest
    steps:
    - name: 延迟三十秒执行
    run: |
    echo "Starting delay..."
    sleep 30 # 延迟 30 秒
    echo "Delay completed."
    - name: 检查分支
    uses: actions/checkout@v4
    with:
    ref: solitude

    - name: 安装 Node
    uses: actions/setup-node@v4
    with:
    node-version: "20.x" #action使用的node版本,建议大版本和本地保持一致。可以在本地用node -v查询版本号。

    - name: 安装 Hexo
    run: |
    export TZ='Asia/Shanghai'
    npm install hexo-cli -g

    - name: 缓存 Hexo
    uses: actions/cache@v4
    id: cache
    with:
    path: |
    node_modules
    .deploy_git
    public
    key: ${{runner.OS}}-${{hashFiles('**/package-lock.json')}}

    - name: 安装依赖
    if: steps.cache.outputs.cache-hit != 'true'
    run: |
    npm install gulp-cli -g #全局安装gulp
    npm install --save
    npm install yamljs --save

    - name: 清除缓存
    run: |
    hexo clean

    - name: 更新友链朋友圈
    run: |
    node link.js
    node links.js
    node autolinks.js

    - name: 生成静态文件
    run: |
    hexo generate

    - name: 提交索引至Algolia
    run: |
    hexo algolia

    - name: 压缩文件
    run: |
    gulp

    # - name: 部署
    # uses: JamesIves/github-pages-deploy-action@v4
    # with:
    # token: ${{ secrets.GITHUBTOKEN }}
    # repository-name: ${{ secrets.GITHUBUSERNAME }}/${{ secrets.GITHUBUSERNAME }}.github.io
    # branch: v3.0.0
    # foldinger: public
    # commit-message: "${{ github.event.head_commit.message }} Updated By Github Actions"
    - name: 部署到Cloudfare Vercel Pages #此处master:master 指从本地的master分支提交到远程仓库的master分支,若远程仓库没有对应分支则新建一个。如有其他需要,可以根据自己的需求更改。
    run: |
    cd ./public
    git init
    git config --global user.name '${{ secrets.GITHUBUSERNAME }}'
    git config --global user.email '${{ secrets.GITHUBEMAIL }}'
    git branch -M solitude
    git add .
    git commit -m "${{ github.event.head_commit.message }} $(date -u +"%Y-%m-%dT%H:%M:%SZ") Updated By Github Actions"
    git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/hexo_code_Solitude.git" solitude:v3.0.0
    git push --force --quiet "https://${{ secrets.GITHUBUSERNAME }}:${{ secrets.GITHUBTOKEN }}@github.com/${{ secrets.GITHUBUSERNAME }}/hexo_code_Solitude.git" solitude:main

    - name: 推送谷歌url
    run: |
    hexo deploy

  7. 需要在仓库添加的环境变量,至于怎么获取token这里不多介绍,我这里是用了两个账号所以有两个token,一个只用设置PAT就好。
    namevalue
    GITHUBUSERNAME你Github的用户名
    GITHUBEMAIL你Github的邮箱
    PAT包含repo和work的权限token
    MY_SECRET_TOKEN另一账号仓库包含repo和work的权限token
  8. 说一下是什么原理

    获取issues的内容 -> 按label生成对应的json -> 推送至output分支和CDN仓库 -> 发送请求至本仓库的另外一工作流 -> 收到请求后,发送请求至另外账号仓库的构建站点的工作流 -> 实现动态友链更新

原教程只能生成json文件到本仓库的 *output 分支的v2文件夹里,这里我加了一个推送到我的CDN仓库的操作。然后通过Github Action获取CDN的json内容生成友链文件,友链检测文件,朋友圈文件,实现三端同步实时更新。
同理可得动态即可短文。

原帖作者
Stellar动态友链教程星日语

18. 添加回复时引用该评论

点击效果,即可跳转,随便找条评论试试,只适用Waline。

  1. 新建 source/custom/js/replycontent.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
    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
    112
    113
    114
    (function() {
    let walineContainer = null;
    let observer = null;

    const initWalineReply = () => {
    // 移除旧的事件监听器
    if (walineContainer) {
    walineContainer.removeEventListener('click', handleClick);
    }

    // 获取新的评论容器
    walineContainer = document.getElementById('waline-wrap');

    if (!walineContainer) {
    return; // 如果没有找到容器则退出
    }

    // 添加点击事件监听器
    walineContainer.addEventListener('click', handleClick, { passive: true });

    // 如果旧观察者存在,则断开连接
    if (observer) {
    observer.disconnect();
    }

    // 创建新的 MutationObserver 实例并开始观察
    observer = new MutationObserver(() => {
    // 在 DOM 变化时重新初始化
    initWalineReply();
    });

    observer.observe(walineContainer, {
    childList: true,
    subtree: true
    });
    };

    const handleClick = (e) => {
    const replyBtn = e.target.closest('button.wl-reply');
    if (!replyBtn || walineContainer.dataset.pjaxLoading === 'true') return;

    // 精准定位当前评论内容
    const commentItem = replyBtn.closest('.wl-card-item');
    const contentNode = commentItem.querySelector('.wl-card > .wl-content');

    if (!contentNode) {
    return;
    }

    // 沙盒处理(保留链接结构)
    const sandbox = contentNode.cloneNode(true);

    // 移除干扰元素
    sandbox.querySelectorAll(
    'span, .wl-meta, svg, img, .wl-quote, blockquote, a[href^="#"]'
    ).forEach(n => n.remove());

    // 转换普通链接为Markdown格式(保留非锚点链接)
    sandbox.querySelectorAll('a:not([href^="#"])').forEach(a => {
    const text = a.textContent.trim();
    const href = a.getAttribute('href') || '';
    a.replaceWith(`[${text}](${href})`);
    });

    // 提取纯净文本(增加过滤@符号)
    const pureText = sandbox.textContent
    .replace(/@\S+/g, '') // 新增@提及过滤
    .replace(/\n{3,}/g, '\n\n')
    .trim()
    .substring(0, 500);

    // 获取编辑器实例
    const editor = walineContainer.querySelector('.wl-editor');
    if (!editor) {
    return;
    }

    // 执行插入
    insertEditorContent(editor, pureText);
    };

    // 通用插入方法(支持Markdown)
    const insertEditorContent = (editor, text) => {
    const insertText = `> ${text}\n\n`;
    const inputEvent = new Event('input', { bubbles: true });

    if (editor.tagName === 'TEXTAREA' || editor.tagName === 'INPUT') {
    // 原生文本域处理
    const start = editor.selectionStart;
    editor.value =
    editor.value.slice(0, start) +
    insertText +
    editor.value.slice(editor.selectionEnd);
    editor.selectionEnd = start + insertText.length;
    } else if (editor.isContentEditable) {
    // 富文本编辑器处理
    const range = document.createRange();
    const selection = window.getSelection();
    range.selectNodeContents(editor);
    range.collapse(false); // 光标移到最后
    range.insertNode(document.createTextNode(insertText));
    selection.removeAllRanges();
    selection.addRange(range);
    }

    editor.dispatchEvent(inputEvent);
    editor.focus();
    };

    // 初始化及事件监听
    document.addEventListener('pjax:complete', initWalineReply);
    initWalineReply(); // 初次调用以进行初始化
    })();

  2. 修改主题配置文件:
    1
    2
    3
    4
    5
    6
    7
    # Extend
    # 扩展
    extends:
    # Insert in head
    # 插入到 head
    head:
    - <script data-pjax src="/custom/js/replycontent.js"></script>

19. 弹出式欢迎界面

首次进入本站即显示,点击效果即刻跳转。

  1. 新建 source/custom/js/welcome.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
    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
    document.addEventListener("DOMContentLoaded", (function() {
    if (!getCookie("agreementAccepted")) {
    const referrer = document.referrer ? new URL(document.referrer).hostname : "直接访问";

    swal({
    title: "欢迎来到 叶泯希 <( ̄︶ ̄)↗[GO!]",
    content: {
    element: "div",
    attributes: {
    innerHTML: `
    <img src='/images/avatar.webp' alt='自定义图标'
    style='width:80px; height:auto; border-radius: 50%;
    display: block; margin: 0 auto;' /><br />
    您来自: ${referrer}<br />
    请您在继续浏览本站之前,仔细阅读以下协议:<br /><br />
    1. <a href='/privacy/' title='隐私协议' data-pjax-state=''>隐私协议</a>&nbsp;&nbsp;
    2. <a href='/disclaimer/' title='免责声明' data-pjax-state=''>免责声明</a>&nbsp;&nbsp;<br />
    3. <a href='/copyright/' title='版权协议' data-pjax-state=''>版权协议</a>&nbsp;&nbsp;
    4. <a href='/comment/' title='评论协议' data-pjax-state=''>评论协议</a>&nbsp;&nbsp;
    5. <a href='/cookies/' title='Cookies' data-pjax-state=''>Cookies</a>&nbsp;&nbsp;<br /><br />
    点击“同意”表示您已阅读并同意遵守以上协议。
    `
    }
    },
    buttons: {
    cancel: "不同意",
    confirm: "同意"
    },
    text: "若未获取新内容请清浏览器缓存。"
    }).then(willProceed => {
    willProceed ? setCookie("agreementAccepted", "true", 30) :
    window.history.length > 1 ? window.history.back() :
    window.location.href = "https://xxx";// 替换为希望重定向的 URL
    });
    }

    function setCookie(name, value, days) {
    const date = new Date();
    date.setTime(date.getTime() + 24 * days * 60 * 60 * 1000); //添加30天的cookie
    const expires = "expires=" + date.toUTCString();
    document.cookie = name + "=" + value + ";" + expires + ";path=/";
    }

    function getCookie(name) {
    const nameEQ = name + "=";
    const ca = document.cookie.split(";");

    for (let i = 0; i < ca.length; i++) {
    let c = ca[i];
    while (" " === c.charAt(0)) c = c.substring(1, c.length);
    if (0 === c.indexOf(nameEQ)) return c.substring(nameEQ.length, c.length);
    }
    return null;
    }
    }));

  2. source/custom/css/custom.css 最后添加(没有就新建):
    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
    /* 弹出框样式 */
    .swal-modal,.swal-text,.swal-title {
    font-family: var(--efu-font) !important;
    background-color: var(--efu-secondbg);
    color: var(--efu-fontcolor) !important;
    }

    .swal-text {
    font-size: 14px;
    }

    .swal-content a:hover {
    background-color: var(--efu-theme-op-deep);
    }
    .swal-content a:not(.fancybox) {
    color: var(--efu-fontcolor);
    font-weight: 700;
    text-decoration: none;
    border-bottom: 2px dotted var(--efu-lighttext);
    padding: 0px 4px;
    border-radius: 4px 4px 0px 0px;
    }

    .swal-button--confirm {
    color: var(--efu-fontcolor);
    background-color: var(--efu-theme-op-deep);
    }
  3. 修改主题配置文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Extend
    # 扩展
    extends:
    # Insert in head
    # 插入到 head
    head:
    - <link rel="stylesheet" href="/custom/css/custom.css">
    - <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
    - <script src="/custom/js/welcome.js"></script>
原帖作者
SweetAlert官网
【Hexo+NexT主题】弹出欢迎弹窗pai233

20. 添加小米字体

所见即所得。

  • 修改主题配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # Font
    # 字体
    font:
    font-size: 20px
    code-font-size: 16px
    # Global font
    # 全局字体
    font-family: "MiSans, sans-serif"

    # Extend
    # 扩展
    extends:
    # Insert in head
    # 插入到 head
    head:
    - <link rel="stylesheet" href="https://font.sec.miui.com/font/css?family=MiSans:400,700:MiSans">

21. 配置swpp和PWA

进入页面即可显示。特别感谢Liushen的帮助!

  1. 安装
    1
    2
    npm install swpp-backends@3.0.0-alpha.906 --save
    npm install hexo-swpp@4.0.0-alpha.131 --save
  2. 在Hexo配置文件最后添加:
    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
    # swpp
    # npm install swpp-backends@3.0.0-alpha.902 --save
    # npm install hexo-swpp@4.0.0-alpha.131 --save
    # https://kmar.top/posts/b70ec88f/
    # https://github.com/EmptyDreams/hexo-swpp
    swpp:
    # 是否启用,默认 false
    enable: true
    # 配置文件路径,以 `/` 结尾表示加载指定文件夹下的所有文件,注意文件夹中只能有配置文件,不能有其它文件及文件夹
    config_path: 'swpp.config.ts'
    # 是否生成 sw
    serviceWorker: true
    # 是否向所有 HTML 插入注册 sw 的代码
    auto_register: true
    # 是否生成 DOM 端的 JS 文件并在 HTML 中插入 script
    gen_dom: true
    # 是否追踪链接,默认 false
    # ⚠️警告!!!未启用链接追踪时不要使用永久缓存,否则会导致被缓存的数据永远不会更新,在您不再使用该资源时,缓存也不会被删除!!!
    track_link: true
    # 生成的 diff 文件的路径(可以是绝对路径也可以是相对路径,使用相对路径时相对于网站发布目录),留空表示不生成(默认为 null)
    gen_diff: './diff.json'
    # 是否在执行 hexo deploy 时自动执行 swpp 指令
    # auto_exec: false
    # 检查更新的网址,默认 "https://registry.npmjs.org",注意不能以斜杠结尾
    npm_url: 'https://registry.npmmirror.com'
    #
    # 排序规则。
    # 该配置项是为了对 hexo 中的一些变量进行排序,避免每次生成 HTML 时由于这些变量的顺序变动导致生成结果不完全相同。
    # 示例:
    # ```yaml
    # # 下面给出的值为插件的缺省值,用户设置该项不会直接覆盖这些值,只有用户也声明 posts、pages 或 tags 时才会覆盖对应的值。
    # swpp:
    # sort_rules:
    # posts: 'title'
    # pages: 'title'
    # tags: 'name'
    # ```
    # 其中 key 值为要排序的变量的名称,value 为变量排序时的依据,
    # 填 false 表示禁用该项排序,填 true 表示以 value 本身为键进行排序,填字符串表示以 value[tag] 为键进行排序。
    # sort_rules:
  3. 在根目录下创建 swpp.config.ts 文件,把我的域名改成你自己的(不懂就看参考里的教程):
    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
    import {
    defineConfig
    } from 'swpp-backends'

    defineConfig({
    compilationEnv: {
    DOMAIN_HOST: new URL('https://blog.418121.xyz'),
    SERVICE_WORKER: "sw",
    JSON_HTML_LIMIT: 10,
    isStable: (url: URL) => {
    return [
    /^(https?:\/\/|\/\/)(cdn|fastly)\.jsdelivr\.net\/npm\/.*@\d+\.\d+\.\d+\//,
    /^(https?:\/\/|\/\/)jsdelivr\.topthink\.com\/.*@\d+\.\d+\.\d+\//,
    /^(https?:\/\/|\/\/)cdn\.jsdmirror\.com\/.*@\d+\.\d+\.\d+\//,
    /^(https?:\/\/|\/\/)cdn\.staticfile\.org\/.*\/\d+\.\d+\.\d+\//,
    /^(https?:\/\/|\/\/)lf\d+-cdn-tos\.bytecdntp\.com\/.*\/\d+\.\d+\.\d+\//,
    /^(https?:\/\/|\/\/)npm\.elemecdn\.com\/.*@\d+\.\d+\.\d+\//
    ].some(it => it.test(url.href))
    },
    VERSION_LENGTH_LIMIT: 512,
    NETWORK_FILE_FETCHER: {
    referer: "https://blog.418121.xyz",
    getStandbyList(url: string | URL): (string | URL)[] {
    if (typeof url === 'string') url = new URL(url)
    if (url.hostname === 'npm.elemecdn.com') {
    return [`https://fastly.jsdelivr.net${url.pathname}`]
    }
    return [url]
    }
    }
    },

    crossEnv: {
    CACHE_NAME: "BlogCache",
    VERSION_PATH: "https://id.v3/",
    ESCAPE: 15,
    },

    runtimeDep: {
    getStandbyRequests: (request: Request): {t: number, l: () => Request[]} | void => {
    const srcUrl = request.url
    const {host, pathname} = new URL(srcUrl)
    // noinspection SpellCheckingInspection
    const commonCdnList = ['jsdelivr.topthink.com', 'cdn.jsdmirror.com', 'fastly.jsdelivr.net']
    // noinspection SpellCheckingInspection
    const elme = 'npm.elemecdn.com'
    const urlMapper = (it: string) => new Request(it, request)
    if (host === elme) {
    return {
    t: 2000,
    l: () => [...commonCdnList.map(it => `https://${it}/npm${pathname}`)].map(urlMapper)
    }
    }
    if (host === 'jsdelivr.topthink.com') {
    commonCdnList.splice(0, 1)
    return {
    t: 2000,
    l: () => [...commonCdnList.map(it => `https://${it}${pathname}`)].map(urlMapper)
    }
    }
    }
    },

    crossDep: {
    matchCacheRule: {
    runOnBrowser: (url: URL) => {
    let { host, pathname } = url;

    // 处理省略index.html的情况
    if (pathname.endsWith('/')) pathname += 'index.html';

    // 仅仅对于blog.418121.xyz')) { 处理 html 和 json
    if (host.endsWith('blog.418121.xyz')) {
    if (pathname.endsWith('.json')) return 2592000000; // 30 dyas
    if (pathname.endsWith('.html')) return 2592000000; // 30 dyas
    if (pathname.endsWith('.webp') || pathname.endsWith('.jpg') || pathname.endsWith('.png') || pathname.endsWith('.gif')) return 2592000000; // 30 dyas
    }
    if (/\.(js|css|woff2|woff|ttf|cur|json)$/.test(url.pathname)) return 2592000000; // 30 dyas

    // return ((url.host.endsWith('blog.418121.xyz')) {') && /(\/|\.json)$/.test(url.pathname)) || /\.(js|css|woff2|woff|ttf|cur)$/.test(url.pathname)) ? 86400000 : false
    },
    runOnNode(url: URL) {
    // @ts-ignore
    return this.runOnBrowser(url)
    }
    }
    },
    })
  4. hexo swpp , 使用Github Action 可以在压缩之前加上:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # ...
    - name: 缓存swpp
    run: |
    hexo swpp

    - name: 压缩文件
    run: |
    gulp
    # ...
  5. 配置 PWA ,修改主题文件 _config_solitude.yml(按自己的来):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # ...
    # --------------------------- start ---------------------------
    # PWA
    # Progressive Web App
    pwa:
    enable: true
    manifest: /manifest.json # manifest.json
    theme_color: "#ff8080" # Theme colort
    mask_icon: /img/pwa/favicon.png # Mask icon
    apple_touch_icon: /img/pwa/favicon.png # Apple touch icon
    bookmark_icon: /img/pwa/favicon.png # Bookmark icon
    favicon_32_32: /img/pwa/favicon_32.png # 32x32 icon
    favicon_16_16: /img/pwa/favicon_16.png # 16x16 icon
    # --------------------------- end ---------------------------
    # ...
  6. 新建 source/manifest.json 文件(并准备好对应的图片):
    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
    {
    "lang": "zh",
    "name": "叶泯希",
    "short_name": "叶泯希",
    "description": "blog.418121.xyz",
    "theme_color": "#ff8080",
    "background_color": "#ff8080",
    "display": "standalone",
    "scope": "/",
    "start_url": "/",
    "icons": [
    {
    "src": "/img/pwa/favicon_36.png",
    "sizes": "36x36",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_48.png",
    "sizes": "48x48",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_72.png",
    "sizes": "72x72",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_96.png",
    "sizes": "96x96",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_144.png",
    "sizes": "144x144",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_192.png",
    "sizes": "192x192",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_256.png",
    "sizes": "256x256",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_384.png",
    "sizes": "384x384",
    "type": "image/png"
    },
    {
    "src": "/img/pwa/favicon_512.png",
    "sizes": "512x512",
    "type": "image/png"
    }
    ]
    }
  7. 网址右边出现安装应用,及成功。
原帖作者
Hexo配置SWPP实现PWALiushen
Swpp Backends 官方文档空梦
Github仓库EmptyDreams

结尾

至此,全篇结束。希望对你有所帮助,有任何问题请在下方留言。可以关注我的 公众号以及订阅我的文章 ,感谢你的支持,是对我最大的动力,当然了,更多的是因为热爱。

下一篇:
自定义表盘导航软件——对小米手环9的折腾