Compare commits
2 Commits
1e833ec7f2
...
8b6578da3c
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b6578da3c | |||
| 851d6ae759 |
@@ -332,12 +332,15 @@ export function setupSystemHandlers(): void {
|
|||||||
const title = typeof payload?.title === 'string' ? payload.title.trim() : '';
|
const title = typeof payload?.title === 'string' ? payload.title.trim() : '';
|
||||||
const body = typeof payload?.body === 'string' ? payload.body : '';
|
const body = typeof payload?.body === 'string' ? payload.body : '';
|
||||||
const mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
|
const suppressSystemNotification = mainWindow?.isVisible() === true
|
||||||
|
&& !mainWindow.isMinimized()
|
||||||
|
&& mainWindow.isMaximized();
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Notification.isSupported()) {
|
if (!suppressSystemNotification && Notification.isSupported()) {
|
||||||
try {
|
try {
|
||||||
const notification = new Notification({
|
const notification = new Notification({
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ Additional runtime guards:
|
|||||||
The Electron main process handles the actual desktop notification and window-attention behavior.
|
The Electron main process handles the actual desktop notification and window-attention behavior.
|
||||||
|
|
||||||
- `show-desktop-notification` creates a system `Notification` with the window icon when supported.
|
- `show-desktop-notification` creates a system `Notification` with the window icon when supported.
|
||||||
|
- `show-desktop-notification` skips the OS toast while the main window is visible and maximized.
|
||||||
- Notification clicks restore, show, and focus the main window.
|
- Notification clicks restore, show, and focus the main window.
|
||||||
- `request-window-attention` flashes the taskbar entry directly when Electron is minimized or otherwise backgrounded and actionable unread exists.
|
- `request-window-attention` flashes the taskbar entry directly when Electron is minimized or otherwise backgrounded and actionable unread exists.
|
||||||
- `show-desktop-notification` can still request attention for live toast delivery.
|
- `show-desktop-notification` can still request attention for live toast delivery.
|
||||||
@@ -210,7 +211,7 @@ The Electron main process handles the actual desktop notification and window-att
|
|||||||
|
|
||||||
### Platform-specific policy
|
### Platform-specific policy
|
||||||
|
|
||||||
- Windows: the facade also plays `AppSound.Notification` before showing the desktop notification.
|
- Windows: the facade only plays `AppSound.Notification` when the app is not the active selected window.
|
||||||
- Linux: desktop alerts are expected to surface through the system notification center, with window attention requested when the app is backgrounded.
|
- Linux: desktop alerts are expected to surface through the system notification center, with window attention requested when the app is backgrounded.
|
||||||
- macOS and browser-only builds use the same desktop notification adapter, but there is no extra renderer-side sound policy in this domain.
|
- macOS and browser-only builds use the same desktop notification adapter, but there is no extra renderer-side sound policy in this domain.
|
||||||
|
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ export class NotificationsFacade {
|
|||||||
!context.isWindowFocused || !context.isDocumentVisible
|
!context.isWindowFocused || !context.isDocumentVisible
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.platformKind === 'windows') {
|
if (this.shouldPlayNotificationSound()) {
|
||||||
this.audio.play(AppSound.Notification);
|
this.audio.play(AppSound.Notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +349,14 @@ export class NotificationsFacade {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldPlayNotificationSound(): boolean {
|
||||||
|
return this.platformKind === 'windows' && !this.isWindowActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private isWindowActive(): boolean {
|
||||||
|
return this._windowFocused() && this._documentVisible() && !this._windowMinimized();
|
||||||
|
}
|
||||||
|
|
||||||
private ensureRoomTracking(roomId: string, channelId: string, baselineTimestamp: number): void {
|
private ensureRoomTracking(roomId: string, channelId: string, baselineTimestamp: number): void {
|
||||||
const settings = this._settings();
|
const settings = this._settings();
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<h4 class="text-base font-semibold text-foreground">Delivery</h4>
|
<h4 class="text-base font-semibold text-foreground">Delivery</h4>
|
||||||
<p class="mt-1 text-sm text-muted-foreground">
|
<p class="mt-1 text-sm text-muted-foreground">
|
||||||
Desktop alerts use the system notification center on Linux and request taskbar attention when the app is not focused. Windows also plays the
|
Desktop alerts use the system notification center on Linux and request taskbar attention when the app is not focused. Maximized app window
|
||||||
configured notification sound.
|
suppress system popups, and only play the configured notification sound while the app is in the background.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -139,7 +139,12 @@ export class InviteComponent implements OnInit {
|
|||||||
RoomsActions.joinRoom({
|
RoomsActions.joinRoom({
|
||||||
roomId: joinResponse.server.id,
|
roomId: joinResponse.server.id,
|
||||||
serverInfo: {
|
serverInfo: {
|
||||||
|
...invite.server,
|
||||||
...joinResponse.server,
|
...joinResponse.server,
|
||||||
|
channels:
|
||||||
|
Array.isArray(joinResponse.server.channels) && joinResponse.server.channels.length > 0
|
||||||
|
? joinResponse.server.channels
|
||||||
|
: invite.server.channels,
|
||||||
sourceId: context.endpoint.id,
|
sourceId: context.endpoint.id,
|
||||||
sourceName: context.endpoint.name,
|
sourceName: context.endpoint.name,
|
||||||
sourceUrl: context.sourceUrl
|
sourceUrl: context.sourceUrl
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ export class ServerSearchComponent implements OnInit {
|
|||||||
maxUsers: room.maxUsers ?? 50,
|
maxUsers: room.maxUsers ?? 50,
|
||||||
hasPassword: typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password,
|
hasPassword: typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password,
|
||||||
isPrivate: room.isPrivate,
|
isPrivate: room.isPrivate,
|
||||||
|
channels: room.channels,
|
||||||
createdAt: room.createdAt,
|
createdAt: room.createdAt,
|
||||||
ownerId: room.hostId,
|
ownerId: room.hostId,
|
||||||
sourceId: room.sourceId,
|
sourceId: room.sourceId,
|
||||||
@@ -272,7 +273,14 @@ export class ServerSearchComponent implements OnInit {
|
|||||||
sourceId: server.sourceId,
|
sourceId: server.sourceId,
|
||||||
sourceUrl: server.sourceUrl
|
sourceUrl: server.sourceUrl
|
||||||
}));
|
}));
|
||||||
const resolvedServer = response.server ?? server;
|
const resolvedServer = {
|
||||||
|
...server,
|
||||||
|
...response.server,
|
||||||
|
channels:
|
||||||
|
Array.isArray(response.server.channels) && response.server.channels.length > 0
|
||||||
|
? response.server.channels
|
||||||
|
: server.channels
|
||||||
|
};
|
||||||
|
|
||||||
this.closePasswordDialog();
|
this.closePasswordDialog();
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
|
|||||||
@@ -311,7 +311,11 @@ export class ServersRailComponent {
|
|||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
serverInfo: {
|
serverInfo: {
|
||||||
...this.toServerInfo(room),
|
...this.toServerInfo(room),
|
||||||
...response.server
|
...response.server,
|
||||||
|
channels:
|
||||||
|
Array.isArray(response.server.channels) && response.server.channels.length > 0
|
||||||
|
? response.server.channels
|
||||||
|
: room.channels
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -370,6 +374,7 @@ export class ServersRailComponent {
|
|||||||
isPrivate: room.isPrivate,
|
isPrivate: room.isPrivate,
|
||||||
createdAt: room.createdAt,
|
createdAt: room.createdAt,
|
||||||
ownerId: room.hostId,
|
ownerId: room.hostId,
|
||||||
|
channels: room.channels,
|
||||||
sourceId: room.sourceId,
|
sourceId: room.sourceId,
|
||||||
sourceName: room.sourceName,
|
sourceName: room.sourceName,
|
||||||
sourceUrl: room.sourceUrl
|
sourceUrl: room.sourceUrl
|
||||||
|
|||||||
@@ -117,6 +117,26 @@ function resolveUserDisplayName(user: Pick<User, 'displayName' | 'username'> | n
|
|||||||
return user?.username?.trim() || 'User';
|
return user?.username?.trim() || 'User';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPersistedChannels(channels: Room['channels'] | undefined): channels is NonNullable<Room['channels']> {
|
||||||
|
return Array.isArray(channels) && channels.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Keep cached channels until directory metadata provides a concrete replacement. */
|
||||||
|
function resolveRoomChannels(
|
||||||
|
cachedChannels: Room['channels'] | undefined,
|
||||||
|
incomingChannels: Room['channels'] | undefined
|
||||||
|
): Room['channels'] | undefined {
|
||||||
|
if (hasPersistedChannels(incomingChannels)) {
|
||||||
|
return incomingChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPersistedChannels(cachedChannels)) {
|
||||||
|
return cachedChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
interface RoomPresenceSignalingMessage {
|
interface RoomPresenceSignalingMessage {
|
||||||
type: string;
|
type: string;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
@@ -302,7 +322,7 @@ export class RoomsEffects {
|
|||||||
const resolvedRoom: Room = {
|
const resolvedRoom: Room = {
|
||||||
...room,
|
...room,
|
||||||
isPrivate: typeof serverInfo?.isPrivate === 'boolean' ? serverInfo.isPrivate : room.isPrivate,
|
isPrivate: typeof serverInfo?.isPrivate === 'boolean' ? serverInfo.isPrivate : room.isPrivate,
|
||||||
channels: Array.isArray(serverInfo?.channels) ? serverInfo.channels : room.channels,
|
channels: resolveRoomChannels(room.channels, serverInfo?.channels),
|
||||||
sourceId: serverInfo?.sourceId ?? room.sourceId,
|
sourceId: serverInfo?.sourceId ?? room.sourceId,
|
||||||
sourceName: serverInfo?.sourceName ?? room.sourceName,
|
sourceName: serverInfo?.sourceName ?? room.sourceName,
|
||||||
sourceUrl: serverInfo?.sourceUrl ?? room.sourceUrl,
|
sourceUrl: serverInfo?.sourceUrl ?? room.sourceUrl,
|
||||||
@@ -336,7 +356,7 @@ export class RoomsEffects {
|
|||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
userCount: 1,
|
userCount: 1,
|
||||||
maxUsers: 50,
|
maxUsers: 50,
|
||||||
channels: Array.isArray(serverInfo.channels) ? serverInfo.channels : undefined,
|
channels: resolveRoomChannels(undefined, serverInfo.channels),
|
||||||
sourceId: serverInfo.sourceId,
|
sourceId: serverInfo.sourceId,
|
||||||
sourceName: serverInfo.sourceName,
|
sourceName: serverInfo.sourceName,
|
||||||
sourceUrl: serverInfo.sourceUrl
|
sourceUrl: serverInfo.sourceUrl
|
||||||
@@ -361,7 +381,7 @@ export class RoomsEffects {
|
|||||||
createdAt: serverData.createdAt || Date.now(),
|
createdAt: serverData.createdAt || Date.now(),
|
||||||
userCount: serverData.userCount,
|
userCount: serverData.userCount,
|
||||||
maxUsers: serverData.maxUsers,
|
maxUsers: serverData.maxUsers,
|
||||||
channels: Array.isArray(serverData.channels) ? serverData.channels : undefined,
|
channels: resolveRoomChannels(undefined, serverData.channels),
|
||||||
sourceId: serverData.sourceId,
|
sourceId: serverData.sourceId,
|
||||||
sourceName: serverData.sourceName,
|
sourceName: serverData.sourceName,
|
||||||
sourceUrl: serverData.sourceUrl
|
sourceUrl: serverData.sourceUrl
|
||||||
@@ -428,7 +448,7 @@ export class RoomsEffects {
|
|||||||
hasPassword: !!serverData.hasPassword,
|
hasPassword: !!serverData.hasPassword,
|
||||||
isPrivate: serverData.isPrivate,
|
isPrivate: serverData.isPrivate,
|
||||||
maxUsers: serverData.maxUsers,
|
maxUsers: serverData.maxUsers,
|
||||||
channels: Array.isArray(serverData.channels) ? serverData.channels : room.channels,
|
channels: resolveRoomChannels(room.channels, serverData.channels),
|
||||||
sourceId: serverData.sourceId ?? room.sourceId,
|
sourceId: serverData.sourceId ?? room.sourceId,
|
||||||
sourceName: serverData.sourceName ?? room.sourceName,
|
sourceName: serverData.sourceName ?? room.sourceName,
|
||||||
sourceUrl: serverData.sourceUrl ?? room.sourceUrl
|
sourceUrl: serverData.sourceUrl ?? room.sourceUrl
|
||||||
|
|||||||
Reference in New Issue
Block a user