Ссылки и состояния без сети
У читателя появилась кнопка сохранения главы. Теперь нужно объяснить, куда ведут остальные ссылки. В автономном режиме обычный учебник быстро превращается в лабиринт: часть переходов работает, часть показывает одинаковую ошибку, а названия не помогают предсказать результат. Исправим этот недостаток через явное описание доступных копий.
Мы продолжаем offline-lab с двумя договорными документами. XML может находиться в обязательной подготовке, Markdown — в наборе выбранных глав. Реестр знает оба материала, но кеш содержит только фактически записанные ответы. Эти две коллекции отвечают на разные вопросы. Реестр помогает найти содержание; кеш помогает понять, какая копия есть на этом устройстве.
Три разных утверждения
Первое утверждение: «Сохранённая копия доступна». Для него достаточно успешного точного поиска в выбранном кеше. Второе: «Материал не сохранён». Оно ничего не сообщает о сервере. Третье: «Сервер ответил404». Его можно сделать лишь по наблюдаемому сетевому ответу, с учётом собственного стенда. Отсутствие сети не является доказательством удаления.
Показатель navigator.onLine можно использовать как подсказку для интерфейса, но значение true не доказывает доступность нужного сервера. Подключение к локальной сети способно существовать без доступа к сайту. Поэтому сообщение «Интернет есть, статья точно откроется» слишком сильное. Проверять результат нужно по самой операции. Navigator.onLine.
Следующий полный вспомогательный модуль assets/availability.js получает реестр и имя кеша. Он не делает сетевой диагностики и возвращает только то, что способен подтвердить локально. Вызывайте его из app.js перед созданием подписей либо после успешного сохранения главы. Новое подключение модуля потребует добавить его в обязательный набор worker с ожидаемым типом javascript.
export async function describeCopies(documents, cacheName) {
const cache = await caches.open(cacheName);
const result = new Map();
for (const item of documents) {
const url = new URL(item.url, location.origin);
if (url.origin !== location.origin || url.search || url.hash) {
throw new Error("Реестр должен содержать точные адреса страниц");
}
const copy = await cache.match(url.href);
result.set(item.article_id, {
url: url.href,
saved: Boolean(copy),
label: copy ? "Есть копия для чтения без сети" :
"Копии на устройстве нет; потребуется получить страницу"
});
}
return result;
}
Ожидаемый результат для начального состояния v2: XML имеет копию, Markdown может её не иметь. После завершённого сохранения Markdown повторный вызов должен показать изменение. При перезагрузке статусы пересчитываются из хранилища. Мы не восстанавливаем надпись «Сохранено» из старого DOM или из памяти закрытой вкладки.
Вспомогательная функция открывает именно кеш выбранной версии. Поиск через все кеши мог бы найти старую главу в выпуске с несовместимыми ресурсами. Пока это простой способ удержать границу. Позднее пакет получит собственный список обязательных файлов, и наличие HTML перестанет быть достаточной проверкой полноты.
Сохраняем поведение ссылки
Обычный элемент a должен оставаться ссылкой на постоянный адрес материала. Он поддерживает открытие в новой вкладке, копирование и навигацию без JavaScript. Не заменяйте все переходы кнопками только ради автономного состояния. Рядом можно показать статус, а для несохранённых результатов предложить понятное действие при возвращении сети.
Необязательно полностью запрещать переход к несохранённой главе. Если сеть доступна, обычная ссылка может успешно открыться. Когда получить страницу не удалось, worker вернёт запасное сообщение503. Оно должно сохранять ясность: нужная копия отсутствует, текущее получение не завершилось, доступны такие-то сохранённые главы. Это отличается от страницы «Материал удалён».
Для списка из двух строк можно показывать подпись всегда. В большой выдаче удобнее фильтр «Только сохранённые» и короткая заметка возле остальных результатов. Но фильтр не должен менять смысл общего количества найденных статей: он сокращает отображение по доступности копии, а не утверждает, что несохранённых документов нет в библиотеке. При снятии фильтра прежние результаты возвращаются с теми же адресами и названиями.
Фокус после ошибки не должен исчезать в пустом контейнере. Запасной HTML содержит заголовок, краткое объяснение и ссылку на главную. Главная работает без сети только после собственной успешной подготовки, поэтому её готовность входит в условия приложения. В первом коротком worker четвёртого урока такая возможность ещё не обещалась.
Для элементов управления используйте конкретные надписи. «Сохранить Markdown для чтения без сети» сообщает действие точнее, чем значок молнии без текста. Сообщение рядом с кнопкой через role=status позволяет объявить завершение без принудительного перемещения фокуса. Доступность интерфейса важна и для автономного режима: потеря сети не отменяет обычную клавиатурную навигацию.
Параметры и якоря
Якорь вроде #query-example обозначает место внутри HTML-документа. При запросе страницы fragment не отправляется как часть сетевого URL, но нужен браузеру для перехода к разделу. В реестре хранится адрес самой главы; в ссылке можно оставить устойчивый якорь. Не нужно сохранять отдельную копию для каждого заголовка.
Query string устроена иначе. Адрес с ?print=1 может выдавать печатную версию, а другой параметр — изменить язык. Точное сопоставление Cache API не обязано возвращать обычную страницу для такого запроса. Настройка ignoreSearch существует, но глобальное включение отменило бы различие всех параметров. Cache.match.
Если редакция решит нормализовать только известные UTM-метки, это отдельное правило для разрешённых документов. Сначала убедитесь, что серверное содержание действительно одинаково. Затем преобразуйте только согласованный набор параметров и сохраните остальные. Наш модуль пока требует чистые адреса реестра и не выдаёт универсальное преобразование за готовую функцию.
Готовый поиск и локальные копии
Курс поиска уже научил получать результаты по библиотеке. Здесь не строим новый индекс и не меняем ранжирование. Добавим к результату статус копии по article_id. Тогда результат «Запросы LINQ to XML» останется известным материалом даже в тот момент, когда его HTML ещё не сохранён на устройстве.
Сетевой поиск способен стать недоступным без подключения. Это не повод показывать «Ничего не найдено», поскольку запрос мог вообще не выполниться. Сообщение должно различать пустую выдачу и невозможность обратиться к сервису. Если готовый компонент имеет собственный локальный индекс, его наличие и совместимость проверяются отдельно от кеша глав.
Получается простая модель: содержание, доступность копии и результат получения независимы. Читатель видит нормальные ссылки и понятные состояния, а программа не подменяет удаление сетевой ошибкой. В следующем уроке сохраним эти правила при появлении нового выпуска приложения.