近一个月的博客施工状况,一些好看有趣的新功能。
Favicon
/ai /micros /now
添加了许多 Slash Pages:
- /ai:AI 使用说明。我认为有必要从关于页面单独提取成一个说明页;
- /micros:我称它为「碎片」,包含简短的想法、公告、备忘录、一些我的悄悄话,当然您也可以理解为“微博”——微型博客。灵感来自于 robinwobin.dev/notes/;
- /now:我最近正在做什么。在看什么书、听什么音乐、写什么代码,或是在玩什么游戏。
404 页面
我还为博客添加了一个迟来的 Lain 主题 404 页面,传送门。
里面还包含一条彩蛋音频,模仿 Lain 标题播报,虽然很可能被您的浏览器拦截播放。
Lain 里的音频来自早期 Mac 的语音合成程序 PlainTalk,我在这里找到了相同的音色 Whisper 制作它。
也推荐您去看看 fauux.neocities.org。
Row
Row 只是普通地在行内并排展示内容,就像 Favicon 里并排展示图片那样。
将内容放在 #+begin_row 里:
#+begin_row 一些文本 - 一些列表项 - 两些列表项 - 三些列表项 - 不要乱玩中文 #+caption: 一些代码 #+begin_src python print("Hello, World!") #+end_src #+caption: 一些图片 #+attr_html: :style width: 250px; file:../assets/stickers/shimejisim/shimejisim_2.webp #+end_row
就可以得到这样的效果:
ox-html 默认将 #+begin_NAME 导出为 <div class="NAME"> ,所以 publish.el 不需要做任何处理。
CSS 中将 <div class="row"> 定义为 Flexbox 布局,子元素横向排列,这样就能并排了:
.row { display: flex; gap: 0.75em; align-items: flex-end; margin-block: 1em; overflow-x: auto; }
只是 justify-content: center 来居中的话,当内容超出容器时,左侧内容被截断且没法滚动查看完全,所以要用 safe center:
.row { justify-content: safe center; }
而且还得防止子元素被压缩,不然屏幕小的时候图里的小人就会脑袋尖尖的:
.row { &>* { flex-shrink: 0; } }
最后,已有的规则是窄屏情况下图片宽度占满全屏,这里得取消它:
.row { & img { max-width: none !important; } }
代码块行号和高亮行
使用方式:
#+attr_html: :hl-lines 4 #+begin_src C -n #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } #+end_src
渲染效果:
1#include <stdio.h> 2 3int main() { 4 printf("Hello, World!\n"); 5 return 0; 6}
行号
Org-mode 内置行号支持,要在 #+begin_src 中加 -n 参数开启。
ox-html 默认会在行号后加冒号(1:),可以通过 export filter 去掉:
(defun blog/html-remove-linenr-colon (output backend _) (when (org-export-derived-backend-p backend 'html) (replace-regexp-in-string "\\(<span class=\"linenr\"> *[0-9]+\\): " "\\1" output)))
用 CSS 将行号对齐、加右侧竖线分隔,并设 user-select: none 防止复制代码时带入行号:
.linenr { display: inline-block; padding-inline-end: 0.75em; margin-inline-end: 0.75em; color: var(--ctp-macchiato-overlay0); border-inline-end: 1px solid var(--ctp-macchiato-surface2); text-align: right; user-select: none; }
高亮行
通过 #+attr_html: :hl-lines 指定要高亮的行,支持单行和范围:
#+attr_html: :hl-lines 1-3,9-17 #+begin_src C -n ... #+end_src
实现分三步。首先,自定义 src-block translator 把 :hl-lines 的值写进容器 <div> 的属性里:
(defun blog/html-src-block (src-block contents info) (let* ((attrs (org-export-read-attribute :attr_html src-block)) (hl (plist-get attrs :hl-lines)) (output (org-html-src-block src-block contents info))) (if hl (replace-regexp-in-string "<div class=\"org-src-container\">" (format "<div class=\"org-src-container\" hl-lines=\"%s\">" hl) output t t) output)))
然后,另一个 export filter 扫描整个导出结果,解析 hl-lines 属性,将匹配行号的 <span class="linenr"> 加上 hl class:
(defun blog/html-highlight-lines (output backend _) (when (org-export-derived-backend-p backend 'html) (with-temp-buffer (insert output) (goto-char (point-min)) (while (re-search-forward "hl-lines=\"\\([^\"]+\\)\"" nil t) (let* ((hl-lines (blog/parse-line-spec (match-string 1))) (pre-end (save-excursion (when (re-search-forward "</pre>" nil t) (copy-marker (point)))))) (when pre-end (while (re-search-forward "<span class=\"linenr\">\\( *[0-9]+\\)" pre-end t) (when (member (string-to-number (match-string 1)) hl-lines) (replace-match "<span class=\"linenr hl\">\\1"))) (set-marker pre-end nil)))) (buffer-string))))
最后,将 .linenr.hl 渲染为主题色:
.linenr.hl { color: var(--ctp-macchiato-teal); border-inline-end-color: var(--ctp-macchiato-teal); }
也还不错吧,和以前博客里的样式不太一样,以前类似荧光笔涂了一整行,而现在就只是行号数字变色了。
Tabs
如果您看过卡拉彼丘 hpatchz 的 -c 选项或更早的一些文章,可能您已经见过 Tabs 了。
实现思路来自 your hex editor should color-code bytes,文章中利用 Tabs 对比展示不同代码,很漂亮。除此之外,这也是一篇非常有意思的文章,如果您对十六进制编辑器和代码高亮感兴趣的话。
这个方案无 JavaScript,利用 <details> 标签的互斥特性,同一 name 的 <details> 组内只能有一个处于展开状态。
用法是在 #+begin_tabs 包裹若干 #+begin_details 块:
#+begin_tabs #+attr_html: :summary Tab A :open t #+begin_details C 语言 #+begin_src c #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } #+end_src #+end_details #+attr_html: :summary Tab B #+begin_details Python 语言 #+begin_src python print("Hello, World!") #+end_src #+end_details #+end_tabs
渲染效果:
Tab A
C 语言
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
Tab B
Python 语言
print("Hello, World!")
publish.el 在处理 <details> 时,检测父块是否为 tabs,若是则注入 name 属性将它们绑为同一互斥组:
(in-tabs (and parent (eq (org-element-type parent) 'special-block) (string= (downcase (org-element-property :type parent)) "tabs"))) ... (when in-tabs (format "name=\"tabs-%d\"" (org-element-property :begin parent)))
样式方面参考了 main.css,用 Grid 实现布局,一行标签栏(auto)、一行内容区(1fr),初始时只有一列:
.tabs { display: grid; grid-template-rows: auto 1fr; grid-template-columns: minmax(0, 1fr); }
通过 :has(details:nth-child(n)) 动态调整列数,有几个 <details> 就分几列(手动写了五列的规则,再多可能也用不上了):
.tabs { &:has(details:nth-child(2)) { grid-template-columns: repeat(2, minmax(0, 1fr)); } &:has(details:nth-child(3)) { grid-template-columns: repeat(3, minmax(0, 1fr)); } &:has(details:nth-child(4)) { grid-template-columns: repeat(4, minmax(0, 1fr)); } &:has(details:nth-child(5)) { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
每个 <details> 通过 subgrid 继承容器的行列定义,并同时占满整个网格区域,形成叠层:
.tabs { >details { display: grid; grid-template-rows: subgrid; grid-template-columns: subgrid; grid-row: 1 / -1; grid-column: 1 / -1; } }
各 <summary> 固定在第一行,按 nth-child 对号入座到对应列,z-index: 1 保证始终可点击:
.tabs { >details { &:nth-child(1)>summary { grid-column: 1; } /* ... */ &:nth-child(5)>summary { grid-column: 5; } >summary { grid-row: 1; z-index: 1; } } }
::details-content 始终占据第二行全宽,展开时用 z-index 浮到叠层最上方:
.tabs { >details { &::details-content { grid-row: 2; grid-column: 1 / -1; } &::details-content { z-index: 1; } } }
这样就实现了标签页的切换展开显示。
还有一些针对 Tabs 中只有代码块元素时的特殊规则,可以在 style.css 里看到。
貌似用到的一些 CSS 特性太“现代”,我也不确定大家的浏览器能不能支持。
Spoiler Tags
设计使用 [[spoiler:][文字]] 链接语法,这样就能在行内文本中插入了。
点击显示剧透:
我不会告诉您再次点击别处可以重新遮盖。
在 publish.el 里注册自定义链接类型,导出为带 tabindex 的 <span class="spoiler">:
(org-link-set-parameters "spoiler" :follow #'ignore :export (lambda (_path desc backend _info) (when (org-export-derived-backend-p backend 'html) (format "<span class=\"spoiler\" tabindex=\"0\">%s</span>" desc))))
:follow #'ignore 防止在 Emacs 中点击时报错。tabindex="0" 让 <span> 可聚焦,支持键盘 Tab 导航。
CSS 靠 :focus 切换显隐,默认文字透明,背景色遮盖,聚焦后还原:
.spoiler { color: transparent; background-color: var(--ctp-macchiato-surface2); cursor: pointer; user-select: none; transition: color 0.2s, background-color 0.2s; } .spoiler:focus { color: inherit; background-color: var(--ctp-macchiato-surface0); outline: none; user-select: text; }
HTML5 fancy
在 publish.el 中为 ox-html 开启 HTML5 导出:
(setq org-html-doctype "html5" org-html-html5-fancy t)
开启后导出结构从 <div> + class 改为语义化 HTML5 标签。变化最明显的是图片,带有 #+caption 的图片原先导出为:
<div class="figure"> <p><img src="..."></p> <p><span class="figure-number">Figure 1: </span>说明文字</p> </div>
现在变成原生的 <figure> 与 <figcaption>:
<figure> <img src="..."> <figcaption><span class="figure-number">Figure 1: </span>说明文字</figcaption> </figure>
文章标题区域也会被包进 <header>,正文内容则包进 <article>,各章节变成 <section>。
相应地,CSS 选择器也需要跟进,不一一列出了。
还有呢
目前来看,已经可以赶上 MkDocs For Material 的效果了,甚至说——超过它。
还有许多有趣的东西正在实现……