普通视图

发布一个Typecho AI文本润色插件

2025年12月6日 19:44

现在AI已经融入到各行各业中,编码的时候都借助AI来辅助编程。那么,在写博客文章的时候,遇到语句不够流畅或表达不够专业的时候,能不能快速的借助AI帮我们对语句做一些润色呢?

为此,我开发了一个Typecho插件,该插件运用AI技术,能够对用户选定的文本进行智能化的润色与优化。不过使用前需要调用一些AI服务,所以先准备好了再说😃

插件目前开启售卖,价格定为 10 元,收入款全部捐给韩红基金会,并在文章底部公布捐款情况,购买请移步到文末,购买后请把订单截图通过邮件发送到我邮件 hi@wangdaodao.com,我会在当天处理邮件,把插件捐款情况回复在邮件中。购买前如果有疑问,也可以发邮件与我沟通,避免引起误会。

[...]

  •  

回邮美学:“会说话的界面”

2025年11月30日 20:58

为什么开始留意“回复邮箱界面”?其实没什么宏大理由,就是2024年7月11日,我在整理收件箱的时候,突然意识到,我收到的每一封认真写的回复,都带着一种温度。

如我们评论一样,结尾附了一个小表情,莫名就轻松了起来,这些细节它其实是个人表达的一部分。尤其在互访互评、合作沟通越来越频繁的现在,一封看起来舒服的回复,真的会让对方更愿意继续对话,甚至记得你。

所以,我开始留意那些“会说话”的界面设计、模板、小功能。不是为了炫技,而是让每次已读,都不冷冰冰。

下面是我目前互访互评中的一些 “会说话的界面”,没有排名,不分先后,有些是官方功能,有些是自己改的,有些可能小众,但对我刚刚好,或许能从中找到属于你的那一款。

雅余 https://yayu.net
2

张洪Heo https://blog.zhheo.com
张洪Heo

李的日志 https://lilog.cn
PixPin_2025-11-30_21-13-42

ACEVS https://acevs.com
3

威言威语 https://www.weisay.com
5

木木木木木 https://immmmm.com
林木木

牧羊人 https://www.shephe.com
牧羊人

