Newer
Older
fixtape / src / popup.js
// Popup: список правок текущей страницы, редактирование/удаление,
// копирование всех правок одним запросом, очистка, toggle режима выбора.
(() => {
  const STORAGE_KEY = 'ft_comments';
  const COPY_HEADER = (url) =>
    `Общий контекст: страница ${url}. Ниже набор отдельных правок; селектор указывает на целевой элемент.`;

  let tab = null;
  let tabUrl = '';
  let comments = []; // правки текущей страницы
  let pickActive = false;

  const listEl = document.getElementById('list');
  const emptyEl = document.getElementById('empty');
  const othersEl = document.getElementById('others');
  const copyBtn = document.getElementById('copy-all');
  const clearBtn = document.getElementById('clear-page');
  const toggleBtn = document.getElementById('toggle-mode');
  const toggleLabel = document.getElementById('toggle-label');
  const hotkeyEl = document.getElementById('hotkey');
  const CLEAR_LABEL = 'Clear';
  const CLEAR_CONFIRM = 'Confirm clear?';

  init();

  async function init() {
    [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    tabUrl = tab ? tab.url.split('#')[0] : '';
    document.getElementById('page-url').textContent = tabUrl;

    try {
      const res = await chrome.tabs.sendMessage(tab.id, { type: 'ft-get-state' });
      pickActive = !!res.active;
      updateToggleLabel();
    } catch {
      toggleBtn.disabled = true; // нет контент-скрипта (chrome:// и т.п.)
    }

    const cmds = await chrome.commands.getAll();
    const cmd = cmds.find((c) => c.name === 'toggle-pick');
    if (cmd && cmd.shortcut) hotkeyEl.textContent = cmd.shortcut;
    else hotkeyEl.remove();

    toggleBtn.addEventListener('click', onToggleMode);
    copyBtn.addEventListener('click', onCopyAll);
    clearBtn.addEventListener('click', onClearPage);
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') resetClearConfirm();
    });
    await render();
  }

  function loadAll() {
    return chrome.storage.local.get({ [STORAGE_KEY]: [] }).then((d) => d[STORAGE_KEY]);
  }

  function saveAll(all) {
    return chrome.storage.local.set({ [STORAGE_KEY]: all });
  }

  async function render() {
    resetClearConfirm();
    const all = await loadAll();
    comments = all.filter((c) => c.url === tabUrl);
    copyBtn.disabled = comments.length === 0;
    clearBtn.disabled = comments.length === 0;

    listEl.innerHTML = '';
    emptyEl.hidden = comments.length > 0;

    comments.forEach((c) => listEl.appendChild(renderItem(c)));

    const others = all.length - comments.length;
    othersEl.textContent = others > 0
      ? `${others} more edits on other pages`
      : '';

    updateBadge(comments.length);
  }

  function renderItem(c) {
    const item = document.createElement('div');
    item.className = 'item';

    const del = document.createElement('button');
    del.className = 'del';
    del.title = 'Удалить правку';
    del.textContent = '✕';
    del.addEventListener('click', () => removeComment(c.id));
    item.appendChild(del);

    const ta = document.createElement('textarea');
    ta.value = c.prompt;
    ta.addEventListener('change', () => updatePrompt(c.id, ta.value.trim()));
    item.appendChild(ta);

    const targets = document.createElement('ul');
    targets.className = 'targets';
    for (const el of c.elements) {
      const li = document.createElement('li');
      li.textContent = elementLine(el);
      li.title = li.textContent;
      targets.appendChild(li);
    }
    item.appendChild(targets);

    return item;
  }

  function elementLine(el) {
    const parts = [`<${el.tag}`];
    if (el.cls) parts.push(` class="${el.cls}"`);
    if (el.text) parts.push(` «${el.text}»`);
    return `\`${el.selector}\` ${parts.join('')}>`;
  }

  async function updatePrompt(id, prompt) {
    if (!prompt) { await removeComment(id); return; }
    const all = await loadAll();
    const c = all.find((x) => x.id === id);
    if (!c) return;
    c.prompt = prompt;
    await saveAll(all);
  }

  async function removeComment(id) {
    const all = await loadAll();
    await saveAll(all.filter((x) => x.id !== id));
    await render();
  }

  let confirmTimer = null;

  function onClearPage() {
    if (!comments.length) return;
    if (!clearBtn.classList.contains('confirming')) {
      clearBtn.classList.add('confirming');
      clearBtn.textContent = CLEAR_CONFIRM;
      confirmTimer = setTimeout(resetClearConfirm, 3000);
      return;
    }
    resetClearConfirm();
    clearPage();
  }

  function resetClearConfirm() {
    clearTimeout(confirmTimer);
    confirmTimer = null;
    clearBtn.classList.remove('confirming');
    clearBtn.textContent = CLEAR_LABEL;
  }

  async function clearPage() {
    const all = await loadAll();
    await saveAll(all.filter((x) => x.url !== tabUrl));
    await render();
  }

  function buildCopyText() {
    const lines = [COPY_HEADER(tabUrl), ''];
    comments.forEach((c, i) => {
      lines.push(`--- Правка ${i + 1} ---`);
      c.elements.forEach((el, j) => {
        const label = c.elements.length > 1 ? `Элемент ${j + 1}` : 'Элемент';
        lines.push(`${label}: ${elementLine(el)}`);
      });
      lines.push(c.prompt, '');
    });
    return lines.join('\n').trimEnd() + '\n';
  }

  async function onCopyAll() {
    if (!comments.length) return;
    const text = buildCopyText();
    try {
      await navigator.clipboard.writeText(text);
      toast('Copied ✓');
    } catch {
      fallbackCopy(text);
      toast('Copied ✓');
    }
  }

  function fallbackCopy(text) {
    const ta = document.createElement('textarea');
    ta.value = text;
    ta.style.position = 'fixed';
    ta.style.opacity = '0';
    document.body.appendChild(ta);
    ta.select();
    document.execCommand('copy');
    ta.remove();
  }

  function toast(msg) {
    const el = document.createElement('div');
    el.className = 'toast';
    el.textContent = msg;
    document.body.appendChild(el);
    setTimeout(() => el.remove(), 1400);
  }

  async function onToggleMode() {
    try {
      const res = await chrome.tabs.sendMessage(tab.id, { type: 'ft-toggle' });
      pickActive = !!res.active;
      updateToggleLabel();
    } catch {
      // нет контент-скрипта
    }
  }

  function updateToggleLabel() {
    toggleLabel.textContent = pickActive ? '🎯 Pick mode: ON' : '🎯 Pick mode';
    toggleBtn.classList.toggle('on', pickActive);
  }

  function updateBadge(count) {
    if (tab && tab.id != null) {
      chrome.action.setBadgeBackgroundColor({ tabId: tab.id, color: '#4f46e5' });
      chrome.action.setBadgeText({ tabId: tab.id, text: count > 0 ? String(count) : '' });
    }
  }
})();