diff --git a/README.md b/README.md index e1a4da8..07040a9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ > [!WARNING] > 目前仍处于 Beta 测试阶段,欢迎反馈测试结果。 - + # cwd-comments diff --git a/docs/.vitepress/components/footerDoc.vue b/docs/.vitepress/components/footerDoc.vue new file mode 100644 index 0000000..ec2f27f --- /dev/null +++ b/docs/.vitepress/components/footerDoc.vue @@ -0,0 +1,38 @@ + + + diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 449becb..ebaae03 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -24,6 +24,7 @@ export default defineConfig({ { text: '快速开始', link: '/guide/getting-started' }, { text: '后端配置', link: '/guide/backend-config' }, { text: '前端配置', link: '/guide/frontend-config' }, + { text: '管理后台', link: '/guide/admin-panel' }, ] }, { diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js new file mode 100644 index 0000000..77c44da --- /dev/null +++ b/docs/.vitepress/theme/index.js @@ -0,0 +1,15 @@ +import DefaultTheme from 'vitepress/theme' +import { h } from 'vue' +import FooterDoc from '../components/footerDoc.vue' + +export default { + ...DefaultTheme, + Layout() { + const Layout = DefaultTheme.Layout + + return h(Layout, null, { + 'doc-footer-before': () => h(FooterDoc), + }) + }, +} + diff --git a/docs/guide/admin-dashboard.md b/docs/guide/admin-panel.md similarity index 100% rename from docs/guide/admin-dashboard.md rename to docs/guide/admin-panel.md diff --git a/docs/guide/frontend-config.md b/docs/guide/frontend-config.md index 9721c45..2ea1000 100644 --- a/docs/guide/frontend-config.md +++ b/docs/guide/frontend-config.md @@ -13,7 +13,6 @@ const comments = new CWDComments({ el: '#comments', apiBaseUrl: 'https://your-api.example.com', - postSlug: window.location.origin + window.location.pathname, }); comments.mount(); @@ -25,10 +24,11 @@ | ------------ | ----------------------- | ---- | ---------------------------------------------------------------- | ------------------------- | | `el` | `string \| HTMLElement` | 是 | - | 挂载元素选择器或 DOM 元素 | | `apiBaseUrl` | `string` | 是 | - | API 基础地址 | -| `postSlug` | `string` | 是 | 当前页面 URL `window.location.origin + window.location.pathname` | 文章唯一标识符 | | `theme` | `'light' \| 'dark'` | 否 | `'light'` | 主题模式 | | `pageSize` | `number` | 否 | `20` | 每页显示评论数 | +头像前缀、博主邮箱和标识等信息由后端接口 `/api/config/comments` 提供,无需在前端进行配置。 + ## 实例方法 | 方法 | 说明 | @@ -43,7 +43,4 @@ ```javascript // 动态切换主题 comments.updateConfig({ theme: 'dark' }); - -// 切换文章 -comments.updateConfig({ postSlug: 'another-post' }); ``` diff --git a/widget/index.html b/widget/index.html index aa73f3c..68b67a8 100644 --- a/widget/index.html +++ b/widget/index.html @@ -129,10 +129,6 @@ -
- - -
-
- - -
@@ -155,7 +147,7 @@

这是一篇示例文章

- 这是一个演示 CWD Comments Widget 的示例页面。你可以在上方的配置面板中修改 API 地址和文章标识符, + 这是一个演示 CWD Comments Widget 的示例页面。你可以在上方的配置面板中修改 API 地址和主题, 然后点击"应用配置"按钮重新加载评论组件。

