普通视图

Received today — 2025年6月8日Blog

子比主题新增一个友圈动态页面

2025年6月7日 23:45

逛博客的时候看到了一个好玩的东西(友圈),说人话就是新建一个单独的页面用于展示已加入友情链接博友的动态,这个东西是在品味苏州博客中看到的,原文教程很详细,我按步骤直接复制粘贴没搞成功,但大概思路是知道的,于是在小C的帮助下经过一天的时间重新搞了一套出来。目前各项功能已基本调试完毕因此把教程发出来。

友圈动态项目简介

核心功能

  1. RSS 聚合展示:从多个 RSS 源抓取内容并按源分类显示。
  2. 每个源展示最新 6 条文章,按时间倒序排列。
  3. 并发抓取:采用 curl_multi 并发请求优化加载速度。
  4. 缓存机制:使用本地缓存文件(feed_cache.json)减少对源站的频繁请求。

样式特性

  • 使用 Flexbox 实现响应式布局,每个 RSS 源为一个块(.rss-block)。
  • 每个块包含:
    • 左侧头像与站点名(.rss-left
    • 右侧文章列表(.rss-right

布局结构

  1. 卡片式设计:每个源用卡片风格独立展示,配有边框和阴影。
  2. 头像 + 名称居中显示:头像是圆形,居中对齐,站点名位于头像下方。
  3. 文章列表一行一条,清晰简洁。
  4. 文章后标注完整时间(含年份):格式为 (YYYY-MM-DD)
  5. 移动端适配良好
    • 小屏下卡片垂直排列,头像居中缩小;
    • 字号适当缩小;
    • 留有顶部边距改善视觉体验。

准备页面所需代码

首先第一步,在主题page目录中新建一个rss.php文件,名字可根据自己喜好去定义但一定要放在page目录中,之后将代码粘贴进去:

点击显示代码
<?php
/*
Template Name: RSS 朋友圈
*/

date_default_timezone_set('Asia/Shanghai');
get_header();
require_once(ABSPATH . WPINC . '/class-simplepie.php');

echo '<p style="color: red; font-size: 18px; margin: 10px 0 10px 0; text-align: center;">以下友情链接网站最新内容每 2 小时获取更新一次</p>';

$transient_key = 'rss_circle_final_style';
$cache_duration = 7200; // 缓存 2 小时

function fetch_all_rss_items($rss_sites, $timeout = 20) {
    $mh = curl_multi_init();
    $chs = [];
    $results = [];

    foreach ($rss_sites as $i => $site) {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $site->link_rss,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_USERAGENT => 'Mozilla/5.0',
        ]);
        curl_multi_add_handle($mh, $ch);
        $chs[$i] = $ch;
    }

    $running = null;
    do {
        curl_multi_exec($mh, $running);
        curl_multi_select($mh);
    } while ($running > 0);

    foreach ($chs as $i => $ch) {
        $body = curl_multi_getcontent($ch);
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);

        if (strlen(trim($body)) < 100) continue;

        $feed = new SimplePie();
        $feed->set_stupidly_fast(true);
        $feed->set_raw_data(ltrim(preg_replace('/^\xEF\xBB\xBF/', '', $body)));
        $feed->set_useragent('Mozilla/5.0');
        $feed->enable_cache(false);
        $feed->init();

        if (!$feed->error()) {
            foreach ($feed->get_items(0, 6) as $item) {
                $results[] = (object)[
                    'title' => $item->get_title(),
                    'link' => $item->get_link(),
                    'date' => $item->get_date('U'),
                    'source_name' => $rss_sites[$i]->link_name,
                    'source_link' => $rss_sites[$i]->link_url,
                    'source_avatar' => $rss_sites[$i]->link_image,
                ];
            }
        }
    }

    curl_multi_close($mh);
    return $results;
}

$data = get_transient($transient_key);
if ($data === false) {
    global $wpdb;
    $rss_sites = $wpdb->get_results("SELECT * FROM wp_links WHERE link_rss != '' AND link_visible = 'Y'");
    $data = fetch_all_rss_items($rss_sites);
    usort($data, fn($a, $b) => $b->date <=> $a->date);
    set_transient($transient_key, $data, $cache_duration);
}

?>

<style>
.rss-grid {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 0 10px;
    box-sizing: border-box;
    width: 100%;
}

.rss-block {
    width: 100%;
    max-width: 1050px;
    border: 1px solid #ddd;
    padding: 20px;
    background: #fafafa;
    border-radius: 8px;
    display: flex;
    gap: 20px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
    font-size: 16px;
    box-sizing: border-box;
}

.rss-left {
    width: 100px;
    text-align: center;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
}


.rss-left img {
    width: 80px;
    height: 80px;
    object-fit: cover;
    border-radius: 50%;
    display: block;
    margin: 0 auto 10px;
    background-color: #f0f0f0;
}

.rss-placeholder {
    width: 80px;
    height: 80px;
    line-height: 80px;
    border-radius: 50%;
    background: #eee;
    color: #999;
    font-size: 14px;
    margin: 0 auto 10px;
}

.rss-name a {
    display: block;
    text-align: center;
    font-weight: 600;
    font-size: 17px;
    color: #222;
    text-decoration: none;
    margin-top: 5px;
}

.rss-name a:hover {
    text-decoration: underline;
}

.rss-right {
    flex: 1;
}

.rss-posts {
    list-style: none;
    padding-left: 0;
    margin: 0;
}

.rss-posts li {
    margin-bottom: 10px;
    font-size: 15px;
}

.rss-posts a {
    text-decoration: none;
    color: #222;
}

.rss-posts a:hover {
    text-decoration: underline;
}

.rss-date {
    color: #999;
    font-size: 0.85em;
    margin-left: 6px;
}

/* 移动端适配 */
@media (max-width: 768px) {
    .rss-block {
        flex-direction: column;
        padding: 15px;
    }

    .rss-left {
        width: 100%;
        text-align: center;
        margin-bottom: 10px;
    }

    .rss-left img,
    .rss-placeholder {
        width: 60px;
        height: 60px;
        line-height: 60px;
        margin-top: 10px;
    }

    .rss-name a {
        font-size: 16px;
    }

    .rss-posts li {
        font-size: 14px;
    }
}
</style>

<?php
if (empty($data)) {
    echo '<p style="padding:20px; text-align:center; color:#999;">暂无RSS数据,请稍后刷新或稍候再试。</p>';
} else {
    // 分组
    $grouped = [];
    foreach ($data as $entry) {
        $grouped[$entry->source_link]['info'] = [
            'name' => $entry->source_name,
            'link' => $entry->source_link,
            'avatar' => $entry->source_avatar,
        ];
        $grouped[$entry->source_link]['items'][] = $entry;
    }

    echo '<div class="rss-grid">';
    foreach ($grouped as $source) {
        echo '<div class="rss-block">';
        echo '<div class="rss-left">';
        if (!empty($source['info']['avatar'])) {
            echo '<img src="' . esc_url($source['info']['avatar']) . '" alt="avatar">';
        } else {
            echo '<div class="rss-placeholder">No Avatar</div>';
        }
        echo '<div class="rss-name"><a href="' . esc_url($source['info']['link']) . '" target="_blank">' . esc_html($source['info']['name']) . '</a></div>';
        echo '</div>';
        echo '<div class="rss-right"><ul class="rss-posts">';
        foreach ($source['items'] as $item) {
            echo '<li>';
            echo '<a href="' . esc_url($item->link) . '" target="_blank">' . esc_html($item->title) . '</a>';
            echo '<span class="rss-date">(' . date('Y-m-d', $item->date) . ')</span>';
            echo '</li>';
        }
        echo '</ul></div>';
        echo '</div>';
    }
    echo '</div>';
}

get_footer();
?>
图片[1] - 登山亦有道

添加链接和订阅地址

WordPress可以很方便的管理链接,并且还可以在详情里边填写链接对应的订阅地址,这里不填程序会尝试自动抓取订阅地址,如果抓取不到还会再主页链接的基础上添加/feed进行保底尝试,因此这一块基本不用担心,但遇到一些特殊的博客程序还是建议手动去填写,因为这次调试中就遇到了不规则命名(/rss2.xml)导致无法正常获取RSS数据的情况。

图片[2] - 登山亦有道

新建友圈页面

新建一个空白页面选择对应的模板并对链接进行命名即可,点击发布如果没什么意外,到这一步就算大功告成了,具体显示效果可以看我的友圈动态页面,我对他进行了强迫症等级的样式优化,现在链接中的页面是迄今为止最完美的一个样式,没有花里胡哨,只有简洁。

图片[3] - 登山亦有道
Received yesterday — 2025年6月7日Blog

服务器主板

2025年6月7日 13:45

昨天测试安装服务器主板,装好后,开机按钮按了没反应,测试电源线和排插都是正常的,还好主板上有一个很小的黑色按钮,可以按开机也正常开机了,说明可能是机箱开关的问题,但开机和重启两个键都不能用,硬盘灯和电源灯显示是正常的,总不可能两个键同时坏吧。

于是我拔了跳线,用螺丝刀短接也不能正常开机,感觉可能是主板或静电的问题。最后换了桌面开关发现正常了,再短接也正常了,说明刚才可能是我短接没有操作好,真的是机箱的开机和重启两个键同时坏 ::twemoji:sweat::

开机问题解决了,又遇到内存插上后不能开机的问题,代码b0,搜索是内存没有插好造成的,反复插也不行,最后发现只要插灰色的插槽都能正常启动,插黑色的就不可以,最后技术那里反馈确实是这样设计的:灰色为主插槽,黑色为副插槽,主插槽要插满的情况下,再插副插槽才可以启动,如果只插副插槽也不能启动的。

我会去插黑色的插槽是因为主板官方说明书上说,CPU下方黑色插槽为主插槽,这显然是标注错误,我反馈给了技术,他们说确实是标错了,会叫美工修改,害我忙活了一下午。。。

20250607133559.jpg

写了个网页版的提词器软件,就叫“CoCo提词器”

2025年6月3日 23:06

演播室配了俩提词器,题词软件不太好用,于是去年在网上魔改的一个提词器用了一年多,我们的主持人说用着还可以,但是看着臃肿和逻辑混乱的代码,心痒痒,要不我重新写个,顺便挑战下自己。

最近一直在使用 Trae 来淘汰 vscode,要不挑战下,看看 AI 能不能帮我写一个比较实用的工具软件。

经过好几天的研究和迭代,共改进了 40 多个版本,一步一步的实现出来了,当然中间还需要在 Ai 写代码的基础上帮 Ai 改代码,尤其是优化和逻辑的控制。比起之前用的那个上几千行代码的提词器,这个提词器仅仅就不到 600 多行的代码。

目前,基础功能已经完善,后续的话,打算做个快捷键配置界面,用于配置快捷键,比如有些题词的无线遥控器,需要映射对应的按键,才能正常工作。

主打的就是离线使用,当然也可以上传到自己的网站上。

目前版本是 0.1,后续随着功能的叠加,逐步升级吧,当然这个小工具是免费开源的。

下载地址:CoCo提词器_CoCoTeleprompter_v0.1.html

以后的更新全部放在这里,同时也同步到 Github 上,项目地址不出意外的话是 https://github.com/yefengs/CoCoTeleprompter/

使用说明:键盘空格为播放/暂停,方向键⬆⬇翻页⬅➡速度。

版本更新记录

制作了款 wordpress 主题 Cooooo

2025年5月24日 23:42

上一个博客主题《Memorable-lit》,缝缝补补也将就用了六七年,况且,博客也是将近一年多没有更新,好多篇博文静静地躺在了草稿箱里……

这次呢,想着借机写一款主题,形式依然是我喜欢单栏。由于很久不写代码,看到代码很生疏了,外加当前前端技术迭代太快了,好多HTML、css3的新特性和js的ES5、ES6,我处于完全看不懂的状态,这个主题的由来是我平时浏览网站的时候,看到别人好看的风格样式,就扒拉扒拉下来了,有的懒得动脑子,就直接参考和借鉴过来。整个主题是使用字节推出的 Trae CN 编辑器,本地电脑搭建环境,外加用上Trae 的 Ai 来写代码,效率是离谱的高,在这个主题绝大部分代码是使用 Ai 的写的。开发效率是有点高,尤其是在解决一些很复杂的逻辑时,你只需告诉 Ai 程序的基本逻辑,它便生成可用的代码,在前端库的选择, 当然我选择的是 jQuery,其实前端JS可以用原生来实现,但是用着jQuery很顺手,代码简单,一行代码就能搞定的,没必要绕弯。别人都在用高级的Hexo、Hugo 等高级,而我依然选择的是 wordpress,可能懒得折腾、懒得写作时用Markdown,也可能是保持了习惯和旧状态,新特性对我没有吸引力吧。

年初一段时间博客感觉被黑了,博客的插件里出现了一些奇奇怪怪的代码,感觉是木马后门之类的代码,之前博客的版本为4.8,也是年久失修的状态,可能存在能利用的漏洞,导致的博客沦陷,当前,wordpress 最新的版本为 6.8.1,借机新主题就在最新的版本上测试开发,前前后后感觉写了近两个月,部分代码也是参考了大佬们的代码,修修补补,也算是正常跑了起来。

当前博客跑在Nginx + Mysql + php 8.1 下,对于 2 核2G 的云主机来说够用,甚至剩余的算力可以跑跑 docker 里的小应用,整体相对比较顺畅。

这次换主题也顺便修修花、剪剪草和施施肥,对于改造博客我也是下了很大的决心,好在一点点雏形出现到基本能用的状态,也是可喜可贺。当然主题若有问题,欢迎评论区提出,当然这个主题足够完善并且很有必要的话,可能也会开源哦。

博客我加个视频播放器、音乐播放器和全景播放器,想着给博友们分享一些我外出拍摄的一些照片和内容吧。

高速惊魂

2025年2月7日 10:15

过年回家的惊和险,春节回家期间,从江苏苏州至四川广安,全程1600多公里,回去是我一人开的车。

晚上在高速开车的时候,差点2次撞上。还好都躲了过来。

但是返程的时候,就没这么幸运了,终究没躲过。

高速被追尾了,后车全责,对方走保险,现在车子在合肥的4s店修理厂修理。
车都爆胎了,我后右轮爆胎,对方左前轮爆胎。不然可以开到苏州来修。
修车期间的交通费还在商讨中,对方保险不出,对方车主也不出。

追尾

尝试做了一个vscode的博客md文件管理工具

2025年1月18日 13:46

全程使用cursor和copilot协助,typescript和vscode插件开发本身一点不懂😂

唧唧歪歪

之前有尝试过在vscode里写博客的文章,虽然vscode的编辑器很好用,插件也很多了,但是文件夹里的文件默认都是按照文件名排序的,想找文章非常不便。以前没有什么办法,因为找不到相关的插件,自己也不会插件开发。现在有AI工具的帮助,自己实现插件就变得可以尝试了。

成品

在花了2,3个小时的不断与AI对话,调整,debug,最后做出了一版Blog文章管理插件,取名BlogMan,支持按照时间排序文章,按照tag和年份分类文章,且排序和分类方式可配。

插件会根据yaml的文件头,将文章的标题,tag, 时间等信息解析出来显示,并按照相关内容进行排序。文章的标题后会显示文章的时间,鼠标放在标题上还会显示文章的tag信息。

切换为年份分组后就会按照年份进行文章分类。

切换为标签分组后就会按照标签进行文章分类。

~~

现在还没有上架到vscode的商店,因为还不知道怎么上架,后面有空再研究研究好了,先自己用用看。不过AI真的开始改变编程开发了,细思极恐啊。

第一次献血

2025年1月8日 20:47

在去年的2024年12月31日我去献血了
今天收到了血液合格的短信通知

短信

第一次献血,跟对象一起去献的,她献血很长时间了,到今天累计3800cc

到献血屋

献血屋

献血前有个检查,让我量血压,我第一次量血压的时候,血压有点高,医生不让献血。等放轻松后,他让我在去量血压,发现可以了,血压降了一点下来,然后去抽血

我们俩这次都献了400cc

献血证

2024回顾

2025年1月4日 20:46

2024年,这年的我26岁,这年,我没有负债了,我在追逐爱情,当然,追逐爱情的路上也是高低起伏。

工作

工作总体感觉还行吧,虽然工资不是很高,但是时间方面比以前的工作好了一些,下半年换了新工作,没有了长时间的上班,没有了24小时待命的抢修,还有了额外的单休。

关于爱情

这是今年的主要两个大事情。

被骗传销

去年还完了所有贷款,没有了负债,上半年也攒了一些钱,便开始去追逐我的爱情。
在6月份的时候,去见了一个网上认识了一个女孩。网恋奔现,刚开始以为就是简单的见一个面,不曾想被骗入传销。骗走了我身上所有的钱,以及还去借了一些钱,后面发现感情也都是假的。在传销窝点里面待了两个月后,我找机会跑了出来。

出来的时候,身无分文,穷困至极。

第二段感情

这是继传销出来之后,才开始的,刚开始的我还是比较小心翼翼,毕竟被骗过一次了,而且身上也没有钱了。但是在一段时间的了解后,我被她满满的爱意包裹着,于是我们便慢慢的靠近彼此,更加深刻的了解了彼此。

就这样,我谈恋爱了。

我们在一起跨年,去了重庆的解放碑,去了她家,见了她的妈妈,以及她小时候待过的地方。

结尾

谁也不知道未来会怎么发展,就像上面两件事,不幸遇见的她,和幸运遇见的她。
所以珍惜当下,珍惜眼前拥有的。

手指爱心

谈恋爱了

2024年12月23日 10:50

最近谈恋爱了,所以没有更新博客,也没有逛博客圈了,在好好对待我的恋爱。
个人的博客暂时没有更新了,但是更新了自己的情侣博客(仅两人可见)。
嗯,会记得我的博友们的,改日拜访大家^_^

信用卡年费扣费

2024年11月23日 22:06

好久之前办的一张招商信用卡,最近没怎么用了,一年没刷满5次。今天看了一下账单,发现扣了我300的年费,顿时就感觉白白丢了300块。

然后去百度了一下,发现居然可以补刷,赶紧试了试,联系了客服,办理补刷。

聊天记录

真的可以的,赚了300,哈哈。

与生活对线的日子

2024年11月3日 23:43

接着这篇文章→与生活对线的日子2022.04.16,已有两年多了,日子还是平淡,但是也不平淡。

上班到现在,除了有事请了一天假,其它时间都在努力工作。

工作,还算可以,现在习惯了,做起来没有之前那么累。加上秋冬季节,户外干活也凉爽了不少。

生活上嘛,住的仍然是公司的宿舍,虽然不是很好,好歹是个落脚点,可以遮风挡雨,想过去改善。但也很无奈,就这里撑过过年吧,明年再打算了。

虽然说最近手里不怎么宽裕,失去过一些东西,但也获得了一些东西。比之前失去的更加珍贵。

有思恋的人,有想去的地方,有想要的生活,还是在用现在的努力,想换来美好的生活。

不知不觉,枫叶在慢慢变红,天更冷了,度过了这个寒冷的季节,我想前面就是春暖花开了吧。

枫叶

国庆香港citywalk

2024年10月13日 16:40

国庆打卡香港,感受文化的交织与碰撞

趁着国庆,一个人从深圳福田口岸到香港,整一个Citywalk,两天时间第一天暴走37000步,第二天27000步,把香港主要的热门地点都转了转,感受很明显,和内地的任何一个城市都完全不一样,整个香港的感觉是冲突融合碰撞。

城市建设

香港的高密度建筑+高密度路网的布局,市区的每栋楼都挨在一起,基本上完全没有间隙,而且一栋或者两栋楼的宽度就是一条路。路都很窄,大量的单行道,每条路都没有非机动车道,除了主干道,其他很多路都没有红绿灯,都靠观察通过。虽然路很窄,但是路上的车开的都很快,通行效率还蛮高的。
机动车道路面水泥路面为主,而现在内地的各省市基本已经逐渐变为沥青路面了。人行道香港主要也是水泥铺装,而内地基本上都是人行道砖,目前走遍的各个城市基本都是一般。
香港的过街天桥很发达,有很多互联多个路口,连接公交地铁,同时还会连接路两边的楼栋,非常方便,通行效率也很高。不过这个应该也只有香港才能做到,其他很少有地方楼栋和道路挨的如此的近。

香港路上能看到不少在建设或者装修的大楼,建设过程中,脚手架都是用竹子做的,不知道是什么原因,内地现在应该都是钢制脚手架了。

公共交通

香港的公共交通,地铁+公交+有轨电车(叮叮车)的组合,车次都非常的密,经常路上一溜排的公交车,除了地铁稍贵,公交和有轨电车应该完全是靠补贴存在的。
公共交通主要覆盖的是主干道,很多支路还是只能靠走,因为路网密倒是不太远。
坐在叮叮车上,晃晃悠悠看遍路两边的感觉真的挺好的,3HK$坐多远都行。

商业

香港的商业让我感觉特别的割裂,一遍有着很多现代化的商业综合体,宽敞,现代化;
另一方面,街上的单面大多门脸开间特别小,每间店铺都有种家门口五金店塞得满满的感觉。
大概是这两年经济不景气,非常多的门面都倒闭关门招租中,即使在最繁华的尖沙咀和铜锣湾也不例外。
另外香港遍地的药店,特别的多,卖的东西也很杂。同时还有很多专门的类似于摊位一条街的存在,卖各种各样大小东西的地方,让我想起了我小时候去的城隍庙。

物价

香港的物价总体上感觉是内地的2倍到3倍,总的来说应该还是人工和房租贵,路边食室随便吃点6-70HK,电子产品等批量化的工业产品之类的倒是不贵些。香港的住宿很贵,条件稍可的都要超过1000了,寸土寸金啊。
最不能理解的还是矿泉水和饮料,超级贵,同时价格悬殊极大,便利店都是十几HK,而不少药店却很便宜,最便宜的我看到是2.5;香港的蜜雪冰城柠檬水是9HK, 所以在大多时候,比矿泉水还便宜。

汽车

香港路上,公交车看到的全是油车,私家车则是各种各样,油车的话从跑车,老爷车到一般的代步车,电车的话能明显看出来的就只有特斯拉和比亚迪了,其他的或许也有,我认不出来。
在香港竟然真的有开大牛,小牛代步的,果然有钱,不考虑油钱和舒适度,只要帅就行了。因为香港是右舵,所以都靠左行驶,刚过去还不太适应。
香港骑摩托车的挺多的,车看起来也都还不错,路上也有专门的摩托车停车位。在香港开车,起步都超级快,每个路口是都油门嘶吼的声音,无论是公交车还是私家车,都是一样。

人文

在香港路上走着,粤语,普通话,日语,韩语,以及带着各种口音的英语,还有我听不出来的语言混杂。从外貌上看,路上碰到的欧美白人,印度人,日韩,其他东南亚人都很多,有不少看得出是旅游的,另外不少则明显就在香港工作。据说香港的外国人占比超过10%。
至于香港本地人和内地过去的游客,从穿衣打扮以及精气神上就是能看出来不同,能够明显看出香港本地人和内地游客,至于具体差异在哪,我描述不清楚。
香港街头还能看到卖电视天线,唱片DVD的小店,同时也还有不少公共电话亭和邮箱,但各处却又wifi覆盖,让我感觉特别的冲突。既现代化,又复古的感觉,很拧巴。全球各地的人又在这里汇集,感觉到不同的文化在这里交织与碰撞。

景点

收费的景点去了坐了太平山的缆车和摩天台上,只要20块的摩天轮也去坐了,就是那一个多小时的队排的不值,又热又晒,还浪费了宝贵的时间。剩下的就是免费的景点和一些热门地段,星光大道,坚尼地城海边,旺角,铜锣湾,尖沙咀啥的都有去,主打一个暴走。

其他

现在从深圳去香港是真的方便,我是从福田口岸过去的,全程自助过关,刷两次港澳通行证就可以了,过关就可以坐上港铁,直达中环附近。

找房奔波

2024年10月9日 11:56

找房奔波

在这边工作了一段时间,稍微熟悉了这里的一些环境后,想去在这边找找房子租。
10月7日,下班后,去附近找了房。
之前先是在58看了房,找了同城租房服务,但是要先交押金再去看房。

委托书

这个押金290(也就是找房的服务费),如果找到合适的房子,也可以抵扣在房租里面。找不到合适的房子的话,也不能退,但是他们可以一直提供服务,直到找到为止。

交了押金后,就有人带我去看房,不得不说,这边的房子真的是贵,基本800起步。看了几个便宜的,600左右这样,但是空间基本就一张床的大小了。要做饭的就更奢侈了。

晚上在下雨,坐着看房小哥的小电驴在雨中穿梭着。

下雨

然后看到了第一间房。

第一间房

这间最开始600,后面叫到550,一个小隔间,屋顶是斜的(斜屋顶没拍着),5楼,没电梯。6间房共享一个厨房,2个厕所。

