diff options
| author | Eltrac <hi@guhub.cn> | 2025-07-14 19:26:28 +0800 |
|---|---|---|
| committer | Eltrac <hi@guhub.cn> | 2025-07-14 19:26:28 +0800 |
| commit | 29af8b6c2bd89c1966512b4305af345b8ee1ef82 (patch) | |
| tree | 07a4801791db7d6a4670d53411f0689ec0090bfe /layouts/_partials/posts | |
| parent | 44c7def3f6958550ccf965971f1819c1d6c514f1 (diff) | |
refactor: clearer division of each partial and better folder structure
Diffstat (limited to 'layouts/_partials/posts')
| -rw-r--r-- | layouts/_partials/posts/action-menu.html | 153 | ||||
| -rw-r--r-- | layouts/_partials/posts/banner.html | 17 | ||||
| -rw-r--r-- | layouts/_partials/posts/comment.html | 44 | ||||
| -rw-r--r-- | layouts/_partials/posts/endnotes.html | 10 | ||||
| -rw-r--r-- | layouts/_partials/posts/now-read-this.html | 19 | ||||
| -rw-r--r-- | layouts/_partials/posts/toc.html | 117 |
6 files changed, 360 insertions, 0 deletions
diff --git a/layouts/_partials/posts/action-menu.html b/layouts/_partials/posts/action-menu.html new file mode 100644 index 00000000..df2a3c2a --- /dev/null +++ b/layouts/_partials/posts/action-menu.html @@ -0,0 +1,153 @@ +<div class="post-actions-container"> + <!-- 操作按钮组 --> + <div class="post-actions-nav"> + <div class="left-actions"> + <button class="action-btn" id="comments-toggle" title="评论"> + <img src="/img/comments.svg" width="20" height="20" alt="评论" class="icon" /> + <span class="count-badge" id="comment-count">...</span> + </button> + + <!-- 点赞按钮 --> + <div class="like-button-container" id="like-container-{{ .File.UniqueID | urlize }}"> + <button class="action-btn like-btn" + onclick="sendLike('{{ .File.UniqueID | urlize }}')" + id="like-btn-{{ .File.UniqueID | urlize }}" + title="为这篇文章献上心脏" + data-liked-title="你已经献上心脏了"> + <img src="/img/heart-organ.svg" width="20" height="20" alt="点赞" class="like-icon" /> + <span class="count-badge" id="like-count-{{ .File.UniqueID | urlize }}">...</span> + </button> + </div> + </div> + + <div class="right-actions"> + <a href="https://t.me/teaparty418" target="_blank" class="action-btn small-btn" title="Telegram 群组" data-umami-event="✈️ Click Telegram link from post tabs"> + <img src="/img/paper-plane.svg" width="16" height="16" alt="Telegram" class="icon" /> + </a> + <a href="/sponsor" class="action-btn small-btn" title="赞助作者" data-umami-event="💗 Click sponsor button from post tabs"> + <img src="/img/coffee.svg" width="16" height="16" alt="赞助" class="icon" /> + </a> + </div> + </div> + + <!-- 评论区域 --> + <div class="comments-section" id="comments-section" style="display: none;"> + {{ partial "posts/comment.html" . }} + </div> +</div> + +<script> +// 评论展开/收起功能 +document.addEventListener('DOMContentLoaded', function() { + const commentsToggle = document.getElementById('comments-toggle'); + const commentsSection = document.getElementById('comments-section'); + + if (commentsToggle && commentsSection) { + commentsToggle.addEventListener('click', function() { + if (commentsSection.style.display === 'none') { + commentsSection.style.display = 'block'; + this.style.background = 'rgba(0, 0, 0, 0.1)'; + } else { + commentsSection.style.display = 'none'; + this.style.background = 'transparent'; + } + }); + } +}); + +// 获取 Giscus 评论数量 +function fetchGiscusCommentCount() { + const commentCountEl = document.getElementById('comment-count'); + if (!commentCountEl) return; + + // 设置初始值 + commentCountEl.textContent = '0'; + + // 监听 Giscus 的消息事件 + window.addEventListener('message', function(event) { + if (event.origin !== 'https://giscus.app') return; + + const giscusData = event.data; + + // 检查是否为 IMetadataMessage - 数据结构是 {giscus: {discussion: {...}}} + if (giscusData.giscus && giscusData.giscus.discussion) { + const discussion = giscusData.giscus.discussion; + + // 计算总评论数:主评论数 + 回复数 + const totalComments = (discussion.totalCommentCount || 0) + (discussion.totalReplyCount || 0); + commentCountEl.textContent = totalComments.toString(); + } + }); +} + +// 点赞功能 +(function() { + const pageId = "{{ .File.UniqueID | urlize }}"; + if (!pageId) { + return; + } + + const countEl = document.getElementById("like-count-" + pageId); + const btnEl = document.getElementById("like-btn-" + pageId); + const containerEl = document.getElementById("like-container-" + pageId); + + if (!countEl || !btnEl || !containerEl) { + return; + } + + function updateTooltip() { + if (containerEl.classList.contains('liked')) { + btnEl.setAttribute('title', btnEl.getAttribute('data-liked-title')); + } else { + btnEl.setAttribute('title', '为这篇文章献上心脏'); + } + } + + function fetchLikes() { + fetch("https://like.guhub.cn/like/" + pageId) + .then(resp => resp.json()) + .then(data => { + countEl.innerText = data.likes ?? 0; + if (localStorage.getItem("liked_" + pageId)) { + btnEl.disabled = true; + containerEl.classList.add("liked"); + updateTooltip(); + } + }) + .catch(() => { + countEl.innerText = "❌"; + }); + } + + window.sendLike = function(pageId) { + if (localStorage.getItem("liked_" + pageId)) return; + + if (window.location.hostname !== 'localhost') { + if (typeof umami !== 'undefined') { + umami.track('👍 Hit Like Button'); + } + } + + fetch("https://like.guhub.cn/like/" + pageId, { method: "POST" }) + .then(resp => resp.json()) + .then(data => { + localStorage.setItem("liked_" + pageId, "1"); + countEl.innerText = data.likes ?? 1; + btnEl.disabled = true; + containerEl.classList.add("liked"); + updateTooltip(); + }) + .catch(() => { + alert("点赞失败,请稍后再试"); + }); + }; + + // 初始化 + fetchLikes(); +})(); + +// 初始化评论数量获取 +document.addEventListener('DOMContentLoaded', function() { + fetchGiscusCommentCount(); +}); +</script> diff --git a/layouts/_partials/posts/banner.html b/layouts/_partials/posts/banner.html new file mode 100644 index 00000000..7ab4c826 --- /dev/null +++ b/layouts/_partials/posts/banner.html @@ -0,0 +1,17 @@ +{{ with .Params.banner }} +<div class="post-banner"> + <img + class="lazy" loading="lazy" + data-pagefind-meta="image[src]" + src="{{ . }}" + > +</div> +<style> +.post-banner img { + width: 100%; + height: auto; + display: block; + border-radius: 0.5rem +} +</style> +{{ end }} diff --git a/layouts/_partials/posts/comment.html b/layouts/_partials/posts/comment.html new file mode 100644 index 00000000..804570bc --- /dev/null +++ b/layouts/_partials/posts/comment.html @@ -0,0 +1,44 @@ +<div class="post-comments"> + {{ with .Params.comments }} + <div class="static-comments"> + <p class="static-comments-heading">{{ T "postPage.staticCommentsSectionHeader" }}</p> + {{ range . }} + <div class="static-comments-item"> + <div class="static-comments-item-header"> + <span class="comment-author">{{ .name }}</span> + <span class="comment-date font-mono">{{ .date }}</span> + </div> + <div class="static-comments-item-content"> + {{ .content | safeHTML }} + </div> + <div class="static-comments-item-footer"> + {{ with .source }} + <p class="font-mono">{{ T "postPage.originallyPostedOn" . }}</p> + {{ end }} + </div> + </div> + {{ end }} + </div> + {{ end }} + <div class="giscus"> + <p style="text-align:center"> + {{ T "postPage.commentNotLoaded" }} + </p> + </div> + <!-- giscus script --> + <script id="giscus-script" + src="https://giscus.app/client.js" + data-repo="{{ .Site.Params.giscus.repo }}" + data-repo-id="{{ .Site.Params.giscus.repoId }}" + data-category="{{ .Site.Params.giscus.category }}" + data-category-id="{{ .Site.Params.giscus.categoryId }}" + data-mapping="{{ .Site.Params.giscus.mapping }}" + data-strict="{{ .Site.Params.giscus.strict }}" + data-reactions-enabled="{{ .Site.Params.giscus.reactionsEnabled }}" + data-emit-metadata="1" + data-input-position="{{ .Site.Params.giscus.inputPosition }}" + data-theme="{{ .Site.Params.giscus.theme }}" + data-lang="{{ T "lang" }}" + crossorigin="anonymous"> + </script> +</div> diff --git a/layouts/_partials/posts/endnotes.html b/layouts/_partials/posts/endnotes.html new file mode 100644 index 00000000..e7245ad4 --- /dev/null +++ b/layouts/_partials/posts/endnotes.html @@ -0,0 +1,10 @@ +{{ if .Params.endnotes }} +<hr> +<div id="post-endnotes"> + <ul> + {{ range .Params.endnotes }} + <li>{{ . | markdownify }}</li> + {{ end }} + </ul> +</div> +{{ end }} diff --git a/layouts/_partials/posts/now-read-this.html b/layouts/_partials/posts/now-read-this.html new file mode 100644 index 00000000..6f2950c2 --- /dev/null +++ b/layouts/_partials/posts/now-read-this.html @@ -0,0 +1,19 @@ +{{ with site.RegularPages.Related . | first 1 }} +<div class="nrt post-content"> + <h4 class="nrt-title">{{ T "nowReadThis" }}</h4> + {{ range . }} + <div class="nrt-header"> + <a class="nrt-link" href="{{ .RelPermalink }}" data-umami-event="🤔 Click Related Post"> + {{ .LinkTitle }} + {{ with .Params.subtitle }} + <span class="nrt-subtitle">:{{ . }}</span> + {{ end }} + </a> + <span class="nrt-reading-time">⏱️ {{ .ReadingTime }}min </span> + </div> + <div class="nrt-summary"> + <p>{{ .Summary | plainify }}</p> + </div> + {{ end }} +</div> +{{ end }} diff --git a/layouts/_partials/posts/toc.html b/layouts/_partials/posts/toc.html new file mode 100644 index 00000000..794328c9 --- /dev/null +++ b/layouts/_partials/posts/toc.html @@ -0,0 +1,117 @@ +{{ if .Params.toc }} +<div id="toc-container"><div id="toc">{{ .TableOfContents }}</div></div> +<style> +/* toc */ +#toc-container { + position: absolute; + bottom: 0; + right: calc(50% + var(--page-width) / 2); + left: auto; + height: 100%; + top: 12rem; + @media screen and (max-width: 1200px) { + display: none + } +} + +#toc { + /* position */ + position: sticky; + top: 3rem; + /* styling */ + font-size: 1rem; + padding: 0 2rem; + @media screen and (max-width: 1500px) { + font-size: 0.825rem; + } + + ul { + margin-block-start: 0; + margin-block-end: 0; + padding-inline-start: 1rem; + + overflow: hidden; + position: relative; + li { + list-style: none; + + & > a { + color: var(--secondary-text-color); + text-decoration: none; + transition: color .3s; + display: inline-flex; + align-items: center; + gap: .5rem; + &::before { + content: ''; + display: inline-block; + width: 1rem; + height: .3rem; + background-color: var(--block-background-color); + border-radius: .2rem; + transition: background .3s; + } + } + + /* nested list */ + ul { + max-height: 25rem; + overflow-y: auto + } + + /* active styles */ + &:not(.active) ul { + max-height: 0 + } + a:hover, + &.active > a { + color: var(--text-color); + } + + &.active > a::before { + background-color: var(--secondary-text-color); + } + } + } +} +</style> +<script> +document.addEventListener('DOMContentLoaded', () => { + const toc = document.getElementById('toc'); + if (!toc) return; + + const headings = document.querySelectorAll('h2, h3'); + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + + const id = entry.target.id; + const tocLink = toc.querySelector(`a[href="#${id}"]`); + if (!tocLink) return; + + const listItem = tocLink.parentElement; + const isH2 = entry.target.tagName === 'H2'; + + // Clear all active states when a new H2 is encountered + if (isH2) { + toc.querySelectorAll('li.active').forEach(el => el.classList.remove('active')); + } + + // Set current item as active + listItem.classList.add('active'); + + // If this is an H3, find and activate its parent H2 + if (!isH2) { + const parentH2Item = listItem.closest('ul').previousElementSibling?.closest('li'); + if (parentH2Item) parentH2Item.classList.add('active'); + } + }); + }, { + rootMargin: '0px 0px -50% 0px', + threshold: 0.5 + }); + + headings.forEach(heading => observer.observe(heading)); +}); +</script> +{{ end }} |
