分类
工具

给 Hexo 增加替换大图并生成缩略图的功能

我厂的官方博客使用 Hexo 搭建,静态页比较符合我厂的技术风格。

我厂博客可能会用到一些很大的火焰图 SVG,厂长担心打开速度会受拖累,所以让我想办法把大图替换成缩略图,单击再打开原图。

经过一些研究,我大概得到以下线索:

  1. Hexo 会加载 `/scripts` 下面的脚本
  2. Hexo 是用钩子机制,跟 WordPress 非常像
  3. Hexo 的钩子函数可以使用 Promise

那么,大概方案就出来了:

  1. 添加脚本,等待 `after_post_render` 钩子。这个钩子触发的时候,博文已经从 markdown 渲染成 html。
  2. 使用 cheerio,找到所有图片,检查图片大小,略过不太大的图片(我的标准是 500K)
  3. 对大图生成缩略图,替换 `src`。

因为目标是 SVG => PNG/JPG,在 npm 里搜了一圈,发现大家不是 phantom.js 就是 puppeteer,那我就不需要用库了,自己直接写好了。

经过反复的尝试,最终完成的代码如下:

hexo.extend.filter.register('after_post_render', async function (data) {
  /** 摘录、详情、内容 */
  const dispose = ['excerpt', 'more', 'content'];
  for (const key of dispose) {
    const $ = cheerio.load(data[key], {
      ignoreWhitespace: false,
      xmlMode: false,
      lowerCaseTags: false,
      decodeEntities: false
    });

    // 获取所有需要调整大小的图片路径
    let images = $('img').map(async function () {
      let src = $(this).attr('src');
      if (!src) {
        if (config.log) {
          console.info("no src attr, skipped...");
          console.info($(this));
        }
        return;
      }

      // 顺便加上 `loading="lazy"`
      $(this).attr('loading', 'lazy');

      // take snapshot fo big SVGs
      const originalSrc = src;
      if (/\.svg$/.test(src)) {
        const img = resolve(__dirname, '../source', originalSrc);
        const {size} = await stat(img);
        if (size <= config.limit) {
          return;
        }
        $(this)
          .attr('data-src', src)
          .attr('src', `${src}.png`);
        return img;
      }
    }).get();
    images = await Promise.all(images);
    images = images.filter(image => !!image);
    // 只启动一次 pupppeteer,希望减少系统消耗
    if (images.length > 0) {
      const browser = await puppeteer.launch({
        defaultViewport: config,
      });
      const page = await browser.newPage();
      for (const image of images) {
        await page.goto(`file://${image}`);
        const buffer = await page.screenshot();
        const name = basename(image);
        console.log('screenshot for svg: ' + name);
        // 这一步很重要,因为 `after_post_render` 的时候图片还没有复制,所以要利用 hexo 自身的复制功能复制图片
        route.set(`images/${name}.png`, buffer);
      }
      await browser.close();
    }
    data[key] = $.html();
  }
  return data;
});

这段代码里,搞清楚应该用 route.set() 稍微花了些时间,Hexo 的文档写的真是差强人意。这个方法可以向指定位置写入内容,写入的可以是文本,也可以是二进制 buffer。如果写入的是绝对路径,那么就是简单的写入;如果是相对路径,就是向博文环境内写入,接下来的复制过程也会复制到最终代码里。

分类
vue

迎接 Vue 3.0

思否(SegmentFault)去年引入了新的运营合伙人,大张旗鼓,计划重新梳理视频教程体系。第一个举措就是推出新系列:思否编程公开课。这个系列的优势在于结合社区运营的知识与一线讲师的实战能力:即社区寻找大家关心的话题,请相关领域的讲师来讲解,达到受众明确、内容扎实的目标。

很高兴能够得到邀请,做了一期《迎接 Vue 3.0》,我也趁机深入了解了一把 Vue 3.0。

Vue 3.0 原计划 2019 年发布,但是开发过程有些波折,一些早期的设计被放弃了,因为不够好;引入了新的更好的设计,于是延期至今,仍然是 alpha 版。在新的设计下,Vue 3.0 更快,快到不需要时间切片;而且还能基本保持与 2.x 版本的兼容。

至于新增的 Compisition API,其实是选修课,类似 Async 之于 Promise。有了它,我们能更好的复用代码、维护代码;不用它,也不耽误我们享受其它升级带来的便利。

其实看过很多 Vue 3.0 的东西之后,我越发感激开发团队为这个框架做得一切,越发感激开源文化为我们带来的新世界。

我备课一向认真充分,所以讲课的内容应该是现在最新最全面的,欢迎大家围观:【思否编程公开课】迎接Vue 3.0

分类
前端

给 Markdown 里的图片增加样式

在 markdown 里添加图片很容易,用这个语法即可:

![alt](/path/to/image "title")

但是如果图片需要一些特殊样式,就不太好搞。比如前两天,老板觉得博文中的二维码太大,不好看,让我改小点。我一开始只知道可以用 HTML 来做,因为 Markdown 内建支持所有 HTML,但总觉得不够优雅,尤其是,不知道怎么要求将来写博客的人都用 HTML 来写。

于是 Google 之,找到这篇文章:How to Style Images With Markdown,写得非常好,列举了很多添加样式的方法,尤其是使用 #hash + CSS 选择器的方法,很有想象力,推荐给大家。

首先,在 Markdown 里,给图片的 URL 添加 hash。这个动作并不会造成任何实质性的影响。

![alt](/path/to/image#thumbnail)

然后,在 CSS 里,定义“src 里包含字符串 #thumbnail”的规则即可:

img[src*="#thumbnail"] {
  max-width: 10rem;
}

[attr*=val] 选择器的意思是 attr 属性里包含 val 字符串。这里用 [src$="#thumbnail"] 效果是一样的。如果你想了解所有 CSS 属性选择器,还是推荐看 MDN 文档

分类
许愿

2020 年计划

今年真的是一言难尽……2019年年底的时候,大家常半开玩笑的说,虽然2019年可能不太好,但有可能是接下来十年里最好的一年。没想到一语成谶,2020 年从各方面来说,都在脱离大家期望的轨道,向着混乱的深渊奔去。

突入其来的冠状病毒穷凶极恶,它不在乎你是谁,也不在乎你在哪儿,更不在乎你想干什么。它就是快快的、稳稳的传播,把疾病和恐慌快速散播到全世界。

然后,就过了可能是几十年来最寡淡的春节。能够不出问题,大家就满足了。希望疫病能够尽快了结,大家的生活能够尽快恢复正常。无论如何,新年计划该做还得做。

分类
生活

瞎猜疫情结束后

我的几个预测,如果蒙对,纯属巧合。

分类
服务器端

解决 Raspberry Pi 4 安装 php-mbstring/php-curl 的问题

最近要在 flarum 上做二次开发,尝试直接用 php -S localhost:8080 未果,于是打算在树莓派上搭个开发环境,省得它整日落灰。

因为在本地创建过仓库,所以这次直接从 GitHub clone 项目下来,然后打算执行 composer install 安装依赖。结果提示差了 php-mbstring(解决汉字等多字节字符)和 php-curl(用于远程请求)两个模块。然后我就打算用 apt install php-curl 安装模块,没想到失败了,仔细看错误信息,因为这个模块依赖 libcurl3,但是系统里是 libcurl4,所以不行。

那就安装 libcurl3 呗,结果系统认为明显 libcurl4 更新,不给装 3,哪怕删了重装都不行。

后来查了半天,找到答案。原来我添加的源是 stretch 的,也就是面向 Debian 9 的;而 Raspberry Pi 的系统是基于 Debian 10,也就是 buster 的,所以依赖处理上,两方面就冲突了。

这个时候,需要用 sh -c 'echo "deb https://packages.sury.org/php/ buster main" > /etc/apt/sources.list.d/php.list' 把属于“buster”的源添加进系统,接着删掉之前 stretch 的源,然后 apt update 之后,就可以正常安装了。


参考链接:https://github.com/oerdnj/deb.sury.org/issues/1193

分类
技术

笔记:七牛云续费免费证书

这是一篇笔记,没啥技术含量。

七牛云有一定的免费额度,对于我这种技术博客来说,可用量充足,所以我很早就开始使用他们家的服务。我启用了二级域名 qiniu.meathill.com,用来存放所有静态资源。

主站 HTTPS 之后,如果加载非 HTTPS 资源,也会报错。所以需要在七牛也开启 HTTPS。这就需要 SSL 证书。七牛提供免费的 TrustAsia 证书,但是一次只能买一年份,不能自动续期,每次换证书都要折腾好久,所以简单写个笔记记一下。

分类
js

利用 Web Speech API 实现语音阅读试题(TTS)

孩子上小学一年级,寒假作业里有一项:每天做20个20以内的加减法。这个作业老师不直接布置,而是让家长负责,方式任意。那么,显而易见,这个工作就由我负责。然后我就顺手抄起 Vue 做了一个 Web App:http://mui.evereditor.com,源代码在:https://github.com/meathill/mui-teacher

然后老婆说,别的教学应用都会把题目念出来,这样比较有上课的感觉,问我能不能也把题目念出来。我之前大略有些印象,可以直接调用浏览器的原生 API 实现 TTS(text to speech),于是就想试试看吧。

使用“Web speech API”作为关键词,很容易找到 MDN 上的这个页面,继而了解到 SpeechSynthesisUtterance 这个类,接下来就简单了,直接参考 Using the Web Speech API 里的 Demo,就完成了下面代码:

doRead(index) {
  const content = this.$refs.line[index].textContent;
  const msg = new SpeechSynthesisUtterance(content.replace('-', '减'));
  speechSynthesis.speak(msg);
},

这里有三个注意事项:

  1. 系统会把 - 读成“至”,但我这里是加减的“减”,所以我要手动把它替换一下
  2. 从某个版本开始,发音必须由用户主动触发,即放在交互性事件里,不能在页面打开时自动读
  3. 发音效果跟浏览器有关,目前 Chrome 和 Safari 效果比较好

这样的 TTS,对于整段文字来说,效果一般,但是读一般的算式足够了,小朋友也挺喜欢。这个 API 还可以用作语音输入,不过考虑到模型的效果,没有尝试,想真正可用的话,还是用那些高级的在线版本吧。


图文无关,想出去玩……

分类
php

PHP built-in web server 支持自动查找入口

使用 php -s localhost:8080 可以快速启动一个开发服务器,非常方便,是我现在需要简单服务器支持时的首选。

不过我最初了解到这个功能的时候,它(可能)还不支持请求重写,也就是说,我们访问 /foo/bar,它就会去当前目录里查找 /foo/bar,找不到就 404。如果想要实现 index.php 重定向,必须手动编写路由文件,比较麻烦。我宁可用 nginx 实现,因为部署上线的时候早晚要用。

最近偶然发现,PHP built-in web server 已经支持请求重写了,如果命中,就会直接返回目标文件;如果没有命中,就会沿着目录往上找,直到找到 index.php 或者 index.html,或者到启动服务器的根目录,然后把请求地址放在 $_SERVER['PATH_INFO'] 里,留待 php 处理。

这样一来,无论是 WordPress,还是 Laravel,还是其它基于路由的单一入口项目,都可以直接使用 PHP built-in web server 开发了,简单方便快捷。甚至连纯前端项目,如果你不熟悉服务器端的配置,也可以简单的安装一个 PHP 来实现。

比如,在本地开发 WordPress,可以这样:

# 安装 php 和 mysql
brew install php
brew install mysql

# 配置 mysql root 用户密码,替换下面的 `NEWPASS` 
$(brew --prefix mysql)/bin/mysqladmin -u root password NEWPASS

# 下载并解压 wordpress.zip,进入目录,启动服务器
php -s localhost:8080

# 完成!

参考文档:

PHP manual: Built-in web server

分类
篮球

再见科比

小时候,受大我4岁的表哥影响,开始看 NBA。当时应该是乔丹第一次退役复出,我印象最深的是他的后仰式跳投和各种总决赛。

乔丹是公认的王者。乔丹二次退役之后,大家开始为他寻找接班人,当时呼声最高的是希尔、哈达威和科比。前两位看起来都比科比合适,更有风度,更儒雅随和。

结果大家都知道了,希尔、哈达威都只有灵光一现,只有科比所在的湖人成就三连冠霸业——但是这里面有多少应该归功奥尼尔,到现在也争不出个定论。

我当时也不喜欢科比,主要是觉得他目无尊长,让乔丹在最后一次全明星抱憾而归。接下来,乔丹彻底退役,我对 NBA 的兴趣也下降了,进入云球迷阶段。不过当时 NBA 也真不好看,原因有三:

  1. 没有统治级球队,没有传奇
  2. 马刺和活塞携手带来泥潭摔跤模式……
  3. 姚明加入 NBA,火箭队成为中国球迷的大主队,常规赛几乎只播火箭队;但是火箭队季后赛都是一轮游,对于云球迷来说,往后面看都不知道谁在打谁……

这种情况持续到 2008 年奥运会,科比来到中国,显示出巨大的影响力,让我感到震惊。同年,加索尔加盟湖人,帮助科比三进总决赛,并且夺下两冠。

时隔多年,我惊讶的发现,自己从一代科黑,生生的被改造成科粉。

以前觉得科比喜欢浪投,喜欢打英雄球;现在觉得,那是科比自信和负责任的表现。

以前觉得科比妨碍了乔丹最后一次表演;现在觉得,职业赛场上,全力以赴也是一种尊重。(感谢德国 8:0 沙特,7:1 巴西)

以前崇拜超级英雄,梦想成为奥尼尔、詹姆斯这种身体素质惊人的超人;现在工作了,发现自己更可能是芸芸众生中的一员,开始喜欢科比这样兢兢业业全力以赴的斗争精神。

另外,互联网的发展让信息传播得更快更多更好,我们也可以看到更多“非”比赛的信息。比如科比手指骨折简单处理一下就上场,跟腱断裂后单脚罚球,等等。让人不得不佩服这位硬汉。

然而我怎么也没想到,他的生命会在今天早上戛然而止,以这样一个无厘头的方式告别世界——乔丹、奥尼尔、甚至詹姆斯,你都可以想象他们无厘头的样子,唯独科比,我脑海里只有他咬碎钢牙战战战战到底的样子……还带着他最会打篮球的女儿——那个前几个月才以 115-27 狂胜对手而被大家熟知的女儿,我都不敢想象在人生的最后时刻,他们爷儿俩是怎么过的……

世事无常,且行且珍惜。今天起开始复工,以纪念科比。