Merge pull request #6869 from SimonBrandner/task/src-ts
Convert `/src` to TS
This commit is contained in:
commit
fe0a68b71e
20 changed files with 335 additions and 279 deletions
|
@ -25,9 +25,9 @@
|
||||||
"bin": {
|
"bin": {
|
||||||
"reskindex": "scripts/reskindex.js"
|
"reskindex": "scripts/reskindex.js"
|
||||||
},
|
},
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.ts",
|
||||||
"matrix_src_main": "./src/index.js",
|
"matrix_src_main": "./src/index.ts",
|
||||||
"matrix_lib_main": "./lib/index.js",
|
"matrix_lib_main": "./lib/index.ts",
|
||||||
"matrix_lib_typings": "./lib/index.d.ts",
|
"matrix_lib_typings": "./lib/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
|
|
6
src/@types/global.d.ts
vendored
6
src/@types/global.d.ts
vendored
|
@ -51,6 +51,7 @@ import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||||
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
||||||
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
|
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
|
||||||
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
||||||
|
import { Skinner } from "../Skinner";
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
|
@ -95,6 +96,7 @@ declare global {
|
||||||
mxSetupEncryptionStore?: SetupEncryptionStore;
|
mxSetupEncryptionStore?: SetupEncryptionStore;
|
||||||
mxRoomScrollStateStore?: RoomScrollStateStore;
|
mxRoomScrollStateStore?: RoomScrollStateStore;
|
||||||
mxActiveWidgetStore?: ActiveWidgetStore;
|
mxActiveWidgetStore?: ActiveWidgetStore;
|
||||||
|
mxSkinner?: Skinner;
|
||||||
mxOnRecaptchaLoaded?: () => void;
|
mxOnRecaptchaLoaded?: () => void;
|
||||||
electron?: Electron;
|
electron?: Electron;
|
||||||
}
|
}
|
||||||
|
@ -157,6 +159,10 @@ declare global {
|
||||||
setSinkId(outputId: string);
|
setSinkId(outputId: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HTMLStyleElement {
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// Add Chrome-specific `instant` ScrollBehaviour
|
// Add Chrome-specific `instant` ScrollBehaviour
|
||||||
type _ScrollBehavior = ScrollBehavior | "instant";
|
type _ScrollBehavior = ScrollBehavior | "instant";
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import * as sdk from './index';
|
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import IdentityAuthClient from './IdentityAuthClient';
|
import IdentityAuthClient from './IdentityAuthClient';
|
||||||
import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
|
import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents";
|
||||||
|
import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk";
|
||||||
|
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
|
||||||
|
|
||||||
function getIdServerDomain() {
|
function getIdServerDomain(): string {
|
||||||
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,10 +41,13 @@ function getIdServerDomain() {
|
||||||
* https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928
|
* https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928
|
||||||
*/
|
*/
|
||||||
export default class AddThreepid {
|
export default class AddThreepid {
|
||||||
|
private sessionId: string;
|
||||||
|
private submitUrl: string;
|
||||||
|
private clientSecret: string;
|
||||||
|
private bind: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.clientSecret = MatrixClientPeg.get().generateClientSecret();
|
this.clientSecret = MatrixClientPeg.get().generateClientSecret();
|
||||||
this.sessionId = null;
|
|
||||||
this.submitUrl = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,7 +56,7 @@ export default class AddThreepid {
|
||||||
* @param {string} emailAddress The email address to add
|
* @param {string} emailAddress The email address to add
|
||||||
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
||||||
*/
|
*/
|
||||||
addEmailAddress(emailAddress) {
|
public addEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
|
||||||
return MatrixClientPeg.get().requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1).then((res) => {
|
return MatrixClientPeg.get().requestAdd3pidEmailToken(emailAddress, this.clientSecret, 1).then((res) => {
|
||||||
this.sessionId = res.sid;
|
this.sessionId = res.sid;
|
||||||
return res;
|
return res;
|
||||||
|
@ -72,7 +76,7 @@ export default class AddThreepid {
|
||||||
* @param {string} emailAddress The email address to add
|
* @param {string} emailAddress The email address to add
|
||||||
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
||||||
*/
|
*/
|
||||||
async bindEmailAddress(emailAddress) {
|
public async bindEmailAddress(emailAddress: string): Promise<IRequestTokenResponse> {
|
||||||
this.bind = true;
|
this.bind = true;
|
||||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||||
// For separate bind, request a token directly from the IS.
|
// For separate bind, request a token directly from the IS.
|
||||||
|
@ -105,7 +109,7 @@ export default class AddThreepid {
|
||||||
* @param {string} phoneNumber The national or international formatted phone number to add
|
* @param {string} phoneNumber The national or international formatted phone number to add
|
||||||
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
|
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
|
||||||
*/
|
*/
|
||||||
addMsisdn(phoneCountry, phoneNumber) {
|
public addMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
|
||||||
return MatrixClientPeg.get().requestAdd3pidMsisdnToken(
|
return MatrixClientPeg.get().requestAdd3pidMsisdnToken(
|
||||||
phoneCountry, phoneNumber, this.clientSecret, 1,
|
phoneCountry, phoneNumber, this.clientSecret, 1,
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
|
@ -129,7 +133,7 @@ export default class AddThreepid {
|
||||||
* @param {string} phoneNumber The national or international formatted phone number to add
|
* @param {string} phoneNumber The national or international formatted phone number to add
|
||||||
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
|
* @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken().
|
||||||
*/
|
*/
|
||||||
async bindMsisdn(phoneCountry, phoneNumber) {
|
public async bindMsisdn(phoneCountry: string, phoneNumber: string): Promise<IRequestMsisdnTokenResponse> {
|
||||||
this.bind = true;
|
this.bind = true;
|
||||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||||
// For separate bind, request a token directly from the IS.
|
// For separate bind, request a token directly from the IS.
|
||||||
|
@ -161,7 +165,7 @@ export default class AddThreepid {
|
||||||
* with a "message" property which contains a human-readable message detailing why
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
* the request failed.
|
* the request failed.
|
||||||
*/
|
*/
|
||||||
async checkEmailLinkClicked() {
|
public async checkEmailLinkClicked(): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||||
if (this.bind) {
|
if (this.bind) {
|
||||||
|
@ -175,7 +179,7 @@ export default class AddThreepid {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await this._makeAddThreepidOnlyRequest();
|
await this.makeAddThreepidOnlyRequest();
|
||||||
|
|
||||||
// The spec has always required this to use UI auth but synapse briefly
|
// The spec has always required this to use UI auth but synapse briefly
|
||||||
// implemented it without, so this may just succeed and that's OK.
|
// implemented it without, so this may just succeed and that's OK.
|
||||||
|
@ -186,9 +190,6 @@ export default class AddThreepid {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pop up an interactive auth dialog
|
|
||||||
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"),
|
||||||
|
@ -208,7 +209,7 @@ export default class AddThreepid {
|
||||||
title: _t("Add Email Address"),
|
title: _t("Add Email Address"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: e.data,
|
||||||
makeRequest: this._makeAddThreepidOnlyRequest,
|
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||||
aestheticsForStagePhases: {
|
aestheticsForStagePhases: {
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
@ -235,16 +236,16 @@ export default class AddThreepid {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} auth UI auth object
|
* @param {{type: string, session?: string}} auth UI auth object
|
||||||
* @return {Promise<Object>} Response from /3pid/add call (in current spec, an empty object)
|
* @return {Promise<Object>} Response from /3pid/add call (in current spec, an empty object)
|
||||||
*/
|
*/
|
||||||
_makeAddThreepidOnlyRequest = (auth) => {
|
private makeAddThreepidOnlyRequest = (auth?: {type: string, session?: string}): Promise<{}> => {
|
||||||
return MatrixClientPeg.get().addThreePidOnly({
|
return MatrixClientPeg.get().addThreePidOnly({
|
||||||
sid: this.sessionId,
|
sid: this.sessionId,
|
||||||
client_secret: this.clientSecret,
|
client_secret: this.clientSecret,
|
||||||
auth,
|
auth,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a phone number verification code as entered by the user and validates
|
* Takes a phone number verification code as entered by the user and validates
|
||||||
|
@ -254,7 +255,7 @@ export default class AddThreepid {
|
||||||
* with a "message" property which contains a human-readable message detailing why
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
* the request failed.
|
* the request failed.
|
||||||
*/
|
*/
|
||||||
async haveMsisdnToken(msisdnToken) {
|
public async haveMsisdnToken(msisdnToken: string): Promise<any[]> {
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const supportsSeparateAddAndBind =
|
const supportsSeparateAddAndBind =
|
||||||
await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind();
|
await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind();
|
||||||
|
@ -291,7 +292,7 @@ export default class AddThreepid {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await this._makeAddThreepidOnlyRequest();
|
await this.makeAddThreepidOnlyRequest();
|
||||||
|
|
||||||
// The spec has always required this to use UI auth but synapse briefly
|
// The spec has always required this to use UI auth but synapse briefly
|
||||||
// implemented it without, so this may just succeed and that's OK.
|
// implemented it without, so this may just succeed and that's OK.
|
||||||
|
@ -302,9 +303,6 @@ export default class AddThreepid {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pop up an interactive auth dialog
|
|
||||||
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"),
|
||||||
|
@ -324,7 +322,7 @@ export default class AddThreepid {
|
||||||
title: _t("Add Phone Number"),
|
title: _t("Add Phone Number"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
authData: e.data,
|
authData: e.data,
|
||||||
makeRequest: this._makeAddThreepidOnlyRequest,
|
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||||
aestheticsForStagePhases: {
|
aestheticsForStagePhases: {
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
|
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
|
||||||
import { createClient } from 'matrix-js-sdk/src/matrix';
|
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
|
import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
|
||||||
import {
|
import {
|
||||||
|
@ -27,23 +27,25 @@ import {
|
||||||
doesIdentityServerHaveTerms,
|
doesIdentityServerHaveTerms,
|
||||||
useDefaultIdentityServer,
|
useDefaultIdentityServer,
|
||||||
} from './utils/IdentityServerUtils';
|
} from './utils/IdentityServerUtils';
|
||||||
import { abbreviateUrl } from './utils/UrlUtils';
|
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||||
|
import { abbreviateUrl } from "./utils/UrlUtils";
|
||||||
|
|
||||||
export class AbortedIdentityActionError extends Error {}
|
export class AbortedIdentityActionError extends Error {}
|
||||||
|
|
||||||
export default class IdentityAuthClient {
|
export default class IdentityAuthClient {
|
||||||
|
private accessToken: string;
|
||||||
|
private tempClient: MatrixClient;
|
||||||
|
private authEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new identity auth client
|
* Creates a new identity auth client
|
||||||
* @param {string} identityUrl The URL to contact the identity server with.
|
* @param {string} identityUrl The URL to contact the identity server with.
|
||||||
* When provided, this class will operate solely within memory, refusing to
|
* When provided, this class will operate solely within memory, refusing to
|
||||||
* persist any information such as tokens. Default null (not provided).
|
* persist any information such as tokens. Default null (not provided).
|
||||||
*/
|
*/
|
||||||
constructor(identityUrl = null) {
|
constructor(identityUrl?: string) {
|
||||||
this.accessToken = null;
|
|
||||||
this.authEnabled = true;
|
|
||||||
|
|
||||||
if (identityUrl) {
|
if (identityUrl) {
|
||||||
// XXX: We shouldn't have to create a whole new MatrixClient just to
|
// XXX: We shouldn't have to create a whole new MatrixClient just to
|
||||||
// do identity server auth. The functions don't take an identity URL
|
// do identity server auth. The functions don't take an identity URL
|
||||||
|
@ -54,32 +56,29 @@ export default class IdentityAuthClient {
|
||||||
baseUrl: "", // invalid by design
|
baseUrl: "", // invalid by design
|
||||||
idBaseUrl: identityUrl,
|
idBaseUrl: identityUrl,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Indicates that we're using the real client, not some workaround.
|
|
||||||
this.tempClient = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get _matrixClient() {
|
private get matrixClient(): MatrixClient {
|
||||||
return this.tempClient ? this.tempClient : MatrixClientPeg.get();
|
return this.tempClient ? this.tempClient : MatrixClientPeg.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
_writeToken() {
|
private writeToken(): void {
|
||||||
if (this.tempClient) return; // temporary client: ignore
|
if (this.tempClient) return; // temporary client: ignore
|
||||||
window.localStorage.setItem("mx_is_access_token", this.accessToken);
|
window.localStorage.setItem("mx_is_access_token", this.accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
_readToken() {
|
private readToken(): string {
|
||||||
if (this.tempClient) return null; // temporary client: ignore
|
if (this.tempClient) return null; // temporary client: ignore
|
||||||
return window.localStorage.getItem("mx_is_access_token");
|
return window.localStorage.getItem("mx_is_access_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCredentials() {
|
public hasCredentials(): boolean {
|
||||||
return this.accessToken != null; // undef or null
|
return Boolean(this.accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a promise that resolves to the access_token string from the IS
|
// Returns a promise that resolves to the access_token string from the IS
|
||||||
async getAccessToken({ check = true } = {}) {
|
public async getAccessToken({ check = true } = {}): Promise<string> {
|
||||||
if (!this.authEnabled) {
|
if (!this.authEnabled) {
|
||||||
// The current IS doesn't support authentication
|
// The current IS doesn't support authentication
|
||||||
return null;
|
return null;
|
||||||
|
@ -87,21 +86,21 @@ export default class IdentityAuthClient {
|
||||||
|
|
||||||
let token = this.accessToken;
|
let token = this.accessToken;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = this._readToken();
|
token = this.readToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = await this.registerForToken(check);
|
token = await this.registerForToken(check);
|
||||||
if (token) {
|
if (token) {
|
||||||
this.accessToken = token;
|
this.accessToken = token;
|
||||||
this._writeToken();
|
this.writeToken();
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check) {
|
if (check) {
|
||||||
try {
|
try {
|
||||||
await this._checkToken(token);
|
await this.checkToken(token);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (
|
if (
|
||||||
e instanceof TermsNotSignedError ||
|
e instanceof TermsNotSignedError ||
|
||||||
|
@ -114,7 +113,7 @@ export default class IdentityAuthClient {
|
||||||
token = await this.registerForToken();
|
token = await this.registerForToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
this.accessToken = token;
|
this.accessToken = token;
|
||||||
this._writeToken();
|
this.writeToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,11 +121,11 @@ export default class IdentityAuthClient {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _checkToken(token) {
|
private async checkToken(token: string): Promise<void> {
|
||||||
const identityServerUrl = this._matrixClient.getIdentityServerUrl();
|
const identityServerUrl = this.matrixClient.getIdentityServerUrl();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this._matrixClient.getIdentityAccount(token);
|
await this.matrixClient.getIdentityAccount(token);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.errcode === "M_TERMS_NOT_SIGNED") {
|
if (e.errcode === "M_TERMS_NOT_SIGNED") {
|
||||||
logger.log("Identity server requires new terms to be agreed to");
|
logger.log("Identity server requires new terms to be agreed to");
|
||||||
|
@ -145,8 +144,8 @@ export default class IdentityAuthClient {
|
||||||
!doesAccountDataHaveIdentityServer() &&
|
!doesAccountDataHaveIdentityServer() &&
|
||||||
!(await doesIdentityServerHaveTerms(identityServerUrl))
|
!(await doesIdentityServerHaveTerms(identityServerUrl))
|
||||||
) {
|
) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const { finished } = Modal.createTrackedDialog(
|
||||||
const { finished } = Modal.createTrackedDialog('Default identity server terms warning', '',
|
'Default identity server terms warning', '',
|
||||||
QuestionDialog, {
|
QuestionDialog, {
|
||||||
title: _t("Identity server has no terms of service"),
|
title: _t("Identity server has no terms of service"),
|
||||||
description: (
|
description: (
|
||||||
|
@ -184,13 +183,13 @@ export default class IdentityAuthClient {
|
||||||
// See also https://github.com/vector-im/element-web/issues/10455.
|
// See also https://github.com/vector-im/element-web/issues/10455.
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerForToken(check=true) {
|
public async registerForToken(check = true): Promise<string> {
|
||||||
const hsOpenIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
const hsOpenIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
// XXX: The spec is `token`, but we used `access_token` for a Sydent release.
|
// XXX: The spec is `token`, but we used `access_token` for a Sydent release.
|
||||||
const { access_token: accessToken, token } =
|
const { access_token: accessToken, token } =
|
||||||
await this._matrixClient.registerWithIdentityServer(hsOpenIdToken);
|
await this.matrixClient.registerWithIdentityServer(hsOpenIdToken);
|
||||||
const identityAccessToken = token ? token : accessToken;
|
const identityAccessToken = token ? token : accessToken;
|
||||||
if (check) await this._checkToken(identityAccessToken);
|
if (check) await this.checkToken(identityAccessToken);
|
||||||
return identityAccessToken;
|
return identityAccessToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,21 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDom from "react-dom";
|
import ReactDom from "react-dom";
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
interface IChildProps {
|
||||||
|
style: React.CSSProperties;
|
||||||
|
ref: (node: React.ReactInstance) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
// either a list of child nodes, or a single child.
|
||||||
|
children: React.ReactNode;
|
||||||
|
|
||||||
|
// optional transition information for changing existing children
|
||||||
|
transition?: object;
|
||||||
|
|
||||||
|
// a list of state objects to apply to each child node in turn
|
||||||
|
startStyles: React.CSSProperties[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The NodeAnimator contains components and animates transitions.
|
* The NodeAnimator contains components and animates transitions.
|
||||||
|
@ -9,55 +24,45 @@ import PropTypes from 'prop-types';
|
||||||
* from DOM order. This makes it a lot simpler and lighter: if you need fully
|
* from DOM order. This makes it a lot simpler and lighter: if you need fully
|
||||||
* automatic positional animation, look at react-shuffle or similar libraries.
|
* automatic positional animation, look at react-shuffle or similar libraries.
|
||||||
*/
|
*/
|
||||||
export default class NodeAnimator extends React.Component {
|
export default class NodeAnimator extends React.Component<IProps> {
|
||||||
static propTypes = {
|
private nodes = {};
|
||||||
// either a list of child nodes, or a single child.
|
private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> };
|
||||||
children: PropTypes.any,
|
public static defaultProps: Partial<IProps> = {
|
||||||
|
|
||||||
// optional transition information for changing existing children
|
|
||||||
transition: PropTypes.object,
|
|
||||||
|
|
||||||
// a list of state objects to apply to each child node in turn
|
|
||||||
startStyles: PropTypes.array,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
startStyles: [],
|
startStyles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.nodes = {};
|
this.updateChildren(this.props.children);
|
||||||
this._updateChildren(this.props.children);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
public componentDidUpdate(): void {
|
||||||
this._updateChildren(this.props.children);
|
this.updateChildren(this.props.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} node element to apply styles to
|
* @param {HTMLElement} node element to apply styles to
|
||||||
* @param {object} styles a key/value pair of CSS properties
|
* @param {React.CSSProperties} styles a key/value pair of CSS properties
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_applyStyles(node, styles) {
|
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
|
||||||
Object.entries(styles).forEach(([property, value]) => {
|
Object.entries(styles).forEach(([property, value]) => {
|
||||||
node.style[property] = value;
|
node.style[property] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateChildren(newChildren) {
|
private updateChildren(newChildren: React.ReactNode): void {
|
||||||
const oldChildren = this.children || {};
|
const oldChildren = this.children || {};
|
||||||
this.children = {};
|
this.children = {};
|
||||||
React.Children.toArray(newChildren).forEach((c) => {
|
React.Children.toArray(newChildren).forEach((c: any) => {
|
||||||
if (oldChildren[c.key]) {
|
if (oldChildren[c.key]) {
|
||||||
const old = oldChildren[c.key];
|
const old = oldChildren[c.key];
|
||||||
const oldNode = ReactDom.findDOMNode(this.nodes[old.key]);
|
const oldNode = ReactDom.findDOMNode(this.nodes[old.key]);
|
||||||
|
|
||||||
if (oldNode && oldNode.style.left !== c.props.style.left) {
|
if (oldNode && (oldNode as HTMLElement).style.left !== c.props.style.left) {
|
||||||
this._applyStyles(oldNode, { left: c.props.style.left });
|
this.applyStyles(oldNode as HTMLElement, { left: c.props.style.left });
|
||||||
// console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left);
|
// console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left);
|
||||||
}
|
}
|
||||||
// clone the old element with the props (and children) of the new element
|
// clone the old element with the props (and children) of the new element
|
||||||
|
@ -66,7 +71,7 @@ export default class NodeAnimator extends React.Component {
|
||||||
} else {
|
} else {
|
||||||
// new element. If we have a startStyle, use that as the style and go through
|
// new element. If we have a startStyle, use that as the style and go through
|
||||||
// the enter animations
|
// the enter animations
|
||||||
const newProps = {};
|
const newProps: Partial<IChildProps> = {};
|
||||||
const restingStyle = c.props.style;
|
const restingStyle = c.props.style;
|
||||||
|
|
||||||
const startStyles = this.props.startStyles;
|
const startStyles = this.props.startStyles;
|
||||||
|
@ -76,7 +81,7 @@ export default class NodeAnimator extends React.Component {
|
||||||
// console.log("mounted@startstyle0: "+JSON.stringify(startStyle));
|
// console.log("mounted@startstyle0: "+JSON.stringify(startStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
newProps.ref = ((n) => this._collectNode(
|
newProps.ref = ((n) => this.collectNode(
|
||||||
c.key, n, restingStyle,
|
c.key, n, restingStyle,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -85,7 +90,7 @@ export default class NodeAnimator extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectNode(k, node, restingStyle) {
|
private collectNode(k: string, node: React.ReactInstance, restingStyle: React.CSSProperties): void {
|
||||||
if (
|
if (
|
||||||
node &&
|
node &&
|
||||||
this.nodes[k] === undefined &&
|
this.nodes[k] === undefined &&
|
||||||
|
@ -96,7 +101,7 @@ export default class NodeAnimator extends React.Component {
|
||||||
// start from startStyle 1: 0 is the one we gave it
|
// start from startStyle 1: 0 is the one we gave it
|
||||||
// to start with, so now we animate 1 etc.
|
// to start with, so now we animate 1 etc.
|
||||||
for (let i = 1; i < startStyles.length; ++i) {
|
for (let i = 1; i < startStyles.length; ++i) {
|
||||||
this._applyStyles(domNode, startStyles[i]);
|
this.applyStyles(domNode as HTMLElement, startStyles[i]);
|
||||||
// console.log("start:"
|
// console.log("start:"
|
||||||
// JSON.stringify(startStyles[i]),
|
// JSON.stringify(startStyles[i]),
|
||||||
// );
|
// );
|
||||||
|
@ -104,7 +109,7 @@ export default class NodeAnimator extends React.Component {
|
||||||
|
|
||||||
// and then we animate to the resting state
|
// and then we animate to the resting state
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this._applyStyles(domNode, restingStyle);
|
this.applyStyles(domNode as HTMLElement, restingStyle);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// console.log("enter:",
|
// console.log("enter:",
|
||||||
|
@ -113,7 +118,7 @@ export default class NodeAnimator extends React.Component {
|
||||||
this.nodes[k] = node;
|
this.nodes[k] = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>{ Object.values(this.children) }</>
|
<>{ Object.values(this.children) }</>
|
||||||
);
|
);
|
|
@ -16,11 +16,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** The types of page which can be shown by the LoggedInView */
|
/** The types of page which can be shown by the LoggedInView */
|
||||||
export default {
|
enum PageType {
|
||||||
HomePage: "home_page",
|
HomePage = "home_page",
|
||||||
RoomView: "room_view",
|
RoomView = "room_view",
|
||||||
RoomDirectory: "room_directory",
|
RoomDirectory = "room_directory",
|
||||||
UserView: "user_view",
|
UserView = "user_view",
|
||||||
GroupView: "group_view",
|
GroupView = "group_view",
|
||||||
MyGroups: "my_groups",
|
MyGroups = "my_groups",
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export default PageType;
|
|
@ -20,10 +20,11 @@ limitations under the License.
|
||||||
* registration code.
|
* registration code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||||
|
|
||||||
// Regex for what a "safe" or "Matrix-looking" localpart would be.
|
// Regex for what a "safe" or "Matrix-looking" localpart would be.
|
||||||
// TODO: Update as needed for https://github.com/matrix-org/matrix-doc/issues/1514
|
// TODO: Update as needed for https://github.com/matrix-org/matrix-doc/issues/1514
|
||||||
|
@ -41,9 +42,11 @@ export const SAFE_LOCALPART_REGEX = /^[a-z0-9=_\-./]+$/;
|
||||||
* @param {bool} options.screen_after
|
* @param {bool} options.screen_after
|
||||||
* If present the screen to redirect to after a successful login or register.
|
* If present the screen to redirect to after a successful login or register.
|
||||||
*/
|
*/
|
||||||
export async function startAnyRegistrationFlow(options) {
|
export async function startAnyRegistrationFlow(
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
options: { go_home_on_cancel?: boolean, go_welcome_on_cancel?: boolean, screen_after?: boolean},
|
||||||
|
): Promise<void> {
|
||||||
if (options === undefined) options = {};
|
if (options === undefined) options = {};
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, {
|
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, {
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
quitOnly: true,
|
quitOnly: true,
|
|
@ -17,27 +17,31 @@ limitations under the License.
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor';
|
import { PushProcessor } from 'matrix-js-sdk/src/pushprocessor';
|
||||||
|
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { IAnnotatedPushRule, PushRuleKind } from "matrix-js-sdk/src/@types/PushRules";
|
||||||
|
|
||||||
export const ALL_MESSAGES_LOUD = 'all_messages_loud';
|
export enum RoomNotifState {
|
||||||
export const ALL_MESSAGES = 'all_messages';
|
AllMessagesLoud = 'all_messages_loud',
|
||||||
export const MENTIONS_ONLY = 'mentions_only';
|
AllMessages = 'all_messages',
|
||||||
export const MUTE = 'mute';
|
MentionsOnly = 'mentions_only',
|
||||||
|
Mute = 'mute',
|
||||||
|
}
|
||||||
|
|
||||||
export const BADGE_STATES = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
|
export const BADGE_STATES = [RoomNotifState.AllMessages, RoomNotifState.AllMessagesLoud];
|
||||||
export const MENTION_BADGE_STATES = [...BADGE_STATES, MENTIONS_ONLY];
|
export const MENTION_BADGE_STATES = [...BADGE_STATES, RoomNotifState.MentionsOnly];
|
||||||
|
|
||||||
export function shouldShowNotifBadge(roomNotifState) {
|
export function shouldShowNotifBadge(roomNotifState: RoomNotifState): boolean {
|
||||||
return BADGE_STATES.includes(roomNotifState);
|
return BADGE_STATES.includes(roomNotifState);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldShowMentionBadge(roomNotifState) {
|
export function shouldShowMentionBadge(roomNotifState: RoomNotifState): boolean {
|
||||||
return MENTION_BADGE_STATES.includes(roomNotifState);
|
return MENTION_BADGE_STATES.includes(roomNotifState);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function aggregateNotificationCount(rooms) {
|
export function aggregateNotificationCount(rooms: Room[]): {count: number, highlight: boolean} {
|
||||||
return rooms.reduce((result, room) => {
|
return rooms.reduce<{count: number, highlight: boolean}>((result, room) => {
|
||||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
const highlight = room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0;
|
||||||
// use helper method to include highlights in the previous version of the room
|
// use helper method to include highlights in the previous version of the room
|
||||||
const notificationCount = getUnreadNotificationCount(room);
|
const notificationCount = getUnreadNotificationCount(room);
|
||||||
|
|
||||||
|
@ -55,9 +59,9 @@ export function aggregateNotificationCount(rooms) {
|
||||||
}, { count: 0, highlight: false });
|
}, { count: 0, highlight: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoomHasBadge(room) {
|
export function getRoomHasBadge(room: Room): boolean {
|
||||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
const highlight = room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0;
|
||||||
const notificationCount = room.getUnreadNotificationCount();
|
const notificationCount = room.getUnreadNotificationCount();
|
||||||
|
|
||||||
const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState);
|
const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState);
|
||||||
|
@ -66,14 +70,14 @@ export function getRoomHasBadge(room) {
|
||||||
return notifBadges || mentionBadges;
|
return notifBadges || mentionBadges;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoomNotifsState(roomId) {
|
export function getRoomNotifsState(roomId: string): RoomNotifState {
|
||||||
if (MatrixClientPeg.get().isGuest()) return ALL_MESSAGES;
|
if (MatrixClientPeg.get().isGuest()) return RoomNotifState.AllMessages;
|
||||||
|
|
||||||
// look through the override rules for a rule affecting this room:
|
// look through the override rules for a rule affecting this room:
|
||||||
// if one exists, it will take precedence.
|
// if one exists, it will take precedence.
|
||||||
const muteRule = findOverrideMuteRule(roomId);
|
const muteRule = findOverrideMuteRule(roomId);
|
||||||
if (muteRule) {
|
if (muteRule) {
|
||||||
return MUTE;
|
return RoomNotifState.Mute;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for everything else, look at the room rule.
|
// for everything else, look at the room rule.
|
||||||
|
@ -89,27 +93,27 @@ export function getRoomNotifsState(roomId) {
|
||||||
// XXX: We have to assume the default is to notify for all messages
|
// XXX: We have to assume the default is to notify for all messages
|
||||||
// (in particular this will be 'wrong' for one to one rooms because
|
// (in particular this will be 'wrong' for one to one rooms because
|
||||||
// they will notify loudly for all messages)
|
// they will notify loudly for all messages)
|
||||||
if (!roomRule || !roomRule.enabled) return ALL_MESSAGES;
|
if (!roomRule || !roomRule.enabled) return RoomNotifState.AllMessages;
|
||||||
|
|
||||||
// a mute at the room level will still allow mentions
|
// a mute at the room level will still allow mentions
|
||||||
// to notify
|
// to notify
|
||||||
if (isMuteRule(roomRule)) return MENTIONS_ONLY;
|
if (isMuteRule(roomRule)) return RoomNotifState.MentionsOnly;
|
||||||
|
|
||||||
const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions);
|
const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions);
|
||||||
if (actionsObject.tweaks.sound) return ALL_MESSAGES_LOUD;
|
if (actionsObject.tweaks.sound) return RoomNotifState.AllMessagesLoud;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setRoomNotifsState(roomId, newState) {
|
export function setRoomNotifsState(roomId: string, newState: RoomNotifState): Promise<void> {
|
||||||
if (newState === MUTE) {
|
if (newState === RoomNotifState.Mute) {
|
||||||
return setRoomNotifsStateMuted(roomId);
|
return setRoomNotifsStateMuted(roomId);
|
||||||
} else {
|
} else {
|
||||||
return setRoomNotifsStateUnmuted(roomId, newState);
|
return setRoomNotifsStateUnmuted(roomId, newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUnreadNotificationCount(room, type=null) {
|
export function getUnreadNotificationCount(room: Room, type: NotificationCountType = null): number {
|
||||||
let notificationCount = room.getUnreadNotificationCount(type);
|
let notificationCount = room.getUnreadNotificationCount(type);
|
||||||
|
|
||||||
// Check notification counts in the old room just in case there's some lost
|
// Check notification counts in the old room just in case there's some lost
|
||||||
|
@ -124,21 +128,21 @@ export function getUnreadNotificationCount(room, type=null) {
|
||||||
// notifying the user for unread messages because they would have extreme
|
// notifying the user for unread messages because they would have extreme
|
||||||
// difficulty changing their notification preferences away from "All Messages"
|
// difficulty changing their notification preferences away from "All Messages"
|
||||||
// and "Noisy".
|
// and "Noisy".
|
||||||
notificationCount += oldRoom.getUnreadNotificationCount("highlight");
|
notificationCount += oldRoom.getUnreadNotificationCount(NotificationCountType.Highlight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notificationCount;
|
return notificationCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRoomNotifsStateMuted(roomId) {
|
function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
// delete the room rule
|
// delete the room rule
|
||||||
const roomRule = cli.getRoomPushRule('global', roomId);
|
const roomRule = cli.getRoomPushRule('global', roomId);
|
||||||
if (roomRule) {
|
if (roomRule) {
|
||||||
promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id));
|
promises.push(cli.deletePushRule('global', PushRuleKind.RoomSpecific, roomRule.rule_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add/replace an override rule to squelch everything in this room
|
// add/replace an override rule to squelch everything in this room
|
||||||
|
@ -146,7 +150,7 @@ function setRoomNotifsStateMuted(roomId) {
|
||||||
// is an override rule, not a room rule: it still pertains to this room
|
// is an override rule, not a room rule: it still pertains to this room
|
||||||
// though, so using the room ID as the rule ID is logical and prevents
|
// though, so using the room ID as the rule ID is logical and prevents
|
||||||
// duplicate copies of the rule.
|
// duplicate copies of the rule.
|
||||||
promises.push(cli.addPushRule('global', 'override', roomId, {
|
promises.push(cli.addPushRule('global', PushRuleKind.Override, roomId, {
|
||||||
conditions: [
|
conditions: [
|
||||||
{
|
{
|
||||||
kind: 'event_match',
|
kind: 'event_match',
|
||||||
|
@ -162,30 +166,30 @@ function setRoomNotifsStateMuted(roomId) {
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRoomNotifsStateUnmuted(roomId, newState) {
|
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
const overrideMuteRule = findOverrideMuteRule(roomId);
|
const overrideMuteRule = findOverrideMuteRule(roomId);
|
||||||
if (overrideMuteRule) {
|
if (overrideMuteRule) {
|
||||||
promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id));
|
promises.push(cli.deletePushRule('global', PushRuleKind.Override, overrideMuteRule.rule_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newState === 'all_messages') {
|
if (newState === RoomNotifState.AllMessages) {
|
||||||
const roomRule = cli.getRoomPushRule('global', roomId);
|
const roomRule = cli.getRoomPushRule('global', roomId);
|
||||||
if (roomRule) {
|
if (roomRule) {
|
||||||
promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id));
|
promises.push(cli.deletePushRule('global', PushRuleKind.RoomSpecific, roomRule.rule_id));
|
||||||
}
|
}
|
||||||
} else if (newState === 'mentions_only') {
|
} else if (newState === RoomNotifState.MentionsOnly) {
|
||||||
promises.push(cli.addPushRule('global', 'room', roomId, {
|
promises.push(cli.addPushRule('global', PushRuleKind.RoomSpecific, roomId, {
|
||||||
actions: [
|
actions: [
|
||||||
'dont_notify',
|
'dont_notify',
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
// https://matrix.org/jira/browse/SPEC-400
|
// https://matrix.org/jira/browse/SPEC-400
|
||||||
promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true));
|
promises.push(cli.setPushRuleEnabled('global', PushRuleKind.RoomSpecific, roomId, true));
|
||||||
} else if ('all_messages_loud') {
|
} else if (newState === RoomNotifState.AllMessagesLoud) {
|
||||||
promises.push(cli.addPushRule('global', 'room', roomId, {
|
promises.push(cli.addPushRule('global', PushRuleKind.RoomSpecific, roomId, {
|
||||||
actions: [
|
actions: [
|
||||||
'notify',
|
'notify',
|
||||||
{
|
{
|
||||||
|
@ -195,13 +199,13 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
// https://matrix.org/jira/browse/SPEC-400
|
// https://matrix.org/jira/browse/SPEC-400
|
||||||
promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true));
|
promises.push(cli.setPushRuleEnabled('global', PushRuleKind.RoomSpecific, roomId, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOverrideMuteRule(roomId) {
|
function findOverrideMuteRule(roomId: string): IAnnotatedPushRule {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (!cli.pushRules ||
|
if (!cli.pushRules ||
|
||||||
!cli.pushRules['global'] ||
|
!cli.pushRules['global'] ||
|
||||||
|
@ -218,7 +222,7 @@ function findOverrideMuteRule(roomId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRuleForRoom(roomId, rule) {
|
function isRuleForRoom(roomId: string, rule: IAnnotatedPushRule): boolean {
|
||||||
if (rule.conditions.length !== 1) {
|
if (rule.conditions.length !== 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -226,6 +230,6 @@ function isRuleForRoom(roomId, rule) {
|
||||||
return (cond.kind === 'event_match' && cond.key === 'room_id' && cond.pattern === roomId);
|
return (cond.kind === 'event_match' && cond.key === 'room_id' && cond.pattern === roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMuteRule(rule) {
|
function isMuteRule(rule: IAnnotatedPushRule): boolean {
|
||||||
return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify');
|
return (rule.actions.length === 1 && rule.actions[0] === 'dont_notify');
|
||||||
}
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 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 {
|
|
||||||
ALL_MESSAGES,
|
|
||||||
ALL_MESSAGES_LOUD,
|
|
||||||
MENTIONS_ONLY,
|
|
||||||
MUTE,
|
|
||||||
} from "./RoomNotifs";
|
|
||||||
|
|
||||||
export type Volume = ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE;
|
|
|
@ -247,13 +247,31 @@ import { objectClone } from "./utils/objects";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
function sendResponse(event, res) {
|
enum Action {
|
||||||
|
CloseScalar = "close_scalar",
|
||||||
|
GetWidgets = "get_widgets",
|
||||||
|
SetWidgets = "set_widgets",
|
||||||
|
SetWidget = "set_widget",
|
||||||
|
JoinRulesState = "join_rules_state",
|
||||||
|
SetPlumbingState = "set_plumbing_state",
|
||||||
|
GetMembershipCount = "get_membership_count",
|
||||||
|
GetRoomEncryptionState = "get_room_enc_state",
|
||||||
|
CanSendEvent = "can_send_event",
|
||||||
|
MembershipState = "membership_state",
|
||||||
|
invite = "invite",
|
||||||
|
BotOptions = "bot_options",
|
||||||
|
SetBotOptions = "set_bot_options",
|
||||||
|
SetBotPower = "set_bot_power",
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendResponse(event: MessageEvent<any>, res: any): void {
|
||||||
const data = objectClone(event.data);
|
const data = objectClone(event.data);
|
||||||
data.response = res;
|
data.response = res;
|
||||||
|
// @ts-ignore
|
||||||
event.source.postMessage(data, event.origin);
|
event.source.postMessage(data, event.origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendError(event, msg, nestedError) {
|
function sendError(event: MessageEvent<any>, msg: string, nestedError?: Error): void {
|
||||||
console.error("Action:" + event.data.action + " failed with message: " + msg);
|
console.error("Action:" + event.data.action + " failed with message: " + msg);
|
||||||
const data = objectClone(event.data);
|
const data = objectClone(event.data);
|
||||||
data.response = {
|
data.response = {
|
||||||
|
@ -264,10 +282,11 @@ function sendError(event, msg, nestedError) {
|
||||||
if (nestedError) {
|
if (nestedError) {
|
||||||
data.response.error._error = nestedError;
|
data.response.error._error = nestedError;
|
||||||
}
|
}
|
||||||
|
// @ts-ignore
|
||||||
event.source.postMessage(data, event.origin);
|
event.source.postMessage(data, event.origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inviteUser(event, roomId, userId) {
|
function inviteUser(event: MessageEvent<any>, roomId: string, userId: string): void {
|
||||||
logger.log(`Received request to invite ${userId} into room ${roomId}`);
|
logger.log(`Received request to invite ${userId} into room ${roomId}`);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
@ -295,7 +314,7 @@ function inviteUser(event, roomId, userId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWidget(event, roomId) {
|
function setWidget(event: MessageEvent<any>, roomId: string): void {
|
||||||
const widgetId = event.data.widget_id;
|
const widgetId = event.data.widget_id;
|
||||||
let widgetType = event.data.type;
|
let widgetType = event.data.type;
|
||||||
const widgetUrl = event.data.url;
|
const widgetUrl = event.data.url;
|
||||||
|
@ -356,7 +375,7 @@ function setWidget(event, roomId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWidgets(event, roomId) {
|
function getWidgets(event: MessageEvent<any>, roomId: string): void {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, _t('You need to be logged in.'));
|
sendError(event, _t('You need to be logged in.'));
|
||||||
|
@ -382,7 +401,7 @@ function getWidgets(event, roomId) {
|
||||||
sendResponse(event, widgetStateEvents);
|
sendResponse(event, widgetStateEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoomEncState(event, roomId) {
|
function getRoomEncState(event: MessageEvent<any>, roomId: string): void {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, _t('You need to be logged in.'));
|
sendError(event, _t('You need to be logged in.'));
|
||||||
|
@ -398,7 +417,7 @@ function getRoomEncState(event, roomId) {
|
||||||
sendResponse(event, roomIsEncrypted);
|
sendResponse(event, roomIsEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPlumbingState(event, roomId, status) {
|
function setPlumbingState(event: MessageEvent<any>, roomId: string, status: string): void {
|
||||||
if (typeof status !== 'string') {
|
if (typeof status !== 'string') {
|
||||||
throw new Error('Plumbing state status should be a string');
|
throw new Error('Plumbing state status should be a string');
|
||||||
}
|
}
|
||||||
|
@ -417,7 +436,7 @@ function setPlumbingState(event, roomId, status) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBotOptions(event, roomId, userId) {
|
function setBotOptions(event: MessageEvent<any>, roomId: string, userId: string): void {
|
||||||
logger.log(`Received request to set options for bot ${userId} in room ${roomId}`);
|
logger.log(`Received request to set options for bot ${userId} in room ${roomId}`);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
@ -433,7 +452,7 @@ function setBotOptions(event, roomId, userId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBotPower(event, roomId, userId, level) {
|
function setBotPower(event: MessageEvent<any>, roomId: string, userId: string, level: number): void {
|
||||||
if (!(Number.isInteger(level) && level >= 0)) {
|
if (!(Number.isInteger(level) && level >= 0)) {
|
||||||
sendError(event, _t('Power level must be positive integer.'));
|
sendError(event, _t('Power level must be positive integer.'));
|
||||||
return;
|
return;
|
||||||
|
@ -464,22 +483,22 @@ function setBotPower(event, roomId, userId, level) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMembershipState(event, roomId, userId) {
|
function getMembershipState(event: MessageEvent<any>, roomId: string, userId: string): void {
|
||||||
logger.log(`membership_state of ${userId} in room ${roomId} requested.`);
|
logger.log(`membership_state of ${userId} in room ${roomId} requested.`);
|
||||||
returnStateEvent(event, roomId, "m.room.member", userId);
|
returnStateEvent(event, roomId, "m.room.member", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJoinRules(event, roomId) {
|
function getJoinRules(event: MessageEvent<any>, roomId: string): void {
|
||||||
logger.log(`join_rules of ${roomId} requested.`);
|
logger.log(`join_rules of ${roomId} requested.`);
|
||||||
returnStateEvent(event, roomId, "m.room.join_rules", "");
|
returnStateEvent(event, roomId, "m.room.join_rules", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function botOptions(event, roomId, userId) {
|
function botOptions(event: MessageEvent<any>, roomId: string, userId: string): void {
|
||||||
logger.log(`bot_options of ${userId} in room ${roomId} requested.`);
|
logger.log(`bot_options of ${userId} in room ${roomId} requested.`);
|
||||||
returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId);
|
returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMembershipCount(event, roomId) {
|
function getMembershipCount(event: MessageEvent<any>, roomId: string): void {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, _t('You need to be logged in.'));
|
sendError(event, _t('You need to be logged in.'));
|
||||||
|
@ -494,7 +513,7 @@ function getMembershipCount(event, roomId) {
|
||||||
sendResponse(event, count);
|
sendResponse(event, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canSendEvent(event, roomId) {
|
function canSendEvent(event: MessageEvent<any>, roomId: string): void {
|
||||||
const evType = "" + event.data.event_type; // force stringify
|
const evType = "" + event.data.event_type; // force stringify
|
||||||
const isState = Boolean(event.data.is_state);
|
const isState = Boolean(event.data.is_state);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
@ -528,7 +547,7 @@ function canSendEvent(event, roomId) {
|
||||||
sendResponse(event, true);
|
sendResponse(event, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnStateEvent(event, roomId, eventType, stateKey) {
|
function returnStateEvent(event: MessageEvent<any>, roomId: string, eventType: string, stateKey: string): void {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, _t('You need to be logged in.'));
|
sendError(event, _t('You need to be logged in.'));
|
||||||
|
@ -547,8 +566,9 @@ function returnStateEvent(event, roomId, eventType, stateKey) {
|
||||||
sendResponse(event, stateEvent.getContent());
|
sendResponse(event, stateEvent.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMessage = function(event) {
|
const onMessage = function(event: MessageEvent<any>): void {
|
||||||
if (!event.origin) { // stupid chrome
|
if (!event.origin) { // stupid chrome
|
||||||
|
// @ts-ignore
|
||||||
event.origin = event.originalEvent.origin;
|
event.origin = event.originalEvent.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,8 +602,8 @@ const onMessage = function(event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.data.action === "close_scalar") {
|
if (event.data.action === Action.CloseScalar) {
|
||||||
dis.dispatch({ action: "close_scalar" });
|
dis.dispatch({ action: Action.CloseScalar });
|
||||||
sendResponse(event, null);
|
sendResponse(event, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -596,10 +616,10 @@ const onMessage = function(event) {
|
||||||
// Get and set user widgets (not associated with a specific room)
|
// Get and set user widgets (not associated with a specific room)
|
||||||
// If roomId is specified, it must be validated, so room-based widgets agreed
|
// If roomId is specified, it must be validated, so room-based widgets agreed
|
||||||
// handled further down.
|
// handled further down.
|
||||||
if (event.data.action === "get_widgets") {
|
if (event.data.action === Action.GetWidgets) {
|
||||||
getWidgets(event, null);
|
getWidgets(event, null);
|
||||||
return;
|
return;
|
||||||
} else if (event.data.action === "set_widget") {
|
} else if (event.data.action === Action.SetWidgets) {
|
||||||
setWidget(event, null);
|
setWidget(event, null);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -614,28 +634,28 @@ const onMessage = function(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get and set room-based widgets
|
// Get and set room-based widgets
|
||||||
if (event.data.action === "get_widgets") {
|
if (event.data.action === Action.GetWidgets) {
|
||||||
getWidgets(event, roomId);
|
getWidgets(event, roomId);
|
||||||
return;
|
return;
|
||||||
} else if (event.data.action === "set_widget") {
|
} else if (event.data.action === Action.SetWidget) {
|
||||||
setWidget(event, roomId);
|
setWidget(event, roomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These APIs don't require userId
|
// These APIs don't require userId
|
||||||
if (event.data.action === "join_rules_state") {
|
if (event.data.action === Action.JoinRulesState) {
|
||||||
getJoinRules(event, roomId);
|
getJoinRules(event, roomId);
|
||||||
return;
|
return;
|
||||||
} else if (event.data.action === "set_plumbing_state") {
|
} else if (event.data.action === Action.SetPlumbingState) {
|
||||||
setPlumbingState(event, roomId, event.data.status);
|
setPlumbingState(event, roomId, event.data.status);
|
||||||
return;
|
return;
|
||||||
} else if (event.data.action === "get_membership_count") {
|
} else if (event.data.action === Action.GetMembershipCount) {
|
||||||
getMembershipCount(event, roomId);
|
getMembershipCount(event, roomId);
|
||||||
return;
|
return;
|
||||||
} else if (event.data.action === "get_room_enc_state") {
|
} else if (event.data.action === Action.GetRoomEncryptionState) {
|
||||||
getRoomEncState(event, roomId);
|
getRoomEncState(event, roomId);
|
||||||
return;
|
return;
|
||||||
} else if (event.data.action === "can_send_event") {
|
} else if (event.data.action === Action.CanSendEvent) {
|
||||||
canSendEvent(event, roomId);
|
canSendEvent(event, roomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -645,19 +665,19 @@ const onMessage = function(event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (event.data.action) {
|
switch (event.data.action) {
|
||||||
case "membership_state":
|
case Action.MembershipState:
|
||||||
getMembershipState(event, roomId, userId);
|
getMembershipState(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
case "invite":
|
case Action.invite:
|
||||||
inviteUser(event, roomId, userId);
|
inviteUser(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
case "bot_options":
|
case Action.BotOptions:
|
||||||
botOptions(event, roomId, userId);
|
botOptions(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
case "set_bot_options":
|
case Action.SetBotOptions:
|
||||||
setBotOptions(event, roomId, userId);
|
setBotOptions(event, roomId, userId);
|
||||||
break;
|
break;
|
||||||
case "set_bot_power":
|
case Action.SetBotPower:
|
||||||
setBotPower(event, roomId, userId, event.data.level);
|
setBotPower(event, roomId, userId, event.data.level);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -667,16 +687,16 @@ const onMessage = function(event) {
|
||||||
};
|
};
|
||||||
|
|
||||||
let listenerCount = 0;
|
let listenerCount = 0;
|
||||||
let openManagerUrl = null;
|
let openManagerUrl: string = null;
|
||||||
|
|
||||||
export function startListening() {
|
export function startListening(): void {
|
||||||
if (listenerCount === 0) {
|
if (listenerCount === 0) {
|
||||||
window.addEventListener("message", onMessage, false);
|
window.addEventListener("message", onMessage, false);
|
||||||
}
|
}
|
||||||
listenerCount += 1;
|
listenerCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopListening() {
|
export function stopListening(): void {
|
||||||
listenerCount -= 1;
|
listenerCount -= 1;
|
||||||
if (listenerCount === 0) {
|
if (listenerCount === 0) {
|
||||||
window.removeEventListener("message", onMessage);
|
window.removeEventListener("message", onMessage);
|
||||||
|
@ -691,6 +711,6 @@ export function stopListening() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setOpenManagerUrl(url) {
|
export function setOpenManagerUrl(url: string): void {
|
||||||
openManagerUrl = url;
|
openManagerUrl = url;
|
||||||
}
|
}
|
|
@ -14,12 +14,20 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Skinner {
|
import React from "react";
|
||||||
constructor() {
|
|
||||||
this.components = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getComponent(name) {
|
export interface IComponents {
|
||||||
|
[key: string]: React.Component;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISkinObject {
|
||||||
|
components: IComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Skinner {
|
||||||
|
public components: IComponents = null;
|
||||||
|
|
||||||
|
public getComponent(name: string): React.Component {
|
||||||
if (!name) throw new Error(`Invalid component name: ${name}`);
|
if (!name) throw new Error(`Invalid component name: ${name}`);
|
||||||
if (this.components === null) {
|
if (this.components === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -30,7 +38,7 @@ class Skinner {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const doLookup = (components) => {
|
const doLookup = (components: IComponents): React.Component => {
|
||||||
if (!components) return null;
|
if (!components) return null;
|
||||||
let comp = components[name];
|
let comp = components[name];
|
||||||
// XXX: Temporarily also try 'views.' as we're currently
|
// XXX: Temporarily also try 'views.' as we're currently
|
||||||
|
@ -58,7 +66,7 @@ class Skinner {
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
load(skinObject) {
|
public load(skinObject: ISkinObject): void {
|
||||||
if (this.components !== null) {
|
if (this.components !== null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Attempted to load a skin while a skin is already loaded"+
|
"Attempted to load a skin while a skin is already loaded"+
|
||||||
|
@ -72,6 +80,7 @@ class Skinner {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have a skin, load our components too
|
// Now that we have a skin, load our components too
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const idx = require("./component-index");
|
const idx = require("./component-index");
|
||||||
if (!idx || !idx.components) throw new Error("Invalid react-sdk component index");
|
if (!idx || !idx.components) throw new Error("Invalid react-sdk component index");
|
||||||
for (const c in idx.components) {
|
for (const c in idx.components) {
|
||||||
|
@ -79,7 +88,7 @@ class Skinner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addComponent(name, comp) {
|
public addComponent(name: string, comp: any) {
|
||||||
let slot = name;
|
let slot = name;
|
||||||
if (comp.replaces !== undefined) {
|
if (comp.replaces !== undefined) {
|
||||||
if (comp.replaces.indexOf('.') > -1) {
|
if (comp.replaces.indexOf('.') > -1) {
|
||||||
|
@ -91,7 +100,7 @@ class Skinner {
|
||||||
this.components[slot] = comp;
|
this.components[slot] = comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
public reset(): void {
|
||||||
this.components = null;
|
this.components = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,8 +114,8 @@ class Skinner {
|
||||||
// See https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/
|
// See https://derickbailey.com/2016/03/09/creating-a-true-singleton-in-node-js-with-es6-symbols/
|
||||||
// or https://nodejs.org/api/modules.html#modules_module_caching_caveats
|
// or https://nodejs.org/api/modules.html#modules_module_caching_caveats
|
||||||
// ("Modules are cached based on their resolved filename")
|
// ("Modules are cached based on their resolved filename")
|
||||||
if (global.mxSkinner === undefined) {
|
if (window.mxSkinner === undefined) {
|
||||||
global.mxSkinner = new Skinner();
|
window.mxSkinner = new Skinner();
|
||||||
}
|
}
|
||||||
export default global.mxSkinner;
|
export default window.mxSkinner;
|
||||||
|
|
|
@ -14,9 +14,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import IdentityAuthClient from './IdentityAuthClient';
|
import IdentityAuthClient from './IdentityAuthClient';
|
||||||
|
|
||||||
export async function getThreepidsWithBindStatus(client, filterMedium) {
|
export async function getThreepidsWithBindStatus(
|
||||||
|
client: MatrixClient, filterMedium?: ThreepidMedium,
|
||||||
|
): Promise<IThreepid[]> {
|
||||||
const userId = client.getUserId();
|
const userId = client.getUserId();
|
||||||
|
|
||||||
let { threepids } = await client.getThreePids();
|
let { threepids } = await client.getThreePids();
|
||||||
|
@ -31,7 +35,7 @@ export async function getThreepidsWithBindStatus(client, filterMedium) {
|
||||||
const identityAccessToken = await authClient.getAccessToken({ check: false });
|
const identityAccessToken = await authClient.getAccessToken({ check: false });
|
||||||
|
|
||||||
// Restructure for lookup query
|
// Restructure for lookup query
|
||||||
const query = threepids.map(({ medium, address }) => [medium, address]);
|
const query = threepids.map(({ medium, address }): [string, string] => [medium, address]);
|
||||||
const lookupResults = await client.bulkLookupThreePids(query, identityAccessToken);
|
const lookupResults = await client.bulkLookupThreePids(query, identityAccessToken);
|
||||||
|
|
||||||
// Record which are already bound
|
// Record which are already bound
|
|
@ -42,7 +42,7 @@ import linkifyMatrix from "../../linkify-matrix";
|
||||||
import * as Lifecycle from '../../Lifecycle';
|
import * as Lifecycle from '../../Lifecycle';
|
||||||
// LifecycleStore is not used but does listen to and dispatch actions
|
// LifecycleStore is not used but does listen to and dispatch actions
|
||||||
import '../../stores/LifecycleStore';
|
import '../../stores/LifecycleStore';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageType from '../../PageTypes';
|
||||||
|
|
||||||
import createRoom, { IOpts } from "../../createRoom";
|
import createRoom, { IOpts } from "../../createRoom";
|
||||||
import { _t, _td, getCurrentLanguage } from '../../languageHandler';
|
import { _t, _td, getCurrentLanguage } from '../../languageHandler';
|
||||||
|
@ -207,7 +207,7 @@ interface IState {
|
||||||
view: Views;
|
view: Views;
|
||||||
// What the LoggedInView would be showing if visible
|
// What the LoggedInView would be showing if visible
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
page_type?: PageTypes;
|
page_type?: PageType;
|
||||||
// The ID of the room we're viewing. This is either populated directly
|
// The ID of the room we're viewing. This is either populated directly
|
||||||
// in the case where we view a room by ID or by RoomView when it resolves
|
// in the case where we view a room by ID or by RoomView when it resolves
|
||||||
// what ID an alias points at.
|
// what ID an alias points at.
|
||||||
|
@ -723,7 +723,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'view_my_groups':
|
case 'view_my_groups':
|
||||||
this.setPage(PageTypes.MyGroups);
|
this.setPage(PageType.MyGroups);
|
||||||
this.notifyNewScreen('groups');
|
this.notifyNewScreen('groups');
|
||||||
break;
|
break;
|
||||||
case 'view_group':
|
case 'view_group':
|
||||||
|
@ -756,7 +756,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
localStorage.setItem("mx_seenSpacesBeta", "1");
|
localStorage.setItem("mx_seenSpacesBeta", "1");
|
||||||
// We just dispatch the page change rather than have to worry about
|
// We just dispatch the page change rather than have to worry about
|
||||||
// what the logic is for each of these branches.
|
// what the logic is for each of these branches.
|
||||||
if (this.state.page_type === PageTypes.MyGroups) {
|
if (this.state.page_type === PageType.MyGroups) {
|
||||||
dis.dispatch({ action: 'view_last_screen' });
|
dis.dispatch({ action: 'view_last_screen' });
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({ action: 'view_my_groups' });
|
dis.dispatch({ action: 'view_my_groups' });
|
||||||
|
@ -842,7 +842,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private setPage(pageType: string) {
|
private setPage(pageType: PageType) {
|
||||||
this.setState({
|
this.setState({
|
||||||
page_type: pageType,
|
page_type: pageType,
|
||||||
});
|
});
|
||||||
|
@ -949,7 +949,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.setState({
|
this.setState({
|
||||||
view: Views.LOGGED_IN,
|
view: Views.LOGGED_IN,
|
||||||
currentRoomId: roomInfo.room_id || null,
|
currentRoomId: roomInfo.room_id || null,
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageType.RoomView,
|
||||||
threepidInvite: roomInfo.threepid_invite,
|
threepidInvite: roomInfo.threepid_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
ready: true,
|
ready: true,
|
||||||
|
@ -977,7 +977,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
currentGroupId: groupId,
|
currentGroupId: groupId,
|
||||||
currentGroupIsNew: payload.group_is_new,
|
currentGroupIsNew: payload.group_is_new,
|
||||||
});
|
});
|
||||||
this.setPage(PageTypes.GroupView);
|
this.setPage(PageType.GroupView);
|
||||||
this.notifyNewScreen('group/' + groupId);
|
this.notifyNewScreen('group/' + groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1020,7 +1020,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
justRegistered,
|
justRegistered,
|
||||||
currentRoomId: null,
|
currentRoomId: null,
|
||||||
});
|
});
|
||||||
this.setPage(PageTypes.HomePage);
|
this.setPage(PageType.HomePage);
|
||||||
this.notifyNewScreen('home');
|
this.notifyNewScreen('home');
|
||||||
ThemeController.isLogin = false;
|
ThemeController.isLogin = false;
|
||||||
this.themeWatcher.recheck();
|
this.themeWatcher.recheck();
|
||||||
|
@ -1038,7 +1038,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
this.notifyNewScreen('user/' + userId);
|
this.notifyNewScreen('user/' + userId);
|
||||||
this.setState({ currentUserId: userId });
|
this.setState({ currentUserId: userId });
|
||||||
this.setPage(PageTypes.UserView);
|
this.setPage(PageType.UserView);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,9 @@ import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextM
|
||||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||||
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
|
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
|
||||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs";
|
import { RoomNotifState } from "../../../RoomNotifs";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import NotificationBadge from "./NotificationBadge";
|
import NotificationBadge from "./NotificationBadge";
|
||||||
import { Volume } from "../../../RoomNotifsTypes";
|
|
||||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||||
import RoomListActions from "../../../actions/RoomListActions";
|
import RoomListActions from "../../../actions/RoomListActions";
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
|
@ -364,7 +363,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private async saveNotifState(ev: ButtonEvent, newState: Volume) {
|
private async saveNotifState(ev: ButtonEvent, newState: RoomNotifState) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (MatrixClientPeg.get().isGuest()) return;
|
if (MatrixClientPeg.get().isGuest()) return;
|
||||||
|
@ -378,10 +377,10 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES);
|
private onClickAllNotifs = ev => this.saveNotifState(ev, RoomNotifState.AllMessages);
|
||||||
private onClickAlertMe = ev => this.saveNotifState(ev, ALL_MESSAGES_LOUD);
|
private onClickAlertMe = ev => this.saveNotifState(ev, RoomNotifState.AllMessagesLoud);
|
||||||
private onClickMentions = ev => this.saveNotifState(ev, MENTIONS_ONLY);
|
private onClickMentions = ev => this.saveNotifState(ev, RoomNotifState.MentionsOnly);
|
||||||
private onClickMute = ev => this.saveNotifState(ev, MUTE);
|
private onClickMute = ev => this.saveNotifState(ev, RoomNotifState.Mute);
|
||||||
|
|
||||||
private renderNotificationsMenu(isActive: boolean): React.ReactElement {
|
private renderNotificationsMenu(isActive: boolean): React.ReactElement {
|
||||||
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived ||
|
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived ||
|
||||||
|
@ -404,25 +403,25 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
<IconizedContextMenuOptionList first>
|
<IconizedContextMenuOptionList first>
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
label={_t("Use default")}
|
label={_t("Use default")}
|
||||||
active={state === ALL_MESSAGES}
|
active={state === RoomNotifState.AllMessages}
|
||||||
iconClassName="mx_RoomTile_iconBell"
|
iconClassName="mx_RoomTile_iconBell"
|
||||||
onClick={this.onClickAllNotifs}
|
onClick={this.onClickAllNotifs}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
label={_t("All messages")}
|
label={_t("All messages")}
|
||||||
active={state === ALL_MESSAGES_LOUD}
|
active={state === RoomNotifState.AllMessagesLoud}
|
||||||
iconClassName="mx_RoomTile_iconBellDot"
|
iconClassName="mx_RoomTile_iconBellDot"
|
||||||
onClick={this.onClickAlertMe}
|
onClick={this.onClickAlertMe}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
label={_t("Mentions & Keywords")}
|
label={_t("Mentions & Keywords")}
|
||||||
active={state === MENTIONS_ONLY}
|
active={state === RoomNotifState.MentionsOnly}
|
||||||
iconClassName="mx_RoomTile_iconBellMentions"
|
iconClassName="mx_RoomTile_iconBellMentions"
|
||||||
onClick={this.onClickMentions}
|
onClick={this.onClickMentions}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
label={_t("None")}
|
label={_t("None")}
|
||||||
active={state === MUTE}
|
active={state === RoomNotifState.Mute}
|
||||||
iconClassName="mx_RoomTile_iconBellCrossed"
|
iconClassName="mx_RoomTile_iconBellCrossed"
|
||||||
onClick={this.onClickMute}
|
onClick={this.onClickMute}
|
||||||
/>
|
/>
|
||||||
|
@ -432,14 +431,14 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const classes = classNames("mx_RoomTile_notificationsButton", {
|
const classes = classNames("mx_RoomTile_notificationsButton", {
|
||||||
// Show bell icon for the default case too.
|
// Show bell icon for the default case too.
|
||||||
mx_RoomTile_iconBell: state === ALL_MESSAGES,
|
mx_RoomTile_iconBell: state === RoomNotifState.AllMessages,
|
||||||
mx_RoomTile_iconBellDot: state === ALL_MESSAGES_LOUD,
|
mx_RoomTile_iconBellDot: state === RoomNotifState.AllMessagesLoud,
|
||||||
mx_RoomTile_iconBellMentions: state === MENTIONS_ONLY,
|
mx_RoomTile_iconBellMentions: state === RoomNotifState.MentionsOnly,
|
||||||
mx_RoomTile_iconBellCrossed: state === MUTE,
|
mx_RoomTile_iconBellCrossed: state === RoomNotifState.Mute,
|
||||||
|
|
||||||
// Only show the icon by default if the room is overridden to muted.
|
// Only show the icon by default if the room is overridden to muted.
|
||||||
// TODO: [FTUE Notifications] Probably need to detect global mute state
|
// TODO: [FTUE Notifications] Probably need to detect global mute state
|
||||||
mx_RoomTile_notificationsButton_show: state === MUTE,
|
mx_RoomTile_notificationsButton_show: state === RoomNotifState.Mute,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -15,20 +15,21 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Skinner from './Skinner';
|
import Skinner, { ISkinObject } from './Skinner';
|
||||||
|
|
||||||
export function loadSkin(skinObject) {
|
export function loadSkin(skinObject: ISkinObject): void {
|
||||||
Skinner.load(skinObject);
|
Skinner.load(skinObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetSkin() {
|
export function resetSkin(): void {
|
||||||
Skinner.reset();
|
Skinner.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComponent(componentName) {
|
export function getComponent(componentName: string): any {
|
||||||
return Skinner.getComponent(componentName);
|
return Skinner.getComponent(componentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import the js-sdk so the proper `request` object can be set. This does some
|
// Import the js-sdk so the proper `request` object can be set. This does some
|
||||||
// magic with the browser injection to make all subsequent imports work fine.
|
// magic with the browser injection to make all subsequent imports work fine.
|
||||||
import "matrix-js-sdk";
|
import "matrix-js-sdk";
|
||||||
|
|
|
@ -22,7 +22,14 @@ import {
|
||||||
tryTransformPermalinkToLocalHref,
|
tryTransformPermalinkToLocalHref,
|
||||||
} from "./utils/permalinks/Permalinks";
|
} from "./utils/permalinks/Permalinks";
|
||||||
|
|
||||||
function matrixLinkify(linkify) {
|
enum Type {
|
||||||
|
URL = "url",
|
||||||
|
UserId = "userid",
|
||||||
|
RoomAlias = "roomalias",
|
||||||
|
GroupId = "groupid"
|
||||||
|
}
|
||||||
|
|
||||||
|
function matrixLinkify(linkify): void {
|
||||||
// Text tokens
|
// Text tokens
|
||||||
const TT = linkify.scanner.TOKENS;
|
const TT = linkify.scanner.TOKENS;
|
||||||
// Multi tokens
|
// Multi tokens
|
||||||
|
@ -173,11 +180,11 @@ function matrixLinkify(linkify) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubs, overwritten in MatrixChat's componentDidMount
|
// stubs, overwritten in MatrixChat's componentDidMount
|
||||||
matrixLinkify.onUserClick = function(e, userId) { e.preventDefault(); };
|
matrixLinkify.onUserClick = function(e: MouseEvent, userId: string) { e.preventDefault(); };
|
||||||
matrixLinkify.onAliasClick = function(e, roomAlias) { e.preventDefault(); };
|
matrixLinkify.onAliasClick = function(e: MouseEvent, roomAlias: string) { e.preventDefault(); };
|
||||||
matrixLinkify.onGroupClick = function(e, groupId) { e.preventDefault(); };
|
matrixLinkify.onGroupClick = function(e: MouseEvent, groupId: string) { e.preventDefault(); };
|
||||||
|
|
||||||
const escapeRegExp = function(string) {
|
const escapeRegExp = function(string): string {
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -196,14 +203,15 @@ matrixLinkify.MATRIXTO_MD_LINK_PATTERN =
|
||||||
matrixLinkify.MATRIXTO_BASE_URL= baseUrl;
|
matrixLinkify.MATRIXTO_BASE_URL= baseUrl;
|
||||||
|
|
||||||
matrixLinkify.options = {
|
matrixLinkify.options = {
|
||||||
events: function(href, type) {
|
events: function(href: string, type: Type | string): Partial<GlobalEventHandlers> {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "url": {
|
case Type.URL: {
|
||||||
// intercept local permalinks to users and show them like userids (in userinfo of current room)
|
// intercept local permalinks to users and show them like userids (in userinfo of current room)
|
||||||
try {
|
try {
|
||||||
const permalink = parsePermalink(href);
|
const permalink = parsePermalink(href);
|
||||||
if (permalink && permalink.userId) {
|
if (permalink && permalink.userId) {
|
||||||
return {
|
return {
|
||||||
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
matrixLinkify.onUserClick(e, permalink.userId);
|
matrixLinkify.onUserClick(e, permalink.userId);
|
||||||
},
|
},
|
||||||
|
@ -214,20 +222,23 @@ matrixLinkify.options = {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "userid":
|
case Type.UserId:
|
||||||
return {
|
return {
|
||||||
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
matrixLinkify.onUserClick(e, href);
|
matrixLinkify.onUserClick(e, href);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
case "roomalias":
|
case Type.RoomAlias:
|
||||||
return {
|
return {
|
||||||
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
matrixLinkify.onAliasClick(e, href);
|
matrixLinkify.onAliasClick(e, href);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
case "groupid":
|
case Type.GroupId:
|
||||||
return {
|
return {
|
||||||
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
matrixLinkify.onGroupClick(e, href);
|
matrixLinkify.onGroupClick(e, href);
|
||||||
},
|
},
|
||||||
|
@ -235,11 +246,11 @@ matrixLinkify.options = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
formatHref: function(href, type) {
|
formatHref: function(href: string, type: Type | string): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'roomalias':
|
case Type.RoomAlias:
|
||||||
case 'userid':
|
case Type.UserId:
|
||||||
case 'groupid':
|
case Type.GroupId:
|
||||||
default: {
|
default: {
|
||||||
return tryTransformEntityToPermalink(href);
|
return tryTransformEntityToPermalink(href);
|
||||||
}
|
}
|
||||||
|
@ -250,8 +261,8 @@ matrixLinkify.options = {
|
||||||
rel: 'noreferrer noopener',
|
rel: 'noreferrer noopener',
|
||||||
},
|
},
|
||||||
|
|
||||||
target: function(href, type) {
|
target: function(href: string, type: Type | string): string {
|
||||||
if (type === 'url') {
|
if (type === Type.URL) {
|
||||||
try {
|
try {
|
||||||
const transformed = tryTransformPermalinkToLocalHref(href);
|
const transformed = tryTransformPermalinkToLocalHref(href);
|
||||||
if (transformed !== href || decodeURIComponent(href).match(matrixLinkify.ELEMENT_URL_PATTERN)) {
|
if (transformed !== href || decodeURIComponent(href).match(matrixLinkify.ELEMENT_URL_PATTERN)) {
|
|
@ -15,20 +15,17 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber";
|
import { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber";
|
||||||
import { getRoomNotifsState, setRoomNotifsState } from "../../RoomNotifs";
|
import { getRoomNotifsState, RoomNotifState, setRoomNotifsState } from "../../RoomNotifs";
|
||||||
import { RoomEchoContext } from "./RoomEchoContext";
|
import { RoomEchoContext } from "./RoomEchoContext";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import { Volume } from "../../RoomNotifsTypes";
|
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
export type CachedRoomValues = Volume;
|
|
||||||
|
|
||||||
export enum CachedRoomKey {
|
export enum CachedRoomKey {
|
||||||
NotificationVolume,
|
NotificationVolume,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedRoomKey, CachedRoomValues> {
|
export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedRoomKey, RoomNotifState> {
|
||||||
private properties = new Map<CachedRoomKey, CachedRoomValues>();
|
private properties = new Map<CachedRoomKey, RoomNotifState>();
|
||||||
|
|
||||||
public constructor(context: RoomEchoContext) {
|
public constructor(context: RoomEchoContext) {
|
||||||
super(context, (k) => this.properties.get(k));
|
super(context, (k) => this.properties.get(k));
|
||||||
|
@ -50,8 +47,8 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
|
||||||
|
|
||||||
private onAccountData = (event: MatrixEvent) => {
|
private onAccountData = (event: MatrixEvent) => {
|
||||||
if (event.getType() === "m.push_rules") {
|
if (event.getType() === "m.push_rules") {
|
||||||
const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume) as Volume;
|
const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume) as RoomNotifState;
|
||||||
const newVolume = getRoomNotifsState(this.context.room.roomId) as Volume;
|
const newVolume = getRoomNotifsState(this.context.room.roomId) as RoomNotifState;
|
||||||
if (currentVolume !== newVolume) {
|
if (currentVolume !== newVolume) {
|
||||||
this.updateNotificationVolume();
|
this.updateNotificationVolume();
|
||||||
}
|
}
|
||||||
|
@ -66,11 +63,11 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
|
||||||
|
|
||||||
// ---- helpers below here ----
|
// ---- helpers below here ----
|
||||||
|
|
||||||
public get notificationVolume(): Volume {
|
public get notificationVolume(): RoomNotifState {
|
||||||
return this.getValue(CachedRoomKey.NotificationVolume);
|
return this.getValue(CachedRoomKey.NotificationVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public set notificationVolume(v: Volume) {
|
public set notificationVolume(v: RoomNotifState) {
|
||||||
this.setValue(_t("Change notification settings"), CachedRoomKey.NotificationVolume, v, async () => {
|
this.setValue(_t("Change notification settings"), CachedRoomKey.NotificationVolume, v, async () => {
|
||||||
return setRoomNotifsState(this.context.room.roomId, v);
|
return setRoomNotifsState(this.context.room.roomId, v);
|
||||||
}, implicitlyReverted);
|
}, implicitlyReverted);
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
||||||
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
|
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
||||||
import * as RoomNotifs from '../../RoomNotifs';
|
import * as RoomNotifs from '../../RoomNotifs';
|
||||||
import * as Unread from '../../Unread';
|
import * as Unread from '../../Unread';
|
||||||
import { NotificationState } from "./NotificationState";
|
import { NotificationState } from "./NotificationState";
|
||||||
|
@ -91,7 +91,7 @@ export class RoomNotificationState extends NotificationState implements IDestroy
|
||||||
this._color = NotificationColor.Unsent;
|
this._color = NotificationColor.Unsent;
|
||||||
this._symbol = "!";
|
this._symbol = "!";
|
||||||
this._count = 1; // not used, technically
|
this._count = 1; // not used, technically
|
||||||
} else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) {
|
} else if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.RoomNotifState.Mute) {
|
||||||
// When muted we suppress all notification states, even if we have context on them.
|
// When muted we suppress all notification states, even if we have context on them.
|
||||||
this._color = NotificationColor.None;
|
this._color = NotificationColor.None;
|
||||||
this._symbol = null;
|
this._symbol = null;
|
||||||
|
@ -101,8 +101,8 @@ export class RoomNotificationState extends NotificationState implements IDestroy
|
||||||
this._symbol = "!";
|
this._symbol = "!";
|
||||||
this._count = 1; // not used, technically
|
this._count = 1; // not used, technically
|
||||||
} else {
|
} else {
|
||||||
const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'highlight');
|
const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Highlight);
|
||||||
const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'total');
|
const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, NotificationCountType.Total);
|
||||||
|
|
||||||
// For a 'true count' we pick the grey notifications first because they include the
|
// For a 'true count' we pick the grey notifications first because they include the
|
||||||
// red notifications. If we don't have a grey count for some reason we use the red
|
// red notifications. If we don't have a grey count for some reason we use the red
|
||||||
|
|
|
@ -17,11 +17,32 @@ limitations under the License.
|
||||||
|
|
||||||
import { _t } from "./languageHandler";
|
import { _t } from "./languageHandler";
|
||||||
|
|
||||||
export const DEFAULT_THEME = "light";
|
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import ThemeWatcher from "./settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "./settings/watchers/ThemeWatcher";
|
||||||
|
|
||||||
export function enumerateThemes() {
|
export const DEFAULT_THEME = "light";
|
||||||
|
|
||||||
|
interface IFontFaces {
|
||||||
|
src: {
|
||||||
|
format: string;
|
||||||
|
url: string;
|
||||||
|
local: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICustomTheme {
|
||||||
|
colors: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
fonts: {
|
||||||
|
faces: IFontFaces[];
|
||||||
|
general: string;
|
||||||
|
monospace: string;
|
||||||
|
};
|
||||||
|
is_dark?: boolean; // eslint-disable-line camelcase
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enumerateThemes(): {[key: string]: string} {
|
||||||
const BUILTIN_THEMES = {
|
const BUILTIN_THEMES = {
|
||||||
"light": _t("Light"),
|
"light": _t("Light"),
|
||||||
"dark": _t("Dark"),
|
"dark": _t("Dark"),
|
||||||
|
@ -34,7 +55,7 @@ export function enumerateThemes() {
|
||||||
return Object.assign({}, customThemeNames, BUILTIN_THEMES);
|
return Object.assign({}, customThemeNames, BUILTIN_THEMES);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCustomTheme() {
|
function clearCustomTheme(): void {
|
||||||
// remove all css variables, we assume these are there because of the custom theme
|
// remove all css variables, we assume these are there because of the custom theme
|
||||||
const inlineStyleProps = Object.values(document.body.style);
|
const inlineStyleProps = Object.values(document.body.style);
|
||||||
for (const prop of inlineStyleProps) {
|
for (const prop of inlineStyleProps) {
|
||||||
|
@ -61,7 +82,7 @@ const allowedFontFaceProps = [
|
||||||
"unicode-range",
|
"unicode-range",
|
||||||
];
|
];
|
||||||
|
|
||||||
function generateCustomFontFaceCSS(faces) {
|
function generateCustomFontFaceCSS(faces: IFontFaces[]): string {
|
||||||
return faces.map(face => {
|
return faces.map(face => {
|
||||||
const src = face.src && face.src.map(srcElement => {
|
const src = face.src && face.src.map(srcElement => {
|
||||||
let format;
|
let format;
|
||||||
|
@ -91,7 +112,7 @@ function generateCustomFontFaceCSS(faces) {
|
||||||
}).join("\n");
|
}).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCustomThemeVars(customTheme) {
|
function setCustomThemeVars(customTheme: ICustomTheme): void {
|
||||||
const { style } = document.body;
|
const { style } = document.body;
|
||||||
|
|
||||||
function setCSSColorVariable(name, hexColor, doPct = true) {
|
function setCSSColorVariable(name, hexColor, doPct = true) {
|
||||||
|
@ -134,7 +155,7 @@ function setCustomThemeVars(customTheme) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCustomTheme(themeName) {
|
export function getCustomTheme(themeName: string): ICustomTheme {
|
||||||
// set css variables
|
// set css variables
|
||||||
const customThemes = SettingsStore.getValue("custom_themes");
|
const customThemes = SettingsStore.getValue("custom_themes");
|
||||||
if (!customThemes) {
|
if (!customThemes) {
|
||||||
|
@ -155,7 +176,7 @@ export function getCustomTheme(themeName) {
|
||||||
*
|
*
|
||||||
* @param {string} theme new theme
|
* @param {string} theme new theme
|
||||||
*/
|
*/
|
||||||
export async function setTheme(theme) {
|
export async function setTheme(theme: string): Promise<void> {
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
const themeWatcher = new ThemeWatcher();
|
const themeWatcher = new ThemeWatcher();
|
||||||
theme = themeWatcher.getEffectiveTheme();
|
theme = themeWatcher.getEffectiveTheme();
|
||||||
|
@ -200,13 +221,14 @@ export async function setTheme(theme) {
|
||||||
// We could alternatively lock or similar to stop the race, but
|
// We could alternatively lock or similar to stop the race, but
|
||||||
// this is probably good enough for now.
|
// this is probably good enough for now.
|
||||||
styleElements[stylesheetName].disabled = false;
|
styleElements[stylesheetName].disabled = false;
|
||||||
Object.values(styleElements).forEach((a) => {
|
Object.values(styleElements).forEach((a: HTMLStyleElement) => {
|
||||||
if (a == styleElements[stylesheetName]) return;
|
if (a == styleElements[stylesheetName]) return;
|
||||||
a.disabled = true;
|
a.disabled = true;
|
||||||
});
|
});
|
||||||
const bodyStyles = global.getComputedStyle(document.body);
|
const bodyStyles = global.getComputedStyle(document.body);
|
||||||
if (bodyStyles.backgroundColor) {
|
if (bodyStyles.backgroundColor) {
|
||||||
document.querySelector('meta[name="theme-color"]').content = bodyStyles.backgroundColor;
|
const metaElement: HTMLMetaElement = document.querySelector('meta[name="theme-color"]');
|
||||||
|
metaElement.content = bodyStyles.backgroundColor;
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
// Returns a promise which resolves when the input promise resolves with its value
|
// Returns a promise which resolves when the input promise resolves with its value
|
||||||
// or when the timeout of ms is reached with the value of given timeoutValue
|
// or when the timeout of ms is reached with the value of given timeoutValue
|
||||||
export async function timeout<T>(promise: Promise<T>, timeoutValue: T, ms: number): Promise<T> {
|
export async function timeout<T, Y>(promise: Promise<T>, timeoutValue: Y, ms: number): Promise<T | Y> {
|
||||||
const timeoutPromise = new Promise<T>((resolve) => {
|
const timeoutPromise = new Promise<T | Y>((resolve) => {
|
||||||
const timeoutId = setTimeout(resolve, ms, timeoutValue);
|
const timeoutId = setTimeout(resolve, ms, timeoutValue);
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue