普通视图

发现新文章,点击刷新页面。
昨天以前叶寻 | Cyrus Yip

排查手机卡顿

2025年2月21日 00:00

最近听朋友说小米 13 卡顿,我感觉不应该啊,这手机处理器是骁龙 8 第二代(2022年末的旗舰处理器)。

用安兔兔 v10.4.4 测试性能,才 134 万分,排行榜上面小米 13 的分数是 155 万,相差 21 万。嗯,这个结果不对劲。

看了内存使用,内存是 12G + 4G,可用只有 3.1G。但是手机也就运行了两个微信。剩余内存太少了吧。

看了下开机时间,居然开了 142 天,震惊!居然有人开手机那么久不重启。

把手机升级到最新的系统,再测一遍性能。原来 134 万,这次 160 万,比参考值多 5 万。这次正常了。操作也不觉得卡。

我猜是卡顿是因为手机开太久了,系统出现内存泄漏,性能下降。嘱咐朋友至少每周重启手机,要安装更新(这也是重启的机会)。

朋友有时候手机快用完电才去充,这样容易损耗电池。我嘱咐他多充电,让手机保持高电量。充电时尽量不要用手机,因为充电手机会发热,这时用手机就更热了,发热会降低电池寿命。

总结一下我的建议:

  • 更新系统
  • 每周重启,或者卡顿就重启,也可以设置每天定时开关机
  • 多充电,不要到 20% 这样低电量才充
  • 以后买新手机先用安兔兔测试性能,和参考值相差几万应该是正常的

手机过了一个星期都没卡,问题算是解决了。期间朋友每天重启手机,一开始手动重启,后来设置了每天自动关开机。

英式英语音标

2025年2月26日 00:00

本文采用 DJ 音标。英式英语中,元音有 20 个,辅音有 24 个,常用辅音连缀有 4 个。

元音:/iː/, /ɪ/, /e/, /æ/, /ɑː/, /ɔː/, /ɒ/, /uː/, /ʊ/, /ə/, /ʌ/, /ɜː/, /eɪ/, /aɪ/, /ɔɪ/, /əʊ/, /aʊ/, /ɪə/, /eə/, /ʊə/

辅音:/p/, /b/, /t/, /d/, /k/, /ɡ/, /f/, /v/, /θ/, /ð/, /s/, /z/, /ʃ/, /ʒ/, /h/, /tʃ/, /dʒ/, /m/, /n/, /ŋ/, /r/, /j/, /w/, /l/1

辅音连缀:/ts/, /dz/, /tr/, /dr/

元音 #

元音包括单元音和双元音。

单元音(Monophthongs) #

  1. /iː/ see /siː/
  2. /ɪ/ bit /bɪt/
  3. /e/ bet /bet/
  4. /æ/ cat /kæt/
  5. /ɑː/ car /kɑː/
  6. /ɔː/ law /lɔː/
  7. /ɒ/ lot /lɒt/
  8. /uː/ blue /bluː/
  9. /ʊ/ put /pʊt/
  10. /ə/ sofa /ˈsəʊ.fə/
  11. /ʌ/ cup /kʌp/
  12. /ɜː/ bird /bɜːd/

双元音(Diphthongs) #

  1. /eɪ/ day /deɪ/
  2. /aɪ/ my /maɪ/
  3. /ɔɪ/ boy /bɔɪ/
  4. /əʊ/ go /ɡəʊ/
  5. /aʊ/ now /naʊ/
  6. /ɪə/ here /hɪə/
  7. /eə/ care /keə/
  8. /ʊə/ tour /tʊə/

辅音(Consonants) #

辅音包括爆破音、摩擦音、破擦音、鼻音、近音、边音。

爆破音(Plosives) #

  1. /p/ pen /pen/
  2. /b/ bat /bæt/
  3. /t/ top /tɒp/
  4. /d/ dog /dɒɡ/
  5. /k/ cat /kæt/
  6. /ɡ/ go /ɡəʊ/

摩擦音(Fricatives) #

  1. /f/ fish /fɪʃ/
  2. /v/ van /væn/
  3. /s/ see /siː/
  4. /z/ zoo /zuː/
  5. /ʃ/ she /ʃiː/
  6. /h/ hat /hæt/
  7. /ʒ/ measure /ˈmeʒə/
  8. /θ/ think /θɪŋk/
  9. /ð/ this /ðɪs/

破擦音(Affricates) #

  1. /tʃ/ chip /tʃɪp/
  2. /dʒ/ jam /dʒæm/

鼻音(Nasals) #

  1. /m/ man /mæn/
  2. /n/ not /nɒt/
  3. /ŋ/ sing /sɪŋ/

近音(Approximants) #

  1. /r/ red /red/
  2. /j/ yes /jes/
  3. /w/ wet /wet/

边音(Lateral Approximant) #

  1. /l/ light /laɪt/ cool /kuːl/

辅音连缀(Consonant Cluster) #

  1. /ts/ cats /kæts/
  2. /dz/ beds /bedz/
  3. /tr/ tree /triː/
  4. /dr/ dream /driːm/

  1. /l/ 有 2 个发音:clear l(示例:like)、dark l(示例:school) ↩︎

KK 音标、DJ 音标、IPA 音标对照表

2025年2月23日 00:00

本文内容摘录自KK音标 - 维基百科,自由的百科全书

单元音 #

KK DJ IPA 单字示例
[i] [iː] [iː] seat
[ɪ] [ɪ] [ɪ] sit
[e] [eɪ] [eɪ] pain
[ɛ] [e] [e] head
[æ] [æ] [æ] fat
[ɑ] [ɑː] [ɑ] hot
[o] [əʊ] [əʊ] nose
[ɔ] [ɔː] [ɔː] four
[ɔ] [ɔ] [ɒ] dog
[u] [uː] [uː] too
[ᴜ] [ʊ] [ʊ] put
[ʌ] [ʌ] [ʌ] sun
[ə] [ə] [ə] again
[ɪr] [ɪə] [ɪə] rear
[ɚ] [ə] [ə˞] brother
[ɝ] [ɜ] [ɜ˞] bird

双元音 #

KK DJ IPA 单字示例
[aɪ] [aɪ] [aɪ] pie
[aᴜ] [aʊ] [aʊ] house
[ɔɪ] [ɔɪ] [ɔɪ] coin

清辅音 #

KK DJ IPA 单字示例
[p] [p] [p] pet
[t] [t] [t] ten
[k] [k] [k] key
[f] [f] [f] fat
[s] [s] [s] sing
[θ] [θ] [θ] thank
[ʃ] [ʃ] [ʃ] short
[tʃ] [tʃ] [tʃ] chair
[h] [h] [h] ha

浊辅音 #

KK DJ IPA 单字示例
[b] [b] [b] book
[d] [d] [d] desk
[g] [g] [ɡ] get
[v] [v] [v] vest
[z] [z] [z] zoo
[ð] [ð] [ð] this
[ʒ] [ʒ] [ʒ] measure
[dʒ] [dʒ] [dʒ] john
[m] [m] [m] mom
[n] [n] [n] nose
[ŋ] [ŋ] [ŋ] sing
[l] [l] [l] long
[r] [r] [r] red
[j] [j] [j] yes
[w] [w] [w] we

痛失小米 9 SE

2025年2月20日 00:00

小米 9 SE 是我的上上台手机,我换手机之后就没用过了。最近拿出来,发现居然能安装最新的 LineageOS 22.1(Android 15)。我一直感觉在 Android 用 root 权限是比较危险的,所以没用过。刚好这台空闲的手机可以让我测试,测试好一些应用或者模块,我就在一加 Ace 3 上面用。我还很喜欢小米 9 SE 小巧的尺寸,现在已经很少可以单手使用的小尺寸手机了。

这台小米 9 SE 有两个比较严重的问题:烧屏(屏幕泛黄、有残影)、电池损耗严重(出门必须带移动电源)。感觉换屏幕太麻烦,就算了。电池倒是可以换个新的。花 69 元人民币买了块诺希牌电池,自己安装。

客服发的安装视频里的工具和实际的工具略有不同。视频里用胶带贴后盖,我拿到的是胶水。视频看了一遍,感觉自己能搞定。

第一步是打开后盖。关机,用吹风机加热后盖,用吸盘贴住后盖底部,用三角拆机片抠开边缘。工具要放远点或者装起来,不然吹风机会吹走工具。

第二步是拆螺丝。按顺序拆下 10 颗螺丝,在纸上画图,用 1~10 的数字标记位置,把螺丝放在纸上。挑起电池排线。

第三步是取电池。加热电池,很不幸把螺丝吹开了,发现有 2 颗螺丝是短的,不知道装的时候该装哪个位置。拉出电池底下延伸出来的胶条,很难拔,还拔断了,胶条还残留在电池底部。接下来用工具用力撬起电池(错误操作),中途还太大力刺伤手指了,疼!

差不多撬起来的时候,电池有点变形了,再撬电池就破了。电池冒烟,我脑子还没反应过来,手就把手机丢到地上了。电池接着就着火和冒烟,燃烧完后电池很胀,屏幕被加热到出现一个白色的实心圆圈,烟的气味很臭很刺鼻,过了一晚才闻不到。幸好人和房子都没事。最后把电池和主板装到袋子,贴上「锂电池」的纸,放在有害垃圾垃圾桶旁边了。附近的人似乎不分类丢垃圾,所以我就把电池直接放垃圾桶旁边,希望回收垃圾的人能看到。

换电池失误的后果好严重,主板都烫坏了,以后还是去售后中心换电池吧。真怀念以前的手机,关机打开后盖就直接换电池了。

将一加 Ace 3 从 PixelOS 14 升级到 PixelOS 15

2025年2月13日 00:00

现在 PixelOS 15 发布了,我就打算从 PixelOS 14 升级上去,但是事情没那么简单。因为 PixelOS 15 要用到 ColorOS 15 的固件,所以不能直接升级。要安装 ColorOS 14,升级到 ColorOS 15(15.0.0.200 或者以上的版本),在 ColorOS 的更新器再次安装 ColorOS 15 以确保两个系统槽位(slot)都是 ColorOS 15。安装 ColorOS 14 的时候会清空用户数据,所以要先备份应用和文件,最后再恢复。

操作有风险,推荐在有条件去售后中心的时候操作,失败了就去售后直接安装 ColorOS 15 吧。

需要用到的文件 #

备份 #

我用 DataBackup 来备份应用与它们的数据、文件。

  • 应用
  • DCIM
  • Pictures
  • Documents

DataBackup 需要 root 权限,我用最新构建的 APatch使用文档,注意最新构建版是个 .zip 文件,需要解压之后安装里面的 .apk),你也可以用其他 root 方案。

备份完删掉一些应用再恢复,看看能不能行。没问题就把数据复制到电脑。

我对微信没有信心,所以将微信聊天记录额外备份到电脑。

有些东西 DataBackup 无法备份,只能自己截图或者用文字记下来。无法备份的东西有:

  • 系统设置(WIFI 密码、星标联系人、闹钟、谷歌账户等)
  • APatch 设置和模块(应该也能备份吧,但总感觉不太对劲)

清除密码 #

退出谷歌账号,删除锁屏密码。

用 9008/EDL 安装 ColorOS 14 #

参考 How To Guide - [12R/Ace 3] EDL DownloadTool to restore your device to OxygenOS/ColorOS | XDA Forums,用 9008/EDL 模式安装 ColorOS 14。

Download OnePlus 12R/Ace 3 EDL Unbrick Firmware/ROM - DroidWin 下载 EDL 刷机包(PJE110domestic_11_14.0.0.317CN01_2024012904340000.zip),登录谷歌账户才行,不然提示下载人数太多。

  1. 解压 zip 包。
  2. 将 xda 帖子的 prog_firehose_ddr.elf 放到 系统包/IMAGES/,覆盖掉里面的 prog_firehose_ddr.elf
  3. 打开 9008驱动.exe,安装。
  4. 打开报错安装我,提示已安装这个产品的另一个版本。
  5. 打开刷机工具1.3。
  6. 区域选择非欧洲,用户名、密码、验证码都输入 1,登录。
  7. 将系统包的文件夹拖拽进去。
  8. 保持默认选项,点击ok,点击开始。
  9. 手机按住两个音量键和电源键,刷机工具显示连接就松手。
  10. 显示 firehose 协议执行失败,好像是因为我前面还没关闭 fastboot firmware flasher,关闭它之后再来用刷机工具1.3就好了。
  11. 6 分钟后完成刷机,再等一段时间,ColorOS 14 开机成功。

第 1 次安装 ColorOS 15 #

ColorOS 14 开机成功,进入设置引导界面,增强服务这里关闭「自动下载」和「夜间自动更新」,后面的能跳过就点跳过。

目前版本信息是 PJE110_14.0.0.317(CN01)

  1. 将手机插到电脑,复制 ColorOS PJE110_15.0.0.500 刷机包到手机的 Download 文件夹。
  2. 开启开发者模式:打开设置-关于本机-版本信息-快速多次点击版本号。
  3. 开启飞行模式(关闭数据和 WIFI)。
  4. 打开设置-应用-应用管理-右上角的三个点-显示系统应用-搜索软件更新-储存占用-清除数据。
  5. 打开设置-关于本机-ColorOS-右上角的三个点-本地安装-选择前面从电脑放进去的 ColorOS 15 刷机包-立即解压-立刻安装。

安装完后重启到 ColorOS 15。

友情提示:升级到 ColorOS 15 之后,原来的免授权 EDL 刷机工具会失效,用 EDL 工具的新方法:Fix Sahara Communication/Protocol Failed Error in Oppo Flash Tool - DroidWin

第 2 次安装 ColorOS 15 #

现在进入了设置引导界面,关闭「自动下载」和「夜间自动更新」。现在版本信息是 PJE110_15.0.0.500(CN01)

像前面一样,在设置里面使用本地安装,现在是第 2 次安装 ColorOS 15,安装完选立即重启。

安装 PixelOS 15 #

现在准备工作都做好了,按照 PixelOS 官方教程 做就行了。

手机关机,按住音量下键和电源键开机进入 fastboot,连接电脑。

1
2
3
4
5
fastboot flash boot boot-aston_20250121_1747.img
fastboot flash init_boot init_boot-aston_20250121_1747.img
fastboot flash vendor_boot vendor_boot-aston_20250121_1747.img
fastboot flash dtbo dtbo-aston_20250121_1747.img
fastboot flash recovery recovery-aston_20250121_1747.img

按手机音量下键,直到显示 Recovery mode,按下电源键确认。现在进入 PixelOS 的 recovery 了。

在手机选择 Apply update - Apply from ADB。

1
adb sideload PixelOS_aston-15.0-20250121-1747.zip

手机询问「Do you want to reboot to recovery now?」(是否重启到 recovery),选择 No。

选择 Factory reset - Format data/factory reset - Format data。

点左上角的左箭头返回,选择 Reboot system now。

简单设置一下:

  • Refresh rate 120 Hz (Adaptive)
  • Tap to wake
  • Tap to sleep
  • Display size and text - Display size(调大 1 档)
  • Network traffic indicator

恢复 #

安装 APatch,修补 boot.img,刷入后手机开机后卡死,触屏和按键没反应。头疼!按住音量上键和电源键强制重启,屏幕熄灭时马上松手并按住音量下键,进入 fastboot。刷入 PixelOS 的 boot.img。

1
2
fastboot flash boot boot-aston_20250121_1747.img
fastboot reboot

KernelSU 也试了下,刷入修补的 init_boot.img 后并不能获取 root 权限。最后只能用 Magisk 了。

把前面备份的数据从电脑复制到手机(里面有 DataBackup),安装 DataBackup 后恢复应用和数据。

有些应用要重新登录或者设置:

  • Bitwarden
  • Outlook
  • 支付宝
  • 欧路词典

不需要重新登录或者设置的应用:

  • 微信
  • KDE Connect