感觉不是很好,又换了一家。

第二个房

这个500,就只有一张床的大小,外面能电磁炉做饭。就是空间太小了。

上面都是特价房了,普通的800,就没看了。
一个人租房真的小贵,后面有时间再看看吧,还是有人合租分担房费划算一些。

新工作,上班了

2024年9月18日 22:18

新工作找到了,今天开始上班。

休息好久了,也好久没怎么干过重活了,今天第一天去,累得不行。

现在在做低压工作,从吴中区搬到了新区(虎丘区),主要在小区挖电缆沟,埋电缆,装柜子。
挖沟的时候刚开始还好,后面就疲了。还可能加上没吃早饭的原因吧,挖到后面就开始头晕。挖不动了,喝了点水后,休息一会才慢慢好点。
再接着就是跟师傅装柜子,做电缆头,一天下来,又是新体验。

工作

这太阳晒得,加上干活,全身很快就湿完了。

下班后住在宿舍的,宿舍也还好,是免费的。
但是除了免费的了,也没啥了,是以前的废弃配电房改造出来的,里面只住了几个人,每人都有一张床,能睡睡觉。好在,有空调。

宿舍

有个落脚点还是可以了,万事开头难,加油!

热爱生活

2024年9月8日 23:14

到苏州了,还没上班,之前上班那边已经进不去了,领导又推荐了新的公司,还在待定中。现在住在朋友家里。

闲暇之余,也喜欢去外面玩玩。

虽然生活喜欢跟你开玩笑,但也还是要热爱生活啊。

第一次单车骑了一次50公里。
跑了一圈一直想跑的太湖。
还拍了好多照片。

热爱生活,积极向上。

轨迹

单车

轨迹

汽车

随拍

随拍

使用echarts.js生成足迹地图(优化版)

2024年8月30日 22:40

较pyecharts可无缝集成至主题中,无需手动生成更新

之前使用pyecharts制作生成足迹地图过于简陋繁琐,如果需要更新地图,需要手动填写再更新生成,最后替换原有地图,过于繁琐麻烦。最近又好好研究了一番echarts.js,算是让足迹地图的嵌入变得稍微优雅了一些。

地图数据

echarts.js绘图需要GeoJson数据作为绘图数据源,搜索网上,主要都是依赖阿里云的altas平台提供的数据源,不过这里提供的数据都是按照省份分割存储的,没有市级的,又不想做地图的下穿等更复杂的操作,索性写了个小脚本,将GeoJson数据做了下重新拼合,制作了一个按照市级分割的GeoJson数据源。制作好的数据放到的github仓库:https://github.com/wherelse/GeoMapData_ChinaCityProcess 需要市级分割地图的朋友可以下载或者调用china_city.json

主题修改

  • 在header中引入echarts.js:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
  • 在需要渲染出地图的位置渲染标签:
<body onresize="resizeFresh()">
<div id="footmap" style="width:95%; height:500px; margin:auto; top:30px"></div>
</body>
  • 在主题文件尾部添加echarts初始化及刷新script:
    <script type="text/javascript">
      var chartDom = document.getElementById("footmap");
      var myChart = echarts.init(chartDom);
      var option;

      myChart.showLoading();
      fetch(
        "https://raw.githubusercontent.com/wherelse/GeoMapData_ChinaCityProcess/master/china_city.json",
        {
          method: "get",
        }
      )
        .then((response) => response.json())
        .then(function (ChinaCityJson) {
          myChart.hideLoading();
          echarts.registerMap("China", ChinaCityJson, {});

          option = {
            title: {
              text: "我的足迹",
              subtext:
                "FootPrint in China\n\n感谢来源于阿里云altas平台的地图数据",
              sublink: "http://datav.aliyun.com/tools/atlas/",
              left: "right",
            },

            visualMap: [
              {
                min: 0,
                max: 1,
                show: false,
                inRange: {
                  // 选中范围中的视觉配置
                  color: ["white","#00AAFF"], // 定义了图形颜色映射的颜色列表,
                },
              },
            ],

            series: [
              {
                name: "China Footprint",
                type: "map",
                map: "China",
                roam: true,
                emphasis: {
                  label: {
                    show: true,
                  },
                },
                data:[
                  {name:"北京市", value: 1},
                  {name:"天津市", value: 1},
                  {name:"南京市", value: 1},
                  //在这里添加更新足迹城市
                ]
              },
            ],
          };
          myChart.setOption(option);
        });

     //窗口大小变化时候,进行刷新页面操作,防止样式混乱
     var x=window.innerWidth;
     function resizeFresh(){
         if(x!=window.innerWidth)
             location.reload();
     }
    </script>
  • 根据自己的足迹更新echarts初始化中的series data中的城市目录即可。渲染效果可参考本博客关于页面

灭螂行动!

2024年8月4日 23:57
在老旧小区出租屋中,蟑螂问题日益严重,厨房和厕所成了它们的乐园。本文详细分享了使用硼酸粉、土豆泥和白糖自制蟑螂诱饵的方法,并记录了七天内灭蟑螂的实际效果。通过调整配比,逐步解决蟑螂困扰,为广大租客提供了有效的灭蟑螂小妙招。

并行操作导致获取数据库连接超时

2024年8月4日 20:40
线上遇到数据库连接超时异常后,通过排查发现问题出在并发处理上。最终,通过在 Parallel.ForEach 中显式设置并行度来解决这个问题。与调整全局线程池设置相比,这种方法更能有效优化性能,避免连接溢出。

VCS+Verdi仿真Xilinx FPGA Vivado工程

2024年7月27日 22:01

在使用过VCS配合Verdi进行波形仿真之后,再也无法忍受vivado那缓慢的仿真与卡顿的界面,Verdi追踪信号更是极快加速问题定位。不过FPGA的IP不能像普通Verilog IP一样直接使用VCS进行编译仿真,需要调用一些Vivado IP Library才可以,下面分享一下如何使用VCS进行FPGA工程波形前仿真。

环境配置

需要使用VCS+Verdi进行仿真,这两个必须要是安装好的,这个参考其他教程。然后就是Vivado,开发 Xilinx FPGA这个也是必备的。这些都有安装后,基础的环境就算是OK了。

FPGA工程基础准备

在工程中,调用的vivado IP核,都需要完成调用和预综合,在对应的IP文件目录生成对应的xxx_sim_netlist.v,这个一般是放在/工程目录/sources/new/ip/xxx 中。这些需要先Ready,是后面在VCS编译过程中调用到这些IP的基础。工程中调用的vivado原语则不用处理,正常使用即可。然后是准备好工程中所有使用到的.v文件,除了testbench以外,其他文件都不需要做特殊的修改。testbench文件需要额外添加以下内容,用于dump fsdb波形和mem等内容:

initial begin
	$fsdbDumpfile("xxx.fsdb"); //xxx根据需要替换为文件名
	$fsdbDumpvars();
end

filelist准备

VCS和Verdi的仿真和波形查看都基于filelist进行维护,而不是和Vivado一样的图形化界面。需要将所有调用的.v文件,lib文件等的目录和文件名整理到filelist文件中,VCS和Verdi会自动根据文件列表分析结构层次。

  1. V文件列表整理:可以使用下面的脚本,一件生成当前目录及子目录下所有文件的filelist

    find . -name "*.v" -exec ls -dl \{\} \; | awk '{print $9}' >> flist.f
    
  2. ip sim netlist列表整理,根据项目中调用的IP情况,将所有调用的IP sim netlist添加到filelist中。

  3. Vivado LIb文件添加,为了能够正常仿真IP和原语,还需要在filelist中添加额外的Vivado库文件,这些文件都存放在vivado的安装目录中,一般必须添加unisim库和glbl库,如果有调用serdes源语或者GT收发器,则还需要调用额外的.vp加密库。目录参考:xxx/xilinx/vivado/$vivado version$/data/srcxxx/xilinx/vivado/$vivado version$/data/secureip/

run脚本准备

用于配置VCS编译选项,调用filelist,可以写一个简单run文件,也可以写一个makefile作为管理,这里以一个简单run文件为例:
vcs -R -full64 -debug_all -debug_region+cell+encrypt -f flist.f
仿真开始后,就可以打开Verdi查看已经完成部分的仿真波形,快捷迅速的追踪信号,仿真速度也会比Vivado自带仿真快很多。

莫名在年龄上的焦虑

2024年2月23日 23:11

真的,时间过得飞快,转瞬即逝的感觉,熟不知自己已经步入34岁了。

那年我觉得我还年轻,我觉得26、27岁算个什么,虽然我初婚在17年,离后至今,很多事情都是历历在目,已经过去6年之久,这6年我收获了什么,光秃秃的额头?蜡黄的皮肤?油腻的脸颊?还是那日渐瘫痪的意志?

自己一直是独居,去年房子装修出来,有了自己的安身之所,不再频繁搬家,不再为熟悉而又陌生的房屋担忧。装修住进去的房子虽然安置了自己喜欢的家具家电,但自己内心越来越觉得空,躺在偌大的沙发上发呆。难道自己今天就这样?明天这样?后天这样?大后天也是这样?

这几年陆陆续续相亲,相亲,相亲,一直在相亲,每一段都是不了了之。我也渴望有个伴侣,有时很真的很害怕孤独,真的害怕,自己性格内向,甚至孤僻,但是每每遇到感觉不错的人时,总是打退堂鼓,总是不主动,总是在犹豫,总是不知所措,总是没有结局。羡慕同事和妻子的你侬我侬,羡慕同事对象送的礼物,羡慕他们有人可以说话,羡慕他们共同有事情可做,羡慕他们回家有人等……其实我心里早有答案,我知道有个人在等我,知道她的样貌,她优雅知性,懂我,而我只想把我好的一面给她,懂她、爱她、惜她……

……

回头发现自己真的不再年轻。

Bye, RStudio/Posit!

2024年1月3日 08:00

Who is down? Me. After more than 10 years at RStudio/Posit, the time has come for me to explore other opportunities. A little over two weeks ago, I was told that I was laid off and my last day would be 2023-12-31. Frankly speaking, I was quite surprised but only for a short moment. I fully respected Posit’s decision, and quickly accepted the conclusion that my contribution no longer deserved a full-time job here. The end of a relationship often does not imply anything wrong or a failure of either party. Instead, it can simply indicate a mismatch, which is normal. People just change. With these amazing years in mind, I left mostly with gratitude in my heart.

# number of days I worked at RStudio/Posit
as.Date('2023-12-31') - as.Date('2013-08-26') + 1

Anyway, I guess this news may surprise some people in the R community and bring up questions or puzzles, so I want to write a blog post to address a few potential questions. If you have more, please feel free to ask me either by comments below or by email.

Acknowledgments

Despite the separation, I hold and value a lot of good memories about RStudio, which I could easily expand into another lengthy blog post, but I will save it for now since I got sick last week and am still recovering. In short, I would like to thank JJ for offering me my first ever full-time job and trusting me for so many years. I thank Joe and Tareef for the long-time mentorship (as well as friendship, I should say). I thank Hadley particularly for the guidance on the bookdown project from 2015 to 2016. I cannot say how much I appreciate Christophe’s help over the years (even a few years before he joined RStudio).

Really, there are too many people that I want to thank in the past ten years. Okay, I will write another post on this in the future after I settle down. As always, I am deeply grateful to the entire R and open source community for their belief and investment in the tools that I have been fortunate to work on.

No, those R packages will not be orphaned

At Posit, I felt blessed to work with super talented and committed engineers, and I believe that our collective work (in particular, R Markdown) has helped make R and reproducible research more accessible to the community and hopefully to the world as a whole.

After my departure from Posit, we are not going to drop these efforts. Posit has generously provided funding for me to continue, as a contractor, to support and extend knitr, rmarkdown, and various packages in this ecosystem. I look forward to continuing my collaboration with the Posit team on our shared areas of interest.

So please do not panic—our existing R packages will still be maintained. The only exception is the DT package, which is not included in the contract, and Posit plans to find a new maintainer for it. Before that happens, I might still be able to help (time permitting), but I cannot promise.

A minimalist has been growing inside me

Over the past three years, I have spent more time thinking and exploring a different approach to building software that is more minimalist and handcrafted than the larger projects like Shiny and Quarto, on which Posit is currently focused. I have become more interested in developing smaller software tools that do fewer things.

To a large extent, I’m leaning towards the “Less is More” or “Worse is Better” philosophy, and I find stoicism and the wabi-sabi concept very appealing. I do not mean my choice is correct or better. All choices are about a series of tradeoffs. I just happen to find a certain choice fits me better. Will I stick to it forever? I do not know.

