feat: ship blog platform admin and deploy stack
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { API_BASE_URL, apiClient } from '../lib/api/client';
|
||||
import { apiClient, resolvePublicApiBaseUrl } from '../lib/api/client';
|
||||
import { getI18n } from '../lib/i18n';
|
||||
import type { Comment } from '../lib/api/client';
|
||||
|
||||
@@ -10,6 +10,7 @@ interface Props {
|
||||
|
||||
const { postSlug, class: className = '' } = Astro.props;
|
||||
const { locale, t } = getI18n(Astro);
|
||||
const publicApiBaseUrl = resolvePublicApiBaseUrl(Astro.url);
|
||||
|
||||
let comments: Comment[] = [];
|
||||
let error: string | null = null;
|
||||
@@ -35,7 +36,7 @@ function formatCommentDate(dateStr: string): string {
|
||||
}
|
||||
---
|
||||
|
||||
<div class={`terminal-comments ${className}`} data-post-slug={postSlug} data-api-base={API_BASE_URL}>
|
||||
<div class={`terminal-comments ${className}`} data-post-slug={postSlug} data-api-base={publicApiBaseUrl}>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="space-y-3">
|
||||
<span class="terminal-kicker">
|
||||
@@ -104,6 +105,35 @@ function formatCommentDate(dateStr: string): string {
|
||||
<p class="mt-2 text-right text-xs text-[var(--text-tertiary)]">{t('comments.maxChars')}</p>
|
||||
</div>
|
||||
|
||||
<div class="hidden" aria-hidden="true">
|
||||
<label>
|
||||
Website
|
||||
<input type="text" name="website" tabindex="-1" autocomplete="off" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-[var(--border-color)] bg-[var(--header-bg)]/60 px-4 py-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-[var(--text-tertiary)]">
|
||||
验证码
|
||||
</p>
|
||||
<button type="button" id="refresh-captcha" class="terminal-action-button px-3 py-2 text-xs">
|
||||
<i class="fas fa-rotate-right"></i>
|
||||
<span>刷新</span>
|
||||
</button>
|
||||
</div>
|
||||
<p id="captcha-question" class="mt-2 text-sm text-[var(--text-secondary)]">加载中...</p>
|
||||
<input type="hidden" name="captchaToken" />
|
||||
<input
|
||||
type="text"
|
||||
name="captchaAnswer"
|
||||
required
|
||||
inputmode="numeric"
|
||||
placeholder="请输入上方答案"
|
||||
class="mt-3 terminal-form-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="replying-to" class="terminal-panel-muted hidden items-center justify-between gap-3 py-3">
|
||||
<span class="text-sm text-[var(--text-secondary)]">
|
||||
{t('common.reply')} -> <span id="reply-target" class="font-medium text-[var(--primary)]"></span>
|
||||
@@ -209,8 +239,12 @@ function formatCommentDate(dateStr: string): string {
|
||||
const cancelReply = document.getElementById('cancel-reply');
|
||||
const replyBtns = document.querySelectorAll('.reply-btn');
|
||||
const messageBox = document.getElementById('comment-message');
|
||||
const captchaQuestion = document.getElementById('captcha-question');
|
||||
const refreshCaptchaBtn = document.getElementById('refresh-captcha');
|
||||
const postSlug = wrapper?.getAttribute('data-post-slug') || '';
|
||||
const apiBase = wrapper?.getAttribute('data-api-base') || '/api';
|
||||
const captchaTokenInput = form?.querySelector('input[name=\"captchaToken\"]') as HTMLInputElement | null;
|
||||
const captchaAnswerInput = form?.querySelector('input[name=\"captchaAnswer\"]') as HTMLInputElement | null;
|
||||
|
||||
function showMessage(message: string, type: 'success' | 'error' | 'info') {
|
||||
if (!messageBox) return;
|
||||
@@ -251,6 +285,37 @@ function formatCommentDate(dateStr: string): string {
|
||||
replyingTo?.removeAttribute('data-reply-to');
|
||||
}
|
||||
|
||||
async function loadCaptcha(showError = true) {
|
||||
if (!captchaQuestion || !captchaTokenInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
captchaQuestion.textContent = '加载中...';
|
||||
captchaTokenInput.value = '';
|
||||
if (captchaAnswerInput) {
|
||||
captchaAnswerInput.value = '';
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiBase}/comments/captcha`);
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
const payload = await response.json() as { token?: string; question?: string };
|
||||
captchaTokenInput.value = payload.token || '';
|
||||
captchaQuestion.textContent = payload.question || '请刷新验证码';
|
||||
} catch (error) {
|
||||
captchaQuestion.textContent = '验证码加载失败,请刷新重试';
|
||||
if (showError) {
|
||||
showMessage(
|
||||
t('comments.submitFailed', { message: error instanceof Error ? error.message : t('common.unknownError') }),
|
||||
'error'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleBtn?.addEventListener('click', () => {
|
||||
formContainer?.classList.toggle('hidden');
|
||||
if (!formContainer?.classList.contains('hidden')) {
|
||||
@@ -285,6 +350,10 @@ function formatCommentDate(dateStr: string): string {
|
||||
resetReply();
|
||||
});
|
||||
|
||||
refreshCaptchaBtn?.addEventListener('click', () => {
|
||||
void loadCaptcha(false);
|
||||
});
|
||||
|
||||
form?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -306,6 +375,9 @@ function formatCommentDate(dateStr: string): string {
|
||||
content: formData.get('content'),
|
||||
scope: 'article',
|
||||
replyToCommentId: replyToId ? Number(replyToId) : null,
|
||||
captchaToken: formData.get('captchaToken'),
|
||||
captchaAnswer: formData.get('captchaAnswer'),
|
||||
website: formData.get('website'),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -318,8 +390,10 @@ function formatCommentDate(dateStr: string): string {
|
||||
resetReply();
|
||||
formContainer?.classList.add('hidden');
|
||||
showMessage(t('comments.submitSuccess'), 'success');
|
||||
void loadCaptcha(false);
|
||||
} catch (error) {
|
||||
showMessage(t('comments.submitFailed', { message: error instanceof Error ? error.message : t('common.unknownError') }), 'error');
|
||||
void loadCaptcha(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -335,4 +409,6 @@ function formatCommentDate(dateStr: string): string {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
void loadCaptcha(false);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user