Das Java Script musste angepasst werden wegen dem Freischalten Button Tommy, der ist jetzt ja da
Code: Alles auswählen
<script>
(function() {
var inBuilder = (typeof MbrsiteApp !== 'undefined') || (window.self !== window.top && /mobirise/i.test(document.referrer));
if (inBuilder) return;
var apiPath = 'guestbook-api.php';
var feedContainer = document.getElementById('js-guestbook-feed');
var paginationContainer = document.getElementById('js-gb-pagination');
var form = document.getElementById('js-guestbook-form');
var statusDiv = document.getElementById('gb-status');
var msgField = document.getElementById('gb-message');
var currentSection = document.getElementById('js-guestbook-feed') ? document.getElementById('js-guestbook-feed').closest('section') : null;
var allEntries = [];
var currentPage = 1;
var itemsPerPage = parseInt('{{perPage}}') || 5;
var isAdmin = false;
var enteredPassword = '';
// 🔐 TEXT-CODIERUNG GEGEN ROTE FEHLER IM EDITOR
var emojiCategories = {
smile: decodeURIComponent("%F0%9F%98%80,%F0%9F%98%83,%F0%9F%98%84,%F0%9F%98%91,%F0%9F%98%86,%F0%9F%98%85,%F0%9F%98%82,%F0%9F%A4%A3,%F0%9F%98%8A,%F0%9F%98%87,%F0%9F%9A%82,%F0%9F%98%83,%F0%9F%98%89,%F0%9F%98%8C,%F0%9F%98%8D,%F0%9F%A5%B0,%F0%9F%98%98,%F0%9F%98%97,%F0%9F%98%99,%F0%9F%98%9A,%F0%9F%98%8B,%F0%9F%98%9B,%F0%9F%98%9D,%F0%9F%98%9C,%F0%9F%A4%AA,%F0%9F%A5%A2,%F0%9F%A7%90,%F0%9F%A5%93,%F0%9F%98%8E,%F0%9F%A5%A9,%F0%9F%A5%B3,%F0%9F%98%8F,%F0%9F%98%92,%F0%9F%98%94,%F0%9F%A7%A0,%F0%9F%92%80,%F0%9F%92%A9,%F0%9F%A5%A1").split(","),
gestures: decodeURIComponent("%F0%9F%91%8B,%F0%9F%A4%9A,%F0%9F%95%90,%F0%9F%94%90,%F0%9F%96%96,%F0%9F%91%8C,%F0%9F%A5%8C,%F0%9F%A5%8F,%F0%9F%90%8C,%F0%9F%A4%9E,%F0%9F%A4%9F,%F0%9F%A5%98,%F0%9F%A5%99,%F0%9F%91%88,%F0%9F%91%89,%F0%9F%91%86,%F0%9F%95%95,%F0%9F%91%87,%F0%9F%95%96,%F0%9F%91%8D,%F0%9F%91%8E,%F0%9F%A5%92,%F0%9F%91%8A,%F0%9F%A5%9B,%F0%9F%A5%9C,%F0%9F%91%8F,%F0%9F%99%8C,%F0%9F%90%90,%F0%9F%A4%B2,%F0%9F%A4%9D,%F0%9F%99%8F,%F0%9F%92%AA").split(","),
camping: decodeURIComponent("%F0%9F%8F%95,%F0%9F%9A%BA,%E2%98%80%EF%B8%8F,%F0%9F%8C%A4%EF%B8%8F,%E2%9B%85,%F0%9F%8C%A6%EF%B8%8F,%F0%9F%8C%A7%EF%B8%8F,%F0%9F%8C%A9%EF%B8%8F,%E2%9D%84%EF%B8%8F,%F0%9F%8C%B2,%F0%9F%8C%B3,%F0%9F%8C%B4,%F0%9F%8C%B5,%F0%9F%8C%B2,%F0%9F%9A%97,%F0%9F%9A%99,%F0%9F%9A%8C,%F0%9F%9A%90,%F0%9F%9A%B2,%F0%9F%9A%B5,%F0%9F%8F%8D,%F0%9F%9A%A6,%E2%9B%B5,%E2%9B%95,%F0%9F%8F%96%EF%B8%8F,%F0%9F%8C%9D,%F0%9F%8C%9E,%F0%9F%8C%A0,%F0%9F%94%A5,%F0%9F%8D%BA,%F0%9F%8D%B7,%E2%98%95,%F0%9F%8D%B4,%F0%9F%A5%A9,%F0%9F%8D%B3").split(","),
};
if (currentSection) {
var titleEl = currentSection.querySelector('.js-gb-main-title');
if (titleEl) {
titleEl.addEventListener('dblclick', function(e) {
e.preventDefault();
var pwCheck = prompt('Bitte Admin-Passwort eingeben:');
if (pwCheck !== null && pwCheck.trim() !== '') {
fetch(apiPath, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'checkLogin', password: pwCheck.trim() })
})
.then(response => response.json())
.then(data => {
if (data.success) {
isAdmin = true;
enteredPassword = pwCheck.trim();
alert('Login erfolgreich! Ungeprüfte Beiträge zeigen jetzt das Freischaltfeld. 🔓');
loadEntries();
} else {
alert(data.error);
}
});
}
});
}
var pickerPanel = currentSection.querySelector('.js-emoji-picker-panel');
var toggleBtn = currentSection.querySelector('.js-toggle-emoji-picker');
var gridContainer = currentSection.querySelector('.js-picker-emojis-grid');
var activeCategory = 'smile';
if (pickerPanel && toggleBtn && gridContainer) {
function renderCategory(cat) {
var list = [];
if (cat === 'recent') {
list = JSON.parse(localStorage.getItem('gb_recent_emojis')) || [decodeURIComponent("%F0%9F%98%80"), decodeURIComponent("%F0%9F%98%82"), decodeURIComponent("%F0%9F%98%89"), decodeURIComponent("%F0%9F%98%8D"), decodeURIComponent("%F0%9F%91%8D")];
} else {
list = emojiCategories[cat] || [];
}
var html = '';
list.forEach(function(emo) {
html += '<span class="picker-pro-emo">' + emo + '</span>';
});
gridContainer.innerHTML = html;
}
renderCategory(activeCategory);
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
if (pickerPanel.style.display === 'none') {
pickerPanel.style.display = 'block';
toggleBtn.classList.add('active');
} else {
pickerPanel.style.display = 'none';
toggleBtn.classList.remove('active');
}
});
pickerPanel.querySelectorAll('.tab-btn').forEach(function(tab) {
tab.addEventListener('click', function(e) {
e.preventDefault();
pickerPanel.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
tab.classList.add('active');
activeCategory = tab.getAttribute('data-cat');
renderCategory(activeCategory);
});
});
gridContainer.addEventListener('click', function(e) {
if (e.target.classList.contains('picker-pro-emo')) {
var chosenEmoji = e.target.textContent;
insertEmoji(chosenEmoji);
var recents = JSON.parse(localStorage.getItem('gb_recent_emojis')) || [decodeURIComponent("%F0%9F%98%80"), decodeURIComponent("%F0%9F%98%82"), decodeURIComponent("%F0%9F%98%89"), decodeURIComponent("%F0%9F%98%8D"), decodeURIComponent("%F0%9F%91%8D")];
recents = recents.filter(item => item !== chosenEmoji);
recents.unshift(chosenEmoji);
if (recents.length > 15) recents.pop();
localStorage.setItem('gb_recent_emojis', JSON.stringify(recents));
if (activeCategory === 'recent') renderCategory('recent');
}
});
}
}
function insertEmoji(emoji) {
if (!msgField) return;
var start = msgField.selectionStart;
var end = msgField.selectionEnd;
var text = msgField.value;
msgField.value = text.substring(0, start) + emoji + text.substring(end);
msgField.focus();
msgField.selectionStart = msgField.selectionEnd = start + emoji.length;
}
function renderFeed() {
if (!feedContainer) return;
if (!allEntries || allEntries.length === 0) {
feedContainer.innerHTML = '<div class="text-center py-4 text-fallback">Noch keine freigeschalteten Einträge vorhanden.</div>';
if (paginationContainer) paginationContainer.innerHTML = '';
return;
}
var startIndex = (currentPage - 1) * itemsPerPage;
var endIndex = startIndex + itemsPerPage;
var pageEntries = allEntries.slice(startIndex, endIndex);
var html = '';
pageEntries.forEach(function(entry) {
var date = new Date(entry.created_at.replace(/-/g, "/"));
var formattedDate = date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) + ' - ' + date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
var styleOverride = (entry.status == 0) ? 'style="border: 1px solid #eab308 !important; background: rgba(234, 179, 8, 0.08) !important;"' : '';
var titleAddon = (entry.status == 0) ? ' <span style="color:#eab308;font-size:0.75rem;">(Wartet auf Freischaltung)</span>' : '';
html += '<div class="guestbook-entry-card mb-3 glass-card" data-id="' + entry.id + '" ' + styleOverride + '>';
html += ' <div class="entry-header">';
html += ' <strong class="entry-author">' + entry.name + titleAddon + '</strong>';
html += ' <div class="d-flex align-items-center gap-3">';
html += ' <span class="entry-date">' + formattedDate + '</span>';
// 🛠️ FIX: Wir nutzen hier wieder die robusten Emojis direkt im JS, damit keine Variablen aus dem Mobirise-Zahnrad fehlen können!
if (isAdmin) {
html += ' <button class="btn-edit-entry" title="Eintrag bearbeiten" style="background:none; border:none; cursor:pointer; font-size:1.1rem; padding:0; line-height:1;">✏️</button>';
html += ' <button class="btn-delete-entry" title="Eintrag löschen" style="background:none; border:none; cursor:pointer; font-size:1.1rem; padding:0; line-height:1;">🗑️</button>';
}
html += ' </div>';
html += ' </div>';
html += ' <p class="entry-text js-entry-text-target">' + entry.message.replace(/\n/g, '<br>') + '</p>';
// 🟢 DER FETTE BUTTON MIT KORREKTEM HOOK
if (isAdmin && entry.status == 0) {
html += ' <div class="mt-3 text-end js-approve-wrapper">';
html += ' <button class="btn btn-sm btn-success btn-approve-action" style="padding: 6px 14px; font-size: 0.85rem; font-weight: bold; border-radius: 6px; border: none; background-color: #22c55e !important; color: #fff !important;">';
html += ' ✅ Eintrag freischalten';
html += ' </button>';
html += ' </div>';
}
html += '</div>';
});
feedContainer.innerHTML = html;
if (isAdmin) {
feedContainer.querySelectorAll('.btn-approve-action').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var card = btn.closest('.guestbook-entry-card');
var id = card.getAttribute('data-id');
approveEntry(id, card);
});
});
feedContainer.querySelectorAll('.btn-delete-entry').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var card = btn.closest('.guestbook-entry-card');
var id = card.getAttribute('data-id');
if (confirm('Möchtest du diesen Eintrag wirklich unwiderruflich löschen?')) {
deleteEntry(id, card);
}
});
});
feedContainer.querySelectorAll('.btn-edit-entry').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
var card = btn.closest('.guestbook-entry-card');
var id = card.getAttribute('data-id');
var textP = card.querySelector('.js-entry-text-target');
if (card.classList.contains('is-editing')) return;
card.classList.add('is-editing');
var origEntry = allEntries.find(item => item.id == id);
var currentRawText = origEntry ? origEntry.message : textP.innerText;
textP.innerHTML = '<textarea class="form-control mb-2 js-edit-textarea" rows="4" style="color:#000 !important; background:#fff !important;">' + currentRawText + '</textarea>' +
'<button class="btn btn-sm btn-success js-save-edit-btn" style="padding:4px 10px; font-size:0.8rem;">💾 Speichern</button> ' +
'<button class="btn btn-sm btn-danger js-cancel-edit-btn" style="padding:4px 10px; font-size:0.8rem;">Abbrechen</button>';
card.querySelector('.js-save-edit-btn').addEventListener('click', function() {
var newText = card.querySelector('.js-edit-textarea').value;
updateEntryText(id, newText, card);
});
card.querySelector('.js-cancel-edit-btn').addEventListener('click', function() {
renderFeed();
});
});
});
}
renderPaginationControls();
}
// 🟢 AJAX-FIX: Passwort wird jetzt absolut wasserdicht mitgesendet
function approveEntry(id, card) {
fetch(apiPath, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'approve', id: id, password: enteredPassword })
})
.then(response => response.json())
.then(data => {
if (data.success) {
var entry = allEntries.find(item => item.id == id);
if (entry) entry.status = 1;
renderFeed();
} else {
alert(data.error);
}
})
.catch(err => alert('Fehler bei der Freischaltung.'));
}
function updateEntryText(id, newText, card) {
fetch(apiPath, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'update', id: id, message: newText, password: enteredPassword })
})
.then(response => response.json())
.then(data => {
if (data.success) {
var entry = allEntries.find(item => item.id == id);
if (entry) entry.message = newText;
renderFeed();
} else {
alert(data.error);
}
})
.catch(err => alert('Fehler beim Speichern.'));
}
function renderPaginationControls() {
if (!paginationContainer) return;
var totalPages = Math.ceil(allEntries.length / itemsPerPage);
if (totalPages <= 1) {
paginationContainer.innerHTML = '';
return;
}
var html = '';
html += '<button class="pag-btn" data-page="' + (currentPage - 1) + '"' + (currentPage === 1 ? ' disabled' : '') + '>«</button>';
for (var i = 1; i <= totalPages; i++) {
html += '<button class="pag-btn' + (i === currentPage ? ' active' : '') + '" data-page="' + i + '">' + i + '</button>';
}
html += '<button class="pag-btn" data-page="' + (currentPage + 1) + '"' + (currentPage === totalPages ? ' disabled' : '') + '>»</button>';
paginationContainer.innerHTML = html;
paginationContainer.querySelectorAll('.pag-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var targetPage = parseInt(btn.getAttribute('data-page'));
if (targetPage >= 1 && targetPage <= totalPages) {
currentPage = targetPage;
renderFeed();
feedContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
});
});
}
function loadEntries() {
var url = apiPath + (isAdmin ? '?adminCheck=' + encodeURIComponent(enteredPassword) : '');
fetch(url)
.then(response => response.json())
.then(data => {
if (!data.success) {
feedContainer.innerHTML = '<div class="alert alert-danger">' + data.error + '</div>';
return;
}
allEntries = data.entries || [];
renderFeed();
})
.catch(err => {
if(feedContainer) {
feedContainer.innerHTML = '<div class="text-center py-4 text-fallback">Warte auf ersten Eintrag...</div>';
}
});
}
function deleteEntry(id, card) {
fetch(apiPath, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete', id: id, password: enteredPassword })
})
.then(response => response.json())
.then(data => {
if (data.success) {
card.style.transition = 'all 0.3s ease';
card.style.opacity = '0';
card.style.transform = 'scale(0.9)';
setTimeout(function() {
allEntries = allEntries.filter(entry => entry.id != id);
var totalPages = Math.ceil(allEntries.length / itemsPerPage);
if (currentPage > totalPages && currentPage > 1) {
currentPage = totalPages;
}
renderFeed();
}, 300);
} else {
alert(data.error);
}
});
}
if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
var nameVal = document.getElementById('gb-name').value;
var msgVal = msgField.value;
statusDiv.style.display = 'block';
statusDiv.className = 'mt-3 small text-fallback';
statusDiv.textContent = 'Eintrag wird zur Prüfung gesendet... ⏳';
fetch(apiPath, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: nameVal, message: msgVal })
})
.then(response => response.json())
.then(data => {
if (data.success) {
statusDiv.textContent = 'Vielen Dank! Dein Eintrag wird geprüft und bald freigeschaltet. 🎉';
form.reset();
currentPage = 1;
loadEntries();
setTimeout(function() { statusDiv.style.display = 'none'; }, 5000);
} else {
statusDiv.textContent = data.error;
}
})
.catch(err => {
statusDiv.textContent = 'Fehler beim Senden.';
});
});
}
loadEntries();
})();
</script>