封装评论组件 内置【Twikoo、Waline】

This commit is contained in:
Han 2025-03-25 17:27:12 +08:00
parent bf2bdf1720
commit 3bf613946c
16 changed files with 215 additions and 43 deletions

View File

@ -25,7 +25,7 @@
- [x] LivePhoto - [x] LivePhoto
- [x] LaTex 数学公式 - [x] LaTex 数学公式
- [x] 赞赏功能 - [x] 赞赏功能
- [x] Twikoo 评论 - [x] 评论 - 内置【Twikoo、Waline】
- [x] 本地搜索 - [x] 本地搜索
- [x] 标签 - [x] 标签
- [x] 分类 - [x] 分类

View File

@ -7,7 +7,9 @@
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"newpost": "node ./script/newpost.js" "newpost": "node ./script/newpost.js",
"offdev": "astro preferences disable devToolbar",
"ondev": "astro preferences enable devToolbar"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.2.1", "@astrojs/mdx": "^4.2.1",
@ -15,7 +17,7 @@
"@astrojs/sitemap": "^3.3.0", "@astrojs/sitemap": "^3.3.0",
"@swup/astro": "^1.5.0", "@swup/astro": "^1.5.0",
"aplayer": "^1.10.1", "aplayer": "^1.10.1",
"astro": "^5.5.3", "astro": "^5.5.4",
"overlayscrollbars": "^2.11.1", "overlayscrollbars": "^2.11.1",
"vanilla-lazyload": "^19.1.3", "vanilla-lazyload": "^19.1.3",
"vh-plugin": "^1.2.2" "vh-plugin": "^1.2.2"
@ -24,6 +26,7 @@
"@playform/compress": "^0.1.7", "@playform/compress": "^0.1.7",
"@types/dplayer": "^1.25.5", "@types/dplayer": "^1.25.5",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@waline/client": "^3.5.6",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"less": "^4.2.2", "less": "^4.2.2",

View File

@ -6,6 +6,7 @@ section.article-list {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 1.6rem; gap: 1.6rem;
width: 100%; width: 100%;
min-width: 0;
height: max-content; height: max-content;
overflow: hidden; overflow: hidden;

View File

@ -319,4 +319,108 @@
} }
} }
} }
div[data-waline] {
// 头像尺寸和圆角
--waline-avatar-size: 2.56rem;
--waline-avatar-radius: 0.58rem;
font-size: var(--vh-size-h2);
.wl-cards {
&>.wl-card-item {
padding-left: 0;
.wl-card {
padding-bottom: 0;
&>.wl-head {
.wl-nick {
color: var(--vh-black-100);
font-size: var(--vh-size-h2);
line-height: 1.18;
&::after {
position: absolute;
content: "";
right: 0;
left: auto;
bottom: 0;
width: 0;
height: 1px;
background-color: var(--vh-black-88);
transition: all 0.28s;
}
&:hover {
&::after {
left: 0;
right: auto;
width: 100%;
}
}
}
}
// &>.wl-meta {}
&>.wl-content {
padding: 0;
p {
padding: 0.18rem 0;
color: var(--vh-black-100);
font-size: var(--vh-size-span);
line-height: 1.75rem;
}
&>p {
&:nth-of-type(1) {
padding: 0;
}
a {
color: var(--vh-black-100);
font-size: 0.76rem;
font-weight: 700;
}
}
}
&>.wl-quote {
border-left: solid 0.25rem var(--vh-main-color-38);
}
}
// item 中 wl-card 虚线间距
&>.wl-card {
padding-bottom: 1.25rem;
border-bottom-color: var(--vh-black-16);
}
}
}
// 图片
img {
&:not(.wl-user-avatar, .wl-reaction-list img) {
max-height: 66px;
object-fit: contain;
cursor: zoom-in;
cursor: -moz-zoom-in;
cursor: -webkit-zoom-in;
}
&.tk-owo-emotion {
max-height: 28px;
cursor: default;
}
}
a {
color: var(--vh-black-100);
}
code {
font-size: 0.68rem;
}
}
} }

View File

@ -40,8 +40,19 @@ export default {
], ],
// 博客音乐组件解析接口 // 博客音乐组件解析接口
vhMusicApi: 'https://music.zhheo.com/meting-api/', vhMusicApi: 'https://music.zhheo.com/meting-api/',
// 评论组件 Twikoo // 评论组件(只允许同时开启一个)
Twikoo: { envId: '' }, Comment: {
// Twikoo 评论
Twikoo: {
enable: false,
envId: ''
},
// Waline 评论
Waline: {
enable: false,
serverURL: ''
}
},
// Han Analytics 统计https://github.com/uxiaohan/HanAnalytics // Han Analytics 统计https://github.com/uxiaohan/HanAnalytics
HanAnalytics: { enable: true, server: 'https://analytics.vvhan.com', siteId: 'Hello-HanHexoBlog' }, HanAnalytics: { enable: true, server: 'https://analytics.vvhan.com', siteId: 'Hello-HanHexoBlog' },
// Google 广告 // Google 广告

