;(function (window, document) { "use strict"; const COUNTER_SELECTOR = ".admin-field-counter"; const GUIDANCE_SELECTOR = ".admin-upload-guidance"; const COUNTER_BOUND_KEY = "adminCounterBound"; const AUTO_GUIDANCE_ATTR = "data-admin-upload-guidance"; const OBSERVER_BOUND_KEY = "__adminFormHelpersObserver"; let generatedFieldToken = 0; const FIELD_RULES = { "/admin/about-us": [ { selector: "#heroTitle", maxLength: 72 }, { selector: "#heroBreadcrumb", maxLength: 120 }, { selector: "#heroBackgroundImage", maxLength: 255 }, { selector: "#introSubheading", maxLength: 40 }, { selector: "#introHeading", maxLength: 72 }, { selector: "#introDescription", maxLength: 260 }, { selector: "#introImage", maxLength: 255 }, { selector: "#missionSubheading", maxLength: 40 }, { selector: "#missionHeading", maxLength: 72 }, { selector: "#missionDescription", maxLength: 260 }, { selector: "#missionCtaLabel", maxLength: 32 }, { selector: "#missionCtaHref", maxLength: 255 }, { selector: "[id^='missionImg_']", maxLength: 255 }, { selector: "#featuresSubheading", maxLength: 40 }, { selector: "#featuresHeading", maxLength: 72 }, { selector: "#featuresDescription", maxLength: 260 }, { selector: "#featuresBgImage", maxLength: 255 }, { selector: "#featuresImage", maxLength: 255 }, { selector: "#featuresCtaLabel", maxLength: 32 }, { selector: "#featuresCtaHref", maxLength: 255 }, { selector: "[id^='missionItemLabel_']", maxLength: 48 }, { selector: "[id^='missionItemDescription_']", maxLength: 160 }, { selector: "[id^='missionItemIcon_']", maxLength: 255 }, { selector: "[id^='missionFeature_']", maxLength: 96 }, { selector: "[id^='featureItemTitle_']", maxLength: 48 }, { selector: "[id^='featureItemDescription_']", maxLength: 160 }, { selector: "[id^='featureItemIcon_']", maxLength: 255 }, { selector: "#newsSubheading", maxLength: 40 }, { selector: "#newsHeading", maxLength: 72 }, { selector: "#newsCtaLabel", maxLength: 32 }, { selector: "#newsCtaHref", maxLength: 255 }, ], "/admin/booking": [ { selector: "#heroBackgroundImage", maxLength: 255 }, { selector: "#heroTitle", maxLength: 72 }, { selector: "#searchBarLocationLabel", maxLength: 32 }, { selector: "#searchBarHolidaySeasonLabel", maxLength: 32 }, { selector: "#searchBarSearchButtonText", maxLength: 24 }, { selector: "input[name^='locationValue_']", maxLength: 32 }, { selector: "input[name^='locationLabel_']", maxLength: 48 }, { selector: "input[name^='holidayValue_']", maxLength: 32 }, { selector: "input[name^='holidayLabel_']", maxLength: 48 }, { selector: "#filterPanelTitle", maxLength: 48 }, { selector: "#filterPanelPriceTitle", maxLength: 40 }, { selector: "#filterPanelPriceLabel", maxLength: 32 }, { selector: "#filterPanelPricePlaceholder", maxLength: 32 }, { selector: "#filterPanelActivitiesTitle", maxLength: 40 }, { selector: "#filterPanelAgeTitle", maxLength: 40 }, { selector: "#filterPanelAgeSelectPlaceholder", maxLength: 32 }, { selector: "#filterPanelRatingTitle", maxLength: 40 }, { selector: "#filterPanelResetButtonText", maxLength: 24 }, { selector: "input[name^='ratingValue_']", maxLength: 8 }, { selector: "input[name^='ratingLabel_']", maxLength: 32 }, { selector: "input[name^='programValue_']", maxLength: 32 }, { selector: "input[name^='programLabel_']", maxLength: 48 }, { selector: "input[name^='campName_']", maxLength: 72 }, { selector: "input[name^='campPriceText_']", maxLength: 32 }, { selector: "input[name^='campProgram_']", maxLength: 32 }, { selector: "input[name^='campImage_']", maxLength: 255 }, { selector: "input[name^='campLink_']", maxLength: 255 }, { selector: "input[name^='discountName_']", maxLength: 48 }, { selector: "textarea[name^='discountDescription_']", maxLength: 180 }, { selector: "input[name^='voucherCode_']", maxLength: 24 }, { selector: "input[name^='voucherDescription_']", maxLength: 120 }, { selector: "[name='formTitle']", maxLength: 64 }, { selector: "[name='formSubtitle']", maxLength: 48 }, { selector: "[name^='stepTitle_']", maxLength: 64 }, { selector: "[name^='sectionTitle_']", maxLength: 64 }, { selector: "[name^='fieldLabel_']", maxLength: 48 }, { selector: "[name^='fieldName_']", maxLength: 32 }, { selector: "[name^='fieldPlaceholder_']", maxLength: 72 }, { selector: "[name^='validationMessage_']", maxLength: 120 }, ], "/admin/pricing": [ { selector: "#heroBackgroundImage", maxLength: 255 }, { selector: "#heroTitle", maxLength: 72 }, { selector: "#pricingSectionSubtitle", maxLength: 40 }, { selector: "#pricingSectionHeading", maxLength: 72 }, { selector: "#pricingSectionDescription", maxLength: 220 }, { selector: ".plan-name", maxLength: 40 }, { selector: ".plan-price", maxLength: 16 }, { selector: ".plan-currency", maxLength: 8 }, { selector: ".plan-period", maxLength: 12 }, { selector: ".plan-button-text", maxLength: 32 }, { selector: ".plan-button-link", maxLength: 255 }, { selector: ".plan-button-icon", maxLength: 64 }, { selector: ".plan-features", maxLength: 320 }, { selector: "#testimonialsSubtitle", maxLength: 40 }, { selector: "#testimonialsHeading", maxLength: 72 }, { selector: "#testimonialsButtonText", maxLength: 32 }, { selector: "#testimonialsButtonLink", maxLength: 255 }, { selector: "#testimonialsButtonIcon", maxLength: 64 }, { selector: "#testimonialsImage", maxLength: 255 }, { selector: ".testimonial-name", maxLength: 48 }, { selector: ".testimonial-role", maxLength: 48 }, { selector: ".testimonial-content", maxLength: 220 }, ], "/admin/visa": [ { selector: "input[name='name']", maxLength: 40 }, { selector: "input[name='icon']", maxLength: 255 }, { selector: "input[name='services[]']", maxLength: 56 }, { selector: "input[name='detail_title']", maxLength: 72 }, { selector: "input[name='mainImage']", maxLength: 255 }, { selector: "textarea[name='description']", maxLength: 360 }, { selector: "textarea[name='additionalInfo']", maxLength: 360 }, { selector: "input[name='tagline']", maxLength: 72 }, { selector: "input[name^='visa_title_']", maxLength: 56 }, { selector: "textarea[name^='visa_desc_']", maxLength: 220 }, { selector: "input[name='process_title']", maxLength: 72 }, { selector: "input[name='step_title[]']", maxLength: 56 }, { selector: "textarea[name='step_desc[]']", maxLength: 180 }, { selector: "input[name='bannerImageGallery']", maxLength: 255 }, { selector: "input[name='category_title[]']", maxLength: 56 }, { selector: "textarea[name='category_desc[]']", maxLength: 180 }, { selector: "input[name='related_title[]']", maxLength: 56 }, { selector: "textarea[name='related_desc[]']", maxLength: 180 }, { selector: "input[name='related_file[]']", maxLength: 255 }, { selector: "input[name='contact_title']", maxLength: 72 }, { selector: "input[name='contact_phone']", maxLength: 32 }, { selector: "input[name='contact_email']", maxLength: 120 }, { selector: "input[name='contact_address']", maxLength: 160 }, { selector: "input[name='contact_image']", maxLength: 255 }, ], "/admin/activity/*": [ { selector: "input[name='heroTitle']", maxLength: 72 }, { selector: "input[name='heroBannerImage']", maxLength: 255 }, { selector: "input[name='name']", maxLength: 72 }, { selector: "input[name='priceText']", maxLength: 32 }, { selector: "input[name='link']", maxLength: 255 }, { selector: "input[name='program']", maxLength: 32 }, { selector: "#customLocations", maxLength: 120 }, { selector: "input[name='image']", maxLength: 255 }, { selector: "input[name='campDetailHeroTitle']", maxLength: 72 }, { selector: "input[name='campDetailHeroBgImage']", maxLength: 255 }, { selector: "input[name='campDetailBasicInfoLocation']", maxLength: 48 }, { selector: "textarea[name='campDetailBasicInfoAgeRange']", maxLength: 120 }, { selector: "input[name='campDetailBasicInfoAccommodationType']", maxLength: 72 }, { selector: "input[name='campDetailBasicInfoCareLevel']", maxLength: 72 }, { selector: "input[name='campDetailBasicInfoLanguages']", maxLength: 72 }, ], "/admin/activity": [], }; const GUIDANCE_RULES = { "/admin/about-us": [ { selector: "#heroBackgroundImage", title: "Upload guidance", lines: [ "Displayed as a wide page hero.", "Recommended upload: at least 1920x700px.", ], }, { selector: "#introImage", title: "Upload guidance", lines: [ "Displayed around 596x787px on desktop.", "Recommended upload: at least 1200x1600px.", ], }, { selector: "#featuresImage", title: "Upload guidance", lines: [ "Displayed around 375x419px on desktop.", "Recommended upload: at least 750x840px.", ], }, ], "/admin/booking": [ { selector: "#heroBackgroundImage", title: "Upload guidance", lines: [ "Booking page hero background.", "Recommended upload: at least 1920x700px.", ], }, { selector: "input[name^='campImage_']", title: "Upload guidance", lines: [ "Used in booking camp cards.", "Recommended upload: a landscape image at 704x432px or larger.", ], }, ], "/admin/pricing": [ { selector: "#heroBackgroundImage", title: "Upload guidance", lines: [ "Pricing page hero background.", "Recommended upload: at least 1920x700px.", ], }, ], "/admin/visa": [ { selector: "input[name='icon']", title: "Upload guidance", lines: [ "Displayed as a small country flag or icon.", "Prefer SVG; otherwise use a square image at 96x96px or larger.", ], }, { selector: "input[name='mainImage'], input[name='bannerImageGallery'], input[name='contact_image'], input[name='related_file[]']", title: "Upload guidance", lines: [ "Used in visa detail content blocks.", "Recommended upload: at least 1000x750px for primary imagery and 800x600px for supporting images.", ], }, ], "/admin/activity/*": [ { selector: "input[name='heroBannerImage'], input[name='campDetailHeroBgImage']", title: "Upload guidance", lines: [ "Activity page hero-style image.", "Recommended upload: at least 1920x700px.", ], }, { selector: "input[name='image']", title: "Upload guidance", lines: [ "Used in activity listing cards.", "Recommended upload: a landscape image at 704x432px or larger.", ], }, ], "/admin/home": [ { selector: "#whyChooseUsMainImage", title: "Upload guidance", lines: [ "Displayed around 318x347px on desktop.", "Recommended upload: at least 750x820px.", ], }, { selector: "#whyChooseUsSecondaryImage", title: "Upload guidance", lines: [ "Displayed around 363x380px on desktop.", "Recommended upload: at least 760x800px.", ], }, { selector: "#testimonialsVideoThumbnail", title: "Upload guidance", lines: [ "Displayed around 416x370px on desktop.", "Recommended upload: at least 832x740px.", ], }, { selector: "[id^='testimonialsAvatar_']", title: "Upload guidance", lines: [ "Displayed around 48x48px.", "Recommended upload: 96x96px or 128x128px square.", ], }, { selector: "#visaCountriesFlag_0", title: "Upload guidance", lines: [ "Displayed around 840x830px on desktop.", "Recommended upload: at least 1000x1000px.", ], }, ], "*": [ { selector: ".btn-upload-image", title: "Upload guidance", lines: [ "Use a clear, high-resolution image that matches the visible frame.", "Prefer SVG for logos or icons and at least 2x the displayed size for raster uploads.", ], }, ], }; function toScope(scope) { return scope && scope.querySelectorAll ? scope : document; } function normalizeText(value) { return String(value ?? "").replace(/\s+/g, " ").trim(); } function buildDescriptor(input) { return [ input?.id, input?.name, input?.placeholder, input?.closest(".col-md-12, .col-md-9, .col-md-6, .col-md-5, .col-md-4, .col-md-3, .col-md-2, .col-lg-12, .col-lg-8, .col-lg-6, .col-lg-4, .col-12")?.querySelector("label")?.textContent, ] .filter(Boolean) .join(" ") .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_[\].-]+/g, " ") .toLowerCase(); } function isUploadDescriptor(descriptor) { return /(?:^|[^a-z])(image|icon|logo|background|banner|thumbnail|avatar|flag|path|src|file)(?:[^a-z]|$)/.test(descriptor); } function resolveUploadTarget(targetId, anchor) { if (targetId) { const byId = document.getElementById(targetId); if (byId) { return byId; } if (window.CSS && typeof window.CSS.escape === "function") { const byName = document.querySelector(`[name="${window.CSS.escape(targetId)}"]`); if (byName) { return byName; } } } return anchor || null; } function hasManualUploadHint(target) { const anchor = resolveGuidanceAnchor(target); if (!anchor) { return false; } const host = anchor.closest(".form-group, .mb-3, .col-md-12, .col-md-9, .col-md-6, .col-md-5, .col-md-4, .col-md-3, .col-md-2, .col-lg-12, .col-lg-8, .col-lg-6, .col-lg-4, .col-12") || anchor.parentElement; if (!host) { return false; } const candidates = [ ...host.querySelectorAll("small.text-muted, small.form-text, .form-text, .text-muted"), ...Array.from(host.nextElementSibling ? host.nextElementSibling.querySelectorAll?.("small.text-muted, small.form-text, .form-text, .text-muted") || [] : []), ]; return candidates.some((node) => { if (node.classList?.contains("admin-field-counter") || node.classList?.contains("admin-upload-guidance")) { return false; } const text = normalizeText(node.textContent || ""); return /recommended|min(imum)? upload|upload|svg|png|webp|render|displayed|size|preview|icon/i.test(text); }); } function getFieldLimit(input) { const dataMax = Number(input?.dataset?.maxlength); if (Number.isFinite(dataMax) && dataMax > 0) { return dataMax; } const attrMax = Number(input?.getAttribute("maxlength")); if (Number.isFinite(attrMax) && attrMax > 0) { return attrMax; } return null; } function getWordLimit(input) { const dataMax = Number(input?.dataset?.maxwords); if (Number.isFinite(dataMax) && dataMax > 0) { return dataMax; } return null; } function getFieldToken(input) { if (!input) { return ""; } if (input.id) { return `id:${input.id}`; } if (input.name) { const indexWithinNameGroup = Array.from(document.querySelectorAll(`[name="${CSS.escape(input.name)}"]`)).indexOf(input); return `name:${input.name}:${Math.max(indexWithinNameGroup, 0)}`; } if (!input.dataset.adminFieldToken) { generatedFieldToken += 1; input.dataset.adminFieldToken = `generated:${generatedFieldToken}`; } return input.dataset.adminFieldToken; } function isDragDropField(element) { if (!element || !element.closest) { return false; } return Boolean( element.closest( ".social-link-item, .floating-contact-action-item, .menu-links-sortable, .social-links-sortable, .sortable-container, .sortable-list, .sortable-item, [data-top-menu-index], [data-top-social-index], [data-bottom-menu-index], [data-bottom-social-index], [draggable='true']", ), ); } function refreshCountersWithin(scope) { if (!scope || !scope.querySelectorAll) { return; } scope .querySelectorAll("input[data-maxlength], textarea[data-maxlength], input[data-maxwords], textarea[data-maxwords], input[maxlength], textarea[maxlength]") .forEach((field) => { if (field.dataset[COUNTER_BOUND_KEY] === "true") { updateCounter(field); } }); } function getCounterRefreshScope(input) { if (!isDragDropField(input)) { return null; } return ( input.closest(".floating-contact-action-item, .social-link-item, [data-top-menu-index], [data-top-social-index], [data-bottom-menu-index], [data-bottom-social-index]") || input.closest(".menu-links-sortable, .social-links-sortable, .sortable-container, .sortable-list") ); } function getCounterHost(input) { return ( input.closest(".input-group") || input.closest(".col, [class^='col-'], [class*=' col-']") || input.parentElement || input ); } function ensureCounterElement(input) { const counterToken = getFieldToken(input); const existingAnywhere = counterToken ? document.querySelector(`[data-counter-for="${counterToken}"]`) : null; if (existingAnywhere) { return existingAnywhere; } const host = getCounterHost(input); const nextSibling = host.nextElementSibling; const matchingNextSibling = nextSibling && nextSibling.matches(COUNTER_SELECTOR) && nextSibling.dataset.counterFor === counterToken ? nextSibling : null; const existing = matchingNextSibling || host.querySelector(`${COUNTER_SELECTOR}[data-counter-for="${counterToken}"]`); if (existing) { return existing; } const counter = document.createElement("small"); counter.className = "form-text admin-field-counter"; counter.dataset.counterFor = counterToken; counter.setAttribute("aria-live", "polite"); if (host.classList?.contains("input-group")) { host.insertAdjacentElement("afterend", counter); } else { host.appendChild(counter); } return counter; } function matchesPathKey(key, pathname) { if (key === "*") { return true; } if (key.endsWith("*")) { return pathname.startsWith(key.slice(0, -1)); } return key === pathname; } function getPathRules(registry) { const pathname = window.location.pathname; return Object.keys(registry).reduce((rules, key) => { if (!matchesPathKey(key, pathname)) { return rules; } return rules.concat(registry[key] || []); }, []); } function findTargets(root, selector) { const targets = []; if (root.matches && root.matches(selector)) { targets.push(root); } if (root.querySelectorAll) { targets.push(...root.querySelectorAll(selector)); } return targets; } function applyFieldRules(scope) { const root = toScope(scope); getPathRules(FIELD_RULES).forEach((rule) => { findTargets(root, rule.selector).forEach((input) => { if (rule.maxLength && !input.dataset.maxlength) { input.dataset.maxlength = String(rule.maxLength); input.setAttribute("maxlength", String(rule.maxLength)); } if (rule.maxWords && !input.dataset.maxwords) { input.dataset.maxwords = String(rule.maxWords); } }); }); root.querySelectorAll("input, textarea").forEach((input) => { if ( input.disabled || input.type === "hidden" || input.type === "file" || input.dataset.maxlength || input.dataset.maxwords || input.getAttribute("maxlength") ) { return; } const type = (input.getAttribute("type") || "").toLowerCase(); if (type && !["text", "email", "tel", "search", "url"].includes(type) && input.tagName !== "TEXTAREA") { return; } const descriptor = buildDescriptor(input); if (/json|editor|html|content-block|blocks/.test(descriptor)) { return; } let inferredMaxLength = 72; if (input.tagName === "TEXTAREA") { inferredMaxLength = /description|content|overview|additional info|quote|note|message|summary/.test(descriptor) ? 500 : 220; } else if (type === "email" || /email/.test(descriptor)) { inferredMaxLength = 120; } else if (type === "tel" || /phone|tel|mobile|whatsapp|zalo/.test(descriptor)) { inferredMaxLength = 32; } else if (/url|href|link/.test(descriptor) || isUploadDescriptor(descriptor)) { inferredMaxLength = 255; } else if (/slug|code|id/.test(descriptor)) { inferredMaxLength = 32; } else if (/title|heading|name|label|subtitle|platform/.test(descriptor)) { inferredMaxLength = 72; } input.dataset.maxlength = String(inferredMaxLength); input.setAttribute("maxlength", String(inferredMaxLength)); }); } function updateCounter(input) { const counter = ensureCounterElement(input); const maxLength = getFieldLimit(input); const maxWords = getWordLimit(input); const currentValue = normalizeText(input.value || ""); const currentLength = currentValue.length; if (maxWords) { const words = currentValue ? currentValue.split(" ") : []; const currentWords = words.filter(Boolean).length; if (maxLength && currentLength > maxLength) { input.value = currentValue.slice(0, maxLength); } if (maxLength) { counter.textContent = `${currentWords}/${maxWords} words, ${Math.min(currentLength, maxLength)}/${maxLength} characters`; counter.classList.toggle("is-danger", currentWords >= maxWords || currentLength >= maxLength); } else { counter.textContent = `${currentWords}/${maxWords} words`; counter.classList.toggle("is-danger", currentWords >= maxWords); } return; } if (!maxLength) { counter.textContent = ""; return; } if (currentLength > maxLength) { input.value = currentValue.slice(0, maxLength); } counter.textContent = `${Math.min(currentLength, maxLength)}/${maxLength} characters`; counter.classList.toggle("is-danger", currentLength >= maxLength); } function bindCounter(input) { if (!input || input.dataset[COUNTER_BOUND_KEY] === "true") { return; } input.dataset[COUNTER_BOUND_KEY] = "true"; const syncCounter = () => { updateCounter(input); const refreshScope = getCounterRefreshScope(input); if (refreshScope) { refreshCountersWithin(refreshScope); } }; syncCounter(); input.addEventListener("input", syncCounter); input.addEventListener("change", syncCounter); input.addEventListener("blur", syncCounter); input.addEventListener("focus", syncCounter); } function buildGuidanceLines(options = {}) { const title = normalizeText(options.title) || "Upload guidance"; const lines = Array.isArray(options.lines) ? options.lines.map(normalizeText).filter(Boolean) : []; if (!lines.length) { lines.push("Use a clear, high-resolution image that matches the visible frame."); lines.push("Prefer a file that is at least 2x the displayed size for crisp rendering."); lines.push("Keep the original aspect ratio unless the page explicitly asks for a crop."); } return { title, lines }; } function resolveGuidanceAnchor(target) { if (!target) { return null; } if (typeof target === "string") { return document.querySelector(target); } if (target instanceof Element) { return target; } return null; } function renderUploadGuidance(target, options = {}) { const anchor = resolveGuidanceAnchor(target); if (!anchor) { return null; } const host = anchor.closest(".form-group, .mb-3, .col-md-12, .col-md-9, .col-md-6, .col-md-4, .col-md-3, .col-lg-12, .col-lg-8, .col-lg-6, .col-lg-4, .col-12") || anchor.parentElement; if (!host) { return null; } const matchingAnchorSibling = anchor.nextElementSibling && anchor.nextElementSibling.matches(GUIDANCE_SELECTOR) && anchor.nextElementSibling.dataset.guidanceFor === (options.for || "") ? anchor.nextElementSibling : null; const matchingHostSibling = host.nextElementSibling && host.nextElementSibling.matches(GUIDANCE_SELECTOR) && host.nextElementSibling.dataset.guidanceFor === (options.for || "") ? host.nextElementSibling : null; const existing = matchingAnchorSibling || matchingHostSibling || host.querySelector(`${GUIDANCE_SELECTOR}[data-guidance-for="${options.for || ""}"]`); if (existing) { return existing; } const payload = buildGuidanceLines(options); const note = document.createElement("div"); note.className = "admin-upload-guidance"; note.dataset.guidanceFor = options.for || ""; note.setAttribute("role", "note"); note.innerHTML = `
${payload.title}
`; const insertionTarget = host !== anchor ? host : anchor; insertionTarget.insertAdjacentElement("afterend", note); return note; } function autoWireGuidance(scope) { const root = toScope(scope); root.querySelectorAll(`[${AUTO_GUIDANCE_ATTR}]`).forEach((anchor) => { const guidanceValue = anchor.getAttribute(AUTO_GUIDANCE_ATTR); if (guidanceValue === "false") { return; } if (isDragDropField(anchor)) { return; } if (hasManualUploadHint(anchor)) { return; } renderUploadGuidance(anchor, { for: anchor.id || anchor.dataset.targetInput || "", title: anchor.dataset.adminUploadGuidanceTitle || "Upload guidance", lines: anchor.dataset.adminUploadGuidance ? anchor.dataset.adminUploadGuidance.split("|").map((part) => part.trim()) : undefined, }); }); } function applyGuidanceRules(scope) { const root = toScope(scope); getPathRules(GUIDANCE_RULES).forEach((rule) => { findTargets(root, rule.selector).forEach((anchor) => { if (isDragDropField(anchor)) { return; } if (hasManualUploadHint(anchor)) { return; } if (anchor.matches(".btn-upload-image")) { const targetId = anchor.dataset.targetInput; const target = resolveUploadTarget(targetId, anchor); renderUploadGuidance(target || anchor, { for: targetId || anchor.id || "", title: rule.title, lines: rule.lines, }); return; } renderUploadGuidance(anchor, { for: anchor.id || anchor.name || "", title: rule.title, lines: rule.lines, }); }); }); root.querySelectorAll("input[type='text'], textarea").forEach((input) => { if (isDragDropField(input)) { return; } const descriptor = buildDescriptor(input); if (!isUploadDescriptor(descriptor)) { return; } if (hasManualUploadHint(input)) { return; } const host = input.parentElement?.querySelector(".admin-upload-guidance") || input.closest(".col-md-12, .col-md-6, .col-md-4, .col-lg-12, .col-lg-6, .col-lg-4, .col-12")?.querySelector(".admin-upload-guidance"); if (host) { return; } renderUploadGuidance(input, { for: input.id || input.name || "", title: "Upload guidance", lines: [ "Use a clear, high-resolution image sized for the frontend frame.", "Prefer SVG for logos or icons and at least 2x the displayed size for raster uploads.", ], }); }); } function observeMutations() { if (document.body[OBSERVER_BOUND_KEY]) { return; } const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (!node || node.nodeType !== 1) { return; } init(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); document.body[OBSERVER_BOUND_KEY] = true; } function init(scope = document) { const root = toScope(scope); applyFieldRules(root); root.querySelectorAll("input[data-maxlength], textarea[data-maxlength], input[data-maxwords], textarea[data-maxwords], input[maxlength], textarea[maxlength]").forEach(bindCounter); autoWireGuidance(root); applyGuidanceRules(root); } function refresh(scope = document) { init(scope); } function shouldAutoInit() { return document.body && document.body.dataset.adminHelpers === "true"; } const api = { init, refresh, renderUploadGuidance, updateCounter, }; window.AdminFormHelpers = api; if (shouldAutoInit()) { if (document.readyState === "loading") { window.addEventListener("load", () => { init(document); observeMutations(); }, { once: true }); } else { init(document); observeMutations(); } } })(window, document);