@seriphxyz/astro 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/package.json +2 -2
- package/src/Comments.astro +85 -3
- package/src/index.ts +18 -0
package/README.md
CHANGED
|
@@ -152,6 +152,49 @@ import Reactions from "@seriphxyz/astro/Reactions";
|
|
|
152
152
|
- `seriph:reaction-added` - Reaction added
|
|
153
153
|
- `seriph:reaction-removed` - Reaction removed
|
|
154
154
|
|
|
155
|
+
### Subscribe
|
|
156
|
+
|
|
157
|
+
Email subscription form with double opt-in:
|
|
158
|
+
|
|
159
|
+
```astro
|
|
160
|
+
---
|
|
161
|
+
import Subscribe from "@seriphxyz/astro/Subscribe";
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
<Subscribe
|
|
165
|
+
siteKey={import.meta.env.SERIPH_SITE_KEY}
|
|
166
|
+
buttonText="Subscribe"
|
|
167
|
+
placeholder="your@email.com"
|
|
168
|
+
/>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Props:**
|
|
172
|
+
- `siteKey` (required) - Your Seriph site key
|
|
173
|
+
- `endpoint` - Base URL (default: `https://seriph.xyz`)
|
|
174
|
+
- `buttonText` - Submit button text (default: `'Subscribe'`)
|
|
175
|
+
- `placeholder` - Email input placeholder
|
|
176
|
+
- `successMessage` - Custom success message
|
|
177
|
+
- `theme` - `'light'` | `'dark'` | `'auto'` (default: `'light'`)
|
|
178
|
+
- `class` - Additional CSS class
|
|
179
|
+
|
|
180
|
+
**Events:**
|
|
181
|
+
- `seriph:subscribed` - Subscription successful
|
|
182
|
+
|
|
183
|
+
### SubscribeForm
|
|
184
|
+
|
|
185
|
+
A more flexible subscription form that wraps your own markup:
|
|
186
|
+
|
|
187
|
+
```astro
|
|
188
|
+
---
|
|
189
|
+
import SubscribeForm from "@seriphxyz/astro/SubscribeForm";
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
<SubscribeForm siteKey={import.meta.env.SERIPH_SITE_KEY}>
|
|
193
|
+
<input name="email" type="email" placeholder="Email" required />
|
|
194
|
+
<button type="submit">Join newsletter</button>
|
|
195
|
+
</SubscribeForm>
|
|
196
|
+
```
|
|
197
|
+
|
|
155
198
|
## JavaScript API
|
|
156
199
|
|
|
157
200
|
For advanced use cases, use the JavaScript API directly:
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Astro components and content loader for Seriph widgets.
|
|
5
5
|
* Re-exports all types, API functions, and controllers from @seriphxyz/core.
|
|
6
6
|
*/
|
|
7
|
-
export { DEFAULT_ENDPOINT, API_PATH, type SeriphConfig, type Comment, type ReactionCounts, type FormSubmitResponse, type SubscribeResponse, type SeriphPost, buildUrl, getSiteKey, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, SubscribeController, FormController, ReactionsController, CommentsController, } from "@seriphxyz/core";
|
|
7
|
+
export { DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY, type SeriphConfig, type Comment, type ReactionCounts, type FormSubmitResponse, type SubscribeResponse, type SeriphPost, buildUrl, getSiteKey, getVisitorId, setVisitorId, type SubmitFormOptions, submitForm, type FetchCommentsOptions, fetchComments, type PostCommentOptions, postComment, type FetchReactionsOptions, type FetchReactionsResponse, fetchReactions, type AddReactionOptions, addReaction, type RemoveReactionOptions, removeReaction, type SubscribeOptions, subscribe, type JoinWaitlistOptions, type JoinWaitlistResponse, joinWaitlist, type ViewCountsOptions, type ViewCounts, type RecordViewResponse, getViewCounts, recordView, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, } from "@seriphxyz/core";
|
|
8
8
|
export { seriphPostsLoader, type SeriphPostsLoaderOptions, } from "./loader.js";
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// Re-export everything from core
|
|
8
8
|
export {
|
|
9
9
|
// Constants
|
|
10
|
-
DEFAULT_ENDPOINT, API_PATH,
|
|
10
|
+
DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY,
|
|
11
11
|
// Helpers
|
|
12
|
-
buildUrl, getSiteKey, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, fetchPosts, fetchPost, SubscribeController, FormController, ReactionsController, CommentsController, } from "@seriphxyz/core";
|
|
12
|
+
buildUrl, getSiteKey, getVisitorId, setVisitorId, submitForm, fetchComments, postComment, fetchReactions, addReaction, removeReaction, subscribe, joinWaitlist, getViewCounts, recordView, fetchPosts, fetchPost, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, } from "@seriphxyz/core";
|
|
13
13
|
// Re-export loader (Astro-specific)
|
|
14
14
|
export { seriphPostsLoader, } from "./loader.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seriphxyz/astro",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Astro components and content loader for Seriph widgets (forms, comments, reactions, posts)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@seriphxyz/core": "0.1.
|
|
44
|
+
"@seriphxyz/core": "0.1.7"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"astro": "^5.0.0"
|
package/src/Comments.astro
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Seriph Comments Component
|
|
4
4
|
*
|
|
5
|
-
* Displays threaded comments with a form to post new comments.
|
|
5
|
+
* Displays threaded comments with a form to post new comments and replies.
|
|
6
6
|
* Customize with CSS custom properties.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
@@ -47,6 +47,11 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
47
47
|
<div class="seriph-comments-list"></div>
|
|
48
48
|
|
|
49
49
|
<form class="seriph-comments-form">
|
|
50
|
+
<div class="seriph-reply-indicator" style="display: none;">
|
|
51
|
+
<span>Replying to <strong class="seriph-reply-to-name"></strong></span>
|
|
52
|
+
<button type="button" class="seriph-cancel-reply">Cancel</button>
|
|
53
|
+
</div>
|
|
54
|
+
<input type="hidden" name="parentId" value="" />
|
|
50
55
|
<slot name="form">
|
|
51
56
|
<div class="seriph-form-group">
|
|
52
57
|
<label for="seriph-author-name">Name</label>
|
|
@@ -76,6 +81,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
76
81
|
<span class="seriph-comment-date"></span>
|
|
77
82
|
</div>
|
|
78
83
|
<div class="seriph-comment-content"></div>
|
|
84
|
+
<button type="button" class="seriph-reply-btn">Reply</button>
|
|
79
85
|
<div class="seriph-comment-replies"></div>
|
|
80
86
|
</div>
|
|
81
87
|
</template>
|
|
@@ -98,14 +104,28 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
98
104
|
const siteKey = (container as HTMLElement).dataset.siteKey;
|
|
99
105
|
const pageId = (container as HTMLElement).dataset.pageId;
|
|
100
106
|
const list = container.querySelector(".seriph-comments-list");
|
|
101
|
-
const form = container.querySelector(".seriph-comments-form");
|
|
107
|
+
const form = container.querySelector(".seriph-comments-form") as HTMLFormElement;
|
|
102
108
|
const template = container.querySelector("#seriph-comment-template") as HTMLTemplateElement;
|
|
109
|
+
const replyIndicator = container.querySelector(".seriph-reply-indicator") as HTMLElement;
|
|
110
|
+
const replyToName = container.querySelector(".seriph-reply-to-name") as HTMLElement;
|
|
111
|
+
const cancelReplyBtn = container.querySelector(".seriph-cancel-reply") as HTMLButtonElement;
|
|
112
|
+
const parentIdInput = form?.querySelector('input[name="parentId"]') as HTMLInputElement;
|
|
103
113
|
|
|
104
114
|
if (!endpoint || !siteKey || !pageId || !list || !form || !template) return;
|
|
105
115
|
|
|
106
116
|
// Record when form was loaded (for time-based spam detection)
|
|
107
117
|
formTimestamps.set(form, Math.floor(Date.now() / 1000));
|
|
108
118
|
|
|
119
|
+
// Cancel reply mode
|
|
120
|
+
function cancelReply() {
|
|
121
|
+
if (parentIdInput) parentIdInput.value = "";
|
|
122
|
+
if (replyIndicator) replyIndicator.style.display = "none";
|
|
123
|
+
const submitBtn = form.querySelector('[type="submit"]') as HTMLButtonElement;
|
|
124
|
+
if (submitBtn) submitBtn.textContent = "Post Comment";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
cancelReplyBtn?.addEventListener("click", cancelReply);
|
|
128
|
+
|
|
109
129
|
async function loadComments() {
|
|
110
130
|
try {
|
|
111
131
|
const response = await fetch(
|
|
@@ -144,6 +164,20 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
144
164
|
).toLocaleDateString();
|
|
145
165
|
el.querySelector(".seriph-comment-content")!.textContent = comment.content;
|
|
146
166
|
|
|
167
|
+
// Add reply button handler
|
|
168
|
+
const replyBtn = el.querySelector(".seriph-reply-btn") as HTMLButtonElement;
|
|
169
|
+
replyBtn?.addEventListener("click", () => {
|
|
170
|
+
if (parentIdInput) parentIdInput.value = comment.id;
|
|
171
|
+
if (replyToName) replyToName.textContent = comment.authorName;
|
|
172
|
+
if (replyIndicator) replyIndicator.style.display = "flex";
|
|
173
|
+
const submitBtn = form.querySelector('[type="submit"]') as HTMLButtonElement;
|
|
174
|
+
if (submitBtn) submitBtn.textContent = "Post Reply";
|
|
175
|
+
// Scroll form into view and focus
|
|
176
|
+
form.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
177
|
+
const contentField = form.querySelector('textarea[name="content"]') as HTMLTextAreaElement;
|
|
178
|
+
setTimeout(() => contentField?.focus(), 300);
|
|
179
|
+
});
|
|
180
|
+
|
|
147
181
|
const repliesContainer = el.querySelector(".seriph-comment-replies")!;
|
|
148
182
|
comment.replies?.forEach((reply) => renderComment(reply, repliesContainer));
|
|
149
183
|
|
|
@@ -164,6 +198,9 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
164
198
|
// Get load timestamp for time-based spam detection
|
|
165
199
|
const loadTimestamp = formTimestamps.get(formEl);
|
|
166
200
|
|
|
201
|
+
// Get parent ID if replying
|
|
202
|
+
const parentId = formData.get("parentId") as string | null;
|
|
203
|
+
|
|
167
204
|
try {
|
|
168
205
|
const response = await fetch(
|
|
169
206
|
`${endpoint}/comments/${encodeURIComponent(pageId)}`,
|
|
@@ -177,6 +214,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
177
214
|
authorName: formData.get("authorName"),
|
|
178
215
|
authorEmail: formData.get("authorEmail") || undefined,
|
|
179
216
|
content: formData.get("content"),
|
|
217
|
+
parentId: parentId ? parseInt(parentId, 10) : undefined,
|
|
180
218
|
_gotcha: honeypot || undefined,
|
|
181
219
|
_seriph_ts: loadTimestamp,
|
|
182
220
|
}),
|
|
@@ -186,6 +224,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
186
224
|
if (!response.ok) throw new Error("Failed to post comment");
|
|
187
225
|
|
|
188
226
|
formEl.reset();
|
|
227
|
+
cancelReply(); // Reset reply mode
|
|
189
228
|
container.dispatchEvent(
|
|
190
229
|
new CustomEvent("seriph:comment-posted", {
|
|
191
230
|
detail: await response.json(),
|
|
@@ -278,12 +317,40 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
278
317
|
grid-template-columns: 1fr 1fr;
|
|
279
318
|
}
|
|
280
319
|
|
|
281
|
-
.seriph-comments-form .seriph-form-group:nth-child(
|
|
320
|
+
.seriph-comments-form .seriph-form-group:nth-child(4),
|
|
321
|
+
.seriph-comments-form .seriph-reply-indicator,
|
|
282
322
|
.seriph-comments-form button[type="submit"] {
|
|
283
323
|
grid-column: 1 / -1;
|
|
284
324
|
}
|
|
285
325
|
}
|
|
286
326
|
|
|
327
|
+
.seriph-reply-indicator {
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 0.5rem;
|
|
331
|
+
padding: 0.5rem 0.75rem;
|
|
332
|
+
background: var(--seriph-notice-bg);
|
|
333
|
+
color: var(--seriph-notice-text);
|
|
334
|
+
border-radius: 0.25rem;
|
|
335
|
+
font-size: 0.8125rem;
|
|
336
|
+
grid-column: 1 / -1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.seriph-cancel-reply {
|
|
340
|
+
margin-left: auto;
|
|
341
|
+
padding: 0.125rem 0.5rem;
|
|
342
|
+
background: transparent;
|
|
343
|
+
border: 1px solid currentColor;
|
|
344
|
+
border-radius: 0.25rem;
|
|
345
|
+
color: inherit;
|
|
346
|
+
font-size: 0.75rem;
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.seriph-cancel-reply:hover {
|
|
351
|
+
background: rgba(0, 0, 0, 0.1);
|
|
352
|
+
}
|
|
353
|
+
|
|
287
354
|
.seriph-form-group {
|
|
288
355
|
margin-bottom: 0;
|
|
289
356
|
}
|
|
@@ -368,6 +435,21 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
|
|
|
368
435
|
font-size: 0.875rem;
|
|
369
436
|
}
|
|
370
437
|
|
|
438
|
+
.seriph-reply-btn {
|
|
439
|
+
margin-top: 0.5rem;
|
|
440
|
+
padding: 0.125rem 0.5rem;
|
|
441
|
+
background: transparent;
|
|
442
|
+
border: none;
|
|
443
|
+
color: var(--seriph-text-muted);
|
|
444
|
+
font-size: 0.75rem;
|
|
445
|
+
cursor: pointer;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.seriph-reply-btn:hover {
|
|
449
|
+
color: var(--seriph-focus-color);
|
|
450
|
+
text-decoration: underline;
|
|
451
|
+
}
|
|
452
|
+
|
|
371
453
|
.seriph-comment-replies {
|
|
372
454
|
margin-left: 1rem;
|
|
373
455
|
margin-top: 0.5rem;
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
// Constants
|
|
11
11
|
DEFAULT_ENDPOINT,
|
|
12
12
|
API_PATH,
|
|
13
|
+
VISITOR_STORAGE_KEY,
|
|
13
14
|
|
|
14
15
|
// Types
|
|
15
16
|
type SeriphConfig,
|
|
@@ -22,6 +23,8 @@ export {
|
|
|
22
23
|
// Helpers
|
|
23
24
|
buildUrl,
|
|
24
25
|
getSiteKey,
|
|
26
|
+
getVisitorId,
|
|
27
|
+
setVisitorId,
|
|
25
28
|
|
|
26
29
|
// API Functions - Forms
|
|
27
30
|
type SubmitFormOptions,
|
|
@@ -35,6 +38,7 @@ export {
|
|
|
35
38
|
|
|
36
39
|
// API Functions - Reactions
|
|
37
40
|
type FetchReactionsOptions,
|
|
41
|
+
type FetchReactionsResponse,
|
|
38
42
|
fetchReactions,
|
|
39
43
|
type AddReactionOptions,
|
|
40
44
|
addReaction,
|
|
@@ -45,6 +49,18 @@ export {
|
|
|
45
49
|
type SubscribeOptions,
|
|
46
50
|
subscribe,
|
|
47
51
|
|
|
52
|
+
// API Functions - Waitlist
|
|
53
|
+
type JoinWaitlistOptions,
|
|
54
|
+
type JoinWaitlistResponse,
|
|
55
|
+
joinWaitlist,
|
|
56
|
+
|
|
57
|
+
// API Functions - Views
|
|
58
|
+
type ViewCountsOptions,
|
|
59
|
+
type ViewCounts,
|
|
60
|
+
type RecordViewResponse,
|
|
61
|
+
getViewCounts,
|
|
62
|
+
recordView,
|
|
63
|
+
|
|
48
64
|
// API Functions - Posts
|
|
49
65
|
type FetchPostsOptions,
|
|
50
66
|
fetchPosts,
|
|
@@ -58,7 +74,9 @@ export {
|
|
|
58
74
|
type FormState,
|
|
59
75
|
type ReactionsState,
|
|
60
76
|
type CommentsState,
|
|
77
|
+
type WaitlistState,
|
|
61
78
|
SubscribeController,
|
|
79
|
+
WaitlistController,
|
|
62
80
|
FormController,
|
|
63
81
|
ReactionsController,
|
|
64
82
|
CommentsController,
|