Merge branch 'develop' into gsouquet/threads-action-bar-19127

pull/21833/head
Germain Souquet 2021-09-28 09:34:43 +01:00
commit e52a33e93c
45 changed files with 668 additions and 468 deletions

View File

@ -1,3 +1,106 @@
Changes in [3.31.0](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0) (2021-09-27)
===================================================================================================
## ✨ Features
* Say Joining space instead of Joining room where we know its a space ([\#6818](https://github.com/matrix-org/matrix-react-sdk/pull/6818)). Fixes vector-im/element-web#19064 and vector-im/element-web#19064.
* Add warning that some spaces may not be relinked to the newly upgraded room ([\#6805](https://github.com/matrix-org/matrix-react-sdk/pull/6805)). Fixes vector-im/element-web#18858 and vector-im/element-web#18858.
* Delabs Spaces, iterate some copy and move communities/space toggle to preferences ([\#6594](https://github.com/matrix-org/matrix-react-sdk/pull/6594)). Fixes vector-im/element-web#18088, vector-im/element-web#18524 vector-im/element-web#18088 and vector-im/element-web#18088.
* Show "Message" in the user info panel instead of "Start chat" ([\#6319](https://github.com/matrix-org/matrix-react-sdk/pull/6319)). Fixes vector-im/element-web#17877 and vector-im/element-web#17877. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix space keyboard shortcuts conflicting with native zoom shortcuts ([\#6804](https://github.com/matrix-org/matrix-react-sdk/pull/6804)).
* Replace plain text emoji at the end of a line ([\#6784](https://github.com/matrix-org/matrix-react-sdk/pull/6784)). Fixes vector-im/element-web#18833 and vector-im/element-web#18833. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Simplify Space Panel layout and fix some edge cases ([\#6800](https://github.com/matrix-org/matrix-react-sdk/pull/6800)). Fixes vector-im/element-web#18694 and vector-im/element-web#18694.
* Show unsent message warning on Space Panel buttons ([\#6778](https://github.com/matrix-org/matrix-react-sdk/pull/6778)). Fixes vector-im/element-web#18891 and vector-im/element-web#18891.
* Hide mute/unmute button in UserInfo for Spaces as it makes no sense ([\#6790](https://github.com/matrix-org/matrix-react-sdk/pull/6790)). Fixes vector-im/element-web#19007 and vector-im/element-web#19007.
* Fix automatic field population in space create menu not validating ([\#6792](https://github.com/matrix-org/matrix-react-sdk/pull/6792)). Fixes vector-im/element-web#19005 and vector-im/element-web#19005.
* Optimize input label transition on focus ([\#6783](https://github.com/matrix-org/matrix-react-sdk/pull/6783)). Fixes vector-im/element-web#12876 and vector-im/element-web#12876. Contributed by [MadLittleMods](https://github.com/MadLittleMods).
* Adapt and re-use the RolesRoomSettingsTab for Spaces ([\#6779](https://github.com/matrix-org/matrix-react-sdk/pull/6779)). Fixes vector-im/element-web#18908 vector-im/element-web#18909 and vector-im/element-web#18908.
* Deduplicate join rule management between rooms and spaces ([\#6724](https://github.com/matrix-org/matrix-react-sdk/pull/6724)). Fixes vector-im/element-web#18798 and vector-im/element-web#18798.
* Add config option to turn on in-room event sending timing metrics ([\#6766](https://github.com/matrix-org/matrix-react-sdk/pull/6766)).
* Improve the upgrade for restricted user experience ([\#6764](https://github.com/matrix-org/matrix-react-sdk/pull/6764)). Fixes vector-im/element-web#18677 and vector-im/element-web#18677.
* Improve tooltips on space quick actions and explore button ([\#6760](https://github.com/matrix-org/matrix-react-sdk/pull/6760)). Fixes vector-im/element-web#18528 and vector-im/element-web#18528.
* Make space members and user info behave more expectedly ([\#6765](https://github.com/matrix-org/matrix-react-sdk/pull/6765)). Fixes vector-im/element-web#17018 and vector-im/element-web#17018.
* hide no-op m.room.encryption events and better word param changes ([\#6747](https://github.com/matrix-org/matrix-react-sdk/pull/6747)). Fixes vector-im/element-web#18597 and vector-im/element-web#18597.
* Respect m.space.parent relations if they hold valid permissions ([\#6746](https://github.com/matrix-org/matrix-react-sdk/pull/6746)). Fixes vector-im/element-web#10935 and vector-im/element-web#10935.
* Space panel accessibility improvements ([\#6744](https://github.com/matrix-org/matrix-react-sdk/pull/6744)). Fixes vector-im/element-web#18892 and vector-im/element-web#18892.
## 🐛 Bug Fixes
* Fix spacing for message composer buttons ([\#6854](https://github.com/matrix-org/matrix-react-sdk/pull/6854)).
* Fix accessing field on oobData which may be undefined ([\#6830](https://github.com/matrix-org/matrix-react-sdk/pull/6830)). Fixes vector-im/element-web#19085 and vector-im/element-web#19085.
* Fix reactions aria-label not being a string and thus being read as [Object object] ([\#6828](https://github.com/matrix-org/matrix-react-sdk/pull/6828)).
* Fix missing null guard in space hierarchy pagination ([\#6821](https://github.com/matrix-org/matrix-react-sdk/pull/6821)). Fixes matrix-org/element-web-rageshakes#6299 and matrix-org/element-web-rageshakes#6299.
* Fix checks to show prompt to start new chats ([\#6812](https://github.com/matrix-org/matrix-react-sdk/pull/6812)).
* Fix room list scroll jumps ([\#6777](https://github.com/matrix-org/matrix-react-sdk/pull/6777)). Fixes vector-im/element-web#17460 vector-im/element-web#18440 and vector-im/element-web#17460. Contributed by [robintown](https://github.com/robintown).
* Fix various message bubble alignment issues ([\#6785](https://github.com/matrix-org/matrix-react-sdk/pull/6785)). Fixes vector-im/element-web#18293, vector-im/element-web#18294 vector-im/element-web#18305 and vector-im/element-web#18293. Contributed by [robintown](https://github.com/robintown).
* Make message bubble font size consistent ([\#6795](https://github.com/matrix-org/matrix-react-sdk/pull/6795)). Contributed by [robintown](https://github.com/robintown).
* Fix edge cases around joining new room which does not belong to active space ([\#6797](https://github.com/matrix-org/matrix-react-sdk/pull/6797)). Fixes vector-im/element-web#19025 and vector-im/element-web#19025.
* Fix edge case space issues around creation and initial view ([\#6798](https://github.com/matrix-org/matrix-react-sdk/pull/6798)). Fixes vector-im/element-web#19023 and vector-im/element-web#19023.
* Stop spinner on space preview if the join fails ([\#6803](https://github.com/matrix-org/matrix-react-sdk/pull/6803)). Fixes vector-im/element-web#19034 and vector-im/element-web#19034.
* Fix emoji picker and stickerpicker not appearing correctly when opened ([\#6793](https://github.com/matrix-org/matrix-react-sdk/pull/6793)). Fixes vector-im/element-web#19012 and vector-im/element-web#19012. Contributed by [Palid](https://github.com/Palid).
* Fix autocomplete not having y-scroll ([\#6794](https://github.com/matrix-org/matrix-react-sdk/pull/6794)). Fixes vector-im/element-web#18997 and vector-im/element-web#18997. Contributed by [Palid](https://github.com/Palid).
* Fix broken edge case with public space creation with no alias ([\#6791](https://github.com/matrix-org/matrix-react-sdk/pull/6791)). Fixes vector-im/element-web#19003 and vector-im/element-web#19003.
* Redirect from /#/welcome to /#/home if already logged in ([\#6786](https://github.com/matrix-org/matrix-react-sdk/pull/6786)). Fixes vector-im/element-web#18990 and vector-im/element-web#18990. Contributed by [aaronraimist](https://github.com/aaronraimist).
* Fix build issues from two conflicting PRs landing without merge conflict ([\#6780](https://github.com/matrix-org/matrix-react-sdk/pull/6780)).
* Render guest settings only in public rooms/spaces ([\#6693](https://github.com/matrix-org/matrix-react-sdk/pull/6693)). Fixes vector-im/element-web#18776 and vector-im/element-web#18776. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix message bubble corners being wrong in the presence of hidden events ([\#6776](https://github.com/matrix-org/matrix-react-sdk/pull/6776)). Fixes vector-im/element-web#18124 and vector-im/element-web#18124. Contributed by [robintown](https://github.com/robintown).
* Debounce read marker update on scroll ([\#6771](https://github.com/matrix-org/matrix-react-sdk/pull/6771)). Fixes vector-im/element-web#18961 and vector-im/element-web#18961.
* Use cursor:pointer on space panel buttons ([\#6770](https://github.com/matrix-org/matrix-react-sdk/pull/6770)). Fixes vector-im/element-web#18951 and vector-im/element-web#18951.
* Fix regressed tab view buttons in space update toast ([\#6761](https://github.com/matrix-org/matrix-react-sdk/pull/6761)). Fixes vector-im/element-web#18781 and vector-im/element-web#18781.
Changes in [3.31.0-rc.2](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0-rc.2) (2021-09-22)
=============================================================================================================
## 🐛 Bug Fixes
* Fix spacing for message composer buttons ([\#6854](https://github.com/matrix-org/matrix-react-sdk/pull/6854)).
Changes in [3.31.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0-rc.1) (2021-09-21)
=============================================================================================================
## ✨ Features
* Say Joining space instead of Joining room where we know its a space ([\#6818](https://github.com/matrix-org/matrix-react-sdk/pull/6818)). Fixes vector-im/element-web#19064 and vector-im/element-web#19064.
* Add warning that some spaces may not be relinked to the newly upgraded room ([\#6805](https://github.com/matrix-org/matrix-react-sdk/pull/6805)). Fixes vector-im/element-web#18858 and vector-im/element-web#18858.
* Delabs Spaces, iterate some copy and move communities/space toggle to preferences ([\#6594](https://github.com/matrix-org/matrix-react-sdk/pull/6594)). Fixes vector-im/element-web#18088, vector-im/element-web#18524 vector-im/element-web#18088 and vector-im/element-web#18088.
* Show "Message" in the user info panel instead of "Start chat" ([\#6319](https://github.com/matrix-org/matrix-react-sdk/pull/6319)). Fixes vector-im/element-web#17877 and vector-im/element-web#17877. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix space keyboard shortcuts conflicting with native zoom shortcuts ([\#6804](https://github.com/matrix-org/matrix-react-sdk/pull/6804)).
* Replace plain text emoji at the end of a line ([\#6784](https://github.com/matrix-org/matrix-react-sdk/pull/6784)). Fixes vector-im/element-web#18833 and vector-im/element-web#18833. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Simplify Space Panel layout and fix some edge cases ([\#6800](https://github.com/matrix-org/matrix-react-sdk/pull/6800)). Fixes vector-im/element-web#18694 and vector-im/element-web#18694.
* Show unsent message warning on Space Panel buttons ([\#6778](https://github.com/matrix-org/matrix-react-sdk/pull/6778)). Fixes vector-im/element-web#18891 and vector-im/element-web#18891.
* Hide mute/unmute button in UserInfo for Spaces as it makes no sense ([\#6790](https://github.com/matrix-org/matrix-react-sdk/pull/6790)). Fixes vector-im/element-web#19007 and vector-im/element-web#19007.
* Fix automatic field population in space create menu not validating ([\#6792](https://github.com/matrix-org/matrix-react-sdk/pull/6792)). Fixes vector-im/element-web#19005 and vector-im/element-web#19005.
* Optimize input label transition on focus ([\#6783](https://github.com/matrix-org/matrix-react-sdk/pull/6783)). Fixes vector-im/element-web#12876 and vector-im/element-web#12876. Contributed by [MadLittleMods](https://github.com/MadLittleMods).
* Adapt and re-use the RolesRoomSettingsTab for Spaces ([\#6779](https://github.com/matrix-org/matrix-react-sdk/pull/6779)). Fixes vector-im/element-web#18908 vector-im/element-web#18909 and vector-im/element-web#18908.
* Deduplicate join rule management between rooms and spaces ([\#6724](https://github.com/matrix-org/matrix-react-sdk/pull/6724)). Fixes vector-im/element-web#18798 and vector-im/element-web#18798.
* Add config option to turn on in-room event sending timing metrics ([\#6766](https://github.com/matrix-org/matrix-react-sdk/pull/6766)).
* Improve the upgrade for restricted user experience ([\#6764](https://github.com/matrix-org/matrix-react-sdk/pull/6764)). Fixes vector-im/element-web#18677 and vector-im/element-web#18677.
* Improve tooltips on space quick actions and explore button ([\#6760](https://github.com/matrix-org/matrix-react-sdk/pull/6760)). Fixes vector-im/element-web#18528 and vector-im/element-web#18528.
* Make space members and user info behave more expectedly ([\#6765](https://github.com/matrix-org/matrix-react-sdk/pull/6765)). Fixes vector-im/element-web#17018 and vector-im/element-web#17018.
* hide no-op m.room.encryption events and better word param changes ([\#6747](https://github.com/matrix-org/matrix-react-sdk/pull/6747)). Fixes vector-im/element-web#18597 and vector-im/element-web#18597.
* Respect m.space.parent relations if they hold valid permissions ([\#6746](https://github.com/matrix-org/matrix-react-sdk/pull/6746)). Fixes vector-im/element-web#10935 and vector-im/element-web#10935.
* Space panel accessibility improvements ([\#6744](https://github.com/matrix-org/matrix-react-sdk/pull/6744)). Fixes vector-im/element-web#18892 and vector-im/element-web#18892.
## 🐛 Bug Fixes
* Revert Firefox composer deletion hacks ([\#6844](https://github.com/matrix-org/matrix-react-sdk/pull/6844)). Fixes vector-im/element-web#19103 and vector-im/element-web#19103. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix accessing field on oobData which may be undefined ([\#6830](https://github.com/matrix-org/matrix-react-sdk/pull/6830)). Fixes vector-im/element-web#19085 and vector-im/element-web#19085.
* Fix pill deletion on Firefox 78 ([\#6832](https://github.com/matrix-org/matrix-react-sdk/pull/6832)). Fixes vector-im/element-web#19077 and vector-im/element-web#19077. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix reactions aria-label not being a string and thus being read as [Object object] ([\#6828](https://github.com/matrix-org/matrix-react-sdk/pull/6828)).
* Fix missing null guard in space hierarchy pagination ([\#6821](https://github.com/matrix-org/matrix-react-sdk/pull/6821)). Fixes matrix-org/element-web-rageshakes#6299 and matrix-org/element-web-rageshakes#6299.
* Fix checks to show prompt to start new chats ([\#6812](https://github.com/matrix-org/matrix-react-sdk/pull/6812)).
* Fix room list scroll jumps ([\#6777](https://github.com/matrix-org/matrix-react-sdk/pull/6777)). Fixes vector-im/element-web#17460 vector-im/element-web#18440 and vector-im/element-web#17460. Contributed by [robintown](https://github.com/robintown).
* Fix various message bubble alignment issues ([\#6785](https://github.com/matrix-org/matrix-react-sdk/pull/6785)). Fixes vector-im/element-web#18293, vector-im/element-web#18294 vector-im/element-web#18305 and vector-im/element-web#18293. Contributed by [robintown](https://github.com/robintown).
* Make message bubble font size consistent ([\#6795](https://github.com/matrix-org/matrix-react-sdk/pull/6795)). Contributed by [robintown](https://github.com/robintown).
* Fix edge cases around joining new room which does not belong to active space ([\#6797](https://github.com/matrix-org/matrix-react-sdk/pull/6797)). Fixes vector-im/element-web#19025 and vector-im/element-web#19025.
* Fix edge case space issues around creation and initial view ([\#6798](https://github.com/matrix-org/matrix-react-sdk/pull/6798)). Fixes vector-im/element-web#19023 and vector-im/element-web#19023.
* Stop spinner on space preview if the join fails ([\#6803](https://github.com/matrix-org/matrix-react-sdk/pull/6803)). Fixes vector-im/element-web#19034 and vector-im/element-web#19034.
* Fix emoji picker and stickerpicker not appearing correctly when opened ([\#6793](https://github.com/matrix-org/matrix-react-sdk/pull/6793)). Fixes vector-im/element-web#19012 and vector-im/element-web#19012. Contributed by [Palid](https://github.com/Palid).
* Fix autocomplete not having y-scroll ([\#6794](https://github.com/matrix-org/matrix-react-sdk/pull/6794)). Fixes vector-im/element-web#18997 and vector-im/element-web#18997. Contributed by [Palid](https://github.com/Palid).
* Fix broken edge case with public space creation with no alias ([\#6791](https://github.com/matrix-org/matrix-react-sdk/pull/6791)). Fixes vector-im/element-web#19003 and vector-im/element-web#19003.
* Redirect from /#/welcome to /#/home if already logged in ([\#6786](https://github.com/matrix-org/matrix-react-sdk/pull/6786)). Fixes vector-im/element-web#18990 and vector-im/element-web#18990. Contributed by [aaronraimist](https://github.com/aaronraimist).
* Fix build issues from two conflicting PRs landing without merge conflict ([\#6780](https://github.com/matrix-org/matrix-react-sdk/pull/6780)).
* Render guest settings only in public rooms/spaces ([\#6693](https://github.com/matrix-org/matrix-react-sdk/pull/6693)). Fixes vector-im/element-web#18776 and vector-im/element-web#18776. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix message bubble corners being wrong in the presence of hidden events ([\#6776](https://github.com/matrix-org/matrix-react-sdk/pull/6776)). Fixes vector-im/element-web#18124 and vector-im/element-web#18124. Contributed by [robintown](https://github.com/robintown).
* Debounce read marker update on scroll ([\#6771](https://github.com/matrix-org/matrix-react-sdk/pull/6771)). Fixes vector-im/element-web#18961 and vector-im/element-web#18961.
* Use cursor:pointer on space panel buttons ([\#6770](https://github.com/matrix-org/matrix-react-sdk/pull/6770)). Fixes vector-im/element-web#18951 and vector-im/element-web#18951.
* Fix regressed tab view buttons in space update toast ([\#6761](https://github.com/matrix-org/matrix-react-sdk/pull/6761)). Fixes vector-im/element-web#18781 and vector-im/element-web#18781.
Changes in [3.30.0](https://github.com/vector-im/element-desktop/releases/tag/v3.30.0) (2021-09-14)
===================================================================================================

View File

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "3.30.0",
"version": "3.31.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {

View File

@ -89,7 +89,6 @@ limitations under the License.
margin: 0px auto;
overflow: auto;
flex: 0 0 auto;
}
.mx_RoomView_auxPanel_fullHeight {

View File

@ -59,3 +59,14 @@ limitations under the License.
border-left-color: $username-variant8-color;
}
}
.mx_ReplyThread--expanded {
.mx_EventTile_body {
display: block;
overflow-y: scroll !important;
}
.mx_EventTile_collapsedCodeBlock {
// !important needed due to .mx_ReplyTile .mx_EventTile_content .mx_EventTile_pre_container > pre
display: block !important;
}
}

View File

@ -18,7 +18,7 @@ a.mx_Pill {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: calc(100% - 1ch);
max-width: 100%;
}
.mx_Pill {

View File

@ -117,6 +117,16 @@ limitations under the License.
mask-image: url('$(res)/img/download.svg');
}
.mx_MessageActionBar_expandMessageButton::after {
mask-size: 12px;
mask-image: url('$(res)/img/element-icons/expand-message.svg');
}
.mx_MessageActionBar_collapseMessageButton::after {
mask-size: 12px;
mask-image: url('$(res)/img/element-icons/collapse-message.svg');
}
.mx_MessageActionBar_downloadButton.mx_MessageActionBar_downloadSpinnerButton::after {
background-color: transparent; // hide the download icon mask
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 11 14"><defs/><path fill="#737D8C" fill-rule="evenodd" d="M.2192.234A.753.753 0 011.2815.2321l3.7243 3.7003L8.7181.2202A.753.753 0 019.7805.2185a.747.747 0 01.0017 1.0589L5.5396 5.52a.753.753 0 01-1.0624.0018L.221 1.2928A.747.747 0 01.2192.234zM9.7822 13.7663a.7529.7529 0 01-1.0623.0017l-3.7243-3.7003L1.2833 13.78a.753.753 0 01-1.0624.0018.7471.7471 0 01-.0017-1.059l4.2426-4.2426a.753.753 0 011.0624-.0017l4.2563 4.2289a.747.747 0 01.0017 1.0589z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 543 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 11 14"><defs/><path fill="#17191C" fill-rule="evenodd" d="M.2192 8.494a.753.753 0 011.0623-.0018l3.7243 3.7003 3.7123-3.7123a.753.753 0 011.0624-.0017.747.747 0 01.0017 1.059L5.5396 13.78a.753.753 0 01-1.0624.0018L.221 9.5528A.747.747 0 01.2192 8.494zM9.7822 5.5063A.753.753 0 018.72 5.508L4.9956 1.8077 1.2833 5.52a.753.753 0 01-1.0624.0018.747.747 0 01-.0017-1.059L4.4618.2202A.753.753 0 015.5242.2185l4.2563 4.2289a.747.747 0 01.0017 1.0589z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@ -49,6 +49,8 @@ import PerformanceMonitor from "../performance";
import UIStore from "../stores/UIStore";
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
/* eslint-disable @typescript-eslint/naming-convention */
@ -92,6 +94,7 @@ declare global {
mxUIStore: UIStore;
mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore;
mxActiveWidgetStore?: ActiveWidgetStore;
mxOnRecaptchaLoaded?: () => void;
electron?: Electron;
}
@ -223,6 +226,15 @@ declare global {
) => string;
isReady: () => boolean;
};
// eslint-disable-next-line no-var, camelcase
var mx_rage_logger: ConsoleLogger;
// eslint-disable-next-line no-var, camelcase
var mx_rage_initPromise: Promise<void>;
// eslint-disable-next-line no-var, camelcase
var mx_rage_initStoragePromise: Promise<void>;
// eslint-disable-next-line no-var, camelcase
var mx_rage_store: IndexedDBLogStore;
}
/* eslint-enable @typescript-eslint/naming-convention */

View File

@ -786,7 +786,7 @@ async function startMatrixClient(startSyncing = true): Promise<void> {
UserActivity.sharedInstance().start();
DMRoomMap.makeShared().start();
IntegrationManagers.sharedInstance().startWatching();
ActiveWidgetStore.start();
ActiveWidgetStore.instance.start();
CallHandler.sharedInstance().start();
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
@ -892,7 +892,7 @@ export function stopMatrixClient(unsetClient = true): void {
UserActivity.sharedInstance().stop();
TypingStore.sharedInstance().reset();
Presence.stop();
ActiveWidgetStore.stop();
ActiveWidgetStore.instance.stop();
IntegrationManagers.sharedInstance().stopWatching();
Mjolnir.sharedInstance().stop();
DeviceListener.sharedInstance().stop();

View File

@ -45,7 +45,7 @@ function getOrCreateContainer(): HTMLDivElement {
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
interface IPosition {
export interface IPosition {
top?: number;
bottom?: number;
left?: number;
@ -430,7 +430,11 @@ export type AboveLeftOf = IPosition & {
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect,
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0): AboveLeftOf => {
export const aboveLeftOf = (
elementRect: DOMRect,
chevronFace = ChevronFace.None,
vPadding = 0,
): AboveLeftOf => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonRight = elementRect.right + window.pageXOffset;

View File

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { User } from "matrix-js-sdk/src/models/user";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
@ -59,7 +58,7 @@ import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPan
interface IProps {
room?: Room; // if showing panels for a given room, this is set
groupId?: string; // if showing panels for a given group, this is set
user?: User; // used if we know the user ahead of opening the panel
member?: RoomMember; // used if we know the room member ahead of opening the panel
resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
@ -100,10 +99,10 @@ export default class RightPanel extends React.Component<IProps, IState> {
// Helper function to split out the logic for getPhaseFromProps() and the constructor
// as both are called at the same time in the constructor.
private getUserForPanel() {
private getUserForPanel(): RoomMember {
if (this.state && this.state.member) return this.state.member;
const lastParams = RightPanelStore.getSharedInstance().roomPanelPhaseParams;
return this.props.user || lastParams['member'];
return this.props.member || lastParams['member'];
}
// gets the current phase from the props and also maybe the store
@ -225,7 +224,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
// XXX: There are three different ways of 'closing' this panel depending on what state
// things are in... this knows far more than it should do about the state of the rest
// of the app and is generally a bit silly.
if (this.props.user) {
if (this.props.member) {
// If we have a user prop then we're displaying a user from the 'user' page type
// in LoggedInView, so need to change the page type to close the panel (we switch
// to the home page which is not obviously the correct thing to do, but I'm not sure

View File

@ -78,7 +78,6 @@ import { objectHasDiff } from "../../utils/objects";
import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
import EditorStateTransfer from "../../utils/EditorStateTransfer";
import { throttle } from "lodash";
import ErrorDialog from '../views/dialogs/ErrorDialog';
@ -158,7 +157,6 @@ export interface IState {
// used by componentDidUpdate to avoid unnecessary checks
atEndOfLiveTimelineInit: boolean;
showTopUnreadMessagesBar: boolean;
auxPanelMaxHeight?: number;
statusBarVisible: boolean;
// We load this later by asking the js-sdk to suggest a version for us.
// This object is the result of Room#getRecommendedVersion()
@ -565,10 +563,6 @@ export default class RoomView extends React.Component<IProps, IState> {
});
window.addEventListener('beforeunload', this.onPageUnload);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
}
this.onResize();
}
shouldComponentUpdate(nextProps, nextState) {
@ -656,9 +650,6 @@ export default class RoomView extends React.Component<IProps, IState> {
}
window.removeEventListener('beforeunload', this.onPageUnload);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
}
// Remove RoomStore listener
if (this.roomStoreToken) {
@ -1619,28 +1610,6 @@ export default class RoomView extends React.Component<IProps, IState> {
};
}
private onResize = () => {
// It seems flexbox doesn't give us a way to constrain the auxPanel height to have
// a minimum of the height of the video element, whilst also capping it from pushing out the page
// so we have to do it via JS instead. In this implementation we cap the height by putting
// a maxHeight on the underlying remote video tag.
// header + footer + status + give us at least 120px of scrollback at all times.
let auxPanelMaxHeight = UIStore.instance.windowHeight -
(54 + // height of RoomHeader
36 + // height of the status area
51 + // minimum height of the message composer
120); // amount of desired scrollback
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
// but it's better than the video going missing entirely
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
if (this.state.auxPanelMaxHeight !== auxPanelMaxHeight) {
this.setState({ auxPanelMaxHeight });
}
};
private onStatusBarVisible = () => {
if (this.unmounted || this.state.statusBarVisible) return;
this.setState({ statusBarVisible: true });
@ -1941,11 +1910,8 @@ export default class RoomView extends React.Component<IProps, IState> {
const auxPanel = (
<AuxPanel
room={this.state.room}
fullHeight={false}
userId={this.context.credentials.userId}
maxHeight={this.state.auxPanelMaxHeight}
showApps={this.state.showApps}
onResize={this.onResize}
resizeNotifier={this.props.resizeNotifier}
>
{ aux }

View File

@ -86,8 +86,8 @@ export default class UserView extends React.Component<IProps, IState> {
public render(): JSX.Element {
if (this.state.loading) {
return <Spinner />;
} else if (this.state.member?.user) {
const panel = <RightPanel user={this.state.member.user} resizeNotifier={this.props.resizeNotifier} />;
} else if (this.state.member) {
const panel = <RightPanel member={this.state.member} resizeNotifier={this.props.resizeNotifier} />;
return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}>
<HomePage />
</MainSplit>);

View File

@ -38,6 +38,7 @@ import ConfirmRedactDialog from '../dialogs/ConfirmRedactDialog';
import ErrorDialog from '../dialogs/ErrorDialog';
import ShareDialog from '../dialogs/ShareDialog';
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { IPosition, ChevronFace } from '../../structures/ContextMenu';
export function canCancel(eventStatus: EventStatus): boolean {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@ -52,7 +53,8 @@ export interface IOperableEventTile {
getEventTileOps(): IEventTileOps;
}
interface IProps {
interface IProps extends IPosition {
chevronFace: ChevronFace;
/* the MatrixEvent associated with the context menu */
mxEvent: MatrixEvent;
/* an optional EventTileOps implementation that can be used to unhide preview widgets */

View File

@ -131,8 +131,13 @@ interface IProps {
}
const isOnlyAdmin = (room: Room): boolean => {
return !room.getJoinedMembers().some(member => {
return member.userId !== room.client.credentials.userId && member.powerLevelNorm === 100;
const userId = room.client.getUserId();
if (room.getMember(userId).powerLevelNorm !== 100) {
return false; // user is not an admin
}
return room.getJoinedMembers().every(member => {
// return true if every other member has a lower power level (we are highest)
return member.userId === userId || member.powerLevelNorm < 100;
});
};

View File

@ -163,7 +163,7 @@ export default class AppTile extends React.Component<IProps, IState> {
if (this.state.hasPermissionToLoad && !hasPermissionToLoad) {
// Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this.persistKey);
if (this.sgWidget) this.sgWidget.stop();
}
@ -198,8 +198,8 @@ export default class AppTile extends React.Component<IProps, IState> {
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
// if it's not remaining on screen, get rid of the PersistedElement container
if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
if (!ActiveWidgetStore.instance.getWidgetPersistence(this.props.app.id)) {
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
PersistedElement.destroyElement(this.persistKey);
}
@ -282,7 +282,7 @@ export default class AppTile extends React.Component<IProps, IState> {
// Delete the widget from the persisted store for good measure.
PersistedElement.destroyElement(this.persistKey);
ActiveWidgetStore.destroyPersistentWidget(this.props.app.id);
ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id);
if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true });
}

View File

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import RoomViewStore from '../../../stores/RoomViewStore';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore';
import WidgetUtils from '../../../utils/WidgetUtils';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -39,13 +39,13 @@ export default class PersistentApp extends React.Component<{}, IState> {
this.state = {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
};
}
public componentDidMount(): void {
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this.onActiveWidgetStoreUpdate);
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate);
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
}
@ -53,7 +53,7 @@ export default class PersistentApp extends React.Component<{}, IState> {
if (this.roomStoreToken) {
this.roomStoreToken.remove();
}
ActiveWidgetStore.removeListener('update', this.onActiveWidgetStoreUpdate);
ActiveWidgetStore.instance.removeListener(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership);
}
@ -68,23 +68,23 @@ export default class PersistentApp extends React.Component<{}, IState> {
private onActiveWidgetStoreUpdate = (): void => {
this.setState({
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(),
});
};
private onMyMembership = async (room: Room, membership: string): Promise<void> => {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
if (membership !== "join") {
// we're not in the room anymore - delete
if (room .roomId === persistentWidgetInRoomId) {
ActiveWidgetStore.destroyPersistentWidget(this.state.persistentWidgetId);
ActiveWidgetStore.instance.destroyPersistentWidget(this.state.persistentWidgetId);
}
}
};
public render(): JSX.Element {
if (this.state.persistentWidgetId) {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId);
const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId);
@ -96,7 +96,7 @@ export default class PersistentApp extends React.Component<{}, IState> {
if (this.state.roomId !== persistentWidgetInRoomId && myMembership === "join") {
// get the widget data
const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => {
return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId();
return ev.getStateKey() === ActiveWidgetStore.instance.getPersistentWidgetId();
});
const app = WidgetUtils.makeAppConfig(
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),

View File

@ -16,6 +16,8 @@ limitations under the License.
*/
import React from 'react';
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
@ -35,6 +37,12 @@ import ReplyTile from "../rooms/ReplyTile";
import Pill from './Pill';
import { Room } from 'matrix-js-sdk/src/models/room';
/**
* This number is based on the previous behavior - if we have message of height
* over 60px then we want to show button that will allow to expand it.
*/
const SHOW_EXPAND_QUOTE_PIXELS = 60;
interface IProps {
// the latest event in this chain of replies
parentEv?: MatrixEvent;
@ -45,6 +53,8 @@ interface IProps {
layout?: Layout;
// Whether to always show a timestamp
alwaysShowTimestamps?: boolean;
isQuoteExpanded?: boolean;
setQuoteExpanded: (isExpanded: boolean) => void;
}
interface IState {
@ -66,6 +76,7 @@ export default class ReplyThread extends React.Component<IProps, IState> {
static contextType = MatrixClientContext;
private unmounted = false;
private room: Room;
private blockquoteRef = React.createRef<HTMLElement>();
constructor(props, context) {
super(props, context);
@ -80,7 +91,7 @@ export default class ReplyThread extends React.Component<IProps, IState> {
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
}
public static getParentEventId(ev: MatrixEvent): string {
public static getParentEventId(ev: MatrixEvent): string | undefined {
if (!ev || ev.isRedacted()) return;
// XXX: For newer relations (annotations, replacements, etc.), we now
@ -137,7 +148,7 @@ export default class ReplyThread extends React.Component<IProps, IState> {
public static getNestedReplyText(
ev: MatrixEvent,
permalinkCreator: RoomPermalinkCreator,
): { body: string, html: string } {
): { body: string, html: string } | null {
if (!ev) return null;
let { body, formatted_body: html } = ev.getContent();
@ -237,37 +248,38 @@ export default class ReplyThread extends React.Component<IProps, IState> {
return replyMixin;
}
public static makeThread(
parentEv: MatrixEvent,
onHeightChanged: () => void,
permalinkCreator: RoomPermalinkCreator,
ref: React.RefObject<ReplyThread>,
layout: Layout,
alwaysShowTimestamps: boolean,
): JSX.Element {
if (!ReplyThread.getParentEventId(parentEv)) return null;
return <ReplyThread
parentEv={parentEv}
onHeightChanged={onHeightChanged}
ref={ref}
permalinkCreator={permalinkCreator}
layout={layout}
alwaysShowTimestamps={alwaysShowTimestamps}
/>;
public static hasThreadReply(event: MatrixEvent) {
return Boolean(ReplyThread.getParentEventId(event));
}
componentDidMount() {
this.initialize();
this.trySetExpandableQuotes();
}
componentDidUpdate() {
this.props.onHeightChanged();
this.trySetExpandableQuotes();
}
componentWillUnmount() {
this.unmounted = true;
}
private trySetExpandableQuotes() {
if (this.props.isQuoteExpanded === undefined && this.blockquoteRef.current) {
const el: HTMLElement | null = this.blockquoteRef.current.querySelector('.mx_EventTile_body');
if (el) {
const code: HTMLElement | null = el.querySelector('code');
const isCodeEllipsisShown = code ? code.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS : false;
const isElipsisShown = el.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS || isCodeEllipsisShown;
if (isElipsisShown) {
this.props.setQuoteExpanded(false);
}
}
}
}
private async initialize(): Promise<void> {
const { parentEv } = this.props;
// at time of making this component we checked that props.parentEv has a parentEventId
@ -321,7 +333,7 @@ export default class ReplyThread extends React.Component<IProps, IState> {
this.initialize();
};
private onQuoteClick = async (): Promise<void> => {
private onQuoteClick = async (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>): Promise<void> => {
const events = [this.state.loadedEv, ...this.state.events];
let loadedEv = null;
@ -373,14 +385,26 @@ export default class ReplyThread extends React.Component<IProps, IState> {
header = <Spinner w={16} h={16} />;
}
const { isQuoteExpanded } = this.props;
const evTiles = this.state.events.map((ev) => {
return <blockquote className={`mx_ReplyThread ${this.getReplyThreadColorClass(ev)}`} key={ev.getId()}>
<ReplyTile
mxEvent={ev}
onHeightChanged={this.props.onHeightChanged}
permalinkCreator={this.props.permalinkCreator}
/>
</blockquote>;
const classname = classNames({
'mx_ReplyThread': true,
[this.getReplyThreadColorClass(ev)]: true,
// We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false
'mx_ReplyThread--expanded': isQuoteExpanded === true,
// We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false
'mx_ReplyThread--collapsed': isQuoteExpanded === false,
});
return (
<blockquote ref={this.blockquoteRef} className={classname} key={ev.getId()}>
<ReplyTile
mxEvent={ev}
onHeightChanged={this.props.onHeightChanged}
permalinkCreator={this.props.permalinkCreator}
toggleExpandedQuote={() => this.props.setQuoteExpanded(!this.props.isQuoteExpanded)}
/>
</blockquote>
);
});
return <div className="mx_ReplyThread_wrapper">

View File

@ -15,107 +15,112 @@ limitations under the License.
*/
import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import * as HtmlUtils from '../../../HtmlUtils';
import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils';
import { formatTime } from '../../../DateUtils';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import classNames from 'classnames';
import RedactedBody from "./RedactedBody";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import AccessibleButton from "../elements/AccessibleButton";
import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog";
import ViewSource from "../../structures/ViewSource";
function getReplacedContent(event) {
const originalContent = event.getOriginalContent();
return originalContent["m.new_content"] || originalContent;
}
@replaceableComponent("views.messages.EditHistoryMessage")
export default class EditHistoryMessage extends React.PureComponent {
static propTypes = {
// the message event being edited
mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired,
previousEdit: PropTypes.instanceOf(MatrixEvent),
isBaseEvent: PropTypes.bool,
};
interface IProps {
// the message event being edited
mxEvent: MatrixEvent;
previousEdit?: MatrixEvent;
isBaseEvent?: boolean;
isTwelveHour?: boolean;
}
constructor(props) {
interface IState {
canRedact: boolean;
sendStatus: EventStatus;
}
@replaceableComponent("views.messages.EditHistoryMessage")
export default class EditHistoryMessage extends React.PureComponent<IProps, IState> {
private content = createRef<HTMLDivElement>();
private pills: Element[] = [];
constructor(props: IProps) {
super(props);
const cli = MatrixClientPeg.get();
const { userId } = cli.credentials;
const event = this.props.mxEvent;
const room = cli.getRoom(event.getRoomId());
if (event.localRedactionEvent()) {
event.localRedactionEvent().on("status", this._onAssociatedStatusChanged);
event.localRedactionEvent().on("status", this.onAssociatedStatusChanged);
}
const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
this.state = { canRedact, sendStatus: event.getAssociatedStatus() };
this._content = createRef();
this._pills = [];
}
_onAssociatedStatusChanged = () => {
private onAssociatedStatusChanged = (): void => {
this.setState({ sendStatus: this.props.mxEvent.getAssociatedStatus() });
};
_onRedactClick = async () => {
private onRedactClick = async (): Promise<void> => {
const event = this.props.mxEvent;
const cli = MatrixClientPeg.get();
const ConfirmAndWaitRedactDialog = sdk.getComponent("dialogs.ConfirmAndWaitRedactDialog");
Modal.createTrackedDialog('Confirm Redact Dialog', 'Edit history', ConfirmAndWaitRedactDialog, {
redact: () => cli.redactEvent(event.getRoomId(), event.getId()),
}, 'mx_Dialog_confirmredact');
};
_onViewSourceClick = () => {
const ViewSource = sdk.getComponent('structures.ViewSource');
private onViewSourceClick = (): void => {
Modal.createTrackedDialog('View Event Source', 'Edit history', ViewSource, {
mxEvent: this.props.mxEvent,
}, 'mx_Dialog_viewsource');
};
pillifyLinks() {
private pillifyLinks(): void {
// not present for redacted events
if (this._content.current) {
pillifyLinks(this._content.current.children, this.props.mxEvent, this._pills);
if (this.content.current) {
pillifyLinks(this.content.current.children, this.props.mxEvent, this.pills);
}
}
componentDidMount() {
public componentDidMount(): void {
this.pillifyLinks();
}
componentWillUnmount() {
unmountPills(this._pills);
public componentWillUnmount(): void {
unmountPills(this.pills);
const event = this.props.mxEvent;
if (event.localRedactionEvent()) {
event.localRedactionEvent().off("status", this._onAssociatedStatusChanged);
event.localRedactionEvent().off("status", this.onAssociatedStatusChanged);
}
}
componentDidUpdate() {
public componentDidUpdate(): void {
this.pillifyLinks();
}
_renderActionBar() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
private renderActionBar(): JSX.Element {
// hide the button when already redacted
let redactButton;
if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) {
redactButton = (
<AccessibleButton onClick={this._onRedactClick}>
<AccessibleButton onClick={this.onRedactClick}>
{ _t("Remove") }
</AccessibleButton>
);
}
const viewSourceButton = (
<AccessibleButton onClick={this._onViewSourceClick}>
<AccessibleButton onClick={this.onViewSourceClick}>
{ _t("View Source") }
</AccessibleButton>
);
@ -128,7 +133,7 @@ export default class EditHistoryMessage extends React.PureComponent {
);
}
render() {
public render(): JSX.Element {
const { mxEvent } = this.props;
const content = getReplacedContent(mxEvent);
let contentContainer;
@ -139,18 +144,22 @@ export default class EditHistoryMessage extends React.PureComponent {
if (this.props.previousEdit) {
contentElements = editBodyDiffToHtml(getReplacedContent(this.props.previousEdit), content);
} else {
contentElements = HtmlUtils.bodyToHtml(content, null, { stripReplyFallback: true });
contentElements = HtmlUtils.bodyToHtml(
content,
null,
{ stripReplyFallback: true, returnString: false },
);
}
if (mxEvent.getContent().msgtype === "m.emote") {
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
contentContainer = (
<div className="mx_EventTile_content" ref={this._content}>*&nbsp;
<div className="mx_EventTile_content" ref={this.content}>*&nbsp;
<span className="mx_MEmoteBody_sender">{ name }</span>
&nbsp;{ contentElements }
</div>
);
} else {
contentContainer = <div className="mx_EventTile_content" ref={this._content}>{ contentElements }</div>;
contentContainer = <div className="mx_EventTile_content" ref={this.content}>{ contentElements }</div>;
}
}
@ -167,7 +176,7 @@ export default class EditHistoryMessage extends React.PureComponent {
<div className="mx_EventTile_line">
<span className="mx_MessageTimestamp">{ timestamp }</span>
{ contentContainer }
{ this._renderActionBar() }
{ this.renderActionBar() }
</div>
</div>
</li>

View File

@ -16,44 +16,50 @@ limitations under the License.
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import { getNameForEventRoom, userLabelForEventRoom }
from '../../../utils/KeyVerificationStateObserver';
import { getNameForEventRoom, userLabelForEventRoom } from '../../../utils/KeyVerificationStateObserver';
import EventTileBubble from "./EventTileBubble";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { EventType } from "matrix-js-sdk/src/@types/event";
interface IProps {
/* the MatrixEvent to show */
mxEvent: MatrixEvent;
}
@replaceableComponent("views.messages.MKeyVerificationConclusion")
export default class MKeyVerificationConclusion extends React.Component {
constructor(props) {
export default class MKeyVerificationConclusion extends React.Component<IProps> {
constructor(props: IProps) {
super(props);
}
componentDidMount() {
public componentDidMount(): void {
const request = this.props.mxEvent.verificationRequest;
if (request) {
request.on("change", this._onRequestChanged);
request.on("change", this.onRequestChanged);
}
MatrixClientPeg.get().on("userTrustStatusChanged", this._onTrustChanged);
MatrixClientPeg.get().on("userTrustStatusChanged", this.onTrustChanged);
}
componentWillUnmount() {
public componentWillUnmount(): void {
const request = this.props.mxEvent.verificationRequest;
if (request) {
request.off("change", this._onRequestChanged);
request.off("change", this.onRequestChanged);
}
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("userTrustStatusChanged", this._onTrustChanged);
cli.removeListener("userTrustStatusChanged", this.onTrustChanged);
}
}
_onRequestChanged = () => {
private onRequestChanged = (): void => {
this.forceUpdate();
};
_onTrustChanged = (userId, status) => {
private onTrustChanged = (userId: string): void => {
const { mxEvent } = this.props;
const request = mxEvent.verificationRequest;
if (!request || request.otherUserId !== userId) {
@ -62,17 +68,17 @@ export default class MKeyVerificationConclusion extends React.Component {
this.forceUpdate();
};
_shouldRender(mxEvent, request) {
public static shouldRender(mxEvent: MatrixEvent, request: VerificationRequest): boolean {
// normally should not happen
if (!request) {
return false;
}
// .cancel event that was sent after the verification finished, ignore
if (mxEvent.getType() === "m.key.verification.cancel" && !request.cancelled) {
if (mxEvent.getType() === EventType.KeyVerificationCancel && !request.cancelled) {
return false;
}
// .done event that was sent after the verification cancelled, ignore
if (mxEvent.getType() === "m.key.verification.done" && !request.done) {
if (mxEvent.getType() === EventType.KeyVerificationDone && !request.done) {
return false;
}
@ -89,11 +95,11 @@ export default class MKeyVerificationConclusion extends React.Component {
return true;
}
render() {
public render(): JSX.Element {
const { mxEvent } = this.props;
const request = mxEvent.verificationRequest;
if (!this._shouldRender(mxEvent, request)) {
if (!MKeyVerificationConclusion.shouldRender(mxEvent, request)) {
return null;
}
@ -103,15 +109,18 @@ export default class MKeyVerificationConclusion extends React.Component {
let title;
if (request.done) {
title = _t("You verified %(name)s", { name: getNameForEventRoom(request.otherUserId, mxEvent) });
title = _t(
"You verified %(name)s",
{ name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()) },
);
} else if (request.cancelled) {
const userId = request.cancellingUserId;
if (userId === myUserId) {
title = _t("You cancelled verifying %(name)s",
{ name: getNameForEventRoom(request.otherUserId, mxEvent) });
{ name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()) });
} else {
title = _t("%(name)s cancelled verifying",
{ name: getNameForEventRoom(userId, mxEvent) });
{ name: getNameForEventRoom(userId, mxEvent.getRoomId()) });
}
}
@ -129,8 +138,3 @@ export default class MKeyVerificationConclusion extends React.Component {
return null;
}
}
MKeyVerificationConclusion.propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
};

View File

@ -17,7 +17,8 @@ limitations under the License.
*/
import React, { useEffect } from 'react';
import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import type { Relations } from 'matrix-js-sdk/src/models/relations';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
@ -35,13 +36,17 @@ import Resend from "../../../Resend";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import DownloadActionButton from "./DownloadActionButton";
import MessageContextMenu from "../context_menus/MessageContextMenu";
import classNames from 'classnames';
import SettingsStore from '../../../settings/SettingsStore';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ReplyThread from '../elements/ReplyThread';
interface IOptionsButtonProps {
mxEvent: MatrixEvent;
getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
// TODO: Types
getTile: () => any | null;
getReplyThread: () => ReplyThread;
permalinkCreator: RoomPermalinkCreator;
onFocusChange: (menuDisplayed: boolean) => void;
@ -57,8 +62,6 @@ const OptionsButton: React.FC<IOptionsButtonProps> =
let contextMenu;
if (menuDisplayed) {
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
const tile = getTile && getTile();
const replyThread = getReplyThread && getReplyThread();
@ -90,7 +93,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> =
interface IReactButtonProps {
mxEvent: MatrixEvent;
reactions: any; // TODO: types
reactions: Relations;
onFocusChange: (menuDisplayed: boolean) => void;
}
@ -127,12 +130,15 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
interface IMessageActionBarProps {
mxEvent: MatrixEvent;
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions?: any; // TODO: types
reactions?: Relations;
// TODO: Types
getTile: () => any | null;
getReplyThread: () => ReplyThread | undefined;
permalinkCreator?: RoomPermalinkCreator;
getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
getReplyThread?: () => ReplyThread;
onFocusChange?: (menuDisplayed: boolean) => void;
toggleThreadExpanded: () => void;
isInThreadTimeline?: boolean;
}
@ -329,6 +335,20 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
toolbarOpts.push(cancelSendingButton);
}
if (this.props.isQuoteExpanded !== undefined && ReplyThread.hasThreadReply(this.props.mxEvent)) {
const expandClassName = classNames({
'mx_MessageActionBar_maskButton': true,
'mx_MessageActionBar_expandMessageButton': !this.props.isQuoteExpanded,
'mx_MessageActionBar_collapseMessageButton': this.props.isQuoteExpanded,
});
toolbarOpts.push(<RovingAccessibleTooltipButton
className={expandClassName}
title={this.props.isQuoteExpanded ? _t("Collapse quotes │ ⇧+click") : _t("Expand quotes │ ⇧+click")}
onClick={this.props.toggleThreadExpanded}
key="expand"
/>);
}
// The menu button should be last, so dump it there.
toolbarOpts.push(<OptionsButton
mxEvent={this.props.mxEvent}

View File

@ -15,22 +15,18 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
interface IProps {
mxEvent: MatrixEvent;
onMessageAllowed: () => void;
}
@replaceableComponent("views.messages.MjolnirBody")
export default class MjolnirBody extends React.Component {
static propTypes = {
mxEvent: PropTypes.object.isRequired,
onMessageAllowed: PropTypes.func.isRequired,
};
constructor() {
super();
}
_onAllowClick = (e) => {
export default class MjolnirBody extends React.Component<IProps> {
private onAllowClick = (e: React.MouseEvent): void => {
e.preventDefault();
e.stopPropagation();
@ -39,11 +35,11 @@ export default class MjolnirBody extends React.Component {
this.props.onMessageAllowed();
};
render() {
public render(): JSX.Element {
return (
<div className='mx_MjolnirBody'><i>{ _t(
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>",
{}, { a: (sub) => <a href="#" onClick={this._onAllowClick}>{ sub }</a> },
{}, { a: (sub) => <a href="#" onClick={this.onAllowClick}>{ sub }</a> },
) }</i></div>
);
}

View File

@ -16,13 +16,18 @@ limitations under the License.
import React, { useContext } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { formatFullDate } from "../../../DateUtils";
import SettingsStore from "../../../settings/SettingsStore";
import { IBodyProps } from "./IBodyProps";
const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
interface IProps {
mxEvent: MatrixEvent;
}
const RedactedBody = React.forwardRef<any, IProps | IBodyProps>(({ mxEvent }, ref) => {
const cli: MatrixClient = useContext(MatrixClientContext);
let text = _t("Message deleted");

View File

@ -17,23 +17,24 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
import RoomAvatar from "../avatars/RoomAvatar";
import ImageView from "../elements/ImageView";
interface IProps {
/* the MatrixEvent to show */
mxEvent: MatrixEvent;
}
@replaceableComponent("views.messages.RoomAvatarEvent")
export default class RoomAvatarEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
};
onAvatarClick = () => {
export default class RoomAvatarEvent extends React.Component<IProps> {
private onAvatarClick = (): void => {
const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent;
const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp;
@ -44,7 +45,6 @@ export default class RoomAvatarEvent extends React.Component {
roomName: room ? room.name : '',
});
const ImageView = sdk.getComponent("elements.ImageView");
const params = {
src: httpUrl,
name: text,
@ -52,10 +52,9 @@ export default class RoomAvatarEvent extends React.Component {
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
};
render() {
public render(): JSX.Element {
const ev = this.props.mxEvent;
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
return (

View File

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher/dispatcher';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
@ -24,15 +23,16 @@ import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
interface IProps {
/* the MatrixEvent to show */
mxEvent: MatrixEvent;
}
@replaceableComponent("views.messages.RoomCreate")
export default class RoomCreate extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
};
_onLinkClicked = e => {
export default class RoomCreate extends React.Component<IProps> {
private onLinkClicked = (e: React.MouseEvent): void => {
e.preventDefault();
const predecessor = this.props.mxEvent.getContent()['predecessor'];
@ -45,7 +45,7 @@ export default class RoomCreate extends React.Component {
});
};
render() {
public render(): JSX.Element {
const predecessor = this.props.mxEvent.getContent()['predecessor'];
if (predecessor === undefined) {
return <div />; // We should never have been instantiated in this case
@ -55,7 +55,7 @@ export default class RoomCreate extends React.Component {
permalinkCreator.load();
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
const link = (
<a href={predecessorPermalink} onClick={this._onLinkClicked}>
<a href={predecessorPermalink} onClick={this.onLinkClicked}>
{ _t("Click here to see older messages.") }
</a>
);

View File

@ -138,6 +138,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// If it's less than 30% we don't add the expansion button.
// We also round the number as it sometimes can be 29.99...
const percentageOfViewport = Math.round(pre.offsetHeight / UIStore.instance.windowHeight * 100);
// TODO: additionally show the button if it's an expanded quoted message
if (percentageOfViewport < 30) return;
const button = document.createElement("span");

View File

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import classNames from 'classnames';
import { lexicographicCompare } from 'matrix-js-sdk/src/utils';
import { Room } from 'matrix-js-sdk/src/models/room';
@ -35,16 +34,6 @@ interface IProps {
room: Room;
userId: string;
showApps: boolean; // Render apps
// maxHeight attribute for the aux panel and the video
// therein
maxHeight: number;
// a callback which is called when the content of the aux panel changes
// content in a way that is likely to make it change size.
onResize: () => void;
fullHeight: boolean;
resizeNotifier: ResizeNotifier;
}
@ -92,13 +81,6 @@ export default class AuxPanel extends React.Component<IProps, IState> {
return objectHasDiff(this.props, nextProps) || objectHasDiff(this.state, nextState);
}
componentDidUpdate(prevProps, prevState) {
// most changes are likely to cause a resize
if (this.props.onResize) {
this.props.onResize();
}
}
private rateLimitedUpdate = throttle(() => {
this.setState({ counters: this.computeCounters() });
}, 500, { leading: true, trailing: true });
@ -138,7 +120,6 @@ export default class AuxPanel extends React.Component<IProps, IState> {
const callView = (
<CallViewForRoom
roomId={this.props.room.roomId}
maxVideoHeight={this.props.maxHeight}
resizeNotifier={this.props.resizeNotifier}
/>
);
@ -148,7 +129,6 @@ export default class AuxPanel extends React.Component<IProps, IState> {
appsDrawer = <AppsDrawer
room={this.props.room}
userId={this.props.userId}
maxHeight={this.props.maxHeight}
showApps={this.props.showApps}
resizeNotifier={this.props.resizeNotifier}
/>;
@ -204,21 +184,12 @@ export default class AuxPanel extends React.Component<IProps, IState> {
}
}
const classes = classNames({
"mx_RoomView_auxPanel": true,
"mx_RoomView_auxPanel_fullHeight": this.props.fullHeight,
});
const style: React.CSSProperties = {};
if (!this.props.fullHeight) {
style.maxHeight = this.props.maxHeight;
}
return (
<AutoHideScrollbar className={classes} style={style}>
<AutoHideScrollbar className="mx_RoomView_auxPanel">
{ stateViews }
{ this.props.children }
{ appsDrawer }
{ callView }
{ this.props.children }
</AutoHideScrollbar>
);
}

View File

@ -58,6 +58,7 @@ import ReactionsRow from '../messages/ReactionsRow';
import { getEventDisplayInfo } from '../../../utils/EventUtils';
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import SettingsStore from "../../../settings/SettingsStore";
import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion";
const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent',
@ -144,8 +145,7 @@ export function getHandlerTile(ev) {
// XXX: This is extremely a hack. Possibly these components should have an interface for
// declining to render?
if (type === "m.key.verification.cancel" || type === "m.key.verification.done") {
const MKeyVerificationConclusion = sdk.getComponent("messages.MKeyVerificationConclusion");
if (!MKeyVerificationConclusion.prototype._shouldRender.call(null, ev, ev.request)) {
if (!MKeyVerificationConclusion.shouldRender(ev, ev.request)) {
return;
}
}
@ -323,7 +323,7 @@ interface IState {
reactions: Relations;
hover: boolean;
isQuoteExpanded?: boolean;
thread?: Thread;
}
@ -331,7 +331,8 @@ interface IState {
export default class EventTile extends React.Component<IProps, IState> {
private suppressReadReceiptAnimation: boolean;
private isListeningForReceipts: boolean;
private tile = React.createRef();
// TODO: Types
private tile = React.createRef<unknown>();
private replyThread = React.createRef<ReplyThread>();
public readonly ref = createRef<HTMLElement>();
@ -889,8 +890,8 @@ export default class EventTile extends React.Component<IProps, IState> {
actionBarFocused: focused,
});
};
getTile = () => this.tile.current;
// TODO: Types
getTile: () => any | null = () => this.tile.current;
getReplyThread = () => this.replyThread.current;
@ -915,6 +916,11 @@ export default class EventTile extends React.Component<IProps, IState> {
});
};
private setQuoteExpanded = (expanded: boolean) => {
this.setState({
isQuoteExpanded: expanded,
});
};
render() {
const msgtype = this.props.mxEvent.getContent().msgtype;
const eventType = this.props.mxEvent.getType() as EventType;
@ -924,6 +930,7 @@ export default class EventTile extends React.Component<IProps, IState> {
isInfoMessage,
isLeftAlignedBubbleMessage,
} = getEventDisplayInfo(this.props.mxEvent);
const { isQuoteExpanded } = this.state;
// This shouldn't happen: the caller should check we support this type
// before trying to instantiate us
@ -936,6 +943,7 @@ export default class EventTile extends React.Component<IProps, IState> {
</div>
</div>;
}
const EventTileType = sdk.getComponent(tileHandler);
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
@ -1057,6 +1065,7 @@ export default class EventTile extends React.Component<IProps, IState> {
getReplyThread={this.getReplyThread}
onFocusChange={this.onActionBarFocusChange}
isInThreadTimeline={isInThreadTimeline}
toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)}
/> : undefined;
const showTimestamp = this.props.mxEvent.getTs()
@ -1229,20 +1238,18 @@ export default class EventTile extends React.Component<IProps, IState> {
}
default: {
let thread;
// When the "showHiddenEventsInTimeline" lab is enabled,
// avoid showing replies for hidden events (events without tiles)
if (haveTileForEvent(this.props.mxEvent)) {
thread = ReplyThread.makeThread(
this.props.mxEvent,
this.props.onHeightChanged,
this.props.permalinkCreator,
this.replyThread,
this.props.layout,
this.props.alwaysShowTimestamps || this.state.hover,
);
}
const thread = haveTileForEvent(this.props.mxEvent) &&
ReplyThread.hasThreadReply(this.props.mxEvent) ? (
<ReplyThread
parentEv={this.props.mxEvent}
onHeightChanged={this.props.onHeightChanged}
ref={this.replyThread}
permalinkCreator={this.props.permalinkCreator}
layout={this.props.layout}
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
isQuoteExpanded={isQuoteExpanded}
setQuoteExpanded={this.setQuoteExpanded}
/>) : null;
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers

View File

@ -16,7 +16,7 @@ limitations under the License.
import React, { useContext, useEffect } from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { IPreviewUrlResponse } from "matrix-js-sdk/src/client";
import { IPreviewUrlResponse, MatrixClient } from "matrix-js-sdk/src/client";
import { useStateToggle } from "../../../hooks/useStateToggle";
import LinkPreviewWidget from "./LinkPreviewWidget";
@ -40,13 +40,7 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
const ts = mxEvent.getTs();
const previews = useAsyncMemo<[string, IPreviewUrlResponse][]>(async () => {
return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => {
try {
return [link, await cli.getUrlPreview(link, ts)];
} catch (error) {
console.error("Failed to get URL preview: " + error);
}
})).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>;
return fetchPreviews(cli, links, ts);
}, [links, ts], []);
useEffect(() => {
@ -89,4 +83,18 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
</div>;
};
const fetchPreviews = (cli: MatrixClient, links: string[], ts: number):
Promise<[string, IPreviewUrlResponse][]> => {
return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => {
try {
const preview = await cli.getUrlPreview(link, ts);
if (preview && Object.keys(preview).length > 0) {
return [link, preview];
}
} catch (error) {
console.error("Failed to get URL preview: " + error);
}
})).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>;
};
export default LinkPreviewGroup;

View File

@ -35,6 +35,7 @@ interface IProps {
highlights?: string[];
highlightLink?: string;
onHeightChanged?(): void;
toggleExpandedQuote?: () => void;
}
@replaceableComponent("views.rooms.ReplyTile")
@ -82,12 +83,17 @@ export default class ReplyTile extends React.PureComponent<IProps> {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Riot when clicked.
e.preventDefault();
dis.dispatch({
action: 'view_room',
event_id: this.props.mxEvent.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
});
// Expand thread on shift key
if (this.props.toggleExpandedQuote && e.shiftKey) {
this.props.toggleExpandedQuote();
} else {
dis.dispatch({
action: 'view_room',
event_id: this.props.mxEvent.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
});
}
}
};

View File

@ -164,6 +164,20 @@ export default class SendMessageComposer extends React.Component<IProps> {
window.addEventListener("beforeunload", this.saveStoredEditorState);
}
public componentDidUpdate(prevProps: IProps): void {
const replyToEventChanged = this.props.replyInThread && (this.props.replyToEvent !== prevProps.replyToEvent);
if (replyToEventChanged) {
this.model.reset([]);
}
if (this.props.replyInThread && this.props.replyToEvent && (!prevProps.replyToEvent || replyToEventChanged)) {
const partCreator = new CommandPartCreator(this.props.room, this.context);
const parts = this.restoreStoredEditorState(partCreator) || [];
this.model.reset(parts);
this.editorRef.current?.focus();
}
}
private onKeyDown = (event: KeyboardEvent): void => {
// ignore any keypress while doing IME compositions
if (this.editorRef.current?.isComposing(event)) {
@ -484,7 +498,12 @@ export default class SendMessageComposer extends React.Component<IProps> {
}
private get editorStateKey() {
return `mx_cider_state_${this.props.room.roomId}`;
let key = `mx_cider_state_${this.props.room.roomId}`;
const thread = this.props.replyToEvent?.getThread();
if (thread) {
key += `_${thread.id}`;
}
return key;
}
private clearStoredEditorState(): void {
@ -492,6 +511,10 @@ export default class SendMessageComposer extends React.Component<IProps> {
}
private restoreStoredEditorState(partCreator: PartCreator): Part[] {
if (this.props.replyInThread && !this.props.replyToEvent) {
return null;
}
const json = localStorage.getItem(this.editorStateKey);
if (json) {
try {

View File

@ -27,9 +27,6 @@ interface IProps {
// What room we should display the call for
roomId: string;
// maxHeight style attribute for the video panel
maxVideoHeight?: number;
resizeNotifier: ResizeNotifier;
}
@ -99,14 +96,12 @@ export default class CallViewForRoom extends React.Component<IProps, IState> {
public render() {
if (!this.state.call) return null;
// We subtract 8 as it the margin-bottom of the mx_CallViewForRoom_ResizeWrapper
const maxHeight = this.props.maxVideoHeight - 8;
return (
<div className="mx_CallViewForRoom">
<Resizable
minHeight={380}
maxHeight={maxHeight}
maxHeight="80vh"
enable={{
top: false,
right: false,

View File

@ -1944,6 +1944,8 @@
"Edit": "Edit",
"Reply": "Reply",
"Thread": "Thread",
"Collapse quotes │ ⇧+click": "Collapse quotes │ ⇧+click",
"Expand quotes │ ⇧+click": "Expand quotes │ ⇧+click",
"Message Actions": "Message Actions",
"Download %(text)s": "Download %(text)s",
"Error decrypting attachment": "Error decrypting attachment",

View File

@ -46,12 +46,10 @@ const FLUSH_RATE_MS = 30 * 1000;
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
// A class which monkey-patches the global console and stores log lines.
class ConsoleLogger {
constructor() {
this.logs = "";
}
export class ConsoleLogger {
private logs = "";
monkeyPatch(consoleObj) {
public monkeyPatch(consoleObj: Console): void {
// Monkey-patch console logging
const consoleFunctionsToLevels = {
log: "I",
@ -69,14 +67,14 @@ class ConsoleLogger {
});
}
log(level, ...args) {
private log(level: string, ...args: (Error | DOMException | object | string)[]): void {
// We don't know what locale the user may be running so use ISO strings
const ts = new Date().toISOString();
// Convert objects and errors to helpful things
args = args.map((arg) => {
if (arg instanceof DOMException) {
return arg.message + ` (${arg.name} | ${arg.code}) ` + (arg.stack ? `\n${arg.stack}` : '');
return arg.message + ` (${arg.name} | ${arg.code})`;
} else if (arg instanceof Error) {
return arg.message + (arg.stack ? `\n${arg.stack}` : '');
} else if (typeof (arg) === 'object') {
@ -118,7 +116,7 @@ class ConsoleLogger {
* @param {boolean} keepLogs True to not delete logs after flushing.
* @return {string} \n delimited log lines to flush.
*/
flush(keepLogs) {
public flush(keepLogs?: boolean): string {
// The ConsoleLogger doesn't care how these end up on disk, it just
// flushes them to the caller.
if (keepLogs) {
@ -131,27 +129,28 @@ class ConsoleLogger {
}
// A class which stores log lines in an IndexedDB instance.
class IndexedDBLogStore {
constructor(indexedDB, logger) {
this.indexedDB = indexedDB;
this.logger = logger;
this.id = "instance-" + Math.random() + Date.now();
this.index = 0;
this.db = null;
export class IndexedDBLogStore {
private id: string;
private index = 0;
private db = null;
private flushPromise = null;
private flushAgainPromise = null;
// these promises are cleared as soon as fulfilled
this.flushPromise = null;
// set if flush() is called whilst one is ongoing
this.flushAgainPromise = null;
constructor(
private indexedDB: IDBFactory,
private logger: ConsoleLogger,
) {
this.id = "instance-" + Math.random() + Date.now();
}
/**
* @return {Promise} Resolves when the store is ready.
*/
connect() {
public connect(): Promise<void> {
const req = this.indexedDB.open("logs");
return new Promise((resolve, reject) => {
req.onsuccess = (event) => {
req.onsuccess = (event: Event) => {
// @ts-ignore
this.db = event.target.result;
// Periodically flush logs to local storage / indexeddb
setInterval(this.flush.bind(this), FLUSH_RATE_MS);
@ -160,6 +159,7 @@ class IndexedDBLogStore {
req.onerror = (event) => {
const err = (
// @ts-ignore
"Failed to open log database: " + event.target.error.name
);
console.error(err);
@ -168,6 +168,7 @@ class IndexedDBLogStore {
// First time: Setup the object store
req.onupgradeneeded = (event) => {
// @ts-ignore
const db = event.target.result;
const logObjStore = db.createObjectStore("logs", {
keyPath: ["id", "index"],
@ -178,7 +179,7 @@ class IndexedDBLogStore {
logObjStore.createIndex("id", "id", { unique: false });
logObjStore.add(
this._generateLogEntry(
this.generateLogEntry(
new Date() + " ::: Log database was created.",
),
);
@ -186,7 +187,7 @@ class IndexedDBLogStore {
const lastModifiedStore = db.createObjectStore("logslastmod", {
keyPath: "id",
});
lastModifiedStore.add(this._generateLastModifiedTime());
lastModifiedStore.add(this.generateLastModifiedTime());
};
});
}
@ -210,7 +211,7 @@ class IndexedDBLogStore {
*
* @return {Promise} Resolved when the logs have been flushed.
*/
flush() {
public flush(): Promise<void> {
// check if a flush() operation is ongoing
if (this.flushPromise) {
if (this.flushAgainPromise) {
@ -227,7 +228,7 @@ class IndexedDBLogStore {
}
// there is no flush promise or there was but it has finished, so do
// a brand new one, destroying the chain which may have been built up.
this.flushPromise = new Promise((resolve, reject) => {
this.flushPromise = new Promise<void>((resolve, reject) => {
if (!this.db) {
// not connected yet or user rejected access for us to r/w to the db.
reject(new Error("No connected database"));
@ -251,9 +252,9 @@ class IndexedDBLogStore {
new Error("Failed to write logs: " + event.target.errorCode),
);
};
objStore.add(this._generateLogEntry(lines));
objStore.add(this.generateLogEntry(lines));
const lastModStore = txn.objectStore("logslastmod");
lastModStore.put(this._generateLastModifiedTime());
lastModStore.put(this.generateLastModifiedTime());
}).then(() => {
this.flushPromise = null;
});
@ -270,12 +271,12 @@ class IndexedDBLogStore {
* log ID). The objects have said log ID in an "id" field and "lines" which
* is a big string with all the new-line delimited logs.
*/
async consume() {
public async consume(): Promise<{lines: string, id: string}[]> {
const db = this.db;
// Returns: a string representing the concatenated logs for this ID.
// Stops adding log fragments when the size exceeds maxSize
function fetchLogs(id, maxSize) {
function fetchLogs(id: string, maxSize: number): Promise<string> {
const objectStore = db.transaction("logs", "readonly").objectStore("logs");
return new Promise((resolve, reject) => {
@ -301,7 +302,7 @@ class IndexedDBLogStore {
}
// Returns: A sorted array of log IDs. (newest first)
function fetchLogIds() {
function fetchLogIds(): Promise<string[]> {
// To gather all the log IDs, query for all records in logslastmod.
const o = db.transaction("logslastmod", "readonly").objectStore(
"logslastmod",
@ -319,8 +320,8 @@ class IndexedDBLogStore {
});
}
function deleteLogs(id) {
return new Promise((resolve, reject) => {
function deleteLogs(id: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
const txn = db.transaction(
["logs", "logslastmod"], "readwrite",
);
@ -389,7 +390,7 @@ class IndexedDBLogStore {
return logs;
}
_generateLogEntry(lines) {
private generateLogEntry(lines: string): {id: string, lines: string, index: number} {
return {
id: this.id,
lines: lines,
@ -397,7 +398,7 @@ class IndexedDBLogStore {
};
}
_generateLastModifiedTime() {
private generateLastModifiedTime(): {id: string, ts: number} {
return {
id: this.id,
ts: Date.now(),
@ -415,15 +416,19 @@ class IndexedDBLogStore {
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
* resultMapper.
*/
function selectQuery(store, keyRange, resultMapper) {
function selectQuery<T>(
store: IDBIndex, keyRange: IDBKeyRange, resultMapper: (cursor: IDBCursorWithValue) => T,
): Promise<T[]> {
const query = store.openCursor(keyRange);
return new Promise((resolve, reject) => {
const results = [];
query.onerror = (event) => {
// @ts-ignore
reject(new Error("Query failed: " + event.target.errorCode));
};
// collect results
query.onsuccess = (event) => {
// @ts-ignore
const cursor = event.target.result;
if (!cursor) {
resolve(results);
@ -442,7 +447,7 @@ function selectQuery(store, keyRange, resultMapper) {
* be set up immediately for the logs.
* @return {Promise} Resolves when set up.
*/
export function init(setUpPersistence = true) {
export function init(setUpPersistence = true): Promise<void> {
if (global.mx_rage_initPromise) {
return global.mx_rage_initPromise;
}
@ -462,7 +467,7 @@ export function init(setUpPersistence = true) {
* then this no-ops.
* @return {Promise} Resolves when complete.
*/
export function tryInitStorage() {
export function tryInitStorage(): Promise<void> {
if (global.mx_rage_initStoragePromise) {
return global.mx_rage_initStoragePromise;
}

View File

@ -1,110 +0,0 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from 'events';
import { MatrixClientPeg } from '../MatrixClientPeg';
import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore";
/**
* Stores information about the widgets active in the app right now:
* * What widget is set to remain always-on-screen, if any
* Only one widget may be 'always on screen' at any one time.
* * Negotiated capabilities for active apps
*/
class ActiveWidgetStore extends EventEmitter {
constructor() {
super();
this._persistentWidgetId = null;
// What room ID each widget is associated with (if it's a room widget)
this._roomIdByWidgetId = {};
this.onRoomStateEvents = this.onRoomStateEvents.bind(this);
this.dispatcherRef = null;
}
start() {
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
}
stop() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
this._roomIdByWidgetId = {};
}
onRoomStateEvents(ev, state) {
// XXX: This listens for state events in order to remove the active widget.
// Everything else relies on views listening for events and calling setters
// on this class which is terrible. This store should just listen for events
// and keep itself up to date.
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
if (ev.getType() !== 'im.vector.modular.widgets') return;
if (ev.getStateKey() === this._persistentWidgetId) {
this.destroyPersistentWidget(this._persistentWidgetId);
}
}
destroyPersistentWidget(id) {
if (id !== this._persistentWidgetId) return;
const toDeleteId = this._persistentWidgetId;
WidgetMessagingStore.instance.stopMessagingById(id);
this.setWidgetPersistence(toDeleteId, false);
this.delRoomId(toDeleteId);
}
setWidgetPersistence(widgetId, val) {
if (this._persistentWidgetId === widgetId && !val) {
this._persistentWidgetId = null;
} else if (this._persistentWidgetId !== widgetId && val) {
this._persistentWidgetId = widgetId;
}
this.emit('update');
}
getWidgetPersistence(widgetId) {
return this._persistentWidgetId === widgetId;
}
getPersistentWidgetId() {
return this._persistentWidgetId;
}
getRoomId(widgetId) {
return this._roomIdByWidgetId[widgetId];
}
setRoomId(widgetId, roomId) {
this._roomIdByWidgetId[widgetId] = roomId;
this.emit('update');
}
delRoomId(widgetId) {
delete this._roomIdByWidgetId[widgetId];
this.emit('update');
}
}
if (global.singletonActiveWidgetStore === undefined) {
global.singletonActiveWidgetStore = new ActiveWidgetStore();
}
export default global.singletonActiveWidgetStore;

View File

@ -0,0 +1,112 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from 'events';
import { MatrixEvent } from "matrix-js-sdk";
import { MatrixClientPeg } from '../MatrixClientPeg';
import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore";
export enum ActiveWidgetStoreEvent {
Update = "update",
}
/**
* Stores information about the widgets active in the app right now:
* * What widget is set to remain always-on-screen, if any
* Only one widget may be 'always on screen' at any one time.
* * Negotiated capabilities for active apps
*/
export default class ActiveWidgetStore extends EventEmitter {
private static internalInstance: ActiveWidgetStore;
private persistentWidgetId: string;
// What room ID each widget is associated with (if it's a room widget)
private roomIdByWidgetId = new Map<string, string>();
public static get instance(): ActiveWidgetStore {
if (!ActiveWidgetStore.internalInstance) {
ActiveWidgetStore.internalInstance = new ActiveWidgetStore();
}
return ActiveWidgetStore.internalInstance;
}
public start(): void {
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
}
public stop(): void {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
this.roomIdByWidgetId.clear();
}
private onRoomStateEvents = (ev: MatrixEvent): void => {
// XXX: This listens for state events in order to remove the active widget.
// Everything else relies on views listening for events and calling setters
// on this class which is terrible. This store should just listen for events
// and keep itself up to date.
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
if (ev.getType() !== 'im.vector.modular.widgets') return;
if (ev.getStateKey() === this.persistentWidgetId) {
this.destroyPersistentWidget(this.persistentWidgetId);
}
};
public destroyPersistentWidget(id: string): void {
if (id !== this.persistentWidgetId) return;
const toDeleteId = this.persistentWidgetId;
WidgetMessagingStore.instance.stopMessagingById(id);
this.setWidgetPersistence(toDeleteId, false);
this.delRoomId(toDeleteId);
}
public setWidgetPersistence(widgetId: string, val: boolean): void {
if (this.persistentWidgetId === widgetId && !val) {
this.persistentWidgetId = null;
} else if (this.persistentWidgetId !== widgetId && val) {
this.persistentWidgetId = widgetId;
}
this.emit(ActiveWidgetStoreEvent.Update);
}
public getWidgetPersistence(widgetId: string): boolean {
return this.persistentWidgetId === widgetId;
}
public getPersistentWidgetId(): string {
return this.persistentWidgetId;
}
public getRoomId(widgetId: string): string {
return this.roomIdByWidgetId.get(widgetId);
}
public setRoomId(widgetId: string, roomId: string): void {
this.roomIdByWidgetId.set(widgetId, roomId);
this.emit(ActiveWidgetStoreEvent.Update);
}
public delRoomId(widgetId: string): void {
this.roomIdByWidgetId.delete(widgetId);
this.emit(ActiveWidgetStoreEvent.Update);
}
}
window.mxActiveWidgetStore = ActiveWidgetStore.instance;

View File

@ -142,14 +142,14 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
// If a persistent widget is active, check to see if it's just been removed.
// If it has, it needs to destroyed otherwise unmounting the node won't kill it
const persistentWidgetId = ActiveWidgetStore.getPersistentWidgetId();
const persistentWidgetId = ActiveWidgetStore.instance.getPersistentWidgetId();
if (persistentWidgetId) {
if (
ActiveWidgetStore.getRoomId(persistentWidgetId) === room.roomId &&
ActiveWidgetStore.instance.getRoomId(persistentWidgetId) === room.roomId &&
!roomInfo.widgets.some(w => w.id === persistentWidgetId)
) {
logger.log(`Persistent widget ${persistentWidgetId} removed from room ${room.roomId}: destroying.`);
ActiveWidgetStore.destroyPersistentWidget(persistentWidgetId);
ActiveWidgetStore.instance.destroyPersistentWidget(persistentWidgetId);
}
}
@ -195,7 +195,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
// A persistent conference widget indicates that we're participating
const widgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type));
return widgets.some(w => ActiveWidgetStore.getWidgetPersistence(w.id));
return widgets.some(w => ActiveWidgetStore.instance.getWidgetPersistence(w.id));
}
}

View File

@ -266,7 +266,7 @@ export class StopGapWidget extends EventEmitter {
WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.messaging);
if (!this.appTileProps.userWidget && this.appTileProps.room) {
ActiveWidgetStore.setRoomId(this.mockWidget.id, this.appTileProps.room.roomId);
ActiveWidgetStore.instance.setRoomId(this.mockWidget.id, this.appTileProps.room.roomId);
}
// Always attach a handler for ViewRoom, but permission check it internally
@ -319,7 +319,7 @@ export class StopGapWidget extends EventEmitter {
if (WidgetType.JITSI.matches(this.mockWidget.type)) {
CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true);
}
ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
ActiveWidgetStore.instance.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value);
ev.preventDefault();
this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack
}
@ -406,13 +406,13 @@ export class StopGapWidget extends EventEmitter {
}
public stop(opts = { forceDestroy: false }) {
if (!opts?.forceDestroy && ActiveWidgetStore.getPersistentWidgetId() === this.mockWidget.id) {
if (!opts?.forceDestroy && ActiveWidgetStore.instance.getPersistentWidgetId() === this.mockWidget.id) {
logger.log("Skipping destroy - persistent widget");
return;
}
if (!this.started) return;
WidgetMessagingStore.instance.stopMessaging(this.mockWidget);
ActiveWidgetStore.delRoomId(this.mockWidget.id);
ActiveWidgetStore.instance.delRoomId(this.mockWidget.id);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().off('event', this.onEvent);

View File

@ -1,5 +1,5 @@
let hasCalled = false;
function remoteRender(event) {
function remoteRender(event: MessageEvent): void {
const data = event.data;
// If we're handling secondary calls, start from scratch
@ -8,13 +8,14 @@ function remoteRender(event) {
}
hasCalled = true;
const img = document.createElement("span"); // we'll mask it as an image
const img: HTMLSpanElement = document.createElement("span"); // we'll mask it as an image
img.id = "img";
const a = document.createElement("a");
const a: HTMLAnchorElement = document.createElement("a");
a.id = "a";
a.rel = "noreferrer noopener";
a.download = data.download;
// @ts-ignore
a.style = data.style;
a.style.fontFamily = "Arial, Helvetica, Sans-Serif";
a.href = window.URL.createObjectURL(data.blob);
@ -23,24 +24,24 @@ function remoteRender(event) {
// Apply image style after so we can steal the anchor's colour.
// Style copied from a rendered version of mx_MFileBody_download_icon
img.style = (data.imgStyle || "" +
"width: 12px; height: 12px;" +
"-webkit-mask-size: 12px;" +
"mask-size: 12px;" +
"-webkit-mask-position: center;" +
"mask-position: center;" +
"-webkit-mask-repeat: no-repeat;" +
"mask-repeat: no-repeat;" +
"display: inline-block;") + "" +
// Always add these styles
`-webkit-mask-image: url('${data.imgSrc}');` +
`mask-image: url('${data.imgSrc}');` +
`background-color: ${a.style.color};`;
if (data.imgStyle) {
// @ts-ignore
img.style = data.imgStyle;
} else {
img.style.width = "12px";
img.style.height = "12px";
img.style.webkitMaskSize = "12px";
img.style.webkitMaskPosition = "center";
img.style.webkitMaskRepeat = "no-repeat";
img.style.display = "inline-block";
img.style.webkitMaskImage = `url('${data.imgSrc}')`;
img.style.backgroundColor = `${a.style.color}`;
}
const body = document.body;
// Don't display scrollbars if the link takes more than one line to display.
body.style = "margin: 0px; overflow: hidden";
body.style .margin = "0px";
body.style.overflow = "hidden";
body.appendChild(a);
if (event.data.auto) {
@ -48,7 +49,7 @@ function remoteRender(event) {
}
}
window.onmessage = function(e) {
window.onmessage = function(e: MessageEvent): void {
if (e.origin === window.location.origin) {
if (e.data.blob) remoteRender(e);
}

View File

@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IInstance } from "matrix-js-sdk/src/client";
import { Protocols } from "../components/views/directory/NetworkDropdown";
// Find a protocol 'instance' with a given instance_id
// in the supplied protocols dict
export function instanceForInstanceId(protocols, instanceId) {
export function instanceForInstanceId(protocols: Protocols, instanceId: string): IInstance {
if (!instanceId) return null;
for (const proto of Object.keys(protocols)) {
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
@ -28,7 +31,7 @@ export function instanceForInstanceId(protocols, instanceId) {
// given an instance_id, return the name of the protocol for
// that instance ID in the supplied protocols dict
export function protocolNameForInstanceId(protocols, instanceId) {
export function protocolNameForInstanceId(protocols: Protocols, instanceId: string): string {
if (!instanceId) return null;
for (const proto of Object.keys(protocols)) {
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;

View File

@ -17,7 +17,7 @@ limitations under the License.
import SdkConfig from '../SdkConfig';
import { MatrixClientPeg } from '../MatrixClientPeg';
export function getHostingLink(campaign) {
export function getHostingLink(campaign: string): string {
const hostingLink = SdkConfig.get().hosting_signup_link;
if (!hostingLink) return null;
if (!campaign) return hostingLink;
@ -27,7 +27,7 @@ export function getHostingLink(campaign) {
try {
const hostingUrl = new URL(hostingLink);
hostingUrl.searchParams.set("utm_campaign", campaign);
return hostingUrl.format();
return hostingUrl.toString();
} catch (e) {
return hostingLink;
}

View File

@ -17,14 +17,14 @@ limitations under the License.
import { MatrixClientPeg } from '../MatrixClientPeg';
import { _t } from '../languageHandler';
export function getNameForEventRoom(userId, roomId) {
export function getNameForEventRoom(userId: string, roomId: string): string {
const client = MatrixClientPeg.get();
const room = client.getRoom(roomId);
const member = room && room.getMember(userId);
return member ? member.name : userId;
}
export function userLabelForEventRoom(userId, roomId) {
export function userLabelForEventRoom(userId: string, roomId: string): string {
const name = getNameForEventRoom(userId, roomId);
if (name !== userId) {
return _t("%(name)s (%(userId)s)", { name, userId });

View File

@ -26,17 +26,17 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle;
* Make an Error object which has a friendlyText property which is already
* translated and suitable for showing to the user.
*
* @param {string} msg message for the exception
* @param {string} message message for the exception
* @param {string} friendlyText
* @returns {Error}
* @returns {{message: string, friendlyText: string}}
*/
function friendlyError(msg, friendlyText) {
const e = new Error(msg);
e.friendlyText = friendlyText;
return e;
function friendlyError(
message: string, friendlyText: string,
): { message: string, friendlyText: string } {
return { message, friendlyText };
}
function cryptoFailMsg() {
function cryptoFailMsg(): string {
return _t('Your browser does not support the required cryptography extensions');
}
@ -49,7 +49,7 @@ function cryptoFailMsg() {
*
*
*/
export async function decryptMegolmKeyFile(data, password) {
export async function decryptMegolmKeyFile(data: ArrayBuffer, password: string): Promise<string> {
const body = unpackMegolmKeyFile(data);
const brand = SdkConfig.get().brand;
@ -124,7 +124,11 @@ export async function decryptMegolmKeyFile(data, password) {
* key-derivation function.
* @return {Promise<ArrayBuffer>} promise for encrypted output
*/
export async function encryptMegolmKeyFile(data, password, options) {
export async function encryptMegolmKeyFile(
data: string,
password: string,
options?: { kdf_rounds?: number }, // eslint-disable-line camelcase
): Promise<ArrayBuffer> {
options = options || {};
const kdfRounds = options.kdf_rounds || 500000;
@ -196,7 +200,7 @@ export async function encryptMegolmKeyFile(data, password, options) {
* @param {String} password password
* @return {Promise<[CryptoKey, CryptoKey]>} promise for [aes key, hmac key]
*/
async function deriveKeys(salt, iterations, password) {
async function deriveKeys(salt: Uint8Array, iterations: number, password: string): Promise<[CryptoKey, CryptoKey]> {
const start = new Date();
let key;
@ -229,7 +233,7 @@ async function deriveKeys(salt, iterations, password) {
}
const now = new Date();
logger.log("E2e import/export: deriveKeys took " + (now - start) + "ms");
logger.log("E2e import/export: deriveKeys took " + (now.getTime() - start.getTime()) + "ms");
const aesKey = keybits.slice(0, 32);
const hmacKey = keybits.slice(32);
@ -271,7 +275,7 @@ const TRAILER_LINE = '-----END MEGOLM SESSION DATA-----';
* @param {ArrayBuffer} data input file
* @return {Uint8Array} unbase64ed content
*/
function unpackMegolmKeyFile(data) {
function unpackMegolmKeyFile(data: ArrayBuffer): Uint8Array {
// parse the file as a great big String. This should be safe, because there
// should be no non-ASCII characters, and it means that we can do string
// comparisons to find the header and footer, and feed it into window.atob.
@ -279,6 +283,7 @@ function unpackMegolmKeyFile(data) {
// look for the start line
let lineStart = 0;
// eslint-disable-next-line no-constant-condition
while (1) {
const lineEnd = fileStr.indexOf('\n', lineStart);
if (lineEnd < 0) {
@ -297,6 +302,7 @@ function unpackMegolmKeyFile(data) {
const dataStart = lineStart;
// look for the end line
// eslint-disable-next-line no-constant-condition
while (1) {
const lineEnd = fileStr.indexOf('\n', lineStart);
const line = fileStr.slice(lineStart, lineEnd < 0 ? undefined : lineEnd).trim();
@ -324,7 +330,7 @@ function unpackMegolmKeyFile(data) {
* @param {Uint8Array} data raw data
* @return {ArrayBuffer} formatted file
*/
function packMegolmKeyFile(data) {
function packMegolmKeyFile(data: Uint8Array): ArrayBuffer {
// we split into lines before base64ing, because encodeBase64 doesn't deal
// terribly well with large arrays.
const LINE_LENGTH = (72 * 4 / 3);
@ -347,7 +353,7 @@ function packMegolmKeyFile(data) {
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
function encodeBase64(uint8Array) {
function encodeBase64(uint8Array: Uint8Array): string {
// Misinterpt the Uint8Array as Latin-1.
// window.btoa expects a unicode string with codepoints in the range 0-255.
const latin1String = String.fromCharCode.apply(null, uint8Array);
@ -360,7 +366,7 @@ function encodeBase64(uint8Array) {
* @param {string} base64 The base64 to decode.
* @return {Uint8Array} The decoded data.
*/
function decodeBase64(base64) {
function decodeBase64(base64: string): Uint8Array {
// window.atob returns a unicode string with codepoints in the range 0-255.
const latin1String = window.atob(base64);
// Encode the string as a Uint8Array

View File

@ -5807,8 +5807,8 @@ mathml-tag-names@^2.1.3:
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "12.5.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/f84905b00398072b592addfb1dae64c8f3a07fa2"
version "13.0.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2515d07c8fc3bf5e1afc8352e3e330cca30dde85"
dependencies:
"@babel/runtime" "^7.12.5"
another-json "^0.2.0"