Как создать расширение Chrome для заметок Pinterest

Salo Автор: Salo 5 мин чтения
Как создать расширение Chrome для заметок Pinterest

В этом руководстве мы создадим минимальный полезный прототип для Chrome Manifest V3: личное текстовое поле на страницах пинов Pinterest, которое использует числовой идентификатор пина как ключ и сохраняет данные в chrome.storage.local. Это намеренно упрощённый прототип, а не готовая к выпуску копия прежней функции заметок Pinterest.

DOM Pinterest может измениться без предупреждения. Держите селекторы в отдельных функциях, не имитируйте защищённые элементы бренда Pinterest и проверяйте расширение на всех поддерживаемых типах страниц и языковых версиях.

1. Создайте расширение Manifest V3

Создайте папку с файлами manifest.json и content.js. Актуальный список доступных ключей приведён в справочнике Chrome по манифесту.

{ "manifest_version": 3, "name": "Pin Context Prototype", "version": "0.1.0", "description": "Сохраняет личную локальную заметку к пину Pinterest.", "permissions": ["storage"], "content_scripts": [{ "matches": ["https://*.pinterest.com/*"], "js": ["content.js"], "run_at": "document_idle" }] }

Шаблон адресов намеренно широкий, потому что Pinterest использует навигацию на стороне клиента. Если функция работает только на части маршрутов или национальных доменов, сузьте шаблон и опишите поддерживаемую область.

2. Получите стабильный ключ из URL пина

Не привязывайте заметку к положению карточки или видимому заголовку. Извлекайте числовой идентификатор из канонического пути /pin/…/ и возвращайте null для всех остальных страниц.

function getPinId(url = location.href) { try { const { pathname } = new URL(url); const match = pathname.match(/\/pin\/(\d+)\/?/); return match ? match[1] : null; } catch { return null; } }

Проверьте корректные URL страницы пина, строки запроса, страницы без пинов, неправильно составленные URL и все языковые шаблоны, поддержку которых заявляете. Никогда не привязывайте заметку незаметно для пользователя, если идентификатор определить однозначно нельзя.

3. Храните версию структуры записи

Опишите схему явно, чтобы в будущем можно было переносить данные между версиями:

async function readNote(pinId) { const key = 'pin-note:' + pinId; const result = await chrome.storage.local.get(key); return result[key] ?? { version: 1, pinId, text: '', updatedAt: null }; } async function writeNote(pinId, text) { const key = 'pin-note:' + pinId; const record = { version: 1, pinId, text, updatedAt: new Date().toISOString() }; await chrome.storage.local.set({ [key]: record }); return record; }

По документации Chrome стандартный лимит для storage.local составляет 10 МБ. Для его увеличения предусмотрено отдельное разрешение unlimitedStorage. Chrome также указывает, что локальное хранилище расширения очищается при его удалении. Прочитайте действующую документацию Storage API и предусмотрите экспорт до того, как предлагать пользователям хранить важные данные.

4. Добавьте изолированный и доступный редактор

Shadow DOM снижает риск случайного конфликта стилей. В примере элемент добавляется в body страницы и не зависит от нестабильного имени класса Pinterest:

async function mountEditor(pinId) { if (document.querySelector('[data-pin-context-root]')) return; const host = document.createElement('aside'); host.dataset.pinContextRoot = ''; host.setAttribute('aria-label', 'Личная заметка к пину'); document.body.append(host); const root = host.attachShadow({ mode: 'open' }); const label = document.createElement('label'); label.textContent = 'Личная заметка'; const editor = document.createElement('textarea'); editor.id = 'pin-note-editor'; label.htmlFor = editor.id; editor.setAttribute('aria-label', 'Личная заметка к этому пину'); const status = document.createElement('span'); status.setAttribute('role', 'status'); const saved = await readNote(pinId); editor.value = saved.text; root.append(label, editor, status); bindAutosave(editor, status, pinId); }

Добавляйте текст интерфейса через textContent, а не через ненадёжный innerHTML. Добавьте заметное состояние фокуса, достаточную контрастность, настоящую подпись поля, управление с клавиатуры и текст статуса, который не перехватывает фокус.

5. Не записывайте данные после каждого нажатия клавиши

function bindAutosave(editor, status, pinId) { let saveTimer; editor.addEventListener('input', () => { status.textContent = 'Не сохранено'; clearTimeout(saveTimer); saveTimer = setTimeout(async () => { try { await writeNote(pinId, editor.value); status.textContent = 'Сохранено локально'; } catch (error) { console.error('Не удалось сохранить заметку к пину', error); status.textContent = 'Не удалось сохранить'; } }, 400); }); }

В полноценной версии также сохраняйте ещё не записанный текст при переходе между страницами, защищайтесь от взаимной перезаписи из двух вкладок, задайте максимальный размер заметки и показывайте ошибки превышения квоты.

6. Обрабатывайте навигацию Pinterest на стороне клиента

Однократный обработчик загрузки страницы не заметит переходы между лентой и страницей пина. Используйте идемпотентную функцию согласования состояния и следите за изменениями, не создавая интерфейс заново при каждой мутации DOM:

let scheduled = false; async function reconcile() { scheduled = false; const pinId = getPinId(); const existing = document.querySelector('[data-pin-context-root]'); if (!pinId) { existing?.remove(); return; } if (existing?.dataset.pinId === pinId) return; existing?.remove(); await mountEditor(pinId); const mounted = document.querySelector('[data-pin-context-root]'); if (mounted) mounted.dataset.pinId = pinId; } new MutationObserver(() => { if (scheduled) return; scheduled = true; requestAnimationFrame(reconcile); }).observe(document.documentElement, { childList: true, subtree: true }); reconcile();

В полноценной версии по возможности отслеживайте состояние навигации напрямую и отключайте ненужные наблюдатели. Профилируйте код на длинных лентах: даже правильно работающая функция плоха, если она снижает производительность страницы.

7. Соблюдайте ограничения безопасности Manifest V3

Manifest V3 запрещает исполняемый код с удалённых серверов. Включайте весь исполняемый JavaScript в пакет расширения, сохраняйте стандартную политику безопасности контента без обоснованной причины для изменения, избегайте динамического выполнения кода и запрашивайте только те разрешения, которые нужны функции. Модель платформы описана в обзоре Chrome по Manifest V3.

8. Добавьте функции, необходимые реальному продукту

9. Проверьте сценарии ошибок

  1. Загрузите распакованное расширение на странице chrome://extensions.
  2. Откройте ленту, доску, страницу пина и маршрут без пина.
  3. Перейдите между двумя пинами без перезагрузки и убедитесь, что их заметки изолированы.
  4. Откройте один пин в двух вкладках и проверьте обработку конфликта.
  5. Отключите хранилище или имитируйте превышение квоты и проверьте предупреждение.
  6. Экспортируйте тестовую запись, удалите и восстановите её.
  7. Проверьте навигацию с клавиатуры, масштабирование, подписи для программы экранного доступа и светлое и тёмное оформление страницы.

Инженерный вывод

Добавить текстовое поле несложно. Надёжность расширения Pinterest зависит от стабильного идентификатора, устойчивой обработки жизненного цикла, доступного интерфейса, ограниченных разрешений, документированного хранения и проверенного способа экспортировать и восстановить данные. Создайте эту основу, прежде чем расширять список функций.

Продолжайте совершенствовать работу с Pinterest

Выберите руководство для следующего шага:

Salo

Об авторе

Salo работает продуктовым дизайнером и хорошо знает Pinterest. Он пишет о цифровой организации, рабочих процессах и инструментах для более ясного мышления.

Попробуйте расширение Notestopin для Chrome

Добавляйте к пинам личные заметки и теги, чтобы потом быстро находить всё сохранённое.

Добавить в Chrome