// 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) : '' });
}
}
})();