Compare commits
1 Commits
v2.1
...
147ad1545c
Author | SHA1 | Date | |
---|---|---|---|
147ad1545c |
@ -50,7 +50,7 @@ img, tag {
|
|||||||
|
|
||||||
.hero-background {
|
.hero-background {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
margin: 0 auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button {
|
.back-button {
|
||||||
|
@ -6,12 +6,14 @@ colors:
|
|||||||
primary_dark: '#005384'
|
primary_dark: '#005384'
|
||||||
secondary: '#00B0F0'
|
secondary: '#00B0F0'
|
||||||
text_dark: '#616161'
|
text_dark: '#616161'
|
||||||
|
|
||||||
favicon:
|
favicon:
|
||||||
path: favicon.png
|
path: favicon.png
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
primary:
|
primary:
|
||||||
name: trixie
|
name: trixie.woff
|
||||||
fallback: sans-serif
|
fallback: sans-serif
|
||||||
secondary:
|
secondary:
|
||||||
name: trixie
|
name: trixie.woff
|
||||||
fallback: serif
|
fallback: serif
|
||||||
|
@ -10,28 +10,24 @@ copy_default_config() {
|
|||||||
mkdir -p /app/config
|
mkdir -p /app/config
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[~] Checking if default config files/folders need to be copied..."
|
echo "[~] Checking if default config files need to be copied..."
|
||||||
files_copied=false
|
files_copied=false
|
||||||
|
|
||||||
# Recursively check all files and folders in /app/default
|
for file in /app/default/*; do
|
||||||
while IFS= read -r src; do
|
filename=$(basename "$file")
|
||||||
relpath="${src#/app/default/}"
|
target="/app/config/$filename"
|
||||||
target="/app/config/$relpath"
|
|
||||||
if [ ! -e "$target" ]; then
|
if [ ! -e "$target" ]; then
|
||||||
echo "[→] Copying: $relpath"
|
echo "[→] Copying default config file: $filename"
|
||||||
if [ -d "$src" ]; then
|
cp -r "$file" "$target"
|
||||||
cp -r "$src" "$target"
|
|
||||||
else
|
|
||||||
cp "$src" "$target"
|
|
||||||
fi
|
|
||||||
files_copied=true
|
files_copied=true
|
||||||
fi
|
fi
|
||||||
done < <(find /app/default -mindepth 1)
|
done
|
||||||
|
|
||||||
if [ "$files_copied" = true ]; then
|
if [ "$files_copied" = true ]; then
|
||||||
echo "[✓] Default configuration files/folders copied successfully."
|
echo "[✓] Default configuration files copied successfully."
|
||||||
else
|
else
|
||||||
echo "[✓] No default files/folders needed to be copied."
|
echo "[✓] No default files needed to be copied."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,8 +415,7 @@ def upload_font():
|
|||||||
fonts_dir = Path(__file__).resolve().parents[3] / "config" / "themes" / theme_name / "fonts"
|
fonts_dir = Path(__file__).resolve().parents[3] / "config" / "themes" / theme_name / "fonts"
|
||||||
fonts_dir.mkdir(parents=True, exist_ok=True)
|
fonts_dir.mkdir(parents=True, exist_ok=True)
|
||||||
file.save(fonts_dir / file.filename)
|
file.save(fonts_dir / file.filename)
|
||||||
font_basename = Path(file.filename).stem
|
return jsonify({"status": "ok", "filename": file.filename})
|
||||||
return jsonify({"status": "ok", "filename": font_basename})
|
|
||||||
|
|
||||||
@app.route("/api/font/remove", methods=["POST"])
|
@app.route("/api/font/remove", methods=["POST"])
|
||||||
def remove_font():
|
def remove_font():
|
||||||
|
@ -1,29 +1,19 @@
|
|||||||
|
// --- Arrays to store gallery and hero images ---
|
||||||
let galleryImages = [];
|
let galleryImages = [];
|
||||||
let heroImages = [];
|
let heroImages = [];
|
||||||
let allTags = [];
|
let allTags = []; // global tag list
|
||||||
let showOnlyUntagged = false;
|
let showOnlyUntagged = false;
|
||||||
|
|
||||||
// --- Fade-in helper ---
|
|
||||||
function applyFadeInImages(container) {
|
|
||||||
container.querySelectorAll("img.fade-in-img").forEach(img => {
|
|
||||||
const onLoad = () => img.classList.add("loaded");
|
|
||||||
if (img.complete && img.naturalHeight !== 0) onLoad();
|
|
||||||
else {
|
|
||||||
img.addEventListener("load", onLoad, { once: true });
|
|
||||||
img.addEventListener("error", () => {
|
|
||||||
console.warn("Image failed to load:", img.dataset?.src || img.src);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Load images from server on page load ---
|
// --- Load images from server on page load ---
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
galleryImages = await (await fetch('/api/gallery')).json();
|
const galleryRes = await fetch('/api/gallery');
|
||||||
|
galleryImages = await galleryRes.json();
|
||||||
updateAllTags();
|
updateAllTags();
|
||||||
renderGallery();
|
renderGallery();
|
||||||
heroImages = await (await fetch('/api/hero')).json();
|
|
||||||
|
const heroRes = await fetch('/api/hero');
|
||||||
|
heroImages = await heroRes.json();
|
||||||
renderHero();
|
renderHero();
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -35,38 +25,25 @@ async function loadData() {
|
|||||||
function updateAllTags() {
|
function updateAllTags() {
|
||||||
allTags = [];
|
allTags = [];
|
||||||
galleryImages.forEach(img => {
|
galleryImages.forEach(img => {
|
||||||
(img.tags || []).forEach(t => {
|
if (img.tags) img.tags.forEach(t => {
|
||||||
if (!allTags.includes(t)) allTags.push(t);
|
if (!allTags.includes(t)) allTags.push(t);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Helper: update count and button visibility ---
|
|
||||||
function updateCountAndButtons(prefix, count) {
|
|
||||||
const countTop = document.getElementById(`${prefix}-count`);
|
|
||||||
const countBottom = document.getElementById(`${prefix}-count-bottom`);
|
|
||||||
if (countTop) countTop.innerHTML = `<p>${count} photos</p>`;
|
|
||||||
if (countBottom) countBottom.innerHTML = `<p>${count} photos</p>`;
|
|
||||||
|
|
||||||
const removeAllBtn = document.getElementById(`remove-all-${prefix}`);
|
|
||||||
const removeAllBtnBottom = document.getElementById(`remove-all-${prefix}-bottom`);
|
|
||||||
if (removeAllBtn) removeAllBtn.style.display = count > 0 ? 'inline-block' : 'none';
|
|
||||||
if (removeAllBtnBottom) removeAllBtnBottom.style.display = count > 0 ? 'inline-block' : 'none';
|
|
||||||
|
|
||||||
const bottomUpload = document.getElementById(`bottom-${prefix}-upload`);
|
|
||||||
if (bottomUpload) bottomUpload.style.display = count > 0 ? 'flex' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Render gallery images with tags and delete buttons ---
|
// --- Render gallery images with tags and delete buttons ---
|
||||||
function renderGallery() {
|
function renderGallery() {
|
||||||
const container = document.getElementById('gallery');
|
const container = document.getElementById('gallery');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
let imagesToShow = showOnlyUntagged
|
|
||||||
? galleryImages.filter(img => !img.tags || img.tags.length === 0)
|
// Filter images if toggle is on
|
||||||
: galleryImages;
|
let imagesToShow = galleryImages;
|
||||||
|
if (showOnlyUntagged) {
|
||||||
|
imagesToShow = galleryImages.filter(img => !img.tags || img.tags.length === 0);
|
||||||
|
}
|
||||||
|
|
||||||
imagesToShow.forEach((img) => {
|
imagesToShow.forEach((img) => {
|
||||||
const i = galleryImages.indexOf(img);
|
const i = galleryImages.indexOf(img); // Use real index for tag/delete actions
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'photo flex-item flex-column';
|
div.className = 'photo flex-item flex-column';
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
@ -82,17 +59,62 @@ function renderGallery() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
|
|
||||||
renderTags(i, img.tags || []);
|
renderTags(i, img.tags || []);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateCountAndButtons('gallery', imagesToShow.length);
|
// Update gallery count (top)
|
||||||
applyFadeInImages(container);
|
const galleryCount = document.getElementById('gallery-count');
|
||||||
|
if (galleryCount) {
|
||||||
|
galleryCount.innerHTML = `<p>${imagesToShow.length} photos</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update gallery count (bottom)
|
||||||
|
const galleryCountBottom = document.getElementById('gallery-count-bottom');
|
||||||
|
if (galleryCountBottom) {
|
||||||
|
galleryCountBottom.innerHTML = `<p>${imagesToShow.length} photos</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide Remove All button (top)
|
||||||
|
const removeAllBtn = document.getElementById('remove-all-gallery');
|
||||||
|
if (removeAllBtn) {
|
||||||
|
removeAllBtn.style.display = imagesToShow.length > 0 ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide bottom upload row
|
||||||
|
const bottomGalleryUpload = document.getElementById('bottom-gallery-upload');
|
||||||
|
if (bottomGalleryUpload) {
|
||||||
|
bottomGalleryUpload.style.display = imagesToShow.length > 0 ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide Remove All button (bottom)
|
||||||
|
const removeAllBtnBottom = document.getElementById('remove-all-gallery-bottom');
|
||||||
|
if (removeAllBtnBottom) {
|
||||||
|
removeAllBtnBottom.style.display = imagesToShow.length > 0 ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade-in effect for loaded images
|
||||||
|
const fadeImages = document.querySelectorAll("img.fade-in-img");
|
||||||
|
fadeImages.forEach(img => {
|
||||||
|
const onLoad = () => {
|
||||||
|
img.classList.add("loaded");
|
||||||
|
};
|
||||||
|
if (img.complete && img.naturalHeight !== 0) {
|
||||||
|
onLoad();
|
||||||
|
} else {
|
||||||
|
img.addEventListener("load", onLoad, { once: true });
|
||||||
|
img.addEventListener("error", () => {
|
||||||
|
console.warn("Image failed to load:", img.dataset.src || img.src);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Render tags for a single image ---
|
// --- Render tags for a single image ---
|
||||||
function renderTags(imgIndex, tags) {
|
function renderTags(imgIndex, tags) {
|
||||||
const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
|
const tagsDisplay = document.querySelector(`.tags-display[data-index="${imgIndex}"]`);
|
||||||
const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
|
const inputContainer = document.querySelector(`.tag-input[data-index="${imgIndex}"]`);
|
||||||
|
|
||||||
tagsDisplay.innerHTML = '';
|
tagsDisplay.innerHTML = '';
|
||||||
inputContainer.innerHTML = '';
|
inputContainer.innerHTML = '';
|
||||||
|
|
||||||
@ -100,6 +122,7 @@ function renderTags(imgIndex, tags) {
|
|||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.className = 'tag';
|
span.className = 'tag';
|
||||||
span.textContent = tag;
|
span.textContent = tag;
|
||||||
|
|
||||||
const remove = document.createElement('span');
|
const remove = document.createElement('span');
|
||||||
remove.className = 'remove-tag';
|
remove.className = 'remove-tag';
|
||||||
remove.textContent = '×';
|
remove.textContent = '×';
|
||||||
@ -108,6 +131,7 @@ function renderTags(imgIndex, tags) {
|
|||||||
updateTags(imgIndex, tags);
|
updateTags(imgIndex, tags);
|
||||||
renderTags(imgIndex, tags);
|
renderTags(imgIndex, tags);
|
||||||
};
|
};
|
||||||
|
|
||||||
span.appendChild(remove);
|
span.appendChild(remove);
|
||||||
tagsDisplay.appendChild(span);
|
tagsDisplay.appendChild(span);
|
||||||
});
|
});
|
||||||
@ -117,10 +141,11 @@ function renderTags(imgIndex, tags) {
|
|||||||
input.placeholder = 'Add tag...';
|
input.placeholder = 'Add tag...';
|
||||||
inputContainer.appendChild(input);
|
inputContainer.appendChild(input);
|
||||||
|
|
||||||
|
// --- Validate button ---
|
||||||
const validateBtn = document.createElement('button');
|
const validateBtn = document.createElement('button');
|
||||||
validateBtn.textContent = '✔️';
|
validateBtn.textContent = '✔️';
|
||||||
validateBtn.className = 'validate-tag-btn';
|
validateBtn.className = 'validate-tag-btn';
|
||||||
validateBtn.style.display = 'none';
|
validateBtn.style.display = 'none'; // hidden by default
|
||||||
validateBtn.style.marginLeft = '4px';
|
validateBtn.style.marginLeft = '4px';
|
||||||
inputContainer.appendChild(validateBtn);
|
inputContainer.appendChild(validateBtn);
|
||||||
|
|
||||||
@ -140,20 +165,30 @@ function renderTags(imgIndex, tags) {
|
|||||||
|
|
||||||
const updateSuggestions = () => {
|
const updateSuggestions = () => {
|
||||||
const value = input.value.toLowerCase();
|
const value = input.value.toLowerCase();
|
||||||
|
|
||||||
const allTagsFlat = galleryImages.flatMap(img => img.tags || []);
|
const allTagsFlat = galleryImages.flatMap(img => img.tags || []);
|
||||||
const tagCount = {};
|
const tagCount = {};
|
||||||
allTagsFlat.forEach(t => tagCount[t] = (tagCount[t] || 0) + 1);
|
allTagsFlat.forEach(t => tagCount[t] = (tagCount[t] || 0) + 1);
|
||||||
const allTagsSorted = Object.keys(tagCount).sort((a, b) => tagCount[b] - tagCount[a]);
|
|
||||||
|
const allTagsSorted = Object.keys(tagCount)
|
||||||
|
.sort((a, b) => tagCount[b] - tagCount[a]);
|
||||||
|
|
||||||
const suggestions = allTagsSorted.filter(t => t.toLowerCase().startsWith(value) && !tags.includes(t));
|
const suggestions = allTagsSorted.filter(t => t.toLowerCase().startsWith(value) && !tags.includes(t));
|
||||||
|
|
||||||
suggestionBox.innerHTML = '';
|
suggestionBox.innerHTML = '';
|
||||||
selectedIndex = -1;
|
selectedIndex = -1;
|
||||||
|
|
||||||
if (suggestions.length) {
|
if (suggestions.length) {
|
||||||
suggestionBox.style.display = 'block';
|
suggestionBox.style.display = 'block';
|
||||||
suggestions.forEach((s, idx) => {
|
suggestions.forEach((s, idx) => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.style.fontStyle = 'italic';
|
li.style.fontStyle = 'italic';
|
||||||
li.style.textAlign = 'left';
|
li.style.textAlign = 'left';
|
||||||
li.innerHTML = `<b>${s.substring(0, input.value.length)}</b>${s.substring(input.value.length)}`;
|
|
||||||
|
const boldPart = `<b>${s.substring(0, input.value.length)}</b>`;
|
||||||
|
const rest = s.substring(input.value.length);
|
||||||
|
li.innerHTML = boldPart + rest;
|
||||||
|
|
||||||
li.addEventListener('mousedown', (e) => {
|
li.addEventListener('mousedown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
addTag(s);
|
addTag(s);
|
||||||
@ -161,6 +196,7 @@ function renderTags(imgIndex, tags) {
|
|||||||
input.focus();
|
input.focus();
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
});
|
});
|
||||||
|
|
||||||
li.onmouseover = () => selectedIndex = idx;
|
li.onmouseover = () => selectedIndex = idx;
|
||||||
suggestionBox.appendChild(li);
|
suggestionBox.appendChild(li);
|
||||||
});
|
});
|
||||||
@ -177,6 +213,7 @@ function renderTags(imgIndex, tags) {
|
|||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
|
validateBtn.style.display = input.value.trim() ? 'inline-block' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('keydown', (e) => {
|
input.addEventListener('keydown', (e) => {
|
||||||
const items = suggestionBox.querySelectorAll('li');
|
const items = suggestionBox.querySelectorAll('li');
|
||||||
if (e.key === 'ArrowDown') {
|
if (e.key === 'ArrowDown') {
|
||||||
@ -207,22 +244,27 @@ function renderTags(imgIndex, tags) {
|
|||||||
validateBtn.style.display = 'none';
|
validateBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('blur', () => {
|
input.addEventListener('blur', () => {
|
||||||
|
setTimeout(() => {
|
||||||
suggestionBox.style.display = 'none';
|
suggestionBox.style.display = 'none';
|
||||||
input.value = '';
|
input.value = '';
|
||||||
validateBtn.style.display = 'none';
|
validateBtn.style.display = 'none';
|
||||||
|
}, 150);
|
||||||
});
|
});
|
||||||
validateBtn.addEventListener('mousedown', (e) => {
|
|
||||||
e.preventDefault();
|
// --- Validate button action ---
|
||||||
|
validateBtn.onclick = () => {
|
||||||
if (input.value.trim()) {
|
if (input.value.trim()) {
|
||||||
addTag(input.value.trim());
|
addTag(input.value.trim());
|
||||||
input.value = '';
|
input.value = '';
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
validateBtn.style.display = 'none';
|
validateBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
input.focus();
|
||||||
updateSuggestions();
|
updateSuggestions();
|
||||||
if (!input.value.trim()) suggestionBox.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Update tags in galleryImages array ---
|
// --- Update tags in galleryImages array ---
|
||||||
@ -250,8 +292,36 @@ function renderHero() {
|
|||||||
`;
|
`;
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
});
|
});
|
||||||
updateCountAndButtons('hero', heroImages.length);
|
|
||||||
applyFadeInImages(container);
|
// Update hero count (top)
|
||||||
|
const heroCount = document.getElementById('hero-count');
|
||||||
|
if (heroCount) {
|
||||||
|
heroCount.innerHTML = `<p>${heroImages.length} photos</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update hero count (bottom)
|
||||||
|
const heroCountBottom = document.getElementById('hero-count-bottom');
|
||||||
|
if (heroCountBottom) {
|
||||||
|
heroCountBottom.innerHTML = `<p>${heroImages.length} photos</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide Remove All button (top)
|
||||||
|
const removeAllBtn = document.getElementById('remove-all-hero');
|
||||||
|
if (removeAllBtn) {
|
||||||
|
removeAllBtn.style.display = heroImages.length > 0 ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide bottom upload row
|
||||||
|
const bottomHeroUpload = document.getElementById('bottom-hero-upload');
|
||||||
|
if (bottomHeroUpload) {
|
||||||
|
bottomHeroUpload.style.display = heroImages.length > 0 ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide Remove All button (bottom)
|
||||||
|
const removeAllBtnBottom = document.getElementById('remove-all-hero-bottom');
|
||||||
|
if (removeAllBtnBottom) {
|
||||||
|
removeAllBtnBottom.style.display = heroImages.length > 0 ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Save gallery to server ---
|
// --- Save gallery to server ---
|
||||||
@ -297,27 +367,33 @@ async function refreshHero() {
|
|||||||
function showToast(message, type = "success", duration = 3000) {
|
function showToast(message, type = "success", duration = 3000) {
|
||||||
const container = document.getElementById("toast-container");
|
const container = document.getElementById("toast-container");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const toast = document.createElement("div");
|
const toast = document.createElement("div");
|
||||||
toast.className = `toast ${type}`;
|
toast.className = `toast ${type}`;
|
||||||
toast.textContent = message;
|
toast.textContent = message;
|
||||||
container.appendChild(toast);
|
container.appendChild(toast);
|
||||||
|
|
||||||
requestAnimationFrame(() => toast.classList.add("show"));
|
requestAnimationFrame(() => toast.classList.add("show"));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast.classList.remove("show");
|
toast.classList.remove("show");
|
||||||
toast.addEventListener("transitionend", () => toast.remove());
|
toast.addEventListener("transitionend", () => toast.remove());
|
||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pendingDelete = null;
|
let pendingDelete = null; // { type: 'gallery'|'hero'|'gallery-all'|'hero-all', index: number|null }
|
||||||
|
|
||||||
// --- Show delete confirmation modal ---
|
// --- Show delete confirmation modal ---
|
||||||
function showDeleteModal(type, index = null) {
|
function showDeleteModal(type, index = null) {
|
||||||
pendingDelete = { type, index };
|
pendingDelete = { type, index };
|
||||||
const modalText = document.getElementById('delete-modal-text');
|
const modalText = document.getElementById('delete-modal-text');
|
||||||
modalText.textContent =
|
if (type === 'gallery-all') {
|
||||||
type === 'gallery-all' ? "Are you sure you want to delete ALL gallery images?"
|
modalText.textContent = "Are you sure you want to delete ALL gallery images?";
|
||||||
: type === 'hero-all' ? "Are you sure you want to delete ALL hero images?"
|
} else if (type === 'hero-all') {
|
||||||
: "Are you sure you want to delete this image?";
|
modalText.textContent = "Are you sure you want to delete ALL hero images?";
|
||||||
|
} else {
|
||||||
|
modalText.textContent = "Are you sure you want to delete this image?";
|
||||||
|
}
|
||||||
document.getElementById('delete-modal').style.display = 'flex';
|
document.getElementById('delete-modal').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,10 +406,15 @@ function hideDeleteModal() {
|
|||||||
// --- Confirm deletion ---
|
// --- Confirm deletion ---
|
||||||
async function confirmDelete() {
|
async function confirmDelete() {
|
||||||
if (!pendingDelete) return;
|
if (!pendingDelete) return;
|
||||||
if (pendingDelete.type === 'gallery') await actuallyDeleteGalleryImage(pendingDelete.index);
|
if (pendingDelete.type === 'gallery') {
|
||||||
else if (pendingDelete.type === 'hero') await actuallyDeleteHeroImage(pendingDelete.index);
|
await actuallyDeleteGalleryImage(pendingDelete.index);
|
||||||
else if (pendingDelete.type === 'gallery-all') await actuallyDeleteAllGalleryImages();
|
} else if (pendingDelete.type === 'hero') {
|
||||||
else if (pendingDelete.type === 'hero-all') await actuallyDeleteAllHeroImages();
|
await actuallyDeleteHeroImage(pendingDelete.index);
|
||||||
|
} else if (pendingDelete.type === 'gallery-all') {
|
||||||
|
await actuallyDeleteAllGalleryImages();
|
||||||
|
} else if (pendingDelete.type === 'hero-all') {
|
||||||
|
await actuallyDeleteAllHeroImages();
|
||||||
|
}
|
||||||
hideDeleteModal();
|
hideDeleteModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,36 +496,41 @@ async function actuallyDeleteAllHeroImages() {
|
|||||||
|
|
||||||
// --- Modal event listeners and bulk delete buttons ---
|
// --- Modal event listeners and bulk delete buttons ---
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
['delete-modal-close', 'delete-modal-cancel'].forEach(id => {
|
document.getElementById('delete-modal-close').onclick = hideDeleteModal;
|
||||||
const el = document.getElementById(id);
|
document.getElementById('delete-modal-cancel').onclick = hideDeleteModal;
|
||||||
if (el) el.onclick = hideDeleteModal;
|
document.getElementById('delete-modal-confirm').onclick = confirmDelete;
|
||||||
});
|
|
||||||
const confirmBtn = document.getElementById('delete-modal-confirm');
|
|
||||||
if (confirmBtn) confirmBtn.onclick = confirmDelete;
|
|
||||||
|
|
||||||
// Gallery filter radios
|
// --- Radio toggle for gallery filter ---
|
||||||
const showAllRadio = document.getElementById('show-all-radio');
|
const showAllRadio = document.getElementById('show-all-radio');
|
||||||
const showUntaggedRadio = document.getElementById('show-untagged-radio');
|
const showUntaggedRadio = document.getElementById('show-untagged-radio');
|
||||||
if (showAllRadio) showAllRadio.addEventListener('change', () => {
|
|
||||||
|
if (showAllRadio && showUntaggedRadio) {
|
||||||
|
showAllRadio.addEventListener('change', () => {
|
||||||
|
if (showAllRadio.checked) {
|
||||||
showOnlyUntagged = false;
|
showOnlyUntagged = false;
|
||||||
renderGallery();
|
renderGallery();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (showUntaggedRadio) showUntaggedRadio.addEventListener('change', () => {
|
showUntaggedRadio.addEventListener('change', () => {
|
||||||
|
if (showUntaggedRadio.checked) {
|
||||||
showOnlyUntagged = true;
|
showOnlyUntagged = true;
|
||||||
renderGallery();
|
renderGallery();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Bulk delete buttons
|
// Bulk delete buttons
|
||||||
[
|
const removeAllGalleryBtn = document.getElementById('remove-all-gallery');
|
||||||
['remove-all-gallery', 'gallery-all'],
|
const removeAllGalleryBtnBottom = document.getElementById('remove-all-gallery-bottom');
|
||||||
['remove-all-gallery-bottom', 'gallery-all'],
|
const removeAllHeroBtn = document.getElementById('remove-all-hero');
|
||||||
['remove-all-hero', 'hero-all'],
|
const removeAllHeroBtnBottom = document.getElementById('remove-all-hero-bottom');
|
||||||
['remove-all-hero-bottom', 'hero-all']
|
if (removeAllGalleryBtn) removeAllGalleryBtn.onclick = () => showDeleteModal('gallery-all');
|
||||||
].forEach(([btnId, type]) => {
|
if (removeAllGalleryBtnBottom) removeAllGalleryBtnBottom.onclick = () => showDeleteModal('gallery-all');
|
||||||
const btn = document.getElementById(btnId);
|
if (removeAllHeroBtn) removeAllHeroBtn.onclick = () => showDeleteModal('hero-all');
|
||||||
if (btn) btn.onclick = () => showDeleteModal(type);
|
if (removeAllHeroBtnBottom) removeAllHeroBtnBottom.onclick = () => showDeleteModal('hero-all');
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Initialize ---
|
// --- Initialize ---
|
||||||
loadData();
|
loadData();
|
@ -12,6 +12,7 @@ function showToast(message, type = "success", duration = 3000) {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Loader helpers ---
|
||||||
function showLoader(text = "Uploading...") {
|
function showLoader(text = "Uploading...") {
|
||||||
const loader = document.getElementById("global-loader");
|
const loader = document.getElementById("global-loader");
|
||||||
if (loader) {
|
if (loader) {
|
||||||
@ -26,65 +27,77 @@ function hideLoader() {
|
|||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// --- Section Forms ---
|
// --- Section Forms ---
|
||||||
const forms = {
|
const infoForm = document.getElementById("info-form");
|
||||||
info: document.getElementById("info-form"),
|
const socialForm = document.getElementById("social-form");
|
||||||
social: document.getElementById("social-form"),
|
const menuForm = document.getElementById("menu-form");
|
||||||
menu: document.getElementById("menu-form"),
|
const footerForm = document.getElementById("footer-form");
|
||||||
footer: document.getElementById("footer-form"),
|
const legalsForm = document.getElementById("legals-form");
|
||||||
legals: document.getElementById("legals-form"),
|
const buildForm = document.getElementById("build-form");
|
||||||
build: document.getElementById("build-form")
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Menu logic ---
|
// --- Menu logic ---
|
||||||
const menuList = document.getElementById("menu-items-list");
|
const menuList = document.getElementById("menu-items-list");
|
||||||
const addMenuBtn = document.getElementById("add-menu-item");
|
const addMenuBtn = document.getElementById("add-menu-item");
|
||||||
let menuItems = [];
|
let menuItems = [];
|
||||||
|
|
||||||
function renderMenuItems() {
|
function renderMenuItems() {
|
||||||
menuList.innerHTML = "";
|
menuList.innerHTML = "";
|
||||||
menuItems.forEach((item, idx) => {
|
menuItems.forEach((item, idx) => {
|
||||||
menuList.innerHTML += `
|
const div = document.createElement("div");
|
||||||
<div style="display:flex;gap:8px;margin-bottom:6px;">
|
div.style.display = "flex";
|
||||||
|
div.style.gap = "8px";
|
||||||
|
div.style.marginBottom = "6px";
|
||||||
|
div.innerHTML = `
|
||||||
<input type="text" placeholder="Label" value="${item.label || ""}" style="flex:1;" data-idx="${idx}" data-type="label" required>
|
<input type="text" placeholder="Label" value="${item.label || ""}" style="flex:1;" data-idx="${idx}" data-type="label" required>
|
||||||
<input type="text" placeholder="?tag=tag1,tag2" value="${item.href || ""}" style="flex:2;" data-idx="${idx}" data-type="href" required>
|
<input type="text" placeholder="?tag=tag1,tag2" value="${item.href || ""}" style="flex:2;" data-idx="${idx}" data-type="href" required>
|
||||||
<button type="button" class="remove-menu-item" data-idx="${idx}">🗑</button>
|
<button type="button" class="remove-menu-item" data-idx="${idx}">🗑</button>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
menuList.appendChild(div);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMenuItemsFromInputs() {
|
function updateMenuItemsFromInputs() {
|
||||||
const inputs = menuList.querySelectorAll("input");
|
const inputs = menuList.querySelectorAll("input");
|
||||||
menuItems = [];
|
const items = [];
|
||||||
for (let i = 0; i < inputs.length; i += 2) {
|
for (let i = 0; i < inputs.length; i += 2) {
|
||||||
const label = inputs[i].value.trim();
|
const label = inputs[i].value.trim();
|
||||||
const href = inputs[i + 1].value.trim();
|
const href = inputs[i + 1].value.trim();
|
||||||
if (label || href) menuItems.push({ label, href });
|
if (label || href) items.push({ label, href });
|
||||||
}
|
}
|
||||||
|
menuItems = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Intellectual property paragraphs logic ---
|
// --- Intellectual property paragraphs logic ---
|
||||||
const ipList = document.getElementById("ip-list");
|
const ipList = document.getElementById("ip-list");
|
||||||
const addIpBtn = document.getElementById("add-ip-paragraph");
|
const addIpBtn = document.getElementById("add-ip-paragraph");
|
||||||
let ipParagraphs = [];
|
let ipParagraphs = [];
|
||||||
|
|
||||||
function renderIpParagraphs() {
|
function renderIpParagraphs() {
|
||||||
ipList.innerHTML = "";
|
ipList.innerHTML = "";
|
||||||
ipParagraphs.forEach((item, idx) => {
|
ipParagraphs.forEach((item, idx) => {
|
||||||
ipList.innerHTML += `
|
const div = document.createElement("div");
|
||||||
<div style="display:flex;gap:8px;margin-bottom:6px;">
|
div.style.display = "flex";
|
||||||
|
div.style.gap = "8px";
|
||||||
|
div.style.marginBottom = "6px";
|
||||||
|
div.innerHTML = `
|
||||||
<textarea placeholder="Paragraph" required style="flex:1;" data-idx="${idx}">${item.paragraph || ""}</textarea>
|
<textarea placeholder="Paragraph" required style="flex:1;" data-idx="${idx}">${item.paragraph || ""}</textarea>
|
||||||
<button type="button" class="remove-ip-paragraph" data-idx="${idx}">🗑</button>
|
<button type="button" class="remove-ip-paragraph" data-idx="${idx}">🗑</button>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
ipList.appendChild(div);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateIpParagraphsFromInputs() {
|
function updateIpParagraphsFromInputs() {
|
||||||
ipParagraphs = Array.from(ipList.querySelectorAll("textarea"))
|
const textareas = ipList.querySelectorAll("textarea");
|
||||||
.map(textarea => ({ paragraph: textarea.value.trim() }))
|
ipParagraphs = Array.from(textareas).map(textarea => ({
|
||||||
.filter(item => item.paragraph !== "");
|
paragraph: textarea.value.trim()
|
||||||
|
})).filter(item => item.paragraph !== "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Build options & Theme select ---
|
// --- Build options ---
|
||||||
const convertImagesCheckbox = document.getElementById("convert-images-checkbox");
|
const convertImagesCheckbox = document.getElementById("convert-images-checkbox");
|
||||||
const resizeImagesCheckbox = document.getElementById("resize-images-checkbox");
|
const resizeImagesCheckbox = document.getElementById("resize-images-checkbox");
|
||||||
|
|
||||||
|
// --- Theme select ---
|
||||||
const themeSelect = document.getElementById("theme-select");
|
const themeSelect = document.getElementById("theme-select");
|
||||||
|
|
||||||
// --- Thumbnail upload and modal logic ---
|
// --- Thumbnail upload and modal logic ---
|
||||||
@ -94,30 +107,40 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const thumbnailPreview = document.getElementById("thumbnail-preview");
|
const thumbnailPreview = document.getElementById("thumbnail-preview");
|
||||||
const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn");
|
const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn");
|
||||||
|
|
||||||
// --- Modal helpers ---
|
// --- Modal elements for delete confirmation ---
|
||||||
function setupModal(modal, closeBtn, confirmBtn, cancelBtn, onConfirm) {
|
const deleteModal = document.getElementById("delete-modal");
|
||||||
if (!modal) return;
|
const deleteModalClose = document.getElementById("delete-modal-close");
|
||||||
if (closeBtn) closeBtn.onclick = () => modal.style.display = "none";
|
const deleteModalConfirm = document.getElementById("delete-modal-confirm");
|
||||||
if (cancelBtn) cancelBtn.onclick = () => modal.style.display = "none";
|
const deleteModalCancel = document.getElementById("delete-modal-cancel");
|
||||||
window.addEventListener("click", (e) => {
|
|
||||||
if (e.target === modal) modal.style.display = "none";
|
|
||||||
});
|
|
||||||
if (confirmBtn && onConfirm) confirmBtn.onclick = onConfirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Thumbnail preview logic ---
|
// --- Modal elements for theme deletion ---
|
||||||
|
const deleteThemeModal = document.getElementById("delete-theme-modal");
|
||||||
|
const deleteThemeModalClose = document.getElementById("delete-theme-modal-close");
|
||||||
|
const deleteThemeModalConfirm = document.getElementById("delete-theme-modal-confirm");
|
||||||
|
const deleteThemeModalCancel = document.getElementById("delete-theme-modal-cancel");
|
||||||
|
const deleteThemeModalText = document.getElementById("delete-theme-modal-text");
|
||||||
|
let themeToDelete = null;
|
||||||
|
|
||||||
|
// --- Show/hide thumbnail preview, remove button, and choose button ---
|
||||||
function updateThumbnailPreview(src) {
|
function updateThumbnailPreview(src) {
|
||||||
if (thumbnailPreview) {
|
if (thumbnailPreview) {
|
||||||
thumbnailPreview.src = src || "";
|
thumbnailPreview.src = src || "";
|
||||||
thumbnailPreview.style.display = src ? "block" : "none";
|
thumbnailPreview.style.display = src ? "block" : "none";
|
||||||
}
|
}
|
||||||
if (removeThumbnailBtn) removeThumbnailBtn.style.display = src ? "inline-block" : "none";
|
if (removeThumbnailBtn) {
|
||||||
if (chooseThumbnailBtn) chooseThumbnailBtn.style.display = src ? "none" : "inline-block";
|
removeThumbnailBtn.style.display = src ? "inline-block" : "none";
|
||||||
|
}
|
||||||
|
if (chooseThumbnailBtn) {
|
||||||
|
chooseThumbnailBtn.style.display = src ? "none" : "inline-block";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Choose thumbnail button triggers file input ---
|
||||||
if (chooseThumbnailBtn && thumbnailUpload) {
|
if (chooseThumbnailBtn && thumbnailUpload) {
|
||||||
chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click());
|
chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Handle thumbnail upload and refresh preview (with cache busting) ---
|
||||||
if (thumbnailUpload) {
|
if (thumbnailUpload) {
|
||||||
thumbnailUpload.addEventListener("change", async (e) => {
|
thumbnailUpload.addEventListener("change", async (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
@ -138,17 +161,25 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
updateSectionStatus("social");
|
updateSectionStatus("social");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Remove thumbnail button triggers modal ---
|
||||||
if (removeThumbnailBtn) {
|
if (removeThumbnailBtn) {
|
||||||
removeThumbnailBtn.addEventListener("click", () => {
|
removeThumbnailBtn.addEventListener("click", () => {
|
||||||
document.getElementById("delete-modal").style.display = "flex";
|
deleteModal.style.display = "flex";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setupModal(
|
|
||||||
document.getElementById("delete-modal"),
|
// --- Modal logic for thumbnail deletion ---
|
||||||
document.getElementById("delete-modal-close"),
|
if (deleteModal && deleteModalClose && deleteModalConfirm && deleteModalCancel) {
|
||||||
document.getElementById("delete-modal-confirm"),
|
deleteModalClose.onclick = deleteModalCancel.onclick = () => {
|
||||||
document.getElementById("delete-modal-cancel"),
|
deleteModal.style.display = "none";
|
||||||
async () => {
|
};
|
||||||
|
window.onclick = function(event) {
|
||||||
|
if (event.target === deleteModal) {
|
||||||
|
deleteModal.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deleteModalConfirm.onclick = async () => {
|
||||||
const res = await fetch("/api/thumbnail/remove", { method: "POST" });
|
const res = await fetch("/api/thumbnail/remove", { method: "POST" });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
@ -158,28 +189,41 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
} else {
|
} else {
|
||||||
showToast("❌ Error removing thumbnail", "error");
|
showToast("❌ Error removing thumbnail", "error");
|
||||||
}
|
}
|
||||||
document.getElementById("delete-modal").style.display = "none";
|
deleteModal.style.display = "none";
|
||||||
updateSectionStatus("social");
|
updateSectionStatus("social");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// --- Theme upload logic ---
|
// --- Theme upload logic (custom theme folder) ---
|
||||||
const themeUpload = document.getElementById("theme-upload");
|
const themeUpload = document.getElementById("theme-upload");
|
||||||
const chooseThemeBtn = document.getElementById("choose-theme-btn");
|
const chooseThemeBtn = document.getElementById("choose-theme-btn");
|
||||||
if (chooseThemeBtn && themeUpload) {
|
if (chooseThemeBtn && themeUpload) {
|
||||||
chooseThemeBtn.addEventListener("click", () => themeUpload.click());
|
chooseThemeBtn.addEventListener("click", () => themeUpload.click());
|
||||||
themeUpload.addEventListener("change", async (e) => {
|
themeUpload.addEventListener("change", async (e) => {
|
||||||
const files = Array.from(e.target.files);
|
const files = Array.from(e.target.files);
|
||||||
if (!files.length) return;
|
if (files.length === 0) return;
|
||||||
showLoader("Uploading theme...");
|
showLoader("Uploading theme...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
files.forEach(file => formData.append("files", file, file.webkitRelativePath || file.name));
|
files.forEach(file => {
|
||||||
|
formData.append("files", file, file.webkitRelativePath || file.name);
|
||||||
|
});
|
||||||
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
hideLoader();
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Theme uploaded!", "success");
|
showToast("✅ Theme uploaded!", "success");
|
||||||
refreshThemes();
|
// Refresh theme select after upload
|
||||||
|
fetch("/api/themes")
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(themes => {
|
||||||
|
themeSelect.innerHTML = "";
|
||||||
|
themes.forEach(theme => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = theme;
|
||||||
|
option.textContent = theme;
|
||||||
|
themeSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
showToast("❌ Error uploading theme", "error");
|
showToast("❌ Error uploading theme", "error");
|
||||||
}
|
}
|
||||||
@ -187,9 +231,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Remove theme logic ---
|
// --- Remove theme button triggers modal ---
|
||||||
const removeThemeBtn = document.getElementById("remove-theme-btn");
|
const removeThemeBtn = document.getElementById("remove-theme-btn");
|
||||||
let themeToDelete = null;
|
|
||||||
if (removeThemeBtn && themeSelect) {
|
if (removeThemeBtn && themeSelect) {
|
||||||
removeThemeBtn.addEventListener("click", () => {
|
removeThemeBtn.addEventListener("click", () => {
|
||||||
const theme = themeSelect.value;
|
const theme = themeSelect.value;
|
||||||
@ -199,16 +242,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
themeToDelete = theme;
|
themeToDelete = theme;
|
||||||
document.getElementById("delete-theme-modal-text").textContent = `Are you sure you want to remove theme "${theme}"?`;
|
deleteThemeModalText.textContent = `Are you sure you want to remove theme "${theme}"?`;
|
||||||
document.getElementById("delete-theme-modal").style.display = "flex";
|
deleteThemeModal.style.display = "flex";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setupModal(
|
|
||||||
document.getElementById("delete-theme-modal"),
|
// --- Modal logic for theme deletion ---
|
||||||
document.getElementById("delete-theme-modal-close"),
|
if (deleteThemeModal && deleteThemeModalClose && deleteThemeModalConfirm && deleteThemeModalCancel) {
|
||||||
document.getElementById("delete-theme-modal-confirm"),
|
deleteThemeModalClose.onclick = deleteThemeModalCancel.onclick = () => {
|
||||||
document.getElementById("delete-theme-modal-cancel"),
|
deleteThemeModal.style.display = "none";
|
||||||
async () => {
|
themeToDelete = null;
|
||||||
|
};
|
||||||
|
window.onclick = function(event) {
|
||||||
|
if (event.target === deleteThemeModal) {
|
||||||
|
deleteThemeModal.style.display = "none";
|
||||||
|
themeToDelete = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deleteThemeModalConfirm.onclick = async () => {
|
||||||
if (!themeToDelete) return;
|
if (!themeToDelete) return;
|
||||||
showLoader("Removing theme...");
|
showLoader("Removing theme...");
|
||||||
const res = await fetch("/api/theme/remove", {
|
const res = await fetch("/api/theme/remove", {
|
||||||
@ -220,18 +271,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
hideLoader();
|
hideLoader();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Theme removed!", "success");
|
showToast("✅ Theme removed!", "success");
|
||||||
refreshThemes();
|
// Refresh theme select
|
||||||
} else {
|
|
||||||
showToast(result.error || "❌ Error removing theme", "error");
|
|
||||||
}
|
|
||||||
document.getElementById("delete-theme-modal").style.display = "none";
|
|
||||||
themeToDelete = null;
|
|
||||||
updateSectionStatus("build");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- Theme select refresh ---
|
|
||||||
function refreshThemes() {
|
|
||||||
fetch("/api/themes")
|
fetch("/api/themes")
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(themes => {
|
.then(themes => {
|
||||||
@ -242,11 +282,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
option.textContent = theme;
|
option.textContent = theme;
|
||||||
themeSelect.appendChild(option);
|
themeSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
loadConfigAndUpdateBuildStatus();
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
showToast(result.error || "❌ Error removing theme", "error");
|
||||||
|
}
|
||||||
|
deleteThemeModal.style.display = "none";
|
||||||
|
themeToDelete = null;
|
||||||
|
updateSectionStatus("build");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Config loading ---
|
// --- Fetch theme list and populate select, then load config and update build status ---
|
||||||
let loadedConfig = {};
|
let loadedConfig = {};
|
||||||
function loadConfigAndUpdateBuildStatus() {
|
function loadConfigAndUpdateBuildStatus() {
|
||||||
fetch("/api/site-info")
|
fetch("/api/site-info")
|
||||||
@ -254,17 +300,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
loadedConfig = data;
|
loadedConfig = data;
|
||||||
// Info
|
// Info
|
||||||
if (forms.info) {
|
if (infoForm) {
|
||||||
forms.info.elements["info.title"].value = data.info?.title || "";
|
infoForm.elements["info.title"].value = data.info?.title || "";
|
||||||
forms.info.elements["info.subtitle"].value = data.info?.subtitle || "";
|
infoForm.elements["info.subtitle"].value = data.info?.subtitle || "";
|
||||||
forms.info.elements["info.description"].value = data.info?.description || "";
|
infoForm.elements["info.description"].value = data.info?.description || "";
|
||||||
forms.info.elements["info.canonical"].value = data.info?.canonical || "";
|
infoForm.elements["info.canonical"].value = data.info?.canonical || "";
|
||||||
forms.info.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || "");
|
infoForm.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || "");
|
||||||
forms.info.elements["info.author"].value = data.info?.author || "";
|
infoForm.elements["info.author"].value = data.info?.author || "";
|
||||||
}
|
}
|
||||||
// Social
|
// Social
|
||||||
if (forms.social) {
|
if (socialForm) {
|
||||||
forms.social.elements["social.instagram_url"].value = data.social?.instagram_url || "";
|
socialForm.elements["social.instagram_url"].value = data.social?.instagram_url || "";
|
||||||
if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || "";
|
if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || "";
|
||||||
updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}?t=${Date.now()}` : "");
|
updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}?t=${Date.now()}` : "");
|
||||||
}
|
}
|
||||||
@ -272,37 +318,66 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
menuItems = Array.isArray(data.menu?.items) ? data.menu.items : [];
|
menuItems = Array.isArray(data.menu?.items) ? data.menu.items : [];
|
||||||
renderMenuItems();
|
renderMenuItems();
|
||||||
// Footer
|
// Footer
|
||||||
if (forms.footer) {
|
if (footerForm) {
|
||||||
forms.footer.elements["footer.copyright"].value = data.footer?.copyright || "";
|
footerForm.elements["footer.copyright"].value = data.footer?.copyright || "";
|
||||||
forms.footer.elements["footer.legal_label"].value = data.footer?.legal_label || "";
|
footerForm.elements["footer.legal_label"].value = data.footer?.legal_label || "";
|
||||||
}
|
}
|
||||||
// Legals
|
// Legals
|
||||||
ipParagraphs = Array.isArray(data.legals?.intellectual_property)
|
ipParagraphs = Array.isArray(data.legals?.intellectual_property)
|
||||||
? data.legals.intellectual_property
|
? data.legals.intellectual_property
|
||||||
: [];
|
: [];
|
||||||
renderIpParagraphs();
|
renderIpParagraphs();
|
||||||
if (forms.legals) {
|
if (legalsForm) {
|
||||||
forms.legals.elements["legals.hoster_name"].value = data.legals?.hoster_name || "";
|
legalsForm.elements["legals.hoster_name"].value = data.legals?.hoster_name || "";
|
||||||
forms.legals.elements["legals.hoster_address"].value = data.legals?.hoster_address || "";
|
legalsForm.elements["legals.hoster_address"].value = data.legals?.hoster_address || "";
|
||||||
forms.legals.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || "";
|
legalsForm.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || "";
|
||||||
}
|
}
|
||||||
// Build
|
// Build
|
||||||
if (themeSelect) themeSelect.value = data.build?.theme || "";
|
if (themeSelect) {
|
||||||
if (convertImagesCheckbox) convertImagesCheckbox.checked = !!data.build?.convert_images;
|
themeSelect.value = data.build?.theme || "";
|
||||||
if (resizeImagesCheckbox) resizeImagesCheckbox.checked = !!data.build?.resize_images;
|
}
|
||||||
|
if (convertImagesCheckbox) {
|
||||||
|
convertImagesCheckbox.checked = !!data.build?.convert_images;
|
||||||
|
}
|
||||||
|
if (resizeImagesCheckbox) {
|
||||||
|
resizeImagesCheckbox.checked = !!data.build?.resize_images;
|
||||||
|
}
|
||||||
|
// Initial status update for all sections except build
|
||||||
["info", "social", "menu", "footer", "legals"].forEach(updateSectionStatus);
|
["info", "social", "menu", "footer", "legals"].forEach(updateSectionStatus);
|
||||||
|
// For build, update status after theme select is set
|
||||||
updateSectionStatus("build");
|
updateSectionStatus("build");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (themeSelect) refreshThemes();
|
|
||||||
else loadConfigAndUpdateBuildStatus();
|
|
||||||
|
|
||||||
// --- Add/remove menu items ---
|
if (themeSelect) {
|
||||||
if (addMenuBtn) addMenuBtn.addEventListener("click", () => {
|
fetch("/api/themes")
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(themes => {
|
||||||
|
themeSelect.innerHTML = "";
|
||||||
|
themes.forEach(theme => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = theme;
|
||||||
|
option.textContent = theme;
|
||||||
|
themeSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
// Now load config and update build status after theme select is ready
|
||||||
|
loadConfigAndUpdateBuildStatus();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If no theme select, just load config
|
||||||
|
loadConfigAndUpdateBuildStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Add menu item ---
|
||||||
|
if (addMenuBtn) {
|
||||||
|
addMenuBtn.addEventListener("click", () => {
|
||||||
menuItems.push({ label: "", href: "" });
|
menuItems.push({ label: "", href: "" });
|
||||||
renderMenuItems();
|
renderMenuItems();
|
||||||
updateSectionStatus("menu");
|
updateSectionStatus("menu");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Remove menu item ---
|
||||||
menuList.addEventListener("click", (e) => {
|
menuList.addEventListener("click", (e) => {
|
||||||
if (e.target.classList.contains("remove-menu-item")) {
|
if (e.target.classList.contains("remove-menu-item")) {
|
||||||
const idx = parseInt(e.target.getAttribute("data-idx"));
|
const idx = parseInt(e.target.getAttribute("data-idx"));
|
||||||
@ -311,17 +386,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
updateSectionStatus("menu");
|
updateSectionStatus("menu");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Update menuItems on input change ---
|
||||||
menuList.addEventListener("input", () => {
|
menuList.addEventListener("input", () => {
|
||||||
updateMenuItemsFromInputs();
|
updateMenuItemsFromInputs();
|
||||||
updateSectionStatus("menu");
|
updateSectionStatus("menu");
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Add/remove IP paragraphs ---
|
// --- Add paragraph ---
|
||||||
if (addIpBtn) addIpBtn.addEventListener("click", () => {
|
if (addIpBtn) {
|
||||||
|
addIpBtn.addEventListener("click", () => {
|
||||||
ipParagraphs.push({ paragraph: "" });
|
ipParagraphs.push({ paragraph: "" });
|
||||||
renderIpParagraphs();
|
renderIpParagraphs();
|
||||||
updateSectionStatus("legals");
|
updateSectionStatus("legals");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Remove paragraph ---
|
||||||
ipList.addEventListener("click", (e) => {
|
ipList.addEventListener("click", (e) => {
|
||||||
if (e.target.classList.contains("remove-ip-paragraph")) {
|
if (e.target.classList.contains("remove-ip-paragraph")) {
|
||||||
const idx = parseInt(e.target.getAttribute("data-idx"));
|
const idx = parseInt(e.target.getAttribute("data-idx"));
|
||||||
@ -330,6 +411,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
updateSectionStatus("legals");
|
updateSectionStatus("legals");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Update ipParagraphs on input change ---
|
||||||
ipList.addEventListener("input", () => {
|
ipList.addEventListener("input", () => {
|
||||||
updateIpParagraphsFromInputs();
|
updateIpParagraphsFromInputs();
|
||||||
updateSectionStatus("legals");
|
updateSectionStatus("legals");
|
||||||
@ -340,16 +423,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
switch (section) {
|
switch (section) {
|
||||||
case "info":
|
case "info":
|
||||||
return {
|
return {
|
||||||
title: forms.info.elements["info.title"].value,
|
title: infoForm.elements["info.title"].value,
|
||||||
subtitle: forms.info.elements["info.subtitle"].value,
|
subtitle: infoForm.elements["info.subtitle"].value,
|
||||||
description: forms.info.elements["info.description"].value,
|
description: infoForm.elements["info.description"].value,
|
||||||
canonical: forms.info.elements["info.canonical"].value,
|
canonical: infoForm.elements["info.canonical"].value,
|
||||||
keywords: forms.info.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean),
|
keywords: infoForm.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean),
|
||||||
author: forms.info.elements["info.author"].value
|
author: infoForm.elements["info.author"].value
|
||||||
};
|
};
|
||||||
case "social":
|
case "social":
|
||||||
return {
|
return {
|
||||||
instagram_url: forms.social.elements["social.instagram_url"].value,
|
instagram_url: socialForm.elements["social.instagram_url"].value,
|
||||||
thumbnail: thumbnailInput ? thumbnailInput.value : ""
|
thumbnail: thumbnailInput ? thumbnailInput.value : ""
|
||||||
};
|
};
|
||||||
case "menu":
|
case "menu":
|
||||||
@ -357,15 +440,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return { items: menuItems };
|
return { items: menuItems };
|
||||||
case "footer":
|
case "footer":
|
||||||
return {
|
return {
|
||||||
copyright: forms.footer.elements["footer.copyright"].value,
|
copyright: footerForm.elements["footer.copyright"].value,
|
||||||
legal_label: forms.footer.elements["footer.legal_label"].value
|
legal_label: footerForm.elements["footer.legal_label"].value
|
||||||
};
|
};
|
||||||
case "legals":
|
case "legals":
|
||||||
updateIpParagraphsFromInputs();
|
updateIpParagraphsFromInputs();
|
||||||
return {
|
return {
|
||||||
hoster_name: forms.legals.elements["legals.hoster_name"].value,
|
hoster_name: legalsForm.elements["legals.hoster_name"].value,
|
||||||
hoster_address: forms.legals.elements["legals.hoster_address"].value,
|
hoster_address: legalsForm.elements["legals.hoster_address"].value,
|
||||||
hoster_contact: forms.legals.elements["legals.hoster_contact"].value,
|
hoster_contact: legalsForm.elements["legals.hoster_contact"].value,
|
||||||
intellectual_property: ipParagraphs
|
intellectual_property: ipParagraphs
|
||||||
};
|
};
|
||||||
case "build":
|
case "build":
|
||||||
@ -450,7 +533,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
values.intellectual_property.every(ip => ip.paragraph)
|
values.intellectual_property.every(ip => ip.paragraph)
|
||||||
);
|
);
|
||||||
case "build":
|
case "build":
|
||||||
return !!values.theme;
|
return !!values.theme; // Only check theme is present
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -478,22 +561,46 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Listen for changes in each section ---
|
// --- Listen for changes in each section ---
|
||||||
Object.entries(forms).forEach(([section, form]) => {
|
[
|
||||||
|
{ form: infoForm, section: "info" },
|
||||||
|
{ form: socialForm, section: "social" },
|
||||||
|
{ form: menuForm, section: "menu" },
|
||||||
|
{ form: footerForm, section: "footer" },
|
||||||
|
{ form: legalsForm, section: "legals" },
|
||||||
|
{ form: buildForm, section: "build" }
|
||||||
|
].forEach(({ form, section }) => {
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
form.addEventListener("input", () => updateSectionStatus(section));
|
form.addEventListener("input", () => updateSectionStatus(section));
|
||||||
form.addEventListener("change", () => updateSectionStatus(section));
|
form.addEventListener("change", () => updateSectionStatus(section));
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Save section handler (form submit) ---
|
||||||
|
[
|
||||||
|
{ form: infoForm, section: "info" },
|
||||||
|
{ form: socialForm, section: "social" },
|
||||||
|
{ form: menuForm, section: "menu" },
|
||||||
|
{ form: footerForm, section: "footer" },
|
||||||
|
{ form: legalsForm, section: "legals" },
|
||||||
|
{ form: buildForm, section: "build" }
|
||||||
|
].forEach(({ form, section }) => {
|
||||||
|
if (!form) return;
|
||||||
form.addEventListener("submit", async (e) => {
|
form.addEventListener("submit", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// Native browser validation
|
||||||
if (!form.reportValidity()) {
|
if (!form.reportValidity()) {
|
||||||
showToast("❌ Please fill all required fields before saving.", "error");
|
showToast("❌ Please fill all required fields before saving.", "error");
|
||||||
updateSectionStatus(section);
|
updateSectionStatus(section);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (section === "social" && (!thumbnailInput || !thumbnailInput.value)) {
|
// Social section: check thumbnail
|
||||||
|
if (section === "social") {
|
||||||
|
if (!thumbnailInput || !thumbnailInput.value) {
|
||||||
showToast("❌ Thumbnail is required.", "error");
|
showToast("❌ Thumbnail is required.", "error");
|
||||||
updateSectionStatus(section);
|
updateSectionStatus(section);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Menu section: check all menu items
|
||||||
if (section === "menu") {
|
if (section === "menu") {
|
||||||
updateMenuItemsFromInputs();
|
updateMenuItemsFromInputs();
|
||||||
if (!menuItems.length || !menuItems.every(item => item.label && item.href)) {
|
if (!menuItems.length || !menuItems.every(item => item.label && item.href)) {
|
||||||
@ -502,6 +609,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Legals section: check all paragraphs
|
||||||
if (section === "legals") {
|
if (section === "legals") {
|
||||||
updateIpParagraphsFromInputs();
|
updateIpParagraphsFromInputs();
|
||||||
if (!ipParagraphs.length || !ipParagraphs.every(ip => ip.paragraph)) {
|
if (!ipParagraphs.length || !ipParagraphs.every(ip => ip.paragraph)) {
|
||||||
@ -510,8 +618,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Build payload for this section only
|
||||||
let payload = {};
|
let payload = {};
|
||||||
payload[section] = getSectionValues(section);
|
payload[section] = getSectionValues(section);
|
||||||
|
|
||||||
const res = await fetch("/api/site-info", {
|
const res = await fetch("/api/site-info", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@ -520,6 +630,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
showToast("✅ Section saved!", "success");
|
showToast("✅ Section saved!", "success");
|
||||||
|
// Reload config for this section
|
||||||
fetch("/api/site-info")
|
fetch("/api/site-info")
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -531,4 +642,5 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
@ -70,11 +70,9 @@ function setupColorPicker(colorId, btnId, textId, initial) {
|
|||||||
function setFontDropdown(selectId, value, options) {
|
function setFontDropdown(selectId, value, options) {
|
||||||
const select = document.getElementById(selectId);
|
const select = document.getElementById(selectId);
|
||||||
if (!select) return;
|
if (!select) return;
|
||||||
select.innerHTML = options.map(opt => {
|
select.innerHTML = options.map(opt =>
|
||||||
// Remove extension if present
|
`<option value="${opt}"${opt === value ? " selected" : ""}>${opt}</option>`
|
||||||
const base = opt.replace(/\.(woff2?|ttf|otf)$/, "");
|
).join("");
|
||||||
return `<option value="${base}"${base === value ? " selected" : ""}>${base}</option>`;
|
|
||||||
}).join("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFallbackDropdown(selectId, value) {
|
function setFallbackDropdown(selectId, value) {
|
||||||
|
@ -11,37 +11,107 @@ function hideLoader() {
|
|||||||
if (loader) loader.classList.remove("active");
|
if (loader) loader.classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Generic upload handler ---
|
|
||||||
function setupUpload(inputId, apiUrl, loaderText, successMsg, refreshFn) {
|
// --- Upload gallery images ---
|
||||||
const input = document.getElementById(inputId);
|
const galleryInput = document.getElementById('upload-gallery');
|
||||||
if (!input) return;
|
if (galleryInput) {
|
||||||
input.addEventListener('change', async (e) => {
|
galleryInput.addEventListener('change', async (e) => {
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
if (!files.length) return;
|
if (!files.length) return;
|
||||||
showLoader(loaderText);
|
showLoader("Uploading photos...");
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
for (const file of files) formData.append('files', file);
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(apiUrl, { method: 'POST', body: formData });
|
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
hideLoader();
|
hideLoader();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
showToast(`✅ ${data.uploaded.length} ${successMsg}`, "success");
|
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
||||||
if (typeof refreshFn === "function") refreshFn();
|
if (typeof refreshGallery === "function") refreshGallery();
|
||||||
} else showToast('Error: ' + data.error, "error");
|
} else showToast('Error: ' + data.error, "error");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
hideLoader();
|
hideLoader();
|
||||||
console.error(err);
|
console.error(err);
|
||||||
showToast('Server error!', "error");
|
showToast('Server error!', "error");
|
||||||
} finally {
|
} finally { e.target.value = ''; }
|
||||||
e.target.value = '';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Setup all upload inputs ---
|
// --- Upload hero images ---
|
||||||
setupUpload('upload-gallery', '/api/gallery/upload', "Uploading photos...", "gallery image(s) uploaded!", refreshGallery);
|
const heroInput = document.getElementById('upload-hero');
|
||||||
setupUpload('upload-hero', '/api/hero/upload', "Uploading hero photos...", "hero image(s) uploaded!", refreshHero);
|
if (heroInput) {
|
||||||
setupUpload('upload-gallery-bottom', '/api/gallery/upload', "Uploading photos...", "gallery image(s) uploaded!", refreshGallery);
|
heroInput.addEventListener('change', async (e) => {
|
||||||
setupUpload('upload-hero-bottom', '/api/hero/upload', "Uploading hero photos...", "hero image(s) uploaded!", refreshHero);
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading hero photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
|
hideLoader();
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
||||||
|
if (typeof refreshHero === "function") refreshHero();
|
||||||
|
} else showToast('Error: ' + data.error, "error");
|
||||||
|
} catch(err) {
|
||||||
|
hideLoader();
|
||||||
|
console.error(err);
|
||||||
|
showToast('Server error!', "error");
|
||||||
|
} finally { e.target.value = ''; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Upload gallery images (bottom button) ---
|
||||||
|
const galleryInputBottom = document.getElementById('upload-gallery-bottom');
|
||||||
|
if (galleryInputBottom) {
|
||||||
|
galleryInputBottom.addEventListener('change', async (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/gallery/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
|
hideLoader();
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(`✅ ${data.uploaded.length} gallery image(s) uploaded!`, "success");
|
||||||
|
if (typeof refreshGallery === "function") refreshGallery();
|
||||||
|
} else showToast('Error: ' + data.error, "error");
|
||||||
|
} catch(err) {
|
||||||
|
hideLoader();
|
||||||
|
console.error(err);
|
||||||
|
showToast('Server error!', "error");
|
||||||
|
} finally { e.target.value = ''; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Upload hero images (bottom button) ---
|
||||||
|
const heroInputBottom = document.getElementById('upload-hero-bottom');
|
||||||
|
if (heroInputBottom) {
|
||||||
|
heroInputBottom.addEventListener('change', async (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
showLoader("Uploading hero photos...");
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const file of files) formData.append('files', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/hero/upload', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
|
hideLoader();
|
||||||
|
if (res.ok) {
|
||||||
|
showToast(`✅ ${data.uploaded.length} hero image(s) uploaded!`, "success");
|
||||||
|
if (typeof refreshHero === "function") refreshHero();
|
||||||
|
} else showToast('Error: ' + data.error, "error");
|
||||||
|
} catch(err) {
|
||||||
|
hideLoader();
|
||||||
|
console.error(err);
|
||||||
|
showToast('Server error!', "error");
|
||||||
|
} finally { e.target.value = ''; }
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user