fix: decode HTML entities in storefront widget label attributes
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
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:
parent
a2c78d703f
commit
3749b4d134
File diff suppressed because one or more lines are too long
@ -121,43 +121,60 @@ function formatDateLabel(dateIso: string): string {
|
|||||||
return date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", timeZone: "UTC" });
|
return date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", timeZone: "UTC" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Liquid's `| escape` filter turns `'` into `'` (and `&` into `&`,
|
||||||
|
// 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 {
|
function readConfig(root: HTMLElement): WidgetConfig {
|
||||||
const d = root.dataset;
|
const d = root.dataset;
|
||||||
|
const get = (key: string, fallback: string): string => {
|
||||||
|
const raw = d[key];
|
||||||
|
return raw ? decodeEntities(raw) : fallback;
|
||||||
|
};
|
||||||
|
|
||||||
const methods: WidgetConfig["methods"] = [];
|
const methods: WidgetConfig["methods"] = [];
|
||||||
if (d.showShipping === "true") {
|
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") {
|
if (d.showLocalDelivery === "true") {
|
||||||
methods.push({
|
methods.push({
|
||||||
value: "LOCAL_DELIVERY",
|
value: "LOCAL_DELIVERY",
|
||||||
label: d.labelLocalDelivery || "Local delivery",
|
label: get("labelLocalDelivery", "Local delivery"),
|
||||||
attrLabel: d.attrLabelLocalDelivery || "Delivery date",
|
attrLabel: get("attrLabelLocalDelivery", "Delivery date"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (d.showPickup === "true") {
|
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 {
|
return {
|
||||||
root,
|
root,
|
||||||
heading: d.heading || "",
|
heading: get("heading", ""),
|
||||||
locationId: d.locationId || null,
|
locationId: d.locationId || null,
|
||||||
googleMapsApiKey: d.googleMapsApiKey || null,
|
googleMapsApiKey: d.googleMapsApiKey || null,
|
||||||
mode: d.mode === "preview" ? "preview" : "full",
|
mode: d.mode === "preview" ? "preview" : "full",
|
||||||
methods,
|
methods,
|
||||||
labels: {
|
labels: {
|
||||||
chooseDate: d.labelChooseDate || "Choose a date",
|
chooseDate: get("labelChooseDate", "Choose a date"),
|
||||||
chooseTime: d.labelChooseTime || "Choose a time",
|
chooseTime: get("labelChooseTime", "Choose a time"),
|
||||||
noDates: d.labelNoDates || "No dates are available right now.",
|
noDates: get("labelNoDates", "No dates are available right now."),
|
||||||
confirmed: d.labelConfirmed || "Confirmed for",
|
confirmed: get("labelConfirmed", "Confirmed for"),
|
||||||
change: d.labelChange || "Change",
|
change: get("labelChange", "Change"),
|
||||||
loading: d.labelLoading || "Loading available dates…",
|
loading: get("labelLoading", "Loading available dates…"),
|
||||||
error: d.labelError || "Couldn't load available dates. Please try again.",
|
error: get("labelError", "Couldn't load available dates. Please try again."),
|
||||||
postalCodeLabel: d.labelPostalCode || "Enter your postal/ZIP code",
|
postalCodeLabel: get("labelPostalCode", "Enter your postal/ZIP code"),
|
||||||
postalCodeSubmit: d.labelPostalCodeSubmit || "Check availability",
|
postalCodeSubmit: get("labelPostalCodeSubmit", "Check availability"),
|
||||||
outOfArea: d.labelOutOfArea || "Sorry, we don't deliver to this address.",
|
outOfArea: get("labelOutOfArea", "Sorry, we don't deliver to this address."),
|
||||||
earliestPrefix: d.labelEarliestPrefix || "Earliest",
|
earliestPrefix: get("labelEarliestPrefix", "Earliest"),
|
||||||
deliveryAtCheckout: d.labelDeliveryAtCheckout || "Enter your address at checkout to see local delivery dates.",
|
deliveryAtCheckout: get("labelDeliveryAtCheckout", "Enter your address at checkout to see local delivery dates."),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user