Skip to content

Commit 9a45723

Browse files
Merge pull request #23 from datacite/toast-update
Update toast notification styles, reduce verbosity, cleanup
2 parents f6870b6 + 8b473b3 commit 9a45723

1 file changed

Lines changed: 52 additions & 42 deletions

File tree

source/javascripts/toast_notifications.js

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
endpoint: "https://datacite.org/wp-json/wp/v2/toast/",
44
site: "",
55
storageKey: "datacite_toasts_dismissed",
6+
maxVisible: 3,
67
};
8+
79
const SCRIPT_OPTIONS = (() => {
810
const script = document.currentScript;
911
if (!script || !script.dataset) return {};
@@ -29,11 +31,11 @@
2931
min-width: 500px;
3032
max-width: 500px;
3133
padding: 20px;
32-
border-color: rgba(36, 59, 84, 0.08);
34+
border-color: rgba(36, 59, 84, 0.32);
3335
border-width: 1px;
3436
border-style: solid;
3537
background-color: #ffffff;
36-
box-shadow: 0px 28px 36px -10px rgba(13, 96, 212, 0.08);
38+
box-shadow: 0px 28px 36px -10px rgba(13, 96, 212, 0.48);
3739
display: flex;
3840
flex-direction: column;
3941
gap: 6px;
@@ -179,7 +181,6 @@
179181
const arr = Array.isArray(parsed) ? parsed : [];
180182
return new Set(arr.map((value) => String(value)));
181183
} catch (err) {
182-
console.warn("[DataCiteToasts] Failed to read dismissed set", err);
183184
return new Set();
184185
}
185186
}
@@ -189,25 +190,16 @@
189190
try {
190191
localStorage.setItem(key, JSON.stringify(Array.from(set)));
191192
} catch (err) {
192-
console.warn("[DataCiteToasts] Failed to persist dismissed set", err);
193+
return;
193194
}
194195
}
195196

196-
function markToastDismissed(id, options) {
197-
if (!id || !options?.storageKey) return;
198-
const set =
199-
options._dismissedSet ||
200-
(options._dismissedSet = readDismissedSet(options.storageKey));
197+
function markToastDismissed(id, dismissedSet, storageKey) {
198+
if (!id || !dismissedSet || !storageKey) return;
201199
const entry = String(id);
202-
if (set.has(entry)) return;
203-
set.add(entry);
204-
writeDismissedSet(set, options.storageKey);
205-
}
206-
207-
function htmlDecode(str) {
208-
const div = document.createElement("div");
209-
div.innerHTML = str;
210-
return div.textContent || str;
200+
if (dismissedSet.has(entry)) return;
201+
dismissedSet.add(entry);
202+
writeDismissedSet(dismissedSet, storageKey);
211203
}
212204

213205
function isActiveToast(acf, now) {
@@ -222,14 +214,28 @@
222214
return true;
223215
}
224216

217+
function isSafeHttpsUri(value) {
218+
if (typeof value !== "string") return false;
219+
const trimmed = value.trim();
220+
if (!trimmed) return false;
221+
222+
try {
223+
const parsed = new URL(trimmed);
224+
if (parsed.protocol !== "https:") return false;
225+
return true;
226+
} catch (err) {
227+
return false;
228+
}
229+
}
230+
225231
function matchesSite(acf, site) {
226-
if (!site) return true;
227-
return (acf.toast_site || [])
228-
.map((s) => s.toLowerCase())
229-
.includes(site.toLowerCase());
232+
const currentSite = site.toLowerCase();
233+
const sites = acf.toast_site;
234+
235+
return sites.some((value) => String(value).toLowerCase() === currentSite);
230236
}
231237

232-
function createToast(notice, options) {
238+
function createToast(notice, dismissedSet, storageKey) {
233239
const {
234240
toast_type,
235241
toast_title,
@@ -241,7 +247,6 @@
241247
} = notice.acf;
242248
const toast = document.createElement("div");
243249
toast.className = "dc-toast";
244-
toast.dataset.type = toast_type || "info";
245250
const toastId = notice.id;
246251
toast.dataset.toastId = toastId;
247252

@@ -252,7 +257,9 @@
252257
dismissBtn.className = "dc-toast__dismiss";
253258
dismissBtn.setAttribute("aria-label", "Dismiss notification");
254259
dismissBtn.innerHTML = "×";
255-
dismissBtn.addEventListener("click", () => dismissToast(toast, options));
260+
dismissBtn.addEventListener("click", () =>
261+
dismissToast(toast, dismissedSet, storageKey),
262+
);
256263

257264
const content = document.createElement("div");
258265
content.className = "dc-toast__content";
@@ -280,12 +287,12 @@
280287

281288
const body = document.createElement("div");
282289
body.className = "dc-toast__body";
283-
body.textContent = toast_message ? htmlDecode(toast_message) : "";
290+
body.textContent = toast_message || "";
284291

285292
textWrap.appendChild(title);
286293
textWrap.appendChild(body);
287294

288-
if (toast_cta_enabled && toast_cta_label && toast_cta_url) {
295+
if (toast_cta_enabled && toast_cta_url && toast_cta_label) {
289296
const toastTitleForAnalytics = (toast_title || "")
290297
.toString()
291298
.trim()
@@ -312,45 +319,48 @@
312319
return toast;
313320
}
314321

315-
function dismissToast(node, options) {
322+
function dismissToast(node, dismissedSet, storageKey) {
316323
if (!node || node.dataset.dismissed === "true") return;
317324
node.dataset.dismissed = "true";
318325
const toastId = node.dataset.toastId;
319-
markToastDismissed(toastId, options);
320-
setTimeout(() => node.remove(), 200);
326+
markToastDismissed(toastId, dismissedSet, storageKey);
327+
node.remove();
321328
}
322329

323330
async function fetchToasts(endpoint) {
324331
const res = await fetch(endpoint);
325-
if (!res.ok) throw new Error(`Toast fetch failed (${res.status})`);
332+
if (!res.ok) return [];
326333
return res.json();
327334
}
328335

329336
async function init() {
330-
const options = { ...DEFAULTS, ...SCRIPT_OPTIONS };
337+
const { endpoint, site, storageKey, maxVisible } = {
338+
...DEFAULTS,
339+
...SCRIPT_OPTIONS,
340+
};
331341

332342
try {
333343
ensureStyles();
334-
const data = await fetchToasts(options.endpoint);
344+
const data = await fetchToasts(endpoint);
335345
const now = new Date();
336-
const dismissedSet = readDismissedSet(options.storageKey);
337-
options._dismissedSet = dismissedSet;
338-
const relevant = (Array.isArray(data) ? data : []).filter((item) => {
346+
const dismissedSet = readDismissedSet(storageKey);
347+
const relevantPosts = (Array.isArray(data) ? data : []).filter((item) => {
339348
if (!item?.acf) return false;
340349
if (!isActiveToast(item.acf, now)) return false;
341-
if (!matchesSite(item.acf, options.site)) return false;
350+
if (!matchesSite(item.acf, site)) return false;
351+
if (item.acf.toast_cta_url && !isSafeHttpsUri(item.acf.toast_cta_url)) return false;
342352
return !dismissedSet.has(String(item.id));
343353
});
344-
345-
if (!relevant.length) return;
354+
const postsToRender = relevantPosts.slice(0, maxVisible);
355+
if (!postsToRender.length) return;
346356

347357
const wrapper = ensureWrapper();
348358

349-
relevant.forEach((notice) =>
350-
wrapper.appendChild(createToast(notice, options)),
359+
postsToRender.forEach((notice) =>
360+
wrapper.appendChild(createToast(notice, dismissedSet, storageKey)),
351361
);
352362
} catch (err) {
353-
console.error("[DataCiteToasts]", err);
363+
return;
354364
}
355365
}
356366

0 commit comments

Comments
 (0)