forked from UKSOURCE/cms.hailearning.edu.vn
feat: Improve home and contact CMS field guidance
This commit is contained in:
@@ -189,6 +189,8 @@
|
||||
|
||||
// Khởi tạo các nút upload ảnh (dùng chung cho toàn bộ các section)
|
||||
initImageUploads();
|
||||
initHomeCharacterCounters(document);
|
||||
initImagePreviewFallbacks(document);
|
||||
});
|
||||
|
||||
// --- UTILITIES (Dùng chung) ---
|
||||
@@ -223,6 +225,43 @@
|
||||
);
|
||||
}
|
||||
|
||||
function clearImagePreviewError(previewImg) {
|
||||
if (!previewImg?.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
previewImg.parentElement.querySelectorAll(".image-preview-missing").forEach((node) => node.remove());
|
||||
}
|
||||
|
||||
function bindImagePreviewFallback(previewImg) {
|
||||
if (!previewImg || previewImg.dataset.previewFallbackBound === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
previewImg.dataset.previewFallbackBound = "true";
|
||||
previewImg.addEventListener("error", () => {
|
||||
previewImg.classList.add("d-none");
|
||||
previewImg.removeAttribute("src");
|
||||
|
||||
if (!previewImg.parentElement?.querySelector(".image-preview-missing")) {
|
||||
const note = document.createElement("small");
|
||||
note.className = "text-warning d-block mt-2 image-preview-missing";
|
||||
note.textContent = "Preview unavailable: current image path could not be loaded.";
|
||||
previewImg.parentElement?.appendChild(note);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initImagePreviewFallbacks(scope = document) {
|
||||
scope.querySelectorAll("img.img-thumbnail").forEach((previewImg) => {
|
||||
bindImagePreviewFallback(previewImg);
|
||||
|
||||
if (previewImg.complete && previewImg.getAttribute("src") && previewImg.naturalWidth === 0) {
|
||||
previewImg.dispatchEvent(new Event("error"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function revokePendingPreview(targetInput) {
|
||||
const previewUrl = pendingPreviewUrls.get(targetInput);
|
||||
if (previewUrl) {
|
||||
@@ -307,6 +346,8 @@
|
||||
|
||||
const previewImg = findImagePreview(input);
|
||||
if (previewImg) {
|
||||
clearImagePreviewError(previewImg);
|
||||
bindImagePreviewFallback(previewImg);
|
||||
previewImg.src = new URL(result.path, window.location.origin).toString();
|
||||
previewImg.classList.remove("d-none");
|
||||
}
|
||||
@@ -364,6 +405,8 @@
|
||||
|
||||
const previewImg = findImagePreview(input);
|
||||
if (previewImg) {
|
||||
clearImagePreviewError(previewImg);
|
||||
bindImagePreviewFallback(previewImg);
|
||||
previewImg.src = previewUrl;
|
||||
previewImg.classList.remove("d-none");
|
||||
}
|
||||
@@ -402,4 +445,93 @@
|
||||
new bootstrap.Toast(toast, { autohide: true, delay: 3000 }).show();
|
||||
toast.addEventListener("hidden.bs.toast", () => toast.remove());
|
||||
}
|
||||
|
||||
function ensureCharacterHint(input) {
|
||||
if (!input || (!input.dataset.maxlength && !input.dataset.maxwords)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!input.id) {
|
||||
input.id = `homeField_${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
|
||||
const field = input.closest(".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") || input.parentElement;
|
||||
const anchor = input.closest(".input-group") || input;
|
||||
const parent = anchor?.parentElement || field;
|
||||
if (!field || !anchor || !parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let hint = field.querySelector(`[data-counter-for="${input.id}"]`);
|
||||
if (!hint) {
|
||||
hint = document.createElement("small");
|
||||
hint.className = "form-text home-limit-counter text-secondary";
|
||||
hint.dataset.counterFor = input.id;
|
||||
}
|
||||
|
||||
if (hint.previousElementSibling !== anchor) {
|
||||
parent.insertBefore(hint, anchor.nextSibling);
|
||||
}
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
function updateCharacterHint(input) {
|
||||
const hint = ensureCharacterHint(input);
|
||||
if (!hint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasWordLimit = Boolean(input.dataset.maxwords);
|
||||
const hasCharLimit = Boolean(input.dataset.maxlength);
|
||||
|
||||
if (hasWordLimit) {
|
||||
const maxWords = Number(input.dataset.maxwords);
|
||||
const normalized = (input.value || "").replace(/\s+/g, " ").trim();
|
||||
const words = normalized ? normalized.split(" ") : [];
|
||||
|
||||
if (Number.isFinite(maxWords) && maxWords > 0 && words.length > maxWords) {
|
||||
input.value = words.slice(0, maxWords).join(" ");
|
||||
} else if (normalized !== input.value) {
|
||||
input.value = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCharLimit) {
|
||||
const max = Number(input.dataset.maxlength);
|
||||
if (Number.isFinite(max) && max > 0 && (input.value || "").length > max) {
|
||||
input.value = (input.value || "").slice(0, max);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWordLimit) {
|
||||
const maxWords = Number(input.dataset.maxwords);
|
||||
const currentWords = input.value ? input.value.split(" ").filter(Boolean).length : 0;
|
||||
const currentLength = (input.value || "").length;
|
||||
const maxLength = Number(input.dataset.maxlength);
|
||||
hint.textContent = hasCharLimit
|
||||
? `${currentWords}/${maxWords} words, ${currentLength}/${maxLength} characters`
|
||||
: `${currentWords}/${maxWords} words`;
|
||||
hint.classList.toggle("text-danger", currentWords >= maxWords || (hasCharLimit && currentLength >= maxLength));
|
||||
return;
|
||||
}
|
||||
|
||||
const max = Number(input.dataset.maxlength);
|
||||
const length = (input.value || "").length;
|
||||
hint.textContent = `${length}/${max} characters`;
|
||||
hint.classList.toggle("text-danger", length >= max);
|
||||
}
|
||||
|
||||
function initHomeCharacterCounters(scope = document) {
|
||||
scope.querySelectorAll("input[data-maxlength], textarea[data-maxlength], input[data-maxwords], textarea[data-maxwords]").forEach((input) => {
|
||||
updateCharacterHint(input);
|
||||
|
||||
if (input.dataset.counterBound === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
input.dataset.counterBound = "true";
|
||||
input.addEventListener("input", () => updateCharacterHint(input));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user