Files
bare/web/js/ui/shell.js
T
mayatnikovandClaude Opus 5 f9ac83bef8 Развести номера ADR: 064/065/066 этой ветки → 073/074/075
Ветка отведена от 522ba89, где последним был ADR-063, и заняла
064–072. Тем временем в main через #3 и #4 пришли свои 064
(строка ввода — div вместо form), 065 (стороны сообщений)
и 066 (серверного перца нет).

Переезжают три ADR этой ветки, 067–072 остаются на месте:
их номера свободны, а внутренние ссылки менять незачем.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QR8uS3zGkybzWRW4GEY1oz
2026-08-24 11:23:12 +03:00

98 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Каркас: сайдбар со списком чатов и место под экран — docs/ui.md, «Каркас»
// и «Список чатов».
import * as pwa from "../pwa.js";
import { INSTALL_IOS, clear, el, mark } from "./dom.js";
import { mount } from "./chats.js";
// Время коммита — в местной зоне и коротко: день с месяцем и часы
// с минутами (ADR-067). Год не показывается: версия отвечает на вопрос
// «что сейчас работает», а не ведёт летопись.
const BUILD_DAY = new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "2-digit" });
const BUILD_TIME = new Intl.DateTimeFormat("ru-RU", { hour: "2-digit", minute: "2-digit" });
// frame отдаёт корень, место под экран и отписку списка чатов.
// screen — что показывать на мобильном, где виден один экран за раз:
// "list" или "screen". active — чат, который сейчас открыт.
export function frame(ctx, screen, active = null) {
const root = el("div", "shell");
root.dataset.screen = screen;
const main = el("main", "main");
const { nav, dispose } = side(ctx, active);
root.append(nav, main);
return { root, main, dispose };
}
function side(ctx, active) {
const nav = el("nav", "side");
const brand = el("div", "brand");
brand.append(mark(), el("span", null, "bare"));
nav.append(brand);
// Баннер установки — над списком чатов (docs/ui.md, «Баннер установки»).
// Место под него занимается сразу, содержимое приезжает из meta.
const place = el("div", "banner-slot");
nav.append(place);
banner(place);
const list = el("div", "list");
const add = el("button", "item item--new", "+ новый чат");
add.type = "button";
add.addEventListener("click", () => ctx.go("#/new"));
const items = el("div");
list.append(add, items);
nav.append(list);
const foot = el("div", "foot");
const me = el("button", "me");
me.type = "button";
const dot = el("i");
dot.setAttribute("aria-hidden", "true");
me.append(dot, el("span", "me__nick", `ты: @${ctx.me.nick}`));
me.addEventListener("click", () => ctx.go("#/settings"));
foot.append(me);
const version = build(ctx.config);
if (version !== null) {
foot.append(el("span", "build", version));
}
nav.append(foot);
return { nav, dispose: mount(items, ctx, active) };
}
// build — версия и время коммита из GET /api/config (ADR-074, ADR-067).
// Конфигурации нет — офлайн-старт до первого ответа сервера — значит,
// и строки нет: выдумывать версию не из чего. Время без версии не бывает:
// её сервер отдаёт всегда, хотя бы как «unknown».
function build(config) {
const version = config?.version;
if (typeof version !== "string" || version === "") {
return null;
}
const at = config?.commitAt;
if (!Number.isFinite(at) || at <= 0) {
return version;
}
return `${version} · ${BUILD_DAY.format(at)} ${BUILD_TIME.format(at)}`;
}
// banner — баннер установки на iOS: пуши там работают только
// у установленного приложения (ADR-011). Крестик закрывает его насовсем.
async function banner(place) {
if (!pwa.iosBrowser() || await pwa.bannerHidden()) {
return;
}
const box = el("div", "banner");
box.append(el("p", "banner__text", INSTALL_IOS));
const close = el("button", "banner__close", "×");
close.type = "button";
close.setAttribute("aria-label", "закрыть");
close.addEventListener("click", () => {
clear(place);
pwa.hideBanner();
});
box.append(close);
place.append(box);
}