From 3da90baf4a625367f7894429e9e0893c19eb7a99 Mon Sep 17 00:00:00 2001 From: Bartek Date: Wed, 18 Feb 2026 22:31:08 +0100 Subject: [PATCH] =?UTF-8?q?Poprawki=20dzia=C5=82ania=20pinesek=20prototypo?= =?UTF-8?q?wania?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/comments.css | 29 +++- assets/js/comments.js | 354 +++++++++++++++++++++++++--------------- 2 files changed, 249 insertions(+), 134 deletions(-) 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() {