mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
Merge branch 'develop' into uhoreg/keytar_logging
This commit is contained in:
commit
0cf10bb69a
127
.eslintrc.js
127
.eslintrc.js
@ -11,111 +11,36 @@ const path = require('path');
|
||||
const matrixJsSdkPath = path.join(path.dirname(require.resolve('matrix-js-sdk')), '..');
|
||||
|
||||
module.exports = {
|
||||
extends: ["matrix-org", "matrix-org/react-legacy"],
|
||||
parser: "babel-eslint",
|
||||
extends: [matrixJsSdkPath + "/.eslintrc.js"],
|
||||
plugins: [
|
||||
"react",
|
||||
"react-hooks",
|
||||
"flowtype",
|
||||
"babel"
|
||||
],
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
globals: {
|
||||
LANGUAGES_FILE: "readonly",
|
||||
},
|
||||
env: {
|
||||
es6: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
legacyDecorators: true,
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
// eslint's built in no-invalid-this rule breaks with class properties
|
||||
"no-invalid-this": "off",
|
||||
// so we replace it with a version that is class property aware
|
||||
"babel/no-invalid-this": "error",
|
||||
|
||||
// We appear to follow this most of the time, so let's enforce it instead
|
||||
// of occasionally following it (or catching it in review)
|
||||
"keyword-spacing": "error",
|
||||
|
||||
/** react **/
|
||||
// This just uses the react plugin to help eslint known when
|
||||
// variables have been used in JSX
|
||||
"react/jsx-uses-vars": "error",
|
||||
// Don't mark React as unused if we're using JSX
|
||||
"react/jsx-uses-react": "error",
|
||||
|
||||
// bind or arrow function in props causes performance issues
|
||||
// (but we currently use them in some places)
|
||||
// It's disabled here, but we should using it sparingly.
|
||||
"react/jsx-no-bind": "off",
|
||||
"react/jsx-key": ["error"],
|
||||
|
||||
// Components in JSX should always be defined.
|
||||
"react/jsx-no-undef": "error",
|
||||
|
||||
// Assert no spacing in JSX curly brackets
|
||||
// <Element prop={ consideredError} prop={notConsideredError} />
|
||||
//
|
||||
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-curly-spacing.md
|
||||
//
|
||||
// Disabled for now - if anything we'd like to *enforce* spacing in JSX
|
||||
// curly brackets for legibility, but in practice it's not clear that the
|
||||
// consistency particularly improves legibility here. --Matthew
|
||||
//
|
||||
// "react/jsx-curly-spacing": ["error", {"when": "never", "children": {"when": "always"}}],
|
||||
|
||||
// Assert spacing before self-closing JSX tags, and no spacing before or
|
||||
// after the closing slash, and no spacing after the opening bracket of
|
||||
// the opening tag or closing tag.
|
||||
//
|
||||
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-tag-spacing.md
|
||||
"react/jsx-tag-spacing": ["error"],
|
||||
|
||||
/** flowtype **/
|
||||
"flowtype/require-parameter-type": ["warn", {
|
||||
"excludeArrowFunctions": true,
|
||||
}],
|
||||
"flowtype/define-flow-type": "warn",
|
||||
"flowtype/require-return-type": ["warn",
|
||||
"always",
|
||||
{
|
||||
"annotateUndefined": "never",
|
||||
"excludeArrowFunctions": true,
|
||||
}
|
||||
],
|
||||
"flowtype/space-after-type-colon": ["warn", "always"],
|
||||
"flowtype/space-before-type-colon": ["warn", "never"],
|
||||
|
||||
/*
|
||||
* things that are errors in the js-sdk config that the current
|
||||
* code does not adhere to, turned down to warn
|
||||
*/
|
||||
"max-len": ["warn", {
|
||||
// apparently people believe the length limit shouldn't apply
|
||||
// to JSX.
|
||||
ignorePattern: '^\\s*<',
|
||||
ignoreComments: true,
|
||||
ignoreRegExpLiterals: true,
|
||||
code: 120,
|
||||
}],
|
||||
"valid-jsdoc": ["warn"],
|
||||
"new-cap": ["warn"],
|
||||
"key-spacing": ["warn"],
|
||||
"prefer-const": ["warn"],
|
||||
|
||||
// crashes currently: https://github.com/eslint/eslint/issues/6274
|
||||
"generator-star-spacing": "off",
|
||||
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
},
|
||||
settings: {
|
||||
flowtype: {
|
||||
onlyFilesWithFlowAnnotation: true
|
||||
},
|
||||
// Things we do that break the ideal style
|
||||
"no-constant-condition": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
"quotes": "off",
|
||||
"indent": "off",
|
||||
},
|
||||
|
||||
overrides: [{
|
||||
files: ["src/**/*.{ts, tsx}"],
|
||||
"extends": ["matrix-org/ts"],
|
||||
"rules": {
|
||||
// We disable this while we're transitioning
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// We'd rather not do this but we do
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
|
||||
"quotes": "off",
|
||||
"no-extra-boolean-cast": "off",
|
||||
}
|
||||
}],
|
||||
};
|
||||
|
122
CHANGELOG.md
122
CHANGELOG.md
@ -1,3 +1,125 @@
|
||||
Changes in [3.0.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.0.0) (2020-07-27)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.10.1...v3.0.0)
|
||||
|
||||
BREAKING CHANGES
|
||||
---
|
||||
|
||||
* The room list components have been replaced as part of this release, so the list, tiles, and other associated components now use a different prop / state contract.
|
||||
|
||||
|
||||
All Changes
|
||||
---
|
||||
|
||||
* Upgrade to JS SDK 8.0.0
|
||||
* Update from Weblate
|
||||
[\#5053](https://github.com/matrix-org/matrix-react-sdk/pull/5053)
|
||||
* RoomList listen to notificationState updates for bolding
|
||||
[\#5051](https://github.com/matrix-org/matrix-react-sdk/pull/5051)
|
||||
* Ensure notification badges stop listening when they unmount
|
||||
[\#5049](https://github.com/matrix-org/matrix-react-sdk/pull/5049)
|
||||
* Improve RoomTile performance
|
||||
[\#5048](https://github.com/matrix-org/matrix-react-sdk/pull/5048)
|
||||
* Reward users for using stable ordering in their room list
|
||||
[\#5047](https://github.com/matrix-org/matrix-react-sdk/pull/5047)
|
||||
* Fix autocomplete suggesting a different thing mid-composition
|
||||
[\#5030](https://github.com/matrix-org/matrix-react-sdk/pull/5030)
|
||||
* Put low priority xor toggle back in the room list context menu
|
||||
[\#5026](https://github.com/matrix-org/matrix-react-sdk/pull/5026)
|
||||
* Fix autocompletion of Community IDs
|
||||
[\#5040](https://github.com/matrix-org/matrix-react-sdk/pull/5040)
|
||||
* Use OpenType tabular numbers in timestamps
|
||||
[\#5042](https://github.com/matrix-org/matrix-react-sdk/pull/5042)
|
||||
* Update packages to modern versions
|
||||
[\#5046](https://github.com/matrix-org/matrix-react-sdk/pull/5046)
|
||||
* Add dismiss button to rebrand toast
|
||||
[\#5044](https://github.com/matrix-org/matrix-react-sdk/pull/5044)
|
||||
* Fix Firefox composer regression exception
|
||||
[\#5039](https://github.com/matrix-org/matrix-react-sdk/pull/5039)
|
||||
* Fix BaseAvatar wrongly using Buttons when it needs not
|
||||
[\#5037](https://github.com/matrix-org/matrix-react-sdk/pull/5037)
|
||||
* Performance improvements round 2: Maps, freezing, dispatching, and flexbox
|
||||
obliteration
|
||||
[\#5038](https://github.com/matrix-org/matrix-react-sdk/pull/5038)
|
||||
* Mixed bag of performance improvements: ScrollPanel and notifications
|
||||
[\#5034](https://github.com/matrix-org/matrix-react-sdk/pull/5034)
|
||||
* Update message previews
|
||||
[\#5025](https://github.com/matrix-org/matrix-react-sdk/pull/5025)
|
||||
* Translate create room buttons
|
||||
[\#5035](https://github.com/matrix-org/matrix-react-sdk/pull/5035)
|
||||
* Escape single quotes in composer placeholder
|
||||
[\#5033](https://github.com/matrix-org/matrix-react-sdk/pull/5033)
|
||||
* Don't hammer on the layout engine with avatar updates for the background
|
||||
[\#5032](https://github.com/matrix-org/matrix-react-sdk/pull/5032)
|
||||
* Ensure incremental updates to the ImportanceAlgorithm trigger A-Z order
|
||||
[\#5031](https://github.com/matrix-org/matrix-react-sdk/pull/5031)
|
||||
* don't syntax highlight languages that begin with "_"
|
||||
[\#5029](https://github.com/matrix-org/matrix-react-sdk/pull/5029)
|
||||
* Convert Modal to TypeScript
|
||||
[\#4956](https://github.com/matrix-org/matrix-react-sdk/pull/4956)
|
||||
* Use new eslint dependency and remove tslint
|
||||
[\#4815](https://github.com/matrix-org/matrix-react-sdk/pull/4815)
|
||||
* Support custom tags in the room list again
|
||||
[\#5024](https://github.com/matrix-org/matrix-react-sdk/pull/5024)
|
||||
* Fix the tag panel context menu
|
||||
[\#5028](https://github.com/matrix-org/matrix-react-sdk/pull/5028)
|
||||
* Tag Watcher don't create new filter if not needed, confuses references
|
||||
[\#5021](https://github.com/matrix-org/matrix-react-sdk/pull/5021)
|
||||
* Convert editor to TypeScript
|
||||
[\#4978](https://github.com/matrix-org/matrix-react-sdk/pull/4978)
|
||||
* Query Matcher use unhomoglyph for a little bit more leniency
|
||||
[\#4977](https://github.com/matrix-org/matrix-react-sdk/pull/4977)
|
||||
* Fix Breadcrumbs2 ending up with 2 tabIndexes on Firefox
|
||||
[\#5017](https://github.com/matrix-org/matrix-react-sdk/pull/5017)
|
||||
* Add min-width to floating Jitsi
|
||||
[\#5023](https://github.com/matrix-org/matrix-react-sdk/pull/5023)
|
||||
* Update crypto event icon to match rest of app styling
|
||||
[\#5020](https://github.com/matrix-org/matrix-react-sdk/pull/5020)
|
||||
* Fix Reactions Row Button vertical misalignment due to forced height
|
||||
[\#5019](https://github.com/matrix-org/matrix-react-sdk/pull/5019)
|
||||
* Use mouseleave instead of mouseout for hover events. Fix tooltip flicker
|
||||
[\#5016](https://github.com/matrix-org/matrix-react-sdk/pull/5016)
|
||||
* Fix slash commands null guard
|
||||
[\#5015](https://github.com/matrix-org/matrix-react-sdk/pull/5015)
|
||||
* Fix field tooltips
|
||||
[\#5014](https://github.com/matrix-org/matrix-react-sdk/pull/5014)
|
||||
* Fix community right panel button regression
|
||||
[\#5022](https://github.com/matrix-org/matrix-react-sdk/pull/5022)
|
||||
* [BREAKING] Remove the old room list
|
||||
[\#5013](https://github.com/matrix-org/matrix-react-sdk/pull/5013)
|
||||
* ellipse senders for images and videos
|
||||
[\#4990](https://github.com/matrix-org/matrix-react-sdk/pull/4990)
|
||||
* Sprinkle and consolidate some tooltips
|
||||
[\#5012](https://github.com/matrix-org/matrix-react-sdk/pull/5012)
|
||||
* Hopefully make cancel dialog a bit less weird
|
||||
[\#4833](https://github.com/matrix-org/matrix-react-sdk/pull/4833)
|
||||
* Fix emoji filterString
|
||||
[\#5011](https://github.com/matrix-org/matrix-react-sdk/pull/5011)
|
||||
* Fix size call for devtools state events
|
||||
[\#5008](https://github.com/matrix-org/matrix-react-sdk/pull/5008)
|
||||
* Fix `this` context in _setupHomeserverManagers for IntegrationManagers
|
||||
[\#5010](https://github.com/matrix-org/matrix-react-sdk/pull/5010)
|
||||
* Sync recently used reactions list across sessions
|
||||
[\#4993](https://github.com/matrix-org/matrix-react-sdk/pull/4993)
|
||||
* Null guard no e2ee for UserInfo
|
||||
[\#5009](https://github.com/matrix-org/matrix-react-sdk/pull/5009)
|
||||
* stop Inter from clobbering Twemoji
|
||||
[\#5007](https://github.com/matrix-org/matrix-react-sdk/pull/5007)
|
||||
* use a proper HTML sanitizer to strip <mx-reply>, rather than a regexp
|
||||
[\#5006](https://github.com/matrix-org/matrix-react-sdk/pull/5006)
|
||||
* Convert room list log setting to a real setting
|
||||
[\#5005](https://github.com/matrix-org/matrix-react-sdk/pull/5005)
|
||||
* Bump lodash from 4.17.15 to 4.17.19 in /test/end-to-end-tests
|
||||
[\#5003](https://github.com/matrix-org/matrix-react-sdk/pull/5003)
|
||||
* Bump lodash from 4.17.15 to 4.17.19
|
||||
[\#5004](https://github.com/matrix-org/matrix-react-sdk/pull/5004)
|
||||
* Convert devtools dialog to use new room state format
|
||||
[\#4936](https://github.com/matrix-org/matrix-react-sdk/pull/4936)
|
||||
* Update checkbox
|
||||
[\#5000](https://github.com/matrix-org/matrix-react-sdk/pull/5000)
|
||||
* Increase width for country code dropdown
|
||||
[\#5001](https://github.com/matrix-org/matrix-react-sdk/pull/5001)
|
||||
|
||||
Changes in [2.10.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.10.1) (2020-07-16)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.10.0...v2.10.1)
|
||||
|
149
package.json
149
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "2.10.1",
|
||||
"version": "3.0.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
@ -45,126 +45,127 @@
|
||||
"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:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
||||
"lint": "yarn lint:types && yarn lint:ts && yarn lint:js && yarn lint:style",
|
||||
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
||||
"lint:js": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
|
||||
"lint:ts": "tslint --project ./tsconfig.json -t stylish",
|
||||
"lint:types": "tsc --noEmit --jsx react",
|
||||
"lint:style": "stylelint 'res/css/**/*.scss'",
|
||||
"test": "jest",
|
||||
"test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.3",
|
||||
"@babel/runtime": "^7.10.5",
|
||||
"await-lock": "^2.0.1",
|
||||
"blueimp-canvas-to-blob": "^3.5.0",
|
||||
"blueimp-canvas-to-blob": "^3.27.0",
|
||||
"browser-encrypt-attachment": "^0.3.0",
|
||||
"browser-request": "^0.3.3",
|
||||
"classnames": "^2.1.2",
|
||||
"commonmark": "^0.28.1",
|
||||
"counterpart": "^0.18.0",
|
||||
"create-react-class": "^15.6.0",
|
||||
"diff-dom": "^4.1.3",
|
||||
"diff-match-patch": "^1.0.4",
|
||||
"classnames": "^2.2.6",
|
||||
"commonmark": "^0.29.1",
|
||||
"counterpart": "^0.18.6",
|
||||
"create-react-class": "^15.6.3",
|
||||
"diff-dom": "^4.1.6",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"emojibase-data": "^5.0.1",
|
||||
"emojibase-regex": "^4.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"file-saver": "^1.3.3",
|
||||
"filesize": "3.5.6",
|
||||
"file-saver": "^1.3.8",
|
||||
"filesize": "3.6.1",
|
||||
"flux": "2.1.1",
|
||||
"focus-visible": "^5.0.2",
|
||||
"fuse.js": "^2.2.0",
|
||||
"gfm.css": "^1.1.1",
|
||||
"focus-visible": "^5.1.0",
|
||||
"fuse.js": "^2.7.4",
|
||||
"gfm.css": "^1.1.2",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^9.15.8",
|
||||
"html-entities": "^1.2.1",
|
||||
"highlight.js": "^10.1.2",
|
||||
"html-entities": "^1.3.1",
|
||||
"is-ip": "^2.0.0",
|
||||
"linkifyjs": "^2.1.6",
|
||||
"lodash": "^4.17.14",
|
||||
"linkifyjs": "^2.1.9",
|
||||
"lodash": "^4.17.19",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"minimist": "^1.2.0",
|
||||
"pako": "^1.0.5",
|
||||
"minimist": "^1.2.5",
|
||||
"pako": "^1.0.11",
|
||||
"parse5": "^5.1.1",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"project-name-generator": "^2.1.7",
|
||||
"prop-types": "^15.5.8",
|
||||
"prop-types": "^15.7.2",
|
||||
"qrcode": "^1.4.4",
|
||||
"qs": "^6.6.0",
|
||||
"re-resizable": "^6.5.2",
|
||||
"react": "^16.9.0",
|
||||
"qs": "^6.9.4",
|
||||
"re-resizable": "^6.5.4",
|
||||
"react": "^16.13.1",
|
||||
"react-beautiful-dnd": "^4.0.1",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-focus-lock": "^2.2.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-focus-lock": "^2.4.1",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"resize-observer-polyfill": "^1.5.0",
|
||||
"sanitize-html": "^1.18.4",
|
||||
"text-encoding-utf-8": "^1.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sanitize-html": "^1.27.1",
|
||||
"text-encoding-utf-8": "^1.0.2",
|
||||
"url": "^0.11.0",
|
||||
"velocity-animate": "^1.5.2",
|
||||
"what-input": "^5.2.6",
|
||||
"what-input": "^5.2.10",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.7.5",
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.7.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.7.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
|
||||
"@babel/plugin-transform-flow-comments": "^7.7.4",
|
||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"@babel/preset-flow": "^7.7.4",
|
||||
"@babel/preset-react": "^7.7.4",
|
||||
"@babel/preset-typescript": "^7.7.4",
|
||||
"@babel/register": "^7.7.4",
|
||||
"@peculiar/webcrypto": "^1.0.22",
|
||||
"@babel/cli": "^7.10.5",
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.10.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.10.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.10.4",
|
||||
"@babel/plugin-transform-flow-comments": "^7.10.4",
|
||||
"@babel/plugin-transform-runtime": "^7.10.5",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/preset-flow": "^7.10.4",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@babel/register": "^7.10.5",
|
||||
"@peculiar/webcrypto": "^1.1.2",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/flux": "^3.1.9",
|
||||
"@types/linkifyjs": "^2.1.3",
|
||||
"@types/lodash": "^4.14.152",
|
||||
"@types/lodash": "^4.14.158",
|
||||
"@types/modernizr": "^3.5.3",
|
||||
"@types/node": "^12.12.41",
|
||||
"@types/node": "^12.12.51",
|
||||
"@types/qrcode": "^1.3.4",
|
||||
"@types/react": "^16.9",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "^1.23.3",
|
||||
"@types/zxcvbn": "^4.4.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"concurrently": "^4.0.1",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"eslint": "^5.12.0",
|
||||
"eslint-config-google": "^0.7.1",
|
||||
"eslint-plugin-babel": "^5.2.1",
|
||||
"eslint-plugin-flowtype": "^2.30.0",
|
||||
"eslint-plugin-jest": "^23.0.4",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"eslint-plugin-react-hooks": "^2.0.1",
|
||||
"estree-walker": "^0.5.0",
|
||||
"chokidar": "^3.4.1",
|
||||
"concurrently": "^4.1.2",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"eslint": "7.5.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-matrix-org": "^0.1.2",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-flowtype": "^2.50.3",
|
||||
"eslint-plugin-jest": "^23.18.0",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"eslint-plugin-react-hooks": "^2.5.1",
|
||||
"estree-walker": "^0.9.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"flow-parser": "^0.57.3",
|
||||
"glob": "^5.0.14",
|
||||
"flow-parser": "0.57.3",
|
||||
"glob": "^5.0.15",
|
||||
"jest": "^24.9.0",
|
||||
"jest-canvas-mock": "^2.2.0",
|
||||
"lolex": "^5.1.2",
|
||||
"matrix-mock-request": "^1.2.3",
|
||||
"matrix-react-test-utils": "^0.2.2",
|
||||
"react-test-renderer": "^16.9.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"source-map-loader": "^0.2.3",
|
||||
"react-test-renderer": "^16.13.1",
|
||||
"rimraf": "^2.7.1",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"stylelint-scss": "^3.9.0",
|
||||
"tslint": "^5.20.1",
|
||||
"typescript": "^3.7.3",
|
||||
"walk": "^2.3.9",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-cli": "^3.1.1"
|
||||
"stylelint-config-standard": "^18.3.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"typescript": "^3.9.7",
|
||||
"walk": "^2.3.14",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12"
|
||||
},
|
||||
"jest": {
|
||||
"testMatch": [
|
||||
|
@ -226,7 +226,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||
}
|
||||
|
||||
#mx_theme_tertiaryAccentColor {
|
||||
color: $roomsublist-label-bg-color;
|
||||
color: $tertiary-accent-color;
|
||||
}
|
||||
|
||||
/* Expected z-indexes for dialogs:
|
||||
|
@ -12,7 +12,6 @@
|
||||
@import "./structures/_HeaderButtons.scss";
|
||||
@import "./structures/_HomePage.scss";
|
||||
@import "./structures/_LeftPanel.scss";
|
||||
@import "./structures/_LeftPanel2.scss";
|
||||
@import "./structures/_MainSplit.scss";
|
||||
@import "./structures/_MatrixChat.scss";
|
||||
@import "./structures/_MyGroups.scss";
|
||||
@ -21,14 +20,12 @@
|
||||
@import "./structures/_RoomDirectory.scss";
|
||||
@import "./structures/_RoomSearch.scss";
|
||||
@import "./structures/_RoomStatusBar.scss";
|
||||
@import "./structures/_RoomSubList.scss";
|
||||
@import "./structures/_RoomView.scss";
|
||||
@import "./structures/_ScrollPanel.scss";
|
||||
@import "./structures/_SearchBox.scss";
|
||||
@import "./structures/_TabbedView.scss";
|
||||
@import "./structures/_TagPanel.scss";
|
||||
@import "./structures/_ToastContainer.scss";
|
||||
@import "./structures/_TopLeftMenuButton.scss";
|
||||
@import "./structures/_UploadBar.scss";
|
||||
@import "./structures/_UserMenu.scss";
|
||||
@import "./structures/_ViewSource.scss";
|
||||
@ -108,7 +105,6 @@
|
||||
@import "./views/elements/_IconButton.scss";
|
||||
@import "./views/elements/_ImageView.scss";
|
||||
@import "./views/elements/_InlineSpinner.scss";
|
||||
@import "./views/elements/_InteractiveTooltip.scss";
|
||||
@import "./views/elements/_ManageIntegsButton.scss";
|
||||
@import "./views/elements/_PowerSelector.scss";
|
||||
@import "./views/elements/_ProgressBar.scss";
|
||||
@ -168,7 +164,6 @@
|
||||
@import "./views/rooms/_EventTile.scss";
|
||||
@import "./views/rooms/_GroupLayout.scss";
|
||||
@import "./views/rooms/_IRCLayout.scss";
|
||||
@import "./views/rooms/_InviteOnlyIcon.scss";
|
||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||
@import "./views/rooms/_LinkPreviewWidget.scss";
|
||||
@import "./views/rooms/_MemberInfo.scss";
|
||||
@ -181,23 +176,18 @@
|
||||
@import "./views/rooms/_PresenceLabel.scss";
|
||||
@import "./views/rooms/_ReplyPreview.scss";
|
||||
@import "./views/rooms/_RoomBreadcrumbs.scss";
|
||||
@import "./views/rooms/_RoomBreadcrumbs2.scss";
|
||||
@import "./views/rooms/_RoomDropTarget.scss";
|
||||
@import "./views/rooms/_RoomHeader.scss";
|
||||
@import "./views/rooms/_RoomList.scss";
|
||||
@import "./views/rooms/_RoomList2.scss";
|
||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
||||
@import "./views/rooms/_RoomSublist2.scss";
|
||||
@import "./views/rooms/_RoomSublist.scss";
|
||||
@import "./views/rooms/_RoomTile.scss";
|
||||
@import "./views/rooms/_RoomTile2.scss";
|
||||
@import "./views/rooms/_RoomTileIcon.scss";
|
||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||
@import "./views/rooms/_SearchBar.scss";
|
||||
@import "./views/rooms/_SendMessageComposer.scss";
|
||||
@import "./views/rooms/_Stickers.scss";
|
||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||
@import "./views/rooms/_UserOnlineDot.scss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||
@import "./views/settings/_AvatarSetting.scss";
|
||||
@import "./views/settings/_CrossSigningPanel.scss";
|
||||
@ -228,6 +218,4 @@
|
||||
@import "./views/verification/_VerificationShowSas.scss";
|
||||
@import "./views/voip/_CallContainer.scss";
|
||||
@import "./views/voip/_CallView.scss";
|
||||
@import "./views/voip/_CallView2.scss";
|
||||
@import "./views/voip/_IncomingCallbox.scss";
|
||||
@import "./views/voip/_VideoView.scss";
|
||||
|
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Update design for custom tags to match new designs
|
||||
|
||||
.mx_LeftPanel_tagPanelContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -50,7 +52,7 @@ limitations under the License.
|
||||
background-color: $accent-color-alt;
|
||||
width: 5px;
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
left: -9px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
top: 2px; // 10 [padding-top] - (56 - 40)/2
|
||||
top: 12px; // just feels right (see comment above about designs needing to be updated)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -15,164 +14,171 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_LeftPanel_container {
|
||||
display: flex;
|
||||
/* LeftPanel 260px */
|
||||
min-width: 260px;
|
||||
max-width: 50%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container.collapsed {
|
||||
min-width: unset;
|
||||
/* Collapsed LeftPanel 50px */
|
||||
flex: 0 0 50px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container.collapsed.mx_LeftPanel_container_hasTagPanel {
|
||||
/* TagPanel 70px + Collapsed LeftPanel 50px */
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_tagPanelContainer {
|
||||
flex: 0 0 70px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_hideButton {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
$tagPanelWidth: 56px; // only applies in this file, used for calculations
|
||||
|
||||
.mx_LeftPanel {
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
background-color: $roomlist-bg-color;
|
||||
min-width: 260px;
|
||||
max-width: 50%;
|
||||
|
||||
.mx_LeftPanel .mx_AppTile_mini {
|
||||
height: 132px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_RoomList_scrollbar {
|
||||
order: 1;
|
||||
|
||||
flex: 1 1 0;
|
||||
|
||||
overflow-y: auto;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu {
|
||||
order: 3;
|
||||
|
||||
border-top: 1px solid $panel-divider-color;
|
||||
margin-left: 16px; /* gutter */
|
||||
margin-right: 16px; /* gutter */
|
||||
flex: 0 0 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu {
|
||||
flex: 0 0 160px;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu_options {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.mx_BottomLeftMenu_options object {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_BottomLeftMenu_options > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_BottomLeftMenu_options .mx_RoleButton {
|
||||
margin-left: 0px;
|
||||
margin-right: 10px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.mx_BottomLeftMenu_options .mx_BottomLeftMenu_settings .mx_RoleButton {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu_settings {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.mx_MatrixChat_useCompactLayout {
|
||||
.mx_LeftPanel .mx_BottomLeftMenu {
|
||||
flex: 0 0 50px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu {
|
||||
flex: 0 0 160px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu_options {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_exploreAndFilterRow {
|
||||
// Create a row-based flexbox for the TagPanel and the room list
|
||||
display: flex;
|
||||
|
||||
.mx_SearchBox {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin: 4px 9px 1px 9px;
|
||||
}
|
||||
}
|
||||
.mx_LeftPanel_tagPanelContainer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: $tagPanelWidth;
|
||||
height: 100%;
|
||||
|
||||
.mx_LeftPanel_explore {
|
||||
flex: 0 0 50%;
|
||||
overflow: hidden;
|
||||
transition: flex-basis 0.2s;
|
||||
box-sizing: border-box;
|
||||
// Create another flexbox so the TagPanel fills the container
|
||||
display: flex;
|
||||
|
||||
&.mx_LeftPanel_explore_hidden {
|
||||
flex-basis: 0;
|
||||
// TagPanel handles its own CSS
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
font-size: $font-14px;
|
||||
margin: 4px 0 1px 9px;
|
||||
padding: 9px;
|
||||
padding-left: 42px;
|
||||
font-weight: 600;
|
||||
color: $notice-secondary-color;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
&:not(.mx_LeftPanel_hasTagPanel) {
|
||||
.mx_LeftPanel_roomListContainer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $primary-bg-color;
|
||||
// Note: The 'room list' in this context is actually everything that isn't the tag
|
||||
// panel, such as the menu options, breadcrumbs, filtering, etc
|
||||
.mx_LeftPanel_roomListContainer {
|
||||
width: calc(100% - $tagPanelWidth);
|
||||
background-color: $roomlist-bg-color;
|
||||
|
||||
// Create another flexbox (this time a column) for the room list components
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_LeftPanel_userHeader {
|
||||
/* 12px top, 12px sides, 20px bottom (using 13px bottom to account
|
||||
* for internal whitespace in the breadcrumbs)
|
||||
*/
|
||||
padding: 12px;
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
// Create another flexbox column for the rows to stack within
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&::before {
|
||||
cursor: pointer;
|
||||
mask: url('$(res)/img/explore.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center center;
|
||||
content: "";
|
||||
left: 14px;
|
||||
top: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: $notice-secondary-color;
|
||||
position: absolute;
|
||||
.mx_LeftPanel_breadcrumbsContainer {
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
margin: 12px 12px 0 12px;
|
||||
flex: 0 0 auto;
|
||||
// Create yet another flexbox, this time within the row, to ensure items stay
|
||||
// aligned correctly. This is also a row-based flexbox.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.mx_IndicatorScrollbar_leftOverflow {
|
||||
mask-image: linear-gradient(90deg, transparent, black 5%);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_rightOverflow {
|
||||
mask-image: linear-gradient(90deg, black, black 95%, transparent);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_rightOverflow.mx_IndicatorScrollbar_leftOverflow {
|
||||
mask-image: linear-gradient(90deg, transparent, black 5%, black 95%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_filterContainer {
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
// Create a flexbox to organize the inputs
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomSearch_expanded + .mx_LeftPanel_exploreButton {
|
||||
// Cheaty way to return the occupied space to the filter input
|
||||
flex-basis: 0;
|
||||
margin: 0;
|
||||
width: 0;
|
||||
|
||||
// Don't forget to hide the masked ::before icon,
|
||||
// using display:none or visibility:hidden would break accessibility
|
||||
&::before {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_exploreButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 20px;
|
||||
background-color: $roomlist-button-bg-color;
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-image: url('$(res)/img/feather-customised/compass.svg');
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_roomListWrapper {
|
||||
overflow: hidden;
|
||||
margin-top: 10px; // so we're not up against the search/filter
|
||||
|
||||
&.mx_LeftPanel_roomListWrapper_stickyBottom {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
&.mx_LeftPanel_roomListWrapper_stickyTop {
|
||||
padding-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_actualRoomListContainer {
|
||||
position: relative; // for sticky headers
|
||||
height: 100%; // ensure scrolling still works
|
||||
}
|
||||
}
|
||||
|
||||
// These styles override the defaults for the minimized (66px) layout
|
||||
&.mx_LeftPanel_minimized {
|
||||
min-width: unset;
|
||||
|
||||
// We have to forcefully set the width to override the resizer's style attribute.
|
||||
&.mx_LeftPanel_hasTagPanel {
|
||||
width: calc(68px + $tagPanelWidth) !important;
|
||||
}
|
||||
&:not(.mx_LeftPanel_hasTagPanel) {
|
||||
width: 68px !important;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_roomListContainer {
|
||||
width: 68px;
|
||||
|
||||
.mx_LeftPanel_filterContainer {
|
||||
// Organize the flexbox into a centered column layout
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.mx_LeftPanel_exploreButton {
|
||||
margin-left: 0;
|
||||
margin-top: 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,197 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||
|
||||
$tagPanelWidth: 56px; // only applies in this file, used for calculations
|
||||
|
||||
.mx_LeftPanel2 {
|
||||
background-color: $roomlist2-bg-color;
|
||||
min-width: 260px;
|
||||
max-width: 50%;
|
||||
|
||||
// Create a row-based flexbox for the TagPanel and the room list
|
||||
display: flex;
|
||||
|
||||
.mx_LeftPanel2_tagPanelContainer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: $tagPanelWidth;
|
||||
height: 100%;
|
||||
|
||||
// Create another flexbox so the TagPanel fills the container
|
||||
display: flex;
|
||||
|
||||
// TagPanel handles its own CSS
|
||||
}
|
||||
|
||||
&:not(.mx_LeftPanel2_hasTagPanel) {
|
||||
.mx_LeftPanel2_roomListContainer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: The 'room list' in this context is actually everything that isn't the tag
|
||||
// panel, such as the menu options, breadcrumbs, filtering, etc
|
||||
.mx_LeftPanel2_roomListContainer {
|
||||
width: calc(100% - $tagPanelWidth);
|
||||
background-color: $roomlist2-bg-color;
|
||||
|
||||
// Create another flexbox (this time a column) for the room list components
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_LeftPanel2_userHeader {
|
||||
/* 12px top, 12px sides, 20px bottom (using 13px bottom to account
|
||||
* for internal whitespace in the breadcrumbs)
|
||||
*/
|
||||
padding: 12px;
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
// Create another flexbox column for the rows to stack within
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_LeftPanel2_breadcrumbsContainer {
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
margin: 12px 12px 0 12px;
|
||||
flex: 0 0 auto;
|
||||
// Create yet another flexbox, this time within the row, to ensure items stay
|
||||
// aligned correctly. This is also a row-based flexbox.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.mx_IndicatorScrollbar_leftOverflow {
|
||||
mask-image: linear-gradient(90deg, transparent, black 5%);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_rightOverflow {
|
||||
mask-image: linear-gradient(90deg, black, black 95%, transparent);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_rightOverflow.mx_IndicatorScrollbar_leftOverflow {
|
||||
mask-image: linear-gradient(90deg, transparent, black 5%, black 95%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel2_filterContainer {
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
// Create a flexbox to organize the inputs
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomSearch_expanded + .mx_LeftPanel2_exploreButton {
|
||||
// Cheaty way to return the occupied space to the filter input
|
||||
flex-basis: 0;
|
||||
margin: 0;
|
||||
width: 0;
|
||||
|
||||
// Don't forget to hide the masked ::before icon,
|
||||
// using display:none or visibility:hidden would break accessibility
|
||||
&::before {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel2_exploreButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 20px;
|
||||
background-color: $roomlist2-button-bg-color;
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-image: url('$(res)/img/feather-customised/compass.svg');
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel2_roomListWrapper {
|
||||
// Create a flexbox to ensure the containing items cause appropriate overflow.
|
||||
display: flex;
|
||||
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 10px; // so we're not up against the search/filter
|
||||
|
||||
&.mx_LeftPanel2_roomListWrapper_stickyBottom {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
&.mx_LeftPanel2_roomListWrapper_stickyTop {
|
||||
padding-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel2_actualRoomListContainer {
|
||||
flex-grow: 1; // fill the available space
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
position: relative; // for sticky headers
|
||||
|
||||
// Create a flexbox to trick the layout engine
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// These styles override the defaults for the minimized (66px) layout
|
||||
&.mx_LeftPanel2_minimized {
|
||||
min-width: unset;
|
||||
|
||||
// We have to forcefully set the width to override the resizer's style attribute.
|
||||
&.mx_LeftPanel2_hasTagPanel {
|
||||
width: calc(68px + $tagPanelWidth) !important;
|
||||
}
|
||||
&:not(.mx_LeftPanel2_hasTagPanel) {
|
||||
width: 68px !important;
|
||||
}
|
||||
|
||||
.mx_LeftPanel2_roomListContainer {
|
||||
width: 68px;
|
||||
|
||||
.mx_LeftPanel2_filterContainer {
|
||||
// Organize the flexbox into a centered column layout
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.mx_LeftPanel2_exploreButton {
|
||||
margin-left: 0;
|
||||
margin-top: 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -21,8 +21,20 @@ limitations under the License.
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// move hit area 5px to the right so it doesn't overlap with the timeline scrollbar
|
||||
.mx_MainSplit > .mx_ResizeHandle.mx_ResizeHandle_horizontal {
|
||||
margin: 0 -10px 0 0;
|
||||
padding: 0 10px 0 0;
|
||||
.mx_MainSplit > .mx_RightPanel_ResizeWrapper {
|
||||
padding: 5px;
|
||||
|
||||
&:hover .mx_RightPanel_ResizeHandle {
|
||||
// Need to use important to override element style attributes
|
||||
// set by re-resizable
|
||||
top: 50% !important;
|
||||
transform: translate(0, -50%);
|
||||
|
||||
height: 64px !important; // to match width of the ones on roomlist
|
||||
width: 4px !important;
|
||||
border-radius: 4px !important;
|
||||
|
||||
background-color: $primary-fg-color;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
/* not the left panel, and not the resize handle, so the roomview/groupview/... */
|
||||
.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_LeftPanel2):not(.mx_ResizeHandle) {
|
||||
.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_ResizeHandle) {
|
||||
background-color: $primary-bg-color;
|
||||
|
||||
flex: 1 1 0;
|
||||
@ -78,3 +78,24 @@ limitations under the License.
|
||||
*/
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_MatrixChat > .mx_LeftPanel2:hover + .mx_ResizeHandle_horizontal,
|
||||
.mx_MatrixChat > .mx_ResizeHandle_horizontal:hover {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
|
||||
height: 64px; // to match width of the ones on roomlist
|
||||
width: 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
content: ' ';
|
||||
|
||||
background-color: $primary-fg-color;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,12 @@ limitations under the License.
|
||||
overflow-x: hidden;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
min-width: 264px;
|
||||
max-width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
margin: 5px;
|
||||
padding: 4px 0;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
|
||||
.mx_RoomView_MessageList {
|
||||
padding: 14px 18px; // top and bottom is 4px smaller to balance with the padding set above
|
||||
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||
.mx_RoomSearch {
|
||||
flex: 1;
|
||||
border-radius: 20px;
|
||||
background-color: $roomlist2-button-bg-color;
|
||||
background-color: $roomlist-button-bg-color;
|
||||
height: 28px;
|
||||
padding: 2px;
|
||||
|
||||
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* a word of explanation about the flex-shrink values employed here:
|
||||
there are 3 priotized categories of screen real-estate grabbing,
|
||||
each with a flex-shrink difference of 4 order of magnitude,
|
||||
so they ideally wouldn't affect each other.
|
||||
lowest category: .mx_RoomSubList
|
||||
flex-shrink: 10000000
|
||||
distribute size of items within the same category by their size
|
||||
middle category: .mx_RoomSubList.resized-sized
|
||||
flex-shrink: 1000
|
||||
applied when using the resizer, will have a max-height set to it,
|
||||
to limit the size
|
||||
highest category: .mx_RoomSubList.resized-all
|
||||
flex-shrink: 1
|
||||
small flex-shrink value (1), is only added if you can drag the resizer so far
|
||||
so in practice you can only assign this category if there is enough space.
|
||||
*/
|
||||
|
||||
.mx_RoomSubList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.mx_RoomSubList_nonEmpty .mx_AutoHideScrollbar_offset {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_labelContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin: 0 8px;
|
||||
padding: 0 8px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_labelContainer.focus-visible:focus-within {
|
||||
background-color: $roomtile-focused-bg-color;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_label {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_label > span {
|
||||
flex: 1 1 auto;
|
||||
text-transform: uppercase;
|
||||
color: $roomsublist-label-fg-color;
|
||||
font-weight: 700;
|
||||
font-size: $font-12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_badge > div {
|
||||
flex: 0 0 auto;
|
||||
border-radius: $font-16px;
|
||||
font-weight: 600;
|
||||
font-size: $font-12px;
|
||||
padding: 0 5px;
|
||||
color: $roomtile-badge-fg-color;
|
||||
background-color: $roomtile-name-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_addRoom, .mx_RoomSubList_badge {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_addRoom {
|
||||
background-color: $roomheader-addroom-bg-color;
|
||||
border-radius: 10px; // 16/2 + 2 padding
|
||||
height: 16px;
|
||||
flex: 0 0 16px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
background-color: $roomheader-addroom-fg-color;
|
||||
mask: url('$(res)/img/icons-room-add.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSubList_badgeHighlight > div {
|
||||
color: $accent-fg-color;
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_chevron {
|
||||
pointer-events: none;
|
||||
mask: url('$(res)/img/feather-customised/dropdown-arrow.svg');
|
||||
mask-repeat: no-repeat;
|
||||
transition: transform 0.2s ease-in;
|
||||
width: 10px;
|
||||
height: 6px;
|
||||
margin-left: 2px;
|
||||
background-color: $roomsublist-label-fg-color;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_chevronDown {
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
|
||||
.mx_RoomSubList_chevronUp {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.mx_RoomSubList_chevronRight {
|
||||
transform: rotateZ(-90deg);
|
||||
}
|
||||
|
||||
.mx_RoomSubList_scroll {
|
||||
/* let rooms list grab as much space as it needs (auto),
|
||||
potentially overflowing and showing a scrollbar */
|
||||
flex: 0 1 auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
.mx_RoomSubList_scroll {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_labelContainer {
|
||||
margin-right: 8px;
|
||||
margin-left: 2px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_addRoom {
|
||||
margin-left: 3px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_label > span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// overflow indicators
|
||||
.mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll {
|
||||
&.mx_IndicatorScrollbar_topOverflow::before {
|
||||
position: sticky;
|
||||
content: "";
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 8px;
|
||||
z-index: 100;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
transition: background-image 0.1s ease-in;
|
||||
background: linear-gradient(to top, $panel-gradient);
|
||||
}
|
||||
|
||||
|
||||
&.mx_IndicatorScrollbar_topOverflow {
|
||||
margin-top: -8px;
|
||||
}
|
||||
}
|
@ -157,7 +157,7 @@ limitations under the License.
|
||||
font-weight: 600;
|
||||
font-size: $font-14px;
|
||||
padding: 0 5px;
|
||||
background-color: $roomtile-name-color;
|
||||
background-color: $muted-fg-color;
|
||||
}
|
||||
|
||||
.mx_TagTile_badgeHighlight {
|
||||
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_TopLeftMenuButton {
|
||||
flex: 0 0 52px;
|
||||
border-bottom: 1px solid $panel-divider-color;
|
||||
color: $topleftmenu-color;
|
||||
background-color: $primary-bg-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
padding: 0 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mx_TopLeftMenuButton .mx_BaseAvatar {
|
||||
margin: 0 7px;
|
||||
}
|
||||
|
||||
.mx_TopLeftMenuButton_name {
|
||||
margin: 0 7px;
|
||||
font-size: $font-18px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mx_TopLeftMenuButton_chevron {
|
||||
margin: 0 7px;
|
||||
mask: url('$(res)/img/feather-customised/dropdown-arrow.svg');
|
||||
mask-repeat: no-repeat;
|
||||
width: $font-22px;
|
||||
height: 6px;
|
||||
background-color: $roomsublist-label-fg-color;
|
||||
}
|
@ -24,7 +24,7 @@ limitations under the License.
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mx_NotificationBadge, .mx_RoomTile2_badgeContainer {
|
||||
.mx_NotificationBadge, .mx_RoomTile_badgeContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_InteractiveTooltip_wrapper {
|
||||
position: fixed;
|
||||
z-index: 5000;
|
||||
}
|
||||
|
||||
.mx_InteractiveTooltip {
|
||||
border-radius: 3px;
|
||||
background-color: $interactive-tooltip-bg-color;
|
||||
color: $interactive-tooltip-fg-color;
|
||||
position: absolute;
|
||||
font-size: $font-10px;
|
||||
font-weight: 600;
|
||||
padding: 6px;
|
||||
z-index: 5001;
|
||||
}
|
||||
|
||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top {
|
||||
top: 10px; // 8px chevron + 2px spacing
|
||||
}
|
||||
|
||||
.mx_InteractiveTooltip_chevron_top {
|
||||
position: absolute;
|
||||
left: calc(50% - 8px);
|
||||
top: -8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-bottom: 8px solid $interactive-tooltip-bg-color;
|
||||
border-right: 8px solid transparent;
|
||||
}
|
||||
|
||||
// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path
|
||||
// by Sebastiano Guerriero (@guerriero_se)
|
||||
@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) {
|
||||
.mx_InteractiveTooltip_chevron_top {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
|
||||
transform: rotate(135deg);
|
||||
border-radius: 0 0 0 3px;
|
||||
top: calc(-8px / 1.414); // sqrt(2) because of rotation
|
||||
}
|
||||
}
|
||||
|
||||
.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom {
|
||||
bottom: 10px; // 8px chevron + 2px spacing
|
||||
}
|
||||
|
||||
.mx_InteractiveTooltip_chevron_bottom {
|
||||
position: absolute;
|
||||
left: calc(50% - 8px);
|
||||
bottom: -8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-top: 8px solid $interactive-tooltip-bg-color;
|
||||
border-right: 8px solid transparent;
|
||||
}
|
||||
|
||||
// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path
|
||||
// by Sebastiano Guerriero (@guerriero_se)
|
||||
@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) {
|
||||
.mx_InteractiveTooltip_chevron_bottom {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
|
||||
transform: rotate(-45deg);
|
||||
border-radius: 0 0 0 3px;
|
||||
bottom: calc(-8px / 1.414); // sqrt(2) because of rotation
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@ limitations under the License.
|
||||
|
||||
.mx_MatrixChat > .mx_ResizeHandle.mx_ResizeHandle_horizontal {
|
||||
margin: 0 -10px 0 0;
|
||||
padding: 0 10px 0 0;
|
||||
padding: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.mx_ResizeHandle > div {
|
||||
|
@ -51,21 +51,27 @@ limitations under the License.
|
||||
.mx_Tooltip {
|
||||
display: none;
|
||||
position: fixed;
|
||||
border: 1px solid $menu-border-color;
|
||||
border-radius: 8px;
|
||||
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
|
||||
background-color: $menu-bg-color;
|
||||
z-index: 6000; // Higher than context menu so tooltips can be used everywhere
|
||||
padding: 10px;
|
||||
pointer-events: none;
|
||||
line-height: $font-14px;
|
||||
font-size: $font-12px;
|
||||
font-weight: 500;
|
||||
color: $primary-fg-color;
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
margin-right: 50px;
|
||||
|
||||
background-color: $inverted-bg-color;
|
||||
color: $accent-fg-color;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
|
||||
.mx_Tooltip_chevron {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.mx_Tooltip_visible {
|
||||
animation: mx_fadein 0.2s forwards;
|
||||
}
|
||||
@ -75,16 +81,15 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_Tooltip_timeline {
|
||||
&.mx_Tooltip {
|
||||
background-color: $inverted-bg-color;
|
||||
color: $accent-fg-color;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
// These tooltips use an older style with a chevron
|
||||
.mx_Field_tooltip {
|
||||
background-color: $menu-bg-color;
|
||||
color: $primary-fg-color;
|
||||
border: 1px solid $menu-border-color;
|
||||
text-align: unset;
|
||||
|
||||
.mx_Tooltip_chevron {
|
||||
display: none;
|
||||
}
|
||||
.mx_Tooltip_chevron {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,4 +17,5 @@ limitations under the License.
|
||||
.mx_MessageTimestamp {
|
||||
color: $event-timestamp-color;
|
||||
font-size: $font-10px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||
|
||||
.mx_ReactionsRowButton {
|
||||
display: inline-flex;
|
||||
height: 20px;
|
||||
line-height: $font-21px;
|
||||
margin-right: 6px;
|
||||
padding: 0 6px;
|
||||
@ -35,11 +34,6 @@ limitations under the License.
|
||||
border-color: $reaction-row-button-selected-border-color;
|
||||
}
|
||||
|
||||
// ignore mouse events for all children, treat it as one entire hoverable entity
|
||||
* {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_ReactionsRowButton_content {
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
|
@ -15,28 +15,45 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_cryptoEvent {
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 24px minmax(0, 1fr) min-content;
|
||||
|
||||
&.mx_cryptoEvent_icon::before,
|
||||
&.mx_cryptoEvent_icon::after {
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: "";
|
||||
background-image: url("$(res)/img/e2e/normal.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||
background-color: $composer-e2e-icon-color;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
// white infill for the transparency
|
||||
&.mx_cryptoEvent_icon::before {
|
||||
background-color: #ffffff;
|
||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 90%;
|
||||
}
|
||||
|
||||
&.mx_cryptoEvent_icon_verified::after {
|
||||
background-image: url("$(res)/img/e2e/verified.svg");
|
||||
mask-image: url("$(res)/img/e2e/verified.svg");
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
&.mx_cryptoEvent_icon_warning::after {
|
||||
background-image: url("$(res)/img/e2e/warning.svg");
|
||||
mask-image: url("$(res)/img/e2e/warning.svg");
|
||||
background-color: $notice-primary-color;
|
||||
}
|
||||
|
||||
.mx_cryptoEvent_title, .mx_cryptoEvent_subtitle, .mx_cryptoEvent_state {
|
||||
|
@ -98,6 +98,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||
|
||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||
height: 114px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar {
|
||||
|
@ -42,7 +42,7 @@ limitations under the License.
|
||||
// white infill for the transparency
|
||||
.mx_E2EIcon::before {
|
||||
background-color: #ffffff;
|
||||
mask: url('$(res)/img/e2e/normal.svg');
|
||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 90%;
|
||||
|
@ -355,7 +355,7 @@ $left-gutter: 64px;
|
||||
|
||||
&::before {
|
||||
background-color: #ffffff;
|
||||
mask: url('$(res)/img/e2e/normal.svg');
|
||||
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 90%;
|
||||
|
@ -181,7 +181,8 @@ $irc-line-height: $font-18px;
|
||||
> span {
|
||||
display: flex;
|
||||
|
||||
> .mx_SenderProfile_name {
|
||||
> .mx_SenderProfile_name,
|
||||
> .mx_SenderProfile_aux {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: var(--name-width);
|
||||
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
@define-mixin mx_InviteOnlyIcon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@define-mixin mx_InviteOnlyIcon_padlock {
|
||||
background-color: $roomtile-name-color;
|
||||
mask-image: url("$(res)/img/feather-customised/lock-solid.svg");
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mx_InviteOnlyIcon_large {
|
||||
@mixin mx_InviteOnlyIcon;
|
||||
margin: 0 4px;
|
||||
|
||||
&::before {
|
||||
@mixin mx_InviteOnlyIcon_padlock;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_InviteOnlyIcon_small {
|
||||
@mixin mx_InviteOnlyIcon;
|
||||
left: -2px;
|
||||
|
||||
&::before {
|
||||
@mixin mx_InviteOnlyIcon_padlock;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
@ -41,8 +41,8 @@ limitations under the License.
|
||||
// with text-align in parent
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
color: $roomtile-badge-fg-color;
|
||||
background-color: $roomtile-name-color;
|
||||
color: $accent-fg-color;
|
||||
background-color: $muted-fg-color;
|
||||
}
|
||||
|
||||
.mx_JumpToBottomButton_highlight .mx_JumpToBottomButton_badge {
|
||||
@ -56,7 +56,7 @@ limitations under the License.
|
||||
border-radius: 19px;
|
||||
box-sizing: border-box;
|
||||
background: $primary-bg-color;
|
||||
border: 1.3px solid $roomtile-name-color;
|
||||
border: 1.3px solid $muted-fg-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -70,5 +70,5 @@ limitations under the License.
|
||||
mask: url('$(res)/img/icon-jump-to-bottom.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: 9px 14px;
|
||||
background: $roomtile-name-color;
|
||||
background: $muted-fg-color;
|
||||
}
|
||||
|
@ -70,15 +70,11 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.mx_MemberList_wrapper {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
.mx_MemberList_invite,
|
||||
.mx_RightPanel_invite {
|
||||
.mx_MemberList_invite {
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
background-color: $button-bg-color;
|
||||
@ -88,11 +84,6 @@ limitations under the License.
|
||||
justify-content: center;
|
||||
color: $button-fg-color;
|
||||
font-weight: 600;
|
||||
|
||||
.mx_RightPanel_icon {
|
||||
padding-right: 5px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MemberList_invite.mx_AccessibleButton_disabled {
|
||||
@ -107,3 +98,11 @@ limitations under the License.
|
||||
background-size: 20px;
|
||||
padding: 8px 0 8px 25px;
|
||||
}
|
||||
|
||||
.mx_MemberList_inviteCommunity span {
|
||||
background-image: url('$(res)/img/icon-invite-people.svg');
|
||||
}
|
||||
|
||||
.mx_MemberList_addRoomToCommunity span {
|
||||
background-image: url('$(res)/img/icons-room-add.svg');
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ limitations under the License.
|
||||
// ^- The count is an element floating within that.
|
||||
|
||||
&.mx_NotificationBadge_visible {
|
||||
background-color: $roomtile2-default-badge-bg-color;
|
||||
background-color: $roomtile-default-badge-bg-color;
|
||||
|
||||
// Create a flexbox to order the count a bit easier
|
||||
display: flex;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -15,98 +15,42 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_RoomBreadcrumbs {
|
||||
position: relative;
|
||||
height: 42px;
|
||||
padding: 8px;
|
||||
padding-bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
// Create a flexbox for the crumbs
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
// repeating circles as empty placeholders
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at center,
|
||||
$breadcrumb-placeholder-bg-color,
|
||||
$breadcrumb-placeholder-bg-color 15px,
|
||||
transparent 16px
|
||||
);
|
||||
background-size: 36px;
|
||||
background-position: 6px -1px;
|
||||
background-repeat: repeat-x;
|
||||
|
||||
|
||||
// Autohide the scrollbar
|
||||
overflow-x: hidden;
|
||||
&:hover {
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.mx_AutoHideScrollbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
align-items: flex-start;
|
||||
|
||||
.mx_RoomBreadcrumbs_crumb {
|
||||
margin-left: 4px;
|
||||
height: 32px;
|
||||
display: inline-block;
|
||||
transition: transform 0.3s, width 0.3s;
|
||||
position: relative;
|
||||
|
||||
.mx_RoomTile_badge {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_dmIndicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_animate {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
width: 32px;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_preAnimate {
|
||||
width: 0;
|
||||
transform: scale(0);
|
||||
// These classes come from the CSSTransition component. There's many more classes we
|
||||
// could care about, but this is all we worried about for now. The animation works by
|
||||
// first triggering the enter state with the newest breadcrumb off screen (-40px) then
|
||||
// sliding it into view.
|
||||
&.mx_RoomBreadcrumbs-enter {
|
||||
margin-left: -40px; // 32px for the avatar, 8px for the margin
|
||||
}
|
||||
&.mx_RoomBreadcrumbs-enter-active {
|
||||
margin-left: 0;
|
||||
|
||||
// Timing function is as-requested by design.
|
||||
// NOTE: The transition time MUST match the value passed to CSSTransition!
|
||||
transition: margin-left 640ms cubic-bezier(0.66, 0.02, 0.36, 1);
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_left {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
// Note: we have to manually control the gradient and stuff, but the IndicatorScrollbar
|
||||
// will deal with left/right positioning for us. Normally we'd use position:sticky on
|
||||
// a few key elements, however that doesn't work in horizontal scrolling scenarios.
|
||||
|
||||
.mx_IndicatorScrollbar_leftOverflowIndicator,
|
||||
.mx_IndicatorScrollbar_rightOverflowIndicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_IndicatorScrollbar_leftOverflowIndicator {
|
||||
background: linear-gradient(to left, $panel-gradient);
|
||||
}
|
||||
|
||||
.mx_IndicatorScrollbar_rightOverflowIndicator {
|
||||
background: linear-gradient(to right, $panel-gradient);
|
||||
}
|
||||
|
||||
&.mx_IndicatorScrollbar_leftOverflow .mx_IndicatorScrollbar_leftOverflowIndicator,
|
||||
&.mx_IndicatorScrollbar_rightOverflow .mx_IndicatorScrollbar_rightOverflowIndicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 15px;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
.mx_RoomBreadcrumbs_placeholder {
|
||||
font-weight: 600;
|
||||
font-size: $font-14px;
|
||||
line-height: 32px; // specifically to match the height this is not scaled
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_Tooltip {
|
||||
margin-left: -42px;
|
||||
margin-top: -42px;
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||
|
||||
.mx_RoomBreadcrumbs2 {
|
||||
width: 100%;
|
||||
|
||||
// Create a flexbox for the crumbs
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
|
||||
.mx_RoomBreadcrumbs2_crumb {
|
||||
margin-right: 8px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
// These classes come from the CSSTransition component. There's many more classes we
|
||||
// could care about, but this is all we worried about for now. The animation works by
|
||||
// first triggering the enter state with the newest breadcrumb off screen (-40px) then
|
||||
// sliding it into view.
|
||||
&.mx_RoomBreadcrumbs2-enter {
|
||||
margin-left: -40px; // 32px for the avatar, 8px for the margin
|
||||
}
|
||||
&.mx_RoomBreadcrumbs2-enter-active {
|
||||
margin-left: 0;
|
||||
|
||||
// Timing function is as-requested by design.
|
||||
// NOTE: The transition time MUST match the value passed to CSSTransition!
|
||||
transition: margin-left 640ms cubic-bezier(0.66, 0.02, 0.36, 1);
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs2_placeholder {
|
||||
font-weight: 600;
|
||||
font-size: $font-14px;
|
||||
line-height: 32px; // specifically to match the height this is not scaled
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs2_Tooltip {
|
||||
margin-left: -42px;
|
||||
margin-top: -42px;
|
||||
|
||||
&.mx_Tooltip {
|
||||
background-color: $inverted-bg-color;
|
||||
color: $accent-fg-color;
|
||||
border: 0;
|
||||
|
||||
.mx_Tooltip_chevron {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_RoomDropTarget_container {
|
||||
background-color: $secondary-accent-color;
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomDropTarget_container {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomDropTarget {
|
||||
font-size: $font-13px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
border: 1px dashed $accent-color;
|
||||
color: $primary-fg-color;
|
||||
background-color: $droptarget-bg-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
.mx_RoomDropTarget_label {
|
||||
position: relative;
|
||||
margin-top: 3px;
|
||||
line-height: $font-21px;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomDropTarget_avatar {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomDropTarget_label {
|
||||
display: none;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -15,56 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_RoomList.mx_RoomList2 {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mx_RoomList {
|
||||
/* take up remaining space below TopLeftMenu */
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.mx_RoomList .mx_ResizeHandle {
|
||||
// needed so the z-index takes effect
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* hide resize handles next to collapsed / empty sublists */
|
||||
.mx_RoomList .mx_RoomSubList:not(.mx_RoomSubList_nonEmpty) + .mx_ResizeHandle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomList_expandButton {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.mx_RoomList_emptySubListTip_container {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
.mx_RoomList_emptySubListTip {
|
||||
font-size: $font-13px;
|
||||
padding: 5px;
|
||||
border: 1px dashed $accent-color;
|
||||
color: $primary-fg-color;
|
||||
background-color: $droptarget-bg-color;
|
||||
border-radius: 4px;
|
||||
line-height: $font-16px;
|
||||
}
|
||||
|
||||
.mx_RoomList_emptySubListTip .mx_RoleButton {
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
.mx_RoomList_headerButtons {
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
padding-right: 7px; // width of the scrollbar, to line things up
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Rename to mx_RoomList during replacement of old component
|
||||
|
||||
.mx_RoomList2 {
|
||||
width: calc(100% - 16px); // 16px of artificial right-side margin (8px is overflowed from the sublists)
|
||||
|
||||
// Create a column-based flexbox for the sublists. That's pretty much all we have to
|
||||
// worry about in this stylesheet.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap; // let the column overflow
|
||||
}
|
@ -58,11 +58,6 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomPreviewBar_dark {
|
||||
background-color: $tagpanel-bg-color;
|
||||
color: $accent-fg-color;
|
||||
}
|
||||
|
||||
.mx_RoomPreviewBar_actions {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -14,20 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||
|
||||
.mx_RoomSublist2 {
|
||||
// The sublist is a column of rows, essentially
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_RoomSublist {
|
||||
margin-left: 8px;
|
||||
margin-bottom: 4px;
|
||||
width: 100%;
|
||||
|
||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
||||
|
||||
.mx_RoomSublist2_headerContainer {
|
||||
.mx_RoomSublist_headerContainer {
|
||||
// Create a flexbox to make alignment easy
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -43,13 +34,13 @@ limitations under the License.
|
||||
// all works by ensuring the header text has a fixed height when sticky so the
|
||||
// fixed height of the container can maintain the scroll position.
|
||||
|
||||
// The combined height must be set in the LeftPanel2 component for sticky headers
|
||||
// The combined height must be set in the LeftPanel component for sticky headers
|
||||
// to work correctly.
|
||||
padding-bottom: 8px;
|
||||
height: 24px;
|
||||
color: $roomlist2-header-color;
|
||||
color: $roomlist-header-color;
|
||||
|
||||
.mx_RoomSublist2_stickable {
|
||||
.mx_RoomSublist_stickable {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
|
||||
@ -61,26 +52,26 @@ limitations under the License.
|
||||
// to identify when a header is sticky. If we didn't have a consistent sticky class,
|
||||
// we'd have to do the "is sticky" checks again on click, as clicking the header
|
||||
// when sticky scrolls instead of collapses the list.
|
||||
&.mx_RoomSublist2_headerContainer_sticky {
|
||||
&.mx_RoomSublist_headerContainer_sticky {
|
||||
position: fixed;
|
||||
height: 32px; // to match the header container
|
||||
// width set by JS
|
||||
width: calc(100% - 22px);
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_headerContainer_stickyBottom {
|
||||
&.mx_RoomSublist_headerContainer_stickyBottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
// We don't have a top style because the top is dependent on the room list header's
|
||||
// height, and is therefore calculated in JS.
|
||||
// The class, mx_RoomSublist2_headerContainer_stickyTop, is applied though.
|
||||
// The class, mx_RoomSublist_headerContainer_stickyTop, is applied though.
|
||||
}
|
||||
|
||||
// Sticky Headers End
|
||||
// ***************************
|
||||
|
||||
.mx_RoomSublist2_badgeContainer {
|
||||
.mx_RoomSublist_badgeContainer {
|
||||
// Create another flexbox row because it's super easy to position the badge this way.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -93,14 +84,14 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.mx_RoomSublist2_headerContainer_withAux) {
|
||||
&:not(.mx_RoomSublist_headerContainer_withAux) {
|
||||
.mx_NotificationBadge {
|
||||
margin-right: 4px; // just to push it over a bit, aligning it with the other elements
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_auxButton,
|
||||
.mx_RoomSublist2_menuButton {
|
||||
.mx_RoomSublist_auxButton,
|
||||
.mx_RoomSublist_menuButton {
|
||||
margin-left: 8px; // should be the same as the notification badge
|
||||
position: relative;
|
||||
width: 24px;
|
||||
@ -122,21 +113,21 @@ limitations under the License.
|
||||
}
|
||||
|
||||
// Hide the menu button by default
|
||||
.mx_RoomSublist2_menuButton {
|
||||
.mx_RoomSublist_menuButton {
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_auxButton::before {
|
||||
.mx_RoomSublist_auxButton::before {
|
||||
mask-image: url('$(res)/img/feather-customised/plus.svg');
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_menuButton::before {
|
||||
.mx_RoomSublist_menuButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_headerText {
|
||||
.mx_RoomSublist_headerText {
|
||||
flex: 1;
|
||||
max-width: calc(100% - 16px); // 16px is the badge width
|
||||
line-height: $font-16px;
|
||||
@ -148,7 +139,7 @@ limitations under the License.
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
.mx_RoomSublist2_collapseBtn {
|
||||
.mx_RoomSublist_collapseBtn {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 12px;
|
||||
@ -169,7 +160,7 @@ limitations under the License.
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_collapseBtn_collapsed::before {
|
||||
&.mx_RoomSublist_collapseBtn_collapsed::before {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-right.svg');
|
||||
}
|
||||
}
|
||||
@ -181,12 +172,12 @@ limitations under the License.
|
||||
// when scrolled to the top above the first sublist (whose header can only
|
||||
// ever stick to top), so we force height to 0 for only that first header.
|
||||
// See also https://github.com/vector-im/riot-web/issues/14429.
|
||||
&:first-child .mx_RoomSublist2_headerContainer {
|
||||
&:first-child .mx_RoomSublist_headerContainer {
|
||||
height: 0;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_resizeBox {
|
||||
.mx_RoomSublist_resizeBox {
|
||||
position: relative;
|
||||
|
||||
// Create another flexbox column for the tiles
|
||||
@ -194,7 +185,7 @@ limitations under the License.
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.mx_RoomSublist2_tiles {
|
||||
.mx_RoomSublist_tiles {
|
||||
flex: 1 0 0;
|
||||
overflow: hidden;
|
||||
// need this to be flex otherwise the overflow hidden from above
|
||||
@ -206,18 +197,18 @@ limitations under the License.
|
||||
mask-image: linear-gradient(0deg, transparent, black 4px);
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_resizerHandles_showNButton {
|
||||
.mx_RoomSublist_resizerHandles_showNButton {
|
||||
flex: 0 0 32px;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_resizerHandles {
|
||||
.mx_RoomSublist_resizerHandles {
|
||||
flex: 0 0 4px;
|
||||
}
|
||||
|
||||
// Class name comes from the ResizableBox component
|
||||
// The hover state needs to use the whole sublist, not just the resizable box,
|
||||
// so that selector is below and one level higher.
|
||||
.mx_RoomSublist2_resizerHandle {
|
||||
.mx_RoomSublist_resizerHandle {
|
||||
cursor: ns-resize;
|
||||
border-radius: 3px;
|
||||
|
||||
@ -235,21 +226,21 @@ limitations under the License.
|
||||
right: calc(50% - 32px) !important;
|
||||
}
|
||||
|
||||
&:hover, &.mx_RoomSublist2_hasMenuOpen {
|
||||
.mx_RoomSublist2_resizerHandle {
|
||||
&:hover, &.mx_RoomSublist_hasMenuOpen {
|
||||
.mx_RoomSublist_resizerHandle {
|
||||
opacity: 0.8;
|
||||
background-color: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showNButton {
|
||||
.mx_RoomSublist_showNButton {
|
||||
cursor: pointer;
|
||||
font-size: $font-13px;
|
||||
line-height: $font-18px;
|
||||
color: $roomtile2-preview-color;
|
||||
color: $roomtile-preview-color;
|
||||
|
||||
// Update the render() function for RoomSublist2 if these change
|
||||
// Update the render() function for RoomSublist if these change
|
||||
// Update the ListLayout class for minVisibleTiles if these change.
|
||||
height: 24px;
|
||||
padding-bottom: 4px;
|
||||
@ -258,7 +249,7 @@ limitations under the License.
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomSublist2_showNButtonChevron {
|
||||
.mx_RoomSublist_showNButtonChevron {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@ -267,52 +258,52 @@ limitations under the License.
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $roomtile2-preview-color;
|
||||
background: $roomtile-preview-color;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showMoreButtonChevron {
|
||||
.mx_RoomSublist_showMoreButtonChevron {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showLessButtonChevron {
|
||||
.mx_RoomSublist_showLessButtonChevron {
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_hasMenuOpen,
|
||||
&:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:focus-within,
|
||||
&:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover {
|
||||
.mx_RoomSublist2_menuButton {
|
||||
&.mx_RoomSublist_hasMenuOpen,
|
||||
&:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:focus-within,
|
||||
&:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:hover {
|
||||
.mx_RoomSublist_menuButton {
|
||||
visibility: visible;
|
||||
width: 24px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_minimized {
|
||||
.mx_RoomSublist2_headerContainer {
|
||||
&.mx_RoomSublist_minimized {
|
||||
.mx_RoomSublist_headerContainer {
|
||||
height: auto;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.mx_RoomSublist2_badgeContainer {
|
||||
.mx_RoomSublist_badgeContainer {
|
||||
order: 0;
|
||||
align-self: flex-end;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_stickable {
|
||||
.mx_RoomSublist_stickable {
|
||||
order: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_auxButton {
|
||||
.mx_RoomSublist_auxButton {
|
||||
order: 2;
|
||||
visibility: visible;
|
||||
width: 32px !important; // !important to override hover styles
|
||||
height: 32px !important; // !important to override hover styles
|
||||
margin-left: 0 !important; // !important to override hover styles
|
||||
background-color: $roomlist2-button-bg-color;
|
||||
background-color: $roomlist-button-bg-color;
|
||||
margin-top: 8px;
|
||||
|
||||
&::before {
|
||||
@ -322,25 +313,25 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_resizeBox {
|
||||
.mx_RoomSublist_resizeBox {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showNButton {
|
||||
.mx_RoomSublist_showNButton {
|
||||
flex-direction: column;
|
||||
|
||||
.mx_RoomSublist2_showNButtonChevron {
|
||||
.mx_RoomSublist_showNButtonChevron {
|
||||
margin-right: 12px; // to center
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_menuButton {
|
||||
.mx_RoomSublist_menuButton {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_hasMenuOpen,
|
||||
& > .mx_RoomSublist2_headerContainer:hover {
|
||||
.mx_RoomSublist2_menuButton {
|
||||
&.mx_RoomSublist_hasMenuOpen,
|
||||
& > .mx_RoomSublist_headerContainer:hover {
|
||||
.mx_RoomSublist_menuButton {
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
bottom: 48px; // align to middle of name, 40px for aux button (with padding) and 8px for alignment
|
||||
@ -352,7 +343,7 @@ limitations under the License.
|
||||
|
||||
// This is the same color as the left panel background because it needs
|
||||
// to occlude the sublist title
|
||||
background-color: $roomlist2-bg-color;
|
||||
background-color: $roomlist-bg-color;
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
@ -360,8 +351,8 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomSublist2_headerContainer:not(.mx_RoomSublist2_headerContainer_withAux) {
|
||||
.mx_RoomSublist2_menuButton {
|
||||
&.mx_RoomSublist_headerContainer:not(.mx_RoomSublist_headerContainer_withAux) {
|
||||
.mx_RoomSublist_menuButton {
|
||||
bottom: 8px; // align to the middle of name, 40px less than the `bottom` above.
|
||||
}
|
||||
}
|
||||
@ -369,7 +360,7 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_contextMenu {
|
||||
.mx_RoomSublist_contextMenu {
|
||||
padding: 20px 16px;
|
||||
width: 250px;
|
||||
|
||||
@ -377,11 +368,11 @@ limitations under the License.
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-right: 16px; // additional 16px
|
||||
border: 1px solid $roomsublist2-divider-color;
|
||||
border: 1px solid $roomsublist-divider-color;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_contextMenu_title {
|
||||
.mx_RoomSublist_contextMenu_title {
|
||||
font-size: $font-15px;
|
||||
line-height: $font-20px;
|
||||
font-weight: 600;
|
||||
@ -393,6 +384,6 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_addRoomTooltip {
|
||||
.mx_RoomSublist_addRoomTooltip {
|
||||
margin-top: -3px;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -15,214 +14,222 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Note: the room tile expects to be in a flexbox column container
|
||||
.mx_RoomTile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
height: 34px;
|
||||
margin: 0;
|
||||
padding: 0 8px 0 10px;
|
||||
position: relative;
|
||||
|
||||
.mx_RoomTile_menuButton {
|
||||
display: none;
|
||||
flex: 0 0 16px;
|
||||
height: 16px;
|
||||
background-image: url('$(res)/img/icon_context.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.mx_UserOnlineDot {
|
||||
display: block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile:focus {
|
||||
filter: none !important;
|
||||
background-color: $roomtile-focused-bg-color;
|
||||
}
|
||||
|
||||
.mx_RoomTile_tooltip {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -54px;
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_nameContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
vertical-align: middle;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mx_RoomTile_labelContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mx_RoomTile_subtext {
|
||||
display: inline-block;
|
||||
font-size: $font-11px;
|
||||
padding: 0 0 0 7px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: clip;
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar_container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar {
|
||||
flex: 0;
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
width: 24px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_RoomTile_hasSubtext .mx_RoomTile_avatar {
|
||||
padding-top: 0;
|
||||
vertical-align: super;
|
||||
}
|
||||
// The tile is also a flexbox row itself
|
||||
display: flex;
|
||||
|
||||
.mx_RoomTile_dm {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -5px;
|
||||
z-index: 2;
|
||||
}
|
||||
&.mx_RoomTile_selected,
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&.mx_RoomTile_hasMenuOpen {
|
||||
background-color: $roomtile-selected-bg-color;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
// Note we match .mx_E2EIcon to make sure this matches more tightly than just
|
||||
// .mx_E2EIcon on its own
|
||||
.mx_RoomTile_e2eIcon.mx_E2EIcon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -5px;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
}
|
||||
.mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_name {
|
||||
font-size: $font-14px;
|
||||
padding: 0 4px;
|
||||
color: $roomtile-name-color;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.mx_RoomTile_nameContainer {
|
||||
flex-grow: 1;
|
||||
min-width: 0; // allow flex to shrink it
|
||||
margin-right: 8px; // spacing to buttons/badges
|
||||
|
||||
.mx_RoomTile_badge {
|
||||
flex: 0 1 content;
|
||||
border-radius: 0.8em;
|
||||
padding: 0 0.4em;
|
||||
color: $roomtile-badge-fg-color;
|
||||
font-weight: 600;
|
||||
font-size: $font-12px;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
.mx_RoomTile {
|
||||
margin: 0 6px;
|
||||
padding: 0 2px;
|
||||
position: relative;
|
||||
// Create a new column layout flexbox for the name parts
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.mx_RoomTile_name,
|
||||
.mx_RoomTile_messagePreview {
|
||||
margin: 0 2px;
|
||||
width: 100%;
|
||||
|
||||
// Ellipsize any text overflow
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mx_RoomTile_name {
|
||||
font-size: $font-14px;
|
||||
line-height: $font-18px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_name.mx_RoomTile_nameHasUnreadEvents {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mx_RoomTile_messagePreview {
|
||||
font-size: $font-13px;
|
||||
line-height: $font-18px;
|
||||
color: $roomtile-preview-color;
|
||||
}
|
||||
|
||||
.mx_RoomTile_nameWithPreview {
|
||||
margin-top: -4px; // shift the name up a bit more
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile_name {
|
||||
.mx_RoomTile_notificationsButton {
|
||||
margin-left: 4px; // spacing between buttons
|
||||
}
|
||||
|
||||
.mx_RoomTile_badgeContainer {
|
||||
height: 16px;
|
||||
// don't set width so that it takes no space when there is no badge to show
|
||||
margin: auto 0; // vertically align
|
||||
|
||||
// Create a flexbox to make aligning dot badges easier
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_NotificationBadge {
|
||||
margin-right: 2px; // centering
|
||||
}
|
||||
|
||||
.mx_NotificationBadge_dot {
|
||||
// make the smaller dot occupy the same width for centering
|
||||
margin-left: 5px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
// The context menu buttons are hidden by default
|
||||
.mx_RoomTile_menuButton,
|
||||
.mx_RoomTile_notificationsButton {
|
||||
width: 20px;
|
||||
min-width: 20px; // yay flex
|
||||
height: 20px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
position: relative;
|
||||
display: none;
|
||||
|
||||
&::before {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile_badge {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 0px;
|
||||
border-radius: 16px;
|
||||
z-index: 3;
|
||||
border: 0.18em solid $secondary-accent-color;
|
||||
}
|
||||
|
||||
.mx_RoomTile_menuButton {
|
||||
display: none; // no design for this for now
|
||||
}
|
||||
.mx_UserOnlineDot {
|
||||
display: none; // no design for this for now
|
||||
}
|
||||
}
|
||||
|
||||
// toggle menuButton and badge on menu displayed
|
||||
.mx_RoomTile_menuDisplayed,
|
||||
// or on keyboard focus of room tile
|
||||
.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:focus-within,
|
||||
// or on pointer hover
|
||||
.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover {
|
||||
.mx_RoomTile_menuButton {
|
||||
// If the room has an overriden notification setting then we always show the notifications menu button
|
||||
.mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show {
|
||||
display: block;
|
||||
}
|
||||
.mx_UserOnlineDot {
|
||||
display: none;
|
||||
|
||||
.mx_RoomTile_menuButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
||||
}
|
||||
|
||||
&:not(.mx_RoomTile_minimized) {
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&.mx_RoomTile_hasMenuOpen {
|
||||
// Hide the badge container on hover because it'll be a menu button
|
||||
.mx_RoomTile_badgeContainer {
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomTile_notificationsButton,
|
||||
.mx_RoomTile_menuButton {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomTile_minimized {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile_unreadNotify .mx_RoomTile_badge,
|
||||
.mx_RoomTile_badge.mx_RoomTile_badgeUnread {
|
||||
background-color: $roomtile-name-color;
|
||||
// We use these both in context menus and the room tiles
|
||||
.mx_RoomTile_iconBell::before {
|
||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
||||
}
|
||||
.mx_RoomTile_iconBellDot::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg');
|
||||
}
|
||||
.mx_RoomTile_iconBellCrossed::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg');
|
||||
}
|
||||
.mx_RoomTile_iconBellMentions::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg');
|
||||
}
|
||||
.mx_RoomTile_iconCheck::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile_highlight .mx_RoomTile_badge,
|
||||
.mx_RoomTile_badge.mx_RoomTile_badgeRed {
|
||||
color: $accent-fg-color;
|
||||
background-color: $warning-color;
|
||||
}
|
||||
.mx_RoomTile_contextMenu {
|
||||
.mx_RoomTile_contextMenu_redRow {
|
||||
.mx_AccessibleButton {
|
||||
color: $warning-color !important; // !important to override styles from context menu
|
||||
}
|
||||
|
||||
.mx_RoomTile_unread, .mx_RoomTile_highlight {
|
||||
.mx_RoomTile_name {
|
||||
font-weight: 600;
|
||||
color: $roomtile-selected-color;
|
||||
.mx_IconizedContextMenu_icon::before {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile_contextMenu_activeRow {
|
||||
&.mx_AccessibleButton, .mx_AccessibleButton {
|
||||
color: $accent-color !important; // !important to override styles from context menu
|
||||
}
|
||||
|
||||
.mx_IconizedContextMenu_icon::before {
|
||||
background-color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_IconizedContextMenu_icon {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconStar::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconArrowDown::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/low-priority.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconSettings::before {
|
||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile_iconSignOut::before {
|
||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile_selected {
|
||||
border-radius: 4px;
|
||||
background-color: $roomtile-selected-bg-color;
|
||||
}
|
||||
|
||||
.mx_DNDRoomTile {
|
||||
transform: none;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.mx_DNDRoomTile_dragging {
|
||||
transform: scale(1.05, 1.05);
|
||||
}
|
||||
|
||||
.mx_RoomTile_arrow {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.mx_RoomTile.mx_RoomTile_transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.mx_RoomTile.mx_RoomTile_transparent:focus {
|
||||
background-color: $roomtile-transparent-focused-color;
|
||||
}
|
||||
|
||||
.mx_GroupInviteTile .mx_RoomTile_name {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -1,241 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||
|
||||
// Note: the room tile expects to be in a flexbox column container
|
||||
.mx_RoomTile2 {
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
|
||||
// The tile is also a flexbox row itself
|
||||
display: flex;
|
||||
|
||||
&.mx_RoomTile2_selected,
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&.mx_RoomTile2_hasMenuOpen {
|
||||
background-color: $roomtile2-selected-bg-color;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar, .mx_RoomTile2_avatarContainer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_nameContainer {
|
||||
flex-grow: 1;
|
||||
min-width: 0; // allow flex to shrink it
|
||||
margin-right: 8px; // spacing to buttons/badges
|
||||
|
||||
// Create a new column layout flexbox for the name parts
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.mx_RoomTile2_name,
|
||||
.mx_RoomTile2_messagePreview {
|
||||
margin: 0 2px;
|
||||
width: 100%;
|
||||
|
||||
// Ellipsize any text overflow
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_name {
|
||||
font-size: $font-14px;
|
||||
line-height: $font-18px;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_name.mx_RoomTile2_nameHasUnreadEvents {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_messagePreview {
|
||||
font-size: $font-13px;
|
||||
line-height: $font-18px;
|
||||
color: $roomtile2-preview-color;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_nameWithPreview {
|
||||
margin-top: -4px; // shift the name up a bit more
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile2_notificationsButton {
|
||||
margin-left: 4px; // spacing between buttons
|
||||
}
|
||||
|
||||
.mx_RoomTile2_badgeContainer {
|
||||
height: 16px;
|
||||
// don't set width so that it takes no space when there is no badge to show
|
||||
margin: auto 0; // vertically align
|
||||
|
||||
// Create a flexbox to make aligning dot badges easier
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_NotificationBadge {
|
||||
margin-right: 2px; // centering
|
||||
}
|
||||
|
||||
.mx_NotificationBadge_dot {
|
||||
// make the smaller dot occupy the same width for centering
|
||||
margin-left: 5px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
// The context menu buttons are hidden by default
|
||||
.mx_RoomTile2_menuButton,
|
||||
.mx_RoomTile2_notificationsButton {
|
||||
width: 20px;
|
||||
min-width: 20px; // yay flex
|
||||
height: 20px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
position: relative;
|
||||
display: none;
|
||||
|
||||
&::before {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
// If the room has an overriden notification setting then we always show the notifications menu button
|
||||
.mx_RoomTile2_notificationsButton.mx_RoomTile2_notificationsButton_show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_menuButton::before {
|
||||
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
||||
}
|
||||
|
||||
&:not(.mx_RoomTile2_minimized) {
|
||||
&:hover,
|
||||
&:focus-within,
|
||||
&.mx_RoomTile2_hasMenuOpen {
|
||||
// Hide the badge container on hover because it'll be a menu button
|
||||
.mx_RoomTile2_badgeContainer {
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomTile2_notificationsButton,
|
||||
.mx_RoomTile2_menuButton {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomTile2_minimized {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.mx_DecoratedRoomAvatar, .mx_RoomTile2_avatarContainer {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use these both in context menus and the room tiles
|
||||
.mx_RoomTile2_iconBell::before {
|
||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
||||
}
|
||||
.mx_RoomTile2_iconBellDot::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg');
|
||||
}
|
||||
.mx_RoomTile2_iconBellCrossed::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg');
|
||||
}
|
||||
.mx_RoomTile2_iconBellMentions::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg');
|
||||
}
|
||||
.mx_RoomTile2_iconCheck::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile2_contextMenu {
|
||||
.mx_RoomTile2_contextMenu_redRow {
|
||||
.mx_AccessibleButton {
|
||||
color: $warning-color !important; // !important to override styles from context menu
|
||||
}
|
||||
|
||||
.mx_IconizedContextMenu_icon::before {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile2_contextMenu_activeRow {
|
||||
&.mx_AccessibleButton, .mx_AccessibleButton {
|
||||
color: $accent-color !important; // !important to override styles from context menu
|
||||
}
|
||||
|
||||
.mx_IconizedContextMenu_icon::before {
|
||||
background-color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_IconizedContextMenu_icon {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomTile2_iconStar::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile2_iconFavorite::before {
|
||||
mask-image: url('$(res)/img/feather-customised/favourites.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile2_iconArrowDown::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/low-priority.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile2_iconSettings::before {
|
||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTile2_iconSignOut::before {
|
||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
background-color: $roomlist2-bg-color; // to match the room list itself
|
||||
background-color: $roomlist-bg-color; // to match the room list itself
|
||||
}
|
||||
|
||||
.mx_RoomTileIcon_globe::before {
|
||||
|
@ -42,7 +42,7 @@ limitations under the License.
|
||||
border-radius: 19px;
|
||||
box-sizing: border-box;
|
||||
background: $primary-bg-color;
|
||||
border: 1.3px solid $roomtile-name-color;
|
||||
border: 1.3px solid $muted-fg-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ limitations under the License.
|
||||
mask-image: url('$(res)/img/icon-jump-to-first-unread.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: 9px 13px;
|
||||
background: $roomtile-name-color;
|
||||
background: $muted-fg-color;
|
||||
}
|
||||
|
||||
.mx_TopUnreadMessagesBar_markAsRead {
|
||||
@ -62,7 +62,7 @@ limitations under the License.
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: $primary-bg-color;
|
||||
border: 1.3px solid $roomtile-name-color;
|
||||
border: 1.3px solid $muted-fg-color;
|
||||
border-radius: 10px;
|
||||
margin: 5px auto;
|
||||
}
|
||||
@ -76,5 +76,5 @@ limitations under the License.
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 10px;
|
||||
mask-position: 4px 4px;
|
||||
background: $roomtile-name-color;
|
||||
background: $muted-fg-color;
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_UserOnlineDot {
|
||||
border-radius: 50%;
|
||||
background-color: $accent-color;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
display: inline-block;
|
||||
}
|
@ -36,12 +36,12 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox2 {
|
||||
.mx_IncomingCallBox {
|
||||
min-width: 250px;
|
||||
background-color: $primary-bg-color;
|
||||
padding: 8px;
|
||||
|
||||
.mx_IncomingCallBox2_CallerInfo {
|
||||
.mx_IncomingCallBox_CallerInfo {
|
||||
display: flex;
|
||||
direction: row;
|
||||
|
||||
@ -68,12 +68,12 @@ limitations under the License.
|
||||
}
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox2_buttons {
|
||||
.mx_IncomingCallBox_buttons {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
> .mx_IncomingCallBox2_spacer {
|
||||
> .mx_IncomingCallBox_spacer {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -18,8 +19,76 @@ limitations under the License.
|
||||
background-color: $accent-color;
|
||||
color: $accent-fg-color;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
font-weight: bold;
|
||||
font-size: $font-13px;
|
||||
|
||||
border-radius: 8px;
|
||||
min-width: 200px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// Hacky vertical align
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
> div > p,
|
||||
> div > h1 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: $font-13px;
|
||||
line-height: $font-15px;
|
||||
}
|
||||
|
||||
> div > p {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> * {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CallView_hangup {
|
||||
position: absolute;
|
||||
|
||||
right: 8px;
|
||||
bottom: 10px;
|
||||
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
|
||||
border-radius: 35px;
|
||||
|
||||
background-color: $notice-primary-color;
|
||||
|
||||
z-index: 101;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
top: 6.5px;
|
||||
left: 7.5px;
|
||||
|
||||
mask: url('$(res)/img/hangup.svg');
|
||||
mask-size: contain;
|
||||
background-size: contain;
|
||||
|
||||
background-color: $primary-fg-color;
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
|
||||
|
||||
.mx_CallView2_voice {
|
||||
background-color: $accent-color;
|
||||
color: $accent-fg-color;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
font-weight: bold;
|
||||
|
||||
border-radius: 8px;
|
||||
min-width: 200px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// Hacky vertical align
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
> div > p,
|
||||
> div > h1 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: $font-13px;
|
||||
line-height: $font-15px;
|
||||
}
|
||||
|
||||
> div > p {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> * {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CallView2_hangup {
|
||||
position: absolute;
|
||||
|
||||
right: 8px;
|
||||
bottom: 10px;
|
||||
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
|
||||
border-radius: 35px;
|
||||
|
||||
background-color: $notice-primary-color;
|
||||
|
||||
z-index: 101;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
top: 6.5px;
|
||||
left: 7.5px;
|
||||
|
||||
mask: url('$(res)/img/hangup.svg');
|
||||
mask-size: contain;
|
||||
background-size: contain;
|
||||
|
||||
background-color: $primary-fg-color;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_IncomingCallBox {
|
||||
text-align: center;
|
||||
border: 1px solid #a4a4a4;
|
||||
border-radius: 8px;
|
||||
background-color: $primary-bg-color;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding: 6px;
|
||||
margin-top: -3px;
|
||||
margin-left: -20px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_chevron {
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
left: -21px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_title {
|
||||
padding: 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_buttons_cell {
|
||||
vertical-align: middle;
|
||||
padding: 6px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_buttons_decline,
|
||||
.mx_IncomingCallBox_buttons_accept {
|
||||
vertical-align: middle;
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
line-height: $font-36px;
|
||||
border-radius: 36px;
|
||||
color: $accent-fg-color;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_buttons_decline {
|
||||
background-color: $voip-decline-color;
|
||||
}
|
||||
|
||||
.mx_IncomingCallBox_buttons_accept {
|
||||
background-color: $voip-accept-color;
|
||||
}
|
@ -108,32 +108,20 @@ $composer-e2e-icon-color: $header-panel-text-primary-color;
|
||||
|
||||
// ********************
|
||||
|
||||
// V2 Room List
|
||||
// TODO: Remove the 2 from all of these when the new list takes over
|
||||
|
||||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-bg-color: rgba(33, 38, 44, 0.90);
|
||||
$roomlist-header-color: #8E99A4;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$roomlist2-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist2-bg-color: rgba(33, 38, 44, 0.90);
|
||||
$roomlist2-header-color: #8E99A4;
|
||||
$roomsublist2-divider-color: $primary-fg-color;
|
||||
|
||||
$roomtile2-preview-color: #A9B2BC;
|
||||
$roomtile2-default-badge-bg-color: #61708b;
|
||||
$roomtile2-selected-bg-color: rgba(141, 151, 165, 0.2);
|
||||
$roomtile-preview-color: #A9B2BC;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2);
|
||||
|
||||
// ********************
|
||||
|
||||
$notice-secondary-color: $roomlist2-header-color;
|
||||
|
||||
$roomtile-name-color: $header-panel-text-primary-color;
|
||||
$roomtile-selected-color: $text-primary-color;
|
||||
$roomtile-notified-color: $text-primary-color;
|
||||
$roomtile-selected-bg-color: $room-highlight-color;
|
||||
$roomtile-focused-bg-color: $room-highlight-color;
|
||||
|
||||
$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1);
|
||||
$notice-secondary-color: $roomlist-header-color;
|
||||
|
||||
$panel-divider-color: transparent;
|
||||
|
||||
|
@ -107,30 +107,19 @@ $composer-e2e-icon-color: $header-panel-text-primary-color;
|
||||
|
||||
// ********************
|
||||
|
||||
// V2 Room List
|
||||
// TODO: Remove the 2 from all of these when the new list takes over
|
||||
|
||||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist2-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist2-bg-color: $header-panel-bg-color;
|
||||
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-bg-color: $header-panel-bg-color;
|
||||
|
||||
$roomsublist2-divider-color: $primary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$roomtile2-preview-color: #9e9e9e;
|
||||
$roomtile2-default-badge-bg-color: #61708b;
|
||||
$roomtile2-selected-bg-color: #1A1D23;
|
||||
$roomtile-preview-color: #9e9e9e;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: #1A1D23;
|
||||
|
||||
// ********************
|
||||
|
||||
$roomtile-name-color: $header-panel-text-primary-color;
|
||||
$roomtile-selected-color: $text-primary-color;
|
||||
$roomtile-notified-color: $text-primary-color;
|
||||
$roomtile-selected-bg-color: $room-highlight-color;
|
||||
$roomtile-focused-bg-color: $room-highlight-color;
|
||||
|
||||
$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
$panel-divider-color: $header-panel-border-color;
|
||||
|
||||
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
||||
|
@ -174,19 +174,16 @@ $header-divider-color: #91a1c0;
|
||||
|
||||
// ********************
|
||||
|
||||
// V2 Room List
|
||||
// TODO: Remove the 2 from all of these when the new list takes over
|
||||
|
||||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist2-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist2-bg-color: $header-panel-bg-color;
|
||||
$roomlist2-header-color: $primary-fg-color;
|
||||
$roomsublist2-divider-color: $primary-fg-color;
|
||||
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-bg-color: $header-panel-bg-color;
|
||||
$roomlist-header-color: $primary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$roomtile2-preview-color: #9e9e9e;
|
||||
$roomtile2-default-badge-bg-color: #61708b;
|
||||
$roomtile2-selected-bg-color: #fff;
|
||||
$roomtile-preview-color: #9e9e9e;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: #fff;
|
||||
|
||||
$presence-online: $accent-color;
|
||||
$presence-away: #d9b072;
|
||||
@ -194,13 +191,6 @@ $presence-offline: #e3e8f0;
|
||||
|
||||
// ********************
|
||||
|
||||
$roomtile-name-color: #61708b;
|
||||
$roomtile-badge-fg-color: $accent-fg-color;
|
||||
$roomtile-selected-color: #212121;
|
||||
$roomtile-notified-color: #212121;
|
||||
$roomtile-selected-bg-color: #fff;
|
||||
$roomtile-focused-bg-color: #fff;
|
||||
|
||||
$username-variant1-color: #368bd6;
|
||||
$username-variant2-color: #ac3ba8;
|
||||
$username-variant3-color: #03b381;
|
||||
@ -210,13 +200,6 @@ $username-variant6-color: #2dc2c5;
|
||||
$username-variant7-color: #5c56f5;
|
||||
$username-variant8-color: #74d12c;
|
||||
|
||||
$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
$roomsublist-background: $secondary-accent-color;
|
||||
$roomsublist-label-fg-color: $roomtile-name-color;
|
||||
$roomsublist-label-bg-color: $tertiary-accent-color;
|
||||
$roomsublist-chevron-color: $accent-color;
|
||||
|
||||
$panel-divider-color: #dee1f3;
|
||||
|
||||
// ********************
|
||||
@ -292,7 +275,7 @@ $progressbar-color: #000;
|
||||
|
||||
$room-warning-bg-color: $yellow-background;
|
||||
|
||||
$memberstatus-placeholder-color: $roomtile-name-color;
|
||||
$memberstatus-placeholder-color: $muted-fg-color;
|
||||
|
||||
$authpage-bg-color: #2e3649;
|
||||
$authpage-modal-bg-color: rgba(255, 255, 255, 0.59);
|
||||
|
@ -25,7 +25,6 @@ $button-link-fg-color: var(--accent-color);
|
||||
$button-primary-bg-color: var(--accent-color);
|
||||
$input-valid-border-color: var(--accent-color);
|
||||
$reaction-row-button-selected-border-color: var(--accent-color);
|
||||
$roomsublist-chevron-color: var(--accent-color);
|
||||
$tab-label-active-bg-color: var(--accent-color);
|
||||
$togglesw-on-color: var(--accent-color);
|
||||
$username-variant3-color: var(--accent-color);
|
||||
@ -40,7 +39,6 @@ $menu-bg-color: var(--timeline-background-color);
|
||||
$avatar-bg-color: var(--timeline-background-color);
|
||||
$message-action-bar-bg-color: var(--timeline-background-color);
|
||||
$primary-bg-color: var(--timeline-background-color);
|
||||
$roomtile-focused-bg-color: var(--timeline-background-color);
|
||||
$togglesw-ball-color: var(--timeline-background-color);
|
||||
$droptarget-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .5
|
||||
$authpage-modal-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .59
|
||||
@ -48,14 +46,13 @@ $roomheader-bg-color: var(--timeline-background-color);
|
||||
//
|
||||
// --roomlist-highlights-color
|
||||
$roomtile-selected-bg-color: var(--roomlist-highlights-color);
|
||||
$roomtile2-selected-bg-color: var(--roomlist-highlights-color);
|
||||
//
|
||||
// --sidebar-color
|
||||
$interactive-tooltip-bg-color: var(--sidebar-color);
|
||||
$tagpanel-bg-color: var(--sidebar-color);
|
||||
$tooltip-timeline-bg-color: var(--sidebar-color);
|
||||
$dialog-backdrop-color: var(--sidebar-color-50pct);
|
||||
$roomlist2-button-bg-color: var(--sidebar-color-15pct);
|
||||
$roomlist-button-bg-color: var(--sidebar-color-15pct);
|
||||
//
|
||||
// --roomlist-background-color
|
||||
$header-panel-bg-color: var(--roomlist-background-color);
|
||||
@ -65,12 +62,10 @@ $panel-gradient: var(--roomlist-background-color-0pct), var(--roomlist-backgroun
|
||||
$dark-panel-bg-color: var(--roomlist-background-color);
|
||||
$input-lighter-bg-color: var(--roomlist-background-color);
|
||||
$plinth-bg-color: var(--roomlist-background-color);
|
||||
$roomsublist-background: var(--roomlist-background-color);
|
||||
$secondary-accent-color: var(--roomlist-background-color);
|
||||
$selected-color: var(--roomlist-background-color);
|
||||
$widget-menu-bar-bg-color: var(--roomlist-background-color);
|
||||
$roomtile-badge-fg-color: var(--roomlist-background-color);
|
||||
$roomlist2-bg-color: var(--roomlist-background-color);
|
||||
$roomlist-bg-color: var(--roomlist-background-color);
|
||||
//
|
||||
// --timeline-text-color
|
||||
$message-action-bar-fg-color: var(--timeline-text-color);
|
||||
@ -87,23 +82,17 @@ $tab-label-fg-color: var(--timeline-text-color);
|
||||
// was #4e5054
|
||||
$authpage-lang-color: var(--timeline-text-color);
|
||||
$roomheader-color: var(--timeline-text-color);
|
||||
//
|
||||
// --roomlist-text-color
|
||||
$roomtile-notified-color: var(--roomlist-text-color);
|
||||
$roomtile-selected-color: var(--roomlist-text-color);
|
||||
// --roomlist-text-secondary-color
|
||||
$roomsublist-label-fg-color: var(--roomlist-text-secondary-color);
|
||||
$roomtile-name-color: var(--roomlist-text-secondary-color);
|
||||
$roomtile2-preview-color: var(--roomlist-text-secondary-color);
|
||||
$roomlist2-header-color: var(--roomlist-text-secondary-color);
|
||||
$roomtile2-default-badge-bg-color: var(--roomlist-text-secondary-color);
|
||||
$roomtile-preview-color: var(--roomlist-text-secondary-color);
|
||||
$roomlist-header-color: var(--roomlist-text-secondary-color);
|
||||
$roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color);
|
||||
|
||||
//
|
||||
// --roomlist-separator-color
|
||||
$input-darker-bg-color: var(--roomlist-separator-color);
|
||||
$panel-divider-color: var(--roomlist-separator-color);// originally #dee1f3, but close enough
|
||||
$primary-hairline-color: var(--roomlist-separator-color);// originally #e5e5e5, but close enough
|
||||
$roomsublist2-divider-color: var(--roomlist-separator-color);
|
||||
$roomsublist-divider-color: var(--roomlist-separator-color);
|
||||
//
|
||||
// --timeline-text-secondary-color
|
||||
$authpage-secondary-color: var(--timeline-text-secondary-color);
|
||||
|
@ -1,25 +1,20 @@
|
||||
/*
|
||||
* Nunito.
|
||||
* Includes extended Latin and Vietnamese character sets
|
||||
* Current URLs are taken from
|
||||
* https://github.com/alexeiva/NunitoFont/releases/tag/v3.500
|
||||
* ...in order to include cyrillic.
|
||||
*
|
||||
* Previously, they were
|
||||
* https://fonts.googleapis.com/css?family=Nunito:400,400i,600,600i,700,700i&subset=latin-ext,vietnamese
|
||||
*
|
||||
* We explicitly do not include Nunito's italic variants, as they are not italic enough
|
||||
* and it's better to rely on the browser's built-in obliquing behaviour.
|
||||
*/
|
||||
|
||||
/* the 'src' links are relative to the bundle.css, which is in a subdirectory.
|
||||
*/
|
||||
|
||||
/* Inter unexpectedly contains various codepoints which collide with emoji, even
|
||||
when variation-16 is applied to request the emoji variant. From eyeballing
|
||||
the emoji picker, these are: 20e3, 23cf, 24c2, 25a0-25c1, 2665, 2764, 2b06, 2b1c.
|
||||
Therefore we define a unicode-range to load which excludes the glyphs
|
||||
(to avoid having to maintain a fork of Inter). */
|
||||
|
||||
$inter-unicode-range: U+0000-20e2,U+20e4-23ce,U+23d0-24c1,U+24c3-259f,U+25c2-2664,U+2666-2763,U+2765-2b05,U+2b07-2b1b,U+2b1d-10FFFF;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-Regular.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-Regular.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -28,6 +23,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-Italic.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-Italic.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -37,6 +33,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-Medium.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-Medium.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -45,6 +42,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-MediumItalic.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-MediumItalic.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -54,6 +52,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-SemiBold.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-SemiBold.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -62,6 +61,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -71,6 +71,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-Bold.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-Bold.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -79,6 +80,7 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range: $inter-unicode-range;
|
||||
src: url("$(res)/fonts/Inter/Inter-BoldItalic.woff2?v=3.13") format("woff2"),
|
||||
url("$(res)/fonts/Inter/Inter-BoldItalic.woff?v=3.13") format("woff");
|
||||
}
|
||||
@ -118,17 +120,3 @@
|
||||
src: local('Inconsolata Bold'), local('Inconsolata-Bold'), url('$(res)/fonts/Inconsolata/QldXNThLqRwH-OJ1UHjlKGHiw71p5_zaDpwm.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* a COLR/CPAL version of Twemoji used for consistent cross-browser emoji
|
||||
* taken from https://github.com/mozilla/twemoji-colr
|
||||
* using the fix from https://github.com/mozilla/twemoji-colr/issues/50 to
|
||||
* work on macOS
|
||||
*/
|
||||
/*
|
||||
// except we now load it dynamically via FontManager to handle browsers
|
||||
// which can't render COLR/CPAL still
|
||||
@font-face {
|
||||
font-family: "Twemoji Mozilla";
|
||||
src: url('$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2') format('woff2');
|
||||
}
|
||||
*/
|
||||
|
@ -19,8 +19,8 @@ $accent-bg-color: rgba(3, 179, 129, 0.16);
|
||||
$notice-primary-color: #ff4b55;
|
||||
$notice-primary-bg-color: rgba(255, 75, 85, 0.16);
|
||||
$primary-fg-color: #2e2f32;
|
||||
$roomlist2-header-color: $primary-fg-color;
|
||||
$notice-secondary-color: $roomlist2-header-color;
|
||||
$roomlist-header-color: $primary-fg-color;
|
||||
$notice-secondary-color: $roomlist-header-color;
|
||||
$header-panel-bg-color: #f3f8fd;
|
||||
|
||||
// typical text (dark-on-white in light skin)
|
||||
@ -174,32 +174,22 @@ $header-divider-color: #91A1C0;
|
||||
|
||||
// ********************
|
||||
|
||||
// V2 Room List
|
||||
// TODO: Remove the 2 from all of these when the new list takes over
|
||||
|
||||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist2-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist2-bg-color: rgba(245, 245, 245, 0.90);
|
||||
$roomsublist2-divider-color: $primary-fg-color;
|
||||
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-bg-color: rgba(245, 245, 245, 0.90);
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$roomtile2-preview-color: #737D8C;
|
||||
$roomtile2-default-badge-bg-color: #61708b;
|
||||
$roomtile2-selected-bg-color: #FFF;
|
||||
$roomtile-preview-color: #737D8C;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: #FFF;
|
||||
|
||||
$presence-online: $accent-color;
|
||||
$presence-away: orange; // TODO: Get color
|
||||
$presence-away: #d9b072;
|
||||
$presence-offline: #E3E8F0;
|
||||
|
||||
// ********************
|
||||
|
||||
$roomtile-name-color: #61708b;
|
||||
$roomtile-badge-fg-color: $accent-fg-color;
|
||||
$roomtile-selected-color: #212121;
|
||||
$roomtile-notified-color: #212121;
|
||||
$roomtile-selected-bg-color: #fff;
|
||||
$roomtile-focused-bg-color: #fff;
|
||||
|
||||
$username-variant1-color: #368bd6;
|
||||
$username-variant2-color: #ac3ba8;
|
||||
$username-variant3-color: #0DBD8B;
|
||||
@ -209,13 +199,6 @@ $username-variant6-color: #2dc2c5;
|
||||
$username-variant7-color: #5c56f5;
|
||||
$username-variant8-color: #74d12c;
|
||||
|
||||
$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
$roomsublist-background: $secondary-accent-color;
|
||||
$roomsublist-label-fg-color: $roomtile-name-color;
|
||||
$roomsublist-label-bg-color: $tertiary-accent-color;
|
||||
$roomsublist-chevron-color: $accent-color;
|
||||
|
||||
$panel-divider-color: transparent;
|
||||
|
||||
// ********************
|
||||
@ -291,7 +274,7 @@ $progressbar-color: #000;
|
||||
|
||||
$room-warning-bg-color: $yellow-background;
|
||||
|
||||
$memberstatus-placeholder-color: $roomtile-name-color;
|
||||
$memberstatus-placeholder-color: $muted-fg-color;
|
||||
|
||||
$authpage-bg-color: #2e3649;
|
||||
$authpage-modal-bg-color: rgba(245, 245, 245, 0.90);
|
||||
|
@ -5,7 +5,7 @@
|
||||
// it can be blurred by the tag panel and room list
|
||||
|
||||
@supports (backdrop-filter: none) {
|
||||
.mx_LeftPanel2 {
|
||||
.mx_LeftPanel {
|
||||
background-image: var(--avatar-url);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
@ -16,12 +16,12 @@
|
||||
backdrop-filter: blur($tagpanel-background-blur-amount);
|
||||
}
|
||||
|
||||
.mx_LeftPanel2 .mx_LeftPanel2_roomListContainer {
|
||||
.mx_LeftPanel .mx_LeftPanel_roomListContainer {
|
||||
backdrop-filter: blur($roomlist-background-blur-amount);
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSublist2_showNButton {
|
||||
.mx_RoomSublist_showNButton {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
|
23
src/@types/global.d.ts
vendored
23
src/@types/global.d.ts
vendored
@ -20,9 +20,11 @@ import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import DeviceListener from "../DeviceListener";
|
||||
import RebrandListener from "../RebrandListener";
|
||||
import { RoomListStore2 } from "../stores/room-list/RoomListStore2";
|
||||
import { RoomListStoreClass } from "../stores/room-list/RoomListStore";
|
||||
import { PlatformPeg } from "../PlatformPeg";
|
||||
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
|
||||
import {IntegrationManagers} from "../integrations/IntegrationManagers";
|
||||
import {ModalManager} from "../Modal";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -32,21 +34,20 @@ declare global {
|
||||
init: () => Promise<void>;
|
||||
};
|
||||
|
||||
mx_ContentMessages: ContentMessages;
|
||||
mx_ToastStore: ToastStore;
|
||||
mx_DeviceListener: DeviceListener;
|
||||
mx_RebrandListener: RebrandListener;
|
||||
mx_RoomListStore2: RoomListStore2;
|
||||
mx_RoomListLayoutStore: RoomListLayoutStore;
|
||||
mxContentMessages: ContentMessages;
|
||||
mxToastStore: ToastStore;
|
||||
mxDeviceListener: DeviceListener;
|
||||
mxRebrandListener: RebrandListener;
|
||||
mxRoomListStore: RoomListStoreClass;
|
||||
mxRoomListLayoutStore: RoomListLayoutStore;
|
||||
mxPlatformPeg: PlatformPeg;
|
||||
|
||||
// TODO: Remove flag before launch: https://github.com/vector-im/riot-web/issues/14231
|
||||
mx_LoudRoomListLogging: boolean;
|
||||
mxIntegrationManagers: typeof IntegrationManagers;
|
||||
singletonModalManager: ModalManager;
|
||||
}
|
||||
|
||||
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
||||
interface ObjectConstructor {
|
||||
fromEntries?(xs: [string|number|symbol, any][]): object
|
||||
fromEntries?(xs: [string|number|symbol, any][]): object;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
|
@ -386,7 +386,7 @@ export default class ContentMessages {
|
||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||
if (isQuoting) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const {finished} = Modal.createTrackedDialog('Upload Reply Warning', '', QuestionDialog, {
|
||||
const {finished} = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
|
||||
title: _t('Replying With Files'),
|
||||
description: (
|
||||
<div>{_t(
|
||||
@ -397,7 +397,7 @@ export default class ContentMessages {
|
||||
hasCancelButton: true,
|
||||
button: _t("Continue"),
|
||||
});
|
||||
const [shouldUpload]: [boolean] = await finished;
|
||||
const [shouldUpload] = await finished;
|
||||
if (!shouldUpload) return;
|
||||
}
|
||||
|
||||
@ -420,12 +420,12 @@ export default class ContentMessages {
|
||||
|
||||
if (tooBigFiles.length > 0) {
|
||||
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
||||
const {finished} = Modal.createTrackedDialog('Upload Failure', '', UploadFailureDialog, {
|
||||
const {finished} = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
|
||||
badFiles: tooBigFiles,
|
||||
totalFiles: files.length,
|
||||
contentMessages: this,
|
||||
});
|
||||
const [shouldContinue]: [boolean] = await finished;
|
||||
const [shouldContinue] = await finished;
|
||||
if (!shouldContinue) return;
|
||||
}
|
||||
|
||||
@ -437,12 +437,12 @@ export default class ContentMessages {
|
||||
for (let i = 0; i < okFiles.length; ++i) {
|
||||
const file = okFiles[i];
|
||||
if (!uploadAll) {
|
||||
const {finished} = Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
|
||||
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, {
|
||||
file,
|
||||
currentIndex: i,
|
||||
totalFiles: okFiles.length,
|
||||
});
|
||||
const [shouldContinue, shouldUploadAll]: [boolean, boolean] = await finished;
|
||||
const [shouldContinue, shouldUploadAll] = await finished;
|
||||
if (!shouldContinue) break;
|
||||
if (shouldUploadAll) {
|
||||
uploadAll = true;
|
||||
@ -621,9 +621,9 @@ export default class ContentMessages {
|
||||
}
|
||||
|
||||
static sharedInstance() {
|
||||
if (window.mx_ContentMessages === undefined) {
|
||||
window.mx_ContentMessages = new ContentMessages();
|
||||
if (window.mxContentMessages === undefined) {
|
||||
window.mxContentMessages = new ContentMessages();
|
||||
}
|
||||
return window.mx_ContentMessages;
|
||||
return window.mxContentMessages;
|
||||
}
|
||||
}
|
||||
|
@ -40,25 +40,16 @@ export class AccessCancelledError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmToDismiss(name) {
|
||||
let description;
|
||||
if (name === "m.cross_signing.user_signing") {
|
||||
description = _t("If you cancel now, you won't complete verifying the other user.");
|
||||
} else if (name === "m.cross_signing.self_signing") {
|
||||
description = _t("If you cancel now, you won't complete verifying your other session.");
|
||||
} else {
|
||||
description = _t("If you cancel now, you won't complete your operation.");
|
||||
}
|
||||
|
||||
async function confirmToDismiss() {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const [sure] = await Modal.createDialog(QuestionDialog, {
|
||||
title: _t("Cancel entering passphrase?"),
|
||||
description,
|
||||
danger: true,
|
||||
cancelButton: _t("Enter passphrase"),
|
||||
button: _t("Cancel"),
|
||||
description: _t("Are you sure you want to cancel entering passphrase?"),
|
||||
danger: false,
|
||||
button: _t("Go Back"),
|
||||
cancelButton: _t("Cancel"),
|
||||
}).finished;
|
||||
return sure;
|
||||
return !sure;
|
||||
}
|
||||
|
||||
async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||
@ -102,7 +93,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||
/* options= */ {
|
||||
onBeforeClose: async (reason) => {
|
||||
if (reason === "backgroundClick") {
|
||||
return confirmToDismiss(ssssItemName);
|
||||
return confirmToDismiss();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
@ -17,16 +17,16 @@ limitations under the License.
|
||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||
import {
|
||||
hideToast as hideBulkUnverifiedSessionsToast,
|
||||
showToast as showBulkUnverifiedSessionsToast
|
||||
showToast as showBulkUnverifiedSessionsToast,
|
||||
} from "./toasts/BulkUnverifiedSessionsToast";
|
||||
import {
|
||||
hideToast as hideSetupEncryptionToast,
|
||||
Kind as SetupKind,
|
||||
showToast as showSetupEncryptionToast
|
||||
showToast as showSetupEncryptionToast,
|
||||
} from "./toasts/SetupEncryptionToast";
|
||||
import {
|
||||
hideToast as hideUnverifiedSessionsToast,
|
||||
showToast as showUnverifiedSessionsToast
|
||||
showToast as showUnverifiedSessionsToast,
|
||||
} from "./toasts/UnverifiedSessionToast";
|
||||
import {privateShouldBeEncrypted} from "./createRoom";
|
||||
|
||||
@ -48,8 +48,8 @@ export default class DeviceListener {
|
||||
private displayingToastsForDeviceIds = new Set<string>();
|
||||
|
||||
static sharedInstance() {
|
||||
if (!window.mx_DeviceListener) window.mx_DeviceListener = new DeviceListener();
|
||||
return window.mx_DeviceListener;
|
||||
if (!window.mxDeviceListener) window.mxDeviceListener = new DeviceListener();
|
||||
return window.mxDeviceListener;
|
||||
}
|
||||
|
||||
start() {
|
||||
|
@ -184,7 +184,7 @@ const transformTags: sanitizeHtml.IOptions["transformTags"] = { // custom to mat
|
||||
if (typeof attribs.class !== 'undefined') {
|
||||
// Filter out all classes other than ones starting with language- for syntax highlighting.
|
||||
const classes = attribs.class.split(/\s/).filter(function(cl) {
|
||||
return cl.startsWith('language-');
|
||||
return cl.startsWith('language-') && !cl.startsWith('language-_');
|
||||
});
|
||||
attribs.class = classes.join(' ');
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -17,6 +18,8 @@ limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Analytics from './Analytics';
|
||||
import dis from './dispatcher/dispatcher';
|
||||
import {defer} from './utils/promise';
|
||||
@ -25,36 +28,48 @@ import AsyncWrapper from './AsyncWrapper';
|
||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
||||
|
||||
class ModalManager {
|
||||
constructor() {
|
||||
this._counter = 0;
|
||||
interface IModal<T extends any[]> {
|
||||
elem: React.ReactNode;
|
||||
className?: string;
|
||||
beforeClosePromise?: Promise<boolean>;
|
||||
closeReason?: string;
|
||||
onBeforeClose?(reason?: string): Promise<boolean>;
|
||||
onFinished(...args: T): void;
|
||||
close(...args: T): void;
|
||||
}
|
||||
|
||||
// The modal to prioritise over all others. If this is set, only show
|
||||
// this modal. Remove all other modals from the stack when this modal
|
||||
// is closed.
|
||||
this._priorityModal = null;
|
||||
// The modal to keep open underneath other modals if possible. Useful
|
||||
// for cases like Settings where the modal should remain open while the
|
||||
// user is prompted for more information/errors.
|
||||
this._staticModal = null;
|
||||
// A list of the modals we have stacked up, with the most recent at [0]
|
||||
// Neither the static nor priority modal will be in this list.
|
||||
this._modals = [
|
||||
/* {
|
||||
elem: React component for this dialog
|
||||
onFinished: caller-supplied onFinished callback
|
||||
className: CSS class for the dialog wrapper div
|
||||
} */
|
||||
];
|
||||
interface IHandle<T extends any[]> {
|
||||
finished: Promise<T>;
|
||||
close(...args: T): void;
|
||||
}
|
||||
|
||||
this.onBackgroundClick = this.onBackgroundClick.bind(this);
|
||||
}
|
||||
interface IProps<T extends any[]> {
|
||||
onFinished?(...args: T): void;
|
||||
// TODO improve typing here once all Modals are TS and we can exhaustively check the props
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
hasDialogs() {
|
||||
return this._priorityModal || this._staticModal || this._modals.length > 0;
|
||||
}
|
||||
interface IOptions<T extends any[]> {
|
||||
onBeforeClose?: IModal<T>["onBeforeClose"];
|
||||
}
|
||||
|
||||
getOrCreateContainer() {
|
||||
type ParametersWithoutFirst<T extends (...args: any) => any> = T extends (a: any, ...args: infer P) => any ? P : never;
|
||||
|
||||
export class ModalManager {
|
||||
private counter = 0;
|
||||
// The modal to prioritise over all others. If this is set, only show
|
||||
// this modal. Remove all other modals from the stack when this modal
|
||||
// is closed.
|
||||
private priorityModal: IModal<any> = null;
|
||||
// The modal to keep open underneath other modals if possible. Useful
|
||||
// for cases like Settings where the modal should remain open while the
|
||||
// user is prompted for more information/errors.
|
||||
private staticModal: IModal<any> = null;
|
||||
// A list of the modals we have stacked up, with the most recent at [0]
|
||||
// Neither the static nor priority modal will be in this list.
|
||||
private modals: IModal<any>[] = [];
|
||||
|
||||
private static getOrCreateContainer() {
|
||||
let container = document.getElementById(DIALOG_CONTAINER_ID);
|
||||
|
||||
if (!container) {
|
||||
@ -66,7 +81,7 @@ class ModalManager {
|
||||
return container;
|
||||
}
|
||||
|
||||
getOrCreateStaticContainer() {
|
||||
private static getOrCreateStaticContainer() {
|
||||
let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID);
|
||||
|
||||
if (!container) {
|
||||
@ -78,63 +93,99 @@ class ModalManager {
|
||||
return container;
|
||||
}
|
||||
|
||||
createTrackedDialog(analyticsAction, analyticsInfo, ...rest) {
|
||||
public hasDialogs() {
|
||||
return this.priorityModal || this.staticModal || this.modals.length > 0;
|
||||
}
|
||||
|
||||
public createTrackedDialog<T extends any[]>(
|
||||
analyticsAction: string,
|
||||
analyticsInfo: string,
|
||||
...rest: Parameters<ModalManager["createDialog"]>
|
||||
) {
|
||||
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||
return this.createDialog(...rest);
|
||||
return this.createDialog<T>(...rest);
|
||||
}
|
||||
|
||||
appendTrackedDialog(analyticsAction, analyticsInfo, ...rest) {
|
||||
public appendTrackedDialog<T extends any[]>(
|
||||
analyticsAction: string,
|
||||
analyticsInfo: string,
|
||||
...rest: Parameters<ModalManager["appendDialog"]>
|
||||
) {
|
||||
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||
return this.appendDialog(...rest);
|
||||
return this.appendDialog<T>(...rest);
|
||||
}
|
||||
|
||||
createDialog(Element, ...rest) {
|
||||
return this.createDialogAsync(Promise.resolve(Element), ...rest);
|
||||
public createDialog<T extends any[]>(
|
||||
Element: React.ComponentType,
|
||||
...rest: ParametersWithoutFirst<ModalManager["createDialogAsync"]>
|
||||
) {
|
||||
return this.createDialogAsync<T>(Promise.resolve(Element), ...rest);
|
||||
}
|
||||
|
||||
appendDialog(Element, ...rest) {
|
||||
return this.appendDialogAsync(Promise.resolve(Element), ...rest);
|
||||
public appendDialog<T extends any[]>(
|
||||
Element: React.ComponentType,
|
||||
...rest: ParametersWithoutFirst<ModalManager["appendDialogAsync"]>
|
||||
) {
|
||||
return this.appendDialogAsync<T>(Promise.resolve(Element), ...rest);
|
||||
}
|
||||
|
||||
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
|
||||
public createTrackedDialogAsync<T extends any[]>(
|
||||
analyticsAction: string,
|
||||
analyticsInfo: string,
|
||||
...rest: Parameters<ModalManager["appendDialogAsync"]>
|
||||
) {
|
||||
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||
return this.createDialogAsync(...rest);
|
||||
return this.createDialogAsync<T>(...rest);
|
||||
}
|
||||
|
||||
appendTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
|
||||
public appendTrackedDialogAsync<T extends any[]>(
|
||||
analyticsAction: string,
|
||||
analyticsInfo: string,
|
||||
...rest: Parameters<ModalManager["appendDialogAsync"]>
|
||||
) {
|
||||
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||
return this.appendDialogAsync(...rest);
|
||||
return this.appendDialogAsync<T>(...rest);
|
||||
}
|
||||
|
||||
_buildModal(prom, props, className, options) {
|
||||
const modal = {};
|
||||
private buildModal<T extends any[]>(
|
||||
prom: Promise<React.ComponentType>,
|
||||
props?: IProps<T>,
|
||||
className?: string,
|
||||
options?: IOptions<T>
|
||||
) {
|
||||
const modal: IModal<T> = {
|
||||
onFinished: props ? props.onFinished : null,
|
||||
onBeforeClose: options.onBeforeClose,
|
||||
beforeClosePromise: null,
|
||||
closeReason: null,
|
||||
className,
|
||||
|
||||
// these will be set below but we need an object reference to pass to getCloseFn before we can do that
|
||||
elem: null,
|
||||
close: null,
|
||||
};
|
||||
|
||||
// never call this from onFinished() otherwise it will loop
|
||||
const [closeDialog, onFinishedProm] = this._getCloseFn(modal, props);
|
||||
const [closeDialog, onFinishedProm] = this.getCloseFn<T>(modal, props);
|
||||
|
||||
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
||||
// otherwise we'll get confused.
|
||||
const modalCount = this._counter++;
|
||||
const modalCount = this.counter++;
|
||||
|
||||
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
||||
// property set here so you can't close the dialog from a button click!
|
||||
modal.elem = (
|
||||
<AsyncWrapper key={modalCount} prom={prom} {...props}
|
||||
onFinished={closeDialog} />
|
||||
);
|
||||
modal.onFinished = props ? props.onFinished : null;
|
||||
modal.className = className;
|
||||
modal.onBeforeClose = options.onBeforeClose;
|
||||
modal.beforeClosePromise = null;
|
||||
modal.elem = <AsyncWrapper key={modalCount} prom={prom} {...props} onFinished={closeDialog} />;
|
||||
modal.close = closeDialog;
|
||||
modal.closeReason = null;
|
||||
|
||||
return {modal, closeDialog, onFinishedProm};
|
||||
}
|
||||
|
||||
_getCloseFn(modal, props) {
|
||||
const deferred = defer();
|
||||
return [async (...args) => {
|
||||
private getCloseFn<T extends any[]>(
|
||||
modal: IModal<T>,
|
||||
props: IProps<T>
|
||||
): [IHandle<T>["close"], IHandle<T>["finished"]] {
|
||||
const deferred = defer<T>();
|
||||
return [async (...args: T) => {
|
||||
if (modal.beforeClosePromise) {
|
||||
await modal.beforeClosePromise;
|
||||
} else if (modal.onBeforeClose) {
|
||||
@ -147,26 +198,26 @@ class ModalManager {
|
||||
}
|
||||
deferred.resolve(args);
|
||||
if (props && props.onFinished) props.onFinished.apply(null, args);
|
||||
const i = this._modals.indexOf(modal);
|
||||
const i = this.modals.indexOf(modal);
|
||||
if (i >= 0) {
|
||||
this._modals.splice(i, 1);
|
||||
this.modals.splice(i, 1);
|
||||
}
|
||||
|
||||
if (this._priorityModal === modal) {
|
||||
this._priorityModal = null;
|
||||
if (this.priorityModal === modal) {
|
||||
this.priorityModal = null;
|
||||
|
||||
// XXX: This is destructive
|
||||
this._modals = [];
|
||||
this.modals = [];
|
||||
}
|
||||
|
||||
if (this._staticModal === modal) {
|
||||
this._staticModal = null;
|
||||
if (this.staticModal === modal) {
|
||||
this.staticModal = null;
|
||||
|
||||
// XXX: This is destructive
|
||||
this._modals = [];
|
||||
this.modals = [];
|
||||
}
|
||||
|
||||
this._reRender();
|
||||
this.reRender();
|
||||
}, deferred.promise];
|
||||
}
|
||||
|
||||
@ -207,38 +258,49 @@ class ModalManager {
|
||||
* @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog
|
||||
* @returns {object} Object with 'close' parameter being a function that will close the dialog
|
||||
*/
|
||||
createDialogAsync(prom, props, className, isPriorityModal, isStaticModal, options = {}) {
|
||||
const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, options);
|
||||
private createDialogAsync<T extends any[]>(
|
||||
prom: Promise<React.ComponentType>,
|
||||
props?: IProps<T>,
|
||||
className?: string,
|
||||
isPriorityModal = false,
|
||||
isStaticModal = false,
|
||||
options: IOptions<T> = {}
|
||||
): IHandle<T> {
|
||||
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options);
|
||||
if (isPriorityModal) {
|
||||
// XXX: This is destructive
|
||||
this._priorityModal = modal;
|
||||
this.priorityModal = modal;
|
||||
} else if (isStaticModal) {
|
||||
// This is intentionally destructive
|
||||
this._staticModal = modal;
|
||||
this.staticModal = modal;
|
||||
} else {
|
||||
this._modals.unshift(modal);
|
||||
this.modals.unshift(modal);
|
||||
}
|
||||
|
||||
this._reRender();
|
||||
this.reRender();
|
||||
return {
|
||||
close: closeDialog,
|
||||
finished: onFinishedProm,
|
||||
};
|
||||
}
|
||||
|
||||
appendDialogAsync(prom, props, className) {
|
||||
const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, {});
|
||||
private appendDialogAsync<T extends any[]>(
|
||||
prom: Promise<React.ComponentType>,
|
||||
props?: IProps<T>,
|
||||
className?: string
|
||||
): IHandle<T> {
|
||||
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {});
|
||||
|
||||
this._modals.push(modal);
|
||||
this._reRender();
|
||||
this.modals.push(modal);
|
||||
this.reRender();
|
||||
return {
|
||||
close: closeDialog,
|
||||
finished: onFinishedProm,
|
||||
};
|
||||
}
|
||||
|
||||
onBackgroundClick() {
|
||||
const modal = this._getCurrentModal();
|
||||
private onBackgroundClick = () => {
|
||||
const modal = this.getCurrentModal();
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
@ -249,21 +311,21 @@ class ModalManager {
|
||||
modal.closeReason = "backgroundClick";
|
||||
modal.close();
|
||||
modal.closeReason = null;
|
||||
};
|
||||
|
||||
private getCurrentModal(): IModal<any> {
|
||||
return this.priorityModal ? this.priorityModal : (this.modals[0] || this.staticModal);
|
||||
}
|
||||
|
||||
_getCurrentModal() {
|
||||
return this._priorityModal ? this._priorityModal : (this._modals[0] || this._staticModal);
|
||||
}
|
||||
|
||||
_reRender() {
|
||||
if (this._modals.length === 0 && !this._priorityModal && !this._staticModal) {
|
||||
private reRender() {
|
||||
if (this.modals.length === 0 && !this.priorityModal && !this.staticModal) {
|
||||
// If there is no modal to render, make all of Riot available
|
||||
// to screen reader users again
|
||||
dis.dispatch({
|
||||
action: 'aria_unhide_main_app',
|
||||
});
|
||||
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
|
||||
ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer());
|
||||
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
|
||||
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -274,49 +336,48 @@ class ModalManager {
|
||||
action: 'aria_hide_main_app',
|
||||
});
|
||||
|
||||
if (this._staticModal) {
|
||||
const classes = "mx_Dialog_wrapper mx_Dialog_staticWrapper "
|
||||
+ (this._staticModal.className ? this._staticModal.className : '');
|
||||
if (this.staticModal) {
|
||||
const classes = classNames("mx_Dialog_wrapper mx_Dialog_staticWrapper", this.staticModal.className);
|
||||
|
||||
const staticDialog = (
|
||||
<div className={classes}>
|
||||
<div className="mx_Dialog">
|
||||
{ this._staticModal.elem }
|
||||
{ this.staticModal.elem }
|
||||
</div>
|
||||
<div className="mx_Dialog_background mx_Dialog_staticBackground" onClick={this.onBackgroundClick}></div>
|
||||
<div className="mx_Dialog_background mx_Dialog_staticBackground" onClick={this.onBackgroundClick} />
|
||||
</div>
|
||||
);
|
||||
|
||||
ReactDOM.render(staticDialog, this.getOrCreateStaticContainer());
|
||||
ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
|
||||
} else {
|
||||
// This is safe to call repeatedly if we happen to do that
|
||||
ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer());
|
||||
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
|
||||
}
|
||||
|
||||
const modal = this._getCurrentModal();
|
||||
if (modal !== this._staticModal) {
|
||||
const classes = "mx_Dialog_wrapper "
|
||||
+ (this._staticModal ? "mx_Dialog_wrapperWithStaticUnder " : '')
|
||||
+ (modal.className ? modal.className : '');
|
||||
const modal = this.getCurrentModal();
|
||||
if (modal !== this.staticModal) {
|
||||
const classes = classNames("mx_Dialog_wrapper", modal.className, {
|
||||
mx_Dialog_wrapperWithStaticUnder: this.staticModal,
|
||||
});
|
||||
|
||||
const dialog = (
|
||||
<div className={classes}>
|
||||
<div className="mx_Dialog">
|
||||
{modal.elem}
|
||||
</div>
|
||||
<div className="mx_Dialog_background" onClick={this.onBackgroundClick}></div>
|
||||
<div className="mx_Dialog_background" onClick={this.onBackgroundClick} />
|
||||
</div>
|
||||
);
|
||||
|
||||
ReactDOM.render(dialog, this.getOrCreateContainer());
|
||||
ReactDOM.render(dialog, ModalManager.getOrCreateContainer());
|
||||
} else {
|
||||
// This is safe to call repeatedly if we happen to do that
|
||||
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
|
||||
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!global.singletonModalManager) {
|
||||
global.singletonModalManager = new ModalManager();
|
||||
if (!window.singletonModalManager) {
|
||||
window.singletonModalManager = new ModalManager();
|
||||
}
|
||||
export default global.singletonModalManager;
|
||||
export default window.singletonModalManager;
|
@ -67,8 +67,8 @@ export default class RebrandListener {
|
||||
private nagAgainAt?: number = null;
|
||||
|
||||
static sharedInstance() {
|
||||
if (!window.mx_RebrandListener) window.mx_RebrandListener = new RebrandListener();
|
||||
return window.mx_RebrandListener;
|
||||
if (!window.mxRebrandListener) window.mxRebrandListener = new RebrandListener();
|
||||
return window.mxRebrandListener;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@ -114,6 +114,11 @@ export default class RebrandListener {
|
||||
}
|
||||
};
|
||||
|
||||
onOneTimeToastDismiss = async () => {
|
||||
localStorage.setItem('mx_rename_dialog_dismissed', 'true');
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
onNagTimerFired = () => {
|
||||
this._reshowTimer = null;
|
||||
this.nagAgainAt = null;
|
||||
@ -143,10 +148,14 @@ export default class RebrandListener {
|
||||
|
||||
if (nagToast || oneTimeToast) {
|
||||
let description;
|
||||
let rejectLabel = null;
|
||||
let onReject = null;
|
||||
if (nagToast) {
|
||||
description = _t("Use your account to sign in to the latest version");
|
||||
} else {
|
||||
description = _t("We’re excited to announce Riot is now Element");
|
||||
rejectLabel = _t("Dismiss");
|
||||
onReject = this.onOneTimeToastDismiss;
|
||||
}
|
||||
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
@ -157,6 +166,8 @@ export default class RebrandListener {
|
||||
description,
|
||||
acceptLabel: _t("Learn More"),
|
||||
onAccept: nagToast ? this.onNagToastLearnMore : this.onOneTimeToastLearnMore,
|
||||
rejectLabel,
|
||||
onReject,
|
||||
},
|
||||
component: GenericToast,
|
||||
priority: 20,
|
||||
|
@ -34,27 +34,6 @@ export function shouldShowMentionBadge(roomNotifState) {
|
||||
return MENTION_BADGE_STATES.includes(roomNotifState);
|
||||
}
|
||||
|
||||
export function countRoomsWithNotif(rooms) {
|
||||
return rooms.reduce((result, room, index) => {
|
||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||
const notificationCount = room.getUnreadNotificationCount();
|
||||
|
||||
const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState);
|
||||
const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState);
|
||||
const isInvite = room.hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite');
|
||||
const badges = notifBadges || mentionBadges || isInvite;
|
||||
|
||||
if (badges) {
|
||||
result.count++;
|
||||
if (highlight) {
|
||||
result.highlight = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, {count: 0, highlight: false});
|
||||
}
|
||||
|
||||
export function aggregateNotificationCount(rooms) {
|
||||
return rooms.reduce((result, room) => {
|
||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||
|
@ -401,14 +401,16 @@ export const Commands = [
|
||||
// If we need an identity server but don't have one, things
|
||||
// get a bit more complex here, but we try to show something
|
||||
// meaningful.
|
||||
let finished = Promise.resolve();
|
||||
let prom = Promise.resolve();
|
||||
if (
|
||||
getAddressType(address) === 'email' &&
|
||||
!MatrixClientPeg.get().getIdentityServerUrl()
|
||||
) {
|
||||
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
|
||||
if (defaultIdentityServerUrl) {
|
||||
({ finished } = Modal.createTrackedDialog('Slash Commands', 'Identity server',
|
||||
const { finished } = Modal.createTrackedDialog<[boolean]>(
|
||||
'Slash Commands',
|
||||
'Identity server',
|
||||
QuestionDialog, {
|
||||
title: _t("Use an identity server"),
|
||||
description: <p>{_t(
|
||||
@ -421,9 +423,9 @@ export const Commands = [
|
||||
)}</p>,
|
||||
button: _t("Continue"),
|
||||
},
|
||||
));
|
||||
);
|
||||
|
||||
finished = finished.then(([useDefault]: any) => {
|
||||
prom = finished.then(([useDefault]) => {
|
||||
if (useDefault) {
|
||||
useDefaultIdentityServer();
|
||||
return;
|
||||
@ -435,7 +437,7 @@ export const Commands = [
|
||||
}
|
||||
}
|
||||
const inviter = new MultiInviter(roomId);
|
||||
return success(finished.then(() => {
|
||||
return success(prom.then(() => {
|
||||
return inviter.invite([address]);
|
||||
}).then(() => {
|
||||
if (inviter.getCompletionState(address) !== "invited") {
|
||||
@ -1049,7 +1051,7 @@ export function parseCommandString(input) {
|
||||
// trim any trailing whitespace, as it can confuse the parser for
|
||||
// IRC-style commands
|
||||
input = input.replace(/\s+$/, '');
|
||||
if (input[0] !== '/') return null; // not a command
|
||||
if (input[0] !== '/') return {}; // not a command
|
||||
|
||||
const bits = input.match(/^(\S+?)(?: +((.|\n)*))?$/);
|
||||
let cmd;
|
||||
|
@ -18,9 +18,9 @@ limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import AccessibleButton, {IProps as IAccessibleButtonProps} from "../../components/views/elements/AccessibleButton";
|
||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||
|
||||
interface IProps extends IAccessibleButtonProps {
|
||||
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||
label?: string;
|
||||
// whether or not the context menu is currently open
|
||||
isExpanded: boolean;
|
||||
|
47
src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Normal file
47
src/accessibility/context_menu/ContextMenuTooltipButton.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
|
||||
|
||||
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
|
||||
// whether or not the context menu is currently open
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
||||
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||
export const ContextMenuTooltipButton: React.FC<IProps> = ({
|
||||
isExpanded,
|
||||
children,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<AccessibleTooltipButton
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu || onClick}
|
||||
aria-haspopup={true}
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
{ children }
|
||||
</AccessibleTooltipButton>
|
||||
);
|
||||
};
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { asyncAction } from './actionCreators';
|
||||
import { TAG_DM } from '../stores/RoomListStore';
|
||||
import Modal from '../Modal';
|
||||
import * as Rooms from '../Rooms';
|
||||
import { _t } from '../languageHandler';
|
||||
@ -24,7 +23,9 @@ import * as sdk from '../index';
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||
import { RoomListStoreTempProxy } from "../stores/room-list/RoomListStoreTempProxy";
|
||||
import RoomListStore from "../stores/room-list/RoomListStore";
|
||||
import { SortAlgorithm } from "../stores/room-list/algorithms/models";
|
||||
import { DefaultTagID } from "../stores/room-list/models";
|
||||
|
||||
export default class RoomListActions {
|
||||
/**
|
||||
@ -51,9 +52,9 @@ export default class RoomListActions {
|
||||
let metaData = null;
|
||||
|
||||
// Is the tag ordered manually?
|
||||
if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||
const lists = RoomListStoreTempProxy.getRoomLists();
|
||||
const newList = [...lists[newTag]];
|
||||
const store = RoomListStore.instance;
|
||||
if (newTag && store.getTagSorting(newTag) === SortAlgorithm.Manual) {
|
||||
const newList = [...store.orderedLists[newTag]];
|
||||
|
||||
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
||||
|
||||
@ -81,11 +82,11 @@ export default class RoomListActions {
|
||||
const roomId = room.roomId;
|
||||
|
||||
// Evil hack to get DMs behaving
|
||||
if ((oldTag === undefined && newTag === TAG_DM) ||
|
||||
(oldTag === TAG_DM && newTag === undefined)
|
||||
if ((oldTag === undefined && newTag === DefaultTagID.DM) ||
|
||||
(oldTag === DefaultTagID.DM && newTag === undefined)
|
||||
) {
|
||||
return Rooms.guessAndSetDMRoom(
|
||||
room, newTag === TAG_DM,
|
||||
room, newTag === DefaultTagID.DM,
|
||||
).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to set direct chat tag " + err);
|
||||
@ -102,12 +103,12 @@ export default class RoomListActions {
|
||||
// but we avoid ever doing a request with TAG_DM.
|
||||
//
|
||||
// if we moved lists, remove the old tag
|
||||
if (oldTag && oldTag !== TAG_DM &&
|
||||
if (oldTag && oldTag !== DefaultTagID.DM &&
|
||||
hasChangedSubLists
|
||||
) {
|
||||
const promiseToDelete = matrixClient.deleteRoomTag(
|
||||
roomId, oldTag,
|
||||
).catch(function (err) {
|
||||
).catch(function(err) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
||||
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
||||
@ -120,14 +121,14 @@ export default class RoomListActions {
|
||||
}
|
||||
|
||||
// if we moved lists or the ordering changed, add the new tag
|
||||
if (newTag && newTag !== TAG_DM &&
|
||||
if (newTag && newTag !== DefaultTagID.DM &&
|
||||
(hasChangedSubLists || metaData)
|
||||
) {
|
||||
// metaData is the body of the PUT to set the tag, so it must
|
||||
// at least be an empty object.
|
||||
metaData = metaData || {};
|
||||
|
||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
|
||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to add tag " + newTag + " to room: " + err);
|
||||
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
||||
|
@ -22,7 +22,6 @@ import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
export default class TagOrderActions {
|
||||
|
||||
/**
|
||||
* Creates an action thunk that will do an asynchronous request to
|
||||
* move a tag in TagOrderStore to destinationIx.
|
||||
|
@ -34,7 +34,8 @@ import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||
const LIMIT = 20;
|
||||
|
||||
// Match for ascii-style ";-)" emoticons or ":wink:" shortcodes provided by emojibase
|
||||
const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|:[+-\\w]*:?)$', 'g');
|
||||
// anchored to only match from the start of parts otherwise it'll show emoji suggestions whilst typing matrix IDs
|
||||
const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|(?:^|\\s):[+-\\w]*:?)$', 'g');
|
||||
|
||||
interface IEmojiShort {
|
||||
emoji: IEmoji;
|
||||
|
@ -18,16 +18,15 @@ limitations under the License.
|
||||
|
||||
import _at from 'lodash/at';
|
||||
import _uniq from 'lodash/uniq';
|
||||
|
||||
function stripDiacritics(str: string): string {
|
||||
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||
}
|
||||
import {removeHiddenChars} from "matrix-js-sdk/src/utils";
|
||||
|
||||
interface IOptions<T extends {}> {
|
||||
keys: Array<string | keyof T>;
|
||||
funcs?: Array<(T) => string>;
|
||||
shouldMatchWordsOnly?: boolean;
|
||||
shouldMatchPrefix?: boolean;
|
||||
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
|
||||
fuzzy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,14 +45,10 @@ interface IOptions<T extends {}> {
|
||||
*/
|
||||
export default class QueryMatcher<T extends Object> {
|
||||
private _options: IOptions<T>;
|
||||
private _keys: IOptions<T>["keys"];
|
||||
private _funcs: Required<IOptions<T>["funcs"]>;
|
||||
private _items: Map<string, {object: T, keyWeight: number}[]>;
|
||||
|
||||
constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
|
||||
this._options = options;
|
||||
this._keys = options.keys;
|
||||
this._funcs = options.funcs || [];
|
||||
|
||||
this.setObjects(objects);
|
||||
|
||||
@ -78,15 +73,17 @@ export default class QueryMatcher<T extends Object> {
|
||||
// type for their values. We assume that those values who's keys have
|
||||
// been specified will be string. Also, we cannot infer all the
|
||||
// types of the keys of the objects at compile.
|
||||
const keyValues = _at<string>(<any>object, this._keys);
|
||||
const keyValues = _at<string>(<any>object, this._options.keys);
|
||||
|
||||
for (const f of this._funcs) {
|
||||
keyValues.push(f(object));
|
||||
if (this._options.funcs) {
|
||||
for (const f of this._options.funcs) {
|
||||
keyValues.push(f(object));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [index, keyValue] of Object.entries(keyValues)) {
|
||||
if (!keyValue) continue; // skip falsy keyValues
|
||||
const key = stripDiacritics(keyValue).toLowerCase();
|
||||
const key = this.processQuery(keyValue);
|
||||
if (!this._items.has(key)) {
|
||||
this._items.set(key, []);
|
||||
}
|
||||
@ -99,7 +96,7 @@ export default class QueryMatcher<T extends Object> {
|
||||
}
|
||||
|
||||
match(query: string): T[] {
|
||||
query = stripDiacritics(query).toLowerCase();
|
||||
query = this.processQuery(query);
|
||||
if (this._options.shouldMatchWordsOnly) {
|
||||
query = query.replace(/[^\w]/g, '');
|
||||
}
|
||||
@ -118,7 +115,7 @@ export default class QueryMatcher<T extends Object> {
|
||||
const index = resultKey.indexOf(query);
|
||||
if (index !== -1 && (!this._options.shouldMatchPrefix || index === 0)) {
|
||||
matches.push(
|
||||
...candidates.map((candidate) => ({index, ...candidate}))
|
||||
...candidates.map((candidate) => ({index, ...candidate})),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -142,4 +139,12 @@ export default class QueryMatcher<T extends Object> {
|
||||
// Now map the keys to the result objects. Also remove any duplicates.
|
||||
return _uniq(matches.map((match) => match.object));
|
||||
}
|
||||
|
||||
private processQuery(query: string): string {
|
||||
if (this._options.fuzzy !== false) {
|
||||
// lower case both the input and the output for consistency
|
||||
return removeHiddenChars(query.toLowerCase()).toLowerCase();
|
||||
}
|
||||
return query.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
@ -38,12 +38,13 @@ export default class AutoHideScrollbar extends React.Component {
|
||||
|
||||
render() {
|
||||
return (<div
|
||||
ref={this._collectContainerRef}
|
||||
style={this.props.style}
|
||||
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
|
||||
onScroll={this.props.onScroll}
|
||||
onWheel={this.props.onWheel}
|
||||
>
|
||||
ref={this._collectContainerRef}
|
||||
style={this.props.style}
|
||||
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
|
||||
onScroll={this.props.onScroll}
|
||||
onWheel={this.props.onWheel}
|
||||
tabIndex={this.props.tabIndex}
|
||||
>
|
||||
{ this.props.children }
|
||||
</div>);
|
||||
}
|
||||
|
@ -461,6 +461,7 @@ export function createMenu(ElementClass, props) {
|
||||
|
||||
// re-export the semantic helper components for simplicity
|
||||
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
||||
export {ContextMenuTooltipButton} from "../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
||||
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
||||
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
||||
|
@ -72,17 +72,17 @@ class CustomRoomTagTile extends React.Component {
|
||||
const tag = this.props.tag;
|
||||
const avatarHeight = 40;
|
||||
const className = classNames({
|
||||
CustomRoomTagPanel_tileSelected: tag.selected,
|
||||
"CustomRoomTagPanel_tileSelected": tag.selected,
|
||||
});
|
||||
const name = tag.name;
|
||||
const badge = tag.badge;
|
||||
const badgeNotifState = tag.badgeNotifState;
|
||||
let badgeElement;
|
||||
if (badge) {
|
||||
if (badgeNotifState) {
|
||||
const badgeClasses = classNames({
|
||||
"mx_TagTile_badge": true,
|
||||
"mx_TagTile_badgeHighlight": badge.highlight,
|
||||
"mx_TagTile_badgeHighlight": badgeNotifState.hasMentions,
|
||||
});
|
||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badgeNotifState.count)}</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -192,7 +192,7 @@ export default class IndicatorScrollbar extends React.Component {
|
||||
ref={this._collectScrollerComponent}
|
||||
wrappedRef={this._collectScroller}
|
||||
onWheel={this.onMouseWheel}
|
||||
{... this.props}
|
||||
{...this.props}
|
||||
>
|
||||
{ leftOverflowIndicator }
|
||||
{ this.props.children }
|
||||
|
@ -1,305 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Key } from '../../Keyboard';
|
||||
import * as sdk from '../../index';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import * as VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||
import SettingsStore from '../../settings/SettingsStore';
|
||||
import {_t} from "../../languageHandler";
|
||||
import Analytics from "../../Analytics";
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
|
||||
|
||||
const LeftPanel = createReactClass({
|
||||
displayName: 'LeftPanel',
|
||||
|
||||
// NB. If you add props, don't forget to update
|
||||
// shouldComponentUpdate!
|
||||
propTypes: {
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
searchFilter: '',
|
||||
breadcrumbs: false,
|
||||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this.focusedElement = null;
|
||||
|
||||
this._breadcrumbsWatcherRef = SettingsStore.watchSetting(
|
||||
"breadcrumbs", null, this._onBreadcrumbsChanged);
|
||||
this._tagPanelWatcherRef = SettingsStore.watchSetting(
|
||||
"TagPanel.enableTagPanel", null, () => this.forceUpdate());
|
||||
|
||||
const useBreadcrumbs = !!SettingsStore.getValue("breadcrumbs");
|
||||
Analytics.setBreadcrumbs(useBreadcrumbs);
|
||||
this.setState({breadcrumbs: useBreadcrumbs});
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
SettingsStore.unwatchSetting(this._breadcrumbsWatcherRef);
|
||||
SettingsStore.unwatchSetting(this._tagPanelWatcherRef);
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
// MatrixChat will update whenever the user switches
|
||||
// rooms, but propagating this change all the way down
|
||||
// the react tree is quite slow, so we cut this off
|
||||
// here. The RoomTiles listen for the room change
|
||||
// events themselves to know when to update.
|
||||
// We just need to update if any of these things change.
|
||||
if (
|
||||
this.props.collapsed !== nextProps.collapsed ||
|
||||
this.props.disabled !== nextProps.disabled
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.state.searchFilter !== nextState.searchFilter) {
|
||||
return true;
|
||||
}
|
||||
if (this.state.searchExpanded !== nextState.searchExpanded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.breadcrumbs !== this.state.breadcrumbs) {
|
||||
Analytics.setBreadcrumbs(this.state.breadcrumbs);
|
||||
}
|
||||
},
|
||||
|
||||
_onBreadcrumbsChanged: function(settingName, roomId, level, valueAtLevel, value) {
|
||||
// Features are only possible at a single level, so we can get away with using valueAtLevel.
|
||||
// The SettingsStore runs on the same tick as the update, so `value` will be wrong.
|
||||
this.setState({breadcrumbs: valueAtLevel});
|
||||
|
||||
// For some reason the setState doesn't trigger a render of the component, so force one.
|
||||
// Probably has to do with the change happening outside of a change detector cycle.
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
_onFocus: function(ev) {
|
||||
this.focusedElement = ev.target;
|
||||
},
|
||||
|
||||
_onBlur: function(ev) {
|
||||
this.focusedElement = null;
|
||||
},
|
||||
|
||||
_onFilterKeyDown: function(ev) {
|
||||
if (!this.focusedElement) return;
|
||||
|
||||
switch (ev.key) {
|
||||
// On enter of rooms filter select and activate first room if such one exists
|
||||
case Key.ENTER: {
|
||||
const firstRoom = ev.target.closest(".mx_LeftPanel").querySelector(".mx_RoomTile");
|
||||
if (firstRoom) {
|
||||
firstRoom.click();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
if (!this.focusedElement) return;
|
||||
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_UP:
|
||||
this._onMoveFocus(ev, true, true);
|
||||
break;
|
||||
case Key.ARROW_DOWN:
|
||||
this._onMoveFocus(ev, false, true);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_onMoveFocus: function(ev, up, trap) {
|
||||
let element = this.focusedElement;
|
||||
|
||||
// unclear why this isn't needed
|
||||
// var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending;
|
||||
// this.focusDirection = up;
|
||||
|
||||
let descending = false; // are we currently descending or ascending through the DOM tree?
|
||||
let classes;
|
||||
|
||||
do {
|
||||
const child = up ? element.lastElementChild : element.firstElementChild;
|
||||
const sibling = up ? element.previousElementSibling : element.nextElementSibling;
|
||||
|
||||
if (descending) {
|
||||
if (child) {
|
||||
element = child;
|
||||
} else if (sibling) {
|
||||
element = sibling;
|
||||
} else {
|
||||
descending = false;
|
||||
element = element.parentElement;
|
||||
}
|
||||
} else {
|
||||
if (sibling) {
|
||||
element = sibling;
|
||||
descending = true;
|
||||
} else {
|
||||
element = element.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
if (element) {
|
||||
classes = element.classList;
|
||||
}
|
||||
} while (element && !(
|
||||
classes.contains("mx_RoomTile") ||
|
||||
classes.contains("mx_RoomSubList_label") ||
|
||||
classes.contains("mx_LeftPanel_filterRooms")));
|
||||
|
||||
if (element) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
element.focus();
|
||||
this.focusedElement = element;
|
||||
} else if (trap) {
|
||||
// if navigation is via up/down arrow-keys, trap in the widget so it doesn't send to composer
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
onSearch: function(term) {
|
||||
this.setState({ searchFilter: term });
|
||||
},
|
||||
|
||||
onSearchCleared: function(source) {
|
||||
if (source === "keyboard") {
|
||||
dis.fire(Action.FocusComposer);
|
||||
}
|
||||
this.setState({searchExpanded: false});
|
||||
},
|
||||
|
||||
collectRoomList: function(ref) {
|
||||
this._roomList = ref;
|
||||
},
|
||||
|
||||
_onSearchFocus: function() {
|
||||
this.setState({searchExpanded: true});
|
||||
},
|
||||
|
||||
_onSearchBlur: function(event) {
|
||||
if (event.target.value.length === 0) {
|
||||
this.setState({searchExpanded: false});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const RoomList = sdk.getComponent('rooms.RoomList');
|
||||
const RoomBreadcrumbs = sdk.getComponent('rooms.RoomBreadcrumbs');
|
||||
const TagPanel = sdk.getComponent('structures.TagPanel');
|
||||
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
|
||||
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||
const CallPreview = sdk.getComponent('voip.CallPreview');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel");
|
||||
let tagPanelContainer;
|
||||
|
||||
const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags");
|
||||
|
||||
if (tagPanelEnabled) {
|
||||
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
|
||||
<TagPanel />
|
||||
{ isCustomTagsEnabled ? <CustomRoomTagPanel /> : undefined }
|
||||
</div>);
|
||||
}
|
||||
|
||||
const containerClasses = classNames(
|
||||
"mx_LeftPanel_container", "mx_fadable",
|
||||
{
|
||||
"collapsed": this.props.collapsed,
|
||||
"mx_LeftPanel_container_hasTagPanel": tagPanelEnabled,
|
||||
"mx_fadable_faded": this.props.disabled,
|
||||
},
|
||||
);
|
||||
|
||||
let exploreButton;
|
||||
if (!this.props.collapsed) {
|
||||
exploreButton = (
|
||||
<div className={classNames("mx_LeftPanel_explore", {"mx_LeftPanel_explore_hidden": this.state.searchExpanded})}>
|
||||
<AccessibleButton onClick={() => dis.fire(Action.ViewRoomDirectory)}>{_t("Explore")}</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const searchBox = (<SearchBox
|
||||
className="mx_LeftPanel_filterRooms"
|
||||
enableRoomSearchFocus={true}
|
||||
blurredPlaceholder={ _t('Filter') }
|
||||
placeholder={ _t('Filter rooms…') }
|
||||
onKeyDown={this._onFilterKeyDown}
|
||||
onSearch={ this.onSearch }
|
||||
onCleared={ this.onSearchCleared }
|
||||
onFocus={this._onSearchFocus}
|
||||
onBlur={this._onSearchBlur}
|
||||
collapsed={this.props.collapsed} />);
|
||||
|
||||
let breadcrumbs;
|
||||
if (this.state.breadcrumbs) {
|
||||
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
|
||||
}
|
||||
|
||||
const roomList = <RoomList
|
||||
onKeyDown={this._onKeyDown}
|
||||
onFocus={this._onFocus}
|
||||
onBlur={this._onBlur}
|
||||
ref={this.collectRoomList}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapsed}
|
||||
searchFilter={this.state.searchFilter}
|
||||
ConferenceHandler={VectorConferenceHandler} />;
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{ tagPanelContainer }
|
||||
<aside className="mx_LeftPanel dark-panel">
|
||||
<TopLeftMenuButton collapsed={this.props.collapsed} />
|
||||
{ breadcrumbs }
|
||||
<CallPreview ConferenceHandler={VectorConferenceHandler} />
|
||||
<div className="mx_LeftPanel_exploreAndFilterRow" onKeyDown={this._onKeyDown} onFocus={this._onFocus} onBlur={this._onBlur}>
|
||||
{ exploreButton }
|
||||
{ searchBox }
|
||||
</div>
|
||||
{roomList}
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default LeftPanel;
|
@ -17,25 +17,26 @@ limitations under the License.
|
||||
import * as React from "react";
|
||||
import { createRef } from "react";
|
||||
import TagPanel from "./TagPanel";
|
||||
import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
||||
import classNames from "classnames";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { _t } from "../../languageHandler";
|
||||
import RoomList2 from "../views/rooms/RoomList2";
|
||||
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist2";
|
||||
import RoomList from "../views/rooms/RoomList";
|
||||
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import UserMenu from "./UserMenu";
|
||||
import RoomSearch from "./RoomSearch";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2";
|
||||
import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs";
|
||||
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
|
||||
import {Key} from "../../Keyboard";
|
||||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||
|
||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
@ -43,7 +44,6 @@ interface IProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
searchFilter: string;
|
||||
showBreadcrumbs: boolean;
|
||||
showTagPanel: boolean;
|
||||
}
|
||||
@ -52,14 +52,15 @@ interface IState {
|
||||
const cssClasses = [
|
||||
"mx_RoomSearch_input",
|
||||
"mx_RoomSearch_icon", // minimized <RoomSearch />
|
||||
"mx_RoomSublist2_headerText",
|
||||
"mx_RoomTile2",
|
||||
"mx_RoomSublist2_showNButton",
|
||||
"mx_RoomSublist_headerText",
|
||||
"mx_RoomTile",
|
||||
"mx_RoomSublist_showNButton",
|
||||
];
|
||||
|
||||
export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
|
||||
private tagPanelWatcherRef: string;
|
||||
private bgImageWatcherRef: string;
|
||||
private focusedElement = null;
|
||||
private isDoingStickyHeaders = false;
|
||||
|
||||
@ -67,13 +68,15 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
searchFilter: "",
|
||||
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
||||
showTagPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
||||
};
|
||||
|
||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
||||
this.bgImageWatcherRef = SettingsStore.watchSetting(
|
||||
"RoomList.backgroundImage", null, this.onBackgroundImageUpdate);
|
||||
this.tagPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
||||
this.setState({showTagPanel: SettingsStore.getValue("TagPanel.enableTagPanel")});
|
||||
});
|
||||
@ -85,15 +88,13 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
|
||||
public componentWillUnmount() {
|
||||
SettingsStore.unwatchSetting(this.tagPanelWatcherRef);
|
||||
SettingsStore.unwatchSetting(this.bgImageWatcherRef);
|
||||
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
||||
this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize);
|
||||
}
|
||||
|
||||
private onSearch = (term: string): void => {
|
||||
this.setState({searchFilter: term});
|
||||
};
|
||||
|
||||
private onExplore = () => {
|
||||
dis.fire(Action.ViewRoomDirectory);
|
||||
};
|
||||
@ -109,6 +110,20 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
}
|
||||
};
|
||||
|
||||
private onBackgroundImageUpdate = () => {
|
||||
// Note: we do this in the LeftPanel as it uses this variable most prominently.
|
||||
const avatarSize = 32; // arbitrary
|
||||
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
|
||||
if (settingBgMxc) {
|
||||
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(settingBgMxc, avatarSize, avatarSize);
|
||||
}
|
||||
const avatarUrlProp = `url(${avatarUrl})`;
|
||||
if (document.body.style.getPropertyValue("--avatar-url") !== avatarUrlProp) {
|
||||
document.body.style.setProperty("--avatar-url", avatarUrlProp);
|
||||
}
|
||||
};
|
||||
|
||||
private handleStickyHeaders(list: HTMLDivElement) {
|
||||
if (this.isDoingStickyHeaders) return;
|
||||
this.isDoingStickyHeaders = true;
|
||||
@ -121,7 +136,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
private doStickyHeaders(list: HTMLDivElement) {
|
||||
const topEdge = list.scrollTop;
|
||||
const bottomEdge = list.offsetHeight + list.scrollTop;
|
||||
const sublists = list.querySelectorAll<HTMLDivElement>(".mx_RoomSublist2");
|
||||
const sublists = list.querySelectorAll<HTMLDivElement>(".mx_RoomSublist");
|
||||
|
||||
const headerRightMargin = 16; // calculated from margins and widths to align with non-sticky tiles
|
||||
const headerStickyWidth = list.clientWidth - headerRightMargin;
|
||||
@ -137,7 +152,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
let lastTopHeader;
|
||||
let firstBottomHeader;
|
||||
for (const sublist of sublists) {
|
||||
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable");
|
||||
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist_stickable");
|
||||
header.style.removeProperty("display"); // always clear display:none first
|
||||
|
||||
// When an element is <=40% off screen, make it take over
|
||||
@ -173,8 +188,8 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
if (style.stickyTop) {
|
||||
if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) {
|
||||
header.classList.add("mx_RoomSublist2_headerContainer_stickyTop");
|
||||
if (!header.classList.contains("mx_RoomSublist_headerContainer_stickyTop")) {
|
||||
header.classList.add("mx_RoomSublist_headerContainer_stickyTop");
|
||||
}
|
||||
|
||||
const newTop = `${list.parentElement.offsetTop}px`;
|
||||
@ -182,8 +197,8 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
header.style.top = newTop;
|
||||
}
|
||||
} else {
|
||||
if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) {
|
||||
header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop");
|
||||
if (header.classList.contains("mx_RoomSublist_headerContainer_stickyTop")) {
|
||||
header.classList.remove("mx_RoomSublist_headerContainer_stickyTop");
|
||||
}
|
||||
if (header.style.top) {
|
||||
header.style.removeProperty('top');
|
||||
@ -191,18 +206,18 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
if (style.stickyBottom) {
|
||||
if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) {
|
||||
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
|
||||
if (!header.classList.contains("mx_RoomSublist_headerContainer_stickyBottom")) {
|
||||
header.classList.add("mx_RoomSublist_headerContainer_stickyBottom");
|
||||
}
|
||||
} else {
|
||||
if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) {
|
||||
header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom");
|
||||
if (header.classList.contains("mx_RoomSublist_headerContainer_stickyBottom")) {
|
||||
header.classList.remove("mx_RoomSublist_headerContainer_stickyBottom");
|
||||
}
|
||||
}
|
||||
|
||||
if (style.stickyTop || style.stickyBottom) {
|
||||
if (!header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) {
|
||||
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
|
||||
if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) {
|
||||
header.classList.add("mx_RoomSublist_headerContainer_sticky");
|
||||
}
|
||||
|
||||
const newWidth = `${headerStickyWidth}px`;
|
||||
@ -210,8 +225,8 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
header.style.width = newWidth;
|
||||
}
|
||||
} else if (!style.stickyTop && !style.stickyBottom) {
|
||||
if (header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) {
|
||||
header.classList.remove("mx_RoomSublist2_headerContainer_sticky");
|
||||
if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) {
|
||||
header.classList.remove("mx_RoomSublist_headerContainer_sticky");
|
||||
}
|
||||
if (header.style.width) {
|
||||
header.style.removeProperty('width');
|
||||
@ -221,16 +236,16 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
|
||||
// add appropriate sticky classes to wrapper so it has
|
||||
// the necessary top/bottom padding to put the sticky header in
|
||||
const listWrapper = list.parentElement; // .mx_LeftPanel2_roomListWrapper
|
||||
const listWrapper = list.parentElement; // .mx_LeftPanel_roomListWrapper
|
||||
if (lastTopHeader) {
|
||||
listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyTop");
|
||||
listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyTop");
|
||||
} else {
|
||||
listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyTop");
|
||||
listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyTop");
|
||||
}
|
||||
if (firstBottomHeader) {
|
||||
listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyBottom");
|
||||
listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyBottom");
|
||||
} else {
|
||||
listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyBottom");
|
||||
listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyBottom");
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +281,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
private onEnter = () => {
|
||||
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile2");
|
||||
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile");
|
||||
if (firstRoom) {
|
||||
firstRoom.click();
|
||||
return true; // to get the field to clear
|
||||
@ -314,7 +329,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
|
||||
private renderHeader(): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_LeftPanel2_userHeader">
|
||||
<div className="mx_LeftPanel_userHeader">
|
||||
<UserMenu isMinimized={this.props.isMinimized} />
|
||||
</div>
|
||||
);
|
||||
@ -324,10 +339,13 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
if (this.state.showBreadcrumbs && !this.props.isMinimized) {
|
||||
return (
|
||||
<IndicatorScrollbar
|
||||
className="mx_LeftPanel2_breadcrumbsContainer mx_AutoHideScrollbar"
|
||||
className="mx_LeftPanel_breadcrumbsContainer mx_AutoHideScrollbar"
|
||||
verticalScrollsHorizontally={true}
|
||||
// Firefox sometimes makes this element focusable due to
|
||||
// overflow:scroll;, so force it out of tab order.
|
||||
tabIndex={-1}
|
||||
>
|
||||
<RoomBreadcrumbs2 />
|
||||
<RoomBreadcrumbs />
|
||||
</IndicatorScrollbar>
|
||||
);
|
||||
}
|
||||
@ -336,19 +354,18 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
private renderSearchExplore(): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
className="mx_LeftPanel2_filterContainer"
|
||||
className="mx_LeftPanel_filterContainer"
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
<RoomSearch
|
||||
onQueryUpdate={this.onSearch}
|
||||
isMinimized={this.props.isMinimized}
|
||||
onVerticalArrow={this.onKeyDown}
|
||||
onEnter={this.onEnter}
|
||||
/>
|
||||
<AccessibleButton
|
||||
className="mx_LeftPanel2_exploreButton"
|
||||
<AccessibleTooltipButton
|
||||
className="mx_LeftPanel_exploreButton"
|
||||
onClick={this.onExplore}
|
||||
title={_t("Explore rooms")}
|
||||
/>
|
||||
@ -358,16 +375,16 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const tagPanel = !this.state.showTagPanel ? null : (
|
||||
<div className="mx_LeftPanel2_tagPanelContainer">
|
||||
<div className="mx_LeftPanel_tagPanelContainer">
|
||||
<TagPanel/>
|
||||
{SettingsStore.isFeatureEnabled("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const roomList = <RoomList2
|
||||
const roomList = <RoomList
|
||||
onKeyDown={this.onKeyDown}
|
||||
resizeNotifier={null}
|
||||
collapsed={false}
|
||||
searchFilter={this.state.searchFilter}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
isMinimized={this.props.isMinimized}
|
||||
@ -375,24 +392,24 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||
/>;
|
||||
|
||||
const containerClasses = classNames({
|
||||
"mx_LeftPanel2": true,
|
||||
"mx_LeftPanel2_hasTagPanel": !!tagPanel,
|
||||
"mx_LeftPanel2_minimized": this.props.isMinimized,
|
||||
"mx_LeftPanel": true,
|
||||
"mx_LeftPanel_hasTagPanel": !!tagPanel,
|
||||
"mx_LeftPanel_minimized": this.props.isMinimized,
|
||||
});
|
||||
|
||||
const roomListClasses = classNames(
|
||||
"mx_LeftPanel2_actualRoomListContainer",
|
||||
"mx_LeftPanel_actualRoomListContainer",
|
||||
"mx_AutoHideScrollbar",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{tagPanel}
|
||||
<aside className="mx_LeftPanel2_roomListContainer">
|
||||
<aside className="mx_LeftPanel_roomListContainer">
|
||||
{this.renderHeader()}
|
||||
{this.renderSearchExplore()}
|
||||
{this.renderBreadcrumbs()}
|
||||
<div className="mx_LeftPanel2_roomListWrapper">
|
||||
<div className="mx_LeftPanel_roomListWrapper">
|
||||
<div
|
||||
className={roomListClasses}
|
||||
onScroll={this.onScroll}
|
@ -40,7 +40,6 @@ import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
||||
import HomePage from "./HomePage";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy";
|
||||
import { DefaultTagID } from "../../stores/room-list/models";
|
||||
import {
|
||||
showToast as showSetPasswordToast,
|
||||
@ -51,9 +50,10 @@ import {
|
||||
hideToast as hideServerLimitToast
|
||||
} from "../../toasts/ServerLimitToast";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import LeftPanel2 from "./LeftPanel2";
|
||||
import LeftPanel from "./LeftPanel";
|
||||
import CallContainer from '../views/voip/CallContainer';
|
||||
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
|
||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
@ -308,8 +308,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
onRoomStateEvents = (ev, state) => {
|
||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||
if (roomLists[DefaultTagID.ServerNotice] && roomLists[DefaultTagID.ServerNotice].some(r => r.roomId === ev.getRoomId())) {
|
||||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||
if (serverNoticeList && serverNoticeList.some(r => r.roomId === ev.getRoomId())) {
|
||||
this._updateServerNoticeEvents();
|
||||
}
|
||||
};
|
||||
@ -328,11 +328,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
_updateServerNoticeEvents = async () => {
|
||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||
if (!roomLists[DefaultTagID.ServerNotice]) return [];
|
||||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||
if (!serverNoticeList) return [];
|
||||
|
||||
const events = [];
|
||||
for (const room of roomLists[DefaultTagID.ServerNotice]) {
|
||||
for (const room of serverNoticeList) {
|
||||
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||
|
||||
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
||||
@ -607,7 +607,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
||||
const RoomView = sdk.getComponent('structures.RoomView');
|
||||
const UserView = sdk.getComponent('structures.UserView');
|
||||
const GroupView = sdk.getComponent('structures.GroupView');
|
||||
@ -661,21 +660,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||
}
|
||||
|
||||
let leftPanel = (
|
||||
const leftPanel = (
|
||||
<LeftPanel
|
||||
isMinimized={this.props.collapseLhs || false}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapseLhs || false}
|
||||
disabled={this.props.leftDisabled}
|
||||
/>
|
||||
);
|
||||
if (SettingsStore.getValue("feature_new_room_list")) {
|
||||
leftPanel = (
|
||||
<LeftPanel2
|
||||
isMinimized={this.props.collapseLhs || false}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||
|
@ -16,77 +16,24 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ResizeHandle from '../views/elements/ResizeHandle';
|
||||
import {Resizer, FixedDistributor} from '../../resizer';
|
||||
import { Resizable } from 're-resizable';
|
||||
|
||||
export default class MainSplit extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._setResizeContainerRef = this._setResizeContainerRef.bind(this);
|
||||
this._onResized = this._onResized.bind(this);
|
||||
_onResized = (event, direction, refToElement, delta) => {
|
||||
window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width);
|
||||
}
|
||||
|
||||
_onResized(size) {
|
||||
window.localStorage.setItem("mx_rhs_size", size);
|
||||
if (this.props.resizeNotifier) {
|
||||
this.props.resizeNotifier.notifyRightHandleResized();
|
||||
}
|
||||
}
|
||||
_loadSidePanelSize() {
|
||||
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
||||
|
||||
_createResizer() {
|
||||
const classNames = {
|
||||
handle: "mx_ResizeHandle",
|
||||
vertical: "mx_ResizeHandle_vertical",
|
||||
reverse: "mx_ResizeHandle_reverse",
|
||||
};
|
||||
const resizer = new Resizer(
|
||||
this.resizeContainer,
|
||||
FixedDistributor,
|
||||
{onResized: this._onResized},
|
||||
);
|
||||
resizer.setClassNames(classNames);
|
||||
let rhsSize = window.localStorage.getItem("mx_rhs_size");
|
||||
if (rhsSize !== null) {
|
||||
rhsSize = parseInt(rhsSize, 10);
|
||||
} else {
|
||||
if (isNaN(rhsSize)) {
|
||||
rhsSize = 350;
|
||||
}
|
||||
resizer.forHandleAt(0).resize(rhsSize);
|
||||
|
||||
resizer.attach();
|
||||
this.resizer = resizer;
|
||||
}
|
||||
|
||||
_setResizeContainerRef(div) {
|
||||
this.resizeContainer = div;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.panel) {
|
||||
this._createResizer();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.resizer) {
|
||||
this.resizer.detach();
|
||||
this.resizer = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const wasPanelSet = this.props.panel && !prevProps.panel;
|
||||
const wasPanelCleared = !this.props.panel && prevProps.panel;
|
||||
|
||||
if (this.resizeContainer && wasPanelSet) {
|
||||
// The resizer can only be created when **both** expanded and the panel is
|
||||
// set. Once both are true, the container ref will mount, which is required
|
||||
// for the resizer to work.
|
||||
this._createResizer();
|
||||
} else if (this.resizer && wasPanelCleared) {
|
||||
this.resizer.detach();
|
||||
this.resizer = null;
|
||||
}
|
||||
return {
|
||||
height: "100%",
|
||||
width: rhsSize,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -97,13 +44,29 @@ export default class MainSplit extends React.Component {
|
||||
|
||||
let children;
|
||||
if (hasResizer) {
|
||||
children = <React.Fragment>
|
||||
<ResizeHandle reverse={true} />
|
||||
children = <Resizable
|
||||
defaultSize={this._loadSidePanelSize()}
|
||||
minWidth={264}
|
||||
maxWidth="50%"
|
||||
enable={{
|
||||
top: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
left: true,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}
|
||||
onResizeStop={this._onResized}
|
||||
className="mx_RightPanel_ResizeWrapper"
|
||||
handleClasses={{left: "mx_RightPanel_ResizeHandle"}}
|
||||
>
|
||||
{ panelView }
|
||||
</React.Fragment>;
|
||||
</Resizable>;
|
||||
}
|
||||
|
||||
return <div className="mx_MainSplit" ref={hasResizer ? this._setResizeContainerRef : undefined}>
|
||||
return <div className="mx_MainSplit">
|
||||
{ bodyView }
|
||||
{ children }
|
||||
</div>;
|
||||
|
@ -58,7 +58,6 @@ import { messageForSyncError } from '../../utils/ErrorUtils';
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
||||
import DMRoomMap from '../../utils/DMRoomMap';
|
||||
import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||
@ -75,6 +74,7 @@ import {
|
||||
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
export enum Views {
|
||||
@ -1844,21 +1844,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
|
||||
updateStatusIndicator(state: string, prevState: string) {
|
||||
// only count visible rooms to not torment the user with notification counts in rooms they can't see
|
||||
// it will include highlights from the previous version of the room internally
|
||||
const notifCount = countRoomsWithNotif(MatrixClientPeg.get().getVisibleRooms()).count;
|
||||
const notificationState = RoomNotificationStateStore.instance.globalState;
|
||||
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
|
||||
|
||||
if (PlatformPeg.get()) {
|
||||
PlatformPeg.get().setErrorStatus(state === 'ERROR');
|
||||
PlatformPeg.get().setNotificationCount(notifCount);
|
||||
PlatformPeg.get().setNotificationCount(numUnreadRooms);
|
||||
}
|
||||
|
||||
this.subTitleStatus = '';
|
||||
if (state === "ERROR") {
|
||||
this.subTitleStatus += `[${_t("Offline")}] `;
|
||||
}
|
||||
if (notifCount > 0) {
|
||||
this.subTitleStatus += `[${notifCount}]`;
|
||||
if (numUnreadRooms > 0) {
|
||||
this.subTitleStatus += `[${numUnreadRooms}]`;
|
||||
}
|
||||
|
||||
this.setPageSubtitle();
|
||||
|
@ -346,9 +346,9 @@ export default class MessagePanel extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
_isUnmounting() {
|
||||
_isUnmounting = () => {
|
||||
return !this._isMounted;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Implement granular (per-room) hide options
|
||||
_shouldShowEvent(mxEv) {
|
||||
@ -571,12 +571,10 @@ export default class MessagePanel extends React.Component {
|
||||
|
||||
const readReceipts = this._readReceiptsByEvent[eventId];
|
||||
|
||||
// Dev note: `this._isUnmounting.bind(this)` is important - it ensures that
|
||||
// the function is run in the context of this class and not EventTile, therefore
|
||||
// ensuring the right `this._mounted` variable is used by read receipts (which
|
||||
// don't update their position if we, the MessagePanel, is unmounting).
|
||||
// use txnId as key if available so that we don't remount during sending
|
||||
ret.push(
|
||||
<li key={eventId}
|
||||
<li
|
||||
key={mxEv.getTxnId() || eventId}
|
||||
ref={this._collectEventNode.bind(this, eventId)}
|
||||
data-scroll-tokens={scrollToken}
|
||||
>
|
||||
@ -590,7 +588,7 @@ export default class MessagePanel extends React.Component {
|
||||
readReceipts={readReceipts}
|
||||
readReceiptMap={this._readReceiptMap}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
checkUnmounting={this._isUnmounting.bind(this)}
|
||||
checkUnmounting={this._isUnmounting}
|
||||
eventSendStatus={mxEv.getAssociatedStatus()}
|
||||
tileShape={this.props.tileShape}
|
||||
isTwelveHour={this.props.isTwelveHour}
|
||||
|
@ -24,9 +24,10 @@ import { throttle } from 'lodash';
|
||||
import { Key } from "../../Keyboard";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||
|
||||
interface IProps {
|
||||
onQueryUpdate: (newQuery: string) => void;
|
||||
isMinimized: boolean;
|
||||
onVerticalArrow(ev: React.KeyboardEvent): void;
|
||||
onEnter(ev: React.KeyboardEvent): boolean;
|
||||
@ -40,6 +41,7 @@ interface IState {
|
||||
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
||||
private searchFilter: NameFilterCondition = new NameFilterCondition();
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
@ -52,6 +54,21 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
|
||||
if (prevState.query !== this.state.query) {
|
||||
const hadSearch = !!this.searchFilter.search.trim();
|
||||
const haveSearch = !!this.state.query.trim();
|
||||
this.searchFilter.search = this.state.query;
|
||||
if (!hadSearch && haveSearch) {
|
||||
// started a new filter - add the condition
|
||||
RoomListStore.instance.addFilter(this.searchFilter);
|
||||
} else if (hadSearch && !haveSearch) {
|
||||
// cleared a filter - remove the condition
|
||||
RoomListStore.instance.removeFilter(this.searchFilter);
|
||||
} // else the filter hasn't changed enough for us to care here
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
}
|
||||
@ -78,19 +95,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||
private onChange = () => {
|
||||
if (!this.inputRef.current) return;
|
||||
this.setState({query: this.inputRef.current.value});
|
||||
this.onSearchUpdated();
|
||||
};
|
||||
|
||||
// it wants this at the top of the file, but we know better
|
||||
// tslint:disable-next-line
|
||||
private onSearchUpdated = throttle(
|
||||
() => {
|
||||
// We can't use the state variable because it can lag behind the input.
|
||||
// The lag is most obvious when deleting/clearing text with the keyboard.
|
||||
this.props.onQueryUpdate(this.inputRef.current.value);
|
||||
}, 200, {trailing: true, leading: true},
|
||||
);
|
||||
|
||||
private onFocus = (ev: React.FocusEvent<HTMLInputElement>) => {
|
||||
this.setState({focused: true});
|
||||
ev.target.select();
|
||||
|
@ -1,496 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import * as sdk from '../../index';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import * as Unread from '../../Unread';
|
||||
import * as RoomNotifs from '../../RoomNotifs';
|
||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||
import IndicatorScrollbar from './IndicatorScrollbar';
|
||||
import {Key} from '../../Keyboard';
|
||||
import { Group } from 'matrix-js-sdk';
|
||||
import PropTypes from 'prop-types';
|
||||
import RoomTile from "../views/rooms/RoomTile";
|
||||
import LazyRenderList from "../views/elements/LazyRenderList";
|
||||
import {_t} from "../../languageHandler";
|
||||
import {RovingTabIndexWrapper} from "../../accessibility/RovingTabIndex";
|
||||
import {toPx} from "../../utils/units";
|
||||
|
||||
// turn this on for drop & drag console debugging galore
|
||||
const debug = false;
|
||||
|
||||
class RoomTileErrorBoundary extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
// Side effects are not permitted here, so we only update the state so
|
||||
// that the next render shows an error message.
|
||||
return { error };
|
||||
}
|
||||
|
||||
componentDidCatch(error, { componentStack }) {
|
||||
// Browser consoles are better at formatting output when native errors are passed
|
||||
// in their own `console.error` invocation.
|
||||
console.error(error);
|
||||
console.error(
|
||||
"The above error occured while React was rendering the following components:",
|
||||
componentStack,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (<div className="mx_RoomTile mx_RoomTileError">
|
||||
{this.props.roomId}
|
||||
</div>);
|
||||
} else {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class RoomSubList extends React.PureComponent {
|
||||
static displayName = 'RoomSubList';
|
||||
static debug = debug;
|
||||
|
||||
static propTypes = {
|
||||
list: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
tagName: PropTypes.string,
|
||||
addRoomLabel: PropTypes.string,
|
||||
|
||||
// passed through to RoomTile and used to highlight room with `!` regardless of notifications count
|
||||
isInvite: PropTypes.bool,
|
||||
|
||||
startAsHidden: PropTypes.bool,
|
||||
showSpinner: PropTypes.bool, // true to show a spinner if 0 elements when expanded
|
||||
collapsed: PropTypes.bool.isRequired, // is LeftPanel collapsed?
|
||||
onHeaderClick: PropTypes.func,
|
||||
incomingCall: PropTypes.object,
|
||||
extraTiles: PropTypes.arrayOf(PropTypes.node), // extra elements added beneath tiles
|
||||
forceExpand: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onHeaderClick: function() {
|
||||
}, // NOP
|
||||
extraTiles: [],
|
||||
isInvite: false,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return {
|
||||
listLength: props.list.length,
|
||||
scrollTop: props.list.length === state.listLength ? state.scrollTop : 0,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hidden: this.props.startAsHidden || false,
|
||||
// some values to get LazyRenderList starting
|
||||
scrollerHeight: 800,
|
||||
scrollTop: 0,
|
||||
// React 16's getDerivedStateFromProps(props, state) doesn't give the previous props so
|
||||
// we have to store the length of the list here so we can see if it's changed or not...
|
||||
listLength: null,
|
||||
};
|
||||
|
||||
this._header = createRef();
|
||||
this._subList = createRef();
|
||||
this._scroller = createRef();
|
||||
this._headerButton = createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
// The header is collapsible if it is hidden or not stuck
|
||||
// The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
|
||||
isCollapsibleOnClick() {
|
||||
const stuck = this._header.current.dataset.stuck;
|
||||
if (!this.props.forceExpand && (this.state.hidden || stuck === undefined || stuck === "none")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onAction = (payload) => {
|
||||
switch (payload.action) {
|
||||
case 'on_room_read':
|
||||
// XXX: Previously RoomList would forceUpdate whenever on_room_read is dispatched,
|
||||
// but this is no longer true, so we must do it here (and can apply the small
|
||||
// optimisation of checking that we care about the room being read).
|
||||
//
|
||||
// Ultimately we need to transition to a state pushing flow where something
|
||||
// explicitly notifies the components concerned that the notif count for a room
|
||||
// has change (e.g. a Flux store).
|
||||
if (this.props.list.some((r) => r.roomId === payload.roomId)) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'view_room':
|
||||
if (this.state.hidden && !this.props.forceExpand && payload.show_room_tile &&
|
||||
this.props.list.some((r) => r.roomId === payload.room_id)
|
||||
) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
if (this.isCollapsibleOnClick()) {
|
||||
// The header isCollapsible, so the click is to be interpreted as collapse and truncation logic
|
||||
const isHidden = !this.state.hidden;
|
||||
this.setState({hidden: isHidden}, () => {
|
||||
this.props.onHeaderClick(isHidden);
|
||||
});
|
||||
} else {
|
||||
// The header is stuck, so the click is to be interpreted as a scroll to the header
|
||||
this.props.onHeaderClick(this.state.hidden, this._header.current.dataset.originalPosition);
|
||||
}
|
||||
};
|
||||
|
||||
onClick = (ev) => {
|
||||
this.toggle();
|
||||
};
|
||||
|
||||
onHeaderKeyDown = (ev) => {
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_LEFT:
|
||||
// On ARROW_LEFT collapse the room sublist
|
||||
if (!this.state.hidden && !this.props.forceExpand) {
|
||||
this.onClick();
|
||||
}
|
||||
ev.stopPropagation();
|
||||
break;
|
||||
case Key.ARROW_RIGHT: {
|
||||
ev.stopPropagation();
|
||||
if (this.state.hidden && !this.props.forceExpand) {
|
||||
// sublist is collapsed, expand it
|
||||
this.onClick();
|
||||
} else if (!this.props.forceExpand) {
|
||||
// sublist is expanded, go to first room
|
||||
const element = this._subList.current && this._subList.current.querySelector(".mx_RoomTile");
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onKeyDown = (ev) => {
|
||||
switch (ev.key) {
|
||||
// On ARROW_LEFT go to the sublist header
|
||||
case Key.ARROW_LEFT:
|
||||
ev.stopPropagation();
|
||||
this._headerButton.current.focus();
|
||||
break;
|
||||
// Consume ARROW_RIGHT so it doesn't cause focus to get sent to composer
|
||||
case Key.ARROW_RIGHT:
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
onRoomTileClick = (roomId, ev) => {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
show_room_tile: true, // to make sure the room gets scrolled into view
|
||||
room_id: roomId,
|
||||
clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)),
|
||||
});
|
||||
};
|
||||
|
||||
_updateSubListCount = () => {
|
||||
// Force an update by setting the state to the current state
|
||||
// Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
|
||||
// method is honoured
|
||||
this.setState(this.state);
|
||||
};
|
||||
|
||||
makeRoomTile = (room) => {
|
||||
return <RoomTileErrorBoundary roomId={room.roomId}><RoomTile
|
||||
room={room}
|
||||
roomSubList={this}
|
||||
tagName={this.props.tagName}
|
||||
key={room.roomId}
|
||||
collapsed={this.props.collapsed || false}
|
||||
unread={Unread.doesRoomHaveUnreadMessages(room)}
|
||||
highlight={this.props.isInvite || RoomNotifs.getUnreadNotificationCount(room, 'highlight') > 0}
|
||||
notificationCount={RoomNotifs.getUnreadNotificationCount(room)}
|
||||
isInvite={this.props.isInvite}
|
||||
refreshSubList={this._updateSubListCount}
|
||||
incomingCall={null}
|
||||
onClick={this.onRoomTileClick}
|
||||
/></RoomTileErrorBoundary>;
|
||||
};
|
||||
|
||||
_onNotifBadgeClick = (e) => {
|
||||
// prevent the roomsublist collapsing
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const room = this.props.list.find(room => RoomNotifs.getRoomHasBadge(room));
|
||||
if (room) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onInviteBadgeClick = (e) => {
|
||||
// prevent the roomsublist collapsing
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// switch to first room in sortedList as that'll be the top of the list for the user
|
||||
if (this.props.list && this.props.list.length > 0) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: this.props.list[0].roomId,
|
||||
});
|
||||
} else if (this.props.extraTiles && this.props.extraTiles.length > 0) {
|
||||
// Group Invites are different in that they are all extra tiles and not rooms
|
||||
// XXX: this is a horrible special case because Group Invite sublist is a hack
|
||||
if (this.props.extraTiles[0].props && this.props.extraTiles[0].props.group instanceof Group) {
|
||||
dis.dispatch({
|
||||
action: 'view_group',
|
||||
group_id: this.props.extraTiles[0].props.group.groupId,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onAddRoom = (e) => {
|
||||
e.stopPropagation();
|
||||
if (this.props.onAddRoom) this.props.onAddRoom();
|
||||
};
|
||||
|
||||
_getHeaderJsx(isCollapsed) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const AccessibleTooltipButton = sdk.getComponent('elements.AccessibleTooltipButton');
|
||||
const subListNotifications = !this.props.isInvite ?
|
||||
RoomNotifs.aggregateNotificationCount(this.props.list) :
|
||||
{count: 0, highlight: true};
|
||||
const subListNotifCount = subListNotifications.count;
|
||||
const subListNotifHighlight = subListNotifications.highlight;
|
||||
|
||||
// When collapsed, allow a long hover on the header to show user
|
||||
// the full tag name and room count
|
||||
let title;
|
||||
if (this.props.collapsed) {
|
||||
title = this.props.label;
|
||||
}
|
||||
|
||||
let incomingCall;
|
||||
if (this.props.incomingCall) {
|
||||
// We can assume that if we have an incoming call then it is for this list
|
||||
const IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
|
||||
incomingCall =
|
||||
<IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={this.props.incomingCall} />;
|
||||
}
|
||||
|
||||
const len = this.props.list.length + this.props.extraTiles.length;
|
||||
let chevron;
|
||||
if (len) {
|
||||
const chevronClasses = classNames({
|
||||
'mx_RoomSubList_chevron': true,
|
||||
'mx_RoomSubList_chevronRight': isCollapsed,
|
||||
'mx_RoomSubList_chevronDown': !isCollapsed,
|
||||
});
|
||||
chevron = (<div className={chevronClasses} />);
|
||||
}
|
||||
|
||||
return <RovingTabIndexWrapper inputRef={this._headerButton}>
|
||||
{({onFocus, isActive, ref}) => {
|
||||
const tabIndex = isActive ? 0 : -1;
|
||||
|
||||
let badge;
|
||||
if (!this.props.collapsed) {
|
||||
const badgeClasses = classNames({
|
||||
'mx_RoomSubList_badge': true,
|
||||
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
|
||||
});
|
||||
// Wrap the contents in a div and apply styles to the child div so that the browser default outline works
|
||||
if (subListNotifCount > 0) {
|
||||
badge = (
|
||||
<AccessibleButton
|
||||
tabIndex={tabIndex}
|
||||
className={badgeClasses}
|
||||
onClick={this._onNotifBadgeClick}
|
||||
aria-label={_t("Jump to first unread room.")}
|
||||
>
|
||||
<div>
|
||||
{ FormattingUtils.formatCount(subListNotifCount) }
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
} else if (this.props.isInvite && this.props.list.length) {
|
||||
// no notifications but highlight anyway because this is an invite badge
|
||||
badge = (
|
||||
<AccessibleButton
|
||||
tabIndex={tabIndex}
|
||||
className={badgeClasses}
|
||||
onClick={this._onInviteBadgeClick}
|
||||
aria-label={_t("Jump to first invite.")}
|
||||
>
|
||||
<div>
|
||||
{ this.props.list.length }
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let addRoomButton;
|
||||
if (this.props.onAddRoom) {
|
||||
addRoomButton = (
|
||||
<AccessibleTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={this.onAddRoom}
|
||||
className="mx_RoomSubList_addRoom"
|
||||
title={this.props.addRoomLabel || _t("Add room")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomSubList_labelContainer" title={title} ref={this._header} onKeyDown={this.onHeaderKeyDown}>
|
||||
<AccessibleButton
|
||||
onFocus={onFocus}
|
||||
tabIndex={tabIndex}
|
||||
inputRef={ref}
|
||||
onClick={this.onClick}
|
||||
className="mx_RoomSubList_label"
|
||||
aria-expanded={!isCollapsed}
|
||||
role="treeitem"
|
||||
aria-level="1"
|
||||
>
|
||||
{ chevron }
|
||||
<span>{this.props.label}</span>
|
||||
{ incomingCall }
|
||||
</AccessibleButton>
|
||||
{ badge }
|
||||
{ addRoomButton }
|
||||
</div>
|
||||
);
|
||||
} }
|
||||
</RovingTabIndexWrapper>;
|
||||
}
|
||||
|
||||
checkOverflow = () => {
|
||||
if (this._scroller.current) {
|
||||
this._scroller.current.checkOverflow();
|
||||
}
|
||||
};
|
||||
|
||||
setHeight = (height) => {
|
||||
if (this._subList.current) {
|
||||
this._subList.current.style.height = toPx(height);
|
||||
}
|
||||
this._updateLazyRenderHeight(height);
|
||||
};
|
||||
|
||||
_updateLazyRenderHeight(height) {
|
||||
this.setState({scrollerHeight: height});
|
||||
}
|
||||
|
||||
_onScroll = () => {
|
||||
this.setState({scrollTop: this._scroller.current.getScrollTop()});
|
||||
};
|
||||
|
||||
_canUseLazyListRendering() {
|
||||
// for now disable lazy rendering as they are already rendered tiles
|
||||
// not rooms like props.list we pass to LazyRenderList
|
||||
return !this.props.extraTiles || !this.props.extraTiles.length;
|
||||
}
|
||||
|
||||
render() {
|
||||
const len = this.props.list.length + this.props.extraTiles.length;
|
||||
const isCollapsed = this.state.hidden && !this.props.forceExpand;
|
||||
|
||||
const subListClasses = classNames({
|
||||
"mx_RoomSubList": true,
|
||||
"mx_RoomSubList_hidden": len && isCollapsed,
|
||||
"mx_RoomSubList_nonEmpty": len && !isCollapsed,
|
||||
});
|
||||
|
||||
let content;
|
||||
if (len) {
|
||||
if (isCollapsed) {
|
||||
// no body
|
||||
} else if (this._canUseLazyListRendering()) {
|
||||
content = (
|
||||
<IndicatorScrollbar ref={this._scroller} className="mx_RoomSubList_scroll" onScroll={this._onScroll}>
|
||||
<LazyRenderList
|
||||
scrollTop={this.state.scrollTop }
|
||||
height={ this.state.scrollerHeight }
|
||||
renderItem={ this.makeRoomTile }
|
||||
itemHeight={34}
|
||||
items={ this.props.list } />
|
||||
</IndicatorScrollbar>
|
||||
);
|
||||
} else {
|
||||
const roomTiles = this.props.list.map(r => this.makeRoomTile(r));
|
||||
const tiles = roomTiles.concat(this.props.extraTiles);
|
||||
content = (
|
||||
<IndicatorScrollbar ref={this._scroller} className="mx_RoomSubList_scroll" onScroll={this._onScroll}>
|
||||
{ tiles }
|
||||
</IndicatorScrollbar>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (this.props.showSpinner && !isCollapsed) {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
content = <Loader />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this._subList}
|
||||
className={subListClasses}
|
||||
role="group"
|
||||
aria-label={this.props.label}
|
||||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
{ this._getHeaderJsx(isCollapsed) }
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -648,7 +648,9 @@ export default createReactClass({
|
||||
|
||||
if (scrollState.stuckAtBottom) {
|
||||
const sn = this._getScrollNode();
|
||||
sn.scrollTop = sn.scrollHeight;
|
||||
if (sn.scrollTop !== sn.scrollHeight) {
|
||||
sn.scrollTop = sn.scrollHeight;
|
||||
}
|
||||
} else if (scrollState.trackedScrollToken) {
|
||||
const itemlist = this._itemlist.current;
|
||||
const trackedNode = this._getTrackedNode();
|
||||
@ -657,7 +659,10 @@ export default createReactClass({
|
||||
const bottomDiff = newBottomOffset - scrollState.bottomOffset;
|
||||
this._bottomGrowth += bottomDiff;
|
||||
scrollState.bottomOffset = newBottomOffset;
|
||||
itemlist.style.height = `${this._getListHeight()}px`;
|
||||
const newHeight = `${this._getListHeight()}px`;
|
||||
if (itemlist.style.height !== newHeight) {
|
||||
itemlist.style.height = newHeight;
|
||||
}
|
||||
debuglog("balancing height because messages below viewport grew by", bottomDiff);
|
||||
}
|
||||
}
|
||||
@ -694,12 +699,16 @@ export default createReactClass({
|
||||
const height = Math.max(minHeight, contentHeight);
|
||||
this._pages = Math.ceil(height / PAGE_SIZE);
|
||||
this._bottomGrowth = 0;
|
||||
const newHeight = this._getListHeight();
|
||||
const newHeight = `${this._getListHeight()}px`;
|
||||
|
||||
const scrollState = this.scrollState;
|
||||
if (scrollState.stuckAtBottom) {
|
||||
itemlist.style.height = `${newHeight}px`;
|
||||
sn.scrollTop = sn.scrollHeight;
|
||||
if (itemlist.style.height !== newHeight) {
|
||||
itemlist.style.height = newHeight;
|
||||
}
|
||||
if (sn.scrollTop !== sn.scrollHeight){
|
||||
sn.scrollTop = sn.scrollHeight;
|
||||
}
|
||||
debuglog("updateHeight to", newHeight);
|
||||
} else if (scrollState.trackedScrollToken) {
|
||||
const trackedNode = this._getTrackedNode();
|
||||
@ -709,7 +718,9 @@ export default createReactClass({
|
||||
// the currently filled piece of the timeline
|
||||
if (trackedNode) {
|
||||
const oldTop = trackedNode.offsetTop;
|
||||
itemlist.style.height = `${newHeight}px`;
|
||||
if (itemlist.style.height !== newHeight) {
|
||||
itemlist.style.height = newHeight;
|
||||
}
|
||||
const newTop = trackedNode.offsetTop;
|
||||
const topDiff = newTop - oldTop;
|
||||
// important to scroll by a relative amount as
|
||||
|
@ -1,158 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TopLeftMenu from '../views/context_menus/TopLeftMenu';
|
||||
import BaseAvatar from '../views/avatars/BaseAvatar';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import * as Avatar from '../../Avatar';
|
||||
import { _t } from '../../languageHandler';
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
|
||||
const AVATAR_SIZE = 28;
|
||||
|
||||
export default class TopLeftMenuButton extends React.Component {
|
||||
static propTypes = {
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static displayName = 'TopLeftMenuButton';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
menuDisplayed: false,
|
||||
profileInfo: null,
|
||||
};
|
||||
}
|
||||
|
||||
async _getProfileInfo() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
const profileInfo = await cli.getProfileInfo(userId);
|
||||
const avatarUrl = Avatar.avatarUrlForUser(
|
||||
{avatarUrl: profileInfo.avatar_url},
|
||||
AVATAR_SIZE, AVATAR_SIZE, "crop");
|
||||
|
||||
return {
|
||||
userId,
|
||||
name: profileInfo.displayname,
|
||||
avatarUrl,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this._dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
try {
|
||||
const profileInfo = await this._getProfileInfo();
|
||||
this.setState({profileInfo});
|
||||
} catch (ex) {
|
||||
console.log("could not fetch profile");
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
}
|
||||
|
||||
onAction = (payload) => {
|
||||
// For accessibility
|
||||
if (payload.action === Action.ToggleUserMenu) {
|
||||
if (this._buttonRef) this._buttonRef.click();
|
||||
}
|
||||
};
|
||||
|
||||
_getDisplayName() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
return _t("Guest");
|
||||
} else if (this.state.profileInfo) {
|
||||
return this.state.profileInfo.name;
|
||||
} else {
|
||||
return MatrixClientPeg.get().getUserId();
|
||||
}
|
||||
}
|
||||
|
||||
openMenu = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ menuDisplayed: true });
|
||||
};
|
||||
|
||||
closeMenu = () => {
|
||||
this.setState({
|
||||
menuDisplayed: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const cli = MatrixClientPeg.get().getUserId();
|
||||
|
||||
const name = this._getDisplayName();
|
||||
let nameElement;
|
||||
let chevronElement;
|
||||
if (!this.props.collapsed) {
|
||||
nameElement = <div className="mx_TopLeftMenuButton_name">
|
||||
{ name }
|
||||
</div>;
|
||||
chevronElement = <span className="mx_TopLeftMenuButton_chevron" />;
|
||||
}
|
||||
|
||||
let contextMenu;
|
||||
if (this.state.menuDisplayed) {
|
||||
const elementRect = this._buttonRef.getBoundingClientRect();
|
||||
|
||||
contextMenu = (
|
||||
<ContextMenu
|
||||
chevronFace="none"
|
||||
left={elementRect.left}
|
||||
top={elementRect.top + elementRect.height}
|
||||
onFinished={this.closeMenu}
|
||||
>
|
||||
<TopLeftMenu displayName={name} userId={cli} onFinished={this.closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<ContextMenuButton
|
||||
className="mx_TopLeftMenuButton"
|
||||
onClick={this.openMenu}
|
||||
inputRef={(r) => this._buttonRef = r}
|
||||
label={_t("Your profile")}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
>
|
||||
<BaseAvatar
|
||||
idName={MatrixClientPeg.get().getUserId()}
|
||||
name={name}
|
||||
url={this.state.profileInfo && this.state.profileInfo.avatarUrl}
|
||||
width={AVATAR_SIZE}
|
||||
height={AVATAR_SIZE}
|
||||
resizeMethod="crop"
|
||||
/>
|
||||
{ nameElement }
|
||||
{ chevronElement }
|
||||
</ContextMenuButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
}
|
||||
}
|
@ -306,9 +306,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||
|
||||
public render() {
|
||||
const avatarSize = 32; // should match border-radius of the avatar
|
||||
const {body} = document;
|
||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
body.style.setProperty("--avatar-url", `url('${avatarUrl}')`);
|
||||
|
||||
let name = <span className="mx_UserMenu_userName">{OwnProfileStore.instance.displayName}</span>;
|
||||
let buttons = (
|
||||
|
@ -134,7 +134,7 @@ const BaseAvatar = (props: IProps) => {
|
||||
aria-hidden="true" />
|
||||
);
|
||||
|
||||
if (onClick !== null) {
|
||||
if (onClick) {
|
||||
return (
|
||||
<AccessibleButton
|
||||
{...otherProps}
|
||||
@ -162,7 +162,7 @@ const BaseAvatar = (props: IProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (onClick !== null) {
|
||||
if (onClick) {
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classNames("mx_BaseAvatar mx_BaseAvatar_image", className)}
|
||||
@ -196,4 +196,4 @@ const BaseAvatar = (props: IProps) => {
|
||||
};
|
||||
|
||||
export default BaseAvatar;
|
||||
export type BaseAvatarType = React.FC<IProps>;
|
||||
export type BaseAvatarType = React.FC<IProps>;
|
||||
|
@ -44,7 +44,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
notificationState: RoomNotificationStateStore.instance.getRoomState(this.props.room, this.props.tag),
|
||||
notificationState: RoomNotificationStateStore.instance.getRoomState(this.props.room),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'CreateRoomButton',
|
||||
propTypes: {
|
||||
onCreateRoom: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onCreateRoom: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
this.props.onCreateRoom();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<button className="mx_CreateRoomButton" onClick={this.onClick}>{ _t("Create Room") }</button>
|
||||
);
|
||||
},
|
||||
});
|
@ -416,7 +416,7 @@ class RoomStateExplorer extends React.PureComponent {
|
||||
{
|
||||
Array.from(this.roomStateEvents.entries()).map(([eventType, allStateKeys]) => {
|
||||
let onClickFn;
|
||||
if (allStateKeys.size() === 1 && allStateKeys.has("")) {
|
||||
if (allStateKeys.size === 1 && allStateKeys.has("")) {
|
||||
onClickFn = this.onViewSourceClick(allStateKeys.get(""));
|
||||
} else {
|
||||
onClickFn = this.browseEventType(eventType);
|
||||
|
@ -35,8 +35,11 @@ import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../
|
||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy";
|
||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
export const KIND_DM = "dm";
|
||||
export const KIND_INVITE = "invite";
|
||||
@ -346,8 +349,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||
|
||||
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
||||
// room list doesn't tag the room for the DMRoomMap, but does for the room list.
|
||||
const taggedRooms = RoomListStoreTempProxy.getRoomLists();
|
||||
const dmTaggedRooms = taggedRooms[DefaultTagID.DM];
|
||||
const dmTaggedRooms = RoomListStore.instance.orderedLists[DefaultTagID.DM];
|
||||
const myUserId = MatrixClientPeg.get().getUserId();
|
||||
for (const dmRoom of dmTaggedRooms) {
|
||||
const otherMembers = dmRoom.getJoinedMembers().filter(u => u.userId !== myUserId);
|
||||
|
@ -27,7 +27,7 @@ export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Elemen
|
||||
* onClick: (required) Event handler for button activation. Should be
|
||||
* implemented exactly like a normal onClick handler.
|
||||
*/
|
||||
export interface IProps extends React.InputHTMLAttributes<Element> {
|
||||
interface IProps extends React.InputHTMLAttributes<Element> {
|
||||
inputRef?: React.Ref<Element>;
|
||||
element?: string;
|
||||
// The kind of button, similar to how Bootstrap works.
|
||||
@ -118,7 +118,7 @@ export default function AccessibleButton({
|
||||
AccessibleButton.defaultProps = {
|
||||
element: 'div',
|
||||
role: 'button',
|
||||
tabIndex: "0",
|
||||
tabIndex: 0,
|
||||
};
|
||||
|
||||
AccessibleButton.displayName = "AccessibleButton";
|
||||
|
@ -19,10 +19,9 @@ import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import {IProps} from "./AccessibleButton";
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
interface ITooltipProps extends IProps {
|
||||
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||
title: string;
|
||||
tooltip?: React.ReactNode;
|
||||
tooltipClassName?: string;
|
||||
@ -46,7 +45,7 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||
});
|
||||
};
|
||||
|
||||
onMouseOut = () => {
|
||||
onMouseLeave = () => {
|
||||
this.setState({
|
||||
hover: false,
|
||||
});
|
||||
@ -61,7 +60,12 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||
label={tooltip || title}
|
||||
/> : <div />;
|
||||
return (
|
||||
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
|
||||
<AccessibleButton
|
||||
{...props}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
aria-label={title}
|
||||
>
|
||||
{ children }
|
||||
{ tip }
|
||||
</AccessibleButton>
|
||||
|
@ -704,6 +704,7 @@ export default class AppTile extends React.Component {
|
||||
|
||||
_onReloadWidgetClick() {
|
||||
// Reload iframe in this way to avoid cross-origin restrictions
|
||||
// eslint-disable-next-line no-self-assign
|
||||
this._appFrame.current.src = this._appFrame.current.src;
|
||||
}
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import * as sdk from '../../../index';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
const CreateRoomButton = function(props) {
|
||||
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||
return (
|
||||
<ActionButton action="view_create_room"
|
||||
mouseOverAction={props.callout ? "callout_create_room" : null}
|
||||
label={_t("Create new room")}
|
||||
iconPath={require("../../../../res/img/icons-create-room.svg")}
|
||||
size={props.size}
|
||||
tooltip={props.tooltip}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
CreateRoomButton.propTypes = {
|
||||
size: PropTypes.string,
|
||||
tooltip: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default CreateRoomButton;
|
@ -248,6 +248,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||
tooltipClassName={classNames("mx_Field_tooltip", tooltipClassName)}
|
||||
visible={(this.state.focused && this.props.forceTooltipVisible) || this.state.feedbackVisible}
|
||||
label={tooltipContent || this.state.feedback}
|
||||
forceOnRight
|
||||
/>;
|
||||
}
|
||||
|
||||
|
@ -1,336 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const InteractiveTooltipContainerId = "mx_InteractiveTooltip_Container";
|
||||
|
||||
// If the distance from tooltip to window edge is below this value, the tooltip
|
||||
// will flip around to the other side of the target.
|
||||
const MIN_SAFE_DISTANCE_TO_WINDOW_EDGE = 20;
|
||||
|
||||
function getOrCreateContainer() {
|
||||
let container = document.getElementById(InteractiveTooltipContainerId);
|
||||
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.id = InteractiveTooltipContainerId;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function isInRect(x, y, rect) {
|
||||
const { top, right, bottom, left } = rect;
|
||||
return x >= left && x <= right && y >= top && y <= bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the positive slope of the diagonal of the rect.
|
||||
*
|
||||
* @param {DOMRect} rect
|
||||
* @return {integer}
|
||||
*/
|
||||
function getDiagonalSlope(rect) {
|
||||
const { top, right, bottom, left } = rect;
|
||||
return (bottom - top) / (right - left);
|
||||
}
|
||||
|
||||
function isInUpperLeftHalf(x, y, rect) {
|
||||
const { bottom, left } = rect;
|
||||
// Negative slope because Y values grow downwards and for this case, the
|
||||
// diagonal goes from larger to smaller Y values.
|
||||
const diagonalSlope = getDiagonalSlope(rect) * -1;
|
||||
return isInRect(x, y, rect) && (y <= bottom + diagonalSlope * (x - left));
|
||||
}
|
||||
|
||||
function isInLowerRightHalf(x, y, rect) {
|
||||
const { bottom, left } = rect;
|
||||
// Negative slope because Y values grow downwards and for this case, the
|
||||
// diagonal goes from larger to smaller Y values.
|
||||
const diagonalSlope = getDiagonalSlope(rect) * -1;
|
||||
return isInRect(x, y, rect) && (y >= bottom + diagonalSlope * (x - left));
|
||||
}
|
||||
|
||||
function isInUpperRightHalf(x, y, rect) {
|
||||
const { top, left } = rect;
|
||||
// Positive slope because Y values grow downwards and for this case, the
|
||||
// diagonal goes from smaller to larger Y values.
|
||||
const diagonalSlope = getDiagonalSlope(rect) * 1;
|
||||
return isInRect(x, y, rect) && (y <= top + diagonalSlope * (x - left));
|
||||
}
|
||||
|
||||
function isInLowerLeftHalf(x, y, rect) {
|
||||
const { top, left } = rect;
|
||||
// Positive slope because Y values grow downwards and for this case, the
|
||||
// diagonal goes from smaller to larger Y values.
|
||||
const diagonalSlope = getDiagonalSlope(rect) * 1;
|
||||
return isInRect(x, y, rect) && (y >= top + diagonalSlope * (x - left));
|
||||
}
|
||||
|
||||
/*
|
||||
* This style of tooltip takes a "target" element as its child and centers the
|
||||
* tooltip along one edge of the target.
|
||||
*/
|
||||
export default class InteractiveTooltip extends React.Component {
|
||||
static propTypes = {
|
||||
// Content to show in the tooltip
|
||||
content: PropTypes.node.isRequired,
|
||||
// Function to call when visibility of the tooltip changes
|
||||
onVisibilityChange: PropTypes.func,
|
||||
// flag to forcefully hide this tooltip
|
||||
forceHidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
contentRect: null,
|
||||
visible: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// Whenever this passthrough component updates, also render the tooltip
|
||||
// in a separate DOM tree. This allows the tooltip content to participate
|
||||
// the normal React rendering cycle: when this component re-renders, the
|
||||
// tooltip content re-renders.
|
||||
// Once we upgrade to React 16, this could be done a bit more naturally
|
||||
// using the portals feature instead.
|
||||
this.renderTooltip();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
|
||||
collectContentRect = (element) => {
|
||||
// We don't need to clean up when unmounting, so ignore
|
||||
if (!element) return;
|
||||
|
||||
this.setState({
|
||||
contentRect: element.getBoundingClientRect(),
|
||||
});
|
||||
}
|
||||
|
||||
collectTarget = (element) => {
|
||||
this.target = element;
|
||||
}
|
||||
|
||||
canTooltipFitAboveTarget() {
|
||||
const { contentRect } = this.state;
|
||||
const targetRect = this.target.getBoundingClientRect();
|
||||
const targetTop = targetRect.top + window.pageYOffset;
|
||||
return (
|
||||
!contentRect ||
|
||||
(targetTop - contentRect.height > MIN_SAFE_DISTANCE_TO_WINDOW_EDGE)
|
||||
);
|
||||
}
|
||||
|
||||
onMouseMove = (ev) => {
|
||||
const { clientX: x, clientY: y } = ev;
|
||||
const { contentRect } = this.state;
|
||||
const targetRect = this.target.getBoundingClientRect();
|
||||
|
||||
// When moving the mouse from the target to the tooltip, we create a
|
||||
// safe area that includes the tooltip, the target, and the trapezoid
|
||||
// ABCD between them:
|
||||
// ┌───────────┐
|
||||
// │ │
|
||||
// │ │
|
||||
// A └───E───F───┘ B
|
||||
// V
|
||||
// ┌─┐
|
||||
// │ │
|
||||
// C└─┘D
|
||||
//
|
||||
// As long as the mouse remains inside the safe area, the tooltip will
|
||||
// stay open.
|
||||
const buffer = 50;
|
||||
if (isInRect(x, y, targetRect)) {
|
||||
return;
|
||||
}
|
||||
if (this.canTooltipFitAboveTarget()) {
|
||||
const contentRectWithBuffer = {
|
||||
top: contentRect.top - buffer,
|
||||
right: contentRect.right + buffer,
|
||||
bottom: contentRect.bottom,
|
||||
left: contentRect.left - buffer,
|
||||
};
|
||||
const trapezoidLeft = {
|
||||
top: contentRect.bottom,
|
||||
right: targetRect.left,
|
||||
bottom: targetRect.bottom,
|
||||
left: contentRect.left - buffer,
|
||||
};
|
||||
const trapezoidCenter = {
|
||||
top: contentRect.bottom,
|
||||
right: targetRect.right,
|
||||
bottom: targetRect.bottom,
|
||||
left: targetRect.left,
|
||||
};
|
||||
const trapezoidRight = {
|
||||
top: contentRect.bottom,
|
||||
right: contentRect.right + buffer,
|
||||
bottom: targetRect.bottom,
|
||||
left: targetRect.right,
|
||||
};
|
||||
|
||||
if (
|
||||
isInRect(x, y, contentRectWithBuffer) ||
|
||||
isInUpperRightHalf(x, y, trapezoidLeft) ||
|
||||
isInRect(x, y, trapezoidCenter) ||
|
||||
isInUpperLeftHalf(x, y, trapezoidRight)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const contentRectWithBuffer = {
|
||||
top: contentRect.top,
|
||||
right: contentRect.right + buffer,
|
||||
bottom: contentRect.bottom + buffer,
|
||||
left: contentRect.left - buffer,
|
||||
};
|
||||
const trapezoidLeft = {
|
||||
top: targetRect.top,
|
||||
right: targetRect.left,
|
||||
bottom: contentRect.top,
|
||||
left: contentRect.left - buffer,
|
||||
};
|
||||
const trapezoidCenter = {
|
||||
top: targetRect.top,
|
||||
right: targetRect.right,
|
||||
bottom: contentRect.top,
|
||||
left: targetRect.left,
|
||||
};
|
||||
const trapezoidRight = {
|
||||
top: targetRect.top,
|
||||
right: contentRect.right + buffer,
|
||||
bottom: contentRect.top,
|
||||
left: targetRect.right,
|
||||
};
|
||||
|
||||
if (
|
||||
isInRect(x, y, contentRectWithBuffer) ||
|
||||
isInLowerRightHalf(x, y, trapezoidLeft) ||
|
||||
isInRect(x, y, trapezoidCenter) ||
|
||||
isInLowerLeftHalf(x, y, trapezoidRight)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.hideTooltip();
|
||||
}
|
||||
|
||||
onTargetMouseOver = (ev) => {
|
||||
this.showTooltip();
|
||||
}
|
||||
|
||||
showTooltip() {
|
||||
// Don't enter visible state if we haven't collected the target yet
|
||||
if (!this.target) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
visible: true,
|
||||
});
|
||||
if (this.props.onVisibilityChange) {
|
||||
this.props.onVisibilityChange(true);
|
||||
}
|
||||
document.addEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
|
||||
hideTooltip() {
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
if (this.props.onVisibilityChange) {
|
||||
this.props.onVisibilityChange(false);
|
||||
}
|
||||
document.removeEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
|
||||
renderTooltip() {
|
||||
const { contentRect, visible } = this.state;
|
||||
if (this.props.forceHidden === true || !visible) {
|
||||
ReactDOM.render(null, getOrCreateContainer());
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetRect = this.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const targetLeft = targetRect.left + window.pageXOffset;
|
||||
const targetBottom = targetRect.bottom + window.pageYOffset;
|
||||
const targetTop = targetRect.top + window.pageYOffset;
|
||||
|
||||
// Place the tooltip above the target by default. If we find that the
|
||||
// tooltip content would extend past the safe area towards the window
|
||||
// edge, flip around to below the target.
|
||||
const position = {};
|
||||
let chevronFace = null;
|
||||
if (this.canTooltipFitAboveTarget()) {
|
||||
position.bottom = window.innerHeight - targetTop;
|
||||
chevronFace = "bottom";
|
||||
} else {
|
||||
position.top = targetBottom;
|
||||
chevronFace = "top";
|
||||
}
|
||||
|
||||
// Center the tooltip horizontally with the target's center.
|
||||
position.left = targetLeft + targetRect.width / 2;
|
||||
|
||||
const chevron = <div className={"mx_InteractiveTooltip_chevron_" + chevronFace} />;
|
||||
|
||||
const menuClasses = classNames({
|
||||
'mx_InteractiveTooltip': true,
|
||||
'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top',
|
||||
'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom',
|
||||
});
|
||||
|
||||
const menuStyle = {};
|
||||
if (contentRect) {
|
||||
menuStyle.left = `-${contentRect.width / 2}px`;
|
||||
}
|
||||
|
||||
const tooltip = <div className="mx_InteractiveTooltip_wrapper" style={{...position}}>
|
||||
<div className={menuClasses}
|
||||
style={menuStyle}
|
||||
ref={this.collectContentRect}
|
||||
>
|
||||
{chevron}
|
||||
{this.props.content}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
ReactDOM.render(tooltip, getOrCreateContainer());
|
||||
}
|
||||
|
||||
render() {
|
||||
// We use `cloneElement` here to append some props to the child content
|
||||
// without using a wrapper element which could disrupt layout.
|
||||
return React.cloneElement(this.props.children, {
|
||||
ref: this.collectTarget,
|
||||
onMouseOver: this.onTargetMouseOver,
|
||||
});
|
||||
}
|
||||
}
|
@ -17,10 +17,10 @@ limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||
|
||||
export default class ManageIntegsButton extends React.Component {
|
||||
constructor(props) {
|
||||
@ -45,9 +45,8 @@ export default class ManageIntegsButton extends React.Component {
|
||||
render() {
|
||||
let integrationsButton = <div />;
|
||||
if (IntegrationManagers.sharedInstance().hasManager()) {
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
integrationsButton = (
|
||||
<AccessibleButton
|
||||
<AccessibleTooltipButton
|
||||
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
||||
title={_t("Manage Integrations")}
|
||||
onClick={this.onManageIntegrations}
|
||||
|
@ -27,6 +27,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||
import escapeHtml from "escape-html";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
|
||||
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
||||
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
||||
@ -92,7 +93,21 @@ export default class ReplyThread extends React.Component {
|
||||
|
||||
// Part of Replies fallback support
|
||||
static stripHTMLReply(html) {
|
||||
return html.replace(/^<mx-reply>[\s\S]+?<\/mx-reply>/, '');
|
||||
// Sanitize the original HTML for inclusion in <mx-reply>. We allow
|
||||
// any HTML, since the original sender could use special tags that we
|
||||
// don't recognize, but want to pass along to any recipients who do
|
||||
// recognize them -- recipients should be sanitizing before displaying
|
||||
// anyways. However, we sanitize to 1) remove any mx-reply, so that we
|
||||
// don't generate a nested mx-reply, and 2) make sure that the HTML is
|
||||
// properly formatted (e.g. tags are closed where necessary)
|
||||
return sanitizeHtml(
|
||||
html,
|
||||
{
|
||||
allowedTags: false, // false means allow everything
|
||||
allowedAttributes: false,
|
||||
exclusiveFilter: (frame) => frame.tag === "mx-reply",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Part of Replies fallback support
|
||||
@ -102,15 +117,19 @@ export default class ReplyThread extends React.Component {
|
||||
let {body, formatted_body: html} = ev.getContent();
|
||||
if (this.getParentEventId(ev)) {
|
||||
if (body) body = this.stripPlainReply(body);
|
||||
if (html) html = this.stripHTMLReply(html);
|
||||
}
|
||||
|
||||
if (!body) body = ""; // Always ensure we have a body, for reasons.
|
||||
|
||||
// Escape the body to use as HTML below.
|
||||
// We also run a nl2br over the result to fix the fallback representation. We do this
|
||||
// after converting the text to safe HTML to avoid user-provided BR's from being converted.
|
||||
if (!html) html = escapeHtml(body).replace(/\n/g, '<br/>');
|
||||
if (html) {
|
||||
// sanitize the HTML before we put it in an <mx-reply>
|
||||
html = this.stripHTMLReply(html);
|
||||
} else {
|
||||
// Escape the body to use as HTML below.
|
||||
// We also run a nl2br over the result to fix the fallback representation. We do this
|
||||
// after converting the text to safe HTML to avoid user-provided BR's from being converted.
|
||||
html = escapeHtml(body).replace(/\n/g, '<br/>');
|
||||
}
|
||||
|
||||
// dev note: do not rely on `body` being safe for HTML usage below.
|
||||
|
||||
|
@ -29,6 +29,7 @@ import FlairStore from '../../../stores/FlairStore';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
|
||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||
@ -114,7 +115,7 @@ export default createReactClass({
|
||||
this.setState({ hover: true });
|
||||
},
|
||||
|
||||
onMouseOut: function() {
|
||||
onMouseLeave: function() {
|
||||
this.setState({ hover: false });
|
||||
},
|
||||
|
||||
@ -151,11 +152,14 @@ export default createReactClass({
|
||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||
}
|
||||
|
||||
// FIXME: this ought to use AccessibleButton for a11y but that causes onMouseOut/onMouseOver to fire too much
|
||||
const contextButton = this.state.hover || this.props.menuDisplayed ?
|
||||
<div className="mx_TagTile_context_button" onClick={this.openMenu} ref={this.props.contextMenuButtonRef}>
|
||||
<AccessibleButton
|
||||
className="mx_TagTile_context_button"
|
||||
onClick={this.openMenu}
|
||||
inputRef={this.props.contextMenuButtonRef}
|
||||
>
|
||||
{"\u00B7\u00B7\u00B7"}
|
||||
</div> : <div ref={this.props.contextMenuButtonRef} />;
|
||||
</AccessibleButton> : <div ref={this.props.contextMenuButtonRef} />;
|
||||
|
||||
const AccessibleTooltipButton = sdk.getComponent("elements.AccessibleTooltipButton");
|
||||
|
||||
@ -168,7 +172,7 @@ export default createReactClass({
|
||||
<div
|
||||
className="mx_TagTile_avatar"
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseOut={this.onMouseOut}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<BaseAvatar
|
||||
name={name}
|
||||
|
@ -37,7 +37,7 @@ export default class TextWithTooltip extends React.Component {
|
||||
this.setState({hover: true});
|
||||
};
|
||||
|
||||
onMouseOut = () => {
|
||||
onMouseLeave = () => {
|
||||
this.setState({hover: false});
|
||||
};
|
||||
|
||||
@ -45,13 +45,12 @@ export default class TextWithTooltip extends React.Component {
|
||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
|
||||
return (
|
||||
<span onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} className={this.props.class}>
|
||||
<span onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className={this.props.class}>
|
||||
{this.props.children}
|
||||
<Tooltip
|
||||
{this.state.hover && <Tooltip
|
||||
label={this.props.tooltip}
|
||||
visible={this.state.hover}
|
||||
tooltipClassName={this.props.tooltipClass}
|
||||
className={"mx_TextWithTooltip_tooltip"} />
|
||||
className={"mx_TextWithTooltip_tooltip"} /> }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -18,18 +18,15 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, {Component, CSSProperties} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import classNames from 'classnames';
|
||||
import { ViewTooltipPayload } from '../../../dispatcher/payloads/ViewTooltipPayload';
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
|
||||
const MIN_TOOLTIP_HEIGHT = 25;
|
||||
|
||||
interface IProps {
|
||||
// Class applied to the element used to position the tooltip
|
||||
className: string;
|
||||
className?: string;
|
||||
// Class applied to the tooltip itself
|
||||
tooltipClassName?: string;
|
||||
// Whether the tooltip is visible or hidden.
|
||||
@ -38,6 +35,7 @@ interface IProps {
|
||||
visible?: boolean;
|
||||
// the react element to put into the tooltip
|
||||
label: React.ReactNode;
|
||||
forceOnRight?: boolean;
|
||||
}
|
||||
|
||||
export default class Tooltip extends React.Component<IProps> {
|
||||
@ -68,18 +66,12 @@ export default class Tooltip extends React.Component<IProps> {
|
||||
|
||||
// Remove the wrapper element, as the tooltip has finished using it
|
||||
public componentWillUnmount() {
|
||||
dis.dispatch<ViewTooltipPayload>({
|
||||
action: Action.ViewTooltip,
|
||||
tooltip: null,
|
||||
parent: null,
|
||||
});
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
||||
document.body.removeChild(this.tooltipContainer);
|
||||
window.removeEventListener('scroll', this.renderTooltip, true);
|
||||
}
|
||||
|
||||
private updatePosition(style: {[key: string]: any}) {
|
||||
private updatePosition(style: CSSProperties) {
|
||||
const parentBox = this.parent.getBoundingClientRect();
|
||||
let offset = 0;
|
||||
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
|
||||
@ -89,8 +81,14 @@ export default class Tooltip extends React.Component<IProps> {
|
||||
// we need so that we're still centered.
|
||||
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
||||
}
|
||||
|
||||
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
|
||||
style.left = 6 + parentBox.right + window.pageXOffset;
|
||||
if (!this.props.forceOnRight && parentBox.right > window.innerWidth / 2) {
|
||||
style.right = window.innerWidth - parentBox.right - window.pageXOffset - 8;
|
||||
} else {
|
||||
style.left = parentBox.right + window.pageXOffset + 6;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
@ -99,7 +97,6 @@ export default class Tooltip extends React.Component<IProps> {
|
||||
// positioned, also taking into account any window zoom
|
||||
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
||||
// tooltips chevron
|
||||
const parent = ReactDOM.findDOMNode(this).parentNode as Element;
|
||||
const style = this.updatePosition({});
|
||||
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
||||
// if it is not meant to be visible on first mount.
|
||||
@ -119,19 +116,12 @@ export default class Tooltip extends React.Component<IProps> {
|
||||
|
||||
// Render the tooltip manually, as we wish it not to be rendered within the parent
|
||||
this.tooltip = ReactDOM.render<Element>(tooltip, this.tooltipContainer);
|
||||
|
||||
// Tell the roomlist about us so it can manipulate us if it wishes
|
||||
dis.dispatch<ViewTooltipPayload>({
|
||||
action: Action.ViewTooltip,
|
||||
tooltip: this.tooltip,
|
||||
parent: parent,
|
||||
});
|
||||
};
|
||||
|
||||
public render() {
|
||||
// Render a placeholder
|
||||
return (
|
||||
<div className={this.props.className} >
|
||||
<div className={this.props.className}>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export default createReactClass({
|
||||
});
|
||||
},
|
||||
|
||||
onMouseOut: function() {
|
||||
onMouseLeave: function() {
|
||||
this.setState({
|
||||
hover: false,
|
||||
});
|
||||
@ -48,7 +48,7 @@ export default createReactClass({
|
||||
label={this.props.helpText}
|
||||
/> : <div />;
|
||||
return (
|
||||
<div className="mx_TooltipButton" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} >
|
||||
<div className="mx_TooltipButton" onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
|
||||
?
|
||||
{ tip }
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user