This philosophical change of mine is not only about software development, but also my daily life. As a result, many friends find it hard to understand me when I ask them not to bring anything but an empty stomach when visiting me—perhaps someone who has visited me before can confirm it in the comments below.

What’s next?

Since I’m no longer a Posit employee, I’m facing some uncertainties now. I need to learn and figure out a few things that are new to me before I can come back to work again. Hopefully this will not take more than a couple of weeks.

The contract work I mentioned above is not enough for me to make a living (well, definitely enough for this minimalist guy but not for the family), so I’m also looking for opportunities that will give me the freedom and flexibility to continue to contribute to the R ecosystem and open source in general. If anyone has a job opening or is interested in my skills, I will be happy to chat, and please feel free to email me.

For now, I have not decided yet whether I want to take a full-time job next or just take this chance to become an independent contractor. It depends on the opportunities that I can get in the next few months.

Update on 2024-03-19

Please ignore the call for help below, and feel free to cancel your monthly sponsorship. I no longer need financial aid. Currently I’m (busy) working for SwissRe as a contractor, and should have enough income this year. For the donations that I have received, I have been passing them to other people and organizations of my choice. If you have suggestions on where I should donate the money, I will be happy to follow your advice. Many thanks to everyone who lent a helping hand to me! Please also feel free to let me know if you need help or know someone else who needs help (e.g., with seeking for job opportunities).

I have never asked for financial support from the community before, because I have never felt the need (thanks to Posit). Now the situation has become different, and I’m a little concerned about the mortgage number in my account. For the first time, I’m mentioning my GitHub sponsorship page in my blog: https://github.com/sponsors/yihui. I will be very grateful if anyone could support me for a few months before I transition into the next stable phase of life. I will notify you when I do not need the sponsorship any more so you can cancel it if you are on a monthly tier. I will be happy to offer some casual help in return just as tiny side jobs. For example:

  • Answer your questions (technical or non-technical);

  • Help you optimize your website, or more importantly, cultivate a habit of writing so you can keep writing for the years to come;

  • Advise on how to make your presentation entertaining (but I refuse to sell my precious 20-year old GIF collections);

  • Share my experience on cooking, gardening, badminton, or even setting up a simple Karaoke system at home (now you are highly skeptical if this so-called minimalist gentleman is genuine);

  • As a “down” expert, write a letter to cheer you up if you are down for some reason (since the pandemic, I seem to have become much better at writing letters).

We don’t say goodbye. So actually this is not a “bye” to anyone, but a “hi” to an unknown new journey. I have enjoyed the past decade, and I’m in full curiosity about the future.

利用pyecharts制作自己的足迹地图

2024年1月1日 17:38

之前有在高德和百度地图的软件上看到自己的足迹地图,不过这两个软件因为我使用的原因,数据都不完整。很多之前去过的地方并没有标记,或者只是坐火车坐高铁路过而已的地方也都记录了下来。然后就萌生了自己能不能做一个属于自己的足迹地图,且能够方便的嵌入在博客中。

尝试

在一番搜索研究,看了看已有的开源项目,首先看到了基于echarts的方案。仔细研究了一番,发现常规的geojson文件都是按照省份划分,如果想要精确到市级,要么做点击下穿,要么要自己花时间去做城市级的json数据整合。一番搜索没有找到现成的数据,又不想花额外的时间,随放弃。

研究

又经过一番搜索,发现了pyecharts,echarts的python开源版本,echarts本身则是基于JavaScript的。pyecharts的官方示例直接就带了一个全国市级地图示例,这不正是我想要的吗。不过这个示例还有不少额外的标记,不太需要,经过又一番搜寻和文档阅读,首先找到了Python-pyecharts生成精确到市的地图, 经过一番魔改修改成了这样,成功把各种标识全部关闭:

from pyecharts import options as opts
from pyecharts.charts import Map
finished=["北京市", "天津市", "南京市", "合肥市","六安市","安庆市","黄山市","池州市","淮南市","焦作市","西安市","福州市","杭州市","绍兴市","苏州市"]#对应地图中的名字
citylist=[]
for each in finished:
    citylist.append([each,100])
map_data = (
    Map()
    .add("中国地图", citylist, "china-cities", is_map_symbol_show=False,)
    .set_series_opts(label_opts=opts.LabelOpts(is_show=False))
    .set_global_opts(
        title_opts=opts.TitleOpts(title="我的城市足迹",is_show=False),
        legend_opts=opts.LegendOpts(is_show=False),  # 隐藏图例
        visualmap_opts=opts.VisualMapOpts(is_show=False),
    )
)
map_data.render("citymap_footprint.html")

完善一下

但是这个时候,pyecharts生成的html文件,地图只在左上角的角落里,缩放之后更是直接被截断。按pyecharts学习2--自适应屏幕居中显示这个博客内容又处理了一番,唉嘿,输出的文件是我想要的样子了,一个全屏可自适应的,除了足迹以外无标记地图。

效果

<iframe src= "/citymap_footprint.html" style= "border:none; width: 100%; height: 50vh; "  scrolling="no"></iframe>

最后使用iframe嵌入到文章或者博客主题中,完美,效果如下:

🚅我的城市足迹

我最后是将我的足迹嵌入到了关于页面,毫无违和感,非常合适。可放大缩小,以后更新也只需要替换一下iframe嵌入的html文件即可。

2023to2024

2024年1月1日 00:00

这是我在博客写下每年愿望的第四个年头,2024又有什么期待呢。

我的2023总结

回看23年初时,写下的几个23年的愿望,现在有实现的吗?

2023年的几个愿望:
1.工作精进,技能增长 (√)
2.多出去走走转转看看 (...√)
3.加强锻炼,身体健康 (x)
4.坚持内容输出,提升自己的输出能力(√)

第一条,工作中有了新的变化,也能力上有提升,算是完成了,详细的下面细说。
第二条,下半年的周末,只要没有雨雪,基本每周都会出去走走,但是现在回头看来,基本没有走出市区,走出最常生活的范围。
第三条,今年算是荒废的一年,基本没有什么锻炼,虽然没有经常生病,但是感觉体能是真的差了不少。
第四条,2023年一年总共写了9篇或长或短的博客,还搭了memos时不时输出一下碎碎念,算是圆满。

2023中我的工作

从去年毕业到现在,毕业也已有一年半的时间,但也算是挺短的。年初部门因为很多人离职,就剩下一群应届生,换了大领导,完全不一样的行事风格。新领导比旧领导要push不少,但是愿意带我们新人,给予机会和帮助。这一年学了不少东西,虽然有些零碎不成系统,但是回看年初的时候工作中的自己已不可同日耳语。同时今年的公司,裁员降本不断,行业整体也不景气,不知道到2024会如何。同时我也从开始基本不加班,到现在加班多了,加班真的会降低生活质量,这算是这一年中工作中相当难受的点。

2023中我的生活

生活上,最大的变化是新家装修完之后,我自己一个人住了,而不是和去年一样和爸妈住一起。之前我还经常做饭,但是自己一个人住后加上经常加班,家里没菜,做饭变得异常麻烦起来,就不自己做饭了,之前还会准备第二天的午饭,带到公司去热,因为不做饭也就不再带了。但是一个人住,对于我这个享受独处的人来说,也是一种释放,每天少了许多我爸的啰嗦和负能量。
另一个是因为周内经常加班,到家一般都是9点多了,周内属于自己的生活时间基本所剩无几,回家之后基本打开电脑随便翻翻网页,就洗漱上床,刷会手机睡觉了。周末则就是拿起相机,出去瞎转,去寻找值得记录的风景。
这一年的生活,如果用一个词来总结,我大概只能用平淡来总结。

2023中的买买买

2023年为我自己花钱最贵的两个,一个是电脑,一个是相机,也算是工作后对之前多年愿望的满足吧。电脑现在有点后悔,稍微配的有些性能过剩了,工作之后已是电子阳痿状态😭,对很多游戏完全没有之前那样想玩的欲望。相机则是刚刚到手,希望后面多拍多用,毕竟拍的越多,用的越多,钱花的越值。
如果说我认为买的最值的,我则是认为一个是新买的小斜挎包,一个是Airpods pro。新包买回来之后基本每天出门都背着,包超级轻且背起来贴合身体,但是容量相当可观,出门所需的东西基本都能塞得下。Airpods pro则是佩戴舒适度超级好,基本无感,作为一个地铁通勤人真的是太有用了。能让我每天通勤过程中,隔绝于地铁那震耳欲聋的噪声之外,拥有一段本失去的时光。

我的2024愿望

经过一番短时间的碎碎念,可能遗漏了不少我在2023的所想,但是还是决定停下来,写下我在2024的愿望。

  1. 工作多学习提升少加班,生活多些滋味。
  2. 运动起来,跑步最简单,就从它开始吧。
  3. 坚持内容输出,多写长文,多记录,尝试更多形式,无论文字,图片还是视频。
  4. 多出去走走,走的更多更远,见更多山河,攀更多山峰。
  5. 精打细算,尽可能多存些钱。

新的一年,开始~~

入手索尼A7C2

2023年12月23日 21:43

SONY A7C2 让我爱不释手

碎碎念

大概6年前,我刚刚对摄影有了些认知的我买了索尼a6300,6300陪伴我度过了大学,研究生,到了工作时期。作为一代神机,真的很能打,有些规格即使到了现在仍然能和有些相机打的有来有回。兜兜转转的几年间,我用6300学习了摄影最基本的知识,摸清了我到底喜欢用相机拍什么。但是毕竟是发布于7年前的老机器了,用起来算是充满了年代感,microusb的充电接口,容量超小的电池,难用的菜单,稀烂的屏幕,都在不断的提示着我。

心路历程

在今年,索尼发布的A7C2在发布前就让我眼前一亮,相比一代有了不少的提升,该有的拨轮,快捷按键都不少,整体的规格基本和A7M4也基本一致。同时那超小的体积和相对轻的重量,和我在使用的6300基本一致,是可以轻松放到我日常通勤的斜跨包当中,随时带着就走,而不用顾忌该如何带走。在几年的相机使用过程中,“能经常带出去拍照的相机才是最好的相机”,是我不断提醒自己,同时又在相机的使用中不断被验证的一句话。

因为工作了,有了自己的可支配收入,买一台全画幅的相机有了足够的预算。然而A7C2一发布就缺货,溢价不断,直到最近12月,价格才逐渐下降,回归到正常水平。在这几个月的纠结犹豫后,想买的心使用没有平复,终于下定决心把6300卖了买A7C2。



上周末,去线下实体店原价入手了银色的A7C2单机,为了搭配机子,在闲鱼淘了一个适马35F2镜头作为挂机镜头。还配了一个银色的底板,提升了握持手感,同时也和机身的颜色很搭。



新机体验

新机子拿到手,手感确实和6300差不多,但是屏幕,电池续航,拍照防抖,AI对焦,菜单这些都提升巨大,尤其是拍照防抖,轻松用0.xs的快门在晚上拍照不会糊片,这个真的太爽了,之前完全不敢想。同时新的菜单比索尼之前的祖传菜单真是好用太多。

同时更高的像素加上全幅,拍出的成片也是能明显感到差别,真的是爱不释手的感觉。另外新的创意外观也是比较好用,有的时候不想修图调色就直接开创意外观直出就可以了。看网上还可以导入自定义lut,暂时还没有尝试,什么时候尝试一下。

样片时间

最后放几张用新相机拍的照片,传递一下拥有新相机的快乐,嘿嘿😁:







Help Needed: Making SearchBuilder Work in the Server Mode in DT

2023年11月21日 08:00

Three years ago, Xianying Tan helped me add the SearchBuilder extension of DataTables to the DT package. This extension did not exist when I first started developing DT, otherwise I would not have spent countless hours on creating a variety of filters in DT by myself and make them work in both the client and server modes. This extension is much, much more flexible than my clumsy “homemade” filters.

Yes, SearchBuilder has been made available to DT three years ago, but the problem is that it only works on the client side. That is, if you render a table in Shiny, you cannot DT::renderDT(server = TRUE) and can only do server = FALSE. The filtering logic is not implemented on the server side.

Users have been asking how to make it work in the server mode, and one asked again last week. Unfortunately I do not really have time for this task, but I think it is an interesting little project, so I’m sharing tips on how it could be possibly implemented, in the hope that someone could pick it up and get it done. I do not think it is technically hard, but it definitely requires some focus time.

Basically, you need to inspect the object q during debugging the internal function DT:::dataTablesFilter, and you will see parameters sent from SearchBuilder in q$searchBuilder. You need to implement the filters by dealing with these parameters with R code. Here is how you can get started:

debug(DT:::dataTablesFilter)

library(shiny)
shinyApp(
  fluidPage(DT::DTOutput('foo')),
  function(input, output) {
    output$foo = DT::renderDT(
      data.frame(
        a = sample(26), b = letters,
        c = factor(rep(c('a', 'b'), 13)),
        d = Sys.Date() + 1:26,
        e = Sys.time() + 1000 * (1:26)
      ),
      options = list(dom = 'Qlfrtip'),
      extensions = c('SearchBuilder', 'DateTime')
    )
  }
)

In DT:::dataTablesFilter, you can see how I implemented searching, pagination, and sorting with R code. Similar things need to be done for parameters in q$searchBuilder.

Please let me know if you need more guidance. I’m sure at least a few users will be grateful if this could be done, and so will I.

A Change in the TinyTeX Installation Path on Windows

2023年11月17日 08:00

Since about a month ago, I have been receiving error reports from TinyTeX users saying “TinyTeX\bin\windows\runscript.tlu:864: no appropriate script or program found: fmtutil” or “I can't find the format file `pdflatex.fmt'!”, which I do not understand.

For the last few days, I scratched my head, banged against the wall, did some research, asked R and LaTeX experts in mailing lists, got reminded of “[[alternative HTML version deleted]]” again (and apologized, of course), dug out an old Windows laptop, proudly created a new user account with my authentic Chinese name for the first time of my life (instead of using Pinyin), went through trial and error, learned a variety of bizarreness of Windows batch scripts as well as the Stack Overflow cures, summoned all Chinese students in my alma mater to test their own Windows machines and the Windows servers in their department, and meditated on the meaning of life for three seconds. Finally I’m happy to announce that I have found a fix and applied it to tinytex (the R package) v0.49.

TLDR; The fix

If you have run into the above errors when rendering R Markdown or Quarto or LaTeX documents to PDF, you can install the latest version of tinytex from CRAN:

install.packages('tinytex')

Please remember to restart R after installation. Then make sure packageVersion('tinytex') >= '0.49').

The problem

If your Windows username does not contain spaces or non-ASCII characters, this problem should not affect you.

Sys.getenv('APPDATA')
xfun::is_ascii(.Last.value) && !grepl(' ', .Last.value)

Although I have used LaTeX for nearly two decades, I have learned for the first time (from Akira Kakuto) that TeX Live does not work on Windows when its installation path contains non-ASCII characters. By default, TinyTeX is installed to the path defined by the environment variable APPDATA, which is of the form C:\Users\username\AppData\Roaming. The problem comes from the username in this path, which can contain multibyte characters, and cause TeX Live to fail with a lot of error messages like below:

! warning: kpathsea: configuration file texmf.cnf not found in these directories: 
....
! ...s\username\AppData\Roaming\TinyTeX\bin\windows\runscript.tlu:941: ...s\username\AppData\Roaming\TinyTeX\bin\windows\runscript.tlu:864: no appropriate script or program found: fmtutil
! Running the command C:\Users\username\AppData\Roaming\TinyTeX\bin\windows\fmtutil-user.exe

! kpathsea: Running mktexfmt pdflatex.fmt

! The command name is C:\Users\username\AppData\Roaming\TinyTeX\bin\windows\mktexfmt

In theory, username containing spaces should be fine, because a space is an ASCII character. However, I have received reports that spaces can be trouble, too. I do know why (is this recent bug fix in base R relevant?).

The change

With tinytex v0.49, when your APPDATA path contains spaces or non-ASCII characters:

  • If you run tinytex::install_tinytex() to install TinyTeX for the first time on a computer, it will install TinyTeX to the path defined by the environment variable ProgramData, which is typically C:\ProgramData. This path has no spaces or non-ASCII characters, but note that it is hidden by default (which is harmless). In addition, this folder is shared by all users in the system. If you have multiple users, this could be a problem. For example, other users can change or override your installation. If this is a concern, you can specify a different path via the dir argument of install_tinytex(). Remember this path should not contain special characters, either.

  • If TinyTeX has already been installed to APPDATA, you will get a warning message telling you how to move it to ProgramData (you can also move it to another place if you want—just specify a different path to the to argument below). You may have to restart R or even the system after moving TinyTeX.

tinytex::copy_tinytex(to = Sys.getenv('ProgramData'), move = TRUE)

The installation script install-bin-windows.bat has also been updated accordingly.

A potential flaw

The above fix is based on the assumption that ProgramData is writable, which appears to be true according to various tests that I asked for from some students. If it is not true, you will have to specify your own installation path in tinytex::install_tinytex(), or if you use the Windows batch file, you can set the environment variable TINYTEX_DIR (which defaults to APPDATA or ProgramData).

Quarto users

The command quarto install tinytex is also impacted by this problem on Windows, and I have submitted a similar fix to Quarto. Before it is applied, moving TinyTeX by yourself can also fix the problem. The only issue is that for non-R users, there is not an automatic solution like calling an R function, and you will have to move it manually (then run tlmgr path add and also tlmgr postaction install script xetex if you use XeLaTeX).

If any Windows users run into any issues when installing or moving TinyTeX to the ProgramData folder, please feel free to let me know. Thanks!

【杂语周刊 vol.03】本末倒置 进展缓慢

2023年11月12日 20:55

回顾本周没有特别重要的事情,只是配合他人工作。

新的小组成立了,我担任组长,对本组的工作还是有些模糊,抽空写了份工作职责和任务分工,感觉不够理想,先临时交给领到了,还的细化再细化。

8号是中国记者节,上周做的视频就仓促发布了,没有精雕细磨,还是比较仓促的。我觉得我只是帮大家打杂,并没有实际参与拍摄和剪辑工作。

周六周天,加了两天班,把单位网站末班重新整理了下,本月底必须完成网站迁移工作,否则某中心的某个主任说要上告上级领导,说我们工作开展缓慢,问题是你得提供一个能用,基本功能能用系统,这实现不小,那也不行,平台是服务于人,别本末倒置。

时间过得非常快,每天有非常多的工作和任务,总是压的自己喘不过气,说是时间挤挤总是有的,但对于我来说,确实有点牵强。大家都在找我,问我在哪,什么时候来单位……我说快了,快了,至于多快,反正很慢。

最近工作状态不好,是得调整调整了……

下周有个活动,需要出差,让我与同事参与,想想也是很兴奋的……

Convert Definition Lists (`<dl>`) to Frames (`<fieldset>`)

2023年11月7日 08:00

When I suggested a department chair in 2019 that they may consider opening a blog so that all students and faculty in the department can write together (which sounded exciting to me), he expressed a concern that some readers might remember authors’ mistakes in the posts. Considering the reputation of the whole department, that is a valid concern. However, to err is human (and to forgive is divine). Everyone makes mistakes. I often update my old posts to correct mistakes or write a note saying certain information is oudated after a few years.

My notes did not have a formal form. Sometimes a note may be a blockquote, and sometimes it is just a normal paragraph. Today I was thinking how I could make them more consistent. One constraint is that it would be nice if I could express it in pure Markdown. Unfortunately, CommonMark does not support fenced Divs, otherwise I would use a fenced Div like:

::: Note
This post is outdated. Please ignore it.
:::

Eventually I decided to hack at definition lists. A note will be like:

Update on 2023-11-09

:   Please ignore this post. The method no longer works.

This will render to the HTML tag <dl> (with <dt> and <dd> inside).

Next I started to think about styling, and recalled the <fieldset> tag that I learned sixteen years ago.1 With a few lines of JavaScript, I was able to change <dl> to <fieldset>. Below is a demo:

Notice on 2023-11-07

Thank you for noticing this new notice!

Your noticing it has been noted, and will be reported to the authorities.

If you want to use my JS code, you can load it via:

<script src="https://cdn.jsdelivr.net/npm/@xiee/utils/js/dl-fieldset.min.js" defer></script>

Then you only need to write Markdown:

Title

:   Content

which will be rendered to HTML:

<dl>
  <dt>Title</dt>
  <dd>Content</dd>
</dl>

Then my JS code will convert it to:

<fieldset>
  <legend>Title</legend>
  Content
</fieldset>

Mission complete.


  1. See? This is why you should blog—things you learned, no matter how long ago, will not be wasted. ↩︎

【杂语周刊 vol.02】意外摔伤 新增家电

2023年11月5日 21:17

今天双十一战线太长了,从10月20好开始了,直到11月12日,总觉的优惠力度不太大。

房子装修好了,也入住了,自4月份开始,9月入住,前前后后折腾了大半年,智能家居也上了,自己组的2.5G网络也部署好了,目前针对我的2.5G网络和智能家居,我另起博文介绍。

 

单位院里子看到的残月和枯黄摇曳欲坠的树叶
单位院里子看到的残月和枯黄摇曳欲坠的树叶

今年618把大部分的家具和家电配齐之后,一直没看到合适的电视,刚开始看好的的小米的ES Pro 75寸,在雷鸟和小米之间犹豫,后来因为其他家具还没备齐,618上价格也没啥竞争力,外加电视并不是刚需,于是拖着一直没买,进了客厅,只见一面大白墙。

今年双11,我正在瞎逛的时候,京东和天猫都开始推小米电视 S Pro 75 Mini LED ,什么MiniLED、75寸、2200nits 的亮度、144Hz高刷、ΔE≈1 、4GB+64GB,就这配置居然5999,果断下单。使用了几天,感觉真心 不错,得益于MiniLED面板,效果和OLED有一拼,黑的地方完全黑,亮的地方很亮。播放我NAS里的4K 120帧的双子杀手,真心丝般顺滑。

本周工作也是比较繁忙的,周末去办公室加班,躺在椅子上,根本无心工作,刷抖音、淘宝,一下子好几个小时没了,工作还是没有做完。

最可气的是,周六晚上过马路,一个没注意不小心被路口固定路障桶的螺钉绊倒了,手机也摔坏了,胳膊和腿也磨破皮了,下巴也磕掉了点皮,还有iPod耳机充电仓也丢了,可谓是损失惨重,14 Pro的外屏和钢化膜全碎,手机左下角天线接缝处也摔裂了,心痛心痛。只能说是安慰自己是舍财免财,舍财免灾。

这周也觉得挺忙的,工作只会永远干不完,真的干不完。

部署 FreshRSS 内容聚合RSS订阅器

2023年10月30日 23:14

 

前年,我找一个 名叫 Lilina 的RSS订阅软件,部署在自己的服务上,我还写了一篇教程(点击查看 自建Rss订阅器),由于作者已经停更了,部署在php7.4及php8.0环境下存在报错,我也尝试修复了报错,也尝试汉化了部分功能,但是,Lilina 或多或少存在一些问题,比如拉取订阅源的时候非常慢,并且数据是以Json格式保存在数据目录下,无论是易用性,还是可靠性,不够完美,前些天逛博客,看到有人用一款名叫FreshRSS的开源订阅软件(基于PHP开发),简单试了下,感觉还可以,我看网上好多教程都是部署在Docker中,虽然宝塔中有Docker,服务器本身环境明明部署了PHP和MySQL,为什么还要开个Docker浪费性能呢。于是研究老半天安装流程,发现宝塔部署很Easy。

首先看看我部署的效果 https://rss.yefengs.com/

由于FreshRSS是基于 PHP + MySQL(可以选择数据库类型,当然MySQL好维护和管理),安装和博客安装别无二法。

部署流程

1.程序包下载

最新Releases发行版  https://github.com/FreshRSS/FreshRSS/releases

当前最新版1.22.0版 https://github.com/FreshRSS/FreshRSS/archive/refs/tags/1.22.0.zip

2.简易部署流程

我们在宝塔上部署,首先新建站点(PHP + MySQL),将源码上传至网站根目录并解压,其次最主要的是配置网站的运行目录,我们来到宝塔网站设置中,找到“网站目录”,在运行目录中选择运行目录为/p,如图所示,然后保存即可。

接下来,打开新建的网站,按照提示,选择中文,一路下一步,在选择数据库时选择MySQL,填入刚才新建网站时数据库的配置信息,直到创建管理员

安装完成之后,就可以添加订阅了(左上角管理订阅那里的+号),可以看看后台的设置信息,程序还是比较简洁的。想不用登录查看订阅的内容,来到设置中心,找到管理 -> 认证 ,找到“允许匿名阅读”启用即可。

想让FreshRSS自动刷新订阅,可以参考宝塔的计划任务来实现,依据自己需求,设置执行周期即可,执行代码如下,具体路径可参照自己网绝对路径即可设置。

php /home/wwwroot/yourdomain.com/app/actualize_script.php > /tmp/FreshRSS.log 2>&1

