WME Permalink Layer Guard

Ostrzega przy kopiowaniu permalinku, gdy w panelu „Udostępnij lokalizację” zaznaczono „Uwzględnij ustawienia warstw”, i pozwala skopiować link bez warstw (usuwa parametr `s`).

// ==UserScript==
// @name         WME Permalink Layer Guard
// @namespace    https://github.com/majormarcin
// @version      0.2.0
// @description  Ostrzega przy kopiowaniu permalinku, gdy w panelu „Udostępnij lokalizację” zaznaczono „Uwzględnij ustawienia warstw”, i pozwala skopiować link bez warstw (usuwa parametr `s`).
// @author      TheMiskov (idea) / ChatGPT (kod)
// @include         https://*.waze.com/*/editor*
// @include         https://*.waze.com/editor*
// @include         https://*.waze.com/map-editor*
// @include         https://*.waze.com/beta_editor*
// @run-at       document-start
// @grant        none
// @icon            https://www.google.com/s2/favicons?sz=64&domain=waze.com
// ==/UserScript==

(function () {
  'use strict';

  // --- TEKSTY UI ---
  const UI_TEXT = {
    title: 'Masz zaznaczone własne warstwy',
    body: 'W panelu „Udostępnij lokalizację” zaznaczono opcję „Uwzględnij ustawienia warstw”. Skopiować permalink bez warstw?',
    cancel: 'Anuluj',
    proceed: 'Skopiuj bez warstw',
  };

  // --- NARZĘDZIA ---
  const isPolish = true; // tylko podpowiedź dla ewentualnych selektorów tekstowych

  function isWMEPermalink(urlStr) {
    try {
      const u = new URL(urlStr);
      const hostOk = /(^|\.)waze\.com$/i.test(u.hostname.replace(/^www\./, ''));
      const pathOk = /\/editor(\/|$)/.test(u.pathname);
      return hostOk && pathOk;
    } catch (e) {
      return false;
    }
  }

  function hasLayerParam(urlStr) {
    try {
      const u = new URL(urlStr);
      return u.searchParams.has('s');
    } catch (e) {
      return /[?&]s=/.test(urlStr);
    }
  }

  function stripLayerParam(urlStr) {
    try {
      const u = new URL(urlStr);
      u.searchParams.delete('s');
      let out = u.toString();
      out = out.replace(/\?$/, '');
      return out;
    } catch (e) {
      return urlStr
        .replace(/([?&])s=[^&#]*&?/, '$1')
        .replace(/\?&/, '?')
        .replace(/\?$/, '');
    }
  }

  // --- MODAL ---
  function ensureStyles() {
    if (document.getElementById('wme-pl-layer-guard-style')) return;
    const style = document.createElement('style');
    style.id = 'wme-pl-layer-guard-style';
    style.textContent = `
      .wmePLG-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); display: flex; align-items: center; justify-content: center; z-index: 100000; }
      .wmePLG-modal { background: #fff; color: #111; border-radius: 14px; width: min(520px, 92vw); box-shadow: 0 10px 30px rgba(0,0,0,.25); overflow: hidden; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
      .wmePLG-header { padding: 14px 18px; font-weight: 700; font-size: 17px; background: #f3f4f6; border-bottom: 1px solid #e5e7eb; }
      .wmePLG-body { padding: 16px 18px; font-size: 14px; line-height: 1.4; }
      .wmePLG-actions { display: flex; gap: 10px; padding: 12px 18px 18px; justify-content: flex-end; }
      .wmePLG-btn { border: 0; border-radius: 10px; padding: 10px 14px; font-size: 14px; cursor: pointer; }
      .wmePLG-cancel { background: #eef2ff; color: #1f2937; }
      .wmePLG-proceed { background: linear-gradient(135deg, #22d3ee, #a78bfa, #f472b6); color: #fff; font-weight: 700; }
    `;
    document.documentElement.appendChild(style);
  }

  function askUser() {
    return new Promise((resolve, reject) => {
      ensureStyles();
      const backdrop = document.createElement('div');
      backdrop.className = 'wmePLG-backdrop';
      const modal = document.createElement('div');
      modal.className = 'wmePLG-modal';
      modal.innerHTML = `
        <div class="wmePLG-header">${UI_TEXT.title}</div>
        <div class="wmePLG-body">${UI_TEXT.body}</div>
        <div class="wmePLG-actions">
          <button class="wmePLG-btn wmePLG-cancel">${UI_TEXT.cancel}</button>
          <button class="wmePLG-btn wmePLG-proceed">${UI_TEXT.proceed}</button>
        </div>`;
      backdrop.appendChild(modal);
      document.body.appendChild(backdrop);

      function cleanup() { backdrop.remove(); }

      modal.querySelector('.wmePLG-cancel').addEventListener('click', () => { cleanup(); reject(new Error('User cancelled')); });
      modal.querySelector('.wmePLG-proceed').addEventListener('click', () => { cleanup(); resolve(true); });
      backdrop.addEventListener('click', (e) => { if (e.target === backdrop) { cleanup(); reject(new Error('User cancelled')); } });
    });
  }

  // --- WYSZUKIWANIE PANELU „UDOSTĘPNIJ LOKALIZACJĘ” ---
  function findSharePanel(root = document) {
    // Szukamy kontenera zawierającego tekst checkboxa
    const marker = Array.from(root.querySelectorAll('label, div, span, p'))
      .find(el => /Uwzględnij ustawienia warstw/i.test(el.textContent || ''));
    if (!marker) return null;
    // panel = najbliższy dialog/panel
    let panel = marker.closest('[role="dialog"], [data-testid*="dialog"], .modal, .mat-dialog-container, .mdc-dialog, .waze-dialog, .share-dialog');
    if (!panel) panel = marker.closest('div');
    return panel || null;
  }

  function getPanelParts(panel) {
    if (!panel) return {};
    // Checkbox
    const checkbox = panel.querySelector('input[type="checkbox"]');
    // Pole permalinku (input/textarea) — wybieramy pierwsze w panelu z wartością zaczynającą się od https
    let linkInput = Array.from(panel.querySelectorAll('input[type="text"], input[type="url"], textarea'))
      .find(el => /^https?:\/\//.test(el.value || '')) || null;
    // Przycisk(i) kopiowania w panelu
    const copyBtns = Array.from(panel.querySelectorAll('button, a, div[role="button"]')).filter(el => {
      const t = (el.innerText || el.getAttribute('aria-label') || el.title || '').toLowerCase();
      return /(copy|kopiuj)/.test(t) || /fa-copy|icon-copy|copy/i.test(el.className || '');
    });
    return { checkbox, linkInput, copyBtns };
  }

  async function handleShareCopy(panel, evTarget) {
    const { checkbox, linkInput } = getPanelParts(panel);
    // jeśli nie znaleziono checkboxa lub inputu, lecimy starym trybem przez schowek
    let url = linkInput?.value;
    let checkboxChecked = !!checkbox?.checked;

    // Jeżeli link jeszcze nie jest w polu (czasem WME wypełnia po kliknięciu), daj mu ułamek sekundy
    if (!url) {
      await new Promise(r => setTimeout(r, 50));
      if (linkInput) url = linkInput.value;
    }

    // Jeżeli checkbox zaznaczony i/lub w linku jest `s=`, pytamy użytkownika
    const suspect = checkboxChecked || (typeof url === 'string' && hasLayerParam(url));

    if (suspect) {
      // blokujemy domyślne kopiowanie WME
      if (evTarget) {
        try { evTarget.preventDefault?.(); evTarget.stopPropagation?.(); evTarget.stopImmediatePropagation?.(); } catch {}
      }
      try {
        await askUser();
        // Mamy akcept — budujemy końcowy link
        let finalUrl = url;
        if (!finalUrl) {
          // fallback: odczyt ze schowka (jeśli kliknięcie zdążyło skopiować)
          try { finalUrl = await navigator.clipboard.readText(); } catch {}
        }
        if (finalUrl && isWMEPermalink(finalUrl)) {
          finalUrl = stripLayerParam(finalUrl);
          await navigator.clipboard.writeText(finalUrl);
        }
        return true; // obsłużone
      } catch (e) {
        // Anulowano — nie kopiujemy nic
        return true; // też traktujemy jako obsłużone, żeby WME nie robił nic dalej
      }
    }
    return false; // nie ingerujemy
  }

  // --- GŁÓWNY OBSERWATOR UI ---
  function hookSharePanel() {
    const seen = new WeakSet();
    const observe = new MutationObserver(() => {
      const panel = findSharePanel(document);
      if (!panel || seen.has(panel)) return;
      seen.add(panel);

      const { copyBtns } = getPanelParts(panel);
      copyBtns.forEach(btn => {
        if (btn._wmePLG_hooked) return;
        btn._wmePLG_hooked = true;
        btn.addEventListener('click', async (ev) => {
          const handled = await handleShareCopy(panel, ev);
          if (handled) return; // zrobiliśmy swoje
        }, true);
      });
    });
    observe.observe(document.documentElement, { childList: true, subtree: true });
  }

  // --- DODATKOWE BEZPIECZNIKI (poza panelem) ---
  function wrapClipboardWriteText() {
    if (!navigator.clipboard || !navigator.clipboard.writeText) return;
    const orig = navigator.clipboard.writeText.bind(navigator.clipboard);
    navigator.clipboard.writeText = async function (text) {
      try {
        if (typeof text === 'string' && isWMEPermalink(text) && hasLayerParam(text)) {
          await askUser();
          const cleaned = stripLayerParam(text);
          return orig(cleaned);
        }
      } catch {}
      return orig(text);
    };
  }

  function interceptCopyEvent() {
    document.addEventListener('copy', async function (e) {
      try {
        const sel = document.getSelection();
        const clipText = e.clipboardData?.getData('text/plain') || sel?.toString() || '';
        if (clipText && isWMEPermalink(clipText) && hasLayerParam(clipText)) {
          e.preventDefault();
          try {
            await askUser();
            const cleaned = stripLayerParam(clipText);
            e.clipboardData.setData('text/plain', cleaned);
          } catch {}
        }
      } catch {}
    }, true);
  }

  function init() {
    hookSharePanel();
    wrapClipboardWriteText();
    interceptCopyEvent();
    console.log('[WME PL Guard] aktywny');
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();