diff options
| -rw-r--r-- | archetypes/weekly.md | 16 | ||||
| -rw-r--r-- | content/article/i-love-musicals.md | 81 | ||||
| -rw-r--r-- | content/article/webmention.md | 1 | ||||
| -rw-r--r-- | content/weekly/72.md | 94 | ||||
| -rw-r--r-- | data/webmentions.json | 380 | ||||
| -rw-r--r-- | layouts/_partials/components/microformats.html | 2 | ||||
| -rw-r--r-- | layouts/_shortcodes/fedicard.html | 148 | ||||
| -rw-r--r-- | layouts/_shortcodes/music.html | 2 |
8 files changed, 659 insertions, 65 deletions
diff --git a/archetypes/weekly.md b/archetypes/weekly.md index c3c9a03f..852b9b75 100644 --- a/archetypes/weekly.md +++ b/archetypes/weekly.md @@ -1,12 +1,12 @@ ---- -title: 稻草人周刊 Vol. ++++ +title = "稻草人周刊 Vol." subtitle: -tags: -date: -categories: 稻草人周刊 -draft: true -toc: true ---- +tags = +date = +categories = "稻草人周刊" +draft = true +toc = true ++++ {{< music cover="" title="" artist="" apple="" spotify="" netease="" >}} diff --git a/content/article/i-love-musicals.md b/content/article/i-love-musicals.md index 88c2517b..2e064c91 100644 --- a/content/article/i-love-musicals.md +++ b/content/article/i-love-musicals.md @@ -1,15 +1,80 @@ +++ title = '我为什么喜欢音乐剧?' -subtitle = '' -slug = '' -tags = [] -categories = '' -date = 2026-03-09T21:08:10+08:00 -draft = true +slug = 'i-love-musicals' +tags = ['爱','音乐','艺术'] +date = 2026-03-12 00:45:15 +draft = false toc = true +toot = 'https://akk.eltr.ac/objects/a4cc3053-bf1e-4541-9492-7757e0038c89' +++ -跟一些朋友聊起音乐剧的时候,他们似乎都不太能接受演戏演到一半突然唱起来的举动。不过,音乐剧和迪士尼电影还是有些不同。在音乐剧里,音乐和舞蹈不仅起抒情作用,还是叙事载体,是推动剧情发展的重要一环。音乐可以用来描绘人物的内心活动,比如《摇滚红与黑》里的《Ding Dong》和《汉密尔顿》里的《Satisfied》;音乐可以展现人物之间的冲突,比如《地狱客栈》里的《Hell's Greatest Dad》和《魔法坏女巫》里的《What Is This Feeling?》;同一首曲子可以由不同的角色演唱,用相似的旋律展现不同的情感和人物形象,比如《Dear Evan Hansen》里的《Requiem》,甚至相同的旋律在不同的场景响起时,会有不同的效果。当然,也少不了一些纯粹的幽默,比如同样是《Dear Evan Hansen》里的《Sincerely Me》(我相当喜欢这首)。 +跟一些朋友聊起音乐剧的时候,他们似乎都不太能接受演戏演到一半突然唱起来的举动。不过,音乐剧和迪士尼电影还是有些不同(尽管我也很喜欢迪士尼电影里的歌!)。在音乐剧里,音乐和舞蹈不仅起抒情作用,还是叙事载体,是推动剧情发展的重要一环。 -和许多人的第一印象不同,音乐剧并不是什么高雅的艺术形式,实际上相当通俗 +音乐可以用来描绘人物的内心活动,比如《摇滚红与黑》里的《Ding Dong》和《汉密尔顿》里的《Satisfied》;音乐可以展现人物之间的冲突,比如《地狱客栈》里的《Hell's Greatest Dad》和《魔法坏女巫》里的《What Is This Feeling?》;同一首曲子可以由不同的角色演唱,用相似的旋律展现不同的情感和人物形象,比如《Dear Evan Hansen》里的《Requiem》,甚至相同的旋律在不同的场景响起时,会有不同的效果,这种手法叫作 Reprise。 +当然,也少不了一些纯粹的幽默,比如同样是《Dear Evan Hansen》里的《Sincerely Me》,我相当喜欢这首。还有一种歌曲形式叫作 [Patter Song](https://en.wikipedia.org/wiki/Patter_song),节奏非常快,歌词的每个音节几乎都是连着的,而且用词通常是押尾韵或者头韵,听感很欢快,不需要什么唱功和技巧。这种歌通常可以快速地推进情节和交代大量信息,很有趣。 + +音乐里有个概念叫作 motif,通常译作「动机」「乐想」,也音译做「母题」。音乐动机是一段旋律、反复出现的几个突出的音形、一小段音乐片段,可以理解为「用来辨别音乐主题的最小单元」。[^1]在一些音乐剧和歌剧里,人物有各自的动机,出场时场上会响起同一段反复出现的旋律,与人物个性相关。不过,我并不确定这是不是常用于音乐剧的手法,我只知道《地狱客栈》这部音乐剧动漫里使用了这种手法,里面所有的主要人物都有他们出场自带的背景音乐,甚至有代表乐器,比如主角夏莉的乐器是中提琴,她的女友维姬是钟琴。 + +说了这么多,你可能会以为我是个音乐迷,所以会喜欢音乐剧。我确实偶尔会听点古典乐,但真的很少,我听过的也是那些非常出众,或者非常有个性的,比如贝多芬的第九交响曲(《欢乐颂》就在其中)和巴赫的《咖啡康塔塔》;说实话,我的音乐品味很「流行」,比起古典乐,我更喜欢 Lady Gaga。 + +我喜欢音乐剧的原因,和音乐本身的关系其实不大。 + +在这之前要先解释清楚什么是音乐剧。和许多人的第一印象不同,音乐剧并不是什么高雅的艺术形式,实际上相当通俗。人们常常把音乐剧(Musical Theater)和歌剧(Opera)搞混,毕竟,真的有一部名为《歌剧魅影》的音乐剧。对普通人来说,区别实在不明显。 + +简单来说,歌剧重点在「歌」,而音乐剧重点在「剧」。歌剧的歌唱通常是连续的,不会被情节、对话和动作打断,而音乐剧的歌唱间隙可能会插入对话和其他台词,甚至歌词本身就可能是对话。此外,音乐剧往往会有更多的舞蹈和娱乐性表演,说是歌舞剧可能更准确。由于音乐剧重点在「剧」,所以理解台词以及歌词就很重要,不然会跟不上剧情,然而,聆听外语歌剧是很常见的,就算听不懂词,也能欣赏音乐。 + +对话穿插在歌唱中,而歌曲本身作为叙事载体推进剧情的例子,可以听这首: + +{{< music title="Ready For This" artist="《Hazbin Hotel》" cover="https://r2.eltr.ac/geedeapro/article/i-love-musicals/Hazbin_Hotel_Soundtrack_Cover.jpg" apple="https://music.apple.com/cn/album/ready-for-this/1728139119?i=1728140367" spotify="https://open.spotify.com/track/6JyzuWC92biyVZ1l4vUYzi" netease="https://music.163.com/#/song?id=2122126997" >}} + +由于音乐剧的表演通常很戏剧化(这个词用在这里貌似很不准确,音乐剧本来就是戏剧),人物的动作很夸张,情感很丰沛,将这种情感与音乐结合在一起就显得格外富有感染力。在我看来,这也是音乐剧可能会被认为不够高雅的另一个原因:情感的表现不够矜持和细腻。无论是快乐、痛苦还是悲伤,都相当夸张。 + +我能想到的例子是《魔法坏女巫》,这本来是一部小说改编的音乐剧,在最近两年被搬上了电影大荧幕。如果仔细观察 Nessa(也就是主角的妹妹,东方坏女巫)在原版音乐剧和电影版中的表现,就会发现电影版 Nessa 的表现几乎可以说是淡漠,没有音乐剧的那种歇斯底里。由于音乐剧版本的原声带里没有收录《The Wicked Witch of the East》这首歌,所以读者可以参考[这个视频](https://www.bilibili.com/video/BV1WQSMBWEuW/)对比区别。 + +我向来更喜欢饱满的情感,就像喝咖啡也更喜欢日晒处理的咖啡豆(这类豆子一般风味浓烈,带有更多的水果风味),如果要剖析的话,大概和我童年没有受到太多关爱有关——但说实话,这类话题谈得多了,已经有些庸俗了。 + +除了情感更饱满,我还更喜欢音乐剧的粉丝群体。说起来,音乐剧是音乐和舞台剧的奇妙组合,这也导致人们对音乐的要求更低,对剧情的要求也更低。这并不是说音乐剧在这两方面都不行,而是说,我很少发现有人对音乐剧的音乐或者剧情做出极端的负面评价。 + +一方面,不少音乐剧都改编自本身口碑就很好的小说或电影,比如《摇滚红与黑》改编自司汤达的《[红与黑](/library/2025/红与黑/)》,《雨中曲》改编自同名电影。另一方面,当所有人都沐浴在听觉和视觉的双重洗礼下,没有人会把心思放在批评上。回想起来,[去年年底](/weekly/61/)去剧院看过《雨中曲》之后,我就相信不会有人讨厌音乐剧,也很少会对某个特定的音乐剧做出负面评价。 + +由于我所在的地方几乎没人看音乐剧,所以当时剧院卖不出去票,但即便人只坐满了不到一半,观众的掌声和喝彩依旧很激烈。音乐剧的舞台是很大的场面,场上能站很多人,在有这么多人的地方还能编排好舞蹈,而舞蹈又能与剧情的场景完美融合在一起,本身就很令人佩服。我印象最深的是开场舞,剧情里应该是电影的拍摄现场,我记得甚至有演员站在剧院后台会用来挂衣服的小推车上跳舞,同时还有另一个人推动着他。 + +音乐剧的剧情衔接也不需要很巧妙地换场和紧密的逻辑,我还记得《Make 'Em Laugh》这首歌演唱到一半,演员为了演示「如何让观众大笑」,甚至直接在舞台上表演撞大墙。倘若是其他的叙事媒介,看到有人在毫无预兆的情况从不知道什么地方搬来一块泡沫墙壁,还真的撞破了,真的会觉得有些奇怪,除非是喜剧。 + +若难以理解,可以想象单独的舞蹈作品,除非编排得很烂,否则很少有人会觉得舞蹈难看,夸张的肢体动作非常吸引注意力。现在,想象这个舞蹈者一边跳舞一边唱歌,歌曲不仅节奏抓耳,歌词也朗朗上口;他的旁边还有很多伴舞,都穿着颜色鲜艳的服装,身体大幅度地摆动着;除了伴舞,可能还有对手,他们唱歌和跳舞都有来有回;而这一切都在发生的同时,故事还在发展,很快就会有冲突、不速之客、意外和转折。 + +说真的,这样的表演难道不能让你全心全意地投入进去吗?难道不会让你甘愿放下手机,把全部的感官和注意力都献给演员们吗?难道不会让你觉得社交媒体和短视频上碎片化的娱乐信息都食之无味吗? + +我每天沉浸在对技术的钻研、对人文社科的探索和无止尽的软件开发以及运维工作当中,尽管富有热情,但总归会感到疲乏。至少我最近明显地感觉到,大量脑力活动过后,我会感到心情低落,这可能是大脑活动消耗了很多糖,血糖快速降低导致的(因为心情低落的同时,我还会感到饿)。 + +这种情绪波动可能是生理和心理的双重作用,但无论如何,我需要将思绪从西西弗斯的命运中暂时抽离出来,投入一项关注身体感官而非精神和直觉的活动,将我的感官完全交给一支管弦乐队、一群剧场演员和背后的剧作家与作曲家。在那里,我不会看到有人讨论某部剧的续集和后几季如何毁了这部作品,不会看到有人动不动就要给编剧寄刀子,不会有人不合时宜地对情节的合理性发出自以为是的质疑。我会把我的心灵,暂时地,全部献给在我眼前上演的艺术。 + +如果真的有人不喜欢音乐剧?那就…… + +{{< music title="The Guy Who Didn't Like Musicals" artist="《The Guy Who Didn't Like Musicals》" cover="https://r2.eltr.ac/geedeapro/article/i-love-musicals/The_Guy_Who_Didnt_Like_Musicals.jpg" spotify="https://open.spotify.com/track/43t4C5nrEykM8ROc0iPiX6" apple="https://music.apple.com/cn/album/the-guy-who-didnt-like-musicals/1447512283?i=1447512287" >}} + +> Should we kill him? <br/> +> 该不该杀了他? +> +> Should we kill him? <br/> +> 该不该杀了他? +> +> Oh, he pines after a cute lil' barista <br/> +> 噢,他追求那个可爱的小咖啡馆服务生 +> +> Isn't that worth a show-stopping fiesta? <br/> +> 难道不值得一场震惊四座的狂欢? +> +> But for some damn reason, he won't join our singing seaon <br/> +> 但出于某些原因,他不愿意加入我们的歌唱季 +> +> What an ass, what a bitch, what a cock <br/> +> 真是个蠢货,一个婊子,一个傻屌 +> +> The guy who didn't like musicals... <br/> +> 那个不喜欢音乐剧的人…… + + + +[^1]: 参见:[动机(音乐) - Wikipedia](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%9C%BA_(%E9%9F%B3%E4%B9%90)) diff --git a/content/article/webmention.md b/content/article/webmention.md index 339b3893..c9bf850a 100644 --- a/content/article/webmention.md +++ b/content/article/webmention.md @@ -6,6 +6,7 @@ categories = '代码炼金术' date = 2026-03-10 23:04:43 draft = false toc = true +toot = 'https://akk.eltr.ac/objects/46ba3e0a-3b8e-4d84-bcc3-e10eebe87397' +++ [Webmention](https://www.w3.org/TR/webmention/) 是一个 W3C 推荐标准,[IndieWeb](https://indieweb.org/) 很喜欢这个标准,甚至制订了名为 [Salmention](https://indieweb.org/Salmention) 的拓展,只可惜 Webmention 本身就没什么人用,太小众了,这个拓展标准更是没多少人跟进和实现。对于独立博客来说,这项技术其实相当有用,实现起来也不复杂,但中文博客中支持发送和接收 Webmention 的很少,中文资料也几乎没有。 diff --git a/content/weekly/72.md b/content/weekly/72.md index b884762b..b8ba40ae 100644 --- a/content/weekly/72.md +++ b/content/weekly/72.md @@ -1,14 +1,13 @@ ---- -title: 稻草人周刊 Vol. -subtitle: -tags: -date: -categories: 稻草人周刊 -draft: true -toc: true ---- - -{{< music cover="" title="" artist="" apple="" spotify="" netease="" >}} ++++ +title = "稻草人周刊 Vol.72" +subtitle = "" +tags = [] +date = 2026-03-11 19:02:39 +draft = true +toc = true ++++ + +{{< music cover="https://r2.eltr.ac/geedeapro/weekly/72/f1bfa131-bb00-4a47-b3a2-c70fbf082b9c.jpg" title="Nothing's About To Happen To Me" artist="Mitski" apple="https://music.apple.com/cn/album/nothings-about-to-happen-to-me/1860622550" spotify="https://open.spotify.com/album/2M9F3AsbWy7n4LwmJ8pqEu" >}} <!--more--> @@ -37,7 +36,80 @@ toc: true 以前我认为这些人是没有教养,现在看来,他们有可能并不是想要这样做,而是因为觉得不这样做很困难。我想我可以多一些包容和理解吧。 +### Org Mode 是最好的标记语言吗? + +> [!note] 📜 +> [Org Mode Syntax Is One of the Most Reasonable Markup Languages to Use for Text](https://karl-voit.at/2017/09/23/orgmode-as-markup-only/) + +> I did not find any tool support for Markdown, AsciiDoc, Wikitext or reStructuredText anywhere that could compete with the cozy Org mode syntax support within Emacs. + +*Well, look harder.* + +作者在这篇文章中对比了 Org Mode 和其他标记语言(Markdown、AsciiDoc、Wikitext 和 reStructuredText)的区别,以及为什么他认为 Org Mode 比这些标记语言更具优势、更合理。 + +文章前半部分的观点我很认同。首先,Org Mode 是规范化的,而 Markdown 却有很多不同的 Flavor,在不同的平台上输入 Markdown 得到的结果不总是一样的,比如 Markdown 很常见的表格就是拓展语法而非最初的标准。我自己也依赖一些并不常见的 Markdown 拓展语法,比如 Obsidan 里常用的 `==高亮标记==` 和 GitHub 上常用的 [Alert](https://github.blog/changelog/2023-12-14-new-markdown-extension-alerts-provide-distinctive-styling-for-significant-content/),这两个语法都被 Hugo 支持,所以我在博客里也经常用,但是在其他地方就不一定了。这的确是 Markdown 的缺陷之一,不够标准化。 + +其次,Org Mode 的行内标记是符合直觉的,`*加粗*` `/斜体/` `_下划线_` `~代码~` 和 `=等宽字=` 都很容易记住,是唯一的语法,而且都是一前一后的单个字符,不像 Markdown 有 `_斜体_` 和 `*斜体*` 两种写法,而 `**加粗**` 和 `~~删除线~~` 居然需要一前一后的双字符进行标记,和其他行内标记不统一。说实话,我之前尝试 Org Mode 时,就挺搀它的行内标记语法的。 + +接下来是我不太赞同的部分。 + +作者说他认为 Markdown 的链接语法令人疑惑,它会忘记 `[文字]` 和 `(链接)` 哪个在前、哪个在后,并且它不理解为什么要用到两种括号。Org Mode 里的链接写作 `[[链接][文字]]`。我可以用相同的逻辑论述为什么 Org Mode 的链接语法令人疑惑。在 Markdown 里,`[]` 是用来包裹文字的,`()` 是用来包裹链接的,至少我永远不会搞错文字和链接各自的括号是什么;我相信大部分 Markdown 用户记住哪边是链接、哪边是文字的方式都是这样的,`[]()` 这样的形式看得多了,就会知道谁在前在后;如此一来,我就不会搞错链接和文字的位置了。反观 Org Mode,链接和文字都用 `[]` 包裹,谁在前在后就更难记住。 + +我认为「我容易忘记语法的顺序」并不是很有说服力的论据,因为我认为 Org Mode 用户能记住 `[[链接][文字]]`,和 Markdown 用户能记住 `[文字](链接)` 的方式一样:见多了就记住了,不是因为哪个语法更符合直觉。两边都有各自的优势和缺陷,仅仅是个人偏好和习惯问题。 + +我尝试过 Org Mode,其中一部分我很喜欢,另一部分则很不习惯。我最不能接受的是 Org Mode 对中日韩文字的支持,它不能解析 `这样的*加粗*文字`,因为 Org Mode 的行内标记前后必须有空格,这显然是欧洲语言的使用逻辑,中文里就是很少用空格。其次,它不支持我很爱用的 `~~删除线~~`。再次,标准化是好事,但拓展性差也是问题,我没办法用 `==高亮标记==` 这种拓展语法。 + +不过,读完这篇文章过后,我的确产生了很多思考,其中之一是:标记语言本身真的有好坏之分吗?我认为,就和代码风格一样,有的风格固然是不好的,比如可读性很差的风格,但有的风格很难分个高下,比如 `{}` 应不应该换行写、缩进应该用空格还是制表符、文件末尾应该不应有空行,标记语言也有很难说是好是坏的语法。用于写配置的标记语言 YAML 和 TOML 也是一样,有人认为 [YAML 有诸多缺陷](https://www.arp242.net/yaml-config.html),而 [TOML 的缺陷](https://hitchdev.com/strictyaml/why-not/toml/)也有人能列出来不少。 + +你猜怎么着?没有一门标记语言是完美的,包括 Org Mode 和 Markdown。 + +如何选择标记语言,应该由使用者自己尝试、权衡和决定,不存在「最合理且对所有人都同样合理」的语言。或许,可以开发一种与 Vim 和 Emacs 类似的标记语言,人们可以自己写配置,调整各种语法,自己创造最适合自己的语言。 + +### 「结果啊……」 + +> [!note] 📜 +> [“It turns out”](https://jsomers.net/blog/it-turns-out) + +作者表示他很喜欢「It turns out」这个表达,他常在 [Paul Graham](https://paulgraham.com/articles.html) 的文章里读到,这个表达应该可以对应中文里的「结果……」「最后发现……」。 + +如果把一句话直接说出来,尤其是那些稍微有些让人难以接受的观点,可能不会起到很好的效果,但如果作者自己在一开始就表达些许的质疑和不确定,然后再以「结果……」「最后发现……」来转折,语气中透露的些许惊讶就会让读者更容易接受这个没有论据支撑的观点。作者说这是一种很巧妙的偷懒技巧,或者说「黑客技巧」(Hack)。 + +在[第 43 期周刊](/weekly/43/#读着顺口与写得正确)中我分享了 Paul Graham 的另一篇文章,题为《Good Writing》。PG 表示他发现「读着顺口」和「写得正确」这两者之间存在联系,简单来说,如果把句子写得更通顺,这个句子表达的意思就更有可能是正确的,内部逻辑可能更连贯——不过前提是修改,PG 表示他经常修改一些句子,使之更顺口,这样就无意识地修正了一些错误表达。 + +我想「It turns out」可能也是 PG 的「顺口表达」之一吧。 + +### 死互联网已经不再是理论了 + +> [!note] 📜 +> [The dead Internet is not a theory anymore.](https://www.adriankrebs.ch/blog/dead-internet/) + +作者最近邀请了一个职位的申请者来参加第一轮面试,结果收到了这样的回复: + +> hey sorry - my agent got a mind of its own and started applying for jobs for me. i'm not currently looking for a job 😅 <br/> +> 嘿不好意思 - 我的 Agent 有了自己的想法,开始帮我申请工作了,我现在没有在找工作。 +> +> please ignore and sorry for that <br/> +> 请忽略,抱歉。 + +作者意识到「互联网已死」已经成现实了:Hacker News 开始限制新用户发表 ShowHN,因为最近出现了太多 Vibe Coded 且低质量的投稿; Reddit 上有很多机器人在评论区发帖宣传 SaaS 产品;LinkdIn 上的更新也是 AI 废料占大多数(作者贴的一条 LinkdIn 帖子的截图,帖子上全是些让人抓不住重点的车轱辘话,居然有两百多条评论和五千多个点赞;GitHub 也逃不过,AI 生成的 Pull Request 下的代码审查和回复竟然也是 AI 生成的。 + +对此我的态度是,我就待在小互联网上好了,与大平台保持距离,和自己选择的真实的人社交。说实话,我一旦在 RSS 更新中看到了 AI 生成的内容,我就会直接把这个订阅源删掉。 + +不过,倒霉的是,我办法屏蔽现实中人类的声音。 + +{{< fedicard "https://akk.eltr.ac/@eltrac/posts/B48U34PYn9IvBEXdqK" >}} + ## 星群 +### Hugo 社交媒体卡片 + +Twitter 和 Mastodon 自带的嵌入卡片可能和网页的风格不统一,而且需要从外部加载资源,在这个过程中可能会暴露访客的 IP 地址和 Cookies 等信息给第三方。作者做了一个在 Hugo 构建过程从 API 拉取数据,静态展示社交媒体内容的卡片,和自己网站的风格更匹配,而且不会追踪任何用户数据。 + +我其实也想添加,但我一直不太能接受在静态网站的构建过程中,从网络加载资源,我认为这会拖慢*世界上最快的静态网站生成器*的构建速度。尽管 Hugo 有构建缓存,但我还是不太想要在构建时引入网络资源。 + + + +访问:[thumbsupdotme/social-cards](https://github.com/thumbsupdotme/social-cards) + ## 切片 diff --git a/data/webmentions.json b/data/webmentions.json index d272e126..18ad0b30 100644 --- a/data/webmentions.json +++ b/data/webmentions.json @@ -110,6 +110,92 @@ "url": "https://akk.eltr.ac/notice/B3JLtVa0efoBQhYFVY#reblogged-by-B2SrMh01QnWasuUyDQ" } ], + "/article/i-love-musicals/": [ + { + "author": { + "name": "Cytrogen", + "photo": "https://avatars.webmention.io/media.otter.homes/310ced382f23dbe7131a3693f6c23b83c4c4a8ff66b84ce6494cc51720597349.jpg", + "type": "card", + "url": "https://m.otter.homes/@Cytrogen" + }, + "content": null, + "published": null, + "received": "2026-03-11T19:45:47Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B49apOoD3DkDE1SXWC/B2SrMPFJRD3np7VvY8", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B49apOoD3DkDE1SXWC#favorited-by-B2SrMPFJRD3np7VvY8" + }, + { + "author": { + "name": "传话太监学徒@大洋东岸", + "photo": "https://avatars.webmention.io/media.cmx.edu.kg/81d701f33b2c793325411e8b6238856492c8203b45bf4902a22047fc018dd5c5.jpg", + "type": "card", + "url": "https://m.cmx.im/@glazygalaxy" + }, + "content": null, + "published": null, + "received": "2026-03-11T23:39:18Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B49apOoD3DkDE1SXWC/B4ABU82psg6dSH1NtA", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B49apOoD3DkDE1SXWC#favorited-by-B4ABU82psg6dSH1NtA" + }, + { + "author": { + "name": "快把偷走的记忆还给我", + "photo": "https://avatars.webmention.io/c7.io/f32dfe8442d79a9502635bcc24212fc85e220d0bcdf090607000605289cd272d.png", + "type": "card", + "url": "https://c7.io/@kidsnoreturn" + }, + "content": null, + "published": null, + "received": "2026-03-11T23:39:18Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B49apOoD3DkDE1SXWC/B2SvPecz6YZoDNOHZo", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B49apOoD3DkDE1SXWC#favorited-by-B2SvPecz6YZoDNOHZo" + }, + { + "author": { + "name": "快把偷走的记忆还给我", + "photo": "https://avatars.webmention.io/c7.io/f32dfe8442d79a9502635bcc24212fc85e220d0bcdf090607000605289cd272d.png", + "type": "card", + "url": "https://c7.io/@kidsnoreturn" + }, + "content": null, + "published": null, + "received": "2026-03-11T23:39:20Z", + "source": "https://brid.gy/repost/mastodon/@eltrac@akk.eltr.ac/B49apOoD3DkDE1SXWC/B2SvPecz6YZoDNOHZo", + "type": "repost-of", + "url": "https://akk.eltr.ac/notice/B49apOoD3DkDE1SXWC#reblogged-by-B2SvPecz6YZoDNOHZo" + }, + { + "author": { + "name": "J.sky", + "photo": "https://avatars.webmention.io/files.mastodon.social/4aa7e000ead9c2de54c6e4e1a83c0f3f371e8ad1994123a86067fdfbf8dc0655.jpg", + "type": "card", + "url": "https://mastodon.social/@J_sky" + }, + "content": null, + "published": null, + "received": "2026-03-12T00:18:17Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B49apOoD3DkDE1SXWC/B38w5506lDT25A15KS", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B49apOoD3DkDE1SXWC#favorited-by-B38w5506lDT25A15KS" + }, + { + "author": { + "name": "形边四行平", + "photo": "https://avatars.webmention.io/fairy.id/7c327ef1de9071dc232712ab0214e2e23b463dbd19b27c7d9b4674cb914f2b88.jpg", + "type": "card", + "url": "https://fairy.id/@mogenasu" + }, + "content": null, + "published": null, + "received": "2026-03-12T05:43:39Z", + "source": "https://brid.gy/repost/mastodon/@eltrac@akk.eltr.ac/B49apOoD3DkDE1SXWC/B2TrPDl0ns8EDcdwKu", + "type": "repost-of", + "url": "https://akk.eltr.ac/notice/B49apOoD3DkDE1SXWC#reblogged-by-B2TrPDl0ns8EDcdwKu" + } + ], "/article/site-report-5/": [ { "author": { @@ -154,6 +240,194 @@ "url": "https://akk.eltr.ac/notice/B430IaNcDB3WKBOZto#favorited-by-B38w5506lDT25A15KS" } ], + "/article/webmention/": [ + { + "author": { + "name": "looechao", + "photo": "https://avatars.webmention.io/social.looechao.com/3be75909259f28adc32dae4839a76cc5ec43270a69b7ad7e0f3b249756b2d6ee.jpg", + "type": "card", + "url": "https://social.looechao.com/users/looe" + }, + "content": null, + "published": null, + "received": "2026-03-10T15:30:14Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B43Iag4Tcid8IR8h2e", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B47Na8WhcQVAf7k6bI#favorited-by-B43Iag4Tcid8IR8h2e" + }, + { + "author": { + "name": "Cytrogen", + "photo": "https://avatars.webmention.io/media.otter.homes/310ced382f23dbe7131a3693f6c23b83c4c4a8ff66b84ce6494cc51720597349.jpg", + "type": "card", + "url": "https://m.otter.homes/@Cytrogen" + }, + "content": null, + "published": null, + "received": "2026-03-10T15:58:06Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B2SrMPFJRD3np7VvY8", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B47Na8WhcQVAf7k6bI#favorited-by-B2SrMPFJRD3np7VvY8" + }, + { + "author": { + "name": "伏枥", + "photo": "https://avatars.webmention.io/c7.io/3b024daedc46bd2daab952cc2c59cc2fd62b234725ef25e1edd9a6cfe1106bda.jpg", + "type": "card", + "url": "https://c7.io/@LeeHenry" + }, + "content": { + "html": "\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca href=\"https://akk.eltr.ac/users/eltrac\" class=\"u-url\"\u003e@\u003cspan\u003eeltrac\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e Eltrac 晚上好!速速用我的月刊链接在 \u003ca href=\"https://webmention.app/\"\u003e\u003cspan\u003ehttps://\u003c/span\u003e\u003cspan\u003ewebmention.app/\u003c/span\u003e\u003cspan\u003e\u003c/span\u003e\u003c/a\u003e 上试验了一下,看起来终于成功发送了 Webmention(P1)不知道是否真的接收到~还蛮好玩的。 \u003c/p\u003e\u003cp\u003eP.S. 之前咱邮件交流的问题,也就是通过网站文章结尾的表单来提交 Webmention ,刚才又试了一下似乎还是会返回超时错误(P2)。上次反馈后我排查了一下,网络层应该是没有防火墙之类的拦截的,不知道问题到底出现在哪里。以后我就用 Webmention.app 来手动发吧。 \u003c/p\u003e\n\u003ca href=\"https://webmention.app/\"\u003eAutomate your outgoing webmentions\u003c/a\u003e", + "text": "@eltrac Eltrac 晚上好!速速用我的月刊链接在 https://webmention.app/ 上试验了一下,看起来终于成功发送了 Webmention(P1)不知道是否真的接收到~还蛮好玩的。 \n\nP.S. 之前咱邮件交流的问题,也就是通过网站文章结尾的表单来提交 Webmention ,刚才又试了一下似乎还是会返回超时错误(P2)。上次反馈后我排查了一下,网络层应该是没有防火墙之类的拦截的,不知道问题到底出现在哪里。以后我就用 Webmention.app 来手动发吧。 \nAutomate your outgoing webmentions" + }, + "published": "2026-03-10T16:31:01+00:00", + "received": "2026-03-10T16:32:07Z", + "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B47UvoqOB55pfUEke8", + "type": "in-reply-to", + "url": "https://c7.io/@LeeHenry/116205830908100820" + }, + { + "author": { + "name": "伏枥", + "photo": "https://avatars.webmention.io/c7.io/3b024daedc46bd2daab952cc2c59cc2fd62b234725ef25e1edd9a6cfe1106bda.jpg", + "type": "card", + "url": "https://c7.io/@LeeHenry" + }, + "content": null, + "published": null, + "received": "2026-03-10T16:32:07Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B44pWRkOQeSGMIIN3Q", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B47Na8WhcQVAf7k6bI#favorited-by-B44pWRkOQeSGMIIN3Q" + }, + { + "author": { + "name": "花島Saki", + "photo": "https://avatars.webmention.io/files.mstdn.party/f114ba351fb35db23831a3d172a154cc85b008d835c950009a711c648d3e5679.jpg", + "type": "card", + "url": "https://mstdn.party/@hanajima_saki" + }, + "content": { + "html": "\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca href=\"https://akk.eltr.ac/users/eltrac\" class=\"u-url\"\u003e@\u003cspan\u003eeltrac\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e 很有帮助的文章,我前段时间也研究了Webmention标准,不过奈于WordPress的兼容性以及使用场景理念最终没有采用(不太喜欢将社交网络的反应直接连接、会显得混乱,而引用只需要使用pingback就满足了)\u003c/p\u003e", + "text": "@eltrac 很有帮助的文章,我前段时间也研究了Webmention标准,不过奈于WordPress的兼容性以及使用场景理念最终没有采用(不太喜欢将社交网络的反应直接连接、会显得混乱,而引用只需要使用pingback就满足了)" + }, + "published": "2026-03-10T16:43:34+00:00", + "received": "2026-03-10T16:59:07Z", + "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B47W3JMFE6JSHoH1Oq", + "type": "in-reply-to", + "url": "https://mstdn.party/@hanajima_saki/116205880267134126" + }, + { + "author": { + "name": "花島Saki", + "photo": "https://avatars.webmention.io/files.mstdn.party/f114ba351fb35db23831a3d172a154cc85b008d835c950009a711c648d3e5679.jpg", + "type": "card", + "url": "https://mstdn.party/@hanajima_saki" + }, + "content": null, + "published": null, + "received": "2026-03-10T16:59:10Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B3bjSNrhW69SacjQdk", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B47Na8WhcQVAf7k6bI#favorited-by-B3bjSNrhW69SacjQdk" + }, + { + "author": { + "name": "Spike Leung", + "photo": "https://avatars.webmention.io/taxodium.ink/2b954e9d7ff8da1cb55e49fc098ea06f464c9ffd1872e1f92cc2ac1bb10ad7c4.png", + "type": "card", + "url": "https://taxodium.ink" + }, + "content": { + "html": "\u003ch1 class=\"p-name\"\u003e给博客添加 Webmention\u003c/h1\u003e\n\u003ch2\u003e目录\u003c/h2\u003e\n\n\u003cul\u003e\u003cli\u003e\u003ca href=\"https://taxodium.ink/add-webmention-to-blog.html#D459C268-A454-4D43-9E49-97D0A90C2F67\"\u003e什么是 Webmention\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://taxodium.ink/add-webmention-to-blog.html#E55EE851-CF6C-4FC6-A4F8-94FD38BFA17F\"\u003e为什么接入 Webmention\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://taxodium.ink/add-webmention-to-blog.html#D55D3AFA-42A0-4D14-A52F-FB34162E31DD\"\u003e如何接入 Webmention\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://taxodium.ink/add-webmention-to-blog.html#D2EB3BEC-396F-43D6-BA15-A89E38A671F8\"\u003e一些问题\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://taxodium.ink/add-webmention-to-blog.html#967313DB-C374-4182-8F81-AE96666BF2CD\"\u003e写在最后\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://taxodium.ink/add-webmention-to-blog.html#806C5BB0-D8CB-4AD8-BC52-2866C9072B41\"\u003e其他参考文章\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n???? The Great Escape Of Our Time - 椅子乐团 The Chairs\n\u003c/p\u003e\n\n\u003ch2\u003e什么是 Webmention\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\nWebmention 是一种通知,表明一个 URL 链接到另一个 URL。\n\u003c/p\u003e\n\n\u003cp\u003e\n例如,Alice 在她的博客上写了一篇有趣的帖子。随后 Bob 在自己的网站上写了一篇回复她的帖子,并链接回 Alice 的原始帖子。\nBob 的发布软件向 Alice 发送一个 Webmention,通知她的文章被回复了,\nAlice 的软件可以将该回复显示为原始帖子上的评论。\n\u003c/p\u003e\n\n\u003cp\u003e\n\u003ca href=\"https://www.w3.org/TR/webmention/#introduction-p-1:~:text=A%20Webmention%20is,the%20original%20post.\"\u003eSource\u003c/a\u003e\n\u003c/p\u003e\n\u003c/blockquote\u003e\n\n\u003cp\u003e\nWebmention 和评论差不多,只是它的门槛相对高一点,需要你写一篇文章,并链接到你想评论的文章。\n(或者在社交媒体如 BlueSky、Mastodon 上发一个帖子,引用文章的链接)\n\u003c/p\u003e\n\n\u003cp\u003e\n如果对方支持了 Webmention,你发送 Webmention 通知后,对方就会将你的内容显示出来。\n\u003c/p\u003e\n\nWebmention 的大致流程\u003ca href=\"https://taxodium.ink/images/add-webmention-to-blog/mermaid.png\"\u003e\u003cimg src=\"https://taxodium.ink/images/add-webmention-to-blog/mermaid.png\" alt=\"mermaid.png\" /\u003e\u003c/a\u003e\n\n\u003cspan\u003e图1 \u003c/span\u003eWebmention 的大致流程,参考 \u003ca href=\"https://www.w3.org/TR/webmention/#protocol-summary\"\u003ehttps://www.w3.org/TR/webmention/#protocol-summary\u003c/a\u003e\n\n\n\u003ch2\u003e为什么接入 Webmention\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\n\u003cb\u003e实现跨站点的对话。\u003c/b\u003e当你链接到某个网站时,你可以发送一个 Webmention 来通知它。如果该网站支持 Webmention,那么该网站可能会将你的帖子显示为评论、赞或其他回应,就这样,你就实现了从一个站点到另一个站点的对话!\n\u003c/p\u003e\n\n\u003cp\u003e\n\u003ca href=\"https://indieweb.org/Webmention#:~:text=Enables%20cross-site%20conversations.%20When%20you%20link%20to%20a%20website%2C%20you%20can%20send%20it%20a%20Webmention%20to%20notify%20it.%20If%20it%20supports%20Webmentions%2C%20then%20that%20website%20may%20display%20your%20post%20as%20a%20comment%2C%20like%2C%20or%20other%20response%2C%20and%20presto%2C%20you%E2%80%99re%20having%20a%20conversation%20from%20one%20site%20to%20another!\"\u003eSource\u003c/a\u003e\n\u003c/p\u003e\n\u003c/blockquote\u003e\n\n\u003cp\u003e\n我觉得 Webmention 和集成在博客中的评论系统是类似的。\n\u003c/p\u003e\n\n\u003cp\u003e\n如果你要回应一篇文章,通过 Webmention,你需要写一篇文章链接到这篇文章,并发送一个 Webmention 通知。通过如 \u003ca href=\"https://brid.gy/\"\u003eBridgy\u003c/a\u003e 之类的服务,也可以收集社交网络上(Blusky、Mastodon 等)关于文章的回应,例如点赞、转发等。\n\u003c/p\u003e\n\n\u003cp\u003e\n但我觉得并没有比集成于博客的评论系统更方便。\n\u003c/p\u003e\n\n\u003cp\u003e\n从数据层面看,除非自己买服务器搭建 Webmention,否则用第三方提供的服务,数据依然不在自己的手上。\n\u003c/p\u003e\n\n\u003cp\u003e\n所以我为什么要接入 Webmention?\n\u003c/p\u003e\n\n\u003cp\u003e\n最开始是在 \u003ca href=\"https://www.owenyoung.com/blog/de-roboticization/#2-xie-bo-ke%20:~:text=%E6%88%91%E7%9A%84%E5%8D%9A%E5%AE%A2%E6%B2%A1%E6%9C%89%E8%AF%84%E8%AE%BA%E5%8A%9F%E8%83%BD%EF%BC%8C%E5%9B%A0%E4%B8%BA%E8%AF%84%E8%AE%BA%E5%8A%9F%E8%83%BD%E4%BC%9A%E5%85%B6%E5%AE%9E%E4%BC%9A%E8%AE%A9%E6%88%91%E5%BC%95%E5%85%A5%E7%A4%BE%E4%BA%A4%E7%BD%91%E7%BB%9C%E7%9A%84%E5%8C%85%E8%A2%B1%E6%84%9F%E3%80%82%E6%88%91%E8%A7%89%E5%BE%97%E8%A7%A3%E5%86%B3%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E7%9A%84%E5%A5%BD%E5%8A%9E%E6%B3%95%E6%98%AF%E5%BC%95%E5%85%A5webmention%E7%9A%84%E5%8A%9F%E8%83%BD%EF%BC%8C%E5%A6%82%E6%9E%9C%E6%9C%89%E4%BA%BA%E6%83%B3%E8%A6%81%E5%9B%9E%E5%BA%94%EF%BC%8C%E5%BE%97%E6%8F%90%E4%BA%A4%E8%87%AA%E5%B7%B1%E7%9A%84%E5%9B%9E%E5%BA%94%E7%BD%91%E5%9D%80%EF%BC%8C%E8%80%8C%E4%B8%8D%E6%98%AF%E5%9C%A8%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E8%AF%84%E8%AE%BA%E6%A1%86%E9%87%8C%E7%95%99%E4%B8%8B%E4%B8%89%E8%A8%80%E4%B8%A4%E8%AF%AD%E3%80%82%E8%BF%99%E6%8F%90%E9%AB%98%E4%BA%86%E8%AF%84%E8%AE%BA%E7%9A%84%E6%88%90%E6%9C%AC%EF%BC%8C%E9%BC%93%E5%8A%B1%E6%83%B3%E8%A6%81%E7%95%99%E8%A8%80%E7%9A%84%E4%BA%BA%E6%8B%A5%E6%9C%89%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8D%9A%E5%AE%A2%EF%BC%8C%E4%BB%A5%E5%8F%8A%E5%86%99%E4%B8%80%E4%BA%9B%E7%BB%8F%E8%BF%87%E6%80%9D%E8%80%83%E7%9A%84%E8%AF%9D%E8%AF%AD%E3%80%82%E6%88%91%E7%9A%84%E5%8D%9A%E5%AE%A2%E5%B7%B2%E7%BB%8F%E6%94%AF%E6%8C%81%E4%BA%86%20Webmention%EF%BC%8C%E6%88%91%E5%9C%A8Now%2C%20I%E2%80%99m%20in%20IndieWeb%3F%E9%87%8C%E6%9C%89%E8%AE%B0%E5%BD%95%E6%88%91%E7%9A%84%E5%AE%9E%E7%8E%B0%E3%80%82\"\u003e我想让自己说话不那么“端着” | Owen Young\u003c/a\u003e 中了解到 Webmention,当时觉得有些麻烦。\n\u003c/p\u003e\n\n\u003cp\u003e\n最近网上冲浪的时候,看到不少博客都有集成 Webmention,然后了解了一下 \u003ca href=\"https://webmention.io/\"\u003ewebmention.io\u003c/a\u003e 的使用,看起来不麻烦,就想着集成看看。\n\u003c/p\u003e\n\n\u003cp\u003e\n非要找个理由的话,就是折腾一下,好玩吧。\n\u003c/p\u003e\n\n\n\n\u003ch2\u003e如何接入 Webmention\u003c/h2\u003e\n\u003cp\u003e\n如果自己搭建的话,可以参照 \u003ca href=\"https://www.w3.org/TR/webmention\"\u003eWebmention 规范\u003c/a\u003e 实现;或者找现成的工具,你可以在 \u003ca href=\"https://indieweb.org/Webmention\"\u003eWebmention | IndieWeb\u003c/a\u003e 找到很多工具和服务。\n\u003c/p\u003e\n\n\u003cp\u003e\n我目前实现很简单,接收依赖 webmention.io 提供的服务,需要自己编写一点 \u003ca href=\"https://taxodium.ink/js/webmention.js\"\u003e脚本\u003c/a\u003e 调用 API;发送通过手动触发;麻烦一些的地方是给博客添加 \u003ca href=\"https://microformats.org/wiki/Main_Page\"\u003emicroformats\u003c/a\u003e 。\n\u003c/p\u003e\n\n\n\u003ch3\u003e作为接收方\u003c/h3\u003e\n\u003cp\u003e\n接收我使用 webmention.io 提供的服务,按照 \u003ca href=\"https://indielogin.com/setup\"\u003eHow to Set Up Your Website for IndieLogin.com\u003c/a\u003e 的说明进行设置。\n\u003c/p\u003e\n\n\u003cp\u003e\n我用 GitHub 进行验证,需要在 GitHub Profile 中包含我的网站,同时在网站添加一个链接。\n\u003c/p\u003e\n\n\n\u003ca href=\"https://taxodium.ink/images/add-webmention-to-blog/github-profile.png\"\u003e\u003cimg src=\"https://taxodium.ink/images/add-webmention-to-blog/github-profile.png\" alt=\"github-profile.png\" /\u003e\u003c/a\u003e\n\n\u003cspan\u003e图2 \u003c/span\u003e我的 GitHub Profile 页面,我把网站的地址添加到了 Profile 中\n\u003cpre\u003e\u003ccode\u003e\u0026lt;\u003cspan\u003ehead\u003c/span\u003e\u0026gt;\n \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e省略其他内容\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003elink\u003c/span\u003e \u003cspan\u003ehref\u003c/span\u003e=\u003cspan\u003e\"https://github.com/spike-leung\"\u003c/span\u003e \u003cspan\u003erel\u003c/span\u003e=\u003cspan\u003e\"me\"\u003c/span\u003e\u0026gt;\n \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e省略其他内容\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n\u0026lt;/\u003cspan\u003ehead\u003c/span\u003e\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\n\n\u003cp\u003e\n这样就能通过 webmention.io 的验证了。\n\u003c/p\u003e\n\n\u003cp\u003e\n接下来按照 webmention.io 提供的 API 获取 Webmentions 的数量和内容,需要在页面上添加 HTML 元素,写 \u003ca href=\"https://taxodium.ink/js/webmention.js\"\u003e一些 JS 代码\u003c/a\u003e 调用 API 渲染数据(也可以用 \u003ca href=\"https://github.com/PlaidWeb/webmention.js\"\u003ewebmention.js\u003c/a\u003e),再写点 CSS 优化一下样式。\n\u003c/p\u003e\n\n\u003cp\u003e\n如果你打算渲染 Webmentions 返回的 HTML,需要注意 \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS\"\u003eCross-site scripting (XSS)\u003c/a\u003e 攻击。\n\u003c/p\u003e\n\n防止 XSS 攻击\u003cp\u003e\n防止 XSS 攻击,\u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API#using_a_csp_to_enforce_trusted_types\"\u003eTrusted Types API\u003c/a\u003e 看起来不错,可惜浏览器支持度不够。\n\u003c/p\u003e\n\n\u003cp\u003e\n防御 XSS 主要从两方面入手:\n\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e净化 (sanitize) 用户输入的内容。可以使用 \u003ca href=\"https://github.com/cure53/DOMPurify\"\u003eDOMPurify\u003c/a\u003e 处理。\u003c/li\u003e\n\u003cli\u003e使用 \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP\"\u003eContent Security Policy (CSP)\u003c/a\u003e 限制执行。\u003ca href=\"https://www.netlify.com/blog/general-availability-content-security-policy-csp-nonce-integration/\"\u003eNetlify 有插件支持\u003c/a\u003e,倒是方便了许多。要注意的是,当开启了 CSP,一些行内的脚本,如写在元素上的 onclick、 onchange 等会被阻止执行,需要将他们挪动到 JS 文件里。\u003c/li\u003e\n\u003c/ul\u003e\u003cp\u003e\n也可以期待一下 \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML\"\u003eElement: setHTML() method\u003c/a\u003e 被广泛支持,它提供了一种 XSS 安全的方法用于插入 HTML 内容到页面中。\n\u003c/p\u003e\n\u003cp\u003e\n搭建好之后,可以用 \u003ca href=\"https://webmention.rocks/#:~:text=Testing%20your%20Receiver\"\u003eWebmention Rocks!\u003c/a\u003e 提供的服务进行测试。\n\u003c/p\u003e\n\n\u003cp\u003e\n至于查看 Webmentions 也有几种方法:\n\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e到对应的页面下看\u003c/li\u003e\n\u003cli\u003e订阅 webmention.io 提供的 \u003ca href=\"https://webmention.io/settings\"\u003eMentions Feed\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e使用 webmention.io 提供的 \u003ca href=\"https://webmention.io/settings/webhooks\"\u003eWeb Hook\u003c/a\u003e 通知自己\u003c/li\u003e\n\u003cli\u003e到 webmention.io 的 \u003ca href=\"https://webmention.io/dashboard\"\u003eDashbaord\u003c/a\u003e 查看\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\u003ch3\u003e作为发送方\u003c/h3\u003e\n\u003cp\u003e\n作为发送方,你只需要在你的文章中包含对方的链接,就可以向对方发送 Webmention 了。\n\u003c/p\u003e\n\n\u003cp\u003e\n但是如果你想让你发送的内容更丰富一些,就需要通过 \u003ca href=\"https://microformats.org/wiki/Main_Page\"\u003emicroformats\u003c/a\u003e 标记网站信息,如用户名、头像、内容、你要提及的链接等,从而让 Webmention 解析展示。\n\u003c/p\u003e\n\n\u003cp\u003e\n按理说只要包含了对方链接,对方就能收到 Webmention。\n\u003c/p\u003e\n\n\u003cp\u003e\n但我发送后却收到了「The Microformats at the source URL do not contain a link to the target URL.」的错误提示。\n\u003c/p\u003e\n\n\u003cp\u003e\n可能是因为我使用 microformats 标记了内容,此时需要确保 microformats 解析后的内容中,包含对方的 URL。\n\u003c/p\u003e\n\u003cp\u003e\n最主要的标记是 \u003ca href=\"https://microformats.org/wiki/h-card\"\u003eh-card\u003c/a\u003e 和 \u003ca href=\"https://microformats.org/wiki/h-entry\"\u003eh-entry\u003c/a\u003e,h-card 标记\u003cem\u003e用户\u003c/em\u003e信息;\nh-entry 标记\u003cem\u003e内容\u003c/em\u003e信息,标记的部分会作为 Webmention 的内容呈现。\n\u003c/p\u003e\n\n\u003cp\u003e\n关于 h-card 和 h-entry 的作用,推荐看 \u003ca href=\"https://aaronparecki.com/2018/06/30/11/your-first-webmention\"\u003eSending your First Webmention from Scratch\u003c/a\u003e,文章循序渐进地引导你添加相应的标记,并且有对应的示例,同时文章本身也可以用来测试 Webmention 的发送。\n\u003c/p\u003e\n\n\u003cp\u003e\nmicroformats 这些标记,可以和 HTML 做类比,它也是有层级关系的,最终会解析成一个 JSON 的树形结构。在你测试的时候,可以从 \u003ca href=\"https://microformats.org/wiki/parsers\"\u003eMicroformats Parsers\u003c/a\u003e 找一个 parser 检查你的文章解析出来的内容。\n\u003c/p\u003e\n\n\u003cp\u003e\n\u003cb\u003e关于 h-entry\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=the%20%22datetime%22%20property.-,what%20is%20the%20bare%20minimum%20list%20of%20required%20properties%20on%20an,a%20nested%20h-card%20with%20author%20details%20like%20name%2C%20photo%2C%20url.,-Examples%20in%20the\"\u003ewhat is the bare minimum list of required properties on an h-entry\u003c/a\u003e 中回答道,没有任何 h-entry 的属性是强制要求的。\n\u003c/p\u003e\n\n\u003cp\u003e\n但推荐至少包含:\n\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=e-content%20-%20full%20content%20of%20the%20entry\"\u003ee-content\u003c/a\u003e 标记文章内容,被标记的内容会作为 Webmention 的内容被发送\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=p-name%20-%20entry%20name/title\"\u003ep-name\u003c/a\u003e 标记文章标题\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=p-author%20-%20who%20wrote%20the%20entry%2C%20optionally%20embedded%20h-card(s)\"\u003ep-author\u003c/a\u003e 标记作者信息,一般会和 h-card 结合使用\u003c/li\u003e\n\u003c/ul\u003e\u003cp\u003e\n一些我建议加上的:\n\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=u-in-reply-to%20-%20the%20URL%20which%20the%20h-entry%20is%20considered%20reply%20to%20(i.e.%20doesn%E2%80%99t%20make%20sense%20without%20context%2C%20could%20show%20up%20in%20comment%20thread)%2C%20optionally%20an%20embedded%20h-cite%20(reply-context)%20(example)\"\u003eu-in-reply-to\u003c/a\u003e 标记你回应的链接。此外 \u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=u-like-of%20-%20the%20URL%20which%20the%20h-entry%20is%20considered%20a%20%E2%80%9Clike%E2%80%9D%20(favorite%2C%20star)%20of.%20Optionally%20an%20embedded%20h-cite\"\u003eu-like-of\u003c/a\u003e、\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=u-repost-of%20-%20the%20URL%20which%20the%20h-entry%20is%20considered%20a%20%E2%80%9Crepost%E2%80%9D%20of.%20Optionally%20an%20embedded%20h-cite\"\u003eu-report-of\u003c/a\u003e 等可以用于标记不同的引用目的\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=dt-published%20-%20when%20the%20entry%20was%20published\"\u003edt-published\u003c/a\u003e 标记文章的发布日期\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=dt-updated%20-%20when%20the%20entry%20was%20updated\"\u003edt-updated\u003c/a\u003e 标记文章的更新日期\u003c/li\u003e\n\u003c/ul\u003e\u003cp\u003e\n\u003cb\u003e关于 h-card\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\nh-card 是用来标记用户信息的,介绍一些我觉得比较有用的:\n\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-card#:~:text=p-name%20-%20The%20full/formatted%20name%20of%20the%20person%20or%20organization\"\u003ep-name\u003c/a\u003e 标记个人或组织的全称\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-card#:~:text=u-email%20-%20email%20address\"\u003eu-email\u003c/a\u003e 标记邮箱,一般标记在 mailto 链接上\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-card#:~:text=u-logo%20-%20a%20logo%20representing%20the%20person%20or%20organization%20(e.g.%20a%20face%20icon)\"\u003eu-logo\u003c/a\u003e 标记你的 logo,一般标记在 \u003ccode\u003e\u0026lt;img\u0026gt;\u003c/code\u003e 标签上\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-card#:~:text=u-photo%20-%20a%20photo%20of%20the%20person%20or%20organization\"\u003eu-photo\u003c/a\u003e 标记你的头像,一般标记在 \u003ccode\u003e\u0026lt;img\u0026gt;\u003c/code\u003e 标签上\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-card#:~:text=u-url%20-%20home%20page%20or%20other%20URL%20representing%20the%20person%20or%20organization\"\u003eu-url\u003c/a\u003e 标记你的主页,一般标记在一个链接上\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://microformats.org/wiki/h-card#:~:text=p-note%20-%20additional%20notes\"\u003ep-note\u003c/a\u003e 标记额外信息,有人会用来写自我介绍\u003c/li\u003e\n\u003c/ul\u003e\u003cp\u003e\n除了这些,还有标记地址、性别、国籍、出生日期、工作等,感兴趣可以自行探索。\n\u003c/p\u003e\n\n\u003cp\u003e\nh-card 可以结合 h-entry 的 p-author 用于标记文章的作者信息,\u003cb\u003e在这种场景下,h-entry 需要是 h-card 的父元素。\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n综上,一个相对完整的 microformats 标记是这样的:\n\u003c/p\u003e\n\n\n\u003cpre\u003e\u003ccode\u003e\u0026lt;\u003cspan\u003e!doctype\u003c/span\u003e html\u0026gt;\n\u0026lt;\u003cspan\u003emeta\u003c/span\u003e \u003cspan\u003echarset\u003c/span\u003e=\u003cspan\u003e\"utf-8\"\u003c/span\u003e\u0026gt;\n\u0026lt;\u003cspan\u003etitle\u003c/span\u003e\u0026gt;\u003cspan\u003e\u003cspan\u003eHello World\u003c/span\u003e\u003c/span\u003e\u0026lt;/\u003cspan\u003etitle\u003c/span\u003e\u0026gt;\n\u0026lt;\u003cspan\u003ebody\u003c/span\u003e\u0026gt;\n \u0026lt;\u003cspan\u003ediv\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"h-entry\"\u003c/span\u003e\u0026gt; \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e注意 h-entry 是其他属性的父元素,包含了 h-card\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003ediv\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"p-author h-card\"\u003c/span\u003e\u0026gt; \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e使用 h-card 结合 p-author 标记作者信息\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003eimg\u003c/span\u003e \u003cspan\u003esrc\u003c/span\u003e=\u003cspan\u003e\"https://aaronpk.com/images/aaronpk.jpg\"\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"u-photo\"\u003c/span\u003e \u003cspan\u003ewidth\u003c/span\u003e=\u003cspan\u003e\"40\"\u003c/span\u003e\u0026gt;\n \u0026lt;\u003cspan\u003ea\u003c/span\u003e \u003cspan\u003ehref\u003c/span\u003e=\u003cspan\u003e\"https://aaronpk.com/\"\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"u-url p-name\"\u003c/span\u003e\u0026gt;Aaron Parecki\u0026lt;/\u003cspan\u003ea\u003c/span\u003e\u0026gt;\n \u0026lt;/\u003cspan\u003ediv\u003c/span\u003e\u0026gt;\n \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e标记引用的链接,表示回复\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003ep\u003c/span\u003e\u0026gt;in reply to: \u0026lt;\u003cspan\u003ea\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"u-in-reply-to\"\u003c/span\u003e \u003cspan\u003ehref\u003c/span\u003e=\u003cspan\u003e\"https://aaronparecki.com/2018/06/30/11/your-first-webmention\"\u003c/span\u003e\u0026gt;@aaronpk\u0026lt;/\u003cspan\u003ea\u003c/span\u003e\u0026gt;\u0026lt;/\u003cspan\u003ep\u003c/span\u003e\u0026gt;\n \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e标记回复的内容\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003ep\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"e-content\"\u003c/span\u003e\u0026gt;Trying out this guide to sending webmentions\u0026lt;/\u003cspan\u003ep\u003c/span\u003e\u0026gt;\n \u0026lt;\u003cspan\u003ep\u003c/span\u003e\u0026gt;\n \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e标记当前文章的 URL\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003ea\u003c/span\u003e \u003cspan\u003ehref\u003c/span\u003e=\u003cspan\u003e\"https://aaronpk.com/reply.html\"\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"u-url\"\u003c/span\u003e\u0026gt;\n \u003cspan\u003e\u0026lt;!-- \u003c/span\u003e\u003cspan\u003e标记当前文章的发布日期\u003c/span\u003e\u003cspan\u003e --\u0026gt;\u003c/span\u003e\n \u0026lt;\u003cspan\u003etime\u003c/span\u003e \u003cspan\u003eclass\u003c/span\u003e=\u003cspan\u003e\"dt-published\"\u003c/span\u003e \u003cspan\u003edatetime\u003c/span\u003e=\u003cspan\u003e\"2018-06-30T17:15:00-0700\"\u003c/span\u003e\u0026gt;July 30, 2018\u0026lt;/\u003cspan\u003etime\u003c/span\u003e\u0026gt;\n \u0026lt;/\u003cspan\u003ea\u003c/span\u003e\u0026gt;\n \u0026lt;/\u003cspan\u003ep\u003c/span\u003e\u0026gt;\n \u0026lt;/\u003cspan\u003ediv\u003c/span\u003e\u0026gt;\n\u0026lt;/\u003cspan\u003ebody\u003c/span\u003e\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\n\n\u003cp\u003e\n具体如何标记,要看你博客中的 HTML 标记是如何排布的,在标记时注意层级关系就好。测试阶段可以多利用 \u003ca href=\"https://microformats.org/wiki/parsers\"\u003eMicroformats Parsers\u003c/a\u003e 解析你的文章,检查解析出来的内容是否符合需要。\n\u003c/p\u003e\n\n\u003cp\u003e\n都设置好之后就可以向集成了 Webmention 的网站发送通知了,你可以:\n\u003c/p\u003e\n\u003cul\u003e\u003cli\u003e找到引用文章的 Webmention 入口,填写你的文章 URL 并提交\u003c/li\u003e\n\u003cli\u003e自己构建 POST 请求,或用第三方服务,如 \u003ca href=\"https://telegraph.p3k.io/\"\u003eTelegraph\u003c/a\u003e 手动发送\u003c/li\u003e\n\u003cli\u003e考虑集成 \u003ca href=\"https://webmention.app/\"\u003ewebmention.app\u003c/a\u003e 等实现自动发送\u003c/li\u003e\n\u003c/ul\u003e\u003cp\u003e\n一般发送之后都会有响应,如果你的 Webmention 没有展示出来,可以基于返回信息进行问题排查。\n\u003c/p\u003e\n\n\n\n\n\u003ch2\u003e一些问题\u003c/h2\u003e\n\n\n\u003ch3\u003e常见问题\u003c/h3\u003e\n\u003cp\u003e\n大部分疑惑 \u003ca href=\"https://indieweb.org/Webmention-faq\"\u003eWebmention-faq | IndieWeb\u003c/a\u003e 中都有解答。\n\u003c/p\u003e\n\n\u003cp\u003e\n分享一些比较常见的问题:\n\u003c/p\u003e\n\n\u003cul\u003e\u003cli\u003e\u003ca href=\"https://indieweb.org/Webmention-faq#Do_webmentions_work_only_when_both_sites_use_webmentions?\"\u003eDo webmentions work only when both sites use webmentions?\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://indieweb.org/Webmention-faq#How_do_you_know_if_a_website_uses_webmentions?\"\u003eHow do you know if a website uses webmentions?\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://indieweb.org/Webmention-faq#Which_links_should_webmentions_be_sent_to\"\u003eWhich links should webmentions be sent to\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://indieweb.org/Webmention-faq#Should_webmentions_be_sent_for_removals\"\u003eShould webmentions be sent for removals\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\u003ch3\u003e关于 \u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=u-in-reply-to%20-%20the%20URL%20which%20the%20h-entry%20is%20considered%20reply%20to%20(i.e.%20doesn%E2%80%99t%20make%20sense%20without%20context%2C%20could%20show%20up%20in%20comment%20thread)%2C%20optionally%20an%20embedded%20h-cite%20(reply-context)%20(example)\"\u003eu-in-reply-to\u003c/a\u003e 的使用\u003c/h3\u003e\n\u003cp\u003e\n\u003ccode\u003eu-in-reply-to\u003c/code\u003e 用于标记 h-entry 中要回复的链接,应该结合 \u003ccode\u003ee-content\u003c/code\u003e 标记回应的内容。\n\u003c/p\u003e\n\n\u003cp\u003e\n如果只是引用了一些链接,并没有对于这些链接的回应,不需要用 \u003ccode\u003eu-in-reply-to\u003c/code\u003e 标记,直接发送 Webmention 就好了。\n\u003c/p\u003e\n\n\n\n\u003ch3\u003e关于 \u003ca href=\"https://microformats.org/wiki/h-entry\"\u003eh-entry\u003c/a\u003e 的使用\u003c/h3\u003e\n\u003cp\u003e\n尽管可以设置多个 h-entry,但我觉得一篇文章一个 h-entry 就够了。\n\u003c/p\u003e\n\n\u003cp\u003e\n原因是 h-entry 往往需要指定文章标题、作者、发布和更新时间、内容等,这和一篇文章的内容是刚好对应上的。\n\u003c/p\u003e\n\n\u003cp\u003e\n如果标记多个 h-entry,比较难保证每个 h-entry 都有这些信息,尽管 \u003ca href=\"https://microformats.org/wiki/h-entry#:~:text=the%20%22datetime%22%20property.-,what%20is%20the%20bare%20minimum%20list%20of%20required%20properties%20on%20an,a%20nested%20h-card%20with%20author%20details%20like%20name%2C%20photo%2C%20url.,-Examples%20in%20the\"\u003eh-entry 不要求包含任一属性\u003c/a\u003e。\n\u003c/p\u003e\n\n\u003cp\u003e\n我的建议是:\n\u003c/p\u003e\n\n\u003cul\u003e\u003cli\u003eh-entry 设置在包含文章内容、作者信息、发布日期等的父元素上\u003c/li\u003e\n\u003cli\u003e\u003cp\u003e\ne-content 设置在包含整篇文章内容的元素上\n\u003c/p\u003e\n\n\u003cp\u003e\n好处是,只要在文章中的链接都能被发现,不需要额外再标记什么。缺点是,e-content 标记的内容可能太多,导致实际回复的部分被淹没其中。\n\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\u003ch3\u003ewebmention.io 的 target 需要完全匹配\u003c/h3\u003e\n\u003cp\u003e\n见 \u003ca href=\"https://github.com/aaronpk/webmention.io/issues/229#issuecomment-3602563563\"\u003eWhen URL has hash,webmention.io'a API not reponse data #229\u003c/a\u003e。\n\u003c/p\u003e\n\n\u003cp\u003e\n有的 webmention 的发送时的 URL 是 \u003ccode\u003ehttps://domain/page.html#abc\u003c/code\u003e ,通过 webmention.io 的 \u003ca href=\"https://github.com/aaronpk/webmention.io?tab=readme-ov-file#api\"\u003eAPI\u003c/a\u003e,target 参数设置为 \u003ccode\u003ehttps://domain/page.html\u003c/code\u003e 是无法获取到数据的,需要将 target 设置为 \u003ccode\u003ehttps://domain/page.html#abc\u003c/code\u003e ,完全匹配才行,因为目前存储的就是完整的 URL。\n\u003c/p\u003e\n\n\u003cp\u003e\n如果 target 是 \u003ccode\u003ehttps://domain/page.html#abc\u003c/code\u003e ,注意需要将 \u003ccode\u003e#\u003c/code\u003e \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent\"\u003e转义\u003c/a\u003e 成 \u003ccode\u003e%23\u003c/code\u003e 。\n\u003c/p\u003e\n\u003cp\u003e\n我目前的处理方法是用 \u003ca href=\"https://github.com/aaronpk/webmention.io?tab=readme-ov-file#find-all-links-to-your-domain\"\u003e基于域名获取所有数据的 API\u003c/a\u003e 先获取所有的数据,然后基于页面的 URL 进行过滤。\n\u003c/p\u003e\n\n\n\n\u003ch3\u003e发送了 webmention 但是没有显示出来\u003c/h3\u003e\n\u003cp\u003e\n一种可能是,webmention 要求源页面中包含目标页面的链接,而有的源页面是由 JS 动态渲染的,不是静态的,导致 webmention 无法从源页面找到目标页面的链接,而认为源页面没有发送 webmention。\n\u003c/p\u003e\n\n\u003cp\u003e\n排查的方法是,在浏览器中查看网络请求,找到源页面返回的内容,搜索一下内容中是否包含目标页面的链接。\n\u003c/p\u003e\n\n\u003cp\u003e\n也可能是网站显示的问题,可以尝试联系作者。\n\u003c/p\u003e\n\n\n\n\n\u003ch2\u003e写在最后\u003c/h2\u003e\n\u003cp\u003e\n以为接入挺简单的,但实际做还是折腾了两天,果真是「纸上得来终觉浅,绝知此事要躬行」呀。\n\u003c/p\u003e\n\n\u003cp\u003e\n如果你链接了我的文章,欢迎你向我发送 Webmention,这样就建立了文章之间的链接。一方面我可以看看别人引用时的评论;另一方面,其他读者也可以通过 Webmention 知道你的文章。\n\u003c/p\u003e\n\n\u003cp\u003e\n我认为这是一件共赢的事,你引用了我的链接,帮我推广了给更多人;而你向引用的文章发送 Webmention,也能帮忙推广你的内容。\n\u003c/p\u003e\n\n\u003cp\u003e\n何乐而不为?\n\u003c/p\u003e\n\n\n\n\u003ch2\u003e其他参考文章\u003c/h2\u003e\n\u003cul\u003e\u003cli\u003e\u003ca href=\"https://jason-memo.dev/posts/webmention/\"\u003e透過 webmention 來搜集 blog 的社群迴響\u003c/a\u003e by Jason Chen\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.xiang578.com/note/641ea60f-a31e-4979-9327-c50509c146e3.html\"\u003eWebmention 实现参考\u003c/a\u003e by Runye\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.owenyoung.com/en/blog/indieweb/\"\u003eNow, I'm in IndieWeb?\u003c/a\u003e by Owen Young\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.cytrogen.icu/posts/1de\"\u003e在 Hexo 项目中添加 Webmention\u003c/a\u003e by Cytrogen\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.geedea.pro/article/webmention/\"\u003eWebmention 简明指南\u003c/a\u003e by Eltrac\u003c/li\u003e\n\u003c/ul\u003e", + "text": "给博客添加 Webmention\n目录\n\n什么是 Webmention\n为什么接入 Webmention\n如何接入 Webmention\n一些问题\n写在最后\n其他参考文章\n\n???? The Great Escape Of Our Time - 椅子乐团 The Chairs\n\n\n什么是 Webmention\n\n\nWebmention 是一种通知,表明一个 URL 链接到另一个 URL。\n\n\n\n例如,Alice 在她的博客上写了一篇有趣的帖子。随后 Bob 在自己的网站上写了一篇回复她的帖子,并链接回 Alice 的原始帖子。\nBob 的发布软件向 Alice 发送一个 Webmention,通知她的文章被回复了,\nAlice 的软件可以将该回复显示为原始帖子上的评论。\n\n\n\nSource\n\n\n\n\nWebmention 和评论差不多,只是它的门槛相对高一点,需要你写一篇文章,并链接到你想评论的文章。\n(或者在社交媒体如 BlueSky、Mastodon 上发一个帖子,引用文章的链接)\n\n\n\n如果对方支持了 Webmention,你发送 Webmention 通知后,对方就会将你的内容显示出来。\n\n\nWebmention 的大致流程\n\n图1 Webmention 的大致流程,参考 https://www.w3.org/TR/webmention/#protocol-summary\n\n\n为什么接入 Webmention\n\n\n实现跨站点的对话。当你链接到某个网站时,你可以发送一个 Webmention 来通知它。如果该网站支持 Webmention,那么该网站可能会将你的帖子显示为评论、赞或其他回应,就这样,你就实现了从一个站点到另一个站点的对话!\n\n\n\nSource\n\n\n\n\n我觉得 Webmention 和集成在博客中的评论系统是类似的。\n\n\n\n如果你要回应一篇文章,通过 Webmention,你需要写一篇文章链接到这篇文章,并发送一个 Webmention 通知。通过如 Bridgy 之类的服务,也可以收集社交网络上(Blusky、Mastodon 等)关于文章的回应,例如点赞、转发等。\n\n\n\n但我觉得并没有比集成于博客的评论系统更方便。\n\n\n\n从数据层面看,除非自己买服务器搭建 Webmention,否则用第三方提供的服务,数据依然不在自己的手上。\n\n\n\n所以我为什么要接入 Webmention?\n\n\n\n最开始是在 我想让自己说话不那么“端着” | Owen Young 中了解到 Webmention,当时觉得有些麻烦。\n\n\n\n最近网上冲浪的时候,看到不少博客都有集成 Webmention,然后了解了一下 webmention.io 的使用,看起来不麻烦,就想着集成看看。\n\n\n\n非要找个理由的话,就是折腾一下,好玩吧。\n\n\n\n\n如何接入 Webmention\n\n如果自己搭建的话,可以参照 Webmention 规范 实现;或者找现成的工具,你可以在 Webmention | IndieWeb 找到很多工具和服务。\n\n\n\n我目前实现很简单,接收依赖 webmention.io 提供的服务,需要自己编写一点 脚本 调用 API;发送通过手动触发;麻烦一些的地方是给博客添加 microformats 。\n\n\n\n作为接收方\n\n接收我使用 webmention.io 提供的服务,按照 How to Set Up Your Website for IndieLogin.com 的说明进行设置。\n\n\n\n我用 GitHub 进行验证,需要在 GitHub Profile 中包含我的网站,同时在网站添加一个链接。\n\n\n\n\n\n图2 我的 GitHub Profile 页面,我把网站的地址添加到了 Profile 中\n\u003chead\u003e\n \u003c!-- 省略其他内容 --\u003e\n \u003clink href=\"https://github.com/spike-leung\" rel=\"me\"\u003e\n \u003c!-- 省略其他内容 --\u003e\n\u003c/head\u003e\n\n\n\n\n这样就能通过 webmention.io 的验证了。\n\n\n\n接下来按照 webmention.io 提供的 API 获取 Webmentions 的数量和内容,需要在页面上添加 HTML 元素,写 一些 JS 代码 调用 API 渲染数据(也可以用 webmention.js),再写点 CSS 优化一下样式。\n\n\n\n如果你打算渲染 Webmentions 返回的 HTML,需要注意 Cross-site scripting (XSS) 攻击。\n\n\n防止 XSS 攻击\n防止 XSS 攻击,Trusted Types API 看起来不错,可惜浏览器支持度不够。\n\n\n\n防御 XSS 主要从两方面入手:\n\n净化 (sanitize) 用户输入的内容。可以使用 DOMPurify 处理。\n使用 Content Security Policy (CSP) 限制执行。Netlify 有插件支持,倒是方便了许多。要注意的是,当开启了 CSP,一些行内的脚本,如写在元素上的 onclick、 onchange 等会被阻止执行,需要将他们挪动到 JS 文件里。\n\n也可以期待一下 Element: setHTML() method 被广泛支持,它提供了一种 XSS 安全的方法用于插入 HTML 内容到页面中。\n\n\n搭建好之后,可以用 Webmention Rocks! 提供的服务进行测试。\n\n\n\n至于查看 Webmentions 也有几种方法:\n\n到对应的页面下看\n订阅 webmention.io 提供的 Mentions Feed\n使用 webmention.io 提供的 Web Hook 通知自己\n到 webmention.io 的 Dashbaord 查看\n作为发送方\n\n作为发送方,你只需要在你的文章中包含对方的链接,就可以向对方发送 Webmention 了。\n\n\n\n但是如果你想让你发送的内容更丰富一些,就需要通过 microformats 标记网站信息,如用户名、头像、内容、你要提及的链接等,从而让 Webmention 解析展示。\n\n\n\n按理说只要包含了对方链接,对方就能收到 Webmention。\n\n\n\n但我发送后却收到了「The Microformats at the source URL do not contain a link to the target URL.」的错误提示。\n\n\n\n可能是因为我使用 microformats 标记了内容,此时需要确保 microformats 解析后的内容中,包含对方的 URL。\n\n\n最主要的标记是 h-card 和 h-entry,h-card 标记用户信息;\nh-entry 标记内容信息,标记的部分会作为 Webmention 的内容呈现。\n\n\n\n关于 h-card 和 h-entry 的作用,推荐看 Sending your First Webmention from Scratch,文章循序渐进地引导你添加相应的标记,并且有对应的示例,同时文章本身也可以用来测试 Webmention 的发送。\n\n\n\nmicroformats 这些标记,可以和 HTML 做类比,它也是有层级关系的,最终会解析成一个 JSON 的树形结构。在你测试的时候,可以从 Microformats Parsers 找一个 parser 检查你的文章解析出来的内容。\n\n\n\n关于 h-entry\n\n\n\nwhat is the bare minimum list of required properties on an h-entry 中回答道,没有任何 h-entry 的属性是强制要求的。\n\n\n\n但推荐至少包含:\n\ne-content 标记文章内容,被标记的内容会作为 Webmention 的内容被发送\np-name 标记文章标题\np-author 标记作者信息,一般会和 h-card 结合使用\n\n一些我建议加上的:\n\nu-in-reply-to 标记你回应的链接。此外 u-like-of、u-report-of 等可以用于标记不同的引用目的\ndt-published 标记文章的发布日期\ndt-updated 标记文章的更新日期\n\n关于 h-card\n\n\n\nh-card 是用来标记用户信息的,介绍一些我觉得比较有用的:\n\np-name 标记个人或组织的全称\nu-email 标记邮箱,一般标记在 mailto 链接上\nu-logo 标记你的 logo,一般标记在 \u003cimg\u003e 标签上\nu-photo 标记你的头像,一般标记在 \u003cimg\u003e 标签上\nu-url 标记你的主页,一般标记在一个链接上\np-note 标记额外信息,有人会用来写自我介绍\n\n除了这些,还有标记地址、性别、国籍、出生日期、工作等,感兴趣可以自行探索。\n\n\n\nh-card 可以结合 h-entry 的 p-author 用于标记文章的作者信息,在这种场景下,h-entry 需要是 h-card 的父元素。\n\n\n\n综上,一个相对完整的 microformats 标记是这样的:\n\n\n\n\u003c!doctype html\u003e\n\u003cmeta charset=\"utf-8\"\u003e\n\u003ctitle\u003eHello World\u003c/title\u003e\n\u003cbody\u003e\n \u003cdiv class=\"h-entry\"\u003e \u003c!-- 注意 h-entry 是其他属性的父元素,包含了 h-card --\u003e\n \u003cdiv class=\"p-author h-card\"\u003e \u003c!-- 使用 h-card 结合 p-author 标记作者信息 --\u003e\n \u003cimg src=\"https://aaronpk.com/images/aaronpk.jpg\" class=\"u-photo\" width=\"40\"\u003e\n \u003ca href=\"https://aaronpk.com/\" class=\"u-url p-name\"\u003eAaron Parecki\u003c/a\u003e\n \u003c/div\u003e\n \u003c!-- 标记引用的链接,表示回复 --\u003e\n \u003cp\u003ein reply to: \u003ca class=\"u-in-reply-to\" href=\"https://aaronparecki.com/2018/06/30/11/your-first-webmention\"\u003e@aaronpk\u003c/a\u003e\u003c/p\u003e\n \u003c!-- 标记回复的内容 --\u003e\n \u003cp class=\"e-content\"\u003eTrying out this guide to sending webmentions\u003c/p\u003e\n \u003cp\u003e\n \u003c!-- 标记当前文章的 URL --\u003e\n \u003ca href=\"https://aaronpk.com/reply.html\" class=\"u-url\"\u003e\n \u003c!-- 标记当前文章的发布日期 --\u003e\n \u003ctime class=\"dt-published\" datetime=\"2018-06-30T17:15:00-0700\"\u003eJuly 30, 2018\u003c/time\u003e\n \u003c/a\u003e\n \u003c/p\u003e\n \u003c/div\u003e\n\u003c/body\u003e\n\n\n\n\n具体如何标记,要看你博客中的 HTML 标记是如何排布的,在标记时注意层级关系就好。测试阶段可以多利用 Microformats Parsers 解析你的文章,检查解析出来的内容是否符合需要。\n\n\n\n都设置好之后就可以向集成了 Webmention 的网站发送通知了,你可以:\n\n找到引用文章的 Webmention 入口,填写你的文章 URL 并提交\n自己构建 POST 请求,或用第三方服务,如 Telegraph 手动发送\n考虑集成 webmention.app 等实现自动发送\n\n一般发送之后都会有响应,如果你的 Webmention 没有展示出来,可以基于返回信息进行问题排查。\n\n\n\n\n\n一些问题\n\n\n常见问题\n\n大部分疑惑 Webmention-faq | IndieWeb 中都有解答。\n\n\n\n分享一些比较常见的问题:\n\n\nDo webmentions work only when both sites use webmentions?\nHow do you know if a website uses webmentions?\nWhich links should webmentions be sent to\nShould webmentions be sent for removals\n关于 u-in-reply-to 的使用\n\nu-in-reply-to 用于标记 h-entry 中要回复的链接,应该结合 e-content 标记回应的内容。\n\n\n\n如果只是引用了一些链接,并没有对于这些链接的回应,不需要用 u-in-reply-to 标记,直接发送 Webmention 就好了。\n\n\n\n\n关于 h-entry 的使用\n\n尽管可以设置多个 h-entry,但我觉得一篇文章一个 h-entry 就够了。\n\n\n\n原因是 h-entry 往往需要指定文章标题、作者、发布和更新时间、内容等,这和一篇文章的内容是刚好对应上的。\n\n\n\n如果标记多个 h-entry,比较难保证每个 h-entry 都有这些信息,尽管 h-entry 不要求包含任一属性。\n\n\n\n我的建议是:\n\n\nh-entry 设置在包含文章内容、作者信息、发布日期等的父元素上\n\ne-content 设置在包含整篇文章内容的元素上\n\n\n\n好处是,只要在文章中的链接都能被发现,不需要额外再标记什么。缺点是,e-content 标记的内容可能太多,导致实际回复的部分被淹没其中。\n\nwebmention.io 的 target 需要完全匹配\n\n见 When URL has hash,webmention.io'a API not reponse data #229。\n\n\n\n有的 webmention 的发送时的 URL 是 https://domain/page.html#abc ,通过 webmention.io 的 API,target 参数设置为 https://domain/page.html 是无法获取到数据的,需要将 target 设置为 https://domain/page.html#abc ,完全匹配才行,因为目前存储的就是完整的 URL。\n\n\n\n如果 target 是 https://domain/page.html#abc ,注意需要将 # 转义 成 %23 。\n\n\n我目前的处理方法是用 基于域名获取所有数据的 API 先获取所有的数据,然后基于页面的 URL 进行过滤。\n\n\n\n\n发送了 webmention 但是没有显示出来\n\n一种可能是,webmention 要求源页面中包含目标页面的链接,而有的源页面是由 JS 动态渲染的,不是静态的,导致 webmention 无法从源页面找到目标页面的链接,而认为源页面没有发送 webmention。\n\n\n\n排查的方法是,在浏览器中查看网络请求,找到源页面返回的内容,搜索一下内容中是否包含目标页面的链接。\n\n\n\n也可能是网站显示的问题,可以尝试联系作者。\n\n\n\n\n\n写在最后\n\n以为接入挺简单的,但实际做还是折腾了两天,果真是「纸上得来终觉浅,绝知此事要躬行」呀。\n\n\n\n如果你链接了我的文章,欢迎你向我发送 Webmention,这样就建立了文章之间的链接。一方面我可以看看别人引用时的评论;另一方面,其他读者也可以通过 Webmention 知道你的文章。\n\n\n\n我认为这是一件共赢的事,你引用了我的链接,帮我推广了给更多人;而你向引用的文章发送 Webmention,也能帮忙推广你的内容。\n\n\n\n何乐而不为?\n\n\n\n\n其他参考文章\n透過 webmention 來搜集 blog 的社群迴響 by Jason Chen\nWebmention 实现参考 by Runye\nNow, I'm in IndieWeb? by Owen Young\n在 Hexo 项目中添加 Webmention by Cytrogen\nWebmention 简明指南 by Eltrac" + }, + "published": "2025-09-25T21:24:00", + "received": "2026-03-10T17:08:32Z", + "source": "https://taxodium.ink/add-webmention-to-blog.html", + "type": "mention-of", + "url": "https://taxodium.ink/add-webmention-to-blog.html" + }, + { + "author": { + "name": "Eltrac :neocat_laptop:", + "photo": "https://avatars.webmention.io/akk-media.eltr.ac/96fdded4db077fd77188a2ca1427f357b0fda90f141c58c9bac3e1706c050c2a.jpg", + "type": "card", + "url": "https://akk.eltr.ac/users/eltrac" + }, + "content": { + "html": "\u003cp\u003e收到了!不过遇到了一个小问题。文中的引用包含 \u003ccode\u003e#锚点\u003c/code\u003e,导致 Webmention 实际上发送到了 \u003ccode\u003e/posts/site-report-3/#%E8%87%AA%E7%94%B1%E4%B8%8E%E4%BB%A3%E4%BB%B7\u003c/code\u003e 而非预期中的 \u003ccode\u003e/posts/site-report-3/\u003c/code\u003e。\u003c/p\u003e\u003cp\u003e应该是接收端处理的问题,不过 Webmention 标准的确没有规定怎么处理锚点。我看看怎么解决,要么在显示 Webmention 的时候忽略锚点,要么就换成 Weepinbell 的时候自己写一下处理锚点的逻辑好了。\u003c/p\u003e", + "text": "收到了!不过遇到了一个小问题。文中的引用包含 #锚点,导致 Webmention 实际上发送到了 /posts/site-report-3/#%E8%87%AA%E7%94%B1%E4%B8%8E%E4%BB%A3%E4%BB%B7 而非预期中的 /posts/site-report-3/。\n\n应该是接收端处理的问题,不过 Webmention 标准的确没有规定怎么处理锚点。我看看怎么解决,要么在显示 Webmention 的时候忽略锚点,要么就换成 Weepinbell 的时候自己写一下处理锚点的逻辑好了。" + }, + "published": "2026-03-11T00:26:46+00:00", + "received": "2026-03-11T09:13:46Z", + "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B48BODFfDieFYBHSzY", + "type": "in-reply-to", + "url": "https://akk.eltr.ac/notice/B48BODFfDieFYBHSzY" + }, + { + "author": { + "name": "这里是无名博客", + "photo": "https://files.mastodon.social/accounts/avatars/116/108/551/430/274/331/original/2d08fc4442fb5013.webp", + "type": "card", + "url": "https://mastodon.social/@here_is_wmbk" + }, + "content": null, + "published": null, + "received": "2026-03-11T09:13:52Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B48tihhI870jkamhKy", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B47Na8WhcQVAf7k6bI#favorited-by-B48tihhI870jkamhKy" + }, + { + "author": { + "name": "Eltrac :neocat_laptop:", + "photo": "https://avatars.webmention.io/akk-media.eltr.ac/96fdded4db077fd77188a2ca1427f357b0fda90f141c58c9bac3e1706c050c2a.jpg", + "type": "card", + "url": "https://akk.eltr.ac/users/eltrac" + }, + "content": { + "html": "\u003cp\u003e\u003ca href=\"http://webmention.io\"\u003ewebmention.io\u003c/a\u003e 这个接收端似乎有做 Pingback 的兼容,也可以接收 Pingback,不过这个标准是要比 Webmention 老一些的。不喜欢把社交媒体和博客同步起来的话,只要不解析 microformats,Webmention 本身的作用和 Pingback 也差不多。\u003c/p\u003e", + "text": "webmention.io 这个接收端似乎有做 Pingback 的兼容,也可以接收 Pingback,不过这个标准是要比 Webmention 老一些的。不喜欢把社交媒体和博客同步起来的话,只要不解析 microformats,Webmention 本身的作用和 Pingback 也差不多。" + }, + "published": "2026-03-11T00:29:18+00:00", + "received": "2026-03-11T09:14:16Z", + "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B48BcDG1zkgzcg814a", + "type": "in-reply-to", + "url": "https://akk.eltr.ac/notice/B48BcDG1zkgzcg814a" + }, + { + "author": { + "name": "J.sky", + "photo": "https://avatars.webmention.io/files.mastodon.social/4aa7e000ead9c2de54c6e4e1a83c0f3f371e8ad1994123a86067fdfbf8dc0655.jpg", + "type": "card", + "url": "https://mastodon.social/@J_sky" + }, + "content": null, + "published": null, + "received": "2026-03-11T13:34:14Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B38w5506lDT25A15KS", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B47Na8WhcQVAf7k6bI#favorited-by-B38w5506lDT25A15KS" + }, + { + "author": { + "name": "J.sky", + "photo": "https://avatars.webmention.io/files.mastodon.social/4aa7e000ead9c2de54c6e4e1a83c0f3f371e8ad1994123a86067fdfbf8dc0655.jpg", + "type": "card", + "url": "https://mastodon.social/@J_sky" + }, + "content": { + "html": "\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca href=\"https://akk.eltr.ac/users/eltrac\" class=\"u-url\"\u003e@\u003cspan\u003eeltrac\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e 文章分析的真是细致哇,收藏了,过几天试试看。\u003c/p\u003e", + "text": "@eltrac 文章分析的真是细致哇,收藏了,过几天试试看。" + }, + "published": "2026-03-11T13:27:14+00:00", + "received": "2026-03-11T13:34:14Z", + "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B47Na8WhcQVAf7k6bI/B49J2xyrnOIitllQ8G", + "type": "in-reply-to", + "url": "https://mastodon.social/@J_sky/116210770572306766" + } + ], "/categories/": [ { "author": { @@ -1183,31 +1457,31 @@ "/glossary/": [ { "author": { - "name": "活泼可爱小僵丝", - "photo": "https://darkwitch.net/system/accounts/avatars/111/296/789/771/189/022/original/11a5cec8541aa8aa.webp", + "name": "KipJayChou ⁂ :neocat_cofe:", + "photo": "https://avatars.webmention.io/files.mstdn.feddit.social/b3a5ebdc7764c92d0a28a39ee05451f98685bd5f10185dc981856443952aa13c.png", "type": "card", - "url": "https://darkwitch.net/@zombiegpt" + "url": "https://mstdn.feddit.social/@admin" }, "content": null, "published": null, "received": "2026-02-20T08:11:41Z", - "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3VQog0Hh9VD8du4dE/B2TgLIrInNMdO1VyPg", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3VQog0Hh9VD8du4dE/B2SrMy0eBj7rdlvD3A", "type": "like-of", - "url": "https://akk.eltr.ac/notice/B3VQog0Hh9VD8du4dE#favorited-by-B2TgLIrInNMdO1VyPg" + "url": "https://akk.eltr.ac/notice/B3VQog0Hh9VD8du4dE#favorited-by-B2SrMy0eBj7rdlvD3A" }, { "author": { - "name": "KipJayChou ⁂ :neocat_cofe:", - "photo": "https://avatars.webmention.io/files.mstdn.feddit.social/b3a5ebdc7764c92d0a28a39ee05451f98685bd5f10185dc981856443952aa13c.png", + "name": "活泼可爱小僵丝", + "photo": "https://darkwitch.net/system/accounts/avatars/111/296/789/771/189/022/original/11a5cec8541aa8aa.webp", "type": "card", - "url": "https://mstdn.feddit.social/@admin" + "url": "https://darkwitch.net/@zombiegpt" }, "content": null, "published": null, "received": "2026-02-20T08:11:41Z", - "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3VQog0Hh9VD8du4dE/B2SrMy0eBj7rdlvD3A", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3VQog0Hh9VD8du4dE/B2TgLIrInNMdO1VyPg", "type": "like-of", - "url": "https://akk.eltr.ac/notice/B3VQog0Hh9VD8du4dE#favorited-by-B2SrMy0eBj7rdlvD3A" + "url": "https://akk.eltr.ac/notice/B3VQog0Hh9VD8du4dE#favorited-by-B2TgLIrInNMdO1VyPg" }, { "author": { @@ -1657,6 +1931,22 @@ "url": "https://akk.eltr.ac/notice/B2c9L0YZlP8Kn83sOG#favorited-by-B2SrMh01QnWasuUyDQ" } ], + "/posts/site-report-3/#%E8%87%AA%E7%94%B1%E4%B8%8E%E4%BB%A3%E4%BB%B7": [ + { + "author": { + "name": "", + "photo": "", + "type": "card", + "url": "" + }, + "content": null, + "published": null, + "received": "2026-03-10T16:22:06Z", + "source": "https://leehenry.top/posts/words_in_wildness/ww-vol07/", + "type": "mention-of", + "url": "https://leehenry.top/posts/words_in_wildness/ww-vol07/" + } + ], "/posts/site-report-4/": [ { "author": { @@ -2520,12 +2810,15 @@ "type": "card", "url": "https://moon.lonewolf.zone/users/caohuak" }, - "content": null, - "published": null, + "content": { + "html": "\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca class=\"u-url\" href=\"https://akk.eltr.ac/users/eltrac\"\u003e@eltrac\u003c/a\u003e\u003c/span\u003e \u0026gt;顺带一提,Soatok 似乎也不太喜欢 Matrix 这个通信协议,他在 2024 年 8 月写过一篇 文章 解释 Matrix OLM 库的安全性问题;不过,时间已经过去一年多了,文章所述问题是否被解决,我就不太了解了。他比较认可的加密通信软件是 Signal。不过他也承认,对于那些不愿意使用手机号注册的非常在意隐私的技术用户而言,Signal 不太合适,也没有其他更好的选择。所以…… 还是继续用 Matrix 吧……\u003c/p\u003e\u003cp\u003e不只是 Matrix 还有 XMPP。\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://soatok.blog/2024/08/04/against-xmppomemo/\"\u003ehttps://soatok.blog/2024/08/04/against-xmppomemo/\u003c/a\u003e\u003c/p\u003e\n\u003ca href=\"https://soatok.blog/2024/08/04/against-xmppomemo/\"\u003eAgainst XMPP+OMEMO - Dhole Moments\u003c/a\u003e", + "text": "@eltrac \u003e顺带一提,Soatok 似乎也不太喜欢 Matrix 这个通信协议,他在 2024 年 8 月写过一篇 文章 解释 Matrix OLM 库的安全性问题;不过,时间已经过去一年多了,文章所述问题是否被解决,我就不太了解了。他比较认可的加密通信软件是 Signal。不过他也承认,对于那些不愿意使用手机号注册的非常在意隐私的技术用户而言,Signal 不太合适,也没有其他更好的选择。所以…… 还是继续用 Matrix 吧……\n\n不只是 Matrix 还有 XMPP。\n\nhttps://soatok.blog/2024/08/04/against-xmppomemo/\nAgainst XMPP+OMEMO - Dhole Moments" + }, + "published": "2026-02-15T16:48:42+00:00", "received": "2026-02-15T16:50:47Z", - "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3LoH9sYAzPq6lD1Rg/B2SrbzldvytzR84O3c", - "type": "like-of", - "url": "https://akk.eltr.ac/notice/B3LoH9sYAzPq6lD1Rg#favorited-by-B2SrbzldvytzR84O3c" + "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B3LoH9sYAzPq6lD1Rg/B3Lqh5d2ngX6MdRtsu", + "type": "in-reply-to", + "url": "https://moon.lonewolf.zone/objects/019c6234-5347-598d-534e-0724a2e91e4f" }, { "author": { @@ -2534,15 +2827,12 @@ "type": "card", "url": "https://moon.lonewolf.zone/users/caohuak" }, - "content": { - "html": "\u003cp\u003e\u003cspan class=\"h-card\"\u003e\u003ca class=\"u-url\" href=\"https://akk.eltr.ac/users/eltrac\"\u003e@eltrac\u003c/a\u003e\u003c/span\u003e \u0026gt;顺带一提,Soatok 似乎也不太喜欢 Matrix 这个通信协议,他在 2024 年 8 月写过一篇 文章 解释 Matrix OLM 库的安全性问题;不过,时间已经过去一年多了,文章所述问题是否被解决,我就不太了解了。他比较认可的加密通信软件是 Signal。不过他也承认,对于那些不愿意使用手机号注册的非常在意隐私的技术用户而言,Signal 不太合适,也没有其他更好的选择。所以…… 还是继续用 Matrix 吧……\u003c/p\u003e\u003cp\u003e不只是 Matrix 还有 XMPP。\u003c/p\u003e\u003cp\u003e\u003ca href=\"https://soatok.blog/2024/08/04/against-xmppomemo/\"\u003ehttps://soatok.blog/2024/08/04/against-xmppomemo/\u003c/a\u003e\u003c/p\u003e\n\u003ca href=\"https://soatok.blog/2024/08/04/against-xmppomemo/\"\u003eAgainst XMPP+OMEMO - Dhole Moments\u003c/a\u003e", - "text": "@eltrac \u003e顺带一提,Soatok 似乎也不太喜欢 Matrix 这个通信协议,他在 2024 年 8 月写过一篇 文章 解释 Matrix OLM 库的安全性问题;不过,时间已经过去一年多了,文章所述问题是否被解决,我就不太了解了。他比较认可的加密通信软件是 Signal。不过他也承认,对于那些不愿意使用手机号注册的非常在意隐私的技术用户而言,Signal 不太合适,也没有其他更好的选择。所以…… 还是继续用 Matrix 吧……\n\n不只是 Matrix 还有 XMPP。\n\nhttps://soatok.blog/2024/08/04/against-xmppomemo/\nAgainst XMPP+OMEMO - Dhole Moments" - }, - "published": "2026-02-15T16:48:42+00:00", + "content": null, + "published": null, "received": "2026-02-15T16:50:47Z", - "source": "https://brid.gy/comment/mastodon/@eltrac@akk.eltr.ac/B3LoH9sYAzPq6lD1Rg/B3Lqh5d2ngX6MdRtsu", - "type": "in-reply-to", - "url": "https://moon.lonewolf.zone/objects/019c6234-5347-598d-534e-0724a2e91e4f" + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3LoH9sYAzPq6lD1Rg/B2SrbzldvytzR84O3c", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B3LoH9sYAzPq6lD1Rg#favorited-by-B2SrbzldvytzR84O3c" }, { "author": { @@ -2580,20 +2870,6 @@ }, { "author": { - "name": "五葉地錦", - "photo": "https://avatars.webmention.io/media.glowisle.me/0c3c50d2c46f0b07829eb0a956be90022ae1bbf46e1163fc65eb70ca86ca40a5.jpg", - "type": "card", - "url": "https://akk.glowisle.me/users/verdant" - }, - "content": null, - "published": null, - "received": "2026-02-15T17:36:18Z", - "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3LoH9sYAzPq6lD1Rg/B35EVEQHNBwHNO2Q3E", - "type": "like-of", - "url": "https://akk.eltr.ac/notice/B3LoH9sYAzPq6lD1Rg#favorited-by-B35EVEQHNBwHNO2Q3E" - }, - { - "author": { "name": "KipJayChou ⁂", "photo": "https://avatars.webmention.io/files.mstdn.feddit.social/b3a5ebdc7764c92d0a28a39ee05451f98685bd5f10185dc981856443952aa13c.png", "type": "card", @@ -2611,6 +2887,20 @@ }, { "author": { + "name": "五葉地錦", + "photo": "https://avatars.webmention.io/media.glowisle.me/0c3c50d2c46f0b07829eb0a956be90022ae1bbf46e1163fc65eb70ca86ca40a5.jpg", + "type": "card", + "url": "https://akk.glowisle.me/users/verdant" + }, + "content": null, + "published": null, + "received": "2026-02-15T17:36:18Z", + "source": "https://brid.gy/like/mastodon/@eltrac@akk.eltr.ac/B3LoH9sYAzPq6lD1Rg/B35EVEQHNBwHNO2Q3E", + "type": "like-of", + "url": "https://akk.eltr.ac/notice/B3LoH9sYAzPq6lD1Rg#favorited-by-B35EVEQHNBwHNO2Q3E" + }, + { + "author": { "name": "Cytrogen", "photo": "https://avatars.webmention.io/media.otter.homes/310ced382f23dbe7131a3693f6c23b83c4c4a8ff66b84ce6494cc51720597349.jpg", "type": "card", @@ -2956,6 +3246,22 @@ "url": "https://c7.io/@eltrac/115869473517221128#favorited-by-115581408620825334" } ], + "/uses/#%E5%AD%98%E5%82%A8": [ + { + "author": { + "name": "", + "photo": "", + "type": "card", + "url": "" + }, + "content": null, + "published": null, + "received": "2026-03-10T16:22:05Z", + "source": "https://leehenry.top/posts/words_in_wildness/ww-vol07/", + "type": "mention-of", + "url": "https://leehenry.top/posts/words_in_wildness/ww-vol07/" + } + ], "/weekly/71/": [ { "author": { diff --git a/layouts/_partials/components/microformats.html b/layouts/_partials/components/microformats.html index cc6bc0bc..8cd64f50 100644 --- a/layouts/_partials/components/microformats.html +++ b/layouts/_partials/components/microformats.html @@ -5,3 +5,5 @@ >{{ .Site.Params.author.name }}</a > </div> + +<a href="{{ .Permalink }}" class="u-url hidden"></a> diff --git a/layouts/_shortcodes/fedicard.html b/layouts/_shortcodes/fedicard.html new file mode 100644 index 00000000..ab24542b --- /dev/null +++ b/layouts/_shortcodes/fedicard.html @@ -0,0 +1,148 @@ +{{- $url := .Get 0 -}} +{{- $parsed := urls.Parse $url -}} +{{- $parts := split $parsed.Path "/" -}} +{{- $id := index $parts (sub (len $parts) 1) -}} +{{- $instance := printf "%s://%s" $parsed.Scheme $parsed.Host -}} +{{- $apiUrl := printf "%s/api/v1/statuses/%s" $instance $id -}} +{{- $data := resources.GetRemote $apiUrl -}} +{{- $json := $data | transform.Unmarshal -}} +{{- $author := $json.account -}} + +{{- $publishedAt := time $json.created_at -}} + +{{- $avatarSrc := $author.avatar -}} +{{- with resources.GetRemote $author.avatar -}} + {{- with .Resize "88x88 q85 webp" -}} + {{- $avatarSrc = .RelPermalink -}} + {{- end -}} +{{- end -}} + +{{- /* Count image attachments */ -}} +{{- $imgCount := 0 -}} +{{- range $json.media_attachments -}}{{- if eq .type "image" -}}{{- $imgCount = add $imgCount 1 -}}{{- end -}}{{- end -}} +{{- $cId := printf "sc-%s" (md5 $url) -}} +{{- $dId := printf "sd-%s" (md5 $url) -}} + +<div class="social-card-final cnt-undo rd bg-neutral-50 p-4 md:px-8 md:py-6 shadow" style="position: relative; overflow: hidden;"> + <div style="display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 8px;"> + <div style="display: flex; align-items: center; gap: 12px;"> + <img src="{{ $avatarSrc }}" style="width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0; object-fit: cover;" /> + <div style="display: flex; flex-direction: column; line-height: 1.2;"> + <span style="font-weight: 700; color: var(--color-neso-fg1); font-size: 1rem;">{{ $author.display_name }}</span> + <span style="color: var(--color-neso-fg3); font-size: 0.9rem;">@{{ $author.acct }}</span> + </div> + </div> + <a href="{{ $url }}" target="_blank" style="color: #6364ff; align-self: flex-start; margin-top: 2px; transition: transform 0.2s ease-in-out; display: flex; flex-shrink: 0;" onmouseover="this.style.transform='scale(1.2)'" onmouseout="this.style.transform='scale(1)'"> +<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 196.52 196.52"> + <path fill="#a730b8" d="M47.9242 72.7966a18.2278 18.2278 0 0 1-7.7959 7.7597l42.7984 42.9653 10.3182-5.2291zm56.4524 56.6704-10.3182 5.2291 21.686 21.7708a18.2278 18.2278 0 0 1 7.7975-7.7608z"/> + <path fill="#5496be" d="M129.6645 102.0765l1.7865 11.4272 27.4149-13.8942a18.2278 18.2278 0 0 1-4.9719-9.8124zm-14.0658 7.1282-57.2891 29.0339a18.2278 18.2278 0 0 1 4.9728 9.8133l54.1027-27.4194z"/> + <path fill="#ce3d1a" d="M69.5312 91.6539l8.1618 8.1933 29.269-57.1387a18.2278 18.2278 0 0 1-9.787-5.0219zm-7.1897 14.0363-14.0022 27.3353a18.2278 18.2278 0 0 1 9.786 5.0214l12.3775-24.1639z"/> + <path fill="#d0188f" d="M39.8906 80.6763a18.2278 18.2278 0 0 1-10.8655 1.7198l8.1762 52.2981a18.2278 18.2278 0 0 1 10.8645-1.7198z"/> + <path fill="#5b36e9" d="M63.3259 148.3109a18.2278 18.2278 0 0 1-1.7322 10.8629l52.2893 8.3907a18.2278 18.2278 0 0 1 1.7322-10.8629z"/> + <path fill="#30b873" d="M134.9148 146.9182a18.2278 18.2278 0 0 1 9.788 5.0224l24.1345-47.117a18.2278 18.2278 0 0 1-9.7875-5.0229z"/> + <path fill="#ebe305" d="M126.1329 33.1603a18.2278 18.2278 0 0 1-7.7975 7.7608l37.3765 37.5207a18.2278 18.2278 0 0 1 7.7969-7.7608z"/> + <path fill="#f47601" d="M44.7704 51.6279a18.2278 18.2278 0 0 1 4.9723 9.8123l47.2478-23.9453a18.2278 18.2278 0 0 1-4.9718-9.8113z"/> + <path fill="#57c115" d="M118.2491 40.9645a18.2278 18.2278 0 0 1-10.8511 1.8123l4.1853 26.8 11.42 1.8324zm-4.2333 44.1927 9.8955 63.3631a18.2278 18.2278 0 0 1 10.88-1.6278l-9.355-59.9035z"/> + <path fill="#dbb210" d="M49.7763 61.6412a18.2278 18.2278 0 0 1-1.694 10.8686l26.8206 4.3077 5.2715-10.2945zm45.9677 7.382-5.272 10.2955 63.3713 10.1777a18.2278 18.2278 0 0 1 1.7606-10.8593z"/> + <path fill="#ffca00" d="M93.4385 23.8419a1 1 0 1 0 33.0924 1.8025 1 1 0 1 0-33.0924-1.8025"/> + <path fill="#64ff00" d="M155.314 85.957a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/> + <path fill="#00a3ff" d="M115.3466 163.9824a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/> + <path fill="#9500ff" d="M28.7698 150.0898a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/> + <path fill="#ff0000" d="M15.2298 63.4781a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/> +</svg> + </a> + </div> + + {{- $bodyText := $json.content -}} + {{- $bodyText = $bodyText | replaceRE `(<p>[\s]*(<a [^>]*class="mention hashtag"[^>]*>#<span>[^<]+</span></a>[\s]*)+</p>[\s]*)+$` "" -}} + {{- $bodyText = $bodyText | replaceRE `<span class="invisible">[^<]*</span>` "" -}} + {{- $bodyText = $bodyText | replaceRE `<a ` `<a style="text-decoration:none;" ` -}} + {{- $bodyText = $bodyText | replaceRE `<p>` `<p style="margin:0 0 0.9em 0;">` -}} + {{- $bodyText = $bodyText | replaceRE `<p [^>]+>[\s]*</p>` "" -}} + + <div style="font-size: 1.05rem; line-height: 1.6; margin-bottom: 12px; color: var(--color-neso-fg1);"> + {{ $bodyText | safeHTML }} + </div> + + {{- if gt $imgCount 0 -}} + {{- if le $imgCount 2 -}} + {{- /* 1–2 images: flex row (2 images split 50/50, cropped) */ -}} + <div style="display: flex; gap: 8px; margin-top: 8px; margin-bottom: 12px; overflow: hidden;"> + {{- range $json.media_attachments -}} + {{- if eq .type "image" -}} + {{- $imgSrc := .preview_url -}} + {{- with resources.GetRemote .preview_url -}} + {{- with .Process "q85 webp" -}} + {{- $imgSrc = .RelPermalink -}} + {{- end -}} + {{- end -}} + <img src="{{ $imgSrc }}" alt="{{ .description }}" onclick="window.__scLb&&window.__scLb(this.src)" style="flex: 1 1 0%; min-width: 0; border-radius: 8px; display: block; margin: 0 !important; cursor: pointer;{{ if gt $imgCount 1 }} height: 250px; object-fit: cover;{{ else }} height: auto; width: 100%;{{ end }}" loading="lazy"> + {{- end -}} + {{- end -}} + </div> + {{- else -}} + {{- /* 3+ images: scroll-snap carousel */ -}} + <style>#{{ $cId }}::-webkit-scrollbar{display:none}</style> + <div style="margin: 8px 0 4px; overflow: hidden;"> + <div id="{{ $cId }}" style="display: flex; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; border-radius: 8px;"> + {{- range $json.media_attachments -}} + {{- if eq .type "image" -}} + {{- $imgSrc := .preview_url -}} + {{- with resources.GetRemote .preview_url -}} + {{- with .Process "q85 webp" -}} + {{- $imgSrc = .RelPermalink -}} + {{- end -}} + {{- end -}} + <img src="{{ $imgSrc }}" alt="{{ .description }}" onclick="window.__scLb&&window.__scLb(this.src)" style="scroll-snap-align: start; flex-shrink: 0; width: 100%; height: 280px; object-fit: cover; border-radius: 8px; cursor: pointer; display: block; margin: 0 !important;" loading="lazy"> + {{- end -}} + {{- end -}} + </div> + {{- $imgAttachments := slice -}} + {{- range $json.media_attachments -}}{{- if eq .type "image" -}}{{- $imgAttachments = $imgAttachments | append . -}}{{- end -}}{{- end -}} + <div id="{{ $dId }}" style="display: flex; justify-content: center; gap: 6px; margin-top: 8px; margin-bottom: 12px;"> + {{- range $i, $_ := $imgAttachments -}} + <button onclick="(function(){var c=document.getElementById('{{ $cId }}');c.scrollTo({left:c.children[{{ $i }}].offsetLeft,behavior:'smooth'});})()" aria-label="Image {{ add $i 1 }}" style="width: 8px; height: 8px; border-radius: 50%; border: none; background: {{ if eq $i 0 }}var(--color-neso-fg2){{ else }}var(--color-neso-deco2){{ end }}; cursor: pointer; padding: 0; transition: background 0.2s;"></button> + {{- end -}} + </div> + </div> + <script>(function(){var c=document.getElementById('{{ $cId }}'),ds=document.getElementById('{{ $dId }}').querySelectorAll('button');c.addEventListener('scroll',function(){var i=Math.round(c.scrollLeft/c.offsetWidth);ds.forEach(function(d,j){d.style.background=j===i?'var(--color-neso-fg2)':'var(--color-neso-deco2)';});},{passive:true});})()</script> + {{- end -}} + {{- end -}} + + {{- /* Link preview card — use original image URL, skip processing (CDN images can return 403) */ -}} + {{- with $json.card -}} + {{- if or (eq .type "link") (eq .type "photo") (eq .type "video") -}} + <a href="{{ .url }}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 8px; margin-bottom: 16px; border: 1px solid var(--color-neso-deco2); border-radius: 10px; overflow: hidden; text-decoration: none; color: inherit;"> + {{- if .image -}} + <img src="{{ .image }}" alt="{{ .title }}" style="width: 100%; height: 180px; object-fit: cover; display: block; margin: 0 !important;"> + {{- end -}} + <div style="padding: 10px 14px;"> + <div style="font-weight: 600; font-size: 0.95rem; color: var(--color-neso-fg1); margin-bottom: 3px;">{{ .title }}</div> + {{- if .description -}}<div style="font-size: 0.85rem; color: var(--color-neso-fg3); margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;">{{ .description }}</div>{{- end -}} + <div style="font-size: 0.8rem; color: var(--color-neso-fg3);">{{ (urls.Parse .url).Host }}</div> + </div> + </a> + {{- end -}} + {{- end -}} + + <div style="border-top: 1px solid var(--color-neso-deco3); padding-top: 12px; margin-top: 16px; display: flex; justify-content: space-between; align-items: center;"> + <time class="social-card-time" datetime="{{ $publishedAt.Format "2006-01-02T15:04:05Z" }}" style="color: var(--color-neso-fg3); font-size: 0.9rem;">{{ $publishedAt.Format "Jan 2, 2006 at 3:04 PM UTC" }}</time> + <a href="{{ $url }}" target="_blank" style="color: #6364ff; font-weight: 600; text-decoration: none; font-size: 0.9rem; transition: color 0.2s ease-in-out;" onmouseover="this.style.color='#8A8BFF'" onmouseout="this.style.color='#6364ff'">View on Fediverse</a> + </div> +</div> + +{{- /* Shared lightbox — injected once per page via DOM API (no innerHTML) */ -}} +<script> +if (!window.__scLb) { + var _lbImg = document.createElement('img'); + _lbImg.style.cssText = 'max-width:92vw;max-height:92vh;object-fit:contain;border-radius:8px;cursor:default;'; + _lbImg.addEventListener('click', function(e) { e.stopPropagation(); }); + var _lbOv = document.createElement('div'); + _lbOv.style.cssText = 'display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.88);cursor:zoom-out;align-items:center;justify-content:center;'; + _lbOv.appendChild(_lbImg); + _lbOv.addEventListener('click', function() { _lbOv.style.display = 'none'; document.body.style.overflow = ''; }); + document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { _lbOv.style.display = 'none'; document.body.style.overflow = ''; } }); + document.body.appendChild(_lbOv); + window.__scLb = function(src) { _lbImg.src = src; _lbOv.style.display = 'flex'; document.body.style.overflow = 'hidden'; }; +} +</script> diff --git a/layouts/_shortcodes/music.html b/layouts/_shortcodes/music.html index 354d2c59..45230a48 100644 --- a/layouts/_shortcodes/music.html +++ b/layouts/_shortcodes/music.html @@ -17,7 +17,7 @@ <a href="{{ . }}" class="{{ $aTagClasses }}" target="_blank" - title="{{ T "music.openIn" "Spotify" }}" + title="{{ T "music.openIn" "Apple Music" }}" alt="Apple Music" data-tooltip > |
