@jamwidgets/astro 0.4.2 → 0.5.1

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  > **Note:** This repo is a read-only mirror. Source lives in a private monorepo.
4
4
  > For issues/PRs, please open them here and we'll sync changes back.
5
5
 
6
- Astro components and content loader for [JamWidgets](https://jamwidgets.com) - widgets for static sites.
6
+ Astro components and content loader for [Jamwidgets](https://jamwidgets.com) - widgets for static sites.
7
7
 
8
8
  ## Installation
9
9
 
@@ -13,7 +13,7 @@ npm install @jamwidgets/astro
13
13
 
14
14
  ## Setup
15
15
 
16
- Add your JamWidgets site key to your `.env`:
16
+ Add your Jamwidgets site key to your `.env`:
17
17
 
18
18
  ```
19
19
  JAMWIDGETS_SITE_KEY=your_site_key_here
@@ -22,7 +22,7 @@ SITE_URL=https://example.com
22
22
 
23
23
  ## Content Loader (Posts)
24
24
 
25
- Fetch posts from JamWidgets at build time using Astro's content collections:
25
+ Fetch posts from Jamwidgets at build time using Astro's content collections:
26
26
 
27
27
  ```ts
28
28
  // src/content.config.ts
@@ -39,7 +39,7 @@ const posts = defineCollection({
39
39
  export const collections = { posts };
40
40
  ```
41
41
 
42
- Set `origin` to an allowed origin for production builds. JamWidgets rejects the
42
+ Set `origin` to an allowed origin for production builds. Jamwidgets rejects the
43
43
  request when the site restricts origins and the loader omits it.
44
44
 
45
45
  Then use in your pages:
@@ -58,11 +58,54 @@ const posts = await getCollection("posts");
58
58
  ))}
59
59
  ```
60
60
 
61
+ Render a post's Markdown body with Astro's content component:
62
+
63
+ ```astro
64
+ ---
65
+ import { getEntry, render } from "astro:content";
66
+ import { Image } from "astro:assets";
67
+ const post = await getEntry("posts", "hello-world");
68
+ if (!post) throw new Error("Post not found");
69
+ const { Content } = await render(post);
70
+ ---
71
+
72
+ {post.data.coverImage && (
73
+ <Image
74
+ src={post.data.coverImage}
75
+ inferSize
76
+ layout="full-width"
77
+ alt={post.data.title}
78
+ />
79
+ )}
80
+ <article><Content /></article>
81
+ ```
82
+
83
+ `post.data.content` is Markdown, not an HTML string. Do not render it with
84
+ `set:html`; use Astro's `render()` and `<Content />` as above.
85
+
86
+ For responsive uploaded images, authorize Jamwidgets' asset host in your Astro
87
+ config. Astro will then generate correctly sized image variants for Markdown
88
+ images. The hostname below is for Jamwidgets Cloud; self-hosted deployments use
89
+ the hostname configured by `ASSET_BASE_URL` (or fall back to
90
+ `<AWS_S3_BUCKET>.s3.<AWS_S3_REGION>.amazonaws.com`):
91
+
92
+ ```js
93
+ import { defineConfig } from "astro/config";
94
+
95
+ export default defineConfig({
96
+ image: {
97
+ domains: ["assets.jamwidgets.com"],
98
+ layout: "constrained",
99
+ responsiveStyles: true,
100
+ },
101
+ });
102
+ ```
103
+
61
104
  ### Loader Options
62
105
 
63
106
  ```ts
64
107
  jamwidgetsPostsLoader({
65
- siteKey: string; // Required - your JamWidgets site key
108
+ siteKey: string; // Required - your Jamwidgets site key
66
109
  endpoint?: string; // Default: 'https://jamwidgets.com'
67
110
  origin?: string; // Site URL used for allowed-origin validation
68
111
  tag?: string; // Filter posts by tag
@@ -91,8 +134,8 @@ import Form from "@jamwidgets/astro/Form";
91
134
  ```
92
135
 
93
136
  **Props:**
94
- - `siteKey` (required) - Your JamWidgets site key
95
- - `formSlug` (required) - The form slug as configured in JamWidgets
137
+ - `siteKey` (required) - Your Jamwidgets site key
138
+ - `formSlug` (required) - The form slug as configured in Jamwidgets
96
139
  - `endpoint` - Base URL (default: `https://jamwidgets.com`)
97
140
  - `theme` - `'light'` | `'dark'` | `'auto'` (default: `'light'`)
98
141
  - `class` - Additional CSS class
@@ -118,7 +161,7 @@ import Comments from "@jamwidgets/astro/Comments";
118
161
  ```
119
162
 
120
163
  **Props:**
121
- - `siteKey` (required) - Your JamWidgets site key
164
+ - `siteKey` (required) - Your Jamwidgets site key
122
165
  - `pageId` (required) - Unique page identifier (e.g., URL path)
123
166
  - `endpoint` - Base URL (default: `https://jamwidgets.com`)
124
167
  - `theme` - `'light'` | `'dark'` | `'auto'` (default: `'light'`)
@@ -144,7 +187,7 @@ import Reactions from "@jamwidgets/astro/Reactions";
144
187
  ```
145
188
 
146
189
  **Props:**
147
- - `siteKey` (required) - Your JamWidgets site key
190
+ - `siteKey` (required) - Your Jamwidgets site key
148
191
  - `pageId` (required) - Unique page identifier
149
192
  - `reactions` - Array of reaction types (default: `['like']`)
150
193
  - `icons` - Custom icons: `{ like: '👍', love: '❤️' }`
@@ -175,7 +218,7 @@ import Subscribe from "@jamwidgets/astro/Subscribe";
175
218
  ```
176
219
 
177
220
  **Props:**
178
- - `siteKey` (required) - Your JamWidgets site key
221
+ - `siteKey` (required) - Your Jamwidgets site key
179
222
  - `endpoint` - Base URL (default: `https://jamwidgets.com`)
180
223
  - `buttonText` - Submit button text (default: `'Subscribe'`)
181
224
  - `placeholder` - Email input placeholder
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Astro components and content loader for Jamwidgets.
5
5
  * Re-exports all types, API functions, and controllers from @jamwidgets/core.
6
6
  */
7
- export { DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY, type JamWidgetsConfig, type SeriphConfig, // deprecated alias
7
+ export { DEFAULT_ENDPOINT, API_PATH, VISITOR_STORAGE_KEY, type JamwidgetsConfig, type SeriphConfig, // deprecated alias
8
8
  type Comment, type ReactionCounts, type FormSubmitResponse, type SubscribeResponse, type JamwidgetsPost, type SeriphPost, // deprecated alias
9
9
  type Announcement, type AnnouncementType, type Poll, type PollOption, type PollSettings, type PollWithResults, type ShowResultsMode, type FeedbackType, 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 SubmitFeedbackOptions, type SubmitFeedbackResponse, submitFeedback, type FetchPollOptions, fetchPoll, type VotePollOptions, type VotePollResponse, votePoll, type FetchAnnouncementsOptions, fetchAnnouncements, type DismissAnnouncementOptions, dismissAnnouncement, type FetchPostsOptions, fetchPosts, type FetchPostOptions, fetchPost, type SiteConfig, fetchSiteConfig, POWERED_BY_URL, POWERED_BY_LABEL, type ControllerStatus, type ControllerListener, type SubscribeState, type FormState, type ReactionsState, type CommentsState, type WaitlistState, type ViewCountsState, type FeedbackState, type PollState, type AnnouncementsState, SubscribeController, WaitlistController, FormController, ReactionsController, CommentsController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, } from "@jamwidgets/core";
10
10
  export { jamwidgetsPostsLoader, seriphPostsLoader, // deprecated alias
package/dist/loader.js CHANGED
@@ -30,7 +30,7 @@ export function jamwidgetsPostsLoader(options) {
30
30
  return {
31
31
  name: "jamwidgets-posts-loader",
32
32
  async load(context) {
33
- const { store, logger } = context;
33
+ const { store, logger, renderMarkdown } = context;
34
34
  try {
35
35
  const url = new URL(`${baseUrl}/posts`);
36
36
  url.searchParams.set("limit", String(limit));
@@ -50,12 +50,15 @@ export function jamwidgetsPostsLoader(options) {
50
50
  throw new Error(`Failed to fetch posts: ${response.status} ${response.statusText}`);
51
51
  }
52
52
  const data = await response.json();
53
+ const entries = await Promise.all(data.posts.map(async (post) => ({
54
+ id: post.slug,
55
+ data: { ...post },
56
+ body: post.content,
57
+ rendered: await renderMarkdown(post.content),
58
+ })));
53
59
  store.clear();
54
- for (const post of data.posts) {
55
- store.set({
56
- id: post.slug,
57
- data: { ...post },
58
- });
60
+ for (const entry of entries) {
61
+ store.set(entry);
59
62
  }
60
63
  logger.info(`Loaded ${data.posts.length} posts from Jamwidgets`);
61
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamwidgets/astro",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Astro components and content loader for Jamwidgets (forms, comments, reactions, posts)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -57,10 +57,10 @@
57
57
  ],
58
58
  "license": "MIT",
59
59
  "dependencies": {
60
- "@jamwidgets/core": "^0.3.4"
60
+ "@jamwidgets/core": "^0.4.0"
61
61
  },
62
62
  "peerDependencies": {
63
- "astro": "^5.0.0 || ^6.0.0 || ^7.0.0"
63
+ "astro": "^5.9.0 || ^6.0.0 || ^7.0.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "astro": "^7.1.3",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Announcements Component
3
+ * Jamwidgets Announcements Component
4
4
  *
5
5
  * Displays site-wide announcements/banners.
6
6
  * Supports dismissible announcements with localStorage persistence.
@@ -12,7 +12,7 @@
12
12
  interface Props {
13
13
  /** Your site key (required) */
14
14
  siteKey: string;
15
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
15
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
16
16
  endpoint?: string;
17
17
  /** Theme preset. Defaults to inheriting the host page. */
18
18
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -73,8 +73,8 @@ import Theme from "./Theme.astro";
73
73
 
74
74
  const visitorId = getVisitorId();
75
75
  const headers = {
76
- "X-JamWidgets-Key": siteKey,
77
- "X-JamWidgets-Visitor": visitorId,
76
+ "X-Jamwidgets-Key": siteKey,
77
+ "X-Jamwidgets-Visitor": visitorId,
78
78
  };
79
79
 
80
80
  async function loadAnnouncements() {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Comments Component
3
+ * Jamwidgets Comments Component
4
4
  *
5
5
  * Displays threaded comments with a form to post new comments and replies.
6
6
  * Customize with CSS custom properties.
@@ -14,7 +14,7 @@ interface Props {
14
14
  siteKey: string;
15
15
  /** Unique identifier for this page (e.g., slug or URL path) */
16
16
  pageId: string;
17
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
17
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
18
18
  endpoint?: string;
19
19
  /** Theme preset. Defaults to inheriting the host page. */
20
20
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -124,8 +124,8 @@ import Theme from "./Theme.astro";
124
124
  const visitorId = getVisitorId();
125
125
  const headers = {
126
126
  ...getAttributionHeaders(pageId),
127
- "X-JamWidgets-Key": siteKey,
128
- "X-JamWidgets-Visitor": visitorId,
127
+ "X-Jamwidgets-Key": siteKey,
128
+ "X-Jamwidgets-Visitor": visitorId,
129
129
  };
130
130
 
131
131
  // Record when form was loaded (for time-based spam detection)
package/src/Embed.astro CHANGED
@@ -1,20 +1,21 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Embed Component
3
+ * Jamwidgets Embed Component
4
4
  *
5
5
  * Hydrates widget placeholders embedded in post content.
6
6
  * Place this component after your post content to activate
7
7
  * any embedded polls, reactions, etc.
8
8
  *
9
9
  * @example
10
- * <article set:html={post.content} />
10
+ * const { Content } = await render(post);
11
+ * <article><Content /></article>
11
12
  * <Embed siteKey={import.meta.env.JAMWIDGETS_SITE_KEY} />
12
13
  */
13
14
 
14
15
  interface Props {
15
16
  /** Your site key (required) */
16
17
  siteKey: string;
17
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
18
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
18
19
  endpoint?: string;
19
20
  /** Theme preset. Defaults to inheriting the host page. */
20
21
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -74,7 +75,7 @@ import Theme from "./Theme.astro";
74
75
  return id;
75
76
  }
76
77
 
77
- class JamWidgetsEmbed extends HTMLElement {
78
+ class JamwidgetsEmbed extends HTMLElement {
78
79
  connectedCallback() {
79
80
  const siteKey = this.dataset.siteKey!;
80
81
  const endpoint = this.dataset.endpoint!;
@@ -124,8 +125,8 @@ import Theme from "./Theme.astro";
124
125
  ) {
125
126
  const visitorId = getVisitorId();
126
127
  const headers = {
127
- "X-JamWidgets-Key": config.siteKey,
128
- "X-JamWidgets-Visitor": visitorId,
128
+ "X-Jamwidgets-Key": config.siteKey,
129
+ "X-Jamwidgets-Visitor": visitorId,
129
130
  };
130
131
 
131
132
  const loadingEl = container.querySelector(".jamwidgets-poll-loading") as HTMLElement;
@@ -313,7 +314,7 @@ import Theme from "./Theme.astro";
313
314
  }
314
315
  }
315
316
 
316
- customElements.define("jamwidgets-embed", JamWidgetsEmbed);
317
+ customElements.define("jamwidgets-embed", JamwidgetsEmbed);
317
318
  </script>
318
319
 
319
320
  <style is:global>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Feedback Component
3
+ * Jamwidgets Feedback Component
4
4
  *
5
5
  * Displays a feedback form for collecting user suggestions.
6
6
  * Supports different feedback types (bug, feature, improvement, other).
@@ -12,7 +12,7 @@
12
12
  interface Props {
13
13
  /** Your site key (required) */
14
14
  siteKey: string;
15
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
15
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
16
16
  endpoint?: string;
17
17
  /** Theme preset. Defaults to inheriting the host page. */
18
18
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -109,8 +109,8 @@ import Theme from "./Theme.astro";
109
109
 
110
110
  const visitorId = getVisitorId();
111
111
  const headers = {
112
- "X-JamWidgets-Key": siteKey,
113
- "X-JamWidgets-Visitor": visitorId,
112
+ "X-Jamwidgets-Key": siteKey,
113
+ "X-Jamwidgets-Visitor": visitorId,
114
114
  "Content-Type": "application/json",
115
115
  };
116
116
 
package/src/Form.astro CHANGED
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Form Component
3
+ * Jamwidgets Form Component
4
4
  *
5
- * A wrapper component that handles form submission to JamWidgets.
5
+ * A wrapper component that handles form submission to Jamwidgets.
6
6
  * You provide your own form fields via the default slot.
7
7
  *
8
8
  * @example
@@ -17,9 +17,9 @@
17
17
  interface Props {
18
18
  /** Your site key (required) */
19
19
  siteKey: string;
20
- /** The form slug as configured in JamWidgets (required) */
20
+ /** The form slug as configured in Jamwidgets (required) */
21
21
  formSlug: string;
22
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
22
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
23
23
  endpoint?: string;
24
24
  /** Theme preset. Defaults to inheriting the host page. */
25
25
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -101,7 +101,7 @@ import Theme from "./Theme.astro";
101
101
  if (!siteKey) {
102
102
  missing.push("siteKey (check JAMWIDGETS_SITE_KEY in your .env)");
103
103
  }
104
- console.error(`JamWidgets form missing required props: ${missing.join(", ")}`);
104
+ console.error(`Jamwidgets form missing required props: ${missing.join(", ")}`);
105
105
  return;
106
106
  }
107
107
 
@@ -138,8 +138,8 @@ import Theme from "./Theme.astro";
138
138
  headers: {
139
139
  ...getAttributionHeaders(),
140
140
  "Content-Type": "application/json",
141
- "X-JamWidgets-Key": siteKey,
142
- "X-JamWidgets-Visitor": getVisitorId(),
141
+ "X-Jamwidgets-Key": siteKey,
142
+ "X-Jamwidgets-Visitor": getVisitorId(),
143
143
  },
144
144
  body: JSON.stringify(data),
145
145
  });
package/src/Poll.astro CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Poll Component
3
+ * Jamwidgets Poll Component
4
4
  *
5
5
  * Displays an interactive poll with voting.
6
6
  * Results are shown based on poll settings.
@@ -17,7 +17,7 @@ interface Props {
17
17
  siteKey: string;
18
18
  /** The poll slug (required) */
19
19
  slug: string;
20
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
20
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
21
21
  endpoint?: string;
22
22
  /** Theme preset. Defaults to inheriting the host page. */
23
23
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -109,8 +109,8 @@ import Theme from "./Theme.astro";
109
109
 
110
110
  const visitorId = getVisitorId();
111
111
  const headers = {
112
- "X-JamWidgets-Key": siteKey,
113
- "X-JamWidgets-Visitor": visitorId,
112
+ "X-Jamwidgets-Key": siteKey,
113
+ "X-Jamwidgets-Visitor": visitorId,
114
114
  };
115
115
 
116
116
  const loadingEl = container.querySelector(".jamwidgets-poll-loading") as HTMLElement;
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets "Powered by" footer.
3
+ * Jamwidgets "Powered by" footer.
4
4
  *
5
5
  * Rendered at the bottom of a drop-in widget. It hydrates hidden and only
6
- * reveals itself when the site is on the Free tier (no custom branding) the
7
- * gate is the `hasCustomBranding` flag from `GET /api/v1/site`. Paid tiers
8
- * Team sites keep it hidden, and if the config fetch fails we stay hidden so a
6
+ * reveals itself when the site is on the Free tier the gate is the
7
+ * `showPoweredBy` flag from `GET /api/v1/site`. Team sites keep it hidden, and
8
+ * if the config fetch fails we stay hidden so a
9
9
  * paying site is never wrongly branded.
10
10
  *
11
11
  * Included by the block widgets (Comments, Form, Subscribe, Poll, Feedback,
@@ -14,7 +14,7 @@
14
14
  interface Props {
15
15
  /** Your site key (required) */
16
16
  siteKey: string;
17
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
17
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
18
18
  endpoint?: string;
19
19
  }
20
20
 
@@ -27,41 +27,41 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
27
27
 
28
28
  <div
29
29
  class="jamwidgets-powered-by"
30
- data-jamwidgets-branding
30
+ data-jamwidgets-powered-by
31
31
  data-endpoint={baseUrl}
32
32
  data-site-key={siteKey}
33
33
  hidden
34
34
  >
35
35
  <a href="https://jamwidgets.com/?utm_source=powered_by" target="_blank" rel="noopener noreferrer">
36
- Powered by JamWidgets
36
+ Powered by Jamwidgets
37
37
  </a>
38
38
  </div>
39
39
 
40
40
  <script>
41
- // Reveal the footer only for sites without custom branding (Free tier).
41
+ // Reveal the footer only when the site config explicitly enables it.
42
42
  // Site config is fetched once per (endpoint, siteKey); on any failure we
43
43
  // leave the footer hidden (fail closed — never brand a paying site).
44
- const brandingCache = new Map<string, Promise<boolean>>();
44
+ const poweredByCache = new Map<string, Promise<boolean>>();
45
45
 
46
- function hasCustomBranding(baseUrl: string, siteKey: string): Promise<boolean> {
46
+ function showsPoweredBy(baseUrl: string, siteKey: string): Promise<boolean> {
47
47
  const key = `${baseUrl}::${siteKey}`;
48
- let pending = brandingCache.get(key);
48
+ let pending = poweredByCache.get(key);
49
49
  if (!pending) {
50
- pending = fetch(`${baseUrl}/site`, { headers: { "X-JamWidgets-Key": siteKey } })
50
+ pending = fetch(`${baseUrl}/site`, { headers: { "X-Jamwidgets-Key": siteKey } })
51
51
  .then((res) => (res.ok ? res.json() : null))
52
- .then((data) => data?.site_config?.hasCustomBranding === true)
53
- .catch(() => true);
54
- brandingCache.set(key, pending);
52
+ .then((data) => data?.site_config?.showPoweredBy === true)
53
+ .catch(() => false);
54
+ poweredByCache.set(key, pending);
55
55
  }
56
56
  return pending;
57
57
  }
58
58
 
59
- document.querySelectorAll<HTMLElement>("[data-jamwidgets-branding]").forEach((el) => {
59
+ document.querySelectorAll<HTMLElement>("[data-jamwidgets-powered-by]").forEach((el) => {
60
60
  const baseUrl = el.dataset.endpoint;
61
61
  const siteKey = el.dataset.siteKey;
62
62
  if (!baseUrl || !siteKey) return;
63
- hasCustomBranding(baseUrl, siteKey).then((custom) => {
64
- if (!custom) el.removeAttribute("hidden");
63
+ showsPoweredBy(baseUrl, siteKey).then((show) => {
64
+ if (show) el.removeAttribute("hidden");
65
65
  });
66
66
  });
67
67
  </script>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Reactions Component
3
+ * Jamwidgets Reactions Component
4
4
  *
5
5
  * Displays reaction buttons (like, clap, etc.) for a page.
6
6
  * Customize with CSS custom properties.
@@ -18,7 +18,7 @@ interface Props {
18
18
  siteKey: string;
19
19
  /** Unique identifier for this page (e.g., slug or URL path) */
20
20
  pageId: string;
21
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
21
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
22
22
  endpoint?: string;
23
23
  /** Reaction types to show (default: ['like']) */
24
24
  reactions?: string[];
@@ -102,8 +102,8 @@ import Theme from "./Theme.astro";
102
102
  const visitorId = getVisitorId();
103
103
  const headers = {
104
104
  ...getAttributionHeaders(pageId),
105
- "X-JamWidgets-Key": siteKey,
106
- "X-JamWidgets-Visitor": visitorId,
105
+ "X-Jamwidgets-Key": siteKey,
106
+ "X-Jamwidgets-Visitor": visitorId,
107
107
  };
108
108
 
109
109
  // Track which reactions the user has made (stored in localStorage)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Subscribe Component
3
+ * Jamwidgets Subscribe Component
4
4
  *
5
5
  * A subscription form for email updates. Implements double opt-in -
6
6
  * users receive a confirmation email and must click to confirm.
@@ -20,7 +20,7 @@
20
20
  interface Props {
21
21
  /** Your site key (required) */
22
22
  siteKey: string;
23
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
23
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
24
24
  endpoint?: string;
25
25
  /** Submit button text (default: 'Subscribe') */
26
26
  buttonText?: string;
@@ -117,7 +117,7 @@ import Theme from "./Theme.astro";
117
117
  const inputEl = formEl.querySelector('input[name="email"]') as HTMLInputElement | null;
118
118
 
119
119
  if (!endpoint || !siteKey) {
120
- console.error("JamWidgets Subscribe: missing siteKey or endpoint");
120
+ console.error("Jamwidgets Subscribe: missing siteKey or endpoint");
121
121
  return;
122
122
  }
123
123
 
@@ -143,8 +143,8 @@ import Theme from "./Theme.astro";
143
143
  method: "POST",
144
144
  headers: {
145
145
  "Content-Type": "application/json",
146
- "X-JamWidgets-Key": siteKey,
147
- "X-JamWidgets-Visitor": getVisitorId(),
146
+ "X-Jamwidgets-Key": siteKey,
147
+ "X-Jamwidgets-Visitor": getVisitorId(),
148
148
  },
149
149
  body: JSON.stringify(data),
150
150
  });
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Headless Subscribe Form
3
+ * Jamwidgets Headless Subscribe Form
4
4
  *
5
5
  * A headless subscription form that handles API/state but lets you provide your own UI.
6
6
  * Use the default slot to provide your own email input and submit button.
@@ -34,7 +34,7 @@
34
34
  interface Props {
35
35
  /** Your site key (required) */
36
36
  siteKey: string;
37
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
37
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
38
38
  endpoint?: string;
39
39
  /** HTML id attribute for the form */
40
40
  id?: string;
@@ -87,7 +87,7 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
87
87
  const siteKey = formEl.dataset.siteKey;
88
88
 
89
89
  if (!endpoint || !siteKey) {
90
- console.error("JamWidgets SubscribeForm: missing siteKey or endpoint");
90
+ console.error("Jamwidgets SubscribeForm: missing siteKey or endpoint");
91
91
  return;
92
92
  }
93
93
 
@@ -107,8 +107,8 @@ const baseUrl = endpoint.replace(/\/+$/, "") + API_PATH;
107
107
  headers: {
108
108
  ...getAttributionHeaders(),
109
109
  "Content-Type": "application/json",
110
- "X-JamWidgets-Key": siteKey,
111
- "X-JamWidgets-Visitor": getVisitorId(),
110
+ "X-Jamwidgets-Key": siteKey,
111
+ "X-Jamwidgets-Visitor": getVisitorId(),
112
112
  },
113
113
  body: JSON.stringify(data),
114
114
  });
package/src/Views.astro CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Views Component
3
+ * Jamwidgets Views Component
4
4
  *
5
5
  * Tracks page views and displays view count.
6
6
  * Automatically records a view when the page loads.
@@ -17,7 +17,7 @@ interface Props {
17
17
  siteKey: string;
18
18
  /** Unique identifier for this page (e.g., slug or URL path) */
19
19
  pageId: string;
20
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
20
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
21
21
  endpoint?: string;
22
22
  /** Show unique visitor count alongside total views (default: false) */
23
23
  showUnique?: boolean;
@@ -86,8 +86,8 @@ import Theme from "./Theme.astro";
86
86
 
87
87
  const visitorId = getVisitorId();
88
88
  const headers = {
89
- "X-JamWidgets-Key": siteKey,
90
- "X-JamWidgets-Visitor": visitorId,
89
+ "X-Jamwidgets-Key": siteKey,
90
+ "X-Jamwidgets-Visitor": visitorId,
91
91
  };
92
92
 
93
93
  const countEl = container.querySelector(".jamwidgets-views-count");
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /**
3
- * JamWidgets Waitlist Component
3
+ * Jamwidgets Waitlist Component
4
4
  *
5
5
  * Displays a waitlist signup form for collecting emails before launch.
6
6
  *
@@ -11,7 +11,7 @@
11
11
  interface Props {
12
12
  /** Your site key (required) */
13
13
  siteKey: string;
14
- /** Base URL of your JamWidgets instance (default: 'https://jamwidgets.com') */
14
+ /** Base URL of your Jamwidgets instance (default: 'https://jamwidgets.com') */
15
15
  endpoint?: string;
16
16
  /** Theme preset. Defaults to inheriting the host page. */
17
17
  theme?: "inherit" | "light" | "dark" | "auto";
@@ -91,8 +91,8 @@ import Theme from "./Theme.astro";
91
91
 
92
92
  const visitorId = getVisitorId();
93
93
  const headers = {
94
- "X-JamWidgets-Key": siteKey,
95
- "X-JamWidgets-Visitor": visitorId,
94
+ "X-Jamwidgets-Key": siteKey,
95
+ "X-Jamwidgets-Visitor": visitorId,
96
96
  "Content-Type": "application/json",
97
97
  };
98
98
 
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@ export {
13
13
  VISITOR_STORAGE_KEY,
14
14
 
15
15
  // Types
16
- type JamWidgetsConfig,
16
+ type JamwidgetsConfig,
17
17
  type SeriphConfig, // deprecated alias
18
18
  type Comment,
19
19
  type ReactionCounts,
@@ -95,7 +95,7 @@ export {
95
95
  type FetchPostOptions,
96
96
  fetchPost,
97
97
 
98
- // Site config + branding
98
+ // Site config + powered-by attribution
99
99
  type SiteConfig,
100
100
  fetchSiteConfig,
101
101
  POWERED_BY_URL,
package/src/loader.ts CHANGED
@@ -78,7 +78,7 @@ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): Lo
78
78
  name: "jamwidgets-posts-loader",
79
79
 
80
80
  async load(context: LoaderContext) {
81
- const { store, logger } = context;
81
+ const { store, logger, renderMarkdown } = context;
82
82
 
83
83
  try {
84
84
  const url = new URL(`${baseUrl}/posts`);
@@ -106,13 +106,18 @@ export function jamwidgetsPostsLoader(options: JamwidgetsPostsLoaderOptions): Lo
106
106
 
107
107
  const data: ApiResponse = await response.json();
108
108
 
109
- store.clear();
110
-
111
- for (const post of data.posts) {
112
- store.set({
109
+ const entries = await Promise.all(
110
+ data.posts.map(async (post) => ({
113
111
  id: post.slug,
114
112
  data: { ...post },
115
- });
113
+ body: post.content,
114
+ rendered: await renderMarkdown(post.content),
115
+ })),
116
+ );
117
+
118
+ store.clear();
119
+ for (const entry of entries) {
120
+ store.set(entry);
116
121
  }
117
122
 
118
123
  logger.info(`Loaded ${data.posts.length} posts from Jamwidgets`);