diff options
| author | yingyu5658 <i@yingyu5658.me> | 2025-12-14 11:10:34 +0800 |
|---|---|---|
| committer | yingyu5658 <i@yingyu5658.me> | 2025-12-14 11:10:34 +0800 |
| commit | 259f5ac57a14569e24682f905c917a2c017ef283 (patch) | |
| tree | 7a486578c9a0d2e2223be20e5ba9a21b69cf6a71 | |
| parent | 58b6e1433fd80393c8f66fe4bf6f50eee095acb3 (diff) | |
| download | blog-259f5ac57a14569e24682f905c917a2c017ef283.tar.gz blog-259f5ac57a14569e24682f905c917a2c017ef283.zip | |
update: about.md category 404.html style.html
- Remove "这个博客是如何搭建的" in about.md
- Add a category "回声周刊"
- Modify text in 404.html
style.css:
- Modify the font-weight about <a>
- Remove .site-name
- Add music-card
| -rw-r--r-- | content/about.md | 31 | ||||
| -rw-r--r-- | content/categories.md | 9 | ||||
| -rw-r--r-- | content/readings.md | 2 | ||||
| -rw-r--r-- | layouts/404.html | 2 | ||||
| -rw-r--r-- | layouts/partials/style.html | 180 | ||||
| -rw-r--r-- | static/js/webmention.js | 649 |
6 files changed, 512 insertions, 361 deletions
diff --git a/content/about.md b/content/about.md index f5214f8..92d9863 100644 --- a/content/about.md +++ b/content/about.md @@ -33,27 +33,30 @@ type: no-comments - 所有文章内容使用[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)协议授权。 - 目前文章字数:{{< word-count "posts" >}} -## 这个博客是如何搭建的 +## 订阅 -本博客使用快速、易用的静态博客生成程序 [Hugo](https://gohugo.io/),在 [hugo-bearneo](https://github.com/rokcso/hugo-bearneo) 主题的基础上进行修改,静态文件托管在Github Pages,图床使用Cloudflare R2对象存储。我使用 Emacs 撰写文章,并推送到远程仓库构建部署。 +如果你喜欢我的文字,可以用以下链接订阅: -## 在哪找到我 +- [订阅全部文章](https://www.glowisle.me/atom.xml/) -- Email: [i@glowisle.me](mailto:i@glowisle.me/) -- 即时通讯: [Verdant](https://t.me/VerdantIO/)(Telegram) -- Mastodon: [c7.io/@Verdant](https://c7.io/@Verdant/) -- Github: [Verdant](https://github.com/yingyu5658/) +- [订阅周刊](https://www.glowisle.me/categories/回声周刊/atom.xml) -## 订阅 +- [订阅Telegram频道的消息](https://channel.glowisle.me/rss.xml/) -如果你喜欢我的文字,可以用以下链接订阅: +订阅某个分类: ``` -https://www.glowisle.me/atom.xml/ +https://www.glowisle.me/categories/分类名/atom.xml ``` -订阅Telegram频道的消息: +## 在哪找到我 -``` -https://channel.glowisle.me/rss.xml/ -``` +- Email: [i@glowisle.me](mailto:i@glowisle.me/) +- 即时通讯: [Verdant](https://t.me/VerdantIO/)(Telegram) +- Mastodon: [c7.io/@Verdant](https://c7.io/@Verdant/) +- Github: [Verdant](https://github.com/yingyu5658/) + +## 鸣谢 + +- 本站样式基于 [Hugo Bear Neo](https://github.com/rokcso/hugo-bearneo/) 主题修改 +- 音乐卡片的修改自 [極客死亡計劃](https://github.com/BigCoke233/geek-death-project) diff --git a/content/categories.md b/content/categories.md index cdb715b..f8875ad 100644 --- a/content/categories.md +++ b/content/categories.md @@ -20,8 +20,15 @@ title: 分类 技术实践 | 技术观点 +### [📖 回声周刊](/categories/回声周刊) + +每周优质阅读分享 | 近期发生的事 + --- +<!-- + ##### [~~🧻 往昔~~](/categories/往昔) -###### ~~早期不成熟的文章合集 | 包含偏激观点 | 冒犯性用语 | 不建议阅读~~ +###### ~~早期不成熟的文章合集 | 包含偏激观点 | 冒犯性用语 | 不建议阅读~~ +--> diff --git a/content/readings.md b/content/readings.md index 18b710f..6710650 100644 --- a/content/readings.md +++ b/content/readings.md @@ -3,7 +3,7 @@ date: "2025-06-07T13:08:07+08:00" title: 读书 type: no-comments --- -这里记录着我读过的书,有时候写写书评或读后感。 +这里记录着我读过的书,有时候写写书评或读后感,你可以在[读书](/categories/读书/)页面查看所有与书籍有关的文章。 - 「⭐」表示我很喜欢的书籍 - 「⚪」表示我觉得还可以的书籍 diff --git a/layouts/404.html b/layouts/404.html index 15198aa..8484782 100644 --- a/layouts/404.html +++ b/layouts/404.html @@ -1,4 +1,4 @@ {{ define "title" }}404{{ end }} {{ define "main" }} <h1>404</h1> -<h2>ʕノ•ᴥ•ʔノ ︵ ┻━┻</h2> +<h2>你似乎来到了无人的岛屿。</h2> {{ end }} diff --git a/layouts/partials/style.html b/layouts/partials/style.html index f108da6..8828dbd 100644 --- a/layouts/partials/style.html +++ b/layouts/partials/style.html @@ -4,7 +4,7 @@ --width-max: 720px; --font-primary: "Noto Serif SC", "Source Han Serif SC", serif; --font-secondary: monospace; - --font-size-primary: 1em; + --font-size-primary: 1.105em; --font-size-secondary: 0.8em; --body-bg-color: #ffffff; --bold-text-color: #222; @@ -22,6 +22,18 @@ --caption-text-color: #666; --toc-text-color: #e5e5e5; --toc-hover-color: #655e5e; + + --music-bg-hover-light: #e5e5e5; /* neutral-200 */ + --music-bg-light: rgba(229, 229, 229, 0.5); /* neutral-200/50 */ + --music-text-secondary: #666; + --music-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + } + + .post-title a:hover, + a.item-link:hover, + .blog-posts a:hover { + text-decoration: underline 0.5px !important; } .wm { @@ -37,6 +49,10 @@ margin-top: 1px; } + article p { + line-height: 40px; + } + .item-link { white-space: nowrap; overflow: hidden; @@ -83,18 +99,147 @@ content: "/"; } - .site-name:hover { - background-color: transparent; - text-decoration: none; - color: #fff; - background-color: #7e4fa0; + .music-card img { + border: none !important; + } + + @media (prefers-color-scheme: dark) { + :root { + --music-bg-hover-dark: #525252; /* neutral-600 */ + --music-bg-dark: rgba(82, 82, 82, 0.5); /* neutral-600/50 */ + --music-text-secondary: #a3a3a3; + } + } + + /* THIS SHIT MADE FROM LLM */ + + /* --- 主容器 --- */ + /* flex flex-col gap-2 */ + .music-card { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + } + + /* --- 信息区域 (左侧) --- */ + /* flex gap-5 items-center */ + .music-info { + display: flex; + align-items: center; + gap: 1.25rem; + } + + /* 封面图 */ + /* w-15 h-15 rounded-lg shadow-lg m-0! */ + .music-cover { + width: 3.75rem; /* 15 * 0.25rem */ + height: 3.75rem; + border-radius: 0.5rem; + box-shadow: var(--music-shadow); + margin: 0 !important; + object-fit: cover; + } + + /* 文本信息 */ + .music-title { + margin: 0; + font-size: 1rem; /* text-size-base */ + font-weight: bold; + line-height: 1.2; + } + + .music-artist { + margin: 0; + font-size: 0.875rem; /* text-size-sm */ + color: var(--music-text-secondary); + } + + /* --- 链接区域 (右侧) --- */ + /* flex gap-2 items-center */ + .music-links { + display: flex; + align-items: center; + gap: 0.5rem; + } + + /* 链接按钮 */ + /* block rounded-full px-3 transition-colors no-underline! text-size-xs inline-flex gap-1 items-center bg... */ + .music-btn { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0 0.75rem; /* px-3 */ + height: 2rem; + border-radius: 9999px; /* rounded-full */ + text-decoration: none !important; + font-size: 0.75rem; /* text-size-xs */ + transition: background-color 0.3s ease; + + /* 浅色模式背景 */ + background-color: var(--music-bg-light); + color: inherit; + } + + .music-btn:hover { + background-color: var(--music-bg-hover-light); } - .site-name { - transition: 0.3s; - width: 66px; - border-radius: 5px; - text-transform: uppercase; + /* 图标 */ + /* w-3 h-3 m-0! */ + .music-icon { + display: block; + width: 0.75rem; + height: 0.75rem; + margin: 0 !important; + } + + /* 按钮文字 */ + .music-btn-text { + display: inline; + } + + /* --- 深色模式覆盖 --- */ + @media (prefers-color-scheme: dark) { + .music-btn { + background-color: var(--music-bg-dark); + } + .music-btn:hover { + background-color: var(--music-bg-hover-dark); + } + } + + /* --- 桌面端/大屏适配 (Media Query: md / min-width: 768px) --- */ + @media (min-width: 768px) { + /* 容器变为横向排列,两端对齐 */ + /* md:flex-row md:justify-between md:items-center */ + .music-card { + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + /* 按钮在大屏下变为方形图标按钮 */ + /* md:p-4 */ + .music-btn { + padding: 1rem; + justify-content: center; + width: auto; + height: auto; + } + + /* 图标变大 */ + /* md:w-5 md:h-5 */ + .music-icon { + width: 1.25rem; + height: 1.25rem; + } + + /* 文字在大屏下隐藏 */ + /* md:hidden */ + .music-btn-text { + display: none; + } } @media (prefers-color-scheme: dark) { @@ -142,7 +287,8 @@ border: #30363d 1px solid; } - .post-title a { + .post-title a, + .blog-posts a { color: #fff !important; } @@ -166,11 +312,7 @@ } a { - font-weight: 700 !important; - } - - a:hover { - color: #ddd !important; + font-weight: 500 !important; } } @@ -216,10 +358,6 @@ font-weight: 600; } - a:hover { - color: #3273dc; - } - .posts-list li { margin-bottom: 12px; } diff --git a/static/js/webmention.js b/static/js/webmention.js index dc9bc5c..d8cada8 100644 --- a/static/js/webmention.js +++ b/static/js/webmention.js @@ -98,124 +98,128 @@ A more detailed example: // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt (function () { - "use strict"; - - // Shim i18next - window.i18next = window.i18next || { - t: function t(/** @type {string} */key) { return key; } - } - const t = window.i18next.t.bind(window.i18next); - - /** - * Read the configuration value. - * - * @param {string} key The configuration key. - * @param {string} dfl The default value. - * @returns {string} - */ - function getCfg(key, dfl) { - return document.currentScript.getAttribute("data-" + key) || dfl; - } - - const refurl = getCfg("page-url", window.location.href.replace(/#.*$/, "")); - const addurls = getCfg("add-urls", undefined); - const containerID = getCfg("id", "webmentions"); - /** @type {Number} */ - const textMaxWords = getCfg("wordcount"); - const maxWebmentions = getCfg("max-webmentions", 30); - const mentionSource = getCfg("prevent-spoofing") ? "wm-source" : "url"; - const sortBy = getCfg("sort-by", "published"); - const sortDir = getCfg("sort-dir", "up"); - /** @type {boolean} */ - const commentsAreReactions = getCfg("comments-are-reactions", false); - - /** - * @typedef MentionType - * @type {"in-reply-to"|"like-of"|"repost-of"|"bookmark-of"|"mention-of"|"rsvp"|"follow-of"} - */ - - /** - * Maps a reaction to a hover title. - * - * @type {Record<MentionType, string>} - */ - const reactTitle = { - "in-reply-to": t("replied"), - "like-of": t("liked"), - "repost-of": t("reposted"), - "bookmark-of": t("bookmarked"), - "mention-of": t("mentioned"), - "rsvp": t("RSVPed"), - "follow-of": t("followed") - }; - - /** - * Maps a reaction to an emoji. - * - * @type {Record<MentionType, string>} - */ - const reactEmoji = { - "in-reply-to": "💬", - "like-of": "❤️", - "repost-of": "🔄", - "bookmark-of": "⭐️", - "mention-of": "💬", - "rsvp": "📅", - "follow-of": "🐜" - }; - - /** - * @typedef RSVPEmoji - * @type {"yes"|"no"|"interested"|"maybe"|null} - */ - - /** - * Maps a RSVP to an emoji. - * - * @type {Record<RSVPEmoji, string>} - */ - const rsvpEmoji = { - "yes": "✅", - "no": "❌", - "interested": "💡", - "maybe": "💭" - }; - - /** - * HTML escapes the string. - * - * @param {string} text The string to be escaped. - * @returns {string} - */ - function entities(text) { - return text.replace(/[&<>"]/g, (tag) => ({ - '&': '&', - '<': '<', - '>': '>', - '"': '"', - }[tag] || tag)); - } - - /** - * Creates the markup for an reaction image. - * - * @param {Reaction} r - * @param {boolean} isComment - * @returns {string} - */ - function reactImage(r, isComment) { - const who = entities( - r.author?.name || r.url.split("/")[2] - ); - /** @type {string} */ - let response = reactTitle[r["wm-property"]] || t("reacted"); - if (!isComment && r.content && r.content.text) { - response += ": " + extractComment(r); + "use strict"; + + // Shim i18next + window.i18next = window.i18next || { + t: function t(/** @type {string} */ key) { + return key; + }, + }; + const t = window.i18next.t.bind(window.i18next); + + /** + * Read the configuration value. + * + * @param {string} key The configuration key. + * @param {string} dfl The default value. + * @returns {string} + */ + function getCfg(key, dfl) { + return document.currentScript.getAttribute("data-" + key) || dfl; + } + + const refurl = getCfg("page-url", window.location.href.replace(/#.*$/, "")); + const addurls = getCfg("add-urls", undefined); + const containerID = getCfg("id", "webmentions"); + /** @type {Number} */ + const textMaxWords = getCfg("wordcount"); + const maxWebmentions = getCfg("max-webmentions", 30); + const mentionSource = getCfg("prevent-spoofing") ? "wm-source" : "url"; + const sortBy = getCfg("sort-by", "published"); + const sortDir = getCfg("sort-dir", "up"); + /** @type {boolean} */ + const commentsAreReactions = getCfg("comments-are-reactions", false); + + /** + * @typedef MentionType + * @type {"in-reply-to"|"like-of"|"repost-of"|"bookmark-of"|"mention-of"|"rsvp"|"follow-of"} + */ + + /** + * Maps a reaction to a hover title. + * + * @type {Record<MentionType, string>} + */ + const reactTitle = { + "in-reply-to": t("replied"), + "like-of": t("liked"), + "repost-of": t("reposted"), + "bookmark-of": t("bookmarked"), + "mention-of": t("mentioned"), + rsvp: t("RSVPed"), + "follow-of": t("followed"), + }; + + /** + * Maps a reaction to an emoji. + * + * @type {Record<MentionType, string>} + */ + const reactEmoji = { + "in-reply-to": "💬", + "like-of": "❤️", + "repost-of": "🔄", + "bookmark-of": "⭐️", + "mention-of": "💬", + rsvp: "📅", + "follow-of": "🐜", + }; + + /** + * @typedef RSVPEmoji + * @type {"yes"|"no"|"interested"|"maybe"|null} + */ + + /** + * Maps a RSVP to an emoji. + * + * @type {Record<RSVPEmoji, string>} + */ + const rsvpEmoji = { + yes: "✅", + no: "❌", + interested: "💡", + maybe: "💭", + }; + + /** + * HTML escapes the string. + * + * @param {string} text The string to be escaped. + * @returns {string} + */ + function entities(text) { + return text.replace( + /[&<>"]/g, + (tag) => + ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + })[tag] || tag, + ); } - let authorPhoto = ''; - if (r.author && r.author.photo) { - authorPhoto = ` + /** + * Creates the markup for an reaction image. + * + * @param {Reaction} r + * @param {boolean} isComment + * @returns {string} + */ + function reactImage(r, isComment) { + const who = entities(r.author?.name || r.url.split("/")[2]); + /** @type {string} */ + let response = reactTitle[r["wm-property"]] || t("reacted"); + if (!isComment && r.content && r.content.text) { + response += ": " + extractComment(r); + } + + let authorPhoto = ""; + if (r.author && r.author.photo) { + authorPhoto = ` <img src="${entities(r.author.photo)}" loading="lazy" @@ -223,22 +227,22 @@ A more detailed example: alt="${who}" > `; - } else { - authorPhoto = ` + } else { + authorPhoto = ` <img class="missing" src="data:image/webp;base64,UklGRkoCAABXRUJQVlA4TD4CAAAvP8APAIV0WduUOLr/m/iqY6SokDJSMD5xYX23SQizRsVdZmIj/f6goYUbiOj/BED7MOPReuBNT3vBesSzIex+SeqMFFkjebFmzH3S7POxDSJ1yaCbCmMnS2R46cRMPyQLw4GBK4esdK60pYwsZakecUCl5zsHv/5cPH08nx9/7i6rEEVCg2hR8VSd30PxMZpVoJZQO6Dixgg6X5oKFCmlVHIDmmMFShWumAXgCuyqVN8hHff/k+9fj8+ei7BVjpxBmZCUJv+6DhWGZwWvs+UoLHFCKsPYpfJtIcEXBTopEEsKwedZUv4ku1FZErKULLyQwFGgnmTs2vBD5qu44xwnG9uyjgrFOd+KRVlXyQfwQlauydaU6AVI7OjKXLUEqNtxJBmQegNDZgV7lxxqYMOMrDyC1NdAGbdiH9Ij0skjG+oTyfO0lmjdgvoH8iIgreuBMRYLSH+R3sAztXgL+XfS7E2bmfo6gnS0TrpnzHT7kL+skj7PgHuBwv/zpN8LDLQg7zfJZLBubMKnyeh6ZGyfDEfc2LYpnlUtG7JqsSHq1WoASbUS4KVaLwB8be5mfsGMDwBcm5VxbuxWxx3nkFanB6lYqsqSkOGkKicoDvXsneR7BkKU7DtaEuT7+pxBGVwx+9gVyqf2pVA9sC2CsmjZ1RJqEJHS4Tj/pCcS0JoyBYOsB91Xjh3OFfQPQhvCAYyeLJlaOoFp0XNNuD0BC8exr8uPx7D1JWkwFdZIXmD3MOPReuDNzHjBesSzIbQD" alt="${who}$" > `; - } + } - let rsvp = ''; - if (r.rsvp && rsvpEmoji[r.rsvp]) { - rsvp = `<sub>${rsvpEmoji[r.rsvp]}</sub>`; - } + let rsvp = ""; + if (r.rsvp && rsvpEmoji[r.rsvp]) { + rsvp = `<sub>${rsvpEmoji[r.rsvp]}</sub>`; + } - return` + return ` <a class="reaction" rel="nofollow ugc" @@ -246,230 +250,229 @@ A more detailed example: href="${r[mentionSource]}" > ${authorPhoto} - ${(reactEmoji[r['wm-property']] || '💥')} + ${reactEmoji[r["wm-property"]] || "💥"} ${rsvp} </a> `; - } - - /** - * Strip the protocol off a URL. - * - * @param {string} url The URL to strip protocol off. - * @returns {string} - */ - function stripurl(url) { - return url.substr(url.indexOf('//')); - } - - /** - * Deduplicate multiple mentions from the same source URL. - * - * @param {Array<Reaction>} mentions Mentions of the source URL. - * @return {Array<Reaction>} - */ - function dedupe(mentions) { - /** @type {Array<Reaction>} */ - const filtered = []; - /** @type {Record<string, boolean>} */ - const seen = {}; - - mentions.forEach(function(r) { - // Strip off the protocol (i.e. treat http and https the same) - const source = stripurl(r.url); - if (!seen[source]) { - filtered.push(r); - seen[source] = true; - } - }); + } - return filtered; - } - - /** - * Extract comments from a reaction. - * - * @param {Reactions} c - * @returns string - */ - function extractComment(c) { - let text = entities(c.content.text); - - if (textMaxWords) { - let words = text.replace(/\s+/g,' ').split(' ', textMaxWords + 1); - if (words.length > textMaxWords) { - words[textMaxWords - 1] += '…'; - words = words.slice(0, textMaxWords); - text = words.join(' '); - } + /** + * Strip the protocol off a URL. + * + * @param {string} url The URL to strip protocol off. + * @returns {string} + */ + function stripurl(url) { + return url.substr(url.indexOf("//")); } - return text; - } - - /** - * Format comments as HTML. - * - * @param {Array<Reaction>} comments The comments to format. - * @returns string - */ - function formatComments(comments) { - const headline = `<h2>${t('Responses')}</h2>`; - const markup = comments - .map((c) => { - const image = reactImage(c, true); - - let source = entities(c.url.split('/')[2]); - if (c.author && c.author.name) { - source = entities(c.author.name); - } - const link = `<a class="source" rel="nofollow ugc" href="${c[mentionSource]}">${source}</a>`; - - let linkclass = "name"; - let linktext = `(${t("mention")})`; - if (c.name) { - linkclass = "name"; - linktext = entities(c.name); - } else if (c.content && c.content.text) { - linkclass = "text"; - linktext = extractComment(c); + /** + * Deduplicate multiple mentions from the same source URL. + * + * @param {Array<Reaction>} mentions Mentions of the source URL. + * @return {Array<Reaction>} + */ + function dedupe(mentions) { + /** @type {Array<Reaction>} */ + const filtered = []; + /** @type {Record<string, boolean>} */ + const seen = {}; + + mentions.forEach(function (r) { + // Strip off the protocol (i.e. treat http and https the same) + const source = stripurl(r.url); + if (!seen[source]) { + filtered.push(r); + seen[source] = true; + } + }); + + return filtered; + } + + /** + * Extract comments from a reaction. + * + * @param {Reactions} c + * @returns string + */ + function extractComment(c) { + let text = entities(c.content.text); + + if (textMaxWords) { + let words = text.replace(/\s+/g, " ").split(" ", textMaxWords + 1); + if (words.length > textMaxWords) { + words[textMaxWords - 1] += "…"; + words = words.slice(0, textMaxWords); + text = words.join(" "); + } } - const type = `<span class="${linkclass}">${linktext}</span>`; + return text; + } - return `<li>${image} ${link} ${type}</li>`; - }) - .join(''); - return ` + /** + * Format comments as HTML. + * + * @param {Array<Reaction>} comments The comments to format. + * @returns string + */ + function formatComments(comments) { + const headline = `<h2>${t("Responses")}</h2>`; + const markup = comments + .map((c) => { + const image = reactImage(c, true); + + let source = entities(c.url.split("/")[2]); + if (c.author && c.author.name) { + source = entities(c.author.name); + } + const link = `<a class="source" rel="nofollow ugc" href="${c[mentionSource]}">${source}</a>`; + + let linkclass = "name"; + let linktext = `(${t("mention")})`; + if (c.name) { + linkclass = "name"; + linktext = entities(c.name); + } else if (c.content && c.content.text) { + linkclass = "text"; + linktext = extractComment(c); + } + + const type = `<span class="${linkclass}">${linktext}</span>`; + + return `<li>${image} ${link} ${type}</li>`; + }) + .join(""); + return ` ${headline} <ul class="comments">${markup}</ul> `; - } - - /** - * @typedef {Object} Reaction - * @property {string} url - * @property {Object?} author - * @property {string?} author.name - * @property {string?} author.photo - * @property {Object?} content - * @property {string?} content.text - * @property {RSVPEmoji?} rsvp - * @property {MentionType?} wm-property - * @property {string?} wm-source - */ - - /** - * Formats a list of reactions as HTML. - * - * @param {Array<Reaction>} reacts List of reactions to format - * @returns string - */ - function formatReactions(reacts) { - const headline = `<h2>${t('Reactions')}</h2>`; - - const markup = reacts.map((r) => reactImage(r)).join(''); - - return ` + } + + /** + * @typedef {Object} Reaction + * @property {string} url + * @property {Object?} author + * @property {string?} author.name + * @property {string?} author.photo + * @property {Object?} content + * @property {string?} content.text + * @property {RSVPEmoji?} rsvp + * @property {MentionType?} wm-property + * @property {string?} wm-source + */ + + /** + * Formats a list of reactions as HTML. + * + * @param {Array<Reaction>} reacts List of reactions to format + * @returns string + */ + function formatReactions(reacts) { + const headline = `<h2>${t("Reactions")}</h2>`; + + const markup = reacts.map((r) => reactImage(r)).join(""); + + return ` ${headline} <ul class="reacts">${markup}</ul> `; - } - - /** - * @typedef WebmentionResponse - * @type {Object} - * @property {Array<Reaction>} children - */ - - /** - * Register event listener. - */ - window.addEventListener("load", async function () { - const container = document.getElementById(containerID); - if (!container) { - // no container, so do nothing - return; - } - - const pages = [stripurl(refurl)]; - if (!!addurls) { - addurls.split('|').forEach(function (url) { - pages.push(stripurl(url)); - }); } - let apiURL = `https://webmention.io/api/mentions.jf2?per-page=${maxWebmentions}&sort-by=${sortBy}&sort-dir=${sortDir}`; - - pages.forEach(function (path) { - apiURL += `&target[]=${encodeURIComponent('http:' + path)}&target[]=${encodeURIComponent('https:' + path)}`; - }); - - /** @type {WebmentionResponse} */ - let json = {}; - try { - const response = await window.fetch(apiURL); - if (response.status >= 200 && response.status < 300) { - json = await response.json(); - } else { - console.error("Could not parse response"); - new Error(response.statusText); - } - } catch(error) { - // Purposefully not escalate further, i.e. no UI update - console.error("Request failed", error); - } - - /** @type {Array<Reaction>} */ - let comments = []; - /** @type {Array<Reaction>} */ - const collects = []; + /** + * @typedef WebmentionResponse + * @type {Object} + * @property {Array<Reaction>} children + */ + + /** + * Register event listener. + */ + window.addEventListener("load", async function () { + const container = document.getElementById(containerID); + if (!container) { + // no container, so do nothing + return; + } - if (commentsAreReactions) { - comments = collects; - } + const pages = [stripurl(refurl)]; + if (!!addurls) { + addurls.split("|").forEach(function (url) { + pages.push(stripurl(url)); + }); + } - /** @type {Record<MentionType, Array<Reaction>>} */ - const mapping = { - "in-reply-to": comments, - "like-of": collects, - "repost-of": collects, - "bookmark-of": collects, - "follow-of": collects, - "mention-of": comments, - "rsvp": comments - }; + let apiURL = `https://webmention.io/api/mentions.jf2?per-page=${maxWebmentions}&sort-by=${sortBy}&sort-dir=${sortDir}`; + + pages.forEach(function (path) { + apiURL += `&target[]=${encodeURIComponent("http:" + path)}&target[]=${encodeURIComponent("https:" + path)}`; + }); + + /** @type {WebmentionResponse} */ + let json = {}; + try { + const response = await window.fetch(apiURL); + if (response.status >= 200 && response.status < 300) { + json = await response.json(); + } else { + console.error("Could not parse response"); + new Error(response.statusText); + } + } catch (error) { + // Purposefully not escalate further, i.e. no UI update + console.error("Request failed", error); + } - json.children.forEach(function(child) { - // Map each mention into its respective container - const store = mapping[child['wm-property']]; - if (store) { - store.push(child); - } - }); + /** @type {Array<Reaction>} */ + let comments = []; + /** @type {Array<Reaction>} */ + const collects = []; - // format the comment-type things - let formattedComments = ''; - if (comments.length > 0 && comments !== collects) { - formattedComments = formatComments(dedupe(comments)); - } + if (commentsAreReactions) { + comments = collects; + } - // format the other reactions - let reactions = ''; - if (collects.length > 0) { - reactions = formatReactions(dedupe(collects)); - } + /** @type {Record<MentionType, Array<Reaction>>} */ + const mapping = { + "in-reply-to": comments, + "like-of": collects, + "repost-of": collects, + "bookmark-of": collects, + "follow-of": collects, + "mention-of": comments, + rsvp: comments, + }; + + json.children.forEach(function (child) { + // Map each mention into its respective container + const store = mapping[child["wm-property"]]; + if (store) { + store.push(child); + } + }); + + // format the comment-type things + let formattedComments = ""; + if (comments.length > 0 && comments !== collects) { + formattedComments = formatComments(dedupe(comments)); + } - let finalHtml =`${formattedComments}${reactions}`; - if(!finalHtml.trim()) { - finalHtml = '<p class="wm-empty">Nothing.</p>'; - } + // format the other reactions + let reactions = ""; + if (collects.length > 0) { + reactions = formatReactions(dedupe(collects)); + } - // 关键修正:将 finalHtml 变量的内容,赋值给容器的 innerHTML 属性 - container.innerHTML = finalHtml; + let finalHtml = `${formattedComments}${reactions}`; + if (!finalHtml.trim()) { + finalHtml = '<p class="wm-empty">Nothing.</p>'; + } - }); -}()); + // 关键修正:将 finalHtml 变量的内容,赋值给容器的 innerHTML 属性 + container.innerHTML = finalHtml; + }); +})(); // End-of-file marker for LibreJS // @license-end |
