Poprawki działania pinesek prototypowania

This commit is contained in:
2026-02-18 22:31:08 +01:00
parent 2496b34bdd
commit 3da90baf4a
2 changed files with 249 additions and 134 deletions

View File

@@ -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;
}

View File

@@ -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 = `<span style="transform: rotate(45deg); display:block;">${commentsList.length}</span>`;
}
// 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);
});
// 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() {