Web3-интерфейс: карточка публикации через DOM
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 8 из 30.
Теперь превратим снимок реестра в карточку курса 17. Для этого не нужен тяжёлый интерфейсный фреймворк: небольшой DOM-модуль позволяет увидеть, какие данные являются текстом, какие — состоянием, а какие — пользовательским действием. Результат — карточка, которая корректно различает отсутствие записи, активный выпуск и деактивацию.
Предполагаются нормализованный снимок и проверенный отпечаток из предыдущих уроков. Карточка пока не отправляет транзакции. Все примеры относятся к локальному проекту, выполнение браузерного приложения не проводилось. Начнём с собственной разметки; хедер и футер ProfessorWeb не копируются в учебный пример и не становятся частью контракта.
Минимальная разметка
Файл index.html содержит контейнер данных и отдельную область сообщений. Идентификаторы удобны для небольшого упражнения. В приложении с несколькими карточками тот же модуль может принимать конкретный контейнер вместо глобального поиска по документу.
<section aria-labelledby="course-title">
<h2 id="course-title">Курс 17</h2>
<p id="course-state">Данные ещё не прочитаны.</p>
<dl>
<dt>Отпечаток выпуска</dt><dd id="course-digest">—</dd>
<dt>Блок наблюдения</dt><dd id="course-block">—</dd>
</dl>
<button id="refresh" type="button">Обновить сведения</button>
<p id="course-message" role="status" aria-live="polite"></p>
</section>
dl связывает названия характеристик с их значениями. Кнопка является настоящим элементом button, а не ссылкой с обработчиком клика: обновление состояния не открывает другой документ. role="status" позволяет сообщить об окончании чтения вспомогательным технологиям без постоянного перемещения фокуса.
Не следует вставлять адрес или отпечаток только в атрибут title. Такое содержание трудно использовать на сенсорном устройстве и не всегда доступно с клавиатуры. Полное значение остаётся текстом в карточке, а сокращённое представление при необходимости дополняется явным действием копирования.
Безопасное отображение
Создадим render-course.js. Функция не делает запросов и не знает кошелёк; она только переводит уже определённое состояние в DOM. Это делает её ответственность короткой и понятной.
const ZERO_HASH = '0x' + '00'.repeat(32);
export function renderCourse(root, snapshot) {
const missing = snapshot.contentHash.toLowerCase() === ZERO_HASH;
root.querySelector('#course-title').textContent =
`Курс ${snapshot.courseId.toString()}`;
root.querySelector('#course-state').textContent = missing
? 'Курс ещё не опубликован.'
: snapshot.active ? 'Выпуск активен.' : 'Выпуск деактивирован.';
root.querySelector('#course-digest').textContent = snapshot.contentHash;
root.querySelector('#course-block').textContent = snapshot.blockNumber.toString();
}
Используем textContent, поэтому полученная строка не исполняется как HTML. Отпечаток обычно состоит из безопасных символов, но это не причина вводить другой способ вставки для остальных полей. Когда позже появится заголовок из манифеста, он тоже остаётся текстом. Проще заранее выбрать безопасную границу, чем исправлять разрозненные случаи после добавления внешнего содержания.
Проверка нулевого отпечатка относится к базовому контракту, который не принимает ноль в publish. Она не предполагает, что active = false всегда значит «не опубликован». После деактивации сохраняется ненулевой отпечаток. Именно это различие карточка выражает человеческим текстом.
Происхождение показанного значения
На странице виден номер блока. В дополнительной области можно показать сеть и адрес экземпляра, особенно перед подписью изменения. Однако не нужно заполнять главный экран всеми внутренними параметрами библиотеки. Основная задача читателя — понять, какой курс существует и какой выпуск предлагается; подробности происхождения доступны по понятному действию.
Копирование отпечатка должно копировать полное значение, даже если визуальное представление сокращено. Текст вида 0x12…89 удобен для быстрого узнавания, но не является пригодным входом проверки. Если интерфейс случайно сохранит сокращённое значение в форму публикации, ABI-кодирование обнаружит неверную длину; лучше не создавать такую ошибку вовсе.
Статус «активен» нельзя окрашивать единственным способом, без текстовой подписи. Цвет полезен как дополнительный акцент. Пользователь, который не различает цвета или слушает страницу, получает ту же информацию из слов. Деактивированная запись не должна исчезать без объяснения: сохранённый отпечаток важен для понимания истории выпуска.
Рендеринг и введённый текст
Позже рядом с карточкой появится форма публикации. Повторное чтение не должно сбрасывать её поля. Разделим область наблюдения и область намерения: renderCourse изменяет только карточку. Пользователь может сравнить новый сетевой выпуск со своим черновиком и решить, следует ли продолжать редактирование.
Если весь контейнер пересоздаётся через innerHTML, введённый текст, фокус и обработчики могут исчезнуть. Эта техника иногда удобна для статической разметки, но при интерактивной форме создаёт нежелательный жизненный цикл. В нашем модуле изменяются конкретные текстовые узлы; кнопка и её обработчик остаются прежними.
Состояние загрузки также не требует удаления карточки. Можно оставить последний подтверждённый снимок и сообщить, что запрошено обновление. Главное — не представлять старое значение как только что полученное. Подпись времени наблюдения и видимый статус ожидания помогут сохранить полезное содержание при медленном RPC.
Недоверенный манифест
Представим, что заголовок курса в манифесте содержит строку <script>…</script>. Вывод через textContent покажет её как текст, а не выполнит. Это ожидаемое свойство DOM-модуля. Проверка хеша документа не меняет правила: хеш может совпадать с действительно опубликованной ошибочной или вредоносной строкой.
Маршруты загрузки обрабатываются отдельно. Если манифест задаёт URL, приложение проверяет допустимый протокол и происхождение по собственной политике. Нельзя объявлять любую строку из проверенного файла доверенной ссылкой. Для открывающихся внешних документов следует также продумать поведение новой вкладки и понятную подпись назначения.
Для упражнения набросайте карточки трёх состояний и добавьте длинный отпечаток, который не помещается в узкий экран. Ожидается перенос или горизонтально ограниченная область, доступность полного значения и отсутствие поломки страницы. Проверку интерфейса в браузере здесь не выполняли; это критерии будущего ручного осмотра. Безопасная вставка текста описана в Node.textContent.
Компонент копирования
Кнопка рядом с отпечатком получает значение из нормализованного снимка, а не извлекает сокращённую строку из DOM. Такой маршрут избегает случайной зависимости от визуального оформления. При изменении вида карточки программное значение остаётся тем же. Подтверждение копирования появляется в области статуса и не заменяет сам отпечаток временным словом «Скопировано».
Если Clipboard API недоступен, карточка сохраняет возможность выделить полный текст. Не нужно прятать значение только ради красивого короткого блока. Также не следует автоматически копировать данные при каждом обновлении: clipboard принадлежит действиям человека, а не фоновому рендерингу.
Для узкой карточки длинная строка может переноситься по границам символов. При этом сохраняется её точный текст: браузерная визуальная разбивка не добавляется к аргументу или сохраняемому значению. Если используется прокручиваемая область, она не должна захватывать всю ширину страницы и скрывать кнопки навигации.