mv element.io @types __mocks__/ debian docker module_system/ playwright res src test webapp Dockerfile .dockerignore .eslintignore .stylelintrc.cjs babel.config.cjs recorder-worklet-loader.cjs .modernizr.json components.json config.json config.sample.json package.json project.json tsconfig.json tsconfig.module_system.json jest.config.ts playwright.config.ts webpack.config.ts build_config.sample.yaml apps/web/

mkdir apps/web/scripts
mv scripts/{cleanup.sh,ci_package.sh,copy-res.ts,deploy.py,package.sh} apps/web/scripts

And a couple of gitignore tweaks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2026-02-24 15:43:58 +00:00
parent e7509c92a1
commit 91a3cb03c1
3408 changed files with 28 additions and 32 deletions
@@ -0,0 +1,371 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2017-2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import {
ClientEvent,
type MatrixClient,
type MatrixEvent,
MatrixEventEvent,
type Room,
RoomEvent,
type IRoomTimelineData,
type RoomState,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import dis from "../dispatcher/dispatcher";
import { type ActionPayload } from "../dispatcher/payloads";
/**
* Create a MatrixActions.sync action that represents a MatrixClient `sync` event,
* each parameter mapping to a key-value in the action.
*
* @param {MatrixClient} matrixClient the matrix client
* @param {string} state the current sync state.
* @param {string} prevState the previous sync state.
* @returns {Object} an action of type MatrixActions.sync.
*/
function createSyncAction(matrixClient: MatrixClient, state: string, prevState: string): ActionPayload {
return {
action: "MatrixActions.sync",
state,
prevState,
matrixClient,
};
}
/**
* @typedef AccountDataAction
* @type {Object}
* @property {string} action 'MatrixActions.accountData'.
* @property {MatrixEvent} event the MatrixEvent that triggered the dispatch.
* @property {string} event_type the type of the MatrixEvent, e.g. "m.direct".
* @property {Object} event_content the content of the MatrixEvent.
* @property {MatrixEvent} previousEvent the previous account data event of the same type, if present
*/
/**
* Create a MatrixActions.accountData action that represents a MatrixClient `accountData`
* matrix event.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} accountDataEvent the account data event.
* @param {MatrixEvent | undefined} previousAccountDataEvent the previous account data event of the same type, if present
* @returns {AccountDataAction} an action of type MatrixActions.accountData.
*/
function createAccountDataAction(
matrixClient: MatrixClient,
accountDataEvent: MatrixEvent,
previousAccountDataEvent?: MatrixEvent,
): ActionPayload {
return {
action: "MatrixActions.accountData",
event: accountDataEvent,
event_type: accountDataEvent.getType(),
event_content: accountDataEvent.getContent(),
previousEvent: previousAccountDataEvent,
};
}
/**
* @typedef RoomAccountDataAction
* @type {Object}
* @property {string} action 'MatrixActions.Room.accountData'.
* @property {MatrixEvent} event the MatrixEvent that triggered the dispatch.
* @property {string} event_type the type of the MatrixEvent, e.g. "m.direct".
* @property {Object} event_content the content of the MatrixEvent.
* @property {Room} room the room where the account data was changed.
*/
/**
* Create a MatrixActions.Room.accountData action that represents a MatrixClient `Room.accountData`
* matrix event.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} accountDataEvent the account data event.
* @param {Room} room the room where account data was changed
* @returns {RoomAccountDataAction} an action of type MatrixActions.Room.accountData.
*/
function createRoomAccountDataAction(
matrixClient: MatrixClient,
accountDataEvent: MatrixEvent,
room: Room,
): ActionPayload {
return {
action: "MatrixActions.Room.accountData",
event: accountDataEvent,
event_type: accountDataEvent.getType(),
event_content: accountDataEvent.getContent(),
room: room,
};
}
/**
* @typedef RoomAction
* @type {Object}
* @property {string} action 'MatrixActions.Room'.
* @property {Room} room the Room that was stored.
*/
/**
* Create a MatrixActions.Room action that represents a MatrixClient `Room`
* matrix event, emitted when a Room is stored in the client.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {Room} room the Room that was stored.
* @returns {RoomAction} an action of type `MatrixActions.Room`.
*/
function createRoomAction(matrixClient: MatrixClient, room: Room): ActionPayload {
return { action: "MatrixActions.Room", room };
}
/**
* @typedef RoomTagsAction
* @type {Object}
* @property {string} action 'MatrixActions.Room.tags'.
* @property {Room} room the Room whose tags changed.
*/
/**
* Create a MatrixActions.Room.tags action that represents a MatrixClient
* `Room.tags` matrix event, emitted when the m.tag room account data
* event is updated.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} roomTagsEvent the m.tag event.
* @param {Room} room the Room whose tags were changed.
* @returns {RoomTagsAction} an action of type `MatrixActions.Room.tags`.
*/
function createRoomTagsAction(matrixClient: MatrixClient, roomTagsEvent: MatrixEvent, room: Room): ActionPayload {
return { action: "MatrixActions.Room.tags", room };
}
/**
* Create a MatrixActions.Room.receipt action that represents a MatrixClient
* `Room.receipt` event, each parameter mapping to a key-value in the action.
*
* @param {MatrixClient} matrixClient the matrix client
* @param {MatrixEvent} event the receipt event.
* @param {Room} room the room the receipt happened in.
* @returns {Object} an action of type MatrixActions.Room.receipt.
*/
function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent, room: Room): ActionPayload {
return {
action: "MatrixActions.Room.receipt",
event,
room,
matrixClient,
};
}
/**
* @typedef IRoomTimelineActionPayload
* @type {Object}
* @property {string} action 'MatrixActions.Room.timeline'.
* @property {boolean} isLiveEvent whether the event was attached to a
* live timeline.
* @property {boolean} isLiveUnfilteredRoomTimelineEvent whether the
* event was attached to a timeline in the set of unfiltered timelines.
* @property {Room} room the Room whose tags changed.
*/
export interface IRoomTimelineActionPayload extends Pick<ActionPayload, "action"> {
action: "MatrixActions.Room.timeline";
event: MatrixEvent;
room: Room | null;
isLiveEvent?: boolean;
isLiveUnfilteredRoomTimelineEvent: boolean;
}
/**
* @typedef IRoomStateEventsActionPayload
* @type {Object}
* @property {string} action 'MatrixActions.RoomState.events'.
* @property {MatrixEvent} event the state event received
* @property {RoomState} state the room state into which the event was applied
* @property {MatrixEvent | null} lastStateEvent the previous value for this (event-type, state-key) tuple in room state
*/
export interface IRoomStateEventsActionPayload extends Pick<ActionPayload, "action"> {
action: "MatrixActions.RoomState.events";
event: MatrixEvent;
state: RoomState;
lastStateEvent: MatrixEvent | null;
}
/**
* Create a MatrixActions.Room.timeline action that represents a
* MatrixClient `Room.timeline` matrix event, emitted when an event
* is added to or removed from a timeline of a room.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} timelineEvent the event that was added/removed.
* @param {?Room} room the Room that was stored.
* @param {boolean} toStartOfTimeline whether the event is being added
* to the start (and not the end) of the timeline.
* @param {boolean} removed whether the event was removed from the
* timeline.
* @param {Object} data
* @param {boolean} data.liveEvent whether the event is a live event,
* belonging to a live timeline.
* @param {EventTimeline} data.timeline the timeline being altered.
* @returns {IRoomTimelineActionPayload} an action of type `MatrixActions.Room.timeline`.
*/
function createRoomTimelineAction(
matrixClient: MatrixClient,
timelineEvent: MatrixEvent,
room: Room | null,
toStartOfTimeline: boolean,
removed: boolean,
data: IRoomTimelineData,
): IRoomTimelineActionPayload {
return {
action: "MatrixActions.Room.timeline",
event: timelineEvent,
isLiveEvent: data.liveEvent,
isLiveUnfilteredRoomTimelineEvent: data.timeline.getTimelineSet() === room?.getUnfilteredTimelineSet(),
room,
};
}
/**
* Create a MatrixActions.Room.timeline action that represents a
* MatrixClient `Room.timeline` matrix event, emitted when an event
* is added to or removed from a timeline of a room.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} event the state event received
* @param {RoomState} state the room state into which the event was applied
* @param {MatrixEvent | null} lastStateEvent the previous value for this (event-type, state-key) tuple in room state
* @returns {IRoomStateEventsActionPayload} an action of type `MatrixActions.RoomState.events`.
*/
function createRoomStateEventsAction(
matrixClient: MatrixClient,
event: MatrixEvent,
state: RoomState,
lastStateEvent: MatrixEvent | null,
): IRoomStateEventsActionPayload {
return {
action: "MatrixActions.RoomState.events",
event,
state,
lastStateEvent,
};
}
/**
* @typedef RoomMembershipAction
* @type {Object}
* @property {string} action 'MatrixActions.Room.myMembership'.
* @property {Room} room to room for which the self-membership changed.
* @property {string} membership the new membership
* @property {string} oldMembership the previous membership, can be null.
*/
/**
* Create a MatrixActions.Room.myMembership action that represents
* a MatrixClient `Room.myMembership` event for the syncing user,
* emitted when the syncing user's membership is updated for a room.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {Room} room to room for which the self-membership changed.
* @param {string} membership the new membership
* @param {string} oldMembership the previous membership, can be null.
* @returns {RoomMembershipAction} an action of type `MatrixActions.Room.myMembership`.
*/
function createSelfMembershipAction(
matrixClient: MatrixClient,
room: Room,
membership: string,
oldMembership: string,
): ActionPayload {
return { action: "MatrixActions.Room.myMembership", room, membership, oldMembership };
}
/**
* @typedef EventDecryptedAction
* @type {Object}
* @property {string} action 'MatrixActions.Event.decrypted'.
* @property {MatrixEvent} event the matrix event that was decrypted.
*/
/**
* Create a MatrixActions.Event.decrypted action that represents
* a MatrixClient `Event.decrypted` matrix event, emitted when a
* matrix event is decrypted.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} event the matrix event that was decrypted.
* @returns {EventDecryptedAction} an action of type `MatrixActions.Event.decrypted`.
*/
function createEventDecryptedAction(matrixClient: MatrixClient, event: MatrixEvent): ActionPayload {
return { action: "MatrixActions.Event.decrypted", event };
}
type Listener = () => void;
type ActionCreator = (matrixClient: MatrixClient, ...args: any) => ActionPayload;
// A list of callbacks to call to unregister all listeners added
let matrixClientListenersStop: Listener[] = [];
/**
* Start listening to events of type eventName on matrixClient and when they are emitted,
* dispatch an action created by the actionCreator function.
* @param {MatrixClient} matrixClient a MatrixClient to register a listener with.
* @param {string} eventName the event to listen to on MatrixClient.
* @param {function} actionCreator a function that should return an action to dispatch
* when given the MatrixClient as an argument as well as
* arguments emitted in the MatrixClient event.
*/
function addMatrixClientListener(
matrixClient: MatrixClient,
eventName: Parameters<MatrixClient["emit"]>[0],
actionCreator: ActionCreator,
): void {
const listener: Listener = (...args) => {
const payload = actionCreator(matrixClient, ...args);
if (payload) {
// Consumers shouldn't have to worry about calling js-sdk methods mid-dispatch, so make this dispatch async
dis.dispatch(payload, false);
}
};
matrixClient.on(eventName, listener);
matrixClientListenersStop.push(() => {
matrixClient.removeListener(eventName, listener);
});
}
/**
* This object is responsible for dispatching actions when certain events are emitted by
* the given MatrixClient.
*/
export default {
/**
* Start listening to certain events from the MatrixClient and dispatch actions when
* they are emitted.
* @param {MatrixClient} matrixClient the MatrixClient to listen to events from
*/
start(matrixClient: MatrixClient) {
addMatrixClientListener(matrixClient, ClientEvent.Sync, createSyncAction);
addMatrixClientListener(matrixClient, ClientEvent.AccountData, createAccountDataAction);
addMatrixClientListener(matrixClient, RoomEvent.AccountData, createRoomAccountDataAction);
addMatrixClientListener(matrixClient, ClientEvent.Room, createRoomAction);
addMatrixClientListener(matrixClient, RoomEvent.Tags, createRoomTagsAction);
addMatrixClientListener(matrixClient, RoomEvent.Receipt, createRoomReceiptAction);
addMatrixClientListener(matrixClient, RoomEvent.Timeline, createRoomTimelineAction);
addMatrixClientListener(matrixClient, RoomEvent.MyMembership, createSelfMembershipAction);
addMatrixClientListener(matrixClient, MatrixEventEvent.Decrypted, createEventDecryptedAction);
addMatrixClientListener(matrixClient, RoomStateEvent.Events, createRoomStateEventsAction);
},
/**
* Stop listening to events.
*/
stop() {
matrixClientListenersStop.forEach((stopListener) => stopListener());
matrixClientListenersStop = [];
},
};
+133
View File
@@ -0,0 +1,133 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { asyncAction } from "./actionCreators";
import Modal from "../Modal";
import * as Rooms from "../Rooms";
import { _t } from "../languageHandler";
import { type AsyncActionPayload } from "../dispatcher/payloads";
import RoomListStore from "../stores/room-list/RoomListStore";
import { SortAlgorithm } from "../stores/room-list/algorithms/models";
import { DefaultTagID, type TagID } from "../stores/room-list/models";
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
export default class RoomListActions {
/**
* Creates an action thunk that will do an asynchronous request to
* tag room.
*
* @param {MatrixClient} matrixClient the matrix client to set the
* account data on.
* @param {Room} room the room to tag.
* @param {string} oldTag the tag to remove (unless oldTag ==== newTag)
* @param {string} newTag the tag with which to tag the room.
* @param {?number} oldIndex the previous position of the room in the
* list of rooms.
* @param {?number} newIndex the new position of the room in the list
* of rooms.
* @returns {AsyncActionPayload} an async action payload
* @see asyncAction
*/
public static tagRoom(
matrixClient: MatrixClient,
room: Room,
oldTag: TagID | null,
newTag: TagID | null,
newIndex: number,
): AsyncActionPayload {
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | undefined;
// Is the tag ordered manually?
const store = RoomListStore.instance;
if (newTag && store.getTagSorting(newTag) === SortAlgorithm.Manual) {
const newList = [...store.orderedLists[newTag]];
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
const indexBefore = newIndex - 1;
const indexAfter = newIndex;
const prevOrder = indexBefore <= 0 ? 0 : newList[indexBefore].tags[newTag].order;
const nextOrder = indexAfter >= newList.length ? 1 : newList[indexAfter].tags[newTag].order;
metaData = {
order: (prevOrder + nextOrder) / 2.0,
};
}
return asyncAction(
"RoomListActions.tagRoom",
() => {
const promises: Promise<any>[] = [];
const roomId = room.roomId;
// Evil hack to get DMs behaving
if (
(oldTag === undefined && newTag === DefaultTagID.DM) ||
(oldTag === DefaultTagID.DM && newTag === undefined)
) {
return Rooms.guessAndSetDMRoom(room, newTag === DefaultTagID.DM).catch((err) => {
logger.error("Failed to set DM tag " + err);
Modal.createDialog(ErrorDialog, {
title: _t("room_list|failed_set_dm_tag"),
description: err?.message ?? _t("invite|failed_generic"),
});
});
}
const hasChangedSubLists = oldTag !== newTag;
// More evilness: We will still be dealing with moving to favourites/low prio,
// but we avoid ever doing a request with TAG_DM.
//
// if we moved lists, remove the old tag
if (oldTag && oldTag !== DefaultTagID.DM && hasChangedSubLists) {
const promiseToDelete = matrixClient.deleteRoomTag(roomId, oldTag).catch(function (err) {
logger.error("Failed to remove tag " + oldTag + " from room: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("room_list|failed_remove_tag", { tagName: oldTag }),
description: err?.message ?? _t("invite|failed_generic"),
});
});
promises.push(promiseToDelete);
}
// if we moved lists or the ordering changed, add the new tag
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
logger.error("Failed to add tag " + newTag + " to room: " + err);
Modal.createDialog(ErrorDialog, {
title: _t("room_list|failed_add_tag", { tagName: newTag }),
description: err?.message ?? _t("invite|failed_generic"),
});
throw err;
});
promises.push(promiseToAdd);
}
return Promise.all(promises);
},
() => {
// For an optimistic update
return {
room,
oldTag,
newTag,
metaData,
};
},
);
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2017 New Vector Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { type AsyncActionFn, AsyncActionPayload } from "../dispatcher/payloads";
/**
* Create an action thunk that will dispatch actions indicating the current
* status of the Promise returned by fn.
*
* @param {string} id the id to give the dispatched actions. This is given a
* suffix determining whether it is pending, successful or
* a failure.
* @param {function} fn a function that returns a Promise.
* @param {function?} pendingFn a function that returns an object to assign
* to the `request` key of the ${id}.pending
* payload.
* @returns {AsyncActionPayload} an async action payload. Includes a function
* that uses its single argument as a dispatch function
* to dispatch the following actions:
* `${id}.pending` and either
* `${id}.success` or
* `${id}.failure`.
*
* The shape of each are:
* { action: '${id}.pending', request }
* { action: '${id}.success', result }
* { action: '${id}.failure', err }
*
* where `request` is returned by `pendingFn` and
* result is the result of the promise returned by
* `fn`.
*/
export function asyncAction(id: string, fn: () => Promise<any>, pendingFn: () => any | null): AsyncActionPayload {
const helper: AsyncActionFn = (dispatch) => {
dispatch({
action: id + ".pending",
request: typeof pendingFn === "function" ? pendingFn() : undefined,
});
fn()
.then((result) => {
dispatch({ action: id + ".success", result });
})
.catch((err) => {
dispatch({ action: id + ".failure", err });
});
};
return new AsyncActionPayload(helper);
}
@@ -0,0 +1,21 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { UserTab } from "../../components/views/dialogs/UserTab";
import { Action } from "../../dispatcher/actions";
import defaultDispatcher from "../../dispatcher/dispatcher";
/**
* Open user device manager settings
*/
export const viewUserDeviceSettings = (): void => {
defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.SessionManager,
});
};