Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86b77ac9f3 | ||
|
|
906a034e26 | ||
|
|
b855eb505d | ||
|
|
cc8e7444f6 | ||
|
|
d7ea408fd0 | ||
|
|
d3f01b5116 | ||
|
|
f84c0d6e9e | ||
|
|
1c62fb4722 | ||
|
|
a6c7333ea3 | ||
|
|
f0b7400d1b | ||
|
|
a86947be48 | ||
|
|
b6f9af3f58 |
Generated
+4
-4
@@ -81,7 +81,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@axe-core/playwright": "4.13.0",
|
"@axe-core/playwright": "4.13.0",
|
||||||
"@lotusguild/element-call-embedded": "0.25.0-lotus.22",
|
"@lotusguild/element-call-embedded": "0.25.0-lotus.23",
|
||||||
"@playwright/test": "1.63.0",
|
"@playwright/test": "1.63.0",
|
||||||
"@rollup/plugin-inject": "5.0.5",
|
"@rollup/plugin-inject": "5.0.5",
|
||||||
"@rollup/plugin-wasm": "6.2.2",
|
"@rollup/plugin-wasm": "6.2.2",
|
||||||
@@ -2695,9 +2695,9 @@
|
|||||||
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
|
||||||
},
|
},
|
||||||
"node_modules/@lotusguild/element-call-embedded": {
|
"node_modules/@lotusguild/element-call-embedded": {
|
||||||
"version": "0.25.0-lotus.22",
|
"version": "0.25.0-lotus.23",
|
||||||
"resolved": "https://code.lotusguild.org/api/packages/LotusGuild/npm/%40lotusguild%2Felement-call-embedded/-/0.25.0-lotus.22/element-call-embedded-0.25.0-lotus.22.tgz",
|
"resolved": "https://code.lotusguild.org/api/packages/LotusGuild/npm/%40lotusguild%2Felement-call-embedded/-/0.25.0-lotus.23/element-call-embedded-0.25.0-lotus.23.tgz",
|
||||||
"integrity": "sha512-AimDKZA4xUKEEyl2XUFUSUDl03tBKFv88T/3igKyH2VqNSPMGnqcQJC5HXIkHLTWrOmg8kLcJ//FFbqlUZiLcA==",
|
"integrity": "sha512-vRmcWeJcU0Accyl2x3mqNEzrIe8djE5BekyYbYOSCsMflC98rZgD1mO7KOe7bA1cX7gparRf6sAt1fotHoU+bA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@matrix-org/matrix-sdk-crypto-wasm": {
|
"node_modules/@matrix-org/matrix-sdk-crypto-wasm": {
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@axe-core/playwright": "4.13.0",
|
"@axe-core/playwright": "4.13.0",
|
||||||
"@lotusguild/element-call-embedded": "0.25.0-lotus.22",
|
"@lotusguild/element-call-embedded": "0.25.0-lotus.23",
|
||||||
"@playwright/test": "1.63.0",
|
"@playwright/test": "1.63.0",
|
||||||
"@rollup/plugin-inject": "5.0.5",
|
"@rollup/plugin-inject": "5.0.5",
|
||||||
"@rollup/plugin-wasm": "6.2.2",
|
"@rollup/plugin-wasm": "6.2.2",
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ export function AccountDataEditor({
|
|||||||
</Chip>
|
</Chip>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No">
|
<Box shrink="No">
|
||||||
<IconButton onClick={requestClose} variant="Surface" aria-label="Close">
|
<IconButton type="button" onClick={requestClose} variant="Surface" aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
|
|||||||
Setup Device Verification
|
Setup Device Verification
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
|
<IconButton type="button" size="300" radii="300" onClick={onCancel} aria-label="Cancel">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
@@ -361,7 +361,7 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
|
|||||||
Reset Device Verification
|
Reset Device Verification
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
|
<IconButton type="button" size="300" radii="300" onClick={onCancel} aria-label="Cancel">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -139,8 +139,11 @@ export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promis
|
|||||||
data.allowFederation,
|
data.allowFederation,
|
||||||
data.additionalCreators,
|
data.additionalCreators,
|
||||||
),
|
),
|
||||||
power_level_content_override:
|
// [Gitea #328] Every non-space room gets call.member at user level, not just
|
||||||
data.type === RoomType.Call ? createVoiceRoomPowerLevelsOverride() : undefined,
|
// voice rooms: the call button is shown in private group rooms too, and with
|
||||||
|
// Synapse's state_default (50) only admins could start or join a call there
|
||||||
|
// ("You don't have permission to join!").
|
||||||
|
power_level_content_override: createVoiceRoomPowerLevelsOverride(),
|
||||||
initial_state: initialState,
|
initial_state: initialState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ export function StateEventEditor({ type, stateKey, requestClose }: StateEventEdi
|
|||||||
</Chip>
|
</Chip>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No">
|
<Box shrink="No">
|
||||||
<IconButton onClick={requestClose} variant="Surface" aria-label="Close">
|
<IconButton type="button" onClick={requestClose} variant="Surface" aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export function PollCreator({ roomId, threadRootId, onClose }: PollCreatorProps)
|
|||||||
Create Poll
|
Create Poll
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" radii="300" onClick={onClose} aria-label="Close">
|
<IconButton type="button" size="300" radii="300" onClick={onClose} aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export function ReportRoomModal({ roomId, onClose }: ReportRoomModalProps) {
|
|||||||
Report Room
|
Report Room
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
|
<IconButton type="button" size="300" onClick={onClose} radii="300" aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export function ReportUserModal({ userId, onClose }: ReportUserModalProps) {
|
|||||||
Report User
|
Report User
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" onClick={onClose} radii="300" aria-label="Close">
|
<IconButton type="button" size="300" onClick={onClose} radii="300" aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ export function ScheduleMessageModal({
|
|||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" radii="300" onClick={onClose} aria-label="Close">
|
<IconButton type="button" size="300" radii="300" onClick={onClose} aria-label="Close">
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -725,7 +725,13 @@ export const MessageDeleteItem = as<
|
|||||||
Delete Message
|
Delete Message
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
size="300"
|
||||||
|
onClick={handleClose}
|
||||||
|
radii="300"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
@@ -865,7 +871,13 @@ export const MessageReportItem = as<
|
|||||||
Report Message
|
Report Message
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
size="300"
|
||||||
|
onClick={handleClose}
|
||||||
|
radii="300"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
<Icon src={Icons.Cross} />
|
<Icon src={Icons.Cross} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -69,8 +69,15 @@ export const Reactions = as<'div', ReactionsProps>(
|
|||||||
const measure = useCallback(() => {
|
const measure = useCallback(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
const rowWidth = container.getBoundingClientRect().width;
|
// [Gitea #305] The width the chips actually get (content box). The border
|
||||||
const gap = parseFloat(getComputedStyle(container).columnGap) || 8;
|
// box grows by 4 px while collapsed (margin -2 / padding 2), and measuring
|
||||||
|
// it made a row within 4 px of fitting flip collapsed↔expanded every frame.
|
||||||
|
const cs = getComputedStyle(container);
|
||||||
|
const rowWidth =
|
||||||
|
container.clientWidth -
|
||||||
|
(parseFloat(cs.paddingLeft) || 0) -
|
||||||
|
(parseFloat(cs.paddingRight) || 0);
|
||||||
|
const gap = parseFloat(cs.columnGap) || 8;
|
||||||
const chips = keys ? keys.split('\u0000').map((k) => chipRefs.current.get(k)) : [];
|
const chips = keys ? keys.split('\u0000').map((k) => chipRefs.current.get(k)) : [];
|
||||||
const widths = chips.map((el) => el?.getBoundingClientRect().width ?? 0);
|
const widths = chips.map((el) => el?.getBoundingClientRect().width ?? 0);
|
||||||
const moreWidth = moreRef.current?.getBoundingClientRect().width ?? 48;
|
const moreWidth = moreRef.current?.getBoundingClientRect().width ?? 48;
|
||||||
@@ -110,8 +117,21 @@ export const Reactions = as<'div', ReactionsProps>(
|
|||||||
style={{
|
style={{
|
||||||
...props.style,
|
...props.style,
|
||||||
...(collapsed && rowHeight
|
...(collapsed && rowHeight
|
||||||
? // 2px breathing room so row-1 focus outlines are not clipped.
|
? // 2px breathing room so row-1 focus outlines are not clipped. Longhand
|
||||||
{ maxHeight: rowHeight + 4, overflow: 'hidden', padding: 2, margin: -2 }
|
// margins so the timeline's marginTop survives (Gitea #305: the
|
||||||
|
// `margin` shorthand overrode it and, once removed, cleared it).
|
||||||
|
{
|
||||||
|
maxHeight: rowHeight + 4,
|
||||||
|
overflow: 'hidden',
|
||||||
|
padding: 2,
|
||||||
|
marginLeft: -2,
|
||||||
|
marginRight: -2,
|
||||||
|
marginBottom: -2,
|
||||||
|
marginTop:
|
||||||
|
props.style?.marginTop !== undefined
|
||||||
|
? `calc(${typeof props.style.marginTop === 'number' ? `${props.style.marginTop}px` : props.style.marginTop} - 2px)`
|
||||||
|
: -2,
|
||||||
|
}
|
||||||
: {}),
|
: {}),
|
||||||
}}
|
}}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
|
|||||||
@@ -596,10 +596,6 @@ function Appearance() {
|
|||||||
/>
|
/>
|
||||||
</SequenceCard>
|
</SequenceCard>
|
||||||
|
|
||||||
<DesktopChromeSetting />
|
|
||||||
<AutostartSetting />
|
|
||||||
<KeychainMirrorSetting />
|
|
||||||
|
|
||||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||||
<SettingTile
|
<SettingTile
|
||||||
title="Twitter Emoji"
|
title="Twitter Emoji"
|
||||||
@@ -2856,6 +2852,23 @@ function PackageUpdate({ help }: { help: NonNullable<ReturnType<typeof packageUp
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Gitea #290] Desktop-only settings in their own section (they used to sit
|
||||||
|
* inside Appearance, between Monochrome Mode and Twitter Emoji). Renders
|
||||||
|
* nothing outside the desktop app.
|
||||||
|
*/
|
||||||
|
function DesktopSettings() {
|
||||||
|
if (!isTauriEnv()) return null;
|
||||||
|
return (
|
||||||
|
<Box direction="Column" gap="100">
|
||||||
|
<Text size="L400">Desktop</Text>
|
||||||
|
<AutostartSetting />
|
||||||
|
<KeychainMirrorSetting />
|
||||||
|
<DesktopChromeSetting />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AppUpdates() {
|
function AppUpdates() {
|
||||||
const { isTauri, status, check, install, installKind } = useTauriUpdater();
|
const { isTauri, status, check, install, installKind } = useTauriUpdater();
|
||||||
if (!isTauri) return null;
|
if (!isTauri) return null;
|
||||||
@@ -2964,6 +2977,7 @@ export function General({ requestClose }: GeneralProps) {
|
|||||||
<StorageUsage />
|
<StorageUsage />
|
||||||
<SettingsSyncSection />
|
<SettingsSyncSection />
|
||||||
<Calls />
|
<Calls />
|
||||||
|
<DesktopSettings />
|
||||||
<AppUpdates />
|
<AppUpdates />
|
||||||
</Box>
|
</Box>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import {
|
|||||||
CALL_SESSION_HEARTBEAT_MS,
|
CALL_SESSION_HEARTBEAT_MS,
|
||||||
clearCallSession,
|
clearCallSession,
|
||||||
decideRejoin,
|
decideRejoin,
|
||||||
othersInCall,
|
|
||||||
rejoinToastBody,
|
|
||||||
readCallSession,
|
readCallSession,
|
||||||
writeCallSession,
|
writeCallSession,
|
||||||
} from '../utils/callRejoin';
|
} from '../utils/callRejoin';
|
||||||
@@ -114,27 +112,6 @@ export function useCallRejoin(embed: CallEmbed | undefined, joined: boolean): vo
|
|||||||
};
|
};
|
||||||
dismissToast(TOAST_ID);
|
dismissToast(TOAST_ID);
|
||||||
clearCallSession();
|
clearCallSession();
|
||||||
// [Gitea #302] A DM call the other person has since left is over;
|
|
||||||
// starting it again would ring them, so just say so.
|
|
||||||
const dm = l.directs.has(room.roomId);
|
|
||||||
if (
|
|
||||||
dm &&
|
|
||||||
!othersInCall(
|
|
||||||
session.memberships.map((m) => m.userId ?? ''),
|
|
||||||
mx.getUserId() ?? '',
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Own id: clicking a toast removes it by id after its onClick runs.
|
|
||||||
setToast({
|
|
||||||
id: `${TOAST_ID}-ended`,
|
|
||||||
iconSrc: Icons.Phone,
|
|
||||||
displayName: 'Call ended',
|
|
||||||
body: `${room.name ?? 'They'} already left the call.`,
|
|
||||||
roomName: room.name ?? '',
|
|
||||||
roomId: room.roomId,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
(l.directs.has(room.roomId) ? l.startDmCall : l.startRoomCall)(room, pref);
|
(l.directs.has(room.roomId) ? l.startDmCall : l.startRoomCall)(room, pref);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -156,7 +133,6 @@ export function useCallRejoin(embed: CallEmbed | undefined, joined: boolean): vo
|
|||||||
.filter((m) => m.userId === mx.getUserId())
|
.filter((m) => m.userId === mx.getUserId())
|
||||||
.map((m) => m.deviceId),
|
.map((m) => m.deviceId),
|
||||||
memberCount: memberships.length,
|
memberCount: memberships.length,
|
||||||
dm: latest.current.directs.has(room.roomId),
|
|
||||||
});
|
});
|
||||||
// The RTC session fills in a moment after sync; keep waiting while it is
|
// The RTC session fills in a moment after sync; keep waiting while it is
|
||||||
// empty unless this is the final (timed-out) look.
|
// empty unless this is the final (timed-out) look.
|
||||||
@@ -176,7 +152,7 @@ export function useCallRejoin(embed: CallEmbed | undefined, joined: boolean): vo
|
|||||||
id: TOAST_ID,
|
id: TOAST_ID,
|
||||||
iconSrc: Icons.Phone,
|
iconSrc: Icons.Phone,
|
||||||
displayName: 'Rejoin voice?',
|
displayName: 'Rejoin voice?',
|
||||||
body: rejoinToastBody(room.name, latest.current.directs.has(room.roomId)),
|
body: `You were in the call in ${room.name ?? 'a room'} before the restart. Tap to rejoin.`,
|
||||||
roomName: room.name ?? '',
|
roomName: room.name ?? '',
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
sticky: true,
|
sticky: true,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { verifyDenoiseAssets } from './denoiseSmokeCheck';
|
|||||||
import { canDelegateCapability } from './utils';
|
import { canDelegateCapability } from './utils';
|
||||||
import { getCallPageUrl } from './callPageUrl';
|
import { getCallPageUrl } from './callPageUrl';
|
||||||
import { restrictWidgetMessages } from '../widgetTransport';
|
import { restrictWidgetMessages } from '../widgetTransport';
|
||||||
|
import { isRingableRoom } from '../../utils/callRing';
|
||||||
|
|
||||||
// Maximum time to wait for the embedded Element Call iframe to progress from
|
// Maximum time to wait for the embedded Element Call iframe to progress from
|
||||||
// initial load to a ready/joined state. If it hasn't by then, we assume the
|
// initial load to a ready/joined state. If it hasn't by then, we assume the
|
||||||
@@ -251,8 +252,18 @@ export class CallEmbed {
|
|||||||
verifyDenoiseAssets(denoiseModel).catch(() => undefined);
|
verifyDenoiseAssets(denoiseModel).catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Gitea #295] Only ask Element Call to notify where a call can ring at all
|
||||||
|
// (DMs, invite-only groups outside a space). In a voice channel or space
|
||||||
|
// room the notification carries an @room mention, so every member got a
|
||||||
|
// highlighted ping (and phone push) whenever a moderator hopped in, and
|
||||||
|
// other clients showed it as a missed call.
|
||||||
|
// `none` needs element-call-embedded ≥ 0.25.0-lotus.23 (older builds ignore it).
|
||||||
if (CallEmbed.startingCall(intent)) {
|
if (CallEmbed.startingCall(intent)) {
|
||||||
params.append('sendNotificationType', CallEmbed.dmCall(intent) ? 'ring' : 'notification');
|
let notify = 'none';
|
||||||
|
if (isRingableRoom(room, CallEmbed.dmCall(intent))) {
|
||||||
|
notify = CallEmbed.dmCall(intent) ? 'ring' : 'notification';
|
||||||
|
}
|
||||||
|
params.append('sendNotificationType', notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Gitea #43] On its own origin when config.json sets elementCallUrl.
|
// [Gitea #43] On its own origin when config.json sets elementCallUrl.
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { describe, it } from 'node:test';
|
import { describe, it } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {
|
import { CALL_REJOIN_MAX_AGE_MS, decideRejoin, RejoinContext } from './callRejoin';
|
||||||
CALL_REJOIN_MAX_AGE_MS,
|
|
||||||
decideRejoin,
|
|
||||||
othersInCall,
|
|
||||||
rejoinToastBody,
|
|
||||||
RejoinContext,
|
|
||||||
} from './callRejoin';
|
|
||||||
|
|
||||||
const base: RejoinContext = {
|
const base: RejoinContext = {
|
||||||
record: {
|
record: {
|
||||||
@@ -60,21 +54,3 @@ describe('decideRejoin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('decideRejoin: DM calls are never auto-rejoined, only offered (#302)', () => {
|
|
||||||
assert.deepEqual(decideRejoin({ ...base, mode: 'auto', dm: true }), {
|
|
||||||
action: 'ask',
|
|
||||||
roomId: '!r',
|
|
||||||
});
|
|
||||||
assert.deepEqual(decideRejoin({ ...base, mode: 'auto' }), { action: 'auto', roomId: '!r' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejoinToastBody and othersInCall (#302)', () => {
|
|
||||||
assert.equal(
|
|
||||||
rejoinToastBody('Bob Builder', true),
|
|
||||||
'You were in a call with Bob Builder before the restart. Tap to rejoin.',
|
|
||||||
);
|
|
||||||
assert.match(rejoinToastBody('Voice Lounge', false), /in the call in Voice Lounge/);
|
|
||||||
assert.equal(othersInCall(['@me:x'], '@me:x'), false);
|
|
||||||
assert.equal(othersInCall(['@me:x', '@bob:x'], '@me:x'), true);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ export type RejoinContext = {
|
|||||||
ownMemberDevices: string[];
|
ownMemberDevices: string[];
|
||||||
/** Number of call memberships in the room, ours included. */
|
/** Number of call memberships in the room, ours included. */
|
||||||
memberCount: number;
|
memberCount: number;
|
||||||
/** A DM call: rejoining one may ring the other person. */
|
|
||||||
dm?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RejoinDecision = { action: 'none' } | { action: 'ask' | 'auto'; roomId: string };
|
export type RejoinDecision = { action: 'none' } | { action: 'ask' | 'auto'; roomId: string };
|
||||||
@@ -85,18 +83,5 @@ export function decideRejoin(ctx: RejoinContext): RejoinDecision {
|
|||||||
// Nobody there any more (our own stale membership doesn't count).
|
// Nobody there any more (our own stale membership doesn't count).
|
||||||
const others = ctx.memberCount - ctx.ownMemberDevices.length;
|
const others = ctx.memberCount - ctx.ownMemberDevices.length;
|
||||||
if (others <= 0) return { action: 'none' };
|
if (others <= 0) return { action: 'none' };
|
||||||
// [Gitea #302] Never rejoin a DM call on our own: if the other person has
|
|
||||||
// just left, starting it again would ring them. Ask instead.
|
|
||||||
if (ctx.dm && mode === 'auto') return { action: 'ask', roomId: record.roomId };
|
|
||||||
return { action: mode, roomId: record.roomId };
|
return { action: mode, roomId: record.roomId };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pure: whether anyone but us is still in the call (by call membership). */
|
|
||||||
export const othersInCall = (memberUserIds: string[], myUserId: string): boolean =>
|
|
||||||
memberUserIds.some((u) => u !== myUserId);
|
|
||||||
|
|
||||||
/** The rejoin toast's text; DMs name the person rather than "the call in <room>". */
|
|
||||||
export const rejoinToastBody = (roomName: string | undefined, dm: boolean): string =>
|
|
||||||
dm
|
|
||||||
? `You were in a call with ${roomName ?? 'them'} before the restart. Tap to rejoin.`
|
|
||||||
: `You were in the call in ${roomName ?? 'a room'} before the restart. Tap to rejoin.`;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user