闪退的应用:

  • Hail
  • GKD

其他设置自己手动处理。

  • 连接 WIFI,登录谷歌账号。
  • 同步 outlook 联系人
  • 设置星标联系人
  • 设置闹钟
  • 克隆应用的功能没了,只好装个 Insular
  • 安装 root 模块

Magisk 的问题 #

Magisk 真的太容易被检测到了,云闪付、支付宝、微信里面都没有人脸支付、指纹支付的选项,云闪付直接提示处于 root 环境。之前用 APatch,支付宝是可以人脸支付的。真是头疼,只能先不用 root 了,支付宝又能用人脸支付了。

2025-02-16 更新:PixelOS 的维护者 inferno0230 提供了支持 KernelSU 的内核。我试了 OP12R-v5.15.175-20250119-0950.zip,在 PixelOS Recovery 里选 Apply update - Apply from ADB,在电脑执行 adb sideload OP12R-v5.15.175-20250119-0950.zip 就安装成功了。我把 KernelSU 管理器安装到 private space,用支付宝人脸识别正常,云闪付也没有提示 root 环境。

清理 #

删除电脑的备份文件、刷机包、刷机工具、微信记录。


文章到这里就结束了,下面是安装 ColosOS 14 的失败尝试,不知道是我操作不当还是工具有问题。

2025-02-20 更新:今天看到用 fastboot 安装原厂系统的教程(How To Guide - Flashing stock firmware on 12r/Ace 3 from fastboot. | XDA Forums),可能我下面失败的原因是没装好驱动,手机在 fastbootd 模式时未被电脑检测到。

Download the usb drivers, extract them and install “android_winusb.inf” by right clicking on it then click install. Install them even if your device is detected in fastboot mode because further in the guide the phone will go in fastbootD mode and your computer might not detect the phone in that mode.

用 fastboot 安装 ColorOS 14(失败) #

  1. 手机关机,按住电源键和音量下键开机,手机震动后松手,进入 fastboot 模式。将手机插到电脑 USB 口。
  2. 打开 Fastboot Firmware Flasher。
  3. 选择 [7] FIRMWARE UNPACKER,解开 ColorOS 14。
  4. 返回主菜单,选择 [3] FLASH ROM
  5. 电脑显示 Rebooting into fastboot,但是手机进了 fastbootd,电脑显示「waiting for any device」。手机可以选择清除数据、重启、关机,我选了选择关机,结果就反复启动了。

用文本文件制作 Anki 牌组

2025年1月30日 00:00

Anki 是开源的记忆软件。我们可以用文本文件制作牌组(一组需要记忆的内容),文本文件更易于修改。

将下面文本保存为 deck.txt,然后用文本编辑器编辑。

1
2
3
4
5
#separator:Pipe
#html:true
hi|你好
why|为何
two lines|第 1 行<br>第 2 行

此文本对应的导出格式是「Cards in Plain Text(.txt)」。

第 1 行的 separator(分隔符)用于区分正面和反面,可以用 Comma(,)、Semicolon(;)、Tab( )、Space( )、Pipe(|)、Colon(:),用名称(例如:Pipe)和符号(例如:|)都可以。我推荐用 |,它很少在卡片里用到,而且可以用键盘直接输入。

第 2 行表示可以使用 HTML 代码。比如:<br> 表示换行。

第 3 行开始就是卡片,一行就是一张卡,分隔符左边是卡片正面,分隔符右边是卡片反面。

在 Anki 创建牌组,将 deck.txt 导入到这个牌组。如果你想制作翻转卡片(反面->正面),导入时 Note Type 选择「Basic and (reversed) card」,这样牌组会同时存在基础卡片(正面->反面)和翻转卡片。


相关资料:Text Files - Anki Manual

在闲鱼遭遇到手后砍价,最后拿回货款

2025年1月1日 00:00

为保护隐私,本故事略有改编。

卖前 #

我在闲鱼卖一台五六年前发售的手机,定价 200 元人民币,原价 2999 元。这台手机可以正常使用,屏幕有一处明显划痕,边框掉漆。

放了几天,有个人猛砍价1到 120 元,我说 180 元他就不回了。

后来又有个人问了,这个人一开始就让我感觉不爽。她的名字类似于「闭嘴吧你」,一副不好沟通的模样,芝麻信用还未授权。

买家说话吞吞吐吐,沟通起来很浪费时间。

买家:你好

我:你好,想了解一下这台手机吗?

买家:是的

我内心:喂,你有话快说,非要我说一句你就说一句吗!

买家开始问有没有换过屏幕和电池,是不是自用。我说原装自用。买家叫我拍边框,拍了她就挑剔有磕碰,又说几年前手机电池不行,换电池都要几十块,以这些为由要求降价。这时我就开始感觉恶心,二手手机本来就有耗损,还按照新机那种标准挑剔,新机至于卖 200 吗?

买家问钢化膜、手机壳、充电器。我都在商品页面写清楚只有手机跟手机壳了。买家还嫌手机壳变色。几年前的手机壳你想要多好,有送都不错了。买家问有没有划痕,我都在商品页面说了在哪个位置有一处明显划痕,她又要我拍照。

充电器我说用 USB Type-C 就行了,其他品牌的充电器也可以的。买家以只有 iPhone 为由(iPhone 15 之前的 iPhone 不使用 Type-C 接口),让我送数据线。为了促成交易,我也送了一条本来在用的,重新买又得十多块,气死。

买家问有没有账号、会不会重启,最后她砍价到 160 元,并且收到马上确认收货(买家确认收货,卖家才会收到货款)。我内心的最低价格是 180 元,她砍那么多我是很不爽的,看她说马上确认收货就算了吧。

下单后她说地址错了。我不知道这是什么套路。让她取消订单,重新上架让她买。

到货 #

发货后买家这个贱人又开始挑剔了,想看购买凭证。我连包装盒都没了,哪来的购买凭证。一般人也不会问这个,这台手机没什么造假价值,又不是奢侈品。

到货后买家并没有爽快得确认收货,从这时开始她越来越恶心了。她说手机缝隙不一致,怀疑拆过机,一边缝隙大,似乎夹了纸片。我也不确定,但之前用手机的时候是没问题的,我就说不用就放着了。于是买家又换另一个理由,说摄像头进灰了影响拍照,又装可怜说贴膜、手机壳、充电头也要钱,希望我优惠点(到手后砍价)。这些配件要钱关我屁事,而且她那么吝啬肯定不会买配件。啊,气死我了!我说摄像头进灰是二手机正常损耗。我手头上刚好有台摄像头进灰的手机,前置摄像头和后置摄像头都进灰了,但是拍照没有明显问题。

过了一天,买家说去手机店清灰要 20 元,问我给她 20 元还是退货2。我说已经最优惠了。她接下来一直以进灰为由缠着我,我就一直说已经最优惠了,后面不想回复了等到货后 3 天自动确认收货。结果这个家伙在最后一天申请了退货,理由是质量问题/功能异常,附上前摄像头的照片,确实有灰尘。最后没收货真是气死我了。

我无法接受退货,因为:

  • 不能容忍她讹人这样错误的行为。
  • 质量问题退货要卖家(我)给运费。
  • 我前面的交流让我觉得她品德低下,怕她弄坏手机再寄回来,真的有人得不到就毁掉。
  • 我不想留下质量问题退货的记录。

闲鱼小法庭 #

我拒绝了退货申请,理由是:问题不存在,补充的文字和截图表示买家有到手刀(到手后砍价)行为。我点击维权之后就进到闲鱼小法庭了。闲鱼小法庭就是解决买家卖家纠纷的方式,由 17 位陪审员(闲鱼用户)投票,17 票 9 胜制,票差小于 4 票时可以申诉。陪审员可以查看双方聊天记录。小法庭有两个环节:前 24 小时是双方举证,发起维权者先发言 1 次,然后开始计时,双方可以发言 5 次;后 24 小时是评审员投票。在我这个例子,如果我赢了就马上到账,如果买家赢了我就必须接受退货。

老实说,第一次上小法庭还是蛮紧张的,要是输了就很难过。发言太多了,我就简单总结一下。

买家的发言:

  • 摄像头进灰影响拍照(附上前摄像头照片,水印表面是用 Android 手机拍的)。
  • 卖家不说明摄像头有灰尘、敷衍了事、不回信息。
  • 急起来说气话,说卖家强买强卖和土匪(拜托,是你自己买又不是我逼你买的)、说用多台手机不行吗。
  • 卖家未标明售出不退。

卖家(我)的发言:

  • 摄像头进灰是手机使用的正常损耗,我之前自拍和打视频电话都没问题,我已经提供了最优惠的价格。
  • 买家买前过度挑剔,也没有表面很在意摄像头灰尘。
  • 买家到手后用模糊的语言砍价(说缝隙大又不提供图片,说摄像头进灰在聊天时也未提供图片),心虚才不会提供照片证明观点。
  • 买家承诺直接确认收货,买后到手刀,这是不诚信。
  • 买家说自己用 iPhone 没 Type-C 数据线,但被我发现他拍照用了 Android 手机,所以他有 Type-C 数据线。买家骗了我一条数据线,这是不诚信。

差不多晚上 12 点的时候开始投票,第二天买家已经取消了退货申请,大概是自知理亏。买家拖 20 小时后自动确认收货,我收到钱了,太好了!

回顾 #

现在回想起来,买家的手段不算很高明,只是我没经验而且想快点卖出手机就中招了。

下面总结一下甄别坏买家和避免纠纷的技巧。

甄别坏买家 #

有以下特征的买家可能是坏买家:

  • 吞吞吐吐(可能没有恶意,但和这样的人聊天很浪费时间)
  • 挑剔,尤其是不合理的挑剔(电池损耗)
  • 用模糊的表达而不是提供证据
  • 表现得很在意问题但只要求砍价(一般人会选择退货)
  • 没有芝麻信用或者买家芝麻信用优秀以下
  • 差评多
  • 新用户

避免纠纷 #

  • 拍 360 度视频/图片
  • 拍装箱视频
  • 用验货宝(闲鱼的验货服务)
  • 声明售出后非质量问题不退

  1. 像 200 元砍到 120 元这种大比例砍价叫做屠龙刀。 ↩︎

  2. 如果不想和买家纠缠,一定要让其确认收货后才发钱给他。 ↩︎

了解与使用 Android 的 root 权限

2024年12月23日 00:00

root 权限 #

root 是 Android 系统的最高权限。Android 系统就像房子。没有 root 的时候用户就是租客,只有使用权,不能随便改动房子。有 root 权限的时候,用户就是房东,有所有权,可以随便改动房子(修改系统)、丢掉原有的家具(卸载系统应用)。

总之,有了 root 权限之后我们就可以完全掌控 Android 系统了。

基本流程 #

获取 root 的前提条件是解锁引导程序(bootloader),解锁了引导程序才能安装 init_boot.imgboot.img

init_boot.imgboot.img 是启动系统需要用到的文件,推荐先备份好它们。修补其中一个并安装后就能获取 root 权限。最后使用 root 管理器来管理 root 权限。

方案 #

目前有三个开源的 root 方案:

Magisk 是最经典的 root 方案,教程和资源最多。缺点是容易被应用检测到,比如:中国农业银行应用检测到 root 会退出。用额外的模块才能隐藏 root。

KernelSU 是内核级 root 方案,不需要刻意隐藏 root,被授权的应用才能感知到 root。App Profile 功能可以授予应用部分权限,比如:只允许应用使用 adb 权限。

APatch 也是内核级 root 方案,KernelSU 是内核级 root 方案,不需要刻意隐藏 root,被授权的应用才能感知到 root。需要设置密码。

我的推荐:KernelSU > APatch > Magisk(> 表示优于)。KernelSU 排第一是因为它不易被检测,而且可以授予应用部分权限。Magisk 排最后是因为容易被检测。

用法 #

root 有两种使用方法。一是管理器授权给应用,二是在管理器安装 root 模块(module)。比如:授权给 Neo Backup 就可以备份应用。安装 BCR 模块之后可以自动录制通话。

root 权限可以很危险,比如用来清除手机数据或者弄坏系统。请勿授权给来源不明的应用,也不要安装来源不明的模块。尽量选择开源、知名的应用和模块。

模块、Zygisk、Xposed #

模块是使用 root 管理器安装的应用。

Zygisk 是修改系统和应用行为的模块,供其他模块使用。Magisk 内置 Zygisk。KernelSU 和 APatch 没有内置 Zygisk,但是可以安装独立版 Zygisk。

以下是独立版 Zygisk:

Xposed 是修改系统和应用行为的模块,供其他模块使用。Xposed 已经停止更新,其继任者 LSPosed 也停止更新。目前还在更新的 LSPosed 修改版为 JingMatrix/LSPosed。注意 LSPosed 依赖于 Zygisk,要先安装 Zygisk 才能用 LSPosed。

安装 Zygisk 和 LSPosed 后,root 特征更明显,root 更容易被检测到。

隐藏 root #

银行应用和游戏检测到 root 后可能会拒绝运行,此时要隐藏 root。

隐藏 root 的模块:

Android 系统允许应用获取所有应用的名字。有些应用发现系统有 root 管理器或者使用 root 的应用,就认为系统有 root 权限而拒绝运行。我们可以使用 Hide My Applist 或者 HMAL 来隐藏应用名称,从而躲过检测。

隐藏完 root 后可以使用root 检测应用查看效果。如果你用的应用没检测出 root,就不要纠结 root 检测应用的结果。

银行应用与 Play Integrity #

一些银行应用会检测 Play Integrity(设备完整性,等级包适 BASIC、DEVICE、STRONG),手机解锁引导程序后 Play Integrity 等级下降,导致银行应用拒绝运行或者隐藏部分功能(比如指纹支付)。可以使用 Play Integrity FixTrickyStore 来修复 Play Integrity。

我的用法 #

我一般用 KernelSU 或者 APatch。

用到 root 的应用有:

模块有:

  • BCR

root 的功能远不止这些,推荐你看 GitHub - fynks/awesome-android-root: A comprehensive and up-to-date list of latest Android root apps that require or utilize root privileges, rooting guides, tips, tricks and tools

推荐可以刷机的一加 Ace 3

2024年12月15日 00:00

上个月买了可以刷机1的一加 Ace 32。一加 Ace 3 的性能不错,处理器是骁龙 8 二代(2022 年末的旗舰处理器),目前在骁龙处理器中仅次于骁龙 8 三代和骁龙 8 至尊版。喜欢刷机的读者可以考虑这台手机。

相关链接:


  1. 刷机指安装操作系统。 ↩︎

  2. 一加 Ace 3 的国际版为 OnePlus 12R。 ↩︎

小米已退烧

2024年12月8日 00:00

小米一开始的口号是为发烧(友)而生,现在新的解锁 bootloader 政策让我感觉小米退烧了。

原本小米解锁 bootloader 的条件是等待 168 小时(7 天)。小米推出 HyperOS 后中国大陆的型号解锁 bootloader 非常难,需要小米社区 5 级,还要考试1

这个解锁政策真是让我大跌眼镜,根本是刁难用户。真搞不明白小米公司怎么想的。如果嫌用户解锁后弄坏手机要去售后,那可以在用户解锁 bootloader 后取消售后。

新解锁政策还有一个恶心的点:只针对中国大陆的设备。凭什么陆版手机解锁就那么难,国际版手机解锁就和以前一样2