@@ -169,4 +161,4 @@ - \ No newline at end of file + diff --git a/widget/public/test.html b/widget/public/test.html new file mode 100644 index 0000000..3f046a1 --- /dev/null +++ b/widget/public/test.html @@ -0,0 +1,9 @@ + +
+ \ No newline at end of file diff --git a/widget/src/core/CWDComments.js b/widget/src/core/CWDComments.js index 0881817..d60730c 100644 --- a/widget/src/core/CWDComments.js +++ b/widget/src/core/CWDComments.js @@ -17,21 +17,24 @@ export class CWDComments { * @param {Object} config - 配置对象 * @param {string|HTMLElement} config.el - 挂载元素选择器或 DOM 元素 * @param {string} config.apiBaseUrl - API 基础地址 - * @param {string} config.postSlug - 文章标识符 - * @param {string} config.postTitle - 文章标题(可选) - * @param {string} config.postUrl - 文章 URL(可选) - * @param {'light'|'dark'} config.theme - 主题 - * @param {number} config.pageSize - 每页评论数 - * @param {string} config.avatarPrefix - 头像服务前缀(可选,默认 https://gravatar.com/avatar/) - * @param {string} config.adminEmail - 博主邮箱(可选,用于显示博主标识) - * @param {string} config.adminBadge - 博主标识文字(可选,默认"博主") + * @param {'light'|'dark'} [config.theme] - 主题(可选) + * @param {number} [config.pageSize] - 每页评论数(可选,默认 20) + * + * 以下字段由组件自动推导或从后端读取,无需通过 config 传入: + * - postSlug:window.location.origin + window.location.pathname + * - postTitle:document.title 或 postSlug + * - postUrl:window.location.href + * - avatarPrefix/adminEmail/adminBadge:通过 /api/config/comments 接口获取 */ constructor(config) { this.config = { ...config }; - if (!this.config.postTitle && typeof document !== 'undefined') { + if (typeof window !== 'undefined') { + this.config.postSlug = window.location.origin + window.location.pathname; + } + if (typeof document !== 'undefined') { this.config.postTitle = document.title || this.config.postSlug; } - if (!this.config.postUrl && typeof window !== 'undefined') { + if (typeof window !== 'undefined') { this.config.postUrl = window.location.href; } this.hostElement = this._resolveElement(config.el); @@ -64,6 +67,30 @@ export class CWDComments { return el; } + async _loadServerConfig() { + try { + const base = this.config.apiBaseUrl; + if (!base) { + return {}; + } + const apiBaseUrl = base.replace(/\/$/, ''); + const res = await fetch(`${apiBaseUrl}/api/config/comments`); + if (!res.ok) { + return {}; + } + const data = await res.json(); + return { + adminEmail: data.adminEmail || '', + adminBadge: data.adminBadge || '', + adminEnabled: !!data.adminEnabled, + avatarPrefix: data.avatarPrefix || '' + }; + } catch (e) { + console.warn('[CWDComments] 加载服务端评论配置失败:', e); + return {}; + } + } + /** * 挂载组件 */ @@ -94,24 +121,35 @@ export class CWDComments { this.mountPoint.setAttribute('data-theme', this.config.theme); } - // 创建 API 客户端和 Store - const api = createApiClient(this.config); - this.store = createCommentStore( - this.config, - api.fetchComments.bind(api), - api.submitComment.bind(api) - ); + (async () => { + const serverConfig = await this._loadServerConfig(); + if (!this._mounted) { + return; + } + if (serverConfig.avatarPrefix) { + this.config.avatarPrefix = serverConfig.avatarPrefix; + } + if (serverConfig.adminEnabled && serverConfig.adminEmail) { + this.config.adminEmail = serverConfig.adminEmail; + } + if (serverConfig.adminEnabled && serverConfig.adminBadge) { + this.config.adminBadge = serverConfig.adminBadge; + } - // 订阅状态变化 - this.unsubscribe = this.store.store.subscribe((state) => { - this._onStateChange(state); - }); + const api = createApiClient(this.config); + this.store = createCommentStore( + this.config, + api.fetchComments.bind(api), + api.submitComment.bind(api) + ); - // 渲染组件 - this._render(); + this.unsubscribe = this.store.store.subscribe((state) => { + this._onStateChange(state); + }); - // 加载评论 - this.store.loadComments(); + this._render(); + this.store.loadComments(); + })(); this._mounted = true; } @@ -339,35 +377,43 @@ export class CWDComments { const prevConfig = { ...this.config }; Object.assign(this.config, newConfig); + if (typeof window !== 'undefined') { + this.config.postSlug = window.location.origin + window.location.pathname; + } + if (typeof document !== 'undefined') { + this.config.postTitle = document.title || this.config.postSlug; + } + if (typeof window !== 'undefined') { + this.config.postUrl = window.location.href; + } // 更新主题 if (newConfig.theme && this.mountPoint) { this.mountPoint.setAttribute('data-theme', newConfig.theme); } - // 如果 postSlug 变化,重新加载评论 - if (newConfig.postSlug && newConfig.postSlug !== prevConfig.postSlug) { - // 重新创建 API 客户端和 Store + const shouldReload = + this.config.apiBaseUrl !== prevConfig.apiBaseUrl || + this.config.pageSize !== prevConfig.pageSize || + this.config.postSlug !== prevConfig.postSlug; + + if (shouldReload) { const api = createApiClient(this.config); - // 取消旧订阅 if (this.unsubscribe) { this.unsubscribe(); } - // 创建新 store this.store = createCommentStore( this.config, api.fetchComments.bind(api), api.submitComment.bind(api) ); - // 重新订阅 this.unsubscribe = this.store.store.subscribe((state) => { this._onStateChange(state); }); - // 加载评论 this.store.loadComments(); } } diff --git a/widget/src/dev.js b/widget/src/dev.js index 50ad305..23717d1 100644 --- a/widget/src/dev.js +++ b/widget/src/dev.js @@ -11,7 +11,6 @@ const STORAGE_KEY = 'cwd-dev-config'; const DEFAULT_CONFIG = { el: '#comments', apiBaseUrl: 'http://localhost:8788', - postSlug: window.location.origin + window.location.pathname, theme: 'light', // 默认 light / dark }; @@ -48,12 +47,10 @@ function saveConfigToStorage(config) { */ function populateInputs(config) { const apiBaseUrlInput = document.getElementById('apiBaseUrl'); - const postSlugInput = document.getElementById('postSlug'); const themeSelect = document.getElementById('theme'); const avatarPrefixInput = document.getElementById('avatarPrefix'); if (apiBaseUrlInput) apiBaseUrlInput.value = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl; - if (postSlugInput) postSlugInput.value = config.postSlug || DEFAULT_CONFIG.postSlug; if (themeSelect) themeSelect.value = config.theme || DEFAULT_CONFIG.theme; if (avatarPrefixInput) avatarPrefixInput.value = config.avatarPrefix || DEFAULT_CONFIG.avatarPrefix; } @@ -63,29 +60,8 @@ function populateInputs(config) { */ function getConfigFromInputs() { const apiBaseUrl = document.getElementById('apiBaseUrl')?.value || DEFAULT_CONFIG.apiBaseUrl; - const postSlug = document.getElementById('postSlug')?.value || DEFAULT_CONFIG.postSlug; const theme = document.getElementById('theme')?.value || DEFAULT_CONFIG.theme; - const avatarPrefix = document.getElementById('avatarPrefix')?.value || DEFAULT_CONFIG.avatarPrefix; - return { apiBaseUrl, postSlug, theme, avatarPrefix }; -} - -async function loadServerCommentConfig(apiBaseUrl) { - try { - const res = await fetch(`${apiBaseUrl}/api/config/comments`); - if (!res.ok) { - return {}; - } - const data = await res.json(); - return { - adminEmail: data.adminEmail || '', - adminBadge: data.adminBadge || '', - adminEnabled: !!data.adminEnabled, - avatarPrefix: data.avatarPrefix || '', - }; - } catch (e) { - console.warn('[CWDComments] 加载服务端评论配置失败:', e); - return {}; - } + return { apiBaseUrl, theme }; } /** @@ -111,21 +87,11 @@ async function initWidget() { // 创建新实例 try { - const serverConfig = await loadServerCommentConfig(config.apiBaseUrl); - widgetInstance = new CWDComments({ el: '#comments', apiBaseUrl: config.apiBaseUrl, - postSlug: config.postSlug, theme: config.theme, - avatarPrefix: serverConfig.avatarPrefix || config.avatarPrefix, pageSize: 20, - ...(serverConfig.adminEnabled && serverConfig.adminEmail - ? { adminEmail: serverConfig.adminEmail } - : {}), - ...(serverConfig.adminEnabled && serverConfig.adminBadge - ? { adminBadge: serverConfig.adminBadge } - : {}), }); widgetInstance.mount(); console.log('[CWDComments] Widget 初始化成功', config); @@ -193,7 +159,7 @@ document.addEventListener('DOMContentLoaded', () => { // 监听输入框变化,实时保存 document.addEventListener('DOMContentLoaded', () => { - const inputs = ['apiBaseUrl', 'postSlug', 'theme', 'avatarPrefix']; + const inputs = ['apiBaseUrl', 'theme']; inputs.forEach((id) => { const element = document.getElementById(id); if (element) { diff --git a/widget/src/index.js b/widget/src/index.js index f0865b9..e736cd2 100644 --- a/widget/src/index.js +++ b/widget/src/index.js @@ -8,8 +8,7 @@ * * ```