chore: merge v4.12.1 — security, calling, editor, media fixes

Key v4.12.1 changes merged:
- Security: sanitize-html updated to v2.17.4
- Calling: video calls in DMs/rooms, user avatars during calls, right-click to start
- Calling: IncomingCallListener with ring sound and answer/reject UI
- Editor: list crash fixes (Firefox + empty headings), codeblock filename support
- Media: URL preview hover state, keyboard nav, click-to-open, OGG audio support
- Date: ISO 8601 (YYYY-MM-DD) date format option
- Misc: stable mutual rooms endpoint, Android notification crash fix

Lotus customisations preserved:
- PiP drag/resize, DM call ring notification, PTT, GIF picker, noise suppression
- Poll voting, message forwarding, image captions, location sharing
- Lotus Terminal design theme
This commit is contained in:
root
2026-05-15 13:41:38 -04:00
54 changed files with 8502 additions and 1023 deletions
+2 -1
View File
@@ -52,7 +52,7 @@ export const createCallEmbed = (
const ongoing =
MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.sessionDescription).length > 0;
const intent = CallEmbed.getIntent(dm, ongoing);
const intent = CallEmbed.getIntent(dm, ongoing, pref?.video);
const initialAudio = forceAudioOff ? false : (pref?.microphone ?? true);
const initialVideo = pref?.video ?? false;
const widget = CallEmbed.getWidget(mx, room, intent, themeKind, noiseSuppression, initialAudio, initialVideo);
@@ -109,6 +109,7 @@ export const useCallJoined = (embed?: CallEmbed): boolean => {
export const useCallHangupEvent = (embed: CallEmbed, callback: () => void) => {
useClientWidgetApiEvent(embed.call, ElementWidgetActions.HangupCall, callback);
useClientWidgetApiEvent(embed.call, ElementWidgetActions.Close, callback);
};
export const useCallMemberSoundSync = (embed: CallEmbed) => {
+4
View File
@@ -25,6 +25,10 @@ export const useDateFormatItems = (): DateFormatItem[] =>
format: 'YYYY/MM/DD',
name: 'YYYY/MM/DD',
},
{
format: 'YYYY-MM-DD',
name: 'YYYY-MM-DD',
},
{
format: '',
name: 'Custom',
+50 -6
View File
@@ -1,9 +1,10 @@
import { useCallback } from 'react';
import { MatrixClient, Method } from 'matrix-js-sdk';
import { useMatrixClient } from './useMatrixClient';
import { AsyncState, useAsyncCallbackValue } from './useAsyncCallback';
import { useSpecVersions } from './useSpecVersions';
export const useMutualRoomsSupport = (): boolean => {
export const useUnstableMutualRoomsSupport = (): boolean => {
const { unstable_features: unstableFeatures } = useSpecVersions();
const supported =
@@ -14,16 +15,59 @@ export const useMutualRoomsSupport = (): boolean => {
return !!supported;
};
export const useMutualRoomsSupport = (): boolean => {
const { unstable_features: unstableFeatures, versions } = useSpecVersions();
const supported =
versions.includes('v1.19') ||
unstableFeatures?.['uk.half-shot.msc2666.query_mutual_rooms.stable'];
return !!supported;
};
type MutualRoomsOK = {
joined: string[];
next_batch?: string;
count: number;
};
const fetchAllMutualRooms = async (mx: MatrixClient, userId: string): Promise<string[]> => {
const mutualRooms: Set<string> = new Set();
let nextBatch: string | undefined;
do {
// eslint-disable-next-line no-await-in-loop
const result = await mx.http.authedRequest<MutualRoomsOK>(
Method.Get,
'/mutual_rooms',
{
user_id: userId,
from: nextBatch,
},
undefined,
{
prefix: '/_matrix/client/v1',
}
);
result.joined.forEach((r) => mutualRooms.add(r));
nextBatch = result.next_batch;
} while (typeof nextBatch === 'string');
return Array.from(mutualRooms);
};
export const useMutualRooms = (userId: string): AsyncState<string[], unknown> => {
const mx = useMatrixClient();
const supported = useMutualRoomsSupport();
const unstableSupport = useUnstableMutualRoomsSupport();
const support = useMutualRoomsSupport();
const [mutualRoomsState] = useAsyncCallbackValue(
useCallback(
() => (supported ? mx._unstable_getSharedRooms(userId) : Promise.resolve([])),
[mx, userId, supported]
)
useCallback(() => {
if (support) return fetchAllMutualRooms(mx, userId);
if (unstableSupport) return mx._unstable_getSharedRooms(userId);
return Promise.resolve([]);
}, [mx, userId, unstableSupport, support])
);
return mutualRoomsState;
+1 -1
View File
@@ -57,7 +57,7 @@ const fillMissingPowers = (powerLevels: IPowerLevels): IPowerLevels =>
return draftPl;
});
const getPowersLevelFromMatrixEvent = (mEvent?: MatrixEvent): IPowerLevels => {
export const getPowersLevelFromMatrixEvent = (mEvent?: MatrixEvent): IPowerLevels => {
const plContent = mEvent?.getContent<IPowerLevels>();
const powerLevels = !plContent ? DEFAULT_POWER_LEVELS : fillMissingPowers(plContent);