View File

@ -46,7 +46,7 @@ top: true
- [x] LivePhoto - [x] LivePhoto
- [x] LaTex数学公式 - [x] LaTex数学公式
- [x] 赞赏功能 - [x] 赞赏功能
- [x] Twikoo 评论 - [x] 评论 - 内置【Twikoo、Waline】
- [x] 本地搜索 - [x] 本地搜索
- [x] 标签 - [x] 标签
- [x] 分类 - [x] 分类

View File

@ -2,12 +2,13 @@
const { frontmatter } = Astro.props; const { frontmatter } = Astro.props;
// 页面 Info // 页面 Info
import SITE_CONFIG from "@/config"; import SITE_CONFIG from "@/config";
const { Description, Twikoo, Title } = SITE_CONFIG; const { Description, Title } = SITE_CONFIG;
// Aside组件 // Aside组件
import Aside from "@/components/Aside/Aside.astro"; import Aside from "@/components/Aside/Aside.astro";
// 公共 Layout // 公共 Layout
import Layout from "@/layouts/Layout/Layout.astro"; import Layout from "@/layouts/Layout/Layout.astro";
// 评论组件 // 评论组件
import { checkComment } from "@/scripts/Comment";
import Comment from "@/components/Comment/Comment.astro"; import Comment from "@/components/Comment/Comment.astro";
// 关于样式 // 关于样式
import "@/styles/About.less"; import "@/styles/About.less";
@ -23,7 +24,7 @@ import "@/styles/ArticleBase.less";
<p>{frontmatter.desc}</p> <p>{frontmatter.desc}</p>
</header> </header>
<main><slot /></main> <main><slot /></main>
{Twikoo.envId && frontmatter.comment != false && <Comment envId={Twikoo.envId} />} {checkComment() && frontmatter.comment != false && <Comment />}
</section> </section>
<Aside /> <Aside />
</section> </section>

View File

@ -2,12 +2,13 @@
const { frontmatter } = Astro.props; const { frontmatter } = Astro.props;
// 页面 Info // 页面 Info
import SITE_CONFIG from "@/config"; import SITE_CONFIG from "@/config";
const { Description, Twikoo, Title } = SITE_CONFIG; const { Description, Title } = SITE_CONFIG;
// Aside组件 // Aside组件
import Aside from "@/components/Aside/Aside.astro"; import Aside from "@/components/Aside/Aside.astro";
// 公共 Layout // 公共 Layout
import Layout from "@/layouts/Layout/Layout.astro"; import Layout from "@/layouts/Layout/Layout.astro";
// 评论组件 // 评论组件
import { checkComment } from "@/scripts/Comment";
import Comment from "@/components/Comment/Comment.astro"; import Comment from "@/components/Comment/Comment.astro";
// ToolLayout 布局样式 // ToolLayout 布局样式
import "./ToolLayout.less"; import "./ToolLayout.less";
@ -24,7 +25,7 @@ import "@/styles/ArticleBase.less";
</header> </header>
<main><slot /></main> <main><slot /></main>
<main class={`${frontmatter.type}-main main`}><section class="vh-space-loading"><span></span><span></span><span></span></section></main> <main class={`${frontmatter.type}-main main`}><section class="vh-space-loading"><span></span><span></span><span></span></section></main>
{Twikoo.envId && frontmatter.comment != false && <Comment envId={Twikoo.envId} />} {checkComment() && frontmatter.comment != false && <Comment />}
</section> </section>
<Aside /> <Aside />
</section> </section>

View File

@ -14,7 +14,7 @@ import getCover from "@/utils/getCover";
const ARTICLE_COVER: string = await getCover(post.data.cover); const ARTICLE_COVER: string = await getCover(post.data.cover);
// 页面 Info // 页面 Info
import SITE_CONFIG from "@/config"; import SITE_CONFIG from "@/config";
const { Site, Title, Author, Twikoo, GoogleAds } = SITE_CONFIG; const { Site, Title, Author, GoogleAds } = SITE_CONFIG;
// 处理文章内容 // 处理文章内容
const description = getDescription(post); const description = getDescription(post);
const { Content, remarkPluginFrontmatter } = await render(post); const { Content, remarkPluginFrontmatter } = await render(post);
@ -29,6 +29,7 @@ import Copyright from "@/components/Copyright/Copyright.astro";
// Reward 组件 // Reward 组件
import Reward from "@/components/Reward/Reward.astro"; import Reward from "@/components/Reward/Reward.astro";
// 评论组件 // 评论组件
import { checkComment } from "@/scripts/Comment";
import Comment from "@/components/Comment/Comment.astro"; import Comment from "@/components/Comment/Comment.astro";
// Google 广告组件 // Google 广告组件
import GoogleAd from "@/components/GoogleAd/GoogleAd.astro"; import GoogleAd from "@/components/GoogleAd/GoogleAd.astro";
@ -70,7 +71,7 @@ import "@/styles/ArticleBase.less";
<!-- 底部谷歌广告 --> <!-- 底部谷歌广告 -->
{GoogleAds.ad_Client && GoogleAds.articleAD_Slot && <GoogleAd className="vh-article-ad" slotID={GoogleAds.articleAD_Slot} />} {GoogleAds.ad_Client && GoogleAds.articleAD_Slot && <GoogleAd className="vh-article-ad" slotID={GoogleAds.articleAD_Slot} />}
</footer> </footer>
{Twikoo.envId && <Comment envId={Twikoo.envId} />} {checkComment() && <Comment />}
</article> </article>
<Aside /> <Aside />
</section> </section>

View File

@ -1,22 +1,37 @@
import SITE_INFO from "@/config"; import SITE_INFO from "@/config";
// 图片灯箱
import "@public/assets/js/view-image.min.js";
declare const ViewImage: any;
import { LoadScript } from "@/utils/index"; import { LoadScript } from "@/utils/index";
declare const twikoo: any; declare const twikoo: any;
// 处理评论区数据 // Twikoo 评论
const formateComment = () => { const TwikooFn = async (commentDOM: string) => {
// 图片灯箱
ViewImage && ViewImage.init(".vh-container>article.vh-article-main img.vh-article-img, main.talking-main>article>.main img, .vh-comment>.twikoo>.tk-comments img:not(.tk-avatar-img,.tk-owo-emotion,.OwO-item img)");
// 处理 URL
document.querySelectorAll('.vh-comment a[href="#"]').forEach(link => link.removeAttribute('href'));
}
// 初始化评论插件
export default async () => {
const commentDOM = '.vh-comment>section'
if (!document.querySelector(commentDOM) || !SITE_INFO.Twikoo.envId) return formateComment();
document.querySelector(commentDOM)!.innerHTML = '<section class="vh-space-loading"><span></span><span></span><span></span></section>' document.querySelector(commentDOM)!.innerHTML = '<section class="vh-space-loading"><span></span><span></span><span></span></section>'
await LoadScript("https://registry.npmmirror.com/twikoo/1.6.41/files/dist/twikoo.all.min.js"); await LoadScript("https://registry.npmmirror.com/twikoo/1.6.41/files/dist/twikoo.all.min.js");
twikoo.init({ envId: SITE_INFO.Twikoo.envId, el: commentDOM, onCommentLoaded: () => setTimeout(formateComment) }) twikoo.init({ envId: SITE_INFO.Comment.Twikoo.envId, el: commentDOM, onCommentLoaded: () => setTimeout(() => document.querySelectorAll('.vh-comment a[href="#"]').forEach(link => link.removeAttribute('href'))) })
} }
// Waline 评论
const WalineFn = async (commentDOM: string, walineInit: any) => {
import('@waline/client/waline.css');
import('@waline/client/waline-meta.css');
const { init } = await import('@waline/client');
walineInit = init({ el: commentDOM, serverURL: SITE_INFO.Comment.Waline.serverURL });
}
// 检查是否开启评论
const checkComment = () => {
const CommentARR: any = Object.keys(SITE_INFO.Comment);
const CommentItem = CommentARR.find((i: keyof typeof SITE_INFO.Comment) => SITE_INFO.Comment[i].enable);
return CommentItem;
}
// 初始化评论插件
const commentInit = async (key: string, walineInit: any) => {
// 评论列表
const CommentList: any = { TwikooFn, WalineFn };
// 评论 DOM
const commentDOM = '.vh-comment>section'
// 初始化评论
CommentList[`${key}Fn`](commentDOM, walineInit);
}
export { checkComment, commentInit }

View File

