Spike AXE A11Y testing in Cypress (#9111)
* Spike AXE A11Y testing in Cypress * Fix NewRoomIntro breaking html/aria list rules * Fix HeaderButtons breaking aria role semantics rules * missing type * Switch left panel from aside to nav and include space panel * Give the page a main heading of the room name when viewing a room * Use header landmark on RoomHeader * Improve aria attributes on composer when autocomplete is closed * Fix aria-owns on RoomHeader * Give Spinner an aria role * Give server picker help button an aria label * Improve auth aria attributes and semantics * Improve heading semantics in use case selection screen * Fix autocomplete attribute to be valid * Fix heading semantics on login page * Improve Cypress axe testing * Add axe tests * Stop synapse after the timeline tests * Await spinners to fade before percy snapshotting timeline tests * Improve naming of plugin * Update snapshots * Fix accidental heading change * Fix double synapse stoppage * Fix Cypress timeline avatar assertions to be DPI agnostic * Fix aria attributes on date separators * delint * Update snapshots * Revert style change * Skip redundant call
This commit is contained in:
parent
05cc5f62dd
commit
d5db131eef
40 changed files with 244 additions and 83 deletions
|
@ -33,9 +33,12 @@ describe("Registration", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers an account and lands on the home screen", () => {
|
it("registers an account and lands on the home screen", () => {
|
||||||
|
cy.injectAxe();
|
||||||
|
|
||||||
cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click();
|
cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click();
|
||||||
cy.get(".mx_ServerPickerDialog_continue").should("be.visible");
|
cy.get(".mx_ServerPickerDialog_continue").should("be.visible");
|
||||||
cy.percySnapshot("Server Picker");
|
cy.percySnapshot("Server Picker");
|
||||||
|
cy.checkA11y();
|
||||||
|
|
||||||
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
||||||
cy.get(".mx_ServerPickerDialog_continue").click();
|
cy.get(".mx_ServerPickerDialog_continue").click();
|
||||||
|
@ -46,6 +49,7 @@ describe("Registration", () => {
|
||||||
// Hide the server text as it contains the randomly allocated Synapse port
|
// Hide the server text as it contains the randomly allocated Synapse port
|
||||||
const percyCSS = ".mx_ServerPicker_server { visibility: hidden !important; }";
|
const percyCSS = ".mx_ServerPicker_server { visibility: hidden !important; }";
|
||||||
cy.percySnapshot("Registration", { percyCSS });
|
cy.percySnapshot("Registration", { percyCSS });
|
||||||
|
cy.checkA11y();
|
||||||
|
|
||||||
cy.get("#mx_RegistrationForm_username").type("alice");
|
cy.get("#mx_RegistrationForm_username").type("alice");
|
||||||
cy.get("#mx_RegistrationForm_password").type("totally a great password");
|
cy.get("#mx_RegistrationForm_password").type("totally a great password");
|
||||||
|
@ -55,16 +59,21 @@ describe("Registration", () => {
|
||||||
|
|
||||||
cy.get(".mx_RegistrationEmailPromptDialog").should("be.visible");
|
cy.get(".mx_RegistrationEmailPromptDialog").should("be.visible");
|
||||||
cy.percySnapshot("Registration email prompt", { percyCSS });
|
cy.percySnapshot("Registration email prompt", { percyCSS });
|
||||||
|
cy.checkA11y();
|
||||||
cy.get(".mx_RegistrationEmailPromptDialog button.mx_Dialog_primary").click();
|
cy.get(".mx_RegistrationEmailPromptDialog button.mx_Dialog_primary").click();
|
||||||
|
|
||||||
cy.stopMeasuring("create-account");
|
cy.stopMeasuring("create-account");
|
||||||
cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy").should("be.visible");
|
cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy").should("be.visible");
|
||||||
cy.percySnapshot("Registration terms prompt", { percyCSS });
|
cy.percySnapshot("Registration terms prompt", { percyCSS });
|
||||||
|
cy.checkA11y();
|
||||||
|
|
||||||
cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy input").click();
|
cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy input").click();
|
||||||
cy.startMeasuring("from-submit-to-home");
|
cy.startMeasuring("from-submit-to-home");
|
||||||
cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click();
|
cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click();
|
||||||
|
|
||||||
|
cy.get(".mx_UseCaseSelection_skip").should("exist");
|
||||||
|
cy.percySnapshot("Use-case selection screen");
|
||||||
|
cy.checkA11y();
|
||||||
cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click();
|
cy.get(".mx_UseCaseSelection_skip .mx_AccessibleButton").click();
|
||||||
|
|
||||||
cy.url().should('contain', '/#/home');
|
cy.url().should('contain', '/#/home');
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { MessageEvent } from "matrix-events-sdk";
|
||||||
|
|
||||||
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
|
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
|
||||||
import type { EventType } from "matrix-js-sdk/src/@types/event";
|
import type { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
|
||||||
import { SynapseInstance } from "../../plugins/synapsedocker";
|
import { SynapseInstance } from "../../plugins/synapsedocker";
|
||||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
import { Layout } from "../../../src/settings/enums/Layout";
|
||||||
|
@ -46,10 +45,14 @@ const expectDisplayName = (e: JQuery<HTMLElement>, displayName: string): void =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
|
const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
|
||||||
cy.getClient().then((cli: MatrixClient) => {
|
cy.all([
|
||||||
|
cy.window({ log: false }),
|
||||||
|
cy.getClient(),
|
||||||
|
]).then(([win, cli]) => {
|
||||||
|
const size = AVATAR_SIZE * win.devicePixelRatio;
|
||||||
expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal(
|
expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal(
|
||||||
// eslint-disable-next-line no-restricted-properties
|
// eslint-disable-next-line no-restricted-properties
|
||||||
cli.mxcUrlToHttp(avatarUrl, AVATAR_SIZE, AVATAR_SIZE, AVATAR_RESIZE_METHOD),
|
cli.mxcUrlToHttp(avatarUrl, size, size, AVATAR_RESIZE_METHOD),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -75,15 +78,17 @@ describe("Timeline", () => {
|
||||||
cy.startSynapse("default").then(data => {
|
cy.startSynapse("default").then(data => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
cy.initTestUser(synapse, OLD_NAME).then(() =>
|
cy.initTestUser(synapse, OLD_NAME).then(() =>
|
||||||
cy.window({ log: false }).then(() => {
|
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
|
||||||
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
|
roomId = _room1Id;
|
||||||
roomId = _room1Id;
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.stopSynapse(synapse);
|
||||||
|
});
|
||||||
|
|
||||||
describe("useOnlyCurrentProfiles", () => {
|
describe("useOnlyCurrentProfiles", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.uploadContent(OLD_AVATAR).then((url) => {
|
cy.uploadContent(OLD_AVATAR).then((url) => {
|
||||||
|
@ -95,10 +100,6 @@ describe("Timeline", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cy.stopSynapse(synapse);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show historical profiles if disabled", () => {
|
it("should show historical profiles if disabled", () => {
|
||||||
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false);
|
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false);
|
||||||
sendEvent(roomId);
|
sendEvent(roomId);
|
||||||
|
@ -146,11 +147,16 @@ describe("Timeline", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("message displaying", () => {
|
describe("message displaying", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.injectAxe();
|
||||||
|
});
|
||||||
|
|
||||||
it("should create and configure a room on IRC layout", () => {
|
it("should create and configure a room on IRC layout", () => {
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary[data-layout=irc] " +
|
cy.contains(".mx_RoomView_body .mx_GenericEventListSummary[data-layout=irc] " +
|
||||||
".mx_GenericEventListSummary_summary", "created and configured the room.");
|
".mx_GenericEventListSummary_summary", "created and configured the room.");
|
||||||
|
cy.get(".mx_Spinner").should("not.exist");
|
||||||
cy.percySnapshot("Configured room on IRC layout");
|
cy.percySnapshot("Configured room on IRC layout");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -174,10 +180,12 @@ describe("Timeline", () => {
|
||||||
.should('have.css', "margin-inline-start", "104px")
|
.should('have.css', "margin-inline-start", "104px")
|
||||||
.should('have.css', "inset-inline-start", "0px");
|
.should('have.css', "inset-inline-start", "0px");
|
||||||
|
|
||||||
|
cy.get(".mx_Spinner").should("not.exist");
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp from snapshot
|
||||||
const percyCSS = ".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp "
|
const percyCSS = ".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp "
|
||||||
+ "{ visibility: hidden !important; }";
|
+ "{ visibility: hidden !important; }";
|
||||||
cy.percySnapshot("Event line with inline start margin on IRC layout", { percyCSS });
|
cy.percySnapshot("Event line with inline start margin on IRC layout", { percyCSS });
|
||||||
|
cy.checkA11y();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set inline start padding to a hidden event line", () => {
|
it("should set inline start padding to a hidden event line", () => {
|
||||||
|
|
|
@ -41,8 +41,11 @@ describe("Login", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("logs in with an existing account and lands on the home screen", () => {
|
it("logs in with an existing account and lands on the home screen", () => {
|
||||||
|
cy.injectAxe();
|
||||||
|
|
||||||
cy.get("#mx_LoginForm_username", { timeout: 15000 }).should("be.visible");
|
cy.get("#mx_LoginForm_username", { timeout: 15000 }).should("be.visible");
|
||||||
cy.percySnapshot("Login");
|
cy.percySnapshot("Login");
|
||||||
|
cy.checkA11y();
|
||||||
|
|
||||||
cy.get(".mx_ServerPicker_change").click();
|
cy.get(".mx_ServerPicker_change").click();
|
||||||
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { performance } from "./performance";
|
||||||
import { synapseDocker } from "./synapsedocker";
|
import { synapseDocker } from "./synapsedocker";
|
||||||
import { webserver } from "./webserver";
|
import { webserver } from "./webserver";
|
||||||
import { docker } from "./docker";
|
import { docker } from "./docker";
|
||||||
|
import { log } from "./log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Cypress.PluginConfig}
|
* @type {Cypress.PluginConfig}
|
||||||
|
@ -31,4 +32,5 @@ export default function(on: PluginEvents, config: PluginConfigOptions) {
|
||||||
performance(on, config);
|
performance(on, config);
|
||||||
synapseDocker(on, config);
|
synapseDocker(on, config);
|
||||||
webserver(on, config);
|
webserver(on, config);
|
||||||
|
log(on, config);
|
||||||
}
|
}
|
||||||
|
|
35
cypress/plugins/log.ts
Normal file
35
cypress/plugins/log.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
import PluginEvents = Cypress.PluginEvents;
|
||||||
|
import PluginConfigOptions = Cypress.PluginConfigOptions;
|
||||||
|
|
||||||
|
export function log(on: PluginEvents, config: PluginConfigOptions) {
|
||||||
|
on("task", {
|
||||||
|
log(message: string) {
|
||||||
|
console.log(message);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
table(message: string) {
|
||||||
|
console.table(message);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
61
cypress/support/axe.ts
Normal file
61
cypress/support/axe.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
|
||||||
|
import "cypress-axe";
|
||||||
|
import * as axe from "axe-core";
|
||||||
|
import { Options } from "cypress-axe";
|
||||||
|
|
||||||
|
import Chainable = Cypress.Chainable;
|
||||||
|
|
||||||
|
function terminalLog(violations: axe.Result[]): void {
|
||||||
|
cy.task(
|
||||||
|
'log',
|
||||||
|
`${violations.length} accessibility violation${
|
||||||
|
violations.length === 1 ? '' : 's'
|
||||||
|
} ${violations.length === 1 ? 'was' : 'were'} detected`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// pluck specific keys to keep the table readable
|
||||||
|
const violationData = violations.map(({ id, impact, description, nodes }) => ({
|
||||||
|
id,
|
||||||
|
impact,
|
||||||
|
description,
|
||||||
|
nodes: nodes.length,
|
||||||
|
}));
|
||||||
|
|
||||||
|
cy.task('table', violationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.overwrite("checkA11y", (
|
||||||
|
originalFn: Chainable["checkA11y"],
|
||||||
|
context?: string | Node | axe.ContextObject | undefined,
|
||||||
|
options: Options = {},
|
||||||
|
violationCallback?: ((violations: axe.Result[]) => void) | undefined,
|
||||||
|
skipFailures?: boolean,
|
||||||
|
): void => {
|
||||||
|
return originalFn(context, {
|
||||||
|
...options,
|
||||||
|
rules: {
|
||||||
|
// Disable contrast checking for now as we have too many issues with it
|
||||||
|
'color-contrast': {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
...options.rules,
|
||||||
|
},
|
||||||
|
}, violationCallback ?? terminalLog, skipFailures);
|
||||||
|
});
|
|
@ -36,3 +36,4 @@ import "./iframes";
|
||||||
import "./timeline";
|
import "./timeline";
|
||||||
import "./network";
|
import "./network";
|
||||||
import "./composer";
|
import "./composer";
|
||||||
|
import "./axe";
|
||||||
|
|
|
@ -7,7 +7,11 @@
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
],
|
],
|
||||||
"types": ["cypress", "@percy/cypress"],
|
"types": [
|
||||||
|
"cypress",
|
||||||
|
"cypress-axe",
|
||||||
|
"@percy/cypress"
|
||||||
|
],
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
|
|
@ -167,10 +167,12 @@
|
||||||
"@typescript-eslint/parser": "^5.6.0",
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||||
"allchange": "^1.0.6",
|
"allchange": "^1.0.6",
|
||||||
|
"axe-core": "^4.4.3",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"blob-polyfill": "^6.0.20211015",
|
"blob-polyfill": "^6.0.20211015",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"cypress": "^10.3.0",
|
"cypress": "^10.3.0",
|
||||||
|
"cypress-axe": "^1.0.0",
|
||||||
"cypress-real-events": "^1.7.1",
|
"cypress-real-events": "^1.7.1",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-to-json": "^3.6.2",
|
"enzyme-to-json": "^3.6.2",
|
||||||
|
|
|
@ -29,20 +29,20 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h1 {
|
||||||
font-size: $font-24px;
|
font-size: $font-24px;
|
||||||
font-weight: 600;
|
font-weight: $font-semi-bold;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
color: $authpage-primary-color;
|
color: $authpage-primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h2 {
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
font-weight: 600;
|
font-weight: $font-semi-bold;
|
||||||
color: $authpage-secondary-color;
|
color: $authpage-secondary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3.mx_AuthBody_centered {
|
h2.mx_AuthBody_centered {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> h4 {
|
> h2 {
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
font-weight: $font-semi-bold;
|
font-weight: $font-semi-bold;
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
margin-left: 8px;
|
margin: 16px 0 16px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> a {
|
> a {
|
||||||
|
|
|
@ -24,7 +24,7 @@ limitations under the License.
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
line-height: $font-20px;
|
line-height: $font-20px;
|
||||||
|
|
||||||
> h3 {
|
> h2 {
|
||||||
font-weight: $font-semi-bold;
|
font-weight: $font-semi-bold;
|
||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
|
|
|
@ -45,7 +45,7 @@ limitations under the License.
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: $font-16px;
|
font-size: $font-16px;
|
||||||
|
|
|
@ -30,9 +30,12 @@ limitations under the License.
|
||||||
border-bottom: 1px solid $menu-selected-color;
|
border-bottom: 1px solid $menu-selected-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DateSeparator > div {
|
.mx_DateSeparator > h2 {
|
||||||
margin: 0 25px;
|
margin: 0 25px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DateSeparator_jumpToDateMenu {
|
.mx_DateSeparator_jumpToDateMenu {
|
||||||
|
|
|
@ -382,7 +382,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
<aside className="mx_LeftPanel_roomListContainer">
|
<div className="mx_LeftPanel_roomListContainer">
|
||||||
{ this.renderSearchDialExplore() }
|
{ this.renderSearchDialExplore() }
|
||||||
{ this.renderBreadcrumbs() }
|
{ this.renderBreadcrumbs() }
|
||||||
{ !this.props.isMinimized && (
|
{ !this.props.isMinimized && (
|
||||||
|
@ -401,7 +401,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
{ roomList }
|
{ roomList }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -674,7 +674,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
<div className={bodyClasses}>
|
<div className={bodyClasses}>
|
||||||
<div className='mx_LeftPanel_outerWrapper'>
|
<div className='mx_LeftPanel_outerWrapper'>
|
||||||
<LeftPanelLiveShareWarning isMinimized={this.props.collapseLhs || false} />
|
<LeftPanelLiveShareWarning isMinimized={this.props.collapseLhs || false} />
|
||||||
<div className='mx_LeftPanel_wrapper'>
|
<nav className='mx_LeftPanel_wrapper'>
|
||||||
<BackdropPanel
|
<BackdropPanel
|
||||||
blurMultiplier={0.5}
|
blurMultiplier={0.5}
|
||||||
backgroundImage={this.state.backgroundImage}
|
backgroundImage={this.state.backgroundImage}
|
||||||
|
@ -693,7 +693,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />
|
<ResizeHandle passRef={this.resizeHandler} id="lp-resizer" />
|
||||||
<div className="mx_RoomView_wrapper">
|
<div className="mx_RoomView_wrapper">
|
||||||
|
|
|
@ -100,11 +100,11 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<CompleteSecurityBody>
|
<CompleteSecurityBody>
|
||||||
<h2 className="mx_CompleteSecurity_header">
|
<h1 className="mx_CompleteSecurity_header">
|
||||||
{ icon }
|
{ icon }
|
||||||
{ title }
|
{ title }
|
||||||
{ skipButton }
|
{ skipButton }
|
||||||
</h2>
|
</h1>
|
||||||
<div className="mx_CompleteSecurity_body">
|
<div className="mx_CompleteSecurity_body">
|
||||||
<SetupEncryptionBody onFinished={this.props.onFinished} />
|
<SetupEncryptionBody onFinished={this.props.onFinished} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -437,7 +437,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<AuthHeader />
|
<AuthHeader />
|
||||||
<AuthBody>
|
<AuthBody>
|
||||||
<h2> { _t('Set a new password') } </h2>
|
<h1> { _t('Set a new password') } </h1>
|
||||||
{ resetPasswordJsx }
|
{ resetPasswordJsx }
|
||||||
</AuthBody>
|
</AuthBody>
|
||||||
</AuthPage>
|
</AuthPage>
|
||||||
|
|
|
@ -600,10 +600,10 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<AuthHeader disableLanguageSelector={this.props.isSyncing || this.state.busyLoggingIn} />
|
<AuthHeader disableLanguageSelector={this.props.isSyncing || this.state.busyLoggingIn} />
|
||||||
<AuthBody>
|
<AuthBody>
|
||||||
<h2>
|
<h1>
|
||||||
{ _t('Sign in') }
|
{ _t('Sign in') }
|
||||||
{ loader }
|
{ loader }
|
||||||
</h2>
|
</h1>
|
||||||
{ errorTextSection }
|
{ errorTextSection }
|
||||||
{ serverDeadSection }
|
{ serverDeadSection }
|
||||||
<ServerPicker
|
<ServerPicker
|
||||||
|
|
|
@ -507,9 +507,9 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
// when there is only a single (or 0) providers we show a wide button with `Continue with X` text
|
// when there is only a single (or 0) providers we show a wide button with `Continue with X` text
|
||||||
if (providers.length > 1) {
|
if (providers.length > 1) {
|
||||||
// i18n: ssoButtons is a placeholder to help translators understand context
|
// i18n: ssoButtons is a placeholder to help translators understand context
|
||||||
continueWithSection = <h3 className="mx_AuthBody_centered">
|
continueWithSection = <h2 className="mx_AuthBody_centered">
|
||||||
{ _t("Continue with %(ssoButtons)s", { ssoButtons: "" }).trim() }
|
{ _t("Continue with %(ssoButtons)s", { ssoButtons: "" }).trim() }
|
||||||
</h3>;
|
</h2>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// i18n: ssoButtons & usernamePassword are placeholders to help translators understand context
|
// i18n: ssoButtons & usernamePassword are placeholders to help translators understand context
|
||||||
|
@ -521,7 +521,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
loginType={this.state.ssoFlow.type === "m.login.sso" ? "sso" : "cas"}
|
loginType={this.state.ssoFlow.type === "m.login.sso" ? "sso" : "cas"}
|
||||||
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
||||||
/>
|
/>
|
||||||
<h3 className="mx_AuthBody_centered">
|
<h2 className="mx_AuthBody_centered">
|
||||||
{ _t(
|
{ _t(
|
||||||
"%(ssoButtons)s Or %(usernamePassword)s",
|
"%(ssoButtons)s Or %(usernamePassword)s",
|
||||||
{
|
{
|
||||||
|
@ -529,7 +529,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
usernamePassword: "",
|
usernamePassword: "",
|
||||||
},
|
},
|
||||||
).trim() }
|
).trim() }
|
||||||
</h3>
|
</h2>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,7 +617,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
} else {
|
} else {
|
||||||
// regardless of whether we're the client that started the registration or not, we should
|
// regardless of whether we're the client that started the registration or not, we should
|
||||||
// try our credentials anyway
|
// try our credentials anyway
|
||||||
regDoneText = <h3>{ _t(
|
regDoneText = <h2>{ _t(
|
||||||
"<a>Log in</a> to your new account.", {},
|
"<a>Log in</a> to your new account.", {},
|
||||||
{
|
{
|
||||||
a: (sub) => <AccessibleButton
|
a: (sub) => <AccessibleButton
|
||||||
|
@ -630,10 +630,10 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
}}
|
}}
|
||||||
>{ sub }</AccessibleButton>,
|
>{ sub }</AccessibleButton>,
|
||||||
},
|
},
|
||||||
) }</h3>;
|
) }</h2>;
|
||||||
}
|
}
|
||||||
body = <div>
|
body = <div>
|
||||||
<h2>{ _t("Registration Successful") }</h2>
|
<h1>{ _t("Registration Successful") }</h1>
|
||||||
{ regDoneText }
|
{ regDoneText }
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -298,7 +298,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
return <>
|
return <>
|
||||||
<p>{ introText }</p>
|
<p>{ introText }</p>
|
||||||
{ this.renderSsoForm(null) }
|
{ this.renderSsoForm(null) }
|
||||||
<h3 className="mx_AuthBody_centered">
|
<h2 className="mx_AuthBody_centered">
|
||||||
{ _t(
|
{ _t(
|
||||||
"%(ssoButtons)s Or %(usernamePassword)s",
|
"%(ssoButtons)s Or %(usernamePassword)s",
|
||||||
{
|
{
|
||||||
|
@ -306,7 +306,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
usernamePassword: "",
|
usernamePassword: "",
|
||||||
},
|
},
|
||||||
).trim() }
|
).trim() }
|
||||||
</h3>
|
</h2>
|
||||||
{ this.renderPasswordForm(null) }
|
{ this.renderPasswordForm(null) }
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
@ -327,16 +327,16 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<AuthHeader />
|
<AuthHeader />
|
||||||
<AuthBody>
|
<AuthBody>
|
||||||
<h2>
|
<h1>
|
||||||
{ _t("You're signed out") }
|
{ _t("You're signed out") }
|
||||||
</h2>
|
</h1>
|
||||||
|
|
||||||
<h3>{ _t("Sign in") }</h3>
|
<h2>{ _t("Sign in") }</h2>
|
||||||
<div>
|
<div>
|
||||||
{ this.renderSignInSection() }
|
{ this.renderSignInSection() }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>{ _t("Clear personal data") }</h3>
|
<h2>{ _t("Clear personal data") }</h2>
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ _t(
|
||||||
"Warning: Your personal data (including encryption keys) is still stored " +
|
"Warning: Your personal data (including encryption keys) is still stored " +
|
||||||
|
|
|
@ -33,7 +33,7 @@ export function AuthHeaderDisplay({ title, icon, serverPicker, children }: Props
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{ current?.icon ?? icon }
|
{ current?.icon ?? icon }
|
||||||
<h2>{ current?.title ?? title }</h2>
|
<h1>{ current?.title ?? title }</h1>
|
||||||
{ children }
|
{ children }
|
||||||
{ current?.hideServerPicker !== true && serverPicker }
|
{ current?.hideServerPicker !== true && serverPicker }
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -22,7 +22,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AuthBody({ flex, children }: PropsWithChildren<Props>) {
|
export default function AuthBody({ flex, children }: PropsWithChildren<Props>) {
|
||||||
return <div className={classNames("mx_AuthBody", { "mx_AuthBody_flex": flex })}>
|
return <main className={classNames("mx_AuthBody", { "mx_AuthBody_flex": flex })}>
|
||||||
{ children }
|
{ children }
|
||||||
</div>;
|
</main>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,9 @@ import { _t } from '../../../languageHandler';
|
||||||
export default class AuthFooter extends React.Component {
|
export default class AuthFooter extends React.Component {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_AuthFooter">
|
<footer className="mx_AuthFooter" role="contentinfo">
|
||||||
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a>
|
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a>
|
||||||
</div>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ import React from 'react';
|
||||||
|
|
||||||
export default class AuthHeaderLogo extends React.PureComponent {
|
export default class AuthHeaderLogo extends React.PureComponent {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
return <div className="mx_AuthHeaderLogo">
|
return <aside className="mx_AuthHeaderLogo">
|
||||||
Matrix
|
Matrix
|
||||||
</div>;
|
</aside>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,7 +422,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||||
<Field
|
<Field
|
||||||
id="mx_LoginForm_password"
|
id="mx_LoginForm_password"
|
||||||
className={pwFieldClass}
|
className={pwFieldClass}
|
||||||
autoComplete="password"
|
autoComplete="current-password"
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
label={_t('Password')}
|
label={_t('Password')}
|
||||||
|
|
|
@ -206,6 +206,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
checked={!this.state.defaultChosen}
|
checked={!this.state.defaultChosen}
|
||||||
onChange={this.onOtherChosen}
|
onChange={this.onOtherChosen}
|
||||||
childrenInLabel={false}
|
childrenInLabel={false}
|
||||||
|
aria-label={_t("Other homeserver")}
|
||||||
>
|
>
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -230,7 +231,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
||||||
{ _t("Continue") }
|
{ _t("Continue") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
|
||||||
<h4>{ _t("Learn more") }</h4>
|
<h2>{ _t("Learn more") }</h2>
|
||||||
<a href="https://matrix.org/faq/#what-is-a-homeserver%3F" target="_blank" rel="noreferrer noopener">
|
<a href="https://matrix.org/faq/#what-is-a-homeserver%3F" target="_blank" rel="noreferrer noopener">
|
||||||
{ _t("About homeservers") }
|
{ _t("About homeservers") }
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -85,8 +85,13 @@ const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_ServerPicker">
|
return <div className="mx_ServerPicker">
|
||||||
<h3>{ title || _t("Homeserver") }</h3>
|
<h2>{ title || _t("Homeserver") }</h2>
|
||||||
{ !disableCustomUrls ? <AccessibleButton className="mx_ServerPicker_help" onClick={onHelpClick} /> : null }
|
{ !disableCustomUrls ? (
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_ServerPicker_help"
|
||||||
|
onClick={onHelpClick}
|
||||||
|
aria-label={_t("Help")}
|
||||||
|
/>): null }
|
||||||
<span className="mx_ServerPicker_server" title={typeof serverName === "string" ? serverName : undefined}>
|
<span className="mx_ServerPicker_server" title={typeof serverName === "string" ? serverName : undefined}>
|
||||||
{ serverName }
|
{ serverName }
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default class Spinner extends React.PureComponent<IProps> {
|
||||||
className="mx_Spinner_icon"
|
className="mx_Spinner_icon"
|
||||||
style={{ width: w, height: h }}
|
style={{ width: w, height: h }}
|
||||||
aria-label={_t("Loading...")}
|
aria-label={_t("Loading...")}
|
||||||
|
role="progressbar"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,7 +57,7 @@ export function UseCaseSelection({ onFinished }: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UseCaseSelection_info mx_UseCaseSelection_slideInDelayed">
|
<div className="mx_UseCaseSelection_info mx_UseCaseSelection_slideInDelayed">
|
||||||
<h2>{ _t("Who will you chat to the most?") }</h2>
|
<h2>{ _t("Who will you chat to the most?") }</h2>
|
||||||
<h4>{ _t("We'll help you get connected.") }</h4>
|
<h3>{ _t("We'll help you get connected.") }</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UseCaseSelection_options mx_UseCaseSelection_slideInDelayed">
|
<div className="mx_UseCaseSelection_options mx_UseCaseSelection_slideInDelayed">
|
||||||
<UseCaseSelectionButton
|
<UseCaseSelectionButton
|
||||||
|
|
|
@ -223,7 +223,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
||||||
isExpanded={!!this.state.contextMenuPosition}
|
isExpanded={!!this.state.contextMenuPosition}
|
||||||
title={_t("Jump to date")}
|
title={_t("Jump to date")}
|
||||||
>
|
>
|
||||||
<div aria-hidden="true">{ this.getLabel() }</div>
|
<h2 aria-hidden="true">{ this.getLabel() }</h2>
|
||||||
<div className="mx_DateSeparator_chevron" />
|
<div className="mx_DateSeparator_chevron" />
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</ContextMenuTooltipButton>
|
</ContextMenuTooltipButton>
|
||||||
|
@ -237,15 +237,15 @@ export default class DateSeparator extends React.Component<IProps, IState> {
|
||||||
if (this.state.jumpToDateEnabled) {
|
if (this.state.jumpToDateEnabled) {
|
||||||
dateHeaderContent = this.renderJumpToDateMenu();
|
dateHeaderContent = this.renderJumpToDateMenu();
|
||||||
} else {
|
} else {
|
||||||
dateHeaderContent = <div aria-hidden="true">{ label }</div>;
|
dateHeaderContent = <h2 aria-hidden="true">{ label }</h2>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ARIA treats <hr/>s as separators, here we abuse them slightly so manually treat this entire thing as one
|
// ARIA treats <hr/>s as separators, here we abuse them slightly so manually treat this entire thing as one
|
||||||
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
||||||
return <h2 className="mx_DateSeparator" role="separator" tabIndex={-1} aria-label={label}>
|
return <div className="mx_DateSeparator" role="separator" tabIndex={-1} aria-label={label}>
|
||||||
<hr role="none" />
|
<hr role="none" />
|
||||||
{ dateHeaderContent }
|
{ dateHeaderContent }
|
||||||
<hr role="none" />
|
<hr role="none" />
|
||||||
</h2>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro
|
||||||
public abstract renderButtons(): JSX.Element;
|
public abstract renderButtons(): JSX.Element;
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
return <div className="mx_HeaderButtons">
|
return <div className="mx_HeaderButtons" role="tablist">
|
||||||
{ this.renderButtons() }
|
{ this.renderButtons() }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -760,7 +760,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
|
|
||||||
const { completionIndex } = this.state;
|
const { completionIndex } = this.state;
|
||||||
const hasAutocomplete = Boolean(this.state.autoComplete);
|
const hasAutocomplete = Boolean(this.state.autoComplete);
|
||||||
let activeDescendant;
|
let activeDescendant: string;
|
||||||
if (hasAutocomplete && completionIndex >= 0) {
|
if (hasAutocomplete && completionIndex >= 0) {
|
||||||
activeDescendant = generateCompletionDomId(completionIndex);
|
activeDescendant = generateCompletionDomId(completionIndex);
|
||||||
}
|
}
|
||||||
|
@ -784,8 +784,8 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
||||||
aria-multiline="true"
|
aria-multiline="true"
|
||||||
aria-autocomplete="list"
|
aria-autocomplete="list"
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-expanded={hasAutocomplete}
|
aria-expanded={hasAutocomplete ? true : undefined}
|
||||||
aria-owns="mx_Autocomplete"
|
aria-owns={hasAutocomplete ? "mx_Autocomplete" : undefined}
|
||||||
aria-activedescendant={activeDescendant}
|
aria-activedescendant={activeDescendant}
|
||||||
dir="auto"
|
dir="auto"
|
||||||
aria-disabled={this.props.disabled}
|
aria-disabled={this.props.disabled}
|
||||||
|
|
|
@ -219,8 +219,7 @@ const NewRoomIntro = () => {
|
||||||
<span> { subText } { subButton } </span>
|
<span> { subText } { subButton } </span>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <div className="mx_NewRoomIntro">
|
return <li className="mx_NewRoomIntro">
|
||||||
|
|
||||||
{ !hasExpectedEncryptionSettings(cli, room) && (
|
{ !hasExpectedEncryptionSettings(cli, room) && (
|
||||||
<EventTileBubble
|
<EventTileBubble
|
||||||
className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
||||||
|
@ -230,7 +229,7 @@ const NewRoomIntro = () => {
|
||||||
) }
|
) }
|
||||||
|
|
||||||
{ body }
|
{ body }
|
||||||
</div>;
|
</li>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NewRoomIntro;
|
export default NewRoomIntro;
|
||||||
|
|
|
@ -45,6 +45,8 @@ import { NotificationStateEvents } from '../../../stores/notifications/Notificat
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
import RoomLiveShareWarning from '../beacon/RoomLiveShareWarning';
|
import RoomLiveShareWarning from '../beacon/RoomLiveShareWarning';
|
||||||
import { BetaPill } from "../beta/BetaCard";
|
import { BetaPill } from "../beta/BetaCard";
|
||||||
|
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||||
|
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
|
|
||||||
export interface ISearchInfo {
|
export interface ISearchInfo {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
|
@ -71,6 +73,7 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
contextMenuPosition?: DOMRect;
|
contextMenuPosition?: DOMRect;
|
||||||
|
rightPanelOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RoomHeader extends React.Component<IProps, IState> {
|
export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
|
@ -89,23 +92,29 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
||||||
notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
this.state = {};
|
this.state = {
|
||||||
|
rightPanelOpen: RightPanelStore.instance.isOpen,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
cli.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
cli.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||||
|
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
cli?.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||||
cli.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
|
||||||
}
|
|
||||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
|
||||||
notiStore.removeListener(NotificationStateEvents.Update, this.onNotificationUpdate);
|
notiStore.removeListener(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||||
|
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onRightPanelStoreUpdate = () => {
|
||||||
|
this.setState({ rightPanelOpen: RightPanelStore.instance.isOpen });
|
||||||
|
};
|
||||||
|
|
||||||
private onRoomStateEvents = (event: MatrixEvent) => {
|
private onRoomStateEvents = (event: MatrixEvent) => {
|
||||||
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
|
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
|
||||||
return;
|
return;
|
||||||
|
@ -230,7 +239,9 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
const roomName = <RoomName room={this.props.room}>
|
const roomName = <RoomName room={this.props.room}>
|
||||||
{ (name) => {
|
{ (name) => {
|
||||||
const roomName = name || oobName;
|
const roomName = name || oobName;
|
||||||
return <div dir="auto" className={textClasses} title={roomName}>{ roomName }</div>;
|
return <div dir="auto" className={textClasses} title={roomName} role="heading" aria-level={1}>
|
||||||
|
{ roomName }
|
||||||
|
</div>;
|
||||||
} }
|
} }
|
||||||
</RoomName>;
|
</RoomName>;
|
||||||
|
|
||||||
|
@ -311,8 +322,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomHeader light-panel">
|
<header className="mx_RoomHeader light-panel">
|
||||||
<div className="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel">
|
<div
|
||||||
|
className="mx_RoomHeader_wrapper"
|
||||||
|
aria-owns={this.state.rightPanelOpen ? "mx_RightPanel" : undefined}
|
||||||
|
>
|
||||||
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
|
<div className="mx_RoomHeader_avatar">{ roomAvatar }</div>
|
||||||
<div className="mx_RoomHeader_e2eIcon">{ e2eIcon }</div>
|
<div className="mx_RoomHeader_e2eIcon">{ e2eIcon }</div>
|
||||||
{ name }
|
{ name }
|
||||||
|
@ -322,7 +336,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
{ buttons }
|
{ buttons }
|
||||||
</div>
|
</div>
|
||||||
<RoomLiveShareWarning roomId={this.props.room.roomId} />
|
<RoomLiveShareWarning roomId={this.props.room.roomId} />
|
||||||
</div>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2408,6 +2408,7 @@
|
||||||
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.",
|
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.",
|
||||||
"Join millions for free on the largest public server": "Join millions for free on the largest public server",
|
"Join millions for free on the largest public server": "Join millions for free on the largest public server",
|
||||||
"Homeserver": "Homeserver",
|
"Homeserver": "Homeserver",
|
||||||
|
"Help": "Help",
|
||||||
"Choose a locale": "Choose a locale",
|
"Choose a locale": "Choose a locale",
|
||||||
"Continue with %(provider)s": "Continue with %(provider)s",
|
"Continue with %(provider)s": "Continue with %(provider)s",
|
||||||
"Sign in with single sign-on": "Sign in with single sign-on",
|
"Sign in with single sign-on": "Sign in with single sign-on",
|
||||||
|
|
|
@ -6,7 +6,7 @@ exports[`DateSeparator renders the date separator correctly 1`] = `
|
||||||
roomId="!unused:example.org"
|
roomId="!unused:example.org"
|
||||||
ts={1639728540000}
|
ts={1639728540000}
|
||||||
>
|
>
|
||||||
<h2
|
<div
|
||||||
aria-label="Today"
|
aria-label="Today"
|
||||||
className="mx_DateSeparator"
|
className="mx_DateSeparator"
|
||||||
role="separator"
|
role="separator"
|
||||||
|
@ -15,15 +15,15 @@ exports[`DateSeparator renders the date separator correctly 1`] = `
|
||||||
<hr
|
<hr
|
||||||
role="none"
|
role="none"
|
||||||
/>
|
/>
|
||||||
<div
|
<h2
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
Today
|
Today
|
||||||
</div>
|
</h2>
|
||||||
<hr
|
<hr
|
||||||
role="none"
|
role="none"
|
||||||
/>
|
/>
|
||||||
</h2>
|
</div>
|
||||||
</DateSeparator>
|
</DateSeparator>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ exports[`DateSeparator when feature_jump_to_date is enabled renders the date sep
|
||||||
roomId="!unused:example.org"
|
roomId="!unused:example.org"
|
||||||
ts={1639728540000}
|
ts={1639728540000}
|
||||||
>
|
>
|
||||||
<h2
|
<div
|
||||||
aria-label="Fri, Dec 17 2021"
|
aria-label="Fri, Dec 17 2021"
|
||||||
className="mx_DateSeparator"
|
className="mx_DateSeparator"
|
||||||
role="separator"
|
role="separator"
|
||||||
|
@ -88,11 +88,11 @@ exports[`DateSeparator when feature_jump_to_date is enabled renders the date sep
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<h2
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
Fri, Dec 17 2021
|
Fri, Dec 17 2021
|
||||||
</div>
|
</h2>
|
||||||
<div
|
<div
|
||||||
className="mx_DateSeparator_chevron"
|
className="mx_DateSeparator_chevron"
|
||||||
/>
|
/>
|
||||||
|
@ -103,6 +103,6 @@ exports[`DateSeparator when feature_jump_to_date is enabled renders the date sep
|
||||||
<hr
|
<hr
|
||||||
role="none"
|
role="none"
|
||||||
/>
|
/>
|
||||||
</h2>
|
</div>
|
||||||
</DateSeparator>
|
</DateSeparator>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -31,6 +31,7 @@ exports[`FontScalingPanel renders the font scaling UI 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-label="Loading..."
|
aria-label="Loading..."
|
||||||
className="mx_Spinner_icon"
|
className="mx_Spinner_icon"
|
||||||
|
role="progressbar"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"height": 32,
|
"height": 32,
|
||||||
|
|
|
@ -12,6 +12,7 @@ exports[`Module Components should override the factory for a ModuleSpinner 1`] =
|
||||||
<div
|
<div
|
||||||
aria-label="Loading..."
|
aria-label="Loading..."
|
||||||
className="mx_Spinner_icon"
|
className="mx_Spinner_icon"
|
||||||
|
role="progressbar"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"height": 32,
|
"height": 32,
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -2714,6 +2714,11 @@ axe-core@^4.4.2:
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.2.tgz#dcf7fb6dea866166c3eab33d68208afe4d5f670c"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.2.tgz#dcf7fb6dea866166c3eab33d68208afe4d5f670c"
|
||||||
integrity sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==
|
integrity sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==
|
||||||
|
|
||||||
|
axe-core@^4.4.3:
|
||||||
|
version "4.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
|
||||||
|
integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==
|
||||||
|
|
||||||
axobject-query@^2.2.0:
|
axobject-query@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||||
|
@ -3539,6 +3544,11 @@ csstype@^3.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
|
||||||
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
|
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
|
||||||
|
|
||||||
|
cypress-axe@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.0.0.tgz#ab4e9486eaa3bb956a90a1ae40d52df42827b4f0"
|
||||||
|
integrity sha512-QBlNMAd5eZoyhG8RGGR/pLtpHGkvgWXm2tkP68scJ+AjYiNNOlJihxoEwH93RT+rWOLrefw4iWwEx8kpEcrvJA==
|
||||||
|
|
||||||
cypress-real-events@^1.7.1:
|
cypress-real-events@^1.7.1:
|
||||||
version "1.7.1"
|
version "1.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.1.tgz#8f430d67c29ea4f05b9c5b0311780120cbc9b935"
|
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.1.tgz#8f430d67c29ea4f05b9c5b0311780120cbc9b935"
|
||||||
|
|
Loading…
Reference in a new issue