Poprawki działania pinesek prototypowania
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user