R Markdown v1: Feature Complete!

2023年10月21日 08:00
Update on 2014-10-09

The markdown package has been superseded by the litedown package. The former is a strict subset of the latter. Please consider using litedown instead.

When we say “R Markdown”, we usually refer to the rmarkdown package, which is based on Pandoc and knitr. Prior to the rmarkdown package, there actually existed an older version of R Markdown, which was based on the markdown package instead of Pandoc. Later we called this version “R Markdown v1”.

R Markdown v1 was more or less an experiment, although many people liked it (perhaps because they had suffered for too long from LaTeX). It did not take long before we started developing v2, i.e., rmarkdown. V1 was much less powerful than v2. For example, it only supported HTML output but not LaTeX or any other output format. The now widespread CommonMark specs did not exist at that time, so v1’s Markdown syntax was chaos just like pretty much any other Markdown conversion tools (each having its own homemade or wild-caught specs) except Pandoc.

After R Markdown v2 became mature, v1 did not seem to be of much value any more. Perhaps it would just quietly fade out and eventually die. But…

But Jeroen Ooms, the great ninja, created the R package commonmark later. That changed the destiny of the markdown package. Previously, markdown was based on a C library, which had been deprecated for a long time. Last year, I removed the C library from markdown, and rewrote the package based on commonmark.

Although I’m a minimalist, commonmark’s Markdown features are too limited in my eyes. On the other hand, Pandoc’s Markdown is too rich to me. What I did in the markdown package was a compromise. You can read this chapter to learn which features are supported in this package.

If you prefer reading slides over documentation, I have given a talk in May, which was not recorded but you will not miss anything by only reading the slides.

This post is not meant to encourage people to use R Markdown v1. On the contrary, I think v2 and Quarto are better choices for most people. I just want to mention the revived markdown package, and there is a small chance that it actually meets some people’s need.

Declaring “feature complete” is hard, and it is definitely not a firm rejection to all future feature requests. It only means that “being feature-rich” is not the goal of this package. In particular, new features that require substantial work are unlikely to be added. Please feel free to request new features without a high expectation that they would be implemented.

Feature complete!

P.S. Currently, the markdown repo is the only GitHub repo that I maintain and has zero open issues. For years, I thought Will Landau was the only person on earth who could possibly achieve this.

A Simple HTML Article Format

2023年10月20日 08:00

A couple of weeks ago I wrote about a lightweight HTML presentation format, snap slides. Today I want to briefly introduce a simple and lightweight HTML article format. You can find an example page here, which is also its documentation. You can generate an article like this with either the R package litedown or Quarto or any other tool that can generate HTML output. Under the hood, this article format is based on CSS and JS, which can be reused on any web pages.

If you use R Markdown or plain Markdown, you can install.packages('litedown') and specify the output format:

output:
  litedown::html_format:
    meta:
      css: ["default", "@article"]
      js: ["@sidenotes", "@appendix"]

If you use Quarto, you can also include the relevant CSS and JS:

format:
  html:
    minimal: true
    toc: true
    include-after-body:
      text: |
        <script src="https://cdn.jsdelivr.net/npm/@xiee/utils/js/sidenotes.min.js" defer></script>
        <script src="https://cdn.jsdelivr.net/npm/@xiee/utils/js/appendix.min.js" defer></script>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xiee/utils/css/default.min.css">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xiee/utils/css/article.min.css">
        <style type="text/css">header{text-align: center;}</style>

If you use other HTML generators, you can reuse the HTML code in the above Quarto example (in the text field).

The main features of this article format are: side elements (TOC, footnotes, sidenotes, and references), full-width elements, floating quotes, margin embedding, and appendices. Again, you can read its documentation to know more.

新房装修分享

2023年10月15日 18:59

新家装修自去年9月初开始,收到去年疫情管控以及自己本身拖拉的影响,拖拖拉拉到了今年8月才基本结束,在今年的9月总算住了进来。装修真是一个费钱,费心,不断妥协的过程,这一年下来,工作的收入基本全砸在了里面,额外还从爸妈那里花了不少钱。不过总算是结束了,用文字和图片的形式记录一下装修的亮点、心得与踩坑。

户型改造

首先是布局改造,整体变动不大,一个是把次卫门口处的洗手池位置改进了厨房,用来放冰箱,不然冰箱没有合适的位置来放。另一个是把原来主卧的门封了起来,把书房和主卧打通,做成了套房的形式。这样减少了客厅开间里门的数量,让电视背景墙更大,整体性更好。同时也把客厅与阳台设计成了一个整体,显得空间更大。

门头抬高

在拆改的过程中,把家里的所有门洞的抬高到了梁,正常门只有约2m高,抬高后的门大概约2.3m高左右。从现在装修完来看,效果相当不错,因为门洞拉高之后,门显得细长一些,有拉伸层高的观感,显得家里层高更高一下。同时也不会像有的人家里加的门楣板那种,尤其是开门之后,看起来整体性并不好。

水电改造

水电改造相比原来的毛坯加了非常多的点位,主要注意的是厨房的厨下用来安装厨下净水器,洗碗机,以及吸烟烟机,冰箱插座的位置需要在设计阶段安排好,避免后续冲突。阳台的家政柜中和水池下柜分别预留了充电插座,以及洗衣机,烘干机,扫地机器人的供电插座点位。在两个卫生间门口留了装小夜灯的插座/86盒位置,卫生间内预留只能马桶以及智能镜柜的供电插座或者供电线。在各个房间的窗户顶位置预留了电动窗帘的点位。剩下的地方基本上能想到后续可能使用到的顺手的地方都留的插座,比如沙发,电视柜(尽可能多,大部分人家中这里的小电器都很集中),房间角落(用来接电风扇之类的)。除了这些需要特别注意的地方,其他都是按照设计师给的点位去做的。
最后完工安装插座总共装了60多个,从目前入住的使用感受来看,点位留的很充足,用起来很顺手。
对了还有记得给开关留零线,这样如果想做智能家居时,只需要换个开关就行,而且不挑。

网络布局

新家的网络布局是完全我一手规划的,在每个房间都有留网线,在客厅和书房有做了特别的规划和加强。设计的思路主要是全屋2.5G网口接入,客厅与书房两台路由器做有线mesh组网。客厅电视柜位置有预留三个网口,一个网络做单独的IPTV接入网口,另外两个网口做主路由(可选)的来程和去程。然后进入交换机中再连接到各个房间中。书房设计了三个网口,一个用来接电脑,一个用来接mesh路由和NAS,还有一个备用。两个路由组网,WiFi覆盖家中的面积还是非常OK的,基本没有死角。同时两个卧室如果需要接网线也有网口可以接,在需要提升网络稳定性可以连接。

卫生间设计

卫生间的设计次卫比较简单,就是马桶,花洒,以及为了解决原次卧门口洗手池位置被移走,放在卫生间中的一个超小洗手池。主卧花的心思比较多,做了下沉式的淋浴间和玻璃隔断,而不是传统的淋浴房。同时为了解决平时不想洗衣服的困扰,还在马桶上方设计了一个壁挂洗烘洗衣机,用来洗平时的贴身衣物。两个卫生间都是用的60*120cm的大砖上墙,最后小效果非常简洁,美缝完之后,一体性非常好。唯一的缺点可能就是贴砖的工费比较贵了。

净水器与管线机

管线机预埋

管线机真的是解决喝水难题的重要发明,要多少温度的水,点一下就可以立刻出来。以前尤其是夏天的时候,烧开水然后再等着喝水实在是太痛苦了。管线机如果想效果好的话最好再水电阶段就做好预埋,我装修的时候就是因为没有考虑好,导致现在管线机的电源线拖在外面。最好的预埋方式就是从厨房橱柜预埋PE净水管到餐边柜,然后餐边柜中预留一个插座,然后预埋50管到安装位置,这样电源线以及净水管都可以隐藏起来。

净水机选择

净水器的话一定要选择支持零陈水的,不然使用管线机这种场景,喝的都是高TDS值的陈水。

书房设计

书房做的比较简单,主要是作为书房和活动室来布置的,硬装上只做了一组书柜,剩下就都没做了。我在书房放了一个2m*0.8m的电动升降书桌。书桌旁边布置了一个矮柜,用来放我的NAS以及其他的小的东西。

书房与厨房外挂门

因为户型改动的原因,导致书房和厨房如果安装传统的门打开后都会干涉正常的行走动线,最后在设计师的推荐下选择了外挂吊柜门,安装后左右推拉不占用空间,看起来也不丑,黑色也算是家中色彩的一抹点缀。

全屋定制

全屋定制需要细心的对设计图纸,有没有哪里和家中的其他设计有冲突或者干涉,如果不仔细对图纸非常容易踩坑。另外全屋定制也是价格相对不透明的一个品类 ,不同的板材,加工工艺差价很大。如果追求质感强烈建议用烤漆工艺,但是真的贵。不过最最重要的是最后的落地安装,如果选择用全屋定制,一定要去工地看看落地细节,不然设计的再好,最后安装一塌糊涂都白搭,很多安装工人的技术以及对细节的重视和追求都非常差劲。

扫地机器人

扫地机器人的选择上现在竞争很激烈和透明,反而没有太多要注意的。只需要事先规划好放扫地机器人的位置,留好电源和上下水,尤其是上下水,这点很重要,有上下水扫地机器人基本就不太用去维护,只需要过一段时间换下尘袋,清理下基站就可以了。另外家中的家具尽量选择机器人可以进的高度,这样家中就基本不会有卫生死角。

智能电动窗帘

电动窗帘一个注意的就是要在水电改造时预留插座,另一个则是做窗帘盒要做宽一些,不然安装后两个窗帘可能会蹭到一起。另外一个点,就是电动窗帘买的时候不要从旗舰店买,会贵很多,拼多多的店铺包安装的价格会便宜很多。我买的杜亚的电动窗帘就是这种情况,拼多多能便宜三分之一到一半。

杂项

如果是壁挂空调,有条件的话可以提前规划一下做管路背出,这样安装出来会显得很简洁好看。

最后
再放些装修完的一些照片:

Create Tabsets from HTML Sections or Bullet Lists via JavaScript and CSS

2023年10月12日 08:00

As I wrote last month, code folding was the most requested feature in blogdown, of which I have given an implementation. Today I will demonstrate an implementation of another top requested feature: tabsets.

How a tabset works

The mechanism of tabsets is fairly simple. It boils down to a click event on a tab link, which triggers the display of a corresponding tab pane. The user interface in HTML is like this:

<div class="tabset">
  <div class="tab-link">Tab 1</div>
  <div class="tab-link">Tab 2</div>

  <div class="tab-pane">Pane 1</div>
  <div class="tab-pane">Pane 2</div>
</div>

If the first tab link is clicked, we can add a class, say, active, to both the first link and the first pane.

  <div class="tab-link active">Tab 1</div>
  <div class="tab-pane active">Pane 1</div>

With some simple CSS, we can control the visibility of panes, and style the clicked link differently, e.g.,

.tab-pane {
  display: none;
}
.tab-pane.active {
  display: block;
}
.tab-link.active {
  border: 1px solid;
}

My implementation

There are several existing implementations of tabsets (e.g., in Bootstrap). The problem is that they are usually not tailored to Markdown users, and you have to prepare the appropriate HTML code by yourself. I have done an implementation today that works for both Markdown and HTML users.

You can find the source code tabsets.js and tabsets.css in my GitHub repo misc.js. For users, you certainly do not need to read the source, but can use it directly:

<script src="https://cdn.jsdelivr.net/npm/@xiee/utils/js/tabsets.min.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xiee/utils/css/tabsets.min.css">

If you are not satisfied with the styling, you can provide your own CSS and do not have to use my CSS.

Demo tabs

Below is an example tabset.

First tab

Hello world! Ciao!

Second tab

Here is a table.

x y z
1
2
3

More text.

A level-4 heading

Third tab

Nested tabs!

Tab 1

Isn’t it cool? Keep going!

Tab 2

Where am I now?

You can also create a tabset using raw HTML:

Another 1

Div pane 1

Another 2

Div pane 2

You can keep nesting but I’ll stop here.

Fourth tab

Enough tabs? Let me show a tabset created from a bullet list instead of section headings:

  • First bullet

    Hi, bullet!

  • Second bullet

    This is the initial active tab.

  • Third bullet

    Bye, bullet!

Okay, I’m done now.

Documentation

HTML users

If you know HTML and prefer writing HTML, the required DOM structure has been mentioned in the first section of this post. Basically, you provide a container element with the class tabset (it does not have to be a <div>). Inside the container, you have a number of elements with the class tab-link, and the same number of elements with the class tab-pane.

