fix: decode HTML entities in storefront widget label attributes
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled

Liquid's `| escape` filter encodes apostrophes/ampersands before the label
strings land in data-* attributes; depending on how the theme processes
translations that could survive into `dataset` still entity-encoded and then
render literally (e.g. "Couldn't load available dates") once assigned to
textContent. readConfig now decodes entities once via a detached <textarea>,
routed through a shared get(key, fallback) helper for every label + heading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
metatroncubeswdev 2026-09-04 01:44:48 -04:00
parent a2c78d703f
commit 3749b4d134
2 changed files with 35 additions and 18 deletions

File diff suppressed because one or more lines are too long

View File

@ -121,43 +121,60 @@ function formatDateLabel(dateIso: string): string {
return date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", timeZone: "UTC" });
}
// Liquid's `| escape` filter turns `'` into `&#39;` (and `&` into `&amp;`,
// etc.) before the string lands in a data-* attribute. Depending on how the
// theme/section double-processes translations, that can survive into
// `dataset` still entity-encoded and then render literally when we assign it
// to `textContent`. Decode once here so labels always show as plain text.
function decodeEntities(value: string): string {
if (!value || value.indexOf("&") === -1) return value;
const el = document.createElement("textarea");
el.innerHTML = value;
return el.value;
}
function readConfig(root: HTMLElement): WidgetConfig {
const d = root.dataset;
const get = (key: string, fallback: string): string => {
const raw = d[key];
return raw ? decodeEntities(raw) : fallback;
};
const methods: WidgetConfig["methods"] = [];
if (d.showShipping === "true") {
methods.push({ value: "SHIPPING", label: d.labelShipping || "Shipping", attrLabel: d.attrLabelShipping || "Shipping date" });
methods.push({ value: "SHIPPING", label: get("labelShipping", "Shipping"), attrLabel: get("attrLabelShipping", "Shipping date") });
}
if (d.showLocalDelivery === "true") {
methods.push({
value: "LOCAL_DELIVERY",
label: d.labelLocalDelivery || "Local delivery",
attrLabel: d.attrLabelLocalDelivery || "Delivery date",
label: get("labelLocalDelivery", "Local delivery"),
attrLabel: get("attrLabelLocalDelivery", "Delivery date"),
});
}
if (d.showPickup === "true") {
methods.push({ value: "PICKUP", label: d.labelPickup || "Pickup", attrLabel: d.attrLabelPickup || "Pickup date" });
methods.push({ value: "PICKUP", label: get("labelPickup", "Pickup"), attrLabel: get("attrLabelPickup", "Pickup date") });
}
return {
root,
heading: d.heading || "",
heading: get("heading", ""),
locationId: d.locationId || null,
googleMapsApiKey: d.googleMapsApiKey || null,
mode: d.mode === "preview" ? "preview" : "full",
methods,
labels: {
chooseDate: d.labelChooseDate || "Choose a date",
chooseTime: d.labelChooseTime || "Choose a time",
noDates: d.labelNoDates || "No dates are available right now.",
confirmed: d.labelConfirmed || "Confirmed for",
change: d.labelChange || "Change",
loading: d.labelLoading || "Loading available dates…",
error: d.labelError || "Couldn't load available dates. Please try again.",
postalCodeLabel: d.labelPostalCode || "Enter your postal/ZIP code",
postalCodeSubmit: d.labelPostalCodeSubmit || "Check availability",
outOfArea: d.labelOutOfArea || "Sorry, we don't deliver to this address.",
earliestPrefix: d.labelEarliestPrefix || "Earliest",
deliveryAtCheckout: d.labelDeliveryAtCheckout || "Enter your address at checkout to see local delivery dates.",
chooseDate: get("labelChooseDate", "Choose a date"),
chooseTime: get("labelChooseTime", "Choose a time"),
noDates: get("labelNoDates", "No dates are available right now."),
confirmed: get("labelConfirmed", "Confirmed for"),
change: get("labelChange", "Change"),
loading: get("labelLoading", "Loading available dates…"),
error: get("labelError", "Couldn't load available dates. Please try again."),
postalCodeLabel: get("labelPostalCode", "Enter your postal/ZIP code"),
postalCodeSubmit: get("labelPostalCodeSubmit", "Check availability"),
outOfArea: get("labelOutOfArea", "Sorry, we don't deliver to this address."),
earliestPrefix: get("labelEarliestPrefix", "Earliest"),
deliveryAtCheckout: get("labelDeliveryAtCheckout", "Enter your address at checkout to see local delivery dates."),
},
};
}