Страница настроек и начальный текст заметки
Заметки уже сохраняются в local и перечитываются после закрытия popup. Теперь пользователь хочет начинать новый текст с привычной фразы, например «Проверить пример:». Создадим страницу options и отдельный ключ settings. Результат — настройка сохраняется через тот же проверенный worker, а новый popup получает начальный текст. Прежние записи при этом не переписываются, поскольку настройка относится к будущему вводу, а не к истории заметок.
Options — локальная страница расширения с DOM. Мы открываем её в отдельной вкладке через options_ui.open_in_tab=true. Такое место подходит и для будущего экспорта с импортом: пользователь читает последствия действия, не пытаясь удержать краткий popup открытым. Это другой контекст, поэтому общие переменные popup.js здесь недоступны. Связь со storage по-прежнему проходит через известные сообщения worker, а не через случайную копию оперативного массива.
От кнопки к странице
Manifest получает options_ui.page="options.html". Кнопка настроек в popup вызывает runtime.openOptionsPage. Она не открывает произвольный адрес из заметки и не принимает имя страницы от сайта. Страница пакета известна заранее. Мы возвращаем кнопке видимость только тогда, когда файл действительно введён в снимок. Это делает учебное состояние конкретным: ранний архив не обещал страницу, которой в нём не было, а текущий имеет все согласованные части.
options.html содержит подпись, textarea, кнопку сохранения и статус. Начальный текст ограничен двумя тысячами символов на уровне интерфейса. Как раньше, это помощь вводу, а не окончательная граница данных. Строка может быть пустой: тогда новые заметки начинаются без подсказки. Значение не является шаблоном HTML или выражением JavaScript. Popup положит его в value поля, поэтому символы разметки останутся буквальным текстом, который читатель может изменить.
При открытии options отправляет GET_SETTINGS. Worker читает ключ settings и при его отсутствии возвращает пустой defaultNote. Это полезный договор первого использования: отсутствие пользовательской настройки не превращается в ошибку и не требует заранее заполнять storage. Поле не получает весь объект local. Нормализованный ответ содержит только известные свойства настроек, поэтому интерфейс не зависит от ключей сохранённых заметок и других будущих элементов хранения.
Полные файлы шага
Показанные файлы полностью совпадают с выбранным состоянием архива. Остальные согласованные файлы находятся в examples/browser-extension-lab/lesson-07/.
options.html
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Настройки заметок</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Настройки заметок</h1>
<label for="default">Начальный текст новой заметки</label>
<textarea id="default" maxlength="2000"></textarea>
<button id="apply" type="button">Сохранить настройки</button>
<p id="status" role="status" aria-live="polite"></p>
<script type="module" src="options.js"></script>
</body>
</html>
options.js
const status = document.querySelector("#status");
const defaultInput = document.querySelector("#default");
async function request(type, payload) {
const reply = await chrome.runtime.sendMessage({ version: 1, type, payload });
if (!reply?.ok) throw new Error(reply?.error ?? "Нет ответа");
return reply.data;
}
request("GET_SETTINGS").then((settings) => {
defaultInput.value = settings.defaultNote;
}).catch((error) => { status.textContent = error.message; });
document.querySelector("#apply").addEventListener("click", async () => {
try {
await request("SET_SETTINGS", { defaultNote: defaultInput.value,
});
status.textContent = "Настройки сохранены";
} catch (error) { status.textContent = error.message; }
});
Ожидаемый результат
Это ожидаемый договор для самостоятельного исследования; расширение не устанавливалось и не запускалось.
SET_SETTINGS defaultNote="Проверить пример:" → ok=true после set.
Новый пустой popup → начальный текст настройки.
Прежние заметки остаются прежними.
Один путь записи
SET_SETTINGS требует объект с defaultNote строкового типа и допустимой длины. Worker сохраняет новый объект settings и возвращает его после завершения set. Options показывает успех только после ответа. Если API отказал, выводится ошибка и прежнее сохранённое состояние не объявляется изменённым. Это тот же принцип, что у SAVE: нажатие кнопки является намерением, а подтверждённый ответ относится к выполненной записи. Пользовательская строка не становится успешной настройкой только из-за обновления DOM.
Worker последовательно обрабатывает команды через текущую очередь Promise. Popup не записывает settings напрямую, options не переписывает заметки, а content script вообще не получает доступ к local через TRUSTED_CONTEXTS. Такой единый путь упрощает объяснение будущих операций. Мы всё ещё не обещаем транзакционность всех многошаговых действий storage, однако знаем, кто имеет обязанность проверять и сохранять значения. Если позже добавить другой writer, этот договор надо пересмотреть.
Обратите внимание на разделение ключей. settings относится к параметрам интерфейса, а note:
Новое окно, новое применение
Новый popup вызывает GET_SETTINGS и переносит defaultNote в textarea. Сохранённая заметка после этого не меняется. Если пользователь уже начал ввод до завершения чтения, код должен избегать неожиданного перезаписывания его текста; в полном снимке начальное значение применяется только к ещё пустому полю. Такая небольшая граница важна в асинхронном интерфейсе: поздний ответ настройки не должен выглядеть как исчезновение только что написанной заметки.
Настройка не является синхронизацией всех одновременно открытых окон. Уже открытый popup не обязан менять свой черновик при сохранении options в другом контексте. В нашем договоре новая настройка применяется при новом чтении. Для немедленного обновления могли бы использовать storage.onChanged, но тогда нужно решить, что делать с уже редактируемым текстом. Мы не добавляем такое поведение молча, потому что оно меняет ожидание пользователя о контроле над черновиком.
Ожидаемый сценарий: читатель сохраняет «Проверить пример:» в options, закрывает страницу и открывает новый popup. Поле содержит эту строку, список прежних заметок остаётся тем же. После редактирования и SAVE новая запись получает фактический текст поля, а не скрытое значение настройки. Если пользователь очистил textarea, расширение не должно заново подставить фразу в уже отправленное намерение. Это показывает, где заканчивается удобный начальный текст и начинается собственная запись.
Настройка и схема данных
Внутри settings уже предусмотрен storeFullUrl, пока не показанный интерфейсом. Поздняя глава сделает его явным выбором приватности. Он по умолчанию false и не даёт сейчас странице дополнительных прав. Разделение структуры и UI должно быть объяснено: наличие свойства не означает автоматически появившуюся кнопку или изменение всех старых заметок. Текущий самостоятельный результат остаётся defaultNote и передача настроек известным сообщениям.
Полные options.html и options.js на этой странице совпадают с lesson-07. Автор не открывал страницу в установленном расширении и не сохранял значения; результаты ожидаемые. Для самостоятельного разбора проследите GET_SETTINGS и SET_SETTINGS отдельно. Первый читает текущий договор, второй проверяет новое намерение и фиксирует его. Если запись отсутствует, чтение применяет безопасное начальное значение. Если ввод неверен, запись не объявляется успешной. Дальше используем эту постоянную страницу для создания собственного JSON-экспорта без сервера и внешнего аккаунта.
Начальный текст не является обязательной частью каждой заметки. Пользователь может удалить его и написать другую фразу. Поэтому SAVE получает фактическое value поля, а не заново соединяет настройки с текстом после нажатия. Это удерживает контроль у человека и предотвращает двойное добавление шаблонной строки. Если позже появятся настоящие шаблоны с переменными, их применение следует сделать явным действием интерфейса и объяснить правила замены. Нынешняя настройка остаётся простой строкой без скрытого форматирования и выполнения выражений.
Документация и исходники
Механизмы главы сверены с первичными источниками: документация 1, документация 2, документация 3. Полный архив «Заметок читателя» содержит все двенадцать состояний. Установка, запуск и публикация не выполнялись.