@ -2,6 +2,8 @@
import vh from 'vh-plugin' import vh from 'vh-plugin'
import { fmtDate } from '@/utils/index' import { fmtDate } from '@/utils/index'
import { $GET } from '@/utils/index' import { $GET } from '@/utils/index'
// 图片懒加载
import vhLzImgInit from "@/scripts/vhLazyImg";
const FriendsInit = async (data: any) => { const FriendsInit = async (data: any) => {
const friendsDOM = document.querySelector('.vh-container>.vh-tools-main>main.friends-main') const friendsDOM = document.querySelector('.vh-container>.vh-tools-main>main.friends-main')
@ -12,6 +14,8 @@ const FriendsInit = async (data: any) => {
res = await $GET(api); res = await $GET(api);
} }
friendsDOM.innerHTML = res.map((i: any) => `<article><a href="${i.link}" target="_blank" rel="noopener nofollow"><header><h2>${i.title}</h2></header><p>${i.content}</p><footer><span><img src="https://icon.bqb.cool/?url=${i.link.split('//')[1].split('/')[0]}" /><em>${i.auther}</em></span><time>${fmtDate(i.date, false)}前</time></footer></a></article>`).join(''); friendsDOM.innerHTML = res.map((i: any) => `<article><a href="${i.link}" target="_blank" rel="noopener nofollow"><header><h2>${i.title}</h2></header><p>${i.content}</p><footer><span><img src="https://icon.bqb.cool/?url=${i.link.split('//')[1].split('/')[0]}" /><em>${i.auther}</em></span><time>${fmtDate(i.date, false)}前</time></footer></a></article>`).join('');
// 图片懒加载
vhLzImgInit();
} catch { } catch {
vh.Toast('获取数据失败') vh.Toast('获取数据失败')
} }

View File

@ -13,6 +13,8 @@ import BackTopInitFn from "@/scripts/BackTop";
import { searchFn, vhSearchInit } from "@/scripts/Search"; import { searchFn, vhSearchInit } from "@/scripts/Search";
// 图片懒加载 // 图片懒加载
import vhLzImgInit from "@/scripts/vhLazyImg"; import vhLzImgInit from "@/scripts/vhLazyImg";
// 图片灯箱
import ViewImage from "@/scripts/ViewImage";
// 顶部导航 Current 状态 // 顶部导航 Current 状态
import initLinkCurrent from "@/scripts/Header"; import initLinkCurrent from "@/scripts/Header";
// 底部网站运行时间 // 底部网站运行时间
@ -24,7 +26,7 @@ import initFriends from "@/scripts/Friends";
// 动态说说初始化 // 动态说说初始化
import initTalking from "@/scripts/Talking"; import initTalking from "@/scripts/Talking";
// 文章评论初始化 // 文章评论初始化
import initComment from "@/scripts/Comment"; import { checkComment, commentInit } from "@/scripts/Comment";
// 移动端侧边栏初始化 // 移动端侧边栏初始化
import initMobileSidebar from "@/scripts/MobileSidebar"; import initMobileSidebar from "@/scripts/MobileSidebar";
// Google 广告 // Google 广告
@ -39,6 +41,7 @@ import SmoothScroll from "@/scripts/Smoothscroll";
// 页面初始化 Only // 页面初始化 Only
const videoList: any[] = []; const videoList: any[] = [];
const MusicList: any[] = []; const MusicList: any[] = [];
let commentLIst: any = { walineInit: null };
const indexInit = async (only: boolean = true) => { const indexInit = async (only: boolean = true) => {
// 预加载搜索数据 // 预加载搜索数据
only && searchFn(""); only && searchFn("");
@ -57,9 +60,11 @@ const indexInit = async (only: boolean = true) => {
// 初始化文章代码块 // 初始化文章代码块
codeInit(); codeInit();
// 文章评论初始化 // 文章评论初始化
initComment(); checkComment() && commentInit(checkComment(), commentLIst)
// 图片懒加载初始化 // 图片懒加载初始化
vhLzImgInit(); vhLzImgInit();
// 图片灯箱
only && ViewImage();
// 友情链接初始化 // 友情链接初始化
initLinks(); initLinks();
// 朋友圈 RSS 初始化 // 朋友圈 RSS 初始化
@ -85,6 +90,9 @@ export default () => {
inRouter(() => indexInit(false)); inRouter(() => indexInit(false));
// 离开当前页面时触发 // 离开当前页面时触发
outRouter(() => { outRouter(() => {
// 销毁评论
commentLIst.walineInit && commentLIst.walineInit.destroy();
commentLIst.walineInit = null;
// 销毁播放器 // 销毁播放器
videoList.forEach((i: any) => i.destroy()); videoList.forEach((i: any) => i.destroy());
videoList.length = 0; videoList.length = 0;

View File

@ -4,10 +4,6 @@ import { fmtDate } from '@/utils/index'
import { $GET } from '@/utils/index' import { $GET } from '@/utils/index'
// 图片懒加载 // 图片懒加载
import vhLzImgInit from "@/scripts/vhLazyImg"; import vhLzImgInit from "@/scripts/vhLazyImg";
// 灯箱JS初始化======
import "@public/assets/js/view-image.min.js";
declare const ViewImage: any;
// 灯箱JS初始化======
const TalkingInit = async (data: any) => { const TalkingInit = async (data: any) => {
const talkingDOM = document.querySelector('.vh-container>.vh-tools-main>main.talking-main') const talkingDOM = document.querySelector('.vh-container>.vh-tools-main>main.talking-main')
@ -20,9 +16,6 @@ const TalkingInit = async (data: any) => {
talkingDOM.innerHTML = res.map((i: any) => `<article><header><img data-vh-lz-src="https://q1.qlogo.cn/g?b=qq&nk=1655466387&s=640" /><p class="info"><span>.𝙃𝙖𝙣</span><time>${fmtDate(i.date)}前</time></p></header><section class="main">${i.content}</section><footer>${i.tags.map((tag: any) => `<span>${tag}</span>`).join('')}</footer></article>`).join(''); talkingDOM.innerHTML = res.map((i: any) => `<article><header><img data-vh-lz-src="https://q1.qlogo.cn/g?b=qq&nk=1655466387&s=640" /><p class="info"><span>.𝙃𝙖𝙣</span><time>${fmtDate(i.date)}前</time></p></header><section class="main">${i.content}</section><footer>${i.tags.map((tag: any) => `<span>${tag}</span>`).join('')}</footer></article>`).join('');
// 图片懒加载 // 图片懒加载
vhLzImgInit(); vhLzImgInit();
// 灯箱JS初始化======
setTimeout(() => (ViewImage && ViewImage.init("main.talking-main>article>.main img, .vh-comment>.twikoo>.tk-comments img:not(.tk-avatar-img,.tk-owo-emotion,.OwO-item img)")));
// 灯箱JS初始化======
} catch { } catch {
vh.Toast('获取数据失败') vh.Toast('获取数据失败')
} }

23
src/scripts/ViewImage.ts Normal file
View File

@ -0,0 +1,23 @@
import { LoadScript } from "@/utils/index";
// 图片灯箱
declare const ViewImage: any;
const ViewImgList: string[] = [
// 文章内图片
".vh-container>article.vh-article-main img.vh-article-img",
// 动态页面图片
"main.talking-main>article>.main img",
// Twikoo 评论区图片
".vh-comment>.twikoo>.tk-comments img:not(.tk-avatar-img,.tk-owo-emotion,.OwO-item img)",
// Waline 评论区图片
".vh-comment div[data-waline] img:not(.wl-user-avatar,.wl-avatar img,.wl-reaction-list img)"
];
// 初始化
export default async () => {
try {
ViewImage.init(ViewImgList.join(","));
} catch (error) {
await LoadScript("/assets/js/view-image.min.js");
ViewImage.init(ViewImgList.join(","));
}
}

View File

@ -2,6 +2,7 @@
import LazyLoad from "vanilla-lazyload"; import LazyLoad from "vanilla-lazyload";
// 初始化图片懒加载 // 初始化图片懒加载
let lazyLoadStatus: any = null;
export default () => { export default () => {
document.querySelectorAll("main>.vh-container img:not(.view-image-container)").forEach((i: any) => { document.querySelectorAll("main>.vh-container img:not(.view-image-container)").forEach((i: any) => {
// 是否包含data-vh-lz-src // 是否包含data-vh-lz-src
@ -10,9 +11,6 @@ export default () => {
i.setAttribute("src", '/assets/images/lazy-loading.webp'); i.setAttribute("src", '/assets/images/lazy-loading.webp');
} }
}); });
new LazyLoad({ if (lazyLoadStatus) return lazyLoadStatus.update();
elements_selector: "img:not(.view-image-container)", lazyLoadStatus = new LazyLoad({ elements_selector: "img:not(.view-image-container)", threshold: 0, data_src: "vh-lz-src" });
threshold: 0,
data_src: "vh-lz-src"
});
} }

View File

@ -131,6 +131,15 @@
a { a {
color: #49B1F5; color: #49B1F5;
transition: all .16s;
&:hover {
color: #1b99ee;
}
span {
color: currentColor;
}
} }
th { th {
@ -165,7 +174,7 @@
} }
&:hover { &:hover {
background-color: #f2f2f2; background-color: #f2f2f266;
} }
} }