diff --git a/package.json b/package.json index f74f86c..544e892 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@swup/scroll-plugin": "^3.3.2", "@tailwindcss/vite": "^4.1.2", "@waline/client": "^3.5.6", + "artalk": "^2.9.1", "astro": "^5.6.1", "astro-compress": "2.3.5", "astro-icon": "^1.1.5", diff --git a/src/components/Comment.astro b/src/components/Comment.astro index a9603cf..838c531 100644 --- a/src/components/Comment.astro +++ b/src/components/Comment.astro @@ -3,6 +3,7 @@ import { commentConfig } from '@/config'; import Giscus from '@components/comment/Giscus.astro'; import Twikoo from '@components/comment/Twikoo.astro'; import Waline from '@components/comment/Waline.astro'; +import Artalk from './comment/Artalk.astro'; --- <div id="page-comment"> @@ -15,6 +16,8 @@ import Waline from '@components/comment/Waline.astro'; return <Giscus />; case 'waline': return <Waline />; + case 'artalk': + return <Artalk />; } })() } diff --git a/src/components/aside/ResentCommentsCard.astro b/src/components/aside/ResentCommentsCard.astro index 276d987..266b507 100644 --- a/src/components/aside/ResentCommentsCard.astro +++ b/src/components/aside/ResentCommentsCard.astro @@ -2,6 +2,7 @@ import { asideConfig, commentConfig } from '@/config'; import I18nKey from '@i18n/I18nKey'; import { i18n } from '@i18n/translation'; +import ArtalkRecentCommentScript from './recent-comments/Artalk.astro'; import TwikooRecentCommentScript from './recent-comments/Twikoo.astro'; import WalineRecentCommentScript from './recent-comments/Waline.astro'; --- @@ -50,6 +51,8 @@ import WalineRecentCommentScript from './recent-comments/Waline.astro'; return <TwikooRecentCommentScript />; case 'waline': return <WalineRecentCommentScript />; + case 'artalk': + return <ArtalkRecentCommentScript />; default: throw new Error( `Unsupported comment provider: '${commentConfig.provider}' for recent comments` diff --git a/src/components/aside/recent-comments/Artalk.astro b/src/components/aside/recent-comments/Artalk.astro new file mode 100644 index 0000000..0be4eb7 --- /dev/null +++ b/src/components/aside/recent-comments/Artalk.astro @@ -0,0 +1,73 @@ +<script> + import { asideConfig, commentConfig } from '@/config'; + import { + cleanCommentHtml, + cleanPlaceholders, + createCommentItem, + getTemplate, + } from './utils.ts'; + + async function setup() { + const artalkConfig = commentConfig.artalk!; + const commentCount = asideConfig.recentComment.count; + + const apiCommentUrl = new URL( + `api/v2/stats/latest_comments?limit=${commentCount}`, + artalkConfig.serverURL + ).toString(); + const apiConfigUrl = new URL(`api/v2/conf`, artalkConfig.serverURL).toString(); + + const responseComment = fetch(apiCommentUrl, { + method: 'GET', + }); + const responseConfig = fetch(apiConfigUrl, { + method: 'GET', + }); + if (!(await responseComment).ok) { + throw new Error('Failed to fetch recent comments'); + } + if (!(await responseConfig).ok) { + throw new Error('Failed to fetch comment config'); + } + + const commentData: { + id: number; + nick: string; + content_marked: string; + date: string; + email_encrypted: string; + page_url: string; + }[] = (await (await responseComment).json()).data; + const configData: { + gravatar: { + mirror: string; + params: string; + }; + } = (await (await responseConfig).json()).frontend_conf; + + const getAvatarUrl = (email: string) => { + return `${configData.gravatar.mirror}${email}?${configData.gravatar.params}`; + }; + + const { container, template } = getTemplate()!; + if (container && !template) { + // 说明已经加载完毕, 模板被删除了 + return; + } + + commentData.forEach((item) => + createCommentItem(container, template!, { + avatarUrl: getAvatarUrl(item.email_encrypted), + commentContent: cleanCommentHtml(item.content_marked), + commentUrl: `${item.page_url}#atk-comment-${item.id}`, + author: item.nick, + time: new Date(item.date), + }) + ); + + cleanPlaceholders(container, template!); + } + + document.addEventListener('astro:page-load', setup); + await setup(); +</script> diff --git a/src/components/aside/recent-comments/Waline.astro b/src/components/aside/recent-comments/Waline.astro index 1c8df9d..29391dc 100644 --- a/src/components/aside/recent-comments/Waline.astro +++ b/src/components/aside/recent-comments/Waline.astro @@ -10,8 +10,10 @@ async function setup() { const walineConfig = commentConfig.waline!; const commentCount = asideConfig.recentComment.count; - const apiUrl = `${walineConfig.serverURL}/api/comment?type=recent&count=${commentCount}`; - console.log('apiUrl', apiUrl); + const apiUrl = new URL( + `api/comment?type=recent&count=${commentCount}`, + walineConfig.serverURL + ).toString(); const response = await fetch(apiUrl, { method: 'GET', diff --git a/src/components/comment/Artalk.astro b/src/components/comment/Artalk.astro new file mode 100644 index 0000000..54ee43e --- /dev/null +++ b/src/components/comment/Artalk.astro @@ -0,0 +1,42 @@ +--- +import 'artalk/Artalk.css'; +--- + +<div id="artalk-wrapper" class="mt-6"></div> + +<script> + import { commentConfig, siteConfig } from '@/config'; + import Artalk from 'artalk'; + + function setup() { + function getCurrentMode(): boolean { + if (!('darkMode' in localStorage)) { + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } + return localStorage.darkMode === 'true'; + } + + const artalk = Artalk.init({ + el: '#artalk-wrapper', + darkMode: getCurrentMode(), + pageKey: window.location.pathname, + pageTitle: document.title, + server: commentConfig.artalk?.serverURL, + site: siteConfig.title, + locale: commentConfig.artalk?.locale || siteConfig.lang, + }); + + document.addEventListener('blog:darkmode-change', (e) => { + // @ts-expect-error CustomEvent.detail is not defined in TypeScript + const { isDark } = e.detail; + artalk.setDarkMode(isDark); + }); + + document.addEventListener('astro:before-swap', () => { + artalk.destroy(); + }); + } + + document.addEventListener('astro:page-load', setup); + setup(); +</script> diff --git a/src/config.ts b/src/config.ts index c9cbaf8..4474cae 100644 --- a/src/config.ts +++ b/src/config.ts @@ -199,4 +199,8 @@ export const commentConfig: CommentConfig = { pageSize: 10, reaction: false, }, + artalk: { + serverURL: 'https://artalk.yourdomain.com', + // locale: 'en', // Optional, default is site language. + }, }; diff --git a/src/types/config.ts b/src/types/config.ts index 5493373..08ac7a0 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -498,7 +498,7 @@ export type CommentConfig = { * * 评论的提供者。 */ - provider: 'twikoo' | 'giscus' | 'waline'; + provider: 'twikoo' | 'giscus' | 'waline' | 'artalk'; /** * The configuration of Twikoo. * @@ -708,4 +708,32 @@ export type CommentConfig = { */ reaction?: boolean | string[]; }; + /** + * The configuration of Artalk. + * Most settings should be configured on the server side, + * so only the most basic configuration is provided here. + * + * Artalk 的配置。大多数设置应当在服务端进行配置,因此这里仅提供最基本的配置。 + * + * @see https://artalk.js.org/ + */ + artalk?: { + /** + * The server URL of Artalk. + * + * Artalk 的服务端地址。 + * + * @see https://artalk.js.org/en/guide/frontend/config.html#server + */ + serverURL: string; + /** + * Language setting of Artalk. + * + * 语言设置 + * + * @default siteConfig.lang + * @see https://artalk.js.org/en/guide/frontend/config.html#locale + */ + locale?: string; + }; };