summaryrefslogtreecommitdiff
path: root/layouts/_partials/posts
diff options
context:
space:
mode:
authorEltrac <hi@guhub.cn>2025-07-14 19:26:28 +0800
committerEltrac <hi@guhub.cn>2025-07-14 19:26:28 +0800
commit29af8b6c2bd89c1966512b4305af345b8ee1ef82 (patch)
tree07a4801791db7d6a4670d53411f0689ec0090bfe /layouts/_partials/posts
parent44c7def3f6958550ccf965971f1819c1d6c514f1 (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.html153
-rw-r--r--layouts/_partials/posts/banner.html17
-rw-r--r--layouts/_partials/posts/comment.html44
-rw-r--r--layouts/_partials/posts/endnotes.html10
-rw-r--r--layouts/_partials/posts/now-read-this.html19
-rw-r--r--layouts/_partials/posts/toc.html117
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 }}