Merge branch 'develop' into travis/cross-room
This commit is contained in:
commit
b86a0dcc15
42 changed files with 664 additions and 227 deletions
|
@ -17,6 +17,7 @@ module.exports = {
|
||||||
"selector-list-comma-newline-after": null,
|
"selector-list-comma-newline-after": null,
|
||||||
"at-rule-no-unknown": null,
|
"at-rule-no-unknown": null,
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
|
"no-empty-first-line": true,
|
||||||
"scss/at-rule-no-unknown": [true, {
|
"scss/at-rule-no-unknown": [true, {
|
||||||
// https://github.com/vector-im/element-web/issues/10544
|
// https://github.com/vector-im/element-web/issues/10544
|
||||||
"ignoreAtRules": ["define-mixin"],
|
"ignoreAtRules": ["define-mixin"],
|
||||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,3 +1,47 @@
|
||||||
|
Changes in [3.29.0](https://github.com/vector-im/element-desktop/releases/tag/v3.29.0) (2021-08-31)
|
||||||
|
===================================================================================================
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
* [Release]Increase general app performance by optimizing layers ([\#6672](https://github.com/matrix-org/matrix-react-sdk/pull/6672)). Fixes vector-im/element-web#18730 and vector-im/element-web#18730. Contributed by [Palid](https://github.com/Palid).
|
||||||
|
* Add a warning on E2EE rooms if you try to make them public ([\#5698](https://github.com/matrix-org/matrix-react-sdk/pull/5698)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Allow pagination of the space hierarchy and use new APIs ([\#6507](https://github.com/matrix-org/matrix-react-sdk/pull/6507)). Fixes vector-im/element-web#18089 and vector-im/element-web#18427.
|
||||||
|
* Improve emoji in composer ([\#6650](https://github.com/matrix-org/matrix-react-sdk/pull/6650)). Fixes vector-im/element-web#18593 and vector-im/element-web#18593. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Allow playback of replied-to voice message ([\#6629](https://github.com/matrix-org/matrix-react-sdk/pull/6629)). Fixes vector-im/element-web#18599 and vector-im/element-web#18599. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Format autocomplete suggestions vertically ([\#6620](https://github.com/matrix-org/matrix-react-sdk/pull/6620)). Fixes vector-im/element-web#17574 and vector-im/element-web#17574. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Remember last `MemberList` search query per-room ([\#6640](https://github.com/matrix-org/matrix-react-sdk/pull/6640)). Fixes vector-im/element-web#18613 and vector-im/element-web#18613. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Sentry rageshakes ([\#6597](https://github.com/matrix-org/matrix-react-sdk/pull/6597)). Fixes vector-im/element-web#11111 and vector-im/element-web#11111. Contributed by [novocaine](https://github.com/novocaine).
|
||||||
|
* Autocomplete has been updated to match modern accessibility standards. Navigate via up/down arrows rather than Tab. Enter or Tab to confirm a suggestion. This should be familiar to Slack & Discord users. You can now use Tab to navigate around the application and do more without touching your mouse. No more accidentally sending half of people's names because the completion didn't fire on Enter! ([\#5659](https://github.com/matrix-org/matrix-react-sdk/pull/5659)). Fixes vector-im/element-web#4872, vector-im/element-web#11071, vector-im/element-web#17171, vector-im/element-web#15646 vector-im/element-web#4872 and vector-im/element-web#4872.
|
||||||
|
* Add new call tile states ([\#6610](https://github.com/matrix-org/matrix-react-sdk/pull/6610)). Fixes vector-im/element-web#18521 and vector-im/element-web#18521. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Left align call tiles ([\#6609](https://github.com/matrix-org/matrix-react-sdk/pull/6609)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Make loading encrypted images look snappier ([\#6590](https://github.com/matrix-org/matrix-react-sdk/pull/6590)). Fixes vector-im/element-web#17878 and vector-im/element-web#17862. Contributed by [Palid](https://github.com/Palid).
|
||||||
|
* Offer a way to create a space based on existing community ([\#6543](https://github.com/matrix-org/matrix-react-sdk/pull/6543)). Fixes vector-im/element-web#18092.
|
||||||
|
* Accessibility improvements in and around Spaces ([\#6569](https://github.com/matrix-org/matrix-react-sdk/pull/6569)). Fixes vector-im/element-web#18094 and vector-im/element-web#18094.
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
* [Release] Fix commit edit history ([\#6690](https://github.com/matrix-org/matrix-react-sdk/pull/6690)). Fixes vector-im/element-web#18742 and vector-im/element-web#18742. Contributed by [Palid](https://github.com/Palid).
|
||||||
|
* Fix images not rendering when sent from other clients. ([\#6661](https://github.com/matrix-org/matrix-react-sdk/pull/6661)). Fixes vector-im/element-web#18702 and vector-im/element-web#18702.
|
||||||
|
* Fix autocomplete scrollbar and make the autocomplete a little smaller ([\#6655](https://github.com/matrix-org/matrix-react-sdk/pull/6655)). Fixes vector-im/element-web#18682 and vector-im/element-web#18682. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix replies on the bubble layout ([\#6451](https://github.com/matrix-org/matrix-react-sdk/pull/6451)). Fixes vector-im/element-web#18184. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Show "Enable encryption in settings" only when the user can do that ([\#6646](https://github.com/matrix-org/matrix-react-sdk/pull/6646)). Fixes vector-im/element-web#18646 and vector-im/element-web#18646. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix cross signing setup from settings screen ([\#6633](https://github.com/matrix-org/matrix-react-sdk/pull/6633)). Fixes vector-im/element-web#17761 and vector-im/element-web#17761.
|
||||||
|
* Fix call tiles on the bubble layout ([\#6647](https://github.com/matrix-org/matrix-react-sdk/pull/6647)). Fixes vector-im/element-web#18648 and vector-im/element-web#18648. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix error on accessing encrypted media without encryption keys ([\#6625](https://github.com/matrix-org/matrix-react-sdk/pull/6625)). Contributed by [Palid](https://github.com/Palid).
|
||||||
|
* Fix jitsi widget sometimes being permanently stuck in the bottom-right corner ([\#6632](https://github.com/matrix-org/matrix-react-sdk/pull/6632)). Fixes vector-im/element-web#17226 and vector-im/element-web#17226. Contributed by [Palid](https://github.com/Palid).
|
||||||
|
* Fix FilePanel pagination in E2EE rooms ([\#6630](https://github.com/matrix-org/matrix-react-sdk/pull/6630)). Fixes vector-im/element-web#18415 and vector-im/element-web#18415. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix call tile buttons ([\#6624](https://github.com/matrix-org/matrix-react-sdk/pull/6624)). Fixes vector-im/element-web#18565 and vector-im/element-web#18565. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix vertical call tile spacing issues ([\#6621](https://github.com/matrix-org/matrix-react-sdk/pull/6621)). Fixes vector-im/element-web#18558 and vector-im/element-web#18558. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix long display names in call tiles ([\#6618](https://github.com/matrix-org/matrix-react-sdk/pull/6618)). Fixes vector-im/element-web#18562 and vector-im/element-web#18562. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Avoid access token overflow ([\#6616](https://github.com/matrix-org/matrix-react-sdk/pull/6616)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Properly handle media errors ([\#6615](https://github.com/matrix-org/matrix-react-sdk/pull/6615)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix glare related regressions ([\#6614](https://github.com/matrix-org/matrix-react-sdk/pull/6614)). Fixes vector-im/element-web#18538 and vector-im/element-web#18538. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix long display names in call toasts ([\#6617](https://github.com/matrix-org/matrix-react-sdk/pull/6617)). Fixes vector-im/element-web#18557 and vector-im/element-web#18557. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix PiP of held calls ([\#6611](https://github.com/matrix-org/matrix-react-sdk/pull/6611)). Fixes vector-im/element-web#18539 and vector-im/element-web#18539. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix call tile behaviour on narrow layouts ([\#6556](https://github.com/matrix-org/matrix-react-sdk/pull/6556)). Fixes vector-im/element-web#18398. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Fix video call persisting when widget removed ([\#6608](https://github.com/matrix-org/matrix-react-sdk/pull/6608)). Fixes vector-im/element-web#15703 and vector-im/element-web#15703.
|
||||||
|
* Fix toast colors ([\#6606](https://github.com/matrix-org/matrix-react-sdk/pull/6606)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Remove tiny scrollbar dot from code blocks ([\#6596](https://github.com/matrix-org/matrix-react-sdk/pull/6596)). Fixes vector-im/element-web#18474. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
* Improve handling of pills in the composer ([\#6353](https://github.com/matrix-org/matrix-react-sdk/pull/6353)). Fixes vector-im/element-web#10134 vector-im/element-web#10896 and vector-im/element-web#15037. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
|
||||||
|
|
||||||
Changes in [3.28.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.1) (2021-08-17)
|
Changes in [3.28.1](https://github.com/vector-im/element-desktop/releases/tag/v3.28.1) (2021-08-17)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.28.1",
|
"version": "3.29.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -92,6 +92,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg');
|
mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MessageActionBar_threadButton::after {
|
||||||
|
mask-image: url('$(res)/img/element-icons/message/thread.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MessageActionBar_editButton::after {
|
.mx_MessageActionBar_editButton::after {
|
||||||
mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg');
|
mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,6 +232,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_threads::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/message/thread.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_icon_share::before {
|
.mx_RoomSummaryCard_icon_share::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/share.svg');
|
mask-image: url('$(res)/img/element-icons/room/share.svg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
max-height: 35vh;
|
max-height: 35vh;
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
box-shadow: 0px -16px 32px $composer-shadow-color;
|
box-shadow: 0px -16px 32px $composer-shadow-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -643,6 +643,7 @@ $hover-select-border: 4px;
|
||||||
|
|
||||||
// Remove some of the default tile padding so that the error is centered
|
// Remove some of the default tile padding so that the error is centered
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
@ -674,3 +675,62 @@ $hover-select-border: 4px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ThreadInfo:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ThreadView {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.mx_ScrollPanel {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mx_RoomView_MessageList {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_senderDetails {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
flex: 1;
|
||||||
|
min-width: none;
|
||||||
|
max-width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mx_SenderProfile {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ThreadView_List {
|
||||||
|
flex: 1;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_roomName {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line {
|
||||||
|
padding-left: 0 !important;
|
||||||
|
order: 10 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -340,3 +340,19 @@ limitations under the License.
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unstable compact mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_MessageComposer.mx_MessageComposer--compact {
|
||||||
|
margin-right: 0;
|
||||||
|
|
||||||
|
.mx_MessageComposer_wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MessageComposer_button:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
res/img/element-icons/message/thread.svg
Normal file
4
res/img/element-icons/message/thread.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 8V8C1.89543 8 1 7.10457 1 6V3C1 1.89543 1.89543 1 3 1H15C16.1046 1 17 1.89484 17 2.9994C17 3.88147 17 4.95392 17 6.00008C17 7.10465 16.1046 8 15 8H10.5" stroke="#737D8C" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.2011 16.7176C12.9087 17.011 12.9088 17.4866 13.2012 17.78C13.4936 18.0734 13.9677 18.0733 14.2601 17.78C14.9484 17.0894 15.6519 16.3829 16.1834 15.8491L16.8282 15.2014L17.0099 15.0188L17.0579 14.9706L17.0702 14.9582L17.0733 14.955L17.0741 14.9542L17.0743 14.954L17.0743 14.954L16.5444 14.4233L17.0744 14.954C17.3663 14.6606 17.3661 14.1855 17.0741 13.8922L14.2539 11.061C13.9616 10.7675 13.4875 10.7674 13.195 11.0606C12.9024 11.3539 12.9023 11.8295 13.1946 12.123L14.7442 13.6787L10.1137 13.6787C8.69795 13.6787 7.49996 12.4759 7.49996 10.9288L7.49996 7.00002C7.49996 6.58581 7.16417 6.25002 6.74996 6.25002C6.33574 6.25002 5.99996 6.58581 5.99996 7.00002L5.99996 10.9288C5.99996 13.2476 7.81395 15.1787 10.1137 15.1787H14.7341C14.2713 15.6436 13.7316 16.1854 13.2011 16.7176Z" fill="#737D8C"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -213,6 +213,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
||||||
opts.pendingEventOrdering = PendingEventOrdering.Detached;
|
opts.pendingEventOrdering = PendingEventOrdering.Detached;
|
||||||
opts.lazyLoadMembers = true;
|
opts.lazyLoadMembers = true;
|
||||||
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
|
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
|
||||||
|
opts.experimentalThreadSupport = SettingsStore.getValue("feature_thread");
|
||||||
|
|
||||||
// Connect the matrix client to the dispatcher and setting handlers
|
// Connect the matrix client to the dispatcher and setting handlers
|
||||||
MatrixActionCreators.start(this.matrixClient);
|
MatrixActionCreators.start(this.matrixClient);
|
||||||
|
|
|
@ -50,7 +50,6 @@ import CallHandler from "./CallHandler";
|
||||||
import { guessAndSetDMRoom } from "./Rooms";
|
import { guessAndSetDMRoom } from "./Rooms";
|
||||||
import { upgradeRoom } from './utils/RoomUpgrade';
|
import { upgradeRoom } from './utils/RoomUpgrade';
|
||||||
import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
|
import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
|
||||||
import ErrorDialog from './components/views/dialogs/ErrorDialog';
|
|
||||||
import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
|
import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
|
||||||
import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog";
|
import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog";
|
||||||
import InfoDialog from "./components/views/dialogs/InfoDialog";
|
import InfoDialog from "./components/views/dialogs/InfoDialog";
|
||||||
|
@ -245,21 +244,6 @@ export const Commands = [
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
new Command({
|
|
||||||
command: 'ddg',
|
|
||||||
args: '<query>',
|
|
||||||
description: _td('Searches DuckDuckGo for results'),
|
|
||||||
runFn: function() {
|
|
||||||
// TODO Don't explain this away, actually show a search UI here.
|
|
||||||
Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
|
|
||||||
title: _t('/ddg is not a command'),
|
|
||||||
description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
|
|
||||||
});
|
|
||||||
return success();
|
|
||||||
},
|
|
||||||
category: CommandCategories.actions,
|
|
||||||
hideCompletionAfterSpace: true,
|
|
||||||
}),
|
|
||||||
new Command({
|
new Command({
|
||||||
command: 'upgraderoom',
|
command: 'upgraderoom',
|
||||||
args: '<new_version>',
|
args: '<new_version>',
|
||||||
|
|
|
@ -45,7 +45,13 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
const currentSecond = roundTimeToTargetFreq(currentTime);
|
const currentSecond = roundTimeToTargetFreq(currentTime);
|
||||||
if (currentSecond === this.nextAmplitudeSecond) {
|
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
|
||||||
|
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
|
||||||
|
// time. Edge and Chrome occasionally lag behind too, but for the most part are on time.
|
||||||
|
//
|
||||||
|
// When this doesn't work properly we end up producing a waveform of nulls and no live preview
|
||||||
|
// of the recorded message.
|
||||||
|
if (currentSecond === this.nextAmplitudeSecond || this.nextAmplitudeSecond === 0) {
|
||||||
// We're expecting exactly one mono input source, so just grab the very first frame of
|
// We're expecting exactly one mono input source, so just grab the very first frame of
|
||||||
// samples for the analysis.
|
// samples for the analysis.
|
||||||
const monoChan = inputs[0][0];
|
const monoChan = inputs[0][0];
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
|
||||||
import CommandProvider from './CommandProvider';
|
import CommandProvider from './CommandProvider';
|
||||||
import CommunityProvider from './CommunityProvider';
|
import CommunityProvider from './CommunityProvider';
|
||||||
import DuckDuckGoProvider from './DuckDuckGoProvider';
|
|
||||||
import RoomProvider from './RoomProvider';
|
import RoomProvider from './RoomProvider';
|
||||||
import UserProvider from './UserProvider';
|
import UserProvider from './UserProvider';
|
||||||
import EmojiProvider from './EmojiProvider';
|
import EmojiProvider from './EmojiProvider';
|
||||||
|
@ -55,7 +54,6 @@ const PROVIDERS = [
|
||||||
EmojiProvider,
|
EmojiProvider,
|
||||||
NotifProvider,
|
NotifProvider,
|
||||||
CommandProvider,
|
CommandProvider,
|
||||||
DuckDuckGoProvider,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (SpaceStore.spacesEnabled) {
|
if (SpaceStore.spacesEnabled) {
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
// The input looks like a command with arguments, perform exact match
|
// The input looks like a command with arguments, perform exact match
|
||||||
const name = command[1].substr(1); // strip leading `/`
|
const name = command[1].substr(1); // strip leading `/`
|
||||||
if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) {
|
if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) {
|
||||||
// some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments
|
// some commands, namely `me` don't suit having the usage shown whilst typing their arguments
|
||||||
if (CommandMap.get(name).hideCompletionAfterSpace) return [];
|
if (CommandMap.get(name).hideCompletionAfterSpace) return [];
|
||||||
matches = [CommandMap.get(name)];
|
matches = [CommandMap.get(name)];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 Aviral Dasgupta
|
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2017, 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 React from 'react';
|
|
||||||
import { _t } from '../languageHandler';
|
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
|
||||||
|
|
||||||
import { TextualCompletion } from './Components';
|
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
|
||||||
|
|
||||||
const DDG_REGEX = /\/ddg\s+(.+)$/g;
|
|
||||||
const REFERRER = 'vector';
|
|
||||||
|
|
||||||
export default class DuckDuckGoProvider extends AutocompleteProvider {
|
|
||||||
constructor() {
|
|
||||||
super(DDG_REGEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getQueryUri(query: string) {
|
|
||||||
return `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}`
|
|
||||||
+ `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCompletions(
|
|
||||||
query: string,
|
|
||||||
selection: ISelectionRange,
|
|
||||||
force = false,
|
|
||||||
limit = -1,
|
|
||||||
): Promise<ICompletion[]> {
|
|
||||||
const { command, range } = this.getCurrentCommand(query, selection);
|
|
||||||
if (!query || !command) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(DuckDuckGoProvider.getQueryUri(command[1]), {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
const json = await response.json();
|
|
||||||
const maxLength = limit > -1 ? limit : json.Results.length;
|
|
||||||
const results = json.Results.slice(0, maxLength).map((result) => {
|
|
||||||
return {
|
|
||||||
completion: result.Text,
|
|
||||||
component: (
|
|
||||||
<TextualCompletion
|
|
||||||
title={result.Text}
|
|
||||||
description={result.Result} />
|
|
||||||
),
|
|
||||||
range,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (json.Answer) {
|
|
||||||
results.unshift({
|
|
||||||
completion: json.Answer,
|
|
||||||
component: (
|
|
||||||
<TextualCompletion
|
|
||||||
title={json.Answer}
|
|
||||||
description={json.AnswerType} />
|
|
||||||
),
|
|
||||||
range,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (json.RelatedTopics && json.RelatedTopics.length > 0) {
|
|
||||||
results.unshift({
|
|
||||||
completion: json.RelatedTopics[0].Text,
|
|
||||||
component: (
|
|
||||||
<TextualCompletion
|
|
||||||
title={json.RelatedTopics[0].Text} />
|
|
||||||
),
|
|
||||||
range,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (json.AbstractText) {
|
|
||||||
results.unshift({
|
|
||||||
completion: json.AbstractText,
|
|
||||||
component: (
|
|
||||||
<TextualCompletion
|
|
||||||
title={json.AbstractText} />
|
|
||||||
),
|
|
||||||
range,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
getName() {
|
|
||||||
return '🔍 ' + _t('Results from DuckDuckGo');
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="mx_Autocomplete_Completion_container_block"
|
|
||||||
role="presentation"
|
|
||||||
aria-label={_t("DuckDuckGo Results")}
|
|
||||||
>
|
|
||||||
{ completions }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1016,6 +1016,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.setStateForNewView({
|
this.setStateForNewView({
|
||||||
view: Views.LOGGED_IN,
|
view: Views.LOGGED_IN,
|
||||||
justRegistered,
|
justRegistered,
|
||||||
|
currentRoomId: null,
|
||||||
});
|
});
|
||||||
this.setPage(PageTypes.HomePage);
|
this.setPage(PageTypes.HomePage);
|
||||||
this.notifyNewScreen('home');
|
this.notifyNewScreen('home');
|
||||||
|
|
|
@ -173,6 +173,8 @@ interface IProps {
|
||||||
onUnfillRequest?(backwards: boolean, scrollToken: string): void;
|
onUnfillRequest?(backwards: boolean, scrollToken: string): void;
|
||||||
|
|
||||||
getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations;
|
getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations;
|
||||||
|
|
||||||
|
hideThreadedMessages?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -265,6 +267,9 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.calculateRoomMembersCount();
|
this.calculateRoomMembersCount();
|
||||||
this.props.room?.on("RoomState.members", this.calculateRoomMembersCount);
|
this.props.room?.on("RoomState.members", this.calculateRoomMembersCount);
|
||||||
|
if (SettingsStore.getValue("feature_thread")) {
|
||||||
|
this.props.room?.getThreads().forEach(thread => thread.fetchReplyChain());
|
||||||
|
}
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,6 +448,12 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
// Always show highlighted event
|
// Always show highlighted event
|
||||||
if (this.props.highlightedEventId === mxEv.getId()) return true;
|
if (this.props.highlightedEventId === mxEv.getId()) return true;
|
||||||
|
|
||||||
|
if (mxEv.replyEventId
|
||||||
|
&& this.props.hideThreadedMessages
|
||||||
|
&& SettingsStore.getValue("feature_thread")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return !shouldHideEvent(mxEv, this.context);
|
return !shouldHideEvent(mxEv, this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,17 +45,21 @@ import GroupRoomInfo from "../views/groups/GroupRoomInfo";
|
||||||
import UserInfo from "../views/right_panel/UserInfo";
|
import UserInfo from "../views/right_panel/UserInfo";
|
||||||
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
|
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
|
||||||
import FilePanel from "./FilePanel";
|
import FilePanel from "./FilePanel";
|
||||||
|
import ThreadView from "./ThreadView";
|
||||||
|
import ThreadPanel from "./ThreadPanel";
|
||||||
import NotificationPanel from "./NotificationPanel";
|
import NotificationPanel from "./NotificationPanel";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
|
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import SpaceStore from "../../stores/SpaceStore";
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
|
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room?: Room; // if showing panels for a given room, this is set
|
room?: Room; // if showing panels for a given room, this is set
|
||||||
groupId?: string; // if showing panels for a given group, 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
|
user?: User; // used if we know the user ahead of opening the panel
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
|
permalinkCreator?: RoomPermalinkCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -309,6 +313,22 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RightPanelPhases.ThreadView:
|
||||||
|
panel = <ThreadView
|
||||||
|
room={this.props.room}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
onClose={this.onClose}
|
||||||
|
mxEvent={this.state.event}
|
||||||
|
permalinkCreator={this.props.permalinkCreator} />;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RightPanelPhases.ThreadPanel:
|
||||||
|
panel = <ThreadPanel
|
||||||
|
roomId={roomId}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
onClose={this.onClose} />;
|
||||||
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.RoomSummary:
|
case RightPanelPhases.RoomSummary:
|
||||||
panel = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
|
panel = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -2052,7 +2052,10 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const showRightPanel = this.state.room && this.state.showRightPanel;
|
const showRightPanel = this.state.room && this.state.showRightPanel;
|
||||||
const rightPanel = showRightPanel
|
const rightPanel = showRightPanel
|
||||||
? <RightPanel room={this.state.room} resizeNotifier={this.props.resizeNotifier} />
|
? <RightPanel
|
||||||
|
room={this.state.room}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} />
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const timelineClasses = classNames("mx_RoomView_timeline", {
|
const timelineClasses = classNames("mx_RoomView_timeline", {
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { RefObject, useContext, useRef, useState } from "react";
|
import React, { RefObject, useContext, useRef, useState } from "react";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { Preset, JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { EventSubscription } from "fbemitter";
|
import { EventSubscription } from "fbemitter";
|
||||||
|
|
||||||
|
@ -505,11 +505,12 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
setError("");
|
setError("");
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
|
const isPublic = space.getJoinRule() === JoinRule.Public;
|
||||||
const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean);
|
const filteredRoomNames = roomNames.map(name => name.trim()).filter(Boolean);
|
||||||
await Promise.all(filteredRoomNames.map(name => {
|
await Promise.all(filteredRoomNames.map(name => {
|
||||||
return createRoom({
|
return createRoom({
|
||||||
createOpts: {
|
createOpts: {
|
||||||
preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat,
|
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
spinner: false,
|
spinner: false,
|
||||||
|
@ -517,6 +518,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
andView: false,
|
andView: false,
|
||||||
inlineErrors: true,
|
inlineErrors: true,
|
||||||
parentSpace: space,
|
parentSpace: space,
|
||||||
|
joinRule: !isPublic ? JoinRule.Restricted : undefined,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
onFinished(filteredRoomNames.length > 0);
|
onFinished(filteredRoomNames.length > 0);
|
||||||
|
|
93
src/components/structures/ThreadPanel.tsx
Normal file
93
src/components/structures/ThreadPanel.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 React from 'react';
|
||||||
|
import { MatrixEvent, Room } from 'matrix-js-sdk/src';
|
||||||
|
import { Thread } from 'matrix-js-sdk/src/models/thread';
|
||||||
|
|
||||||
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
|
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||||
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||||
|
|
||||||
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
|
import EventTile from '../views/rooms/EventTile';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
roomId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
resizeNotifier: ResizeNotifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
threads?: Thread[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.ThreadView")
|
||||||
|
export default class ThreadPanel extends React.Component<IProps, IState> {
|
||||||
|
private room: Room;
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
this.room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount(): void {
|
||||||
|
this.room.on("Thread.update", this.onThreadEventReceived);
|
||||||
|
this.room.on("Thread.ready", this.onThreadEventReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount(): void {
|
||||||
|
this.room.removeListener("Thread.update", this.onThreadEventReceived);
|
||||||
|
this.room.removeListener("Thread.ready", this.onThreadEventReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onThreadEventReceived = () => this.updateThreads();
|
||||||
|
|
||||||
|
private updateThreads = (callback?: () => void): void => {
|
||||||
|
this.setState({
|
||||||
|
threads: this.room.getThreads(),
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderEventTile(event: MatrixEvent): JSX.Element {
|
||||||
|
return <EventTile
|
||||||
|
key={event.getId()}
|
||||||
|
mxEvent={event}
|
||||||
|
enableFlair={false}
|
||||||
|
showReadReceipts={false}
|
||||||
|
as="div"
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<BaseCard
|
||||||
|
className="mx_ThreadPanel"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
this.state?.threads.map((thread: Thread) => {
|
||||||
|
if (thread.ready) {
|
||||||
|
return this.renderEventTile(thread.rootEvent);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</BaseCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
147
src/components/structures/ThreadView.tsx
Normal file
147
src/components/structures/ThreadView.tsx
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 React from 'react';
|
||||||
|
import { MatrixEvent, Room } from 'matrix-js-sdk/src';
|
||||||
|
import { Thread } from 'matrix-js-sdk/src/models/thread';
|
||||||
|
|
||||||
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
|
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||||
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
|
import { TileShape } from '../views/rooms/EventTile';
|
||||||
|
import MessageComposer from '../views/rooms/MessageComposer';
|
||||||
|
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||||
|
import { Layout } from '../../settings/Layout';
|
||||||
|
import TimelinePanel from './TimelinePanel';
|
||||||
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
import { ActionPayload } from '../../dispatcher/payloads';
|
||||||
|
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
|
||||||
|
import { Action } from '../../dispatcher/actions';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
room: Room;
|
||||||
|
onClose: () => void;
|
||||||
|
resizeNotifier: ResizeNotifier;
|
||||||
|
mxEvent: MatrixEvent;
|
||||||
|
permalinkCreator?: RoomPermalinkCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
replyToEvent?: MatrixEvent;
|
||||||
|
thread?: Thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.ThreadView")
|
||||||
|
export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
|
private dispatcherRef: string;
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount(): void {
|
||||||
|
this.setupThread(this.props.mxEvent);
|
||||||
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount(): void {
|
||||||
|
this.teardownThread();
|
||||||
|
dis.unregister(this.dispatcherRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.mxEvent !== this.props.mxEvent) {
|
||||||
|
this.teardownThread();
|
||||||
|
this.setupThread(this.props.mxEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.room !== this.props.room) {
|
||||||
|
dis.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.RoomSummary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAction = (payload: ActionPayload): void => {
|
||||||
|
if (payload.phase == RightPanelPhases.ThreadView && payload.event) {
|
||||||
|
if (payload.event !== this.props.mxEvent) {
|
||||||
|
this.teardownThread();
|
||||||
|
this.setupThread(payload.event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private setupThread = (mxEv: MatrixEvent) => {
|
||||||
|
const thread = mxEv.getThread();
|
||||||
|
if (thread) {
|
||||||
|
thread.on("Thread.update", this.updateThread);
|
||||||
|
thread.once("Thread.ready", this.updateThread);
|
||||||
|
this.updateThread(thread);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private teardownThread = () => {
|
||||||
|
if (this.state.thread) {
|
||||||
|
this.state.thread.removeListener("Thread.update", this.updateThread);
|
||||||
|
this.state.thread.removeListener("Thread.ready", this.updateThread);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private updateThread = (thread?: Thread) => {
|
||||||
|
if (thread) {
|
||||||
|
this.setState({ thread });
|
||||||
|
} else {
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<BaseCard
|
||||||
|
className="mx_ThreadView"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
|
withoutScrollContainer={true}
|
||||||
|
>
|
||||||
|
{ this.state.thread && (
|
||||||
|
<TimelinePanel
|
||||||
|
manageReadReceipts={false}
|
||||||
|
manageReadMarkers={false}
|
||||||
|
timelineSet={this.state?.thread?.timelineSet}
|
||||||
|
showUrlPreview={false}
|
||||||
|
tileShape={TileShape.Notif}
|
||||||
|
empty={<div>empty</div>}
|
||||||
|
alwaysShowTimestamps={true}
|
||||||
|
layout={Layout.Group}
|
||||||
|
hideThreadedMessages={false}
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
<MessageComposer
|
||||||
|
room={this.props.room}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
replyToEvent={this.state?.thread?.replyToEvent}
|
||||||
|
showReplyPreview={false}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
</BaseCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -126,6 +126,8 @@ interface IProps {
|
||||||
|
|
||||||
// callback which is called when we wish to paginate the timeline window.
|
// callback which is called when we wish to paginate the timeline window.
|
||||||
onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise<boolean>;
|
onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise<boolean>;
|
||||||
|
|
||||||
|
hideThreadedMessages?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -214,6 +216,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
timelineCap: Number.MAX_VALUE,
|
timelineCap: Number.MAX_VALUE,
|
||||||
className: 'mx_RoomView_messagePanel',
|
className: 'mx_RoomView_messagePanel',
|
||||||
sendReadReceiptOnLoad: true,
|
sendReadReceiptOnLoad: true,
|
||||||
|
hideThreadedMessages: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
private lastRRSentEventId: string = undefined;
|
private lastRRSentEventId: string = undefined;
|
||||||
|
@ -1511,6 +1514,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
layout={this.props.layout}
|
layout={this.props.layout}
|
||||||
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
|
hideThreadedMessages={this.props.hideThreadedMessages}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace });
|
await createSpace(name, joinRule === JoinRule.Public, alias, topic, avatar, {}, { parentSpace, joinRule });
|
||||||
|
|
||||||
onFinished(true);
|
onFinished(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -80,7 +80,7 @@ const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => {
|
||||||
|
|
||||||
const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => {
|
const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => {
|
||||||
const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
||||||
const [state, setState] = useState<string>(RoomsToLeave.All);
|
const [state, setState] = useState<string>(RoomsToLeave.None);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state === RoomsToLeave.All) {
|
if (state === RoomsToLeave.All) {
|
||||||
|
@ -97,11 +97,11 @@ const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave
|
||||||
onChange={setState}
|
onChange={setState}
|
||||||
definitions={[
|
definitions={[
|
||||||
{
|
{
|
||||||
value: RoomsToLeave.All,
|
|
||||||
label: _t("Leave all rooms and spaces"),
|
|
||||||
}, {
|
|
||||||
value: RoomsToLeave.None,
|
value: RoomsToLeave.None,
|
||||||
label: _t("Don't leave any"),
|
label: _t("Don't leave any"),
|
||||||
|
}, {
|
||||||
|
value: RoomsToLeave.All,
|
||||||
|
label: _t("Leave all rooms and spaces"),
|
||||||
}, {
|
}, {
|
||||||
value: RoomsToLeave.Specific,
|
value: RoomsToLeave.Specific,
|
||||||
label: _t("Leave specific rooms and spaces"),
|
label: _t("Leave specific rooms and spaces"),
|
||||||
|
|
|
@ -419,6 +419,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<MemberAvatar
|
<MemberAvatar
|
||||||
member={mxEvent.sender}
|
member={mxEvent.sender}
|
||||||
|
fallbackUserId={mxEvent.getSender()}
|
||||||
width={32}
|
width={32}
|
||||||
height={32}
|
height={32}
|
||||||
viewUserOnClick={true}
|
viewUserOnClick={true}
|
||||||
|
|
|
@ -173,16 +173,16 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onChangeFilter = (filter: string) => {
|
private onChangeFilter = (filter: string) => {
|
||||||
filter = filter.toLowerCase(); // filter is case insensitive stored lower-case
|
const lcFilter = filter.toLowerCase().trim(); // filter is case insensitive
|
||||||
for (const cat of this.categories) {
|
for (const cat of this.categories) {
|
||||||
let emojis;
|
let emojis;
|
||||||
// If the new filter string includes the old filter string, we don't have to re-filter the whole dataset.
|
// If the new filter string includes the old filter string, we don't have to re-filter the whole dataset.
|
||||||
if (filter.includes(this.state.filter)) {
|
if (lcFilter.includes(this.state.filter)) {
|
||||||
emojis = this.memoizedDataByCategory[cat.id];
|
emojis = this.memoizedDataByCategory[cat.id];
|
||||||
} else {
|
} else {
|
||||||
emojis = cat.id === "recent" ? this.recentlyUsed : DATA_BY_CATEGORY[cat.id];
|
emojis = cat.id === "recent" ? this.recentlyUsed : DATA_BY_CATEGORY[cat.id];
|
||||||
}
|
}
|
||||||
emojis = emojis.filter(emoji => this.emojiMatchesFilter(emoji, filter));
|
emojis = emojis.filter(emoji => this.emojiMatchesFilter(emoji, lcFilter));
|
||||||
this.memoizedDataByCategory[cat.id] = emojis;
|
this.memoizedDataByCategory[cat.id] = emojis;
|
||||||
cat.enabled = emojis.length > 0;
|
cat.enabled = emojis.length > 0;
|
||||||
// The setState below doesn't re-render the header and we already have the refs for updateVisibility, so...
|
// The setState below doesn't re-render the header and we already have the refs for updateVisibility, so...
|
||||||
|
@ -194,9 +194,12 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
||||||
setTimeout(this.updateVisibility, 0);
|
setTimeout(this.updateVisibility, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean =>
|
private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean => {
|
||||||
[emoji.annotation, ...emoji.shortcodes, emoji.emoticon, ...emoji.unicode.split(ZERO_WIDTH_JOINER)]
|
return emoji.annotation.toLowerCase().includes(filter) ||
|
||||||
.some(x => x?.includes(filter));
|
emoji.emoticon?.toLowerCase().includes(filter) ||
|
||||||
|
emoji.shortcodes.some(x => x.toLowerCase().includes(filter)) ||
|
||||||
|
emoji.unicode.split(ZERO_WIDTH_JOINER).includes(filter);
|
||||||
|
};
|
||||||
|
|
||||||
private onEnterFilter = () => {
|
private onEnterFilter = () => {
|
||||||
const btn = this.bodyRef.current.querySelector<HTMLButtonElement>(".mx_EmojiPicker_item");
|
const btn = this.bodyRef.current.querySelector<HTMLButtonElement>(".mx_EmojiPicker_item");
|
||||||
|
|
|
@ -23,6 +23,8 @@ import { EventStatus } from 'matrix-js-sdk/src/models/event';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
import { Action } from '../../../dispatcher/actions';
|
||||||
|
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
|
||||||
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
|
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
|
||||||
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
|
@ -34,6 +36,7 @@ import Resend from "../../../Resend";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||||
import DownloadActionButton from "./DownloadActionButton";
|
import DownloadActionButton from "./DownloadActionButton";
|
||||||
|
import SettingsStore from '../../../settings/SettingsStore';
|
||||||
|
|
||||||
const OptionsButton = ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => {
|
const OptionsButton = ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
@ -170,6 +173,17 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onThreadClick = () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.ThreadView,
|
||||||
|
allowClose: false,
|
||||||
|
refireParams: {
|
||||||
|
event: this.props.mxEvent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onEditClick = (ev) => {
|
onEditClick = (ev) => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'edit_event',
|
action: 'edit_event',
|
||||||
|
@ -254,12 +268,22 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
// The only catch is we do the reply button first so that we can make sure the react
|
// The only catch is we do the reply button first so that we can make sure the react
|
||||||
// button is the very first button without having to do length checks for `splice()`.
|
// button is the very first button without having to do length checks for `splice()`.
|
||||||
if (this.context.canReply) {
|
if (this.context.canReply) {
|
||||||
toolbarOpts.splice(0, 0, <RovingAccessibleTooltipButton
|
toolbarOpts.splice(0, 0, <>
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
<RovingAccessibleTooltipButton
|
||||||
title={_t("Reply")}
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
||||||
onClick={this.onReplyClick}
|
title={_t("Reply")}
|
||||||
key="reply"
|
onClick={this.onReplyClick}
|
||||||
/>);
|
key="reply"
|
||||||
|
/>
|
||||||
|
{ SettingsStore.getValue("feature_thread") && (
|
||||||
|
<RovingAccessibleTooltipButton
|
||||||
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||||
|
title={_t("Thread")}
|
||||||
|
onClick={this.onThreadClick}
|
||||||
|
key="thread"
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</>);
|
||||||
}
|
}
|
||||||
if (this.context.canReact) {
|
if (this.context.canReact) {
|
||||||
toolbarOpts.splice(0, 0, <ReactButton
|
toolbarOpts.splice(0, 0, <ReactButton
|
||||||
|
|
|
@ -220,6 +220,13 @@ const onRoomFilesClick = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onRoomThreadsClick = () => {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.ThreadPanel,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onRoomSettingsClick = () => {
|
const onRoomSettingsClick = () => {
|
||||||
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
||||||
};
|
};
|
||||||
|
@ -273,6 +280,11 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
||||||
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
|
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
|
||||||
{ _t("Show files") }
|
{ _t("Show files") }
|
||||||
</Button>
|
</Button>
|
||||||
|
{ SettingsStore.getValue("feature_thread") && (
|
||||||
|
<Button className="mx_RoomSummaryCard_icon_threads" onClick={onRoomThreadsClick}>
|
||||||
|
{ _t("Show threads") }
|
||||||
|
</Button>
|
||||||
|
) }
|
||||||
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
|
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
|
||||||
{ _t("Share room") }
|
{ _t("Share room") }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import { Thread } from 'matrix-js-sdk/src/models/thread';
|
||||||
|
|
||||||
import ReplyThread from "../elements/ReplyThread";
|
import ReplyThread from "../elements/ReplyThread";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -55,6 +56,8 @@ import ReadReceiptMarker from "./ReadReceiptMarker";
|
||||||
import MessageActionBar from "../messages/MessageActionBar";
|
import MessageActionBar from "../messages/MessageActionBar";
|
||||||
import ReactionsRow from '../messages/ReactionsRow';
|
import ReactionsRow from '../messages/ReactionsRow';
|
||||||
import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
||||||
|
import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
const eventTileTypes = {
|
const eventTileTypes = {
|
||||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||||
|
@ -299,6 +302,9 @@ interface IProps {
|
||||||
|
|
||||||
// whether or not to display the sender
|
// whether or not to display the sender
|
||||||
hideSender?: boolean;
|
hideSender?: boolean;
|
||||||
|
|
||||||
|
// whether or not to display thread info
|
||||||
|
showThreadInfo?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -315,6 +321,8 @@ interface IState {
|
||||||
reactions: Relations;
|
reactions: Relations;
|
||||||
|
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
|
|
||||||
|
thread?: Thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.EventTile")
|
@replaceableComponent("views.rooms.EventTile")
|
||||||
|
@ -351,6 +359,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
reactions: this.getReactions(),
|
reactions: this.getReactions(),
|
||||||
|
|
||||||
hover: false,
|
hover: false,
|
||||||
|
|
||||||
|
thread: this.props.mxEvent?.getThread(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't do RR animations until we are mounted
|
// don't do RR animations until we are mounted
|
||||||
|
@ -451,8 +461,20 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
client.on("Room.receipt", this.onRoomReceipt);
|
client.on("Room.receipt", this.onRoomReceipt);
|
||||||
this.isListeningForReceipts = true;
|
this.isListeningForReceipts = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SettingsStore.getValue("feature_thread")) {
|
||||||
|
this.props.mxEvent.once("Thread.ready", this.updateThread);
|
||||||
|
this.props.mxEvent.on("Thread.update", this.updateThread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateThread = (thread) => {
|
||||||
|
this.setState({
|
||||||
|
thread,
|
||||||
|
});
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
|
@ -463,7 +485,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
||||||
if (objectHasDiff(this.state, nextState)) {
|
if (objectHasDiff(this.state, nextState)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -491,6 +513,43 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderThreadInfo(): React.ReactNode {
|
||||||
|
if (!SettingsStore.getValue("feature_thread")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thread = this.state.thread;
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
if (!thread || this.props.showThreadInfo === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatars = Array.from(thread.participants).map((mxId: string) => {
|
||||||
|
const member = room.getMember(mxId);
|
||||||
|
return <MemberAvatar key={member.userId} member={member} width={14} height={14} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="mx_ThreadInfo"
|
||||||
|
onClick={() => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.ThreadView,
|
||||||
|
refireParams: {
|
||||||
|
event: this.props.mxEvent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="mx_EventListSummary_avatars">
|
||||||
|
{ avatars }
|
||||||
|
</span>
|
||||||
|
{ thread.length - 1 } { thread.length === 2 ? 'reply' : 'replies' }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private onRoomReceipt = (ev, room) => {
|
private onRoomReceipt = (ev, room) => {
|
||||||
// ignore events for other rooms
|
// ignore events for other rooms
|
||||||
const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
@ -1180,6 +1239,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
{ keyRequestInfo }
|
{ keyRequestInfo }
|
||||||
{ actionBar }
|
{ actionBar }
|
||||||
{ this.props.layout === Layout.IRC && (reactionsRow) }
|
{ this.props.layout === Layout.IRC && (reactionsRow) }
|
||||||
|
{ this.renderThreadInfo() }
|
||||||
</div>
|
</div>
|
||||||
{ this.props.layout !== Layout.IRC && (reactionsRow) }
|
{ this.props.layout !== Layout.IRC && (reactionsRow) }
|
||||||
{ msgOption }
|
{ msgOption }
|
||||||
|
|
|
@ -185,8 +185,8 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
members: members,
|
members: members,
|
||||||
filteredJoinedMembers: this.filterMembers(members, 'join'),
|
filteredJoinedMembers: this.filterMembers(members, 'join', searchQuery),
|
||||||
filteredInvitedMembers: this.filterMembers(members, 'invite'),
|
filteredInvitedMembers: this.filterMembers(members, 'invite', searchQuery),
|
||||||
canInvite: this.canInvite,
|
canInvite: this.canInvite,
|
||||||
|
|
||||||
// ideally we'd size this to the page height, but
|
// ideally we'd size this to the page height, but
|
||||||
|
|
|
@ -183,7 +183,9 @@ interface IProps {
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
permalinkCreator: RoomPermalinkCreator;
|
permalinkCreator: RoomPermalinkCreator;
|
||||||
replyToEvent?: MatrixEvent;
|
replyToEvent?: MatrixEvent;
|
||||||
|
showReplyPreview?: boolean;
|
||||||
e2eStatus?: E2EStatus;
|
e2eStatus?: E2EStatus;
|
||||||
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -201,6 +203,11 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
private messageComposerInput: SendMessageComposer;
|
private messageComposerInput: SendMessageComposer;
|
||||||
private voiceRecordingButton: VoiceRecordComposerTile;
|
private voiceRecordingButton: VoiceRecordComposerTile;
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
showReplyPreview: true,
|
||||||
|
compact: false,
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
||||||
|
@ -362,7 +369,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const controls = [
|
const controls = [
|
||||||
this.state.me ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
|
this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
|
||||||
this.props.e2eStatus ?
|
this.props.e2eStatus ?
|
||||||
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
|
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
|
||||||
null,
|
null,
|
||||||
|
@ -450,11 +457,19 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const classes = classNames({
|
||||||
|
"mx_MessageComposer": true,
|
||||||
|
"mx_GroupLayout": true,
|
||||||
|
"mx_MessageComposer--compact": this.props.compact,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MessageComposer mx_GroupLayout">
|
<div className={classes}>
|
||||||
{ recordingTooltip }
|
{ recordingTooltip }
|
||||||
<div className="mx_MessageComposer_wrapper">
|
<div className="mx_MessageComposer_wrapper">
|
||||||
<ReplyPreview permalinkCreator={this.props.permalinkCreator} />
|
{ this.props.showReplyPreview && (
|
||||||
|
<ReplyPreview permalinkCreator={this.props.permalinkCreator} />
|
||||||
|
) }
|
||||||
<div className="mx_MessageComposer_row">
|
<div className="mx_MessageComposer_row">
|
||||||
{ controls }
|
{ controls }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,8 +54,8 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
||||||
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) ? <AccessibleButton
|
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) ? <AccessibleButton
|
||||||
className="mx_SpacePublicShare_inviteButton"
|
className="mx_SpacePublicShare_inviteButton"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showRoomInviteDialog(space.roomId);
|
|
||||||
if (onFinished) onFinished();
|
if (onFinished) onFinished();
|
||||||
|
showRoomInviteDialog(space.roomId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3>{ _t("Invite people") }</h3>
|
<h3>{ _t("Invite people") }</h3>
|
||||||
|
|
|
@ -426,9 +426,6 @@
|
||||||
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
|
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
|
||||||
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
|
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
|
||||||
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
|
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
|
||||||
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
|
|
||||||
"/ddg is not a command": "/ddg is not a command",
|
|
||||||
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
|
|
||||||
"Upgrades a room to a new version": "Upgrades a room to a new version",
|
"Upgrades a room to a new version": "Upgrades a room to a new version",
|
||||||
"You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.",
|
"You do not have the required permissions to use this command.": "You do not have the required permissions to use this command.",
|
||||||
"Changes your display nickname": "Changes your display nickname",
|
"Changes your display nickname": "Changes your display nickname",
|
||||||
|
@ -811,6 +808,7 @@
|
||||||
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
|
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
|
||||||
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.",
|
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.",
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
|
"Threaded messaging": "Threaded messaging",
|
||||||
"Custom user status messages": "Custom user status messages",
|
"Custom user status messages": "Custom user status messages",
|
||||||
"Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)",
|
"Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)",
|
||||||
"Render simple counters in room header": "Render simple counters in room header",
|
"Render simple counters in room header": "Render simple counters in room header",
|
||||||
|
@ -1808,6 +1806,7 @@
|
||||||
"%(count)s people|other": "%(count)s people",
|
"%(count)s people|other": "%(count)s people",
|
||||||
"%(count)s people|one": "%(count)s person",
|
"%(count)s people|one": "%(count)s person",
|
||||||
"Show files": "Show files",
|
"Show files": "Show files",
|
||||||
|
"Show threads": "Show threads",
|
||||||
"Share room": "Share room",
|
"Share room": "Share room",
|
||||||
"Room settings": "Room settings",
|
"Room settings": "Room settings",
|
||||||
"Trusted": "Trusted",
|
"Trusted": "Trusted",
|
||||||
|
@ -1927,6 +1926,7 @@
|
||||||
"React": "React",
|
"React": "React",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
"Reply": "Reply",
|
"Reply": "Reply",
|
||||||
|
"Thread": "Thread",
|
||||||
"Message Actions": "Message Actions",
|
"Message Actions": "Message Actions",
|
||||||
"Download %(text)s": "Download %(text)s",
|
"Download %(text)s": "Download %(text)s",
|
||||||
"Error decrypting attachment": "Error decrypting attachment",
|
"Error decrypting attachment": "Error decrypting attachment",
|
||||||
|
@ -2413,8 +2413,8 @@
|
||||||
"Clear cache and resync": "Clear cache and resync",
|
"Clear cache and resync": "Clear cache and resync",
|
||||||
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
||||||
"Updating %(brand)s": "Updating %(brand)s",
|
"Updating %(brand)s": "Updating %(brand)s",
|
||||||
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
|
||||||
"Don't leave any": "Don't leave any",
|
"Don't leave any": "Don't leave any",
|
||||||
|
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
||||||
"Leave specific rooms and spaces": "Leave specific rooms and spaces",
|
"Leave specific rooms and spaces": "Leave specific rooms and spaces",
|
||||||
"Search %(spaceName)s": "Search %(spaceName)s",
|
"Search %(spaceName)s": "Search %(spaceName)s",
|
||||||
"You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
|
"You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
|
||||||
|
@ -3004,8 +3004,6 @@
|
||||||
"Commands": "Commands",
|
"Commands": "Commands",
|
||||||
"Command Autocomplete": "Command Autocomplete",
|
"Command Autocomplete": "Command Autocomplete",
|
||||||
"Community Autocomplete": "Community Autocomplete",
|
"Community Autocomplete": "Community Autocomplete",
|
||||||
"Results from DuckDuckGo": "Results from DuckDuckGo",
|
|
||||||
"DuckDuckGo Results": "DuckDuckGo Results",
|
|
||||||
"Emoji": "Emoji",
|
"Emoji": "Emoji",
|
||||||
"Emoji Autocomplete": "Emoji Autocomplete",
|
"Emoji Autocomplete": "Emoji Autocomplete",
|
||||||
"Notify the whole room": "Notify the whole room",
|
"Notify the whole room": "Notify the whole room",
|
||||||
|
|
|
@ -211,6 +211,15 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
supportedLevels: LEVELS_FEATURE,
|
supportedLevels: LEVELS_FEATURE,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"feature_thread": {
|
||||||
|
isFeature: true,
|
||||||
|
// Requires a reload as we change an option flag on the `js-sdk`
|
||||||
|
// And the entire sync history needs to be parsed again
|
||||||
|
controller: new ReloadOnChangeController(),
|
||||||
|
displayName: _td("Threaded messaging"),
|
||||||
|
supportedLevels: LEVELS_FEATURE,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
"feature_custom_status": {
|
"feature_custom_status": {
|
||||||
isFeature: true,
|
isFeature: true,
|
||||||
displayName: _td("Custom user status messages"),
|
displayName: _td("Custom user status messages"),
|
||||||
|
|
|
@ -71,7 +71,13 @@ export default class DeviceSettingsHandler extends SettingsHandler {
|
||||||
// Special case for old useIRCLayout setting
|
// Special case for old useIRCLayout setting
|
||||||
if (settingName === "layout") {
|
if (settingName === "layout") {
|
||||||
const settings = this.getSettings() || {};
|
const settings = this.getSettings() || {};
|
||||||
if (settings["useIRCLayout"]) return Layout.IRC;
|
if (settings["useIRCLayout"]) {
|
||||||
|
// Set the new layout setting and delete the old one so that we
|
||||||
|
// can delete this block of code after some time
|
||||||
|
settings["layout"] = Layout.IRC;
|
||||||
|
delete settings["useIRCLayout"];
|
||||||
|
localStorage.setItem("mx_local_settings", JSON.stringify(settings));
|
||||||
|
}
|
||||||
return settings[settingName];
|
return settings[settingName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,10 @@ export enum RightPanelPhases {
|
||||||
SpaceMemberList = "SpaceMemberList",
|
SpaceMemberList = "SpaceMemberList",
|
||||||
SpaceMemberInfo = "SpaceMemberInfo",
|
SpaceMemberInfo = "SpaceMemberInfo",
|
||||||
Space3pidMemberInfo = "Space3pidMemberInfo",
|
Space3pidMemberInfo = "Space3pidMemberInfo",
|
||||||
|
|
||||||
|
// Thread stuff
|
||||||
|
ThreadView = "ThreadView",
|
||||||
|
ThreadPanel = "ThreadPanel",
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the phases that are safe to persist (the ones that don't require additional
|
// These are the phases that are safe to persist (the ones that don't require additional
|
||||||
|
|
|
@ -145,9 +145,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
return this._allRoomsInHome;
|
return this._allRoomsInHome;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setActiveRoomInSpace(space: Room | null): Promise<void> {
|
public setActiveRoomInSpace(space: Room | null): void {
|
||||||
if (space && !space.isSpaceRoom()) return;
|
if (space && !space.isSpaceRoom()) return;
|
||||||
if (space !== this.activeSpace) await this.setActiveSpace(space);
|
if (space !== this.activeSpace) this.setActiveSpace(space);
|
||||||
|
|
||||||
if (space) {
|
if (space) {
|
||||||
const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications();
|
const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications();
|
||||||
|
@ -190,7 +190,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
* @param contextSwitch whether to switch the user's context,
|
* @param contextSwitch whether to switch the user's context,
|
||||||
* should not be done when the space switch is done implicitly due to another event like switching room.
|
* should not be done when the space switch is done implicitly due to another event like switching room.
|
||||||
*/
|
*/
|
||||||
public async setActiveSpace(space: Room | null, contextSwitch = true) {
|
public setActiveSpace(space: Room | null, contextSwitch = true) {
|
||||||
if (space === this.activeSpace || (space && !space.isSpaceRoom())) return;
|
if (space === this.activeSpace || (space && !space.isSpaceRoom())) return;
|
||||||
|
|
||||||
this._activeSpace = space;
|
this._activeSpace = space;
|
||||||
|
@ -293,11 +293,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space) {
|
if (space) {
|
||||||
const suggestedRooms = await this.fetchSuggestedRooms(space);
|
this.loadSuggestedRooms(space);
|
||||||
if (this._activeSpace === space) {
|
}
|
||||||
this._suggestedRooms = suggestedRooms;
|
}
|
||||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
|
||||||
}
|
private async loadSuggestedRooms(space) {
|
||||||
|
const suggestedRooms = await this.fetchSuggestedRooms(space);
|
||||||
|
if (this._activeSpace === space) {
|
||||||
|
this._suggestedRooms = suggestedRooms;
|
||||||
|
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,6 +670,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.onSpaceUpdate();
|
this.onSpaceUpdate();
|
||||||
this.emit(room.roomId);
|
this.emit(room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (room === this.activeSpace && // current space
|
||||||
|
this.matrixClient.getRoom(ev.getStateKey())?.getMyMembership() !== "join" && // target not joined
|
||||||
|
ev.getPrevContent().suggested !== ev.getContent().suggested // suggested flag changed
|
||||||
|
) {
|
||||||
|
this.loadSuggestedRooms(room);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EventType.SpaceParent:
|
case EventType.SpaceParent:
|
||||||
|
@ -678,12 +690,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
this.emit(room.roomId);
|
this.emit(room.roomId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
case EventType.RoomMember:
|
// listening for m.room.member events in onRoomState above doesn't work as the Member object isn't updated by then
|
||||||
if (room.isSpaceRoom()) {
|
private onRoomStateMembers = (ev: MatrixEvent) => {
|
||||||
this.onSpaceMembersChange(ev);
|
const room = this.matrixClient.getRoom(ev.getRoomId());
|
||||||
}
|
if (room?.isSpaceRoom()) {
|
||||||
break;
|
this.onSpaceMembersChange(ev);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -743,6 +757,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
|
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
|
||||||
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
|
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
|
||||||
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
|
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
|
||||||
|
this.matrixClient.removeListener("RoomState.members", this.onRoomStateMembers);
|
||||||
this.matrixClient.removeListener("accountData", this.onAccountData);
|
this.matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
await this.reset();
|
await this.reset();
|
||||||
|
@ -754,6 +769,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.matrixClient.on("Room.myMembership", this.onRoom);
|
this.matrixClient.on("Room.myMembership", this.onRoom);
|
||||||
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
|
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
|
||||||
this.matrixClient.on("RoomState.events", this.onRoomState);
|
this.matrixClient.on("RoomState.events", this.onRoomState);
|
||||||
|
this.matrixClient.on("RoomState.members", this.onRoomStateMembers);
|
||||||
this.matrixClient.on("accountData", this.onAccountData);
|
this.matrixClient.on("accountData", this.onAccountData);
|
||||||
|
|
||||||
this.matrixClient.getCapabilities().then(capabilities => {
|
this.matrixClient.getCapabilities().then(capabilities => {
|
||||||
|
|
|
@ -185,7 +185,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
limitPerRoom: number,
|
limitPerRoom: number,
|
||||||
roomIds: (string | Symbols.AnyRoom)[] = null,
|
roomIds: (string | Symbols.AnyRoom)[] = null,
|
||||||
): Promise<object[]> {
|
): Promise<object[]> {
|
||||||
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, 25) : 25; // arbitrary choice
|
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
|
||||||
|
|
||||||
const rooms = this.pickRooms(roomIds);
|
const rooms = this.pickRooms(roomIds);
|
||||||
const allResults: IEvent[] = [];
|
const allResults: IEvent[] = [];
|
||||||
|
@ -212,7 +212,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
limitPerRoom: number,
|
limitPerRoom: number,
|
||||||
roomIds: (string | Symbols.AnyRoom)[] = null,
|
roomIds: (string | Symbols.AnyRoom)[] = null,
|
||||||
): Promise<object[]> {
|
): Promise<object[]> {
|
||||||
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, 100) : 100; // arbitrary choice
|
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
|
||||||
|
|
||||||
const rooms = this.pickRooms(roomIds);
|
const rooms = this.pickRooms(roomIds);
|
||||||
const allResults: IEvent[] = [];
|
const allResults: IEvent[] = [];
|
||||||
|
|
|
@ -562,7 +562,7 @@ describe("SpaceStore", () => {
|
||||||
]);
|
]);
|
||||||
mkSpace(space3).getMyMembership.mockReturnValue("invite");
|
mkSpace(space3).getMyMembership.mockReturnValue("invite");
|
||||||
await run();
|
await run();
|
||||||
await store.setActiveSpace(null);
|
store.setActiveSpace(null);
|
||||||
expect(store.activeSpace).toBe(null);
|
expect(store.activeSpace).toBe(null);
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -570,31 +570,31 @@ describe("SpaceStore", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to home space", async () => {
|
it("switch to home space", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
fn.mockClear();
|
fn.mockClear();
|
||||||
|
|
||||||
await store.setActiveSpace(null);
|
store.setActiveSpace(null);
|
||||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null);
|
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null);
|
||||||
expect(store.activeSpace).toBe(null);
|
expect(store.activeSpace).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to invited space", async () => {
|
it("switch to invited space", async () => {
|
||||||
const space = client.getRoom(space3);
|
const space = client.getRoom(space3);
|
||||||
await store.setActiveSpace(space);
|
store.setActiveSpace(space);
|
||||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||||
expect(store.activeSpace).toBe(space);
|
expect(store.activeSpace).toBe(space);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to top level space", async () => {
|
it("switch to top level space", async () => {
|
||||||
const space = client.getRoom(space1);
|
const space = client.getRoom(space1);
|
||||||
await store.setActiveSpace(space);
|
store.setActiveSpace(space);
|
||||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||||
expect(store.activeSpace).toBe(space);
|
expect(store.activeSpace).toBe(space);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to subspace", async () => {
|
it("switch to subspace", async () => {
|
||||||
const space = client.getRoom(space2);
|
const space = client.getRoom(space2);
|
||||||
await store.setActiveSpace(space);
|
store.setActiveSpace(space);
|
||||||
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||||
expect(store.activeSpace).toBe(space);
|
expect(store.activeSpace).toBe(space);
|
||||||
});
|
});
|
||||||
|
@ -602,7 +602,7 @@ describe("SpaceStore", () => {
|
||||||
it("switch to unknown space is a nop", async () => {
|
it("switch to unknown space is a nop", async () => {
|
||||||
expect(store.activeSpace).toBe(null);
|
expect(store.activeSpace).toBe(null);
|
||||||
const space = client.getRoom(room1); // not a space
|
const space = client.getRoom(room1); // not a space
|
||||||
await store.setActiveSpace(space);
|
store.setActiveSpace(space);
|
||||||
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space);
|
||||||
expect(store.activeSpace).toBe(null);
|
expect(store.activeSpace).toBe(null);
|
||||||
});
|
});
|
||||||
|
@ -635,59 +635,59 @@ describe("SpaceStore", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
it("last viewed room in target space is the current viewed and in both spaces", async () => {
|
it("last viewed room in target space is the current viewed and in both spaces", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(client.getRoom(space2));
|
store.setActiveSpace(client.getRoom(space2));
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
expect(getCurrentRoom()).toBe(room2);
|
expect(getCurrentRoom()).toBe(room2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("last viewed room in target space is in the current space", async () => {
|
it("last viewed room in target space is in the current space", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(client.getRoom(space2));
|
store.setActiveSpace(client.getRoom(space2));
|
||||||
expect(getCurrentRoom()).toBe(space2);
|
expect(getCurrentRoom()).toBe(space2);
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
expect(getCurrentRoom()).toBe(room2);
|
expect(getCurrentRoom()).toBe(room2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("last viewed room in target space is not in the current space", async () => {
|
it("last viewed room in target space is not in the current space", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(client.getRoom(space2));
|
store.setActiveSpace(client.getRoom(space2));
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
expect(getCurrentRoom()).toBe(room1);
|
expect(getCurrentRoom()).toBe(room1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("last viewed room is target space is not known", async () => {
|
it("last viewed room is target space is not known", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
localStorage.setItem(`mx_space_context_${space2}`, orphan2);
|
localStorage.setItem(`mx_space_context_${space2}`, orphan2);
|
||||||
await store.setActiveSpace(client.getRoom(space2));
|
store.setActiveSpace(client.getRoom(space2));
|
||||||
expect(getCurrentRoom()).toBe(space2);
|
expect(getCurrentRoom()).toBe(space2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("last viewed room is target space is no longer in that space", async () => {
|
it("last viewed room is target space is no longer in that space", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
localStorage.setItem(`mx_space_context_${space2}`, room1);
|
localStorage.setItem(`mx_space_context_${space2}`, room1);
|
||||||
await store.setActiveSpace(client.getRoom(space2));
|
store.setActiveSpace(client.getRoom(space2));
|
||||||
expect(getCurrentRoom()).toBe(space2); // Space home instead of room1
|
expect(getCurrentRoom()).toBe(space2); // Space home instead of room1
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no last viewed room in target space", async () => {
|
it("no last viewed room in target space", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(client.getRoom(space2));
|
store.setActiveSpace(client.getRoom(space2));
|
||||||
expect(getCurrentRoom()).toBe(space2);
|
expect(getCurrentRoom()).toBe(space2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no last viewed room in home space", async () => {
|
it("no last viewed room in home space", async () => {
|
||||||
await store.setActiveSpace(client.getRoom(space1));
|
store.setActiveSpace(client.getRoom(space1));
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(null);
|
store.setActiveSpace(null);
|
||||||
expect(getCurrentRoom()).toBeNull(); // Home
|
expect(getCurrentRoom()).toBeNull(); // Home
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -715,28 +715,28 @@ describe("SpaceStore", () => {
|
||||||
|
|
||||||
it("no switch required, room is in current space", async () => {
|
it("no switch required, room is in current space", async () => {
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(client.getRoom(space1), false);
|
store.setActiveSpace(client.getRoom(space1), false);
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to canonical parent space for room", async () => {
|
it("switch to canonical parent space for room", async () => {
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(client.getRoom(space2), false);
|
store.setActiveSpace(client.getRoom(space2), false);
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
expect(store.activeSpace).toBe(client.getRoom(space2));
|
expect(store.activeSpace).toBe(client.getRoom(space2));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to first containing space for room", async () => {
|
it("switch to first containing space for room", async () => {
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(client.getRoom(space2), false);
|
store.setActiveSpace(client.getRoom(space2), false);
|
||||||
viewRoom(room3);
|
viewRoom(room3);
|
||||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to home for orphaned room", async () => {
|
it("switch to home for orphaned room", async () => {
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(client.getRoom(space1), false);
|
store.setActiveSpace(client.getRoom(space1), false);
|
||||||
viewRoom(orphan1);
|
viewRoom(orphan1);
|
||||||
expect(store.activeSpace).toBeNull();
|
expect(store.activeSpace).toBeNull();
|
||||||
});
|
});
|
||||||
|
@ -744,7 +744,7 @@ describe("SpaceStore", () => {
|
||||||
it("when switching rooms in the all rooms home space don't switch to related space", async () => {
|
it("when switching rooms in the all rooms home space don't switch to related space", async () => {
|
||||||
await setShowAllRooms(true);
|
await setShowAllRooms(true);
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(null, false);
|
store.setActiveSpace(null, false);
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
expect(store.activeSpace).toBeNull();
|
expect(store.activeSpace).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe("SpaceWatcher", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
filter = null;
|
filter = null;
|
||||||
store.removeAllListeners();
|
store.removeAllListeners();
|
||||||
await store.setActiveSpace(null);
|
store.setActiveSpace(null);
|
||||||
client.getVisibleRooms.mockReturnValue(rooms = []);
|
client.getVisibleRooms.mockReturnValue(rooms = []);
|
||||||
|
|
||||||
space1 = mkSpace(space1Id);
|
space1 = mkSpace(space1Id);
|
||||||
|
@ -95,7 +95,7 @@ describe("SpaceWatcher", () => {
|
||||||
await setShowAllRooms(true);
|
await setShowAllRooms(true);
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
|
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter["space"]).toBe(space1);
|
||||||
|
@ -114,7 +114,7 @@ describe("SpaceWatcher", () => {
|
||||||
await setShowAllRooms(false);
|
await setShowAllRooms(false);
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
|
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter["space"]).toBe(space1);
|
||||||
|
@ -124,22 +124,22 @@ describe("SpaceWatcher", () => {
|
||||||
await setShowAllRooms(true);
|
await setShowAllRooms(true);
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
|
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter["space"]).toBe(space1);
|
||||||
await SpaceStore.instance.setActiveSpace(null);
|
SpaceStore.instance.setActiveSpace(null);
|
||||||
|
|
||||||
expect(filter).toBeNull();
|
expect(filter).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates filter correctly for space -> home transition", async () => {
|
it("updates filter correctly for space -> home transition", async () => {
|
||||||
await setShowAllRooms(false);
|
await setShowAllRooms(false);
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter["space"]).toBe(space1);
|
||||||
await SpaceStore.instance.setActiveSpace(null);
|
SpaceStore.instance.setActiveSpace(null);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(null);
|
expect(filter["space"]).toBe(null);
|
||||||
|
@ -147,12 +147,12 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
it("updates filter correctly for space -> space transition", async () => {
|
it("updates filter correctly for space -> space transition", async () => {
|
||||||
await setShowAllRooms(false);
|
await setShowAllRooms(false);
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space1);
|
expect(filter["space"]).toBe(space1);
|
||||||
await SpaceStore.instance.setActiveSpace(space2);
|
SpaceStore.instance.setActiveSpace(space2);
|
||||||
|
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
expect(filter["space"]).toBe(space2);
|
expect(filter["space"]).toBe(space2);
|
||||||
|
@ -160,7 +160,7 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
it("doesn't change filter when changing showAllRooms mode to true", async () => {
|
it("doesn't change filter when changing showAllRooms mode to true", async () => {
|
||||||
await setShowAllRooms(false);
|
await setShowAllRooms(false);
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
|
@ -173,7 +173,7 @@ describe("SpaceWatcher", () => {
|
||||||
|
|
||||||
it("doesn't change filter when changing showAllRooms mode to false", async () => {
|
it("doesn't change filter when changing showAllRooms mode to false", async () => {
|
||||||
await setShowAllRooms(true);
|
await setShowAllRooms(true);
|
||||||
await SpaceStore.instance.setActiveSpace(space1);
|
SpaceStore.instance.setActiveSpace(space1);
|
||||||
|
|
||||||
new SpaceWatcher(mockRoomListStore);
|
new SpaceWatcher(mockRoomListStore);
|
||||||
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
expect(filter).toBeInstanceOf(SpaceFilterCondition);
|
||||||
|
|
|
@ -5792,8 +5792,8 @@ mathml-tag-names@^2.1.3:
|
||||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||||
version "12.3.1"
|
version "12.4.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/92d822d494cc1179affbf1f14fc8635ed5595b3c"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2783d162b77d6629c574f35e88bea9ae29765c34"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue