Merge remote-tracking branch 'origin/release-v3.43.0'

# Conflicts:
#	CHANGELOG.md
#	package.json
#	src/components/views/messages/MessageActionBar.tsx
This commit is contained in:
Michael Telatynski 2022-04-26 11:49:16 +01:00
commit 6b0156d876
680 changed files with 12433 additions and 5108 deletions

View file

@ -1,5 +1,6 @@
src/component-index.js
test/end-to-end-tests/node_modules/
test/end-to-end-tests/element/
test/end-to-end-tests/synapse/
test/end-to-end-tests/lib/
# Legacy skinning file that some people might still have
src/component-index.js

View file

@ -92,6 +92,7 @@ module.exports = {
files: [
"src/**/*.{ts,tsx}",
"test/**/*.{ts,tsx}",
"cypress/**/*.ts",
],
extends: [
"plugin:matrix-org/typescript",

View file

@ -0,0 +1,49 @@
# Produce a build of element-web with this version of react-sdk
# and any matching branches of element-web and js-sdk, output it
# as an artifact and run integration tests.
name: Element Web - Build and Test
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
env:
# This must be set for fetchdep.sh to get the right branch
PR_NUMBER: ${{github.event.number}}
steps:
- uses: actions/checkout@v2
- name: Build
run: scripts/ci/layered.sh && cd element-web && cp element.io/develop/config.json config.json && CI_PACKAGE=true yarn build
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: previewbuild
path: element-web/webapp
# We'll only use this in a triggered job, then we're done with it
retention-days: 1
cypress:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Download build
uses: actions/download-artifact@v3
with:
name: previewbuild
path: webapp
- name: Run Cypress tests
uses: cypress-io/github-action@v2
with:
# The built in Electron runner seems to grind to a halt trying
# to run the tests, so use chrome.
browser: chrome
start: npx serve -p 8080 webapp
- name: Upload Artifact
if: failure()
uses: actions/upload-artifact@v2
with:
name: cypress-results
path: |
cypress/screenshots
cypress/videos
cypress/synapselogs

View file

@ -1,23 +0,0 @@
# Produce a 'layered build' (a build of element-web with this version of
# react-sdk) and output it as an artifact
name: Layered Preview Build
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
env:
# This must be set for fetchdep.sh to get the right branch
PR_NUMBER: ${{github.event.number}}
steps:
- uses: actions/checkout@v2
- name: Build
run: scripts/ci/layered.sh && cd element-web && cp element.io/develop/config.json config.json && CI_PACKAGE=true yarn build
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: previewbuild
path: element-web/webapp
# We'll only use this in a triggered job, then we're done with it
retention-days: 1

View file

@ -3,7 +3,7 @@
name: Upload Preview Build to Netlify
on:
workflow_run:
workflows: ["Layered Preview Build"]
workflows: ["Element Web - Build and Test"]
types:
- completed
jobs:

View file

@ -25,10 +25,11 @@ jobs:
run: "./scripts/ci/install-deps.sh --ignore-scripts"
- name: Run tests with coverage
run: "yarn install && yarn reskindex && yarn coverage"
run: "yarn install && yarn coverage"
- name: Upload coverage
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: false
verbose: true
override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}

9
.gitignore vendored
View file

@ -11,6 +11,7 @@ package-lock.json
/matrix-react-sdk-*.tgz
/.idea
# Legacy skinning file that some people might still have
/src/component-index.js
.DS_Store
@ -18,3 +19,11 @@ package-lock.json
.vscode
.vscode/
/cypress/videos
/cypress/downloads
/cypress/screenshots
/cypress/synapselogs
# These could have files in them but don't currently
# Cypress will still auto-create them though...
/cypress/fixtures

View file

@ -1,3 +1,48 @@
Changes in [3.43.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.43.0) (2022-04-26)
=====================================================================================================
## ✨ Features
* Improve performance of switching to rooms with lots of servers and ACLs ([\#8347](https://github.com/matrix-org/matrix-react-sdk/pull/8347)).
* Avoid a reflow when setting caret position on an empty composer ([\#8348](https://github.com/matrix-org/matrix-react-sdk/pull/8348)).
* Add message right-click context menu as a labs feature ([\#5672](https://github.com/matrix-org/matrix-react-sdk/pull/5672)).
* Live location sharing - basic maximised beacon map ([\#8310](https://github.com/matrix-org/matrix-react-sdk/pull/8310)).
* Live location sharing - render users own beacons in timeline ([\#8296](https://github.com/matrix-org/matrix-react-sdk/pull/8296)).
* Improve Threads beta around degraded mode ([\#8318](https://github.com/matrix-org/matrix-react-sdk/pull/8318)).
* Live location sharing - beacon in timeline happy path ([\#8285](https://github.com/matrix-org/matrix-react-sdk/pull/8285)).
* Add copy button to View Source screen ([\#8278](https://github.com/matrix-org/matrix-react-sdk/pull/8278)). Fixes vector-im/element-web#21482. Contributed by @olivialivia.
* Add heart effect ([\#6188](https://github.com/matrix-org/matrix-react-sdk/pull/6188)). Contributed by @CicadaCinema.
* Update new room icon ([\#8239](https://github.com/matrix-org/matrix-react-sdk/pull/8239)).
## 🐛 Bug Fixes
* Fix: "Code formatting button does not escape backticks" ([\#8181](https://github.com/matrix-org/matrix-react-sdk/pull/8181)). Contributed by @yaya-usman.
* Fix beta indicator dot causing excessive CPU usage ([\#8340](https://github.com/matrix-org/matrix-react-sdk/pull/8340)). Fixes vector-im/element-web#21793.
* Fix overlapping timestamps on empty messages ([\#8205](https://github.com/matrix-org/matrix-react-sdk/pull/8205)). Fixes vector-im/element-web#21381. Contributed by @goelesha.
* Fix power selector not showing up in user info when state_default undefined ([\#8297](https://github.com/matrix-org/matrix-react-sdk/pull/8297)). Fixes vector-im/element-web#21669.
* Avoid looking up settings during timeline rendering ([\#8313](https://github.com/matrix-org/matrix-react-sdk/pull/8313)). Fixes vector-im/element-web#21740.
* Fix a soft crash with video rooms ([\#8333](https://github.com/matrix-org/matrix-react-sdk/pull/8333)).
* Fixes call tiles overflow ([\#8096](https://github.com/matrix-org/matrix-react-sdk/pull/8096)). Fixes vector-im/element-web#20254. Contributed by @luixxiul.
* Fix a bug with emoji autocomplete sorting where adding the final ":" would cause the emoji with the typed shortcode to no longer be at the top of the autocomplete list. ([\#8086](https://github.com/matrix-org/matrix-react-sdk/pull/8086)). Fixes vector-im/element-web#19302. Contributed by @commonlawfeature.
* Fix image preview sizing for edge cases ([\#8322](https://github.com/matrix-org/matrix-react-sdk/pull/8322)). Fixes vector-im/element-web#20088.
* Refactor SecurityRoomSettingsTab and remove unused state ([\#8306](https://github.com/matrix-org/matrix-react-sdk/pull/8306)). Fixes matrix-org/element-web-rageshakes#12002.
* Don't show the prompt to enable desktop notifications immediately after registration ([\#8274](https://github.com/matrix-org/matrix-react-sdk/pull/8274)).
* Stop tracking threads if threads support is disabled ([\#8308](https://github.com/matrix-org/matrix-react-sdk/pull/8308)). Fixes vector-im/element-web#21766.
* Fix some issues with threads rendering ([\#8305](https://github.com/matrix-org/matrix-react-sdk/pull/8305)). Fixes vector-im/element-web#21670.
* Fix threads rendering issue in Safari ([\#8298](https://github.com/matrix-org/matrix-react-sdk/pull/8298)). Fixes vector-im/element-web#21757.
* Fix space panel width change on hovering over space item ([\#8299](https://github.com/matrix-org/matrix-react-sdk/pull/8299)). Fixes vector-im/element-web#19891.
* Hide the reply in thread button in deployments where beta is forcibly disabled ([\#8294](https://github.com/matrix-org/matrix-react-sdk/pull/8294)). Fixes vector-im/element-web#21753.
* Prevent soft crash around room list header context menu when space changes ([\#8289](https://github.com/matrix-org/matrix-react-sdk/pull/8289)). Fixes matrix-org/element-web-rageshakes#11416, matrix-org/element-web-rageshakes#11692, matrix-org/element-web-rageshakes#11739, matrix-org/element-web-rageshakes#11772, matrix-org/element-web-rageshakes#11891 matrix-org/element-web-rageshakes#11858 and matrix-org/element-web-rageshakes#11456.
* When selecting reply in thread on a thread response open existing thread ([\#8291](https://github.com/matrix-org/matrix-react-sdk/pull/8291)). Fixes vector-im/element-web#21743.
* Handle thread bundled relationships coming from the server via MSC3666 ([\#8292](https://github.com/matrix-org/matrix-react-sdk/pull/8292)). Fixes vector-im/element-web#21450.
* Fix: Avatar preview does not update when same file is selected repeatedly ([\#8288](https://github.com/matrix-org/matrix-react-sdk/pull/8288)). Fixes vector-im/element-web#20098.
* Fix a bug where user gets a warning when changing powerlevel from **Admin** to **custom level (100)** ([\#8248](https://github.com/matrix-org/matrix-react-sdk/pull/8248)). Fixes vector-im/element-web#21682. Contributed by @Jumeb.
* Use a consistent alignment for all text items in a list ([\#8276](https://github.com/matrix-org/matrix-react-sdk/pull/8276)). Fixes vector-im/element-web#21731. Contributed by @luixxiul.
* Fixes button labels being collapsed per a character in CJK languages ([\#8212](https://github.com/matrix-org/matrix-react-sdk/pull/8212)). Fixes vector-im/element-web#21287. Contributed by @luixxiul.
* Fix: Remove jittery timeline scrolling after jumping to an event ([\#8263](https://github.com/matrix-org/matrix-react-sdk/pull/8263)).
* Fix regression of edits showing up in the timeline with hidden events shown ([\#8260](https://github.com/matrix-org/matrix-react-sdk/pull/8260)). Fixes vector-im/element-web#21694.
* Fix reporting events not working ([\#8257](https://github.com/matrix-org/matrix-react-sdk/pull/8257)). Fixes vector-im/element-web#21713.
* Make Jitsi widgets in video rooms immutable ([\#8244](https://github.com/matrix-org/matrix-react-sdk/pull/8244)). Fixes vector-im/element-web#21647.
* Fix: Ensure links to events scroll the correct events into view ([\#8250](https://github.com/matrix-org/matrix-react-sdk/pull/8250)). Fixes vector-im/element-web#19934.
Changes in [3.42.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.42.4) (2022-04-14)
=====================================================================================================
@ -483,7 +528,7 @@ Changes in [3.39.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/
=====================================================================================================
## 🐛 Bug Fixes
* Fix the sticker picker ([\#7692](https://github.com/matrix-org/matrix-react-sdk/pull/7692)). Fixes vector-im/element-web#20797.
* Fix the sticker picker ([\#7692](https://github.com/matrix-org/matrix-react-sdk/pull/7692)). Fixes vector-im/element-web#20797.
* Ensure UserInfo can be rendered without a room ([\#7687](https://github.com/matrix-org/matrix-react-sdk/pull/7687)). Fixes vector-im/element-web#20830.
* Fix publishing address wrongly demanding the alias be available ([\#7690](https://github.com/matrix-org/matrix-react-sdk/pull/7690)). Fixes vector-im/element-web#12013 and vector-im/element-web#20833.
@ -1342,7 +1387,7 @@ Changes in [3.32.1](https://github.com/vector-im/element-desktop/releases/tag/v3
## 🐛 Bug Fixes
* Upgrade to matrix-js-sdk#14.0.1
Changes in [3.32.0](https://github.com/vector-im/element-desktop/releases/tag/v3.32.0) (2021-10-11)
===================================================================================================
@ -2488,7 +2533,7 @@ related to file upload. When uploading a file, the local file preview can lead
to execution of scripts embedded in the uploaded file, but only after several
user interactions to open the preview in a separate tab. This only impacts the
local user while in the process of uploading. It cannot be exploited remotely
or by other users. Thanks to [Muhammad Zaid Ghifari](https://github.com/MR-ZHEEV)
or by other users. Thanks to [Muhammad Zaid Ghifari](https://github.com/MR-ZHEEV)
for responsibly disclosing this via Matrix's Security Disclosure Policy.
## All changes
@ -6483,7 +6528,7 @@ Changes in [2.1.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.1.0-rc.1...v2.1.0-rc.2)
* Fix error in previous attempt to upgrade JS SDK
* Fix error in previous attempt to upgrade JS SDK
Changes in [2.1.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.1.0-rc.1) (2020-02-13)
=============================================================================================================

View file

@ -65,13 +65,9 @@ practices that anyone working with the SDK needs to be aware of and uphold:
component is a view or a structure, and then a broad functional grouping
(e.g. 'rooms' here)
* After creating a new component you must run `yarn reskindex` to regenerate
the `component-index.js` for the SDK (used in future for skinning)
<!-- TODO: Remove this once this approach to skinning is replaced -->
* The view's CSS file MUST have the same name (e.g. view/rooms/MessageTile.css).
CSS for matrix-react-sdk currently resides in
https://github.com/vector-im/element-web/tree/master/src/skins/vector/css/matrix-react-sdk.
https://github.com/matrix-org/matrix-react-sdk/tree/master/res/css.
* Per-view CSS is optional - it could choose to inherit all its styling from
the context of the rest of the app, although this is unusual for any but
@ -158,9 +154,6 @@ cd matrix-react-sdk
git checkout develop
yarn link matrix-js-sdk
yarn install
# Generate the `component-index.js` file.
yarn reskindex
```
See the [help for `yarn link`](https://classic.yarnpkg.com/docs/cli/link) for

View file

@ -1,9 +1,14 @@
const EventEmitter = require("events");
const { LngLat, NavigationControl } = require('maplibre-gl');
const { LngLat, NavigationControl, LngLatBounds } = require('maplibre-gl');
class MockMap extends EventEmitter {
addControl = jest.fn();
removeControl = jest.fn();
zoomIn = jest.fn();
zoomOut = jest.fn();
setCenter = jest.fn();
setStyle = jest.fn();
fitBounds = jest.fn();
}
const MockMapInstance = new MockMap();
@ -14,10 +19,12 @@ const MockGeolocateInstance = new MockGeolocateControl();
const MockMarker = {}
MockMarker.setLngLat = jest.fn().mockReturnValue(MockMarker);
MockMarker.addTo = jest.fn().mockReturnValue(MockMarker);
MockMarker.remove = jest.fn().mockReturnValue(MockMarker);
module.exports = {
Map: jest.fn().mockReturnValue(MockMapInstance),
GeolocateControl: jest.fn().mockReturnValue(MockGeolocateInstance),
Marker: jest.fn().mockReturnValue(MockMarker),
LngLat,
NavigationControl
LngLatBounds,
NavigationControl,
};

View file

@ -13,7 +13,6 @@ module.exports = {
"@babel/preset-react",
],
"plugins": [
["@babel/plugin-proposal-decorators", {legacy: true}],
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-class-properties",

4
cypress.json Normal file
View file

@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:8080",
"videoUploadOnPasses": false
}

View file

@ -0,0 +1,52 @@
/*
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 { SynapseInstance } from "../../plugins/synapsedocker/index";
describe("Registration", () => {
let synapseId;
let synapsePort;
beforeEach(() => {
cy.task<SynapseInstance>("synapseStart", "consent").then(result => {
synapseId = result.synapseId;
synapsePort = result.port;
});
cy.visit("/#/register");
});
afterEach(() => {
cy.task("synapseStop", synapseId);
});
it("registers an account and lands on the home screen", () => {
cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click();
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(`http://localhost:${synapsePort}`);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get('.mx_ServerPickerDialog').should('not.exist');
cy.get("#mx_RegistrationForm_username").type("alice");
cy.get("#mx_RegistrationForm_password").type("totally a great password");
cy.get("#mx_RegistrationForm_passwordConfirm").type("totally a great password");
cy.get(".mx_Login_submit").click();
cy.get(".mx_RegistrationEmailPromptDialog button.mx_Dialog_primary").click();
cy.get(".mx_InteractiveAuthEntryComponents_termsPolicy input").click();
cy.get(".mx_InteractiveAuthEntryComponents_termsSubmit").click();
cy.url().should('contain', '/#/home');
});
});

View file

@ -14,16 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* minimal-sdk.js
*
* Starts up the skin system with even less than `skinned-sdk`.
*/
/// <reference types="cypress" />
import * as sdk from "../src/index";
import { synapseDocker } from "./synapsedocker/index";
const components = {};
sdk.loadSkin({ components });
export default sdk;
export default function(on, config) {
synapseDocker(on, config);
}

View file

@ -0,0 +1,212 @@
/*
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 * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
import * as childProcess from "child_process";
import * as fse from "fs-extra";
// A cypress plugins to add command to start & stop synapses in
// docker with preset templates.
interface SynapseConfig {
configDir: string;
registrationSecret: string;
}
export interface SynapseInstance extends SynapseConfig {
synapseId: string;
port: number;
}
const synapses = new Map<string, SynapseInstance>();
function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
const templateDir = path.join(__dirname, "templates", template);
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'react-sdk-synapsedocker-'));
// change permissions on the temp directory so the docker container can see its contents
await fse.chmod(tempDir, 0o777);
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'homeserver.yaml' });
const registrationSecret = randB64Bytes(16);
const macaroonSecret = randB64Bytes(16);
const formSecret = randB64Bytes(16);
// now copy homeserver.yaml, applying sustitutions
console.log(`Gen ${path.join(templateDir, "homeserver.yaml")}`);
let hsYaml = await fse.readFile(path.join(templateDir, "homeserver.yaml"), "utf8");
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
await fse.writeFile(path.join(tempDir, "homeserver.yaml"), hsYaml);
// now generate a signing key (we could use synapse's config generation for
// this, or we could just do this...)
// NB. This assumes the homeserver.yaml specifies the key in this location
const signingKey = randB64Bytes(32);
console.log(`Gen ${path.join(templateDir, "localhost.signing.key")}`);
await fse.writeFile(path.join(tempDir, "localhost.signing.key"), `ed25519 x ${signingKey}`);
return {
configDir: tempDir,
registrationSecret,
};
}
// Start a synapse instance: the template must be the name of
// one of the templates in the cypress/plugins/synapsedocker/templates
// directory
async function synapseStart(template: string): Promise<SynapseInstance> {
const synCfg = await cfgDirFromTemplate(template);
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
const containerName = `react-sdk-cypress-synapse-${crypto.randomBytes(4).toString("hex")}`;
const synapseId = await new Promise<string>((resolve, reject) => {
childProcess.execFile('docker', [
"run",
"--name", containerName,
"-d",
"-v", `${synCfg.configDir}:/data`,
"-p", "8008/tcp",
"matrixdotorg/synapse:develop",
"run",
], (err, stdout) => {
if (err) reject(err);
resolve(stdout.trim());
});
});
// Get the port that docker allocated: specifying only one
// port above leaves docker to just grab a free one, although
// in hindsight we need to put the port in public_baseurl in the
// config really, so this will probably need changing to use a fixed
// / configured port.
const port = await new Promise<number>((resolve, reject) => {
childProcess.execFile('docker', [
"port", synapseId, "8008",
], { encoding: 'utf8' }, (err, stdout) => {
if (err) reject(err);
resolve(Number(stdout.trim().split(":")[1]));
});
});
synapses.set(synapseId, Object.assign({
port,
synapseId,
}, synCfg));
console.log(`Started synapse with id ${synapseId} on port ${port}.`);
return synapses.get(synapseId);
}
async function synapseStop(id) {
const synCfg = synapses.get(id);
if (!synCfg) throw new Error("Unknown synapse ID");
try {
const synapseLogsPath = path.join("cypress", "synapselogs", id);
await fse.ensureDir(synapseLogsPath);
const stdoutFile = await fse.open(path.join(synapseLogsPath, "stdout.log"), "w");
const stderrFile = await fse.open(path.join(synapseLogsPath, "stderr.log"), "w");
await new Promise<void>((resolve, reject) => {
childProcess.spawn('docker', [
"logs",
id,
], {
stdio: ["ignore", stdoutFile, stderrFile],
}).once('close', resolve);
});
await fse.close(stdoutFile);
await fse.close(stderrFile);
await new Promise<void>((resolve, reject) => {
childProcess.execFile('docker', [
"stop",
id,
], err => {
if (err) reject(err);
resolve();
});
});
} finally {
await new Promise<void>((resolve, reject) => {
childProcess.execFile('docker', [
"rm",
id,
], err => {
if (err) reject(err);
resolve();
});
});
}
await fse.remove(synCfg.configDir);
synapses.delete(id);
console.log(`Stopped synapse id ${id}.`);
// cypres deliberately fails if you return 'undefined', so
// return null to signal all is well and we've handled the task.
return null;
}
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
export function synapseDocker(on, config) {
on("task", {
synapseStart, synapseStop,
});
on("after:spec", async (spec) => {
// Cleans up any remaining synapse instances after a spec run
// This is on the theory that we should avoid re-using synapse
// instances between spec runs: they should be cheap enough to
// start that we can have a separate one for each spec run or even
// test. If we accidentally re-use synapses, we could inadvertantly
// make our tests depend on each other.
for (const synId of synapses.keys()) {
console.warn(`Cleaning up synapse ID ${synId} after ${spec.name}`);
await synapseStop(synId);
}
});
on("before:run", async () => {
// tidy up old synapse log files before each run
await fse.emptyDir(path.join("cypress", "synapselogs"));
});
}

View file

@ -0,0 +1,3 @@
# Meta-template for synapse templates
To make another template, you can copy this directory

View file

@ -0,0 +1,72 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
# XXX: This won't actually be right: it lets docker allocate an ephemeral port,
# so we have a chicken-and-egg problem
public_baseurl: http://localhost:8008/
# Listener is always port 8008 (configured in the container)
listeners:
- port: 8008
tls: false
bind_addresses: ['::']
type: http
x_forwarded: true
resources:
- names: [client, federation, consent]
compress: false
# An sqlite in-memory database is fast & automatically wipes each time
database:
name: "sqlite3"
args:
database: ":memory:"
# Needs to be configured to log to the console like a good docker process
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
# These placeholders will be be replaced with values generated at start
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
# Signing key must be here: it will be generated to this file
signing_key_path: "/data/localhost.signing.key"
email:
enable_notifs: false
smtp_host: "localhost"
smtp_port: 25
smtp_user: "exampleusername"
smtp_pass: "examplepassword"
require_transport_security: False
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
app_name: Matrix
notif_template_html: notif_mail.html
notif_template_text: notif_mail.txt
notif_for_new_users: True
client_base_url: "http://localhost/element"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true

View file

@ -0,0 +1,50 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: INFO
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View file

@ -0,0 +1 @@
A synapse configured with user privacy consent enabled

View file

@ -0,0 +1,84 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: http://localhost:5005/
listeners:
- port: 8008
tls: false
bind_addresses: ['::']
type: http
x_forwarded: true
resources:
- names: [client, federation, consent]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
email:
enable_notifs: false
smtp_host: "localhost"
smtp_port: 25
smtp_user: "exampleusername"
smtp_pass: "examplepassword"
require_transport_security: False
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
app_name: Matrix
notif_template_html: notif_mail.html
notif_template_text: notif_mail.txt
notif_for_new_users: True
client_base_url: "http://localhost/element"
user_consent:
template_dir: /data/res/templates/privacy
version: 1.0
server_notice_content:
msgtype: m.text
body: >-
To continue using this homeserver you must review and agree to the
terms and conditions at %(consent_uri)s
send_server_notice_to_guests: True
block_events_error: >-
To continue using this homeserver you must review and agree to the
terms and conditions at %(consent_uri)s
require_at_registration: true
server_notices:
system_mxid_localpart: notices
system_mxid_display_name: "Server Notices"
system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ"
room_name: "Server Notices"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true

View file

@ -0,0 +1,50 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: INFO
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View file

@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<title>Test Privacy policy</title>
</head>
<body>
{% if has_consented %}
<p>
Thank you, you've already accepted the license.
</p>
{% else %}
<p>
Please accept the license!
</p>
<form method="post" action="consent">
<input type="hidden" name="v" value="{{version}}"/>
<input type="hidden" name="u" value="{{user}}"/>
<input type="hidden" name="h" value="{{userhmac}}"/>
<input type="submit" value="Sure thing!"/>
</form>
{% endif %}
</body>
</html>

View file

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<title>Test Privacy policy</title>
</head>
<body>
<p>Danke schon</p>
</body>
</html>

3
cypress/support/index.ts Normal file
View file

@ -0,0 +1,3 @@
// Empty file to prevent cypress from recreating a helpful example
// file on every run (their example file doesn't use semicolons and
// so fails our lint rules).

9
cypress/tsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "es2016",
"lib": ["es2020", "dom"],
"types": ["cypress"],
"moduleResolution": "node"
},
"include": ["**/*.ts"]
}

View file

@ -1,71 +1,18 @@
# Skinning
The react-sdk can be skinned to replace presentation components, CSS, or
other relevant parts of the SDK. Typically consumers will replace entire
components and get the ability for custom CSS as a result.
Skinning in the context of the react-sdk is component replacement rather than CSS. This means you can override (replace)
any accessible component in the project to implement custom behaviour, look & feel, etc. Depending on your approach,
overriding CSS classes to apply custom styling is also possible, though harder to do.
This doc isn't exhaustive on how skinning works, though it should cover
some of the more complicated parts such as component replacement.
At present, the react-sdk offers no stable interface for components - this means properties and state can and do change
at any time without notice. Once we determine the react-sdk to be stable enough to use as a proper SDK, we will adjust
this policy. In the meantime, skinning is done completely at your own risk.
## Loading a skin
The approach you take is up to you - we suggest using a module replacement plugin, as found in
[webpack](https://webpack.js.org/plugins/normal-module-replacement-plugin/), though you're free to use whichever build
system works for you. The react-sdk does not have any particular functions to call to load skins, so simply replace or
extend the components/stores/etc you're after and build. As a reminder though, this is done completely at your own risk
as we cannot guarantee a stable interface at this time.
1. Generate a `component-index.js` (preferably using the tools that the react-sdk
exposes). This can typically be done with a npm script like `"reskindex -h src/header"`.
2. In your app's entry point, add something like this code:
```javascript
import {loadSkin} from "matrix-react-sdk";
loadSkin(import("component-index").components);
// The rest of your imports go under this.
```
3. Import the remainder of the SDK and bootstrap your app.
It is extremely important that you **do not** import anything else from the
SDK prior to loading your skin as otherwise the skin might not work. Loading
the skin should be one of the first things your app does, if not the very
first thing.
Additionally, **do not** provide `loadSkin` with the react-sdk components
themselves otherwise the app might explode. The SDK is already aware of its
components and doesn't need to be told.
## Replacing components
Components that replace the react-sdk ones MUST have a `replaces` static
key on the component's class to describe which component it overrides. For
example, if your `VectorAuthPage` component is meant to replace the react-sdk
`AuthPage` component then you'd add `static replaces = 'views.auth.AuthPage';`
to the `VectorAuthPage` class.
Other than that, the skin just needs to be loaded normally as mentioned above.
Consumers of the SDK likely will not be interested in the rest of this section.
### SDK developer notes
Components in the react-sdk MUST be decorated with the `@replaceableComponent`
function. For components that can't use the decorator, they must use a
variation that provides similar functionality. The decorator gives consumers
an opportunity to load skinned components by abusing import ordering and
behaviour.
Decorators are executed at import time which is why we can abuse the import
ordering behaviour: importing `loadSkin` doesn't trigger any components to
be imported, allowing the consumer to specify a skin. When the consumer does
import a component (for example, `MatrixChat`), it starts to pull in all the
components via `import` statements. When the components get pulled in the
decorator checks with the skinned components to see if it should be replacing
the component being imported. The decorator then effectively replaces the
components when needed by specifying the skinned component as an override for
the SDK's component, which should in theory override critical functions like
`render()` and lifecycle event handlers.
The decorator also means that older usage of `getComponent()` is no longer
required because components should be replaced by the decorator. Eventually
the react-sdk should only have one usage of `getComponent()`: the decorator.
The decorator assumes that if `getComponent()` returns null that there is
no skinned version of the component and continues on using the SDK's component.
In previous versions of the SDK, the function would throw an error instead
because it also expected the skin to list the SDK's components as well, however
that is no longer possible due to the above.
In short, components should always be `import`ed.
Taking a look at [element-web](https://github.com/vector-im/element-web)'s approach to skinning may be worthwhile, as it
overrides some relatively simple components.

View file

@ -1,88 +0,0 @@
Guide to data types used by the Slate-based Rich Text Editor
------------------------------------------------------------
We always store the Slate editor state in its Value form.
The schema for the Value is the same whether the editor is in MD or rich text mode, and is currently (rather arbitrarily)
dictated by the schema expected by slate-md-serializer, simply because it was the only bit of the pipeline which
has opinions on the schema. (slate-html-serializer lets you define how to serialize whatever schema you like).
The BLOCK_TAGS and MARK_TAGS give the mapping from HTML tags to the schema's node types (for blocks, which describe
block content like divs, and marks, which describe inline formatted sections like spans).
We use <p/> as the parent tag for the message (XXX: although some tags are technically not allowed to be nested within p's)
Various conversions are performed as content is moved between HTML, MD, and plaintext representations of HTML and MD.
The primitives used are:
* Markdown.js - models commonmark-formatted MD strings (as entered by the composer in MD mode)
* toHtml() - renders them to HTML suitable for sending on the wire
* isPlainText() - checks whether the parsed MD contains anything other than simple text.
* toPlainText() - renders MD to plain text in order to remove backslashes. Only works if the MD is already plaintext (otherwise it just emits HTML)
* slate-html-serializer
* converts Values to HTML (serialising) using our schema rules
* converts HTML to Values (deserialising) using our schema rules
* slate-md-serializer
* converts rich Values to MD strings (serialising) but using a non-commonmark generic MD dialect.
* This should use commonmark, but we use the serializer here for expedience rather than writing a commonmark one.
* slate-plain-serializer
* converts Values to plain text strings (serialising them) by concatenating the strings together
* converts Values from plain text strings (deserialiasing them).
* Used to initialise the editor by deserializing "" into a Value. Apparently this is the idiomatic way to initialise a blank editor.
* Used (as a bodge) to turn a rich text editor into a MD editor, when deserialising the converted MD string of the editor into a value
* PlainWithPillsSerializer
* A fork of slate-plain-serializer which is aware of Pills (hence the name) and Emoji.
* It can be configured to output Pills as:
* "plain": Pills are rendered via their 'completion' text - e.g. 'Matthew'; used for sending messages)
* "md": Pills are rendered as MD, e.g. [Matthew](https://matrix.to/#/@matthew:matrix.org) )
* "id": Pills are rendered as IDs, e.g. '@matthew:matrix.org' (used for authoring / commands)
* Emoji nodes are converted to inline utf8 emoji.
The actual conversion transitions are:
* Quoting:
* The message being quoted is taken as HTML
* ...and deserialised into a Value
* ...and then serialised into MD via slate-md-serializer if the editor is in MD mode
* Roundtripping between MD and rich text editor mode
* From MD to richtext (mdToRichEditorState):
* Serialise the MD-format Value to a MD string (converting pills to MD) with PlainWithPillsSerializer in 'md' mode
* Convert that MD string to HTML via Markdown.js
* Deserialise that Value to HTML via slate-html-serializer
* From richtext to MD (richToMdEditorState):
* Serialise the richtext-format Value to a MD string with slate-md-serializer (XXX: this should use commonmark)
* Deserialise that to a plain text value via slate-plain-serializer
* Loading history in one format into an editor which is in the other format
* Uses the same functions as for roundtripping
* Scanning the editor for a slash command
* If the editor is a single line node starting with /, then serialize it to a string with PlainWithPillsSerializer in 'id' mode
So that pills get converted to IDs suitable for commands being passed around
* Sending messages
* In RT mode:
* If there is rich content, serialize the RT-format Value to HTML body via slate-html-serializer
* Serialize the RT-format Value to the plain text fallback via PlainWithPillsSerializer in 'plain' mode
* In MD mode:
* Serialize the MD-format Value into an MD string with PlainWithPillsSerializer in 'md' mode
* Parse the string with Markdown.js
* If it contains no formatting:
* Send as plaintext (as taken from Markdown.toPlainText())
* Otherwise
* Send as HTML (as taken from Markdown.toHtml())
* Serialize the RT-format Value to the plain text fallback via PlainWithPillsSerializer in 'plain' mode
* Pasting HTML
* Deserialize HTML to a RT Value via slate-html-serializer
* In RT mode, insert it straight into the editor as a fragment
* In MD mode, serialise it to an MD string via slate-md-serializer and then insert the string into the editor as a fragment.
The various scenarios and transitions could be drawn into a pretty diagram if one felt the urge, but hopefully the above
gives sufficient detail on how it's all meant to work.

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "3.42.4",
"version": "3.43.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -22,9 +22,6 @@
"README.md",
"package.json"
],
"bin": {
"reskindex": "scripts/reskindex.js"
},
"main": "./lib/index.ts",
"matrix_src_main": "./src/index.ts",
"matrix_lib_main": "./lib/index.ts",
@ -37,23 +34,23 @@
"i18n": "matrix-gen-i18n",
"prunei18n": "matrix-prune-i18n",
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
"reskindex": "node scripts/reskindex.js -h header",
"make-component": "node scripts/make-react-component.js",
"reskindex:watch": "node scripts/reskindex.js -h header -w",
"rethemendex": "res/css/rethemendex.sh",
"clean": "rimraf lib",
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
"build:compile": "yarn reskindex && babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js,.tsx\" src",
"build:types": "tsc --emitDeclarationOnly --jsx react",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"",
"start:all": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:build",
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
"lint:js": "eslint --max-warnings 0 src test",
"lint:js-fix": "eslint --fix src test",
"lint:types": "tsc --noEmit --jsx react",
"lint:js": "eslint --max-warnings 0 src test cypress",
"lint:js-fix": "eslint --fix src test cypress",
"lint:types": "tsc --noEmit --jsx react && tsc --noEmit -p cypress",
"lint:style": "stylelint \"res/css/**/*.scss\"",
"test": "jest",
"test:cypress": "cypress run",
"test:cypress:open": "cypress open",
"test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080",
"coverage": "yarn test --coverage"
},
@ -64,7 +61,6 @@
"@types/geojson": "^7946.0.8",
"await-lock": "^2.1.0",
"blurhash": "^1.1.3",
"browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3",
"cheerio": "^1.0.0-rc.9",
"classnames": "^2.2.6",
@ -93,8 +89,9 @@
"lodash": "^4.17.20",
"maplibre-gl": "^1.15.2",
"matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#daad3faed54f0b1f1e026a7498b4653e4d01cd90",
"matrix-encrypt-attachment": "^1.0.3",
"matrix-events-sdk": "^0.0.1-beta.7",
"matrix-js-sdk": "17.0.0",
"matrix-js-sdk": "17.1.0",
"matrix-widget-api": "^0.1.0-beta.18",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
@ -125,7 +122,6 @@
"@babel/eslint-plugin": "^7.12.10",
"@babel/parser": "^7.12.11",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-decorators": "^7.12.12",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
@ -138,7 +134,7 @@
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@peculiar/webcrypto": "^1.1.4",
"@sentry/types": "^6.10.0",
"@sinonjs/fake-timers": "^7.0.2",
"@sinonjs/fake-timers": "^9.1.2",
"@types/classnames": "^2.2.11",
"@types/commonmark": "^0.27.4",
"@types/counterpart": "^0.18.1",
@ -148,6 +144,7 @@
"@types/escape-html": "^1.0.1",
"@types/file-saver": "^2.0.3",
"@types/flux": "^3.1.9",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^26.0.20",
"@types/lodash": "^4.14.168",
"@types/modernizr": "^3.5.3",
@ -158,6 +155,7 @@
"@types/react": "17.0.14",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "17.0.9",
"@types/react-test-renderer": "^17.0.1",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
"@types/zxcvbn": "^4.4.0",
@ -168,7 +166,7 @@
"babel-jest": "^26.6.3",
"blob-polyfill": "^6.0.20211015",
"chokidar": "^3.5.1",
"concurrently": "^5.3.0",
"cypress": "^9.5.4",
"enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2",
"eslint": "8.9.0",
@ -178,6 +176,7 @@
"eslint-plugin-matrix-org": "^0.4.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"fs-extra": "^10.0.1",
"glob": "^7.1.6",
"jest": "^27.4.0",
"jest-canvas-mock": "^2.3.0",

View file

@ -254,7 +254,7 @@ legend {
}
// These are magic constants which are excluded from tinting, to let themes
// (which only have CSS, unlike skins) tell the app what their non-tinted
// (which only have CSS) tell the app what their non-tinted
// colourscheme is by inspecting the stylesheet DOM.
//
// They are not used for layout!!

View file

@ -4,14 +4,21 @@
@import "./_font-sizes.scss";
@import "./_font-weights.scss";
@import "./_spacing.scss";
@import "./components/views/beacon/_BeaconStatus.scss";
@import "./components/views/beacon/_BeaconViewDialog.scss";
@import "./components/views/beacon/_LeftPanelLiveShareWarning.scss";
@import "./components/views/beacon/_LiveTimeRemaining.scss";
@import "./components/views/beacon/_OwnBeaconStatus.scss";
@import "./components/views/beacon/_RoomLiveShareWarning.scss";
@import "./components/views/beacon/_StyledLiveBeaconIcon.scss";
@import "./components/views/location/_LiveDurationDropdown.scss";
@import "./components/views/location/_LocationShareMenu.scss";
@import "./components/views/location/_MapError.scss";
@import "./components/views/location/_Marker.scss";
@import "./components/views/location/_ShareDialogButtons.scss";
@import "./components/views/location/_ShareType.scss";
@import "./components/views/location/_ZoomButtons.scss";
@import "./components/views/messages/_MBeaconBody.scss";
@import "./components/views/spaces/_QuickThemeSwitcher.scss";
@import "./structures/_AutoHideScrollbar.scss";
@import "./structures/_BackdropPanel.scss";

View 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.
*/
.mx_BeaconStatus {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
padding: $spacing-8;
color: var(--color);
font-size: $font-12px;
}
.mx_BeaconStatus_Loading,
.mx_BeaconStatus_Stopped {
--color: $tertiary-content;
}
.mx_BeaconStatus_Active,
.mx_BeaconStatus_Error {
--color: $primary-content;
}
.mx_BeaconStatus_icon {
height: 32px;
width: 32px;
flex: 0 0 32px;
margin-right: $spacing-8;
}
.mx_BeaconStatus_description {
flex: 1;
display: flex;
flex-direction: column;
line-height: $font-14px;
padding-right: $spacing-8;
// TODO handle text-overflow
}
.mx_BeaconStatus_expiryTime {
color: $secondary-content;
}

View file

@ -0,0 +1,79 @@
/*
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.
*/
.mx_BeaconViewDialog_wrapper .mx_Dialog {
padding: 0px;
/* Unset contain and position to allow the close button
to appear outside the dialog */
contain: unset;
position: unset;
}
.mx_BeaconViewDialog {
/* subtract 0.5px to prevent single-pixel margin due to rounding */
width: calc(80vw - 0.5px);
height: calc(80vh - 0.5px);
overflow: hidden;
.mx_Dialog_header {
margin: 0px;
padding: 0px;
position: unset;
.mx_Dialog_title {
display: none;
}
.mx_Dialog_cancelButton {
z-index: 4010;
position: absolute;
right: 5vw;
top: 5vh;
width: 20px;
height: 20px;
background-color: $dialog-close-external-color;
}
}
}
.mx_BeaconViewDialog_map {
width: 80vw;
height: 80vh;
border-radius: 8px;
}
.mx_BeaconViewDialog_mapFallback {
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: url('$(res)/img/location/map.svg');
background-size: cover;
}
.mx_BeaconViewDialog_mapFallbackIcon {
width: 65px;
margin-bottom: $spacing-16;
color: $quaternary-content;
}
.mx_BeaconViewDialog_mapFallbackMessage {
color: $secondary-content;
margin-bottom: $spacing-16;
}

View file

@ -0,0 +1,20 @@
/*
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.
*/
.mx_LiveTimeRemaining {
color: $secondary-content;
font-size: $font-12px;
}

View file

@ -0,0 +1,27 @@
/*
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.
*/
.mx_EventTile[data-layout="bubble"] .mx_OwnBeaconStatus_button {
// align to top to make room for timestamp
// in bubble view
align-self: start;
}
.mx_OwnBeaconStatus_destructiveButton {
// override button link_inline styles
color: $alert !important;
font-weight: $font-semi-bold !important;
}

View file

@ -39,12 +39,6 @@ limitations under the License.
font-size: $font-15px;
}
.mx_RoomLiveShareWarning_expiry {
color: $secondary-content;
font-size: $font-12px;
margin-right: $spacing-16;
}
.mx_RoomLiveShareWarning_spinner {
margin-right: $spacing-16;
}
@ -54,6 +48,10 @@ limitations under the License.
margin-left: $spacing-16;
}
.mx_RoomLiveShareWarning_stopButton {
margin-left: $spacing-16;
}
.mx_RoomLiveShareWarning_closeButtonIcon {
height: $font-18px;
padding: $spacing-4;

View file

@ -33,3 +33,8 @@ limitations under the License.
background-color: $alert;
border-color: $alert;
}
.mx_StyledLiveBeaconIcon.mx_StyledLiveBeaconIcon_idle {
background-color: $quaternary-content;
border-color: $quaternary-content;
}

View file

@ -0,0 +1,46 @@
/*
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.
*/
.mx_Marker_defaultColor {
color: $accent;
}
.mx_Marker_border {
width: 42px;
height: 42px;
border-radius: 50%;
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
background-color: currentColor;
display: flex;
justify-content: center;
align-items: center;
// caret down
&::before {
content: '';
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid currentColor;
position: absolute;
bottom: -4px;
}
}
.mx_Marker_icon {
color: white;
height: 20px;
}

View file

@ -0,0 +1,45 @@
/*
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.
*/
.mx_ZoomButtons {
position: absolute;
bottom: $spacing-32;
right: $spacing-24;
}
.mx_ZoomButtons_button {
@mixin ButtonResetDefault;
margin-top: $spacing-8;
border-radius: 4px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 24px;
width: 24px;
background: $background;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
}
.mx_ZoomButtons_icon {
height: 10px;
width: 10px;
color: $primary-content;
}

View file

@ -0,0 +1,65 @@
/*
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.
*/
.mx_MBeaconBody {
position: relative;
height: 220px;
width: 325px;
border-radius: $timeline-image-border-radius;
overflow: hidden;
}
.mx_MBeaconBody_map {
height: 100%;
width: 100%;
z-index: 0; // keeps the entire map under the message action bar
&:not(.mx_MBeaconBody_mapFallback) {
cursor: pointer;
}
}
.mx_MBeaconBody_mapFallback {
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
// pushes spinner/icon up
// to appear more centered with the footer
padding-bottom: 50px;
background: url('$(res)/img/location/map.svg');
background-size: cover;
}
.mx_MBeaconBody_mapFallbackIcon {
width: 65px;
color: $quaternary-content;
}
.mx_MBeaconBody_chin {
position: absolute;
bottom: 0;
width: 100%;
background-color: $overlay-background;
}
.mx_EventTile[data-layout="bubble"] .mx_EventTile_line .mx_MBeaconBody {
max-width: 100%;
width: 450px;
}

View file

@ -251,7 +251,8 @@ $activeBorderColor: $primary-content;
margin-top: auto;
margin-bottom: auto;
display: none;
position: relative;
position: absolute;
right: 4px;
&::before {
top: 3px;
@ -327,6 +328,16 @@ $activeBorderColor: $primary-content;
}
}
.mx_SpaceItem:not(.mx_SpaceItem_new) {
.mx_SpaceButton:hover,
.mx_SpaceButton:focus-within,
.mx_SpaceButton_hasMenuOpen {
&:not(.mx_SpaceButton_narrow):not(.mx_SpaceButton_invite) .mx_SpaceButton_name {
max-width: calc(100% - 56px);
}
}
}
/* root space buttons are bigger and not indented */
& > .mx_AutoHideScrollbar {
flex: 1;

View file

@ -108,26 +108,25 @@ limitations under the License.
}
.mx_Toast_title {
display: flex;
align-items: center;
column-gap: 8px;
width: 100%;
box-sizing: border-box;
h2 {
grid-column: 1 / 3;
grid-row: 1;
margin: 0;
font-size: $font-15px;
font-weight: 600;
display: inline;
width: auto;
vertical-align: middle;
}
span {
padding-left: 8px;
float: right;
.mx_Toast_title_countIndicator {
font-size: $font-12px;
line-height: $font-22px;
color: $secondary-content;
margin-inline-start: auto; // on the end side of the div
}
}
@ -137,17 +136,14 @@ limitations under the License.
}
.mx_Toast_buttons {
float: right;
display: flex;
justify-content: flex-end;
column-gap: 5px;
.mx_AccessibleButton {
min-width: 96px;
box-sizing: border-box;
}
.mx_AccessibleButton + .mx_AccessibleButton {
margin-left: 5px;
}
}
.mx_Toast_description {
@ -157,10 +153,6 @@ limitations under the License.
margin: 4px 0 11px 0;
font-size: $font-12px;
.mx_AccessibleButton_kind_link {
font-size: inherit;
}
a {
text-decoration: none;
}

View file

@ -147,11 +147,6 @@ limitations under the License.
margin-top: 8px;
}
}
.mx_AccessibleButton_kind_link {
font-weight: normal;
font-size: inherit;
}
}
}

View file

@ -34,8 +34,13 @@ limitations under the License.
padding: 0.5em 1em 0.5em 1em;
word-wrap: break-word;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.mx_ViewSource_details {
margin-top: 0.8em;
}
.mx_ViewSource_container {
max-width: calc(100% - 24px);
}

View file

@ -88,13 +88,12 @@ limitations under the License.
div.mx_AccessibleButton_kind_link.mx_Login_forgot {
display: block;
margin: 0 auto;
// style it as a link
font-size: inherit;
&.mx_AccessibleButton_disabled {
cursor: not-allowed;
}
}
.mx_Login_spinner {
display: flex;
justify-content: center;

View file

@ -90,6 +90,22 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/room/pin.svg');
}
.mx_MessageContextMenu_iconCopy::before {
mask-image: url($copy-button-url);
}
.mx_MessageContextMenu_iconEdit::before {
mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg');
}
.mx_MessageContextMenu_iconReply::before {
mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg');
}
.mx_MessageContextMenu_iconReact::before {
mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg');
}
.mx_MessageContextMenu_iconViewInRoom::before {
mask-image: url('$(res)/img/element-icons/view-in-room.svg');
}

View file

@ -58,10 +58,6 @@ limitations under the License.
line-height: $font-15px;
}
.mx_AccessibleButton_kind_link {
font-size: inherit;
}
a, .mx_AccessibleButton_kind_link {
color: $accent;
text-decoration: underline;

View file

@ -21,9 +21,4 @@ limitations under the License.
line-height: $font-20px;
margin-bottom: 24px;
}
.mx_AccessibleButton_kind_link {
font-size: inherit;
line-height: inherit;
}
}

View file

@ -48,49 +48,10 @@ limitations under the License.
background-color: $dialog-close-external-color;
}
}
.mx_MLocationBody {
position: absolute;
.mx_MLocationBody_map {
width: 80vw;
height: 80vh;
}
.mx_MLocationBody_zoomButtons {
position: absolute;
display: grid;
grid-template-columns: auto;
grid-row-gap: 8px;
right: 24px;
bottom: 48px;
.mx_AccessibleButton {
background-color: $background;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
border-radius: 4px;
width: 24px;
height: 24px;
.mx_MLocationBody_zoomButton {
background-color: $primary-content;
margin: 4px;
width: 16px;
height: 16px;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}
.mx_MLocationBody_plusButton {
mask-image: url('$(res)/img/element-icons/plus-button.svg');
}
.mx_MLocationBody_minusButton {
mask-image: url('$(res)/img/element-icons/minus-button.svg');
}
}
}
}
}
.mx_LocationViewDialog_map {
width: 80vw;
height: 80vh;
border-radius: 8px;
}

View file

@ -22,84 +22,99 @@ limitations under the License.
margin: 0;
padding: 0;
}
}
.mx_RoomSettingsDialog_BridgeList li {
list-style-type: none;
padding: 5px;
margin-bottom: 8px;
border-width: 1px 1px;
border-color: $primary-hairline-color;
border-style: solid;
border-radius: 5px;
li {
list-style-type: none;
.column-icon {
float: left;
padding-right: 10px;
&.mx_RoomSettingsDialog_BridgeList_listItem {
display: flex;
flex-wrap: wrap;
gap: $spacing-8;
padding: 5px;
margin-bottom: $spacing-8;
* {
// border-style around each bridge list item
border-width: 1px 1px;
border-color: $primary-hairline-color;
border-style: solid;
border-radius: 5px;
border: 1px solid $input-darker-bg-color;
}
.noProtocolIcon {
width: 48px;
height: 48px;
background: $input-darker-bg-color;
border-radius: 5px;
}
.mx_RoomSettingsDialog_column_icon {
.mx_RoomSettingsDialog_protocolIcon,
.mx_RoomSettingsDialog_protocolIcon span,
.mx_RoomSettingsDialog_noProtocolIcon {
box-sizing: border-box;
border-radius: 5px;
border: 1px solid $input-darker-bg-color;
}
.protocol-icon {
float: left;
margin-right: 5px;
img {
border-radius: 5px;
border-width: 1px 1px;
border-color: $primary-hairline-color;
.mx_RoomSettingsDialog_noProtocolIcon,
.mx_RoomSettingsDialog_protocolIcon img {
border-radius: 5px;
}
.mx_RoomSettingsDialog_noProtocolIcon {
width: 48px;
height: 48px;
background: $input-darker-bg-color;
}
.mx_RoomSettingsDialog_protocolIcon {
img {
border-width: 1px 1px;
border-color: $primary-hairline-color;
}
span {
/* Correct letter placement */
left: auto;
}
}
}
span {
/* Correct letter placement */
left: auto;
}
}
}
.column-data {
display: inline-block;
width: 85%;
.mx_RoomSettingsDialog_column_data {
display: inline-block;
width: 85%;
> h3 {
margin-top: 0px;
margin-bottom: 0px;
font-size: 16pt;
color: $primary-content;
}
.mx_RoomSettingsDialog_column_data_details,
.mx_RoomSettingsDialog_column_data_metadata,
.mx_RoomSettingsDialog_column_data_metadata li,
.mx_RoomSettingsDialog_column_data_protocolName {
margin-bottom: 0;
}
> * {
margin-top: 4px;
margin-bottom: 0;
}
.mx_RoomSettingsDialog_column_data_details,
.mx_RoomSettingsDialog_column_data_metadata {
margin-top: $spacing-4;
}
.workspace-channel-details {
color: $primary-content;
font-weight: 600;
.mx_RoomSettingsDialog_column_data_metadata li {
margin-top: $spacing-8;
}
.channel {
margin-left: 5px;
}
}
.mx_RoomSettingsDialog_column_data_protocolName {
margin-top: 0;
font-size: 16pt;
color: $primary-content;
}
.metadata {
color: $muted-fg-color;
margin-bottom: 0;
overflow-y: visible;
text-overflow: ellipsis;
white-space: normal;
padding: 0;
.mx_RoomSettingsDialog_workspace_channel_details {
color: $primary-content;
font-weight: $font-semi-bold;
> li {
padding: 0;
border: 0;
.mx_RoomSettingsDialog_channel {
margin-inline-start: 5px;
}
}
.mx_RoomSettingsDialog_metadata {
color: $muted-fg-color;
margin-bottom: 0;
overflow-y: visible;
text-overflow: ellipsis;
white-space: normal;
padding: 0;
}
}
}
}

View file

@ -77,6 +77,7 @@ limitations under the License.
.mx_AccessibleButton_hasKind {
&.mx_AccessibleButton_kind_link {
font-size: $font-14px;
margin: 7px 18px;
&.mx_SettingsTab_showAdvanced {

View file

@ -241,7 +241,6 @@ limitations under the License.
.mx_SpotlightDialog_recentSearches > h4 > .mx_AccessibleButton_kind_link {
padding: 0;
float: right;
font-weight: normal;
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-content;

View file

@ -39,6 +39,7 @@ limitations under the License.
justify-content: center;
font-size: $font-14px;
border: none; // override default <button /> styles
word-break: keep-all; // prevent button text in Chinese/Japanese/Korean (CJK) from being collapsed
&.mx_AccessibleButton_kind_primary_sm,
&.mx_AccessibleButton_kind_danger_sm,
@ -130,14 +131,20 @@ limitations under the License.
}
}
&.mx_AccessibleButton_kind_link {
&.mx_AccessibleButton_kind_link,
&.mx_AccessibleButton_kind_link_inline {
color: $accent;
font-size: inherit;
font-weight: normal;
line-height: inherit;
}
&.mx_AccessibleButton_kind_link {
padding: 0;
}
&.mx_AccessibleButton_kind_link_inline {
color: $accent;
font-size: inherit;
display: inline;
padding: 0 2px;
}

View file

@ -18,14 +18,17 @@ limitations under the License.
.mx_CopyableText {
display: flex;
justify-content: space-between;
border-radius: 5px;
border: solid 1px $light-fg-color;
margin-bottom: 10px;
margin-top: 10px;
padding: 10px;
width: max-content;
max-width: 100%;
&.mx_CopyableText_border {
border-radius: 5px;
border: solid 1px $light-fg-color;
margin-bottom: 10px;
margin-top: 10px;
padding: 10px;
}
.mx_CopyableText_copyButton {
flex-shrink: 0;
width: 20px;

View file

@ -29,7 +29,9 @@ limitations under the License.
margin-left: 16px; // distance from <Field>
}
.mx_Field, .mx_Field input, .mx_AccessibleButton {
.mx_Field,
.mx_Field input,
.mx_AccessibleButton {
// So they look related to each other by feeling the same
border-radius: 8px;
}
@ -39,39 +41,48 @@ limitations under the License.
display: flex;
flex-wrap: wrap;
margin-top: 12px; // this plus 12px from the tags makes 24px from the input
}
.mx_TagComposer_tag {
padding: 6px 8px 8px 12px;
position: relative;
margin-right: 12px;
margin-top: 12px;
.mx_Tag {
margin-right: 12px;
margin-top: 12px;
}
}
// Cheaty way to get an opacified variable colour background
&::before {
content: '';
border-radius: 20px;
background-color: $tertiary-content;
opacity: 0.15;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
.mx_Tag {
// Pass through the pointer otherwise we have effectively put a whole div
// on top of the component, which makes it hard to interact with buttons.
pointer-events: none;
}
}
font-size: $font-15px;
.mx_AccessibleButton {
background-image: url('$(res)/img/subtract.svg');
width: 16px;
height: 16px;
margin-left: 8px;
display: inline-block;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 8px;
color: $primary-content;
background: $quinary-content;
>svg:first-child {
width: 1em;
color: $secondary-content;
transform: scale(1.25);
transform-origin: center;
}
.mx_Tag_delete {
border-radius: 50%;
text-align: center;
width: 1.066666em; // 16px;
height: 1.066666em;
line-height: 1em;
color: $secondary-content;
background: $system;
position: relative;
svg {
width: .5em;
vertical-align: middle;
cursor: pointer;
}
}
}

View file

@ -55,39 +55,6 @@ limitations under the License.
.maplibregl-user-location-dot {
display: none;
}
.mx_MLocationBody_markerBorder {
width: 31px;
height: 31px;
border-radius: 50%;
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
background-color: currentColor;
display: flex;
align-items: center;
justify-content: center;
}
.mx_MLocationBody_pointer {
position: absolute;
bottom: -3px;
left: 11px;
width: 9px;
height: 5px;
&::before {
mask-image: url('$(res)/img/location/pointer.svg');
mask-position: center;
mask-repeat: no-repeat;
mask-size: 9px;
content: '';
display: inline-block;
width: 9px;
height: 5px;
position: absolute;
background-color: currentColor;
}
}
}
.mx_LocationPicker_footer {
@ -106,11 +73,6 @@ limitations under the License.
}
}
.mx_MLocationBody_markerIcon {
color: white;
height: 20px;
}
.mx_LocationPicker_pinText {
position: absolute;
top: $spacing-16;
@ -135,11 +97,3 @@ limitations under the License.
width: 100%;
height: 48px;
}
// live marker color is set by user color class
// generated from userid
// others are $accent
.mx_MLocationBody_marker-Self,
.mx_MLocationBody_marker-Pin {
color: $accent;
}

View file

@ -19,22 +19,24 @@ limitations under the License.
width: 100%;
.mx_CallEvent {
position: relative;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: $spacing-4 0;
position: relative;
margin: $spacing-4 0;
padding: $spacing-12 $spacing-24;
box-sizing: border-box;
background-color: $dark-panel-bg-color;
border-radius: 8px;
width: 65%;
box-sizing: border-box;
height: 60px;
margin: 4px 0;
height: fit-content;
.mx_CallEvent_iconButton {
display: inline-flex;
margin-right: 8px;
&::before {
content: '';
@ -62,6 +64,13 @@ limitations under the License.
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
}
&.mx_CallEvent_rejected,
&.mx_CallEvent_noAnswer {
.mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/declined-voice.svg');
}
}
}
&.mx_CallEvent_video {
@ -70,44 +79,49 @@ limitations under the License.
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
}
&.mx_CallEvent_rejected,
&.mx_CallEvent_noAnswer {
.mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/declined-video.svg');
}
}
}
&.mx_CallEvent_voice.mx_CallEvent_missed .mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/missed-voice.svg');
}
&.mx_CallEvent_missed {
&.mx_CallEvent_voice {
.mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/missed-voice.svg');
}
}
&.mx_CallEvent_video.mx_CallEvent_missed .mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/missed-video.svg');
}
&.mx_CallEvent_voice.mx_CallEvent_rejected .mx_CallEvent_type_icon::before,
&.mx_CallEvent_voice.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/declined-voice.svg');
}
&.mx_CallEvent_video.mx_CallEvent_rejected .mx_CallEvent_type_icon::before,
&.mx_CallEvent_video.mx_CallEvent_noAnswer .mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/declined-video.svg');
&.mx_CallEvent_video {
.mx_CallEvent_type_icon::before {
mask-image: url('$(res)/img/voip/missed-video.svg');
}
}
}
.mx_CallEvent_info {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 12px;
min-width: 0;
width: fit-content;
max-width: 100%;
.mx_CallEvent_info_basic {
display: flex;
flex-direction: column;
gap: $spacing-4;
margin-left: 10px; // To match mx_CallEvent
margin-right: 10px;
min-width: 0;
.mx_CallEvent_sender {
font-weight: 600;
font-size: 1.5rem;
line-height: 1.8rem;
margin-bottom: 3px;
margin-bottom: $spacing-4;
overflow: hidden;
white-space: nowrap;
@ -115,12 +129,12 @@ limitations under the License.
}
.mx_CallEvent_type {
display: flex;
align-items: center;
font-weight: 400;
color: $secondary-content;
font-size: 1.2rem;
line-height: $font-13px;
display: flex;
align-items: center;
.mx_CallEvent_type_icon {
height: 13px;
@ -143,16 +157,17 @@ limitations under the License.
.mx_CallEvent_content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
color: $secondary-content;
margin-right: 16px;
gap: 12px; // See mx_IncomingCallToast_buttons
min-width: max-content;
gap: $spacing-12; // See mx_IncomingCallToast_buttons
margin-inline-start: 42px; // avatar (32px) + mx_CallEvent_info_basic margin (10px)
word-break: break-word;
max-width: fit-content;
.mx_CallEvent_content_button {
@mixin CallButton;
padding: 0 12px;
padding: 0 $spacing-12;
span::before {
mask-size: 16px;
@ -162,8 +177,10 @@ limitations under the License.
}
}
.mx_CallEvent_content_button_reject span::before {
mask-image: url('$(res)/img/element-icons/call/hangup.svg');
.mx_CallEvent_content_button_reject {
span::before {
mask-image: url('$(res)/img/element-icons/call/hangup.svg');
}
}
.mx_CallEvent_content_tooltip {
@ -171,16 +188,12 @@ limitations under the License.
}
}
.mx_MessageTimestamp {
margin-left: 16px;
}
&.mx_CallEvent_narrow {
height: unset;
width: 290px;
flex-direction: column;
align-items: unset;
gap: 16px;
gap: $spacing-4 $spacing-16;
height: unset;
min-width: 290px;
.mx_CallEvent_iconButton {
position: absolute;
@ -194,18 +207,36 @@ limitations under the License.
.mx_CallEvent_info {
align-items: unset;
margin-top: 12px;
margin-right: 12px;
.mx_CallEvent_sender {
margin-bottom: 8px;
}
}
.mx_CallEvent_content {
margin-left: 54px; // mx_CallEvent margin (12px) + avatar (32px) + mx_CallEvent_info_basic margin (10px)
margin-bottom: 16px;
}
}
}
}
.mx_EventTile[data-layout="bubble"] {
.mx_EventTile_e2eIcon + .mx_CallEvent_wrapper {
.mx_CallEvent {
position: relative;
// 5px (gap) + 14px (e2e icon size * mask-size) + 9px (margin-left of e2e icon)
right: calc(5px + 14px + 9px);
}
}
}
.mx_EventTile_leftAlignedBubble {
.mx_CallEvent_wrapper {
.mx_CallEvent {
&.mx_CallEvent_narrow {
gap: $spacing-8 $spacing-4;
}
}
}
}
.mx_IRCLayout {
.mx_CallEvent_wrapper {
.mx_CallEvent {
margin-inline-start: $spacing-4; // display green line
}
}
}

View file

@ -38,6 +38,12 @@ $timeline-image-border-radius: 8px;
// Necessary for the border radius to apply correctly to the placeholder
overflow: hidden;
contain: paint;
min-height: $font-44px;
min-width: $font-44px;
display: flex;
justify-content: center;
align-items: center;
}
.mx_MImageBody_thumbnail {

View file

@ -22,56 +22,6 @@ limitations under the License.
border-radius: $timeline-image-border-radius;
}
.mx_MLocationBody_markerBorder {
width: 31px;
height: 31px;
border-radius: 50%;
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
background-color: $accent;
// See _LocationPicker.scss
display: flex;
justify-content: center;
align-items: center;
.mx_BaseAvatar {
margin: 0;
line-height: 1;
}
}
.mx_MLocationBody_pointer {
position: absolute;
bottom: -3px;
left: 11px;
width: 9px;
height: 5px;
&::before {
mask-image: url('$(res)/img/location/pointer.svg');
mask-position: center;
mask-repeat: no-repeat;
mask-size: 9px;
content: '';
display: inline-block;
width: 9px;
height: 5px;
position: absolute;
background-color: $accent;
}
}
.mx_MLocationBody_markerContents {
background-color: $location-marker-color;
margin: 0;
width: 31px;
height: 31px;
mask-repeat: no-repeat;
mask-size: 16px;
mask-position: center;
mask-image: url('$(res)/img/element-icons/location.svg');
}
}
/* In the timeline, we fit the width of the container */

View file

@ -165,6 +165,10 @@ limitations under the License.
}
}
.mx_GenericEventListSummary > .mx_EventTile_line {
padding-left: 30px !important; // Override main timeline styling - align summary text with message text
}
.mx_EventTile:not([data-layout=bubble]) {
.mx_EventTile_e2eIcon {
left: 8px;
@ -212,19 +216,21 @@ limitations under the License.
}
// handling for hidden events (e.g reactions) in the thread view
&.mx_ThreadView .mx_GenericEventListSummary_unstyledList .mx_EventTile_info {
&.mx_ThreadView .mx_EventTile_info {
padding-top: 0 !important; // override main timeline padding
.mx_EventTile_line {
padding-left: 0 !important; // override main timeline padding
.mx_EventTile_content {
margin-left: 54px; // align with text
width: calc(100% - 54px - 8px); // match width of parent
margin-left: 48px; // align with text
width: calc(100% - 48px - 8px); // match width of parent
}
}
.mx_EventTile_avatar {
position: absolute;
left: 36px !important; // override main timeline positioning
left: 30px !important; // override main timeline positioning
z-index: 9; // position above the hover styling
}
@ -237,7 +243,7 @@ limitations under the License.
text-align: left;
font-size: $font-12px;
align-items: center;
justify-content: end;
justify-content: flex-end;
gap: 4px;
position: relative;
top: 2px;

View file

@ -39,6 +39,7 @@ limitations under the License.
overflow: hidden;
position: relative; // offset parent for jump to bottom button
flex: 1;
border-radius: 8px;
}
.mx_AutoHideScrollbar {
@ -54,29 +55,61 @@ limitations under the License.
margin-right: 0;
}
.mx_EventTile:not([data-layout="bubble"]) .mx_EventTile_line {
padding-left: 36px;
padding-right: 36px;
.mx_EventTile:not([data-layout="bubble"]) {
.mx_EventTile_line {
padding-left: 36px;
padding-right: 36px;
}
.mx_ReactionsRow {
padding: 0;
// See margin setting of ReactionsRow on _EventTile.scss
margin-left: 36px;
margin-right: 8px;
}
.mx_ThreadInfo {
margin-left: 36px;
margin-right: 0;
max-width: min(calc(100% - 36px), 600px);
}
.mx_EventTile_avatar {
top: 12px;
left: -3px;
}
.mx_MessageTimestamp {
right: -4px;
left: auto;
}
.mx_EventTile_msgOption {
margin-right: 2px;
}
&.mx_EventTile_info {
.mx_EventTile_line {
padding-left: 36px;
}
.mx_EventTile_avatar {
left: 18px;
}
}
}
.mx_EventTile:not([data-layout="bubble"]) .mx_ReactionsRow {
padding-left: 36px;
padding-right: 36px;
}
.mx_GroupLayout {
.mx_EventTile {
> .mx_DisambiguatedProfile {
margin-left: 36px;
}
.mx_EventTile:not([data-layout="bubble"]) .mx_ThreadInfo {
margin-left: 36px;
margin-right: 0;
max-width: min(calc(100% - 36px), 600px);
}
.mx_GroupLayout .mx_EventTile > .mx_DisambiguatedProfile {
margin-left: 36px;
}
.mx_EventTile:not([data-layout="bubble"]) .mx_EventTile_avatar {
top: 12px;
left: -3px;
.mx_EventTile_line {
padding-bottom: 8px;
}
}
}
.mx_CallEvent_wrapper {
@ -87,36 +120,15 @@ limitations under the License.
}
}
.mx_EventTile:not([data-layout="bubble"]) .mx_MessageTimestamp {
right: -4px;
left: auto;
}
.mx_EventTile:not([data-layout="bubble"]) .mx_EventTile_msgOption {
margin-right: 2px;
}
.mx_GenericEventListSummary:not([data-layout=bubble]) .mx_EventTile_line,
.mx_GenericEventListSummary:not([data-layout=bubble]) > .mx_GenericEventListSummary_unstyledList > .mx_EventTile_info .mx_EventTile_avatar ~ .mx_EventTile_line {
padding-left: 36px;
}
.mx_GroupLayout .mx_EventTile .mx_EventTile_line {
padding-bottom: 8px;
}
.mx_EventTile_readAvatars {
top: -10px;
}
.mx_EventTile:not([data-layout="bubble"]).mx_EventTile_info .mx_EventTile_line {
padding-left: 36px;
}
.mx_EventTile:not([data-layout="bubble"]).mx_EventTile_info .mx_EventTile_avatar {
left: 18px;
}
.mx_WhoIsTypingTile {
margin-left: -12px; // undo padding on the message list
}
@ -124,4 +136,12 @@ limitations under the License.
.mx_WhoIsTypingTile_avatars {
flex-basis: 48px; // 12 (padding on message list) + 36 (padding on event lines)
}
&.mx_BaseCard {
// For a chat timeline on the right panel when the widget is maximised
// TODO: rename ThreadPanel
&.mx_ThreadPanel {
padding-right: 8px; // .mx_RightPanel padding
}
}
}

View file

@ -114,6 +114,11 @@ limitations under the License.
padding-right: 48px;
}
.mx_MImageBody_thumbnail_container {
min-height: calc(1.8rem + var(--gutterSize) + var(--gutterSize));
min-width: calc(1.8rem + var(--gutterSize) + var(--gutterSize));
}
.mx_CallEvent {
background-color: unset;
@ -130,7 +135,8 @@ limitations under the License.
.mx_MImageBody::before,
.mx_MVideoBody .mx_MVideoBody_container,
.mx_MediaBody,
.mx_MLocationBody_map {
.mx_MLocationBody_map,
.mx_MBeaconBody {
border-bottom-right-radius: var(--cornerRadius) !important;
}
}
@ -155,7 +161,8 @@ limitations under the License.
.mx_MImageBody::before,
.mx_MVideoBody .mx_MVideoBody_container,
.mx_MediaBody,
.mx_MLocationBody_map {
.mx_MLocationBody_map,
.mx_MBeaconBody {
border-bottom-left-radius: var(--cornerRadius) !important;
}
}
@ -300,7 +307,8 @@ limitations under the License.
.mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
.mx_MLocationBody_map {
.mx_MLocationBody_map,
.mx_MBeaconBody {
border-top-left-radius: 0;
}
}
@ -311,7 +319,8 @@ limitations under the License.
.mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
.mx_MLocationBody_map {
.mx_MLocationBody_map,
.mx_MBeaconBody {
border-bottom-left-radius: var(--cornerRadius);
}
}
@ -323,7 +332,8 @@ limitations under the License.
.mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
.mx_MLocationBody_map {
.mx_MLocationBody_map,
.mx_MBeaconBody {
border-top-right-radius: 0;
}
}
@ -334,7 +344,8 @@ limitations under the License.
.mx_MVideoBody .mx_MVideoBody_container,
.mx_MImageBody::before,
.mx_MediaBody,
.mx_MLocationBody_map {
.mx_MLocationBody_map,
.mx_MBeaconBody {
border-bottom-right-radius: var(--cornerRadius);
}
}

View file

@ -317,6 +317,7 @@ $left-gutter: 64px;
.mx_EventTile_line {
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
margin-right: 110px;
min-height: $font-14px;
}
.mx_ThreadInfo {
@ -693,6 +694,16 @@ $left-gutter: 64px;
visibility: visible;
}
// Inverse of the above to *disable* the animation on any indicators. This approach
// is less pretty, but is easier to target because otherwise we need to define the
// animation for when it's shown which means duplicating the style definition in
// multiple places.
.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput='keyboard'] :focus-within):not(.focus-visible:focus-within) {
.mx_MessageActionBar .mx_Indicator {
animation: none;
}
}
@media only screen and (max-width: 480px) {
.mx_EventTile_line,
@ -762,7 +773,8 @@ $left-gutter: 64px;
bottom: 0;
width: 60px;
box-sizing: border-box;
background: linear-gradient(270deg, $system 52.6%, transparent 100%);
// XXX: We use `$system-transparent` instead of `transparent` to work around a Safari <15.4 bug
background: linear-gradient(270deg, $system 50%, $system-transparent 100%);
opacity: 0;
transform: translateX(60px);
@ -1004,6 +1016,7 @@ $threadInfoLineHeight: calc(2 * $font-12px);
}
.mx_EventTile[data-layout=group] {
$spacing-start: 48px;
width: 100%;
.mx_EventTile_content,
@ -1011,8 +1024,9 @@ $threadInfoLineHeight: calc(2 * $font-12px);
.mx_RedactedBody,
.mx_UnknownBody,
.mx_MPollBody,
.mx_ReplyChain_wrapper {
margin-left: 48px;
.mx_ReplyChain_wrapper,
.mx_ReactionsRow {
margin-left: $spacing-start;
margin-right: 8px;
.mx_EventTile_content,
@ -1023,11 +1037,6 @@ $threadInfoLineHeight: calc(2 * $font-12px);
}
}
.mx_ReactionsRow {
margin-left: 36px;
margin-right: 8px;
}
.mx_MessageTimestamp {
top: 2px !important;
width: auto;
@ -1051,10 +1060,14 @@ $threadInfoLineHeight: calc(2 * $font-12px);
}
}
}
.mx_EventTile_mediaLine {
padding-inline-start: $spacing-start;
}
}
.mx_EventTile_mediaLine {
padding-left: 36px !important;
padding-left: 36px;
padding-right: 50px;
.mx_MImageBody {

View file

@ -23,10 +23,6 @@ limitations under the License.
}
}
.mx_AccessibleButton_kind_link {
font-size: inherit;
}
.mx_NewRoomIntro_buttons {
margin-top: 28px;
display: flex;

View file

@ -92,8 +92,6 @@ limitations under the License.
.mx_AccessibleButton_kind_link {
margin-left: 12px;
font-size: inherit;
line-height: inherit;
}
}

View file

@ -61,6 +61,7 @@ limitations under the License.
margin-bottom: 28px;
> .mx_AccessibleButton_kind_link {
font-size: $font-14px;
margin-right: 10px;
}
}

View file

@ -50,10 +50,6 @@ limitations under the License.
font-size: $font-12px;
line-height: $font-15px;
margin-right: 32px;
.mx_AccessibleButton_kind_link {
font-size: inherit;
}
}
}

View file

@ -32,9 +32,5 @@ limitations under the License.
margin-top: 20px;
}
}
.mx_AccessibleButton_kind_link {
font-size: inherit;
}
}
}

View file

@ -65,6 +65,7 @@ limitations under the License.
display: inline-block;
margin: auto 18px;
color: #368bd6;
font-size: $font-14px; // See _SpaceSettingsDialog.scss
}
> .mx_SpaceBasicSettings_avatar_remove {

View file

@ -93,10 +93,6 @@ $spacePanelWidth: 68px;
width: min-content;
}
.mx_AccessibleButton_kind_link {
font-size: inherit;
}
.mx_AccessibleButton_disabled {
cursor: not-allowed;
}
@ -118,7 +114,5 @@ $spacePanelWidth: 68px;
.mx_AccessibleButton_kind_link {
color: $accent;
position: relative;
font-size: inherit;
line-height: inherit;
}
}

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 8.00001C2 7.56779 2.35038 7.21741 2.78261 7.21741L13.2173 7.21741C13.6496 7.21741 13.9999 7.56779 13.9999 8.00001C13.9999 8.43223 13.6496 8.78262 13.2173 8.78262L2.78261 8.78262C2.35038 8.78262 2 8.43223 2 8.00001Z" fill="#17191C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 8.00001C2 7.56779 2.35038 7.21741 2.78261 7.21741L13.2173 7.21741C13.6496 7.21741 13.9999 7.56779 13.9999 8.00001C13.9999 8.43223 13.6496 8.78262 13.2173 8.78262L2.78261 8.78262C2.35038 8.78262 2 8.43223 2 8.00001Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 393 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.78269 2.78285C8.78269 2.35063 8.43231 2.00024 8.00009 2.00024C7.56787 2.00024 7.21748 2.35063 7.21748 2.78285V7.21748L2.78285 7.21748C2.35063 7.21748 2.00024 7.56787 2.00024 8.00009C2.00024 8.43231 2.35063 8.78269 2.78285 8.78269L7.21748 8.7827V13.2176C7.21748 13.6498 7.56787 14.0002 8.00009 14.0002C8.43231 14.0002 8.7827 13.6498 8.7827 13.2176V8.7827L13.2176 8.7827C13.6498 8.7827 14.0002 8.43231 14.0002 8.00009C14.0002 7.56787 13.6498 7.21749 13.2176 7.21749L8.78269 7.21748V2.78285Z" fill="#17191C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.78269 2.78285C8.78269 2.35063 8.43231 2.00024 8.00009 2.00024C7.56787 2.00024 7.21748 2.35063 7.21748 2.78285V7.21748L2.78285 7.21748C2.35063 7.21748 2.00024 7.56787 2.00024 8.00009C2.00024 8.43231 2.35063 8.78269 2.78285 8.78269L7.21748 8.7827V13.2176C7.21748 13.6498 7.56787 14.0002 8.00009 14.0002C8.43231 14.0002 8.7827 13.6498 8.7827 13.2176V8.7827L13.2176 8.7827C13.6498 8.7827 14.0002 8.43231 14.0002 8.00009C14.0002 7.56787 13.6498 7.21749 13.2176 7.21749L8.78269 7.21748V2.78285Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 662 B

After

Width:  |  Height:  |  Size: 667 B

View file

@ -1,5 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.393 1.00573C11.9421 1.06434 12.3398 1.55703 12.2812 2.1062L12.0245 4.51168H13.8482C14.4005 4.51168 14.8482 4.95939 14.8482 5.51168C14.8482 6.06396 14.4005 6.51168 13.8482 6.51168H13.0089C12.5534 5.89782 11.8231 5.5 11 5.5C10.1769 5.5 9.44663 5.89782 8.9911 6.51168H6.36673L6.05542 9.42868C5.70806 9.85802 5.5 10.4047 5.5 11C5.5 11.4052 5.5964 11.7879 5.76753 12.1264L5.55616 14.107C5.49755 14.6562 5.00485 15.0538 4.45569 14.9952C3.90652 14.9366 3.50884 14.4439 3.56745 13.8948L3.79318 11.7796H2.0005C1.44822 11.7796 1.0005 11.3319 1.0005 10.7796C1.0005 10.2273 1.44822 9.77962 2.0005 9.77962H4.00661L4.35537 6.51168H2.64188C2.0896 6.51168 1.64188 6.06396 1.64188 5.51168C1.64188 4.95939 2.0896 4.51168 2.64188 4.51168H4.56881L4.84817 1.89397C4.90677 1.3448 5.39947 0.947122 5.94864 1.00573C6.49781 1.06434 6.89548 1.55703 6.83688 2.1062L6.58016 4.51168H10.0131L10.2925 1.89397C10.3511 1.3448 10.8438 0.947122 11.393 1.00573Z" fill="#737D8C"/>
<path d="M8 11H14" stroke="#737D8C" stroke-width="2" stroke-linecap="round"/>
<path d="M11 8L11 14" stroke="#737D8C" stroke-width="2" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.393 1.00262C11.9421 1.06122 12.3398 1.55392 12.2812 2.10309L12.0245 4.50857H13.8482C14.4004 4.50857 14.8482 4.95628 14.8482 5.50857C14.8482 6.06085 14.4004 6.50857 13.8482 6.50857H13.0598C12.6805 5.45391 11.6713 4.69975 10.486 4.69975C9.30072 4.69975 8.29156 5.45391 7.91221 6.50857H6.36671L6.19745 8.09457C5.28778 8.53869 4.66113 9.47279 4.66113 10.5533C4.66113 11.4255 5.06957 12.2024 5.70563 12.7031L5.55614 14.1039C5.49753 14.6531 5.00484 15.0507 4.45567 14.9921C3.9065 14.9335 3.50883 14.4408 3.56743 13.8917L3.79316 11.7765H2.00049C1.4482 11.7765 1.00049 11.3288 1.00049 10.7765C1.00049 10.2242 1.4482 9.77651 2.00049 9.77651H4.0066L4.35535 6.50857H2.64186C2.08958 6.50857 1.64186 6.06085 1.64186 5.50857C1.64186 4.95628 2.08958 4.50857 2.64186 4.50857H4.56879L4.84815 1.89085C4.90676 1.34169 5.39946 0.94401 5.94862 1.00262C6.49779 1.06122 6.89547 1.55392 6.83686 2.10309L6.58015 4.50857H10.0131L10.2925 1.89085C10.3511 1.34169 10.8438 0.94401 11.393 1.00262Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4108 7.28091C11.4108 6.72863 10.963 6.28091 10.4108 6.28091C9.85847 6.28091 9.41076 6.72863 9.41076 7.28091L9.41076 9.44016H7.28076C6.72848 9.44016 6.28076 9.88787 6.28076 10.4402C6.28076 10.9924 6.72848 11.4402 7.28076 11.4402H9.41076L9.41076 13.5994C9.41076 14.1517 9.85847 14.5994 10.4108 14.5994C10.963 14.5994 11.4108 14.1517 11.4108 13.5994V11.4402H13.539C14.0913 11.4402 14.539 10.9924 14.539 10.4402C14.539 9.88787 14.0913 9.44016 13.539 9.44016H11.4108V7.28091Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

9
res/img/location/map.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

153
res/img/matrix.svg Normal file
View file

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="793.322px" height="340.809px" viewBox="0 0 793.322 340.809" enable-background="new 0 0 793.322 340.809"
xml:space="preserve">
<path opacity="0.5" fill="#FFFFFF" d="M34.004,340.809H2c-1.104,0-2-0.896-2-2V2c0-1.104,0.896-2,2-2h32.004c1.104,0,2,0.896,2,2
v7.71c0,1.104-0.896,2-2,2h-21.13v317.386h21.13c1.104,0,2,0.896,2,2.001v7.712C36.004,339.913,35.108,340.809,34.004,340.809
L34.004,340.809z"/>
<path opacity="0.5" fill="#FFFFFF" d="M10.875,9.711v321.386h23.13v7.711H1.999V2.001h32.006v7.71H10.875z"/>
<path opacity="0.5" fill="#FFFFFF" d="M252.402,233.711h-32.993c-1.104,0-2-0.896-2-2v-68.073c0-3.949-0.154-7.722-0.457-11.213
c-0.289-3.282-1.074-6.153-2.332-8.53c-1.204-2.276-3.017-4.119-5.384-5.476c-2.393-1.362-5.775-2.056-10.042-2.056
c-4.238,0-7.674,0.798-10.213,2.371c-2.565,1.596-4.604,3.701-6.053,6.258c-1.498,2.643-2.51,5.694-3.013,9.067
c-0.526,3.513-0.793,7.125-0.793,10.741v66.91c0,1.104-0.896,2-2,2h-32.991c-1.104,0-2-0.896-2-2v-67.373
c0-3.435-0.078-6.964-0.228-10.485c-0.148-3.251-0.767-6.278-1.841-8.995c-1.018-2.571-2.667-4.584-5.047-6.153
c-2.372-1.552-6.029-2.341-10.865-2.341c-1.372,0-3.265,0.328-5.629,0.976c-2.28,0.624-4.536,1.826-6.705,3.577
c-2.152,1.732-4.036,4.306-5.605,7.655c-1.569,3.356-2.367,7.877-2.367,13.438v69.701c0,1.104-0.895,2-2,2H68.857
c-1.104,0-2-0.896-2-2V111.594c0-1.104,0.896-1.999,2-1.999h31.13c1.104,0,2,0.896,2,1.999v11.007
c3.834-4.499,8.248-8.152,13.173-10.896c6.396-3.559,13.799-5.362,22.002-5.362c7.846,0,15.127,1.548,21.642,4.604
c5.794,2.722,10.424,7.26,13.791,13.52c3.449-4.362,7.833-8.306,13.071-11.752c6.422-4.228,14.102-6.371,22.824-6.371
c6.499,0,12.625,0.807,18.209,2.399c5.686,1.628,10.635,4.271,14.712,7.857c4.088,3.605,7.318,8.357,9.601,14.123
c2.25,5.719,3.391,12.649,3.391,20.604v80.384C254.402,232.815,253.507,233.711,252.402,233.711L252.402,233.711z"/>
<path opacity="0.5" fill="#FFFFFF" d="M99.988,111.595v16.264h0.463c4.338-6.191,9.563-10.998,15.684-14.406
c6.117-3.402,13.129-5.11,21.027-5.11c7.588,0,14.521,1.475,20.793,4.415c6.274,2.945,11.038,8.131,14.291,15.567
c3.56-5.265,8.4-9.913,14.521-13.94c6.117-4.025,13.358-6.042,21.724-6.042c6.351,0,12.234,0.776,17.66,2.325
c5.418,1.549,10.065,4.027,13.938,7.434c3.869,3.41,6.889,7.863,9.062,13.357c2.167,5.504,3.253,12.122,3.253,19.869v80.385H219.41
v-68.074c0-4.025-0.154-7.82-0.465-11.385c-0.313-3.56-1.161-6.656-2.555-9.293c-1.395-2.631-3.45-4.724-6.157-6.274
c-2.711-1.543-6.391-2.322-11.037-2.322s-8.403,0.896-11.269,2.671c-2.868,1.784-5.112,4.109-6.737,6.971
c-1.626,2.869-2.711,6.12-3.252,9.762c-0.545,3.638-0.814,7.318-0.814,11.035v66.91h-32.991v-67.375c0-3.562-0.081-7.087-0.23-10.57
c-0.158-3.487-0.814-6.7-1.978-9.645c-1.162-2.94-3.099-5.304-5.809-7.088c-2.711-1.775-6.699-2.671-11.965-2.671
c-1.551,0-3.603,0.349-6.156,1.048c-2.556,0.697-5.036,2.016-7.435,3.949c-2.404,1.938-4.454,4.726-6.158,8.363
c-1.705,3.642-2.556,8.402-2.556,14.287v69.701h-32.99V111.595H99.988z"/>
<path opacity="0.5" fill="#FFFFFF" d="M304.909,236.733c-5.883,0-11.46-0.729-16.574-2.163c-5.192-1.464-9.806-3.774-13.713-6.871
c-3.944-3.117-7.068-7.111-9.282-11.871c-2.205-4.733-3.324-10.412-3.324-16.876c0-7.13,1.293-13.117,3.846-17.797
c2.542-4.674,5.877-8.464,9.912-11.263c3.97-2.752,8.556-4.842,13.63-6.209c4.901-1.322,9.937-2.394,14.961-3.184
c4.986-0.775,9.949-1.404,14.754-1.872c4.679-0.452,8.88-1.139,12.489-2.039c3.412-0.854,6.118-2.09,8.042-3.672
c1.666-1.37,2.416-3.384,2.292-6.151c-0.002-3.289-0.502-5.816-1.492-7.595c-0.998-1.798-2.283-3.15-3.927-4.138
c-1.703-1.02-3.725-1.713-6.012-2.062c-2.47-0.37-5.146-0.557-7.947-0.557c-6.034,0-10.789,1.271-14.135,3.783
c-3.233,2.424-5.155,6.64-5.714,12.527c-0.098,1.026-0.961,1.812-1.992,1.812h-32.992c-0.552,0-1.079-0.229-1.457-0.629
c-0.376-0.402-0.572-0.941-0.54-1.491c0.485-8.073,2.55-14.894,6.142-20.272c3.548-5.331,8.147-9.682,13.661-12.931
c5.424-3.191,11.612-5.498,18.392-6.857c6.684-1.335,13.5-2.013,20.26-2.013c6.096,0,12.365,0.437,18.626,1.296
c6.377,0.88,12.285,2.622,17.562,5.177c5.376,2.604,9.845,6.29,13.282,10.951c3.498,4.744,5.271,11.048,5.271,18.731v62.494
c0,5.307,0.306,10.462,0.915,15.319c0.576,4.64,1.572,8.116,2.963,10.338c0.385,0.616,0.407,1.395,0.055,2.031
c-0.353,0.635-1.022,1.03-1.75,1.03h-33.457c-0.861,0-1.624-0.55-1.898-1.367c-0.646-1.941-1.176-3.939-1.572-5.936
c-0.141-0.696-0.267-1.402-0.38-2.12c-4.825,4.184-10.349,7.24-16.474,9.105C320.033,235.609,312.489,236.733,304.909,236.733
L304.909,236.733z M341.941,176.661c-0.809,0.409-1.676,0.768-2.596,1.074c-2.161,0.72-4.511,1.326-6.988,1.807
c-2.442,0.475-5.033,0.872-7.699,1.186c-2.631,0.311-5.251,0.697-7.784,1.146c-2.329,0.433-4.705,1.035-7.051,1.792
c-2.194,0.711-4.114,1.667-5.699,2.842c-1.531,1.128-2.785,2.587-3.731,4.335c-0.917,1.709-1.385,3.97-1.385,6.719
c0,2.598,0.465,4.778,1.385,6.481c0.928,1.722,2.142,3.035,3.716,4.018c1.644,1.026,3.601,1.757,5.816,2.17
c2.344,0.439,4.799,0.663,7.297,0.663c6.105,0,10.836-0.996,14.063-2.961c3.244-1.973,5.666-4.349,7.199-7.062
c1.568-2.78,2.542-5.62,2.892-8.436c0.376-3.019,0.565-5.436,0.565-7.187V176.661L341.941,176.661z"/>
<path opacity="0.5" fill="#FFFFFF" d="M273.544,129.255c3.405-5.113,7.744-9.215,13.012-12.316
c5.264-3.097,11.186-5.303,17.771-6.621c6.582-1.315,13.205-1.976,19.865-1.976c6.042,0,12.158,0.428,18.354,1.277
c6.195,0.855,11.85,2.522,16.962,4.997c5.111,2.477,9.292,5.926,12.546,10.338c3.253,4.414,4.879,10.262,4.879,17.543v62.494
c0,5.428,0.31,10.611,0.931,15.567c0.615,4.959,1.701,8.676,3.251,11.153H347.66c-0.621-1.86-1.126-3.755-1.511-5.693
c-0.39-1.933-0.661-3.908-0.813-5.923c-5.267,5.422-11.465,9.217-18.585,11.386c-7.127,2.163-14.407,3.251-21.842,3.251
c-5.733,0-11.077-0.698-16.033-2.09c-4.958-1.395-9.293-3.562-13.01-6.51c-3.718-2.938-6.622-6.656-8.713-11.147
s-3.138-9.84-3.138-16.033c0-6.813,1.199-12.43,3.604-16.84c2.399-4.417,5.495-7.939,9.295-10.575
c3.793-2.632,8.129-4.607,13.01-5.923c4.878-1.315,9.795-2.358,14.752-3.137c4.957-0.772,9.835-1.393,14.638-1.857
c4.801-0.466,9.062-1.164,12.779-2.093c3.718-0.929,6.658-2.282,8.829-4.065c2.165-1.781,3.172-4.375,3.02-7.785
c0-3.56-0.58-6.389-1.742-8.479c-1.161-2.09-2.711-3.719-4.646-4.88c-1.937-1.161-4.183-1.936-6.737-2.325
c-2.557-0.382-5.309-0.58-8.248-0.58c-6.506,0-11.617,1.395-15.335,4.183c-3.716,2.788-5.889,7.437-6.506,13.94h-32.991
C268.199,140.794,270.132,134.363,273.544,129.255z M338.713,175.838c-2.09,0.696-4.337,1.275-6.736,1.741
c-2.402,0.465-4.918,0.853-7.551,1.161c-2.635,0.313-5.268,0.698-7.899,1.163c-2.48,0.461-4.919,1.086-7.317,1.857
c-2.404,0.779-4.495,1.822-6.274,3.138c-1.784,1.317-3.216,2.985-4.3,4.994c-1.085,2.014-1.626,4.571-1.626,7.668
c0,2.94,0.541,5.422,1.626,7.431c1.084,2.017,2.558,3.604,4.416,4.765s4.025,1.976,6.507,2.438c2.475,0.466,5.031,0.698,7.665,0.698
c6.505,0,11.537-1.082,15.103-3.253c3.561-2.166,6.192-4.762,7.899-7.785c1.702-3.019,2.749-6.072,3.137-9.174
c0.384-3.097,0.58-5.576,0.58-7.434v-12.316C342.547,174.173,340.805,175.14,338.713,175.838z"/>
<path opacity="0.5" fill="#FFFFFF" d="M444.542,234.874c-5.187,0-10.173-0.361-14.823-1.069c-4.802-0.732-9.104-2.183-12.779-4.313
c-3.789-2.185-6.821-5.341-9.006-9.375c-2.163-3.986-3.26-9.232-3.26-15.59v-68.859h-17.981c-1.104,0-2-0.896-2-1.999v-22.073
c0-1.104,0.896-1.999,2-1.999h17.981V75.582c0-1.104,0.896-2,2-2h32.992c1.104,0,2,0.896,2,2v34.014h22.162c1.104,0,2,0.896,2,1.999
v22.073c0,1.104-0.896,1.999-2,1.999h-22.162v57.479c0,6.229,1.198,8.731,2.202,9.733c1.004,1.007,3.506,2.205,9.738,2.205
c1.804,0,3.542-0.076,5.161-0.225c1.604-0.144,3.174-0.367,4.669-0.665c0.13-0.026,0.261-0.039,0.391-0.039
c0.458,0,0.907,0.159,1.27,0.454c0.463,0.379,0.73,0.946,0.73,1.546v25.555c0,0.979-0.707,1.813-1.672,1.974
c-2.834,0.472-6.041,0.794-9.527,0.957C451.015,234.798,447.718,234.874,444.542,234.874L444.542,234.874z"/>
<path opacity="0.5" fill="#FFFFFF" d="M463.825,111.595v22.072h-24.161v59.479c0,5.573,0.928,9.292,2.788,11.149
c1.856,1.859,5.576,2.788,11.152,2.788c1.859,0,3.638-0.076,5.343-0.232c1.703-0.152,3.33-0.388,4.878-0.696v25.557
c-2.788,0.465-5.887,0.773-9.293,0.931c-3.407,0.149-6.737,0.23-9.99,0.23c-5.111,0-9.953-0.35-14.521-1.048
c-4.571-0.695-8.597-2.047-12.081-4.063c-3.486-2.011-6.236-4.88-8.248-8.597c-2.016-3.714-3.021-8.595-3.021-14.639v-70.859h-19.98
v-22.072h19.98V75.583h32.992v36.012H463.825z"/>
<path opacity="0.5" fill="#FFFFFF" d="M512.613,233.711h-32.991c-1.104,0-2-0.896-2-2V111.594c0-1.104,0.896-1.999,2-1.999h31.366
c1.104,0,2,0.896,2,1.999v15.069c0.967-1.516,2.034-2.978,3.199-4.382c2.754-3.312,5.949-6.182,9.496-8.522
c3.545-2.332,7.385-4.169,11.415-5.462c4.056-1.298,8.327-1.954,12.691-1.954c2.341,0,4.953,0.418,7.766,1.243
c0.852,0.25,1.437,1.032,1.437,1.92v30.67c0,0.6-0.269,1.167-0.732,1.547c-0.361,0.296-0.808,0.452-1.265,0.452
c-0.133,0-0.265-0.013-0.398-0.039c-1.484-0.3-3.299-0.565-5.392-0.787c-2.098-0.224-4.136-0.339-6.062-0.339
c-5.706,0-10.572,0.95-14.467,2.823c-3.862,1.86-7.012,4.428-9.361,7.629c-2.389,3.263-4.115,7.12-5.127,11.47
c-1.043,4.479-1.574,9.409-1.574,14.647v54.132C514.613,232.815,513.717,233.711,512.613,233.711L512.613,233.711z"/>
<path opacity="0.5" fill="#FFFFFF" d="M510.988,111.595V133.9h0.465c1.546-3.72,3.636-7.163,6.272-10.341
c2.634-3.172,5.652-5.885,9.06-8.131c3.405-2.242,7.047-3.985,10.923-5.228c3.868-1.237,7.898-1.859,12.081-1.859
c2.168,0,4.566,0.39,7.202,1.163v30.67c-1.551-0.312-3.41-0.584-5.576-0.814c-2.17-0.233-4.26-0.35-6.274-0.35
c-6.041,0-11.152,1.01-15.332,3.021c-4.182,2.014-7.55,4.761-10.107,8.247c-2.555,3.487-4.379,7.55-5.462,12.198
c-1.083,4.645-1.625,9.682-1.625,15.102v54.133h-32.991V111.595H510.988z"/>
<path opacity="0.5" fill="#FFFFFF" d="M603.923,233.711H570.93c-1.104,0-2-0.896-2-2V111.594c0-1.104,0.896-1.999,2-1.999h32.994
c1.104,0,2,0.896,2,1.999v120.117C605.923,232.815,605.027,233.711,603.923,233.711L603.923,233.711z M603.923,95.006H570.93
c-1.104,0-2-0.896-2-1.999V65.825c0-1.104,0.896-2,2-2h32.994c1.104,0,2,0.896,2,2v27.182
C605.923,94.11,605.027,95.006,603.923,95.006L603.923,95.006z"/>
<path opacity="0.5" fill="#FFFFFF" d="M570.93,93.007V65.824h32.994v27.183H570.93z M603.924,111.595v120.117H570.93V111.595
H603.924z"/>
<path opacity="0.5" fill="#FFFFFF" d="M742.163,233.711h-37.64c-0.671,0-1.297-0.335-1.667-0.896l-23.426-35.352l-23.426,35.352
c-0.369,0.561-0.995,0.896-1.667,0.896h-36.938c-0.741,0-1.424-0.411-1.77-1.067c-0.345-0.654-0.3-1.449,0.118-2.061l42.435-62.055
l-38.71-55.793c-0.424-0.613-0.474-1.408-0.128-2.069c0.343-0.658,1.028-1.071,1.771-1.071h37.636c0.665,0,1.287,0.33,1.658,0.882
l19.477,28.893l19.255-28.884c0.372-0.556,0.996-0.891,1.665-0.891h36.475c0.746,0,1.43,0.415,1.776,1.078
c0.343,0.66,0.289,1.46-0.139,2.071l-38.69,55.082l43.578,62.744c0.424,0.61,0.474,1.408,0.128,2.066
C743.591,233.298,742.908,233.711,742.163,233.711L742.163,233.711z"/>
<path opacity="0.5" fill="#FFFFFF" d="M621.115,111.595h37.637l21.144,31.365l20.911-31.365h36.476l-39.496,56.226l44.377,63.892
h-37.64l-25.093-37.87l-25.094,37.87h-36.938l43.213-63.193L621.115,111.595z"/>
<path opacity="0.5" fill="#FFFFFF" d="M791.322,340.809h-32.008c-1.105,0-2-0.896-2-2v-7.712c0-1.105,0.896-2.001,2-2.001h21.13
V11.71h-21.13c-1.105,0-2-0.896-2-2V2c0-1.104,0.896-2,2-2h32.008c1.104,0,2,0.896,2,2v336.809
C793.322,339.913,792.426,340.809,791.322,340.809L791.322,340.809z"/>
<path opacity="0.5" fill="#FFFFFF" d="M782.443,331.097V9.711h-23.13v-7.71h32.008v336.807h-32.008v-7.711H782.443z"/>
<path d="M10.875,9.711v321.386h23.13v7.711H1.999V2.001h32.006v7.71H10.875z"/>
<path d="M99.988,111.595v16.264h0.463c4.338-6.191,9.563-10.998,15.684-14.406c6.117-3.402,13.129-5.11,21.027-5.11
c7.588,0,14.521,1.475,20.793,4.415c6.274,2.945,11.038,8.131,14.291,15.567c3.56-5.265,8.4-9.913,14.521-13.94
c6.117-4.025,13.358-6.042,21.724-6.042c6.351,0,12.234,0.776,17.66,2.325c5.418,1.549,10.065,4.027,13.938,7.434
c3.869,3.41,6.889,7.863,9.062,13.357c2.167,5.504,3.253,12.122,3.253,19.869v80.385H219.41v-68.074
c0-4.025-0.154-7.82-0.465-11.385c-0.313-3.56-1.161-6.656-2.555-9.293c-1.395-2.631-3.45-4.724-6.157-6.274
c-2.711-1.543-6.391-2.322-11.037-2.322s-8.403,0.896-11.269,2.671c-2.868,1.784-5.112,4.109-6.737,6.971
c-1.626,2.869-2.711,6.12-3.252,9.762c-0.545,3.638-0.814,7.318-0.814,11.035v66.91h-32.991v-67.375c0-3.562-0.081-7.087-0.23-10.57
c-0.158-3.487-0.814-6.7-1.978-9.645c-1.162-2.94-3.099-5.304-5.809-7.088c-2.711-1.775-6.699-2.671-11.965-2.671
c-1.551,0-3.603,0.349-6.156,1.048c-2.556,0.697-5.036,2.016-7.435,3.949c-2.404,1.938-4.454,4.726-6.158,8.363
c-1.705,3.642-2.556,8.402-2.556,14.287v69.701h-32.99V111.595H99.988z"/>
<path d="M273.544,129.255c3.405-5.113,7.744-9.215,13.012-12.316c5.264-3.097,11.186-5.303,17.771-6.621
c6.582-1.315,13.205-1.976,19.865-1.976c6.042,0,12.158,0.428,18.354,1.277c6.195,0.855,11.85,2.522,16.962,4.997
c5.111,2.477,9.292,5.926,12.546,10.338c3.253,4.414,4.879,10.262,4.879,17.543v62.494c0,5.428,0.31,10.611,0.931,15.567
c0.615,4.959,1.701,8.676,3.251,11.153H347.66c-0.621-1.86-1.126-3.755-1.511-5.693c-0.39-1.933-0.661-3.908-0.813-5.923
c-5.267,5.422-11.465,9.217-18.585,11.386c-7.127,2.163-14.407,3.251-21.842,3.251c-5.733,0-11.077-0.698-16.033-2.09
c-4.958-1.395-9.293-3.562-13.01-6.51c-3.718-2.938-6.622-6.656-8.713-11.147s-3.138-9.84-3.138-16.033
c0-6.813,1.199-12.43,3.604-16.84c2.399-4.417,5.495-7.939,9.295-10.575c3.793-2.632,8.129-4.607,13.01-5.923
c4.878-1.315,9.795-2.358,14.752-3.137c4.957-0.772,9.835-1.393,14.638-1.857c4.801-0.466,9.062-1.164,12.779-2.093
c3.718-0.929,6.658-2.282,8.829-4.065c2.165-1.781,3.172-4.375,3.02-7.785c0-3.56-0.58-6.389-1.742-8.479
c-1.161-2.09-2.711-3.719-4.646-4.88c-1.937-1.161-4.183-1.936-6.737-2.325c-2.557-0.382-5.309-0.58-8.248-0.58
c-6.506,0-11.617,1.395-15.335,4.183c-3.716,2.788-5.889,7.437-6.506,13.94h-32.991
C268.199,140.794,270.132,134.363,273.544,129.255z M338.713,175.838c-2.09,0.696-4.337,1.275-6.736,1.741
c-2.402,0.465-4.918,0.853-7.551,1.161c-2.635,0.313-5.268,0.698-7.899,1.163c-2.48,0.461-4.919,1.086-7.317,1.857
c-2.404,0.779-4.495,1.822-6.274,3.138c-1.784,1.317-3.216,2.985-4.3,4.994c-1.085,2.014-1.626,4.571-1.626,7.668
c0,2.94,0.541,5.422,1.626,7.431c1.084,2.017,2.558,3.604,4.416,4.765s4.025,1.976,6.507,2.438c2.475,0.466,5.031,0.698,7.665,0.698
c6.505,0,11.537-1.082,15.103-3.253c3.561-2.166,6.192-4.762,7.899-7.785c1.702-3.019,2.749-6.072,3.137-9.174
c0.384-3.097,0.58-5.576,0.58-7.434v-12.316C342.547,174.173,340.805,175.14,338.713,175.838z"/>
<path d="M463.825,111.595v22.072h-24.161v59.479c0,5.573,0.928,9.292,2.788,11.149c1.856,1.859,5.576,2.788,11.152,2.788
c1.859,0,3.638-0.076,5.343-0.232c1.703-0.152,3.33-0.388,4.878-0.696v25.557c-2.788,0.465-5.887,0.773-9.293,0.931
c-3.407,0.149-6.737,0.23-9.99,0.23c-5.111,0-9.953-0.35-14.521-1.048c-4.571-0.695-8.597-2.047-12.081-4.063
c-3.486-2.011-6.236-4.88-8.248-8.597c-2.016-3.714-3.021-8.595-3.021-14.639v-70.859h-19.98v-22.072h19.98V75.583h32.992v36.012
H463.825z"/>
<path d="M510.988,111.595V133.9h0.465c1.546-3.72,3.636-7.163,6.272-10.341c2.634-3.172,5.652-5.885,9.06-8.131
c3.405-2.242,7.047-3.985,10.923-5.228c3.868-1.237,7.898-1.859,12.081-1.859c2.168,0,4.566,0.39,7.202,1.163v30.67
c-1.551-0.312-3.41-0.584-5.576-0.814c-2.17-0.233-4.26-0.35-6.274-0.35c-6.041,0-11.152,1.01-15.332,3.021
c-4.182,2.014-7.55,4.761-10.107,8.247c-2.555,3.487-4.379,7.55-5.462,12.198c-1.083,4.645-1.625,9.682-1.625,15.102v54.133h-32.991
V111.595H510.988z"/>
<path d="M570.93,93.007V65.824h32.994v27.183H570.93z M603.924,111.595v120.117H570.93V111.595H603.924z"/>
<path d="M621.115,111.595h37.637l21.144,31.365l20.911-31.365h36.476l-39.496,56.226l44.377,63.892h-37.64l-25.093-37.87
l-25.094,37.87h-36.938l43.213-63.193L621.115,111.595z"/>
<path d="M782.443,331.097V9.711h-23.13v-7.71h32.008v336.807h-32.008v-7.711H782.443z"/>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -7,7 +7,9 @@ $quaternary-content: #6F7882;
$quinary-content: #394049;
$system: #21262C;
$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: #15191E;
$overlay-background: rgba($background, 0.85);
$panel-base: #8D97A5; // This color is not intended for use in the app
$panels: rgba($system, 0.9);

View file

@ -1,5 +1,6 @@
// Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741
$system: #21262C;
$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
// unified palette
// try to use these colors when possible
@ -97,7 +98,10 @@ $tertiary-content: $tertiary-fg-color;
$quaternary-content: #6F7882;
$quinary-content: $quaternary-content;
$system: #21262C;
$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: $primary-bg-color;
$overlay-background: rgba($background, 0.85);
$panels: rgba($system, 0.9);
$panel-base: #8D97A5; // This color is not intended for use in the app
$panel-selected: rgba($panel-base, 0.3);

View file

@ -150,7 +150,10 @@ $tertiary-content: $tertiary-fg-color;
$quaternary-content: #6F7882;
$quinary-content: $quaternary-content;
$system: #F4F6FA;
$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: $primary-bg-color;
$overlay-background: rgba($background, 0.85);
$panels: rgba($system, 0.9);
$panel-base: #8D97A5; // This color is not intended for use in the app
$panel-selected: rgba($tertiary-content, 0.3);

View file

@ -26,6 +26,8 @@ $secondary-content: var(--secondary-content, $secondary-content);
$tertiary-content: var(--tertiary-content, $tertiary-content);
$quaternary-content: var(--quaternary-content, $quaternary-content);
$quinary-content: var(--quinary-content, $quinary-content);
// XXX: workaround for Safari 15.3 linear-gradient bug https://github.com/vector-im/element-web/issues/21670
$system-transparent: var(--system-transparent, rgba($system, 0.0));
$system: var(--system, $system);
$background: var(--background, $background);
$panels: rgba($system, 0.9);

View file

@ -34,7 +34,9 @@ $quaternary-content: #c1c6cd;
$quinary-content: #E3E8F0;
$system: #F4F6FA;
$system-transparent: rgba($system, 0.0); // XXX: workaround for Safari 15.3 linear-gradient bug
$background: #ffffff;
$overlay-background: rgba($background, 0.85);
$panels: rgba($system, 0.9);
$panel-selected: rgba($tertiary-content, 0.3);

View file

@ -31,7 +31,6 @@ yarn link matrix-js-sdk
yarn link matrix-analytics-events
yarn link
yarn install --pure-lockfile
yarn reskindex
# Finally, set up element-web
scripts/fetchdep.sh vector-im element-web

View file

@ -57,7 +57,10 @@ BRANCH_ARRAY=(${head//:/ })
TRY_ORG=$deforg
TRY_BRANCH=${BRANCH_ARRAY[0]}
if [[ "$head" == *":"* ]]; then
TRY_ORG=${BRANCH_ARRAY[0]}
# ... but only match that fork if it's a real fork
if [ "${BRANCH_ARRAY[0]}" != "matrix-org" ]; then
TRY_ORG=${BRANCH_ARRAY[0]}
fi
TRY_BRANCH=${BRANCH_ARRAY[1]}
fi
clone ${TRY_ORG} $defrepo ${TRY_BRANCH}

View file

@ -6,7 +6,7 @@ const path = require('path');
* Unsophisticated script to create a styled, unit-tested react component.
* -filePath / -f : path to the component to be created, including new component name, excluding extension, relative to src
* -withStyle / -s : optional, flag to create a style file for the component. Defaults to false.
*
*
* eg:
* ```
* node srcipts/make-react-component.js -f components/toasts/NewToast -s
@ -15,7 +15,7 @@ const path = require('path');
* - src/components/toasts/NewToast.tsx
* - test/components/toasts/NewToast-test.tsx
* - res/css/components/toasts/_NewToast.scss
*
*
*/
const TEMPLATES = {
@ -34,7 +34,6 @@ export default %%ComponentName%%;
import React from 'react';
import { mount } from 'enzyme';
import '%%SkinnedSdkPath%%';
import %%ComponentName%% from '%%RelativeComponentPath%%';
describe('<%%ComponentName%% />', () => {
@ -85,10 +84,8 @@ const makeFile = async ({
const relativePathToComponent = path.parse(path.relative(path.dirname(newFilePath), componentFilePath || ''));
const importComponentPath = path.join(relativePathToComponent.dir, relativePathToComponent.name);
const skinnedSdkPath = path.relative(path.dirname(newFilePath), 'test/skinned-sdk')
try {
await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath, skinnedSdkPath), { flag: 'wx' });
await fs.writeFile(newFilePath, fillTemplate(template, componentName, importComponentPath), { flag: 'wx' });
console.log(`Created ${path.relative(process.cwd(), newFilePath)}`);
return newFilePath;
} catch (error) {
@ -104,7 +101,6 @@ const makeFile = async ({
const fillTemplate = (template, componentName, relativeComponentFilePath, skinnedSdkPath) =>
template.replace(/%%ComponentName%%/g, componentName)
.replace(/%%RelativeComponentPath%%/g, relativeComponentFilePath)
.replace(/%%SkinnedSdkPath%%/g, skinnedSdkPath)
const makeReactComponent = async () => {

View file

@ -1,97 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const { promises: fsp } = fs;
const path = require('path');
const glob = require('glob');
const util = require('util');
const args = require('minimist')(process.argv);
const chokidar = require('chokidar');
const componentIndex = path.join('src', 'component-index.js');
const componentIndexTmp = componentIndex+".tmp";
const componentsDir = path.join('src', 'components');
const componentJsGlob = '**/*.js';
const componentTsGlob = '**/*.tsx';
let prevFiles = [];
async function reskindex() {
const jsFiles = glob.sync(componentJsGlob, {cwd: componentsDir}).sort();
const tsFiles = glob.sync(componentTsGlob, {cwd: componentsDir}).sort();
const files = [...tsFiles, ...jsFiles];
if (!filesHaveChanged(files, prevFiles)) {
return;
}
prevFiles = files;
const header = args.h || args.header;
const strm = fs.createWriteStream(componentIndexTmp);
// Wait for the open event to ensure the file descriptor is set
await new Promise(resolve => strm.once("open", resolve));
if (header) {
strm.write(fs.readFileSync(header));
strm.write('\n');
}
strm.write("/*\n");
strm.write(" * THIS FILE IS AUTO-GENERATED\n");
strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
strm.write(" * You are not a salmon.\n");
strm.write(" */\n\n");
strm.write("let components = {};\n");
for (let i = 0; i < files.length; ++i) {
const file = files[i].replace('.js', '').replace('.tsx', '');
const moduleName = (file.replace(/\//g, '.'));
const importName = moduleName.replace(/\./g, "$");
strm.write("import " + importName + " from './components/" + file + "';\n");
strm.write(importName + " && (components['"+moduleName+"'] = " + importName + ");");
strm.write('\n');
strm.uncork();
}
strm.write("export {components};\n");
// Ensure the file has been fully written to disk before proceeding
await util.promisify(fs.fsync)(strm.fd);
await util.promisify(strm.end);
await fsp.rename(componentIndexTmp, componentIndex);
}
// Expects both arrays of file names to be sorted
function filesHaveChanged(files, prevFiles) {
if (files.length !== prevFiles.length) {
return true;
}
// Check for name changes
for (let i = 0; i < files.length; i++) {
if (prevFiles[i] !== files[i]) {
return true;
}
}
return false;
}
// Wrapper since await at the top level is not well supported yet
function run() {
(async function() {
await reskindex();
console.log("Reskindex completed");
})();
}
// -w indicates watch mode where any FS events will trigger reskindex
if (!args.w) {
run();
return;
}
let watchDebouncer = null;
chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
if (path === componentIndex) return;
if (watchDebouncer) clearTimeout(watchDebouncer);
watchDebouncer = setTimeout(run, 1000);
});

View file

@ -1,38 +0,0 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
declare module "browser-encrypt-attachment" {
interface IInfo {
v: string;
key: {
alg: string;
key_ops: string[]; // eslint-disable-line camelcase
kty: string;
k: string;
ext: boolean;
};
iv: string;
hashes: {[alg: string]: string};
}
interface IEncryptedAttachment {
data: ArrayBuffer;
info: IInfo;
}
export function encryptAttachment(plaintextBuffer: ArrayBuffer): Promise<IEncryptedAttachment>;
export function decryptAttachment(ciphertextBuffer: ArrayBuffer, info: IInfo): Promise<ArrayBuffer>;
}

View file

@ -29,7 +29,6 @@ import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
import { IntegrationManagers } from "../integrations/IntegrationManagers";
import { ModalManager } from "../Modal";
import SettingsStore from "../settings/SettingsStore";
import { ActiveRoomObserver } from "../ActiveRoomObserver";
import { Notifier } from "../Notifier";
import type { Renderer } from "react-dom";
import RightPanelStore from "../stores/right-panel/RightPanelStore";
@ -50,7 +49,6 @@ import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import { Skinner } from "../Skinner";
import AutoRageshakeStore from "../stores/AutoRageshakeStore";
import { IConfigOptions } from "../IConfigOptions";
@ -83,7 +81,6 @@ declare global {
mxDeviceListener: DeviceListener;
mxRoomListStore: RoomListStoreClass;
mxRoomListLayoutStore: RoomListLayoutStore;
mxActiveRoomObserver: ActiveRoomObserver;
mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers;
singletonModalManager: ModalManager;
@ -107,7 +104,6 @@ declare global {
mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore;
mxActiveWidgetStore?: ActiveWidgetStore;
mxSkinner?: Skinner;
mxOnRecaptchaLoaded?: () => void;
electron?: Electron;
mxSendSentryReport: (userText: string, issueUrl: string, error: Error) => Promise<void>;

View file

@ -1,84 +0,0 @@
/*
Copyright 2017 - 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.
*/
import { logger } from "matrix-js-sdk/src/logger";
import RoomViewStore from './stores/RoomViewStore';
type Listener = (isActive: boolean) => void;
/**
* Consumes changes from the RoomViewStore and notifies specific things
* about when the active room changes. Unlike listening for RoomViewStore
* changes, you can subscribe to only changes relevant to a particular
* room.
*
* TODO: If we introduce an observer for something else, factor out
* the adding / removing of listeners & emitting into a common class.
*/
export class ActiveRoomObserver {
private listeners: {[key: string]: Listener[]} = {};
private _activeRoomId = RoomViewStore.getRoomId();
constructor() {
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.
RoomViewStore.addListener(this.onRoomViewStoreUpdate);
}
public get activeRoomId(): string {
return this._activeRoomId;
}
public addListener(roomId, listener) {
if (!this.listeners[roomId]) this.listeners[roomId] = [];
this.listeners[roomId].push(listener);
}
public removeListener(roomId, listener) {
if (this.listeners[roomId]) {
const i = this.listeners[roomId].indexOf(listener);
if (i > -1) {
this.listeners[roomId].splice(i, 1);
}
} else {
logger.warn("Unregistering unrecognised listener (roomId=" + roomId + ")");
}
}
private emit(roomId, isActive: boolean) {
if (!this.listeners[roomId]) return;
for (const l of this.listeners[roomId]) {
l.call(null, isActive);
}
}
private onRoomViewStoreUpdate = () => {
// emit for the old room ID
if (this._activeRoomId) this.emit(this._activeRoomId, false);
// update our cache
this._activeRoomId = RoomViewStore.getRoomId();
// and emit for the new one
if (this._activeRoomId) this.emit(this._activeRoomId, true);
};
}
if (window.mxActiveRoomObserver === undefined) {
window.mxActiveRoomObserver = new ActiveRoomObserver();
}
export default window.mxActiveRoomObserver;

View file

@ -23,7 +23,7 @@ import { getCurrentLanguage, _t, _td, IVariables } from './languageHandler';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
import Modal from './Modal';
import * as sdk from './index';
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import { SnakedObject } from "./utils/SnakedObject";
import { IConfigOptions } from "./IConfigOptions";
@ -406,14 +406,12 @@ export class Analytics {
{ expl: _td('Your device resolution'), value: resolution },
];
// FIXME: Using an import will result in test failures
const piwikConfig = SdkConfig.get("piwik");
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
if (typeof piwikConfig === 'object') {
piwik = new SnakedObject(piwikConfig);
}
const cookiePolicyUrl = piwik?.get("policy_url");
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
const cookiePolicyLink = _t(
"Our complete cookie policy can be found <CookiePolicyLink>here</CookiePolicyLink>.",
{},

View file

@ -17,9 +17,11 @@ limitations under the License.
import React, { ComponentType } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import * as sdk from './index';
import { _t } from './languageHandler';
import { IDialogProps } from "./components/views/dialogs/IDialogProps";
import BaseDialog from "./components/views/dialogs/BaseDialog";
import DialogButtons from "./components/views/elements/DialogButtons";
import Spinner from "./components/views/elements/Spinner";
type AsyncImport<T> = { default: T };
@ -78,9 +80,6 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
const Component = this.state.component;
return <Component {...this.props} />;
} else if (this.state.error) {
// FIXME: Using an import will result in test failures
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <BaseDialog onFinished={this.props.onFinished} title={_t("Error")}>
{ _t("Unable to load! Check your network connectivity and try again.") }
<DialogButtons primaryButton={_t("Dismiss")}
@ -90,7 +89,6 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
</BaseDialog>;
} else {
// show a spinner until the component is loaded.
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
}
}

View file

@ -57,7 +57,7 @@ function isValidHexColor(color: string): boolean {
return typeof color === "string" &&
(color.length === 7 || color.length === 9) &&
color.charAt(0) === "#" &&
!color.substr(1).split("").some(c => isNaN(parseInt(c, 16)));
!color.slice(1).split("").some(c => isNaN(parseInt(c, 16)));
}
function urlForColor(color: string): string {

View file

@ -145,6 +145,13 @@ export default abstract class BasePlatform {
return false;
}
/**
* Returns true if platform allows overriding native context menus
*/
public allowOverridingNativeContextMenus(): boolean {
return false;
}
/**
* Returns true if the platform supports displaying
* notifications, otherwise false.

View file

@ -44,7 +44,6 @@ import { WidgetType } from "./widgets/WidgetType";
import { SettingLevel } from "./settings/SettingLevel";
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import InviteDialog, { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialog";
import WidgetStore from "./stores/WidgetStore";
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
@ -54,12 +53,15 @@ import { Action } from './dispatcher/actions';
import VoipUserMapper from './VoipUserMapper';
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
import SdkConfig from './SdkConfig';
import { ensureDMExists, findDMForUser } from './createRoom';
import { ensureDMExists } from './createRoom';
import { Container, WidgetLayoutStore } from './stores/widgets/WidgetLayoutStore';
import IncomingCallToast, { getIncomingCallToastKey } from './toasts/IncomingCallToast';
import ToastStore from './stores/ToastStore';
import Resend from './Resend';
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
import { findDMForUser } from "./utils/direct-messages";
import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes";
import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
export const PROTOCOL_PSTN = 'm.protocol.pstn';
export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn';
@ -67,9 +69,6 @@ export const PROTOCOL_SIP_NATIVE = 'im.vector.protocol.sip_native';
export const PROTOCOL_SIP_VIRTUAL = 'im.vector.protocol.sip_virtual';
const CHECK_PROTOCOLS_ATTEMPTS = 3;
// Event type for room account data and room creation content used to mark rooms as virtual rooms
// (and store the ID of their native room)
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
enum AudioID {
Ring = 'ringAudio',
@ -1094,14 +1093,17 @@ export default class CallHandler extends EventEmitter {
*/
public showTransferDialog(call: MatrixCall): void {
call.setRemoteOnHold(true);
const { finished } = Modal.createTrackedDialog(
'Transfer Call', '', InviteDialog, { kind: KIND_CALL_TRANSFER, call },
/*className=*/"mx_InviteDialog_transferWrapper", /*isPriority=*/false, /*isStatic=*/true,
);
finished.then((results: boolean[]) => {
if (results.length === 0 || results[0] === false) {
call.setRemoteOnHold(false);
}
dis.dispatch<OpenInviteDialogPayload>({
action: Action.OpenInviteDialog,
kind: KIND_CALL_TRANSFER,
call,
analyticsName: "Transfer Call",
className: "mx_InviteDialog_transferWrapper",
onFinishedCallback: (results) => {
if (results.length === 0 || results[0] === false) {
call.setRemoteOnHold(false);
}
},
});
}

View file

@ -19,7 +19,7 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IUploadOpts } from "matrix-js-sdk/src/@types/requests";
import { MsgType } from "matrix-js-sdk/src/@types/event";
import encrypt from "browser-encrypt-attachment";
import encrypt from "matrix-encrypt-attachment";
import extractPngChunks from "png-chunks-extract";
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
import { logger } from "matrix-js-sdk/src/logger";
@ -28,7 +28,6 @@ import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
import Spinner from "./components/views/elements/Spinner";
@ -41,27 +40,23 @@ import {
UploadStartedPayload,
} from "./dispatcher/payloads/UploadPayload";
import { IUpload } from "./models/IUpload";
import { BlurhashEncoder } from "./BlurhashEncoder";
import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { TimelineRenderingType } from "./contexts/RoomContext";
import RoomViewStore from "./stores/RoomViewStore";
import { RoomViewStore } from "./stores/RoomViewStore";
import { addReplyToMessageContent } from "./utils/Reply";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import UploadFailureDialog from "./components/views/dialogs/UploadFailureDialog";
import UploadConfirmDialog from "./components/views/dialogs/UploadConfirmDialog";
import { createThumbnail } from "./utils/image-media";
import { attachRelation } from "./components/views/rooms/SendMessageComposer";
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
// scraped out of a macOS hidpi (5660ppm) screenshot png
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
export class UploadCanceledError extends Error {}
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
interface IMediaConfig {
"m.upload.size"?: number;
}
@ -77,103 +72,6 @@ interface IContent {
url?: string;
}
interface IThumbnail {
info: {
// eslint-disable-next-line camelcase
thumbnail_info: {
w: number;
h: number;
mimetype: string;
size: number;
};
w: number;
h: number;
[BLURHASH_FIELD]: string;
};
thumbnail: Blob;
}
/**
* Create a thumbnail for a image DOM element.
* The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
* The thumbnail will have the same aspect ratio as the original.
* Draws the element into a canvas using CanvasRenderingContext2D.drawImage
* Then calls Canvas.toBlob to get a blob object for the image data.
*
* Since it needs to calculate the dimensions of the source image and the
* thumbnailed image it returns an info object filled out with information
* about the original image and the thumbnail.
*
* @param {HTMLElement} element The element to thumbnail.
* @param {number} inputWidth The width of the image in the input element.
* @param {number} inputHeight the width of the image in the input element.
* @param {string} mimeType The mimeType to save the blob as.
* @param {boolean} calculateBlurhash Whether to calculate a blurhash of the given image too.
* @return {Promise} A promise that resolves with an object with an info key
* and a thumbnail key.
*/
export async function createThumbnail(
element: ThumbnailableElement,
inputWidth: number,
inputHeight: number,
mimeType: string,
calculateBlurhash = true,
): Promise<IThumbnail> {
let targetWidth = inputWidth;
let targetHeight = inputHeight;
if (targetHeight > MAX_HEIGHT) {
targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
targetHeight = MAX_HEIGHT;
}
if (targetWidth > MAX_WIDTH) {
targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
targetWidth = MAX_WIDTH;
}
let canvas: HTMLCanvasElement | OffscreenCanvas;
let context: CanvasRenderingContext2D;
try {
canvas = new window.OffscreenCanvas(targetWidth, targetHeight);
context = canvas.getContext("2d");
} catch (e) {
// Fallback support for other browsers (Safari and Firefox for now)
canvas = document.createElement("canvas");
(canvas as HTMLCanvasElement).width = targetWidth;
(canvas as HTMLCanvasElement).height = targetHeight;
context = canvas.getContext("2d");
}
context.drawImage(element, 0, 0, targetWidth, targetHeight);
let thumbnailPromise: Promise<Blob>;
if (window.OffscreenCanvas) {
thumbnailPromise = (canvas as OffscreenCanvas).convertToBlob({ type: mimeType });
} else {
thumbnailPromise = new Promise<Blob>(resolve => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
}
const imageData = context.getImageData(0, 0, targetWidth, targetHeight);
// thumbnailPromise and blurhash promise are being awaited concurrently
const blurhash = calculateBlurhash ? await BlurhashEncoder.instance.getBlurhash(imageData) : undefined;
const thumbnail = await thumbnailPromise;
return {
info: {
thumbnail_info: {
w: targetWidth,
h: targetHeight,
mimetype: thumbnail.type,
size: thumbnail.size,
},
w: inputWidth,
h: inputHeight,
[BLURHASH_FIELD]: blurhash,
},
thumbnail,
};
}
/**
* Load a file into a newly created image element.
*
@ -472,7 +370,7 @@ export default class ContentMessages {
return;
}
const replyToEvent = RoomViewStore.getQuotingEvent();
const replyToEvent = RoomViewStore.instance.getQuotingEvent();
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
await this.ensureMediaConfigFetched(matrixClient);
@ -491,8 +389,6 @@ export default class ContentMessages {
}
if (tooBigFiles.length > 0) {
// FIXME: Using an import will result in Element crashing
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
badFiles: tooBigFiles,
totalFiles: files.length,
@ -509,8 +405,6 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
if (!uploadAll) {
// FIXME: Using an import will result in Element crashing
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
const { finished } = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
'', UploadConfirmDialog, {
file,
@ -689,8 +583,6 @@ export default class ContentMessages {
{ fileName: upload.fileName },
);
}
// FIXME: Using an import will result in Element crashing
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
title: _t('Upload Failed'),
description: desc,

View file

@ -36,9 +36,9 @@ import {
} from "./toasts/UnverifiedSessionToast";
import { accessSecretStorage, isSecretStorageBeingAccessed } from "./SecurityManager";
import { isSecureBackupRequired } from './utils/WellKnownUtils';
import { isLoggedIn } from './components/structures/MatrixChat';
import { ActionPayload } from "./dispatcher/payloads";
import { Action } from "./dispatcher/actions";
import { isLoggedIn } from "./utils/login";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;

View file

@ -60,10 +60,19 @@ import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestore
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
import { setSentryUser } from "./sentry";
import SdkConfig from "./SdkConfig";
import { DialogOpener } from "./utils/DialogOpener";
import { Action } from "./dispatcher/actions";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
dis.register((payload) => {
if (payload.action === Action.TriggerLogout) {
// noinspection JSIgnoredPromiseFromCall - we don't care if it fails
onLoggedOut();
}
});
interface ILoadSessionOpts {
enableGuest?: boolean;
guestHsUrl?: string;
@ -791,6 +800,7 @@ async function startMatrixClient(startSyncing = true): Promise<void> {
TypingStore.sharedInstance().reset();
ToastStore.sharedInstance().reset();
DialogOpener.instance.prepare();
Notifier.start();
UserActivity.sharedInstance().start();
DMRoomMap.makeShared().start();
@ -865,8 +875,9 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void
Analytics.disable();
if (window.localStorage) {
// try to save any 3pid invites from being obliterated
// try to save any 3pid invites from being obliterated and registration time
const pendingInvites = ThreepidInviteStore.instance.getWireInvites();
const registrationTime = window.localStorage.getItem("mx_registration_time");
window.localStorage.clear();
@ -876,13 +887,17 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void
logger.error("idbDelete failed for account:mx_access_token", e);
}
// now restore those invites
// now restore those invites and registration time
if (!opts?.deleteEverything) {
pendingInvites.forEach(i => {
const roomId = i.roomId;
delete i.roomId; // delete to avoid confusing the store
ThreepidInviteStore.instance.storeInvite(roomId, i);
});
if (registrationTime) {
window.localStorage.setItem("mx_registration_time", registrationTime);
}
}
}

View file

@ -27,7 +27,6 @@ import { verificationMethods } from 'matrix-js-sdk/src/crypto';
import { SHOW_QR_CODE_METHOD } from "matrix-js-sdk/src/crypto/verification/QRCode";
import { logger } from "matrix-js-sdk/src/logger";
import * as sdk from './index';
import createMatrixClient from './utils/createMatrixClient';
import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
@ -37,6 +36,7 @@ import * as StorageManager from './utils/StorageManager';
import IdentityAuthClient from './IdentityAuthClient';
import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } from './SecurityManager';
import SecurityCustomisations from "./customisations/Security";
import CryptoStoreTooNewDialog from "./components/views/dialogs/CryptoStoreTooNewDialog";
export interface IMatrixClientCreds {
homeserverUrl: string;
@ -117,7 +117,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
};
private matrixClient: MatrixClient = null;
private justRegisteredUserId: string;
private justRegisteredUserId: string | null = null;
// the credentials used to init the current client object.
// used if we tear it down & recreate it with a different store
@ -136,7 +136,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
MatrixActionCreators.stop();
}
public setJustRegisteredUserId(uid: string): void {
public setJustRegisteredUserId(uid: string | null): void {
this.justRegisteredUserId = uid;
if (uid) {
window.localStorage.setItem("mx_registration_time", String(new Date().getTime()));
@ -151,9 +151,14 @@ class MatrixClientPegClass implements IMatrixClientPeg {
}
public userRegisteredWithinLastHours(hours: number): boolean {
if (hours <= 0) {
return false;
}
try {
const date = new Date(window.localStorage.getItem("mx_registration_time"));
return ((new Date().getTime() - date.getTime()) / 36e5) <= hours;
const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"));
const diff = Date.now() - registrationTime;
return (diff / 36e5) <= hours;
} catch (e) {
return false;
}
@ -200,9 +205,6 @@ class MatrixClientPegClass implements IMatrixClientPeg {
} catch (e) {
if (e && e.name === 'InvalidCryptoStoreError') {
// The js-sdk found a crypto DB too new for it to use
// FIXME: Using an import will result in test failures
const CryptoStoreTooNewDialog =
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
Modal.createDialog(CryptoStoreTooNewDialog);
}
// this can happen for a number of reasons, the most likely being

View file

@ -37,7 +37,7 @@ import SettingsStore from "./settings/SettingsStore";
import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast";
import { SettingLevel } from "./settings/SettingLevel";
import { isPushNotifyDisabled } from "./settings/controllers/NotificationControllers";
import RoomViewStore from "./stores/RoomViewStore";
import { RoomViewStore } from "./stores/RoomViewStore";
import UserActivity from "./UserActivity";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
@ -401,7 +401,7 @@ export const Notifier = {
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions?.notify) {
if (RoomViewStore.getRoomId() === room.roomId &&
if (RoomViewStore.instance.getRoomId() === room.roomId &&
UserActivity.sharedInstance().userActiveRecently() &&
!Modal.hasDialogs()
) {

View file

@ -24,10 +24,12 @@ import { MatrixClientPeg } from './MatrixClientPeg';
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
import { _t } from './languageHandler';
import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
import InviteDialog from "./components/views/dialogs/InviteDialog";
import BaseAvatar from "./components/views/avatars/BaseAvatar";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialogTypes";
import { Member } from "./utils/direct-messages";
export interface IInviteResult {
states: CompletionStates;

View file

@ -19,9 +19,6 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixClientPeg } from './MatrixClientPeg';
import AliasCustomisations from './customisations/Alias';
import DMRoomMap from "./utils/DMRoomMap";
import SpaceStore from "./stores/spaces/SpaceStore";
import { _t } from "./languageHandler";
/**
* Given a room object, return the alias we should use for it,
@ -157,48 +154,3 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
if (oldestUser === undefined) return myUserId;
return oldestUser.userId;
}
export function spaceContextDetailsText(space: Room): string {
if (!space.isSpaceRoom()) return undefined;
const [parent, secondParent, ...otherParents] = SpaceStore.instance.getKnownParents(space.roomId);
if (secondParent && !otherParents?.length) {
// exactly 2 edge case for improved i18n
return _t("%(space1Name)s and %(space2Name)s", {
space1Name: space.client.getRoom(parent)?.name,
space2Name: space.client.getRoom(secondParent)?.name,
});
} else if (parent) {
return _t("%(spaceName)s and %(count)s others", {
spaceName: space.client.getRoom(parent)?.name,
count: otherParents.length,
});
}
return space.getCanonicalAlias();
}
export function roomContextDetailsText(room: Room): string {
if (room.isSpaceRoom()) return undefined;
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
if (dmPartner) {
return dmPartner;
}
const [parent, secondParent, ...otherParents] = SpaceStore.instance.getKnownParents(room.roomId);
if (secondParent && !otherParents?.length) {
// exactly 2 edge case for improved i18n
return _t("%(space1Name)s and %(space2Name)s", {
space1Name: room.client.getRoom(parent)?.name,
space2Name: room.client.getRoom(secondParent)?.name,
});
} else if (parent) {
return _t("%(spaceName)s and %(count)s others", {
spaceName: room.client.getRoom(parent)?.name,
count: otherParents.length,
});
}
return room.getCanonicalAlias();
}

View file

@ -241,7 +241,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
import RoomViewStore from './stores/RoomViewStore';
import { RoomViewStore } from './stores/RoomViewStore';
import { _t } from './languageHandler';
import { IntegrationManagers } from "./integrations/IntegrationManagers";
import { WidgetType } from "./widgets/WidgetType";
@ -638,7 +638,7 @@ const onMessage = function(event: MessageEvent<any>): void {
}
}
if (roomId !== RoomViewStore.getRoomId()) {
if (roomId !== RoomViewStore.instance.getRoomId()) {
sendError(event, _t('Room %(roomId)s not visible', { roomId: roomId }));
return;
}

View file

@ -25,7 +25,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { ComponentType } from "react";
import Modal from './Modal';
import * as sdk from './index';
import { MatrixClientPeg } from './MatrixClientPeg';
import { _t } from './languageHandler';
import { isSecureBackupRequired } from './utils/WellKnownUtils';
@ -34,6 +33,7 @@ import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreK
import SettingsStore from "./settings/SettingsStore";
import SecurityCustomisations from "./customisations/Security";
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
// This stores the secret storage private keys in memory for the JS SDK. This is
// only meant to act as a cache to avoid prompting the user multiple times
@ -360,8 +360,6 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
throw new Error("Secret storage creation canceled");
}
} else {
// FIXME: Using an import will result in test failures
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
const { finished } = Modal.createTrackedDialog(

Some files were not shown because too many files have changed in this diff Show more