mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into gsouquet/threads-action-bar-19127
commit
e52a33e93c
103
CHANGELOG.md
103
CHANGELOG.md
|
@ -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)
|
||||
===================================================================================================
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -89,7 +89,6 @@ limitations under the License.
|
|||
margin: 0px auto;
|
||||
|
||||
overflow: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.mx_RoomView_auxPanel_fullHeight {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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}>*
|
||||
<div className="mx_EventTile_content" ref={this.content}>*
|
||||
<span className="mx_MEmoteBody_sender">{ name }</span>
|
||||
{ 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>
|
|
@ -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,
|
||||
};
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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 (
|
|
@ -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>
|
||||
);
|
|
@ -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");
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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 });
|
|
@ -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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue