mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-15 17:21:33 +03:00
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into spaces-jump-to-room
This commit is contained in:
commit
a99b24ef83
666 changed files with 5083 additions and 5561 deletions
38
.eslintrc.js
38
.eslintrc.js
|
@ -1,7 +1,9 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ["matrix-org", "matrix-org/react-legacy"],
|
plugins: ["matrix-org"],
|
||||||
parser: "babel-eslint",
|
extends: [
|
||||||
|
"plugin:matrix-org/babel",
|
||||||
|
"plugin:matrix-org/react",
|
||||||
|
],
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
node: true,
|
node: true,
|
||||||
|
@ -15,12 +17,32 @@ module.exports = {
|
||||||
"prefer-promise-reject-errors": "off",
|
"prefer-promise-reject-errors": "off",
|
||||||
"no-async-promise-executor": "off",
|
"no-async-promise-executor": "off",
|
||||||
"quotes": "off",
|
"quotes": "off",
|
||||||
},
|
"no-extra-boolean-cast": "off",
|
||||||
|
|
||||||
|
// Bind or arrow functions in props causes performance issues (but we
|
||||||
|
// currently use them in some places).
|
||||||
|
// It's disabled here, but we should using it sparingly.
|
||||||
|
"react/jsx-no-bind": "off",
|
||||||
|
"react/jsx-key": ["error"],
|
||||||
|
},
|
||||||
overrides: [{
|
overrides: [{
|
||||||
"files": ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"],
|
files: [
|
||||||
"extends": ["matrix-org/ts"],
|
"src/**/*.{ts,tsx}",
|
||||||
"rules": {
|
"test/**/*.{ts,tsx}",
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
"plugin:matrix-org/typescript",
|
||||||
|
"plugin:matrix-org/react",
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
// Things we do that break the ideal style
|
||||||
|
"prefer-promise-reject-errors": "off",
|
||||||
|
"quotes": "off",
|
||||||
|
"no-extra-boolean-cast": "off",
|
||||||
|
|
||||||
|
// Remove Babel things manually due to override limitations
|
||||||
|
"@babel/no-invalid-this": ["off"],
|
||||||
|
|
||||||
// We're okay being explicit at the moment
|
// We're okay being explicit at the moment
|
||||||
"@typescript-eslint/no-empty-interface": "off",
|
"@typescript-eslint/no-empty-interface": "off",
|
||||||
// We disable this while we're transitioning
|
// We disable this while we're transitioning
|
||||||
|
@ -28,8 +50,6 @@ module.exports = {
|
||||||
// We'd rather not do this but we do
|
// We'd rather not do this but we do
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
|
||||||
"quotes": "off",
|
|
||||||
"no-extra-boolean-cast": "off",
|
|
||||||
"no-restricted-properties": [
|
"no-restricted-properties": [
|
||||||
"error",
|
"error",
|
||||||
...buildRestrictedPropertiesOptions(
|
...buildRestrictedPropertiesOptions(
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[include]
|
|
||||||
src/**/*.js
|
|
||||||
test/**/*.js
|
|
||||||
|
|
||||||
[ignore]
|
|
||||||
node_modules/
|
|
|
@ -10,7 +10,6 @@ module.exports = {
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
"@babel/preset-typescript",
|
"@babel/preset-typescript",
|
||||||
"@babel/preset-flow",
|
|
||||||
"@babel/preset-react",
|
"@babel/preset-react",
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
@ -19,7 +18,6 @@ module.exports = {
|
||||||
"@babel/plugin-proposal-numeric-separator",
|
"@babel/plugin-proposal-numeric-separator",
|
||||||
"@babel/plugin-proposal-class-properties",
|
"@babel/plugin-proposal-class-properties",
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
"@babel/plugin-transform-flow-comments",
|
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
],
|
],
|
||||||
|
|
14
package.json
14
package.json
|
@ -104,16 +104,16 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.10",
|
"@babel/cli": "^7.12.10",
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
|
"@babel/eslint-parser": "^7.12.10",
|
||||||
|
"@babel/eslint-plugin": "^7.12.10",
|
||||||
"@babel/parser": "^7.12.11",
|
"@babel/parser": "^7.12.11",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "^7.12.12",
|
"@babel/plugin-proposal-decorators": "^7.12.12",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
||||||
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
|
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
||||||
"@babel/plugin-transform-flow-comments": "^7.12.1",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
"@babel/plugin-transform-runtime": "^7.12.10",
|
||||||
"@babel/preset-env": "^7.12.11",
|
"@babel/preset-env": "^7.12.11",
|
||||||
"@babel/preset-flow": "^7.12.1",
|
|
||||||
"@babel/preset-react": "^7.12.10",
|
"@babel/preset-react": "^7.12.10",
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@babel/register": "^7.12.10",
|
"@babel/register": "^7.12.10",
|
||||||
|
@ -139,18 +139,16 @@
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "^2.3.1",
|
"@types/sanitize-html": "^2.3.1",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@types/zxcvbn": "^4.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||||
"@typescript-eslint/parser": "^4.14.0",
|
"@typescript-eslint/parser": "^4.17.0",
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||||
"babel-eslint": "^10.1.0",
|
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"concurrently": "^5.3.0",
|
"concurrently": "^5.3.0",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"eslint": "7.18.0",
|
"eslint": "7.18.0",
|
||||||
"eslint-config-matrix-org": "^0.2.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-plugin-babel": "^5.3.1",
|
"eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main",
|
||||||
"eslint-plugin-flowtype": "^5.2.0",
|
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
|
|
|
@ -134,12 +134,15 @@ limitations under the License.
|
||||||
.mx_Toast_buttons {
|
.mx_Toast_buttons {
|
||||||
float: right;
|
float: right;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
min-width: 96px;
|
min-width: 96px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton + .mx_AccessibleButton {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Toast_description {
|
.mx_Toast_description {
|
||||||
|
|
14
src/@types/global.d.ts
vendored
14
src/@types/global.d.ts
vendored
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
|
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
|
||||||
import * as ModernizrStatic from "modernizr";
|
import * as ModernizrStatic from "modernizr";
|
||||||
|
|
||||||
import ContentMessages from "../ContentMessages";
|
import ContentMessages from "../ContentMessages";
|
||||||
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||||
import ToastStore from "../stores/ToastStore";
|
import ToastStore from "../stores/ToastStore";
|
||||||
|
@ -127,11 +128,24 @@ declare global {
|
||||||
setSinkId(outputId: string);
|
setSinkId(outputId: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Chrome-specific `instant` ScrollBehaviour
|
||||||
|
type _ScrollBehavior = ScrollBehavior | "instant";
|
||||||
|
|
||||||
|
interface _ScrollOptions {
|
||||||
|
behavior?: _ScrollBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _ScrollIntoViewOptions extends _ScrollOptions {
|
||||||
|
block?: ScrollLogicalPosition;
|
||||||
|
inline?: ScrollLogicalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
interface Element {
|
interface Element {
|
||||||
// Safari & IE11 only have this prefixed: we used prefixed versions
|
// Safari & IE11 only have this prefixed: we used prefixed versions
|
||||||
// previously so let's continue to support them for now
|
// previously so let's continue to support them for now
|
||||||
webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
|
webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
|
||||||
msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
|
msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
|
||||||
|
scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Error {
|
interface Error {
|
||||||
|
|
|
@ -189,7 +189,6 @@ export default class AddThreepid {
|
||||||
// pop up an interactive auth dialog
|
// pop up an interactive auth dialog
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
|
|
||||||
|
|
||||||
const dialogAesthetics = {
|
const dialogAesthetics = {
|
||||||
[SSOAuthEntry.PHASE_PREAUTH]: {
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
title: _t("Use Single Sign On to continue"),
|
title: _t("Use Single Sign On to continue"),
|
||||||
|
|
|
@ -348,7 +348,7 @@ export default abstract class BasePlatform {
|
||||||
/**
|
/**
|
||||||
* Create and store a pickle key for encrypting libolm objects.
|
* Create and store a pickle key for encrypting libolm objects.
|
||||||
* @param {string} userId the user ID for the user that the pickle key is for.
|
* @param {string} userId the user ID for the user that the pickle key is for.
|
||||||
* @param {string} userId the device ID that the pickle key is for.
|
* @param {string} deviceId the device ID that the pickle key is for.
|
||||||
* @returns {string|null} the pickle key, or null if the platform does not
|
* @returns {string|null} the pickle key, or null if the platform does not
|
||||||
* support storing pickle keys.
|
* support storing pickle keys.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -80,7 +80,7 @@ import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
import { UIFeature } from "./settings/UIFeature";
|
import { UIFeature } from "./settings/UIFeature";
|
||||||
import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker"
|
import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker";
|
||||||
import { Action } from './dispatcher/actions';
|
import { Action } from './dispatcher/actions';
|
||||||
import VoipUserMapper from './VoipUserMapper';
|
import VoipUserMapper from './VoipUserMapper';
|
||||||
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
||||||
|
@ -166,7 +166,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
|
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
if (!window.mxCallHandler) {
|
if (!window.mxCallHandler) {
|
||||||
window.mxCallHandler = new CallHandler()
|
window.mxCallHandler = new CallHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.mxCallHandler;
|
return window.mxCallHandler;
|
||||||
|
@ -185,7 +185,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
const nativeUser = this.assertedIdentityNativeUsers[call.callId];
|
const nativeUser = this.assertedIdentityNativeUsers[call.callId];
|
||||||
if (nativeUser) {
|
if (nativeUser) {
|
||||||
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
||||||
if (room) return room.roomId
|
if (room) return room.roomId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
action: 'incoming_call',
|
action: 'incoming_call',
|
||||||
call: call,
|
call: call,
|
||||||
}, true);
|
}, true);
|
||||||
}
|
};
|
||||||
|
|
||||||
getCallForRoom(roomId: string): MatrixCall {
|
getCallForRoom(roomId: string): MatrixCall {
|
||||||
return this.calls.get(roomId) || null;
|
return this.calls.get(roomId) || null;
|
||||||
|
@ -816,7 +816,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
|
|
||||||
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
|
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
|
||||||
console.log("Adding call for room ", mappedRoomId);
|
console.log("Adding call for room ", mappedRoomId);
|
||||||
this.calls.set(mappedRoomId, call)
|
this.calls.set(mappedRoomId, call);
|
||||||
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
this.emit(CallHandlerEvent.CallsChanged, this.calls);
|
||||||
this.setCallListeners(call);
|
this.setCallListeners(call);
|
||||||
|
|
||||||
|
@ -872,7 +872,7 @@ export default class CallHandler extends EventEmitter {
|
||||||
this.dialNumber(payload.number);
|
this.dialNumber(payload.number);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private async dialNumber(number: string) {
|
private async dialNumber(number: string) {
|
||||||
const results = await this.pstnLookup(number);
|
const results = await this.pstnLookup(number);
|
||||||
|
|
|
@ -338,8 +338,8 @@ const getRoomStats = (roomId: string) => {
|
||||||
"is_encrypted": cli?.isRoomEncrypted(roomId),
|
"is_encrypted": cli?.isRoomEncrypted(roomId),
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
"is_public": room?.currentState.getStateEvents("m.room.join_rules", "")?.getContent()?.join_rule === "public",
|
"is_public": room?.currentState.getStateEvents("m.room.join_rules", "")?.getContent()?.join_rule === "public",
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
// async wrapper for regex-powered String.prototype.replace
|
// async wrapper for regex-powered String.prototype.replace
|
||||||
const strReplaceAsync = async (str: string, regex: RegExp, fn: (...args: string[]) => Promise<string>) => {
|
const strReplaceAsync = async (str: string, regex: RegExp, fn: (...args: string[]) => Promise<string>) => {
|
||||||
|
@ -414,7 +414,7 @@ export default class CountlyAnalytics {
|
||||||
|
|
||||||
this.anonymous = anonymous;
|
this.anonymous = anonymous;
|
||||||
if (anonymous) {
|
if (anonymous) {
|
||||||
await this.changeUserKey(randomString(64))
|
await this.changeUserKey(randomString(64));
|
||||||
} else {
|
} else {
|
||||||
await this.changeUserKey(await hashHex(MatrixClientPeg.get().getUserId()), true);
|
await this.changeUserKey(await hashHex(MatrixClientPeg.get().getUserId()), true);
|
||||||
}
|
}
|
||||||
|
@ -438,7 +438,7 @@ export default class CountlyAnalytics {
|
||||||
await this.track("Opt-Out" );
|
await this.track("Opt-Out" );
|
||||||
this.endSession();
|
this.endSession();
|
||||||
window.clearInterval(this.heartbeatIntervalId);
|
window.clearInterval(this.heartbeatIntervalId);
|
||||||
window.clearTimeout(this.activityIntervalId)
|
window.clearTimeout(this.activityIntervalId);
|
||||||
this.baseUrl = null;
|
this.baseUrl = null;
|
||||||
// remove listeners bound in trackSessions()
|
// remove listeners bound in trackSessions()
|
||||||
window.removeEventListener("beforeunload", this.endSession);
|
window.removeEventListener("beforeunload", this.endSession);
|
||||||
|
@ -669,7 +669,7 @@ export default class CountlyAnalytics {
|
||||||
count,
|
count,
|
||||||
platform: this.appPlatform,
|
platform: this.appPlatform,
|
||||||
app_version: this.appVersion,
|
app_version: this.appVersion,
|
||||||
}
|
};
|
||||||
|
|
||||||
this.pendingEvents.push(ev);
|
this.pendingEvents.push(ev);
|
||||||
if (this.pendingEvents.length > MAX_PENDING_EVENTS) {
|
if (this.pendingEvents.length > MAX_PENDING_EVENTS) {
|
||||||
|
@ -680,7 +680,7 @@ export default class CountlyAnalytics {
|
||||||
private getOrientation = (): Orientation => {
|
private getOrientation = (): Orientation => {
|
||||||
return window.matchMedia("(orientation: landscape)").matches
|
return window.matchMedia("(orientation: landscape)").matches
|
||||||
? Orientation.Landscape
|
? Orientation.Landscape
|
||||||
: Orientation.Portrait
|
: Orientation.Portrait;
|
||||||
};
|
};
|
||||||
|
|
||||||
private reportOrientation = () => {
|
private reportOrientation = () => {
|
||||||
|
@ -749,7 +749,7 @@ export default class CountlyAnalytics {
|
||||||
const request: Parameters<typeof CountlyAnalytics.prototype.request>[0] = {
|
const request: Parameters<typeof CountlyAnalytics.prototype.request>[0] = {
|
||||||
begin_session: 1,
|
begin_session: 1,
|
||||||
user_details: JSON.stringify(userDetails),
|
user_details: JSON.stringify(userDetails),
|
||||||
}
|
};
|
||||||
|
|
||||||
const metrics = this.getMetrics();
|
const metrics = this.getMetrics();
|
||||||
if (metrics) {
|
if (metrics) {
|
||||||
|
@ -773,7 +773,7 @@ export default class CountlyAnalytics {
|
||||||
|
|
||||||
private endSession = () => {
|
private endSession = () => {
|
||||||
if (this.sessionStarted) {
|
if (this.sessionStarted) {
|
||||||
window.removeEventListener("resize", this.reportOrientation)
|
window.removeEventListener("resize", this.reportOrientation);
|
||||||
|
|
||||||
this.reportViewDuration();
|
this.reportViewDuration();
|
||||||
this.request({
|
this.request({
|
||||||
|
|
|
@ -138,7 +138,7 @@ export function getHtmlText(insaneHtml: string): string {
|
||||||
selfClosing: [],
|
selfClosing: [],
|
||||||
allowedSchemes: [],
|
allowedSchemes: [],
|
||||||
disallowedTagsMode: 'discard',
|
disallowedTagsMode: 'discard',
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -156,7 +156,7 @@ const messageComposerBindings = (): KeyBinding<MessageComposerAction>[] => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
};
|
||||||
|
|
||||||
const autocompleteBindings = (): KeyBinding<AutocompleteAction>[] => {
|
const autocompleteBindings = (): KeyBinding<AutocompleteAction>[] => {
|
||||||
return [
|
return [
|
||||||
|
@ -207,7 +207,7 @@ const autocompleteBindings = (): KeyBinding<AutocompleteAction>[] => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
};
|
||||||
|
|
||||||
const roomListBindings = (): KeyBinding<RoomListAction>[] => {
|
const roomListBindings = (): KeyBinding<RoomListAction>[] => {
|
||||||
return [
|
return [
|
||||||
|
@ -248,7 +248,7 @@ const roomListBindings = (): KeyBinding<RoomListAction>[] => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
};
|
||||||
|
|
||||||
const roomBindings = (): KeyBinding<RoomAction>[] => {
|
const roomBindings = (): KeyBinding<RoomAction>[] => {
|
||||||
const bindings: KeyBinding<RoomAction>[] = [
|
const bindings: KeyBinding<RoomAction>[] = [
|
||||||
|
@ -312,7 +312,7 @@ const roomBindings = (): KeyBinding<RoomAction>[] => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
};
|
||||||
|
|
||||||
const navigationBindings = (): KeyBinding<NavigationAction>[] => {
|
const navigationBindings = (): KeyBinding<NavigationAction>[] => {
|
||||||
return [
|
return [
|
||||||
|
@ -396,7 +396,7 @@ const navigationBindings = (): KeyBinding<NavigationAction>[] => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
};
|
||||||
|
|
||||||
export const defaultBindingsProvider: IKeyBindingsProvider = {
|
export const defaultBindingsProvider: IKeyBindingsProvider = {
|
||||||
getMessageComposerBindings: messageComposerBindings,
|
getMessageComposerBindings: messageComposerBindings,
|
||||||
|
@ -404,4 +404,4 @@ export const defaultBindingsProvider: IKeyBindingsProvider = {
|
||||||
getRoomListBindings: roomListBindings,
|
getRoomListBindings: roomListBindings,
|
||||||
getRoomBindings: roomBindings,
|
getRoomBindings: roomBindings,
|
||||||
getNavigationBindings: navigationBindings,
|
getNavigationBindings: navigationBindings,
|
||||||
}
|
};
|
||||||
|
|
|
@ -140,12 +140,12 @@ export type KeyCombo = {
|
||||||
ctrlKey?: boolean;
|
ctrlKey?: boolean;
|
||||||
metaKey?: boolean;
|
metaKey?: boolean;
|
||||||
shiftKey?: boolean;
|
shiftKey?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type KeyBinding<T extends string> = {
|
export type KeyBinding<T extends string> = {
|
||||||
action: T;
|
action: T;
|
||||||
keyCombo: KeyCombo;
|
keyCombo: KeyCombo;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to check if a KeyboardEvent matches a KeyCombo
|
* Helper method to check if a KeyboardEvent matches a KeyCombo
|
||||||
|
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
||||||
import { createClient } from 'matrix-js-sdk/src/matrix';
|
import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import {decryptAES, encryptAES} from "matrix-js-sdk/src/crypto/aes";
|
import { decryptAES, encryptAES, IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes";
|
||||||
|
|
||||||
import { IMatrixClientCreds, MatrixClientPeg } from './MatrixClientPeg';
|
import { IMatrixClientCreds, MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import SecurityCustomisations from "./customisations/Security";
|
import SecurityCustomisations from "./customisations/Security";
|
||||||
|
@ -303,7 +303,7 @@ export interface IStoredSession {
|
||||||
hsUrl: string;
|
hsUrl: string;
|
||||||
isUrl: string;
|
isUrl: string;
|
||||||
hasAccessToken: boolean;
|
hasAccessToken: boolean;
|
||||||
accessToken: string | object;
|
accessToken: string | IEncryptedPayload;
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
|
|
|
@ -190,7 +190,6 @@ export default class Login {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a login request to the given server, and format the response
|
* Send a login request to the given server, and format the response
|
||||||
* as a MatrixClientCreds
|
* as a MatrixClientCreds
|
||||||
|
|
|
@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -78,7 +78,7 @@ class Presence {
|
||||||
this.setState(State.Online);
|
this.setState(State.Online);
|
||||||
this.unavailableTimer.restart();
|
this.unavailableTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the presence state.
|
* Set the presence state.
|
||||||
|
|
|
@ -104,7 +104,6 @@ export function setDMRoom(roomId: string, userId: string): Promise<void> {
|
||||||
dmRoomMap[userId] = roomList;
|
dmRoomMap[userId] = roomList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
|
return MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,7 +208,6 @@ Example:
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
membership_state AND bot_options
|
membership_state AND bot_options
|
||||||
--------------------------------
|
--------------------------------
|
||||||
Get the content of the "m.room.member" or "m.room.bot.options" state event respectively.
|
Get the content of the "m.room.member" or "m.room.bot.options" state event respectively.
|
||||||
|
|
|
@ -135,7 +135,7 @@ async function getSecretStorageKey(
|
||||||
|
|
||||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||||
if (keyFromCustomisations) {
|
if (keyFromCustomisations) {
|
||||||
console.log("Using key from security customisations (secret storage)")
|
console.log("Using key from security customisations (secret storage)");
|
||||||
cacheSecretStorageKey(keyId, keyInfo, keyFromCustomisations);
|
cacheSecretStorageKey(keyId, keyInfo, keyFromCustomisations);
|
||||||
return [keyId, keyFromCustomisations];
|
return [keyId, keyFromCustomisations];
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ export async function getDehydrationKey(
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||||
if (keyFromCustomisations) {
|
if (keyFromCustomisations) {
|
||||||
console.log("Using key from security customisations (dehydration)")
|
console.log("Using key from security customisations (dehydration)");
|
||||||
return keyFromCustomisations;
|
return keyFromCustomisations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
//@flow
|
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Aviral Dasgupta
|
Copyright 2017 Aviral Dasgupta
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { UIFeature } from "./settings/UIFeature";
|
import { UIFeature } from "./settings/UIFeature";
|
||||||
import {CHAT_EFFECTS} from "./effects"
|
import { CHAT_EFFECTS } from "./effects";
|
||||||
import CallHandler from "./CallHandler";
|
import CallHandler from "./CallHandler";
|
||||||
import { guessAndSetDMRoom } from "./Rooms";
|
import { guessAndSetDMRoom } from "./Rooms";
|
||||||
|
|
||||||
|
@ -1176,7 +1176,7 @@ export const Commands = [
|
||||||
})());
|
})());
|
||||||
},
|
},
|
||||||
category: CommandCategories.effects,
|
category: CommandCategories.effects,
|
||||||
})
|
});
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ function textForMemberEvent(ev): () => string | null {
|
||||||
targetName,
|
targetName,
|
||||||
reason,
|
reason,
|
||||||
})
|
})
|
||||||
: _t('%(senderName)s withdrew %(targetName)s\'s invitation', { senderName, targetName })
|
: _t('%(senderName)s withdrew %(targetName)s\'s invitation', { senderName, targetName });
|
||||||
} else if (prevContent.membership === "join") {
|
} else if (prevContent.membership === "join") {
|
||||||
return () => reason
|
return () => reason
|
||||||
? _t('%(senderName)s kicked %(targetName)s: %(reason)s', {
|
? _t('%(senderName)s kicked %(targetName)s: %(reason)s', {
|
||||||
|
@ -492,7 +492,7 @@ const onPinnedMessagesClick = (): void => {
|
||||||
phase: RightPanelPhases.PinnedMessages,
|
phase: RightPanelPhases.PinnedMessages,
|
||||||
allowClose: false,
|
allowClose: false,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null {
|
function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null {
|
||||||
if (!SettingsStore.getValue("feature_pinning")) return null;
|
if (!SettingsStore.getValue("feature_pinning")) return null;
|
||||||
|
|
458
src/Tinter.js
458
src/Tinter.js
|
@ -1,458 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DEBUG = 0;
|
|
||||||
|
|
||||||
// utility to turn #rrggbb or rgb(r,g,b) into [red,green,blue]
|
|
||||||
function colorToRgb(color) {
|
|
||||||
if (!color) {
|
|
||||||
return [0, 0, 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color[0] === '#') {
|
|
||||||
color = color.slice(1);
|
|
||||||
if (color.length === 3) {
|
|
||||||
color = color[0] + color[0] +
|
|
||||||
color[1] + color[1] +
|
|
||||||
color[2] + color[2];
|
|
||||||
}
|
|
||||||
const val = parseInt(color, 16);
|
|
||||||
const r = (val >> 16) & 255;
|
|
||||||
const g = (val >> 8) & 255;
|
|
||||||
const b = val & 255;
|
|
||||||
return [r, g, b];
|
|
||||||
} else {
|
|
||||||
const match = color.match(/rgb\((.*?),(.*?),(.*?)\)/);
|
|
||||||
if (match) {
|
|
||||||
return [
|
|
||||||
parseInt(match[1]),
|
|
||||||
parseInt(match[2]),
|
|
||||||
parseInt(match[3]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [0, 0, 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility to turn [red,green,blue] into #rrggbb
|
|
||||||
function rgbToColor(rgb) {
|
|
||||||
const val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
|
||||||
return '#' + (0x1000000 + val).toString(16).slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Tinter {
|
|
||||||
constructor() {
|
|
||||||
// The default colour keys to be replaced as referred to in CSS
|
|
||||||
// (should be overridden by .mx_theme_accentColor and .mx_theme_secondaryAccentColor)
|
|
||||||
this.keyRgb = [
|
|
||||||
"rgb(118, 207, 166)", // Vector Green
|
|
||||||
"rgb(234, 245, 240)", // Vector Light Green
|
|
||||||
"rgb(211, 239, 225)", // roomsublist-label-bg-color (20% Green overlaid on Light Green)
|
|
||||||
];
|
|
||||||
|
|
||||||
// Some algebra workings for calculating the tint % of Vector Green & Light Green
|
|
||||||
// x * 118 + (1 - x) * 255 = 234
|
|
||||||
// x * 118 + 255 - 255 * x = 234
|
|
||||||
// x * 118 - x * 255 = 234 - 255
|
|
||||||
// (255 - 118) x = 255 - 234
|
|
||||||
// x = (255 - 234) / (255 - 118) = 0.16
|
|
||||||
|
|
||||||
// The colour keys to be replaced as referred to in SVGs
|
|
||||||
this.keyHex = [
|
|
||||||
"#76CFA6", // Vector Green
|
|
||||||
"#EAF5F0", // Vector Light Green
|
|
||||||
"#D3EFE1", // roomsublist-label-bg-color (20% Green overlaid on Light Green)
|
|
||||||
"#FFFFFF", // white highlights of the SVGs (for switching to dark theme)
|
|
||||||
"#000000", // black lowlights of the SVGs (for switching to dark theme)
|
|
||||||
];
|
|
||||||
|
|
||||||
// track the replacement colours actually being used
|
|
||||||
// defaults to our keys.
|
|
||||||
this.colors = [
|
|
||||||
this.keyHex[0],
|
|
||||||
this.keyHex[1],
|
|
||||||
this.keyHex[2],
|
|
||||||
this.keyHex[3],
|
|
||||||
this.keyHex[4],
|
|
||||||
];
|
|
||||||
|
|
||||||
// track the most current tint request inputs (which may differ from the
|
|
||||||
// end result stored in this.colors
|
|
||||||
this.currentTint = [
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
];
|
|
||||||
|
|
||||||
this.cssFixups = [
|
|
||||||
// { theme: {
|
|
||||||
// style: a style object that should be fixed up taken from a stylesheet
|
|
||||||
// attr: name of the attribute to be clobbered, e.g. 'color'
|
|
||||||
// index: ordinal of primary, secondary or tertiary
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
];
|
|
||||||
|
|
||||||
// CSS attributes to be fixed up
|
|
||||||
this.cssAttrs = [
|
|
||||||
"color",
|
|
||||||
"backgroundColor",
|
|
||||||
"borderColor",
|
|
||||||
"borderTopColor",
|
|
||||||
"borderBottomColor",
|
|
||||||
"borderLeftColor",
|
|
||||||
];
|
|
||||||
|
|
||||||
this.svgAttrs = [
|
|
||||||
"fill",
|
|
||||||
"stroke",
|
|
||||||
];
|
|
||||||
|
|
||||||
// List of functions to call when the tint changes.
|
|
||||||
this.tintables = [];
|
|
||||||
|
|
||||||
// the currently loaded theme (if any)
|
|
||||||
this.theme = undefined;
|
|
||||||
|
|
||||||
// whether to force a tint (e.g. after changing theme)
|
|
||||||
this.forceTint = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a callback to fire when the tint changes.
|
|
||||||
* This is used to rewrite the tintable SVGs with the new tint.
|
|
||||||
*
|
|
||||||
* It's not possible to unregister a tintable callback. So this can only be
|
|
||||||
* used to register a static callback. If a set of tintables will change
|
|
||||||
* over time then the best bet is to register a single callback for the
|
|
||||||
* entire set.
|
|
||||||
*
|
|
||||||
* To ensure the tintable work happens at least once, it is also called as
|
|
||||||
* part of registration.
|
|
||||||
*
|
|
||||||
* @param {Function} tintable Function to call when the tint changes.
|
|
||||||
*/
|
|
||||||
registerTintable(tintable) {
|
|
||||||
this.tintables.push(tintable);
|
|
||||||
tintable();
|
|
||||||
}
|
|
||||||
|
|
||||||
getKeyRgb() {
|
|
||||||
return this.keyRgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
tint(primaryColor, secondaryColor, tertiaryColor) {
|
|
||||||
return;
|
|
||||||
// eslint-disable-next-line no-unreachable
|
|
||||||
this.currentTint[0] = primaryColor;
|
|
||||||
this.currentTint[1] = secondaryColor;
|
|
||||||
this.currentTint[2] = tertiaryColor;
|
|
||||||
|
|
||||||
this.calcCssFixups();
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
console.log("Tinter.tint(" + primaryColor + ", " +
|
|
||||||
secondaryColor + ", " +
|
|
||||||
tertiaryColor + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!primaryColor) {
|
|
||||||
primaryColor = this.keyRgb[0];
|
|
||||||
secondaryColor = this.keyRgb[1];
|
|
||||||
tertiaryColor = this.keyRgb[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!secondaryColor) {
|
|
||||||
const x = 0.16; // average weighting factor calculated from vector green & light green
|
|
||||||
const rgb = colorToRgb(primaryColor);
|
|
||||||
rgb[0] = x * rgb[0] + (1 - x) * 255;
|
|
||||||
rgb[1] = x * rgb[1] + (1 - x) * 255;
|
|
||||||
rgb[2] = x * rgb[2] + (1 - x) * 255;
|
|
||||||
secondaryColor = rgbToColor(rgb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tertiaryColor) {
|
|
||||||
const x = 0.19;
|
|
||||||
const rgb1 = colorToRgb(primaryColor);
|
|
||||||
const rgb2 = colorToRgb(secondaryColor);
|
|
||||||
rgb1[0] = x * rgb1[0] + (1 - x) * rgb2[0];
|
|
||||||
rgb1[1] = x * rgb1[1] + (1 - x) * rgb2[1];
|
|
||||||
rgb1[2] = x * rgb1[2] + (1 - x) * rgb2[2];
|
|
||||||
tertiaryColor = rgbToColor(rgb1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.forceTint == false &&
|
|
||||||
this.colors[0] === primaryColor &&
|
|
||||||
this.colors[1] === secondaryColor &&
|
|
||||||
this.colors[2] === tertiaryColor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.forceTint = false;
|
|
||||||
|
|
||||||
this.colors[0] = primaryColor;
|
|
||||||
this.colors[1] = secondaryColor;
|
|
||||||
this.colors[2] = tertiaryColor;
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
console.log("Tinter.tint final: (" + primaryColor + ", " +
|
|
||||||
secondaryColor + ", " +
|
|
||||||
tertiaryColor + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
// go through manually fixing up the stylesheets.
|
|
||||||
this.applyCssFixups();
|
|
||||||
|
|
||||||
// tell all the SVGs to go fix themselves up
|
|
||||||
// we don't do this as a dispatch otherwise it will visually lag
|
|
||||||
this.tintables.forEach(function(tintable) {
|
|
||||||
tintable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tintSvgWhite(whiteColor) {
|
|
||||||
this.currentTint[3] = whiteColor;
|
|
||||||
|
|
||||||
if (!whiteColor) {
|
|
||||||
whiteColor = this.colors[3];
|
|
||||||
}
|
|
||||||
if (this.colors[3] === whiteColor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.colors[3] = whiteColor;
|
|
||||||
this.tintables.forEach(function(tintable) {
|
|
||||||
tintable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tintSvgBlack(blackColor) {
|
|
||||||
this.currentTint[4] = blackColor;
|
|
||||||
|
|
||||||
if (!blackColor) {
|
|
||||||
blackColor = this.colors[4];
|
|
||||||
}
|
|
||||||
if (this.colors[4] === blackColor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.colors[4] = blackColor;
|
|
||||||
this.tintables.forEach(function(tintable) {
|
|
||||||
tintable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setTheme(theme) {
|
|
||||||
this.theme = theme;
|
|
||||||
|
|
||||||
// update keyRgb from the current theme CSS itself, if it defines it
|
|
||||||
if (document.getElementById('mx_theme_accentColor')) {
|
|
||||||
this.keyRgb[0] = window.getComputedStyle(
|
|
||||||
document.getElementById('mx_theme_accentColor')).color;
|
|
||||||
}
|
|
||||||
if (document.getElementById('mx_theme_secondaryAccentColor')) {
|
|
||||||
this.keyRgb[1] = window.getComputedStyle(
|
|
||||||
document.getElementById('mx_theme_secondaryAccentColor')).color;
|
|
||||||
}
|
|
||||||
if (document.getElementById('mx_theme_tertiaryAccentColor')) {
|
|
||||||
this.keyRgb[2] = window.getComputedStyle(
|
|
||||||
document.getElementById('mx_theme_tertiaryAccentColor')).color;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.calcCssFixups();
|
|
||||||
this.forceTint = true;
|
|
||||||
|
|
||||||
this.tint(this.currentTint[0], this.currentTint[1], this.currentTint[2]);
|
|
||||||
|
|
||||||
if (theme === 'dark') {
|
|
||||||
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
|
||||||
// XXX: obviously this shouldn't be hardcoded here.
|
|
||||||
this.tintSvgWhite('#2d2d2d');
|
|
||||||
this.tintSvgBlack('#dddddd');
|
|
||||||
} else {
|
|
||||||
this.tintSvgWhite('#ffffff');
|
|
||||||
this.tintSvgBlack('#000000');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
calcCssFixups() {
|
|
||||||
// cache our fixups
|
|
||||||
if (this.cssFixups[this.theme]) return;
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
console.debug("calcCssFixups start for " + this.theme + " (checking " +
|
|
||||||
document.styleSheets.length +
|
|
||||||
" stylesheets)");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cssFixups[this.theme] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < document.styleSheets.length; i++) {
|
|
||||||
const ss = document.styleSheets[i];
|
|
||||||
try {
|
|
||||||
if (!ss) continue; // well done safari >:(
|
|
||||||
// Chromium apparently sometimes returns null here; unsure why.
|
|
||||||
// see $14534907369972FRXBx:matrix.org in HQ
|
|
||||||
// ...ah, it's because there's a third party extension like
|
|
||||||
// privacybadger inserting its own stylesheet in there with a
|
|
||||||
// resource:// URI or something which results in a XSS error.
|
|
||||||
// See also #vector:matrix.org/$145357669685386ebCfr:matrix.org
|
|
||||||
// ...except some browsers apparently return stylesheets without
|
|
||||||
// hrefs, which we have no choice but ignore right now
|
|
||||||
|
|
||||||
// XXX seriously? we are hardcoding the name of vector's CSS file in
|
|
||||||
// here?
|
|
||||||
//
|
|
||||||
// Why do we need to limit it to vector's CSS file anyway - if there
|
|
||||||
// are other CSS files affecting the doc don't we want to apply the
|
|
||||||
// same transformations to them?
|
|
||||||
//
|
|
||||||
// Iterating through the CSS looking for matches to hack on feels
|
|
||||||
// pretty horrible anyway. And what if the application skin doesn't use
|
|
||||||
// Vector Green as its primary color?
|
|
||||||
// --richvdh
|
|
||||||
|
|
||||||
// Yes, tinting assumes that you are using the Element skin for now.
|
|
||||||
// The right solution will be to move the CSS over to react-sdk.
|
|
||||||
// And yes, the default assets for the base skin might as well use
|
|
||||||
// Vector Green as any other colour.
|
|
||||||
// --matthew
|
|
||||||
|
|
||||||
// stylesheets we don't have permission to access (eg. ones from extensions) have a null
|
|
||||||
// href and will throw exceptions if we try to access their rules.
|
|
||||||
if (!ss.href || !ss.href.match(new RegExp('/theme-' + this.theme + '.css$'))) continue;
|
|
||||||
if (ss.disabled) continue;
|
|
||||||
if (!ss.cssRules) continue;
|
|
||||||
|
|
||||||
if (DEBUG) console.debug("calcCssFixups checking " + ss.cssRules.length + " rules for " + ss.href);
|
|
||||||
|
|
||||||
for (let j = 0; j < ss.cssRules.length; j++) {
|
|
||||||
const rule = ss.cssRules[j];
|
|
||||||
if (!rule.style) continue;
|
|
||||||
if (rule.selectorText && rule.selectorText.match(/#mx_theme/)) continue;
|
|
||||||
for (let k = 0; k < this.cssAttrs.length; k++) {
|
|
||||||
const attr = this.cssAttrs[k];
|
|
||||||
for (let l = 0; l < this.keyRgb.length; l++) {
|
|
||||||
if (rule.style[attr] === this.keyRgb[l]) {
|
|
||||||
this.cssFixups[this.theme].push({
|
|
||||||
style: rule.style,
|
|
||||||
attr: attr,
|
|
||||||
index: l,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Catch any random exceptions that happen here: all sorts of things can go
|
|
||||||
// wrong with this (nulls, SecurityErrors) and mostly it's for other
|
|
||||||
// stylesheets that we don't want to proces anyway. We should not propagate an
|
|
||||||
// exception out since this will cause the app to fail to start.
|
|
||||||
console.log("Failed to calculate CSS fixups for a stylesheet: " + ss.href, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (DEBUG) {
|
|
||||||
console.log("calcCssFixups end (" +
|
|
||||||
this.cssFixups[this.theme].length +
|
|
||||||
" fixups)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyCssFixups() {
|
|
||||||
if (DEBUG) {
|
|
||||||
console.log("applyCssFixups start (" +
|
|
||||||
this.cssFixups[this.theme].length +
|
|
||||||
" fixups)");
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.cssFixups[this.theme].length; i++) {
|
|
||||||
const cssFixup = this.cssFixups[this.theme][i];
|
|
||||||
try {
|
|
||||||
cssFixup.style[cssFixup.attr] = this.colors[cssFixup.index];
|
|
||||||
} catch (e) {
|
|
||||||
// Firefox Quantum explodes if you manually edit the CSS in the
|
|
||||||
// inspector and then try to do a tint, as apparently all the
|
|
||||||
// fixups are then stale.
|
|
||||||
console.error("Failed to apply cssFixup in Tinter! ", e.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (DEBUG) console.log("applyCssFixups end");
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: we could just move this all into TintableSvg, but as it's so similar
|
|
||||||
// to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg)
|
|
||||||
// keeping it here for now.
|
|
||||||
calcSvgFixups(svgs) {
|
|
||||||
// go through manually fixing up SVG colours.
|
|
||||||
// we could do this by stylesheets, but keeping the stylesheets
|
|
||||||
// updated would be a PITA, so just brute-force search for the
|
|
||||||
// key colour; cache the element and apply.
|
|
||||||
|
|
||||||
if (DEBUG) console.log("calcSvgFixups start for " + svgs);
|
|
||||||
const fixups = [];
|
|
||||||
for (let i = 0; i < svgs.length; i++) {
|
|
||||||
let svgDoc;
|
|
||||||
try {
|
|
||||||
svgDoc = svgs[i].contentDocument;
|
|
||||||
} catch (e) {
|
|
||||||
let msg = 'Failed to get svg.contentDocument of ' + svgs[i].toString();
|
|
||||||
if (e.message) {
|
|
||||||
msg += e.message;
|
|
||||||
}
|
|
||||||
if (e.stack) {
|
|
||||||
msg += ' | stack: ' + e.stack;
|
|
||||||
}
|
|
||||||
console.error(msg);
|
|
||||||
}
|
|
||||||
if (!svgDoc) continue;
|
|
||||||
const tags = svgDoc.getElementsByTagName("*");
|
|
||||||
for (let j = 0; j < tags.length; j++) {
|
|
||||||
const tag = tags[j];
|
|
||||||
for (let k = 0; k < this.svgAttrs.length; k++) {
|
|
||||||
const attr = this.svgAttrs[k];
|
|
||||||
for (let l = 0; l < this.keyHex.length; l++) {
|
|
||||||
if (tag.getAttribute(attr) &&
|
|
||||||
tag.getAttribute(attr).toUpperCase() === this.keyHex[l]) {
|
|
||||||
fixups.push({
|
|
||||||
node: tag,
|
|
||||||
attr: attr,
|
|
||||||
index: l,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (DEBUG) console.log("calcSvgFixups end");
|
|
||||||
|
|
||||||
return fixups;
|
|
||||||
}
|
|
||||||
|
|
||||||
applySvgFixups(fixups) {
|
|
||||||
if (DEBUG) console.log("applySvgFixups start for " + fixups);
|
|
||||||
for (let i = 0; i < fixups.length; i++) {
|
|
||||||
const svgFixup = fixups[i];
|
|
||||||
svgFixup.node.setAttribute(svgFixup.attr, this.colors[svgFixup.index]);
|
|
||||||
}
|
|
||||||
if (DEBUG) console.log("applySvgFixups end");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (global.singletonTinter === undefined) {
|
|
||||||
global.singletonTinter = new Tinter();
|
|
||||||
}
|
|
||||||
export default global.singletonTinter;
|
|
|
@ -68,7 +68,6 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return matches.filter(cmd => cmd.isEnabled()).map((result) => {
|
return matches.filter(cmd => cmd.isEnabled()).map((result) => {
|
||||||
let completion = result.getCommand() + ' ';
|
let completion = result.getCommand() + ' ';
|
||||||
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);
|
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);
|
||||||
|
|
|
@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { HTMLAttributes } from "react";
|
import React, { HTMLAttributes, WheelEvent } from "react";
|
||||||
|
|
||||||
interface IProps extends HTMLAttributes<HTMLDivElement> {
|
interface IProps extends Omit<HTMLAttributes<HTMLDivElement>, "onScroll"> {
|
||||||
className?: string;
|
className?: string;
|
||||||
onScroll?: () => void;
|
onScroll?: (event: Event) => void;
|
||||||
onWheel?: () => void;
|
onWheel?: (event: WheelEvent) => void;
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
tabIndex?: number,
|
tabIndex?: number,
|
||||||
wrappedRef?: (ref: HTMLDivElement) => void;
|
wrappedRef?: (ref: HTMLDivElement) => void;
|
||||||
|
|
|
@ -16,9 +16,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Filter } from 'matrix-js-sdk/src/filter';
|
import { Filter } from 'matrix-js-sdk/src/filter';
|
||||||
|
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
|
||||||
|
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||||
|
@ -28,25 +32,33 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||||
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
|
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
roomId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
resizeNotifier: ResizeNotifier
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
timelineSet: EventTimelineSet;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the filtered file using a TimelinePanel
|
* Component which shows the filtered file using a TimelinePanel
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("structures.FilePanel")
|
@replaceableComponent("structures.FilePanel")
|
||||||
class FilePanel extends React.Component {
|
class FilePanel extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
|
||||||
roomId: PropTypes.string.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is used to track if a decrypted event was a live event and should be
|
// This is used to track if a decrypted event was a live event and should be
|
||||||
// added to the timeline.
|
// added to the timeline.
|
||||||
decryptingEvents = new Set();
|
private decryptingEvents = new Set<string>();
|
||||||
|
public noRoom: boolean;
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
timelineSet: null,
|
timelineSet: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
|
private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: true, removed: true, data: any): void => {
|
||||||
if (room?.roomId !== this.props?.roomId) return;
|
if (room?.roomId !== this.props?.roomId) return;
|
||||||
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
||||||
|
|
||||||
|
@ -60,7 +72,7 @@ class FilePanel extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onEventDecrypted = (ev, err) => {
|
private onEventDecrypted = (ev: MatrixEvent, err?: any): void => {
|
||||||
if (ev.getRoomId() !== this.props.roomId) return;
|
if (ev.getRoomId() !== this.props.roomId) return;
|
||||||
const eventId = ev.getId();
|
const eventId = ev.getId();
|
||||||
|
|
||||||
|
@ -70,7 +82,7 @@ class FilePanel extends React.Component {
|
||||||
this.addEncryptedLiveEvent(ev);
|
this.addEncryptedLiveEvent(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
addEncryptedLiveEvent(ev, toStartOfTimeline) {
|
public addEncryptedLiveEvent(ev: MatrixEvent): void {
|
||||||
if (!this.state.timelineSet) return;
|
if (!this.state.timelineSet) return;
|
||||||
|
|
||||||
const timeline = this.state.timelineSet.getLiveTimeline();
|
const timeline = this.state.timelineSet.getLiveTimeline();
|
||||||
|
@ -84,7 +96,7 @@ class FilePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
public async componentDidMount(): Promise<void> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
await this.updateTimelineSet(this.props.roomId);
|
await this.updateTimelineSet(this.props.roomId);
|
||||||
|
@ -105,7 +117,7 @@ class FilePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount(): void {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (client === null) return;
|
if (client === null) return;
|
||||||
|
|
||||||
|
@ -117,7 +129,7 @@ class FilePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchFileEventsServer(room) {
|
public async fetchFileEventsServer(room: Room): Promise<void> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
const filter = new Filter(client.credentials.userId);
|
const filter = new Filter(client.credentials.userId);
|
||||||
|
@ -141,7 +153,7 @@ class FilePanel extends React.Component {
|
||||||
return timelineSet;
|
return timelineSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPaginationRequest = (timelineWindow, direction, limit) => {
|
private onPaginationRequest = (timelineWindow: TimelineWindow, direction: string, limit: number): void => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const eventIndex = EventIndexPeg.get();
|
const eventIndex = EventIndexPeg.get();
|
||||||
const roomId = this.props.roomId;
|
const roomId = this.props.roomId;
|
||||||
|
@ -159,7 +171,7 @@ class FilePanel extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async updateTimelineSet(roomId: string) {
|
public async updateTimelineSet(roomId: string): Promise<void> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
const eventIndex = EventIndexPeg.get();
|
const eventIndex = EventIndexPeg.get();
|
||||||
|
@ -195,7 +207,7 @@ class FilePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return <BaseCard
|
return <BaseCard
|
||||||
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
|
@ -126,12 +126,11 @@ class CategoryRoomList extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
|
||||||
const addButton = this.props.editing ?
|
const addButton = this.props.editing ?
|
||||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton"
|
(<AccessibleButton className="mx_GroupView_featuredThings_addButton"
|
||||||
onClick={this.onAddRoomsToSummaryClicked}
|
onClick={this.onAddRoomsToSummaryClicked}
|
||||||
>
|
>
|
||||||
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
<img src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
||||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||||
{ _t('Add a Room') }
|
{ _t('Add a Room') }
|
||||||
</div>
|
</div>
|
||||||
|
@ -300,10 +299,9 @@ class RoleUserList extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
|
||||||
const addButton = this.props.editing ?
|
const addButton = this.props.editing ?
|
||||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
|
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
|
||||||
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
<img src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
||||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||||
{ _t('Add a User') }
|
{ _t('Add a User') }
|
||||||
</div>
|
</div>
|
||||||
|
@ -363,7 +361,10 @@ class FeaturedUser extends React.Component {
|
||||||
"Failed to remove a user from the summary of %(groupId)s",
|
"Failed to remove a user from the summary of %(groupId)s",
|
||||||
{ groupId: this.props.groupId },
|
{ groupId: this.props.groupId },
|
||||||
),
|
),
|
||||||
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
|
description: _t(
|
||||||
|
"The user '%(displayName)s' could not be removed from the summary.",
|
||||||
|
{ displayName },
|
||||||
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -855,7 +856,6 @@ export default class GroupView extends React.Component {
|
||||||
_getRoomsNode() {
|
_getRoomsNode() {
|
||||||
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
const TooltipButton = sdk.getComponent('elements.TooltipButton');
|
const TooltipButton = sdk.getComponent('elements.TooltipButton');
|
||||||
|
|
||||||
|
@ -871,7 +871,7 @@ export default class GroupView extends React.Component {
|
||||||
onClick={this._onAddRoomsClick}
|
onClick={this._onAddRoomsClick}
|
||||||
>
|
>
|
||||||
<div className="mx_GroupView_rooms_header_addRow_button">
|
<div className="mx_GroupView_rooms_header_addRow_button">
|
||||||
<TintableSvg src={require("../../../res/img/icons-room-add.svg")} width="24" height="24" />
|
<img src={require("../../../res/img/icons-room-add.svg")} width="24" height="24" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_GroupView_rooms_header_addRow_label">
|
<div className="mx_GroupView_rooms_header_addRow_label">
|
||||||
{ _t('Add rooms to this community') }
|
{ _t('Add rooms to this community') }
|
||||||
|
|
|
@ -117,7 +117,6 @@ const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return <AutoHideScrollbar className="mx_HomePage mx_HomePage_default">
|
return <AutoHideScrollbar className="mx_HomePage mx_HomePage_default">
|
||||||
<div className="mx_HomePage_default_wrapper">
|
<div className="mx_HomePage_default_wrapper">
|
||||||
{ introSection }
|
{ introSection }
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default class HostSignupAction extends React.PureComponent<IProps, IState
|
||||||
private openDialog = async () => {
|
private openDialog = async () => {
|
||||||
this.props.onClick?.();
|
this.props.onClick?.();
|
||||||
await HostSignupStore.instance.setHostSignupActive(true);
|
await HostSignupStore.instance.setHostSignupActive(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const hostSignupConfig = SdkConfig.get().hostSignup;
|
const hostSignupConfig = SdkConfig.get().hostSignup;
|
||||||
|
|
|
@ -70,7 +70,6 @@ export default class IndicatorScrollbar extends React.Component {
|
||||||
this._autoHideScrollbar = autoHideScrollbar;
|
this._autoHideScrollbar = autoHideScrollbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
|
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
|
||||||
const curLen = this.props.children && this.props.children.length || 0;
|
const curLen = this.props.children && this.props.children.length || 0;
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onDialPad = () => {
|
private onDialPad = () => {
|
||||||
dis.fire(Action.OpenDialPad);
|
dis.fire(Action.OpenDialPad);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onExplore = () => {
|
private onExplore = () => {
|
||||||
dis.fire(Action.ViewRoomDirectory);
|
dis.fire(Action.ViewRoomDirectory);
|
||||||
|
@ -136,7 +136,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
private refreshStickyHeaders = () => {
|
private refreshStickyHeaders = () => {
|
||||||
if (!this.listContainerRef.current) return; // ignore: no headers to sticky
|
if (!this.listContainerRef.current) return; // ignore: no headers to sticky
|
||||||
this.handleStickyHeaders(this.listContainerRef.current);
|
this.handleStickyHeaders(this.listContainerRef.current);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onBreadcrumbsUpdate = () => {
|
private onBreadcrumbsUpdate = () => {
|
||||||
const newVal = BreadcrumbsStore.instance.visible;
|
const newVal = BreadcrumbsStore.instance.visible;
|
||||||
|
|
|
@ -319,7 +319,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
this.setState({
|
this.setState({
|
||||||
usageLimitDismissed: true,
|
usageLimitDismissed: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
||||||
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
||||||
|
|
|
@ -34,7 +34,6 @@ import dis from "../../dispatcher/dispatcher";
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
|
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import Tinter from "../../Tinter";
|
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite';
|
import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite';
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
|
@ -283,11 +282,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
this.pageChanging = false;
|
this.pageChanging = false;
|
||||||
|
|
||||||
// check we have the right tint applied for this theme.
|
|
||||||
// N.B. we don't call the whole of setTheme() here as we may be
|
|
||||||
// racing with the theme CSS download finishing from index.js
|
|
||||||
Tinter.tint();
|
|
||||||
|
|
||||||
// For PersistentElement
|
// For PersistentElement
|
||||||
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);
|
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);
|
||||||
|
|
||||||
|
@ -668,7 +662,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.createRoom(payload.public, payload.defaultName);
|
this.createRoom(payload.public, payload.defaultName);
|
||||||
break;
|
break;
|
||||||
case 'view_create_group': {
|
case 'view_create_group': {
|
||||||
let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog")
|
let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
||||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
||||||
CreateGroupDialog = CreateCommunityPrototypeDialog;
|
CreateGroupDialog = CreateCommunityPrototypeDialog;
|
||||||
}
|
}
|
||||||
|
@ -1131,8 +1125,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
description: (
|
description: (
|
||||||
<span>
|
<span>
|
||||||
{ isSpace
|
{ isSpace
|
||||||
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {spaceName: roomToLeave.name})
|
? _t(
|
||||||
: _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
"Are you sure you want to leave the space '%(spaceName)s'?",
|
||||||
|
{ spaceName: roomToLeave.name },
|
||||||
|
)
|
||||||
|
: _t(
|
||||||
|
"Are you sure you want to leave the room '%(roomName)s'?",
|
||||||
|
{ roomName: roomToLeave.name },
|
||||||
|
)}
|
||||||
{ warnings }
|
{ warnings }
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
@ -1263,7 +1263,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// HACK: This is a pretty brutal way of threading the invite back through
|
// HACK: This is a pretty brutal way of threading the invite back through
|
||||||
// our systems, but it's the safest we have for now.
|
// our systems, but it's the safest we have for now.
|
||||||
const params = ThreepidInviteStore.instance.translateToWireFormat(threepidInvite);
|
const params = ThreepidInviteStore.instance.translateToWireFormat(threepidInvite);
|
||||||
this.showScreen(`room/${threepidInvite.roomId}`, params)
|
this.showScreen(`room/${threepidInvite.roomId}`, params);
|
||||||
} else {
|
} else {
|
||||||
// The user has just logged in after registering,
|
// The user has just logged in after registering,
|
||||||
// so show the homepage.
|
// so show the homepage.
|
||||||
|
@ -1573,10 +1573,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Fire the tinter right on startup to ensure the default theme is applied
|
|
||||||
// A later sync can/will correct the tint to be the right value for the user
|
|
||||||
const colorScheme = SettingsStore.getValue("roomColor");
|
|
||||||
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -123,7 +123,7 @@ export default class MyGroups extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
{/*<div className="mx_MyGroups_joinBox mx_MyGroups_headerCard">
|
{/*<div className="mx_MyGroups_joinBox mx_MyGroups_headerCard">
|
||||||
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onJoinGroupClick}>
|
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onJoinGroupClick}>
|
||||||
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="50" height="50" />
|
<img src={require("../../../res/img/icons-create-room.svg")} width="50" height="50" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<div className="mx_MyGroups_headerCard_content">
|
<div className="mx_MyGroups_headerCard_content">
|
||||||
<div className="mx_MyGroups_headerCard_header">
|
<div className="mx_MyGroups_headerCard_header">
|
||||||
|
|
|
@ -22,6 +22,7 @@ import BaseCard from "../views/right_panel/BaseCard";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import TimelinePanel from "./TimelinePanel";
|
import TimelinePanel from "./TimelinePanel";
|
||||||
import Spinner from "../views/elements/Spinner";
|
import Spinner from "../views/elements/Spinner";
|
||||||
|
import { TileShape } from "../views/rooms/EventTile";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
|
@ -48,7 +49,7 @@ export default class NotificationPanel extends React.PureComponent<IProps> {
|
||||||
manageReadMarkers={false}
|
manageReadMarkers={false}
|
||||||
timelineSet={timelineSet}
|
timelineSet={timelineSet}
|
||||||
showUrlPreview={false}
|
showUrlPreview={false}
|
||||||
tileShape="notif"
|
tileShape={TileShape.Notif}
|
||||||
empty={emptyState}
|
empty={emptyState}
|
||||||
alwaysShowTimestamps={true}
|
alwaysShowTimestamps={true}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -45,7 +45,6 @@ import ScrollPanel from "./ScrollPanel";
|
||||||
import Spinner from "../views/elements/Spinner";
|
import Spinner from "../views/elements/Spinner";
|
||||||
import { ActionPayload } from "../../dispatcher/payloads";
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
|
|
||||||
|
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 800;
|
const MAX_TOPIC_LENGTH = 800;
|
||||||
|
|
||||||
|
@ -95,7 +94,7 @@ interface IPublicRoomsRequest {
|
||||||
@replaceableComponent("structures.RoomDirectory")
|
@replaceableComponent("structures.RoomDirectory")
|
||||||
export default class RoomDirectory extends React.Component<IProps, IState> {
|
export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
private readonly startTime: number;
|
private readonly startTime: number;
|
||||||
private unmounted = false
|
private unmounted = false;
|
||||||
private nextBatch: string = null;
|
private nextBatch: string = null;
|
||||||
private filterTimeout: NodeJS.Timeout;
|
private filterTimeout: NodeJS.Timeout;
|
||||||
private protocols: Protocols;
|
private protocols: Protocols;
|
||||||
|
@ -207,9 +206,9 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
this.getMoreRooms();
|
this.getMoreRooms();
|
||||||
};
|
};
|
||||||
|
|
||||||
private getMoreRooms() {
|
private getMoreRooms(): Promise<boolean> {
|
||||||
if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms
|
if (this.state.selectedCommunityId) return Promise.resolve(false); // no more rooms
|
||||||
if (!MatrixClientPeg.get()) return Promise.resolve();
|
if (!MatrixClientPeg.get()) return Promise.resolve(false);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -239,12 +238,12 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
// if the filter or server has changed since this request was sent,
|
// if the filter or server has changed since this request was sent,
|
||||||
// throw away the result (don't even clear the busy flag
|
// throw away the result (don't even clear the busy flag
|
||||||
// since we must still have a request in flight)
|
// since we must still have a request in flight)
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
// if we've been unmounted, we don't care either.
|
// if we've been unmounted, we don't care either.
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.filterString) {
|
if (this.state.filterString) {
|
||||||
|
@ -264,14 +263,13 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
filterString != this.state.filterString ||
|
filterString != this.state.filterString ||
|
||||||
roomServer != this.state.roomServer ||
|
roomServer != this.state.roomServer ||
|
||||||
nextBatch != this.nextBatch) {
|
nextBatch != this.nextBatch) {
|
||||||
// as above: we don't care about errors for old
|
// as above: we don't care about errors for old requests either
|
||||||
// requests either
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
// if we've been unmounted, we don't care either.
|
// if we've been unmounted, we don't care either.
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||||
|
|
|
@ -37,7 +37,6 @@ import Modal from '../../Modal';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import Tinter from '../../Tinter';
|
|
||||||
import rateLimitedFunc from '../../ratelimitedfunc';
|
import rateLimitedFunc from '../../ratelimitedfunc';
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
import eventSearch, { searchPagination } from '../../Searching';
|
import eventSearch, { searchPagination } from '../../Searching';
|
||||||
|
@ -287,7 +286,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
if (this.state.room) {
|
if (this.state.room) {
|
||||||
this.checkWidgets(this.state.room);
|
this.checkWidgets(this.state.room);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private checkWidgets = (room) => {
|
private checkWidgets = (room) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -679,10 +678,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
// cancel any pending calls to the rate_limited_funcs
|
// cancel any pending calls to the rate_limited_funcs
|
||||||
this.updateRoomMembers.cancelPendingCall();
|
this.updateRoomMembers.cancelPendingCall();
|
||||||
|
|
||||||
// no need to do this as Dir & Settings are now overlays. It just burnt CPU.
|
|
||||||
// console.log("Tinter.tint from RoomView.unmount");
|
|
||||||
// Tinter.tint(); // reset colourscheme
|
|
||||||
|
|
||||||
for (const watcher of this.settingWatchers) {
|
for (const watcher of this.settingWatchers) {
|
||||||
SettingsStore.unwatchSetting(watcher);
|
SettingsStore.unwatchSetting(watcher);
|
||||||
}
|
}
|
||||||
|
@ -698,7 +693,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
replyingToEvent: this.state.replyToEvent,
|
replyingToEvent: this.state.replyToEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private onRightPanelStoreUpdate = () => {
|
private onRightPanelStoreUpdate = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1062,10 +1057,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
private updateTint() {
|
private updateTint() {
|
||||||
const room = this.state.room;
|
const room = this.state.room;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
console.log("Tinter.tint from updateTint");
|
|
||||||
const colorScheme = SettingsStore.getValue("roomColor", room.roomId);
|
|
||||||
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAccountData = (event: MatrixEvent) => {
|
private onAccountData = (event: MatrixEvent) => {
|
||||||
|
@ -1079,12 +1070,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
private onRoomAccountData = (event: MatrixEvent, room: Room) => {
|
private onRoomAccountData = (event: MatrixEvent, room: Room) => {
|
||||||
if (room.roomId == this.state.roomId) {
|
if (room.roomId == this.state.roomId) {
|
||||||
const type = event.getType();
|
const type = event.getType();
|
||||||
if (type === "org.matrix.room.color_scheme") {
|
if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
|
||||||
const colorScheme = event.getContent();
|
|
||||||
// XXX: we should validate the event
|
|
||||||
console.log("Tinter.tint from onRoomAccountData");
|
|
||||||
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
|
||||||
} else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
|
|
||||||
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
|
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
|
||||||
this.updatePreviewUrlVisibility(room);
|
this.updatePreviewUrlVisibility(room);
|
||||||
}
|
}
|
||||||
|
@ -1157,7 +1143,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSearchResultsFillRequest = (backwards: boolean) => {
|
private onSearchResultsFillRequest = (backwards: boolean): Promise<boolean> => {
|
||||||
if (!backwards) {
|
if (!backwards) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
@ -1323,7 +1309,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
this.handleSearchResult(searchPromise);
|
this.handleSearchResult(searchPromise);
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleSearchResult(searchPromise: Promise<any>) {
|
private handleSearchResult(searchPromise: Promise<any>): Promise<boolean> {
|
||||||
// keep a record of the current search id, so that if the search terms
|
// keep a record of the current search id, so that if the search terms
|
||||||
// change before we get a response, we can ignore the results.
|
// change before we get a response, we can ignore the results.
|
||||||
const localSearchId = this.searchId;
|
const localSearchId = this.searchId;
|
||||||
|
@ -1336,7 +1322,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
debuglog("search complete");
|
debuglog("search complete");
|
||||||
if (this.unmounted || !this.state.searching || this.searchId != localSearchId) {
|
if (this.unmounted || !this.state.searching || this.searchId != localSearchId) {
|
||||||
console.error("Discarding stale search results");
|
console.error("Discarding stale search results");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// postgres on synapse returns us precise details of the strings
|
// postgres on synapse returns us precise details of the strings
|
||||||
|
@ -1368,6 +1354,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
description: ((error && error.message) ? error.message :
|
description: ((error && error.message) ? error.message :
|
||||||
_t("Server may be unavailable, overloaded, or search timed out :(")),
|
_t("Server may be unavailable, overloaded, or search timed out :(")),
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
searchInProgress: false,
|
searchInProgress: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {createRef} from "react";
|
import React, { createRef, CSSProperties, ReactNode, SyntheticEvent, KeyboardEvent } from "react";
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import { getKeyBindingsManager, RoomAction } from "../../KeyBindingsManager";
|
import { getKeyBindingsManager, RoomAction } from "../../KeyBindingsManager";
|
||||||
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
|
|
||||||
const DEBUG_SCROLL = false;
|
const DEBUG_SCROLL = false;
|
||||||
|
|
||||||
// The amount of extra scroll distance to allow prior to unfilling.
|
// The amount of extra scroll distance to allow prior to unfilling.
|
||||||
// See _getExcessHeight.
|
// See getExcessHeight.
|
||||||
const UNPAGINATION_PADDING = 6000;
|
const UNPAGINATION_PADDING = 6000;
|
||||||
// The number of milliseconds to debounce calls to onUnfillRequest, to prevent
|
// The number of milliseconds to debounce calls to onUnfillRequest, to prevent
|
||||||
// many scroll events causing many unfilling requests.
|
// many scroll events causing many unfilling requests.
|
||||||
|
@ -43,6 +43,75 @@ if (DEBUG_SCROLL) {
|
||||||
debuglog = function() {};
|
debuglog = function() {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
/* stickyBottom: if set to true, then once the user hits the bottom of
|
||||||
|
* the list, any new children added to the list will cause the list to
|
||||||
|
* scroll down to show the new element, rather than preserving the
|
||||||
|
* existing view.
|
||||||
|
*/
|
||||||
|
stickyBottom?: boolean;
|
||||||
|
|
||||||
|
/* startAtBottom: if set to true, the view is assumed to start
|
||||||
|
* scrolled to the bottom.
|
||||||
|
* XXX: It's likely this is unnecessary and can be derived from
|
||||||
|
* stickyBottom, but I'm adding an extra parameter to ensure
|
||||||
|
* behaviour stays the same for other uses of ScrollPanel.
|
||||||
|
* If so, let's remove this parameter down the line.
|
||||||
|
*/
|
||||||
|
startAtBottom?: boolean;
|
||||||
|
|
||||||
|
/* className: classnames to add to the top-level div
|
||||||
|
*/
|
||||||
|
className?: string;
|
||||||
|
|
||||||
|
/* style: styles to add to the top-level div
|
||||||
|
*/
|
||||||
|
style?: CSSProperties;
|
||||||
|
|
||||||
|
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
||||||
|
*/
|
||||||
|
resizeNotifier?: ResizeNotifier;
|
||||||
|
|
||||||
|
/* fixedChildren: allows for children to be passed which are rendered outside
|
||||||
|
* of the wrapper
|
||||||
|
*/
|
||||||
|
fixedChildren?: ReactNode;
|
||||||
|
|
||||||
|
/* onFillRequest(backwards): a callback which is called on scroll when
|
||||||
|
* the user nears the start (backwards = true) or end (backwards =
|
||||||
|
* false) of the list.
|
||||||
|
*
|
||||||
|
* This should return a promise; no more calls will be made until the
|
||||||
|
* promise completes.
|
||||||
|
*
|
||||||
|
* The promise should resolve to true if there is more data to be
|
||||||
|
* retrieved in this direction (in which case onFillRequest may be
|
||||||
|
* called again immediately), or false if there is no more data in this
|
||||||
|
* directon (at this time) - which will stop the pagination cycle until
|
||||||
|
* the user scrolls again.
|
||||||
|
*/
|
||||||
|
onFillRequest?(backwards: boolean): Promise<boolean>;
|
||||||
|
|
||||||
|
/* onUnfillRequest(backwards): a callback which is called on scroll when
|
||||||
|
* there are children elements that are far out of view and could be removed
|
||||||
|
* without causing pagination to occur.
|
||||||
|
*
|
||||||
|
* This function should accept a boolean, which is true to indicate the back/top
|
||||||
|
* of the panel and false otherwise, and a scroll token, which refers to the
|
||||||
|
* first element to remove if removing from the front/bottom, and last element
|
||||||
|
* to remove if removing from the back/top.
|
||||||
|
*/
|
||||||
|
onUnfillRequest?(backwards: boolean, scrollToken: string): void;
|
||||||
|
|
||||||
|
/* onScroll: a callback which is called whenever any scroll happens.
|
||||||
|
*/
|
||||||
|
onScroll?(event: Event): void;
|
||||||
|
|
||||||
|
/* onUserScroll: callback which is called when the user interacts with the room timeline
|
||||||
|
*/
|
||||||
|
onUserScroll?(event: SyntheticEvent): void;
|
||||||
|
}
|
||||||
|
|
||||||
/* This component implements an intelligent scrolling list.
|
/* This component implements an intelligent scrolling list.
|
||||||
*
|
*
|
||||||
* It wraps a list of <li> children; when items are added to the start or end
|
* It wraps a list of <li> children; when items are added to the start or end
|
||||||
|
@ -84,97 +153,54 @@ if (DEBUG_SCROLL) {
|
||||||
* offset as normal.
|
* offset as normal.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export interface IScrollState {
|
||||||
|
stuckAtBottom: boolean;
|
||||||
|
trackedNode?: HTMLElement;
|
||||||
|
trackedScrollToken?: string;
|
||||||
|
bottomOffset?: number;
|
||||||
|
pixelOffset?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPreventShrinkingState {
|
||||||
|
offsetFromBottom: number;
|
||||||
|
offsetNode: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.ScrollPanel")
|
@replaceableComponent("structures.ScrollPanel")
|
||||||
export default class ScrollPanel extends React.Component {
|
export default class ScrollPanel extends React.Component<IProps> {
|
||||||
static propTypes = {
|
|
||||||
/* stickyBottom: if set to true, then once the user hits the bottom of
|
|
||||||
* the list, any new children added to the list will cause the list to
|
|
||||||
* scroll down to show the new element, rather than preserving the
|
|
||||||
* existing view.
|
|
||||||
*/
|
|
||||||
stickyBottom: PropTypes.bool,
|
|
||||||
|
|
||||||
/* startAtBottom: if set to true, the view is assumed to start
|
|
||||||
* scrolled to the bottom.
|
|
||||||
* XXX: It's likely this is unnecessary and can be derived from
|
|
||||||
* stickyBottom, but I'm adding an extra parameter to ensure
|
|
||||||
* behaviour stays the same for other uses of ScrollPanel.
|
|
||||||
* If so, let's remove this parameter down the line.
|
|
||||||
*/
|
|
||||||
startAtBottom: PropTypes.bool,
|
|
||||||
|
|
||||||
/* onFillRequest(backwards): a callback which is called on scroll when
|
|
||||||
* the user nears the start (backwards = true) or end (backwards =
|
|
||||||
* false) of the list.
|
|
||||||
*
|
|
||||||
* This should return a promise; no more calls will be made until the
|
|
||||||
* promise completes.
|
|
||||||
*
|
|
||||||
* The promise should resolve to true if there is more data to be
|
|
||||||
* retrieved in this direction (in which case onFillRequest may be
|
|
||||||
* called again immediately), or false if there is no more data in this
|
|
||||||
* directon (at this time) - which will stop the pagination cycle until
|
|
||||||
* the user scrolls again.
|
|
||||||
*/
|
|
||||||
onFillRequest: PropTypes.func,
|
|
||||||
|
|
||||||
/* onUnfillRequest(backwards): a callback which is called on scroll when
|
|
||||||
* there are children elements that are far out of view and could be removed
|
|
||||||
* without causing pagination to occur.
|
|
||||||
*
|
|
||||||
* This function should accept a boolean, which is true to indicate the back/top
|
|
||||||
* of the panel and false otherwise, and a scroll token, which refers to the
|
|
||||||
* first element to remove if removing from the front/bottom, and last element
|
|
||||||
* to remove if removing from the back/top.
|
|
||||||
*/
|
|
||||||
onUnfillRequest: PropTypes.func,
|
|
||||||
|
|
||||||
/* onScroll: a callback which is called whenever any scroll happens.
|
|
||||||
*/
|
|
||||||
onScroll: PropTypes.func,
|
|
||||||
|
|
||||||
/* onUserScroll: callback which is called when the user interacts with the room timeline
|
|
||||||
*/
|
|
||||||
onUserScroll: PropTypes.func,
|
|
||||||
|
|
||||||
/* className: classnames to add to the top-level div
|
|
||||||
*/
|
|
||||||
className: PropTypes.string,
|
|
||||||
|
|
||||||
/* style: styles to add to the top-level div
|
|
||||||
*/
|
|
||||||
style: PropTypes.object,
|
|
||||||
|
|
||||||
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
|
||||||
*/
|
|
||||||
resizeNotifier: PropTypes.object,
|
|
||||||
|
|
||||||
/* fixedChildren: allows for children to be passed which are rendered outside
|
|
||||||
* of the wrapper
|
|
||||||
*/
|
|
||||||
fixedChildren: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
stickyBottom: true,
|
stickyBottom: true,
|
||||||
startAtBottom: true,
|
startAtBottom: true,
|
||||||
onFillRequest: function(backwards) { return Promise.resolve(false); },
|
onFillRequest: function(backwards: boolean) { return Promise.resolve(false); },
|
||||||
onUnfillRequest: function(backwards, scrollToken) {},
|
onUnfillRequest: function(backwards: boolean, scrollToken: string) {},
|
||||||
onScroll: function() {},
|
onScroll: function() {},
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
private readonly pendingFillRequests: Record<"b" | "f", boolean> = {
|
||||||
super(props);
|
b: null,
|
||||||
|
f: null,
|
||||||
|
};
|
||||||
|
private readonly itemlist = createRef<HTMLOListElement>();
|
||||||
|
private unmounted = false;
|
||||||
|
private scrollTimeout: Timer;
|
||||||
|
private isFilling: boolean;
|
||||||
|
private fillRequestWhileRunning: boolean;
|
||||||
|
private scrollState: IScrollState;
|
||||||
|
private preventShrinkingState: IPreventShrinkingState;
|
||||||
|
private unfillDebouncer: NodeJS.Timeout;
|
||||||
|
private bottomGrowth: number;
|
||||||
|
private pages: number;
|
||||||
|
private heightUpdateInProgress: boolean;
|
||||||
|
private divScroll: HTMLDivElement;
|
||||||
|
|
||||||
this._pendingFillRequests = {b: null, f: null};
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
if (this.props.resizeNotifier) {
|
if (this.props.resizeNotifier) {
|
||||||
this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
|
this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetScrollState();
|
this.resetScrollState();
|
||||||
|
|
||||||
this._itemlist = createRef();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -203,18 +229,18 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onScroll = ev => {
|
private onScroll = ev => {
|
||||||
// skip scroll events caused by resizing
|
// skip scroll events caused by resizing
|
||||||
if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return;
|
if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return;
|
||||||
debuglog("onScroll", this._getScrollNode().scrollTop);
|
debuglog("onScroll", this.getScrollNode().scrollTop);
|
||||||
this._scrollTimeout.restart();
|
this.scrollTimeout.restart();
|
||||||
this._saveScrollState();
|
this.saveScrollState();
|
||||||
this.updatePreventShrinking();
|
this.updatePreventShrinking();
|
||||||
this.props.onScroll(ev);
|
this.props.onScroll(ev);
|
||||||
this.checkFillState();
|
this.checkFillState();
|
||||||
};
|
};
|
||||||
|
|
||||||
onResize = () => {
|
private onResize = () => {
|
||||||
debuglog("onResize");
|
debuglog("onResize");
|
||||||
this.checkScroll();
|
this.checkScroll();
|
||||||
// update preventShrinkingState if present
|
// update preventShrinkingState if present
|
||||||
|
@ -225,11 +251,11 @@ export default class ScrollPanel extends React.Component {
|
||||||
|
|
||||||
// after an update to the contents of the panel, check that the scroll is
|
// after an update to the contents of the panel, check that the scroll is
|
||||||
// where it ought to be, and set off pagination requests if necessary.
|
// where it ought to be, and set off pagination requests if necessary.
|
||||||
checkScroll = () => {
|
public checkScroll = () => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._restoreSavedScrollState();
|
this.restoreSavedScrollState();
|
||||||
this.checkFillState();
|
this.checkFillState();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -238,8 +264,8 @@ export default class ScrollPanel extends React.Component {
|
||||||
// note that this is independent of the 'stuckAtBottom' state - it is simply
|
// note that this is independent of the 'stuckAtBottom' state - it is simply
|
||||||
// about whether the content is scrolled down right now, irrespective of
|
// about whether the content is scrolled down right now, irrespective of
|
||||||
// whether it will stay that way when the children update.
|
// whether it will stay that way when the children update.
|
||||||
isAtBottom = () => {
|
public isAtBottom = () => {
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
// fractional values (both too big and too small)
|
// fractional values (both too big and too small)
|
||||||
// for scrollTop happen on certain browsers/platforms
|
// for scrollTop happen on certain browsers/platforms
|
||||||
// when scrolled all the way down. E.g. Chrome 72 on debian.
|
// when scrolled all the way down. E.g. Chrome 72 on debian.
|
||||||
|
@ -278,10 +304,10 @@ export default class ScrollPanel extends React.Component {
|
||||||
// |#########| - |
|
// |#########| - |
|
||||||
// |#########| |
|
// |#########| |
|
||||||
// `---------' -
|
// `---------' -
|
||||||
_getExcessHeight(backwards) {
|
private getExcessHeight(backwards: boolean): number {
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
const contentHeight = this._getMessagesHeight();
|
const contentHeight = this.getMessagesHeight();
|
||||||
const listHeight = this._getListHeight();
|
const listHeight = this.getListHeight();
|
||||||
const clippedHeight = contentHeight - listHeight;
|
const clippedHeight = contentHeight - listHeight;
|
||||||
const unclippedScrollTop = sn.scrollTop + clippedHeight;
|
const unclippedScrollTop = sn.scrollTop + clippedHeight;
|
||||||
|
|
||||||
|
@ -293,13 +319,13 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the scroll state and send out backfill requests if necessary.
|
// check the scroll state and send out backfill requests if necessary.
|
||||||
checkFillState = async (depth=0) => {
|
public checkFillState = async (depth = 0): Promise<void> => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFirstCall = depth === 0;
|
const isFirstCall = depth === 0;
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
|
|
||||||
// if there is less than a screenful of messages above or below the
|
// if there is less than a screenful of messages above or below the
|
||||||
// viewport, try to get some more messages.
|
// viewport, try to get some more messages.
|
||||||
|
@ -330,17 +356,17 @@ export default class ScrollPanel extends React.Component {
|
||||||
// do make a note when a new request comes in while already running one,
|
// do make a note when a new request comes in while already running one,
|
||||||
// so we can trigger a new chain of calls once done.
|
// so we can trigger a new chain of calls once done.
|
||||||
if (isFirstCall) {
|
if (isFirstCall) {
|
||||||
if (this._isFilling) {
|
if (this.isFilling) {
|
||||||
debuglog("_isFilling: not entering while request is ongoing, marking for a subsequent request");
|
debuglog("isFilling: not entering while request is ongoing, marking for a subsequent request");
|
||||||
this._fillRequestWhileRunning = true;
|
this.fillRequestWhileRunning = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debuglog("_isFilling: setting");
|
debuglog("isFilling: setting");
|
||||||
this._isFilling = true;
|
this.isFilling = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemlist = this._itemlist.current;
|
const itemlist = this.itemlist.current;
|
||||||
const firstTile = itemlist && itemlist.firstElementChild;
|
const firstTile = itemlist && itemlist.firstElementChild as HTMLElement;
|
||||||
const contentTop = firstTile && firstTile.offsetTop;
|
const contentTop = firstTile && firstTile.offsetTop;
|
||||||
const fillPromises = [];
|
const fillPromises = [];
|
||||||
|
|
||||||
|
@ -348,13 +374,13 @@ export default class ScrollPanel extends React.Component {
|
||||||
// try backward filling
|
// try backward filling
|
||||||
if (!firstTile || (sn.scrollTop - contentTop) < sn.clientHeight) {
|
if (!firstTile || (sn.scrollTop - contentTop) < sn.clientHeight) {
|
||||||
// need to back-fill
|
// need to back-fill
|
||||||
fillPromises.push(this._maybeFill(depth, true));
|
fillPromises.push(this.maybeFill(depth, true));
|
||||||
}
|
}
|
||||||
// if scrollTop gets to 2 screens from the end (so 1 screen below viewport),
|
// if scrollTop gets to 2 screens from the end (so 1 screen below viewport),
|
||||||
// try forward filling
|
// try forward filling
|
||||||
if ((sn.scrollHeight - sn.scrollTop) < sn.clientHeight * 2) {
|
if ((sn.scrollHeight - sn.scrollTop) < sn.clientHeight * 2) {
|
||||||
// need to forward-fill
|
// need to forward-fill
|
||||||
fillPromises.push(this._maybeFill(depth, false));
|
fillPromises.push(this.maybeFill(depth, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fillPromises.length) {
|
if (fillPromises.length) {
|
||||||
|
@ -365,26 +391,26 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isFirstCall) {
|
if (isFirstCall) {
|
||||||
debuglog("_isFilling: clearing");
|
debuglog("isFilling: clearing");
|
||||||
this._isFilling = false;
|
this.isFilling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._fillRequestWhileRunning) {
|
if (this.fillRequestWhileRunning) {
|
||||||
this._fillRequestWhileRunning = false;
|
this.fillRequestWhileRunning = false;
|
||||||
this.checkFillState();
|
this.checkFillState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if unfilling is possible and send an unfill request if necessary
|
// check if unfilling is possible and send an unfill request if necessary
|
||||||
_checkUnfillState(backwards) {
|
private checkUnfillState(backwards: boolean): void {
|
||||||
let excessHeight = this._getExcessHeight(backwards);
|
let excessHeight = this.getExcessHeight(backwards);
|
||||||
if (excessHeight <= 0) {
|
if (excessHeight <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const origExcessHeight = excessHeight;
|
const origExcessHeight = excessHeight;
|
||||||
|
|
||||||
const tiles = this._itemlist.current.children;
|
const tiles = this.itemlist.current.children;
|
||||||
|
|
||||||
// The scroll token of the first/last tile to be unpaginated
|
// The scroll token of the first/last tile to be unpaginated
|
||||||
let markerScrollToken = null;
|
let markerScrollToken = null;
|
||||||
|
@ -413,11 +439,11 @@ export default class ScrollPanel extends React.Component {
|
||||||
if (markerScrollToken) {
|
if (markerScrollToken) {
|
||||||
// Use a debouncer to prevent multiple unfill calls in quick succession
|
// Use a debouncer to prevent multiple unfill calls in quick succession
|
||||||
// This is to make the unfilling process less aggressive
|
// This is to make the unfilling process less aggressive
|
||||||
if (this._unfillDebouncer) {
|
if (this.unfillDebouncer) {
|
||||||
clearTimeout(this._unfillDebouncer);
|
clearTimeout(this.unfillDebouncer);
|
||||||
}
|
}
|
||||||
this._unfillDebouncer = setTimeout(() => {
|
this.unfillDebouncer = setTimeout(() => {
|
||||||
this._unfillDebouncer = null;
|
this.unfillDebouncer = null;
|
||||||
debuglog("unfilling now", backwards, origExcessHeight);
|
debuglog("unfilling now", backwards, origExcessHeight);
|
||||||
this.props.onUnfillRequest(backwards, markerScrollToken);
|
this.props.onUnfillRequest(backwards, markerScrollToken);
|
||||||
}, UNFILL_REQUEST_DEBOUNCE_MS);
|
}, UNFILL_REQUEST_DEBOUNCE_MS);
|
||||||
|
@ -425,9 +451,9 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there is already a pending fill request. If not, set one off.
|
// check if there is already a pending fill request. If not, set one off.
|
||||||
_maybeFill(depth, backwards) {
|
private maybeFill(depth: number, backwards: boolean): Promise<void> {
|
||||||
const dir = backwards ? 'b' : 'f';
|
const dir = backwards ? 'b' : 'f';
|
||||||
if (this._pendingFillRequests[dir]) {
|
if (this.pendingFillRequests[dir]) {
|
||||||
debuglog("Already a "+dir+" fill in progress - not starting another");
|
debuglog("Already a "+dir+" fill in progress - not starting another");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -436,7 +462,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
|
|
||||||
// onFillRequest can end up calling us recursively (via onScroll
|
// onFillRequest can end up calling us recursively (via onScroll
|
||||||
// events) so make sure we set this before firing off the call.
|
// events) so make sure we set this before firing off the call.
|
||||||
this._pendingFillRequests[dir] = true;
|
this.pendingFillRequests[dir] = true;
|
||||||
|
|
||||||
// wait 1ms before paginating, because otherwise
|
// wait 1ms before paginating, because otherwise
|
||||||
// this will block the scroll event handler for +700ms
|
// this will block the scroll event handler for +700ms
|
||||||
|
@ -445,13 +471,13 @@ export default class ScrollPanel extends React.Component {
|
||||||
return new Promise(resolve => setTimeout(resolve, 1)).then(() => {
|
return new Promise(resolve => setTimeout(resolve, 1)).then(() => {
|
||||||
return this.props.onFillRequest(backwards);
|
return this.props.onFillRequest(backwards);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this._pendingFillRequests[dir] = false;
|
this.pendingFillRequests[dir] = false;
|
||||||
}).then((hasMoreResults) => {
|
}).then((hasMoreResults) => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Unpaginate once filling is complete
|
// Unpaginate once filling is complete
|
||||||
this._checkUnfillState(!backwards);
|
this.checkUnfillState(!backwards);
|
||||||
|
|
||||||
debuglog(""+dir+" fill complete; hasMoreResults:"+hasMoreResults);
|
debuglog(""+dir+" fill complete; hasMoreResults:"+hasMoreResults);
|
||||||
if (hasMoreResults) {
|
if (hasMoreResults) {
|
||||||
|
@ -477,7 +503,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
* the number of pixels the bottom of the tracked child is above the
|
* the number of pixels the bottom of the tracked child is above the
|
||||||
* bottom of the scroll panel.
|
* bottom of the scroll panel.
|
||||||
*/
|
*/
|
||||||
getScrollState = () => this.scrollState;
|
public getScrollState = (): IScrollState => this.scrollState;
|
||||||
|
|
||||||
/* reset the saved scroll state.
|
/* reset the saved scroll state.
|
||||||
*
|
*
|
||||||
|
@ -491,35 +517,35 @@ export default class ScrollPanel extends React.Component {
|
||||||
* no use if no children exist yet, or if you are about to replace the
|
* no use if no children exist yet, or if you are about to replace the
|
||||||
* child list.)
|
* child list.)
|
||||||
*/
|
*/
|
||||||
resetScrollState = () => {
|
public resetScrollState = (): void => {
|
||||||
this.scrollState = {
|
this.scrollState = {
|
||||||
stuckAtBottom: this.props.startAtBottom,
|
stuckAtBottom: this.props.startAtBottom,
|
||||||
};
|
};
|
||||||
this._bottomGrowth = 0;
|
this.bottomGrowth = 0;
|
||||||
this._pages = 0;
|
this.pages = 0;
|
||||||
this._scrollTimeout = new Timer(100);
|
this.scrollTimeout = new Timer(100);
|
||||||
this._heightUpdateInProgress = false;
|
this.heightUpdateInProgress = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* jump to the top of the content.
|
* jump to the top of the content.
|
||||||
*/
|
*/
|
||||||
scrollToTop = () => {
|
public scrollToTop = (): void => {
|
||||||
this._getScrollNode().scrollTop = 0;
|
this.getScrollNode().scrollTop = 0;
|
||||||
this._saveScrollState();
|
this.saveScrollState();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* jump to the bottom of the content.
|
* jump to the bottom of the content.
|
||||||
*/
|
*/
|
||||||
scrollToBottom = () => {
|
public scrollToBottom = (): void => {
|
||||||
// the easiest way to make sure that the scroll state is correctly
|
// the easiest way to make sure that the scroll state is correctly
|
||||||
// saved is to do the scroll, then save the updated state. (Calculating
|
// saved is to do the scroll, then save the updated state. (Calculating
|
||||||
// it ourselves is hard, and we can't rely on an onScroll callback
|
// it ourselves is hard, and we can't rely on an onScroll callback
|
||||||
// happening, since there may be no user-visible change here).
|
// happening, since there may be no user-visible change here).
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
sn.scrollTop = sn.scrollHeight;
|
sn.scrollTop = sn.scrollHeight;
|
||||||
this._saveScrollState();
|
this.saveScrollState();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -527,18 +553,18 @@ export default class ScrollPanel extends React.Component {
|
||||||
*
|
*
|
||||||
* @param {number} mult: -1 to page up, +1 to page down
|
* @param {number} mult: -1 to page up, +1 to page down
|
||||||
*/
|
*/
|
||||||
scrollRelative = mult => {
|
public scrollRelative = (mult: number): void => {
|
||||||
const scrollNode = this._getScrollNode();
|
const scrollNode = this.getScrollNode();
|
||||||
const delta = mult * scrollNode.clientHeight * 0.9;
|
const delta = mult * scrollNode.clientHeight * 0.9;
|
||||||
scrollNode.scrollBy(0, delta);
|
scrollNode.scrollBy(0, delta);
|
||||||
this._saveScrollState();
|
this.saveScrollState();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll up/down in response to a scroll key
|
* Scroll up/down in response to a scroll key
|
||||||
* @param {object} ev the keyboard event
|
* @param {object} ev the keyboard event
|
||||||
*/
|
*/
|
||||||
handleScrollKey = ev => {
|
public handleScrollKey = (ev: KeyboardEvent) => {
|
||||||
let isScrolling = false;
|
let isScrolling = false;
|
||||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||||
switch (roomAction) {
|
switch (roomAction) {
|
||||||
|
@ -575,17 +601,17 @@ export default class ScrollPanel extends React.Component {
|
||||||
* node (specifically, the bottom of it) will be positioned. If omitted, it
|
* node (specifically, the bottom of it) will be positioned. If omitted, it
|
||||||
* defaults to 0.
|
* defaults to 0.
|
||||||
*/
|
*/
|
||||||
scrollToToken = (scrollToken, pixelOffset, offsetBase) => {
|
public scrollToToken = (scrollToken: string, pixelOffset: number, offsetBase: number): void => {
|
||||||
pixelOffset = pixelOffset || 0;
|
pixelOffset = pixelOffset || 0;
|
||||||
offsetBase = offsetBase || 0;
|
offsetBase = offsetBase || 0;
|
||||||
|
|
||||||
// set the trackedScrollToken so we can get the node through _getTrackedNode
|
// set the trackedScrollToken so we can get the node through getTrackedNode
|
||||||
this.scrollState = {
|
this.scrollState = {
|
||||||
stuckAtBottom: false,
|
stuckAtBottom: false,
|
||||||
trackedScrollToken: scrollToken,
|
trackedScrollToken: scrollToken,
|
||||||
};
|
};
|
||||||
const trackedNode = this._getTrackedNode();
|
const trackedNode = this.getTrackedNode();
|
||||||
const scrollNode = this._getScrollNode();
|
const scrollNode = this.getScrollNode();
|
||||||
if (trackedNode) {
|
if (trackedNode) {
|
||||||
// set the scrollTop to the position we want.
|
// set the scrollTop to the position we want.
|
||||||
// note though, that this might not succeed if the combination of offsetBase and pixelOffset
|
// note though, that this might not succeed if the combination of offsetBase and pixelOffset
|
||||||
|
@ -595,34 +621,34 @@ export default class ScrollPanel extends React.Component {
|
||||||
// enough so it ends up in the top of the viewport.
|
// enough so it ends up in the top of the viewport.
|
||||||
debuglog("scrollToken: setting scrollTop", { offsetBase, pixelOffset, offsetTop: trackedNode.offsetTop });
|
debuglog("scrollToken: setting scrollTop", { offsetBase, pixelOffset, offsetTop: trackedNode.offsetTop });
|
||||||
scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset;
|
scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset;
|
||||||
this._saveScrollState();
|
this.saveScrollState();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveScrollState() {
|
private saveScrollState(): void {
|
||||||
if (this.props.stickyBottom && this.isAtBottom()) {
|
if (this.props.stickyBottom && this.isAtBottom()) {
|
||||||
this.scrollState = { stuckAtBottom: true };
|
this.scrollState = { stuckAtBottom: true };
|
||||||
debuglog("saved stuckAtBottom state");
|
debuglog("saved stuckAtBottom state");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollNode = this._getScrollNode();
|
const scrollNode = this.getScrollNode();
|
||||||
const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight);
|
const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight);
|
||||||
|
|
||||||
const itemlist = this._itemlist.current;
|
const itemlist = this.itemlist.current;
|
||||||
const messages = itemlist.children;
|
const messages = itemlist.children;
|
||||||
let node = null;
|
let node = null;
|
||||||
|
|
||||||
// TODO: do a binary search here, as items are sorted by offsetTop
|
// TODO: do a binary search here, as items are sorted by offsetTop
|
||||||
// loop backwards, from bottom-most message (as that is the most common case)
|
// loop backwards, from bottom-most message (as that is the most common case)
|
||||||
for (let i = messages.length - 1; i >= 0; --i) {
|
for (let i = messages.length - 1; i >= 0; --i) {
|
||||||
if (!messages[i].dataset.scrollTokens) {
|
if (!(messages[i] as HTMLElement).dataset.scrollTokens) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
node = messages[i];
|
node = messages[i];
|
||||||
// break at the first message (coming from the bottom)
|
// break at the first message (coming from the bottom)
|
||||||
// that has it's offsetTop above the bottom of the viewport.
|
// that has it's offsetTop above the bottom of the viewport.
|
||||||
if (this._topFromBottom(node) > viewportBottom) {
|
if (this.topFromBottom(node) > viewportBottom) {
|
||||||
// Use this node as the scrollToken
|
// Use this node as the scrollToken
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -634,7 +660,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
const scrollToken = node.dataset.scrollTokens.split(',')[0];
|
const scrollToken = node.dataset.scrollTokens.split(',')[0];
|
||||||
debuglog("saving anchored scroll state to message", node && node.innerText, scrollToken);
|
debuglog("saving anchored scroll state to message", node && node.innerText, scrollToken);
|
||||||
const bottomOffset = this._topFromBottom(node);
|
const bottomOffset = this.topFromBottom(node);
|
||||||
this.scrollState = {
|
this.scrollState = {
|
||||||
stuckAtBottom: false,
|
stuckAtBottom: false,
|
||||||
trackedNode: node,
|
trackedNode: node,
|
||||||
|
@ -644,35 +670,35 @@ export default class ScrollPanel extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async _restoreSavedScrollState() {
|
private async restoreSavedScrollState(): Promise<void> {
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
|
|
||||||
if (scrollState.stuckAtBottom) {
|
if (scrollState.stuckAtBottom) {
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
if (sn.scrollTop !== sn.scrollHeight) {
|
if (sn.scrollTop !== sn.scrollHeight) {
|
||||||
sn.scrollTop = sn.scrollHeight;
|
sn.scrollTop = sn.scrollHeight;
|
||||||
}
|
}
|
||||||
} else if (scrollState.trackedScrollToken) {
|
} else if (scrollState.trackedScrollToken) {
|
||||||
const itemlist = this._itemlist.current;
|
const itemlist = this.itemlist.current;
|
||||||
const trackedNode = this._getTrackedNode();
|
const trackedNode = this.getTrackedNode();
|
||||||
if (trackedNode) {
|
if (trackedNode) {
|
||||||
const newBottomOffset = this._topFromBottom(trackedNode);
|
const newBottomOffset = this.topFromBottom(trackedNode);
|
||||||
const bottomDiff = newBottomOffset - scrollState.bottomOffset;
|
const bottomDiff = newBottomOffset - scrollState.bottomOffset;
|
||||||
this._bottomGrowth += bottomDiff;
|
this.bottomGrowth += bottomDiff;
|
||||||
scrollState.bottomOffset = newBottomOffset;
|
scrollState.bottomOffset = newBottomOffset;
|
||||||
const newHeight = `${this._getListHeight()}px`;
|
const newHeight = `${this.getListHeight()}px`;
|
||||||
if (itemlist.style.height !== newHeight) {
|
if (itemlist.style.height !== newHeight) {
|
||||||
itemlist.style.height = newHeight;
|
itemlist.style.height = newHeight;
|
||||||
}
|
}
|
||||||
debuglog("balancing height because messages below viewport grew by", bottomDiff);
|
debuglog("balancing height because messages below viewport grew by", bottomDiff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this._heightUpdateInProgress) {
|
if (!this.heightUpdateInProgress) {
|
||||||
this._heightUpdateInProgress = true;
|
this.heightUpdateInProgress = true;
|
||||||
try {
|
try {
|
||||||
await this._updateHeight();
|
await this.updateHeight();
|
||||||
} finally {
|
} finally {
|
||||||
this._heightUpdateInProgress = false;
|
this.heightUpdateInProgress = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debuglog("not updating height because request already in progress");
|
debuglog("not updating height because request already in progress");
|
||||||
|
@ -680,11 +706,11 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content?
|
// need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content?
|
||||||
async _updateHeight() {
|
private async updateHeight(): Promise<void> {
|
||||||
// wait until user has stopped scrolling
|
// wait until user has stopped scrolling
|
||||||
if (this._scrollTimeout.isRunning()) {
|
if (this.scrollTimeout.isRunning()) {
|
||||||
debuglog("updateHeight waiting for scrolling to end ... ");
|
debuglog("updateHeight waiting for scrolling to end ... ");
|
||||||
await this._scrollTimeout.finished();
|
await this.scrollTimeout.finished();
|
||||||
} else {
|
} else {
|
||||||
debuglog("updateHeight getting straight to business, no scrolling going on.");
|
debuglog("updateHeight getting straight to business, no scrolling going on.");
|
||||||
}
|
}
|
||||||
|
@ -694,14 +720,14 @@ export default class ScrollPanel extends React.Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
const itemlist = this._itemlist.current;
|
const itemlist = this.itemlist.current;
|
||||||
const contentHeight = this._getMessagesHeight();
|
const contentHeight = this.getMessagesHeight();
|
||||||
const minHeight = sn.clientHeight;
|
const minHeight = sn.clientHeight;
|
||||||
const height = Math.max(minHeight, contentHeight);
|
const height = Math.max(minHeight, contentHeight);
|
||||||
this._pages = Math.ceil(height / PAGE_SIZE);
|
this.pages = Math.ceil(height / PAGE_SIZE);
|
||||||
this._bottomGrowth = 0;
|
this.bottomGrowth = 0;
|
||||||
const newHeight = `${this._getListHeight()}px`;
|
const newHeight = `${this.getListHeight()}px`;
|
||||||
|
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
if (scrollState.stuckAtBottom) {
|
if (scrollState.stuckAtBottom) {
|
||||||
|
@ -713,7 +739,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
debuglog("updateHeight to", newHeight);
|
debuglog("updateHeight to", newHeight);
|
||||||
} else if (scrollState.trackedScrollToken) {
|
} else if (scrollState.trackedScrollToken) {
|
||||||
const trackedNode = this._getTrackedNode();
|
const trackedNode = this.getTrackedNode();
|
||||||
// if the timeline has been reloaded
|
// if the timeline has been reloaded
|
||||||
// this can be called before scrollToBottom or whatever has been called
|
// this can be called before scrollToBottom or whatever has been called
|
||||||
// so don't do anything if the node has disappeared from
|
// so don't do anything if the node has disappeared from
|
||||||
|
@ -735,17 +761,17 @@ export default class ScrollPanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTrackedNode() {
|
private getTrackedNode(): HTMLElement {
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
const trackedNode = scrollState.trackedNode;
|
const trackedNode = scrollState.trackedNode;
|
||||||
|
|
||||||
if (!trackedNode || !trackedNode.parentElement) {
|
if (!trackedNode || !trackedNode.parentElement) {
|
||||||
let node;
|
let node;
|
||||||
const messages = this._itemlist.current.children;
|
const messages = this.itemlist.current.children;
|
||||||
const scrollToken = scrollState.trackedScrollToken;
|
const scrollToken = scrollState.trackedScrollToken;
|
||||||
|
|
||||||
for (let i = messages.length-1; i >= 0; --i) {
|
for (let i = messages.length-1; i >= 0; --i) {
|
||||||
const m = messages[i];
|
const m = messages[i] as HTMLElement;
|
||||||
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
|
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
|
||||||
// There might only be one scroll token
|
// There might only be one scroll token
|
||||||
if (m.dataset.scrollTokens &&
|
if (m.dataset.scrollTokens &&
|
||||||
|
@ -768,45 +794,45 @@ export default class ScrollPanel extends React.Component {
|
||||||
return scrollState.trackedNode;
|
return scrollState.trackedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getListHeight() {
|
private getListHeight(): number {
|
||||||
return this._bottomGrowth + (this._pages * PAGE_SIZE);
|
return this.bottomGrowth + (this.pages * PAGE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMessagesHeight() {
|
private getMessagesHeight(): number {
|
||||||
const itemlist = this._itemlist.current;
|
const itemlist = this.itemlist.current;
|
||||||
const lastNode = itemlist.lastElementChild;
|
const lastNode = itemlist.lastElementChild as HTMLElement;
|
||||||
const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0;
|
const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0;
|
||||||
const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0;
|
const firstNodeTop = itemlist.firstElementChild ? (itemlist.firstElementChild as HTMLElement).offsetTop : 0;
|
||||||
// 18 is itemlist padding
|
// 18 is itemlist padding
|
||||||
return lastNodeBottom - firstNodeTop + (18 * 2);
|
return lastNodeBottom - firstNodeTop + (18 * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
_topFromBottom(node) {
|
private topFromBottom(node: HTMLElement): number {
|
||||||
// current capped height - distance from top = distance from bottom of container to top of tracked element
|
// current capped height - distance from top = distance from bottom of container to top of tracked element
|
||||||
return this._itemlist.current.clientHeight - node.offsetTop;
|
return this.itemlist.current.clientHeight - node.offsetTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get the DOM node which has the scrollTop property we care about for our
|
/* get the DOM node which has the scrollTop property we care about for our
|
||||||
* message panel.
|
* message panel.
|
||||||
*/
|
*/
|
||||||
_getScrollNode() {
|
private getScrollNode(): HTMLDivElement {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
// this shouldn't happen, but when it does, turn the NPE into
|
// this shouldn't happen, but when it does, turn the NPE into
|
||||||
// something more meaningful.
|
// something more meaningful.
|
||||||
throw new Error("ScrollPanel._getScrollNode called when unmounted");
|
throw new Error("ScrollPanel.getScrollNode called when unmounted");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._divScroll) {
|
if (!this.divScroll) {
|
||||||
// Likewise, we should have the ref by this point, but if not
|
// Likewise, we should have the ref by this point, but if not
|
||||||
// turn the NPE into something meaningful.
|
// turn the NPE into something meaningful.
|
||||||
throw new Error("ScrollPanel._getScrollNode called before AutoHideScrollbar ref collected");
|
throw new Error("ScrollPanel.getScrollNode called before AutoHideScrollbar ref collected");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._divScroll;
|
return this.divScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectScroll = divScroll => {
|
private collectScroll = (divScroll: HTMLDivElement) => {
|
||||||
this._divScroll = divScroll;
|
this.divScroll = divScroll;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -814,15 +840,15 @@ export default class ScrollPanel extends React.Component {
|
||||||
anything below it changes, by calling updatePreventShrinking, to keep
|
anything below it changes, by calling updatePreventShrinking, to keep
|
||||||
the same minimum bottom offset, effectively preventing the timeline to shrink.
|
the same minimum bottom offset, effectively preventing the timeline to shrink.
|
||||||
*/
|
*/
|
||||||
preventShrinking = () => {
|
public preventShrinking = (): void => {
|
||||||
const messageList = this._itemlist.current;
|
const messageList = this.itemlist.current;
|
||||||
const tiles = messageList && messageList.children;
|
const tiles = messageList && messageList.children;
|
||||||
if (!messageList) {
|
if (!messageList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let lastTileNode;
|
let lastTileNode;
|
||||||
for (let i = tiles.length - 1; i >= 0; i--) {
|
for (let i = tiles.length - 1; i >= 0; i--) {
|
||||||
const node = tiles[i];
|
const node = tiles[i] as HTMLElement;
|
||||||
if (node.dataset.scrollTokens) {
|
if (node.dataset.scrollTokens) {
|
||||||
lastTileNode = node;
|
lastTileNode = node;
|
||||||
break;
|
break;
|
||||||
|
@ -841,8 +867,8 @@ export default class ScrollPanel extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */
|
/** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */
|
||||||
clearPreventShrinking = () => {
|
public clearPreventShrinking = (): void => {
|
||||||
const messageList = this._itemlist.current;
|
const messageList = this.itemlist.current;
|
||||||
const balanceElement = messageList && messageList.parentElement;
|
const balanceElement = messageList && messageList.parentElement;
|
||||||
if (balanceElement) balanceElement.style.paddingBottom = null;
|
if (balanceElement) balanceElement.style.paddingBottom = null;
|
||||||
this.preventShrinkingState = null;
|
this.preventShrinkingState = null;
|
||||||
|
@ -857,11 +883,11 @@ export default class ScrollPanel extends React.Component {
|
||||||
from the bottom of the marked tile grows larger than
|
from the bottom of the marked tile grows larger than
|
||||||
what it was when marking.
|
what it was when marking.
|
||||||
*/
|
*/
|
||||||
updatePreventShrinking = () => {
|
public updatePreventShrinking = (): void => {
|
||||||
if (this.preventShrinkingState) {
|
if (this.preventShrinkingState) {
|
||||||
const sn = this._getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
const messageList = this._itemlist.current;
|
const messageList = this.itemlist.current;
|
||||||
const { offsetNode, offsetFromBottom } = this.preventShrinkingState;
|
const { offsetNode, offsetFromBottom } = this.preventShrinkingState;
|
||||||
// element used to set paddingBottom to balance the typing notifs disappearing
|
// element used to set paddingBottom to balance the typing notifs disappearing
|
||||||
const balanceElement = messageList.parentElement;
|
const balanceElement = messageList.parentElement;
|
||||||
|
@ -898,13 +924,15 @@ export default class ScrollPanel extends React.Component {
|
||||||
// list-style-type: none; is no longer a list
|
// list-style-type: none; is no longer a list
|
||||||
return (
|
return (
|
||||||
<AutoHideScrollbar
|
<AutoHideScrollbar
|
||||||
wrappedRef={this._collectScroll}
|
wrappedRef={this.collectScroll}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
onWheel={this.props.onUserScroll}
|
onWheel={this.props.onUserScroll}
|
||||||
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
|
className={`mx_ScrollPanel ${this.props.className}`}
|
||||||
|
style={this.props.style}
|
||||||
|
>
|
||||||
{ this.props.fixedChildren }
|
{ this.props.fixedChildren }
|
||||||
<div className="mx_RoomView_messageListWrapper">
|
<div className="mx_RoomView_messageListWrapper">
|
||||||
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
<ol ref={this.itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
|
@ -112,12 +112,12 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
onViewRoomClick(false);
|
onViewRoomClick(false);
|
||||||
}
|
};
|
||||||
const onJoinClick = (ev: ButtonEvent) => {
|
const onJoinClick = (ev: ButtonEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
onViewRoomClick(true);
|
onViewRoomClick(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
let button;
|
let button;
|
||||||
if (joinedRoom) {
|
if (joinedRoom) {
|
||||||
|
@ -137,7 +137,7 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
} else {
|
} else {
|
||||||
checkbox = <TextWithTooltip
|
checkbox = <TextWithTooltip
|
||||||
tooltip={_t("You don't have permission")}
|
tooltip={_t("You don't have permission")}
|
||||||
onClick={ev => { ev.stopPropagation() }}
|
onClick={ev => { ev.stopPropagation(); }}
|
||||||
>
|
>
|
||||||
<StyledCheckbox disabled={true} />
|
<StyledCheckbox disabled={true} />
|
||||||
</TextWithTooltip>;
|
</TextWithTooltip>;
|
||||||
|
@ -340,7 +340,7 @@ export const HierarchyLevel = ({
|
||||||
</Tile>
|
</Tile>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutate argument refreshToken to force a reload
|
// mutate argument refreshToken to force a reload
|
||||||
|
|
|
@ -37,7 +37,7 @@ import * as Email from "../../email";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier"
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import MainSplit from './MainSplit';
|
import MainSplit from './MainSplit';
|
||||||
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||||
import { ActionPayload } from "../../dispatcher/payloads";
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
|
@ -162,7 +162,7 @@ const SpaceInfo = ({ space }) => {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
) : null}
|
) : null}
|
||||||
</RoomMemberCount> }
|
</RoomMemberCount> }
|
||||||
</div>
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBetaClick = () => {
|
const onBetaClick = () => {
|
||||||
|
@ -592,14 +592,14 @@ const SpaceSetupPrivateScope = ({ space, justCreatedOpts, onFinished }) => {
|
||||||
|
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_SpaceRoomView_privateScope_justMeButton"
|
className="mx_SpaceRoomView_privateScope_justMeButton"
|
||||||
onClick={() => { onFinished(false) }}
|
onClick={() => { onFinished(false); }}
|
||||||
>
|
>
|
||||||
<h3>{ _t("Just me") }</h3>
|
<h3>{ _t("Just me") }</h3>
|
||||||
<div>{ _t("A private space to organise your rooms") }</div>
|
<div>{ _t("A private space to organise your rooms") }</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_SpaceRoomView_privateScope_meAndMyTeammatesButton"
|
className="mx_SpaceRoomView_privateScope_meAndMyTeammatesButton"
|
||||||
onClick={() => { onFinished(true) }}
|
onClick={() => { onFinished(true); }}
|
||||||
>
|
>
|
||||||
<h3>{ _t("Me and my teammates") }</h3>
|
<h3>{ _t("Me and my teammates") }</h3>
|
||||||
<div>{ _t("A private space for you and your teammates") }</div>
|
<div>{ _t("A private space for you and your teammates") }</div>
|
||||||
|
@ -686,7 +686,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
let buttonLabel = _t("Skip for now");
|
let buttonLabel = _t("Skip for now");
|
||||||
if (emailAddresses.some(name => name.trim())) {
|
if (emailAddresses.some(name => name.trim())) {
|
||||||
onClick = onNextClick;
|
onClick = onNextClick;
|
||||||
buttonLabel = busy ? _t("Inviting...") : _t("Continue")
|
buttonLabel = busy ? _t("Inviting...") : _t("Continue");
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_inviteTeammates">
|
return <div className="mx_SpaceRoomView_inviteTeammates">
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -123,7 +123,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onRoom = (room: Room): void => {
|
private onRoom = (room: Room): void => {
|
||||||
this.removePendingJoinRoom(room.roomId);
|
this.removePendingJoinRoom(room.roomId);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onTagStoreUpdate = () => {
|
private onTagStoreUpdate = () => {
|
||||||
this.forceUpdate(); // we don't have anything useful in state to update
|
this.forceUpdate(); // we don't have anything useful in state to update
|
||||||
|
@ -185,7 +185,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
if (this.state.pendingRoomJoin.delete(roomId)) {
|
if (this.state.pendingRoomJoin.delete(roomId)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
pendingRoomJoin: new Set<string>(this.state.pendingRoomJoin),
|
pendingRoomJoin: new Set<string>(this.state.pendingRoomJoin),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
} else if (hostSignupConfig) {
|
} else if (hostSignupConfig) {
|
||||||
if (hostSignupConfig && hostSignupConfig.url) {
|
if (hostSignupConfig && hostSignupConfig.url) {
|
||||||
// If hostSignup.domains is set to a non-empty array, only show
|
// If hostSignup.domains is set to a non-empty array, only show
|
||||||
|
@ -509,7 +509,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
} else if (MatrixClientPeg.get().isGuest()) {
|
} else if (MatrixClientPeg.get().isGuest()) {
|
||||||
primaryOptionList = (
|
primaryOptionList = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|
|
@ -501,9 +501,9 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
{ flows.map(flow => {
|
{ flows.map(flow => {
|
||||||
const stepRenderer = this.stepRendererMap[flow.type];
|
const stepRenderer = this.stepRendererMap[flow.type];
|
||||||
return <React.Fragment key={flow.type}>{ stepRenderer() }</React.Fragment>
|
return <React.Fragment key={flow.type}>{ stepRenderer() }</React.Fragment>;
|
||||||
}) }
|
}) }
|
||||||
</React.Fragment>
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderPasswordStep = () => {
|
private renderPasswordStep = () => {
|
||||||
|
|
|
@ -267,7 +267,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onUIAuthFinished = async (success: boolean, response: any) => {
|
private onUIAuthFinished = async (success: boolean, response: any) => {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
@ -487,7 +487,13 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
||||||
/>
|
/>
|
||||||
<h3 className="mx_AuthBody_centered">
|
<h3 className="mx_AuthBody_centered">
|
||||||
{ _t("%(ssoButtons)s Or %(usernamePassword)s", { ssoButtons: "", usernamePassword: ""}).trim() }
|
{_t(
|
||||||
|
"%(ssoButtons)s Or %(usernamePassword)s",
|
||||||
|
{
|
||||||
|
ssoButtons: "",
|
||||||
|
usernamePassword: "",
|
||||||
|
},
|
||||||
|
).trim()}
|
||||||
</h3>
|
</h3>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -354,7 +354,6 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||||
CountlyAnalytics.instance.track("onboarding_terms_begin");
|
CountlyAnalytics.instance.track("onboarding_terms_begin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,7 +322,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
const result = await this.validatePasswordRules(fieldState);
|
const result = await this.validatePasswordRules(fieldState);
|
||||||
this.markFieldValid(LoginField.Password, result.valid);
|
this.markFieldValid(LoginField.Password, result.valid);
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
private renderLoginField(loginType: IState["loginType"], autoFocus: boolean) {
|
private renderLoginField(loginType: IState["loginType"], autoFocus: boolean) {
|
||||||
const classes = {
|
const classes = {
|
||||||
|
|
|
@ -49,7 +49,7 @@ const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 2
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WidgetAvatar;
|
export default WidgetAvatar;
|
||||||
|
|
|
@ -42,13 +42,13 @@ export default class CallContextMenu extends React.Component<IProps> {
|
||||||
onHoldClick = () => {
|
onHoldClick = () => {
|
||||||
this.props.call.setRemoteOnHold(true);
|
this.props.call.setRemoteOnHold(true);
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
};
|
||||||
|
|
||||||
onUnholdClick = () => {
|
onUnholdClick = () => {
|
||||||
CallHandler.sharedInstance().setActiveCallRoomId(this.props.call.roomId);
|
CallHandler.sharedInstance().setActiveCallRoomId(this.props.call.roomId);
|
||||||
|
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
};
|
||||||
|
|
||||||
onTransferClick = () => {
|
onTransferClick = () => {
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
|
@ -56,7 +56,7 @@ export default class CallContextMenu extends React.Component<IProps> {
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
);
|
);
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const holdUnholdCaption = this.props.call.isRemoteOnHold() ? _t("Resume") : _t("Hold");
|
const holdUnholdCaption = this.props.call.isRemoteOnHold() ? _t("Resume") : _t("Hold");
|
||||||
|
|
|
@ -37,18 +37,17 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
value: '',
|
value: '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onDigitPress = (digit) => {
|
onDigitPress = (digit) => {
|
||||||
this.props.call.sendDtmfDigit(digit);
|
this.props.call.sendDtmfDigit(digit);
|
||||||
this.setState({ value: this.state.value + digit });
|
this.setState({ value: this.state.value + digit });
|
||||||
}
|
};
|
||||||
|
|
||||||
onChange = (ev) => {
|
onChange = (ev) => {
|
||||||
this.setState({ value: ev.target.value });
|
this.setState({ value: ev.target.value });
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <ContextMenu {...this.props}>
|
return <ContextMenu {...this.props}>
|
||||||
|
|
|
@ -23,7 +23,6 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
* menu.
|
* menu.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@replaceableComponent("views.context_menus.GenericElementContextMenu")
|
@replaceableComponent("views.context_menus.GenericElementContextMenu")
|
||||||
export default class GenericElementContextMenu extends React.Component {
|
export default class GenericElementContextMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
|
@ -207,7 +207,7 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onPermalinkClick = (e: Event) => {
|
onPermalinkClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
||||||
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
|
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onCancel = (): void => {
|
private onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onSubmit = (): void => {
|
private onSubmit = (): void => {
|
||||||
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
|
if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) {
|
||||||
|
@ -110,7 +110,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
private onDownload = async (): Promise<void> => {
|
private onDownload = async (): Promise<void> => {
|
||||||
this.setState({ downloadBusy: true });
|
this.setState({ downloadBusy: true });
|
||||||
|
@ -139,25 +139,25 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onTextChange = (ev: React.FormEvent<HTMLTextAreaElement>): void => {
|
private onTextChange = (ev: React.FormEvent<HTMLTextAreaElement>): void => {
|
||||||
this.setState({ text: ev.currentTarget.value });
|
this.setState({ text: ev.currentTarget.value });
|
||||||
}
|
};
|
||||||
|
|
||||||
private onIssueUrlChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
private onIssueUrlChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.setState({ issueUrl: ev.currentTarget.value });
|
this.setState({ issueUrl: ev.currentTarget.value });
|
||||||
}
|
};
|
||||||
|
|
||||||
private sendProgressCallback = (progress: string): void => {
|
private sendProgressCallback = (progress: string): void => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ progress });
|
this.setState({ progress });
|
||||||
}
|
};
|
||||||
|
|
||||||
private downloadProgressCallback = (downloadProgress: string): void => {
|
private downloadProgressCallback = (downloadProgress: string): void => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ downloadProgress });
|
this.setState({ downloadProgress });
|
||||||
}
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
|
|
@ -93,7 +93,6 @@ export default class ChangelogDialog extends React.Component<IProps> {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QuestionDialog
|
<QuestionDialog
|
||||||
title={_t("Changelog")}
|
title={_t("Changelog")}
|
||||||
|
|
|
@ -175,7 +175,7 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
|
||||||
|
|
||||||
let preview = <img src={this.state.avatarPreview} className="mx_CreateCommunityPrototypeDialog_avatar" />;
|
let preview = <img src={this.state.avatarPreview} className="mx_CreateCommunityPrototypeDialog_avatar" />;
|
||||||
if (!this.state.avatarPreview) {
|
if (!this.state.avatarPreview) {
|
||||||
preview = <div className="mx_CreateCommunityPrototypeDialog_placeholderAvatar" />
|
preview = <div className="mx_CreateCommunityPrototypeDialog_placeholderAvatar" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -62,13 +62,13 @@ abstract class GenericEditor<
|
||||||
} else {
|
} else {
|
||||||
this.props.onBack();
|
this.props.onBack();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
protected onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
protected onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
// @ts-ignore: Unsure how to convince TS this is okay when the state
|
// @ts-ignore: Unsure how to convince TS this is okay when the state
|
||||||
// type can be extended.
|
// type can be extended.
|
||||||
this.setState({ [e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
|
this.setState({ [e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
|
||||||
}
|
};
|
||||||
|
|
||||||
protected abstract send();
|
protected abstract send();
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendC
|
||||||
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
|
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
|
||||||
}
|
}
|
||||||
this.setState({ message });
|
this.setState({ message });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.message) {
|
if (this.state.message) {
|
||||||
|
@ -264,7 +264,7 @@ class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountD
|
||||||
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
|
message = _t('Failed to send custom event.') + ' (' + e.toString() + ')';
|
||||||
}
|
}
|
||||||
this.setState({ message });
|
this.setState({ message });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.message) {
|
if (this.state.message) {
|
||||||
|
@ -442,19 +442,19 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
|
||||||
} else {
|
} else {
|
||||||
this.props.onBack();
|
this.props.onBack();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private editEv = () => {
|
private editEv = () => {
|
||||||
this.setState({ editing: true });
|
this.setState({ editing: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
private onQueryEventType = (filterEventType: string) => {
|
private onQueryEventType = (filterEventType: string) => {
|
||||||
this.setState({ queryEventType: filterEventType });
|
this.setState({ queryEventType: filterEventType });
|
||||||
}
|
};
|
||||||
|
|
||||||
private onQueryStateKey = (filterStateKey: string) => {
|
private onQueryStateKey = (filterStateKey: string) => {
|
||||||
this.setState({ queryStateKey: filterStateKey });
|
this.setState({ queryStateKey: filterStateKey });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.event) {
|
if (this.state.event) {
|
||||||
|
@ -570,19 +570,19 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
||||||
} else {
|
} else {
|
||||||
this.props.onBack();
|
this.props.onBack();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
private onChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ [e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
|
this.setState({ [e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
|
||||||
}
|
};
|
||||||
|
|
||||||
private editEv = () => {
|
private editEv = () => {
|
||||||
this.setState({ editing: true });
|
this.setState({ editing: true });
|
||||||
}
|
};
|
||||||
|
|
||||||
private onQueryEventType = (queryEventType: string) => {
|
private onQueryEventType = (queryEventType: string) => {
|
||||||
this.setState({ queryEventType });
|
this.setState({ queryEventType });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.event) {
|
if (this.state.event) {
|
||||||
|
@ -676,7 +676,7 @@ class ServersInRoomList extends React.PureComponent<IExplorerProps, IServersInRo
|
||||||
|
|
||||||
private onQuery = (query: string) => {
|
private onQuery = (query: string) => {
|
||||||
this.setState({ query });
|
this.setState({ query });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -739,7 +739,7 @@ const VerificationRequestExplorer: React.FC<{
|
||||||
<dd>{JSON.stringify(request.observeOnly)}</dd>
|
<dd>{JSON.stringify(request.observeOnly)}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
};
|
||||||
|
|
||||||
class VerificationExplorer extends React.PureComponent<IExplorerProps> {
|
class VerificationExplorer extends React.PureComponent<IExplorerProps> {
|
||||||
static getLabel() {
|
static getLabel() {
|
||||||
|
@ -751,7 +751,7 @@ class VerificationExplorer extends React.PureComponent<IExplorerProps> {
|
||||||
|
|
||||||
private onNewRequest = () => {
|
private onNewRequest = () => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const cli = this.context;
|
const cli = this.context;
|
||||||
|
@ -1221,11 +1221,11 @@ export default class DevtoolsDialog extends React.PureComponent<IProps, IState>
|
||||||
|
|
||||||
private onBack = () => {
|
private onBack = () => {
|
||||||
this.setState({ mode: null });
|
this.setState({ mode: null });
|
||||||
}
|
};
|
||||||
|
|
||||||
private onCancel = () => {
|
private onCancel = () => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let body;
|
let body;
|
||||||
|
|
|
@ -122,7 +122,7 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
|
||||||
const url = mediaFromMxc(this.state.currentAvatarUrl).srcHttp;
|
const url = mediaFromMxc(this.state.currentAvatarUrl).srcHttp;
|
||||||
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
||||||
} else {
|
} else {
|
||||||
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />
|
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ const existingIssuesUrl = "https://github.com/vector-im/element-web/issues" +
|
||||||
"?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
|
"?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
|
||||||
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
|
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
|
||||||
|
|
||||||
|
|
||||||
export default (props) => {
|
export default (props) => {
|
||||||
const [rating, setRating] = useState("");
|
const [rating, setRating] = useState("");
|
||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
case PostmessageAction.CloseDialog:
|
case PostmessageAction.CloseDialog:
|
||||||
return this.closeDialog();
|
return this.closeDialog();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private maximizeDialog = () => {
|
private maximizeDialog = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -96,7 +96,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
this.sendMessage({
|
this.sendMessage({
|
||||||
action: PostmessageAction.Maximize,
|
action: PostmessageAction.Maximize,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
private minimizeDialog = () => {
|
private minimizeDialog = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -106,7 +106,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
this.sendMessage({
|
this.sendMessage({
|
||||||
action: PostmessageAction.Minimize,
|
action: PostmessageAction.Minimize,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
private closeDialog = async () => {
|
private closeDialog = async () => {
|
||||||
window.removeEventListener("message", this.messageHandler);
|
window.removeEventListener("message", this.messageHandler);
|
||||||
|
@ -114,7 +114,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
PersistedElement.destroyElement("host_signup");
|
PersistedElement.destroyElement("host_signup");
|
||||||
// Finally clear the flag in
|
// Finally clear the flag in
|
||||||
return HostSignupStore.instance.setHostSignupActive(false);
|
return HostSignupStore.instance.setHostSignupActive(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onCloseClick = async () => {
|
private onCloseClick = async () => {
|
||||||
if (this.state.completed) {
|
if (this.state.completed) {
|
||||||
|
@ -137,16 +137,16 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private sendMessage = (message: IPostmessageResponseData) => {
|
private sendMessage = (message: IPostmessageResponseData) => {
|
||||||
this.iframeRef.current.contentWindow.postMessage(message, this.config.url);
|
this.iframeRef.current.contentWindow.postMessage(message, this.config.url);
|
||||||
}
|
};
|
||||||
|
|
||||||
private async sendAccountDetails() {
|
private async sendAccountDetails() {
|
||||||
const openIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
const openIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
if (!openIdToken || !openIdToken.access_token) {
|
if (!openIdToken || !openIdToken.access_token) {
|
||||||
console.warn("Failed to connect to homeserver for OpenID token.")
|
console.warn("Failed to connect to homeserver for OpenID token.");
|
||||||
this.setState({
|
this.setState({
|
||||||
completed: true,
|
completed: true,
|
||||||
error: _t("Failed to connect to your homeserver. Please close this dialog and try again."),
|
error: _t("Failed to connect to your homeserver. Please close this dialog and try again."),
|
||||||
|
@ -171,7 +171,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
return this.sendAccountDetails();
|
return this.sendAccountDetails();
|
||||||
}
|
}
|
||||||
return this.closeDialog();
|
return this.closeDialog();
|
||||||
}
|
};
|
||||||
|
|
||||||
private onAccountDetailsRequest = () => {
|
private onAccountDetailsRequest = () => {
|
||||||
const textComponent = (
|
const textComponent = (
|
||||||
|
@ -215,7 +215,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
||||||
onFinished: this.onAccountDetailsDialogFinished,
|
onFinished: this.onAccountDetailsDialogFinished,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
window.addEventListener("message", this.messageHandler);
|
window.addEventListener("message", this.messageHandler);
|
||||||
|
|
|
@ -427,7 +427,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
|
|
||||||
private onConsultFirstChange = (ev) => {
|
private onConsultFirstChange = (ev) => {
|
||||||
this.setState({ consultFirst: ev.target.checked });
|
this.setState({ consultFirst: ev.target.checked });
|
||||||
}
|
};
|
||||||
|
|
||||||
public static buildRecents(excludedTargetIds: Set<string>): IRecentUser[] {
|
public static buildRecents(excludedTargetIds: Set<string>): IRecentUser[] {
|
||||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
||||||
|
@ -696,7 +696,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return roomOptions;
|
return roomOptions;
|
||||||
},
|
},
|
||||||
{ invite: [], invite_3pid: [] },
|
{ invite: [], invite_3pid: [] },
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await createRoom(createRoomOptions);
|
await createRoom(createRoomOptions);
|
||||||
|
@ -729,7 +729,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await inviteMultipleToRoom(this.props.roomId, targetIds)
|
const result = await inviteMultipleToRoom(this.props.roomId, targetIds);
|
||||||
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
|
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
|
||||||
if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too
|
if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
|
@ -1365,7 +1365,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
<div />
|
<div />
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
} else if (this.props.kind === KIND_INVITE) {
|
} else if (this.props.kind === KIND_INVITE) {
|
||||||
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
||||||
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
|
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
|
||||||
|
|
|
@ -102,7 +102,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
||||||
|
|
||||||
private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>) => {
|
private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>) => {
|
||||||
this.props.onFinished(true, ev.detail.data);
|
this.props.onFinished(true, ev.detail.data);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onButtonEnableToggle = (ev: CustomEvent<ISetModalButtonEnabledActionRequest>) => {
|
private onButtonEnableToggle = (ev: CustomEvent<ISetModalButtonEnabledActionRequest>) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -160,7 +160,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
|
||||||
break;
|
break;
|
||||||
case ModalButtonKind.Secondary:
|
case ModalButtonKind.Secondary:
|
||||||
kind = "primary_outline";
|
kind = "primary_outline";
|
||||||
break
|
break;
|
||||||
case ModalButtonKind.Danger:
|
case ModalButtonKind.Danger:
|
||||||
kind = "danger";
|
kind = "danger";
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -40,7 +40,6 @@ interface IState {
|
||||||
nature?: EXTENDED_NATURE;
|
nature?: EXTENDED_NATURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const MODERATED_BY_STATE_EVENT_TYPE = [
|
const MODERATED_BY_STATE_EVENT_TYPE = [
|
||||||
"org.matrix.msc3215.room.moderation.moderated_by",
|
"org.matrix.msc3215.room.moderation.moderated_by",
|
||||||
/**
|
/**
|
||||||
|
@ -75,7 +74,7 @@ type Moderation = {
|
||||||
moderationRoomId: string;
|
moderationRoomId: string;
|
||||||
// The id of the bot in charge of forwarding abuse reports to the moderation room.
|
// The id of the bot in charge of forwarding abuse reports to the moderation room.
|
||||||
moderationBotUserId: string;
|
moderationBotUserId: string;
|
||||||
}
|
};
|
||||||
/*
|
/*
|
||||||
* A dialog for reporting an event.
|
* A dialog for reporting an event.
|
||||||
*
|
*
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
|
||||||
{entries}
|
{entries}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prompt the user to set an email address.
|
* Prompt the user to set an email address.
|
||||||
*
|
*
|
||||||
|
|
|
@ -63,11 +63,11 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount() {
|
||||||
this.openManager(0, true);
|
this.openManager(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
openManager = async (i: number, force = false) => {
|
openManager = async (i, force = false) => {
|
||||||
if (i === this.state.currentIndex && !force) return;
|
if (i === this.state.currentIndex && !force) return;
|
||||||
|
|
||||||
const manager = this.state.managers[i];
|
const manager = this.state.managers[i];
|
||||||
|
|
|
@ -31,7 +31,7 @@ interface ITermsCheckboxProps {
|
||||||
class TermsCheckbox extends React.PureComponent<ITermsCheckboxProps> {
|
class TermsCheckbox extends React.PureComponent<ITermsCheckboxProps> {
|
||||||
private onChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
private onChange = (ev: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.props.onChange(this.props.url, ev.currentTarget.checked);
|
this.props.onChange(this.props.url, ev.currentTarget.checked);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <input type="checkbox"
|
return <input type="checkbox"
|
||||||
|
@ -80,11 +80,11 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
|
||||||
|
|
||||||
private onCancelClick = (): void => {
|
private onCancelClick = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onNextClick = (): void => {
|
private onNextClick = (): void => {
|
||||||
this.props.onFinished(true, Object.keys(this.state.agreedUrls).filter((url) => this.state.agreedUrls[url]));
|
this.props.onFinished(true, Object.keys(this.state.agreedUrls).filter((url) => this.state.agreedUrls[url]));
|
||||||
}
|
};
|
||||||
|
|
||||||
private nameForServiceType(serviceType: SERVICE_TYPES, host: string): JSX.Element {
|
private nameForServiceType(serviceType: SERVICE_TYPES, host: string): JSX.Element {
|
||||||
switch (serviceType) {
|
switch (serviceType) {
|
||||||
|
@ -114,7 +114,7 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
|
||||||
this.setState({
|
this.setState({
|
||||||
agreedUrls: Object.assign({}, this.state.agreedUrls, { [url]: checked }),
|
agreedUrls: Object.assign({}, this.state.agreedUrls, { [url]: checked }),
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
totalFiles: 1,
|
totalFiles: 1,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -56,15 +56,15 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||||
|
|
||||||
private onCancelClick = () => {
|
private onCancelClick = () => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onUploadClick = () => {
|
private onUploadClick = () => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onUploadAllClick = () => {
|
private onUploadAllClick = () => {
|
||||||
this.props.onFinished(true, true);
|
this.props.onFinished(true, true);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
|
@ -79,7 +79,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
|
private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
|
||||||
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
|
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
|
||||||
this.setState({ mjolnirEnabled: newValue });
|
this.setState({ mjolnirEnabled: newValue });
|
||||||
}
|
};
|
||||||
|
|
||||||
_getTabs() {
|
_getTabs() {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
|
@ -169,7 +169,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
||||||
|
|
||||||
private onRecoveryKeyFileUploadClick = () => {
|
private onRecoveryKeyFileUploadClick = () => {
|
||||||
this.fileUpload.current.click();
|
this.fileUpload.current.click();
|
||||||
}
|
};
|
||||||
|
|
||||||
private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement>) => {
|
private onPassPhraseNext = async (ev: FormEvent<HTMLFormElement>) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { CrossSigningKeys } from 'matrix-js-sdk/src/client';
|
||||||
|
|
||||||
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
|
@ -71,7 +73,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
|
||||||
|
|
||||||
private async queryKeyUploadAuth(): Promise<void> {
|
private async queryKeyUploadAuth(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {} as CrossSigningKeys);
|
||||||
// We should never get here: the server should always require
|
// We should never get here: the server should always require
|
||||||
// UI auth to upload device signing keys. If we do, we upload
|
// UI auth to upload device signing keys. If we do, we upload
|
||||||
// no keys which would be a no-op.
|
// no keys which would be a no-op.
|
||||||
|
@ -139,7 +141,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
|
||||||
throw new Error("Cross-signing key upload auth canceled");
|
throw new Error("Cross-signing key upload auth canceled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private bootstrapCrossSigning = async (): Promise<void> => {
|
private bootstrapCrossSigning = async (): Promise<void> => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -163,11 +165,11 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
|
||||||
this.setState({ error: e });
|
this.setState({ error: e });
|
||||||
console.error("Error bootstrapping cross-signing", e);
|
console.error("Error bootstrapping cross-signing", e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private onCancel = (): void => {
|
private onCancel = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content;
|
let content;
|
||||||
|
|
|
@ -216,7 +216,9 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
|
||||||
if (removableServers.has(server)) {
|
if (removableServers.has(server)) {
|
||||||
const onClick = async () => {
|
const onClick = async () => {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
const {finished} = Modal.createTrackedDialog("Network Dropdown", "Remove server", QuestionDialog, {
|
const { finished } = Modal.createTrackedDialog(
|
||||||
|
"Network Dropdown", "Remove server", QuestionDialog,
|
||||||
|
{
|
||||||
title: _t("Are you sure?"),
|
title: _t("Are you sure?"),
|
||||||
description: _t("Are you sure you want to remove <b>%(serverName)s</b>", {
|
description: _t("Are you sure you want to remove <b>%(serverName)s</b>", {
|
||||||
serverName: server,
|
serverName: server,
|
||||||
|
@ -225,7 +227,9 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
|
||||||
}),
|
}),
|
||||||
button: _t("Remove"),
|
button: _t("Remove"),
|
||||||
fixedWidth: false,
|
fixedWidth: false,
|
||||||
}, "mx_NetworkDropdown_dialog");
|
},
|
||||||
|
"mx_NetworkDropdown_dialog",
|
||||||
|
);
|
||||||
|
|
||||||
const [ok] = await finished;
|
const [ok] = await finished;
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
|
@ -62,8 +62,6 @@ export default class ActionButton extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
|
||||||
|
|
||||||
let tooltip;
|
let tooltip;
|
||||||
if (this.state.showTooltip) {
|
if (this.state.showTooltip) {
|
||||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
|
@ -71,7 +69,7 @@ export default class ActionButton extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = this.props.iconPath ?
|
const icon = this.props.iconPath ?
|
||||||
(<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
|
(<img src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
|
||||||
undefined;
|
undefined;
|
||||||
|
|
||||||
const classNames = ["mx_RoleButton"];
|
const classNames = ["mx_RoleButton"];
|
||||||
|
|
|
@ -53,7 +53,6 @@ export default class AddressTile extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
|
||||||
|
|
||||||
const nameClasses = classNames({
|
const nameClasses = classNames({
|
||||||
"mx_AddressTile_name": true,
|
"mx_AddressTile_name": true,
|
||||||
|
@ -124,7 +123,7 @@ export default class AddressTile extends React.Component {
|
||||||
if (this.props.canDismiss) {
|
if (this.props.canDismiss) {
|
||||||
dismiss = (
|
dismiss = (
|
||||||
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} >
|
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} >
|
||||||
<TintableSvg src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
|
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import dis from "../../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { UserTab } from "../dialogs/UserSettingsDialog";
|
import { UserTab } from "../dialogs/UserSettingsDialog";
|
||||||
|
|
||||||
|
|
||||||
export enum WarningKind {
|
export enum WarningKind {
|
||||||
Files,
|
Files,
|
||||||
Search,
|
Search,
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import BaseDialog from "..//dialogs/BaseDialog"
|
import BaseDialog from "..//dialogs/BaseDialog";
|
||||||
import AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
import { getDesktopCapturerSources } from "matrix-js-sdk/src/webrtc/call";
|
import { getDesktopCapturerSources } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -44,7 +44,7 @@ export class ExistingSource extends React.Component<DesktopCapturerSourceIProps>
|
||||||
|
|
||||||
onClick = (ev) => {
|
onClick = (ev) => {
|
||||||
this.props.onSelect(this.props.source);
|
this.props.onSelect(this.props.source);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
@ -108,19 +108,19 @@ export default class DesktopCapturerSourcePicker extends React.Component<
|
||||||
|
|
||||||
onSelect = (source) => {
|
onSelect = (source) => {
|
||||||
this.props.onFinished(source);
|
this.props.onFinished(source);
|
||||||
}
|
};
|
||||||
|
|
||||||
onScreensClick = (ev) => {
|
onScreensClick = (ev) => {
|
||||||
this.setState({ selectedTab: Tabs.Screens });
|
this.setState({ selectedTab: Tabs.Screens });
|
||||||
}
|
};
|
||||||
|
|
||||||
onWindowsClick = (ev) => {
|
onWindowsClick = (ev) => {
|
||||||
this.setState({ selectedTab: Tabs.Windows });
|
this.setState({ selectedTab: Tabs.Windows });
|
||||||
}
|
};
|
||||||
|
|
||||||
onCloseClick = (ev) => {
|
onCloseClick = (ev) => {
|
||||||
this.props.onFinished(null);
|
this.props.onFinished(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let sources;
|
let sources;
|
||||||
|
|
|
@ -144,7 +144,6 @@ EditableTextContainer.propTypes = {
|
||||||
blurToSubmit: PropTypes.bool,
|
blurToSubmit: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
EditableTextContainer.defaultProps = {
|
EditableTextContainer.defaultProps = {
|
||||||
initialValue: "",
|
initialValue: "",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
import React, { FunctionComponent, useEffect, useRef } from 'react';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import ICanvasEffect from '../../../effects/ICanvasEffect';
|
import ICanvasEffect from '../../../effects/ICanvasEffect';
|
||||||
import { CHAT_EFFECTS } from '../../../effects'
|
import { CHAT_EFFECTS } from '../../../effects';
|
||||||
import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
|
import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -32,7 +32,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
||||||
if (!name) return null;
|
if (!name) return null;
|
||||||
let effect: ICanvasEffect | null = effectsRef.current[name] || null;
|
let effect: ICanvasEffect | null = effectsRef.current[name] || null;
|
||||||
if (effect === null) {
|
if (effect === null) {
|
||||||
const options = CHAT_EFFECTS.find((e) => e.command === name)?.options
|
const options = CHAT_EFFECTS.find((e) => e.command === name)?.options;
|
||||||
try {
|
try {
|
||||||
const { default: Effect } = await import(`../../../effects/${name}`);
|
const { default: Effect } = await import(`../../../effects/${name}`);
|
||||||
effect = new Effect(options);
|
effect = new Effect(options);
|
||||||
|
@ -56,7 +56,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
||||||
const effect = payload.action.substr(actionPrefix.length);
|
const effect = payload.action.substr(actionPrefix.length);
|
||||||
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current));
|
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const dispatcherRef = dis.register(onAction);
|
const dispatcherRef = dis.register(onAction);
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
canvas.height = UIStore.instance.windowHeight;
|
canvas.height = UIStore.instance.windowHeight;
|
||||||
|
@ -89,7 +89,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
||||||
right: 0,
|
right: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default EffectsOverlay;
|
export default EffectsOverlay;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,21 +14,27 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ErrorInfo } from 'react';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import PlatformPeg from '../../../PlatformPeg';
|
import PlatformPeg from '../../../PlatformPeg';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import BugReportDialog from '../dialogs/BugReportDialog';
|
||||||
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
error: Error;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This error boundary component can be used to wrap large content areas and
|
* This error boundary component can be used to wrap large content areas and
|
||||||
* catch exceptions during rendering in the component tree below them.
|
* catch exceptions during rendering in the component tree below them.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.elements.ErrorBoundary")
|
@replaceableComponent("views.elements.ErrorBoundary")
|
||||||
export default class ErrorBoundary extends React.PureComponent {
|
export default class ErrorBoundary extends React.PureComponent<{}, IState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -37,13 +43,13 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromError(error) {
|
static getDerivedStateFromError(error: Error): Partial<IState> {
|
||||||
// Side effects are not permitted here, so we only update the state so
|
// Side effects are not permitted here, so we only update the state so
|
||||||
// that the next render shows an error message.
|
// that the next render shows an error message.
|
||||||
return { error };
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error, { componentStack }) {
|
componentDidCatch(error: Error, { componentStack }: ErrorInfo): void {
|
||||||
// Browser consoles are better at formatting output when native errors are passed
|
// Browser consoles are better at formatting output when native errors are passed
|
||||||
// in their own `console.error` invocation.
|
// in their own `console.error` invocation.
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -53,7 +59,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClearCacheAndReload = () => {
|
private onClearCacheAndReload = (): void => {
|
||||||
if (!PlatformPeg.get()) return;
|
if (!PlatformPeg.get()) return;
|
||||||
|
|
||||||
MatrixClientPeg.get().stopClient();
|
MatrixClientPeg.get().stopClient();
|
||||||
|
@ -62,11 +68,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onBugReport = () => {
|
private onBugReport = (): void => {
|
||||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
|
||||||
if (!BugReportDialog) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
|
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
|
||||||
label: 'react-soft-crash',
|
label: 'react-soft-crash',
|
||||||
});
|
});
|
||||||
|
@ -74,7 +76,6 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
|
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
|
||||||
|
|
||||||
let bugReportSection;
|
let bugReportSection;
|
||||||
|
@ -95,7 +96,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
"the rooms or groups you have visited and the usernames of " +
|
"the rooms or groups you have visited and the usernames of " +
|
||||||
"other users. They do not contain messages.",
|
"other users. They do not contain messages.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<AccessibleButton onClick={this._onBugReport} kind='primary'>
|
<AccessibleButton onClick={this.onBugReport} kind='primary'>
|
||||||
{_t("Submit debug logs")}
|
{_t("Submit debug logs")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -105,7 +106,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
<div className="mx_ErrorBoundary_body">
|
<div className="mx_ErrorBoundary_body">
|
||||||
<h1>{_t("Something went wrong!")}</h1>
|
<h1>{_t("Something went wrong!")}</h1>
|
||||||
{ bugReportSection }
|
{ bugReportSection }
|
||||||
<AccessibleButton onClick={this._onClearCacheAndReload} kind='danger'>
|
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>
|
||||||
{_t("Clear cache and reload")}
|
{_t("Clear cache and reload")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {ReactChildren, useEffect} from 'react';
|
import React, { ReactNode, useEffect } from 'react';
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
|
@ -31,11 +31,11 @@ interface IProps {
|
||||||
// Whether or not to begin with state.expanded=true
|
// Whether or not to begin with state.expanded=true
|
||||||
startExpanded?: boolean,
|
startExpanded?: boolean,
|
||||||
// The list of room members for which to show avatars next to the summary
|
// The list of room members for which to show avatars next to the summary
|
||||||
summaryMembers?: RoomMember[],
|
summaryMembers?: RoomMember[];
|
||||||
// The text to show as the summary of this event list
|
// The text to show as the summary of this event list
|
||||||
summaryText?: string,
|
summaryText?: string;
|
||||||
// An array of EventTiles to render when expanded
|
// An array of EventTiles to render when expanded
|
||||||
children: ReactChildren,
|
children: ReactNode[];
|
||||||
// Called when the event list expansion is toggled
|
// Called when the event list expansion is toggled
|
||||||
onToggle?(): void;
|
onToggle?(): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
|
|
||||||
|
|
||||||
class FlairAvatar extends React.Component {
|
class FlairAvatar extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
|
@ -30,7 +30,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { formatFullDate } from "../../../DateUtils";
|
import { formatFullDate } from "../../../DateUtils";
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"
|
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { normalizeWheelEvent } from "../../../utils/Mouse";
|
import { normalizeWheelEvent } from "../../../utils/Mouse";
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private recalculateZoom = () => {
|
private recalculateZoom = () => {
|
||||||
this.setZoomAndRotation();
|
this.setZoomAndRotation();
|
||||||
}
|
};
|
||||||
|
|
||||||
private setZoomAndRotation = (inputRotation?: number) => {
|
private setZoomAndRotation = (inputRotation?: number) => {
|
||||||
const image = this.image.current;
|
const image = this.image.current;
|
||||||
|
@ -158,7 +158,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
rotation: rotation,
|
rotation: rotation,
|
||||||
zoom: zoom,
|
zoom: zoom,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
private zoom(delta: number) {
|
private zoom(delta: number) {
|
||||||
const newZoom = this.state.zoom + delta;
|
const newZoom = this.state.zoom + delta;
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default class InlineSpinner extends React.PureComponent<IProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
w: 16,
|
w: 16,
|
||||||
h: 16,
|
h: 16,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default class InviteReason extends React.PureComponent<IProps, IState> {
|
||||||
this.setState({
|
this.setState({
|
||||||
hidden: false,
|
hidden: false,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactChildren } from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
|
@ -26,21 +26,11 @@ import { isValid3pidInvite } from "../../../RoomInvite";
|
||||||
import EventListSummary from "./EventListSummary";
|
import EventListSummary from "./EventListSummary";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps extends Omit<ComponentProps<typeof EventListSummary>, "summaryText" | "summaryMembers"> {
|
||||||
// An array of member events to summarise
|
|
||||||
events: MatrixEvent[];
|
|
||||||
// The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
|
// The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
|
||||||
summaryLength?: number;
|
summaryLength?: number;
|
||||||
// The maximum number of avatars to display in the summary
|
// The maximum number of avatars to display in the summary
|
||||||
avatarsMaxLength?: number;
|
avatarsMaxLength?: number;
|
||||||
// The minimum number of events needed to trigger summarisation
|
|
||||||
threshold?: number,
|
|
||||||
// Whether or not to begin with state.expanded=true
|
|
||||||
startExpanded?: boolean,
|
|
||||||
// An array of EventTiles to render when expanded
|
|
||||||
children: ReactChildren;
|
|
||||||
// Called when the MELS expansion is toggled
|
|
||||||
onToggle?(): void,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUserEvents {
|
interface IUserEvents {
|
||||||
|
@ -66,6 +56,7 @@ enum TransitionType {
|
||||||
ChangedName = "changed_name",
|
ChangedName = "changed_name",
|
||||||
ChangedAvatar = "changed_avatar",
|
ChangedAvatar = "changed_avatar",
|
||||||
NoChange = "no_change",
|
NoChange = "no_change",
|
||||||
|
ServerAcl = "server_acl",
|
||||||
}
|
}
|
||||||
|
|
||||||
const SEP = ",";
|
const SEP = ",";
|
||||||
|
@ -298,6 +289,12 @@ export default class MemberEventListSummary extends React.Component<IProps> {
|
||||||
? _t("%(severalUsers)smade no changes %(count)s times", { severalUsers: "", count: repeats })
|
? _t("%(severalUsers)smade no changes %(count)s times", { severalUsers: "", count: repeats })
|
||||||
: _t("%(oneUser)smade no changes %(count)s times", { oneUser: "", count: repeats });
|
: _t("%(oneUser)smade no changes %(count)s times", { oneUser: "", count: repeats });
|
||||||
break;
|
break;
|
||||||
|
case "server_acl":
|
||||||
|
res = (userCount > 1)
|
||||||
|
? _t("%(severalUsers)schanged the server ACLs %(count)s times",
|
||||||
|
{ severalUsers: "", count: repeats })
|
||||||
|
: _t("%(oneUser)schanged the server ACLs %(count)s times", { oneUser: "", count: repeats });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -324,6 +321,10 @@ export default class MemberEventListSummary extends React.Component<IProps> {
|
||||||
return TransitionType.Invited;
|
return TransitionType.Invited;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.mxEvent.getType() === 'm.room.server_acl') {
|
||||||
|
return TransitionType.ServerAcl;
|
||||||
|
}
|
||||||
|
|
||||||
switch (e.mxEvent.getContent().membership) {
|
switch (e.mxEvent.getContent().membership) {
|
||||||
case 'invite': return TransitionType.Invited;
|
case 'invite': return TransitionType.Invited;
|
||||||
case 'ban': return TransitionType.Banned;
|
case 'ban': return TransitionType.Banned;
|
||||||
|
@ -410,19 +411,23 @@ export default class MemberEventListSummary extends React.Component<IProps> {
|
||||||
// Object mapping user IDs to an array of IUserEvents
|
// Object mapping user IDs to an array of IUserEvents
|
||||||
const userEvents: Record<string, IUserEvents[]> = {};
|
const userEvents: Record<string, IUserEvents[]> = {};
|
||||||
eventsToRender.forEach((e, index) => {
|
eventsToRender.forEach((e, index) => {
|
||||||
const userId = e.getStateKey();
|
const userId = e.getType() === 'm.room.server_acl' ? e.getSender() : e.getStateKey();
|
||||||
// Initialise a user's events
|
// Initialise a user's events
|
||||||
if (!userEvents[userId]) {
|
if (!userEvents[userId]) {
|
||||||
userEvents[userId] = [];
|
userEvents[userId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target) {
|
if (e.getType() === 'm.room.server_acl') {
|
||||||
|
latestUserAvatarMember.set(userId, e.sender);
|
||||||
|
} else if (e.target) {
|
||||||
latestUserAvatarMember.set(userId, e.target);
|
latestUserAvatarMember.set(userId, e.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
let displayName = userId;
|
let displayName = userId;
|
||||||
if (e.getType() === 'm.room.third_party_invite') {
|
if (e.getType() === 'm.room.third_party_invite') {
|
||||||
displayName = e.getContent().display_name;
|
displayName = e.getContent().display_name;
|
||||||
|
} else if (e.getType() === 'm.room.server_acl') {
|
||||||
|
displayName = e.sender.name;
|
||||||
} else if (e.target) {
|
} else if (e.target) {
|
||||||
displayName = e.target.name;
|
displayName = e.target.name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,4 +187,4 @@ export default class PersistedElement extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPersistKey = (appId: string) => 'widget_' + appId;
|
export const getPersistKey = (appId) => 'widget_' + appId;
|
||||||
|
|
|
@ -48,7 +48,7 @@ const getIcon = (brand: IdentityProviderBrand | string) => {
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const SSOButton: React.FC<ISSOButtonProps> = ({
|
const SSOButton: React.FC<ISSOButtonProps> = ({
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
|
|
@ -87,7 +87,7 @@ const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }
|
||||||
<span className="mx_ServerPicker_server">{serverName}</span>
|
<span className="mx_ServerPicker_server">{serverName}</span>
|
||||||
{ editBtn }
|
{ editBtn }
|
||||||
{ desc }
|
{ desc }
|
||||||
</div>
|
</div>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ServerPicker;
|
export default ServerPicker;
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Dropdown from "../../views/elements/Dropdown"
|
import Dropdown from "../../views/elements/Dropdown";
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import PlatformPeg from "../../../PlatformPeg";
|
import PlatformPeg from "../../../PlatformPeg";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
@ -67,8 +67,8 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
||||||
langs.push({
|
langs.push({
|
||||||
label: language,
|
label: language,
|
||||||
value: language,
|
value: language,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
this.setState({ languages: langs });
|
this.setState({ languages: langs });
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
this.setState({ languages: ['en'] });
|
this.setState({ languages: ['en'] });
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 OpenMarket Ltd
|
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Tinter from "../../../Tinter";
|
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
|
||||||
|
|
||||||
@replaceableComponent("views.elements.TintableSvg")
|
|
||||||
class TintableSvg extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
src: PropTypes.string.isRequired,
|
|
||||||
width: PropTypes.string.isRequired,
|
|
||||||
height: PropTypes.string.isRequired,
|
|
||||||
className: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
// list of currently mounted TintableSvgs
|
|
||||||
static mounts = {};
|
|
||||||
static idSequence = 0;
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.fixups = [];
|
|
||||||
|
|
||||||
this.id = TintableSvg.idSequence++;
|
|
||||||
TintableSvg.mounts[this.id] = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
delete TintableSvg.mounts[this.id];
|
|
||||||
}
|
|
||||||
|
|
||||||
tint = () => {
|
|
||||||
// TODO: only bother running this if the global tint settings have changed
|
|
||||||
// since we loaded!
|
|
||||||
Tinter.applySvgFixups(this.fixups);
|
|
||||||
};
|
|
||||||
|
|
||||||
onLoad = event => {
|
|
||||||
// console.log("TintableSvg.onLoad for " + this.props.src);
|
|
||||||
this.fixups = Tinter.calcSvgFixups([event.target]);
|
|
||||||
Tinter.applySvgFixups(this.fixups);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
|
|
||||||
type="image/svg+xml"
|
|
||||||
data={this.props.src}
|
|
||||||
width={this.props.width}
|
|
||||||
height={this.props.height}
|
|
||||||
onLoad={this.onLoad}
|
|
||||||
tabIndex="-1"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register with the Tinter so that we will be told if the tint changes
|
|
||||||
Tinter.registerTintable(function() {
|
|
||||||
if (TintableSvg.mounts) {
|
|
||||||
Object.keys(TintableSvg.mounts).forEach((id) => {
|
|
||||||
TintableSvg.mounts[id].tint();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default TintableSvg;
|
|
|
@ -17,7 +17,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import React, { Component, CSSProperties } from 'react';
|
import React, { Component, CSSProperties } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable babel/no-invalid-this */
|
/* eslint-disable @typescript-eslint/no-invalid-this */
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
|
|
@ -234,7 +234,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
||||||
className="mx_EmojiPicker_body"
|
className="mx_EmojiPicker_body"
|
||||||
wrappedRef={ref => {
|
wrappedRef={ref => {
|
||||||
// @ts-ignore - AutoHideScrollbar should accept a RefObject or fall back to its own instead
|
// @ts-ignore - AutoHideScrollbar should accept a RefObject or fall back to its own instead
|
||||||
this.bodyRef.current = ref
|
this.bodyRef.current = ref;
|
||||||
}}
|
}}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
>
|
>
|
||||||
|
|
|
@ -32,7 +32,7 @@ import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
// XXX this class copies a lot from RoomTile.js
|
// XXX this class copies a lot from RoomTile.js
|
||||||
@replaceableComponent("views.groups.GroupInviteTile")
|
@replaceableComponent("views.groups.GroupInviteTile")
|
||||||
export default class GroupInviteTile extends React.Component {
|
export default class GroupInviteTile extends React.Component {
|
||||||
static propTypes: {
|
static propTypes = {
|
||||||
group: PropTypes.object.isRequired,
|
group: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,4 +33,4 @@ const HostSignupContainer = () => {
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HostSignupContainer
|
export default HostSignupContainer;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +16,12 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { formatFullDateNoTime } from '../../../DateUtils';
|
import { formatFullDateNoTime } from '../../../DateUtils';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
function getdaysArray() {
|
function getDaysArray(): string[] {
|
||||||
return [
|
return [
|
||||||
_t('Sunday'),
|
_t('Sunday'),
|
||||||
_t('Monday'),
|
_t('Monday'),
|
||||||
|
@ -33,17 +33,17 @@ function getdaysArray() {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.messages.DateSeparator")
|
interface IProps {
|
||||||
export default class DateSeparator extends React.Component {
|
ts: number;
|
||||||
static propTypes = {
|
}
|
||||||
ts: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
getLabel() {
|
@replaceableComponent("views.messages.DateSeparator")
|
||||||
|
export default class DateSeparator extends React.Component<IProps> {
|
||||||
|
private getLabel() {
|
||||||
const date = new Date(this.props.ts);
|
const date = new Date(this.props.ts);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const yesterday = new Date();
|
const yesterday = new Date();
|
||||||
const days = getdaysArray();
|
const days = getDaysArray();
|
||||||
yesterday.setDate(today.getDate() - 1);
|
yesterday.setDate(today.getDate() - 1);
|
||||||
|
|
||||||
if (date.toDateString() === today.toDateString()) {
|
if (date.toDateString() === today.toDateString()) {
|
|
@ -51,7 +51,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
|
||||||
decryptedThumbnailUrl: null,
|
decryptedThumbnailUrl: null,
|
||||||
decryptedBlob: null,
|
decryptedBlob: null,
|
||||||
error: null,
|
error: null,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbScale(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number) {
|
thumbScale(fullWidth: number, fullHeight: number, thumbWidth: number, thumbHeight: number) {
|
||||||
|
@ -182,7 +182,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
|
||||||
this.videoRef.current.play();
|
this.videoRef.current.play();
|
||||||
});
|
});
|
||||||
this.props.onHeightChanged();
|
this.props.onHeightChanged();
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const content = this.props.mxEvent.getContent();
|
const content = this.props.mxEvent.getContent();
|
||||||
|
|
|
@ -106,6 +106,6 @@ export default class MVoiceMessageBody extends React.PureComponent<IProps, IStat
|
||||||
<RecordingPlayback playback={this.state.playback} />
|
<RecordingPlayback playback={this.state.playback} />
|
||||||
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
|
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||||
private onDecrypted = () => {
|
private onDecrypted = () => {
|
||||||
// Decryption changes whether the event is actionable
|
// Decryption changes whether the event is actionable
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
};
|
||||||
|
|
||||||
private onReactionsChange = () => {
|
private onReactionsChange = () => {
|
||||||
// TODO: Call `onHeightChanged` as needed
|
// TODO: Call `onHeightChanged` as needed
|
||||||
|
@ -136,7 +136,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||||
// has changed (this is triggered by events for that purpose only) and
|
// has changed (this is triggered by events for that purpose only) and
|
||||||
// `PureComponent`s shallow state / props compare would otherwise filter this out.
|
// `PureComponent`s shallow state / props compare would otherwise filter this out.
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
};
|
||||||
|
|
||||||
private getMyReactions() {
|
private getMyReactions() {
|
||||||
const reactions = this.props.reactions;
|
const reactions = this.props.reactions;
|
||||||
|
@ -155,7 +155,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||||
this.setState({
|
this.setState({
|
||||||
showAll: true,
|
showAll: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { mxEvent, reactions } = this.props;
|
const { mxEvent, reactions } = this.props;
|
||||||
|
|
|
@ -79,13 +79,13 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
|
||||||
tooltipRendered: true,
|
tooltipRendered: true,
|
||||||
tooltipVisible: true,
|
tooltipVisible: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onMouseLeave = () => {
|
onMouseLeave = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
tooltipVisible: false,
|
tooltipVisible: false,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { mxEvent, content, count, reactionEvents, myReactionEvent } = this.props;
|
const { mxEvent, content, count, reactionEvents, myReactionEvent } = this.props;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue