fix(web): notification media preview and theme switching
Some checks failed
CI / test (push) Failing after 19s

- show media labels instead of raw URLs in browser notifications

- support notification icon preview for image messages

- implement effective light/dark/system theme application

- apply appearance prefs on app startup
This commit is contained in:
2026-03-08 11:07:30 +03:00
parent 663df37d92
commit 0e44988634
4 changed files with 109 additions and 8 deletions

View File

@@ -5,6 +5,7 @@ import { AuthPage } from "../pages/AuthPage";
import { ChatsPage } from "../pages/ChatsPage";
import { useAuthStore } from "../store/authStore";
import { useChatStore } from "../store/chatStore";
import { applyAppearancePreferences, getAppPreferences } from "../utils/preferences";
const PENDING_INVITE_TOKEN_KEY = "bm_pending_invite_token";
@@ -18,6 +19,10 @@ export function App() {
const setActiveChatId = useChatStore((s) => s.setActiveChatId);
const [joiningInvite, setJoiningInvite] = useState(false);
useEffect(() => {
applyAppearancePreferences(getAppPreferences());
}, []);
useEffect(() => {
if (!accessToken) {
return;

View File

@@ -255,9 +255,10 @@ function maybeShowBrowserNotification(chatId: number, message: Message, activeCh
return;
}
const title = chat?.display_title || chat?.title || "New message";
const body = prefs.messagePreview ? (message.text?.trim() || messagePreviewByType(message.type)) : "New message";
const preview = buildNotificationPreview(message, prefs.messagePreview);
const notification = new Notification(title, {
body,
body: preview.body,
icon: preview.image,
tag: `chat-${chatId}`,
});
notification.onclick = () => {
@@ -278,3 +279,33 @@ function messagePreviewByType(type: Message["type"]): string {
if (type === "circle_video") return "Video message";
return "New message";
}
function buildNotificationPreview(
message: Message,
withPreview: boolean
): { body: string; image?: string } {
if (!withPreview) {
return { body: "New message" };
}
if (message.type !== "text") {
if (message.type === "image") {
const imageUrl = typeof message.text === "string" && isLikelyUrl(message.text) ? message.text : undefined;
return { body: "🖼 Photo", image: imageUrl };
}
return { body: messagePreviewByType(message.type) };
}
const text = message.text?.trim();
if (!text) {
return { body: "New message" };
}
return { body: text };
}
function isLikelyUrl(value: string): boolean {
try {
const parsed = new URL(value);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}

View File

@@ -4,6 +4,16 @@
@tailwind components;
@tailwind utilities;
:root {
--bm-font-size: 16px;
--bm-bg-primary: #101e30;
--bm-bg-secondary: #162233;
--bm-bg-tertiary: #19283a;
--bm-text-color: #e5edf9;
--bm-panel-bg: rgba(19, 31, 47, 0.9);
--bm-panel-border: rgba(146, 174, 208, 0.14);
}
html,
body,
#root {
@@ -17,8 +27,8 @@ body {
background:
radial-gradient(circle at 22% 18%, rgba(36, 68, 117, 0.35), transparent 26%),
radial-gradient(circle at 82% 12%, rgba(24, 95, 102, 0.25), transparent 30%),
linear-gradient(180deg, #101e30 0%, #162233 55%, #19283a 100%);
color: #e5edf9;
linear-gradient(180deg, var(--bm-bg-primary) 0%, var(--bm-bg-secondary) 55%, var(--bm-bg-tertiary) 100%);
color: var(--bm-text-color);
}
* {
@@ -26,8 +36,8 @@ body {
}
.tg-panel {
background: rgba(19, 31, 47, 0.9);
border: 1px solid rgba(146, 174, 208, 0.14);
background: var(--bm-panel-bg);
border: 1px solid var(--bm-panel-border);
backdrop-filter: blur(8px);
}
@@ -46,3 +56,47 @@ body {
background: rgba(126, 159, 201, 0.35);
border-radius: 999px;
}
html[data-theme="light"] {
--bm-bg-primary: #eef3fb;
--bm-bg-secondary: #f5f8fd;
--bm-bg-tertiary: #ffffff;
--bm-text-color: #0f172a;
--bm-panel-bg: rgba(255, 255, 255, 0.93);
--bm-panel-border: rgba(15, 23, 42, 0.12);
}
html[data-theme="light"] .tg-chat-wallpaper {
background:
radial-gradient(circle at 14% 18%, rgba(59, 130, 246, 0.08), transparent 30%),
radial-gradient(circle at 86% 74%, rgba(34, 197, 94, 0.06), transparent 33%),
linear-gradient(160deg, rgba(15, 23, 42, 0.01) 0%, rgba(15, 23, 42, 0.02) 100%);
}
html[data-theme="light"] .bg-slate-900\/95,
html[data-theme="light"] .bg-slate-900\/90,
html[data-theme="light"] .bg-slate-900\/80,
html[data-theme="light"] .bg-slate-900\/70,
html[data-theme="light"] .bg-slate-900\/60,
html[data-theme="light"] .bg-slate-900,
html[data-theme="light"] .bg-slate-800\/80,
html[data-theme="light"] .bg-slate-800\/70,
html[data-theme="light"] .bg-slate-800\/60,
html[data-theme="light"] .bg-slate-800 {
background-color: rgba(255, 255, 255, 0.92) !important;
}
html[data-theme="light"] .border-slate-700\/80,
html[data-theme="light"] .border-slate-700\/70,
html[data-theme="light"] .border-slate-700\/60,
html[data-theme="light"] .border-slate-700\/50,
html[data-theme="light"] .border-slate-700 {
border-color: rgba(15, 23, 42, 0.14) !important;
}
html[data-theme="light"] .text-slate-100,
html[data-theme="light"] .text-slate-200,
html[data-theme="light"] .text-slate-300,
html[data-theme="light"] .text-slate-400 {
color: #334155 !important;
}

View File

@@ -73,8 +73,20 @@ export function applyAppearancePreferences(prefs: AppPreferences): void {
if (typeof document === "undefined") {
return;
}
const resolvedTheme = resolveTheme(prefs.theme);
document.documentElement.style.setProperty("--bm-font-size", `${prefs.messageFontSize}px`);
document.documentElement.setAttribute("data-theme", prefs.theme);
document.documentElement.setAttribute("data-theme", resolvedTheme);
document.documentElement.setAttribute("data-theme-mode", prefs.theme);
}
function resolveTheme(theme: ThemeMode): "light" | "dark" {
if (theme === "light" || theme === "dark") {
return theme;
}
if (typeof window === "undefined") {
return "dark";
}
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function normalizeFontSize(value: number | undefined): number {
@@ -84,4 +96,3 @@ function normalizeFontSize(value: number | undefined): number {
}
return Math.max(12, Math.min(24, Math.round(input)));
}