新解锁政策大大影响了第三方 ROM 的开发。本小米老用户感到伤心、失望、生气。买陆版小米手机刷机的日子可能一去不复返了。


  1. Xiaomi-HyperOS-BootLoader-Bypass 可以绕过 HyperOS 的解锁限制。 ↩︎

  2. 国际版手机解锁也没那么容易了,我在 PixelOS Chat 看到有人申请解锁时提示额度已满(https://t.me/pixeloschat/466581)。 ↩︎

一加 Ace 3 刷机前的准备与安装 PixelOS 的过程

2024年12月5日 00:00

PixelOS 升级流程:将一加 Ace 3 从 PixelOS 14 升级到 PixelOS 15

注意事项 #

  • 本文的操作可能会过时,建议参考文末的文章。
  • 拿到手机要马上关闭自动更新,避免更新到不合适刷机的版本。
  • 解锁 bootloader 重启后马上关掉自动更新。
  • 解锁 bootloader 时会清除数据。如果确定要刷机,建议买到手机马上解锁,后面拿到 root 权限可以用 Neo Backup 备份应用数据。

名词解释 #

  • 刷机:安装操作系统
  • ROM:操作系统。ROM 本义是可读存储器(read-only memory),在刷机的语境下是(安装于 ROM 的)操作系统的意思。
  • bootloader:引导程序,需要先解锁它才能安装其他操作系统
  • root:最高权限
  • ocdt.img:每台一加手机特有的分区
  • persist.img:每台手机特有的分区

恢复系统教程 #

先看这篇恢复系统的教程:How To Guide - [12R/Ace 3] EDL DownloadTool to restore your device to OxygenOS/ColorOS | XDA Forums,最好用不上啦。

测试硬件功能 #

测试硬件功能,参考 charter/device-support-requirements.md at main · LineageOS/charter

确定硬件没问题后,如果刷机后有问题就是 ROM 有问题。

  • 扬声器、听筒
  • WIFI
  • 通话
  • USB
  • 蓝牙
  • 前置摄像头(拍照、录像)
  • 后置摄像头(0.6/1/2/5 倍数,拍照、录像)
  • 指纹
  • NFC
  • 红外线(遥控)
  • 定位
  • 陀螺仪(指南针)
  • 距离传感器(通话时会熄屏)
  • 光传感器(自动亮度)
  • 网络(只测了 5G)

记录版本信息 #

记录版本信息,可能以后有用。打开设置->关于手机->版本信息。

1
2
3
4
5
6
7
8
版本号
PJE110_14.0.0.813(CN01U140P02)
基带版本
Q_V1_P14,Q_V1_P14
内核版本
5.15.123-android-13-8-00766-gf04dea8b48fa
SOTA 版本号
U140P02(BRB1CN01)

解锁 bootloader #

在电脑安装 adb 和 fastboot,参看 Using ADB and fastboot | LineageOS Wiki

解锁时会清除数据。如果确定要刷机,建议买到手机马上解锁,后面拿到 root 权限可以用 Neo Backup 备份应用数据。

打开设置->关于手机->版本信息,快速多次点击版本号,开启开发者模式。

返回设置,打开系统与更新->开发者选项,开启 OEM 解锁和 USB 调试。

手机通过数据线插到电脑 USB 口。

1
2
3
4
adb devices # 手机按允许调试
adb reboot bootloader # 进入 fastboot 模式
fastboot devices # 应该会看到编号
fastboot flashing unlock # 解锁 bootloader,用音量键选择 UNLOCK THE BOOTLOADER,按电源键确认

现在手机重置了,开机后跳过可以跳过的设置,进入桌面。打开设置,搜索「更新」,打开「自动更新设置」,关闭自动下载和夜间自动更新。

获取 root 权限 #

Oxygen Updater 不能下载一加 Ace 3 的 ROM,所以从大侠阿木的网站下载当前版本的 ROM,也就是 PJE110_14.0.0.813

解压文件,获得 payload.bin

安装 payload-dumper-go

1
2
# Arch Linux 的安装方法
paru -S payload-dumper-go-bin

解压 payload.bin

1
payload-dumper-go payload.bin

进入解压目录,将 init_boot.img 传到手机。

1
adb push init_boot.img /sdcard/Download/

在电脑下载 Magisk,通过 adb 安装到手机。

1
adb install app-release.apk # 在手机同意安装

在手机打开 Magisk,点击「Magisk 安装->选择并修复一个文件」,选择 init_boot.img,点击「开始」。在文件管理将修补好的文件重命名为 magisk_patched.img

magisk_patched.img 复制到电脑。

1
adb pull /sdcard/Download/magisk_patched.img

获取 root 权限。

1
2
3
adb reboot bootloader
fastboot flash init_boot magisk_patched.img
fastboot reboot

备份 ocdt.img 和 persist.img #

每台一加手机的 ocdt.img 和 persist.img 都是独一无二的,所以先要备份下来。

1
2
3
4
5
6
adb shell
su # 在手机授权
dd if=/dev/block/bootdevice/by-name/ocdt of=/sdcard/Download/ocdt.img
dd if=/dev/block/bootdevice/by-name/persist of=/sdcard/Download/persist.img
exit
exit

现在 ocdt.img 和 persist.img 都在手机的 Download 文件夹,将它们复制到电脑。

1
2
adb pull /sdcard/Download/ocdt.img
adb pull /sdcard/Download/persist.img

把前面记录的版本信息保存成文本文件,和这两个 .img 文件一起备份到网盘。

安装 Pixel OS #

参考以下资料安装 Pixel OS。

我的安装过程有错误操作,为了避免误导读者,就删掉了。如果你真的想看,请点击这里

系统自带谷歌服务,用 YASNAC 测试 SafetyNet,Basic integrity 显示 pass。

再次测试硬件功能 #

参考前面的章节。既然在原装系统没事,我这次就懒得测了。

参考资料 #

OPPO / 一加 / ColorOS 的 Google Play 安装教程

2024年11月15日 00:00
  1. 开启谷歌服务:打开设置,搜索「google」,点击「Google 设置」,开启「Google 移动服务」。
  2. 安装 Google Play:打开应用商店,搜索「google play」,升级「谷歌应用市场」。升级完后桌面会出现「Play 商店」。

测试机型:一加 OnePlus Ace 3(ColorOS 14)、OPPO Reno12(ColorOS 14)。

概念解析:物理像素、逻辑像素、CSS 像素、物理分辨率、逻辑分辨率

2024年11月2日 00:00

物理(physical)表示实际的值,逻辑(logical)表示经过缩放的值。

物理像素 #

像素(pixel)是显示器的基本单位,一个像素显示一种颜色。为了和逻辑像素(logical pixel)区分,像素(pixel)又称物理像素(physical pixel)。

逻辑像素 / CSS 像素 #

逻辑像素(logical pixel),亦称 CSS 像素(CSS pixel),是经过操作系统缩放的像素。CSS 的 px 就是 CSS 像素。

物理分辨率 #

物理分辨率(physical resolution)表示像素的数量。1920×1080 分辨率就表示显示器水平方向有 1920 个像素,垂直方向有 1080 个像素,共 2073600 个像素。尺寸相同时,显示器分辨率越高,显示效果就越精细。

逻辑分辨率 #

逻辑分辨率(logical resolution)是经过系统缩放的分辨率。

我用 1920×1080 分辨率,27 英寸的显示器,文字看得清楚。假如我换成同尺寸 3840×2160 的显示器,此时显示器像素数是前者的 4 倍,可以容纳更多的文字,文字会变得小得看不清。在浏览器按几下 Ctrl - 调节缩放也可以看到过小的文字。

在系统设置 200% 缩放后,分辨率就变成了 (3840/2)×(2160/2),也就是 1920×1080,此时文字大小又正常了。3840×2160 是物理分辨率,1920×1080是逻辑分辨率。

相关的 Web API #

以 2560×1440 物理分辨率,200% 缩放的显示器为例,展示相关 Web API 的用法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 逻辑宽度
console.log(window.screen.width) // 1280
// 逻辑高度
console.log(window.screen.height) // 720
// 缩放(物理高度/逻辑高度)
console.log(window.devicePixelRatio) // 2
// 物理宽度,需要借助 devicePixelRatio 来计算
console.log(window.screen.width*window.devicePixelRatio) // 2560
// 物理高度,需要借助 devicePixelRatio 来计算
console.log(window.screen.height*window.devicePixelRatio) // 1440

在线查看分辨率 #

我写了个查看物理分辨率、逻辑分辨率和视口大小的网站,欢迎使用。

网站链接:https://resolution-viewer.cyrusyip.org/

源代码:https://github.com/CyrusYip/resolution-viewer

jQuery 使用笔记

2024年10月23日 00:00

jQuery 是一个经典的 JavaScript 库,其功能为修改 HTML 元素、处理事件、制作动画、发送请求(Ajax)。jQuery 首次发布于 2006 年 8 月 26 日,现在(2024 年)已经 18 岁了。截止于 2024 年 10 月 23 日,有 75.8% 的网站使用了 jQuery1

虽然 jQuery 的使用率高,但新项目没必要用 jQuery,原生 JavaScript 已经可以实现 jQuery 的操作,参看 You Might Not Need jQueryYou-Dont-Need-jQuery。维护老项目时可能会用到 jQuery。

jQuery 的引入方法很多,我在本文采用 CDN 引入。

1
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>

下面介绍一些常见用法。

获取元素 #

jQuery 使用 CSS 选择器获取元素。

1
2
3
$(document) // 获取整个文档
$("#id1") // 获取 id 为 id1 的元素
$(".class1") // 获取 class 为 class1 的元素

插入元素 #

1
2
<!-- HTML 示例 -->
<div id="container"><p>Container</p></div>
1
2
3
4
$("#container").append("<p>inside-end</p>"); // 在 #container 末尾插入子元素
$("#container").prepend("<p>inside-start</p>"); // 在 #container 开头插入子元素
$("#container").after("<p>outside-end</p>") // 在 #container 后面插入兄弟元素
$("#container").before("<p>outside-start</p>") // 在 #container 前面插入兄弟元素

现在页面上的元素是这样的:

1
2
3
4
5
6
7
<p>outside-start</p>
<div id="container">
 <p>inside-start</p>
 <p>Container</p>
 <p>inside-end</p>
</div>
<p>outside-end</p>

取值与赋值 #

jQuery 使用同一个函数来取值和赋值,根据参数数量进行不同操作。

1
2
<!-- HTML 示例 -->
<h1 id="title">Title</h1>
1
2
3
4
$("#title").html() // 获取 #title 的值
$("#title").html("Another title") // 修改 #title 的值
$("#title").width() // 获取 #title 的宽度
$("#title").width(1) // 修改 #title 的宽度

链式调用 #

jQuery 的每个操作都会返回 jQuery 对象,所以可以进行连续操作。

1
2
3
4
5
<!-- HTML 示例 -->
<div id="container">
 <p>Paragraph 1</p>
 <p>Paragraph 2</p>
</div>
1
2
3
4
5
6
7
$("div") // 选择 div 元素
 .find("p") // 查找 div 里的 p 元素
 .eq(1) // 选择第 2 个元素
 .html("end") // 把第 2 个元素内容修改为 end
 .end() // 退回到上一个选中的元素,也就是 .find("p")
 .eq(0) // 选择第 1 个元素
 .html("start") // 将第 1 个元素内容修改为 start

现在页面是 start 在上面,end 在下面。

资料 #

优化 Hugo 博客速度:Pjax、dynamic script、preload、minification

2024年10月18日 00:00

本文介绍了优化博客速度的几个方式:Pjax(免刷新加载页面)、dynamic script(动态插入脚本)、rel=preload(预加载)、minification(极简化)。

本博客是 Hugo 生成的静态网站,感觉访问速度还不算慢。有天我看别人的博客(大概是单页应用),发现点击链接居然没刷新网页就加载了新页面,速度非常快。那时我想:要是我的 Hugo 博客也能这么流畅就好了。我感觉把博客改成单页应用要耗费不少时间,遂作罢。

后来我发现可以用 Pjax 技术让静态网站实现免刷新加载页面。Pjax 的意思是 pushState(修改 URL)+ Ajax(asynchronous JavaScript and XML,发送请求)。通俗来说,Pjax 就是请求网页、替换内容、修改 URL,这个过程比加载整个页面更快。

搜索 Pjax 库,找到两个好几年没更新的库(MoOx/pjaxdefunkt/jquery-pjax),我不想用不维护的库。我快放弃的时候发现了一个支持 Pjax 的 Hugo 主题:hugo-theme-luna,从自述文件可以看出 Pjax 是用 swup 实现的,然后我就用它了。

Pjax 免刷新加载页面 #

如果网站没有 JavaScript 代码,那直接加载 swup 就好了。

建议使用这些插件:

  • Head Plugin:刷新 <head> 元素的内容和 <html> 元素的 lang 属性。
  • Preload Plugin:光标在链接时预加载 URL,用户点击时就会内容会马上替换,还可以配置自动加载出现在可见区域的链接。
  • Progress Bar Plugin:加载时间较长时显示进度条。

注意要先加载插件再加载 swup。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<script src="https://unpkg.com/@swup/head-plugin@2"></script>
<script src="https://unpkg.com/@swup/preload-plugin@3"></script>
<script src="https://unpkg.com/@swup/progress-plugin@3"></script>
<script src="https://unpkg.com/swup@4"></script>

<script>
 const swup = new Swup({
 containers: ["body"], // 替换 <body> 的内容
 plugins: [
 new SwupHeadPlugin(),
 new SwupPreloadPlugin({ preloadVisibleLinks: true }), // 预加载页面可见的链接
 new SwupProgressPlugin(),
 ],
 });
</script>

我的博客用到了 3 个 JavaScript 程序:Google Analytics(流量统计)、Giscus(评论服务)、Disqus(评论服务),用了 swup 之后要考虑是否需要额外处理。

读者点击链接加载新页面后的处理:

Dynamic Script 动态插入脚本 #

我写了个动态插入脚本的函数,可以设置等待时间、加载前执行的函数、async、加载后执行的函数、attribute。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function loadScript({
 url,
 delay,
 onloadCallback,
 async,
 preloadCallback,
 attributes = {},
}) {
 function load() {
 if (preloadCallback) preloadCallback();

 const script = document.createElement("script");
 script.src = url;
 script.async = async;

 // Set attributes
 Object.entries(attributes).forEach(([key, value]) => {
 script.setAttribute(key, value);
 });

 if (onloadCallback) script.onload = onloadCallback;

 document.body.appendChild(script);
 }

 if (typeof delay !== "undefined") {
 setTimeout(load, delay);
 } else {
 load();
 }
}

我给 Google Analytics、Giscus、Disqus 加了 100ms 等待时间,给 swup 添加了 200ms 等待时间。

rel=preload 预加载 #

<link> 标签使用 rel="preload" 可以让浏览器提前下载资源(字体、图片、脚本、样式等),后面动态插入脚本时浏览器就可以马上执行下载好的脚本。

示例:

1
2
3
4
5
<head>
 <link rel="preload" as="script" href="https://unpkg.com/@swup/head-plugin@2">
 <link rel="preload" as="script" href="https://unpkg.com/@swup/preload-plugin@3">
 <link rel="preload" as="script" href="https://unpkg.com/swup@4">
</head>

preload 应该叫 predownload,因为它并不会提前加载脚本,只是提前下载。

Minification 极简化 #

这次优化博客没用到极简化,不过既然讲到优化博客了,就顺便说一下吧。极简化就是移除代码中不必要的内容(比如:空格、空行、缩进),从而减小文件大小,这样浏览器就能更快下载完网页。

Hugo 可以极简化 HTML、CSS 和 JavaScript 文件。本博客的代码用 Hugo 极简化了。举个例子,样式文件大小原本是 13.8 kB,经过极简化后是 7.4 kB(原文件的 53.6%),传输过程中再经过 Brotli 压缩后是 2.1 kB(原文件的 15.2%)。

相关代码 #

以下是这次优化用到的代码:

感想 #

我已经用 swup 两三天了,非常满意,真没想到静态网站可以变成单页应用。Preload 插件能让浏览器会自动提前加载页面,读者点击链接时页面会瞬间加载,这个感觉太爽啦!

参考资料 #

将 LineageOS 20 升级到 LineageOS 21

2024年10月11日 00:00

我手机用的系统是开源的 LineageOS 20(Android 13),跨版本升级到 LineageOS 21(Android 14)需要用电脑手动操作。因为怕操作失误导致无法开机,所以想着先备份资料,但我懒得备份资料,于是好几个月没升级。今天就来升级了。

备份 #

虽然升级 LineageOS 不会清空储存空间,但为了保险起见,我按照自己的需求备份以下内容:

  1. 微信聊天记录
  2. 图片
  3. 电子邮箱账户截图
  4. 闹钟截图
  5. 应用列表

升级 #

按照 Upgrade LineageOS on lisa | LineageOS Wiki 的步骤,安装 LineageOS 21、重启到 Recovery、安装 Google Apps、重启。

这里碰到一个奇怪的问题:手机通过数据线直接插到笔记本电脑的 USB 口,用 fastboot 可以检测到设备,但安装新系统时会报错。数据线插到 USB 集线器才行。

感受 #

升级过程比我预想的简单。系统正常运作,所以清除了前面的备份。目前遇到两个问题:状态栏的快捷设置在亮色模式下仍然是暗色;Fcitx5 输入法底下的导航栏不见了,将「导航栏背景」设置为「跟随键盘背景色」即可解决。

如果你也要升级 LineageOS,不要照搬我的步骤,要遵循 LineageOS Wiki 的步骤,不同设备的升级步骤可能有差异。

开放的心态

2024年10月1日 00:00

在网上看到有人争论同类事物的好坏(比如:Vim 和 VS Code、Arch linux 和 Ubuntu),我认为没有必要去争。纠结哪个最好容易导致无尽的争吵。没有完美的工具,我们应该去思考工具的特点和适用场景。两个工具适用场景相似时可以先随便选一个,不要追求完美,适合就继续用,不适合就换另一个。

拿厨刀举例,主厨刀(chef’s knife)和切片刀哪个好?这个问题没有绝对的答案。

主厨刀
切片刀

主厨刀更轻更小,刀尖有弧度。刀尖有弧度就可以用摇刀切(rock chop),适合切蒜末。

切片刀更重,用它切菜时更流畅,使用者更省力。切片刀更宽大,可以把食材拍扁,还可以把切好的食材铲起来倒入锅中。

所以哪把刀好还是得看使用者的偏好,如果你喜欢轻巧就用主厨刀,喜欢功能多就用切片刀。

当然其实这两把刀都能切蔬菜和肉,不是很了解的情况下任选一把即可。选其他工具也一样,没实际用过就感受不到细微的差别,与其纠结,不如先随便挑一个用着。


图片出处:

冷泡茶教程

2024年9月25日 00:00

冷泡茶是用低温水泡的茶,苦味比热泡茶淡,有清凉感。

材料与工具 #

  • 茶叶(红茶、绿茶等)
  • 水壶
  • 冰箱

流程 #

  1. 茶叶与冷水的重量比例为 1:50,将茶叶与冷水加到到水壶,关闭盖子。
  2. 将水壶放入冰箱冷藏室,泡 4~8 小时。可以睡前泡,这样第二天早上就能喝了。

补充资料 #

QWERTY 键盘标点符号的中英文名称

2024年9月20日 00:00

AmE 表示 American English(美式英语),BrE 表示 British English(英式英语),表格的空白表示没有对应名称。

标点 英文 中文
~ tilde 波浪号
! exclamation point (AmE) / exclamation mark (BrE) 感叹号
@ at sign / at symbol
# hash / pound sign / number sign 井号
$ dollar sign 美元符号
% percent sign 百分号
^ caret 脱字符
& ampersand / and sign
* asterisk 星号
( ) parenthesis (AmE) / bracket (BrE) / round bracket (BrE) 圆括号
- hyphen / minus sign 连字符/减号
+ plus sign 加号
` backtick/backquote/grave 反引号/重音符
_ underscore 下划线
= equal sign (AmE) / equals sign (BrE) 等号
{ } brace / curly bracket 花括号
| verticle bar / pipe 竖线/管道
[ ] square bracket 方括号
\ backslash 反斜线/反斜杠
: colon 冒号
" double quote / double quotation mark 双引号
; semicolon 分号
' apostrophe / single quote / single quotation mark 撇号/单引号
< less-than sign 小于号
> greater-than sign 大于号
? question mark 问号
, comma 逗号
. period (AmE) / full stop (BrE) 句号
/ slash / forward slash (正)斜线/(正)斜杠

参考资料:Wikipedia, the free encyclopedia

少囤草稿,尽快发布

2024年9月19日 00:00

笔记软件里面的草稿越来越多,但是发布的文章却越来越少了。造成这个情况的原因有:用于写博客的时间少了、对文章的要求过高。囤着草稿是不好的,写好的文章应该发出来,为了帮助自己和他人。

刚写博客那会,我对文章内容没有太高要求,就是想着写好后几天内改几次(吐槽:这要求还不高吗),力求通俗易懂。后来写作要求提升了两次。

第一次提升要求是因为我查 Linux 资料时经常看 ArchWiki。ArchWiki 的内容又新又全面,而且注册个账号就可以贡献内容,我自己也贡献了一些内容。在这个过程中我觉得知识应该像 ArchWiki 那样,集中在一个地方,每个话题都有单独的页面,还方便大家贡献。慢慢我就不想写那些别人写过的内容,除非我能写得更好。能查到的内容就没必要写了。这就是程序员说的「Don’t repeat yourself」,不要重复产生信息。

第二次提升要求是因为我写出了排在谷歌搜索首位的文章,这给网站带来了持续的访问量,会有更多人看到我的博客,我很开心。在此之前,我一直觉得推广博客很难,到处去分享感觉就像滥发消息(spamming)。从此之后,我会优先写别人没写过的话题,写重复的话题尽量保证质量好到能排到谷歌搜索的第一页。

最近我刻意改变,将文章的重心放到帮助自己,降低要求。要求就两个:自己能读懂(可以对别人来说没那么通俗)、完整(不要写一半就发,写完初稿就可以发了)。发稿速度马上就上涨了,感觉非常好。

最近发布了四篇文章:

Grid 教程和 Flexbox 教程都是参考 CSS-Tricks 的教程写的。之前我也看过 CSS-Tricks 那两篇教程(CSS Flexbox Layout Guide | CSS-TricksCSS Grid Layout Guide | CSS-Tricks),但是自己写教程的过程中对两个 CSS 布局的理解更深入了,对我来说很有用。写好之后我哪里不懂也可以看自己写的教程,自己写的教程查起来更方便。写重复的话题可以加深理解,这也不意味着做复读机,我在写的过程中会有新的感悟,写出一些新内容。

写博客就像做开源项目,要让自己感觉舒服,对自己有用,这样才容易坚持。

菜谱:酱油蒜香炒蛋

2024年9月18日 00:00

材料 #

  • 蒜末
  • 酱油

步骤 #

  1. 将蛋打入碗中,不要搅拌
  2. 加酱油,搅拌均匀蛋黄和蛋清
  3. 加蒜末
  4. 下锅前搅拌
  5. 煎蛋或者炒蛋

备注 #

  • 可用鸡蛋或者鸭蛋,混蛋也行
  • 可用刀将蒜头切成蒜末,越碎越好
  • 搅拌蛋后再加酱油就看不清加了多少酱油
  • 蒜末会沉淀,所以下锅前搅拌

用柠檬酸和热水去除电热水瓶和保温瓶的水垢

2024年8月31日 00:00

柠檬酸是天然的清洁剂,可去除电热水瓶和保温瓶内的顽固水垢。它的用法是先浸泡,后清洗。如果水垢未完全清除,可以重复上述步骤。

材料 #

  • 柠檬酸除垢剂

电热水瓶浸泡方法 #

  1. 倒入冷水和柠檬酸(用量请参考说明书)。
  2. 煮沸后继续浸泡 30 分钟。

保温瓶浸泡方法 #

  1. 先倒入热水,再倒柠檬酸。
  2. 浸泡 3~4 小时。

清洗方法 #

电热水瓶和保温瓶的清洗方法是一样的。

  1. 倒掉混合液。
  2. 用清水冲洗。
  3. 如果冲洗后还有水垢,可以用厨房抹布擦。如果手无法伸入瓶内,可以用筷子抵住抹布擦拭。

CSS Grid 布局教程

2024年9月16日 00:00

CSS Grid 是二维布局方法,也就是用竖线和横线将内容划分成格子,像棋盘一样。本文只介绍常见用法,要了解全部用法请看 MDN Web Docs。推荐大家看完后做文末提到的习题。

概念 #

网格容器(Grid Container)、网格项(Grid Item) #

display: grid | inline-grid 使元素变成网格容器,其子元素叫网格项(其他后代不算),按照网格布局排列。下面代码的 .container 是网格容器,.item 是网格项,.sub-item 不是网格项。

1
2
3
.container {
 display: grid;
}
1
2
3
4
5
6
7
<div class="container">
 <div class="item"></div>
 <div class="item">
 <p class="sub-item"></p>
 </div>
 <div class="item"></div>
</div>

网格线(Grid Line) #

划分网格的竖线(vertical line)或横线(horizontal line)。横线方向和书写方向一致(英文是从左到右),竖线方向是从上到下。同一条网格线可以有多个名称。

虚线是网格线

网格单元(Grid Cell) #

网格布局的最小单位,就像电子表格的单元格和棋盘的格子,相邻的 2 个竖线之间和相邻的 2 条横线之间的区域。1 个网格项可以使用多个网格单元。

网格轨道(Grid Track) #

2 条相邻网格线之间的区域,也就是 1 个横排(row)或者 1 个竖排(column)。

网格区域(Grid Area) #

由 4 条网格线划分的区域,也就是长方形,比如 1 个网格单位、4 个网格单位、6 个网格单位。

示例(A 表示网格区域):

1
2
A???
????
1
2
AA??
AA??
1
2
AAA?
AAA?

显性网格(Explicit Grid)、隐性网格(Implicit Grid) #

显性网格有固定数量的网格轨道,比如 3×3。如果此时加入额外的网格项,网格将自动添加 auto 尺寸的隐性网格轨道,原来的显性网格轨道加上自动添加的隐性网格轨道就是隐性轨道。只定义竖网格线也可以产生隐性网格。

fr(fraction)1 份可用空间 #

fr 表示网格容器的 1 份可用空间。

1
2
3
4
5
6
7
.container {
 display: grid;
 /* 3 个竖排,比例为 1:2:1 */
 grid-template-columns: 1fr 2fr 1fr;
 /* 3 个横排,第 1 横排为 30px,剩余横排比例为 1:1 */
 grid-template-rows: 30px 1fr 1fr;
}

minmax() 最大最小值 #

参考资料:minmax() - CSS: Cascading Style Sheets | MDN

minmax() 函数设置最小值和最大值。下面代码表示第 1 横排的尺寸最小 50px,最大 auto(根据内容自动扩大)。

1
2
3
.container {
 grid-template-rows: minmax(50px, auto) 1fr 1fr;
}

repeat() 函数 #

参考资料:repeat() - CSS: Cascading Style Sheets | MDN

repeat() 函数用于表示重复的网格轨道片段。

在线示例

1
2
3
.container {
 grid-template-rows: repeat(3, 1fr); /* 1fr 1fr 1fr */
}

在线示例

1
2
3
.container {
 grid-template-rows: 2fr repeat(2, 1fr); /* 2fr 1fr 1fr */
}

除了使用固定的数字,还可用 auto-fitauto-fill

网格容器的属性 #

网格容器可以使用以下属性。

  • display
  • grid-template-columns
  • grid-template-rows
  • grid-template-areas
  • grid-template
  • grid-column-gap
  • grid-row-gap
  • grid-gap
  • justify-items
  • align-items
  • place-items
  • justify-content
  • align-content
  • place-content
  • grid-auto-columns
  • grid-auto-rows
  • grid-auto-flow
  • grid

display 设置容器 #

对元素使用,使其变成网格容器。

1
2
3
.container {
 display: grid | inline-grid;
}

值:

  • grid:生成 block 级网格容器
  • inline-grid:生成 inline 级网格容器

grid-template-columns、grid-template-rows,网格线名称与网格轨道 #

参考资料:

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item6">6</div>
 <div class="item item7">7</div>
 <div class="item item8">8</div>
 <div class="item item9">9</div>
</div>
1
2
3
4
5
6
7
8
9
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 /* 3 个竖排,比例为 1:2:1 */
 grid-template-columns: 1fr 2fr 1fr;
 /* 3 个横排,第 1 横排为 30px,剩余横排比例为 1:1 */
 grid-template-rows: 30px 1fr 1fr;
}

现在网格项排列成这样:

1
2
3
1 2 3
4 5 6
7 8 9

可以用 [] 定义网格线名称,用空格分隔多个名称。

1
2
3
.container {
 grid-template-columns: [column1-start] 1fr [column2-start] 2fr [column3-start] 1fr [column-end another-name];
}

grid-column-startgrid-row-start 可以改变网格项的位置,在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.container {
 display: grid;
 grid-template-columns: [column1-start] 1fr [column2-start] 2fr [column3-start] 1fr [column-end];
 grid-template-rows: 30px 1fr 1fr;
}

.item9 {
 grid-column-start: column2-start;
 grid-row-start: 2; /* 第 2 条横线 */
}

现在 .item9 占据了 .item5 的位置。

1
2
3
1 2 3
4 9 5
6 7 8

grid-template-areas 网格区域名称 #

参考资料:grid-template-areas - CSS: Cascading Style Sheets | MDN

grid-template-areas 以网格区域名称表示网格的结构。grid-template-areas 的优点是放置网格项时不需要用网格线(数网格线或者命名真的太痛苦了)。相同名称可以用多次,表示占用多个网格单元。英文句号 . 表示不使用此网格单元。grid-area 定义元素对应的网格区域名称。

grid-template-areas 会自动使用 -start-end 命名网格线。header 网格区域的起始网格线(横线与竖线)都是 header-start,终止网格线(横线与竖线)都是 header-end。同一条网格线可以有多个名称。

示例(在线版):

1
2
3
4
5
6
<div class="container">
 <header>header</header>
 <aside>sidebar</aside>
 <main>main</main>
 <footer>footer</footer>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
header,aside,main,footer {
 border: 1px solid green;
}

.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template-areas:
 "header header header header"
 "main main . aside"
 "footer footer footer footer";
 grid-template-columns: 1fr 1fr 1fr 50px;
 grid-template-rows: auto;
}

header {
 grid-area: header;
}
aside {
 grid-area: aside;
}
main {
 grid-area: main;
}
footer {
 grid-area: footer;
}

grid-template(grid-template-rows、grid-template-columns、grid-template-areas 的缩写) #

参考资料:

grid-templategrid-template-rowsgrid-template-columnsgrid-template-areas 的缩写。grid-template 不会重置隐性网格属性。

只设置 grid-template-rowsgrid-template-columns 的用法是用 / 隔开两者。

示例(在线版):

1
2
3
4
5
6
7
8
9
.container {
 display: grid;
 /* 横排比例 1:1:1,竖排比例 1:3:1 */
 grid-template: 1fr 1fr 1fr / 1fr 3fr 1fr;
 /* 等价于
 grid-template-rows: 1fr 1fr 1fr;
 grid-template-columns: 1fr 3fr 1fr;
 */
}

同时设置 grid-template-rowsgrid-template-columnsgrid-template-areas 的用法:

  1. 先写 grid-template-areas
  2. 在每横排右边写上尺寸
  3. 在最后的横排加上 / 和竖排的尺寸(/ 从下一行开始写也行)

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template:
 "header header header" auto
 "main main aside " 1fr
 "footer footer footer" auto / auto auto 50px;
 /* 等价于
 grid-template-areas:
 "header header header"
 "main main aside"
 "footer footer footer";
 grid-template-columns: auto auto 50px;
 grid-template-rows: auto 1fr auto;
 */
}

row-gap、column-gap、gap 间隔 #

参考资料:

row-gap 表示横排之间的间隔,column-gap 表示竖排之间的间隔。gap 是前面两者的缩写,使用 1 个值表示横排间隔和竖排间隔一样,使用 2 个值分别表示横排间隔是竖排间隔。

示例:

1
2
3
4
5
6
.container {
 row-gap: 30px;
 column-gap: 10px;
 gap: 10px; /* 竖排间隔和横排间隔都是 10px */
 gap: 30px 10px; /* 横排间隔 30px,竖排间隔 10px */
}

以前这 3 个属性前面要加上 grid-,比如 grid-row-gap。带 grid- 前缀的属性已被弃用,浏览器为了保持兼容,仍然支持这些属性。

justify-items,网格项的 inline 轴(横轴)对齐 #

参考资料:

justify-items 设置网格项 inline 轴(横轴)对齐方式,默认值为 stretch(占满网格单元宽度)。

1
2
3
.container {
 justify-items: start | end | center | stretch;
}

align-items,网格项的 block 轴(竖轴)对齐 #

参考资料:

align-items 设置网格项 block 轴(竖轴)的对齐方式,默认值为 stretch(占满网格单元高度)。baseline 表示按基线对齐。内容有多行时,first baseline 表示按照首行的基线对齐,last baseline 表示按照尾行的基线对齐。

1
2
3
.container {
 align-items: start | end | center | stretch | baseline | first baseline | last baseline;
}

place-items(align-items、justify-items 的缩写) #

参考资料:

place-itemsalign-itemsjustify-items 的缩写。使用 1 个值同时设置 2 个属性,使用 2 个值分别设置两个属性。

1
2
3
4
.container {
 place-items: center; /* 正中间 */
 place-items: start end; /* 右上角 */
}

justify-content,网格项整体 inline 轴(横轴)对齐 #

参考资料:

如果网格项的总尺寸小于网格容器的尺寸,网格容器会有多于的空白,此时网格项被放置于左上角(使用从左向右的语言)。justify-content 设置 inline 轴(横轴)的对齐方式。Flex 布局也有 justify-content,用法类似,参看 CSS Flexbox 布局教程#justify-content-主轴对齐(发布前看看这个链接对不对)。

在线示例

1
2
3
.container {
 justify-content: start | end | center | stretch | space-around | space-between | space-evenly; 
}

align-content,网格项整体的 block 轴(竖轴)对齐 #

参考资料:

如果网格项的总尺寸小于网格容器的尺寸,网格容器会有多于的空白,此时网格项被放置于左上角(使用从左向右的语言)。align-content 设置 block 轴(竖轴)的对齐方式。Flex 布局也有 align-content,用法类似。

在线示例

1
2
3
.container {
 align-content: start | end | center | stretch | space-around | space-between | space-evenly; 
}

place-content(align-content、justify-content 的缩写) #

参考资料:place-content - CSS: Cascading Style Sheets | MDN

place-contentalign-contentjustify-content 的缩写。用 1 个值同时设置两者,用 2 个值分别设置两者。

示例(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.container {
 place-content: start end;
 /*
 align-content: start;
 justify-content: end;
 */
 
 place-content: center;
 /*
 align-content: center;
 justify-content: center;
 */
}

grid-auto-columns、grid-auto-rows 隐性网格轨道大小 #

参考资料:

如果网格项数量多于已定义的网格项数量,那么多出了网格项就位于隐性网格轨道。grid-auto-columns 设置竖向隐性网格轨道大小,grid-auto-rows 设置横向隐性网格轨道大小,两者默认值都是 auto

在线示例

1
2
3
.container {
 grid-auto-rows: 100px;
}

grid-auto-flow 自动放置算法 #

参考资料:

1
2
3
.container {
 grid-auto-flow: row | column | row dense | column dense;
}

grid-auto-flow 设置网格项的放置算法。

row(默认值):横向放置网格项,必要时添加新的横排。

1
2
3
1 2 3
4 5 6
7 8 9

column:竖向放置网格项,必要时添加新的竖排,在线示例

1
2
3
1 4 7
2 5 8
3 6 9

dense 关键词表示后面的元素可以移动到前面的空位,示例(在线示例):

1
2
3
4
5
6
7
8
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item6">6</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.item {
 border: 1px solid green;
}

.container {
 border: 1px solid red;
 height: 200px;
 width: 200px;
 display: grid;
 grid-template-columns: 1fr 1fr 1fr;
 grid-auto-flow: row dense; /* .item3 提前放置于 .item1 右边 */
}

.item1, .item2 {
 grid-column: span 2; /* 2fr */
}

grid( grid-template-rows、grid-template-columns、grid-template-areas、grid-auto-rows、grid-auto-columns、grid-auto-flow 的缩写) #

参考资料:

gridgrid-template-rowsgrid-template-columnsgrid-template-areasgrid-auto-rowsgrid-auto-columnsgrid-auto-flow 的缩写。

创建显性网格时用法与 grid-template 一样。

创建隐性网格时有 2 种用法(左边设置横排,右边设置竖排,中间以 / 分隔):

  1. 显性横排,隐性竖排,grid-auto-flowcolumn<grid-template-rows> / [ auto-flow && dense? ] <grid-auto-columns>?
  2. 隐性横排,显性竖排,grid-auto-flowrow[ auto-flow && dense? ] <grid-auto-rows>? / <grid-template-columns>

显性横排隐性竖排示例(在线版):

1
2
3
4
.container {
 /* 横排比例 1:2:1,按照竖排方向放置网格项,竖排尺寸 50px */
 grid: 1fr 2fr 1fr / auto-flow 50px;
}

隐性横排显性竖排示例(在线版):

1
2
3
4
.container {
 /* 隐性横排,按照横排方向放置网格项,竖排比例 1:2:1 */
 grid: auto-flow / 1fr 2fr 1fr;
}

网格项的属性 #

网格项可以使用以下属性:

  • grid-column-start
  • grid-column-end
  • grid-row-start
  • grid-row-end
  • grid-column
  • grid-row
  • grid-area
  • justify-self
  • align-self
  • place-self
  • subgrid
  • order

grid-column-start、grid-column-end、grid-row-start、grid-row-end 网格项位置 #

参考资料:

这几个属性通过网格线的起点与终点规定 1 个网格项的位置,默认值为 auto(自动放置)。span 数字 表示占用多少个网格轨道,不加数字时为 1。

示例(在线版):

1
2
3
4
5
6
7
8
<div class="container">
 <div class="item item1">1</div>
 <div class="item item2">2</div>
 <div class="item item3">3</div>
 <div class="item item4">4</div>
 <div class="item item5">5</div>
 <div class="item item-x">x</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template: 1fr 1fr 1fr / 1fr [col2-start] 1fr 1fr;
}

.item {
 border: 1px solid green;
}

.item-x {
 /* 从 col2-start 竖网格线到倒数第 1 条竖网格线 */
 grid-column-start: col2-start;
 grid-column-end: -1;
 /* 从第 2 条横网格线开始,占用 2 个横向网格轨道 */
 grid-row-start: 2;
 grid-row-end: span 2;
}

grid-column(grid-column-start、grid-column-end 的缩写)、grid-row(grid-row-start、grid-row-end 的缩写) #

参考资料:

grid-columngrid-column-start / grid-column-end 的缩写,使用 1 个值时只设置 grid-column-start

grid-rowgrid-row-start / grid-row-end 的缩写,使用 1 个值时值设置 grid-row-start

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.item-x {
 grid-column: col2-start / -1;
 grid-row: 2 / span 2;
 /*
 grid-column-start: col2-start;
 grid-column-end: -1;
 grid-row-start: 2;
 grid-row-end: span 2; 
 */
}

grid-area(grid-row-start、grid-column-start、grid-row-end、grid-column-end 的缩写) #

grid-area 有两个用法。一是 grid-row-start / grid-column-start / grid-row-end / grid-column-end 的缩写(吐槽:这个顺序不好读,-start 后面应该跟 -end);二是搭配 grid-template-area,使用名称指定位置。

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.item-x {
 grid-area: 2 / col2-start / span 2 / -1;
 
 /*
 grid-column: col2-start / -1;
 grid-row: 2 / span 2;
 
 grid-column-start: col2-start;
 grid-column-end: -1;
 grid-row-start: 2;
 grid-row-end: span 2; 
 */
}

justify-self,inline 轴(横轴)对齐 #

justify-self 设置 1 个网格项的 inline 轴(横轴)对齐方式,默认值是 stretch

1
2
3
.item {
 justify-self: start | end | center | stretch;
}

在线示例

1
2
3
.item4 {
 justify-self: center;
}

align-self,block 轴(竖轴)对齐 #

align-self 设置 1 个网格项的 block 轴(竖轴)对齐方式,默认是 stretch

1
2
3
.item {
 align-self: start | end | center | stretch;
}

在线示例

1
2
3
.item4 {
 align-self: end;
}

place-self(align-self、justify-self 的缩写) #

参考资料:

place-selfalign-self justify-self 的缩写,只使用 1 个值时同时设置两者。

示例:

1
2
3
4
.item4 {
 place-self: center; /* 正中间 */
 place-self: end start; /* 左下角 */
}

subgrid 继承网格容器属性 #

参考资料:Subgrid - CSS: Cascading Style Sheets | MDN

虽然 subgrid 不是属性,但用于网格项,所以列举于此。

示例(在线版):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="container">
 <div class="item item1">Item1</div>
 <div class="item item2">Item2</div>
 <div class="item item3">Item3</div>
 <div class="item item4">Item4</div>
 <div class="item item5">Item5</div>
 <div class="item item6">Item6, 4×4
 <div class="item6-1">Item6.1, 1×1</div>
 <div class="item6-2">Item6.2, 1×1</div>
 </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.container {
 border: 1px solid red;
 height: 400px;
 display: grid;
 grid-template: 1fr 1fr 1fr / 1fr 1fr 1fr;
 gap: 10px;
}

.item {
 border: 1px solid green;
}

.item6 {
 border: 1px solid yellow;
 grid-area: 2 / 2 / span 2 / span 2;
 display: grid;
 /*
 用 subgrid 才能继承容器的 gap 属性,
 可以删掉下面两行代码看看差异 */
 grid-template-columns: subgrid;
 grid-template-rows: subgrid;
}

.item6-1 {
 border: 1px solid blue;
 /* 使用 .item6 的网格线编号,不要用 .container 的网格线编号 */
 grid-column-start: 2;
 grid-row-start: 2;
}

.item6-2 {
 border: 1px solid blue;
 grid-column-start: 2;
 grid-row-start: 1;
}

order 顺序 #

参考资料:order - CSS: Cascading Style Sheets | MDN

网格项默认按照源代码顺序出现。order 设置网格项的出现顺序,默认值为 0,可使用正数和负数。

示例(在线版):

1
2
3
4
5
6
.item1 {
 order: 1;
}
.item9 {
 order: -1;
}

待写内容 #

参考资料 #

图片出处 #

本文使用的图片出自 CSS Grid Layout Module Level 1#grid-concepts

练习 #

CSS BEM 命名规范入门教程

2024年9月8日 00:00

BEM(Block, Element, Modifier)是 HTML/CSS 类的命名方法,它可以让 HTML 和 CSS 代码更有条理。

概念与用法 #

一开始看不懂没关系,后面有示例。

  • block(块):可以独立使用的 HTML 元素(比如:<nav>),可以不包含 element。
  • element(元素):依附于 block 的 HTML 元素,无法独立存在(比如:<li>),前面要加上双下划线 __
  • modifier(修饰符):表示 block 或者 element 的状态和外观,前面要加上双连字符 --
  • 用单连字符 - 连接单词,比如:search-form
  • element 只属于 block,而不是另一个 element。错误写法:block__element1__element2,正确写法:block__element2
  • 使用 modifier 时,同时保留不含 modifier 的类名,比如:<a class="menu__link menu__link--active" href="/zh-cn/">主页</a>

HTML 示例 #

<nav> 元素是 block,它包含的 element 有 <ul><li><a>--active 是修饰符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<nav class="menu">
 <ul class="menu__list">
 <li class="menu__item">
 <a class="menu__link menu__link--active" href="/zh-cn/">主页</a>
 </li>
 <li class="menu__item">
 <a class="menu__link" href="/zh-cn/posts/">文章</a>
 </li>
 <li class="menu__item">
 <a class="menu__link" href="/zh-cn/about/">关于</a>
 </li>
 </ul>
</nav>

CSS 示例 #

在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* 横向列表 */
.menu__list {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-between;
 list-style-type: none;
 padding-inline-start: 0;
}

/* 加粗和当前页面匹配的链接 */
.menu__link--active {
 font-weight: bolder;
}

SCSS 示例 #

我更推荐用 SCSS,用父选择器 & 可以把 block 和 element 的样式都放在一起,这种结构可以清晰地展现它们的层级关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.menu {
 // 横向列表
 &__list {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap;
 justify-content: space-between;
 list-style-type: none;
 padding-inline-start: 0;
 }

 // 加粗和当前页面匹配的链接
 &__link {
 &--active {
 font-weight: bolder;
 }
 }
}

替代方案:原子式 CSS #

如果你觉得 BEM 太冗长或者难以维护,那你可以试试原子式1 CSS 框架(atomic CSS framework),比如:Tailwind CSSUnoCSS。这些框架会提供 utility(预定义的类)。开发者只需要写预定义的类名,不需要写 CSS 代码。以下是一些 Tailwind CSS 的 utility:

1
2
3
4
5
6
.flex {
 display: flex;
}
.flex-row {
 flex-direction: row;
}

用 Tailwind CSS 重写之前的 CSS 代码(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<script src="https://cdn.tailwindcss.com"></script>
<script>
 tailwind.config = {
 corePlugins: {
 preflight: false,
 }
 }
</script>
<nav>
 <ul class="flex flex-row flex-wrap justify-between list-none ps-0">
 <li>
 <a class="font-bold" href="/zh-cn/">主页</a>
 </li>
 <li>
 <a href="/zh-cn/posts/">文章</a>
 </li>
 <li>
 <a href="/zh-cn/about/">关于</a>
 </li>
 </ul>
</nav>

补充资料 #


  1. atomic CSS 一般译为「原子化 CSS」,我认为这个翻译是错的。「化」表示状态变化,对应英文的「-ize」和「-ify」词缀,比如:净化(purify)、标准化(standardize)。atomic 可以拆分成 atom(原子)和 -ic(形容词后缀),所以我把 atomic 翻译成原子式,「式」表示形式、样式。atomic 也可以翻译成原子型,「型」表示类型、样式。 ↩︎

CSS Flexbox 布局教程

2024年9月6日 00:00

Flexbox(弹性盒子)是一维的布局方法,也就是在一条横线(row)或者竖线(column)上排列元素。推荐大家看完本文后做文末提到的习题

flex 容器的属性 #

flex 容器(flex container)的属性有 displayflex-directionflex-wrapflex-flowjustify-contentalign-itemsalign-contentrow-gapcolumn-gapgap

flex 容器与 flex 项 #

以下的 section 元素包含了 3 个 article 元素。

1
2
3
4
5
<section>
 <article>Article 1</article>
 <article>Article 2</article>
 <article>Article 3</article>
</section>

要排列 article 元素,将它们的父元素设置为 flex 容器。

1
2
3
section {
 display: flex | inline-flex; /* inline-flex 表示容器是 inline 元素 */
}

在上述例子中,section 元素是 flex 容器(flex container),article 元素是 flex 项(flex item)。

flex-direction 方向、主轴、交叉轴 #

flex-direction 定义排列方向和主轴。

1
2
3
.container {
 flex-direction: row | row-reverse | column | column-reverse;
}
  • row(默认值):横向,和文字方向(dir)一致,如果方向是从左往右,那么就是从左往右排列子元素
  • row-reverse:和前者相反,从右往左
  • column:纵向,从上到下
  • column-reverse:和前者相反,从下到上

主轴(main axis)就是和 flex 项排列方向一致的轴,交叉轴(cross axis)则是与主轴垂直的轴。一定要分清楚方向和两条轴,在对齐 flex 项时会用到。

1
2
3
4
.container {
 display: flex;
 flex-direction: row;
}

上述代码表示从左到右排列 flex 项,主轴是从左到右,交叉轴是从上到下。

轴的图示

flex-wrap 换行 #

flex-wrap 控制是否换行,默认不换行。

1
2
3
.container {
 flex-wrap: nowrap | wrap | wrap-reverse;
}
  • nowrap(默认值):全部 flex 项排列成一条线
  • wrap:溢出时换行
  • wrap-reverse:溢出时换行,但后面的元素会排到前面

示例代码(在线示例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<section>
 <article>Article 1</article>
 <article>Article 2</article>
 <article>Article 3</article>
 <article>Article 4</article>
 <article>Article 5</article>
 <article>Article 6</article>
 <article>Article 7</article>
 <article>Article 8</article>
 <article>Article 9</article>
</section>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
section {
 display: flex;
 flex-direction: row;
 flex-wrap: wrap-reverse;
}

article {
 width: 100%;
 outline: 1px solid green;
}

flex-flow(flex-direction 和 flex-wrap 的缩写) #

flex-flowflex-directionflex-wrap 的缩写,默认值为 row nowrap

下面两份代码效果一样:

1
2
3
4
section {
 flex-direction: row;
 flex-wrap: wrap;
}
1
2
3
section {
 flex-flow: row wrap;
}

justify-content 主轴对齐 #

justify-content 设置主轴的对齐方式。

1
2
3
.container {
 justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right ... + safe | unsafe;
}

下面只介绍常用的值。

  • flex-start(默认):将所有 flex 项放到主轴的起点。如果主轴是 row(从左到右),那么所有 flex 项贴近左边。例子(. 表示空间):Item1Item2Item3......
  • flex-end:将所有 flex 项放到主轴的终点。例子:......Item1Item2Item3
  • center:将所有 flex 项放在主轴的中间。例子:....Item1Item2Item3....。
  • space-between:均匀分配 flex 项,每个 flex 项之间的距离一样,头部 flex 项贴近主轴开头,尾部 flex 项贴近主轴末尾,例子:Item1..Item2..Item3
  • space-around:均匀分配 flex 项,每个 flex 项的前后间隔一样,例子:.Item1..Item2..Item3.
  • space-evenly:均匀分配 flex 项,每个 flex 项之间的距离一样,头部 flex 项和主轴开头有一样的距离,尾部 flex 项和主轴末尾有一样的距离。例子:..Item1..Item2..Item3..

align-items 交叉轴对齐 #

交叉轴垂直于主轴。align-items 设置交叉轴的对齐方式。

1
2
3
.container {
 align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + ... safe | unsafe;
}

align-items 用法和 justify-content 类似,这里介绍一些不一样的值。

  • stretch(默认值):使 flex 项填满容器。
  • baseline:根据基线对齐,用于字体大小不同的 flex 项,图示

align-content 交叉轴对齐(仅用于多行 flex 项) #

align-content 设置多行 flex 项的交叉轴对齐方式,flex 项只有一行时(flex-wrap: nowrap;)无效,图示

1
2
3
.container {
 align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + ... safe | unsafe;
}

align-content 用法和 align-items 类似。

row-gap、column-gap、gap 间隔 #

如果在 flex 项上画横线和竖线,row-gap 表示横线的间隔大小,column-gap 表示竖线的间隔大小,gap 是前面两者的缩写,在线示例

1
2
3
4
5
6
7
8
.container {
 display: flex;
 flex-wrap: wrap;
 row-gap: 20px;
 column-gap: 5px;
 gap: 20px 5px; /* row-gap, column-gap */
 gap: 20px; /* row-gap 和 column-gap 都是 20px */
}

gap 只是设置最小间隔,使用 space-between 可以加大间隔。

flex 项的属性 #

flex 项(flex item)的属性有 orderflex-growflex-shrinkflex-basisflexalign-self

flexbox 没有 justify-self,参看 Box alignment in flexbox - CSS: Cascading Style Sheets | MDN#there_is_no_justify-self_in_flexbox

order 顺序 #

默认情况下,flex 项按照源代码出现的顺序排列。使用 order 属性可以改变顺序,默认值为 0,数字越大位置越后,可以使用负值(比如:-1)。如果多个 flex 项的 order 值一样,它们还是按原始顺序排列。

1
2
3
4
5
6
7
8
9
#item1 {
 order: 2;
}
#item2 {
 order: 1;
}
#item3 {
 order: -1;
}

图示:

1
item3 | item2 | item1

flex-basis 大小 #

flex-basis 设置 flex 项的大小,默认为 auto

1
2
3
.item {
 flex-basis: 50px;
}

flex-grow 增长 #

flex 容器有多余空间时,flex-grow 设置 flex 项的增长系数,默认为 0(不增长),负值无效。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.item1 {
 flex-grow: 1;
}

.item2 {
 flex-grow: 2;
}

.item3 {
 flex-grow: 1;
}

上面代码表示将多余空间(假设为 100px)均分为 4 份,.item1 的宽度增加 1 份(25px),.item2 的宽度增加 2 份(50px),.item3 的宽度增加 1 份(25px),在线示例

flex-shrink 收缩 #

空间不足时,flex-shrink 设置 flex 项的收缩指数,默认为 1(可收缩),0 表示不可收缩,负值无效。flex-shrink 的效果和 flex-grow 类似。

假设有 1 个 200px 宽度的 flex 容器,它包含 3 个 flex 项(每个宽度 100px,共 300px),此时有 100px 溢出的宽度。

1
2
3
4
5
6
7
.container {
 display: flex;
 width: 200px;
}
.item {
 flex-basis: 100px;
}

如果 flex 项总宽度减去 100px,那么就不会溢出,在线示例

1
2
3
4
5
6
7
8
9
.item1 {
 flex-shrink: 1;
}
.item2 {
 flex-shrink: 2;
}
.item3 {
 flex-shrink: 1;
}

上面代码表示把溢出的 100px 均分为 4 份。.item1 宽度减去 1 份(25px),变成 75px;.item2 宽度减去 2 份(50px),变成 50px;.item3 宽度减去 1 份(25px),变成 75px。

flex(flex-grow、flex-shrink 和 flex-basis 的缩写) #

flexflex-growflex-shrinkflex-basis 的缩写,默认为 0 1 auto。推荐使用 flex 代替那 3 个属性,它会自动使用合理的默认值。比如使用 flex: 1 设置 flex-grow: 1 时,flex-basis 会变成 0%。

flex 可以使用 1~3 个值,具体用法请看 flex - CSS: Cascading Style Sheets | MDN

使用 flex 可以轻松设置 flex 项的大小比例,在线示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.container {
 display: flex;
 flex-direction: row;
 width: 100px;
}

.item1 { /* 25px */
 flex: 1;
}
.item2 { /* 50px */
 flex: 2;
}
.item3 { /* 25px */
 flex: 1;
}

align-self 交叉轴对齐 #

align-self 设置单个 flex 项的交叉轴对齐方式,用法和 align-items 一样,两者可以同时用。

习题 #

参考资料 #

2024-08-03 本站改动:使用 Yue 主题和 giscus 评论,post 目录改为 posts

2024年8月3日 00:00

本文列举了本站于2024年7月28日至2024年08月03日的改动。

  • 清理无用文件
  • 使用我写的 Yue 主题(感觉自己从几乎不折腾博客一下走到折腾博客的尽头了,用自己的主题太爽了,哈哈)
  • 讲 utterances 评论服务改为 giscus(终于可以回复评论了,不需要引用文字或者 @
  • post 文件夹改为 posts

改动很多,最好在 Git 仓库建一个分支搭配子域名(如 test.cyrusyip.org)测试,没问题再合并代码至主分支。修改网站要注意功能和链接(URL)变动,改了链接要修改文章内部链接、重定向、迁移评论。

清理无用文件 #

不再使用以下软件/服务:

  • blogdown
  • Netlify
  • Vercel
  • ……

删除相关文件,修订自述文件和 package.json

将主题换为 Yue #

先构建一次,保存起来

1
2
3
4
cd cyrusyip-blog
mkdir ~/Desktop/blog-builds
rm -r public; npx hugo # hugo 前面多了 npx 是因为我用 npm 安装 hugo
mv public ~/Desktop/blog-builds/jane

删除 Jane 主题和相关文件,重命名配置和中文内容文件夹。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 删除相关文件
rm -r assets layouts resources
git add --all
git commit --message 'chore: remove custom files related to jane theme'
# 删除主题
git submodule deinit themes/jane # 修改 .git/config
git rm themes/jane # 修改 .gitmodules
rm -rf .git/modules/themes/jane # 删除本地文件
git commit --message 'feat!: remove jane theme'
# 重命名
mv config.toml _config.toml
mv content/zh-cn content/zh-CN
git add --all
git commit --message 'refactor: rename to content/zh-CN and _config.toml'

安装主题。

1
2
git submodule add --depth=1 https://github.com/CyrusYip/hugo-theme-yue themes/hugo-theme-yue
git commit --message 'feat: add hugo-theme-yue'

写新配置,对照原来的配置(_config.toml)。删掉了 keywords、description、Google Analytics、Disqus、utterances,后面再加回来。

1
2
3
cp themes/hugo-theme-yue/exampleSite/hugo.yaml .
# 写好新配置后删除旧配置
rm _config.toml

构建,对比目录名和文件名变化,如果没变化表示 URL(链接)没变化。用 tree 命令列举两次构建的文件,复制到剪切板,使用 Diffchecker 对比。

1
2
3
4
5
rm -r public; npx hugo
# 旧主题
tree ~/Desktop/blog-builds/jane | wl-copy
# 新主题
tree ~/Desktop/blog-builds/yue | wl-copy

新主题编译的网站少了一些没用到的页码,这里无需处理。新网站还多了一篇 2021-01-29-python-calculate-utility-bills.Rmarkdown,这是 Rmarkdown 文件,不应该出现于此,在配置文件忽略它。

1
2
# hugo.yaml
ignoreFiles: ["\\.Rmd$", "\\.Rmarkdown$", "_cache$", "\\.knit\\.md$", "\\.utf8\\.md$"]

构建网站后再对比,没这个 .Rmarkdown 文件了。

对比:

  • 原主题编译的网站有 607 个目录,480 个文件,构建时间 750ms。
  • 新主题编译的网站有 398 个目录,354 个文件,构建时间 450ms。新网站关闭了 enableGitInfo,还没添加统计和评论功能(只是加脚本应该不太会影响构建时间)。

用肉眼对比新旧网站,看看有没有问题。看样式、文章数量、日期等。

添加本站原有的功能 #

  • Google Analytics
  • utterances
  • Disqus
    • 只在 cyrusyip.org 上面显示
    • 这次加上了多语言功能
  • 图片说明文字
  • 页脚
    • 社交媒体、Github、Stack overflow、Twitter
    • CC BY-NC-SA 4.0 协议
  • meta description
  • keywords(meta keywords 已过时,参看 Meta Keywords:是什么、为什么不 | Sukka’s Blog

将 utterances 评论改为 giscus #

另建一个站点测试会比较稳妥,但这次为了节约时间就直接改了。

giscusGitHub Discussions 驱动,功能比 utterances 强,支持多语言和回复评论。

转换步骤:

  1. 删除网站的 utterances
  2. 给相关 issue 打标签「Comments」,issue 不多我就直接在网页操作了,一次可以改一页,多的话用 gh issue edit 命令
  3. 开启 Discussion,新建类别「Comments」
  4. 测试 giscus 是否能新建讨论,标题是否正确
  5. 转换 issue(Comments 标签)为 discussion( Comments 类别),参看 Managing discussions - GitHub Docs#converting-issues-based-on-labels。页面显示「Open issues with label ‘Comments’ are being converted to discussions」,但是过了一两个小时都没反应,最后手动转换了。
  6. 创建 giscus.json 配置文件,限制域名,参看 giscus/ADVANCED-USAGE.md#origins
  7. 在 GitHub 卸载 utterances
  8. 把仓库名 blog-comment 改为 blog-comments,去 giscus 官网重新获取脚本(非必须步骤,这里存了超过一则评论,所以用复数 comments)

post 文件夹改为 posts #

之前用的 Even 主题Jane 主题 要求内容放在 content/post 文件夹,但是标签(/tags/)和类别(/categories/)用的是复数,我觉得这非常不协调。这次换主题终于可以改成复数 posts 了。

改了之后网站链接(URL)会变化,需要重定向旧链接、修改文章内链接、迁移评论,参考我之前写的文章(真是帮了大忙):

重命名文件夹 #

构建未修改的网站并保存。

1
2
3
cd cyrusyip-blog
rm -r public; npx hugo
mv public ~/Desktop/blog-builds/before

重命名文件夹。

1
2
mv content/en/post content/en/posts
mv content/zh-CN/post content/zh-CN/posts

预览并修改配置。

1
rm -r public; npx hugo server --navigateToChanged --bind 0.0.0.0
1
2
3
4
5
6
7
8
--- a/hugo.yaml
+++ b/hugo.yaml

- post: /post/:year/:month/:day/:slug/
+ posts: /posts/:year/:month/:day/:slug/

- pageRef: /post
+ pageRef: /posts
1
2
3
4
5
6
7
8
--- a/frontmatter.json
+++ b/frontmatter.json

- "path": "[[workspace]]/content/en/post",
+ "path": "[[workspace]]/content/en/posts",

- "path": "[[workspace]]/content/zh-CN/post",
+ "path": "[[workspace]]/content/zh-CN/posts",
1
2
git add --all
git commit --message "refactor: content/*/post -> content/*/posts"

关掉 Hugo 服务器,构建修改后的网站并保存。

1
2
rm -r public; npx hugo
mv public ~/Desktop/blog-builds/after

复制两个网站目录的结构到剪切板,用 Diffchecker 对比。

1
2
3
4
# 旧网站,Original text
tree ~/Desktop/blog-builds/before | wl-copy
# 新网站,Changed text
tree ~/Desktop/blog-builds/after | wl-copy

就只是 post 目录变成了 posts

1
2
3
4
5
- /en/post
+ /en/posts

- /zh-cn/post
+ /zh-cn/posts

修改文章内部链接 #

要将 [本站的某文章](/zh-cn/post/2021/01/01/hi/) 这样的链接改成 [本站的某文章](/zh-cn/posts/2021/01/01/hi/)。 获取包含链接的文件,并删掉不需要改的链接。

1
2
3
mkdir todo
rg '\[.*\]\(/.*\)' content --only-matching >> todo/internal-links
rg 'cyrusyip\.org' content >> todo/internal-links

链接记录节选:

1
2
3
content/zh-CN/posts/2021-06-19-ac2100-openwrt.md:[红米 AC2100 刷 breed 后刷回官方固件](/zh-cn/post/2022/10/16/ac2100-stock-firmware/)
content/zh-CN/posts/2021-08-30-raspberry-pi-4b-openwrt.md:[在 OpenWrt 控制树莓派 Argon Mini Fan](/zh-cn/post/2021/09/15/openwrt-argon-mini-fan/)
content/zh-CN/posts/2021-08-30-raspberry-pi-4b-openwrt.md:[树莓派 4B 超频教程](/zh-cn/post/2021/09/20/raspberry-pi4-overclock/)

自己手动改链接,改一条就删去一条记录。使用 hugo server --navigateToChanged 命令预览网站可以很方便地查看变化。

我一开始让 ChatGPT 写 Node.js 脚本帮我改,多番交流后它生成了一些不存在的链接,看来这个工作还是太难为它。

重定向 #

现在本站使用 Cloudflare Pages 构建,参考 Redirects · Cloudflare Pages docs 设置重定向。我有点搞不懂我之前写的一些重定向规则,所以要写好注释,未来的我会感谢现在写注释的我。

1
2
3
4
5
6
7
# static/_redirect
######################################################################
# Rename "content/{en,zh-CN}/post" to "content/{en,zh-CN}/posts"
# URL changes: /en/post/ -> /en/posts/ , /zh-cn/post/ -> /zh-cn/posts/
######################################################################
/en/post/* /en/posts/:splat 308
/zh-cn/post/* /zh-cn/posts/:splat 308

测试链接:

关闭评论 #

关闭 Disqus 和 giscus 评论,以免迁移时有读者留言。

迁移 Disqus 评论 #

参考教程:How to download, edit, and upload a URL Map CSV | Disqus

打开迁移工具,点击「Start URL mapper」->「you can download a CSV here」,去邮箱拿到下载链接,下载并解压。这个 CSV 里面的链接非常乱,有无评论的、带查询参数的(/?utterances=xxx)、失效链接。建一个 Git 仓库,把 CSV 文件放进去并提交,后面方便看改动。

修改 CSV,留下以 https://cyrusyip.org/zh-cn/posthttps://cyrusyip.org/en/post 开头的。

文件节选:

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/

在 Vim 执行 :%s/.*/&,&,现在每行都有两个一样的链接,提交改动。

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/,https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/,https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/

执行 :%s/\v(.*)(post)/\1posts,现在把每行第二个链接的 post 改成 posts,提交改动。

1
2
https://cyrusyip.org/zh-cn/post/2021/03/08/girls-day-womens-day-and-goddesses-day/,https://cyrusyip.org/zh-cn/posts/2021/03/08/girls-day-womens-day-and-goddesses-day/
https://cyrusyip.org/zh-cn/post/2021/01/10/remove-odor/,https://cyrusyip.org/zh-cn/posts/2021/01/10/remove-odor/

把修改后的 CSV 文件上传到 Disqus 的 URL mapper。

迁移 giscus 评论 #

将标题的 post 改为 posts

示例:

1
2
- zh-cn/post/2024/04/05/i-hate-obscure-words/
+ zh-cn/posts/2024/04/05/i-hate-obscure-words/

GitHub CLI 不支持修改 Discussion,又得手动改了。

查看评论 #

这几篇文章有 Disqus 评论,打开看看是否有评论。

Discussions 里找几篇文章,打开看看是否有评论。

开启评论 #

可以看到评论,重新打开 Disqus 和 giscus。

菜谱:煎荷包蛋

2024年7月5日 00:00

材料与工具 #

  • 冷藏的鸡蛋(1 个约 51g)
  • 电磁炉
  • 精铁平底炒锅(已开锅)
  • 花生油(不要使用菜籽油这种低闪点的油,热锅后倒下去可能会着火)
  • 锅铲
  • 筷子
  • 计时器
  • 两个碗(一个装鸡蛋,另一个装少量水)

第一次煎蛋的流程 #

  1. 打蛋到碗里
  2. 打开计时器记录热锅时间(后面会用到),电磁炉开到最高档位(2200w)。用手沾一点点水甩入锅,如果直接蒸发就是温度不够,如果水珠弹跳说明温度够了,记下热锅时间,重置计时器,等水珠蒸发。
  3. 暂停电磁炉,倒油,摇晃锅使锅底都被油覆盖
  4. 倒蛋,马上调到 500W1,按下计时器
  5. 等大概 1.5 分钟。等待期间可用锅铲轻推蛋的底部,若不能推动则等待15秒再推,重复此过程直至蛋可以推开。这段时间可以加白胡椒粉2调味。
  6. 铲蛋,用筷子夹住边缘并慢慢地翻面
  7. 大概煎40秒,试着用锅铲轻推,如果不行就等待后再推,能推开就可以铲起来吃了。

继续煎蛋的流程 #

  1. 如果粘锅了,洗锅、烧干水,从头开始
  2. 如果没粘锅,铲走残渣后按照第一次的流程煎蛋
  3. 注意现在锅里有油,不要洒水测试温度,水和油混合会炸开,请参考第一次记录的热锅时间进行热锅

窍门 #

  • 蛋下锅那瞬间温度要够高,之后根据你的喜好调节温度和煎的时间
    • 要脆的就用更大档位
    • 要嫩的就用更小档位
    • 要溏心蛋就减少煎的时间
    • 要太阳蛋(单面荷包蛋)就一面煎好后加水稍微覆盖锅底,合盖利用蒸汽煮熟热蛋的上面
  • 粘锅时等待一段时间鸡蛋会放开锅底
  • 多加油更容易成功,熟练后再根据自己的喜好调节油量
  • 鸡蛋也可以用常温的
  • 可以直接打蛋到锅中,但是蛋壳弄进去就没法拿出来了,如果你会铁砂掌也可以拿
  • 测试温度的原理是莱顿弗罗斯特现象,水接触温度远超其沸点的铁锅时,水底部会产生有隔热作用的蒸汽,令水沸腾的速度大大减慢
  • 不粘锅煎蛋更容易成功,但是不要干烧,要先加油再加热
  • 可以每次煎 2 个蛋,但是 3 个就太多了,很难翻面

  1. 我这个电磁炉 500W 时是间隔加热,持续加热的电磁炉应该也是可以的 ↩︎

  2. 胡椒研磨成粉后香味会散发,不推荐用成品胡椒粉,推荐用胡椒加研磨器 ↩︎

Hugo:自定义标签标题

2024年7月9日 00:00

背景 #

一开始我用 Hugo 做中文网站时打算在文章里用中文标签,但是链接里面的中文会被转换为人类读不懂的字符。比如:/zh-cn/tags/博客/ 会变成 /zh-cn/tags/%E5%8D%9A%E5%AE%A2/。我不喜欢这样,当时搜索一番也没找到办法,就用英文标签了。现在终于知道怎么改了,最终改为链接里面还是用英文,但是页面就显示中文。

下面是针对 hugo-theme-jane(ef8a126)主题的更改方法,方法是通用的,但是不同主题修改的部分可能不同。开始前先厘清两个概念:标签名(tag name)、标签标题(tag title)。在文章前页(front matter)写的是标签名,用于链接;在 _index.md 文件定义的是 title 是标签标题,它会显示于页面。

1
2
3
4
5
6
---
# /content/zh-cn/post/2021-03-21-dongman.md
title: 动漫这个词不能再用了
tags:
 - anime # 标签名
---
1
2
3
4
---
# /content/zh-cn/tags/anime/_index.md
title: 日本动画 # 标签标题
---

如果你启用了 GitInfo 而且不想更改 lastmod,请参考 Hugo:给文章添加 lastmod(上次修改时间)

修改标签单页标题(/zh-cn/tags/*/ #

以 anime(日本动画)为例。先自定义一个标签的标题。

1
2
mkdir --parents content/zh-cn/tags/anime/
touch content/zh-cn/tags/anime/_index.md

添加内容:

1
2
3
4
---
# /content/zh-cn/tags/anime/_index.md
title: 日本动画
---

文章前页(front matter)的 tags 还是保持用 anime。

1
2
3
4
5
6
---
# /content/zh-cn/post/2021-03-21-dongman.md
title: 动漫这个词不能再用了
tags:
 - anime
---

现在 /zh-cn/tags/anime/ 会显示「日本动画」。

修改文章底下的标签标题(/zh-cn/post/* #

在浏览器 inspect 找到 CSS 类,然后去 VS Code 搜索。

本地预览网站。随便打开一篇文章(http://localhost:1313/zh-cn/post/2022/12/03/ajin-oad/),滚动到底部的标签,右击 Inspect,可以看到对于的 HTML 代码。

1
2
3
<div class="post-tags">
 <a href="http://localhost:1313/zh-cn/tags/anime/">anime</a>
</div>

搜索主题目录。

1
rg '<div class="post-tags">' themes/jane

可以发现此代码就在 themes/jane/layouts/post/single.html,将其复制到 layouts/post/(我之前复制过,这次跳过此步骤)。

编辑 layouts/post/single.html

找到下面这段代码:

1
2
3
4
5
6
7
8
<div class="post-tags">
 {{ range . }}
 {{- $name := . -}}
 {{- with $.Site.GetPage "taxonomy" (printf "tags/%s" $name) | default ($.Site.GetPage "taxonomy" (printf "tags/%s" ($name | urlize))) -}}
 <a href="{{ .Permalink }}">{{ $name }}</a>
 {{ end -}}
 {{ end }}
</div>

<a href="{{ .Permalink }}">{{ $name }}</a> 里面的 $name 改为 .LinkTitle。改完是这样的:

1
<a href="{{ .Permalink }}">{{ .LinkTitle }}</a>

现在 http://localhost:1313/zh-cn/post/2021/03/21/dongman/ 底下会显示标签「日本动画」。

修改标签列表页面(/zh-cn/tags/ #

用前面的方法 Inspect 元素并搜索代码。

1
rg '<div class="tag-cloud-tags">' themes/jane

可以发现标签列表页面的模板为 themes/jane/layouts/_default/terms.html,复制一份到根目录再改。

1
2
3
4
mkdir --parents layouts/_default/
cp themes/jane/layouts/_default/terms.html layouts/_default/
git add layouts/_default/terms.html
git commit --message 'chore: copy layouts/_default/terms.html'

打开 layouts/_default/terms.html,搜索 tag-cloud-tags,找到这段代码。

1
2
3
4
5
6
<div class="tag-cloud-tags">
...
 <a href="{{ .Permalink }}"
 style="font-size:{{$currentFontSize}}{{$fontUnit}}">{{ $tagName }}</a>
...
</div>

将里面的 $tagName 改为 .LinkTitle

1
2
<a href="{{ .Permalink }}"
 style="font-size:{{$currentFontSize}}{{$fontUnit}}">{{ .LinkTitle }}</a>

提交改动。

1
2
git add layouts/_default/terms.html layouts/post/single.html
git commit --message 'feat: use tag title'

修改所有标签名,加上标签标题 #

标签名全部改成全小写,以连字符分割。改动比较多,新建一个分支,提交完再合并到主分支。

1
git switch --create customize-tag-titles

使用 VS Code 插件 Front Matter CMS 修改标签名。

加标签标题就比较繁琐,要自己创建文件。我让 ChatGPT 给我写了个脚本,好使!

我说的话:

I am customizing tag titles in a Hugo website. I want you to create a CLI program.

1
create-tag-title directory tag-name tag-title

If I run

1
create-tag-title content/en arch-linux 'Arch Linux'

the program should create content/en/tags/arch-linux/_index.md, the content of _index.md should be:

1
2
3
---
title: Arch Linux
---

ChatGPT 给的 Python 脚本(Shebang 是我加的1):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python 
import os
import sys

def create_tag_title(directory, tag_name, tag_title):
 # Construct the path to the tag directory
 tag_dir = os.path.join(directory, 'tags', tag_name)
 
 # Make the directories if they don't exist
 os.makedirs(tag_dir, exist_ok=True)
 
 # Define the path for the _index.md file
 index_file_path = os.path.join(tag_dir, '_index.md')
 
 # Define the content of the _index.md file
 index_content = f"""---
title: {tag_title}
---
"""
 # Write the content to the _index.md file
 with open(index_file_path, 'w') as index_file:
 index_file.write(index_content)

 print(f"Created {index_file_path} with title: {tag_title}")

if __name__ == '__main__':
 # Ensure the correct number of arguments are provided
 if len(sys.argv) != 4:
 print("Usage: create-tag-title directory tag-name tag-title")
 sys.exit(1)

 # Read the arguments from the command line
 directory = sys.argv[1]
 tag_name = sys.argv[2]
 tag_title = sys.argv[3]

 # Call the function to create the tag title file
 create_tag_title(directory, tag_name, tag_title)

自己写提交信息还是太繁琐,让 ChatGPT 把这部分也加上。

我说的话:

Besides creating the _index.md, I want to stage content/ directory and commit. Example:

1
git add content/; git commit -m 'tag-title: arch-linux -> Arch Linux'

ChatGPT 返回的代码(Shellbang 是我加的):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/env python
import os
import sys
import subprocess

def create_tag_title(directory, tag_name, tag_title):
 # Construct the path to the tag directory
 tag_dir = os.path.join(directory, 'tags', tag_name)
 
 # Make the directories if they don't exist
 os.makedirs(tag_dir, exist_ok=True)
 
 # Define the path for the _index.md file
 index_file_path = os.path.join(tag_dir, '_index.md')
 
 # Define the content of the _index.md file
 index_content = f"""---
title: {tag_title}
---
"""
 # Write the content to the _index.md file
 with open(index_file_path, 'w') as index_file:
 index_file.write(index_content)

 print(f"Created {index_file_path} with title: {tag_title}")

 # Stage the content directory
 try:
 subprocess.run(['git', 'add', directory], check=True)
 print(f"Staged {directory}")

 # Commit the changes
 commit_message = f'tag-title: {tag_name} -> {tag_title}'
 subprocess.run(['git', 'commit', '-m', commit_message], check=True)
 print(f"Committed with message: '{commit_message}'")
 except subprocess.CalledProcessError as e:
 print(f"Error during git operation: {e}")

if __name__ == '__main__':
 # Ensure the correct number of arguments are provided
 if len(sys.argv) != 4:
 print("Usage: create-tag-title directory tag-name tag-title")
 sys.exit(1)

 # Read the arguments from the command line
 directory = sys.argv[1]
 tag_name = sys.argv[2]
 tag_title = sys.argv[3]

 # Call the function to create the tag title file
 create_tag_title(directory, tag_name, tag_title)

我使用的 Python 版本是 3.12.4。

检查链接变化 #

可以在修改前后构建网站,用于对比。如果链接有变化,需要重定向旧链接。改了一堆东西,我已经没心思检查了,忽略此步骤。

参考资料 #


  1. 写完文章我才想到可以让 ChatGPT 加 Shebang ↩︎

Hugo:补全文章的 ISO 8601 日期

2024年5月25日 00:00

背景 #

本网站使用 Hugo 构建,文章日期(date)是这样的:

1
date: '2020-10-02'

这里缺少了时间和时区。于是我给全部文章都设置了同一个时区(时间没填就是 00:00:00),但这个办法有隐患。如果我在另一个时区写文章,这时新的文章还是使用了旧时区。所以最好在每篇文章写上日期、时间和时区。

Hugo 日期的格式为 ISO 8601,示例:

1
date: 2020-10-02T00:00:00+08:00

补全日期的方法就是去掉引号1、补上 T00:00:00+08:00T00:00:00 是时间,+08:00是时区。

修改日期前先给全部文章加上 lastmod(上次修改时间),不然修改日期后 lastmod 全部变成今天了。

操作流程 #

在根目录新建 use-iso8601-date.sh,添加执行权限。

1
2
touch use-iso8601-date.sh
chmod +x use-iso8601-date.sh

use-iso8601-date.sh 填入脚本内容。

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
# usage: ./use-iso8601-date.sh directory-name
directory="$1"
files=$(find "$directory" -type f)
for file in $files; do
 sed -i "s|date: '\(.*\)'|date: \1T00:00:00+08:00|g" "$file"
 sed -i 's|date: "\(.*\)"|date: \1T00:00:00+08:00|g' "$file"
done

修改前先构建网站,后面用于对比。

1
2
3
rm --recursive public/
hugo
mv public public-original

补全日期。

1
./use-iso8601-date.sh content

删除时区配置。

1
2
3
4
# config.toml

# 删掉下面这行
timeZone = "Etc/GMT-8"

再次构建网站。

1
2
hugo
mv public public-changed

使用文件对比工具(我用的是 Kompare)对比 public-originalpublic-changed,有一处差异:/en/post/2023/11/06/hugo-top-level-404/ 文内代码块的日期也被修改了。这样改也没错。

预览,稍微和 https://cyrusyip.org/ 对比一下,看起来一样。

1
hugo server

删除构建的网站。

1
rm --recursive public public-original public-changed

查看差异后提交改动。

1
2
3
git diff
git add --update
git commit --message 'feat: use ISO 8601 date'

删除脚本。

1
rm use-iso8601-date.sh

  1. 日期的引号去不去都行,我觉得不用更简洁就选择去掉。 ↩︎

Hugo:给文章添加 lastmod(上次修改时间)

2024年5月25日 00:00

背景:本站文章的 lastmod(上次修改时间)就是 Git 提交的 author date(作者日期)。我需要批量修改文章(Hugo:补全文章的 ISO 8601 日期),这会导致所有文章的 lastmod 都变成今天。文章内容没变就不应该改 lastmod,所以我打算给每篇文章都加上 lastmod,后面改动文章就 lastmod 就不会变。


先把 lastmod 的优先级调至最高,不然 Hugo 会继续使用 Git 提交日期。

1
2
3
# config.toml
[frontmatter]
 lastmod = ['lastmod', ':default']

在根目录新建 add-lastmod.sh,添加执行权限

1
2
touch add-lastmod.sh
chmod +x add-lastmod.sh

add-lastmod.sh 填入脚本内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env bash
# usage: ./add-lastmod.sh directory-name
directory="$1"
files=$(find "$directory" -type f)
for file in $files; do
 echo "${file}"
 lastmod_date=$(git log --no-show-signature -1 --format=%aI "$file") # example: 2024-05-16T14:23:53+08:00
 echo "$lastmod_date"
 # Use awk to insert the lastmod line above the second ---
 awk -v lastmod="lastmod: $lastmod_date # remove this line if the content is actually changed" '
 BEGIN { frontmatter = 0 }
 /^---$/ { frontmatter++ }
 frontmatter == 2 && !printed { print lastmod; printed = 1 }
 { print }
 ' "$file" >tmpfile && mv tmpfile "$file"
done

修改前先构建网站,后面用于对比。

1
2
3
rm --recursive public/
hugo
mv public public-original

添加 lastmod。

1
./add-lastmod.sh content

再次构建网站。

1
2
hugo
mv public public-changed

使用文件对比工具(我用的是 Kompare)对比 public-originalpublic-changed,结果完全相同。

预览,稍微和 https://cyrusyip.org/ 对比一下,看起来一样。

1
hugo server

删除构建的网站。

1
rm --recursive public public-original public-changed

查看差异后提交改动。

1
2
3
git diff
git add --update
git commit --message 'chore: add lastmod before changing date'

删除脚本。

1
rm add-lastmod.sh

拒绝烂词

2024年4月5日 23:37
迷因图:简单点,说话的方式简单点

最近发现自己越来越讨厌网络词语了,但是一直没细想原因,今天决定仔细思考一下。

我讨厌的词语有(次级列表表示讨厌原因):

  • yyds -> yong yuan de shen -> 永远的神 (形容杰出的人)
    • yyds 是拼音缩写,增加了理解难度。
  • IP -> intellectual property -> 知识产权(可能指「跨媒体制作」或者「网络红人」)
    • IP 是缩写,增加了理解难度;「跨媒体制作」意思清晰,没必要发明新词;「intellectual property」意思增加了,容易造成歧义。
  • city walk(在城市散步)
    • 中文已有「散步」,没必要发明 city walk。
  • 走心(原指「变心」或者「走神」。现在网上有「用心」、「动心」的意思,贬义转褒义了)
    • 中文已有「用心」、「动心」,没必要发明新词;「走心」的意思增加了,容易造成歧义。
  • 百元机(1000元人民币以下的手机)
    • 百元机(1000元以下的手机)不符合直觉,百元机按直觉应该是一百元能买到的手机。
  • 千元机(1000元~2000元人民币的手机)
    • 千元机(1000元~2000元人民币的手机)同样是不符合直觉。
  • emo(引申自 emotion,可以指一切负面情绪,比如:颓废、忧郁、悲伤)
    • emo 意思模糊,你到底是哪种情绪呢?
迷因图:你要不要聽聽看 你現在到底在講什麼

这些词语的问题就是:晦涩难懂、不必要地创造新词、重新定义词语导致歧义、不符合直觉、意义模糊。语言的目的就是传递信息,而费解的词语会阻碍这个过程。我写博客时越来越追求文章通俗简洁,所以就很讨厌这些词语。看来我不是讨厌网络词语,是讨厌烂词。

世界在变化,我们需要创造新词来描述新事物。近年也有一些我觉得不错的网络词语:

  • 全职儿女(一种职业,在家通过做家务等劳动换取父母支付的薪水。)
  • 躺平(很形象地表达不想努力。)
  • 打工人(突出上班就是给别人干活,有自嘲意味。)

前面批评了烂词,我也顺便从中总结我对语言的态度:

  • 保持准确
  • 保持通俗,使更多人理解
  • 继续使用原有词语,能继续用就不需要发明新词
  • 修正费解的词语。比如:鲁棒性(robustness)应该是健壮性;自行车(bicycle)不会自行移动,应该说脚踏车或者双轮车。
  • 可以从外语引入缺乏的词语。比如:咖啡(coffee)、电话(日语借词)。

最后,推荐我自己的中文训练方法:

  • 看到网络词语就在心中翻译
  • 看到中英夹杂就想怎么用纯中文表达
  • 用通俗的词语代替网络词语
  • 表达时考虑受众的知识水平

本文图片出处:

跳过 Android 应用广告

2024年3月19日 00:00

GKD 是开源的屏幕点击应用,可以自动跳过应用的开屏广告和应用内广告。实际测试 GKD 可以跳过铁路 12306 和京东的开屏广告。

使用方法:

  1. 根据官方文档安装并授予权限。
  2. 点击「订阅->右下角加号」,添加规则订阅。

默认规则订阅链接:https://fastly.jsdelivr.net/npm/@gkd-kit/subscription。默认规则于 2024 年 2 月 1日 停止更新,如果失效了可以试试第三方规则或者自己编写规则。GKD 不能跳过所有应用广告,得有对应的规则才行。

KDE Plasma X11 黑屏后重建账号

2023年12月5日 00:00

最近 Arch Linux 升级重启后进 KDE Plasma X11 就黑屏剩下光标,某次升级后 Wayland 也用不上了。在 Arch 论坛看到一个相似的情况,但是里面的方法在我这边没用。提问和等待也要花时间,索性就新建账号吧,至少新账户用 KDE Plasma 没问题。

新建用户 #

按下 Ctrl Alt F4 进入 tty 新建用户。

1
2
useradd --create-home --groups wheel --shell /bin/bash cyrus2
passwd cyrus2

Ctrl Alt F1(也可能是 F2 或者 F3) 返回 sddm 登录新账户。

配置 #

  1. 软件基本都是装在系统,无需重装。
  2. 连接 WIFI
  3. 安装配置文件
  4. 安装 zinit: bash -c "$(curl --fail --show-error --silent --location https://raw.githubusercontent.com/zdharma-continuum/zinit/HEAD/scripts/install.sh)"
  5. 设置默认 Shell:chsh --shell /usr/bin/zsh
  6. 安装 kio-admin,这样 dolphin 才能用管理员权限访问旧账户的家目录
  7. 从旧账号复制配置文件
    • ~/.ssh
    • ~/.gnupg
    • ~/.local/share/keyrings/ (seahorse)
  8. 移动家目录的文件(用 dolphin 复制容易卡住,还时不时询问密码,不要一次弄太多。sudo lf 里面复制的文件权限会变 root。复制 git 仓库超慢,最好忽略。)
    1. Desktop
    2. Documents
    3. Downloads
    4. Music
    5. Pictures
    6. Videos
    7. VirtualBox VMs
  9. 登录软件
    1. 登录 Firefox 和 Brave(Brave 浏览器同步码的最后一个单词每天都不一样
      1. 登录 Bitwarden
    2. 登录 MegaSync 同步文件,同步完后删除旧账户的网盘文件
    3. 登录 Joplin
    4. 登录 VS Code,同步设置
    5. 登录 GitHub CLI
  10. 克隆常用的 Git 仓库
  11. 配置 KDE
    1. 添加 Netspeed Widget
    2. 新建两个 Virtual Desktop
    3. 开机启动(autostart)
      1. MegaSync
      2. Fcitx5
      3. Joplin
    4. bismuth 快捷键设置,Settings -> Shortcuts -> Shortcuts -> Window Tiling,打开需要使用的快捷键
    5. 其他快捷键
      1. Switch to Next Screen = Meta +,
      2. 点击 Window Behavior,关闭 Active screen follows mouse,打开 Separate screen focus
      3. Lock Session = Meta+Ctrl+Space
    6. 特效
      1. Desktop Effects -> Virtual Desktop Switching Animation -> Fade Desktop
    7. 设置 plasma-hud 快捷键
      1
      2
      3
      
      cp ~/.config/kwinrc{,.bak} # 备份
      kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Alt "com.github.zren.PlasmaHUD,/PlasmaHUD,com.github.zren.PlasmaHUD,toggleHUD"
      qdbus org.kde.KWin /KWin reconfigure
      
  12. GoldenDict 导入词典
  13. 清理旧家目录里不需要的文件,比如 ~/.cache

使用默认配置的感想 #

KDE Plasma 默认单击打开文件,这还挺好用的。

如何召唤人工客服

2023年11月27日 00:00

目前中国的购物网站有很多机器人客服。它们假装真人,却常常答非所问。和客服聊天先发送咒语「人工客服」就可以驱散机器人,召唤真人。如果说一次不行,就说多几次。次数越多,威力越大。

小米 / 红米 / HyperOS / MIUI 的自动通话录音教程

2023年11月27日 00:00

本文仅测试于中国版 MIUI 和 HyperOS,适用于 Redmi K40S(MIUI 14.0.7.0.TLMCNXM)、Redmi Note 12 Turbo、Redmi Turbo 3(HyperOS 1.0.16.0.UNPCNXM)。

HyperOS 设置方法 #

通话录音:

  1. 打开设置
  2. 搜索「录音」
  3. 点击「通话录音」(应用设置/其他设置/系统应用设置/电话/通话录音)
  4. 开启「自动录音」

应用录音:

  1. 打开设置
  2. 搜索「录音」
  3. 点击「应用通话录音」(录音机)
  4. 点击「应用通话录音」
  5. 开启「应用通话录音」
  6. 同意权限
  7. 开启「自动开启录音」
  8. 选择需要录音的应用(支持微信和 QQ)

MIUI 设置方法 #

通话录音:打开设置 -> 应用设置 -> 系统应用设置 -> 电话 -> 通话录音 -> 打开「录音通知」和「自动录音」 -> 选择「自动录音对象」

应用录音:打开设置 -> 应用设置 -> 系统应用设置 -> 录音机 -> 应用通话录音 -> 打开「应用通话录音」和「自动开启录音」-> 选择需要录音的应用(支持微信和 QQ)

把 Hugo 网站从 Vercel 搬到 Cloudflare Pages

2023年11月5日 00:00

起因 #

本 Hugo 网站原本使用 Vercel 构建,但是它只获取 Git 源代码仓库的前十次提交1,多数文章的更新时间会出错,变成最新提交的时间。于是我就改用支持克隆整个仓库的 Cloudflare Pages。本站的 RSS 订阅文件消耗流量大,搬到无限流量的 Cloudflare Pages 也比较合适。本文发布时本站已搬到 Cloudflare Pages 超过一个月,一切正常。

搬迁的主要步骤包括:写重定向配置文件、解决 Cloudflare Pages 导致的问题、创建 Cloudflare Pages 应用、删除 Vercel 项目。

重定向配置 #

Cloudflare Pages 的重定向配置写法和 Vercel 不一样。参考 Redirects · Cloudflare Pages docs 写一份。

Vercel 的重定向配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
 "redirects": [
 {
 "source": "/(index.xml)",
 "destination": "/zh-cn/$1"
 },
 {
 "source": "/(post/index.xml)",
 "destination": "/zh-cn/$1"
 },
 {
 "source": "/(about|subscribe|donate)(/?)",
 "destination": "/zh-cn/$1$2"
 },
 {
 "source": "(/post/[0-9].*)",
 "destination": "/zh-cn$1"
 },
 {
 "source": "/(tags|categories)(/?.*)",
 "destination": "/zh-cn/$1$2"
 }
 ]
}

Cloudflare Pages 的重定向配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# This file is used by Cloudflare Pages
# To improve performance, put splats and placeholders to the bottom

###########################
### Old Chinese Website ###
###########################
## RSS
/index.xml /zh-cn/index.xml 308
/index.xml/ /zh-cn/index.xml 308
/post/index.xml /zh-cn/post/index.xml 308
/post/index.xml/ /zh-cn/post/index.xml 308
## Menu
/about /zh-cn/about/ 308
/about/ /zh-cn/about/ 308
/subscribe /zh-cn/subscribe/ 308
/subscribe/ /zh-cn/subscribe/ 308
/donate /zh-cn/donate/ 308
/donate/ /zh-cn/donate/ 308
/tags /zh-cn/tags/ 308
/categories /zh-cn/categories/ 308
/tags/* /zh-cn/tags/:splat 308
/categories/* /zh-cn/categories/:splat 308
## Post
/post/:year/:month/:day/:slug /zh-cn/post/:year/:month/:day/:slug/ 308
/post/:year/:month/:day/:slug/ /zh-cn/post/:year/:month/:day/:slug/ 308
# Examples:
# /post/2020/10/02/ubuntu-compile-goldendict
# /post/2020/10/02/ubuntu-compile-goldendict/


#######################
### Current Website ###
#######################
## RSS links shouldn't contain a trailing slash
/en/index.xml/ /en/index.xml 308
/zh-cn/index.xml/ /zh-cn/index.xml 308

在根目录创建 404 页面 #

本 Hugo 多语言网站的 defaultContentLanguageInSubdir = "true" 配置导致构建网站后根目录没有 404.html 文件,/en//zh-cn/ 才有。此时 Cloudflare 认为网站是单页应用,用户访问 /non-existent-page/ 这样的路径网页就会跳转到 / 并且返回 200 状态码。这是错误的行为,所以要让 Hugo 构建后根目录有 404.html。解决方法请看 Create Top-Level 404 Page for a Multilingual Hugo Site,我目前使用了自己写的 404 页面

去掉缓存规则 #

之前在 Cloudflare 设置了缓存 RSS 文件,现在不需要这些规则了,删除之。

自定义样式使用 SCSS 格式 #

Cloudflare 会缓存 custom.css,改用 SCSS 格式就不会出问题。

创建应用 #

使用以下配置创建 Cloudflare Pages 应用,然后配置域名。

Build settings
Framework preset None
Build command git fetch --unshallow && hugo
Build output directory public
Environment variables
HUGO_VERSION 0.99.1

具体操作请看教程:

Cloudflare Pages 的缺点 #

  • 重定向不支持正则表达式
  • 一个仓库只能导入一次
  • 不支持配置文件

重定向例子 #


  1. Configuring a Build | Vercel Docs: When you make a deployment, Vercel builds your project. During this time, Vercel performs a “shallow clone” on your Git repository using the command git clone --depth=10 (...) and fetches ten levels of git commit history. This means that only the latest ten commits are pulled and not the entire repository history. ↩︎

无需 root 修复 LineageOS 的银行应用 / SafetyNet / Play Integrity

2023年10月12日 00:00

有些银行应用在没有 root 的 LineageOS 打开会提示「不支持在 root 设备使用」,然后退出。用 Lineage Recovery 安装由 althafvly 或者你的维护者提供的 ih8sn,这样银行应用就正常了。

详细解释请看 How to Fix Banking Apps / SafetyNet / Play Integrity on LineageOS without Root

❌
❌