@jamwidgets/astro 0.6.1 → 0.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jamwidgets/astro",
3
- "version": "0.6.1",
3
+ "version": "0.6.4",
4
4
  "description": "Astro components and content loaders for Jamwidgets",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,7 +60,7 @@
60
60
  ],
61
61
  "license": "MIT",
62
62
  "dependencies": {
63
- "@jamwidgets/core": "^0.5.1"
63
+ "@jamwidgets/core": "^0.5.3"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "astro": "^5.9.0 || ^6.0.0 || ^7.0.0"
@@ -54,16 +54,7 @@ import Theme from "./Theme.astro";
54
54
  isDismissible: boolean;
55
55
  }
56
56
 
57
- // Get or create a visitor ID for anonymous users
58
- const VISITOR_KEY = "jamwidgets_visitor_id";
59
- function getVisitorId(): string {
60
- let id = localStorage.getItem(VISITOR_KEY);
61
- if (!id) {
62
- id = crypto.randomUUID();
63
- localStorage.setItem(VISITOR_KEY, id);
64
- }
65
- return id;
66
- }
57
+ import { getVisitorId } from "@jamwidgets/core";
67
58
 
68
59
  document.querySelectorAll("[data-jamwidgets-announcements]").forEach((container) => {
69
60
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -89,6 +80,16 @@ import Theme from "./Theme.astro";
89
80
  }
90
81
  }
91
82
 
