// ==UserScript==
// @name Ozon Copy Data
// @namespace https://github.com/qFamouse/
// @version 1.5
// @description Копирование данных с карточки товара Ozon
// @author Famouse
// @match https://ozon.ru/product/*
// @match https://www.ozon.ru/product/*
// @match https://ozon.by/product/*
// @match https://www.ozon.by/product/*
// @match https://ozon.kz/product/*
// @match https://www.ozon.kz/product/*
// @match https://ozon.uz/product/*
// @match https://www.ozon.uz/product/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=ozon.by
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// ─── Константы ────────────────────────────────────────────────────────────
const SEPARATOR = '\n\n' + '='.repeat(60) + '\n\n';
const DEFAULT_SETTINGS = {
copyTitle: true,
copyArticle: true,
copyBrand: true,
copyCategory: true,
copyPrice: true,
copyOldPrice: true,
copyRating: true,
copyUrl: false,
copyImage: false,
copyDescription: true,
copyComposition: true,
copyComplectation: true,
copyUsage: true,
copyCharacteristics: true
};
// ─── Инициализация стилей (один раз) ──────────────────────────────────────
const globalStyle = document.createElement('style');
globalStyle.textContent = `
@keyframes ocd-slideIn {
from { transform: translateX(400px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(globalStyle);
// ─── Настройки ────────────────────────────────────────────────────────────
function loadSettings() {
const saved = GM_getValue('ozon_copy_settings', null);
// Мёрджим с дефолтами — новые ключи не потеряются при обновлении скрипта
return saved ? { ...DEFAULT_SETTINGS, ...JSON.parse(saved) } : { ...DEFAULT_SETTINGS };
}
function saveSettings(settings) {
GM_setValue('ozon_copy_settings', JSON.stringify(settings));
}
let settings = loadSettings();
// ─── Накопительный буфер ──────────────────────────────────────────────────
function getAccumulatedData() {
return GM_getValue('ozon_accumulated_buffer', '');
}
function getAccumulatedCount() {
const data = getAccumulatedData();
if (!data) return 0;
// Считаем по разделителям, а не по "Название:" — не зависит от настроек
return (data.match(new RegExp('={60}', 'g')) || []).length + 1;
}
function addToAccumulatedData(newData) {
const current = getAccumulatedData();
if (current) {
const newArticleMatch = newData.match(/Артикул:\s*(\d+)/);
if (newArticleMatch) {
const articleRegex = new RegExp(`Артикул:\\s*${newArticleMatch[1]}\\b`);
if (articleRegex.test(current)) {
return { isDuplicate: true, data: current };
}
} else {
const entries = current.split(SEPARATOR);
if (entries.some(entry => entry.trim() === newData.trim())) {
return { isDuplicate: true, data: current };
}
}
}
const updated = current ? current + SEPARATOR + newData : newData;
GM_setValue('ozon_accumulated_buffer', updated);
return { isDuplicate: false, data: updated };
}
function clearAccumulatedData() {
GM_setValue('ozon_accumulated_buffer', '');
}
function removeFromAccumulatedData(index) {
const entries = getAccumulatedData().split(SEPARATOR);
entries.splice(index, 1);
GM_setValue('ozon_accumulated_buffer', entries.join(SEPARATOR));
}
function getAccumulatedEntries() {
const data = getAccumulatedData();
if (!data) return [];
return data.split(SEPARATOR);
}
// ─── Парсинг JSON-LD ──────────────────────────────────────────────────────
function getJsonLd() {
try {
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
for (const s of scripts) {
const d = JSON.parse(s.textContent);
if (d['@type'] === 'Product') return d;
}
} catch {}
return null;
}
// ─── Извлечение данных ────────────────────────────────────────────────────
function extractData() {
const data = {};
const ld = getJsonLd(); // надёжный структурированный источник
// Название
if (settings.copyTitle) {
const el = document.querySelector('h1[class*="tsHeadline"]') ||
document.querySelector('[data-widget="webProductHeading"] h1');
data.title = el?.textContent.trim() ?? ld?.name ?? null;
}
// Артикул (SKU)
if (settings.copyArticle) {
if (ld?.sku) {
data.article = ld.sku;
} else {
const el = Array.from(document.querySelectorAll('button, div'))
.find(el => el.textContent.includes('Артикул:'));
const match = el?.textContent.match(/Артикул:\s*(\d+)/);
data.article = match ? match[1] : null;
}
}
// Бренд
if (settings.copyBrand) {
data.brand = ld?.brand ?? null;
if (!data.brand) {
// Последний элемент breadcrumb — это обычно бренд на Ozon
const crumbs = document.querySelectorAll('[data-widget="breadCrumbs"] ol li span');
if (crumbs.length > 0) data.brand = crumbs[crumbs.length - 1]?.textContent.trim() ?? null;
}
}
// Категория (хлебные крошки без бренда)
if (settings.copyCategory) {
const crumbs = document.querySelectorAll('[data-widget="breadCrumbs"] ol li a span');
if (crumbs.length > 0) {
data.category = Array.from(crumbs).map(el => el.textContent.trim()).join(' > ');
} else {
data.category = null;
}
}
// Текущая цена
if (settings.copyPrice) {
if (ld?.offers?.price && ld?.offers?.priceCurrency) {
data.price = `${ld.offers.price} ${ld.offers.priceCurrency}`;
} else {
const el = document.querySelector('[class*="tsHeadline600Large"]') ||
document.querySelector('[data-widget="webPrice"] span');
data.price = el?.textContent.trim() ?? null;
}
}
// Старая цена
if (settings.copyOldPrice) {
const el = document.querySelector('[class*="pdp_bf7"]') ||
Array.from(document.querySelectorAll('span'))
.find(el => el.style.textDecoration === 'line-through');
data.oldPrice = el?.textContent.trim() ?? null;
}
// Рейтинг и отзывы
if (settings.copyRating) {
const rating = ld?.aggregateRating?.ratingValue;
const count = ld?.aggregateRating?.reviewCount;
if (rating) {
data.rating = count ? `${rating} (${Number(count).toLocaleString('ru-RU')} отзывов)` : String(rating);
} else {
// Fallback по DOM
const ratingEl = document.querySelector('[data-widget="webSingleProductScore"] [class*="tsBodyControl"]');
data.rating = ratingEl?.textContent.trim() ?? null;
}
}
// URL страницы
if (settings.copyUrl) {
data.url = ld?.offers?.url ?? window.location.href;
}
// Изображение (первое / главное)
if (settings.copyImage) {
data.image = ld?.image ?? null;
if (!data.image) {
const img = document.querySelector('[data-widget="webGallery"] img');
data.image = img?.src ?? null;
}
}
// Описание
if (settings.copyDescription) {
if (ld?.description) {
data.description = ld.description.replace(/\s+/g, ' ').trim();
} else {
const el = document.querySelector('#section-description');
data.description = el?.textContent.replace(/\s+/g, ' ').trim() ?? null;
}
}
// Один проход по h3 для Состав / Комплектация / Способ применения
if (settings.copyComposition || settings.copyComplectation || settings.copyUsage) {
const h3Map = {};
document.querySelectorAll('h3').forEach(h => {
h3Map[h.textContent.trim()] = h.nextElementSibling;
});
if (settings.copyComposition) {
data.composition = h3Map['Состав']?.textContent.trim() ?? null;
}
if (settings.copyComplectation) {
data.complectation = h3Map['Комплектация']?.textContent.trim() ?? null;
}
if (settings.copyUsage) {
data.usage = h3Map['Способ применения']?.textContent.trim() ?? null;
}
}
// Характеристики
if (settings.copyCharacteristics) {
const chars = {};
const charSection = document.querySelector('#section-characteristics, [id*="characteristics"]');
if (charSection) {
charSection.querySelectorAll('dl').forEach(dl => {
const key = dl.querySelector('dt')?.textContent.trim();
const value = dl.querySelector('dd')?.textContent.trim();
if (key && value) chars[key] = value;
});
}
data.characteristics = Object.keys(chars).length > 0 ? chars : null;
}
return data;
}
// ─── Форматирование ───────────────────────────────────────────────────────
function formatData(data) {
const result = [];
if (data.title) result.push(`Название: ${data.title}`);
if (data.article) result.push(`Артикул: ${data.article}`);
if (data.brand) result.push(`Бренд: ${data.brand}`);
if (data.category) result.push(`Категория: ${data.category}`);
if (data.price) result.push(`Цена: ${data.price}`);
if (data.oldPrice) result.push(`Старая цена: ${data.oldPrice}`);
if (data.rating) result.push(`Рейтинг: ${data.rating}`);
if (data.url) result.push(`URL: ${data.url}`);
if (data.image) result.push(`Изображение: ${data.image}`);
if (data.description) result.push(`\nОписание:\n${data.description}`);
if (data.composition) result.push(`\nСостав:\n${data.composition}`);
if (data.complectation) result.push(`\nКомплектация:\n${data.complectation}`);
if (data.usage) result.push(`\nСпособ применения:\n${data.usage}`);
if (data.characteristics) {
result.push('\nХарактеристики:');
for (const [key, value] of Object.entries(data.characteristics)) {
result.push(` ${key}: ${value}`);
}
}
return result.join('\n');
}
// ─── Буфер обмена ─────────────────────────────────────────────────────────
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
} catch {
// Fallback для браузеров без clipboard API
const ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;left:-9999px;top:0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
}
// ─── Уведомления ─────────────────────────────────────────────────────────
const NOTIFICATION_COLORS = {
success: '#4CAF50',
warning: '#FF9800',
error: '#F44336',
info: '#2196F3'
};
function showNotification(message, type = 'success') {
const el = document.createElement('div');
el.textContent = message;
el.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${NOTIFICATION_COLORS[type]};
color: white;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10002;
font-size: 14px;
font-weight: 500;
animation: ocd-slideIn 0.3s ease;
`;
document.body.appendChild(el);
setTimeout(() => {
el.style.transition = 'all 0.3s ease';
el.style.transform = 'translateX(400px)';
el.style.opacity = '0';
setTimeout(() => el.remove(), 300);
}, 3000);
}
// ─── Вспомогательные функции UI ───────────────────────────────────────────
function makeButton(label, cssExtra) {
const btn = document.createElement('button');
btn.textContent = label;
btn.style.cssText = `
padding: 12px 16px;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
transition: background 0.2s;
${cssExtra}
`;
return btn;
}
function setHover(btn, normalBg, hoverBg) {
btn.addEventListener('mouseenter', () => { btn.style.background = hoverBg; });
btn.addEventListener('mouseleave', () => { btn.style.background = normalBg; });
}
// ─── Панель настроек ──────────────────────────────────────────────────────
function closeOverlay(overlay) {
overlay.remove();
}
function createSettingsPanel() {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
z-index: 10001;
display: flex; align-items: center; justify-content: center;
`;
const panel = document.createElement('div');
panel.style.cssText = `
background: white;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0,0,0,0.15);
width: 420px;
max-width: 95vw;
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden;
`;
// ── Заголовок + вкладки ──────────────────────────────────────────────
const header = document.createElement('div');
header.style.cssText = `
padding: 20px 24px 0;
border-bottom: 1px solid #e0e0e0;
flex-shrink: 0;
`;
const titleEl = document.createElement('h3');
titleEl.textContent = 'Ozon Copy Data';
titleEl.style.cssText = 'margin: 0 0 16px 0; color: #1a1a1a; font-size: 18px; font-weight: 600;';
header.appendChild(titleEl);
const tabs = document.createElement('div');
tabs.style.cssText = 'display: flex; gap: 0;';
function makeTab(label) {
const tab = document.createElement('button');
tab.textContent = label;
tab.style.cssText = `
padding: 8px 18px; border: none; background: none; cursor: pointer;
font-size: 14px; font-weight: 500; color: #888;
border-bottom: 2px solid transparent; margin-bottom: -1px;
transition: color 0.15s;
`;
return tab;
}
const tabSettings = makeTab('Поля');
const tabBuffer = makeTab('Буфер');
tabs.append(tabSettings, tabBuffer);
header.appendChild(tabs);
panel.appendChild(header);
// ── Тело панели (скроллируемое) ───────────────────────────────────────
const body = document.createElement('div');
body.style.cssText = 'padding: 20px 24px; overflow-y: auto; flex: 1;';
panel.appendChild(body);
// ── Футер ─────────────────────────────────────────────────────────────
const footer = document.createElement('div');
footer.style.cssText = `
padding: 12px 24px 20px;
border-top: 1px solid #e0e0e0;
flex-shrink: 0;
display: flex; gap: 8px; flex-direction: column;
`;
panel.appendChild(footer);
// ── Вкладка «Поля» ────────────────────────────────────────────────────
const fieldOptions = [
{ key: 'copyTitle', label: 'Название' },
{ key: 'copyArticle', label: 'Артикул' },
{ key: 'copyBrand', label: 'Бренд' },
{ key: 'copyCategory', label: 'Категория' },
{ key: 'copyPrice', label: 'Цена' },
{ key: 'copyOldPrice', label: 'Старая цена' },
{ key: 'copyRating', label: 'Рейтинг и отзывы' },
{ key: 'copyUrl', label: 'URL товара' },
{ key: 'copyImage', label: 'Ссылка на изображение' },
{ key: 'copyDescription', label: 'Описание' },
{ key: 'copyComposition', label: 'Состав' },
{ key: 'copyComplectation', label: 'Комплектация' },
{ key: 'copyUsage', label: 'Способ применения' },
{ key: 'copyCharacteristics', label: 'Характеристики' }
];
const settingsPane = document.createElement('div');
fieldOptions.forEach(opt => {
const label = document.createElement('label');
label.style.cssText = `
display: flex; align-items: center;
margin-bottom: 12px; cursor: pointer; user-select: none;
`;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = settings[opt.key];
checkbox.style.cssText = 'margin-right: 10px; width: 16px; height: 16px; cursor: pointer; flex-shrink: 0;';
checkbox.addEventListener('change', () => {
settings[opt.key] = checkbox.checked;
saveSettings(settings);
});
const labelText = document.createElement('span');
labelText.textContent = opt.label;
labelText.style.cssText = 'color: #333; font-size: 14px;';
label.append(checkbox, labelText);
settingsPane.appendChild(label);
});
// ── Вкладка «Буфер» ───────────────────────────────────────────────────
const bufferPane = document.createElement('div');
function getEntryLabel(entry) {
const titleMatch = entry.match(/Название:\s*(.+)/);
const articleMatch = entry.match(/Артикул:\s*(\d+)/);
const title = titleMatch ? titleMatch[1].trim() : null;
const article = articleMatch ? articleMatch[1].trim() : null;
if (title && article) return `${title} (${article})`;
if (title) return title;
if (article) return `Артикул: ${article}`;
return entry.slice(0, 60).trim();
}
function renderBufferList() {
bufferPane.innerHTML = '';
const entries = getAccumulatedEntries();
if (entries.length === 0) {
const empty = document.createElement('div');
empty.textContent = 'Буфер пуст';
empty.style.cssText = 'color: #999; font-size: 14px; text-align: center; padding: 24px 0;';
bufferPane.appendChild(empty);
return;
}
const countEl = document.createElement('div');
countEl.textContent = `Товаров: ${entries.length}`;
countEl.style.cssText = 'color: #888; font-size: 13px; margin-bottom: 12px;';
bufferPane.appendChild(countEl);
entries.forEach((entry, idx) => {
const row = document.createElement('div');
row.style.cssText = `
display: flex; align-items: flex-start; gap: 10px;
padding: 10px 12px; border-radius: 8px; background: #f8f8f8;
margin-bottom: 8px;
`;
const nameEl = document.createElement('div');
nameEl.textContent = getEntryLabel(entry);
nameEl.style.cssText = `
flex: 1; font-size: 13px; color: #222; line-height: 1.4;
word-break: break-word;
`;
const delBtn = document.createElement('button');
delBtn.title = 'Удалить';
delBtn.innerHTML = ``;
delBtn.style.cssText = `
flex-shrink: 0; padding: 4px; border: none; background: none;
color: #bbb; cursor: pointer; border-radius: 4px;
display: flex; align-items: center; transition: color 0.15s, background 0.15s;
`;
delBtn.addEventListener('mouseenter', () => { delBtn.style.color = '#e53935'; delBtn.style.background = '#fce8e8'; });
delBtn.addEventListener('mouseleave', () => { delBtn.style.color = '#bbb'; delBtn.style.background = 'none'; });
delBtn.addEventListener('click', () => {
removeFromAccumulatedData(idx);
updateCounter();
renderBufferList();
updateFooterButtons();
});
row.append(nameEl, delBtn);
bufferPane.appendChild(row);
});
}
// ── Футер-кнопки ─────────────────────────────────────────────────────
const copyBufferBtn = makeButton('Скопировать буфер', 'cursor: pointer;');
setHover(copyBufferBtn, '#005bff', '#0047cc');
copyBufferBtn.addEventListener('click', async () => {
const accumulated = getAccumulatedData();
if (accumulated) {
await copyToClipboard(accumulated);
showNotification('Буфер скопирован в clipboard', 'success');
}
});
const clearBtn = makeButton('Очистить буфер', 'cursor: pointer;');
setHover(clearBtn, '#FF5722', '#E64A19');
clearBtn.addEventListener('click', () => {
if (confirm('Очистить накопительный буфер?')) {
clearAccumulatedData();
updateCounter();
renderBufferList();
updateFooterButtons();
showNotification('Буфер очищен', 'info');
}
});
const closeBtn = makeButton('Закрыть', 'background: #f5f5f5; color: #333; margin-top: 0;');
closeBtn.style.color = '#333';
setHover(closeBtn, '#f5f5f5', '#e0e0e0');
closeBtn.addEventListener('click', () => closeOverlay(overlay));
footer.append(copyBufferBtn, clearBtn, closeBtn);
function updateFooterButtons() {
const hasData = getAccumulatedData().length > 0;
copyBufferBtn.disabled = !hasData;
copyBufferBtn.style.background = hasData ? '#005bff' : '#ccc';
copyBufferBtn.style.cursor = hasData ? 'pointer' : 'not-allowed';
clearBtn.disabled = !hasData;
clearBtn.style.background = hasData ? '#FF5722' : '#ccc';
clearBtn.style.cursor = hasData ? 'pointer' : 'not-allowed';
}
// ── Переключение вкладок ─────────────────────────────────────────────
function activateTab(tab, pane, showFooterBtns) {
[tabSettings, tabBuffer].forEach(t => {
t.style.color = '#888';
t.style.borderBottomColor = 'transparent';
});
tab.style.color = '#005bff';
tab.style.borderBottomColor = '#005bff';
body.innerHTML = '';
body.appendChild(pane);
// Кнопки буфера показываем только на вкладке «Буфер»
copyBufferBtn.style.display = showFooterBtns ? '' : 'none';
clearBtn.style.display = showFooterBtns ? '' : 'none';
}
tabSettings.addEventListener('click', () => activateTab(tabSettings, settingsPane, false));
tabBuffer.addEventListener('click', () => {
activateTab(tabBuffer, bufferPane, true);
renderBufferList();
updateFooterButtons();
});
// Открываем сразу «Поля»
activateTab(tabSettings, settingsPane, false);
// ── Закрытие ─────────────────────────────────────────────────────────
overlay.appendChild(panel);
overlay.addEventListener('click', e => { if (e.target === overlay) closeOverlay(overlay); });
const onKeyDown = e => {
if (e.key === 'Escape') { closeOverlay(overlay); document.removeEventListener('keydown', onKeyDown); }
};
document.addEventListener('keydown', onKeyDown);
document.body.appendChild(overlay);
}
// ─── UI: Счётчик ──────────────────────────────────────────────────────────
const counterBadge = document.createElement('div');
counterBadge.style.cssText = `
position: absolute; top: -6px; right: -6px;
background: #FF5722; color: white; border-radius: 50%;
min-width: 20px; height: 20px; font-size: 11px; font-weight: bold;
display: none; align-items: center; justify-content: center;
border: 2px solid white; padding: 0 4px;
`;
function updateCounter() {
const hasData = getAccumulatedData().length > 0;
const count = hasData ? getAccumulatedCount() : 0;
counterBadge.textContent = count;
counterBadge.style.display = count > 0 ? 'flex' : 'none';
}
// ─── UI: Кнопки ───────────────────────────────────────────────────────────
// Кнопка — скопировать товар
const copyButton = document.createElement('button');
copyButton.innerHTML = '📋';
copyButton.title = 'Копировать данные товара';
copyButton.style.cssText = `
position: fixed; bottom: 80px; right: 20px; z-index: 10000;
background: #005bff; color: white; border: none;
padding: 12px; border-radius: 50%; font-size: 20px; cursor: pointer;
box-shadow: 0 2px 10px rgba(0,91,255,0.3); transition: all 0.2s;
width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;
`;
copyButton.addEventListener('mouseenter', () => copyButton.style.transform = 'scale(1.1)');
copyButton.addEventListener('mouseleave', () => copyButton.style.transform = 'scale(1)');
copyButton.addEventListener('click', async () => {
try {
const text = formatData(extractData());
await copyToClipboard(text);
copyButton.innerHTML = '✅';
copyButton.style.background = '#28a745';
setTimeout(() => { copyButton.innerHTML = '📋'; copyButton.style.background = '#005bff'; }, 1500);
} catch (err) {
copyButton.innerHTML = '❌';
copyButton.style.background = '#dc3545';
setTimeout(() => { copyButton.innerHTML = '📋'; copyButton.style.background = '#005bff'; }, 1500);
console.error('[OzonCopyData] Ошибка копирования:', err);
}
});
// Кнопка — добавить в буфер
const accumulateButton = document.createElement('button');
accumulateButton.innerHTML = `
`;
accumulateButton.title = 'Добавить товар в буфер';
accumulateButton.style.cssText = `
position: fixed; bottom: 140px; right: 20px; z-index: 10000;
background: #005bff; color: white; border: none;
padding: 12px; border-radius: 50%; cursor: pointer;
box-shadow: 0 2px 10px rgba(0,91,255,0.3); transition: all 0.2s;
width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;
`;
accumulateButton.addEventListener('mouseenter', () => {
accumulateButton.style.transform = 'scale(1.1)';
accumulateButton.style.boxShadow = '0 4px 15px rgba(0,91,255,0.4)';
});
accumulateButton.addEventListener('mouseleave', () => {
accumulateButton.style.transform = 'scale(1)';
accumulateButton.style.boxShadow = '0 2px 10px rgba(0,91,255,0.3)';
});
accumulateButton.addEventListener('click', () => {
try {
const text = formatData(extractData());
const result = addToAccumulatedData(text);
if (result.isDuplicate) {
showNotification('Этот товар уже в буфере', 'warning');
} else {
updateCounter();
showNotification('Товар добавлен в буфер', 'success');
accumulateButton.style.background = '#28a745';
setTimeout(() => { accumulateButton.style.background = '#005bff'; }, 300);
}
} catch (err) {
showNotification('Ошибка при добавлении', 'error');
console.error('[OzonCopyData] Ошибка:', err);
}
});
accumulateButton.appendChild(counterBadge);
// Кнопка — настройки
const settingsButton = document.createElement('button');
settingsButton.innerHTML = '⚙️';
settingsButton.title = 'Настройки копирования';
settingsButton.style.cssText = `
position: fixed; bottom: 200px; right: 20px; z-index: 10000;
background: #6c757d; color: white; border: none;
padding: 10px; border-radius: 50%; font-size: 18px; cursor: pointer;
box-shadow: 0 2px 8px rgba(108,117,125,0.3); transition: all 0.2s;
width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
`;
settingsButton.addEventListener('mouseenter', () => {
settingsButton.style.transform = 'scale(1.1)';
settingsButton.style.background = '#5a6268';
});
settingsButton.addEventListener('mouseleave', () => {
settingsButton.style.transform = 'scale(1)';
settingsButton.style.background = '#6c757d';
});
settingsButton.addEventListener('click', createSettingsPanel);
document.body.append(copyButton, accumulateButton, settingsButton);
updateCounter();
// ─── Команды в меню Tampermonkey ──────────────────────────────────────────
GM_registerMenuCommand('📋 Скопировать товар', async () => {
try {
const text = formatData(extractData());
await copyToClipboard(text);
showNotification('Данные скопированы', 'success');
} catch (err) {
showNotification('Ошибка копирования', 'error');
}
});
GM_registerMenuCommand('➕ Добавить в буфер', () => {
try {
const text = formatData(extractData());
const result = addToAccumulatedData(text);
updateCounter();
showNotification(result.isDuplicate ? 'Уже в буфере' : 'Добавлено в буфер',
result.isDuplicate ? 'warning' : 'success');
} catch (err) {
showNotification('Ошибка', 'error');
}
});
GM_registerMenuCommand('📤 Скопировать весь буфер', async () => {
const data = getAccumulatedData();
if (data) {
await copyToClipboard(data);
showNotification('Буфер скопирован', 'success');
} else {
showNotification('Буфер пуст', 'warning');
}
});
GM_registerMenuCommand('🗑️ Очистить буфер', () => {
if (confirm('Очистить накопительный буфер?')) {
clearAccumulatedData();
updateCounter();
showNotification('Буфер очищен', 'info');
}
});
})();