diff --git a/api-comments.php b/api-comments.php new file mode 100644 index 0000000..6eda297 --- /dev/null +++ b/api-comments.php @@ -0,0 +1,55 @@ + 'error', 'message' => 'Brak ścieżki pliku']); + exit; + } + + try { + $stmt = $pdo->prepare("SELECT * FROM prototype_comments WHERE page_path = ? ORDER BY created_at DESC"); + $stmt->execute([$pagePath]); + $comments = $stmt->fetchAll(); + echo json_encode(['status' => 'success', 'data' => $comments]); + } catch (PDOException $e) { + echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); + } + exit; +} + +// 2. DODAWANIE KOMENTARZA (POST) +if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'add') { + // Odczyt danych JSON z body requestu + $input = json_decode(file_get_contents('php://input'), true); + + $pagePath = $input['page_path'] ?? ''; + $selector = $input['selector'] ?? ''; + $comment = $input['comment'] ?? ''; + $author = $input['author'] ?? 'Anonim'; // Możesz tu potem wpiąć sesję użytkownika + + if (!$pagePath || !$selector || !$comment) { + echo json_encode(['status' => 'error', 'message' => 'Brakuje danych']); + exit; + } + + try { + $stmt = $pdo->prepare("INSERT INTO prototype_comments (page_path, dom_selector, author, comment) VALUES (?, ?, ?, ?)"); + $stmt->execute([$pagePath, $selector, $author, $comment]); + + echo json_encode(['status' => 'success', 'id' => $pdo->lastInsertId()]); + } catch (PDOException $e) { + echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); + } + exit; +} +?> \ No newline at end of file diff --git a/assets/css/comments.css b/assets/css/comments.css new file mode 100644 index 0000000..f6eeef3 --- /dev/null +++ b/assets/css/comments.css @@ -0,0 +1,225 @@ +/* assets/css/comments.css */ + +/* Pasek narzędzi na górze */ +#prototype-topbar { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 50px; + background: #232f3e; + color: white; + z-index: 100000; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + font-family: system-ui, -apple-system, sans-serif; +} + +#prototype-topbar .mode-switch { + display: flex; + align-items: center; + gap: 10px; +} + +/* Przełącznik (Toggle) */ +.switch-label { + position: relative; + display: inline-block; + width: 40px; + height: 20px; +} + +.switch-label input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 20px; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 2px; + bottom: 2px; + background-color: white; + transition: .4s; + border-radius: 50%; +} + +input:checked+.slider { + background-color: #2196F3; +} + +input:checked+.slider:before { + transform: translateX(20px); +} + +/* Pinezki (Markery) */ +.comment-marker { + position: absolute; + width: 24px; + height: 24px; + background: #ff3e1d; + border: 2px solid white; + border-radius: 50% 50% 50% 0; + transform: rotate(-45deg) translate(-50%, -100%); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); + cursor: pointer; + z-index: 10000; + transition: transform 0.2s; +} + +.comment-marker:hover { + z-index: 10001; + transform: rotate(-45deg) scale(1.2); +} + +.comment-marker.resolved { + background: #71dd37; + /* Zielony dla rozwiązanych */ +} + +/* Dymek z komentarzem (Tooltip) */ +.comment-popover { + position: absolute; + background: white; + border: 1px solid #d9dee3; + border-radius: 6px; + padding: 10px; + width: 250px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); + z-index: 10002; + font-size: 13px; + color: #333; +} + +.comment-popover h6 { + margin: 0 0 5px 0; + font-size: 12px; + color: #888; + font-weight: bold; +} + +.comment-popover p { + margin: 0; + line-height: 1.4; +} + +/* Tryb wyboru - podświetlanie elementów */ +.comment-mode-active *:hover { + outline: 2px dashed #2196F3 !important; + cursor: comment !important; +} + +/* Wykluczenia, żeby nie podświetlać samego UI komentarzy */ +.comment-mode-active #prototype-topbar *:hover, +.comment-mode-active .comment-marker:hover, +.comment-mode-active .comment-popover *:hover { + outline: none !important; + cursor: default !important; +} + +/* Modal do dodawania (prosty) */ +#comment-input-box { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + z-index: 100002; + width: 300px; + display: none; +} + +#comment-input-box textarea { + width: 100%; + height: 80px; + margin-bottom: 10px; + padding: 5px; +} + +#comment-input-box-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 100001; + display: none; + backdrop-filter: blur(2px); +} + +/* --- POPRAWKA: Wykluczenie okienka edycji z efektów wizualnych --- */ + +/* Nawet w trybie comment-mode-active, nasze okno dialogowe ma wyglądać normalnie */ +.comment-mode-active #comment-input-box, +.comment-mode-active #comment-input-box * { + outline: none !important; + cursor: auto !important; + /* Przywraca normalny kursor (strzałkę/łapkę) */ +} + +/* --- POPRAWKA: Wykluczenie okienka edycji z efektów wizualnych --- */ + +/* assets/css/comments.css */ + +/* ... poprzednie style bez zmian ... */ + +/* POPRAWKI DLA MODALA I FOCUSU */ +#comment-input-box { + /* Ustawiamy fixed/absolute dynamicznie w JS, ale bazowo: */ + z-index: 100000; + /* Musi być wyżej niż Bootstrap Modal (zwykle 1055) */ + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + width: 320px; + display: none; + /* Centrowanie - teraz zrobimy to sprytniej w CSS */ + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +#comment-input-box-overlay { + position: fixed; + /* JS zmieni na absolute jeśli w modalu */ + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(2px); + z-index: 99999; + display: none; +} + +/* Ważne: Pineski muszą być nad wszystkim */ +.comment-marker { + z-index: 100001; + pointer-events: auto !important; + /* Żeby dało się w nie klikać nawet w trybie blokady */ +} \ No newline at end of file diff --git a/assets/js/comments.js b/assets/js/comments.js new file mode 100644 index 0000000..aecba26 --- /dev/null +++ b/assets/js/comments.js @@ -0,0 +1,358 @@ +// assets/js/comments.js + +(function () { + // Konfiguracja API + const baseUrl = (typeof MAGICO_BASE_URL !== 'undefined') ? MAGICO_BASE_URL : ''; + const API_URL = baseUrl + '/api-comments.php'; + const CURRENT_PATH = window.location.pathname.replace(/^\/|\/$/g, ''); + + let isCommentMode = false; + let pendingElement = null; // Element, który chcemy skomentować + let markersData = []; // Przechowujemy dane o markerach lokalnie do szybkiego odświeżania + + // --- 1. GENEROWANIE UI --- + function initUI() { + document.body.style.paddingTop = '50px'; + + const bar = document.createElement('div'); + bar.id = 'prototype-topbar'; + bar.innerHTML = ` +
${safeComment.innerHTML}
+ `; + + document.body.appendChild(pop); + activePopover = pop; + pop._associatedMarker = marker; + + // 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 + + // 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; + } + }); + + // Aktualizuj też pozycję otwartego popovera przy scrollu + const originalUpdatePositions = updateMarkerPositions; + // ... w sumie updateMarkerPositions jest niżej zdefiniowana, + // lepiej wrzucimy to w updateMarkerPositions bezpośrednio. + + // Funkcja wywoływana przy scrollowaniu - musi być szybka + function updateMarkerPositions() { + const markers = document.querySelectorAll('.comment-marker'); + + markers.forEach(marker => { + const index = marker.dataset.index; + const data = markersData[index]; + if (!data) return; + + try { + const el = document.querySelector(data.dom_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'; + + 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); + + if (rect.right + 260 > window.innerWidth) { + left = (rect.left + scrollLeft - 260); // Flip to left + } + + activePopover.style.top = top + 'px'; + activePopover.style.left = left + 'px'; + } + } else { + marker.style.display = 'none'; // Ukryj jeśli element zniknął + } + } catch (e) { } + }); + + // Jeśli jest aktywny popover, zamknij go przy szybkim scrollu żeby nie pływał dziwnie? + // Albo po prostu zostaw. + } + + // START + initUI(); + loadComments(); + + // 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); + }); + + observer.observe(document.body, { + childList: true, subtree: true, attributes: true, + attributeFilter: ['class', 'style', 'hidden'] + }); + +})(); \ No newline at end of file diff --git a/db_connect.php b/db_connect.php new file mode 100644 index 0000000..da40b69 --- /dev/null +++ b/db_connect.php @@ -0,0 +1,21 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; + +try { + $pdo = new PDO($dsn, $user, $pass, $options); +} catch (\PDOException $e) { + // W środowisku produkcyjnym nie pokazuj błędów użytkownikom, ale tu to prototyp: + throw new \PDOException($e->getMessage(), (int) $e->getCode()); +} +?> \ No newline at end of file diff --git a/footer-sneat.php b/footer-sneat.php index 1a04b7c..c9bd2c2 100644 --- a/footer-sneat.php +++ b/footer-sneat.php @@ -27,4 +27,13 @@ - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/header-sneat.php b/header-sneat.php index 608fa60..2d74032 100644 --- a/header-sneat.php +++ b/header-sneat.php @@ -44,6 +44,10 @@ if ($_SERVER['HTTP_HOST'] === 'localhost' || $_SERVER['HTTP_HOST'] === '127.0.0. + + + + diff --git a/prototype/travel/travel-zapytanie-dialog.php b/prototype/travel/travel-zapytanie-dialog.php index d69f0a1..4e75a78 100644 --- a/prototype/travel/travel-zapytanie-dialog.php +++ b/prototype/travel/travel-zapytanie-dialog.php @@ -1,4 +1,7 @@ - +
Dodaj notatkę
+ +