mirror of https://github.com/vector-im/riot-web
				
				
				
			Merge branch 'experimental' of github.com:matrix-org/matrix-react-sdk into erikj/state_counters
						commit
						cbf9ff6aee
					
				| 
						 | 
				
			
			@ -18,7 +18,6 @@ src/components/structures/ScrollPanel.js
 | 
			
		|||
src/components/structures/SearchBox.js
 | 
			
		||||
src/components/structures/TimelinePanel.js
 | 
			
		||||
src/components/structures/UploadBar.js
 | 
			
		||||
src/components/structures/UserSettings.js
 | 
			
		||||
src/components/views/avatars/BaseAvatar.js
 | 
			
		||||
src/components/views/avatars/MemberAvatar.js
 | 
			
		||||
src/components/views/create_room/RoomAlias.js
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								.travis.yml
								
								
								
								
							
							
						
						
									
										22
									
								
								.travis.yml
								
								
								
								
							| 
						 | 
				
			
			@ -14,8 +14,22 @@ node_js:
 | 
			
		|||
addons:
 | 
			
		||||
    chrome: stable
 | 
			
		||||
install:
 | 
			
		||||
    - npm install
 | 
			
		||||
# install synapse prerequisites for end to end tests
 | 
			
		||||
    - ./scripts/travis/install-deps.sh
 | 
			
		||||
matrix:
 | 
			
		||||
    include:
 | 
			
		||||
        - name: Linting Checks
 | 
			
		||||
          script:
 | 
			
		||||
            # run the linter, but exclude any files known to have errors or warnings.
 | 
			
		||||
            - npm run lintwithexclusions
 | 
			
		||||
        - name: End-to-End Tests
 | 
			
		||||
          if: branch = develop
 | 
			
		||||
          install:
 | 
			
		||||
            - sudo apt-get install build-essential python2.7-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev
 | 
			
		||||
script:
 | 
			
		||||
    ./scripts/travis.sh
 | 
			
		||||
          script:
 | 
			
		||||
            - ./scripts/travis/end-to-end-tests.sh
 | 
			
		||||
        - name: Unit Tests
 | 
			
		||||
          script:
 | 
			
		||||
            - ./scripts/travis/unit-tests.sh
 | 
			
		||||
        - name: Riot-web Unit Tests
 | 
			
		||||
          script:
 | 
			
		||||
            - ./scripts/travis/riot-unit-tests.sh
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,7 +47,7 @@
 | 
			
		|||
    "start:init": "babel src -d lib --source-maps --copy-files",
 | 
			
		||||
    "lint": "eslint src/",
 | 
			
		||||
    "lintall": "eslint src/ test/",
 | 
			
		||||
    "lintwithexclusions": "eslint --max-warnings 20 --ignore-path .eslintignore.errorfiles src test",
 | 
			
		||||
    "lintwithexclusions": "eslint --max-warnings 19 --ignore-path .eslintignore.errorfiles src test",
 | 
			
		||||
    "clean": "rimraf lib",
 | 
			
		||||
    "prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt",
 | 
			
		||||
    "test": "karma start --single-run=true --browsers ChromeHeadless",
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +118,7 @@
 | 
			
		|||
    "babel-preset-react": "^6.24.1",
 | 
			
		||||
    "chokidar": "^1.6.1",
 | 
			
		||||
    "concurrently": "^4.0.1",
 | 
			
		||||
    "eslint": "^5.8.0",
 | 
			
		||||
    "eslint": "^5.12.0",
 | 
			
		||||
    "eslint-config-google": "^0.7.1",
 | 
			
		||||
    "eslint-plugin-babel": "^5.2.1",
 | 
			
		||||
    "eslint-plugin-flowtype": "^2.30.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@
 | 
			
		|||
@import "./structures/_ContextualMenu.scss";
 | 
			
		||||
@import "./structures/_CreateRoom.scss";
 | 
			
		||||
@import "./structures/_FilePanel.scss";
 | 
			
		||||
@import "./structures/_GroupGridView.scss";
 | 
			
		||||
@import "./structures/_GroupView.scss";
 | 
			
		||||
@import "./structures/_HomePage.scss";
 | 
			
		||||
@import "./structures/_LeftPanel.scss";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,130 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2017 Vector Creations 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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(3, calc(100% / 3));
 | 
			
		||||
    grid-template-rows: repeat(2, calc(100% / 2));
 | 
			
		||||
    flex: 1 1 0;
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rightPanel {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
 | 
			
		||||
    .mx_GroupGridView_tabs {
 | 
			
		||||
        flex: 0 0 52px;
 | 
			
		||||
        border-bottom: 1px solid $primary-hairline-color;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
 | 
			
		||||
        > div {
 | 
			
		||||
            justify-content: flex-end;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            margin-right: 10px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_RightPanel {
 | 
			
		||||
        flex: 1 0 auto !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView > .mx_MainSplit {
 | 
			
		||||
    flex: 1 1 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_emptyTile {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-top: 100px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_tile {
 | 
			
		||||
    border-right: 1px solid $panel-divider-color;
 | 
			
		||||
    border-bottom: 1px solid $panel-divider-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_activeTile {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_activeTile:before,
 | 
			
		||||
.mx_GroupGridView_activeTile:after {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    content: "";
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    z-index: 3500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_activeTile:before {
 | 
			
		||||
    border-radius: 14px;
 | 
			
		||||
    border: 8px solid $gridview-focus-border-glow-color;
 | 
			
		||||
    margin: -8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_activeTile:after {
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    border: 2px solid $gridview-focus-border-color;
 | 
			
		||||
    margin: -2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_tile > .mx_RoomView {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms > *:nth-child(1) {
 | 
			
		||||
    grid-column: 1;
 | 
			
		||||
    grid-row: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms > *:nth-child(2) {
 | 
			
		||||
    grid-column: 2;
 | 
			
		||||
    grid-row: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms > *:nth-child(3) {
 | 
			
		||||
    grid-column: 3;
 | 
			
		||||
    grid-row: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms > *:nth-child(4) {
 | 
			
		||||
    grid-column: 1;
 | 
			
		||||
    grid-row: 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms > *:nth-child(5) {
 | 
			
		||||
    grid-column: 2;
 | 
			
		||||
    grid-row: 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_GroupGridView_rooms > *:nth-child(6) {
 | 
			
		||||
    grid-column: 3;
 | 
			
		||||
    grid-row: 2;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -41,10 +41,6 @@ limitations under the License.
 | 
			
		|||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_LeftPanel_callView {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_LeftPanel {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,14 +73,16 @@ limitations under the License.
 | 
			
		|||
.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_ResizeHandle) {
 | 
			
		||||
    background-color: $primary-bg-color;
 | 
			
		||||
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    flex: 1 1 0;
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
 | 
			
		||||
    /* Experimental fix for https://github.com/vector-im/vector-web/issues/947
 | 
			
		||||
       and https://github.com/vector-im/vector-web/issues/946.
 | 
			
		||||
       Empirically this stops the MessagePanel's width exploding outwards when
 | 
			
		||||
       gemini is in 'prevented' mode
 | 
			
		||||
       */
 | 
			
		||||
    overflow-x: auto;
 | 
			
		||||
    // disabling this for now as it clips the active room rect on the grid view
 | 
			
		||||
    // overflow-x: auto;
 | 
			
		||||
 | 
			
		||||
    /* To fix https://github.com/vector-im/riot-web/issues/3298 where Safari
 | 
			
		||||
       needed height 100% all the way down to the HomePage. Height does not
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,10 +14,6 @@ See the License for the specific language governing permissions and
 | 
			
		|||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
.mx_CreateKeyBackupDialog {
 | 
			
		||||
    padding-right: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_CreateKeyBackupDialog .mx_Dialog_title {
 | 
			
		||||
    /* TODO: Consider setting this for all dialog titles. */
 | 
			
		||||
    margin-bottom: 1em;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ limitations under the License.
 | 
			
		|||
    padding-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
    &:before {
 | 
			
		||||
        mask: url("../../../img/e2e/lock-warning.svg");
 | 
			
		||||
        mask: url("../../img/e2e/lock-warning.svg");
 | 
			
		||||
        mask-repeat: no-repeat;
 | 
			
		||||
        background-color: $primary-fg-color;
 | 
			
		||||
        content: "";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,11 @@
 | 
			
		|||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* More specific to override `.markdown-body a` text-decoration */
 | 
			
		||||
.mx_EventTile_content .markdown-body a.mx_Pill {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* More specific to override `.markdown-body a` color */
 | 
			
		||||
.mx_EventTile_content .markdown-body a.mx_UserPill,
 | 
			
		||||
.mx_UserPill {
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +36,9 @@
 | 
			
		|||
    background-color: $accent-color ! important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* More specific to override `.markdown-body a` color */
 | 
			
		||||
.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me,
 | 
			
		||||
.mx_EventTile_content .markdown-body a.mx_AtRoomPill,
 | 
			
		||||
.mx_EventTile_content .mx_AtRoomPill,
 | 
			
		||||
.mx_MessageComposer_input .mx_AtRoomPill {
 | 
			
		||||
    color: $accent-fg-color;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,4 +56,6 @@ limitations under the License.
 | 
			
		|||
    border-radius: 5px;
 | 
			
		||||
    background: $imagebody-giflabel;
 | 
			
		||||
    border: 2px solid $imagebody-giflabel-border;
 | 
			
		||||
    color: $imagebody-giflabel-color;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,10 @@ limitations under the License.
 | 
			
		|||
.mx_MemberList_query,
 | 
			
		||||
.mx_GroupMemberList_query,
 | 
			
		||||
.mx_GroupRoomList_query {
 | 
			
		||||
    flex: 0 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_MemberList .gm-scrollbar-container {
 | 
			
		||||
    flex: 1 1 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,12 +54,14 @@ limitations under the License.
 | 
			
		|||
    align-items: center;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_RoomTile_labelContainer {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_RoomTile_subtext {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +101,6 @@ limitations under the License.
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
.mx_RoomTile_name {
 | 
			
		||||
    flex: 1 5 auto;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    font-weight: 600;
 | 
			
		||||
    padding: 0 6px;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<svg width="22px" height="14px" viewBox="0 0 22 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 | 
			
		||||
    <!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
 | 
			
		||||
    <title>Group 2</title>
 | 
			
		||||
    <desc>Created with Sketch.</desc>
 | 
			
		||||
    <g id="Experiments" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
 | 
			
		||||
        <g id="multi-room-test-copy-8" transform="translate(-826.000000, -15.000000)" stroke="#929EB4" stroke-width="1.6">
 | 
			
		||||
            <g id="Group-4" transform="translate(341.000000, 7.000000)">
 | 
			
		||||
                <g id="Group-2" transform="translate(486.000000, 8.000000)">
 | 
			
		||||
                    <path d="M20,1 L2.30926389e-14,1" id="Line-10"></path>
 | 
			
		||||
                    <path d="M20,7 L3,7" id="Line-10-Copy"></path>
 | 
			
		||||
                    <path d="M20,13 L6,13" id="Line-10-Copy-2"></path>
 | 
			
		||||
                </g>
 | 
			
		||||
            </g>
 | 
			
		||||
        </g>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 995 B  | 
| 
						 | 
				
			
			@ -162,8 +162,13 @@ $lightbox-bg-color: #454545;
 | 
			
		|||
$lightbox-fg-color: #ffffff;
 | 
			
		||||
$lightbox-border-color: #ffffff;
 | 
			
		||||
 | 
			
		||||
/*** GroupGridView ***/
 | 
			
		||||
$gridview-focus-border-glow-color: rgba(134, 193, 165, 0.5);
 | 
			
		||||
$gridview-focus-border-color: rgba(134, 193, 165, 1);
 | 
			
		||||
 | 
			
		||||
$imagebody-giflabel: rgba(1, 1, 1, 0.7);
 | 
			
		||||
$imagebody-giflabel-border: rgba(1, 1, 1, 0.2);
 | 
			
		||||
$imagebody-giflabel-color: rgba(0, 0, 0, 1);
 | 
			
		||||
 | 
			
		||||
// unused?
 | 
			
		||||
$progressbar-color: #000;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,6 +99,7 @@ $lightbox-background-bg-color: #000;
 | 
			
		|||
 | 
			
		||||
$imagebody-giflabel: rgba(0, 0, 0, 0.7);
 | 
			
		||||
$imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
 | 
			
		||||
$imagebody-giflabel-color: rgba(255, 255, 255, 1);
 | 
			
		||||
 | 
			
		||||
$greyed-fg-color: #888;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -183,6 +184,9 @@ $lightbox-bg-color: #454545;
 | 
			
		|||
$lightbox-fg-color: #ffffff;
 | 
			
		||||
$lightbox-border-color: #ffffff;
 | 
			
		||||
 | 
			
		||||
/*** GroupGridView ***/
 | 
			
		||||
$gridview-focus-border-glow-color: rgba(134, 193, 165, 0.5);
 | 
			
		||||
$gridview-focus-border-color: rgba(134, 193, 165, 1);
 | 
			
		||||
// unused?
 | 
			
		||||
$progressbar-color: #000;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -175,8 +175,13 @@ $lightbox-bg-color: #454545;
 | 
			
		|||
$lightbox-fg-color: #ffffff;
 | 
			
		||||
$lightbox-border-color: #ffffff;
 | 
			
		||||
 | 
			
		||||
/*** GroupGridView ***/
 | 
			
		||||
$gridview-focus-border-glow-color: rgba(134, 193, 165, 0.5);
 | 
			
		||||
$gridview-focus-border-color: rgba(134, 193, 165, 1);
 | 
			
		||||
 | 
			
		||||
$imagebody-giflabel: rgba(0, 0, 0, 0.7);
 | 
			
		||||
$imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
 | 
			
		||||
$imagebody-giflabel-color: rgba(255, 255, 255, 1);
 | 
			
		||||
 | 
			
		||||
// unused?
 | 
			
		||||
$progressbar-color: #000;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,18 +24,4 @@ rm -r node_modules/matrix-react-sdk
 | 
			
		|||
ln -s "$REACT_SDK_DIR" node_modules/matrix-react-sdk
 | 
			
		||||
 | 
			
		||||
npm run build
 | 
			
		||||
npm run test
 | 
			
		||||
popd
 | 
			
		||||
 | 
			
		||||
if [ "$TRAVIS_BRANCH" = "develop" ]
 | 
			
		||||
then
 | 
			
		||||
    # run end to end tests
 | 
			
		||||
    scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master
 | 
			
		||||
    pushd matrix-react-end-to-end-tests
 | 
			
		||||
    ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
 | 
			
		||||
    # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
 | 
			
		||||
    # CHROME_PATH=$(which google-chrome-stable) ./run.sh
 | 
			
		||||
    ./install.sh
 | 
			
		||||
    ./run.sh --travis
 | 
			
		||||
    popd
 | 
			
		||||
fi
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
#
 | 
			
		||||
# script which is run by the travis build (after `npm run test`).
 | 
			
		||||
#
 | 
			
		||||
# clones riot-web develop and runs the tests against our version of react-sdk.
 | 
			
		||||
 | 
			
		||||
set -ev
 | 
			
		||||
 | 
			
		||||
RIOT_WEB_DIR=riot-web
 | 
			
		||||
REACT_SDK_DIR=`pwd`
 | 
			
		||||
 | 
			
		||||
scripts/travis/build.sh
 | 
			
		||||
# run end to end tests
 | 
			
		||||
scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master
 | 
			
		||||
pushd matrix-react-end-to-end-tests
 | 
			
		||||
ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
 | 
			
		||||
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
 | 
			
		||||
# CHROME_PATH=$(which google-chrome-stable) ./run.sh
 | 
			
		||||
./install.sh
 | 
			
		||||
./run.sh --travis
 | 
			
		||||
popd
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
set -ex
 | 
			
		||||
 | 
			
		||||
npm install
 | 
			
		||||
scripts/fetchdep.sh matrix-org matrix-js-sdk
 | 
			
		||||
rm -r node_modules/matrix-js-sdk || true
 | 
			
		||||
ln -s ../matrix-js-sdk node_modules/matrix-js-sdk
 | 
			
		||||
| 
						 | 
				
			
			@ -9,9 +9,3 @@ ln -s ../matrix-js-sdk node_modules/matrix-js-sdk
 | 
			
		|||
cd matrix-js-sdk
 | 
			
		||||
npm install
 | 
			
		||||
cd ..
 | 
			
		||||
 | 
			
		||||
npm run test
 | 
			
		||||
./.travis-test-riot.sh
 | 
			
		||||
 | 
			
		||||
# run the linter, but exclude any files known to have errors or warnings.
 | 
			
		||||
npm run lintwithexclusions
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
#
 | 
			
		||||
# script which is run by the travis build (after `npm run test`).
 | 
			
		||||
#
 | 
			
		||||
# clones riot-web develop and runs the tests against our version of react-sdk.
 | 
			
		||||
 | 
			
		||||
set -ev
 | 
			
		||||
 | 
			
		||||
RIOT_WEB_DIR=riot-web
 | 
			
		||||
 | 
			
		||||
scripts/travis/build.sh
 | 
			
		||||
pushd "$RIOT_WEB_DIR"
 | 
			
		||||
npm run test
 | 
			
		||||
popd
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
#
 | 
			
		||||
# script which is run by the travis build (after `npm run test`).
 | 
			
		||||
#
 | 
			
		||||
# clones riot-web develop and runs the tests against our version of react-sdk.
 | 
			
		||||
 | 
			
		||||
set -ev
 | 
			
		||||
 | 
			
		||||
scripts/travis/build.sh
 | 
			
		||||
npm run test
 | 
			
		||||
| 
						 | 
				
			
			@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
 | 
			
		|||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import RoomViewStore from './stores/RoomViewStore';
 | 
			
		||||
import OpenRoomsStore from './stores/OpenRoomsStore';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consumes changes from the RoomViewStore and notifies specific things
 | 
			
		||||
 * Consumes changes from the OpenRoomsStore and notifies specific things
 | 
			
		||||
 * about when the active room changes. Unlike listening for RoomViewStore
 | 
			
		||||
 * changes, you can subscribe to only changes relevant to a particular
 | 
			
		||||
 * room.
 | 
			
		||||
| 
						 | 
				
			
			@ -28,11 +28,15 @@ import RoomViewStore from './stores/RoomViewStore';
 | 
			
		|||
class ActiveRoomObserver {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this._listeners = {};
 | 
			
		||||
 | 
			
		||||
        this._activeRoomId = RoomViewStore.getRoomId();
 | 
			
		||||
        const roomStore = OpenRoomsStore.getActiveRoomStore();
 | 
			
		||||
        this._activeRoomId = roomStore && roomStore.getRoomId();
 | 
			
		||||
        // TODO: We could self-destruct when the last listener goes away, or at least
 | 
			
		||||
        // stop listening.
 | 
			
		||||
        this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this));
 | 
			
		||||
        this._roomStoreToken = OpenRoomsStore.addListener(this._onOpenRoomsStoreUpdate.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getActiveRoomId() {
 | 
			
		||||
        return this._activeRoomId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addListener(roomId, listener) {
 | 
			
		||||
| 
						 | 
				
			
			@ -51,23 +55,23 @@ class ActiveRoomObserver {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _emit(roomId) {
 | 
			
		||||
    _emit(roomId, newActiveRoomId) {
 | 
			
		||||
        if (!this._listeners[roomId]) return;
 | 
			
		||||
 | 
			
		||||
        for (const l of this._listeners[roomId]) {
 | 
			
		||||
            l.call();
 | 
			
		||||
            l.call(l, newActiveRoomId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onRoomViewStoreUpdate() {
 | 
			
		||||
    _onOpenRoomsStoreUpdate() {
 | 
			
		||||
        const activeRoomStore = OpenRoomsStore.getActiveRoomStore();
 | 
			
		||||
        const newActiveRoomId = activeRoomStore && activeRoomStore.getRoomId();
 | 
			
		||||
        // emit for the old room ID
 | 
			
		||||
        if (this._activeRoomId) this._emit(this._activeRoomId);
 | 
			
		||||
 | 
			
		||||
        if (this._activeRoomId) this._emit(this._activeRoomId, newActiveRoomId);
 | 
			
		||||
        // update our cache
 | 
			
		||||
        this._activeRoomId = RoomViewStore.getRoomId();
 | 
			
		||||
 | 
			
		||||
        this._activeRoomId = newActiveRoomId;
 | 
			
		||||
        // and emit for the new one
 | 
			
		||||
        if (this._activeRoomId) this._emit(this._activeRoomId);
 | 
			
		||||
        if (this._activeRoomId) this._emit(this._activeRoomId, this._activeRoomId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ limitations under the License.
 | 
			
		|||
export default {
 | 
			
		||||
    HomePage: "home_page",
 | 
			
		||||
    RoomView: "room_view",
 | 
			
		||||
    GroupGridView: "group_grid_view",
 | 
			
		||||
    UserSettings: "user_settings",
 | 
			
		||||
    RoomDirectory: "room_directory",
 | 
			
		||||
    UserView: "user_view",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,7 @@ class UserActivity {
 | 
			
		|||
     * Can be called multiple times with the same already running timer, which is a NO-OP.
 | 
			
		||||
     * Can be called before the user becomes active, in which case it is only started
 | 
			
		||||
     * later on when the user does become active.
 | 
			
		||||
     * @param {Timer} timer the timer to use
 | 
			
		||||
     */
 | 
			
		||||
    timeWhileActive(timer) {
 | 
			
		||||
        // important this happens first
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018 New Vector Ltd
 | 
			
		||||
Copyright 2018, 2019 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ import { scorePassword } from '../../../../utils/PasswordScorer';
 | 
			
		|||
 | 
			
		||||
import FileSaver from 'file-saver';
 | 
			
		||||
 | 
			
		||||
import { _t, _td } from '../../../../languageHandler';
 | 
			
		||||
import { _t } from '../../../../languageHandler';
 | 
			
		||||
 | 
			
		||||
const PHASE_PASSPHRASE = 0;
 | 
			
		||||
const PHASE_PASSPHRASE_CONFIRM = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ export default React.createClass({
 | 
			
		|||
            info = await MatrixClientPeg.get().createKeyBackupVersion(
 | 
			
		||||
                this._keyBackupInfo,
 | 
			
		||||
            );
 | 
			
		||||
            await MatrixClientPeg.get().backupAllGroupSessions(info.version);
 | 
			
		||||
            await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
 | 
			
		||||
            this.setState({
 | 
			
		||||
                phase: PHASE_DONE,
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -344,7 +344,10 @@ export default React.createClass({
 | 
			
		|||
    _renderPhaseShowKey: function() {
 | 
			
		||||
        let bodyText;
 | 
			
		||||
        if (this.state.setPassPhrase) {
 | 
			
		||||
            bodyText = _t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase.");
 | 
			
		||||
            bodyText = _t(
 | 
			
		||||
                "As a safety net, you can use it to restore your encrypted message " +
 | 
			
		||||
                "history if you forget your Recovery Passphrase.",
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            bodyText = _t("As a safety net, you can use it to restore your encrypted message history.");
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -352,7 +355,7 @@ export default React.createClass({
 | 
			
		|||
        return <div>
 | 
			
		||||
            <p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p>
 | 
			
		||||
            <p>{bodyText}</p>
 | 
			
		||||
            <p className="mx_CreateKeyBackupDialog_primaryContainer">
 | 
			
		||||
            <div className="mx_CreateKeyBackupDialog_primaryContainer">
 | 
			
		||||
                <div className="mx_CreateKeyBackupDialog_recoveryKeyHeader">
 | 
			
		||||
                    {_t("Your Recovery Key")}
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -369,7 +372,7 @@ export default React.createClass({
 | 
			
		|||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -405,7 +408,6 @@ export default React.createClass({
 | 
			
		|||
    _renderBusyPhase: function(text) {
 | 
			
		||||
        const Spinner = sdk.getComponent('views.elements.Spinner');
 | 
			
		||||
        return <div>
 | 
			
		||||
            <p>{_t(text)}</p>
 | 
			
		||||
            <Spinner />
 | 
			
		||||
        </div>;
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -413,8 +415,10 @@ export default React.createClass({
 | 
			
		|||
    _renderPhaseDone: function() {
 | 
			
		||||
        const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
 | 
			
		||||
        return <div>
 | 
			
		||||
            <p>{_t("Backup created")}</p>
 | 
			
		||||
            <p>{_t("Your encryption keys are now being backed up to your Homeserver.")}</p>
 | 
			
		||||
            <p>{_t(
 | 
			
		||||
                "Your encryption keys are now being backed up in the background " +
 | 
			
		||||
                "to your Homeserver. The initial backup could take several minutes. " +
 | 
			
		||||
                "You can view key backup upload progress in Settings.")}</p>
 | 
			
		||||
            <DialogButtons primaryButton={_t('Close')}
 | 
			
		||||
                onPrimaryButtonClick={this._onDone}
 | 
			
		||||
                hasCancel={false}
 | 
			
		||||
| 
						 | 
				
			
			@ -451,7 +455,9 @@ export default React.createClass({
 | 
			
		|||
            case PHASE_KEEPITSAFE:
 | 
			
		||||
                return _t('Keep it safe');
 | 
			
		||||
            case PHASE_BACKINGUP:
 | 
			
		||||
                return _t('Backing up...');
 | 
			
		||||
                return _t('Starting backup...');
 | 
			
		||||
            case PHASE_DONE:
 | 
			
		||||
                return _t('Backup Started');
 | 
			
		||||
            default:
 | 
			
		||||
                return _t("Create Key Backup");
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -488,7 +494,7 @@ export default React.createClass({
 | 
			
		|||
                    content = this._renderPhaseKeepItSafe();
 | 
			
		||||
                    break;
 | 
			
		||||
                case PHASE_BACKINGUP:
 | 
			
		||||
                    content = this._renderBusyPhase(_td("Backing up..."));
 | 
			
		||||
                    content = this._renderBusyPhase();
 | 
			
		||||
                    break;
 | 
			
		||||
                case PHASE_DONE:
 | 
			
		||||
                    content = this._renderPhaseDone();
 | 
			
		||||
| 
						 | 
				
			
			@ -503,7 +509,7 @@ export default React.createClass({
 | 
			
		|||
            <BaseDialog className='mx_CreateKeyBackupDialog'
 | 
			
		||||
                onFinished={this.props.onFinished}
 | 
			
		||||
                title={this._titleForPhase(this.state.phase)}
 | 
			
		||||
                hasCancel={[PHASE_DONE].includes(this.state.phase)}
 | 
			
		||||
                hasCancel={[PHASE_PASSPHRASE, PHASE_DONE].includes(this.state.phase)}
 | 
			
		||||
            >
 | 
			
		||||
            <div>
 | 
			
		||||
                {content}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
 | 
			
		|||
        dis.dispatch({ action: 'view_user_settings' });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onSetupClick = async() => {
 | 
			
		||||
    onSetupClick = async () => {
 | 
			
		||||
        // TODO: Should change to a restore key backup flow that checks the
 | 
			
		||||
        // recovery passphrase while at the same time also cross-signing the
 | 
			
		||||
        // device as well in a single flow.  Since we don't have that yet, we'll
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +79,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
 | 
			
		|||
            <BaseDialog className="mx_NewRecoveryMethodDialog"
 | 
			
		||||
                onFinished={this.props.onFinished}
 | 
			
		||||
                title={title}
 | 
			
		||||
                hasCancel={false}
 | 
			
		||||
            >
 | 
			
		||||
                <div>
 | 
			
		||||
                    <p>{_t(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,7 +61,7 @@ export default class CommunityProvider extends AutocompleteProvider {
 | 
			
		|||
        if (command) {
 | 
			
		||||
            const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join');
 | 
			
		||||
 | 
			
		||||
            const groups = (await Promise.all(joinedGroups.map(async({groupId}) => {
 | 
			
		||||
            const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => {
 | 
			
		||||
                try {
 | 
			
		||||
                    return FlairStore.getGroupProfileCached(cli, groupId);
 | 
			
		||||
                } catch (e) { // if FlairStore failed, fall back to just groupId
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
/*
 | 
			
		||||
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 OpenRoomsStore from '../../stores/OpenRoomsStore';
 | 
			
		||||
import dis from '../../dispatcher';
 | 
			
		||||
import {_t} from '../../languageHandler';
 | 
			
		||||
import RoomView from './RoomView';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import MainSplit from './MainSplit';
 | 
			
		||||
import RightPanel from './RightPanel';
 | 
			
		||||
import RoomHeaderButtons from '../views/right_panel/RoomHeaderButtons';
 | 
			
		||||
 | 
			
		||||
export default class RoomGridView extends React.Component {
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
        super(props);
 | 
			
		||||
        this.state = {
 | 
			
		||||
            roomStores: OpenRoomsStore.getRoomStores(),
 | 
			
		||||
            activeRoomStore: OpenRoomsStore.getActiveRoomStore(),
 | 
			
		||||
        };
 | 
			
		||||
        this.onRoomsChanged = this.onRoomsChanged.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidUpdate(_, prevState) {
 | 
			
		||||
        const store = this.state.activeRoomStore;
 | 
			
		||||
        if (store) {
 | 
			
		||||
            store.getDispatcher().dispatch({action: 'focus_composer'});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        this.componentDidUpdate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillMount() {
 | 
			
		||||
        this._unmounted = false;
 | 
			
		||||
        this._openRoomsStoreRegistration = OpenRoomsStore.addListener(this.onRoomsChanged);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount() {
 | 
			
		||||
        this._unmounted = true;
 | 
			
		||||
        if (this._openRoomsStoreRegistration) {
 | 
			
		||||
            this._openRoomsStoreRegistration.remove();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onRoomsChanged() {
 | 
			
		||||
        if (this._unmounted) return;
 | 
			
		||||
        this.setState({
 | 
			
		||||
            roomStores: OpenRoomsStore.getRoomStores(),
 | 
			
		||||
            activeRoomStore: OpenRoomsStore.getActiveRoomStore(),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _setActive(i) {
 | 
			
		||||
        const store = OpenRoomsStore.getRoomStoreAt(i);
 | 
			
		||||
        if (store !== this.state.activeRoomStore) {
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
                action: 'group_grid_set_active',
 | 
			
		||||
                room_id: store.getRoomId(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        let roomStores = this.state.roomStores.slice(0, 6);
 | 
			
		||||
        const emptyCount = 6 - roomStores.length;
 | 
			
		||||
        if (emptyCount) {
 | 
			
		||||
            const emptyTiles = Array.from({length: emptyCount}, () => null);
 | 
			
		||||
            roomStores = roomStores.concat(emptyTiles);
 | 
			
		||||
        }
 | 
			
		||||
        const activeRoomId = this.state.activeRoomStore && this.state.activeRoomStore.getRoomId();
 | 
			
		||||
        let rightPanel;
 | 
			
		||||
        if (activeRoomId) {
 | 
			
		||||
            rightPanel = (
 | 
			
		||||
                <div className="mx_GroupGridView_rightPanel">
 | 
			
		||||
                    <div className="mx_GroupGridView_tabs"><RoomHeaderButtons /></div>
 | 
			
		||||
                    <RightPanel roomId={activeRoomId} />
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (<main className="mx_GroupGridView">
 | 
			
		||||
            <MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs} >
 | 
			
		||||
                <div className="mx_GroupGridView_rooms">
 | 
			
		||||
                    { roomStores.map((roomStore, i) => {
 | 
			
		||||
                        if (roomStore) {
 | 
			
		||||
                            const isActive = roomStore === this.state.activeRoomStore;
 | 
			
		||||
                            const tileClasses = classNames({
 | 
			
		||||
                                "mx_GroupGridView_tile": true,
 | 
			
		||||
                                "mx_GroupGridView_activeTile": isActive,
 | 
			
		||||
                            });
 | 
			
		||||
                            return (<section
 | 
			
		||||
                                    onClick={() => {this._setActive(i);}}
 | 
			
		||||
                                    key={roomStore.getRoomId()}
 | 
			
		||||
                                    className={tileClasses}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <RoomView
 | 
			
		||||
                                        collapsedRhs={this.props.collapsedRhs}
 | 
			
		||||
                                        isGrid={true}
 | 
			
		||||
                                        roomViewStore={roomStore}
 | 
			
		||||
                                        isActive={isActive}
 | 
			
		||||
                                    />
 | 
			
		||||
                                </section>);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return (<section className={"mx_GroupGridView_emptyTile"} key={`empty-${i}`}>{_t("No room in this tile yet.")}</section>);
 | 
			
		||||
                        }
 | 
			
		||||
                    }) }
 | 
			
		||||
                </div>
 | 
			
		||||
            </MainSplit>
 | 
			
		||||
        </main>);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -781,7 +781,7 @@ export default React.createClass({
 | 
			
		|||
            ),
 | 
			
		||||
            button: _t("Leave"),
 | 
			
		||||
            danger: this.state.isUserPrivileged,
 | 
			
		||||
            onFinished: async(confirmed) => {
 | 
			
		||||
            onFinished: async (confirmed) => {
 | 
			
		||||
                if (!confirmed) return;
 | 
			
		||||
 | 
			
		||||
                this.setState({membershipBusy: true});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ import sessionStore from '../../stores/SessionStore';
 | 
			
		|||
import MatrixClientPeg from '../../MatrixClientPeg';
 | 
			
		||||
import SettingsStore from "../../settings/SettingsStore";
 | 
			
		||||
import RoomListStore from "../../stores/RoomListStore";
 | 
			
		||||
import OpenRoomsStore from "../../stores/OpenRoomsStore";
 | 
			
		||||
 | 
			
		||||
import TagOrderActions from '../../actions/TagOrderActions';
 | 
			
		||||
import RoomListActions from '../../actions/RoomListActions';
 | 
			
		||||
| 
						 | 
				
			
			@ -416,6 +417,7 @@ const LoggedInView = React.createClass({
 | 
			
		|||
        const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
 | 
			
		||||
        const HomePage = sdk.getComponent('structures.HomePage');
 | 
			
		||||
        const GroupView = sdk.getComponent('structures.GroupView');
 | 
			
		||||
        const GroupGridView = sdk.getComponent('structures.GroupGridView');
 | 
			
		||||
        const MyGroups = sdk.getComponent('structures.MyGroups');
 | 
			
		||||
        const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
 | 
			
		||||
        const CookieBar = sdk.getComponent('globals.CookieBar');
 | 
			
		||||
| 
						 | 
				
			
			@ -428,7 +430,14 @@ const LoggedInView = React.createClass({
 | 
			
		|||
 | 
			
		||||
        switch (this.props.page_type) {
 | 
			
		||||
            case PageTypes.RoomView:
 | 
			
		||||
                if (!OpenRoomsStore.getActiveRoomStore()) {
 | 
			
		||||
                    console.warn(`LoggedInView: getCurrentRoomStore not set!`);
 | 
			
		||||
                }
 | 
			
		||||
                else if (OpenRoomsStore.getActiveRoomStore().getRoomId() !== this.props.currentRoomId) {
 | 
			
		||||
                    console.warn(`LoggedInView: room id in store not the same as in props: ${OpenRoomsStore.getActiveRoomStore().getRoomId()} & ${this.props.currentRoomId}`);
 | 
			
		||||
                }
 | 
			
		||||
                page_element = <RoomView
 | 
			
		||||
                        roomViewStore={OpenRoomsStore.getActiveRoomStore()}
 | 
			
		||||
                        ref='roomView'
 | 
			
		||||
                        autoJoin={this.props.autoJoin}
 | 
			
		||||
                        onRegistered={this.props.onRegistered}
 | 
			
		||||
| 
						 | 
				
			
			@ -442,7 +451,9 @@ const LoggedInView = React.createClass({
 | 
			
		|||
                        ConferenceHandler={this.props.ConferenceHandler}
 | 
			
		||||
                    />;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case PageTypes.GroupGridView:
 | 
			
		||||
                page_element = <GroupGridView collapsedRhs={this.props.collapsedRhs} />;
 | 
			
		||||
                break;
 | 
			
		||||
            case PageTypes.UserSettings:
 | 
			
		||||
                page_element = <UserSettings
 | 
			
		||||
                    onClose={this.props.onCloseAllSettings}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,14 +71,13 @@ export default class MainSplit extends React.Component {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidUpdate(prevProps) {
 | 
			
		||||
        const wasExpanded = !this.props.collapsedRhs && prevProps.collapsedRhs;
 | 
			
		||||
        const wasCollapsed = this.props.collapsedRhs && !prevProps.collapsedRhs;
 | 
			
		||||
        const wasPanelSet = this.props.panel && !prevProps.panel;
 | 
			
		||||
        const wasPanelCleared = !this.props.panel && prevProps.panel;
 | 
			
		||||
        const shouldAllowResizing =
 | 
			
		||||
            !this.props.collapsedRhs &&
 | 
			
		||||
            this.props.panel;
 | 
			
		||||
 | 
			
		||||
        if (wasExpanded || wasPanelSet) {
 | 
			
		||||
        if (shouldAllowResizing && !this.resizer) {
 | 
			
		||||
            this._createResizer();
 | 
			
		||||
        } else if (wasCollapsed || wasPanelCleared) {
 | 
			
		||||
        } else if (!shouldAllowResizing && this.resizer) {
 | 
			
		||||
            this.resizer.detach();
 | 
			
		||||
            this.resizer = null;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -651,6 +651,9 @@ export default React.createClass({
 | 
			
		|||
            case 'view_group':
 | 
			
		||||
                this._viewGroup(payload);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'group_grid_view':
 | 
			
		||||
                this._viewGroupGrid(payload);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'view_home_page':
 | 
			
		||||
                this._viewHome();
 | 
			
		||||
                break;
 | 
			
		||||
| 
						 | 
				
			
			@ -862,6 +865,7 @@ export default React.createClass({
 | 
			
		|||
    //                               room name and avatar from an invite email)
 | 
			
		||||
    _viewRoom: function(roomInfo) {
 | 
			
		||||
        this.focusComposer = true;
 | 
			
		||||
        console.log("!!! MatrixChat._viewRoom", roomInfo);
 | 
			
		||||
 | 
			
		||||
        const newState = {
 | 
			
		||||
            currentRoomId: roomInfo.room_id || null,
 | 
			
		||||
| 
						 | 
				
			
			@ -910,6 +914,9 @@ export default React.createClass({
 | 
			
		|||
            if (roomInfo.event_id && roomInfo.highlighted) {
 | 
			
		||||
                presentedId += "/" + roomInfo.event_id;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // TODO: only emit this when we're not in grid mode?
 | 
			
		||||
            this.notifyNewScreen('room/' + presentedId);
 | 
			
		||||
            newState.ready = true;
 | 
			
		||||
            this.setState(newState);
 | 
			
		||||
| 
						 | 
				
			
			@ -926,6 +933,11 @@ export default React.createClass({
 | 
			
		|||
        this.notifyNewScreen('group/' + groupId);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _viewGroupGrid: function(payload) {
 | 
			
		||||
        this._setPage(PageTypes.GroupGridView);
 | 
			
		||||
        // this.notifyNewScreen('grid/' + payload.group_id);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _viewHome: function() {
 | 
			
		||||
        // The home page requires the "logged in" view, so we'll set that.
 | 
			
		||||
        this.setStateForNewView({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -165,7 +165,7 @@ export default class RightPanel extends React.Component {
 | 
			
		|||
        } else if (this.state.phase === RightPanel.Phase.GroupRoomList) {
 | 
			
		||||
            panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
 | 
			
		||||
        } else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) {
 | 
			
		||||
            panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />;
 | 
			
		||||
            panel = <MemberInfo roomId={this.props.roomId} member={this.state.member} key={this.props.roomId || this.state.member.userId} />;
 | 
			
		||||
        } else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) {
 | 
			
		||||
            panel = <GroupMemberInfo
 | 
			
		||||
                groupMember={this.state.member}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2015, 2016 OpenMarket Ltd
 | 
			
		||||
Copyright 2017 Vector Creations Ltd
 | 
			
		||||
Copyright 2018 New Vector Ltd
 | 
			
		||||
Copyright 2018, 2019 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +36,6 @@ const ContentMessages = require("../../ContentMessages");
 | 
			
		|||
const Modal = require("../../Modal");
 | 
			
		||||
const sdk = require('../../index');
 | 
			
		||||
const CallHandler = require('../../CallHandler');
 | 
			
		||||
const dis = require("../../dispatcher");
 | 
			
		||||
const Tinter = require("../../Tinter");
 | 
			
		||||
const rate_limited_func = require('../../ratelimitedfunc');
 | 
			
		||||
const ObjectUtils = require('../../ObjectUtils');
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +45,6 @@ import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
 | 
			
		|||
 | 
			
		||||
import MainSplit from './MainSplit';
 | 
			
		||||
import RightPanel from './RightPanel';
 | 
			
		||||
import RoomViewStore from '../../stores/RoomViewStore';
 | 
			
		||||
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
 | 
			
		||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
 | 
			
		||||
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +92,8 @@ module.exports = React.createClass({
 | 
			
		|||
 | 
			
		||||
        // Servers the RoomView can use to try and assist joins
 | 
			
		||||
        viaServers: PropTypes.arrayOf(PropTypes.string),
 | 
			
		||||
        // the store for this room view
 | 
			
		||||
        roomViewStore: PropTypes.object.isRequired,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getInitialState: function() {
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +155,7 @@ module.exports = React.createClass({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    componentWillMount: function() {
 | 
			
		||||
        this.dispatcherRef = dis.register(this.onAction);
 | 
			
		||||
        this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction);
 | 
			
		||||
        MatrixClientPeg.get().on("Room", this.onRoom);
 | 
			
		||||
        MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
 | 
			
		||||
        MatrixClientPeg.get().on("Room.name", this.onRoomName);
 | 
			
		||||
| 
						 | 
				
			
			@ -166,7 +166,7 @@ module.exports = React.createClass({
 | 
			
		|||
        MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
 | 
			
		||||
        this._fetchMediaConfig();
 | 
			
		||||
        // Start listening for RoomViewStore updates
 | 
			
		||||
        this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
 | 
			
		||||
        this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate);
 | 
			
		||||
        this._onRoomViewStoreUpdate(true);
 | 
			
		||||
 | 
			
		||||
        WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate);
 | 
			
		||||
| 
						 | 
				
			
			@ -197,8 +197,8 @@ module.exports = React.createClass({
 | 
			
		|||
        if (this.unmounted) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!initial && this.state.roomId !== RoomViewStore.getRoomId()) {
 | 
			
		||||
        const store = this.props.roomViewStore;
 | 
			
		||||
        if (!initial && this.state.roomId !== store.getRoomId()) {
 | 
			
		||||
            // RoomView explicitly does not support changing what room
 | 
			
		||||
            // is being viewed: instead it should just be re-mounted when
 | 
			
		||||
            // switching rooms. Therefore, if the room ID changes, we
 | 
			
		||||
| 
						 | 
				
			
			@ -212,22 +212,21 @@ module.exports = React.createClass({
 | 
			
		|||
            // it was, it means we're about to be unmounted.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const newState = {
 | 
			
		||||
            roomId: RoomViewStore.getRoomId(),
 | 
			
		||||
            roomAlias: RoomViewStore.getRoomAlias(),
 | 
			
		||||
            roomLoading: RoomViewStore.isRoomLoading(),
 | 
			
		||||
            roomLoadError: RoomViewStore.getRoomLoadError(),
 | 
			
		||||
            joining: RoomViewStore.isJoining(),
 | 
			
		||||
            initialEventId: RoomViewStore.getInitialEventId(),
 | 
			
		||||
            isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
 | 
			
		||||
            forwardingEvent: RoomViewStore.getForwardingEvent(),
 | 
			
		||||
            shouldPeek: RoomViewStore.shouldPeek(),
 | 
			
		||||
            showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", RoomViewStore.getRoomId()),
 | 
			
		||||
            editingRoomSettings: RoomViewStore.isEditingSettings(),
 | 
			
		||||
            roomId: store.getRoomId(),
 | 
			
		||||
            roomAlias: store.getRoomAlias(),
 | 
			
		||||
            roomLoading: store.isRoomLoading(),
 | 
			
		||||
            roomLoadError: store.getRoomLoadError(),
 | 
			
		||||
            joining: store.isJoining(),
 | 
			
		||||
            initialEventId: store.getInitialEventId(),
 | 
			
		||||
            isInitialEventHighlighted: store.isInitialEventHighlighted(),
 | 
			
		||||
            forwardingEvent: store.getForwardingEvent(),
 | 
			
		||||
            shouldPeek: store.shouldPeek(),
 | 
			
		||||
            showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", store.getRoomId()),
 | 
			
		||||
            editingRoomSettings: store.isEditingSettings(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (this.state.editingRoomSettings && !newState.editingRoomSettings) dis.dispatch({action: 'focus_composer'});
 | 
			
		||||
        if (this.state.editingRoomSettings && !newState.editingRoomSettings) this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
 | 
			
		||||
 | 
			
		||||
        // Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
 | 
			
		||||
        console.log(
 | 
			
		||||
| 
						 | 
				
			
			@ -389,7 +388,7 @@ module.exports = React.createClass({
 | 
			
		|||
 | 
			
		||||
        // XXX: EVIL HACK to autofocus inviting on empty rooms.
 | 
			
		||||
        // We use the setTimeout to avoid racing with focus_composer.
 | 
			
		||||
        if (this.state.room &&
 | 
			
		||||
        if (this.props.isActive !== false && this.state.room &&
 | 
			
		||||
            this.state.room.getJoinedMemberCount() == 1 &&
 | 
			
		||||
            this.state.room.getLiveTimeline() &&
 | 
			
		||||
            this.state.room.getLiveTimeline().getEvents() &&
 | 
			
		||||
| 
						 | 
				
			
			@ -443,7 +442,7 @@ module.exports = React.createClass({
 | 
			
		|||
            roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
 | 
			
		||||
            roomView.removeEventListener('dragend', this.onDragLeaveOrEnd);
 | 
			
		||||
        }
 | 
			
		||||
        dis.unregister(this.dispatcherRef);
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().unregister(this.dispatcherRef);
 | 
			
		||||
        if (MatrixClientPeg.get()) {
 | 
			
		||||
            MatrixClientPeg.get().removeListener("Room", this.onRoom);
 | 
			
		||||
            MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
 | 
			
		||||
| 
						 | 
				
			
			@ -611,17 +610,10 @@ module.exports = React.createClass({
 | 
			
		|||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    async onRoomRecoveryReminderFinished(backupCreated) {
 | 
			
		||||
        // If the user cancelled the key backup dialog, it suggests they don't
 | 
			
		||||
        // want to be reminded anymore.
 | 
			
		||||
        if (!backupCreated) {
 | 
			
		||||
            await SettingsStore.setValue(
 | 
			
		||||
                "showRoomRecoveryReminder",
 | 
			
		||||
                null,
 | 
			
		||||
                SettingLevel.ACCOUNT,
 | 
			
		||||
                false,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    onRoomRecoveryReminderDontAskAgain: function() {
 | 
			
		||||
        // Called when the option to not ask again is set:
 | 
			
		||||
        // force an update to hide the recovery reminder
 | 
			
		||||
        this.forceUpdate();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onKeyBackupStatus() {
 | 
			
		||||
| 
						 | 
				
			
			@ -842,7 +834,7 @@ module.exports = React.createClass({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    onSearchResultsResize: function() {
 | 
			
		||||
        dis.dispatch({ action: 'timeline_resize' }, true);
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({ action: 'timeline_resize' }, true);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onSearchResultsFillRequest: function(backwards) {
 | 
			
		||||
| 
						 | 
				
			
			@ -863,7 +855,7 @@ module.exports = React.createClass({
 | 
			
		|||
 | 
			
		||||
    onInviteButtonClick: function() {
 | 
			
		||||
        // call AddressPickerDialog
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'view_invite',
 | 
			
		||||
            roomId: this.state.room.roomId,
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -885,7 +877,7 @@ module.exports = React.createClass({
 | 
			
		|||
            // Join this room once the user has registered and logged in
 | 
			
		||||
            const signUrl = this.props.thirdPartyInvite ?
 | 
			
		||||
                this.props.thirdPartyInvite.inviteSignUrl : undefined;
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                action: 'do_after_sync_prepared',
 | 
			
		||||
                deferred_action: {
 | 
			
		||||
                    action: 'join_room',
 | 
			
		||||
| 
						 | 
				
			
			@ -895,7 +887,7 @@ module.exports = React.createClass({
 | 
			
		|||
 | 
			
		||||
            // Don't peek whilst registering otherwise getPendingEventList complains
 | 
			
		||||
            // Do this by indicating our intention to join
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                action: 'will_join',
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -906,20 +898,20 @@ module.exports = React.createClass({
 | 
			
		|||
                    if (submitted) {
 | 
			
		||||
                        this.props.onRegistered(credentials);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        dis.dispatch({
 | 
			
		||||
                        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                            action: 'cancel_after_sync_prepared',
 | 
			
		||||
                        });
 | 
			
		||||
                        dis.dispatch({
 | 
			
		||||
                        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                            action: 'cancel_join',
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                onDifferentServerClicked: (ev) => {
 | 
			
		||||
                    dis.dispatch({action: 'start_registration'});
 | 
			
		||||
                    this.props.roomViewStore.getDispatcher().dispatch({action: 'start_registration'});
 | 
			
		||||
                    close();
 | 
			
		||||
                },
 | 
			
		||||
                onLoginClick: (ev) => {
 | 
			
		||||
                    dis.dispatch({action: 'start_login'});
 | 
			
		||||
                    this.props.roomViewStore.getDispatcher().dispatch({action: 'start_login'});
 | 
			
		||||
                    close();
 | 
			
		||||
                },
 | 
			
		||||
            }).close;
 | 
			
		||||
| 
						 | 
				
			
			@ -929,7 +921,7 @@ module.exports = React.createClass({
 | 
			
		|||
        Promise.resolve().then(() => {
 | 
			
		||||
            const signUrl = this.props.thirdPartyInvite ?
 | 
			
		||||
                this.props.thirdPartyInvite.inviteSignUrl : undefined;
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                action: 'join_room',
 | 
			
		||||
                opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -994,10 +986,10 @@ module.exports = React.createClass({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    uploadFile: async function(file) {
 | 
			
		||||
        dis.dispatch({action: 'focus_composer'});
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
 | 
			
		||||
 | 
			
		||||
        if (MatrixClientPeg.get().isGuest()) {
 | 
			
		||||
            dis.dispatch({action: 'require_registration'});
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'});
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1021,14 +1013,14 @@ module.exports = React.createClass({
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        // Send message_sent callback, for things like _checkIfAlone because after all a file is still a message.
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'message_sent',
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    injectSticker: function(url, info, text) {
 | 
			
		||||
        if (MatrixClientPeg.get().isGuest()) {
 | 
			
		||||
            dis.dispatch({action: 'require_registration'});
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'});
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1229,7 +1221,7 @@ module.exports = React.createClass({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    onSettingsClick: function() {
 | 
			
		||||
        dis.dispatch({ action: 'open_room_settings' });
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({ action: 'open_room_settings' });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onSettingsSaveClick: function() {
 | 
			
		||||
| 
						 | 
				
			
			@ -1262,31 +1254,31 @@ module.exports = React.createClass({
 | 
			
		|||
                });
 | 
			
		||||
                // still editing room settings
 | 
			
		||||
            } else {
 | 
			
		||||
                dis.dispatch({ action: 'close_settings' });
 | 
			
		||||
                this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' });
 | 
			
		||||
            }
 | 
			
		||||
        }).finally(() => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                uploadingRoomSettings: false,
 | 
			
		||||
            });
 | 
			
		||||
            dis.dispatch({ action: 'close_settings' });
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' });
 | 
			
		||||
        }).done();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onCancelClick: function() {
 | 
			
		||||
        console.log("updateTint from onCancelClick");
 | 
			
		||||
        this.updateTint();
 | 
			
		||||
        dis.dispatch({ action: 'close_settings' });
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' });
 | 
			
		||||
        if (this.state.forwardingEvent) {
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                action: 'forward_event',
 | 
			
		||||
                event: null,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        dis.dispatch({action: 'focus_composer'});
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onLeaveClick: function() {
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'leave_room',
 | 
			
		||||
            room_id: this.state.room.roomId,
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,7 +1286,7 @@ module.exports = React.createClass({
 | 
			
		|||
 | 
			
		||||
    onForgetClick: function() {
 | 
			
		||||
        MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
 | 
			
		||||
            dis.dispatch({ action: 'view_next_room' });
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_next_room' });
 | 
			
		||||
        }, function(err) {
 | 
			
		||||
            const errCode = err.errcode || _t("unknown error code");
 | 
			
		||||
            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
 | 
			
		||||
| 
						 | 
				
			
			@ -1311,7 +1303,7 @@ module.exports = React.createClass({
 | 
			
		|||
            rejecting: true,
 | 
			
		||||
        });
 | 
			
		||||
        MatrixClientPeg.get().leave(this.state.roomId).done(function() {
 | 
			
		||||
            dis.dispatch({ action: 'view_next_room' });
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_next_room' });
 | 
			
		||||
            self.setState({
 | 
			
		||||
                rejecting: false,
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -1337,7 +1329,7 @@ module.exports = React.createClass({
 | 
			
		|||
        // using /leave rather than /join. In the short term though, we
 | 
			
		||||
        // just ignore them.
 | 
			
		||||
        // https://github.com/vector-im/vector-web/issues/1134
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'view_room_directory',
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -1356,7 +1348,7 @@ module.exports = React.createClass({
 | 
			
		|||
    // jump down to the bottom of this room, where new events are arriving
 | 
			
		||||
    jumpToLiveTimeline: function() {
 | 
			
		||||
        this.refs.messagePanel.jumpToLiveTimeline();
 | 
			
		||||
        dis.dispatch({action: 'focus_composer'});
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // jump up to wherever our read marker is
 | 
			
		||||
| 
						 | 
				
			
			@ -1446,7 +1438,7 @@ module.exports = React.createClass({
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    onFullscreenClick: function() {
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'video_fullscreen',
 | 
			
		||||
            fullscreen: true,
 | 
			
		||||
        }, true);
 | 
			
		||||
| 
						 | 
				
			
			@ -1571,6 +1563,7 @@ module.exports = React.createClass({
 | 
			
		|||
                        <RoomHeader ref="header"
 | 
			
		||||
                            room={this.state.room}
 | 
			
		||||
                            oobData={this.props.oobData}
 | 
			
		||||
                            isGrid={this.props.isGrid}
 | 
			
		||||
                            collapsedRhs={this.props.collapsedRhs}
 | 
			
		||||
                        />
 | 
			
		||||
                        <div className="mx_RoomView_body">
 | 
			
		||||
| 
						 | 
				
			
			@ -1617,6 +1610,7 @@ module.exports = React.createClass({
 | 
			
		|||
                    <div className="mx_RoomView">
 | 
			
		||||
                        <RoomHeader
 | 
			
		||||
                            ref="header"
 | 
			
		||||
                            isGrid={this.props.isGrid}
 | 
			
		||||
                            room={this.state.room}
 | 
			
		||||
                            collapsedRhs={this.props.collapsedRhs}
 | 
			
		||||
                        />
 | 
			
		||||
| 
						 | 
				
			
			@ -1704,7 +1698,7 @@ module.exports = React.createClass({
 | 
			
		|||
            aux = <RoomUpgradeWarningBar room={this.state.room} />;
 | 
			
		||||
            hideCancel = true;
 | 
			
		||||
        } else if (showRoomRecoveryReminder) {
 | 
			
		||||
            aux = <RoomRecoveryReminder onFinished={this.onRoomRecoveryReminderFinished} />;
 | 
			
		||||
            aux = <RoomRecoveryReminder onDontAskAgainSet={this.onRoomRecoveryReminderDontAskAgain} />;
 | 
			
		||||
            hideCancel = true;
 | 
			
		||||
        } else if (this.state.showingPinned) {
 | 
			
		||||
            hideCancel = true; // has own cancel
 | 
			
		||||
| 
						 | 
				
			
			@ -1758,7 +1752,9 @@ module.exports = React.createClass({
 | 
			
		|||
        if (canSpeak) {
 | 
			
		||||
            messageComposer =
 | 
			
		||||
                <MessageComposer
 | 
			
		||||
                    roomViewStore={this.props.roomViewStore}
 | 
			
		||||
                    room={this.state.room}
 | 
			
		||||
                    isGrid={this.props.isGrid}
 | 
			
		||||
                    onResize={this.onChildResize}
 | 
			
		||||
                    uploadFile={this.uploadFile}
 | 
			
		||||
                    callState={this.state.callState}
 | 
			
		||||
| 
						 | 
				
			
			@ -1885,11 +1881,14 @@ module.exports = React.createClass({
 | 
			
		|||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const rightPanel = this.state.room ? <RightPanel roomId={this.state.room.roomId} /> : undefined;
 | 
			
		||||
        const rightPanel = this.state.room && !this.props.isGrid ?
 | 
			
		||||
            <RightPanel roomId={this.state.room.roomId} /> :
 | 
			
		||||
            undefined;
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
 | 
			
		||||
                <RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
 | 
			
		||||
                    isGrid={this.props.isGrid}
 | 
			
		||||
                    oobData={this.props.oobData}
 | 
			
		||||
                    editing={this.state.editingRoomSettings}
 | 
			
		||||
                    saving={this.state.uploadingRoomSettings}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -835,7 +835,7 @@ module.exports = React.createClass({
 | 
			
		|||
        SettingsStore.getLabsFeatures().forEach((featureId) => {
 | 
			
		||||
            // TODO: this ought to be a separate component so that we don't need
 | 
			
		||||
            // to rebind the onChange each time we render
 | 
			
		||||
            const onChange = async(e) => {
 | 
			
		||||
            const onChange = async (e) => {
 | 
			
		||||
                const checked = e.target.checked;
 | 
			
		||||
                if (featureId === "feature_lazyloading") {
 | 
			
		||||
                    const confirmed = await this._onLazyLoadChanging(checked);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ export default class GroupInviteTileContextMenu extends React.Component {
 | 
			
		|||
        Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, {
 | 
			
		||||
            title: _t('Reject invitation'),
 | 
			
		||||
            description: _t('Are you sure you want to reject the invitation?'),
 | 
			
		||||
            onFinished: async(shouldLeave) => {
 | 
			
		||||
            onFinished: async (shouldLeave) => {
 | 
			
		||||
                if (!shouldLeave) return;
 | 
			
		||||
 | 
			
		||||
                // FIXME: controller shouldn't be loading a view :(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ export default class StatusMessageContextMenu extends React.Component {
 | 
			
		|||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onClearClick = async(e) => {
 | 
			
		||||
    _onClearClick = async (e) => {
 | 
			
		||||
        await MatrixClientPeg.get()._unstable_setStatusMessage("");
 | 
			
		||||
        this.setState({message: ""});
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ import dis from '../../../dispatcher';
 | 
			
		|||
import TagOrderActions from '../../../actions/TagOrderActions';
 | 
			
		||||
import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		||||
import sdk from '../../../index';
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
export default class TagTileContextMenu extends React.Component {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +35,7 @@ export default class TagTileContextMenu extends React.Component {
 | 
			
		|||
 | 
			
		||||
        this._onViewCommunityClick = this._onViewCommunityClick.bind(this);
 | 
			
		||||
        this._onRemoveClick = this._onRemoveClick.bind(this);
 | 
			
		||||
        this._onViewAsGridClick = this._onViewAsGridClick.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onViewCommunityClick() {
 | 
			
		||||
| 
						 | 
				
			
			@ -53,8 +55,28 @@ export default class TagTileContextMenu extends React.Component {
 | 
			
		|||
        this.props.onFinished();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onViewAsGridClick() {
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
            action: 'group_grid_view',
 | 
			
		||||
            group_id: this.props.tag,
 | 
			
		||||
        });
 | 
			
		||||
        this.props.onFinished();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const TintableSvg = sdk.getComponent("elements.TintableSvg");
 | 
			
		||||
        let gridViewOption;
 | 
			
		||||
        if (SettingsStore.isFeatureEnabled("feature_gridview")) {
 | 
			
		||||
            gridViewOption = (<div className="mx_TagTileContextMenu_item" onClick={this._onViewAsGridClick} >
 | 
			
		||||
                <TintableSvg
 | 
			
		||||
                    className="mx_TagTileContextMenu_item_icon"
 | 
			
		||||
                    src="img/feather-icons/grid.svg"
 | 
			
		||||
                    width="15"
 | 
			
		||||
                    height="15"
 | 
			
		||||
                />
 | 
			
		||||
                { _t('View as Grid') }
 | 
			
		||||
            </div>);
 | 
			
		||||
        }
 | 
			
		||||
        return <div>
 | 
			
		||||
            <div className="mx_TagTileContextMenu_item" onClick={this._onViewCommunityClick} >
 | 
			
		||||
                <TintableSvg
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +87,7 @@ export default class TagTileContextMenu extends React.Component {
 | 
			
		|||
                />
 | 
			
		||||
                { _t('View Community') }
 | 
			
		||||
            </div>
 | 
			
		||||
            { gridViewOption }
 | 
			
		||||
            <hr className="mx_TagTileContextMenu_separator" />
 | 
			
		||||
            <div className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick} >
 | 
			
		||||
                <img className="mx_TagTileContextMenu_item_icon" src="img/icon_context_delete.svg" width="15" height="15" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018 New Vector Ltd
 | 
			
		||||
Copyright 2018, 2019 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -15,48 +15,150 @@ limitations under the License.
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import QuestionDialog from './QuestionDialog';
 | 
			
		||||
import Modal from '../../../Modal';
 | 
			
		||||
import sdk from '../../../index';
 | 
			
		||||
import dis from '../../../dispatcher';
 | 
			
		||||
import { _t } from '../../../languageHandler';
 | 
			
		||||
import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
export default (props) => {
 | 
			
		||||
    const description = _t("For security, logging out will delete any end-to-end " +
 | 
			
		||||
                  "encryption keys from this browser. If you want to be able " +
 | 
			
		||||
                  "to decrypt your conversation history from future Riot sessions, " +
 | 
			
		||||
                  "please export your room keys for safe-keeping.");
 | 
			
		||||
export default class LogoutDialog extends React.Component {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
        this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this);
 | 
			
		||||
        this._onExportE2eKeysClicked = this._onExportE2eKeysClicked.bind(this);
 | 
			
		||||
        this._onFinished = this._onFinished.bind(this);
 | 
			
		||||
        this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this);
 | 
			
		||||
        this._onLogoutConfirm = this._onLogoutConfirm.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const onExportE2eKeysClicked = () => {
 | 
			
		||||
    _onSettingsLinkClick() {
 | 
			
		||||
        // close dialog
 | 
			
		||||
        if (this.props.onFinished) {
 | 
			
		||||
            this.props.onFinished();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onExportE2eKeysClicked() {
 | 
			
		||||
        Modal.createTrackedDialogAsync('Export E2E Keys', '',
 | 
			
		||||
            import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
 | 
			
		||||
            {
 | 
			
		||||
                matrixClient: MatrixClientPeg.get(),
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const onFinished = (confirmed) => {
 | 
			
		||||
    _onFinished(confirmed) {
 | 
			
		||||
        if (confirmed) {
 | 
			
		||||
            dis.dispatch({action: 'logout'});
 | 
			
		||||
        }
 | 
			
		||||
        // close dialog
 | 
			
		||||
        if (props.onFinished) {
 | 
			
		||||
            props.onFinished();
 | 
			
		||||
        if (this.props.onFinished) {
 | 
			
		||||
            this.props.onFinished();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _onSetRecoveryMethodClick() {
 | 
			
		||||
        Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
 | 
			
		||||
            import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // close dialog
 | 
			
		||||
        if (this.props.onFinished) {
 | 
			
		||||
            this.props.onFinished();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onLogoutConfirm() {
 | 
			
		||||
        dis.dispatch({action: 'logout'});
 | 
			
		||||
 | 
			
		||||
        // close dialog
 | 
			
		||||
        if (this.props.onFinished) {
 | 
			
		||||
            this.props.onFinished();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        let description;
 | 
			
		||||
        if (SettingsStore.isFeatureEnabled("feature_keybackup")) {
 | 
			
		||||
            description = <div>
 | 
			
		||||
                <p>{_t(
 | 
			
		||||
                    "When you log out, you'll lose your secure message history. To prevent " +
 | 
			
		||||
                    "this, set up a recovery method.",
 | 
			
		||||
                )}</p>
 | 
			
		||||
                <p>{_t(
 | 
			
		||||
                    "Alternatively, advanced users can also manually export encryption keys in " +
 | 
			
		||||
                    "<a>Settings</a> before logging out.", {},
 | 
			
		||||
                    {
 | 
			
		||||
                        a: sub => <a href='#/settings' onClick={this._onSettingsLinkClick}>{sub}</a>,
 | 
			
		||||
                    },
 | 
			
		||||
                )}</p>
 | 
			
		||||
            </div>;
 | 
			
		||||
        } else {
 | 
			
		||||
            description = <div>{_t(
 | 
			
		||||
                "For security, logging out will delete any end-to-end " +
 | 
			
		||||
                "encryption keys from this browser. If you want to be able " +
 | 
			
		||||
                "to decrypt your conversation history from future Riot sessions, " +
 | 
			
		||||
                "please export your room keys for safe-keeping.",
 | 
			
		||||
            )}</div>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (SettingsStore.isFeatureEnabled("feature_keybackup")) {
 | 
			
		||||
            if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
			
		||||
                const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
 | 
			
		||||
                const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
 | 
			
		||||
                // Not quite a standard question dialog as the primary button cancels
 | 
			
		||||
                // the action and does something else instead, whilst non-default button
 | 
			
		||||
                // confirms the action.
 | 
			
		||||
                return (<BaseDialog
 | 
			
		||||
                    title={_t("Warning!")}
 | 
			
		||||
                    contentId='mx_Dialog_content'
 | 
			
		||||
                    hasCancel={false}
 | 
			
		||||
                    onFinsihed={this._onFinished}
 | 
			
		||||
                >
 | 
			
		||||
                    <div className="mx_Dialog_content" id='mx_Dialog_content'>
 | 
			
		||||
                        { description }
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <DialogButtons primaryButton={_t('Set a Recovery Method')}
 | 
			
		||||
                        hasCancel={false}
 | 
			
		||||
                        onPrimaryButtonClick={this._onSetRecoveryMethodClick}
 | 
			
		||||
                        focus={true}
 | 
			
		||||
                    >
 | 
			
		||||
                        <button onClick={this._onLogoutConfirm}>
 | 
			
		||||
                            {_t("I understand, log out without")}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </DialogButtons>
 | 
			
		||||
                </BaseDialog>);
 | 
			
		||||
            } else {
 | 
			
		||||
                const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
 | 
			
		||||
                return (<QuestionDialog
 | 
			
		||||
                    hasCancelButton={true}
 | 
			
		||||
                    title={_t("Sign out")}
 | 
			
		||||
        description={<div>{description}</div>}
 | 
			
		||||
                    // TODO: This is made up by me and would need to also mention verifying
 | 
			
		||||
                    // once you can restorew a backup by verifying a device
 | 
			
		||||
                    description={_t(
 | 
			
		||||
                        "When signing in again, you can access encrypted chat history by " +
 | 
			
		||||
                        "restoring your key backup. You'll need your recovery key.",
 | 
			
		||||
                    )}
 | 
			
		||||
                    button={_t("Sign out")}
 | 
			
		||||
                    onFinished={this._onFinished}
 | 
			
		||||
                />);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
 | 
			
		||||
            return (<QuestionDialog
 | 
			
		||||
                hasCancelButton={true}
 | 
			
		||||
                title={_t("Sign out")}
 | 
			
		||||
                description={description}
 | 
			
		||||
                button={_t("Sign out")}
 | 
			
		||||
                extraButtons={[
 | 
			
		||||
                    (<button key="export" className="mx_Dialog_primary"
 | 
			
		||||
                    onClick={onExportE2eKeysClicked}>
 | 
			
		||||
                            onClick={this._onExportE2eKeysClicked}>
 | 
			
		||||
                       { _t("Export E2E room keys") }
 | 
			
		||||
                    </button>),
 | 
			
		||||
                ]}
 | 
			
		||||
        onFinished={onFinished}
 | 
			
		||||
                onFinished={this._onFinished}
 | 
			
		||||
            />);
 | 
			
		||||
};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -271,7 +271,7 @@ const Pill = React.createClass({
 | 
			
		|||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const classes = classNames(pillClass, {
 | 
			
		||||
        const classes = classNames("mx_Pill", pillClass, {
 | 
			
		||||
            "mx_UserPill_me": userId === MatrixClientPeg.get().credentials.userId,
 | 
			
		||||
            "mx_UserPill_selected": this.props.isSelected,
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,7 +78,6 @@ export default class HeaderButtons extends React.Component {
 | 
			
		|||
                    // till show_right_panel, just without the fromHeader flag
 | 
			
		||||
                    // as that would hide the right panel again
 | 
			
		||||
                    dis.dispatch(Object.assign({}, payload, {fromHeader: false}));
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    phase: payload.phase,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,6 @@ import Unread from '../../../Unread';
 | 
			
		|||
import { findReadReceiptFromUserId } from '../../../utils/Receipt';
 | 
			
		||||
import withMatrixClient from '../../../wrappers/withMatrixClient';
 | 
			
		||||
import AccessibleButton from '../elements/AccessibleButton';
 | 
			
		||||
import RoomViewStore from '../../../stores/RoomViewStore';
 | 
			
		||||
import SdkConfig from '../../../SdkConfig';
 | 
			
		||||
import MultiInviter from "../../../utils/MultiInviter";
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +49,7 @@ module.exports = withMatrixClient(React.createClass({
 | 
			
		|||
    propTypes: {
 | 
			
		||||
        matrixClient: PropTypes.object.isRequired,
 | 
			
		||||
        member: PropTypes.object.isRequired,
 | 
			
		||||
        roomId: PropTypes.string,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getInitialState: function() {
 | 
			
		||||
| 
						 | 
				
			
			@ -713,8 +713,8 @@ module.exports = withMatrixClient(React.createClass({
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            if (!member || !member.membership || member.membership === 'leave') {
 | 
			
		||||
                const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
 | 
			
		||||
                const onInviteUserButton = async() => {
 | 
			
		||||
                const roomId = member && member.roomId ? member.roomId : this.props.roomId;
 | 
			
		||||
                const onInviteUserButton = async () => {
 | 
			
		||||
                    try {
 | 
			
		||||
                        // We use a MultiInviter to re-use the invite logic, even though
 | 
			
		||||
                        // we're only inviting one user.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,6 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		|||
import Modal from '../../../Modal';
 | 
			
		||||
import sdk from '../../../index';
 | 
			
		||||
import dis from '../../../dispatcher';
 | 
			
		||||
import RoomViewStore from '../../../stores/RoomViewStore';
 | 
			
		||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
 | 
			
		||||
import Stickerpicker from './Stickerpicker';
 | 
			
		||||
import { makeRoomPermalink } from '../../../matrix-to';
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +62,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
                isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
 | 
			
		||||
            },
 | 
			
		||||
            showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
 | 
			
		||||
            isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
 | 
			
		||||
            isQuoting: Boolean(this.props.roomViewStore.getQuotingEvent()),
 | 
			
		||||
            tombstone: this._getRoomTombstone(),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +74,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
        // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
 | 
			
		||||
        MatrixClientPeg.get().on("event", this.onEvent);
 | 
			
		||||
        MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
 | 
			
		||||
        this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
 | 
			
		||||
        this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate);
 | 
			
		||||
        this._waitForOwnMember();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,14 +123,14 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    _onRoomViewStoreUpdate() {
 | 
			
		||||
        const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
 | 
			
		||||
        const isQuoting = Boolean(this.props.roomViewStore.getQuotingEvent());
 | 
			
		||||
        if (this.state.isQuoting === isQuoting) return;
 | 
			
		||||
        this.setState({ isQuoting });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onUploadClick(ev) {
 | 
			
		||||
        if (MatrixClientPeg.get().isGuest()) {
 | 
			
		||||
            dis.dispatch({action: 'require_registration'});
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'});
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +164,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
 | 
			
		||||
        const isQuoting = Boolean(this.props.roomViewStore.getQuotingEvent());
 | 
			
		||||
        let replyToWarning = null;
 | 
			
		||||
        if (isQuoting) {
 | 
			
		||||
            replyToWarning = <p>{
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +228,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
        if (!call) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'hangup',
 | 
			
		||||
            // hangup the call for this room, which may not be the room in props
 | 
			
		||||
            // (e.g. conferences which will hangup the 1:1 room instead)
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +237,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    onCallClick(ev) {
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'place_call',
 | 
			
		||||
            type: ev.shiftKey ? "screensharing" : "video",
 | 
			
		||||
            room_id: this.props.room.roomId,
 | 
			
		||||
| 
						 | 
				
			
			@ -246,7 +245,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    onVoiceCallClick(ev) {
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'place_call',
 | 
			
		||||
            type: "voice",
 | 
			
		||||
            room_id: this.props.room.roomId,
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +281,7 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
        ev.preventDefault();
 | 
			
		||||
 | 
			
		||||
        const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
            action: 'view_room',
 | 
			
		||||
            highlighted: true,
 | 
			
		||||
            room_id: replacementRoomId,
 | 
			
		||||
| 
						 | 
				
			
			@ -421,8 +420,10 @@ export default class MessageComposer extends React.Component {
 | 
			
		|||
 | 
			
		||||
            controls.push(
 | 
			
		||||
                <MessageComposerInput
 | 
			
		||||
                    roomViewStore={this.props.roomViewStore}
 | 
			
		||||
                    ref={(c) => this.messageComposerInput = c}
 | 
			
		||||
                    key="controls_input"
 | 
			
		||||
                    isGrid={this.props.isGrid}
 | 
			
		||||
                    onResize={this.props.onResize}
 | 
			
		||||
                    room={this.props.room}
 | 
			
		||||
                    placeholder={placeholderText}
 | 
			
		||||
| 
						 | 
				
			
			@ -529,5 +530,6 @@ MessageComposer.propTypes = {
 | 
			
		|||
    uploadAllowed: PropTypes.func.isRequired,
 | 
			
		||||
 | 
			
		||||
    // string representing the current room app drawer state
 | 
			
		||||
    showApps: PropTypes.bool
 | 
			
		||||
    showApps: PropTypes.bool,
 | 
			
		||||
    roomViewStore: PropTypes.object.isRequired,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,8 +41,6 @@ import sdk from '../../../index';
 | 
			
		|||
import { _t, _td } from '../../../languageHandler';
 | 
			
		||||
import Analytics from '../../../Analytics';
 | 
			
		||||
 | 
			
		||||
import dis from '../../../dispatcher';
 | 
			
		||||
 | 
			
		||||
import * as RichText from '../../../RichText';
 | 
			
		||||
import * as HtmlUtils from '../../../HtmlUtils';
 | 
			
		||||
import Autocomplete from './Autocomplete';
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +56,6 @@ import {asciiRegexp, unicodeRegexp, shortnameToUnicode, emojioneList, asciiList,
 | 
			
		|||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
 | 
			
		||||
import {makeUserPermalink} from "../../../matrix-to";
 | 
			
		||||
import ReplyPreview from "./ReplyPreview";
 | 
			
		||||
import RoomViewStore from '../../../stores/RoomViewStore';
 | 
			
		||||
import ReplyThread from "../elements/ReplyThread";
 | 
			
		||||
import {ContentHelpers} from 'matrix-js-sdk';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +118,7 @@ function onSendMessageFailed(err, room) {
 | 
			
		|||
    // XXX: temporary logging to try to diagnose
 | 
			
		||||
    // https://github.com/vector-im/riot-web/issues/3148
 | 
			
		||||
    console.log('MessageComposer got send failure: ' + err.name + '('+err+')');
 | 
			
		||||
    dis.dispatch({
 | 
			
		||||
    this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
        action: 'message_send_failed',
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -135,6 +132,18 @@ function rangeEquals(a: Range, b: Range): boolean {
 | 
			
		|||
        && a.isBackward === b.isBackward);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class NoopHistoryManager {
 | 
			
		||||
    getItem() {}
 | 
			
		||||
    save() {}
 | 
			
		||||
 | 
			
		||||
    get currentIndex() { return 0; }
 | 
			
		||||
    set currentIndex(_) {}
 | 
			
		||||
 | 
			
		||||
    get history() { return []; }
 | 
			
		||||
    set history(_) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * The textInput part of the MessageComposer
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -150,6 +159,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
        onFilesPasted: PropTypes.func,
 | 
			
		||||
 | 
			
		||||
        onInputStateChanged: PropTypes.func,
 | 
			
		||||
        roomViewStore: PropTypes.object.isRequired,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    client: MatrixClient;
 | 
			
		||||
| 
						 | 
				
			
			@ -344,12 +354,16 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillMount() {
 | 
			
		||||
        this.dispatcherRef = dis.register(this.onAction);
 | 
			
		||||
        this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction);
 | 
			
		||||
        if (this.props.isGrid) {
 | 
			
		||||
            this.historyManager = new NoopHistoryManager();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount() {
 | 
			
		||||
        dis.unregister(this.dispatcherRef);
 | 
			
		||||
        this.props.roomViewStore.getDispatcher().unregister(this.dispatcherRef);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _collectEditor = (e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -1120,7 +1134,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const replyingToEv = RoomViewStore.getQuotingEvent();
 | 
			
		||||
        const replyingToEv = this.props.roomViewStore.getQuotingEvent();
 | 
			
		||||
        const mustSendHTML = Boolean(replyingToEv);
 | 
			
		||||
 | 
			
		||||
        if (this.state.isRichTextEnabled) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1208,14 +1222,14 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
 | 
			
		||||
            // Clear reply_to_event as we put the message into the queue
 | 
			
		||||
            // if the send fails, retry will handle resending.
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                action: 'reply_to_event',
 | 
			
		||||
                event: null,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.client.sendMessage(this.props.room.roomId, content).then((res) => {
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.props.roomViewStore.getDispatcher().dispatch({
 | 
			
		||||
                action: 'message_sent',
 | 
			
		||||
            });
 | 
			
		||||
        }).catch((e) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -1260,7 +1274,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    selectHistory = async(up) => {
 | 
			
		||||
    selectHistory = async (up) => {
 | 
			
		||||
        const delta = up ? -1 : 1;
 | 
			
		||||
 | 
			
		||||
        // True if we are not currently selecting history, but composing a message
 | 
			
		||||
| 
						 | 
				
			
			@ -1308,7 +1322,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    onTab = async(e) => {
 | 
			
		||||
    onTab = async (e) => {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            someCompletions: null,
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -1330,7 +1344,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
        up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    onEscape = async(e) => {
 | 
			
		||||
    onEscape = async (e) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        if (this.autocomplete) {
 | 
			
		||||
            this.autocomplete.onEscape(e);
 | 
			
		||||
| 
						 | 
				
			
			@ -1349,7 +1363,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
    /* If passed null, restores the original editor content from state.originalEditorState.
 | 
			
		||||
     * If passed a non-null displayedCompletion, modifies state.originalEditorState to compute new state.editorState.
 | 
			
		||||
     */
 | 
			
		||||
    setDisplayedCompletion = async(displayedCompletion: ?Completion): boolean => {
 | 
			
		||||
    setDisplayedCompletion = async (displayedCompletion: ?Completion): boolean => {
 | 
			
		||||
        const activeEditorState = this.state.originalEditorState || this.state.editorState;
 | 
			
		||||
 | 
			
		||||
        if (displayedCompletion == null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1589,7 +1603,7 @@ export default class MessageComposerInput extends React.Component {
 | 
			
		|||
        return (
 | 
			
		||||
            <div className="mx_MessageComposer_input_wrapper" onClick={this.focusComposer}>
 | 
			
		||||
                <div className="mx_MessageComposer_autocomplete_wrapper">
 | 
			
		||||
                    <ReplyPreview />
 | 
			
		||||
                    <ReplyPreview roomViewStore={this.props.roomViewStore} />
 | 
			
		||||
                    <Autocomplete
 | 
			
		||||
                        ref={(e) => this.autocomplete = e}
 | 
			
		||||
                        room={this.props.room}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ import React from 'react';
 | 
			
		|||
import dis from '../../../dispatcher';
 | 
			
		||||
import sdk from '../../../index';
 | 
			
		||||
import { _t } from '../../../languageHandler';
 | 
			
		||||
import RoomViewStore from '../../../stores/RoomViewStore';
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
function cancelQuoting() {
 | 
			
		||||
| 
						 | 
				
			
			@ -38,7 +37,7 @@ export default class ReplyPreview extends React.Component {
 | 
			
		|||
 | 
			
		||||
        this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
 | 
			
		||||
 | 
			
		||||
        this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
 | 
			
		||||
        this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate);
 | 
			
		||||
        this._onRoomViewStoreUpdate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +49,7 @@ export default class ReplyPreview extends React.Component {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    _onRoomViewStoreUpdate() {
 | 
			
		||||
        const event = RoomViewStore.getQuotingEvent();
 | 
			
		||||
        const event = this.props.roomViewStore.getQuotingEvent();
 | 
			
		||||
        if (this.state.event !== event) {
 | 
			
		||||
            this.setState({ event });
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import { _t } from '../../../languageHandler';
 | 
			
		|||
import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		||||
import Modal from "../../../Modal";
 | 
			
		||||
import RateLimitedFunc from '../../../ratelimitedfunc';
 | 
			
		||||
import dis from '../../../dispatcher';
 | 
			
		||||
 | 
			
		||||
import * as linkify from 'linkifyjs';
 | 
			
		||||
import linkifyElement from 'linkifyjs/element';
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +153,14 @@ module.exports = React.createClass({
 | 
			
		|||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onToggleRightPanelClick: function(ev) {
 | 
			
		||||
        if (this.props.collapsedRhs) {
 | 
			
		||||
            dis.dispatch({action: "show_right_panel"});
 | 
			
		||||
        } else {
 | 
			
		||||
            dis.dispatch({action: "hide_right_panel"});
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _hasUnreadPins: function() {
 | 
			
		||||
        const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
 | 
			
		||||
        if (!currentPinEvent) return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -409,6 +418,17 @@ module.exports = React.createClass({
 | 
			
		|||
                </div>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let toggleRightPanelButton;
 | 
			
		||||
        if (this.props.isGrid) {
 | 
			
		||||
            toggleRightPanelButton =
 | 
			
		||||
                <AccessibleButton
 | 
			
		||||
                    className="mx_RoomHeader_button"
 | 
			
		||||
                    onClick={this.onToggleRightPanelClick}
 | 
			
		||||
                    title={_t('Toggle right panel')}>
 | 
			
		||||
                    <TintableSvg src="img/feather-icons/toggle-right-panel.svg" width="20" height="20" />
 | 
			
		||||
                </AccessibleButton>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className={"mx_RoomHeader light-panel " + (this.props.editing ? "mx_RoomHeader_editing" : "")}>
 | 
			
		||||
                <div className="mx_RoomHeader_wrapper">
 | 
			
		||||
| 
						 | 
				
			
			@ -419,7 +439,8 @@ module.exports = React.createClass({
 | 
			
		|||
                    { saveButton }
 | 
			
		||||
                    { cancelButton }
 | 
			
		||||
                    { rightRow }
 | 
			
		||||
                    <RoomHeaderButtons collapsedRhs={this.props.collapsedRhs} />
 | 
			
		||||
                    { !this.props.isGrid ? <RoomHeaderButtons collapsedRhs={this.props.collapsedRhs} /> : undefined }
 | 
			
		||||
                    { toggleRightPanelButton }
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018 New Vector Ltd
 | 
			
		||||
Copyright 2018, 2019 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -20,10 +20,16 @@ import sdk from "../../../index";
 | 
			
		|||
import { _t } from "../../../languageHandler";
 | 
			
		||||
import Modal from "../../../Modal";
 | 
			
		||||
import MatrixClientPeg from "../../../MatrixClientPeg";
 | 
			
		||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
export default class RoomRecoveryReminder extends React.PureComponent {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        onFinished: PropTypes.func.isRequired,
 | 
			
		||||
        // called if the user sets the option to suppress this reminder in the future
 | 
			
		||||
        onDontAskAgainSet: PropTypes.func,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static defaultProps = {
 | 
			
		||||
        onDontAskAgainSet: function() {},
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +88,6 @@ export default class RoomRecoveryReminder extends React.PureComponent {
 | 
			
		|||
            Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
 | 
			
		||||
                userId: MatrixClientPeg.get().credentials.userId,
 | 
			
		||||
                device: this.state.unverifiedDevice,
 | 
			
		||||
                onFinished: this.props.onFinished,
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -91,9 +96,6 @@ export default class RoomRecoveryReminder extends React.PureComponent {
 | 
			
		|||
        // we'll show the create key backup flow.
 | 
			
		||||
        Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
 | 
			
		||||
            import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
 | 
			
		||||
            {
 | 
			
		||||
                onFinished: this.props.onFinished,
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,10 +105,14 @@ export default class RoomRecoveryReminder extends React.PureComponent {
 | 
			
		|||
        Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder",
 | 
			
		||||
            import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"),
 | 
			
		||||
            {
 | 
			
		||||
                onDontAskAgain: () => {
 | 
			
		||||
                    // Report false to the caller, who should prevent the
 | 
			
		||||
                    // reminder from appearing in the future.
 | 
			
		||||
                    this.props.onFinished(false);
 | 
			
		||||
                onDontAskAgain: async () => {
 | 
			
		||||
                    await SettingsStore.setValue(
 | 
			
		||||
                        "showRoomRecoveryReminder",
 | 
			
		||||
                        null,
 | 
			
		||||
                        SettingLevel.ACCOUNT,
 | 
			
		||||
                        false,
 | 
			
		||||
                    );
 | 
			
		||||
                    this.props.onDontAskAgainSet();
 | 
			
		||||
                },
 | 
			
		||||
                onSetup: () => {
 | 
			
		||||
                    this.showSetupDialog();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,6 @@ import * as RoomNotifs from '../../../RoomNotifs';
 | 
			
		|||
import * as FormattingUtils from '../../../utils/FormattingUtils';
 | 
			
		||||
import AccessibleButton from '../elements/AccessibleButton';
 | 
			
		||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
 | 
			
		||||
import RoomViewStore from '../../../stores/RoomViewStore';
 | 
			
		||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
module.exports = React.createClass({
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +61,7 @@ module.exports = React.createClass({
 | 
			
		|||
            roomName: this.props.room.name,
 | 
			
		||||
            notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
 | 
			
		||||
            notificationCount: this.props.room.getUnreadNotificationCount(),
 | 
			
		||||
            selected: this.props.room.roomId === RoomViewStore.getRoomId(),
 | 
			
		||||
            selected: this.props.room.roomId === ActiveRoomObserver.getActiveRoomId(),
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,9 +116,9 @@ module.exports = React.createClass({
 | 
			
		|||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _onActiveRoomChange: function() {
 | 
			
		||||
    _onActiveRoomChange: function(activeRoomId) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            selected: this.props.room.roomId === RoomViewStore.getRoomId(),
 | 
			
		||||
            selected: this.props.room.roomId === activeRoomId,
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,13 +21,15 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		|||
import { _t } from '../../../languageHandler';
 | 
			
		||||
import Modal from '../../../Modal';
 | 
			
		||||
 | 
			
		||||
export default class KeyBackupPanel extends React.Component {
 | 
			
		||||
export default class KeyBackupPanel extends React.PureComponent {
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
        this._startNewBackup = this._startNewBackup.bind(this);
 | 
			
		||||
        this._deleteBackup = this._deleteBackup.bind(this);
 | 
			
		||||
        this._verifyDevice = this._verifyDevice.bind(this);
 | 
			
		||||
        this._onKeyBackupSessionsRemaining =
 | 
			
		||||
            this._onKeyBackupSessionsRemaining.bind(this);
 | 
			
		||||
        this._onKeyBackupStatus = this._onKeyBackupStatus.bind(this);
 | 
			
		||||
        this._restoreBackup = this._restoreBackup.bind(this);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +38,7 @@ export default class KeyBackupPanel extends React.Component {
 | 
			
		|||
            loading: true,
 | 
			
		||||
            error: null,
 | 
			
		||||
            backupInfo: null,
 | 
			
		||||
            sessionsRemaining: 0,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +46,10 @@ export default class KeyBackupPanel extends React.Component {
 | 
			
		|||
        this._loadBackupStatus();
 | 
			
		||||
 | 
			
		||||
        MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatus);
 | 
			
		||||
        MatrixClientPeg.get().on(
 | 
			
		||||
            'crypto.keyBackupSessionsRemaining',
 | 
			
		||||
            this._onKeyBackupSessionsRemaining,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount() {
 | 
			
		||||
| 
						 | 
				
			
			@ -50,9 +57,19 @@ export default class KeyBackupPanel extends React.Component {
 | 
			
		|||
 | 
			
		||||
        if (MatrixClientPeg.get()) {
 | 
			
		||||
            MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatus);
 | 
			
		||||
            MatrixClientPeg.get().removeListener(
 | 
			
		||||
                'crypto.keyBackupSessionsRemaining',
 | 
			
		||||
                this._onKeyBackupSessionsRemaining,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onKeyBackupSessionsRemaining(sessionsRemaining) {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            sessionsRemaining,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onKeyBackupStatus() {
 | 
			
		||||
        this._loadBackupStatus();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -144,57 +161,70 @@ export default class KeyBackupPanel extends React.Component {
 | 
			
		|||
        } else if (this.state.backupInfo) {
 | 
			
		||||
            let clientBackupStatus;
 | 
			
		||||
            if (MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
			
		||||
                clientBackupStatus = _t("This device is uploading keys to this backup");
 | 
			
		||||
                clientBackupStatus = _t("This device is using key backup");
 | 
			
		||||
            } else {
 | 
			
		||||
                // XXX: display why and how to fix it
 | 
			
		||||
                clientBackupStatus = _t(
 | 
			
		||||
                    "This device is <b>not</b> uploading keys to this backup", {},
 | 
			
		||||
                    "This device is <b>not</b> using key backup", {},
 | 
			
		||||
                    {b: x => <b>{x}</b>},
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let uploadStatus;
 | 
			
		||||
            const { sessionsRemaining } = this.state;
 | 
			
		||||
            if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
 | 
			
		||||
                // No upload status to show when backup disabled.
 | 
			
		||||
                uploadStatus = "";
 | 
			
		||||
            } else if (sessionsRemaining > 0) {
 | 
			
		||||
                uploadStatus = <div>
 | 
			
		||||
                    {_t("Backing up %(sessionsRemaining)s keys...", { sessionsRemaining })} <br />
 | 
			
		||||
                </div>;
 | 
			
		||||
            } else {
 | 
			
		||||
                uploadStatus = <div>
 | 
			
		||||
                    {_t("All keys backed up")} <br />
 | 
			
		||||
                </div>;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => {
 | 
			
		||||
                const deviceName = sig.device.getDisplayName() || sig.device.deviceId;
 | 
			
		||||
                const sigStatusSubstitutions = {
 | 
			
		||||
                    validity: sub =>
 | 
			
		||||
                const validity = sub =>
 | 
			
		||||
                    <span className={sig.valid ? 'mx_KeyBackupPanel_sigValid' : 'mx_KeyBackupPanel_sigInvalid'}>
 | 
			
		||||
                        {sub}
 | 
			
		||||
                        </span>,
 | 
			
		||||
                    verify: sub =>
 | 
			
		||||
                    </span>;
 | 
			
		||||
                const verify = sub =>
 | 
			
		||||
                    <span className={sig.device.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
 | 
			
		||||
                        {sub}
 | 
			
		||||
                        </span>,
 | 
			
		||||
                    device: sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>,
 | 
			
		||||
                };
 | 
			
		||||
                    </span>;
 | 
			
		||||
                const device = sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>;
 | 
			
		||||
                let sigStatus;
 | 
			
		||||
                if (sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key()) {
 | 
			
		||||
                    sigStatus = _t(
 | 
			
		||||
                        "Backup has a <validity>valid</validity> signature from this device",
 | 
			
		||||
                        {}, sigStatusSubstitutions,
 | 
			
		||||
                        {}, { validity },
 | 
			
		||||
                    );
 | 
			
		||||
                } else if (sig.valid && sig.device.isVerified()) {
 | 
			
		||||
                    sigStatus = _t(
 | 
			
		||||
                        "Backup has a <validity>valid</validity> signature from " +
 | 
			
		||||
                        "<verify>verified</verify> device <device></device>",
 | 
			
		||||
                        {}, sigStatusSubstitutions,
 | 
			
		||||
                        {}, { validity, verify, device },
 | 
			
		||||
                    );
 | 
			
		||||
                } else if (sig.valid && !sig.device.isVerified()) {
 | 
			
		||||
                    sigStatus = _t(
 | 
			
		||||
                        "Backup has a <validity>valid</validity> signature from " +
 | 
			
		||||
                        "<verify>unverified</verify> device <device></device>",
 | 
			
		||||
                        {}, sigStatusSubstitutions,
 | 
			
		||||
                        {}, { validity, verify, device },
 | 
			
		||||
                    );
 | 
			
		||||
                } else if (!sig.valid && sig.device.isVerified()) {
 | 
			
		||||
                    sigStatus = _t(
 | 
			
		||||
                        "Backup has an <validity>invalid</validity> signature from " +
 | 
			
		||||
                        "<verify>verified</verify> device <device></device>",
 | 
			
		||||
                        {}, sigStatusSubstitutions,
 | 
			
		||||
                        {}, { validity, verify, device },
 | 
			
		||||
                    );
 | 
			
		||||
                } else if (!sig.valid && !sig.device.isVerified()) {
 | 
			
		||||
                    sigStatus = _t(
 | 
			
		||||
                        "Backup has an <validity>invalid</validity> signature from " +
 | 
			
		||||
                        "<verify>unverified</verify> device <device></device>",
 | 
			
		||||
                        {}, sigStatusSubstitutions,
 | 
			
		||||
                        {}, { validity, verify, device },
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -219,6 +249,7 @@ export default class KeyBackupPanel extends React.Component {
 | 
			
		|||
                {_t("Backup version: ")}{this.state.backupInfo.version}<br />
 | 
			
		||||
                {_t("Algorithm: ")}{this.state.backupInfo.algorithm}<br />
 | 
			
		||||
                {clientBackupStatus}<br />
 | 
			
		||||
                {uploadStatus}
 | 
			
		||||
                <div>{backupSigStatuses}</div><br />
 | 
			
		||||
                <br />
 | 
			
		||||
                <AccessibleButton className="mx_UserSettings_button"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,42 +17,10 @@ limitations under the License.
 | 
			
		|||
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const flux = require("flux");
 | 
			
		||||
 | 
			
		||||
class MatrixDispatcher extends flux.Dispatcher {
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Object|function} payload Required. The payload to dispatch.
 | 
			
		||||
     *        If an Object, must contain at least an 'action' key.
 | 
			
		||||
     *        If a function, must have the signature (dispatch) => {...}.
 | 
			
		||||
     * @param {boolean=} sync Optional. Pass true to dispatch
 | 
			
		||||
     *        synchronously. This is useful for anything triggering
 | 
			
		||||
     *        an operation that the browser requires user interaction
 | 
			
		||||
     *        for.
 | 
			
		||||
     */
 | 
			
		||||
    dispatch(payload, sync) {
 | 
			
		||||
        // Allow for asynchronous dispatching by accepting payloads that have the
 | 
			
		||||
        // type `function (dispatch) {...}`
 | 
			
		||||
        if (typeof payload === 'function') {
 | 
			
		||||
            payload((action) => {
 | 
			
		||||
                this.dispatch(action, sync);
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            super.dispatch(payload);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Unless the caller explicitly asked for us to dispatch synchronously,
 | 
			
		||||
            // we always set a timeout to do this: The flux dispatcher complains
 | 
			
		||||
            // if you dispatch from within a dispatch, so rather than action
 | 
			
		||||
            // handlers having to worry about not calling anything that might
 | 
			
		||||
            // then dispatch, we just do dispatches asynchronously.
 | 
			
		||||
            setTimeout(super.dispatch.bind(this, payload), 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
import MatrixDispatcher from "./matrix-dispatcher";
 | 
			
		||||
 | 
			
		||||
if (global.mxDispatcher === undefined) {
 | 
			
		||||
    global.mxDispatcher = new MatrixDispatcher();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = global.mxDispatcher;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -352,8 +352,10 @@
 | 
			
		|||
    "Delete your backed up encryption keys from the server? You will no longer be able to use your recovery key to read encrypted message history": "Delete your backed up encryption keys from the server? You will no longer be able to use your recovery key to read encrypted message history",
 | 
			
		||||
    "Delete backup": "Delete backup",
 | 
			
		||||
    "Unable to load key backup status": "Unable to load key backup status",
 | 
			
		||||
    "This device is uploading keys to this backup": "This device is uploading keys to this backup",
 | 
			
		||||
    "This device is <b>not</b> uploading keys to this backup": "This device is <b>not</b> uploading keys to this backup",
 | 
			
		||||
    "This device is using key backup": "This device is using key backup",
 | 
			
		||||
    "This device is <b>not</b> using key backup": "This device is <b>not</b> using key backup",
 | 
			
		||||
    "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...",
 | 
			
		||||
    "All keys backed up": "All keys backed up",
 | 
			
		||||
    "Backup has a <validity>valid</validity> signature from this device": "Backup has a <validity>valid</validity> signature from this device",
 | 
			
		||||
    "Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device></device>": "Backup has a <validity>valid</validity> signature from <verify>verified</verify> device <device></device>",
 | 
			
		||||
    "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> device <device></device>": "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> device <device></device>",
 | 
			
		||||
| 
						 | 
				
			
			@ -967,7 +969,12 @@
 | 
			
		|||
    "Clear cache and resync": "Clear cache and resync",
 | 
			
		||||
    "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
 | 
			
		||||
    "Updating Riot": "Updating Riot",
 | 
			
		||||
    "When you log out, you'll lose your secure message history. To prevent this, set up a recovery method.": "When you log out, you'll lose your secure message history. To prevent this, set up a recovery method.",
 | 
			
		||||
    "Alternatively, advanced users can also manually export encryption keys in <a>Settings</a> before logging out.": "Alternatively, advanced users can also manually export encryption keys in <a>Settings</a> before logging out.",
 | 
			
		||||
    "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.": "For security, logging out will delete any end-to-end encryption keys from this browser. If you want to be able to decrypt your conversation history from future Riot sessions, please export your room keys for safe-keeping.",
 | 
			
		||||
    "Set a Recovery Method": "Set a Recovery Method",
 | 
			
		||||
    "I understand, log out without": "I understand, log out without",
 | 
			
		||||
    "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery key.": "When signing in again, you can access encrypted chat history by restoring your key backup. You'll need your recovery key.",
 | 
			
		||||
    "Thanks for testing the Riot Redesign. If you run into any bugs or visual issues, please let us know on GitHub.": "Thanks for testing the Riot Redesign. If you run into any bugs or visual issues, please let us know on GitHub.",
 | 
			
		||||
    "To help avoid duplicate issues, please <existingIssuesLink>view existing issues</existingIssuesLink> first (and add a +1) or <newIssueLink>create a new issue</newIssueLink> if you can't find it.": "To help avoid duplicate issues, please <existingIssuesLink>view existing issues</existingIssuesLink> first (and add a +1) or <newIssueLink>create a new issue</newIssueLink> if you can't find it.",
 | 
			
		||||
    "Report bugs & give feedback": "Report bugs & give feedback",
 | 
			
		||||
| 
						 | 
				
			
			@ -1376,21 +1383,20 @@
 | 
			
		|||
    "Your Recovery Key": "Your Recovery Key",
 | 
			
		||||
    "Copy to clipboard": "Copy to clipboard",
 | 
			
		||||
    "Download": "Download",
 | 
			
		||||
    "I've made a copy": "I've made a copy",
 | 
			
		||||
    "Your Recovery Key has been <b>copied to your clipboard</b>, paste it to:": "Your Recovery Key has been <b>copied to your clipboard</b>, paste it to:",
 | 
			
		||||
    "Your Recovery Key is in your <b>Downloads</b> folder.": "Your Recovery Key is in your <b>Downloads</b> folder.",
 | 
			
		||||
    "<b>Print it</b> and store it somewhere safe": "<b>Print it</b> and store it somewhere safe",
 | 
			
		||||
    "<b>Save it</b> on a USB key or backup drive": "<b>Save it</b> on a USB key or backup drive",
 | 
			
		||||
    "<b>Copy it</b> to your personal cloud storage": "<b>Copy it</b> to your personal cloud storage",
 | 
			
		||||
    "Backup created": "Backup created",
 | 
			
		||||
    "Your encryption keys are now being backed up to your Homeserver.": "Your encryption keys are now being backed up to your Homeserver.",
 | 
			
		||||
    "Your encryption keys are now being backed up in the background to your Homeserver. The initial backup could take several minutes. You can view key backup upload progress in Settings.": "Your encryption keys are now being backed up in the background to your Homeserver. The initial backup could take several minutes. You can view key backup upload progress in Settings.",
 | 
			
		||||
    "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another device.",
 | 
			
		||||
    "Set up Secure Message Recovery": "Set up Secure Message Recovery",
 | 
			
		||||
    "Create a Recovery Passphrase": "Create a Recovery Passphrase",
 | 
			
		||||
    "Confirm Recovery Passphrase": "Confirm Recovery Passphrase",
 | 
			
		||||
    "Recovery Key": "Recovery Key",
 | 
			
		||||
    "Keep it safe": "Keep it safe",
 | 
			
		||||
    "Backing up...": "Backing up...",
 | 
			
		||||
    "Starting backup...": "Starting backup...",
 | 
			
		||||
    "Backup Started": "Backup Started",
 | 
			
		||||
    "Create Key Backup": "Create Key Backup",
 | 
			
		||||
    "Unable to create key backup": "Unable to create key backup",
 | 
			
		||||
    "Retry": "Retry",
 | 
			
		||||
| 
						 | 
				
			
			@ -1405,5 +1411,8 @@
 | 
			
		|||
    "Failed to set direct chat tag": "Failed to set direct chat tag",
 | 
			
		||||
    "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
 | 
			
		||||
    "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room",
 | 
			
		||||
    "Render simple counters in room header": "Render simple counters in room header"
 | 
			
		||||
    "Render simple counters in room header": "Render simple counters in room header",
 | 
			
		||||
    "View as Grid": "View as Grid",
 | 
			
		||||
    "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu": "Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu",
 | 
			
		||||
    "No room in this tile yet.": "No room in this tile yet."
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2015, 2016 OpenMarket Ltd
 | 
			
		||||
Copyright 2017 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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const flux = require("flux");
 | 
			
		||||
 | 
			
		||||
export default class MatrixDispatcher extends flux.Dispatcher {
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Object|function} payload Required. The payload to dispatch.
 | 
			
		||||
     *        If an Object, must contain at least an 'action' key.
 | 
			
		||||
     *        If a function, must have the signature (dispatch) => {...}.
 | 
			
		||||
     * @param {boolean=} sync Optional. Pass true to dispatch
 | 
			
		||||
     *        synchronously. This is useful for anything triggering
 | 
			
		||||
     *        an operation that the browser requires user interaction
 | 
			
		||||
     *        for.
 | 
			
		||||
     */
 | 
			
		||||
    dispatch(payload, sync) {
 | 
			
		||||
        // Allow for asynchronous dispatching by accepting payloads that have the
 | 
			
		||||
        // type `function (dispatch) {...}`
 | 
			
		||||
        if (typeof payload === 'function') {
 | 
			
		||||
            payload((action) => {
 | 
			
		||||
                this.dispatch(action, sync);
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            super.dispatch(payload);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Unless the caller explicitly asked for us to dispatch synchronously,
 | 
			
		||||
            // we always set a timeout to do this: The flux dispatcher complains
 | 
			
		||||
            // if you dispatch from within a dispatch, so rather than action
 | 
			
		||||
            // handlers having to worry about not calling anything that might
 | 
			
		||||
            // then dispatch, we just do dispatches asynchronously.
 | 
			
		||||
            setTimeout(super.dispatch.bind(this, payload), 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -108,6 +108,12 @@ export const SETTINGS = {
 | 
			
		|||
        supportedLevels: LEVELS_FEATURE,
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    "feature_gridview": {
 | 
			
		||||
        isFeature: true,
 | 
			
		||||
        displayName: _td("Allow up to 6 rooms in a community to be shown simultaneously in a grid via the context menu"),
 | 
			
		||||
        supportedLevels: LEVELS_FEATURE,
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    "MessageComposerInput.dontSuggestEmoji": {
 | 
			
		||||
        supportedLevels: LEVELS_ACCOUNT_SETTINGS,
 | 
			
		||||
        displayName: _td('Disable Emoji suggestions while typing'),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,277 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018 New Vector Ltd
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
import MatrixDispatcher from '../matrix-dispatcher';
 | 
			
		||||
import dis from '../dispatcher';
 | 
			
		||||
import {RoomViewStore} from './RoomViewStore';
 | 
			
		||||
import GroupStore from './GroupStore';
 | 
			
		||||
import {Store} from 'flux/utils';
 | 
			
		||||
import MatrixClientPeg from '../MatrixClientPeg';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function matchesRoom(payload, roomStore) {
 | 
			
		||||
    if (!roomStore) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.room_alias) {
 | 
			
		||||
        return payload.room_alias === roomStore.getRoomAlias();
 | 
			
		||||
    }
 | 
			
		||||
    return payload.room_id === roomStore.getRoomId();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A class for keeping track of the RoomViewStores of the rooms shown on the screen.
 | 
			
		||||
 * Routes the dispatcher actions to the store of currently active room.
 | 
			
		||||
 */
 | 
			
		||||
class OpenRoomsStore extends Store {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(dis);
 | 
			
		||||
 | 
			
		||||
        // Initialise state
 | 
			
		||||
        this._state = {
 | 
			
		||||
            rooms: [],
 | 
			
		||||
            currentIndex: null,
 | 
			
		||||
            group_id: null,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this._forwardingEvent = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getRoomStores() {
 | 
			
		||||
        return this._state.rooms.map((r) => r.store);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getActiveRoomStore() {
 | 
			
		||||
        const openRoom = this._getActiveOpenRoom();
 | 
			
		||||
        if (openRoom) {
 | 
			
		||||
            return openRoom.store;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getRoomStoreAt(index) {
 | 
			
		||||
        if (index >= 0 && index < this._state.rooms.length) {
 | 
			
		||||
            return this._state.rooms[index].store;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _getActiveOpenRoom() {
 | 
			
		||||
        const index = this._state.currentIndex;
 | 
			
		||||
        if (index !== null && index < this._state.rooms.length) {
 | 
			
		||||
            return this._state.rooms[index];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _setState(newState) {
 | 
			
		||||
        this._state = Object.assign(this._state, newState);
 | 
			
		||||
        this.__emitChange();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _hasRoom(payload) {
 | 
			
		||||
        return this._roomIndex(payload) !== -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _roomIndex(payload) {
 | 
			
		||||
        return this._state.rooms.findIndex((r) => matchesRoom(payload, r.store));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _cleanupOpenRooms() {
 | 
			
		||||
        this._state.rooms.forEach((room) => {
 | 
			
		||||
            room.dispatcher.unregister(room.dispatcherRef);
 | 
			
		||||
            room.dispatcher.unregister(room.store.getDispatchToken());
 | 
			
		||||
        });
 | 
			
		||||
        this._setState({
 | 
			
		||||
            rooms: [],
 | 
			
		||||
            group_id: null,
 | 
			
		||||
            currentIndex: null,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _createOpenRoom(roomId, roomAlias) {
 | 
			
		||||
        const dispatcher = new MatrixDispatcher();
 | 
			
		||||
        // forward all actions coming from the room dispatcher
 | 
			
		||||
        // to the global one
 | 
			
		||||
        const dispatcherRef = dispatcher.register((payload) => {
 | 
			
		||||
            // block a view_room action for the same room because it will switch to
 | 
			
		||||
            // single room mode in MatrixChat
 | 
			
		||||
            if (payload.action === 'view_room' && roomId === payload.room_id) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            payload.grid_src_room_id = roomId;
 | 
			
		||||
            payload.grid_src_room_alias = roomAlias;
 | 
			
		||||
            this.getDispatcher().dispatch(payload);
 | 
			
		||||
        });
 | 
			
		||||
        const openRoom = {
 | 
			
		||||
            store: new RoomViewStore(dispatcher),
 | 
			
		||||
            dispatcher,
 | 
			
		||||
            dispatcherRef,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        dispatcher.dispatch({
 | 
			
		||||
            action: 'view_room',
 | 
			
		||||
            room_id: roomId,
 | 
			
		||||
            room_alias: roomAlias,
 | 
			
		||||
        }, true);
 | 
			
		||||
 | 
			
		||||
        return openRoom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _setSingleOpenRoom(payload) {
 | 
			
		||||
        this._setState({
 | 
			
		||||
            rooms: [this._createOpenRoom(payload.room_id, payload.room_alias)],
 | 
			
		||||
            currentIndex: 0,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _setGroupOpenRooms(groupId) {
 | 
			
		||||
        this._cleanupOpenRooms();
 | 
			
		||||
        // TODO: register to GroupStore updates
 | 
			
		||||
        const rooms = GroupStore.getGroupRooms(groupId);
 | 
			
		||||
        const openRooms = rooms.map((room) => {
 | 
			
		||||
            return this._createOpenRoom(room.roomId);
 | 
			
		||||
        });
 | 
			
		||||
        this._setState({
 | 
			
		||||
            rooms: openRooms,
 | 
			
		||||
            group_id: groupId,
 | 
			
		||||
            currentIndex: 0,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _forwardAction(payload) {
 | 
			
		||||
        // don't forward an event to a room dispatcher
 | 
			
		||||
        // if the event originated from that dispatcher, as this
 | 
			
		||||
        // would cause the event to be observed twice in that
 | 
			
		||||
        // dispatcher
 | 
			
		||||
        if (payload.grid_src_room_id || payload.grid_src_room_alias) {
 | 
			
		||||
            const srcPayload = {
 | 
			
		||||
                room_id: payload.grid_src_room_id,
 | 
			
		||||
                room_alias: payload.grid_src_room_alias,
 | 
			
		||||
            };
 | 
			
		||||
            const srcIndex = this._roomIndex(srcPayload);
 | 
			
		||||
            if (srcIndex === this._state.currentIndex) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const currentRoom = this._getActiveOpenRoom();
 | 
			
		||||
        if (currentRoom) {
 | 
			
		||||
            currentRoom.dispatcher.dispatch(payload, true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _resolveRoomAlias(payload) {
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await MatrixClientPeg.get()
 | 
			
		||||
                .getRoomIdForAlias(payload.room_alias);
 | 
			
		||||
            this.getDispatcher().dispatch({
 | 
			
		||||
                action: 'view_room',
 | 
			
		||||
                room_id: result.room_id,
 | 
			
		||||
                event_id: payload.event_id,
 | 
			
		||||
                highlighted: payload.highlighted,
 | 
			
		||||
                room_alias: payload.room_alias,
 | 
			
		||||
                auto_join: payload.auto_join,
 | 
			
		||||
                oob_data: payload.oob_data,
 | 
			
		||||
            });
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            this._forwardAction({
 | 
			
		||||
                action: 'view_room_error',
 | 
			
		||||
                room_id: null,
 | 
			
		||||
                room_alias: payload.room_alias,
 | 
			
		||||
                err: err,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _viewRoom(payload) {
 | 
			
		||||
        console.log("!!! OpenRoomsStore: view_room", payload);
 | 
			
		||||
        if (!payload.room_id && payload.room_alias) {
 | 
			
		||||
            this._resolveRoomAlias(payload);
 | 
			
		||||
        }
 | 
			
		||||
        const currentStore = this.getActiveRoomStore();
 | 
			
		||||
        if (!matchesRoom(payload, currentStore)) {
 | 
			
		||||
            if (this._hasRoom(payload)) {
 | 
			
		||||
                const roomIndex = this._roomIndex(payload);
 | 
			
		||||
                this._setState({currentIndex: roomIndex});
 | 
			
		||||
            } else {
 | 
			
		||||
                this._cleanupOpenRooms();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.getActiveRoomStore()) {
 | 
			
		||||
            console.log("OpenRoomsStore: _setSingleOpenRoom");
 | 
			
		||||
            this._setSingleOpenRoom(payload);
 | 
			
		||||
        }
 | 
			
		||||
        console.log("OpenRoomsStore: _forwardAction");
 | 
			
		||||
        this._forwardAction(payload);
 | 
			
		||||
        if (this._forwardingEvent) {
 | 
			
		||||
            this.getDispatcher().dispatch({
 | 
			
		||||
                action: 'send_event',
 | 
			
		||||
                room_id: payload.room_id,
 | 
			
		||||
                event: this._forwardingEvent,
 | 
			
		||||
            });
 | 
			
		||||
            this._forwardingEvent = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    __onDispatch(payload) {
 | 
			
		||||
        let proposedIndex;
 | 
			
		||||
        switch (payload.action) {
 | 
			
		||||
            // view_room:
 | 
			
		||||
            //      - room_alias:   '#somealias:matrix.org'
 | 
			
		||||
            //      - room_id:      '!roomid123:matrix.org'
 | 
			
		||||
            //      - event_id:     '$213456782:matrix.org'
 | 
			
		||||
            //      - event_offset: 100
 | 
			
		||||
            //      - highlighted:  true
 | 
			
		||||
            case 'view_room':
 | 
			
		||||
                this._viewRoom(payload);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'view_my_groups':
 | 
			
		||||
            case 'view_group':
 | 
			
		||||
                this._forwardAction(payload);
 | 
			
		||||
                this._cleanupOpenRooms();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'will_join':
 | 
			
		||||
            case 'cancel_join':
 | 
			
		||||
            case 'join_room':
 | 
			
		||||
            case 'join_room_error':
 | 
			
		||||
            case 'on_logged_out':
 | 
			
		||||
            case 'reply_to_event':
 | 
			
		||||
            case 'open_room_settings':
 | 
			
		||||
            case 'close_settings':
 | 
			
		||||
            case 'focus_composer':
 | 
			
		||||
                this._forwardAction(payload);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'forward_event':
 | 
			
		||||
                this._forwardingEvent = payload.event;
 | 
			
		||||
                break;
 | 
			
		||||
            case 'group_grid_set_active':
 | 
			
		||||
                proposedIndex = this._roomIndex(payload);
 | 
			
		||||
                if (proposedIndex !== -1) {
 | 
			
		||||
                    this._setState({
 | 
			
		||||
                        currentIndex: proposedIndex,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 'group_grid_view':
 | 
			
		||||
                if (payload.group_id !== this._state.group_id) {
 | 
			
		||||
                    this._setGroupOpenRooms(payload.group_id);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let singletonOpenRoomsStore = null;
 | 
			
		||||
if (!singletonOpenRoomsStore) {
 | 
			
		||||
    singletonOpenRoomsStore = new OpenRoomsStore();
 | 
			
		||||
}
 | 
			
		||||
module.exports = singletonOpenRoomsStore;
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +277,7 @@ class RoomListStore extends Store {
 | 
			
		|||
        const roomCache = this._state.roomCache;
 | 
			
		||||
        if (!roomCache[roomId]) roomCache[roomId] = {};
 | 
			
		||||
 | 
			
		||||
        if (value) roomCache[roomId][type] = value;
 | 
			
		||||
        if (typeof value !== "undefined") roomCache[roomId][type] = value;
 | 
			
		||||
        else delete roomCache[roomId][type];
 | 
			
		||||
 | 
			
		||||
        this._setState({roomCache});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ 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 dis from '../dispatcher';
 | 
			
		||||
import {Store} from 'flux/utils';
 | 
			
		||||
import MatrixClientPeg from '../MatrixClientPeg';
 | 
			
		||||
import sdk from '../index';
 | 
			
		||||
| 
						 | 
				
			
			@ -53,12 +52,12 @@ const INITIAL_STATE = {
 | 
			
		|||
*  with a subset of the js-sdk.
 | 
			
		||||
 *  ```
 | 
			
		||||
 */
 | 
			
		||||
class RoomViewStore extends Store {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(dis);
 | 
			
		||||
export class RoomViewStore extends Store {
 | 
			
		||||
    constructor(dispatcher) {
 | 
			
		||||
        super(dispatcher);
 | 
			
		||||
 | 
			
		||||
        // Initialise state
 | 
			
		||||
        this._state = INITIAL_STATE;
 | 
			
		||||
        this._state = Object.assign({}, INITIAL_STATE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _setState(newState) {
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +84,8 @@ class RoomViewStore extends Store {
 | 
			
		|||
                });
 | 
			
		||||
                break;
 | 
			
		||||
            case 'view_room_error':
 | 
			
		||||
                // should not go over dispatcher anymore
 | 
			
		||||
                // but be internal to RoomViewStore
 | 
			
		||||
                this._viewRoomError(payload);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'will_join':
 | 
			
		||||
| 
						 | 
				
			
			@ -150,22 +151,11 @@ class RoomViewStore extends Store {
 | 
			
		|||
                // pull the user out of Room Settings
 | 
			
		||||
                isEditingSettings: false,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if (this._state.forwardingEvent) {
 | 
			
		||||
                dis.dispatch({
 | 
			
		||||
                    action: 'send_event',
 | 
			
		||||
                    room_id: newState.roomId,
 | 
			
		||||
                    event: this._state.forwardingEvent,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this._setState(newState);
 | 
			
		||||
 | 
			
		||||
            if (payload.auto_join) {
 | 
			
		||||
                this._joinRoom(payload);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (payload.room_alias) {
 | 
			
		||||
            // Resolve the alias and then do a second dispatch with the room ID acquired
 | 
			
		||||
            this._setState({
 | 
			
		||||
                roomId: null,
 | 
			
		||||
                initialEventId: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -175,25 +165,6 @@ class RoomViewStore extends Store {
 | 
			
		|||
                roomLoading: true,
 | 
			
		||||
                roomLoadError: null,
 | 
			
		||||
            });
 | 
			
		||||
            MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
 | 
			
		||||
            (result) => {
 | 
			
		||||
                dis.dispatch({
 | 
			
		||||
                    action: 'view_room',
 | 
			
		||||
                    room_id: result.room_id,
 | 
			
		||||
                    event_id: payload.event_id,
 | 
			
		||||
                    highlighted: payload.highlighted,
 | 
			
		||||
                    room_alias: payload.room_alias,
 | 
			
		||||
                    auto_join: payload.auto_join,
 | 
			
		||||
                    oob_data: payload.oob_data,
 | 
			
		||||
                });
 | 
			
		||||
            }, (err) => {
 | 
			
		||||
                dis.dispatch({
 | 
			
		||||
                    action: 'view_room_error',
 | 
			
		||||
                    room_id: null,
 | 
			
		||||
                    room_alias: payload.room_alias,
 | 
			
		||||
                    err: err,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -219,7 +190,7 @@ class RoomViewStore extends Store {
 | 
			
		|||
            // stream yet, and that's the point at which we'd consider
 | 
			
		||||
            // the user joined to the room.
 | 
			
		||||
        }, (err) => {
 | 
			
		||||
            dis.dispatch({
 | 
			
		||||
            this.getDispatcher().dispatch({
 | 
			
		||||
                action: 'join_room_error',
 | 
			
		||||
                err: err,
 | 
			
		||||
            });
 | 
			
		||||
| 
						 | 
				
			
			@ -335,8 +306,7 @@ class RoomViewStore extends Store {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let singletonRoomViewStore = null;
 | 
			
		||||
if (!singletonRoomViewStore) {
 | 
			
		||||
    singletonRoomViewStore = new RoomViewStore();
 | 
			
		||||
}
 | 
			
		||||
module.exports = singletonRoomViewStore;
 | 
			
		||||
const MatrixDispatcher = require("../matrix-dispatcher");
 | 
			
		||||
const backwardsCompatInstance = new RoomViewStore(new MatrixDispatcher());
 | 
			
		||||
 | 
			
		||||
export default backwardsCompatInstance;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,6 @@ Once a timer is finished or aborted, it can't be started again
 | 
			
		|||
a new one through `clone()` or `cloneIfRun()`.
 | 
			
		||||
*/
 | 
			
		||||
export default class Timer {
 | 
			
		||||
 | 
			
		||||
    constructor(timeout) {
 | 
			
		||||
        this._timeout = timeout;
 | 
			
		||||
        this._onTimeout = this._onTimeout.bind(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +69,7 @@ export default class Timer {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * if not started before, starts the timer.
 | 
			
		||||
     * @returns {Timer} the same timer
 | 
			
		||||
     */
 | 
			
		||||
    start() {
 | 
			
		||||
        if (!this.isRunning()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +81,7 @@ export default class Timer {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * (re)start the timer. If it's running, reset the timeout. If not, start it.
 | 
			
		||||
     * @returns {Timer} the same timer
 | 
			
		||||
     */
 | 
			
		||||
    restart() {
 | 
			
		||||
        if (this.isRunning()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +99,7 @@ export default class Timer {
 | 
			
		|||
    /**
 | 
			
		||||
     * if the timer is running, abort it,
 | 
			
		||||
     * and reject the promise for this timer.
 | 
			
		||||
     * @returns {Timer} the same timer
 | 
			
		||||
     */
 | 
			
		||||
    abort() {
 | 
			
		||||
        if (this.isRunning()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue