Grupowanie komentarzy
This commit is contained in:
@@ -5,8 +5,41 @@ header('Content-Type: application/json');
|
||||
// Dołączamy połączenie do bazy (upewnij się, że plik db_connect.php istnieje i ma poprawne dane)
|
||||
require_once 'db_connect.php';
|
||||
|
||||
session_start();
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
// 0. AUTORYZACJA (LOGOWANIE)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'auth') {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$pass = $input['password'] ?? '';
|
||||
|
||||
// $PROTOTYPE_PASSWORD jest zdefiniowane w db_connect.php
|
||||
if (isset($PROTOTYPE_PASSWORD) && $pass === $PROTOTYPE_PASSWORD) {
|
||||
$_SESSION['prototype_auth'] = true;
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Nieprawidłowe hasło']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 0. SPRAWDZENIE SESJI (Opcjonalne dla list, wymagane dla add)
|
||||
function isAuthorized()
|
||||
{
|
||||
return !empty($_SESSION['prototype_auth']);
|
||||
}
|
||||
|
||||
// 0.5 SPRAWDZENIE STANU AUTORYZACJI (GET) - dla frontendowego odtworzenia sesji
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'check_auth') {
|
||||
if (isAuthorized()) {
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Brak sesji']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. POBIERANIE KOMENTARZY (GET)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'list') {
|
||||
$pagePath = $_GET['page_path'] ?? '';
|
||||
@@ -20,6 +53,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'list') {
|
||||
$stmt = $pdo->prepare("SELECT * FROM prototype_comments WHERE page_path = ? ORDER BY created_at DESC");
|
||||
$stmt->execute([$pagePath]);
|
||||
$comments = $stmt->fetchAll();
|
||||
|
||||
// XSS PROTECTION: Escaping danych przed wysłaniem
|
||||
foreach ($comments as &$c) {
|
||||
$c['author'] = htmlspecialchars($c['author'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$c['comment'] = htmlspecialchars($c['comment'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
// Selector musi zostać oryginalny, bo JS go używa do querySelector!
|
||||
// $c['dom_selector'] = htmlspecialchars($c['dom_selector'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
echo json_encode(['status' => 'success', 'data' => $comments]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||
@@ -29,6 +71,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'list') {
|
||||
|
||||
// 2. DODAWANIE KOMENTARZA (POST)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'add') {
|
||||
// Wymagana autoryzacja
|
||||
if (!isAuthorized()) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Brak autoryzacji. Odśwież stronę i podaj hasło.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Odczyt danych JSON z body requestu
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
@@ -52,4 +100,55 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'add') {
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. ROZWIĄZYWANIE KOMENTARZA (POST)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'resolve') {
|
||||
if (!isAuthorized()) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Brak autoryzacji']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = $input['id'] ?? 0;
|
||||
|
||||
if (!$id) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Brak ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Zmieniamy flagę is_resolved na 1
|
||||
$stmt = $pdo->prepare("UPDATE prototype_comments SET is_resolved = 1 WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(['status' => 'success']);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. USUWANIE KOMENTARZA (POST)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'delete') {
|
||||
if (!isAuthorized()) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Brak autoryzacji']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$id = $input['id'] ?? 0;
|
||||
|
||||
if (!$id) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Brak ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM prototype_comments WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
echo json_encode(['status' => 'success']);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -87,7 +87,7 @@ input:checked+.slider:before {
|
||||
|
||||
.comment-marker:hover {
|
||||
z-index: 10001;
|
||||
transform: rotate(-45deg) scale(1.2);
|
||||
transform: rotate(-45deg) translate(-50%, -100%) scale(1.2);
|
||||
}
|
||||
|
||||
.comment-marker.resolved {
|
||||
|
||||
@@ -45,11 +45,43 @@
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// Listenery UI
|
||||
document.getElementById('comment-toggle').addEventListener('change', function (e) {
|
||||
isCommentMode = e.target.checked;
|
||||
toggleCommentMode(isCommentMode);
|
||||
// Listenery UI
|
||||
const toggle = document.getElementById('comment-toggle');
|
||||
|
||||
toggle.addEventListener('change', function (e) {
|
||||
if (e.target.checked) {
|
||||
// Próba włączenia - autoryzacja
|
||||
checkAuth().then(ok => {
|
||||
if (ok) {
|
||||
isCommentMode = true;
|
||||
localStorage.setItem('magico_comment_mode', 'true');
|
||||
toggleCommentMode(true);
|
||||
} else {
|
||||
e.target.checked = false; // Cofnij switch
|
||||
}
|
||||
});
|
||||
} else {
|
||||
isCommentMode = false;
|
||||
localStorage.removeItem('magico_comment_mode');
|
||||
toggleCommentMode(false);
|
||||
}
|
||||
});
|
||||
|
||||
// --- RESTORE SESSION ---
|
||||
const savedMode = localStorage.getItem('magico_comment_mode');
|
||||
if (savedMode === 'true') {
|
||||
// Sprawdź cicho czy sesja PHP jest aktywna
|
||||
checkAuth(true).then(ok => {
|
||||
if (ok) {
|
||||
toggle.checked = true;
|
||||
isCommentMode = true;
|
||||
toggleCommentMode(true);
|
||||
} else {
|
||||
localStorage.removeItem('magico_comment_mode'); // Sesja wygasła
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('cancel-comment').addEventListener('click', closeInputBox);
|
||||
document.getElementById('save-comment').addEventListener('click', saveComment);
|
||||
|
||||
@@ -57,6 +89,51 @@
|
||||
window.addEventListener('scroll', updateMarkerPositions, true);
|
||||
}
|
||||
|
||||
// --- 1.5 AUTORYZACJA ---
|
||||
// Prosta weryfikacja - czy mamy flagę w sesji JS?
|
||||
// Lepiej: przy włączeniu zapytać o hasło jeśli nie mamy, i wysłać do API.
|
||||
// API ustawi sesję PHP.
|
||||
|
||||
let isAuthorized = false; // Lokalna flaga, aby nie pytać co chwilę
|
||||
|
||||
async function checkAuth(silent = false) {
|
||||
if (isAuthorized) return true;
|
||||
|
||||
// Najpierw zapytaj API czy już jesteśmy zalogowani (sesja PHP)
|
||||
try {
|
||||
const check = await fetch(API_URL + '?action=check_auth');
|
||||
const checkData = await check.json();
|
||||
if (checkData.status === 'success') {
|
||||
isAuthorized = true;
|
||||
return true;
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
if (silent) return false;
|
||||
|
||||
const pass = prompt("Podaj hasło do trybu komentowania:");
|
||||
if (!pass) return false;
|
||||
|
||||
try {
|
||||
const res = await fetch(API_URL + '?action=auth', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ password: pass })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
isAuthorized = true;
|
||||
return true;
|
||||
} else {
|
||||
alert("Błąd: " + (data.message || "Nieprawidłowe hasło"));
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
alert("Błąd połączenia z API");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. LOGIKA TRYBU (AGRESYWNE BLOKOWANIE) ---
|
||||
function toggleCommentMode(active) {
|
||||
if (active) {
|
||||
@@ -220,18 +297,58 @@
|
||||
document.querySelectorAll('.comment-marker').forEach(el => el.remove());
|
||||
if (activePopover) { activePopover.remove(); activePopover = null; }
|
||||
|
||||
markersData.forEach((c, index) => {
|
||||
// Grupowanie komentarzy po selektorze
|
||||
const grouped = {};
|
||||
markersData.forEach((c) => {
|
||||
// Pomiń rozwiązane, jeśli chcemy je ukrywać (na razie pokazujemy wszystkie, albo tylko nierozwiązane?)
|
||||
// User: "archiwum zmienia flagę is_resolved". Zakładamy że archiwum = ukryte?
|
||||
// "Archiwum" zwykle oznacza ukryte. Ukryjmy rozwiązane.
|
||||
if (c.is_resolved == 1) return;
|
||||
|
||||
if (!grouped[c.dom_selector]) grouped[c.dom_selector] = [];
|
||||
grouped[c.dom_selector].push(c);
|
||||
});
|
||||
|
||||
Object.keys(grouped).forEach((selector, index) => {
|
||||
const commentsList = grouped[selector];
|
||||
if (!commentsList.length) return;
|
||||
|
||||
try {
|
||||
const el = document.querySelector(c.dom_selector);
|
||||
const el = document.querySelector(selector);
|
||||
if (el && el.offsetParent !== null) { // Tylko widoczne elementy
|
||||
const marker = document.createElement('div');
|
||||
marker.className = 'comment-marker';
|
||||
marker.dataset.index = index; // Żeby łatwo znaleźć dane
|
||||
marker.dataset.selector = selector;
|
||||
|
||||
// Zdarzenie kliknięcia w marker
|
||||
marker.addEventListener('click', (e) => {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
showPopover(marker, c);
|
||||
// Jeśli więcej niż 1 komentarz, pokaż licznik
|
||||
if (commentsList.length > 1) {
|
||||
marker.textContent = commentsList.length;
|
||||
marker.style.display = 'flex';
|
||||
marker.style.alignItems = 'center';
|
||||
marker.style.justifyContent = 'center';
|
||||
marker.style.color = 'white';
|
||||
marker.style.fontSize = '12px';
|
||||
marker.style.fontWeight = 'bold';
|
||||
// Resetuejmu transform dla tekstu żeby był czytelny? Nie, rotacja jest na pinezce.
|
||||
// Tekst też się obróci. Trzeba by go odkręcić.
|
||||
// Prościej: dodajmy span w środku który odkręcimy.
|
||||
marker.innerHTML = `<span style="transform: rotate(45deg); display:block;">${commentsList.length}</span>`;
|
||||
}
|
||||
|
||||
// Zdarzenie HOVER (najazd myszką)
|
||||
marker.addEventListener('mouseenter', (e) => {
|
||||
showPopover(marker, commentsList);
|
||||
});
|
||||
|
||||
marker.addEventListener('mouseleave', (e) => {
|
||||
marker._leaveTimeout = setTimeout(() => {
|
||||
if (activePopover && activePopover._associatedMarker === marker) {
|
||||
if (!activePopover.matches(':hover')) {
|
||||
activePopover.remove();
|
||||
activePopover = null;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
document.body.appendChild(marker);
|
||||
@@ -243,48 +360,96 @@
|
||||
updateMarkerPositions();
|
||||
}
|
||||
|
||||
function showPopover(marker, data) {
|
||||
function showPopover(marker, commentsList) {
|
||||
if (activePopover) activePopover.remove();
|
||||
|
||||
const pop = document.createElement('div');
|
||||
pop.className = 'comment-popover';
|
||||
|
||||
// Escape HTML - XSS Protection
|
||||
const safeAuthor = document.createElement('div');
|
||||
safeAuthor.textContent = data.author || 'Anonim';
|
||||
const safeComment = document.createElement('div');
|
||||
safeComment.textContent = data.comment || '';
|
||||
// Budowanie listy komentarzy
|
||||
let html = '<div style="max-height: 200px; overflow-y: auto;">';
|
||||
|
||||
pop.innerHTML = `
|
||||
<h6>${safeAuthor.innerHTML}</h6>
|
||||
<p>${safeComment.innerHTML}</p>
|
||||
`;
|
||||
commentsList.forEach(data => {
|
||||
// Escape (chociaż API to robi, warto mieć warstwę w JS w razie czego, ale API już robi htmlspecialchars)
|
||||
// Skoro API robi htmlspecialchars, to tutaj możemy bezpiecznie wstawić
|
||||
// Ale uwaga: JS textContent jest bezpieczniejszy.
|
||||
|
||||
// Hack na szybkie budowanie bezpiecznego HTML w pętli stringów jest trudny.
|
||||
// Zbudujmy to jako elementy DOM potem? Albo zaufajmy API + textContent buildera.
|
||||
|
||||
// Zrobimy placeholder i podstawimy wartości.
|
||||
|
||||
html += `
|
||||
<div class="comment-item" style="border-bottom:1px solid #eee; padding-bottom:8px; margin-bottom:8px;">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:4px;">
|
||||
<h6 style="margin:0;">${data.author || 'Anonim'}</h6>
|
||||
<span style="font-size:10px; color:#999;">${data.created_at}</span>
|
||||
</div>
|
||||
<p style="margin-bottom:5px;">${data.comment}</p>
|
||||
<div style="text-align:right; gap:5px; display:flex; justify-content:flex-end;">
|
||||
<button class="btn btn-xs btn-outline-success btn-resolve" data-id="${data.id}" style="font-size:10px; padding: 2px 5px;">Rozwiąż</button>
|
||||
<button class="btn btn-xs btn-outline-danger btn-delete" data-id="${data.id}" style="font-size:10px; padding: 2px 5px;">Usuń</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
pop.innerHTML = html;
|
||||
document.body.appendChild(pop);
|
||||
activePopover = pop;
|
||||
pop._associatedMarker = marker;
|
||||
|
||||
// Bindowanie akcji
|
||||
pop.querySelectorAll('.btn-resolve').forEach(btn => {
|
||||
btn.addEventListener('click', () => resolveComment(btn.dataset.id));
|
||||
});
|
||||
pop.querySelectorAll('.btn-delete').forEach(btn => {
|
||||
btn.addEventListener('click', () => deleteComment(btn.dataset.id));
|
||||
});
|
||||
|
||||
// Obsługa zamykania przy wyjechaniu z popovera
|
||||
pop.addEventListener('mouseleave', () => {
|
||||
if (activePopover === pop) {
|
||||
activePopover.remove();
|
||||
activePopover = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Pozycjonowanie
|
||||
const rect = marker.getBoundingClientRect();
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
||||
|
||||
pop.style.top = (rect.top + scrollTop - 10) + 'px'; // Trochę wyżej
|
||||
pop.style.left = (rect.right + scrollLeft + 10) + 'px'; // Obok markera
|
||||
pop.style.top = (rect.top + scrollTop - 5) + 'px';
|
||||
pop.style.left = (rect.right + scrollLeft + 2) + 'px';
|
||||
|
||||
// Jeśli wychodzi poza ekran z prawej, daj na lewo
|
||||
if (rect.right + 260 > window.innerWidth) {
|
||||
pop.style.left = (rect.left + scrollLeft - 260) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Zamknij popover przy kliknięciu w tło
|
||||
window.addEventListener('click', (e) => {
|
||||
if (activePopover && !e.target.closest('.comment-popover') && !e.target.closest('.comment-marker')) {
|
||||
activePopover.remove();
|
||||
activePopover = null;
|
||||
}
|
||||
});
|
||||
function resolveComment(id) {
|
||||
if (!confirm('Czy na pewno chcesz rozwiązać/zarchiwizować ten komentarz?')) return;
|
||||
fetch(API_URL + '?action=resolve', {
|
||||
method: 'POST', body: JSON.stringify({ id: id })
|
||||
}).then(res => res.json()).then(res => {
|
||||
if (res.status === 'success') loadComments();
|
||||
else alert('Błąd: ' + res.message);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteComment(id) {
|
||||
if (!confirm('Czy na pewno chcesz trwale usunąć ten komentarz?')) return;
|
||||
fetch(API_URL + '?action=delete', {
|
||||
method: 'POST', body: JSON.stringify({ id: id })
|
||||
}).then(res => res.json()).then(res => {
|
||||
if (res.status === 'success') loadComments();
|
||||
else alert('Błąd: ' + res.message);
|
||||
});
|
||||
}
|
||||
|
||||
// (Usunięto click-outside listener, bo teraz działamy na hover)
|
||||
|
||||
// Aktualizuj też pozycję otwartego popovera przy scrollu
|
||||
const originalUpdatePositions = updateMarkerPositions;
|
||||
@@ -296,44 +461,47 @@
|
||||
const markers = document.querySelectorAll('.comment-marker');
|
||||
|
||||
markers.forEach(marker => {
|
||||
const index = marker.dataset.index;
|
||||
const data = markersData[index];
|
||||
if (!data) return;
|
||||
const selector = marker.dataset.selector;
|
||||
if (!selector) return;
|
||||
|
||||
try {
|
||||
const el = document.querySelector(data.dom_selector);
|
||||
const el = document.querySelector(selector);
|
||||
if (el && el.offsetParent !== null) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
// Jeśli element wyjechał poza ekran (jest w scrollowanym divie ale schowany)
|
||||
// Można go ukryć, ale na razie po prostu przesuwamy
|
||||
marker.style.top = (rect.top + window.scrollY) + 'px';
|
||||
marker.style.left = (rect.left + window.scrollX + (rect.width / 2)) + 'px';
|
||||
marker.style.display = 'block';
|
||||
marker.style.display = (marker.textContent.length > 0) ? 'flex' : 'block';
|
||||
// Flex dla licznika, block dla zwykłego? A w stylach mamy display?
|
||||
// W renderMarkers ustawilismy display:flex ręcznie dla licznika.
|
||||
// Tu musimy uwazac zeby tego nie zepsuc. Pominmy style.display='block' jesli ma flex.
|
||||
if (marker.style.display !== 'flex') marker.style.display = 'block';
|
||||
|
||||
if (activePopover && activePopover._associatedMarker === marker) {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
||||
|
||||
// Recalculate position
|
||||
let top = (rect.top + scrollTop - 10);
|
||||
let left = (rect.right + scrollLeft + 10);
|
||||
let top = (rect.top + scrollTop - 5);
|
||||
let left = (rect.right + scrollLeft + 2);
|
||||
|
||||
if (rect.right + 260 > window.innerWidth) {
|
||||
left = (rect.left + scrollLeft - 260); // Flip to left
|
||||
left = (rect.left + scrollLeft - 260);
|
||||
}
|
||||
|
||||
activePopover.style.top = top + 'px';
|
||||
activePopover.style.left = left + 'px';
|
||||
}
|
||||
} else {
|
||||
marker.style.display = 'none'; // Ukryj jeśli element zniknął
|
||||
marker.style.display = 'none';
|
||||
if (activePopover && activePopover._associatedMarker === marker) {
|
||||
activePopover.remove();
|
||||
activePopover = null;
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
});
|
||||
|
||||
// Jeśli jest aktywny popover, zamknij go przy szybkim scrollu żeby nie pływał dziwnie?
|
||||
// Albo po prostu zostaw.
|
||||
}
|
||||
|
||||
// START
|
||||
@@ -343,11 +511,77 @@
|
||||
// Odświeżanie przy zmianie rozmiaru okna
|
||||
window.addEventListener('resize', updateMarkerPositions);
|
||||
|
||||
// MutationObserver - odświeżaj pozycje jak coś się zmieni w DOM (np otwarcie taba)
|
||||
const observer = new MutationObserver(() => {
|
||||
// Debounce dla wydajności
|
||||
if (window.commentUpdateTimeout) clearTimeout(window.commentUpdateTimeout);
|
||||
window.commentUpdateTimeout = setTimeout(renderMarkers, 200);
|
||||
// MutationObserver - odświeżaj pozycje jak coś się zmieni w wygenerowanym DOM (ale ignoruj nasze markery)
|
||||
// Helper sprawdzający czy element jest częścią naszego UI
|
||||
function isOurElement(node) {
|
||||
if (!node || node.nodeType !== 1) return false;
|
||||
|
||||
if (node.classList && (
|
||||
node.classList.contains('comment-marker') ||
|
||||
node.classList.contains('comment-popover') ||
|
||||
node.id === 'comment-input-box' ||
|
||||
node.id === 'comment-input-box-overlay'
|
||||
)) return true;
|
||||
|
||||
if (node.closest && (
|
||||
node.closest('.comment-marker') ||
|
||||
node.closest('.comment-popover') ||
|
||||
node.closest('#comment-input-box')
|
||||
)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
let shouldUpdate = false;
|
||||
|
||||
for (const mutation of mutations) {
|
||||
// 1. Zmiany atrybutów na naszych elementach
|
||||
if (mutation.type === 'attributes') {
|
||||
if (isOurElement(mutation.target)) continue;
|
||||
}
|
||||
|
||||
// 2. Zmiany w strukturze DOM (dodawanie/usuwanie węzłów)
|
||||
if (mutation.type === 'childList') {
|
||||
let externalChanges = false;
|
||||
|
||||
// Sprawdź dodane
|
||||
if (mutation.addedNodes.length > 0) {
|
||||
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
||||
const node = mutation.addedNodes[i];
|
||||
if (node.nodeType === 1 && !isOurElement(node)) {
|
||||
externalChanges = true; break;
|
||||
}
|
||||
// Ignorujemy same zmiany tekstowe/puste znaki w body, chyba że to istotne?
|
||||
// Dla bezpieczeństwa: jeśli to tekst i nie jest pusty -> update
|
||||
if (node.nodeType === 3 && node.textContent.trim() !== '') {
|
||||
externalChanges = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sprawdź usunięte (tylko jeśli jeszcze nie wykryto zmian)
|
||||
if (!externalChanges && mutation.removedNodes.length > 0) {
|
||||
for (let i = 0; i < mutation.removedNodes.length; i++) {
|
||||
const node = mutation.removedNodes[i];
|
||||
if (node.nodeType === 1 && !isOurElement(node)) {
|
||||
externalChanges = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Jeśli zmiany nie dotyczyły "zewnętrznych" elementów (czyli tylko nasze), ignoruj.
|
||||
if (!externalChanges) continue;
|
||||
}
|
||||
|
||||
shouldUpdate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldUpdate) {
|
||||
if (window.commentUpdateTimeout) clearTimeout(window.commentUpdateTimeout);
|
||||
window.commentUpdateTimeout = setTimeout(renderMarkers, 200);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
|
||||
@@ -3,8 +3,12 @@ $host = 'localhost';
|
||||
$db = 'srv105686_magicoprototype';
|
||||
$user = 'srv105686_magicoprototype';
|
||||
$pass = 'pMMdmYHXSBs6wM5LHNjd';
|
||||
$pass = 'pMMdmYHXSBs6wM5LHNjd';
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
// Hasło do trybu komentowania
|
||||
$PROTOTYPE_PASSWORD = 'magic'; // Zmień to hasło!
|
||||
|
||||
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
|
||||
Reference in New Issue
Block a user