Merge branch 'develop' into uhoreg/keytar_logging
This commit is contained in:
commit
0cf10bb69a
251 changed files with 9427 additions and 10889 deletions
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')), '..');
|
const matrixJsSdkPath = path.join(path.dirname(require.resolve('matrix-js-sdk')), '..');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
extends: ["matrix-org", "matrix-org/react-legacy"],
|
||||||
parser: "babel-eslint",
|
parser: "babel-eslint",
|
||||||
extends: [matrixJsSdkPath + "/.eslintrc.js"],
|
|
||||||
plugins: [
|
env: {
|
||||||
"react",
|
browser: true,
|
||||||
"react-hooks",
|
node: true,
|
||||||
"flowtype",
|
},
|
||||||
"babel"
|
|
||||||
],
|
|
||||||
globals: {
|
globals: {
|
||||||
LANGUAGES_FILE: "readonly",
|
LANGUAGES_FILE: "readonly",
|
||||||
},
|
},
|
||||||
env: {
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
legacyDecorators: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
rules: {
|
||||||
// eslint's built in no-invalid-this rule breaks with class properties
|
// Things we do that break the ideal style
|
||||||
"no-invalid-this": "off",
|
"no-constant-condition": "off",
|
||||||
// so we replace it with a version that is class property aware
|
"prefer-promise-reject-errors": "off",
|
||||||
"babel/no-invalid-this": "error",
|
"no-async-promise-executor": "off",
|
||||||
|
"quotes": "off",
|
||||||
// We appear to follow this most of the time, so let's enforce it instead
|
"indent": "off",
|
||||||
// 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
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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)
|
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)
|
[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",
|
"name": "matrix-react-sdk",
|
||||||
"version": "2.10.1",
|
"version": "3.0.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -45,126 +45,127 @@
|
||||||
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
|
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && yarn start:all",
|
||||||
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"",
|
"start:all": "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\"",
|
"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: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:types": "tsc --noEmit --jsx react",
|
||||||
"lint:style": "stylelint 'res/css/**/*.scss'",
|
"lint:style": "stylelint 'res/css/**/*.scss'",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080"
|
"test:e2e": "./test/end-to-end-tests/run.sh --riot-url http://localhost:8080"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.8.3",
|
"@babel/runtime": "^7.10.5",
|
||||||
"await-lock": "^2.0.1",
|
"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-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.2.6",
|
||||||
"commonmark": "^0.28.1",
|
"commonmark": "^0.29.1",
|
||||||
"counterpart": "^0.18.0",
|
"counterpart": "^0.18.6",
|
||||||
"create-react-class": "^15.6.0",
|
"create-react-class": "^15.6.3",
|
||||||
"diff-dom": "^4.1.3",
|
"diff-dom": "^4.1.6",
|
||||||
"diff-match-patch": "^1.0.4",
|
"diff-match-patch": "^1.0.5",
|
||||||
"emojibase-data": "^5.0.1",
|
"emojibase-data": "^5.0.1",
|
||||||
"emojibase-regex": "^4.0.1",
|
"emojibase-regex": "^4.0.1",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.8",
|
||||||
"filesize": "3.5.6",
|
"filesize": "3.6.1",
|
||||||
"flux": "2.1.1",
|
"flux": "2.1.1",
|
||||||
"focus-visible": "^5.0.2",
|
"focus-visible": "^5.1.0",
|
||||||
"fuse.js": "^2.2.0",
|
"fuse.js": "^2.7.4",
|
||||||
"gfm.css": "^1.1.1",
|
"gfm.css": "^1.1.2",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"highlight.js": "^9.15.8",
|
"highlight.js": "^10.1.2",
|
||||||
"html-entities": "^1.2.1",
|
"html-entities": "^1.3.1",
|
||||||
"is-ip": "^2.0.0",
|
"is-ip": "^2.0.0",
|
||||||
"linkifyjs": "^2.1.6",
|
"linkifyjs": "^2.1.9",
|
||||||
"lodash": "^4.17.14",
|
"lodash": "^4.17.19",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.5",
|
||||||
"pako": "^1.0.5",
|
"pako": "^1.0.11",
|
||||||
"parse5": "^5.1.1",
|
"parse5": "^5.1.1",
|
||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
"project-name-generator": "^2.1.7",
|
"project-name-generator": "^2.1.7",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.7.2",
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
"qs": "^6.6.0",
|
"qs": "^6.9.4",
|
||||||
"re-resizable": "^6.5.2",
|
"re-resizable": "^6.5.4",
|
||||||
"react": "^16.9.0",
|
"react": "^16.13.1",
|
||||||
"react-beautiful-dnd": "^4.0.1",
|
"react-beautiful-dnd": "^4.0.1",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.13.1",
|
||||||
"react-focus-lock": "^2.2.1",
|
"react-focus-lock": "^2.4.1",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"resize-observer-polyfill": "^1.5.0",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"sanitize-html": "^1.18.4",
|
"sanitize-html": "^1.27.1",
|
||||||
"text-encoding-utf-8": "^1.0.1",
|
"text-encoding-utf-8": "^1.0.2",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"velocity-animate": "^1.5.2",
|
"velocity-animate": "^1.5.2",
|
||||||
"what-input": "^5.2.6",
|
"what-input": "^5.2.10",
|
||||||
"zxcvbn": "^4.4.2"
|
"zxcvbn": "^4.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.7.5",
|
"@babel/cli": "^7.10.5",
|
||||||
"@babel/core": "^7.7.5",
|
"@babel/core": "^7.10.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.7.4",
|
"@babel/plugin-proposal-export-default-from": "^7.10.4",
|
||||||
"@babel/plugin-proposal-numeric-separator": "^7.7.4",
|
"@babel/plugin-proposal-numeric-separator": "^7.10.4",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
|
"@babel/plugin-proposal-object-rest-spread": "^7.10.4",
|
||||||
"@babel/plugin-transform-flow-comments": "^7.7.4",
|
"@babel/plugin-transform-flow-comments": "^7.10.4",
|
||||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
"@babel/plugin-transform-runtime": "^7.10.5",
|
||||||
"@babel/preset-env": "^7.7.6",
|
"@babel/preset-env": "^7.10.4",
|
||||||
"@babel/preset-flow": "^7.7.4",
|
"@babel/preset-flow": "^7.10.4",
|
||||||
"@babel/preset-react": "^7.7.4",
|
"@babel/preset-react": "^7.10.4",
|
||||||
"@babel/preset-typescript": "^7.7.4",
|
"@babel/preset-typescript": "^7.10.4",
|
||||||
"@babel/register": "^7.7.4",
|
"@babel/register": "^7.10.5",
|
||||||
"@peculiar/webcrypto": "^1.0.22",
|
"@peculiar/webcrypto": "^1.1.2",
|
||||||
"@types/classnames": "^2.2.10",
|
"@types/classnames": "^2.2.10",
|
||||||
"@types/counterpart": "^0.18.1",
|
"@types/counterpart": "^0.18.1",
|
||||||
"@types/flux": "^3.1.9",
|
"@types/flux": "^3.1.9",
|
||||||
"@types/linkifyjs": "^2.1.3",
|
"@types/linkifyjs": "^2.1.3",
|
||||||
"@types/lodash": "^4.14.152",
|
"@types/lodash": "^4.14.158",
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/node": "^12.12.41",
|
"@types/node": "^12.12.51",
|
||||||
"@types/qrcode": "^1.3.4",
|
"@types/qrcode": "^1.3.4",
|
||||||
"@types/react": "^16.9",
|
"@types/react": "^16.9",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "^1.23.3",
|
"@types/sanitize-html": "^1.23.3",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@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",
|
"babel-jest": "^24.9.0",
|
||||||
"chokidar": "^3.3.1",
|
"chokidar": "^3.4.1",
|
||||||
"concurrently": "^4.0.1",
|
"concurrently": "^4.1.2",
|
||||||
"enzyme": "^3.10.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-adapter-react-16": "^1.15.1",
|
"enzyme-adapter-react-16": "^1.15.2",
|
||||||
"eslint": "^5.12.0",
|
"eslint": "7.5.0",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-plugin-babel": "^5.2.1",
|
"eslint-config-matrix-org": "^0.1.2",
|
||||||
"eslint-plugin-flowtype": "^2.30.0",
|
"eslint-plugin-babel": "^5.3.1",
|
||||||
"eslint-plugin-jest": "^23.0.4",
|
"eslint-plugin-flowtype": "^2.50.3",
|
||||||
"eslint-plugin-react": "^7.7.0",
|
"eslint-plugin-jest": "^23.18.0",
|
||||||
"eslint-plugin-react-hooks": "^2.0.1",
|
"eslint-plugin-react": "^7.20.3",
|
||||||
"estree-walker": "^0.5.0",
|
"eslint-plugin-react-hooks": "^2.5.1",
|
||||||
|
"estree-walker": "^0.9.0",
|
||||||
"file-loader": "^3.0.1",
|
"file-loader": "^3.0.1",
|
||||||
"flow-parser": "^0.57.3",
|
"flow-parser": "0.57.3",
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.15",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"jest-canvas-mock": "^2.2.0",
|
"jest-canvas-mock": "^2.2.0",
|
||||||
"lolex": "^5.1.2",
|
"lolex": "^5.1.2",
|
||||||
"matrix-mock-request": "^1.2.3",
|
"matrix-mock-request": "^1.2.3",
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
"react-test-renderer": "^16.9.0",
|
"react-test-renderer": "^16.13.1",
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.7.1",
|
||||||
"source-map-loader": "^0.2.3",
|
"source-map-loader": "^0.2.4",
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^9.10.1",
|
||||||
"stylelint-config-standard": "^18.2.0",
|
"stylelint-config-standard": "^18.3.0",
|
||||||
"stylelint-scss": "^3.9.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
"tslint": "^5.20.1",
|
"typescript": "^3.9.7",
|
||||||
"typescript": "^3.7.3",
|
"walk": "^2.3.14",
|
||||||
"walk": "^2.3.9",
|
"webpack": "^4.43.0",
|
||||||
"webpack": "^4.20.2",
|
"webpack-cli": "^3.3.12"
|
||||||
"webpack-cli": "^3.1.1"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"testMatch": [
|
"testMatch": [
|
||||||
|
|
|
@ -226,7 +226,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
#mx_theme_tertiaryAccentColor {
|
#mx_theme_tertiaryAccentColor {
|
||||||
color: $roomsublist-label-bg-color;
|
color: $tertiary-accent-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Expected z-indexes for dialogs:
|
/* Expected z-indexes for dialogs:
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
@import "./structures/_HeaderButtons.scss";
|
@import "./structures/_HeaderButtons.scss";
|
||||||
@import "./structures/_HomePage.scss";
|
@import "./structures/_HomePage.scss";
|
||||||
@import "./structures/_LeftPanel.scss";
|
@import "./structures/_LeftPanel.scss";
|
||||||
@import "./structures/_LeftPanel2.scss";
|
|
||||||
@import "./structures/_MainSplit.scss";
|
@import "./structures/_MainSplit.scss";
|
||||||
@import "./structures/_MatrixChat.scss";
|
@import "./structures/_MatrixChat.scss";
|
||||||
@import "./structures/_MyGroups.scss";
|
@import "./structures/_MyGroups.scss";
|
||||||
|
@ -21,14 +20,12 @@
|
||||||
@import "./structures/_RoomDirectory.scss";
|
@import "./structures/_RoomDirectory.scss";
|
||||||
@import "./structures/_RoomSearch.scss";
|
@import "./structures/_RoomSearch.scss";
|
||||||
@import "./structures/_RoomStatusBar.scss";
|
@import "./structures/_RoomStatusBar.scss";
|
||||||
@import "./structures/_RoomSubList.scss";
|
|
||||||
@import "./structures/_RoomView.scss";
|
@import "./structures/_RoomView.scss";
|
||||||
@import "./structures/_ScrollPanel.scss";
|
@import "./structures/_ScrollPanel.scss";
|
||||||
@import "./structures/_SearchBox.scss";
|
@import "./structures/_SearchBox.scss";
|
||||||
@import "./structures/_TabbedView.scss";
|
@import "./structures/_TabbedView.scss";
|
||||||
@import "./structures/_TagPanel.scss";
|
@import "./structures/_TagPanel.scss";
|
||||||
@import "./structures/_ToastContainer.scss";
|
@import "./structures/_ToastContainer.scss";
|
||||||
@import "./structures/_TopLeftMenuButton.scss";
|
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
@import "./structures/_UserMenu.scss";
|
@import "./structures/_UserMenu.scss";
|
||||||
@import "./structures/_ViewSource.scss";
|
@import "./structures/_ViewSource.scss";
|
||||||
|
@ -108,7 +105,6 @@
|
||||||
@import "./views/elements/_IconButton.scss";
|
@import "./views/elements/_IconButton.scss";
|
||||||
@import "./views/elements/_ImageView.scss";
|
@import "./views/elements/_ImageView.scss";
|
||||||
@import "./views/elements/_InlineSpinner.scss";
|
@import "./views/elements/_InlineSpinner.scss";
|
||||||
@import "./views/elements/_InteractiveTooltip.scss";
|
|
||||||
@import "./views/elements/_ManageIntegsButton.scss";
|
@import "./views/elements/_ManageIntegsButton.scss";
|
||||||
@import "./views/elements/_PowerSelector.scss";
|
@import "./views/elements/_PowerSelector.scss";
|
||||||
@import "./views/elements/_ProgressBar.scss";
|
@import "./views/elements/_ProgressBar.scss";
|
||||||
|
@ -168,7 +164,6 @@
|
||||||
@import "./views/rooms/_EventTile.scss";
|
@import "./views/rooms/_EventTile.scss";
|
||||||
@import "./views/rooms/_GroupLayout.scss";
|
@import "./views/rooms/_GroupLayout.scss";
|
||||||
@import "./views/rooms/_IRCLayout.scss";
|
@import "./views/rooms/_IRCLayout.scss";
|
||||||
@import "./views/rooms/_InviteOnlyIcon.scss";
|
|
||||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||||
@import "./views/rooms/_LinkPreviewWidget.scss";
|
@import "./views/rooms/_LinkPreviewWidget.scss";
|
||||||
@import "./views/rooms/_MemberInfo.scss";
|
@import "./views/rooms/_MemberInfo.scss";
|
||||||
|
@ -181,23 +176,18 @@
|
||||||
@import "./views/rooms/_PresenceLabel.scss";
|
@import "./views/rooms/_PresenceLabel.scss";
|
||||||
@import "./views/rooms/_ReplyPreview.scss";
|
@import "./views/rooms/_ReplyPreview.scss";
|
||||||
@import "./views/rooms/_RoomBreadcrumbs.scss";
|
@import "./views/rooms/_RoomBreadcrumbs.scss";
|
||||||
@import "./views/rooms/_RoomBreadcrumbs2.scss";
|
|
||||||
@import "./views/rooms/_RoomDropTarget.scss";
|
|
||||||
@import "./views/rooms/_RoomHeader.scss";
|
@import "./views/rooms/_RoomHeader.scss";
|
||||||
@import "./views/rooms/_RoomList.scss";
|
@import "./views/rooms/_RoomList.scss";
|
||||||
@import "./views/rooms/_RoomList2.scss";
|
|
||||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
||||||
@import "./views/rooms/_RoomSublist2.scss";
|
@import "./views/rooms/_RoomSublist.scss";
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
@import "./views/rooms/_RoomTile2.scss";
|
|
||||||
@import "./views/rooms/_RoomTileIcon.scss";
|
@import "./views/rooms/_RoomTileIcon.scss";
|
||||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||||
@import "./views/rooms/_SearchBar.scss";
|
@import "./views/rooms/_SearchBar.scss";
|
||||||
@import "./views/rooms/_SendMessageComposer.scss";
|
@import "./views/rooms/_SendMessageComposer.scss";
|
||||||
@import "./views/rooms/_Stickers.scss";
|
@import "./views/rooms/_Stickers.scss";
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
@import "./views/rooms/_UserOnlineDot.scss";
|
|
||||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
@import "./views/settings/_AvatarSetting.scss";
|
@import "./views/settings/_AvatarSetting.scss";
|
||||||
@import "./views/settings/_CrossSigningPanel.scss";
|
@import "./views/settings/_CrossSigningPanel.scss";
|
||||||
|
@ -228,6 +218,4 @@
|
||||||
@import "./views/verification/_VerificationShowSas.scss";
|
@import "./views/verification/_VerificationShowSas.scss";
|
||||||
@import "./views/voip/_CallContainer.scss";
|
@import "./views/voip/_CallContainer.scss";
|
||||||
@import "./views/voip/_CallView.scss";
|
@import "./views/voip/_CallView.scss";
|
||||||
@import "./views/voip/_CallView2.scss";
|
|
||||||
@import "./views/voip/_IncomingCallbox.scss";
|
|
||||||
@import "./views/voip/_VideoView.scss";
|
@import "./views/voip/_VideoView.scss";
|
||||||
|
|
|
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO: Update design for custom tags to match new designs
|
||||||
|
|
||||||
.mx_LeftPanel_tagPanelContainer {
|
.mx_LeftPanel_tagPanelContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -50,7 +52,7 @@ limitations under the License.
|
||||||
background-color: $accent-color-alt;
|
background-color: $accent-color-alt;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -15px;
|
left: -9px;
|
||||||
border-radius: 0 3px 3px 0;
|
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 2020 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2018 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,164 +14,171 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_LeftPanel_container {
|
$tagPanelWidth: 56px; // only applies in this file, used for calculations
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeftPanel {
|
.mx_LeftPanel {
|
||||||
flex: 1;
|
background-color: $roomlist-bg-color;
|
||||||
overflow-x: hidden;
|
min-width: 260px;
|
||||||
display: flex;
|
max-width: 50%;
|
||||||
flex-direction: column;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeftPanel .mx_AppTile_mini {
|
// Create a row-based flexbox for the TagPanel and the room list
|
||||||
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 {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_LeftPanel_tagPanelContainer {
|
||||||
flex: 1 1 0;
|
flex-grow: 0;
|
||||||
min-width: 0;
|
flex-shrink: 0;
|
||||||
margin: 4px 9px 1px 9px;
|
flex-basis: $tagPanelWidth;
|
||||||
}
|
height: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeftPanel_explore {
|
// Create another flexbox so the TagPanel fills the container
|
||||||
flex: 0 0 50%;
|
display: flex;
|
||||||
overflow: hidden;
|
|
||||||
transition: flex-basis 0.2s;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&.mx_LeftPanel_explore_hidden {
|
// TagPanel handles its own CSS
|
||||||
flex-basis: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
&:not(.mx_LeftPanel_hasTagPanel) {
|
||||||
font-size: $font-14px;
|
.mx_LeftPanel_roomListContainer {
|
||||||
margin: 4px 0 1px 9px;
|
width: 100%;
|
||||||
padding: 9px;
|
}
|
||||||
padding-left: 42px;
|
}
|
||||||
font-weight: 600;
|
|
||||||
color: $notice-secondary-color;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
&:hover {
|
// Note: The 'room list' in this context is actually everything that isn't the tag
|
||||||
background-color: $primary-bg-color;
|
// 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 {
|
.mx_LeftPanel_breadcrumbsContainer {
|
||||||
cursor: pointer;
|
overflow-y: hidden;
|
||||||
mask: url('$(res)/img/explore.svg');
|
overflow-x: scroll;
|
||||||
mask-repeat: no-repeat;
|
margin: 12px 12px 0 12px;
|
||||||
mask-position: center center;
|
flex: 0 0 auto;
|
||||||
content: "";
|
// Create yet another flexbox, this time within the row, to ensure items stay
|
||||||
left: 14px;
|
// aligned correctly. This is also a row-based flexbox.
|
||||||
top: 10px;
|
display: flex;
|
||||||
width: 16px;
|
align-items: center;
|
||||||
height: 16px;
|
|
||||||
background-color: $notice-secondary-color;
|
&.mx_IndicatorScrollbar_leftOverflow {
|
||||||
position: absolute;
|
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%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// move hit area 5px to the right so it doesn't overlap with the timeline scrollbar
|
.mx_MainSplit > .mx_RightPanel_ResizeWrapper {
|
||||||
.mx_MainSplit > .mx_ResizeHandle.mx_ResizeHandle_horizontal {
|
padding: 5px;
|
||||||
margin: 0 -10px 0 0;
|
|
||||||
padding: 0 10px 0 0;
|
&: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/... */
|
/* 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;
|
background-color: $primary-bg-color;
|
||||||
|
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
@ -78,3 +78,24 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
height: 100%;
|
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;
|
overflow-x: hidden;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 264px;
|
|
||||||
max-width: 50%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 5px;
|
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.mx_RoomView_MessageList {
|
.mx_RoomView_MessageList {
|
||||||
padding: 14px 18px; // top and bottom is 4px smaller to balance with the padding set above
|
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 {
|
.mx_RoomSearch {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: $roomlist2-button-bg-color;
|
background-color: $roomlist-button-bg-color;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 2px;
|
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-weight: 600;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
background-color: $roomtile-name-color;
|
background-color: $muted-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagTile_badgeHighlight {
|
.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;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationBadge, .mx_RoomTile2_badgeContainer {
|
.mx_NotificationBadge, .mx_RoomTile_badgeContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 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 {
|
.mx_MatrixChat > .mx_ResizeHandle.mx_ResizeHandle_horizontal {
|
||||||
margin: 0 -10px 0 0;
|
margin: 0 -10px 0 0;
|
||||||
padding: 0 10px 0 0;
|
padding: 0 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ResizeHandle > div {
|
.mx_ResizeHandle > div {
|
||||||
|
|
|
@ -51,21 +51,27 @@ limitations under the License.
|
||||||
.mx_Tooltip {
|
.mx_Tooltip {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
border: 1px solid $menu-border-color;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
|
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
|
z-index: 6000; // Higher than context menu so tooltips can be used everywhere
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
line-height: $font-14px;
|
line-height: $font-14px;
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $primary-fg-color;
|
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
margin-right: 50px;
|
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 {
|
&.mx_Tooltip_visible {
|
||||||
animation: mx_fadein 0.2s forwards;
|
animation: mx_fadein 0.2s forwards;
|
||||||
}
|
}
|
||||||
|
@ -75,16 +81,15 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Tooltip_timeline {
|
// These tooltips use an older style with a chevron
|
||||||
&.mx_Tooltip {
|
.mx_Field_tooltip {
|
||||||
background-color: $inverted-bg-color;
|
background-color: $menu-bg-color;
|
||||||
color: $accent-fg-color;
|
color: $primary-fg-color;
|
||||||
border: 0;
|
border: 1px solid $menu-border-color;
|
||||||
text-align: center;
|
text-align: unset;
|
||||||
|
|
||||||
.mx_Tooltip_chevron {
|
.mx_Tooltip_chevron {
|
||||||
display: none;
|
display: unset;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,5 @@ limitations under the License.
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
color: $event-timestamp-color;
|
color: $event-timestamp-color;
|
||||||
font-size: $font-10px;
|
font-size: $font-10px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_ReactionsRowButton {
|
.mx_ReactionsRowButton {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 20px;
|
|
||||||
line-height: $font-21px;
|
line-height: $font-21px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
|
@ -35,11 +34,6 @@ limitations under the License.
|
||||||
border-color: $reaction-row-button-selected-border-color;
|
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 {
|
.mx_ReactionsRowButton_content {
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -15,28 +15,45 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_cryptoEvent {
|
.mx_cryptoEvent {
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 24px minmax(0, 1fr) min-content;
|
grid-template-columns: 24px minmax(0, 1fr) min-content;
|
||||||
|
|
||||||
|
&.mx_cryptoEvent_icon::before,
|
||||||
&.mx_cryptoEvent_icon::after {
|
&.mx_cryptoEvent_icon::after {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
content: "";
|
content: "";
|
||||||
background-image: url("$(res)/img/e2e/normal.svg");
|
top: 0;
|
||||||
background-repeat: no-repeat;
|
bottom: 0;
|
||||||
background-size: 100%;
|
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;
|
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 {
|
&.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 {
|
&.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 {
|
.mx_cryptoEvent_title, .mx_cryptoEvent_subtitle, .mx_cryptoEvent_state {
|
||||||
|
|
|
@ -98,6 +98,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
|
|
||||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||||
height: 114px;
|
height: 114px;
|
||||||
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileMenuBar {
|
.mx_AppTileMenuBar {
|
||||||
|
|
|
@ -42,7 +42,7 @@ limitations under the License.
|
||||||
// white infill for the transparency
|
// white infill for the transparency
|
||||||
.mx_E2EIcon::before {
|
.mx_E2EIcon::before {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
mask: url('$(res)/img/e2e/normal.svg');
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 90%;
|
mask-size: 90%;
|
||||||
|
|
|
@ -355,7 +355,7 @@ $left-gutter: 64px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
mask: url('$(res)/img/e2e/normal.svg');
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 90%;
|
mask-size: 90%;
|
||||||
|
|
|
@ -181,7 +181,8 @@ $irc-line-height: $font-18px;
|
||||||
> span {
|
> span {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> .mx_SenderProfile_name {
|
> .mx_SenderProfile_name,
|
||||||
|
> .mx_SenderProfile_aux {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
min-width: var(--name-width);
|
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
|
// with text-align in parent
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
color: $roomtile-badge-fg-color;
|
color: $accent-fg-color;
|
||||||
background-color: $roomtile-name-color;
|
background-color: $muted-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_JumpToBottomButton_highlight .mx_JumpToBottomButton_badge {
|
.mx_JumpToBottomButton_highlight .mx_JumpToBottomButton_badge {
|
||||||
|
@ -56,7 +56,7 @@ limitations under the License.
|
||||||
border-radius: 19px;
|
border-radius: 19px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: $primary-bg-color;
|
background: $primary-bg-color;
|
||||||
border: 1.3px solid $roomtile-name-color;
|
border: 1.3px solid $muted-fg-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,5 +70,5 @@ limitations under the License.
|
||||||
mask: url('$(res)/img/icon-jump-to-bottom.svg');
|
mask: url('$(res)/img/icon-jump-to-bottom.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: 9px 14px;
|
mask-position: 9px 14px;
|
||||||
background: $roomtile-name-color;
|
background: $muted-fg-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,15 +70,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.mx_MemberList_wrapper {
|
.mx_MemberList_wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MemberList_invite {
|
||||||
.mx_MemberList_invite,
|
|
||||||
.mx_RightPanel_invite {
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: $button-bg-color;
|
background-color: $button-bg-color;
|
||||||
|
@ -88,11 +84,6 @@ limitations under the License.
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: $button-fg-color;
|
color: $button-fg-color;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
.mx_RightPanel_icon {
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberList_invite.mx_AccessibleButton_disabled {
|
.mx_MemberList_invite.mx_AccessibleButton_disabled {
|
||||||
|
@ -107,3 +98,11 @@ limitations under the License.
|
||||||
background-size: 20px;
|
background-size: 20px;
|
||||||
padding: 8px 0 8px 25px;
|
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.
|
// ^- The count is an element floating within that.
|
||||||
|
|
||||||
&.mx_NotificationBadge_visible {
|
&.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
|
// Create a flexbox to order the count a bit easier
|
||||||
display: flex;
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,98 +15,42 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomBreadcrumbs {
|
.mx_RoomBreadcrumbs {
|
||||||
position: relative;
|
width: 100%;
|
||||||
height: 42px;
|
|
||||||
padding: 8px;
|
// Create a flexbox for the crumbs
|
||||||
padding-bottom: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
// 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%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomBreadcrumbs_crumb {
|
.mx_RoomBreadcrumbs_crumb {
|
||||||
margin-left: 4px;
|
margin-right: 8px;
|
||||||
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;
|
|
||||||
width: 32px;
|
width: 32px;
|
||||||
transform: scale(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomBreadcrumbs_preAnimate {
|
// These classes come from the CSSTransition component. There's many more classes we
|
||||||
width: 0;
|
// could care about, but this is all we worried about for now. The animation works by
|
||||||
transform: scale(0);
|
// 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 {
|
.mx_RoomBreadcrumbs_placeholder {
|
||||||
opacity: 0.5;
|
font-weight: 600;
|
||||||
}
|
font-size: $font-14px;
|
||||||
|
line-height: 32px; // specifically to match the height this is not scaled
|
||||||
// Note: we have to manually control the gradient and stuff, but the IndicatorScrollbar
|
height: 32px;
|
||||||
// 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_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 2020 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,56 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomList.mx_RoomList2 {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomList {
|
.mx_RoomList {
|
||||||
/* take up remaining space below TopLeftMenu */
|
padding-right: 7px; // width of the scrollbar, to line things up
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
.mx_RoomPreviewBar_actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,20 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
.mx_RoomSublist {
|
||||||
|
|
||||||
.mx_RoomSublist2 {
|
|
||||||
// The sublist is a column of rows, essentially
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
flex-shrink: 0; // to convince safari's layout engine the flexbox is fine
|
.mx_RoomSublist_headerContainer {
|
||||||
|
|
||||||
.mx_RoomSublist2_headerContainer {
|
|
||||||
// Create a flexbox to make alignment easy
|
// Create a flexbox to make alignment easy
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
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
|
// 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.
|
// 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.
|
// to work correctly.
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
color: $roomlist2-header-color;
|
color: $roomlist-header-color;
|
||||||
|
|
||||||
.mx_RoomSublist2_stickable {
|
.mx_RoomSublist_stickable {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 100%;
|
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,
|
// 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
|
// we'd have to do the "is sticky" checks again on click, as clicking the header
|
||||||
// when sticky scrolls instead of collapses the list.
|
// when sticky scrolls instead of collapses the list.
|
||||||
&.mx_RoomSublist2_headerContainer_sticky {
|
&.mx_RoomSublist_headerContainer_sticky {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
height: 32px; // to match the header container
|
height: 32px; // to match the header container
|
||||||
// width set by JS
|
// width set by JS
|
||||||
width: calc(100% - 22px);
|
width: calc(100% - 22px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_RoomSublist2_headerContainer_stickyBottom {
|
&.mx_RoomSublist_headerContainer_stickyBottom {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't have a top style because the top is dependent on the room list header's
|
// 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.
|
// 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
|
// Sticky Headers End
|
||||||
// ***************************
|
// ***************************
|
||||||
|
|
||||||
.mx_RoomSublist2_badgeContainer {
|
.mx_RoomSublist_badgeContainer {
|
||||||
// Create another flexbox row because it's super easy to position the badge this way.
|
// Create another flexbox row because it's super easy to position the badge this way.
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -93,14 +84,14 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.mx_RoomSublist2_headerContainer_withAux) {
|
&:not(.mx_RoomSublist_headerContainer_withAux) {
|
||||||
.mx_NotificationBadge {
|
.mx_NotificationBadge {
|
||||||
margin-right: 4px; // just to push it over a bit, aligning it with the other elements
|
margin-right: 4px; // just to push it over a bit, aligning it with the other elements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_auxButton,
|
.mx_RoomSublist_auxButton,
|
||||||
.mx_RoomSublist2_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
margin-left: 8px; // should be the same as the notification badge
|
margin-left: 8px; // should be the same as the notification badge
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
@ -122,21 +113,21 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the menu button by default
|
// Hide the menu button by default
|
||||||
.mx_RoomSublist2_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
width: 0;
|
width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_auxButton::before {
|
.mx_RoomSublist_auxButton::before {
|
||||||
mask-image: url('$(res)/img/feather-customised/plus.svg');
|
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');
|
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_headerText {
|
.mx_RoomSublist_headerText {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: calc(100% - 16px); // 16px is the badge width
|
max-width: calc(100% - 16px); // 16px is the badge width
|
||||||
line-height: $font-16px;
|
line-height: $font-16px;
|
||||||
|
@ -148,7 +139,7 @@ limitations under the License.
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
.mx_RoomSublist2_collapseBtn {
|
.mx_RoomSublist_collapseBtn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
|
@ -169,7 +160,7 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
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');
|
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
|
// 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.
|
// 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.
|
// See also https://github.com/vector-im/riot-web/issues/14429.
|
||||||
&:first-child .mx_RoomSublist2_headerContainer {
|
&:first-child .mx_RoomSublist_headerContainer {
|
||||||
height: 0;
|
height: 0;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_resizeBox {
|
.mx_RoomSublist_resizeBox {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
// Create another flexbox column for the tiles
|
// Create another flexbox column for the tiles
|
||||||
|
@ -194,7 +185,7 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.mx_RoomSublist2_tiles {
|
.mx_RoomSublist_tiles {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// need this to be flex otherwise the overflow hidden from above
|
// 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);
|
mask-image: linear-gradient(0deg, transparent, black 4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_resizerHandles_showNButton {
|
.mx_RoomSublist_resizerHandles_showNButton {
|
||||||
flex: 0 0 32px;
|
flex: 0 0 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_resizerHandles {
|
.mx_RoomSublist_resizerHandles {
|
||||||
flex: 0 0 4px;
|
flex: 0 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class name comes from the ResizableBox component
|
// Class name comes from the ResizableBox component
|
||||||
// The hover state needs to use the whole sublist, not just the resizable box,
|
// The hover state needs to use the whole sublist, not just the resizable box,
|
||||||
// so that selector is below and one level higher.
|
// so that selector is below and one level higher.
|
||||||
.mx_RoomSublist2_resizerHandle {
|
.mx_RoomSublist_resizerHandle {
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
|
@ -235,21 +226,21 @@ limitations under the License.
|
||||||
right: calc(50% - 32px) !important;
|
right: calc(50% - 32px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &.mx_RoomSublist2_hasMenuOpen {
|
&:hover, &.mx_RoomSublist_hasMenuOpen {
|
||||||
.mx_RoomSublist2_resizerHandle {
|
.mx_RoomSublist_resizerHandle {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
background-color: $primary-fg-color;
|
background-color: $primary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_showNButton {
|
.mx_RoomSublist_showNButton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: $font-13px;
|
font-size: $font-13px;
|
||||||
line-height: $font-18px;
|
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.
|
// Update the ListLayout class for minVisibleTiles if these change.
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
|
@ -258,7 +249,7 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.mx_RoomSublist2_showNButtonChevron {
|
.mx_RoomSublist_showNButtonChevron {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
@ -267,52 +258,52 @@ limitations under the License.
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
mask-repeat: no-repeat;
|
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');
|
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');
|
mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_RoomSublist2_hasMenuOpen,
|
&.mx_RoomSublist_hasMenuOpen,
|
||||||
&:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:focus-within,
|
&:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:focus-within,
|
||||||
&:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover {
|
&:not(.mx_RoomSublist_minimized) > .mx_RoomSublist_headerContainer:hover {
|
||||||
.mx_RoomSublist2_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_RoomSublist2_minimized {
|
&.mx_RoomSublist_minimized {
|
||||||
.mx_RoomSublist2_headerContainer {
|
.mx_RoomSublist_headerContainer {
|
||||||
height: auto;
|
height: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.mx_RoomSublist2_badgeContainer {
|
.mx_RoomSublist_badgeContainer {
|
||||||
order: 0;
|
order: 0;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_stickable {
|
.mx_RoomSublist_stickable {
|
||||||
order: 1;
|
order: 1;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_auxButton {
|
.mx_RoomSublist_auxButton {
|
||||||
order: 2;
|
order: 2;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
width: 32px !important; // !important to override hover styles
|
width: 32px !important; // !important to override hover styles
|
||||||
height: 32px !important; // !important to override hover styles
|
height: 32px !important; // !important to override hover styles
|
||||||
margin-left: 0 !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;
|
margin-top: 8px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
@ -322,25 +313,25 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_resizeBox {
|
.mx_RoomSublist_resizeBox {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_showNButton {
|
.mx_RoomSublist_showNButton {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.mx_RoomSublist2_showNButtonChevron {
|
.mx_RoomSublist_showNButtonChevron {
|
||||||
margin-right: 12px; // to center
|
margin-right: 12px; // to center
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_RoomSublist2_hasMenuOpen,
|
&.mx_RoomSublist_hasMenuOpen,
|
||||||
& > .mx_RoomSublist2_headerContainer:hover {
|
& > .mx_RoomSublist_headerContainer:hover {
|
||||||
.mx_RoomSublist2_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 48px; // align to middle of name, 40px for aux button (with padding) and 8px for alignment
|
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
|
// This is the same color as the left panel background because it needs
|
||||||
// to occlude the sublist title
|
// to occlude the sublist title
|
||||||
background-color: $roomlist2-bg-color;
|
background-color: $roomlist-bg-color;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -360,8 +351,8 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_RoomSublist2_headerContainer:not(.mx_RoomSublist2_headerContainer_withAux) {
|
&.mx_RoomSublist_headerContainer:not(.mx_RoomSublist_headerContainer_withAux) {
|
||||||
.mx_RoomSublist2_menuButton {
|
.mx_RoomSublist_menuButton {
|
||||||
bottom: 8px; // align to the middle of name, 40px less than the `bottom` above.
|
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;
|
padding: 20px 16px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
|
|
||||||
|
@ -377,11 +368,11 @@ limitations under the License.
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
margin-right: 16px; // additional 16px
|
margin-right: 16px; // additional 16px
|
||||||
border: 1px solid $roomsublist2-divider-color;
|
border: 1px solid $roomsublist-divider-color;
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_contextMenu_title {
|
.mx_RoomSublist_contextMenu_title {
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
line-height: $font-20px;
|
line-height: $font-20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -393,6 +384,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_addRoomTooltip {
|
.mx_RoomSublist_addRoomTooltip {
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,214 +14,222 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Note: the room tile expects to be in a flexbox column container
|
||||||
.mx_RoomTile {
|
.mx_RoomTile {
|
||||||
display: flex;
|
margin-bottom: 4px;
|
||||||
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;
|
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
width: 24px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTile_hasSubtext .mx_RoomTile_avatar {
|
// The tile is also a flexbox row itself
|
||||||
padding-top: 0;
|
display: flex;
|
||||||
vertical-align: super;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTile_dm {
|
&.mx_RoomTile_selected,
|
||||||
display: block;
|
&:hover,
|
||||||
position: absolute;
|
&:focus-within,
|
||||||
bottom: 0;
|
&.mx_RoomTile_hasMenuOpen {
|
||||||
right: -5px;
|
background-color: $roomtile-selected-bg-color;
|
||||||
z-index: 2;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note we match .mx_E2EIcon to make sure this matches more tightly than just
|
.mx_DecoratedRoomAvatar, .mx_RoomTile_avatarContainer {
|
||||||
// .mx_E2EIcon on its own
|
margin-right: 8px;
|
||||||
.mx_RoomTile_e2eIcon.mx_E2EIcon {
|
}
|
||||||
height: 14px;
|
|
||||||
width: 14px;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
right: -5px;
|
|
||||||
z-index: 1;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTile_name {
|
.mx_RoomTile_nameContainer {
|
||||||
font-size: $font-14px;
|
flex-grow: 1;
|
||||||
padding: 0 4px;
|
min-width: 0; // allow flex to shrink it
|
||||||
color: $roomtile-name-color;
|
margin-right: 8px; // spacing to buttons/badges
|
||||||
white-space: nowrap;
|
|
||||||
overflow-x: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTile_badge {
|
// Create a new column layout flexbox for the name parts
|
||||||
flex: 0 1 content;
|
display: flex;
|
||||||
border-radius: 0.8em;
|
flex-direction: column;
|
||||||
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;
|
|
||||||
justify-content: center;
|
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;
|
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 {
|
// If the room has an overriden notification setting then we always show the notifications menu button
|
||||||
position: absolute;
|
.mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show {
|
||||||
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 {
|
|
||||||
display: block;
|
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,
|
// We use these both in context menus and the room tiles
|
||||||
.mx_RoomTile_badge.mx_RoomTile_badgeUnread {
|
.mx_RoomTile_iconBell::before {
|
||||||
background-color: $roomtile-name-color;
|
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_contextMenu {
|
||||||
.mx_RoomTile_badge.mx_RoomTile_badgeRed {
|
.mx_RoomTile_contextMenu_redRow {
|
||||||
color: $accent-fg-color;
|
.mx_AccessibleButton {
|
||||||
background-color: $warning-color;
|
color: $warning-color !important; // !important to override styles from context menu
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile_unread, .mx_RoomTile_highlight {
|
.mx_IconizedContextMenu_icon::before {
|
||||||
.mx_RoomTile_name {
|
background-color: $warning-color;
|
||||||
font-weight: 600;
|
}
|
||||||
color: $roomtile-selected-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;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 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 {
|
.mx_RoomTileIcon_globe::before {
|
||||||
|
|
|
@ -42,7 +42,7 @@ limitations under the License.
|
||||||
border-radius: 19px;
|
border-radius: 19px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: $primary-bg-color;
|
background: $primary-bg-color;
|
||||||
border: 1.3px solid $roomtile-name-color;
|
border: 1.3px solid $muted-fg-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/icon-jump-to-first-unread.svg');
|
mask-image: url('$(res)/img/icon-jump-to-first-unread.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: 9px 13px;
|
mask-position: 9px 13px;
|
||||||
background: $roomtile-name-color;
|
background: $muted-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TopUnreadMessagesBar_markAsRead {
|
.mx_TopUnreadMessagesBar_markAsRead {
|
||||||
|
@ -62,7 +62,7 @@ limitations under the License.
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
background: $primary-bg-color;
|
background: $primary-bg-color;
|
||||||
border: 1.3px solid $roomtile-name-color;
|
border: 1.3px solid $muted-fg-color;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin: 5px auto;
|
margin: 5px auto;
|
||||||
}
|
}
|
||||||
|
@ -76,5 +76,5 @@ limitations under the License.
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: 10px;
|
mask-size: 10px;
|
||||||
mask-position: 4px 4px;
|
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;
|
min-width: 250px;
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|
||||||
.mx_IncomingCallBox2_CallerInfo {
|
.mx_IncomingCallBox_CallerInfo {
|
||||||
display: flex;
|
display: flex;
|
||||||
direction: row;
|
direction: row;
|
||||||
|
|
||||||
|
@ -68,12 +68,12 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_IncomingCallBox2_buttons {
|
.mx_IncomingCallBox_buttons {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
> .mx_IncomingCallBox2_spacer {
|
> .mx_IncomingCallBox_spacer {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,8 +19,76 @@ limitations under the License.
|
||||||
background-color: $accent-color;
|
background-color: $accent-color;
|
||||||
color: $accent-fg-color;
|
color: $accent-fg-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
font-weight: bold;
|
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;
|
$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
|
$roomtile-preview-color: #A9B2BC;
|
||||||
$roomlist2-bg-color: rgba(33, 38, 44, 0.90);
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomlist2-header-color: #8E99A4;
|
$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2);
|
||||||
$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);
|
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
$notice-secondary-color: $roomlist2-header-color;
|
$notice-secondary-color: $roomlist-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);
|
|
||||||
|
|
||||||
$panel-divider-color: transparent;
|
$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;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
|
||||||
$roomlist2-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist2-bg-color: $header-panel-bg-color;
|
$roomlist-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
$roomsublist2-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
$roomtile2-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile2-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile2-selected-bg-color: #1A1D23;
|
$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;
|
$panel-divider-color: $header-panel-border-color;
|
||||||
|
|
||||||
$widget-menu-bar-bg-color: $header-panel-bg-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;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
|
||||||
$roomlist2-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist2-bg-color: $header-panel-bg-color;
|
$roomlist-bg-color: $header-panel-bg-color;
|
||||||
$roomlist2-header-color: $primary-fg-color;
|
$roomlist-header-color: $primary-fg-color;
|
||||||
$roomsublist2-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
$roomtile2-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile2-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile2-selected-bg-color: #fff;
|
$roomtile-selected-bg-color: #fff;
|
||||||
|
|
||||||
$presence-online: $accent-color;
|
$presence-online: $accent-color;
|
||||||
$presence-away: #d9b072;
|
$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-variant1-color: #368bd6;
|
||||||
$username-variant2-color: #ac3ba8;
|
$username-variant2-color: #ac3ba8;
|
||||||
$username-variant3-color: #03b381;
|
$username-variant3-color: #03b381;
|
||||||
|
@ -210,13 +200,6 @@ $username-variant6-color: #2dc2c5;
|
||||||
$username-variant7-color: #5c56f5;
|
$username-variant7-color: #5c56f5;
|
||||||
$username-variant8-color: #74d12c;
|
$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;
|
$panel-divider-color: #dee1f3;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
@ -292,7 +275,7 @@ $progressbar-color: #000;
|
||||||
|
|
||||||
$room-warning-bg-color: $yellow-background;
|
$room-warning-bg-color: $yellow-background;
|
||||||
|
|
||||||
$memberstatus-placeholder-color: $roomtile-name-color;
|
$memberstatus-placeholder-color: $muted-fg-color;
|
||||||
|
|
||||||
$authpage-bg-color: #2e3649;
|
$authpage-bg-color: #2e3649;
|
||||||
$authpage-modal-bg-color: rgba(255, 255, 255, 0.59);
|
$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);
|
$button-primary-bg-color: var(--accent-color);
|
||||||
$input-valid-border-color: var(--accent-color);
|
$input-valid-border-color: var(--accent-color);
|
||||||
$reaction-row-button-selected-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);
|
$tab-label-active-bg-color: var(--accent-color);
|
||||||
$togglesw-on-color: var(--accent-color);
|
$togglesw-on-color: var(--accent-color);
|
||||||
$username-variant3-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);
|
$avatar-bg-color: var(--timeline-background-color);
|
||||||
$message-action-bar-bg-color: var(--timeline-background-color);
|
$message-action-bar-bg-color: var(--timeline-background-color);
|
||||||
$primary-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);
|
$togglesw-ball-color: var(--timeline-background-color);
|
||||||
$droptarget-bg-color: var(--timeline-background-color-50pct); //still needs alpha at .5
|
$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
|
$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
|
// --roomlist-highlights-color
|
||||||
$roomtile-selected-bg-color: var(--roomlist-highlights-color);
|
$roomtile-selected-bg-color: var(--roomlist-highlights-color);
|
||||||
$roomtile2-selected-bg-color: var(--roomlist-highlights-color);
|
|
||||||
//
|
//
|
||||||
// --sidebar-color
|
// --sidebar-color
|
||||||
$interactive-tooltip-bg-color: var(--sidebar-color);
|
$interactive-tooltip-bg-color: var(--sidebar-color);
|
||||||
$tagpanel-bg-color: var(--sidebar-color);
|
$tagpanel-bg-color: var(--sidebar-color);
|
||||||
$tooltip-timeline-bg-color: var(--sidebar-color);
|
$tooltip-timeline-bg-color: var(--sidebar-color);
|
||||||
$dialog-backdrop-color: var(--sidebar-color-50pct);
|
$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
|
// --roomlist-background-color
|
||||||
$header-panel-bg-color: var(--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);
|
$dark-panel-bg-color: var(--roomlist-background-color);
|
||||||
$input-lighter-bg-color: var(--roomlist-background-color);
|
$input-lighter-bg-color: var(--roomlist-background-color);
|
||||||
$plinth-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);
|
$secondary-accent-color: var(--roomlist-background-color);
|
||||||
$selected-color: var(--roomlist-background-color);
|
$selected-color: var(--roomlist-background-color);
|
||||||
$widget-menu-bar-bg-color: var(--roomlist-background-color);
|
$widget-menu-bar-bg-color: var(--roomlist-background-color);
|
||||||
$roomtile-badge-fg-color: var(--roomlist-background-color);
|
$roomlist-bg-color: var(--roomlist-background-color);
|
||||||
$roomlist2-bg-color: var(--roomlist-background-color);
|
|
||||||
//
|
//
|
||||||
// --timeline-text-color
|
// --timeline-text-color
|
||||||
$message-action-bar-fg-color: var(--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
|
// was #4e5054
|
||||||
$authpage-lang-color: var(--timeline-text-color);
|
$authpage-lang-color: var(--timeline-text-color);
|
||||||
$roomheader-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
|
// --roomlist-text-secondary-color
|
||||||
$roomsublist-label-fg-color: var(--roomlist-text-secondary-color);
|
$roomtile-preview-color: var(--roomlist-text-secondary-color);
|
||||||
$roomtile-name-color: var(--roomlist-text-secondary-color);
|
$roomlist-header-color: var(--roomlist-text-secondary-color);
|
||||||
$roomtile2-preview-color: var(--roomlist-text-secondary-color);
|
$roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color);
|
||||||
$roomlist2-header-color: var(--roomlist-text-secondary-color);
|
|
||||||
$roomtile2-default-badge-bg-color: var(--roomlist-text-secondary-color);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// --roomlist-separator-color
|
// --roomlist-separator-color
|
||||||
$input-darker-bg-color: var(--roomlist-separator-color);
|
$input-darker-bg-color: var(--roomlist-separator-color);
|
||||||
$panel-divider-color: var(--roomlist-separator-color);// originally #dee1f3, but close enough
|
$panel-divider-color: var(--roomlist-separator-color);// originally #dee1f3, but close enough
|
||||||
$primary-hairline-color: var(--roomlist-separator-color);// originally #e5e5e5, 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
|
// --timeline-text-secondary-color
|
||||||
$authpage-secondary-color: var(--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.
|
/* 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-face {
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-Regular.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-Regular.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -28,6 +23,7 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-Italic.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-Italic.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -37,6 +33,7 @@
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-Medium.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-Medium.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -45,6 +42,7 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-MediumItalic.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-MediumItalic.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -54,6 +52,7 @@
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-SemiBold.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-SemiBold.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -62,6 +61,7 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-SemiBoldItalic.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,7 @@
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-Bold.woff2?v=3.13") format("woff2"),
|
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");
|
url("$(res)/fonts/Inter/Inter-Bold.woff?v=3.13") format("woff");
|
||||||
}
|
}
|
||||||
|
@ -79,6 +80,7 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
unicode-range: $inter-unicode-range;
|
||||||
src: url("$(res)/fonts/Inter/Inter-BoldItalic.woff2?v=3.13") format("woff2"),
|
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");
|
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');
|
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;
|
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-color: #ff4b55;
|
||||||
$notice-primary-bg-color: rgba(255, 75, 85, 0.16);
|
$notice-primary-bg-color: rgba(255, 75, 85, 0.16);
|
||||||
$primary-fg-color: #2e2f32;
|
$primary-fg-color: #2e2f32;
|
||||||
$roomlist2-header-color: $primary-fg-color;
|
$roomlist-header-color: $primary-fg-color;
|
||||||
$notice-secondary-color: $roomlist2-header-color;
|
$notice-secondary-color: $roomlist-header-color;
|
||||||
$header-panel-bg-color: #f3f8fd;
|
$header-panel-bg-color: #f3f8fd;
|
||||||
|
|
||||||
// typical text (dark-on-white in light skin)
|
// 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;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
|
||||||
$roomlist2-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist2-bg-color: rgba(245, 245, 245, 0.90);
|
$roomlist-bg-color: rgba(245, 245, 245, 0.90);
|
||||||
$roomsublist2-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
$roomtile2-preview-color: #737D8C;
|
$roomtile-preview-color: #737D8C;
|
||||||
$roomtile2-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile2-selected-bg-color: #FFF;
|
$roomtile-selected-bg-color: #FFF;
|
||||||
|
|
||||||
$presence-online: $accent-color;
|
$presence-online: $accent-color;
|
||||||
$presence-away: orange; // TODO: Get color
|
$presence-away: #d9b072;
|
||||||
$presence-offline: #E3E8F0;
|
$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-variant1-color: #368bd6;
|
||||||
$username-variant2-color: #ac3ba8;
|
$username-variant2-color: #ac3ba8;
|
||||||
$username-variant3-color: #0DBD8B;
|
$username-variant3-color: #0DBD8B;
|
||||||
|
@ -209,13 +199,6 @@ $username-variant6-color: #2dc2c5;
|
||||||
$username-variant7-color: #5c56f5;
|
$username-variant7-color: #5c56f5;
|
||||||
$username-variant8-color: #74d12c;
|
$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;
|
$panel-divider-color: transparent;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
@ -291,7 +274,7 @@ $progressbar-color: #000;
|
||||||
|
|
||||||
$room-warning-bg-color: $yellow-background;
|
$room-warning-bg-color: $yellow-background;
|
||||||
|
|
||||||
$memberstatus-placeholder-color: $roomtile-name-color;
|
$memberstatus-placeholder-color: $muted-fg-color;
|
||||||
|
|
||||||
$authpage-bg-color: #2e3649;
|
$authpage-bg-color: #2e3649;
|
||||||
$authpage-modal-bg-color: rgba(245, 245, 245, 0.90);
|
$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
|
// it can be blurred by the tag panel and room list
|
||||||
|
|
||||||
@supports (backdrop-filter: none) {
|
@supports (backdrop-filter: none) {
|
||||||
.mx_LeftPanel2 {
|
.mx_LeftPanel {
|
||||||
background-image: var(--avatar-url);
|
background-image: var(--avatar-url);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
@ -16,12 +16,12 @@
|
||||||
backdrop-filter: blur($tagpanel-background-blur-amount);
|
backdrop-filter: blur($tagpanel-background-blur-amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LeftPanel2 .mx_LeftPanel2_roomListContainer {
|
.mx_LeftPanel .mx_LeftPanel_roomListContainer {
|
||||||
backdrop-filter: blur($roomlist-background-blur-amount);
|
backdrop-filter: blur($roomlist-background-blur-amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSublist2_showNButton {
|
.mx_RoomSublist_showNButton {
|
||||||
background-color: transparent !important;
|
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 ToastStore from "../stores/ToastStore";
|
||||||
import DeviceListener from "../DeviceListener";
|
import DeviceListener from "../DeviceListener";
|
||||||
import RebrandListener from "../RebrandListener";
|
import RebrandListener from "../RebrandListener";
|
||||||
import { RoomListStore2 } from "../stores/room-list/RoomListStore2";
|
import { RoomListStoreClass } from "../stores/room-list/RoomListStore";
|
||||||
import { PlatformPeg } from "../PlatformPeg";
|
import { PlatformPeg } from "../PlatformPeg";
|
||||||
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
|
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
|
||||||
|
import {IntegrationManagers} from "../integrations/IntegrationManagers";
|
||||||
|
import {ModalManager} from "../Modal";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -32,21 +34,20 @@ declare global {
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
mx_ContentMessages: ContentMessages;
|
mxContentMessages: ContentMessages;
|
||||||
mx_ToastStore: ToastStore;
|
mxToastStore: ToastStore;
|
||||||
mx_DeviceListener: DeviceListener;
|
mxDeviceListener: DeviceListener;
|
||||||
mx_RebrandListener: RebrandListener;
|
mxRebrandListener: RebrandListener;
|
||||||
mx_RoomListStore2: RoomListStore2;
|
mxRoomListStore: RoomListStoreClass;
|
||||||
mx_RoomListLayoutStore: RoomListLayoutStore;
|
mxRoomListLayoutStore: RoomListLayoutStore;
|
||||||
mxPlatformPeg: PlatformPeg;
|
mxPlatformPeg: PlatformPeg;
|
||||||
|
mxIntegrationManagers: typeof IntegrationManagers;
|
||||||
// TODO: Remove flag before launch: https://github.com/vector-im/riot-web/issues/14231
|
singletonModalManager: ModalManager;
|
||||||
mx_LoudRoomListLogging: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
||||||
interface ObjectConstructor {
|
interface ObjectConstructor {
|
||||||
fromEntries?(xs: [string|number|symbol, any][]): object
|
fromEntries?(xs: [string|number|symbol, any][]): object;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -386,7 +386,7 @@ export default class ContentMessages {
|
||||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||||
if (isQuoting) {
|
if (isQuoting) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
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'),
|
title: _t('Replying With Files'),
|
||||||
description: (
|
description: (
|
||||||
<div>{_t(
|
<div>{_t(
|
||||||
|
@ -397,7 +397,7 @@ export default class ContentMessages {
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
});
|
});
|
||||||
const [shouldUpload]: [boolean] = await finished;
|
const [shouldUpload] = await finished;
|
||||||
if (!shouldUpload) return;
|
if (!shouldUpload) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,12 +420,12 @@ export default class ContentMessages {
|
||||||
|
|
||||||
if (tooBigFiles.length > 0) {
|
if (tooBigFiles.length > 0) {
|
||||||
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
||||||
const {finished} = Modal.createTrackedDialog('Upload Failure', '', UploadFailureDialog, {
|
const {finished} = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
|
||||||
badFiles: tooBigFiles,
|
badFiles: tooBigFiles,
|
||||||
totalFiles: files.length,
|
totalFiles: files.length,
|
||||||
contentMessages: this,
|
contentMessages: this,
|
||||||
});
|
});
|
||||||
const [shouldContinue]: [boolean] = await finished;
|
const [shouldContinue] = await finished;
|
||||||
if (!shouldContinue) return;
|
if (!shouldContinue) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,12 +437,12 @@ export default class ContentMessages {
|
||||||
for (let i = 0; i < okFiles.length; ++i) {
|
for (let i = 0; i < okFiles.length; ++i) {
|
||||||
const file = okFiles[i];
|
const file = okFiles[i];
|
||||||
if (!uploadAll) {
|
if (!uploadAll) {
|
||||||
const {finished} = Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
|
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, {
|
||||||
file,
|
file,
|
||||||
currentIndex: i,
|
currentIndex: i,
|
||||||
totalFiles: okFiles.length,
|
totalFiles: okFiles.length,
|
||||||
});
|
});
|
||||||
const [shouldContinue, shouldUploadAll]: [boolean, boolean] = await finished;
|
const [shouldContinue, shouldUploadAll] = await finished;
|
||||||
if (!shouldContinue) break;
|
if (!shouldContinue) break;
|
||||||
if (shouldUploadAll) {
|
if (shouldUploadAll) {
|
||||||
uploadAll = true;
|
uploadAll = true;
|
||||||
|
@ -621,9 +621,9 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
if (window.mx_ContentMessages === undefined) {
|
if (window.mxContentMessages === undefined) {
|
||||||
window.mx_ContentMessages = new ContentMessages();
|
window.mxContentMessages = new ContentMessages();
|
||||||
}
|
}
|
||||||
return window.mx_ContentMessages;
|
return window.mxContentMessages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,25 +40,16 @@ export class AccessCancelledError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmToDismiss(name) {
|
async function confirmToDismiss() {
|
||||||
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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const [sure] = await Modal.createDialog(QuestionDialog, {
|
const [sure] = await Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Cancel entering passphrase?"),
|
title: _t("Cancel entering passphrase?"),
|
||||||
description,
|
description: _t("Are you sure you want to cancel entering passphrase?"),
|
||||||
danger: true,
|
danger: false,
|
||||||
cancelButton: _t("Enter passphrase"),
|
button: _t("Go Back"),
|
||||||
button: _t("Cancel"),
|
cancelButton: _t("Cancel"),
|
||||||
}).finished;
|
}).finished;
|
||||||
return sure;
|
return !sure;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
|
@ -102,7 +93,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
/* options= */ {
|
/* options= */ {
|
||||||
onBeforeClose: async (reason) => {
|
onBeforeClose: async (reason) => {
|
||||||
if (reason === "backgroundClick") {
|
if (reason === "backgroundClick") {
|
||||||
return confirmToDismiss(ssssItemName);
|
return confirmToDismiss();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,16 +17,16 @@ limitations under the License.
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import {
|
import {
|
||||||
hideToast as hideBulkUnverifiedSessionsToast,
|
hideToast as hideBulkUnverifiedSessionsToast,
|
||||||
showToast as showBulkUnverifiedSessionsToast
|
showToast as showBulkUnverifiedSessionsToast,
|
||||||
} from "./toasts/BulkUnverifiedSessionsToast";
|
} from "./toasts/BulkUnverifiedSessionsToast";
|
||||||
import {
|
import {
|
||||||
hideToast as hideSetupEncryptionToast,
|
hideToast as hideSetupEncryptionToast,
|
||||||
Kind as SetupKind,
|
Kind as SetupKind,
|
||||||
showToast as showSetupEncryptionToast
|
showToast as showSetupEncryptionToast,
|
||||||
} from "./toasts/SetupEncryptionToast";
|
} from "./toasts/SetupEncryptionToast";
|
||||||
import {
|
import {
|
||||||
hideToast as hideUnverifiedSessionsToast,
|
hideToast as hideUnverifiedSessionsToast,
|
||||||
showToast as showUnverifiedSessionsToast
|
showToast as showUnverifiedSessionsToast,
|
||||||
} from "./toasts/UnverifiedSessionToast";
|
} from "./toasts/UnverifiedSessionToast";
|
||||||
import {privateShouldBeEncrypted} from "./createRoom";
|
import {privateShouldBeEncrypted} from "./createRoom";
|
||||||
|
|
||||||
|
@ -48,8 +48,8 @@ export default class DeviceListener {
|
||||||
private displayingToastsForDeviceIds = new Set<string>();
|
private displayingToastsForDeviceIds = new Set<string>();
|
||||||
|
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
if (!window.mx_DeviceListener) window.mx_DeviceListener = new DeviceListener();
|
if (!window.mxDeviceListener) window.mxDeviceListener = new DeviceListener();
|
||||||
return window.mx_DeviceListener;
|
return window.mxDeviceListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
|
|
@ -184,7 +184,7 @@ const transformTags: sanitizeHtml.IOptions["transformTags"] = { // custom to mat
|
||||||
if (typeof attribs.class !== 'undefined') {
|
if (typeof attribs.class !== 'undefined') {
|
||||||
// Filter out all classes other than ones starting with language- for syntax highlighting.
|
// Filter out all classes other than ones starting with language- for syntax highlighting.
|
||||||
const classes = attribs.class.split(/\s/).filter(function(cl) {
|
const classes = attribs.class.split(/\s/).filter(function(cl) {
|
||||||
return cl.startsWith('language-');
|
return cl.startsWith('language-') && !cl.startsWith('language-_');
|
||||||
});
|
});
|
||||||
attribs.class = classes.join(' ');
|
attribs.class = classes.join(' ');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -17,6 +18,8 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import {defer} from './utils/promise';
|
import {defer} from './utils/promise';
|
||||||
|
@ -25,36 +28,48 @@ import AsyncWrapper from './AsyncWrapper';
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
||||||
|
|
||||||
class ModalManager {
|
interface IModal<T extends any[]> {
|
||||||
constructor() {
|
elem: React.ReactNode;
|
||||||
this._counter = 0;
|
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
|
interface IHandle<T extends any[]> {
|
||||||
// this modal. Remove all other modals from the stack when this modal
|
finished: Promise<T>;
|
||||||
// is closed.
|
close(...args: T): void;
|
||||||
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
|
|
||||||
} */
|
|
||||||
];
|
|
||||||
|
|
||||||
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() {
|
interface IOptions<T extends any[]> {
|
||||||
return this._priorityModal || this._staticModal || this._modals.length > 0;
|
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);
|
let container = document.getElementById(DIALOG_CONTAINER_ID);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
|
@ -66,7 +81,7 @@ class ModalManager {
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrCreateStaticContainer() {
|
private static getOrCreateStaticContainer() {
|
||||||
let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID);
|
let container = document.getElementById(STATIC_DIALOG_CONTAINER_ID);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
|
@ -78,63 +93,99 @@ class ModalManager {
|
||||||
return container;
|
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);
|
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);
|
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||||
return this.appendDialog(...rest);
|
return this.appendDialog<T>(...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
createDialog(Element, ...rest) {
|
public createDialog<T extends any[]>(
|
||||||
return this.createDialogAsync(Promise.resolve(Element), ...rest);
|
Element: React.ComponentType,
|
||||||
|
...rest: ParametersWithoutFirst<ModalManager["createDialogAsync"]>
|
||||||
|
) {
|
||||||
|
return this.createDialogAsync<T>(Promise.resolve(Element), ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
appendDialog(Element, ...rest) {
|
public appendDialog<T extends any[]>(
|
||||||
return this.appendDialogAsync(Promise.resolve(Element), ...rest);
|
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);
|
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);
|
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||||
return this.appendDialogAsync(...rest);
|
return this.appendDialogAsync<T>(...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildModal(prom, props, className, options) {
|
private buildModal<T extends any[]>(
|
||||||
const modal = {};
|
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
|
// 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,
|
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
||||||
// otherwise we'll get confused.
|
// otherwise we'll get confused.
|
||||||
const modalCount = this._counter++;
|
const modalCount = this.counter++;
|
||||||
|
|
||||||
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
||||||
// property set here so you can't close the dialog from a button click!
|
// property set here so you can't close the dialog from a button click!
|
||||||
modal.elem = (
|
modal.elem = <AsyncWrapper key={modalCount} prom={prom} {...props} onFinished={closeDialog} />;
|
||||||
<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.close = closeDialog;
|
modal.close = closeDialog;
|
||||||
modal.closeReason = null;
|
|
||||||
|
|
||||||
return {modal, closeDialog, onFinishedProm};
|
return {modal, closeDialog, onFinishedProm};
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCloseFn(modal, props) {
|
private getCloseFn<T extends any[]>(
|
||||||
const deferred = defer();
|
modal: IModal<T>,
|
||||||
return [async (...args) => {
|
props: IProps<T>
|
||||||
|
): [IHandle<T>["close"], IHandle<T>["finished"]] {
|
||||||
|
const deferred = defer<T>();
|
||||||
|
return [async (...args: T) => {
|
||||||
if (modal.beforeClosePromise) {
|
if (modal.beforeClosePromise) {
|
||||||
await modal.beforeClosePromise;
|
await modal.beforeClosePromise;
|
||||||
} else if (modal.onBeforeClose) {
|
} else if (modal.onBeforeClose) {
|
||||||
|
@ -147,26 +198,26 @@ class ModalManager {
|
||||||
}
|
}
|
||||||
deferred.resolve(args);
|
deferred.resolve(args);
|
||||||
if (props && props.onFinished) props.onFinished.apply(null, 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) {
|
if (i >= 0) {
|
||||||
this._modals.splice(i, 1);
|
this.modals.splice(i, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._priorityModal === modal) {
|
if (this.priorityModal === modal) {
|
||||||
this._priorityModal = null;
|
this.priorityModal = null;
|
||||||
|
|
||||||
// XXX: This is destructive
|
// XXX: This is destructive
|
||||||
this._modals = [];
|
this.modals = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._staticModal === modal) {
|
if (this.staticModal === modal) {
|
||||||
this._staticModal = null;
|
this.staticModal = null;
|
||||||
|
|
||||||
// XXX: This is destructive
|
// XXX: This is destructive
|
||||||
this._modals = [];
|
this.modals = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this._reRender();
|
this.reRender();
|
||||||
}, deferred.promise];
|
}, deferred.promise];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,38 +258,49 @@ class ModalManager {
|
||||||
* @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog
|
* @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
|
* @returns {object} Object with 'close' parameter being a function that will close the dialog
|
||||||
*/
|
*/
|
||||||
createDialogAsync(prom, props, className, isPriorityModal, isStaticModal, options = {}) {
|
private createDialogAsync<T extends any[]>(
|
||||||
const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, options);
|
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) {
|
if (isPriorityModal) {
|
||||||
// XXX: This is destructive
|
// XXX: This is destructive
|
||||||
this._priorityModal = modal;
|
this.priorityModal = modal;
|
||||||
} else if (isStaticModal) {
|
} else if (isStaticModal) {
|
||||||
// This is intentionally destructive
|
// This is intentionally destructive
|
||||||
this._staticModal = modal;
|
this.staticModal = modal;
|
||||||
} else {
|
} else {
|
||||||
this._modals.unshift(modal);
|
this.modals.unshift(modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._reRender();
|
this.reRender();
|
||||||
return {
|
return {
|
||||||
close: closeDialog,
|
close: closeDialog,
|
||||||
finished: onFinishedProm,
|
finished: onFinishedProm,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
appendDialogAsync(prom, props, className) {
|
private appendDialogAsync<T extends any[]>(
|
||||||
const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className, {});
|
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.modals.push(modal);
|
||||||
this._reRender();
|
this.reRender();
|
||||||
return {
|
return {
|
||||||
close: closeDialog,
|
close: closeDialog,
|
||||||
finished: onFinishedProm,
|
finished: onFinishedProm,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackgroundClick() {
|
private onBackgroundClick = () => {
|
||||||
const modal = this._getCurrentModal();
|
const modal = this.getCurrentModal();
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -249,21 +311,21 @@ class ModalManager {
|
||||||
modal.closeReason = "backgroundClick";
|
modal.closeReason = "backgroundClick";
|
||||||
modal.close();
|
modal.close();
|
||||||
modal.closeReason = null;
|
modal.closeReason = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
private getCurrentModal(): IModal<any> {
|
||||||
|
return this.priorityModal ? this.priorityModal : (this.modals[0] || this.staticModal);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCurrentModal() {
|
private reRender() {
|
||||||
return this._priorityModal ? this._priorityModal : (this._modals[0] || this._staticModal);
|
if (this.modals.length === 0 && !this.priorityModal && !this.staticModal) {
|
||||||
}
|
|
||||||
|
|
||||||
_reRender() {
|
|
||||||
if (this._modals.length === 0 && !this._priorityModal && !this._staticModal) {
|
|
||||||
// If there is no modal to render, make all of Riot available
|
// If there is no modal to render, make all of Riot available
|
||||||
// to screen reader users again
|
// to screen reader users again
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'aria_unhide_main_app',
|
action: 'aria_unhide_main_app',
|
||||||
});
|
});
|
||||||
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
|
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
|
||||||
ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer());
|
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,49 +336,48 @@ class ModalManager {
|
||||||
action: 'aria_hide_main_app',
|
action: 'aria_hide_main_app',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._staticModal) {
|
if (this.staticModal) {
|
||||||
const classes = "mx_Dialog_wrapper mx_Dialog_staticWrapper "
|
const classes = classNames("mx_Dialog_wrapper mx_Dialog_staticWrapper", this.staticModal.className);
|
||||||
+ (this._staticModal.className ? this._staticModal.className : '');
|
|
||||||
|
|
||||||
const staticDialog = (
|
const staticDialog = (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className="mx_Dialog">
|
<div className="mx_Dialog">
|
||||||
{ this._staticModal.elem }
|
{ this.staticModal.elem }
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactDOM.render(staticDialog, this.getOrCreateStaticContainer());
|
ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
|
||||||
} else {
|
} else {
|
||||||
// This is safe to call repeatedly if we happen to do that
|
// This is safe to call repeatedly if we happen to do that
|
||||||
ReactDOM.unmountComponentAtNode(this.getOrCreateStaticContainer());
|
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateStaticContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = this._getCurrentModal();
|
const modal = this.getCurrentModal();
|
||||||
if (modal !== this._staticModal) {
|
if (modal !== this.staticModal) {
|
||||||
const classes = "mx_Dialog_wrapper "
|
const classes = classNames("mx_Dialog_wrapper", modal.className, {
|
||||||
+ (this._staticModal ? "mx_Dialog_wrapperWithStaticUnder " : '')
|
mx_Dialog_wrapperWithStaticUnder: this.staticModal,
|
||||||
+ (modal.className ? modal.className : '');
|
});
|
||||||
|
|
||||||
const dialog = (
|
const dialog = (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className="mx_Dialog">
|
<div className="mx_Dialog">
|
||||||
{modal.elem}
|
{modal.elem}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_background" onClick={this.onBackgroundClick}></div>
|
<div className="mx_Dialog_background" onClick={this.onBackgroundClick} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactDOM.render(dialog, this.getOrCreateContainer());
|
ReactDOM.render(dialog, ModalManager.getOrCreateContainer());
|
||||||
} else {
|
} else {
|
||||||
// This is safe to call repeatedly if we happen to do that
|
// This is safe to call repeatedly if we happen to do that
|
||||||
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
|
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.singletonModalManager) {
|
if (!window.singletonModalManager) {
|
||||||
global.singletonModalManager = new ModalManager();
|
window.singletonModalManager = new ModalManager();
|
||||||
}
|
}
|
||||||
export default global.singletonModalManager;
|
export default window.singletonModalManager;
|
|
@ -67,8 +67,8 @@ export default class RebrandListener {
|
||||||
private nagAgainAt?: number = null;
|
private nagAgainAt?: number = null;
|
||||||
|
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
if (!window.mx_RebrandListener) window.mx_RebrandListener = new RebrandListener();
|
if (!window.mxRebrandListener) window.mxRebrandListener = new RebrandListener();
|
||||||
return window.mx_RebrandListener;
|
return window.mxRebrandListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -114,6 +114,11 @@ export default class RebrandListener {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onOneTimeToastDismiss = async () => {
|
||||||
|
localStorage.setItem('mx_rename_dialog_dismissed', 'true');
|
||||||
|
this.recheck();
|
||||||
|
};
|
||||||
|
|
||||||
onNagTimerFired = () => {
|
onNagTimerFired = () => {
|
||||||
this._reshowTimer = null;
|
this._reshowTimer = null;
|
||||||
this.nagAgainAt = null;
|
this.nagAgainAt = null;
|
||||||
|
@ -143,10 +148,14 @@ export default class RebrandListener {
|
||||||
|
|
||||||
if (nagToast || oneTimeToast) {
|
if (nagToast || oneTimeToast) {
|
||||||
let description;
|
let description;
|
||||||
|
let rejectLabel = null;
|
||||||
|
let onReject = null;
|
||||||
if (nagToast) {
|
if (nagToast) {
|
||||||
description = _t("Use your account to sign in to the latest version");
|
description = _t("Use your account to sign in to the latest version");
|
||||||
} else {
|
} else {
|
||||||
description = _t("We’re excited to announce Riot is now Element");
|
description = _t("We’re excited to announce Riot is now Element");
|
||||||
|
rejectLabel = _t("Dismiss");
|
||||||
|
onReject = this.onOneTimeToastDismiss;
|
||||||
}
|
}
|
||||||
|
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
|
@ -157,6 +166,8 @@ export default class RebrandListener {
|
||||||
description,
|
description,
|
||||||
acceptLabel: _t("Learn More"),
|
acceptLabel: _t("Learn More"),
|
||||||
onAccept: nagToast ? this.onNagToastLearnMore : this.onOneTimeToastLearnMore,
|
onAccept: nagToast ? this.onNagToastLearnMore : this.onOneTimeToastLearnMore,
|
||||||
|
rejectLabel,
|
||||||
|
onReject,
|
||||||
},
|
},
|
||||||
component: GenericToast,
|
component: GenericToast,
|
||||||
priority: 20,
|
priority: 20,
|
||||||
|
|
|
@ -34,27 +34,6 @@ export function shouldShowMentionBadge(roomNotifState) {
|
||||||
return MENTION_BADGE_STATES.includes(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) {
|
export function aggregateNotificationCount(rooms) {
|
||||||
return rooms.reduce((result, room) => {
|
return rooms.reduce((result, room) => {
|
||||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||||
|
|
|
@ -401,14 +401,16 @@ export const Commands = [
|
||||||
// If we need an identity server but don't have one, things
|
// If we need an identity server but don't have one, things
|
||||||
// get a bit more complex here, but we try to show something
|
// get a bit more complex here, but we try to show something
|
||||||
// meaningful.
|
// meaningful.
|
||||||
let finished = Promise.resolve();
|
let prom = Promise.resolve();
|
||||||
if (
|
if (
|
||||||
getAddressType(address) === 'email' &&
|
getAddressType(address) === 'email' &&
|
||||||
!MatrixClientPeg.get().getIdentityServerUrl()
|
!MatrixClientPeg.get().getIdentityServerUrl()
|
||||||
) {
|
) {
|
||||||
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
|
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
|
||||||
if (defaultIdentityServerUrl) {
|
if (defaultIdentityServerUrl) {
|
||||||
({ finished } = Modal.createTrackedDialog('Slash Commands', 'Identity server',
|
const { finished } = Modal.createTrackedDialog<[boolean]>(
|
||||||
|
'Slash Commands',
|
||||||
|
'Identity server',
|
||||||
QuestionDialog, {
|
QuestionDialog, {
|
||||||
title: _t("Use an identity server"),
|
title: _t("Use an identity server"),
|
||||||
description: <p>{_t(
|
description: <p>{_t(
|
||||||
|
@ -421,9 +423,9 @@ export const Commands = [
|
||||||
)}</p>,
|
)}</p>,
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
},
|
},
|
||||||
));
|
);
|
||||||
|
|
||||||
finished = finished.then(([useDefault]: any) => {
|
prom = finished.then(([useDefault]) => {
|
||||||
if (useDefault) {
|
if (useDefault) {
|
||||||
useDefaultIdentityServer();
|
useDefaultIdentityServer();
|
||||||
return;
|
return;
|
||||||
|
@ -435,7 +437,7 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const inviter = new MultiInviter(roomId);
|
const inviter = new MultiInviter(roomId);
|
||||||
return success(finished.then(() => {
|
return success(prom.then(() => {
|
||||||
return inviter.invite([address]);
|
return inviter.invite([address]);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (inviter.getCompletionState(address) !== "invited") {
|
if (inviter.getCompletionState(address) !== "invited") {
|
||||||
|
@ -1049,7 +1051,7 @@ export function parseCommandString(input) {
|
||||||
// trim any trailing whitespace, as it can confuse the parser for
|
// trim any trailing whitespace, as it can confuse the parser for
|
||||||
// IRC-style commands
|
// IRC-style commands
|
||||||
input = input.replace(/\s+$/, '');
|
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)*))?$/);
|
const bits = input.match(/^(\S+?)(?: +((.|\n)*))?$/);
|
||||||
let cmd;
|
let cmd;
|
||||||
|
|
|
@ -18,9 +18,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
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;
|
label?: string;
|
||||||
// whether or not the context menu is currently open
|
// whether or not the context menu is currently open
|
||||||
isExpanded: boolean;
|
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 { asyncAction } from './actionCreators';
|
||||||
import { TAG_DM } from '../stores/RoomListStore';
|
|
||||||
import Modal from '../Modal';
|
import Modal from '../Modal';
|
||||||
import * as Rooms from '../Rooms';
|
import * as Rooms from '../Rooms';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
|
@ -24,7 +23,9 @@ import * as sdk from '../index';
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { AsyncActionPayload } from "../dispatcher/payloads";
|
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 {
|
export default class RoomListActions {
|
||||||
/**
|
/**
|
||||||
|
@ -51,9 +52,9 @@ export default class RoomListActions {
|
||||||
let metaData = null;
|
let metaData = null;
|
||||||
|
|
||||||
// Is the tag ordered manually?
|
// Is the tag ordered manually?
|
||||||
if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
const store = RoomListStore.instance;
|
||||||
const lists = RoomListStoreTempProxy.getRoomLists();
|
if (newTag && store.getTagSorting(newTag) === SortAlgorithm.Manual) {
|
||||||
const newList = [...lists[newTag]];
|
const newList = [...store.orderedLists[newTag]];
|
||||||
|
|
||||||
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
||||||
|
|
||||||
|
@ -81,11 +82,11 @@ export default class RoomListActions {
|
||||||
const roomId = room.roomId;
|
const roomId = room.roomId;
|
||||||
|
|
||||||
// Evil hack to get DMs behaving
|
// Evil hack to get DMs behaving
|
||||||
if ((oldTag === undefined && newTag === TAG_DM) ||
|
if ((oldTag === undefined && newTag === DefaultTagID.DM) ||
|
||||||
(oldTag === TAG_DM && newTag === undefined)
|
(oldTag === DefaultTagID.DM && newTag === undefined)
|
||||||
) {
|
) {
|
||||||
return Rooms.guessAndSetDMRoom(
|
return Rooms.guessAndSetDMRoom(
|
||||||
room, newTag === TAG_DM,
|
room, newTag === DefaultTagID.DM,
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Failed to set direct chat tag " + err);
|
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.
|
// but we avoid ever doing a request with TAG_DM.
|
||||||
//
|
//
|
||||||
// if we moved lists, remove the old tag
|
// if we moved lists, remove the old tag
|
||||||
if (oldTag && oldTag !== TAG_DM &&
|
if (oldTag && oldTag !== DefaultTagID.DM &&
|
||||||
hasChangedSubLists
|
hasChangedSubLists
|
||||||
) {
|
) {
|
||||||
const promiseToDelete = matrixClient.deleteRoomTag(
|
const promiseToDelete = matrixClient.deleteRoomTag(
|
||||||
roomId, oldTag,
|
roomId, oldTag,
|
||||||
).catch(function (err) {
|
).catch(function(err) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
||||||
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
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 we moved lists or the ordering changed, add the new tag
|
||||||
if (newTag && newTag !== TAG_DM &&
|
if (newTag && newTag !== DefaultTagID.DM &&
|
||||||
(hasChangedSubLists || metaData)
|
(hasChangedSubLists || metaData)
|
||||||
) {
|
) {
|
||||||
// metaData is the body of the PUT to set the tag, so it must
|
// metaData is the body of the PUT to set the tag, so it must
|
||||||
// at least be an empty object.
|
// at least be an empty object.
|
||||||
metaData = metaData || {};
|
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");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Failed to add tag " + newTag + " to room: " + err);
|
console.error("Failed to add tag " + newTag + " to room: " + err);
|
||||||
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
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";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
export default class TagOrderActions {
|
export default class TagOrderActions {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an action thunk that will do an asynchronous request to
|
* Creates an action thunk that will do an asynchronous request to
|
||||||
* move a tag in TagOrderStore to destinationIx.
|
* move a tag in TagOrderStore to destinationIx.
|
||||||
|
|
|
@ -34,7 +34,8 @@ import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
// Match for ascii-style ";-)" emoticons or ":wink:" shortcodes provided by emojibase
|
// 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 {
|
interface IEmojiShort {
|
||||||
emoji: IEmoji;
|
emoji: IEmoji;
|
||||||
|
|
|
@ -18,16 +18,15 @@ limitations under the License.
|
||||||
|
|
||||||
import _at from 'lodash/at';
|
import _at from 'lodash/at';
|
||||||
import _uniq from 'lodash/uniq';
|
import _uniq from 'lodash/uniq';
|
||||||
|
import {removeHiddenChars} from "matrix-js-sdk/src/utils";
|
||||||
function stripDiacritics(str: string): string {
|
|
||||||
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IOptions<T extends {}> {
|
interface IOptions<T extends {}> {
|
||||||
keys: Array<string | keyof T>;
|
keys: Array<string | keyof T>;
|
||||||
funcs?: Array<(T) => string>;
|
funcs?: Array<(T) => string>;
|
||||||
shouldMatchWordsOnly?: boolean;
|
shouldMatchWordsOnly?: boolean;
|
||||||
shouldMatchPrefix?: 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> {
|
export default class QueryMatcher<T extends Object> {
|
||||||
private _options: IOptions<T>;
|
private _options: IOptions<T>;
|
||||||
private _keys: IOptions<T>["keys"];
|
|
||||||
private _funcs: Required<IOptions<T>["funcs"]>;
|
|
||||||
private _items: Map<string, {object: T, keyWeight: number}[]>;
|
private _items: Map<string, {object: T, keyWeight: number}[]>;
|
||||||
|
|
||||||
constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
|
constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
|
||||||
this._options = options;
|
this._options = options;
|
||||||
this._keys = options.keys;
|
|
||||||
this._funcs = options.funcs || [];
|
|
||||||
|
|
||||||
this.setObjects(objects);
|
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
|
// type for their values. We assume that those values who's keys have
|
||||||
// been specified will be string. Also, we cannot infer all the
|
// been specified will be string. Also, we cannot infer all the
|
||||||
// types of the keys of the objects at compile.
|
// 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) {
|
if (this._options.funcs) {
|
||||||
keyValues.push(f(object));
|
for (const f of this._options.funcs) {
|
||||||
|
keyValues.push(f(object));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [index, keyValue] of Object.entries(keyValues)) {
|
for (const [index, keyValue] of Object.entries(keyValues)) {
|
||||||
if (!keyValue) continue; // skip falsy keyValues
|
if (!keyValue) continue; // skip falsy keyValues
|
||||||
const key = stripDiacritics(keyValue).toLowerCase();
|
const key = this.processQuery(keyValue);
|
||||||
if (!this._items.has(key)) {
|
if (!this._items.has(key)) {
|
||||||
this._items.set(key, []);
|
this._items.set(key, []);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +96,7 @@ export default class QueryMatcher<T extends Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
match(query: string): T[] {
|
match(query: string): T[] {
|
||||||
query = stripDiacritics(query).toLowerCase();
|
query = this.processQuery(query);
|
||||||
if (this._options.shouldMatchWordsOnly) {
|
if (this._options.shouldMatchWordsOnly) {
|
||||||
query = query.replace(/[^\w]/g, '');
|
query = query.replace(/[^\w]/g, '');
|
||||||
}
|
}
|
||||||
|
@ -118,7 +115,7 @@ export default class QueryMatcher<T extends Object> {
|
||||||
const index = resultKey.indexOf(query);
|
const index = resultKey.indexOf(query);
|
||||||
if (index !== -1 && (!this._options.shouldMatchPrefix || index === 0)) {
|
if (index !== -1 && (!this._options.shouldMatchPrefix || index === 0)) {
|
||||||
matches.push(
|
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.
|
// Now map the keys to the result objects. Also remove any duplicates.
|
||||||
return _uniq(matches.map((match) => match.object));
|
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() {
|
render() {
|
||||||
return (<div
|
return (<div
|
||||||
ref={this._collectContainerRef}
|
ref={this._collectContainerRef}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
|
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
|
||||||
onScroll={this.props.onScroll}
|
onScroll={this.props.onScroll}
|
||||||
onWheel={this.props.onWheel}
|
onWheel={this.props.onWheel}
|
||||||
>
|
tabIndex={this.props.tabIndex}
|
||||||
|
>
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -461,6 +461,7 @@ export function createMenu(ElementClass, props) {
|
||||||
|
|
||||||
// re-export the semantic helper components for simplicity
|
// re-export the semantic helper components for simplicity
|
||||||
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
||||||
|
export {ContextMenuTooltipButton} from "../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||||
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
||||||
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
||||||
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
||||||
|
|
|
@ -72,17 +72,17 @@ class CustomRoomTagTile extends React.Component {
|
||||||
const tag = this.props.tag;
|
const tag = this.props.tag;
|
||||||
const avatarHeight = 40;
|
const avatarHeight = 40;
|
||||||
const className = classNames({
|
const className = classNames({
|
||||||
CustomRoomTagPanel_tileSelected: tag.selected,
|
"CustomRoomTagPanel_tileSelected": tag.selected,
|
||||||
});
|
});
|
||||||
const name = tag.name;
|
const name = tag.name;
|
||||||
const badge = tag.badge;
|
const badgeNotifState = tag.badgeNotifState;
|
||||||
let badgeElement;
|
let badgeElement;
|
||||||
if (badge) {
|
if (badgeNotifState) {
|
||||||
const badgeClasses = classNames({
|
const badgeClasses = classNames({
|
||||||
"mx_TagTile_badge": true,
|
"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 (
|
return (
|
||||||
|
|
|
@ -192,7 +192,7 @@ export default class IndicatorScrollbar extends React.Component {
|
||||||
ref={this._collectScrollerComponent}
|
ref={this._collectScrollerComponent}
|
||||||
wrappedRef={this._collectScroller}
|
wrappedRef={this._collectScroller}
|
||||||
onWheel={this.onMouseWheel}
|
onWheel={this.onMouseWheel}
|
||||||
{... this.props}
|
{...this.props}
|
||||||
>
|
>
|
||||||
{ leftOverflowIndicator }
|
{ leftOverflowIndicator }
|
||||||
{ this.props.children }
|
{ 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 * as React from "react";
|
||||||
import { createRef } from "react";
|
import { createRef } from "react";
|
||||||
import TagPanel from "./TagPanel";
|
import TagPanel from "./TagPanel";
|
||||||
|
import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import RoomList2 from "../views/rooms/RoomList2";
|
import RoomList from "../views/rooms/RoomList";
|
||||||
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist2";
|
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import UserMenu from "./UserMenu";
|
import UserMenu from "./UserMenu";
|
||||||
import RoomSearch from "./RoomSearch";
|
import RoomSearch from "./RoomSearch";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs";
|
||||||
import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2";
|
|
||||||
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
|
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
|
||||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
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 {Key} from "../../Keyboard";
|
||||||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||||
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||||
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -43,7 +44,6 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
searchFilter: string;
|
|
||||||
showBreadcrumbs: boolean;
|
showBreadcrumbs: boolean;
|
||||||
showTagPanel: boolean;
|
showTagPanel: boolean;
|
||||||
}
|
}
|
||||||
|
@ -52,14 +52,15 @@ interface IState {
|
||||||
const cssClasses = [
|
const cssClasses = [
|
||||||
"mx_RoomSearch_input",
|
"mx_RoomSearch_input",
|
||||||
"mx_RoomSearch_icon", // minimized <RoomSearch />
|
"mx_RoomSearch_icon", // minimized <RoomSearch />
|
||||||
"mx_RoomSublist2_headerText",
|
"mx_RoomSublist_headerText",
|
||||||
"mx_RoomTile2",
|
"mx_RoomTile",
|
||||||
"mx_RoomSublist2_showNButton",
|
"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 listContainerRef: React.RefObject<HTMLDivElement> = createRef();
|
||||||
private tagPanelWatcherRef: string;
|
private tagPanelWatcherRef: string;
|
||||||
|
private bgImageWatcherRef: string;
|
||||||
private focusedElement = null;
|
private focusedElement = null;
|
||||||
private isDoingStickyHeaders = false;
|
private isDoingStickyHeaders = false;
|
||||||
|
|
||||||
|
@ -67,13 +68,15 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchFilter: "",
|
|
||||||
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
||||||
showTagPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
showTagPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
||||||
};
|
};
|
||||||
|
|
||||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
RoomListStore.instance.on(LISTS_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.tagPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
||||||
this.setState({showTagPanel: SettingsStore.getValue("TagPanel.enableTagPanel")});
|
this.setState({showTagPanel: SettingsStore.getValue("TagPanel.enableTagPanel")});
|
||||||
});
|
});
|
||||||
|
@ -85,15 +88,13 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
SettingsStore.unwatchSetting(this.tagPanelWatcherRef);
|
SettingsStore.unwatchSetting(this.tagPanelWatcherRef);
|
||||||
|
SettingsStore.unwatchSetting(this.bgImageWatcherRef);
|
||||||
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
RoomListStore.instance.off(LISTS_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);
|
this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSearch = (term: string): void => {
|
|
||||||
this.setState({searchFilter: term});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onExplore = () => {
|
private onExplore = () => {
|
||||||
dis.fire(Action.ViewRoomDirectory);
|
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) {
|
private handleStickyHeaders(list: HTMLDivElement) {
|
||||||
if (this.isDoingStickyHeaders) return;
|
if (this.isDoingStickyHeaders) return;
|
||||||
this.isDoingStickyHeaders = true;
|
this.isDoingStickyHeaders = true;
|
||||||
|
@ -121,7 +136,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
private doStickyHeaders(list: HTMLDivElement) {
|
private doStickyHeaders(list: HTMLDivElement) {
|
||||||
const topEdge = list.scrollTop;
|
const topEdge = list.scrollTop;
|
||||||
const bottomEdge = list.offsetHeight + 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 headerRightMargin = 16; // calculated from margins and widths to align with non-sticky tiles
|
||||||
const headerStickyWidth = list.clientWidth - headerRightMargin;
|
const headerStickyWidth = list.clientWidth - headerRightMargin;
|
||||||
|
@ -137,7 +152,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
let lastTopHeader;
|
let lastTopHeader;
|
||||||
let firstBottomHeader;
|
let firstBottomHeader;
|
||||||
for (const sublist of sublists) {
|
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
|
header.style.removeProperty("display"); // always clear display:none first
|
||||||
|
|
||||||
// When an element is <=40% off screen, make it take over
|
// 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 (style.stickyTop) {
|
||||||
if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) {
|
if (!header.classList.contains("mx_RoomSublist_headerContainer_stickyTop")) {
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_stickyTop");
|
header.classList.add("mx_RoomSublist_headerContainer_stickyTop");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTop = `${list.parentElement.offsetTop}px`;
|
const newTop = `${list.parentElement.offsetTop}px`;
|
||||||
|
@ -182,8 +197,8 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
header.style.top = newTop;
|
header.style.top = newTop;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) {
|
if (header.classList.contains("mx_RoomSublist_headerContainer_stickyTop")) {
|
||||||
header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop");
|
header.classList.remove("mx_RoomSublist_headerContainer_stickyTop");
|
||||||
}
|
}
|
||||||
if (header.style.top) {
|
if (header.style.top) {
|
||||||
header.style.removeProperty('top');
|
header.style.removeProperty('top');
|
||||||
|
@ -191,18 +206,18 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.stickyBottom) {
|
if (style.stickyBottom) {
|
||||||
if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) {
|
if (!header.classList.contains("mx_RoomSublist_headerContainer_stickyBottom")) {
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
|
header.classList.add("mx_RoomSublist_headerContainer_stickyBottom");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) {
|
if (header.classList.contains("mx_RoomSublist_headerContainer_stickyBottom")) {
|
||||||
header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom");
|
header.classList.remove("mx_RoomSublist_headerContainer_stickyBottom");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.stickyTop || style.stickyBottom) {
|
if (style.stickyTop || style.stickyBottom) {
|
||||||
if (!header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) {
|
if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) {
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
|
header.classList.add("mx_RoomSublist_headerContainer_sticky");
|
||||||
}
|
}
|
||||||
|
|
||||||
const newWidth = `${headerStickyWidth}px`;
|
const newWidth = `${headerStickyWidth}px`;
|
||||||
|
@ -210,8 +225,8 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
header.style.width = newWidth;
|
header.style.width = newWidth;
|
||||||
}
|
}
|
||||||
} else if (!style.stickyTop && !style.stickyBottom) {
|
} else if (!style.stickyTop && !style.stickyBottom) {
|
||||||
if (header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) {
|
if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) {
|
||||||
header.classList.remove("mx_RoomSublist2_headerContainer_sticky");
|
header.classList.remove("mx_RoomSublist_headerContainer_sticky");
|
||||||
}
|
}
|
||||||
if (header.style.width) {
|
if (header.style.width) {
|
||||||
header.style.removeProperty('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
|
// add appropriate sticky classes to wrapper so it has
|
||||||
// the necessary top/bottom padding to put the sticky header in
|
// 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) {
|
if (lastTopHeader) {
|
||||||
listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyTop");
|
listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyTop");
|
||||||
} else {
|
} else {
|
||||||
listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyTop");
|
listWrapper.classList.remove("mx_LeftPanel_roomListWrapper_stickyTop");
|
||||||
}
|
}
|
||||||
if (firstBottomHeader) {
|
if (firstBottomHeader) {
|
||||||
listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyBottom");
|
listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyBottom");
|
||||||
} else {
|
} 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 = () => {
|
private onEnter = () => {
|
||||||
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile2");
|
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile");
|
||||||
if (firstRoom) {
|
if (firstRoom) {
|
||||||
firstRoom.click();
|
firstRoom.click();
|
||||||
return true; // to get the field to clear
|
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 {
|
private renderHeader(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_LeftPanel2_userHeader">
|
<div className="mx_LeftPanel_userHeader">
|
||||||
<UserMenu isMinimized={this.props.isMinimized} />
|
<UserMenu isMinimized={this.props.isMinimized} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -324,10 +339,13 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
if (this.state.showBreadcrumbs && !this.props.isMinimized) {
|
if (this.state.showBreadcrumbs && !this.props.isMinimized) {
|
||||||
return (
|
return (
|
||||||
<IndicatorScrollbar
|
<IndicatorScrollbar
|
||||||
className="mx_LeftPanel2_breadcrumbsContainer mx_AutoHideScrollbar"
|
className="mx_LeftPanel_breadcrumbsContainer mx_AutoHideScrollbar"
|
||||||
verticalScrollsHorizontally={true}
|
verticalScrollsHorizontally={true}
|
||||||
|
// Firefox sometimes makes this element focusable due to
|
||||||
|
// overflow:scroll;, so force it out of tab order.
|
||||||
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<RoomBreadcrumbs2 />
|
<RoomBreadcrumbs />
|
||||||
</IndicatorScrollbar>
|
</IndicatorScrollbar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -336,19 +354,18 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
private renderSearchExplore(): React.ReactNode {
|
private renderSearchExplore(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="mx_LeftPanel2_filterContainer"
|
className="mx_LeftPanel_filterContainer"
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
>
|
>
|
||||||
<RoomSearch
|
<RoomSearch
|
||||||
onQueryUpdate={this.onSearch}
|
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
onVerticalArrow={this.onKeyDown}
|
onVerticalArrow={this.onKeyDown}
|
||||||
onEnter={this.onEnter}
|
onEnter={this.onEnter}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className="mx_LeftPanel2_exploreButton"
|
className="mx_LeftPanel_exploreButton"
|
||||||
onClick={this.onExplore}
|
onClick={this.onExplore}
|
||||||
title={_t("Explore rooms")}
|
title={_t("Explore rooms")}
|
||||||
/>
|
/>
|
||||||
|
@ -358,16 +375,16 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const tagPanel = !this.state.showTagPanel ? null : (
|
const tagPanel = !this.state.showTagPanel ? null : (
|
||||||
<div className="mx_LeftPanel2_tagPanelContainer">
|
<div className="mx_LeftPanel_tagPanelContainer">
|
||||||
<TagPanel/>
|
<TagPanel/>
|
||||||
|
{SettingsStore.isFeatureEnabled("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const roomList = <RoomList2
|
const roomList = <RoomList
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
resizeNotifier={null}
|
resizeNotifier={null}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
searchFilter={this.state.searchFilter}
|
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
|
@ -375,24 +392,24 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
const containerClasses = classNames({
|
const containerClasses = classNames({
|
||||||
"mx_LeftPanel2": true,
|
"mx_LeftPanel": true,
|
||||||
"mx_LeftPanel2_hasTagPanel": !!tagPanel,
|
"mx_LeftPanel_hasTagPanel": !!tagPanel,
|
||||||
"mx_LeftPanel2_minimized": this.props.isMinimized,
|
"mx_LeftPanel_minimized": this.props.isMinimized,
|
||||||
});
|
});
|
||||||
|
|
||||||
const roomListClasses = classNames(
|
const roomListClasses = classNames(
|
||||||
"mx_LeftPanel2_actualRoomListContainer",
|
"mx_LeftPanel_actualRoomListContainer",
|
||||||
"mx_AutoHideScrollbar",
|
"mx_AutoHideScrollbar",
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
{tagPanel}
|
{tagPanel}
|
||||||
<aside className="mx_LeftPanel2_roomListContainer">
|
<aside className="mx_LeftPanel_roomListContainer">
|
||||||
{this.renderHeader()}
|
{this.renderHeader()}
|
||||||
{this.renderSearchExplore()}
|
{this.renderSearchExplore()}
|
||||||
{this.renderBreadcrumbs()}
|
{this.renderBreadcrumbs()}
|
||||||
<div className="mx_LeftPanel2_roomListWrapper">
|
<div className="mx_LeftPanel_roomListWrapper">
|
||||||
<div
|
<div
|
||||||
className={roomListClasses}
|
className={roomListClasses}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
|
@ -40,7 +40,6 @@ import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
||||||
import HomePage from "./HomePage";
|
import HomePage from "./HomePage";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy";
|
|
||||||
import { DefaultTagID } from "../../stores/room-list/models";
|
import { DefaultTagID } from "../../stores/room-list/models";
|
||||||
import {
|
import {
|
||||||
showToast as showSetPasswordToast,
|
showToast as showSetPasswordToast,
|
||||||
|
@ -51,9 +50,10 @@ import {
|
||||||
hideToast as hideServerLimitToast
|
hideToast as hideServerLimitToast
|
||||||
} from "../../toasts/ServerLimitToast";
|
} from "../../toasts/ServerLimitToast";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import LeftPanel2 from "./LeftPanel2";
|
import LeftPanel from "./LeftPanel";
|
||||||
import CallContainer from '../views/voip/CallContainer';
|
import CallContainer from '../views/voip/CallContainer';
|
||||||
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
|
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)
|
// 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.
|
// 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) => {
|
onRoomStateEvents = (ev, state) => {
|
||||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||||
if (roomLists[DefaultTagID.ServerNotice] && roomLists[DefaultTagID.ServerNotice].some(r => r.roomId === ev.getRoomId())) {
|
if (serverNoticeList && serverNoticeList.some(r => r.roomId === ev.getRoomId())) {
|
||||||
this._updateServerNoticeEvents();
|
this._updateServerNoticeEvents();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -328,11 +328,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateServerNoticeEvents = async () => {
|
_updateServerNoticeEvents = async () => {
|
||||||
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||||
if (!roomLists[DefaultTagID.ServerNotice]) return [];
|
if (!serverNoticeList) return [];
|
||||||
|
|
||||||
const events = [];
|
const events = [];
|
||||||
for (const room of roomLists[DefaultTagID.ServerNotice]) {
|
for (const room of serverNoticeList) {
|
||||||
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||||
|
|
||||||
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
||||||
|
@ -607,7 +607,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
|
||||||
const RoomView = sdk.getComponent('structures.RoomView');
|
const RoomView = sdk.getComponent('structures.RoomView');
|
||||||
const UserView = sdk.getComponent('structures.UserView');
|
const UserView = sdk.getComponent('structures.UserView');
|
||||||
const GroupView = sdk.getComponent('structures.GroupView');
|
const GroupView = sdk.getComponent('structures.GroupView');
|
||||||
|
@ -661,21 +660,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftPanel = (
|
const leftPanel = (
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
|
isMinimized={this.props.collapseLhs || false}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
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 (
|
return (
|
||||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
|
|
|
@ -16,77 +16,24 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ResizeHandle from '../views/elements/ResizeHandle';
|
import { Resizable } from 're-resizable';
|
||||||
import {Resizer, FixedDistributor} from '../../resizer';
|
|
||||||
|
|
||||||
export default class MainSplit extends React.Component {
|
export default class MainSplit extends React.Component {
|
||||||
constructor(props) {
|
_onResized = (event, direction, refToElement, delta) => {
|
||||||
super(props);
|
window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width);
|
||||||
this._setResizeContainerRef = this._setResizeContainerRef.bind(this);
|
|
||||||
this._onResized = this._onResized.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onResized(size) {
|
_loadSidePanelSize() {
|
||||||
window.localStorage.setItem("mx_rhs_size", size);
|
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
||||||
if (this.props.resizeNotifier) {
|
|
||||||
this.props.resizeNotifier.notifyRightHandleResized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_createResizer() {
|
if (isNaN(rhsSize)) {
|
||||||
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 {
|
|
||||||
rhsSize = 350;
|
rhsSize = 350;
|
||||||
}
|
}
|
||||||
resizer.forHandleAt(0).resize(rhsSize);
|
|
||||||
|
|
||||||
resizer.attach();
|
return {
|
||||||
this.resizer = resizer;
|
height: "100%",
|
||||||
}
|
width: rhsSize,
|
||||||
|
};
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -97,13 +44,29 @@ export default class MainSplit extends React.Component {
|
||||||
|
|
||||||
let children;
|
let children;
|
||||||
if (hasResizer) {
|
if (hasResizer) {
|
||||||
children = <React.Fragment>
|
children = <Resizable
|
||||||
<ResizeHandle reverse={true} />
|
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 }
|
{ panelView }
|
||||||
</React.Fragment>;
|
</Resizable>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_MainSplit" ref={hasResizer ? this._setResizeContainerRef : undefined}>
|
return <div className="mx_MainSplit">
|
||||||
{ bodyView }
|
{ bodyView }
|
||||||
{ children }
|
{ children }
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -58,7 +58,6 @@ import { messageForSyncError } from '../../utils/ErrorUtils';
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
||||||
import DMRoomMap from '../../utils/DMRoomMap';
|
import DMRoomMap from '../../utils/DMRoomMap';
|
||||||
import { countRoomsWithNotif } from '../../RoomNotifs';
|
|
||||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||||
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
||||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
|
@ -75,6 +74,7 @@ import {
|
||||||
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||||
|
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -1844,21 +1844,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatusIndicator(state: string, prevState: string) {
|
updateStatusIndicator(state: string, prevState: string) {
|
||||||
// only count visible rooms to not torment the user with notification counts in rooms they can't see
|
const notificationState = RoomNotificationStateStore.instance.globalState;
|
||||||
// it will include highlights from the previous version of the room internally
|
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
|
||||||
const notifCount = countRoomsWithNotif(MatrixClientPeg.get().getVisibleRooms()).count;
|
|
||||||
|
|
||||||
if (PlatformPeg.get()) {
|
if (PlatformPeg.get()) {
|
||||||
PlatformPeg.get().setErrorStatus(state === 'ERROR');
|
PlatformPeg.get().setErrorStatus(state === 'ERROR');
|
||||||
PlatformPeg.get().setNotificationCount(notifCount);
|
PlatformPeg.get().setNotificationCount(numUnreadRooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subTitleStatus = '';
|
this.subTitleStatus = '';
|
||||||
if (state === "ERROR") {
|
if (state === "ERROR") {
|
||||||
this.subTitleStatus += `[${_t("Offline")}] `;
|
this.subTitleStatus += `[${_t("Offline")}] `;
|
||||||
}
|
}
|
||||||
if (notifCount > 0) {
|
if (numUnreadRooms > 0) {
|
||||||
this.subTitleStatus += `[${notifCount}]`;
|
this.subTitleStatus += `[${numUnreadRooms}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setPageSubtitle();
|
this.setPageSubtitle();
|
||||||
|
|
|
@ -346,9 +346,9 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_isUnmounting() {
|
_isUnmounting = () => {
|
||||||
return !this._isMounted;
|
return !this._isMounted;
|
||||||
}
|
};
|
||||||
|
|
||||||
// TODO: Implement granular (per-room) hide options
|
// TODO: Implement granular (per-room) hide options
|
||||||
_shouldShowEvent(mxEv) {
|
_shouldShowEvent(mxEv) {
|
||||||
|
@ -571,12 +571,10 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
const readReceipts = this._readReceiptsByEvent[eventId];
|
const readReceipts = this._readReceiptsByEvent[eventId];
|
||||||
|
|
||||||
// Dev note: `this._isUnmounting.bind(this)` is important - it ensures that
|
// use txnId as key if available so that we don't remount during sending
|
||||||
// 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).
|
|
||||||
ret.push(
|
ret.push(
|
||||||
<li key={eventId}
|
<li
|
||||||
|
key={mxEv.getTxnId() || eventId}
|
||||||
ref={this._collectEventNode.bind(this, eventId)}
|
ref={this._collectEventNode.bind(this, eventId)}
|
||||||
data-scroll-tokens={scrollToken}
|
data-scroll-tokens={scrollToken}
|
||||||
>
|
>
|
||||||
|
@ -590,7 +588,7 @@ export default class MessagePanel extends React.Component {
|
||||||
readReceipts={readReceipts}
|
readReceipts={readReceipts}
|
||||||
readReceiptMap={this._readReceiptMap}
|
readReceiptMap={this._readReceiptMap}
|
||||||
showUrlPreview={this.props.showUrlPreview}
|
showUrlPreview={this.props.showUrlPreview}
|
||||||
checkUnmounting={this._isUnmounting.bind(this)}
|
checkUnmounting={this._isUnmounting}
|
||||||
eventSendStatus={mxEv.getAssociatedStatus()}
|
eventSendStatus={mxEv.getAssociatedStatus()}
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
isTwelveHour={this.props.isTwelveHour}
|
isTwelveHour={this.props.isTwelveHour}
|
||||||
|
|
|
@ -24,9 +24,10 @@ import { throttle } from 'lodash';
|
||||||
import { Key } from "../../Keyboard";
|
import { Key } from "../../Keyboard";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
|
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onQueryUpdate: (newQuery: string) => void;
|
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
onVerticalArrow(ev: React.KeyboardEvent): void;
|
onVerticalArrow(ev: React.KeyboardEvent): void;
|
||||||
onEnter(ev: React.KeyboardEvent): boolean;
|
onEnter(ev: React.KeyboardEvent): boolean;
|
||||||
|
@ -40,6 +41,7 @@ interface IState {
|
||||||
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
private dispatcherRef: string;
|
private dispatcherRef: string;
|
||||||
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
||||||
|
private searchFilter: NameFilterCondition = new NameFilterCondition();
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -52,6 +54,21 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
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() {
|
public componentWillUnmount() {
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
}
|
}
|
||||||
|
@ -78,19 +95,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
private onChange = () => {
|
private onChange = () => {
|
||||||
if (!this.inputRef.current) return;
|
if (!this.inputRef.current) return;
|
||||||
this.setState({query: this.inputRef.current.value});
|
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>) => {
|
private onFocus = (ev: React.FocusEvent<HTMLInputElement>) => {
|
||||||
this.setState({focused: true});
|
this.setState({focused: true});
|
||||||
ev.target.select();
|
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) {
|
if (scrollState.stuckAtBottom) {
|
||||||
const sn = this._getScrollNode();
|
const sn = this._getScrollNode();
|
||||||
sn.scrollTop = sn.scrollHeight;
|
if (sn.scrollTop !== sn.scrollHeight) {
|
||||||
|
sn.scrollTop = sn.scrollHeight;
|
||||||
|
}
|
||||||
} else if (scrollState.trackedScrollToken) {
|
} else if (scrollState.trackedScrollToken) {
|
||||||
const itemlist = this._itemlist.current;
|
const itemlist = this._itemlist.current;
|
||||||
const trackedNode = this._getTrackedNode();
|
const trackedNode = this._getTrackedNode();
|
||||||
|
@ -657,7 +659,10 @@ export default createReactClass({
|
||||||
const bottomDiff = newBottomOffset - scrollState.bottomOffset;
|
const bottomDiff = newBottomOffset - scrollState.bottomOffset;
|
||||||
this._bottomGrowth += bottomDiff;
|
this._bottomGrowth += bottomDiff;
|
||||||
scrollState.bottomOffset = newBottomOffset;
|
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);
|
debuglog("balancing height because messages below viewport grew by", bottomDiff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -694,12 +699,16 @@ export default createReactClass({
|
||||||
const height = Math.max(minHeight, contentHeight);
|
const height = Math.max(minHeight, contentHeight);
|
||||||
this._pages = Math.ceil(height / PAGE_SIZE);
|
this._pages = Math.ceil(height / PAGE_SIZE);
|
||||||
this._bottomGrowth = 0;
|
this._bottomGrowth = 0;
|
||||||
const newHeight = this._getListHeight();
|
const newHeight = `${this._getListHeight()}px`;
|
||||||
|
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
if (scrollState.stuckAtBottom) {
|
if (scrollState.stuckAtBottom) {
|
||||||
itemlist.style.height = `${newHeight}px`;
|
if (itemlist.style.height !== newHeight) {
|
||||||
sn.scrollTop = sn.scrollHeight;
|
itemlist.style.height = newHeight;
|
||||||
|
}
|
||||||
|
if (sn.scrollTop !== sn.scrollHeight){
|
||||||
|
sn.scrollTop = sn.scrollHeight;
|
||||||
|
}
|
||||||
debuglog("updateHeight to", newHeight);
|
debuglog("updateHeight to", newHeight);
|
||||||
} else if (scrollState.trackedScrollToken) {
|
} else if (scrollState.trackedScrollToken) {
|
||||||
const trackedNode = this._getTrackedNode();
|
const trackedNode = this._getTrackedNode();
|
||||||
|
@ -709,7 +718,9 @@ export default createReactClass({
|
||||||
// the currently filled piece of the timeline
|
// the currently filled piece of the timeline
|
||||||
if (trackedNode) {
|
if (trackedNode) {
|
||||||
const oldTop = trackedNode.offsetTop;
|
const oldTop = trackedNode.offsetTop;
|
||||||
itemlist.style.height = `${newHeight}px`;
|
if (itemlist.style.height !== newHeight) {
|
||||||
|
itemlist.style.height = newHeight;
|
||||||
|
}
|
||||||
const newTop = trackedNode.offsetTop;
|
const newTop = trackedNode.offsetTop;
|
||||||
const topDiff = newTop - oldTop;
|
const topDiff = newTop - oldTop;
|
||||||
// important to scroll by a relative amount as
|
// 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() {
|
public render() {
|
||||||
const avatarSize = 32; // should match border-radius of the avatar
|
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 name = <span className="mx_UserMenu_userName">{OwnProfileStore.instance.displayName}</span>;
|
||||||
let buttons = (
|
let buttons = (
|
||||||
|
|
|
@ -134,7 +134,7 @@ const BaseAvatar = (props: IProps) => {
|
||||||
aria-hidden="true" />
|
aria-hidden="true" />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (onClick !== null) {
|
if (onClick) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
@ -162,7 +162,7 @@ const BaseAvatar = (props: IProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClick !== null) {
|
if (onClick) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className={classNames("mx_BaseAvatar mx_BaseAvatar_image", className)}
|
className={classNames("mx_BaseAvatar mx_BaseAvatar_image", className)}
|
||||||
|
@ -196,4 +196,4 @@ const BaseAvatar = (props: IProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BaseAvatar;
|
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);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
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]) => {
|
Array.from(this.roomStateEvents.entries()).map(([eventType, allStateKeys]) => {
|
||||||
let onClickFn;
|
let onClickFn;
|
||||||
if (allStateKeys.size() === 1 && allStateKeys.has("")) {
|
if (allStateKeys.size === 1 && allStateKeys.has("")) {
|
||||||
onClickFn = this.onViewSourceClick(allStateKeys.get(""));
|
onClickFn = this.onViewSourceClick(allStateKeys.get(""));
|
||||||
} else {
|
} else {
|
||||||
onClickFn = this.browseEventType(eventType);
|
onClickFn = this.browseEventType(eventType);
|
||||||
|
|
|
@ -35,8 +35,11 @@ import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../
|
||||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy";
|
|
||||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
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_DM = "dm";
|
||||||
export const KIND_INVITE = "invite";
|
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
|
// 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.
|
// room list doesn't tag the room for the DMRoomMap, but does for the room list.
|
||||||
const taggedRooms = RoomListStoreTempProxy.getRoomLists();
|
const dmTaggedRooms = RoomListStore.instance.orderedLists[DefaultTagID.DM];
|
||||||
const dmTaggedRooms = taggedRooms[DefaultTagID.DM];
|
|
||||||
const myUserId = MatrixClientPeg.get().getUserId();
|
const myUserId = MatrixClientPeg.get().getUserId();
|
||||||
for (const dmRoom of dmTaggedRooms) {
|
for (const dmRoom of dmTaggedRooms) {
|
||||||
const otherMembers = dmRoom.getJoinedMembers().filter(u => u.userId !== myUserId);
|
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
|
* onClick: (required) Event handler for button activation. Should be
|
||||||
* implemented exactly like a normal onClick handler.
|
* implemented exactly like a normal onClick handler.
|
||||||
*/
|
*/
|
||||||
export interface IProps extends React.InputHTMLAttributes<Element> {
|
interface IProps extends React.InputHTMLAttributes<Element> {
|
||||||
inputRef?: React.Ref<Element>;
|
inputRef?: React.Ref<Element>;
|
||||||
element?: string;
|
element?: string;
|
||||||
// The kind of button, similar to how Bootstrap works.
|
// The kind of button, similar to how Bootstrap works.
|
||||||
|
@ -118,7 +118,7 @@ export default function AccessibleButton({
|
||||||
AccessibleButton.defaultProps = {
|
AccessibleButton.defaultProps = {
|
||||||
element: 'div',
|
element: 'div',
|
||||||
role: 'button',
|
role: 'button',
|
||||||
tabIndex: "0",
|
tabIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
AccessibleButton.displayName = "AccessibleButton";
|
AccessibleButton.displayName = "AccessibleButton";
|
||||||
|
|
|
@ -19,10 +19,9 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import {IProps} from "./AccessibleButton";
|
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
|
|
||||||
interface ITooltipProps extends IProps {
|
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
title: string;
|
title: string;
|
||||||
tooltip?: React.ReactNode;
|
tooltip?: React.ReactNode;
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
|
@ -46,7 +45,7 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMouseOut = () => {
|
onMouseLeave = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
hover: false,
|
hover: false,
|
||||||
});
|
});
|
||||||
|
@ -61,7 +60,12 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
label={tooltip || title}
|
label={tooltip || title}
|
||||||
/> : <div />;
|
/> : <div />;
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
|
<AccessibleButton
|
||||||
|
{...props}
|
||||||
|
onMouseOver={this.onMouseOver}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
aria-label={title}
|
||||||
|
>
|
||||||
{ children }
|
{ children }
|
||||||
{ tip }
|
{ tip }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
|
|
@ -704,6 +704,7 @@ export default class AppTile extends React.Component {
|
||||||
|
|
||||||
_onReloadWidgetClick() {
|
_onReloadWidgetClick() {
|
||||||
// Reload iframe in this way to avoid cross-origin restrictions
|
// 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;
|
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)}
|
tooltipClassName={classNames("mx_Field_tooltip", tooltipClassName)}
|
||||||
visible={(this.state.focused && this.props.forceTooltipVisible) || this.state.feedbackVisible}
|
visible={(this.state.focused && this.props.forceTooltipVisible) || this.state.feedbackVisible}
|
||||||
label={tooltipContent || this.state.feedback}
|
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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||||
|
|
||||||
export default class ManageIntegsButton extends React.Component {
|
export default class ManageIntegsButton extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -45,9 +45,8 @@ export default class ManageIntegsButton extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let integrationsButton = <div />;
|
let integrationsButton = <div />;
|
||||||
if (IntegrationManagers.sharedInstance().hasManager()) {
|
if (IntegrationManagers.sharedInstance().hasManager()) {
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
|
||||||
integrationsButton = (
|
integrationsButton = (
|
||||||
<AccessibleButton
|
<AccessibleTooltipButton
|
||||||
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
||||||
title={_t("Manage Integrations")}
|
title={_t("Manage Integrations")}
|
||||||
onClick={this.onManageIntegrations}
|
onClick={this.onManageIntegrations}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import escapeHtml from "escape-html";
|
import escapeHtml from "escape-html";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
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
|
// 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
|
// 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
|
// Part of Replies fallback support
|
||||||
static stripHTMLReply(html) {
|
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
|
// Part of Replies fallback support
|
||||||
|
@ -102,15 +117,19 @@ export default class ReplyThread extends React.Component {
|
||||||
let {body, formatted_body: html} = ev.getContent();
|
let {body, formatted_body: html} = ev.getContent();
|
||||||
if (this.getParentEventId(ev)) {
|
if (this.getParentEventId(ev)) {
|
||||||
if (body) body = this.stripPlainReply(body);
|
if (body) body = this.stripPlainReply(body);
|
||||||
if (html) html = this.stripHTMLReply(html);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!body) body = ""; // Always ensure we have a body, for reasons.
|
if (!body) body = ""; // Always ensure we have a body, for reasons.
|
||||||
|
|
||||||
// Escape the body to use as HTML below.
|
if (html) {
|
||||||
// We also run a nl2br over the result to fix the fallback representation. We do this
|
// sanitize the HTML before we put it in an <mx-reply>
|
||||||
// after converting the text to safe HTML to avoid user-provided BR's from being converted.
|
html = this.stripHTMLReply(html);
|
||||||
if (!html) html = escapeHtml(body).replace(/\n/g, '<br/>');
|
} 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.
|
// 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 GroupStore from '../../../stores/GroupStore';
|
||||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import AccessibleButton from "./AccessibleButton";
|
||||||
|
|
||||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
// 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:
|
// 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 });
|
this.setState({ hover: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseOut: function() {
|
onMouseLeave: function() {
|
||||||
this.setState({ hover: false });
|
this.setState({ hover: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -151,11 +152,14 @@ export default createReactClass({
|
||||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
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 ?
|
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"}
|
{"\u00B7\u00B7\u00B7"}
|
||||||
</div> : <div ref={this.props.contextMenuButtonRef} />;
|
</AccessibleButton> : <div ref={this.props.contextMenuButtonRef} />;
|
||||||
|
|
||||||
const AccessibleTooltipButton = sdk.getComponent("elements.AccessibleTooltipButton");
|
const AccessibleTooltipButton = sdk.getComponent("elements.AccessibleTooltipButton");
|
||||||
|
|
||||||
|
@ -168,7 +172,7 @@ export default createReactClass({
|
||||||
<div
|
<div
|
||||||
className="mx_TagTile_avatar"
|
className="mx_TagTile_avatar"
|
||||||
onMouseOver={this.onMouseOver}
|
onMouseOver={this.onMouseOver}
|
||||||
onMouseOut={this.onMouseOut}
|
onMouseLeave={this.onMouseLeave}
|
||||||
>
|
>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default class TextWithTooltip extends React.Component {
|
||||||
this.setState({hover: true});
|
this.setState({hover: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMouseOut = () => {
|
onMouseLeave = () => {
|
||||||
this.setState({hover: false});
|
this.setState({hover: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,13 +45,12 @@ export default class TextWithTooltip extends React.Component {
|
||||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
|
|
||||||
return (
|
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}
|
{this.props.children}
|
||||||
<Tooltip
|
{this.state.hover && <Tooltip
|
||||||
label={this.props.tooltip}
|
label={this.props.tooltip}
|
||||||
visible={this.state.hover}
|
|
||||||
tooltipClassName={this.props.tooltipClass}
|
tooltipClassName={this.props.tooltipClass}
|
||||||
className={"mx_TextWithTooltip_tooltip"} />
|
className={"mx_TextWithTooltip_tooltip"} /> }
|
||||||
</span>
|
</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 ReactDOM from 'react-dom';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ViewTooltipPayload } from '../../../dispatcher/payloads/ViewTooltipPayload';
|
|
||||||
import { Action } from '../../../dispatcher/actions';
|
|
||||||
|
|
||||||
const MIN_TOOLTIP_HEIGHT = 25;
|
const MIN_TOOLTIP_HEIGHT = 25;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// Class applied to the element used to position the tooltip
|
// Class applied to the element used to position the tooltip
|
||||||
className: string;
|
className?: string;
|
||||||
// Class applied to the tooltip itself
|
// Class applied to the tooltip itself
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
// Whether the tooltip is visible or hidden.
|
// Whether the tooltip is visible or hidden.
|
||||||
|
@ -38,6 +35,7 @@ interface IProps {
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
// the react element to put into the tooltip
|
// the react element to put into the tooltip
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
|
forceOnRight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Tooltip extends React.Component<IProps> {
|
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
|
// Remove the wrapper element, as the tooltip has finished using it
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
dis.dispatch<ViewTooltipPayload>({
|
|
||||||
action: Action.ViewTooltip,
|
|
||||||
tooltip: null,
|
|
||||||
parent: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
||||||
document.body.removeChild(this.tooltipContainer);
|
document.body.removeChild(this.tooltipContainer);
|
||||||
window.removeEventListener('scroll', this.renderTooltip, true);
|
window.removeEventListener('scroll', this.renderTooltip, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePosition(style: {[key: string]: any}) {
|
private updatePosition(style: CSSProperties) {
|
||||||
const parentBox = this.parent.getBoundingClientRect();
|
const parentBox = this.parent.getBoundingClientRect();
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
|
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.
|
// we need so that we're still centered.
|
||||||
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
|
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;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +97,6 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
// positioned, also taking into account any window zoom
|
// positioned, also taking into account any window zoom
|
||||||
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
||||||
// tooltips chevron
|
// tooltips chevron
|
||||||
const parent = ReactDOM.findDOMNode(this).parentNode as Element;
|
|
||||||
const style = this.updatePosition({});
|
const style = this.updatePosition({});
|
||||||
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
||||||
// if it is not meant to be visible on first mount.
|
// 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
|
// Render the tooltip manually, as we wish it not to be rendered within the parent
|
||||||
this.tooltip = ReactDOM.render<Element>(tooltip, this.tooltipContainer);
|
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() {
|
public render() {
|
||||||
// Render a placeholder
|
// Render a placeholder
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className} >
|
<div className={this.props.className}>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseOut: function() {
|
onMouseLeave: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
hover: false,
|
hover: false,
|
||||||
});
|
});
|
||||||
|
@ -48,7 +48,7 @@ export default createReactClass({
|
||||||
label={this.props.helpText}
|
label={this.props.helpText}
|
||||||
/> : <div />;
|
/> : <div />;
|
||||||
return (
|
return (
|
||||||
<div className="mx_TooltipButton" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} >
|
<div className="mx_TooltipButton" onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
|
||||||
?
|
?
|
||||||
{ tip }
|
{ tip }
|
||||||
</div>
|
</div>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue