diff --git a/assets/css/comments.css b/assets/css/comments.css
index b98858c..f52ab88 100644
--- a/assets/css/comments.css
+++ b/assets/css/comments.css
@@ -104,7 +104,8 @@ input:checked+.slider:before {
padding: 10px;
width: 250px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
- z-index: 10002;
+ z-index: 100002;
+ /* Musi być wyżej niż marker (100001) i topbar (100000) */
font-size: 13px;
color: #333;
}
@@ -268,4 +269,30 @@ body.comments-hidden .comment-marker {
.comment-list-item:hover {
background: #f9f9f9;
+}
+
+/* --- FIX DLA MODALI PROTOTYPU --- */
+/* Wymuszamy, aby standardowe modale (Bootstrap) nie chowały się pod paskiem */
+.modal {
+ padding-top: 50px !important;
+ /* Odsuwamy od góry o wysokość paska */
+}
+
+/* Opcjonalnie: Jeśli modal ma backdrop, też go przesuwamy,
+ albo po prostu upewniamy się że modal-dialog ma margines */
+.modal-dialog {
+ margin-top: 20px !important;
+ /* Dodatkowy margines żeby nie dotykało paska */
+}
+
+/* Jeśli używasz offcanvas lub innych overlayów */
+.offcanvas {
+ top: 50px !important;
+ height: calc(100% - 50px) !important;
+}
+
+/* Poprawka dla backdropu, żeby nie zakrywał paska (jeśli pasek ma być na wierzchu) */
+.modal-backdrop {
+ top: 50px !important;
+ height: calc(100% - 50px) !important;
}
\ No newline at end of file
diff --git a/assets/js/comments.js b/assets/js/comments.js
index 5872dcd..07dbc0f 100644
--- a/assets/js/comments.js
+++ b/assets/js/comments.js
@@ -362,79 +362,84 @@
let activePopover = null;
function renderMarkers() {
- // Usuń stare
- document.querySelectorAll('.comment-marker').forEach(el => el.remove());
- if (activePopover) { activePopover.remove(); activePopover = null; }
+ window.magicoIsUpdating = true;
+ try {
+ // Usuń stare
+ document.querySelectorAll('.comment-marker').forEach(el => el.remove());
+ if (activePopover) { activePopover.remove(); activePopover = null; }
- // Grupowanie komentarzy po selektorze
- const grouped = {};
- let activeCount = 0;
+ // Grupowanie komentarzy po selektorze
+ const grouped = {};
+ let activeCount = 0;
- markersData.forEach((c) => {
- if (c.is_resolved == 0) activeCount++;
+ markersData.forEach((c) => {
+ if (c.is_resolved == 0) activeCount++;
- // 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;
+ // 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);
- });
+ if (!grouped[c.dom_selector]) grouped[c.dom_selector] = [];
+ grouped[c.dom_selector].push(c);
+ });
- // Aktualizacja licznika na pasku
- const cntEl = document.getElementById('comments-count-val');
- if (cntEl) cntEl.textContent = activeCount;
+ // Aktualizacja licznika na pasku
+ const cntEl = document.getElementById('comments-count-val');
+ if (cntEl) cntEl.textContent = activeCount;
- Object.keys(grouped).forEach((selector, index) => {
- const commentsList = grouped[selector];
- if (!commentsList.length) return;
+ Object.keys(grouped).forEach((selector, index) => {
+ const commentsList = grouped[selector];
+ if (!commentsList.length) return;
- try {
- const el = document.querySelector(selector);
- if (el && el.offsetParent !== null) { // Tylko widoczne elementy
- const marker = document.createElement('div');
- marker.className = 'comment-marker';
- marker.dataset.selector = selector;
+ try {
+ const el = document.querySelector(selector);
+ if (el && el.offsetParent !== null) { // Tylko widoczne elementy
+ const marker = document.createElement('div');
+ marker.className = 'comment-marker';
+ marker.dataset.selector = selector;
- // 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 = `${commentsList.length}`;
- }
+ // 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 = `${commentsList.length}`;
+ }
- // Zdarzenie HOVER (najazd myszką)
- marker.addEventListener('mouseenter', (e) => {
- showPopover(marker, commentsList);
- });
+ // 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;
+ marker.addEventListener('mouseleave', (e) => {
+ marker._leaveTimeout = setTimeout(() => {
+ if (activePopover && activePopover._associatedMarker === marker) {
+ if (!activePopover.matches(':hover')) {
+ activePopover.remove();
+ activePopover = null;
+ }
}
- }
- }, 300);
- });
+ }, 300);
+ });
- document.body.appendChild(marker);
- }
- } catch (err) { }
- });
+ document.body.appendChild(marker);
+ }
+ } catch (err) { }
+ });
- // Po stworzeniu od razu ustaw pozycje
- updateMarkerPositions();
+ // Po stworzeniu od razu ustaw pozycje
+ updateMarkerPositions();
+ } finally {
+ setTimeout(() => { window.magicoIsUpdating = false; }, 0);
+ }
}
function showPopover(marker, commentsList) {
@@ -505,7 +510,7 @@
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
pop.style.top = (rect.top + scrollTop - 5) + 'px';
- pop.style.left = (rect.right + scrollLeft + 2) + 'px';
+ pop.style.left = (rect.right + scrollLeft) + 'px'; // Usunięto przerwę +2px
if (rect.right + 260 > window.innerWidth) {
pop.style.left = (rect.left + scrollLeft - 260) + 'px';
@@ -541,50 +546,62 @@
// Funkcja wywoływana przy scrollowaniu - musi być szybka
function updateMarkerPositions() {
- const markers = document.querySelectorAll('.comment-marker');
+ window.magicoIsUpdating = true; // Zaczynamy aktualizację
+ try {
+ const markers = document.querySelectorAll('.comment-marker');
- markers.forEach(marker => {
- const selector = marker.dataset.selector;
- if (!selector) return;
+ markers.forEach(marker => {
+ const selector = marker.dataset.selector;
+ if (!selector) return;
- try {
- const el = document.querySelector(selector);
- if (el && el.offsetParent !== null) {
- const rect = el.getBoundingClientRect();
+ try {
+ 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)
- marker.style.top = (rect.top + window.scrollY) + 'px';
- marker.style.left = (rect.left + window.scrollX + (rect.width / 2)) + 'px';
- 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';
+ // Jeśli element wyjechał poza ekran (jest w scrollowanym divie ale schowany)
+ marker.style.top = (rect.top + window.scrollY) + 'px';
+ marker.style.left = (rect.left + window.scrollX + (rect.width / 2)) + 'px';
+ 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;
+ 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 - 5);
- let left = (rect.right + scrollLeft + 2);
+ // Recalculate position
+ let top = (rect.top + scrollTop - 5);
+ let left = (rect.right + scrollLeft);
+ if (rect.right + 260 > window.innerWidth) {
+ left = (rect.left + scrollLeft - 260);
+ }
- if (rect.right + 260 > window.innerWidth) {
- left = (rect.left + scrollLeft - 260);
+ activePopover.style.top = top + 'px';
+ activePopover.style.left = left + 'px';
+ }
+ } else {
+ marker.style.display = 'none';
+ if (activePopover && activePopover._associatedMarker === marker) {
+ activePopover.remove();
+ activePopover = null;
}
-
- activePopover.style.top = top + 'px';
- activePopover.style.left = left + 'px';
}
- } else {
- marker.style.display = 'none';
- if (activePopover && activePopover._associatedMarker === marker) {
- activePopover.remove();
- activePopover = null;
- }
- }
- } catch (e) { }
- });
+ } catch (e) { }
+ });
+ } finally {
+ // Mały timeout, bo MutationObserver jest asynchroniczny (microtask)
+ // Ale my chcemy zablokować detekcję zmian, które WŁAŚNIE zaszły.
+ // Ponieważ observer odpala się "później", flaga false może zostać ustawiona ZA WCZEŚNIE?
+ // Nie, observer zbiera zmiany i odpala callback.
+ // Jeśli callback odpali się kiedy flaga jest true, to return.
+ // Ale callback odpali się w następnym ticku.
+ // Więc musimy przetrzymać flagę true do następnego ticku?
+ // Tak, bezpieczniej setTimeout(..., 0).
+ setTimeout(() => { window.magicoIsUpdating = false; }, 0);
+ }
}
// START
@@ -599,52 +616,95 @@
function isOurElement(node) {
if (!node || node.nodeType !== 1) return false;
+ // Lista ID elementów systemu
+ const ourIds = [
+ 'prototype-topbar',
+ 'comment-input-box',
+ 'comment-input-box-overlay',
+ 'all-comments-dialog',
+ 'all-comments-dialog-overlay'
+ ];
+
+ if (ourIds.includes(node.id)) return true;
+
if (node.classList && (
node.classList.contains('comment-marker') ||
- node.classList.contains('comment-popover') ||
- node.id === 'comment-input-box' ||
- node.id === 'comment-input-box-overlay'
+ node.classList.contains('comment-popover')
)) return true;
- if (node.closest && (
- node.closest('.comment-marker') ||
- node.closest('.comment-popover') ||
- node.closest('#comment-input-box')
- )) return true;
+ // Sprawdź rodziców
+ if (node.closest) {
+ for (const id of ourIds) {
+ if (node.closest('#' + id)) return true;
+ }
+ if (node.closest('.comment-marker') || node.closest('.comment-popover')) return true;
+ }
return false;
}
- const observer = new MutationObserver((mutations) => {
+ // --- 8. OBSŁUGA OBSERVERA I PĘTLI ---
+
+ // Zmienna na observer zdefiniowana niżej, ale potrzebujemy jej tu.
+ // Przenieśmy definicję observera wyżej albo użyjmy funkcji.
+
+ let observer; // deklaracja wstępna
+
+ function ignoreObserver(action) {
+ if (observer) observer.disconnect();
+ try {
+ action();
+ } finally {
+ if (observer) startObserver();
+ }
+ }
+
+ function startObserver() {
+ observer.observe(document.body, {
+ childList: true, subtree: true, attributes: true,
+ attributeFilter: ['class', 'style', 'hidden']
+ });
+ }
+
+ // --- MODYFIKACJA renderMarkers i updateMarkerPositions ---
+
+ // Przechwytujemy stare funkcje i opakowujemy je
+ // Ale lepiej po prostu zmienić ich ciała w kodzie (co robię tym replace'm)
+
+ // Nadpiszmy renderMarkers, żeby używało ignoreObserver
+ // Uwaga: Funkcja renderMarkers i updateMarkerPositions są wyżej.
+ // Ten tool replace musi być sprytny.
+ // Zamiast nadpisywać, zmienię logikę observera na dole pliku i podmienię wywołania.
+
+ // W tym bloku (EndLine: 734) jest końcówka pliku i definicja observera.
+ // Zdefiniujmy observera tutaj poprawnie.
+
+ observer = new MutationObserver((mutations) => {
+ // Jeśli my sami aktualizujemy, ignoruj wszystko
+ if (window.magicoIsUpdating) return;
+
let shouldUpdate = false;
+ // Optymalizacja: Sprawdzamy czy zmiany są ISTOTNE
for (const mutation of mutations) {
- // 1. Zmiany atrybutów na naszych elementach
- if (mutation.type === 'attributes') {
- if (isOurElement(mutation.target)) continue;
- }
+ // Ignorujemy zmiany na naszych elementach (nawet jeśli disconnect nie zadziałał idealnie)
+ if (isOurElement(mutation.target)) continue;
- // 2. Zmiany w strukturze DOM (dodawanie/usuwanie węzłów)
+ // Zmiany childList
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;
- }
+ for (let i = 0; i < mutation.addedNodes.length; i++) {
+ const node = mutation.addedNodes[i];
+ if (node.nodeType === 1 && !isOurElement(node)) {
+ externalChanges = true; break;
+ }
+ 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) {
+ // Sprawdź usunięte
+ if (!externalChanges) {
for (let i = 0; i < mutation.removedNodes.length; i++) {
const node = mutation.removedNodes[i];
if (node.nodeType === 1 && !isOurElement(node)) {
@@ -652,8 +712,6 @@
}
}
}
-
- // Jeśli zmiany nie dotyczyły "zewnętrznych" elementów (czyli tylko nasze), ignoruj.
if (!externalChanges) continue;
}
@@ -663,14 +721,44 @@
if (shouldUpdate) {
if (window.commentUpdateTimeout) clearTimeout(window.commentUpdateTimeout);
- window.commentUpdateTimeout = setTimeout(renderMarkers, 200);
+ window.commentUpdateTimeout = setTimeout(() => {
+ // Renderowanie wyzwolone przez zmiany zewnętrzne
+ // Nie musimy tu robić ignoreObserver bo renderMarkers samo to zrobi wewnątrz?
+ // Nie, bo renderMarkers to funkcja.
+ // Wywołajmy renderMarkers.
+ renderMarkers();
+ }, 200);
}
});
- observer.observe(document.body, {
- childList: true, subtree: true, attributes: true,
- attributeFilter: ['class', 'style', 'hidden']
- });
+ startObserver();
+
+ // EXPORT funkcji do użycia wewnątrz renderMarkers/update
+ // Ponieważ nie edytuję całego pliku, musimy jakoś wstrzyknąć ignoreObserver do wywołań wyżej?
+ // Nie da się bez edycji tamtych funkcji.
+ // WIĘC: Zostawiam observera jak jest (z ulepszonym isOurElement),
+ // ALE w renderMarkers i updateMarkerPositions dodam wywołania disconnect/connect.
+
+ // Czekaj, nie mam dostępu do zmiennej `observer` wewnątrz funkcji zadeklarowanych wyżej, jeśli zadeklaruję ją na dole.
+ // Ale w JS var/let w tym samym scope (IIFE) są widoczne.
+ // Muszę przenieść deklarację `let observer` na górę IIFE (innym replacem) lub
+ // Zmienić observer na `window.magicoObserver`? Nieładnie.
+
+ // Zrobię tak: Zdefiniuję `ignoreObserver` tutaj, i użyję `observer.disconnect()` wewnątrz.
+ // Ale muszę mieć pewność że `observer` jest zdefiniowany.
+ // W obecnym kodzie jest zdefiniowany jako `const observer = ...` na dole.
+ // const nie jest hoisted.
+
+ // PLAN B:
+ // Zmienię definicję `const observer` na `let observer` i przesunę definicję funkcji `isOurElement` i observera na dół,
+ // a potem w replace'ach wyżej dodam wywołania wrapperów.
+
+ // LUB PROŚCIEJ:
+ // Zmodyfikujmy `renderMarkers` i `updateMarkerPositions` żeby sprawdzały flagę globalną `window.magicoIsUpdating`.
+ // A observer będzie sprawdzał tę flagę.
+
+ // IMPLEMENTACJA FLAGI:
+ window.magicoIsUpdating = false;
// --- 7. DIALOG Z LISTĄ WSZYSTKICH ---
function showAllCommentsDialog() {