湘铭`Blog https://xiangming.site
湘铭

粽叶加米 https://wordpace.com
粽叶加米

荒野菲克 https://ficor.net
菲克_2025-12-02_2

漫漫长路 https://mmcl.net
漫漫长路

崔话记 https://cuixiping.com
崔话记20251207

云心怀鹤 (我的主题是纸质信封)
云心怀鹤_2025-11-30

最后,如果你也有一个非它不可的回复界面、模板、签名、小习惯,哪怕只是一个自动追加的喝口水再聊。

欢迎留言告诉我,我会访问你的网站并留言,选一些收录进这篇文章,标注你的名字或昵称(可匿名)。不为评比,只为互相种草,一起把回邮件这件小事,做得更贴心一点。

什么玩意儿?| 网站新增暗夜模式,AI搓出来的代码分享

东西坏了,靠我修,其实我也很不喜欢修东西,只是我还不想扔了它,所以我坚持坏了就修,这就显得我很主动,东西就觉得我离不开它了,它就可以一直坏一直坏,然后我一直修一直修。有没有一种可能?有一天,我不想修了呢?或者它不配了呢?我不要它了,那它是个什么玩意儿?

— 源于2025-10-09更新的一段内容。

那天我挺憋屈的,缘由说来也很搞笑,鸡毛蒜皮的一丁点小事,就这样燃起来的家庭纷争,我自身也是很诧异,为什么就这么点小事我压不住自己的怒火了,可能因为情绪总有一个临界点,我一直是忍让型的追求家和万事兴的态度,但是当下,有一个认知突然占据了我的思想,凭什么赚钱养家的是我,忍气吞声的还是我,所以我就炸了。

我本不想吵架,因为吵赢了只是把矛盾推向更高的地方,无法调和。所以大晚上的我就到操场上快走慢跑了8公里发泄自己的情绪,跑着跑着,越跑越不对劲,情绪更沮丧了,累瘫了坐在十字路口的小石墩上看着红绿灯变换读秒,挺落寞的,也挺心疼自己。

末了,我跟自己说:算了,都是自己惯的,能过就过,不能过就算了,这次我绝不先认怂。

闹矛盾之前,有听她念叨说 iPhone 17 Pro Max 京东本地都没货,所以我记在心里了,贝勒爷的同学家长有位是卖apple周边设备的,所以就跟他讲有货跟我说一声,找他拿一台。

闹矛盾之后,卖手机的说是手机有货了,溢价450元,问我要什么颜色,这都吵架冷战了,我也不好认怂去问她喜欢什么颜色啊,本来想问的,但是,呃,先开口不就输了吗?好,我不问。

不知道她喜欢什么颜色,就拿橙色和银色两台512GB回家,丢在桌子上。这次我对自己大方起来了,以往都是给她追新款,然后我自己随意将就,两台手机13和14用了好多年了,我也没舍得给自己换。心想让她先挑,剩下的我自己用,我也要开始自己享受消费了,这种心理有点变态,呃,为啥我赚钱给她优质生活,然后我自己对自己这么抠,然后她还可以这么高高在上的理所应当的不照顾我的感受。

手机在餐边柜桌子上放了两天,她也没动,我也没动,其实她动了,就是没拆封,因为装在袋子里的叠放顺序变了,肯定是我不在家的时候,自己拿起来纠结了半天,到底拆不拆呢,拆了不就认输了吗?

第三天早上,我还在睡觉,她到房间来,叫醒我,要我给她贴贴膜,拿着橙色和银色比划了半天,纠结要选什么颜色,我说你随便,你选好一台,另一台给我留着,然后我继续睡觉,十一点多起床后,她说要不她拿橙色吧,好的,那我就用修炼了十几年的贴膜技术完美的给她贴好了,但是这时候我还是端着呢,我话不多说,贴完橙色贴银色,贴好后,她拿走银色对比了一下,说银色比较商务,还是你拿吧。

一切好像从未发生过,呃,吵架,呃,根本没有发生过。晚上回房的时候,主动贴贴了,假装无意的翻个身靠着我,在试探我是不是不生气了,其实我早不生气了,真没骨气,唉。我内心其实很清楚,她的性格都是这些年我给她娇惯出来的,我当面指责她恃宠而骄,她并不否认。

但是日子总要过下去的吧,其实她还不错,就是这个家传的性格,有些太蛮横不讲理,事事都要顺遂她的心意,逆反她就是跟她势不两立。但是也相对收敛了很多,可能她也采集到我不忍了的想法,所以不敢再危险边缘来回试探蹦跶了。

果然,修东西,我还是一如既往的专业。


叨叨完,开始下一个主题,关于网站新增暗夜模式。

之前有博友评论说网站没有暗夜模式,可能体验感不佳吧,我现在对于修改网站主题有相当大的心理阴影,因为我的主题都是仿的,看到这个好看我就扒了,然后套进去自己的主题里,导致主题代码垒得跟屎山一样,一个主题里有七八个CSS样式文件,和一大堆可能无用可能有用的js文件,我主张,能用就行。但是这就给修改增加了很大的难度,有时候改一点点小东西,完犊子,前端垮了,垮在哪里,我也不知道,我再找找,就很难。

前几天逛 Jeffer.Z 的新作站点 FindBlog 觉得他的暗夜模式配色挺好看的,嗯,蠢蠢欲动的参照他站点的配色开始折腾了,他说站点黑夜模式是一个第三方的js库,直接调用就行。/npm/darkreader@4.9.80/darkreader.js

听不懂,一点都听不懂,想到用插件来实现,因为我主题魔改的东西太多,所以导致插件装上去后,各种惨不忍睹的前端画面出现了,算了,我用我最简单暴力的方式来折腾吧。

我的想法是这样,我创建两个不同的配色方案样式文件,一个style-dark.css,一个style-light.css,然后通过js判断来识别加载哪一个文件,按道理说这个应该就可以满足我的需求,我打开了Gemini对接的聊天框,https://chat.erduo.tech,搭建来自己用的,因为对比之下,元宝的Deepseek和混元产出的代码内容,总是有各种问题出现,当然,可能是我站着尿尿的姿势不对,也可能是对准的方向不对,总之,我还是用的Gemini 2.5 Flash模型解决。

最早生成的插件php代码如下:

<?php
/**
 * Plugin Name: 日夜模式切换器
 * Plugin URI: https://www.edzbe.com
 * Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
 * Version: 1.0.1
 * Author: 耳朵的主人
 * Author URI: https://www.edzbe.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: daynight-switcher
 * Domain Path: /languages
 */

// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 注册并加载日夜模式样式和脚本
 */

function dns_enqueue_styles_scripts() {
    // 获取主题目录URI(确保CSS文件位于主题的/css/子目录)
    $theme_css_dir_uri = get_template_directory_uri() . '/css/';

    // 注册白天模式样式(WordPress会自动生成ID:daynight-switcher-light-style-css)
    wp_enqueue_style(
        'daynight-switcher-light-style',
        $theme_css_dir_uri . 'style-light.css',
        array(),
        '1.0.0'
    );

    // 注册暗夜模式样式(WordPress会自动生成ID:daynight-switcher-dark-style-css)
    wp_enqueue_style(
        'daynight-switcher-dark-style',
        $theme_css_dir_uri . 'style-dark.css',
        array(),
        '1.0.0'
    );

    // 注册切换脚本(在页脚加载,确保DOM就绪)
    wp_enqueue_script(
        'daynight-switcher-script',
        plugins_url( 'js/daynight-switcher.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // 将PHP数据传递给JS(如样式表ID、按钮ID)
    wp_localize_script(
        'daynight-switcher-script',
        'dns_data',
        array(
            'lightStyleId' => 'daynight-switcher-light-style-css',
            'darkStyleId' => 'daynight-switcher-dark-style-css',
            'buttonId'    => 'daynight-toggle',
      //     'textLight'   => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
      //        'textDark'    => esc_html__( '切换到白天模式', 'daynight-switcher' ),
        )
    );
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_styles_scripts' );

/**
 * 在页面<body>开头添加切换按钮(兼容WP5.2+)
 */

function dns_add_toggle_button() {
    if ( is_admin() ) return; // 排除后台
    ?>
    <button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
        <?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
    </button>
    <?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );

/**
 * 关键修复:提前在页面头部禁用非必要样式表,避免初始闪烁
 * 逻辑:根据本地存储或系统时间,立即禁用不需要的样式表
 */

function dns_early_disable_unnecessary_styles() {
    ?>
    <script>
        (function() {
            // 1. 获取用户偏好或系统默认模式
            const savedMode = localStorage.getItem('theme-mode');
            let initialMode;
           
            if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
                initialMode = savedMode;
            } else {
                const hour = new Date().getHours();
                initialMode = (hour >= 6 && hour < 18) ? 'light' : 'dark'; // 6-18点白天
            }

            // 2. 禁用不需要的样式表(避免页面初始渲染错误)
            const lightStyle = document.getElementById('<?php echo esc_js('daynight-switcher-light-style-css'); ?>');
            const darkStyle = document.getElementById('<?php echo esc_js('daynight-switcher-dark-style-css'); ?>');
           
            if (lightStyle && darkStyle) {
                if (initialMode === 'light') {
                    darkStyle.disabled = true; // 禁用暗夜样式
                } else {
                    lightStyle.disabled = true; // 禁用白天样式
                }
            }
        })();
    </script>
    <?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_early_disable_unnecessary_styles', 1 );
</head></body>

Js源码如下:

document.addEventListener('DOMContentLoaded', function() {
    // 检查本地化数据是否正常
    if (typeof dns_data === 'undefined') {
        console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
        return;
    }

    // 解构本地化数据
    const { lightStyleId, darkStyleId, buttonId, textLight, textDark } = dns_data;

    // 获取DOM元素
    const lightStyle = document.getElementById(lightStyleId);
    const darkStyle = document.getElementById(darkStyleId);
    const toggleButton = document.getElementById(buttonId);

    // 元素存在性检查
    if (!lightStyle || !darkStyle || !toggleButton) {
        console.error('日夜模式切换器:未找到核心元素,请检查样式表/按钮ID是否正确!');
        return;
    }

    console.log('日夜模式切换器:核心元素加载完成!');

    /**
     * 设置主题模式(更新样式、按钮文本、body类)
     * @param {string} mode - 'light'或'dark'
     * @param {boolean} save - 是否保存到localStorage
     */

    function setMode(mode, save = true) {
        // 更新样式表状态
        if (mode === 'light') {
            lightStyle.disabled = false;
            darkStyle.disabled = true;
            toggleButton.textContent = textLight;
            toggleButton.setAttribute('aria-pressed', 'false');
        } else {
            lightStyle.disabled = true;
            darkStyle.disabled = false;
            toggleButton.textContent = textDark;
            toggleButton.setAttribute('aria-pressed', 'true');
        }

        // 更新body类(方便扩展CSS)
        document.body.classList.remove('light-mode', 'dark-mode');
        document.body.classList.add(`${mode}-mode`);

        // 保存用户偏好(可选)
        if (save) localStorage.setItem('theme-mode', mode);
    }

    /**
     * 获取系统默认模式
     * @returns {string} 'light'或'dark'
     */

    function getSystemMode() {
        const hour = new Date().getHours();
        return (hour >= 6 && hour < 18) ? 'light' : 'dark';
    }

    // --- 初始化 ---
    const savedMode = localStorage.getItem('theme-mode');
    const initialMode = savedMode && (savedMode === 'light' || savedMode === 'dark')
        ? savedMode
        : getSystemMode();

    setMode(initialMode, false); // 初始化模式(不保存)
    console.log(`日夜模式切换器:初始模式为${initialMode}`);

    // --- 切换按钮点击事件 ---
    toggleButton.addEventListener('click', function() {
        const currentMode = document.body.classList.contains('light-mode') ? 'light' : 'dark';
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        setMode(newMode); // 切换并保存偏好
        console.log(`日夜模式切换器:已切换至${newMode}`);
    });
});

如上方式实现了暗夜模式的识别和切换,根据用户端的时间,采用“用户偏好优先,无偏好时根据本地时间自动判断,并支持手动切换和保存偏好”的模式。设定6点-18点为白天模式,18-次日6点为暗夜模式。

这个版本有一个问题出现了。

初始页面加载时的优化 (防止闪烁 FOUC):

1.dns_early_disable_unnecessary_styles 这个PHP函数非常关键。它在页面标签中以最高优先级(wp_head, 1)执行一个小的JavaScript片段。

2.这个JS片段会立即检查 localStorage 或根据时间判断初始模式,然后直接禁用不需要的CSS文件(style-light.cssstyle-dark.css)。

3.这样做的好处是,在浏览器开始渲染页面之前,已经确定并加载了正确的样式,有效避免了页面先显示一种模式然后迅速切换到另一种模式的“闪烁”现象 (Flash Of Unstyled Content)。

页面前端依然很不给面子的闪烁,白天和暗夜模式底色反差太大,一闪一闪让人很不舒服,本想着退而求其次,将就就好,管它白天闪晚上,还是晚上闪白天。然后,这两三天来,白天都在看房,跟着中介看这套看那套的,折腾得累半死,回家洗完澡一躺就睡了,这个问题也就这样搁置了几天。晚上得闲,一打开页面,页面切换的时候,闪得我自己都很不舒服,算了算了,我还是再修一修吧。

出现这个闪烁的原因,很可能是因为浏览器在解析HTML并应用内联JS脚本禁用CSS之前,已经短暂地加载并应用了所有样式表。如果 style-dark.css 中的样式优先级更高,或者它定义的默认颜色在没有其他限制的情况下会覆盖 style-light.css,就会导致先显示深色。

解决这个问题的更健壮方法是利用 link 标签的 media 属性,它在CSS加载层面就阻止了不必要的样式表被应用,而不是在JS加载后才去禁用。

产生了第二版插件代码:

  1. wp_enqueue_style 中使用 media=”not all”:
    • 通过在PHP中将 style-dark.css 的 media 属性设置为 ‘not all’,我们告诉浏览器,这个样式表在任何媒体类型下都不适用。这意味着浏览器在解析HTML时,根本不会加载和应用 style-dark.css 的内容,从而避免了它在初始渲染时可能造成的深色闪烁。
    • style-light.css 仍然保持 media=”all”,确保默认的白天模式样式能被加载。
  2. 早期JS脚本中切换 media 属性:
    • 在 dns_early_disable_unnecessary_styles 中,我们不再使用 style.disabled = true/false,而是修改 style.media 属性。
    • link.media = ‘all’ 会让浏览器立即应用该样式表。
    • link.media = ‘not all’ 会让浏览器立即禁用该样式表。
    • 这种方法比 disabled 属性更可靠,因为它在CSS加载和应用机制的更底层进行控制,通常能更快、更彻底地防止不希望的样式被渲染。
  3. setMode 函数中切换 media 属性:
    • 手动切换模式时,JavaScript也需要同步地修改样式表的 media 属性,以确保页面动态地更新为正确的模式。

php代码如下:

<?php
/**
 * Plugin Name: 日夜模式切换器
 * Plugin URI: https://www.edzbe.com
 * Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
 * Version: 1.0.2
 * Author: 耳朵的主人
 * Author URI: https://www.edzbe.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: daynight-switcher
 * Domain Path: /languages
 */

// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 注册并加载日夜模式样式和脚本
 */

function dns_enqueue_styles_scripts() {
    // 获取主题目录URI(确保CSS文件位于主题的/css/子目录)
    $theme_css_dir_uri = get_template_directory_uri() . '/css/';

    // 注册白天模式样式(默认 media="all",提供一个稳定的基线,避免布局错乱)
    wp_enqueue_style(
        'daynight-switcher-light-style',
        $theme_css_dir_uri . 'style-light.css',
        array(),
        '1.0.0',
        'all' // 白天模式默认加载,作为页面的基础颜色模式
    );

    // 注册暗夜模式样式(默认 media="not all",仅在需要时激活)
    wp_enqueue_style(
        'daynight-switcher-dark-style',
        $theme_css_dir_uri . 'style-dark.css',
        array(),
        '1.0.0',
        'not all' // 暗夜模式初始不加载,避免闪烁
    );

    // 注册切换脚本(在页脚加载,确保DOM就绪)
    wp_enqueue_script(
        'daynight-switcher-script',
        plugins_url( 'js/daynight-switcher.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // 将PHP数据传递给JS(如样式表ID、按钮ID)
    wp_localize_script(
        'daynight-switcher-script',
        'dns_data',
        array(
            'lightStyleId' => 'daynight-switcher-light-style-css',
            'darkStyleId' => 'daynight-switcher-dark-style-css',
            'buttonId'    => 'daynight-toggle',
            'textLight'   => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
            'textDark'    => esc_html__( '切换到白天模式', 'daynight-switcher' ),
        )
    );
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_styles_scripts' );

/**
 * 在页面<body>开头添加切换按钮(兼容WP5.2+)
 */

function dns_add_toggle_button() {
    if ( is_admin() ) return; // 排除后台
    ?>
    <button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
        <?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
    </button>
    <?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );

/**
 * 关键修复:提前在页面头部设置正确的样式表和body类,避免初始闪烁
 * 逻辑:根据本地存储或系统时间,在初始加载时,如果需要暗色模式,则立即激活暗色模式并禁用白天模式。
 * 否则(即需要白天模式),白天模式已经默认加载,只需设置body类。
 */

function dns_early_disable_unnecessary_styles() {
    ?>
    <script>
        (function() {
            // 1. 获取用户偏好或系统默认模式
            const savedMode = localStorage.getItem('theme-mode');
            let initialMode;
           
            if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
                initialMode = savedMode;
            } else {
                const hour = new Date().getHours();
                initialMode = (hour >= 6 && hour < 18) ? 'light' : 'dark'; // 6-18点白天
            }

            // 2. 只有在初始模式是“dark”时才需要立即操作样式表,
            // 因为“light”模式的样式表已经默认加载 media="all"
            if (initialMode === 'dark') {
                const lightStyle = document.getElementById('<?php echo esc_js('daynight-switcher-light-style-css'); ?>');
                const darkStyle = document.getElementById('<?php echo esc_js('daynight-switcher-dark-style-css'); ?>');
               
                if (lightStyle && darkStyle) {
                    lightStyle.media = 'not all'; // 禁用白天样式
                    darkStyle.media = 'all';      // 激活暗夜样式
                }
            }
           
            // 3. 设置 body 的 class,确保全局背景色等样式同步
            // 这一步在任何模式下都执行,以确保 body 类始终是正确的
            document.body.classList.remove('light-mode', 'dark-mode'); // 移除可能的旧类
            document.body.classList.add(`${initialMode}-mode`); // 添加当前模式类

        })();
    </script>
    <?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_early_disable_unnecessary_styles', 1 );


</head></body>

js代码如下:

document.addEventListener('DOMContentLoaded', function() {
    // 检查本地化数据是否正常
    if (typeof dns_data === 'undefined') {
        console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
        return;
    }

    // 解构本地化数据
    const { lightStyleId, darkStyleId, buttonId, textLight, textDark } = dns_data;

    // 获取DOM元素
    const lightStyle = document.getElementById(lightStyleId);
    const darkStyle = document.getElementById(darkStyleId);
    const toggleButton = document.getElementById(buttonId);

    // 元素存在性检查
    if (!lightStyle || !darkStyle || !toggleButton) {
        console.error('日夜模式切换器:未找到核心元素,请检查样式表/按钮ID是否正确!');
        return;
    }

    console.log('日夜模式切换器:核心元素加载完成!');

    /**
     * 设置主题模式(更新样式、按钮文本、body类)
     * @param {string} mode - 'light'或'dark'
     * @param {boolean} save - 是否保存到localStorage
     */

    function setMode(mode, save = true) {
        // 更新样式表状态,通过修改 media 属性来激活/禁用样式
        if (mode === 'light') {
            lightStyle.media = 'all';     // 激活白天样式
            darkStyle.media = 'not all';  // 禁用暗夜样式
            toggleButton.textContent = textLight;
            toggleButton.setAttribute('aria-pressed', 'false');
        } else { // mode === 'dark'
            lightStyle.media = 'not all'; // 禁用白天样式
            darkStyle.media = 'all';      // 激活暗夜样式
            toggleButton.textContent = textDark;
            toggleButton.setAttribute('aria-pressed', 'true');
        }

        // 更新body类(方便扩展CSS)
        document.body.classList.remove('light-mode', 'dark-mode');
        document.body.classList.add(`${mode}-mode`);

        // 保存用户偏好(可选)
        if (save) localStorage.setItem('theme-mode', mode);
    }

    /**
     * 获取系统默认模式
     * @returns {string} 'light'或'dark'
     */

    function getSystemMode() {
        const hour = new Date().getHours();
        return (hour >= 6 && hour < 18) ? 'light' : 'dark';
    }

    // --- 初始化 ---
    // 页面加载时的初始化逻辑已经由PHP中的dns_early_disable_unnecessary_styles处理,
    // 这里再次调用setMode主要是为了同步按钮文本和body class,
    // 并且确保在JS完全加载后,如果有未被早期脚本处理的逻辑(虽然现在应该没有了),也能正确执行。
    const savedMode = localStorage.getItem('theme-mode');
    const initialMode = savedMode && (savedMode === 'light' || savedMode === 'dark')
        ? savedMode
        : getSystemMode();

    // 注意:这里的 setMode(initialMode, false) 仅仅是确保DOM加载完成后,按钮文本等细节能够正确初始化。
    // 真正避免闪烁的样式切换,由 dns_early_disable_unnecessary_styles 中的内联脚本处理。
    setMode(initialMode, false);
    console.log(`日夜模式切换器:初始模式为${initialMode}`);

    // --- 切换按钮点击事件 ---
    toggleButton.addEventListener('click', function() {
        const currentMode = document.body.classList.contains('light-mode') ? 'light' : 'dark';
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        setMode(newMode); // 切换并保存偏好
        console.log(`日夜模式切换器:已切换至${newMode}`);
    });
});

 

如上代码,其实应该是正常解决方案的,就是创建的 style-light.css style-dark.css 文件如果只有配色方案,网页加载的时候,先加载 style.css 框架样式文件,这样页面就正常显示,只是配色根据选择加载,但是我又失败了,因为我开始弄的时候,把默认的style.css 框架样式直接套到 style-light.css style-dark.css 这两个文件里了,要修改,又要重新开始分离出配色单独创建样式表,不行,不行,这工作量太大了,我不要。

我给Gemini发送了我的想法解决这个问题: 先加载页面的判断设置,直接屏蔽不加载另一种模式。Gemini很给力的给出了解决方案。

只加载所需的模式样式,而不是先加载一个再切换,避免任何形式的“闪烁”或“布局混乱”。

我之前的方案试图通过 `media` 属性和内联 JS 来解决,但在某些浏览器或加载条件下,即使是极快的切换,也可能出现视觉上的延迟。实现“直接屏蔽不加载另一种模式”的最彻底方法是:在 PHP 生成 HTML 的 `<head>` 部分时,就根据用户的偏好或时间判断,只输出当前所需模式的 `<link>` 标签,而完全不输出另一个模式的 `<link>` 标签。

但这有个挑战:PHP 在服务器端运行,它无法直接读取用户浏览器中的 `localStorage`。它只能读取 `$_COOKIE`。所以,为了让 PHP 在第一次加载页面时就知道用户偏好,JavaScript 必须在每次模式切换时,除了更新 `localStorage`,还要同时更新一个 Cookie

最终版本php代码如下:

<?php
/**
 * Plugin Name: 日夜模式切换器
 * Plugin URI: https://www.edzbe.com
 * Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
 * Version: 1.0.3
 * Author: 耳朵的主人
 * Author URI: https://www.edzbe.com
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: daynight-switcher
 * Domain Path: /languages
 */

// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 提前判断初始模式(PHP服务器端)
 * 优先级:Cookie (用户偏好) > 服务器时间
 * @return string 'light' 或 'dark'
 */

function dns_get_initial_mode_server_side() {
    // 1. 尝试从Cookie获取用户偏好
    if ( isset( $_COOKIE['theme-mode'] ) && in_array( $_COOKIE['theme-mode'], ['light', 'dark'] ) ) {
        return $_COOKIE['theme-mode'];
    }

    // 2. 如果Cookie不存在,根据服务器时间判断
    $hour = (int) current_time( 'H', true ); // 使用WordPress的 current_time 获取当前小时,避免时区问题
    return ($hour >= 6 && $hour < 18) ? 'light' : 'dark'; // 6-18点白天
}

/**
 * 注册并加载日夜模式脚本
 * 注意:日夜模式的样式文件不再在这里直接 enqueue,而是由 dns_output_conditional_styles 动态输出。
 */

function dns_enqueue_scripts() {
    // 获取主题目录URI
    $theme_css_dir_uri = get_template_directory_uri() . '/css/'; // 仍然传递给JS,以防需要手动构建 link 标签 fallback

    // 注册切换脚本(在页脚加载,确保DOM就绪)
    wp_enqueue_script(
        'daynight-switcher-script',
        plugins_url( 'js/daynight-switcher.js', __FILE__ ),
        array(),
        '1.0.0',
        true
    );

    // 将PHP数据传递给JS(如样式表ID、按钮ID、CSS文件路径)
    wp_localize_script(
        'daynight-switcher-script',
        'dns_data',
        array(
            'lightStyleId'      => 'daynight-switcher-light-style-css',
            'darkStyleId'       => 'daynight-switcher-dark-style-css',
            'buttonId'          => 'daynight-toggle',
            'textLight'         => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
            'textDark'          => esc_html__( '切换到白天模式', 'daynight-switcher' ),
            'lightStyleUrl'     => $theme_css_dir_uri . 'style-light.css', // 传递CSS路径给JS,用于动态创建link标签
            'darkStyleUrl'      => $theme_css_dir_uri . 'style-dark.css',  // 传递CSS路径给JS,用于动态创建link标签
        )
    );
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_scripts' ); // 重命名函数,因为不再enqueue styles

/**
 * 在页面<body>开头添加切换按钮(兼容WP5.2+)
 */

function dns_add_toggle_button() {
    if ( is_admin() ) return; // 排除后台
    ?>
    <button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
        <?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
    </button>
    <?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );

/**
 * 核心:在页面头部根据PHP判断动态输出正确的样式表和body类
 * 这将避免任何模式切换时的闪烁。
 */

function dns_output_conditional_styles() {
    if ( is_admin() ) return; // 排除后台

    $initial_mode = dns_get_initial_mode_server_side();
    $theme_css_dir_uri = get_template_directory_uri() . '/css/';
    $version = '1.0.0'; // 样式表的版本号

    // 输出正确的样式表
    if ( $initial_mode === 'light' ) {
        echo '<link rel="stylesheet" id="daynight-switcher-light-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-light.css?ver=' . $version ) . '" type="text/css" media="all"/>';
        echo '<link rel="stylesheet" id="daynight-switcher-dark-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-dark.css?ver=' . $version ) . '" type="text/css" media="not all"/>';
    } else { // dark mode
        echo '<link rel="stylesheet" id="daynight-switcher-light-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-light.css?ver=' . $version ) . '" type="text/css" media="not all"/>';
        echo '<link rel="stylesheet" id="daynight-switcher-dark-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-dark.css?ver=' . $version ) . '" type="text/css" media="all"/>';
    }

    // 立即在body标签上设置正确的class,防止任何延迟闪烁
    // 注意:这个内联脚本在<?php标签内,所以它会先于任何其他JS执行
    ?>
    <script>
        // 确保body class在HTML解析时就存在
        document.documentElement.classList.add('<?php echo esc_js( $initial_mode ); ?>-mode');
        document.body.classList.add('<?php echo esc_js( $initial_mode ); ?>-mode');

        // 后备或纠正逻辑(如果localStorage与PHP判断不符,或Cookie过期)
        (function() {
            const savedMode = localStorage.getItem('theme-mode');
            const currentInitialMode = '<?php echo esc_js( $initial_mode ); ?>'; // PHP决定的初始模式

            if (savedMode && savedMode !== currentInitialMode) {
                // 如果localStorage和PHP的初始判断不一致,JS会立即纠正
                const lightStyle = document.getElementById('daynight-switcher-light-style-css');
                const darkStyle = document.getElementById('daynight-switcher-dark-style-css');

                if (lightStyle && darkStyle) {
                    if (savedMode === 'light') {
                        lightStyle.media = 'all';
                        darkStyle.media = 'not all';
                    } else { // savedMode === 'dark'
                        lightStyle.media = 'not all';
                        darkStyle.media = 'all';
                    }
                }
                document.documentElement.classList.remove('light-mode', 'dark-mode');
                document.documentElement.classList.add(`${savedMode}-mode`);
                document.body.classList.remove('light-mode', 'dark-mode');
                document.body.classList.add(`${savedMode}-mode`);
            }
        })();
    </script>
    <?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_output_conditional_styles', 1 );


</head></body>

js代码如下:

document.addEventListener('DOMContentLoaded', function() {
    // 检查本地化数据是否正常
    if (typeof dns_data === 'undefined') {
        console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
        return;
    }

    // 解构本地化数据
    const { lightStyleId, darkStyleId, buttonId, textLight, textDark, lightStyleUrl, darkStyleUrl } = dns_data;

    // 获取DOM元素
    const lightStyle = document.getElementById(lightStyleId);
    const darkStyle = document.getElementById(darkStyleId);
    const toggleButton = document.getElementById(buttonId);

    // 确保样式表元素存在。如果PHP动态输出失败,这里可能是null。
    // 但是在最新的方案中,PHP一定会输出这两个link标签,只是media属性不同。
    if (!lightStyle || !darkStyle || !toggleButton) {
        // Fallback: 如果PHP没有输出link标签,JS动态创建它们
        console.warn('日夜模式切换器:未找到核心样式表元素。尝试动态创建。');
        // 如果lightStyle或darkStyle不存在,这里需要动态创建。
        // 为了简化,这里假设它们总是由PHP输出。如果真的未找到,需要更复杂的JS fallback。
        // 目前先跳过,因为PHP应该确保它们存在。
        // return; // 暂时不返回,让下面的逻辑有机会运行
    }


    console.log('日夜模式切换器:核心元素加载完成!');

    /**
     * 设置Cookie
     * @param {string} name - Cookie名称
     * @param {string} value - Cookie值
     * @param {number} days - Cookie过期天数
     */

    function setCookie(name, value, days) {
        let expires = "";
        if (days) {
            const date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toUTCString();
        }
        document.cookie = name + "=" + (value || "")  + expires + "; path=/";
    }

    /**
     * 设置主题模式(更新样式、按钮文本、body类、同步Cookie)
     * @param {string} mode - 'light'或'dark'
     * @param {boolean} save - 是否保存到localStorage和Cookie
     */

    function setMode(mode, save = true) {
        // 更新样式表状态,通过修改 media 属性来激活/禁用样式
        if (mode === 'light') {
            if (lightStyle) lightStyle.media = 'all';     // 激活白天样式
            if (darkStyle) darkStyle.media = 'not all';  // 禁用暗夜样式
            toggleButton.textContent = textLight;
            toggleButton.setAttribute('aria-pressed', 'false');
        } else { // mode === 'dark'
            if (lightStyle) lightStyle.media = 'not all'; // 禁用白天样式
            if (darkStyle) darkStyle.media = 'all';      // 激活暗夜样式
            toggleButton.textContent = textDark;
            toggleButton.setAttribute('aria-pressed', 'true');
        }

        // 更新body和html根元素的类(方便扩展CSS)
        document.documentElement.classList.remove('light-mode', 'dark-mode');
        document.documentElement.classList.add(`${mode}-mode`);
        document.body.classList.remove('light-mode', 'dark-mode');
        document.body.classList.add(`${mode}-mode`);

        // 保存用户偏好(可选)到 localStorage 和 Cookie
        if (save) {
            localStorage.setItem('theme-mode', mode);
            setCookie('theme-mode', mode, 30); // Cookie保存30天
        }
    }

    /**
     * 获取系统默认模式
     * @returns {string} 'light'或'dark'
     */

    function getSystemMode() {
        const hour = new Date().getHours();
        return (hour >= 6 && hour < 18) ? 'light' : 'dark';
    }

    // --- 初始化 ---
    // 页面加载时的初始模式判断和样式设置已由PHP在服务器端完成,
    // 这里的JS初始化主要是确保按钮文本正确,并处理LocalStorage和Cookie可能不同步的情况。
    const savedMode = localStorage.getItem('theme-mode');
    let initialMode;

    if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
        initialMode = savedMode;
    } else {
        initialMode = getSystemMode();
    }

    // 确保当前的Cookie也与initialMode一致,防止PHP下次加载时判断错误
    setCookie('theme-mode', initialMode, 30);

    // 调用 setMode 来同步按钮文本,以及确保 body class 即使在PHP未能设置时也能被设置(虽然PHP现在会设置)
    setMode(initialMode, false);
    console.log(`日夜模式切换器:初始模式为${initialMode}`);


    // --- 切换按钮点击事件 ---
    toggleButton.addEventListener('click', function() {
        // 从html根元素获取当前模式,因为CSS可能也通过html根元素控制
        const currentMode = document.documentElement.classList.contains('light-mode') ? 'light' : 'dark';
        const newMode = currentMode === 'light' ? 'dark' : 'light';
        setMode(newMode); // 切换并保存偏好
        console.log(`日夜模式切换器:已切换至${newMode}`);
    });
});

核心原理和解决闪烁的方法:

1. PHP 服务器端预判: PHP 在页面生成之初(在 `wp_head` 动作中,优先级为1),就通过 `dns_get_initial_mode_server_side()` 函数,根据 `theme-mode` Cookie 或服务器时间,判断出应该显示的模式。

2. 只输出所需 `<link>` 标签: `dns_output_conditional_styles()` 函数根据 PHP 的判断结果,直接在 HTML 的 `<head>` 中输出 两个 `<link>` 标签。
• 正确的模式 的 `<link>` 标签 `media` 属性是 `all`
• 不正确的模式 的 `<link>` 标签 `media` 属性是 `not all`
这样,在浏览器开始解析和渲染 HTML 时,它只会加载并应用正确的模式样式,从而从源头上避免了加载错误样式导致的闪烁。

3. `document.documentElement` `document.body` 类: PHP 还会输出一个内联 `<script>`,它会立即为 `<html>``<body>` 元素添加正确的模式类(例如 `dark-mode`)。这对于依赖这些类来设置背景色、字体颜色等全局样式的 CSS 规则至关重要,确保页面在渲染时就有正确的颜色基调。

4. JavaScript Cookie 同步: JS 代码在 `setMode` 时,除了更新 `localStorage`,还会更新一个 `theme-mode` Cookie。这样,用户下次访问时,PHP 就能从 Cookie 中读取到用户上次的选择。

5. 内联 JS 后备纠正: PHP 输出的内联 JS 中含有一个小的逻辑,用于比较 PHP 的判断(来自 Cookie 或时间)和 `localStorage` 中的用户偏好。如果两者不一致(例如,用户清除了 Cookie 但 `localStorage` 还在),这个内联 JS 会立即纠正模式,提供额外的鲁棒性。

代码仅供参考,在需要调用按钮的前端,添加按钮:

<button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
<?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
</button>

还有一个问题就是,header.php文件必须包含<?php wp_head(); ?>footer.php文件代码必须包含 <?php wp_footer(); ?> ,按钮在二者之间的位置插入,否则无法正常加载,为什么,我也不知道,我是一步一步修过来的,怎么修的,不知道。

顺便提供下我的按钮图标:

完整插件代码下载:daynight-switcher

伪技术贴完工,睡觉。

Docker部署美食记录应用Mealie

2025年10月12日 15:14

前言

  • 最近刷抖音,经常看到一些保姆级别的美食教学视频,跟着步骤做了一些,ld孩子都说好吃,但是单独发篇文章的话,格式步骤一直没有自己中意的,所以想着部署一个美食记录应用,参考网上有发布的文章,最后选择了Mealie,部署在本地绿联NAS上,通过Frp内网穿透出去,方便随时查看。
  • 我选用Mealie的原因主要是为了记录,没有多余的推荐功能,单纯的记录,可以分享给家庭成员,或者直接公开,这很适合我的需求。也顺便推荐两个美食应用,tamari和HowToCook,两个应用有部分自定义功能,这两个应用除了少量的记录,更多是推荐功能,大家有需要的可以根据自己需求部署。

Mealie

Mealie主页.png

services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:v3.3.2 # 
    container_name: mealie
    restart: always
    ports:
        - "9925:9000" # 
    deploy:
      resources:
        limits:
          memory: 1000M # 
    volumes:
      - mealie-data:/app/data/
    environment:
      # Set Backend ENV Variables Here
      ALLOW_SIGNUP: "false"
      PUID: 1000
      PGID: 1000
      TZ: America/Anchorage
      BASE_URL: https://mealie.yourdomain.com

volumes:
  mealie-data:

绿联部署过程

  • 拉取镜像,因为没有上架dockerhub,该步需要进入SSH后,docker pull一下,或者直接创建项目先拉取创建一下项目,然后在删除,保留镜像,这里使用绿联自带的Docker容器创建,删除保留镜像主要是想把文件存储到本地指定的存储空间里
  • 镜像拉取成功后,直接创建。

Mealie绿联部署1.png

  • 根据compose配置对应参数(颜色那个请忽略,未生效)

Mealie绿联部署2.png

  • 选择对应的文件夹以及端口号

Mealie绿联部署3.png

  • 部署成功,通过IP:端口号,访问页面,进入初始化配置。

Mealie安装.png

  • 创建菜谱(这里以之前的牛肉锅仔演示)

Mealie创建菜谱.png

  • 这里可以逐个添加配料,可以批量添加。

Mealie设置配方.png

  • 填写具体的烹饪过程,支持md语法好评。

Mealie烹饪过程.png

  • 如果想要非家庭成员查看到菜谱,需要选择公开配方和取消私有家庭。

Mealie公开配方.png
Mealie允许访问.png

  • 这样一个菜谱就记录好了。我的菜谱地址:mealie

tamari

Tamari.png

HowToCook

碎碎念

  • 后面如果尝试了做什么菜的话,成功的话,就把详细的步骤记录到mealie上,这样方便下次再做的话可以及时查看,也顺便在半月记里面做一个简单的总结。
  •  

Snapic Just 640px 主题入门

2025年10月9日 21:18

Snapic Just 640px是我在用的主题,作者更新很快,但一些修改只能在文件修,所以来总结一下我全部的设置。

Lopwon:不推荐从外部引入字体,访问不顺畅会导致自己的页面加载受阻,如果非得使用,需要放置在主题本身的 css 文件之下,也就是在 head 标签内的最下方,以覆盖主题 css 中的字体设置。

1、我是直接在head.php的< head >部分填写以下代码:

  <link rel="stylesheet" href="https://registry.npmmirror.com/lxgw-wenkai-screen-web/1.321.0/files/style.css" media="print" onload="this.media='all'"/>
  <style>
    body {
      font-family: "LXGW WenKai Screen", "微软雅黑", "Microsoft YaHei", "Georgia", "STZhongsong", serif;
      font-weight: normal;
    }
  </style>

宽度我设置的是640、800、960,可以自定义。

Lopwon:如果指定是代码位置的话,在 functions.php 文件:找到变量 $optionsPannelWidth 每行 label 对应一项宽度设置,可以新增也可以删除,value 为对应的宽度值,在前端点选后会把值传递到 html 的样式中的容器宽度变量 --body-width 来定义页面内容的宽度。

2、找到functions.php的$optionsPannelWidt:

        $optionsPannelWidth = '
            <div class="width">
                <label><input type="radio" name="width" value="640px"><span>窄栏</span></label>
                <label><input type="radio" name="width" value="800px"><span>适中</span></label>
                <label><input type="radio" name="width" value="960px"><span>宽栏</span></label>
                <label><input type="radio" name="width" value="default"><span>默认</span></label>
            </div>
        ';

同时在 functions.php 文件 找到变量 $width 修改相应的宽度设置。

    $width = new Typecho_Widget_Helper_Form_Element_Radio(
        'width',
        [
            '640px'     => _t('640px'),
            '800px'     => _t('800px'),
            '960px' => _t('960px')
        ],

右侧设置选择自己喜欢的阅览界面大小

3、下面是副标题的自定义设置,作者:@老街里,在 functions.php文件,我插入在变量 $catalog 结尾后,最新主题v1.6.0,第83代码行。

    $SubTitle = new Typecho_Widget_Helper_Form_Element_Text(
        'subTitle', null, '',
        _t('网站副标题'),
        _t('· 输入副标题文字,将显示在网站主标题后方,留空则不显示。')
    );
    $form->addInput($SubTitle);

同时在 head.php 文件,把<title><?php $this->archiveTitle([], '', ' - '); $this->options->title(); ?></title>替换成如下代码:

<title>
    <?php 
    if ($this->is('index')) {
        // 主标题
        $title = $this->options->title();
        // 副标题(去除首尾空白字符)
        $SubTitle = trim($this->options->subTitle);

        // 拼接标题
        if (!empty($SubTitle)) {
            echo $title . ' - ' . $SubTitle;
        } else {
            echo $title;
        }
    } else {
        $this->archiveTitle([], '', ' - '); 
        $this->options->title();
    }
    ?>
</title>

Lopwon:这个机制是避免浏览者点开图片后,直接翻图看文章中的所有图片,而不去阅读文字,我的博客同样使用了这样的机制,如果是文内图集,才能在图集内翻图,其他图片使用单图灯箱效果。

4、图片翻图,原主题是不能设置翻图,只能一张张的阅览,当然作者也给出解决方案,在 functions.php 文件 :

  1. 找到 'fotos-' . $fotoNum 替换为 'fotos' 
  2. 找到 'img-' . $imgNum 也替换为 'fotos'

5、如何自定义翻页翻篇的灵敏度?

1. 搜索 foot.php 文件,找到两处 navigation 函数,如下操作:

    navigation(true, 2, 1); // 第 1 个参数为 true 的,保持不变,第 2 个参数为电脑端鼠标滚轮阀值,第 3 个参数为移动端手势滑动阀值

    navigation(false, 2, 1); // 第 1 个参数为 false 的,保持不变,第 2 个参数为电脑端鼠标滚轮阀值,第 3 个参数为移动端手势滑动阀值

6、签名字体修改,更加符合主题风格 将以上修正相对路径后的 @font-face {...} 样式代码,复制。粘贴至 css/snapic.just640px.front.css 文件中,如 @font-face 的下面,并如下操作。

@font-face {
    font-family: 'Ma Shan Zheng'; /* 自定义字体名称,如果有空格,请用引号包裹,如 'Noto Sans SC' */
    src: url('../font/myfont/subset-MaShanZheng-Regular.eot');
    src: url('../font/myfont/subset-MaShanZheng-Regular.eot?#iefix') format('embedded-opentype'),
        url('../font/myfont/subset-MaShanZheng-Regular.woff2') format('woff2'),
        url('../font/myfont/subset-MaShanZheng-Regular.woff') format('woff'),
        url('../font/myfont/subset-MaShanZheng-Regular.ttf') format('truetype'),
        url('../font/myfont/subset-MaShanZheng-Regular.svg#MaShanZheng-Regular') format('svg');
    font-weight: normal; /* 字体粗细,可选 */
    font-style: normal; /* 字体样式,可选 */
    font-display: swap; /* 字体显示,必选 */

}

.sign span:first-child a 部分修改样式 font-family: 'Ma Shan Zheng'

7、这条是备注,Typecho的feed显示200字符?当然也可以使用我的插件RSS,若修改找到 feed 文件:

function safeSubstr($str, $length) {
    preg_match_all('/./us', $str, $ar);
    return implode('', array_slice($ar[0], 0, $length));
}

$this->feed->addItem([
    'title'           => $this->title,
    'content'         => $this->options->feedFullText? $this->content : safeSubstr($this->content, 200). "<p class=\"more\"><a href=\"{$this->permalink}\" title=\"{$this->title}\">[...]</a></p>",
    'date'            => $this->created,
    'link'            => $this->permalink,
    'author'          => $this->author,
    'excerpt'         => $this->___description(),
    'comments'        => $this->commentsNum,
    'commentsFeedUrl' => $feedUrl,
   'suffix'          => $suffix
]);

8、隐藏·的图片描述如何设置?也是@老街里修改的,找到 functions.php文件:

$buildImageHtml = function ($imageUrl, $altText, $gallery) use ($placeholder) {
    // 检查alt文本中是否包含"·",如果有则创建带样式的图片描述(隐藏·)
    if (stripos($altText, '·') !== false) {
        // 分割·前后的内容并取后面的部分,同时移除·符号
        $description = ltrim(explode('·', $altText, 2)[1] ?? '', '·');
        // 添加text-align: center样式使描述居中
        $escaped = sprintf('<span class="escaped"><span class="clamp">%s</span></span>', $description);
    } else {
        $escaped = '';
    }

    $blurStyle = (mb_strpos($altText, '羞') !== false) ? 'style="filter: blur(5px);"' : '';

9、备案号添加,在 foot.php 文件:

<?php echo filing('桂ICP备2022001355号-2','桂公网安备 45012702000102号','21313123'); ?>

10、添加RSS订阅和版权声明小图标在备案末尾,在 functions.php文件,找到filing部分:

function filing($icp = '', $psb = '')
{
    $icp = isset($icp) ? (string)$icp : '';
    $psb = isset($psb) ? (string)$psb : '';
    $footerDisplay = (empty($icp) && empty($psb)) ? 'style="display:none;"' : '';
    $icpDisplay = empty($icp) ? 'style="display:none;"' : '';
    $psbDisplay = empty($psb) ? 'style="display:none;"' : '';
    return <<< Lopwon
        <filing {$footerDisplay}>
            <section id="icp" {$icpDisplay}>
                <a href="http://beian.miit.gov.cn/" rel="noopener noreferrer" target="_blank">{$icp}</a>
            </section>
            <section id="psb" {$psbDisplay}>
                <a href="http://www.beian.gov.cn/" rel="noopener noreferrer" target="_blank">{$psb}</a>
            </section>
            <span style="display: flex;flex-direction: row; ">  
            <a href="https://bluehe.cn/rss.xml" rel="noopener noreferrer" target="_blank"><img src="https://bluehe.cn/usr/themes/SnapicJust640px/img/rss.png" style="width: 16px; height: 16px;"></a>  
            <a href="https://bluehe.cn/copyright.html" rel="noopener noreferrer" target="_blank"><img src="https://img.bluehe.cn/i/2025/04/08/67f4d7c3e4f70.png" style="width: 16px; height: 16px;"></a>  
            <span>
        </filing>
Lopwon;
}

主题的评论在侧边栏,包括设置阅读宽度、网站背景、导航栏,以上就是部分修改内容,更加完善,待更新......

文 / 寻鹤
源 / bluehe.cn

论数据备份的重要性

2025年9月12日 23:35

说起博客,也将近玩了十年了,十年里从刚开始玩emlog大前端,后来发现生态没有typecho好,就慢慢转到了typecho,转到typecho不久,期间又对WordPress感兴趣,所以抛弃typecho来到WordPress的怀抱,那时候上学生活费紧缺,买的机子其实真带不动以前的WordPress不知道现在WordPress怎样呢,然后再次回到了typecho怀抱,我相信很多人都跟我一样。

于是自从博客的文章从WordPress转回typecho后,一直就有一个历史遗留问题,那就是当时用的是typecho的官方插件wordpress-to-typecho,这会导致转回来的文章使用的语法不是Markdown而是html(如下图所示),当进行文章修改的时候会有诸多不便,我不信只有我一个人有这个问题吧哈哈
wordpress-to-typecho

还有一个问题就是typecho后台自带的备份功能,我相信当时很多小白也没少用这个来备份数据之后进行迁移或者程序升级的唯一途径,但是这个备份功能会有一个致命性问题,因为备份只会生成一个bat文件,恢复数据的时候有几率会碰上SQLSTATE[HY000]: General error:错误导致数据恢复不成功或者丢失数据,这也是我19年还是20年的时候遇到过的坑。

前几天刚好有空,刚好Cursor会员也准备过期了,就打算把这个问题一次性解决掉,于是Article Markdown Backup 插件就诞生了,启用插件后,点击控制台-文章备份与转换就能进入设置页面,很简洁,就只有四个功能

文章备份 - 备份所有文章和评论数据到JSON文件
文章恢复 - 从备份文件恢复文章和评论数据
格式转换 - 将HTML格式的文章转换为Markdown格式
文件管理 - 支持上传备份文件和选择服务器备份文件

ArticleMarkdownBackup

下面是转换前和转换后的样式,插件能转换基础的markdown格式,但是如果你的主题有很多个性化的短代码,则需要自己动手修改或者在插件基础上增加更多的转换格式了:
[photos]
转换前
转换后
[/photos]


然后就是简单的备份功能,备份功能会把你的文章数据连同评论一起导出存储为json格式保存在/usr/plugins/ArticleMarkdownBackup/backups目录,也在博主自己的网站上测试过,备份204篇文章然后全部删除然后成功恢复全部数据,在开发环境和正式部署环境都暂时没有发现丢失数据的bug(但是也建议对数据库进行及时的备份,插件只能起到临时辅助的作用,长期备份还得要备份整个数据库),如果发现bug也可以随时提出来,及时进行修复,插件是基于typecho1.3.0
进行开发,理论上支持1.2.0-1.3.0版本,更低版本的还没有测试过,个人建议可以升级到1.2.1版本。


2025/09/14更新版本

Cid策略管理

新增文章Cid管理策略(默认关闭)

  • 进入后台 → 插件 → “Article Markdown Backup”插件 → 设置
  • 在“策略管理”中选择“开启”,并在“CID 连贯策略”中选择一种策略
  • 保存配置后生效;未开启时,不会对文章写入/发布产生影响

策略说明:

  • 按最小可用位(跳过附件)
    skip:为新内容选择从1开始的最小未被有效内容或附件占用的CID;发现附件仅跳过不删除
  • 按最小可用位(忽略附件,遇附件则删除)
    ignore:同上,但若最小可用位被附件占用,会删除该附件后使用该CID
  • 按新增可用位(从现有最大CID开始)
    grow_skip:为新内容分配从“最大有效CID + 1”起连续递增的CID;发现附件仅跳过不删除
  • 按新增可用位(忽略附件,遇附件则删除)grow_ignore:同上,但遇附件占位会删除附件后使用该CID

PS.关于全部文章Cid重新排序

评论区好友@尚寂新提及到的一起解决"文章pid和评论cid自增数连续"这个问题本地demo已经做出来了,但是在本地测试的时候出现一些问题,还在找合适的办法把历史文章和相关评论数据影响降到最小后尝试发布新版本
风险策略


2025/09/15更新版本

  • 新增风险策略:参考博文
  • 控制台-插件-设置 开启(需要先开启策略)
  • 该策略会直接修改全部文章的cid从1~N重新排列
  • 该策略会删除所有附件并归还占用Cid给文章使用
  • 该策略会导致重新排列前后的同一个文章链接是不一样的内容
  • 该操作不可逆,建议先自行备份数据库,避免在不同环境下出现错误。
    风险策略

2025/09/22更新版本

增强了数据恢复的兼容性,新版本已优化数据导入逻辑,使其能够兼容处理备份文件中包含了非官方字段的情况,从而确保从备份数据到全新Typecho安装的平滑迁移。

插件下载:

Github官方仓库:Article Markdown Backup

WordPress集成Mastodon嘟文之插件篇-Include Mastodon Feed

2025年9月8日 20:50

 

WordPress集成Mastodon嘟文之插件篇-Include Mastodon Feed - 第1张图片

前段时间虽然说是“断网”了,还是偷偷折腾了GoToSocial和Mastodon。GoToSocial和Mastodon都属于通过“ActivityPub”协议实现与多个平台的联邦特性与其他平台互联互通。GoToSocial虽然轻量,但是颜值和功能还是没有Mastodon好,最终选择了Mastodon。至于怎么玩Mastodon,要么像我这样自建,要么就用网上的平台,比如官方的mastodon.social和草莓县m.cmx.im。有兴趣的可以一起玩玩!目前我自建的为@laozhang@suo.si,官方平台和草莓县上分别是@laozhang@mastodon.social和@laozhang@m.cmx.im。为什么自建了还在其他平台注册呢,我怕以后我驾驭不了自建的mastodon,毕竟玩的时间久了数据都都会达到十几个G。或是感觉到单独花二百块钱搞服务器来玩mastodon不值的时候,我可能就把自建的关停掉。

东西玩多了,都想把他们“集成”到一起,比如老张博客首页的“最新动态”就是调取用 Typecho 搭建的https://zhangbo.net老张随笔第一篇内容。有兴趣的可以按《WordPress首页调用typecho方法!》的方法来;又比如在Wordpress的页面里显示Memos内容,方法可以按《巧搬空白Koobai大佬Memos页面,完美与博客整合。》来。

不难看出,在Wordpress里调用memos或是Typecho,都是完全靠代码,毕竟像这样玩的人少更甚是memos等太过小众,没有人折腾这东西,想要实现一些功能必须要靠自己才行。而同时玩Wordpress和Mastodon却大有人在,所以你想实现的功能别人都早就折腾过了。就比如在WordPress中集成Mastodon嘟文。先看效果https://laozhang.org/mastodon.html,这是我在博客里集成了@laozhang@suo.si所发的所有嘟文。那实现这个功能就只是简单的一个插件,Include Mastodon Feed。

1.安装Include Mastodon Feed

直接在后台插件菜单里搜索安装即可,并启用。

2.查找Mastodon 用户ID

不管是自建的Mastodon还是其他平台,把下面的代码内换成自己的用户名和相应的域名后在浏览器内打开,即可显示你的ID

https://yourinstance.com/api/v2/search?q=yourusername@yourinstance.com&resolve=true&limit=5

3.创建mastodon页面

在Wordpress后台新建页面,把下面代码换成自己的ID和相应域名即可。(换成英文中括号)

【include-mastodon-feed instance="YOUR-INSTANCE" account="YOUR-ACCOUNT-ID"】

4.根据自己喜好,自定义CSS

这个可以根据自己的喜好,自己折腾。

 

怎么样,插件版是不是太简单了!无脑操作即可!

 

腾讯云EdgeOne免费无限量CDN流量,良心到哭了(抽兑换码)

2025年7月7日 16:53

这篇文章介绍了如何通过腾讯云EO提供的免费CDN服务,加速个人站点的访问速度,解决因国家地域广、网络环境复杂导致的站点访问慢、流量成本高等问题。作者以自己的博客为例,指出2000人的日访问量消耗约2-3G流量,若使用传统CDN,成本较高,而腾讯云EO的免费版套餐则提供了无限流量和请求数,大大降低了小站点的运营成本。文中详细介绍了获取兑换码、激活套餐、绑定域名、配置CNAME解析以及SSL证书的完整流程,同时提醒用户需合理使用,避免滥用导致封号。

发现轻量写作新宠:Ech0,记录灵感的最佳选择

2025年7月2日 21:26

在快节奏的现代生活中,随时记录灵感、想法或随手笔记变得越来越重要。如果你正在寻找一款轻量、开源、自托管的笔记工具,那么今天我要向大家安利一个宝藏项目 Ech0!它不仅简洁高效,还能让你完全掌控自己的数据。与此同时,也会把它与另一款热门开源笔记工具 Memos 做个对比,看看它们各自的亮点在哪里!

对 Memos 感兴趣的,也可以看看部署轻量私有化备忘录 memos

Ech0 是什么?为什么值得一试?

Ech0 是一款开源自托管轻量级发布平台,专为快速记录和分享个人想法、文字和链接而设计。它的核心理念是简洁、自由和隐私至上,让你专注于内容的创作,而无需被复杂的界面或繁琐的功能干扰。

Ech0 的核心亮点

  • 极简体验,专注创作,Ech0 提供了一个干净直观的界面,写作时几乎没有干扰。你可以快速输入文字、插入链接或上传一张图片,随时捕捉灵感。它的设计初衷是轻量分享,更像是一个简化的朋友圈或微博客
  • 完全开源,数据自控,Ech0 遵循 MIT 协议,完全免费,无广告、无追踪、无订阅。它使用本地 SQLite 数据库存储数据,部署后所有内容都保存在你的服务器上,隐私和安全有保障。备份和恢复也超级简单
  • RSS 支持
  • Docker 部署支持,(基本特性了,如果不支持的话, 就不会考虑了)
  • 轻量设计,性能友好, 相比较而言 Memos,真是好多了

与 Memos 的对比

仅从 Memos v0.18.2 版本为例,后续版本我觉得都是破坏性更新。本文主要从我个人角度简单对比一下

设计理念与使用场景

  • Ech0:更像是一个轻量级的微博客平台,适合快速记录和分享简短的想法、链接或灵感。它的界面极简,强调即时性和轻量分享,类似朋友圈的体验,适合喜欢快速发布内容的用户
  • Memos:定位为全面的知识管理和笔记工具,适合需要系统化整理笔记、支持复杂 Markdown 格式的用户。它更适合长期的知识积累,比如技术笔记、项目管理或个人知识库

功能丰富度

  • Ech0:功能精简,专注于文字、链接和单张图片的快速分享。RSS 支持让内容分发更便捷,但目前只有管理员可以发布内容,适合个人或小范围使用
  • Memos:功能更全面,支持丰富的 Markdown 语法、图片和嵌入内容,甚至包括 API 集成和多数据库支持(SQLite、PostgreSQL、MySQL)。它还提供标签、过滤器等功能,适合需要复杂笔记管理的用户

数据与隐私/部署与维护

两者都一样开源,且都支持 Docker

社区与扩展性

  • Ech0:社区较小,项目主要由作者维护(但是我觉得还是很有前景的)
  • Memos:社区活跃,功能更新频繁

谁更适合你?

  • 选择 Ech0:如果你想要一款极简、轻量的工具,专注于快速记录和分享灵感,或者希望搭建一个类似朋友圈的个人内容平台,Ech0 是绝佳选择。它的低学习曲线和极简设计特别适合个人用户或小团队
  • 选择 Memos:如果你需要一个功能更丰富、支持复杂笔记管理的工具,或者计划长期维护一个知识库,Memos 会更适合你。它的 API 和多平台支持也更适合技术爱好者或需要集成其他工具的用户

对于我而言,短时间内 Ech0 还没放替代我的 Memos,数据迁移令人头大。但是期待 Echo 干翻 Memos,支持从 Memos 导入、支持 OIDC 等等。

项目

项目地址: https://github.com/lin-snow/Ech0
Demo 地址: https://memo.vaaat.com/

值得一提的是,Ech0 还有一个二次开发版本 Ech0 Noise(高度完善的轻量说说笔记系统及 memos 的开源替代品),它在原版基础上增加了更现代化的 UI 设计(例如毛玻璃背景)和一键数据备份/恢复功能,视觉和体验上更像一个精致的社交平台。如果你喜欢更美观的界面,可以尝试这个版本。

听说还支持从 Memos 导入到 Noise, 没实操过

项目地址: https://github.com/rcy1314/echo-noise
Demo 地址: https://note.noisework.cn/

如何开始使用 Ech0

原版部署

  • docker-compose.yaml
services:
  ech0:
    image: sn0wl1n/ech0:latest
    # image: ccr.ccs.tencentyun.com/k7scn/ech0
    container_name: ech0
    ports:
      - '6277:6277'
    volumes:
      - ./ech0/data:/app/data
    environment:
      - JWT_SECRET="Ooghooch1Oojanguz5dei1ahw0IPoh9d" 
    restart: always
  • 启动完成后,访问 ip:6277 即可使用,首次使用注册的账号会被设置为管理员(目前仅管理员支持发布内容)
  • JWT_SECRET 推荐修改为随机字符串 pwgen 32 1
  • 支持自定义配置文件,需要挂载到 /app/data/config/config.yaml, 具体可以参考官方示例

Noise 二开版本部署

  • docker-compose.yaml
services:
  noise:
    image: noise233/echo-noise
    # image: ccr.ccs.tencentyun.com/k7scn/echo-noise
    container_name: noise
    ports:
      - '1314:1314'
    volumes:
      - ./noise/data:/app/data
    restart: always
  • 启动完成后,访问 ip:1314 即可使用,默认账号信息 admin/admin
  • 应该也是支持自定义配置文件, 具体可以参考官方示例

PS: demo 没跑起来, 提示账号密码错误 😂

截图

原版

发布

魔改

写在最后

无论你是想快速记录生活中的灵感,还是需要一个完全属于自己的内容分享平台,Ech0 都能以它的简洁和高效打动你。相比功能更全面的 Memos,Ech0 更像是一个轻盈的创作小助手,让你专注于思想的流动。如果你追求极简和隐私,Ech0 绝对值得一试!快去部署你的 Ech0,记录下你的每一个灵感火花吧! 你更喜欢 Ech0 的轻量分享,还是 Memos 的全面管理?欢迎在评论区分享你的想法!

最近感冒了状态不是很好, 没太多精力去看,7 月争取恢复一下节奏,给大家带来更多更好玩的开源软件。


关注我的微信公众号,更多技术吐槽和干货!

  •  

我的 WordPress 博客主题与插件设置分享

2025年6月17日 00:58

首先,感谢大家一直以来对我这个小破站的关注与支持♥️。自博客成型以来,陆陆续续有网友来询问我博客主题的相关设置。于是我打算在这篇文章中简单拆解一下目前的主题配置,希望能对同为新手的朋友有所帮助,也希望你能在这个过程中摸索出属于自己的博客风格。

主题

我使用的是免费版的 Blocksy 主题,只需在 WordPress 主题商店中搜索 “blocksy” 即可下载安装。

接着,需要安装一个名为 Blocksy Companion 的插件,同样可以在 WordPress 插件商店中直接搜索并安装。

Blocksy 是一个设置简便且具备较高自定义性的主题。如果要详解我如何从“默认”样式调整为现在的样式,过程会略显复杂,加上时间久远,具体配置我也记不太清了 😅。你可以参考 Dayu 的 这篇文章,导入我准备的 dat 文件即可快速还原样式。

虽然我和 Dayu 都使用了 Blocksy 主题,但呈现出的风格各有不同。如果你更喜欢他博客的设计风格,也不妨试试看 😉

封面

有不少朋友问过我博客文章的封面图是怎么做的,其实每一张封面都是我用 Keynote(苹果自带的演示软件)手动制作的。

我会先制作一个模板文件,之后每次只需修改标题并替换封面中的物体图片即可。

封面中的物体图像,若不是软件图标或商品图,一般来自 Unsplash 这类提供免费图片的站点,或是通过 AI 生成。之后我会用 PhotoShop 或豆包工具抠出主体图像,替换进 Keynote 模板中,整个流程算是比较简单的。

主题相关插件

以下是我目前博客中使用的一些插件:

字体

本博客采用小米的 MiSans 字体。

只需在 自定义额外 CSS 中添加以下代码,即可生效:

/* 字体相关css */
@import url("https://font.sec.miui.com/font/css?family=MiSans:400,450:Chinese_Simplify,Latin&display=swap");

@font-face {
font-family: "MiSans", sans-serif;
font-weight: normal;
font-style: normal;
}
*:not([class*="icon"]):not(i) {
font-family: "MiSans" !important;
}

其他自定义 CSS

为了实现当前博客的视觉效果,我还在 自定义额外 CSS 中加入了一些样式代码,其中相当一部分是配合深色模式所使用的。

.stackablelink a{
color: #475671
}

/* 说说相关css */
.atk-content {
color: var(--theme-palette-color-3);
}

a.commentsLink{
color: var(--theme-palette-color-3);
border: 1px solid currentcolor;
border-radius:8px;
}

div.atk-comment-count.atk-dropdown-wrap {
color: var(--theme-palette-color-3);
}

button.atk-send-btn {
border-radius: 8px !important;
}

div.atk-main-editor {
border-radius:8px;
}

ul.atk-dropdown.atk-fade-in{
border-radius:8px !important;
}


/* 黑色模式下,搜索页面背景色 */
html.dmb-html-dark [data-header*="type-1"] #search-modal {
  background-color: rgba(71, 79, 92, 0.98);
}

/* 黑色模式下,好物页面文字 */
html.dmb-html-dark div.goods-exhibition-title {
  color: #475671;
}

html.dmb-html-dark div.goods-exhibition-description {
  color: #475671;
}

/* 黑色模式下,应用页面文字 */
html.dmb-html-dark div.app-name {
color: #475671;
}

html.dmb-html-dark p.app-description {
color: #475671;
}


/* 黑色模式下,书影音页面文字 */
html.dmb-html-dark button.neodb-nav-item {
color: #ffffff;
}

html.dmb-html-dark button.neodb-type-item {
color: #ffffff;
}

html.dmb-html-dark button.load-more-button.button {
color: #ffffff;
background-color: #293241;
}

html.dmb-html-dark .neodb-title a {
color: #ffffff !important
}


/* 黑色模式下,友情链接页面文字 */
html.dmb-html-dark a.latest-post-title {
color: #475671
}

html.dmb-html-dark div.friend-card {
background-color: #293241
}

/* 文章目录的样式 */
.rtoc-mokuji-content.frame2::before {
  border-radius: 8px;
}
button.rtoc_open_close.rtoc_open {
border-radius: 8px;
color: #475671;
}

/* 回复按钮样式 */
a.comment-reply-link {
border-radius: 8px !important;
}

#cancel-comment-reply-link {
border-radius: 8px;
}

我的 WordPress 博客主题与插件设置分享最先出现在Jack's Space

1+1>2的课程安排

2025年6月13日 10:27

大二我们第一次有食品专业课程的时候,我们就已经接触到综述这个东西。那个学期我们有两门很重要的课程,一门是食品化学,另外一门是食品微生物学。这两门课程可以这么说,是食品专业最基础最重要的课程。这两门课程是往后那些高楼大厦的基础,这两门课程之前我们学的基本都是通用的基础课程,还没有接触过专业课。这两门课程对食品专业来说又是通用的基础。

在这之前我学过高等数学、线性代数、概率论、大学物理、物理化学、无机化学、有机化学、生物化学。我感觉这些课程是我们学习食品化学和食品微生物学的基础。到底有多大用处呢?我不知道,但我觉得这是很有必要的。老师在上面说专业课某些原理的时候,因为有前面那些课程的支持,所以我们就不需要深入进去了。

我印象非常深刻的是食品化学刚开课没多久,老师就已经把综述的作业布置给了我们。综述是什么东西?我们甚至没看过专业文献。老师没有要求我们写专业文献,因为没有看过,我们写不出来,但综述这个东西就强迫着我们必须去看你的那个主题相关的专业文献。专业文献去哪里找?期刊杂志当然有,纸质的电子版都有,但综述这门作业每个学生都有不同的主题,所以要有针对性。显然去图书馆翻杂志就不太现实了,当然你也可以一直去翻那些纸质。看专业文献不是一本是一堆,很多期,除非你一直都有翻,一直都翻很多,否则可能很难撞上你感兴趣的,或是你需要收集数据的。所以那个学期,我们也开了一门我觉得非常有用的课程,叫文献检索。如果没有文献检索这门课程,食品化学给我们布置了这么一个任务的时候。我们简直不知道该怎么活了。这两门课程同时出现,又有这么个任务的,我感觉出现了1+1>2的效果。因为有文献检索这门课程的技术指导,所以我们知道该去哪里该用什么方法找我们想要的资料,正是因为我们有这么一个食品化学综述的奋斗目标,所以我们就更加会努力地通过文献检索那门课程学回来的技术去试验、熟悉。最终的结果就是我们找到了那个综述PPT要求我们收集的数据。因为有大量的练习,所以当文件检索课程需要我们完成某些课题的检索任务,我们可以快速且得心应手。食品化学这门课程,我们一个大班上课,超过100人。理论上每个人都要拿着自己那篇综述的PPT上台去讲,但实际上我们根本没有那么多时间。我就是那个没有机会上去讲的人,最终只能把PPT交上去结束任务。我觉得有幸上讲台讲自己PPT的那些学生虽然很慌、虽然很彷徨、虽然很无助,但这的确是一个非常好的锻炼机会,如果你在大二,如果你在第一次接触专业课的时候就已经经历过这样的事情,毕业答辩的时候,根本没什么好慌的。你做自己那个PPT的时候,你是按照你的思路去整理资料、得出数据、得出结论。这个时候食品化学的那个老师就会像是毕业答辩时那样挑刺,跟你针锋相对,为什么是这个结论?凭什么得出这个结论?收集的这些数据到底有没有代表性,这样的现象到底能不能得出这样的结论?所以综述还真不仅仅是把别人的数据最终拼凑出一个东西这么简单。如果你不去思考其中的缘由,必定会被那门课的老师喷得你无地自容。食品化学那门课,我有两个老师。她们轮流上课,两个老师都姓吴,其中一个姓吴的老师,我们称之为灭绝师太。如果你的综述上台讲的时候没被她喷得一塌糊涂,就算非常成功了。

我敢肯定,不是所有学校的食品化学课程都会有这样的安排。我觉得,我读书的时候,华农这个食品化学课程的这种要求非常接地气、非常实在,并且影响我一辈子。

2025年7款平替 AList 私有部署的网盘软件

2025年6月12日 11:57

7款平替 AList 私有部署的网盘软件
7款平替 AList 私有部署的网盘软件

AList 是一个支持多存储的文件列表/WebDAV程序,使用 Gin 和 Solidjs。它可以将 40 多款主流网盘聚合到一个网页上,进行文件管理、视频播放等操作。

AList 是一个开源项目,已经卖给了其他公司,现在最新可用收购前版本是 3.40.0

下图来源

作者TG回应
作者TG回应

吃瓜地址:Alist 收集用户信息提交记录Alist被卖 Github用户讨论Alist被卖 V2讨论Alist被卖 V2讨论

今天主要推荐几款可以平替 AList 私有部署的网盘软件。

如何取消对 alist 的授权

Alist 取消授权 Github讨论合集

  1. 百度网盘(来源: https://pan.baidu.com/union/doc/Jl0j9pza3
  2. 百度网盘App:我的 - 设置 - 帐号管理 - 授权管理 - Alist - 解除授权
  3. 阿里云盘App:我的 - 设置(右上齿轮) - 隐私设置 - 授权管理 - Alist - 解除授权
  4. OneDrive 解除授权:https://account.live.com/consent/Manage
  5. 115APP - 生活 下滑 -账号与安全 - 多端登录管理 - 第三方登录
  6. 联通云盘 - 在网页查询登录账号 - 以后建议 按照 教程抓包登录
  7. 一刻相册 头像-应用设置-账号管理-授权管理
  8. 谷歌(网盘和相册)- 管理您的谷歌账号 - 安全性 - 您与第三方应用和服务的关联 - 有权访问账号 - 选择对应的产品
  9. Dropbox - 设置 - 应用 - 查看 Dropbox 内容

Alist 变形

go-drive

go-drive 是一款简单的云驱动器映射网络应用程序,支持本地、FTP/SFTP、S3、OneDrive、WebDAV 和 Google Drive。

目前支持的云存储

  • 本地文件
  • FTP
  • SFTP
  • WebDAV
  • S3
  • OneDrive (支持世纪互联版及 SharePoint 站点)
  • Google Drive
  • Dropbox
  • 七牛云

功能

  • 文件管理

    • 上传,下载,复制,移动,重命名,删除
    • 拖拽/粘贴上传文件(夹)
    • 拖拽复制/移动/挂载
    • 支持不经过服务器上传及下载,减少服务器流量消耗(仅部分云存储支持,详见下文)
    • 上传文件支持断点续传

    按住 Ctrl, Shift 可进行多选

    从系统复制文件(夹)后,按 Ctrl + V 直接粘贴上传

    按住 Alt 并点击文件,可直接下载文件

    拖拽文件(夹)时,按住 Ctrl 进行复制,按住 Alt 创建路径挂载(仅管理员可用)

  • 文件打包下载

  • 基于用户/组的权限控制

    支持为某目录或文件分配权限,支持限制到用户或组

  • 图片浏览(PhotoSwipe)

  • 音乐播放(APlayer)

  • 文本编辑(CodeMirror)

  • 代码编辑(Monaco Editor)

  • 展示缩略图

    支持自定义缩略图生成策略,详见缩略图配置

  • 路径挂载

    允许管理员将某文件/目录挂载到任意位置

  • Drive 管理界面

  • 文件搜索

  • 通过 WebDAV 访问

  • 定时任务(gocron)

    支持简单的任务类型(复制/移动/删除)和 JavaScript 脚本类型

go-drive 预览图
go-drive 预览图

红枫云盘

红枫云盘官网地址) 是一款无服务端的多协议云盘文件上传和管理软件,提供了文件上传、预览、操作、同步备份等功能

支持的存储

  • 本地文件
  • FTP
  • SFTP
  • S3
  • SMB
  • Webdav
  • Alist
  • Github
  • Github Release
  • Mirror(镜像站,支持文件查看和下载,支持格式:清华源、阿里源或者其他 NGINX 文件列表源)
  • 又拍云

功能

  • 支持文件列表查看/复制/移动/删除/重命名/上传/下载
  • 支持桌面端拖拽上传(文件或者文件夹)
  • 支持文件多选及操作
  • 支持文件列表信息缓存
  • 支持回收站
  • 支持视频、音频、图片和文本文件的预览
  • 支持文件加密和压缩
  • 支持各存储之间的备份和同步(测试中)
  • 支持多语言(中文、英文)
  • 支持 Web, Android, MacOSWindows

红枫云盘预览图
红枫云盘预览图

Zdir

Zdir前台演示帮助文档) 是一款集文件索引、在线预览与分享于一体的多功能私有存储程序,支持 WebDAV 和离线下载。

  • 挂载第三方存储(S3/WebDAV/SMB)(需要付费)
  • 文件索引
  • 文件预览(支持图片、文档、音乐、视频等预览)
  • 私有文件
  • 私有文件分享
  • 文件管理(上传、下载、删除、重命名)
  • 图片预览
  • 图床模式
  • API支持
  • WebDAV服务端支持(需要付费)
  • 全局文件搜索
  • 前后台一体化
  • 文本编辑
  • 音乐列表模式
  • 文件移动、复制
  • 离线下载
  • 多语言
  • 在线解压
  • HTML小工具
  • Ofiice预览

Docker部署命令

docker run --privileged -d --name="zdir" \
  -v /opt/zdir/data:/opt/zdir/data \
  -v /data/public:/opt/zdir/data/public \
  -v /data/private:/opt/zdir/data/private \
  -p 6080:6080 \
  --restart=always \
  pub.tcp.mk/helloz/zdir

Zdir功能图
Zdir功能图

Zdir 会员图
Zdir 会员图

File Browser

File Browser官网地址)国外作者开源的一款私有存储软件,支持多用户且界面友好,但是不支持挂载第三方存储,适合管理和分享本地文件。

filebrowser 提供了一个指定目录下的文件管理界面,可用于上传、删除、预览、重命名和编辑文件。它允许创建多个用户,每个用户可以拥有自己的目录。它可作为独立应用程序使用。

File Browser预览图
File Browser预览图

FileBrowser Quantum

FileBrowser Quantum 最好的免费自托管网络文件管理器,是File Browser开源项目的一个大型分叉。

  1. 支持多种来源
  2. ✅ 登录支持 OIDC、密码 + 2FA 和代理。
  3. ✅ 全新用户界面
  4. ✅ 通过 config.yaml 配置文件简化配置。
  5. ✅ 超高效索引和实时更新
    • 键入时实时显示搜索结果。
    • 用户界面实时监控和更新。
    • 搜索支持文件和文件夹大小以及各种过滤器。
  6. ✅ 更好地浏览列表
    • 更多文件类型预览,如办公和视频文件预览
    • 即时切换视图模式和排序顺序,无需重新加载数据。
    • 显示文件夹大小。
    • 导航时会记住上次滚动的位置。
  7. 开发人员应用程序接口支持
    • 能够创建长期有效的 API 标记。
    • /swagger 端点为启用 API 的用户提供了有用的 Swagger 页面。

Zfile

Zfile演示地址) 是一款使用JAVA开发的在线目录展示程序,支持将本地文件、FTP、SFTP、S3、OneDrive 等存储在网站上展示并浏览,Zfile分为免费的开源版本和功能更强大的捐赠版本。

  • 支持本地存储、Amazon S3、阿里云、腾讯云、又拍云、FTP、SFTP、OneDrive、SharePoint 等存储类型
  • WebDav 需要付费

Zfile 功能图
Zfile 功能图

dirlist

dirlist预览地址)是彩虹大佬用 PHP 开发的目录列表程序,无需数据库,安装简单,使用方便。dirlist 功能上稍微要弱一些且不支持挂载第三方存储,如果用不到这些功能的话 dirlist 也是一个不错的选择。

  • 在线预览图片、视频、音频、文本文件、Markdown文件、Office文档等
  • 自动识别目录下的README.md文件并展示(类GitHub)
  • 后台管理可设置网站标题、公告、底部代码等信息
  • 文件搜索功能,支持缓存文件索引
  • 自带全新的文件管理功能
  • 支持中文文件名编码设置,解决乱码问题
  • 支持安装在子目录

dirlist预览图
dirlist预览图

Cloudreve

Cloudreve 可以让您快速搭建起公私兼备的网盘系统。Cloudreve 在底层支持不同的云存储平台。可以使用 Cloudreve 搭建个人用网盘、文件分享系统,亦或是针对大小团体的公有云系统。

  • 支持使用本机、从机、七牛、阿里云 OSS、腾讯云 COS、华为云 OBS、S3、OneDrive 作为存储端,上传/下载均支持客户端直传,无需服务器中转。
  • 将磁力链、种子文件、下载链接交给 Cloudreve 处理,Cloudreve 会在服务端下载您指定的文件,并自动上传到对应的存储端中。您还可以通过从机 Cloudreve 节点,将不同的离线下载和转存任务分配到不同服务器处理,减轻主节点的负载压力。

Cloudreve 本身是一款“网盘”而非“其他网盘的管理器”,其所支持的存储提供商大多为公有云的存储专用产品,而非针对个人消费者的网盘。下面情况并不适合使用 Cloudreve:

  • 需要用 Cloudreve 管理多个网盘上的文件;
  • 对于 Cloudreve 存储的文件,我同时也会使用其他文件管理器一起管理;
  • 想把其他网盘存储服务“再分发”给我的用户使用;

Cloudreve 预览图
Cloudreve 预览图

监控升级

2025年6月10日 15:12

监控升级

  近期将家中服役十年有余的老监控升级,此次更新采用poe网线非传统同轴线,需要旧线拆除再和更换超五类网线,以前的线是走外墙入了Pvc白管,这里涉及到一些高空作业问题。在闲鱼挂单问了惠州范围的高空师傅,我预估的工作时间在3小时左右,并声明我会在现场帮忙的前提下,给我的报价在400-1500之间,闲鱼一直挂着300我的心理价也是这个价位,我想着100元一小时差不多,挂了有大半个月没管他,主机和摄像头早一年前就已经准备好。

  某天一位小哥联系我,我具体跟他说了作业内容,4321每层楼放两根网线,把旧线剪掉作废他说可以。为什么放两根,因为这些年来租户用的都是四芯线只有百兆宽带,借此机会把租户网络也升级了。

  与其约好时间,那天中午一点到我家开始做前期工作,看他拉着大包小包工具麻绳,到楼顶转了一圈没找到较好的支撑点,七楼可以,光是前期工作就做了一小时,换上专用服装和各种固定扣件固定两个点,安全第一不能马虎。楼与楼之间间隔太小,只能从一楼往上爬到五楼,先把白管内的同轴线抽出,每层楼弯头居然用胶水粘死了,用工具撬开部分。

  开始往每层楼放线与师傅商议尽最少次数往返做最快速度,由于间隔太小见他往上爬都很费劲。两捆不同颜色网线开始往下放,先放一楼两根一前一后门,在接着放二三四楼,期间遇到N多插曲影响了更换进度,我则在楼梯间跑上跑下已数不清次数,尽量让他少上下往返。最终完工比预估的时间多了一个多小时,事先说好300元他虽然全程都没有抱怨做事也稳妥,但自己心里过意不去,结账时到店里买了包烟加一顿饭钱给他。一位贵州小伙,做高空很多年跟公司签了合同有固定作业时间,是做半个月休半个月,闲不住时出来接下单。

  第二天起来全身酸痛,尤其是小腿。干了亡约车后身体缺乏锻炼稍微做点事都气喘吁吁。自己花了大半天时间给网线制作水晶头,在将旧的摄像头更换下来,一切整备就绪开机,每个镜头都能一次点亮,现在可以随时随地在手机上查看监控回放,摄像头均带有录音功能,Nice!!只是硬盘还是用回之前的1Tb..录像只保存10天左右,用坏了在换大一点硬盘。


域名续费

  月初已收到域名续费通知,一直没去处理。现好不容易上来冒个泡除下草,一起把这重要事情处理了。

  •  

Docker 部署 Mastodon

使用 Docker 部署 Mastodon 实例时,需注意选择合适的用户名和域名,因为一旦加入联邦宇宙,这些信息将无法更改。部署前应确定实例规模,以便进行相应优化。基本步骤包括准备 VPS、安装 Docker 和相关插件,并配置必要的文件如 compose.yml 和 .env.production。在设置过程中,要根据提示输入信息并生成环境变量文件,确保正确配置数据库、Redis 及 Elasticsearch 等服务。此外,还需通过反向代理工具如 Nginx 或 Cloudflare Tunnels 来管理流量,并创建管理员账号以维护实例。最后,通过调整目录权限和 SSL 配置来保证安全性与稳定性。

  •  

Jefrss Simple 订阅插件 Wp-rss 朋友圈订阅插件

2025年3月1日 01:54

Jefrss Reader Lite1.03 WP-Rss博友圈展示插件

该版本为单独新作的插件,我的邻居页面插件是旧版的,一直没有换,推荐使用现在这个版本的插件。

 

展示短代码支持六种主题展示方式,6个短代码展示主题分别借鉴(抄袭)6位博友。参考在主题中心有提示,请大家多多关注访问6位提供思路博主。

用户主题展示。

杂志主题  FindBlog

现代主题  @惟康   @从良未遂

默认主题  文案笔记姐

网格主题 耳朵的主人

下载地址:jefrss-simple_AYweS.zip

如果有使用的问题,可以邮件或者评论区沟通。

============ =================

2025年3月4日更新

解决插件停用报错

解决添加异常

解决后台时间和前台展示时间不同步问题

请点击下载插件然后升级

==============================

2025年3月6日更新

解决插件链接验证权限问题*编辑文章冲突

解决站点JS冲突问题

==============================

2025年3月10日

重新打包文件上传,修改了下载链接的文件名称,之前一直上传的都是旧版本,导致博友安装并不是最新版的。

 

===============================

如果有问题请邮件或者评论,由于个人时间问题,回复或者更新较慢请见谅。

  •  

RSS to Email使用教程

2025年3月1日 00:01

一个简单的功能介绍和使用说明:

1、注册登录后自动进入个人首页,有账户类型、余额、消费记录、充值记录和Newsletter列表。

2、点击创建,进入创建页面,可创建自己的RSS转Newsletter任务:

  • 名称:任务的名称,自定义
  • 简介:Newsletter的简介
  • RSS URL:RSS地址
  • 发送频率:抓取RSS发邮件的频率,支持每5分钟、每天、每周
  • 是否启用:勾选则会按设置的频率发邮件,否则不发

3、Newsletter详情页(比如拾月通讯

4、Newsletter详情的「订阅组件代码」,提供一段代码可以贴在自己的博客中,支持读者直接在博客订阅。

点击邮件订阅,弹出浮窗,填写邮箱即可订阅,且支持RSS地址订阅。(此交互灵感来自typlog),点击本站菜单栏「订阅」观看效果。

5、我非常在意数据所有权,所以Newsletter的订阅列表可以方便的导入和导出

  •  

新项目RSS to Email上线

2025年2月28日 23:47

上一篇拾月推荐(第38期)中提到我在做一个实验,其实是测试我的新项目:RSS to Email

我的博客同时支持RSS和Email订阅,其中Email订阅的服务换过很多次,或是因为价格,或是因为服务关停。

2023年4月,我用Python写了一个RSS转Newsletter的脚本,实现了自给自足,每次文章发布后,执行一次脚本就能发送邮件。其中Email列表使用Notion存储,Email地址收集使用NotionForm。

上述方案够用但不完美。比如:

  1. NotionForm收集Email地址无法去重
  2. 取消订阅无法自动化完成,需要收集Email地址,我再手动从Notion中删除

因此,一起想自己做个网站实现RSS转Email,今天春节趁着DeepSeek大爆发,顺势借AI编程做了个框架出来,之后陆陆续续优化一些细节,已经达到基本可用的状态,决定正式上线开放注册。

产品地址:https://rsstoemail.skyue.com

本产品需要付费使用,原因有二:

  1. 网站运营有成本,且邮件发送越多成本越高,无边际递减效应
  2. 我想赚钱

同时定价上有三个基本原则:

  1. 邮件发送量小的用户,使用成本低 --> 按量付费
  2. 与用户建立长期信任关系 --> 给予种子用户&老用户更优惠的价格
  3. 让用户没有后顾之忧 --> 任何时候都可以退款

具体来说:

  1. 标准账户:每自然年内前20000封邮件0.002元/封,超过部分0.0015元/封
  2. VIP账户:所有邮件均0.0015元/封

VIP用户即为种子用户和老用户:

  • 种子用户:前100位付费用户
  • 老用户:网站刚上线,暂未定义

同时,考虑网站刚上线,为降低用户试用门槛,前100位注册用户赠送5元。

最后,本站尚未接入支付系统,因为充值需要走支付宝转账,具体参考网站定价页面的充值说明。充值退款均为手动处理,如有延误还请谅解。

  •  

Gotosocial 通过API获取用户Timeline

2025年2月17日 10:14

在测试Gotosocial 时发现
通过链接${Host}/api/v1/accounts/${userId}/statuses?limit=${limit}&exclude_replies=true&only_public=true
访问不到数据,提示Unauthorized: token not supplied需要提供token.

mastodonpleroma 还是可以正常访问的.

为了更好的解决这个问题(其实之前通过cloudflare workers已经可以解决了)
参见 https://www.imsun.org/archives/1643.html#%E9%80%9A%E8%BF%87Cloudflare-Workers%E8%8E%B7%E5%8F%96json%E6%95%B0%E6%8D%AE

这次使用vercel拿到数据.

https://github.com/jkjoy/Gotosocial-API-Vercel

主要代码是python

直接部署在vercel

环境变量

HOST=Gotosocial实例地址
USER_ID=Gotosocial用户ID
TOKEN=access_token

演示 https://gts.ima.cm


本文由 老孙博客 原创发布

转载请注明出处

  •  
❌