fix(web): notification media preview and theme switching
Some checks failed
CI / test (push) Failing after 19s
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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user