When the i-th link is clicked, the i-th pane will be shown. You can set a certain tab link to be active initially by adding the class active to its HTML tag.

Note that you can have nested tabsets, e.g., a tabset inside a tab pane of a parent tabset.

Markdown users

If you prefer writing Markdown to be rendered to HTML by other tools (e.g., Hugo or the R package markdown), here is how you create a tabset:

  1. Start with an element with the class tabset. This can be any type of element. For example, a heading:

    ## Demo tabs {.tabset}
    

    or an empty <div>:

    <div class="tabset"></div>
    

    You can also use a fenced Div with the class tabset:

    ::: tabset
    :::
    
  2. Below this element, write either a bullet list or a series of sections.

    • If you write a bullet list, the first element of each bullet item will become the tab link, and the rest of elements will become the tab pane, e.g.,

      * Tab one
      
        Content of tab one.
      
      * Tab two <!--active-->
      
        Content of tab two.
      

      I’d recommend this method since it is easier and more natural to create a tabset. However, please make sure to indent the tab pane content properly in the bullet list (using the visual mode to write Markdown in RStudio can help a lot).

      To specify an initial active tab, add a comment <!--active--> to the bullet item.

    • If you write sections, the first section heading level will be the level of headings to be converted to tabs, e.g.,

      ### First tab (level-3)
      
      Some tab content.
      
      ### Second tab
      
      More tab content.
      
      #### A normal heading
      
      This is a level-4 heading, so it will *not* be
      converted to a tab.
      

      You can set a certain tab to be active initially by adding the class active to the heading, e.g.,

      ### Second tab {.active}
      

      One downside of using section headings to create a tabset is that the headings may be included in the table of contents of a page, which is why I do not recommend this method.

  3. If you use sections to create a tabset, there are two ways to end the tabset (if you create a tabset with a bullet list, you do not need a special way to end it—it just ends where the list ends):

    1. Either start a upper-level heading (e.g., level 2 for the previous example), e.g.,

      ## My tabs {.tabset}
      
      ### Tab one
      
      ### Tab two
      
      ## A new level-2 section
      
      The previous tabset will be ended before this section.
      
    2. or write an HTML comment of the form <!-- tabset:ID -->, where ID is the ID of the element in Step #1, e.g.,

      ## My tabs {.tabset #my-tabs}
      
      ...
      
      <!-- tabset:my-tabs -->
      
      The previous tabset will be ended before this comment.
      

You can nest tabsets in other tabsets if you want, e.g.,

<div class="tabset"></div>

- Tab one

  Content

- Tab two

  Content

  <div class="tabset"></div>

    - Child tab one

      Content

    - Child tab two

      Content

- Tab three

If you use bullet lists to create tabsets, you can also put the list items inside the tabset div, which may be more intuitive, e.g.,

:::: tabset
-   Tab one

    Content

-   Tab two

    Content

    ::: tabset
    -   Child tab one

        Content

    -   Child tab two

        Content
    :::

-   Tab three
::::

I hope you can find this simple tabset implementation useful (it is not tied to blogdown or Hugo). Please feel free to let me know if you have any suggestions or comments.

Three Useful Functions in Base R: `regexec()`, `strrep()`, and `append()`

2023年10月11日 08:00

I just finished reviewing a pull request in the knitr repo that tries to improve the error message when it fails to parse YAML, and I feel three base R functions are worth mentioning to more R users. I have been inspired by Maëlle Salmon’s 3 functions blog series, and finally started writing one by myself.

regexec(): get substrings with patterns

If you want to master string processing using regular expressions (regex) with base R, the two help pages ?grep and ?regexp are pretty much all you need. Although I had read them many times in the past, I did not discover regexec() until about three years ago, while this function was first introduced in R 2.14.0 (2011-10-31).

This function gives you the positions of substring groups captured by your regular expressions. It will be much easier to understand if you actually get the substrings instead of their positions, which can be done via regmatches(), another indispensable function when you work with functions like regexec() and regexpr(). For example:

x = 'abbbbcdefg'
m = regexec('a(b+)', x)  # positions
regmatches(x, m)  # substrings
[[1]]
[1] "abbbb" "bbbb"

The length of the returned value depends on how many () groups you have in the regular expression. In the above example, the first value is the whole match (abbbb is matched by a(b+)), and the second value is for the first group (b+) (any number of consecutive b’s).

If you do not know regexec() or regmatches(), it is natural to do substr() like the aforementioned pull request originally did:

message = e$message
regex = "line (?<line>\\d+), column (?<column>\\d+)"
regex_result = regexpr(regex, message, perl = TRUE)
starts = attr(regex_result, "capture.start")
lengths = attr(regex_result, "capture.length")
line_index = substr(message, starts[,"line"], starts[,"line"] + lengths[,"line"] - 1)
column_index = substr(message, starts[,"column"], starts[,"column"] + lengths[,"column"] - 1)
line_index = as.integer(line_index)
column_index = as.integer(column_index)

Its goal is to extract a line and column number from a string of the form "line x, column y". I rewrote the code (using my obnoxious one-letter-variable-name style) as:

x = e$message
r = "line (?<row>\\d+), column (?<col>\\d+)"
m = regmatches(x, regexec(r, x, perl = TRUE))[[1]][-1]
row = as.integer(m['row'])
col = as.integer(m['col'])

Note that (<?NAME>...) means a named capture, so you could later extract the substrings by names instead of numeric indices, e.g., m['row'] instead of m[1]. But this is not important. It is okay to use a numeric index.

BTW, if you are new to regular expressions and not sure if you should use perl = TRUE or FALSE (often the default) in the regex family of functions, I’d recommend perl = TRUE. Perl-compatible regular expressions (PCRE) should cause you fewer surprises and are more powerful.

strrep(): repeat a string for a number of times

How many times have you done this?

paste(rep('abc', 10), collapse = '')

I have done it for numerous times. Now, no more rep() or paste(). Use strrep() instead:

strrep('abc', 10)

It is even vectorized like most other base R functions, e.g.,

strrep(c('abc', 'defg'), c(3, 4))

I do not want to pretend that I have always known everything—in fact, I did not discover this function until about two years ago.

It is common to generate N spaces like the original pull request did:

spaces = paste(rep(" ", column_index), collapse = "")
cursor = paste(spaces, "^~~~~~", collapse = "")

And I rewrote it as:

cursor = paste0(strrep(" ", col), "^~~~~~")

append(): insert elements to a vector

Maëlle has mentioned append() in her post. Interestingly, it could be used in this pull request, too. Original code:

split_indexes = seq_along(meta) <= line_index
before_cursor = meta[split_indexes]
after_cursor = meta[!split_indexes]
error_message = c(
  "Failed to parse YAML: ", e$message, "\n",
  before_cursor,
  cursor,
  after_cursor
)

New code:

x = c("Failed to parse YAML: ", x, "\n", append(meta, cursor, row))

I remember when I first learned S-Plus in 2004, I was surprised to see a classmate wrote a t.test() function by herself (which was actually cool) and she was equally surprised when I told her that there was a built-in t.test() function. I think similar things still happen today. If you are not aware of regexec(), strrep(), or append(), it is easy and tempting to reinvent them, which can make your code lengthy and complicated.

斜挎包挑选记

2023年10月5日 21:10

之前一直有两个包常用于日常通勤和外出出行,一个是多年前买的小米的10L小背包,一个是去年在拼多多买的高仿NIID R1斜挎包。
小米小背包
小米小背包从买到现在一直在使用,包很轻,容量却相当可观,背个iPad Pro加上水杯,伞,再加上轻薄外套都不在话。说实话,算是爱不释手的类型,但是也因为包本身很轻,所以都是薄薄的布料,当我出行的时候想带相机[Ps: 相机是索尼α6300,相机加上镜头一斤多]时,就不敢往里放,害怕磕碰。同时也因为是双肩包,同时分类收纳隔层也不多,外出的时候其实不是很方便,取东西或者放钥匙等都不好用,所以我基本上只在需要背比较多东西且不在意磕碰的时候才会使用,过去一年中上班通勤需要带饭的时候一般我都会使用它。另外小米小背包真的很便宜,当时买的大概才20元左右,用了这么多年,一点坏的迹象都没有。
R1
高仿NIID R1斜挎包,去年四十多购于拼多多,皮质质感的表面,但是除此之外做工很拉垮,内里也很松散,不过正版300+的价格,40多的东西也不能有什么品质的要求。这个包的容量也不算小,放个iPad Pro +雨伞水杯也没问题,但是衣服是放不下的。这个包内部有不少分隔,不过由于设计不是很合理,加上买的盗版,内衬松散,放东西用起来很难受,我基本只用来主袋放伞,前袋放钥匙和公交卡,其他空间都是浪费的。这个包的内衬还算稍微有些厚,所以我想带相机出门的时候一般会把相机放在包里,但是,放了相机之后,基本上其他的东西都不太放得下了,背的感觉也会变差很多。

最近因为这个斜挎包用着不是特别合用,就萌生了换一个新的斜挎包的想法,心中理想的包型是内部分隔合理,体积不是很大,背在身上看起来要相对贴合。这样基本能满足我日常通勤的需要,能够包正常需要随身携带的东西收纳清楚同时方便取拿,不浪费空间。同时最好在需要的时候能够放下我的相机。确定了这个目标,心理大概有个筛选条件了,经过一番搜索比较,发现满足我需求的包大概都落在长度在30cm左右,容量在5-10L这样。在淘宝,京东,拼多多一番搜索之后,将目光放在了这几个之中:
[1] 马可·莱登单肩包

这个包体积和我之前的斜挎包差不多,但是内部的隔层看起来要合理很多,但是缺少前拉链,拿东西都需要翻开上盖才行。整体的设计看起来感觉还可以。价格淘宝上约150RMB。

[2] 光影行星星云斜挎包

这款包的设计和之前的包有很多相似之处,甚至前袋都是一样的倾斜设计,倾斜设计一点不好用。然后体积和容积也是基本差不多,基本被我PASS了。这款的价格正版价格大概200+,仿版约80RMB左右。

[3] tomtoc 斜挎包


这款包体积和容积相较之前的也差不多,这个包的外形设计更加简洁,同时前袋设计整齐,内衬的设计看起来也划分也算是比较合理,是我心中的备选之一。这款的价格RMB200+接近300。

[4] Bellroy Lite Sling

这款包的体积和容积稍小,同时包很轻薄。这个的前袋设计也是整齐的,取拿东西应该也比较方便。因为包的设计是轻薄类型,内衬的质感就差了些,不过分隔看起来还是比较科学的。这款的设计也算是比较喜欢,这款国内仿版拼多多50RMB左右,正版则是要700+RMB,实在消费不起。

Bellroy这个品牌还有其他的几个型号,设计和质感从评论中看起来我也中意,不过价格来到1000+,而且国内并没有仿版,只得放弃,暂时消费水平还没有到在一个包上花费上千元。

目前还在[3]和[4]中纠结,准备是先买一个Bellroy Lite Sling试试看,毕竟便宜,如果感觉不喜欢,就在双11买一个tomtoc 的斜挎包。

新包到手

拼多多仿品Bellroy Lite Sling到手,银色的布料观感还可以,还带点反光质感。布料比较薄但是强度还不错。主袋拉链比较顺滑,副带拉链比较涩。包整体很轻。算是能完美符合我通勤时使用的小包,但是有时出门时想塞个相机就装不下了。能够完美符合我通勤+外出拍照随身携带相机的小包据需寻找中。

我还在

2023年9月25日 15:21
正如好多博友说的,一年多没更了。很多人在问:你还在吗?我知道大家在想什么,譬如是不是要关站了?十年之约还能不能 […]

verible-verilog-format 使用指南

2023年8月6日 12:59

Verible 是一套 SystemVerilog / verilog 开发工具,包括解析器、样式检查器、格式化程序和语言服务器。这里为主要分享关于格式化工具verible-verilog-format的使用。用来格式化verilog代码,实现代码风格统一。

verible 项目托管于github,项目地址:https://github.com/chipsalliance/verible

安装

verible可以自行编译安装,也可以下载已经编译好的可执行文件。下载路径见github release。下载完成后,将下载到的压缩包解压,在文件夹的子目录verible-bin/中就可以找到 verible-verilog-format 了。

使用方法

使用命令调用:

verible-verilog-format -- [options] filename.v

举例:以原位替换的方式,按照默认规则进行verilog代码格式化, inplace及为原位替换选项。

verible-verilog-format --inplace testbench.v