83
+ function safeLinkUrl(value?: string): string | null {
84
+ if (!value) return null;
85
+ try {
86
+ const url = new URL(value, window.location.href);
87
+ return url.protocol === "http:" || url.protocol === "https:" ? url.href : null;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
92
93
  function renderAnnouncements(announcements: Announcement[]) {
93
94
  container.innerHTML = "";
94
95
 
@@ -97,25 +98,31 @@ import Theme from "./Theme.astro";
97
98
  el.className = `jamwidgets-announcement jamwidgets-announcement-${announcement.announcementType}`;
98
99
  el.dataset.announcementId = announcement.id;
99
100
 
100
- let content = `<span class="jamwidgets-announcement-content">${announcement.content}</span>`;
101
-
102
- if (announcement.linkUrl) {
103
- content += ` <a href="${announcement.linkUrl}" class="jamwidgets-announcement-link">${announcement.linkText || "Learn more"}</a>`;
101
+ const contentEl = document.createElement("span");
102
+ contentEl.className = "jamwidgets-announcement-content";
103
+ contentEl.textContent = announcement.content;
104
+ el.append(contentEl);
105
+
106
+ const linkUrl = safeLinkUrl(announcement.linkUrl);
107
+ if (linkUrl) {
108
+ const link = document.createElement("a");
109
+ link.className = "jamwidgets-announcement-link";
110
+ link.href = linkUrl;
111
+ link.textContent = announcement.linkText || "Learn more";
112
+ el.append(" ", link);
104
113
  }
105
114
 
106
115
  if (announcement.isDismissible) {
107
- content += `
116
+ el.insertAdjacentHTML("beforeend", `
108
117
  <button type="button" class="jamwidgets-announcement-dismiss" aria-label="Dismiss">
109
118
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
110
119
  <line x1="18" y1="6" x2="6" y2="18"></line>
111
120
  <line x1="6" y1="6" x2="18" y2="18"></line>
112
121
  </svg>
113
122
  </button>
114
- `;
123
+ `);
115
124
  }
116
125
 
117
- el.innerHTML = content;
118
-
119
126
  // Handle dismiss
120
127
  const dismissBtn = el.querySelector(".jamwidgets-announcement-dismiss");
121
128
  dismissBtn?.addEventListener("click", async () => {
package/src/Embed.astro CHANGED
@@ -64,16 +64,7 @@ import Theme from "./Theme.astro";
64
64
  userVotes?: string[];
65
65
  }
66
66
 
67
- // Get or create a visitor ID for anonymous users
68
- const VISITOR_KEY = "jamwidgets_visitor_id";
69
- function getVisitorId(): string {
70
- let id = localStorage.getItem(VISITOR_KEY);
71
- if (!id) {
72
- id = crypto.randomUUID();
73
- localStorage.setItem(VISITOR_KEY, id);
74
- }
75
- return id;
76
- }
67
+ import { getVisitorId } from "@jamwidgets/core";
77
68
 
78
69
  class JamwidgetsEmbed extends HTMLElement {
79
70
  connectedCallback() {
@@ -188,11 +179,13 @@ import Theme from "./Theme.astro";
188
179
  const inputType = poll!.settings.multiSelect ? "checkbox" : "radio";
189
180
  optionEl.innerHTML = `
190
181
  <label class="jamwidgets-poll-label ${isSelected ? "jamwidgets-selected" : ""}">
191
- <input type="${inputType}" name="poll-${poll!.id}" value="${option.id}" ${isSelected ? "checked" : ""} />
192
- <span class="jamwidgets-poll-text">${option.text}</span>
182
+ <input type="${inputType}" ${isSelected ? "checked" : ""} />
183
+ <span class="jamwidgets-poll-text"></span>
193
184
  </label>
194
185
  `;
195
186
  const input = optionEl.querySelector("input")!;
187
+ input.name = `poll-${poll!.id}`;
188
+ input.value = option.id;
196
189
  input.addEventListener("change", () => {
197
190
  if (poll!.settings.multiSelect) {
198
191
  if (input.checked) {
@@ -215,7 +208,7 @@ import Theme from "./Theme.astro";
215
208
  optionEl.innerHTML = `
216
209
  <div class="jamwidgets-poll-result ${isSelected ? "jamwidgets-voted" : ""}">
217
210
  <div class="jamwidgets-poll-bar" style="width: ${percent}%"></div>
218
- <span class="jamwidgets-poll-text">${option.text}</span>
211
+ <span class="jamwidgets-poll-text"></span>
219
212
  <span class="jamwidgets-poll-percent">${percent}%</span>
220
213
  </div>
221
214
  `;
@@ -223,10 +216,11 @@ import Theme from "./Theme.astro";
223
216
  // Show without results (voted but results hidden)
224
217
  optionEl.innerHTML = `
225
218
  <div class="jamwidgets-poll-result jamwidgets-no-results ${isSelected ? "jamwidgets-voted" : ""}">
226
- <span class="jamwidgets-poll-text">${option.text}</span>
219
+ <span class="jamwidgets-poll-text"></span>
227
220
  </div>
228
221
  `;
229
222
  }
223
+ optionEl.querySelector(".jamwidgets-poll-text")!.textContent = option.text;
230
224
  optionsEl.appendChild(optionEl);
231
225
  });
232
226
 
@@ -90,16 +90,7 @@ import Theme from "./Theme.astro";
90
90
  </div>
91
91
 
92
92
  <script>
93
- // Get or create a visitor ID for anonymous users
94
- const VISITOR_KEY = "jamwidgets_visitor_id";
95
- function getVisitorId(): string {
96
- let id = localStorage.getItem(VISITOR_KEY);
97
- if (!id) {
98
- id = crypto.randomUUID();
99
- localStorage.setItem(VISITOR_KEY, id);
100
- }
101
- return id;
102
- }
93
+ import { getVisitorId } from "@jamwidgets/core";
103
94
 
104
95
  document.querySelectorAll("[data-jamwidgets-feedback]").forEach((container) => {
105
96
  const endpoint = (container as HTMLElement).dataset.endpoint;
package/src/Poll.astro CHANGED
@@ -89,16 +89,7 @@ import Theme from "./Theme.astro";
89
89
  userVotes?: string[];
90
90
  }
91
91
 
92
- // Get or create a visitor ID for anonymous users
93
- const VISITOR_KEY = "jamwidgets_visitor_id";
94
- function getVisitorId(): string {
95
- let id = localStorage.getItem(VISITOR_KEY);
96
- if (!id) {
97
- id = crypto.randomUUID();
98
- localStorage.setItem(VISITOR_KEY, id);
99
- }
100
- return id;
101
- }
92
+ import { getVisitorId } from "@jamwidgets/core";
102
93
 
103
94
  document.querySelectorAll("[data-jamwidgets-poll]").forEach((container) => {
104
95
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -172,11 +163,13 @@ import Theme from "./Theme.astro";
172
163
  const inputType = poll!.settings.multiSelect ? "checkbox" : "radio";
173
164
  optionEl.innerHTML = `
174
165
  <label class="jamwidgets-poll-label ${isSelected ? "jamwidgets-selected" : ""}">
175
- <input type="${inputType}" name="poll-${poll!.id}" value="${option.id}" ${isSelected ? "checked" : ""} />
176
- <span class="jamwidgets-poll-text">${option.text}</span>
166
+ <input type="${inputType}" ${isSelected ? "checked" : ""} />
167
+ <span class="jamwidgets-poll-text"></span>
177
168
  </label>
178
169
  `;
179
170
  const input = optionEl.querySelector("input")!;
171
+ input.name = `poll-${poll!.id}`;
172
+ input.value = option.id;
180
173
  input.addEventListener("change", () => {
181
174
  if (poll!.settings.multiSelect) {
182
175
  if (input.checked) {
@@ -199,7 +192,7 @@ import Theme from "./Theme.astro";
199
192
  optionEl.innerHTML = `
200
193
  <div class="jamwidgets-poll-result ${isSelected ? "jamwidgets-voted" : ""}">
201
194
  <div class="jamwidgets-poll-bar" style="width: ${percent}%"></div>
202
- <span class="jamwidgets-poll-text">${option.text}</span>
195
+ <span class="jamwidgets-poll-text"></span>
203
196
  <span class="jamwidgets-poll-percent">${percent}%</span>
204
197
  </div>
205
198
  `;
@@ -207,10 +200,11 @@ import Theme from "./Theme.astro";
207
200
  // Show without results (voted but results hidden)
208
201
  optionEl.innerHTML = `
209
202
  <div class="jamwidgets-poll-result jamwidgets-no-results ${isSelected ? "jamwidgets-voted" : ""}">
210
- <span class="jamwidgets-poll-text">${option.text}</span>
203
+ <span class="jamwidgets-poll-text"></span>
211
204
  </div>
212
205
  `;
213
206
  }
207
+ optionEl.querySelector(".jamwidgets-poll-text")!.textContent = option.text;
214
208
  optionsEl.appendChild(optionEl);
215
209
  });
216
210
 
@@ -106,11 +106,14 @@ import Theme from "./Theme.astro";
106
106
  "X-Jamwidgets-Visitor": visitorId,
107
107
  };
108
108
 
109
- // Track which reactions the user has made (stored in localStorage)
110
109
  const storageKey = `jamwidgets-reactions-${pageId}`;
111
- const userReactions = new Set<string>(
112
- JSON.parse(localStorage.getItem(storageKey) || "[]")
113
- );
110
+ const userReactions = new Set<string>();
111
+ try {
112
+ const stored = JSON.parse(localStorage.getItem(storageKey) || "[]");
113
+ if (Array.isArray(stored)) {
114
+ for (const type of stored) if (typeof type === "string") userReactions.add(type);
115
+ }
116
+ } catch {}
114
117
 
115
118
  // Apply initial "reacted" state
116
119
  container.querySelectorAll(".jamwidgets-reaction-btn").forEach((btn) => {
@@ -134,7 +137,7 @@ import Theme from "./Theme.astro";
134
137
 
135
138
  // Sync with server's user reactions
136
139
  serverUserReactions.forEach((type: string) => userReactions.add(type));
137
- localStorage.setItem(storageKey, JSON.stringify([...userReactions]));
140
+ try { localStorage.setItem(storageKey, JSON.stringify([...userReactions])); } catch {}
138
141
 
139
142
  container.querySelectorAll(".jamwidgets-reaction-btn").forEach((btn) => {
140
143
  const type = (btn as HTMLElement).dataset.reactionType;
@@ -204,7 +207,7 @@ import Theme from "./Theme.astro";
204
207
  }),
205
208
  );
206
209
  }
207
- localStorage.setItem(storageKey, JSON.stringify([...userReactions]));
210
+ try { localStorage.setItem(storageKey, JSON.stringify([...userReactions])); } catch {}
208
211
  } catch (e) {
209
212
  console.error(isReacted ? "Failed to remove reaction:" : "Failed to add reaction:", e);
210
213
  } finally {
@@ -93,16 +93,7 @@ import Theme from "./Theme.astro";
93
93
  message: string;
94
94
  }
95
95
 
96
- // Get or create a visitor ID for anonymous users
97
- const VISITOR_KEY = "jamwidgets_visitor_id";
98
- function getVisitorId(): string {
99
- let id = localStorage.getItem(VISITOR_KEY);
100
- if (!id) {
101
- id = crypto.randomUUID();
102
- localStorage.setItem(VISITOR_KEY, id);
103
- }
104
- return id;
105
- }
96
+ import { getVisitorId } from "@jamwidgets/core";
106
97
 
107
98
  document.querySelectorAll("[data-jamwidgets-subscribe]").forEach((form) => {
108
99
  form.addEventListener("submit", async (e) => {
package/src/Views.astro CHANGED
@@ -65,16 +65,7 @@ import Theme from "./Theme.astro";
65
65
  </div>
66
66
 
67
67
  <script>
68
- // Get or create a visitor ID for anonymous users
69
- const VISITOR_KEY = "jamwidgets_visitor_id";
70
- function getVisitorId(): string {
71
- let id = localStorage.getItem(VISITOR_KEY);
72
- if (!id) {
73
- id = crypto.randomUUID();
74
- localStorage.setItem(VISITOR_KEY, id);
75
- }
76
- return id;
77
- }
68
+ import { getVisitorId } from "@jamwidgets/core";
78
69
 
79
70
  document.querySelectorAll("[data-jamwidgets-views]").forEach((container) => {
80
71
  const endpoint = (container as HTMLElement).dataset.endpoint;
@@ -71,16 +71,7 @@ import Theme from "./Theme.astro";
71
71
  </div>
72
72
 
73
73
  <script>
74
- // Get or create a visitor ID for anonymous users
75
- const VISITOR_KEY = "jamwidgets_visitor_id";
76
- function getVisitorId(): string {
77
- let id = localStorage.getItem(VISITOR_KEY);
78
- if (!id) {
79
- id = crypto.randomUUID();
80
- localStorage.setItem(VISITOR_KEY, id);
81
- }
82
- return id;
83
- }
74
+ import { getVisitorId } from "@jamwidgets/core";
84
75
 
85
76
  document.querySelectorAll("[data-jamwidgets-waitlist]").forEach((container) => {
86
77
  const endpoint = (container as HTMLElement).dataset.endpoint;