其他可用配置参数可以参考,以为为部分选项的,全部选项见:https://chipsalliance.github.io/verible/verilog_format.html:

--column_limit 
(Target line length limit to stay under when formatting.);default: 100;
行代码长度限制,默认最长100个字符。 使用示例: --column_limit=200 设置最长字符数为200个

--indentation_spaces 
(Each indentation level adds this many spaces.);default: 2;
代码层级缩进深度,默认为2字符 使用示例:--indentation_spaces=4 设置代码层级缩进为4字符 

--assignment_statement_alignment 
(Format various assignments:{align,flush-left,preserve,infer}); default: infer;
赋值语句对齐方式:{对齐、左对齐、保留、推断} 默认值:推断;   

--case_items_alignment 
(Format case items:{align,flush-left,preserve,infer}); default: infer;
case语句格式化对齐方式:{对齐、左对齐、保留、推断} 默认值:推断;
      
--class_member_variable_alignment 
(Format class member variables:{align,flush-left,preserve,infer}); default: infer;
类成员变量格式化对齐方式:{对齐、左对齐、保留、推断} 默认值:推断;    

--compact_indexing_and_selections 
(Use compact binary expressions inside indexing / bit selection operators); default: true;
使用紧凑的二进制表达式索引/位选择运算符; 默认值:true;    

--distribution_items_alignment 
(Aligh distribution items: {align,flush-left,preserve,infer}); default: infer;
distribution对齐方式:{对齐、左对齐、保留、推断}); 默认值:推断;
    
--enum_assignment_statement_alignment 
 (Format assignments with enums: {align,flush-left,preserve,infer}); default: infer;
 枚举对齐方式:{对齐、左对齐、保留、推断}); 默认值:推断;
     
--expand_coverpoints 
 (If true, always expand coverpoints.); default: false;
 
--formal_parameters_alignment 
 (Format formal parameters: {align,flush-left,preserve,infer}); default: infer;
格式形参对齐方式:{对齐、左对齐、保留、推断} 默认值:推断;    

--formal_parameters_indentation 
 (Indent formal parameters: {indent,wrap});default: wrap;
形参缩进方式{indent,wrap};默认:换行;
    
--module_net_variable_alignment 
(Format net/variable declarations: {align,flush-left,preserve,infer}); default: infer;
wire声明对齐方式:{align,flush-left,preserve,infer}); 默认值:推断;
    
--named_parameter_alignment 
(Format named actual parameters: {align,flush-left,preserve,infer}); default: infer;
命名参数对齐方式:{对齐、左对齐、保留、推断} 默认值:推断; 
    
--named_parameter_indentation 
(Indent named parameter assignments:{indent,wrap}); default: wrap;
命名参数缩进方式:{缩进,换行}); 默认:换行;
    
--named_port_alignment 
(Format named port connections:{align,flush-left,preserve,infer}); default: infer;
端口名称对齐方式:{对齐、左对齐、保留、推断}); 默认值:推断;
   
--named_port_indentation 
(Indent named port connections: {indent,wrap});default: wrap;
端口名称缩进方式:{缩进,换行}); 默认:换行;
    
--port_declarations_alignment 
(Format port declarations:{align,flush-left,preserve,infer}); default: infer;
端口声明格式:{对齐、左对齐、保留、推断} 默认值:推断;
    
--port_declarations_indentation 
(Indent port declarations: {indent,wrap});default: wrap;
端口声明缩进方式:{缩进,换行}); 默认:换行;
    
--port_declarations_right_align_packed_dimensions
(If true, packed dimensions in contexts with enabled alignment are aligned to the right.); default: false;
对齐的上下文中的尺寸将向右对齐。默认值:false;
    
--port_declarations_right_align_unpacked_dimensions 
(If true, unpacked dimensions in contexts with enabled alignment are aligned to the right.); default: false;
    
--struct_union_members_alignment 
(Format struct/union members: {align,flush-left,preserve,infer}); default: infer;
结构体,联合成员变量对齐方式: 默认:推断
    
--try_wrap_long_lines (If true, let the formatter attempt to optimize line
 wrapping decisions where wrapping is needed, else leave them unformatted.
 This is a short-term measure to reduce risk-of-harm.); default: false;
    
--wrap_end_else_clauses 
(Split end and else keywords into separate lines); default: false;
将 end 和 else 关键字分成单独的行;默认值:false;

--inplace (If true, overwrite the input file on successful conditions.);default: false;
原位替换为格式化后结果,默认:false   

根据filelist文件批量处理

为了方便使用,使用python写了一个基于filelist.f进行批量处理的脚本, 可以根据filelist进行代码批量格式化,有需要的可以自取:

https://github.com/wherelse/verilog-formatter

https://circuitcove.com/tools-verible/ 
https://chipsalliance.github.io/verible/verilog_format.html

从零开始挑选相机

2022年10月15日 22:03
如何从一个连手机拍照基本参数都一窍不通的小白,经过各种学习选出一款性价比不错的相机作为恋爱 100 天的礼物送给女朋友。

189List:一个全新的天翼云网盘的目录列表程序,CTList升级版

2022年1月22日 23:04

说明:由于CTList因为接口问题导致程序无法使用,加上存在一些问题,所以直接放弃更新;最近萌咖大佬抽时间,摒弃CTlist的短板,开发了新的列表程序,这里暂且命名189List,可以说几乎将性能优化到极致,之前爆内存等问题统统不存在了,且安装配置更加简单。

当然,如今用天翼网盘的人估计也不多了,也不怕被滥用,所以现在的程序已经不需要授权码了,直接可以使用,且样式和CTlist差不多,这里就不截图了。

功能

  • 支持添加多账户(多配置互不干扰)
  • 支持自动签到(每天自动增加网盘容量)
  • 支持异步缓存目录结构,无须等待
  • 支持加密访问路径(隐私分享)
  • 支持展示任意目录,自定义根目录
  • 支持以JSON格式输出内容(作为后端)
  • 支持获取预览图链接,方便前端展示
  • 支持只读挂载到PotPlayer(WebDAV), nPlayer(WebDAV),kodi(WebDAV)

安装

Github地址:https://github.com/MoeClub/vList/tree/master/189List

这里只说Linux的用法。

1、安装CTList

#新建并进入CTList目录
mkdir /opt/189List && cd $_

#64位系统下载
wget https://raw.githubusercontent.com/MoeClub/vList/master/189List/amd64/linux/189List

#32位系统下载
wget https://raw.githubusercontent.com/MoeClub/vList/master/189List/i386/linux/189List

#arm32架构下载
wget https://raw.githubusercontent.com/MoeClub/vList/master/189List/arm/linux/189List

#arm64架构下载
wget https://raw.githubusercontent.com/MoeClub/vList/master/189List/arm64/linux/189List

下载好二进制后,继续使用命令:

#给予权限
chmod +x 189List

#下载主题文件
wget https://raw.githubusercontent.com/MoeClub/vList/master/index.html

2、新建配置文件

vi /opt/189List/config.json

首先按一下键盘的i键,进入编辑模式,这时候可以使用键盘进行编辑,编辑代码详解如下:

#单账号,rootId为展示的目录id,默认为根目录;rootPath指定某账户访问路径, 如ip:8000/189List,多账户时每个路径必须唯一
[
  {
    "user": "手机号",
    "passwd": "密码",
    "rootId": "-11",
    "rootPath": "/189List"
  }
]

#多账号,以下为2个账号,有几个复制几个,注意用英文逗号分隔
[
  {
    "user": "手机号",
    "passwd": "密码",
    "rootId": "-11",
    "rootPath": "/189List"
  },
  {
    "user": "手机号",
    "passwd": "密码",
    "rootId": "-11",
    "rootPath": "/189List"
  }
]

全部编辑好了后,按一下键盘的esc键退出编辑模式,接下来输入英文:wqenter键确定,即保存退出。

全部配置参数详情参考,可根据需求自行加入:

#json文件格式标准中规定最后一行数据没有逗号.
[
  {
    "disable": false,
    // 是否关闭该配置
    
    "user": "手机号",
    
    "passwd": "密码",
    
    "rootId": "-11",
    // 根目录文件夹ID, 默认 -11
    
    "rootPath": "/Cloud189",
    // 挂载的虚拟路径
    
    "authItem": "abc:123@/Movie|xyz:456@/Private/*",
    // HTTP 401 加密项, 以 | 为分隔符. 可以 * 结尾匹配路径. 路径为去掉挂载虚拟路径后的路径.
    // abc:123@/Movie  当访问 /Cloud189/Movie 时需要提供用户名 abc 和 密码 123, 但 /Cloud189/Movie 下的子项无需提供用户名密码.
    // xyz:456@/Private/*   当访问 /Cloud189/Private 及其子项时需要提供用户名 xyz 和 密码 456.
    
    "nodeInterval": 1800,
    // 目录刷新间隔, 最小值: 300
    
    "linkInterval": 300
    // 下载链接刷新间隔, 最小值: 60, 最大值 360
    
  }
]

3、调高limits

这里调整下limitslinux下可能由于高并发情况下会出错,使用命令:

[ -f /etc/security/limits.conf ] && LIMIT='262144' && sed -i '/^\(\*\|root\)[[:space:]]*\(hard\|soft\)[[:space:]]*\(nofile\|memlock\)/d' /etc/security/limits.conf && echo -ne "*\thard\tmemlock\t${LIMIT}\n*\tsoft\tmemlock\t${LIMIT}\nroot\thard\tmemlock\t${LIMIT}\nroot\tsoft\tmemlock\t${LIMIT}\n*\thard\tnofile\t${LIMIT}\n*\tsoft\tnofile\t${LIMIT}\nroot\thard\tnofile\t${LIMIT}\nroot\tsoft\tnofile\t${LIMIT}\n\n" >>/etc/security/limits.conf

windows系统下不需要调。

4、启动189List

新建一个简单的systemd配置文件,适用CentOS 7Debian 8+Ubuntu 16+

使用命令:

#设置你的运行监听端口,即你可以通过ip:端口访问程序,这里默认8000。
port="8000"
#将以下代码一起复制到SSH运行
cat > /etc/systemd/system/189list.service <<EOF
[Unit]
Description=189list
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/189List
ExecStart=/opt/189List/189List -bind 0.0.0.0 -port ${port}
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
#启动并设置开机自启
systemctl start 189list
systemctl enable 189list

相关命令:

启动:systemctl start 189list
停止:systemctl stop 189list
重启:systemctl restart 189list
查看状态:systemctl status 189list

启动后就可以使用ip:8000或其它端口访问程序列表了,有些访问不了的注意下防火墙端口没打开,可使用命令:

#CentOS 7
firewall-cmd --zone=public --add-port=8000/tcp --permanent
firewall-cmd --reload

#Debian/Ubuntu
ufw allow 8000

安装过宝塔面板的,可以直接去后台安全组开放端口,且有些服务商,如阿里云,腾讯云还需要去后台面板的安全组开放该端口才可以访问。

绑定域名

提示:有宝塔面板的直接安装nginx绑定,没有的就可以使用caddy,2选1即可。

宝塔面板

先进入宝塔面板,然后点击左侧网站,添加站点,再点击添加好了的域名名称,这时候就进入了站点配置,点击反向代理,目标URL填入http://127.0.0.1:8000,不要设置缓存,再启用反向代理即可。

如果要启用SSL,就需要在设置反向代理之前,直接在站点配置点击SSL,申请免费let证书,然后再启用反代即可。

Caddy绑定

安装Caddy

mkdir /usr/local/caddy
wget -O /usr/local/caddy/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64"
chmod +x /usr/local/caddy/caddy

配置Caddy

#以下全部内容是一个整体,请修改域名后一起复制到SSH运行!
echo "https://www.moerats.com {
reverse_proxy 127.0.0.1:8000 {
      header_up X-Real-IP {remote_host}
      header_up X-Forwarded-Proto {scheme}
   }
}"> /usr/local/caddy/Caddyfile

注意该配置会自动配置ssl证书,请提前解析好域名并生效,且服务器80/443端口为开放状态,不然绑定会出错。

启动Caddy

#将以下代码一起复制到SSH运行
cat > /etc/systemd/system/caddy.service <<EOF
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
User=root
ExecStart=/usr/local/caddy/caddy run --environ --config /usr/local/caddy/Caddyfile
ExecReload=/usr/local/caddy/caddy reload --config /usr/local/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
EOF

#启动并设置开机自启
systemctl start caddy
systemctl enable caddy

就可以打开域名进行访问了。

最后更多功能和参数还在调试中,后续放出来;使用过程中有BUG也可以随时反馈。

❌